import { Server as SocketIOServer } from 'socket.io'; import type { Server as HttpServer } from 'http'; import { validateSession, sessionCookieName } from '$lib/server/auth.js'; let io: SocketIOServer | null = null; export function initSocketIO(server: HttpServer): SocketIOServer { io = new SocketIOServer(server, { cors: { origin: process.env.ORIGIN || 'http://localhost:5173', credentials: true } }); // Auth middleware — validate session cookie io.use(async (socket, next) => { const cookieHeader = socket.handshake.headers.cookie; if (!cookieHeader) { // Allow anonymous connections (for public boards) socket.data.user = null; return next(); } const cookies = parseCookies(cookieHeader); const sessionId = cookies[sessionCookieName()]; if (!sessionId) { socket.data.user = null; return next(); } const session = await validateSession(sessionId); if (session) { socket.data.user = session.user; } else { socket.data.user = null; } next(); }); io.on('connection', (socket) => { // Join board room socket.on('join-board', (boardId: string) => { socket.join(`board:${boardId}`); // Broadcast presence socket.to(`board:${boardId}`).emit('user-joined', { user: socket.data.user ? { id: socket.data.user.id, name: socket.data.user.name } : null, socketId: socket.id }); }); // Leave board room socket.on('leave-board', (boardId: string) => { socket.leave(`board:${boardId}`); socket.to(`board:${boardId}`).emit('user-left', { socketId: socket.id }); }); socket.on('disconnect', () => { // Socket.IO handles room cleanup automatically }); }); return io; } export function getIO(): SocketIOServer | null { return io; } /** * Emit an event to all clients in a board room. */ export function emitToBoardRoom(boardId: string, event: string, data: unknown) { if (io) { io.to(`board:${boardId}`).emit(event, data); } } /** * Emit a notification to a specific user across all their connections. */ export function emitToUser(userId: string, event: string, data: unknown) { if (io) { io.to(`user:${userId}`).emit(event, data); } } function parseCookies(cookieHeader: string): Record { const cookies: Record = {}; cookieHeader.split(';').forEach((cookie) => { const [name, ...rest] = cookie.trim().split('='); cookies[name] = decodeURIComponent(rest.join('=')); }); return cookies; }