import { WebSocketServer } from 'ws'; import { getUserBySession, readSessionToken } from './auth.js'; // per-user channel hub: any client of user U receives messages targeted to U. const channels = new Map(); // userId -> Set export function attachWs(server) { const wss = new WebSocketServer({ noServer: true }); server.on('upgrade', (req, socket, head) => { if (!req.url.startsWith('/ws')) return socket.destroy(); const token = readSessionToken(req); const user = getUserBySession(token); if (!user) { socket.write('HTTP/1.1 401 Unauthorized\r\n\r\n'); socket.destroy(); return; } wss.handleUpgrade(req, socket, head, (ws) => { ws.user = user; addClient(user.id, ws); ws.on('close', () => removeClient(user.id, ws)); ws.on('message', (raw) => { let msg; try { msg = JSON.parse(raw.toString()); } catch { return; } // Re-broadcast every message to all connections of the same user. // (e.g. phone sends `{type:"command", action:"play", stationId:7}` → kiosk receives) broadcastToUser(user.id, msg, ws); }); ws.send(JSON.stringify({ type: 'hello', user: { id: user.id, username: user.username, role: user.role } })); }); }); return wss; } function addClient(userId, ws) { if (!channels.has(userId)) channels.set(userId, new Set()); channels.get(userId).add(ws); } function removeClient(userId, ws) { const set = channels.get(userId); if (!set) return; set.delete(ws); if (!set.size) channels.delete(userId); } export function broadcastToUser(userId, msg, except) { const set = channels.get(userId); if (!set) return; const payload = JSON.stringify(msg); for (const ws of set) { if (ws === except) continue; if (ws.readyState === ws.OPEN) ws.send(payload); } }