Kraft102's picture
fix: sql.js Docker/Alpine compatibility layer for PatternMemory and FailureMemory
5a81b95
import { createContext, useContext, useState, useCallback, useEffect, ReactNode } from 'react';
import { toast } from '@/hooks/use-toast';
export type NotificationSeverity = 'critical' | 'warning' | 'info';
export interface Notification {
id: string;
title: string;
message: string;
severity: NotificationSeverity;
timestamp: number;
read: boolean;
source?: string;
}
interface NotificationSettings {
soundEnabled: boolean;
desktopEnabled: boolean;
criticalOnly: boolean;
}
interface NotificationContextType {
notifications: Notification[];
unreadCount: number;
settings: NotificationSettings;
addNotification: (notification: Omit<Notification, 'id' | 'timestamp' | 'read'>) => void;
markAsRead: (id: string) => void;
markAllAsRead: () => void;
clearAll: () => void;
updateSettings: (settings: Partial<NotificationSettings>) => void;
}
const NotificationContext = createContext<NotificationContextType | null>(null);
const SETTINGS_KEY = 'cyber-notification-settings';
const NOTIFICATIONS_KEY = 'cyber-notifications';
// Audio for notifications
const playNotificationSound = (severity: NotificationSeverity) => {
try {
const audioContext = new (window.AudioContext || (window as any).webkitAudioContext)();
const oscillator = audioContext.createOscillator();
const gainNode = audioContext.createGain();
oscillator.connect(gainNode);
gainNode.connect(audioContext.destination);
// Different frequencies for different severities
const freq = severity === 'critical' ? 880 : severity === 'warning' ? 660 : 440;
oscillator.frequency.value = freq;
oscillator.type = 'sine';
gainNode.gain.setValueAtTime(0.3, audioContext.currentTime);
gainNode.gain.exponentialRampToValueAtTime(0.01, audioContext.currentTime + 0.3);
oscillator.start(audioContext.currentTime);
oscillator.stop(audioContext.currentTime + 0.3);
} catch (e) {
console.warn('Could not play notification sound:', e);
}
};
// Request desktop notification permission
export const requestNotificationPermission = async (): Promise<NotificationPermission> => {
if (!('Notification' in window)) {
return 'denied';
}
if (Notification.permission === 'default') {
return await Notification.requestPermission();
}
return Notification.permission;
};
// Get current permission status
export const getNotificationPermission = (): NotificationPermission | 'unsupported' => {
if (!('Notification' in window)) {
return 'unsupported';
}
return Notification.permission;
};
const showDesktopNotification = (notification: Notification) => {
if ('Notification' in window && Notification.permission === 'granted') {
const severityEmoji = notification.severity === 'critical' ? '🚨' :
notification.severity === 'warning' ? '⚠️' : 'ℹ️';
new Notification(`${severityEmoji} ${notification.title}`, {
body: notification.message,
icon: '/icon-192.png',
badge: '/icon-192.png',
tag: notification.id,
requireInteraction: notification.severity === 'critical',
silent: false,
});
}
};
export const NotificationProvider = ({ children }: { children: ReactNode }) => {
const [notifications, setNotifications] = useState<Notification[]>([]);
const [settings, setSettings] = useState<NotificationSettings>({
soundEnabled: true,
desktopEnabled: false,
criticalOnly: false,
});
// Load settings and notifications from localStorage
useEffect(() => {
const savedSettings = localStorage.getItem(SETTINGS_KEY);
if (savedSettings) {
try {
setSettings(JSON.parse(savedSettings));
} catch (e) {}
}
const savedNotifications = localStorage.getItem(NOTIFICATIONS_KEY);
if (savedNotifications) {
try {
setNotifications(JSON.parse(savedNotifications));
} catch (e) {}
}
}, []);
// Save notifications to localStorage
useEffect(() => {
localStorage.setItem(NOTIFICATIONS_KEY, JSON.stringify(notifications.slice(0, 100)));
}, [notifications]);
// Save settings to localStorage
useEffect(() => {
localStorage.setItem(SETTINGS_KEY, JSON.stringify(settings));
}, [settings]);
const addNotification = useCallback((notification: Omit<Notification, 'id' | 'timestamp' | 'read'>) => {
const newNotification: Notification = {
...notification,
id: `notif-${Date.now()}-${Math.random().toString(36).substr(2, 9)}`,
timestamp: Date.now(),
read: false,
};
// Check if we should show this notification based on settings
if (settings.criticalOnly && notification.severity !== 'critical') {
return;
}
setNotifications(prev => [newNotification, ...prev.slice(0, 99)]);
// Play sound
if (settings.soundEnabled) {
playNotificationSound(notification.severity);
}
// Show desktop notification
if (settings.desktopEnabled) {
showDesktopNotification(newNotification);
}
// Show toast
toast({
title: newNotification.title,
description: newNotification.message,
variant: notification.severity === 'critical' ? 'destructive' : 'default',
});
}, [settings]);
const markAsRead = useCallback((id: string) => {
setNotifications(prev =>
prev.map(n => n.id === id ? { ...n, read: true } : n)
);
}, []);
const markAllAsRead = useCallback(() => {
setNotifications(prev => prev.map(n => ({ ...n, read: true })));
}, []);
const clearAll = useCallback(() => {
setNotifications([]);
}, []);
const updateSettings = useCallback((newSettings: Partial<NotificationSettings>) => {
setSettings(prev => {
const updated = { ...prev, ...newSettings };
// Request permission if enabling desktop notifications
if (newSettings.desktopEnabled && !prev.desktopEnabled) {
requestNotificationPermission();
}
return updated;
});
}, []);
const unreadCount = notifications.filter(n => !n.read).length;
return (
<NotificationContext.Provider value={{
notifications,
unreadCount,
settings,
addNotification,
markAsRead,
markAllAsRead,
clearAll,
updateSettings,
}}>
{children}
</NotificationContext.Provider>
);
};
export const useNotifications = () => {
const context = useContext(NotificationContext);
if (!context) {
throw new Error('useNotifications must be used within a NotificationProvider');
}
return context;
};