Skip to content

API and Socket.IO

The notification system uses REST API endpoints and Socket.IO events for communication.

The API endpoints serve as a fallback solution when Socket.IO is unavailable. The NotificationStore automatically starts polling every 30 seconds if the WebSocket connection is lost.

notificationStore.svelte.ts
// Poll for new notifications only if WebSocket is disconnected
setInterval(() => {
if (browser && !this.state.isConnected) {
console.log('[NotificationStore] WebSocket disconnected, polling for notifications');
this.loadNotifications();
}
}, 30000);

Important: The API endpoints do not need to be called manually during normal usage. The store automatically handles the fallback.

Fetch all notifications for the current user.

Response:

{
"notifications": [
{
"id": 1,
"userId": 123,
"appName": "users",
"title": { "hu": "Új csoport", "en": "New group" },
"message": { "hu": "Csoport létrehozva", "en": "Group created" },
"details": null,
"type": "success",
"isRead": false,
"data": { "section": "groups", "groupId": "456" },
"createdAt": "2024-01-15T10:30:00Z",
"readAt": null
}
]
}

Send a new notification.

Request:

{
"userId": 123,
"appName": "users",
"title": { "hu": "Új csoport", "en": "New group" },
"message": { "hu": "Csoport létrehozva", "en": "Group created" },
"type": "success",
"data": { "section": "groups", "groupId": "456" }
}

Mark a notification as read.

Delete a notification.

Mark all notifications as read.

Delete all notifications.

Register a user on Socket.IO.

socket.emit('register', userId);

Happens automatically: The NotificationStore automatically registers the user on connection.

Mark a notification as read.

socket.emit('notification:mark-read', notificationId);

Mark all notifications as read.

socket.emit('notification:mark-all-read', userId);

A new notification has arrived.

socket.on('notification:new', (notification: Notification) => {
console.log('New notification:', notification);
});

Handled automatically: The NotificationStore automatically handles this event.

Unread notification count updated.

socket.on('notification:unread-count', (count: number) => {
console.log('Unread notifications:', count);
});
lib/server/socket/index.ts
export function initializeSocketIO(serverOrIo: HTTPServer | SocketIOServer) {
if (io) {
logger.warn('[Socket.IO] Already initialized');
return io;
}
io = new SocketIOServer(serverOrIo, {
cors: { origin: '*', methods: ['GET', 'POST'] },
path: '/socket.io/',
pingTimeout: 60000,
pingInterval: 25000,
transports: ['websocket', 'polling']
});
// Event handlers...
}
export async function sendNotification(payload: NotificationPayload): Promise<void> {
let socketIO: SocketIOServer | null = null;
try {
socketIO = getSocketIO();
} catch (error) {
console.warn('[sendNotification] Socket.IO not initialized, will save to database only');
}
let targetUserIds: number[] = [];
if (payload.broadcast) {
const allUsers = await db.select({ id: users.id }).from(users);
targetUserIds = allUsers.map((u) => u.id);
} else if (payload.userId) {
targetUserIds = [payload.userId];
} else if (payload.userIds) {
targetUserIds = payload.userIds;
}
for (const userId of targetUserIds) {
const notification: NewNotification = {
userId,
appName: payload.appName || null,
title: normalizeContent(payload.title) as any,
message: normalizeContent(payload.message) as any,
type: payload.type || 'info',
data: payload.data || null
};
const saved = await notificationRepository.create(notification);
if (socketIO) {
socketIO.to(`user:${userId}`).emit('notification:new', saved);
const unreadCount = await notificationRepository.getUnreadCount(userId);
socketIO.to(`user:${userId}`).emit('notification:unread-count', unreadCount);
}
}
}
export const notificationRepository = {
async create(notification: NewNotification): Promise<Notification> {
const [created] = await db.insert(notifications).values(notification).returning();
return created;
},
async getByUserId(userId: number, limit = 50): Promise<Notification[]> {
return db.select().from(notifications)
.where(eq(notifications.userId, userId))
.orderBy(desc(notifications.createdAt))
.limit(limit);
},
async getUnreadCount(userId: number): Promise<number> {
const result = await db.select().from(notifications)
.where(and(eq(notifications.userId, userId), eq(notifications.isRead, false)));
return result.length;
},
async markAsRead(id: number): Promise<Notification | undefined> {
const [updated] = await db.update(notifications)
.set({ isRead: true, readAt: new Date() })
.where(eq(notifications.id, id))
.returning();
return updated;
},
async markAllAsRead(userId: number): Promise<void> {
await db.update(notifications)
.set({ isRead: true, readAt: new Date() })
.where(and(eq(notifications.userId, userId), eq(notifications.isRead, false)));
},
async delete(id: number): Promise<void> {
await db.delete(notifications).where(eq(notifications.id, id));
},
async deleteAllByUserId(userId: number): Promise<void> {
await db.delete(notifications).where(eq(notifications.userId, userId));
}
};