import { Server } from 'socket.io'; let io: Server | null = null; // Track online users per request: { requestId: Set } const onlineUsersPerRequest = new Map>(); export function initSocket(httpServer: any) { const defaultOrigins = [ 'http://localhost:3000', 'http://127.0.0.1:3000', 'http://localhost:5173', 'http://127.0.0.1:5173' ]; const configured = (process.env.FRONTEND_ORIGIN || '').split(',').map(s => s.trim()).filter(Boolean); const origins = configured.length ? configured : defaultOrigins; io = new Server(httpServer, { cors: { origin: origins, methods: ['GET', 'POST'], credentials: true }, path: '/socket.io', transports: ['websocket', 'polling'] }); io.on('connection', (socket: any) => { let currentRequestId: string | null = null; let currentUserId: string | null = null; // Join user's personal notification room socket.on('join:user', (data: { userId: string }) => { const userId = typeof data === 'string' ? data : data.userId; socket.join(`user:${userId}`); currentUserId = userId; console.log(`[Socket] User ${userId} joined personal notification room`); }); socket.on('join:request', (data: { requestId: string; userId?: string }) => { const requestId = typeof data === 'string' ? data : data.requestId; const userId = typeof data === 'object' ? data.userId : null; socket.join(`request:${requestId}`); currentRequestId = requestId; currentUserId = userId || null; if (userId) { // Track this user as online for this request if (!onlineUsersPerRequest.has(requestId)) { onlineUsersPerRequest.set(requestId, new Set()); } onlineUsersPerRequest.get(requestId)!.add(userId); const onlineUsers = Array.from(onlineUsersPerRequest.get(requestId) || []); // Broadcast presence to others in the room socket.to(`request:${requestId}`).emit('presence:join', { userId, requestId }); // Send current online users to the newly joined socket socket.emit('presence:online', { requestId, userIds: onlineUsers }); } }); // Handle request for current online users (when component loads after join) socket.on('request:online-users', (data: { requestId: string }) => { const requestId = typeof data === 'string' ? data : data.requestId; const onlineUsers = Array.from(onlineUsersPerRequest.get(requestId) || []); socket.emit('presence:online', { requestId, userIds: onlineUsers }); }); socket.on('leave:request', (requestId: string) => { socket.leave(`request:${requestId}`); if (currentUserId && onlineUsersPerRequest.has(requestId)) { onlineUsersPerRequest.get(requestId)!.delete(currentUserId); if (onlineUsersPerRequest.get(requestId)!.size === 0) { onlineUsersPerRequest.delete(requestId); } // Broadcast leave to others socket.to(`request:${requestId}`).emit('presence:leave', { userId: currentUserId, requestId }); } if (requestId === currentRequestId) { currentRequestId = null; currentUserId = null; } }); socket.on('disconnect', () => { // Handle user going offline when connection drops if (currentRequestId && currentUserId && onlineUsersPerRequest.has(currentRequestId)) { onlineUsersPerRequest.get(currentRequestId)!.delete(currentUserId); if (onlineUsersPerRequest.get(currentRequestId)!.size === 0) { onlineUsersPerRequest.delete(currentRequestId); } // Broadcast disconnect to others in the room socket.to(`request:${currentRequestId}`).emit('presence:leave', { userId: currentUserId, requestId: currentRequestId }); } }); }); return io; } export function emitToRequestRoom(requestId: string, event: string, payload: any) { if (!io) return; io.to(`request:${requestId}`).emit(event, payload); } export function emitToUser(userId: string, event: string, payload: any) { if (!io) return; io.to(`user:${userId}`).emit(event, payload); console.log(`[Socket] Emitted '${event}' to user ${userId}`); }