96 lines
2.3 KiB
TypeScript
96 lines
2.3 KiB
TypeScript
import { type NotificationItem, parseNotificationEvent } from '$lib/utils/notifications';
|
|
|
|
export type { NotificationItem };
|
|
|
|
export function createNotificationStream() {
|
|
let notifications = $state<NotificationItem[]>([]);
|
|
let unreadCount = $state(0);
|
|
let eventSource: EventSource | null = null;
|
|
|
|
async function fetchNotifications(): Promise<void> {
|
|
try {
|
|
const res = await fetch('/api/notifications?size=10');
|
|
if (res.ok) {
|
|
const data = await res.json();
|
|
notifications = data.content ?? [];
|
|
}
|
|
} catch (e) {
|
|
console.error('Failed to fetch notifications', e);
|
|
}
|
|
}
|
|
|
|
async function fetchUnreadCount(): Promise<void> {
|
|
try {
|
|
const res = await fetch('/api/notifications/unread-count');
|
|
if (res.ok) {
|
|
const data = await res.json();
|
|
unreadCount = data.count;
|
|
}
|
|
} catch (e) {
|
|
console.error('Failed to fetch unread count', e);
|
|
}
|
|
}
|
|
|
|
async function markRead(notification: NotificationItem): Promise<void> {
|
|
if (!notification.read) {
|
|
try {
|
|
await fetch(`/api/notifications/${notification.id}/read`, { method: 'PATCH' });
|
|
notification.read = true;
|
|
unreadCount = Math.max(0, unreadCount - 1);
|
|
} catch (e) {
|
|
console.error('Failed to mark notification as read', e);
|
|
}
|
|
}
|
|
}
|
|
|
|
async function markAllRead(): Promise<void> {
|
|
try {
|
|
await fetch('/api/notifications/read-all', { method: 'POST' });
|
|
for (const n of notifications) {
|
|
n.read = true;
|
|
}
|
|
unreadCount = 0;
|
|
} catch (e) {
|
|
console.error('Failed to mark all notifications as read', e);
|
|
}
|
|
}
|
|
|
|
function init(): void {
|
|
fetchUnreadCount();
|
|
eventSource = new EventSource('/api/notifications/stream');
|
|
eventSource.addEventListener('notification', (e) => {
|
|
const notification = parseNotificationEvent(e.data);
|
|
if (!notification) return;
|
|
notifications = [notification, ...notifications];
|
|
if (!notification.read) unreadCount += 1;
|
|
});
|
|
eventSource.onopen = () => {
|
|
fetchUnreadCount();
|
|
};
|
|
eventSource.onerror = () => {
|
|
// Close on error to avoid repeated reconnect noise
|
|
eventSource?.close();
|
|
};
|
|
}
|
|
|
|
function destroy(): void {
|
|
eventSource?.close();
|
|
eventSource = null;
|
|
}
|
|
|
|
return {
|
|
get notifications() {
|
|
return notifications;
|
|
},
|
|
get unreadCount() {
|
|
return unreadCount;
|
|
},
|
|
fetchNotifications,
|
|
fetchUnreadCount,
|
|
markRead,
|
|
markAllRead,
|
|
init,
|
|
destroy
|
|
};
|
|
}
|