/** * Realtime distributor for the dating server. Holds an in-memory map * of `userId -> Set` so handlers can broadcast events to * specific users without each handler caring about transport details. * * Single-process only: this hub does NOT persist subscriptions or * coordinate across instances. Sufficient for the local demo; a * production deployment would replace this with Redis pub/sub or * similar. */ const sockets = new Map(); // userId -> Set export function attach(userId, socket) { if (!userId || !socket) return () => {}; let set = sockets.get(userId); if (!set) { set = new Set(); sockets.set(userId, set); } set.add(socket); const detach = () => { const current = sockets.get(userId); if (!current) return; current.delete(socket); if (current.size === 0) sockets.delete(userId); }; socket.on('close', detach); socket.on('error', detach); return detach; } /** * Send `event` to every socket subscribed by any of `userIds`. Each * `event` is a plain object that gets JSON-encoded once for all * recipients. * * Failures on individual sockets are swallowed: the close/error * handlers attached in `attach` clean them up later, and a single bad * socket must never block the rest of the broadcast. */ export function broadcast(userIds, event) { if (!Array.isArray(userIds) || userIds.length === 0) return; const payload = JSON.stringify(event); const seen = new Set(); for (const userId of userIds) { if (!userId || seen.has(userId)) continue; seen.add(userId); const set = sockets.get(userId); if (!set || set.size === 0) continue; for (const socket of set) { try { if (socket.readyState === socket.OPEN) socket.send(payload); } catch { // best-effort } } } } export function snapshot() { const result = {}; for (const [userId, set] of sockets) result[userId] = set.size; return result; }