import type { WebSocket } from "ws"; import type { MeetingRole } from "./token"; export type Participant = { ws: WebSocket; participantId: string; name: string; role: MeetingRole; }; type Room = { participants: Map; kickedParticipantIds: Set; }; // In-memory only — this server process is the single source of truth for // signaling (no media is ever stored or relayed through it). If this app is // ever scaled to multiple instances, this state needs to move to a shared // store (e.g. Redis pub/sub) alongside a real SFU. const rooms = new Map(); function getOrCreateRoom(eventSlug: string): Room { let room = rooms.get(eventSlug); if (!room) { room = { participants: new Map(), kickedParticipantIds: new Set() }; rooms.set(eventSlug, room); } return room; } export function isKicked(eventSlug: string, participantId: string): boolean { return rooms.get(eventSlug)?.kickedParticipantIds.has(participantId) ?? false; } export function joinRoom(eventSlug: string, participant: Participant): Participant[] { const room = getOrCreateRoom(eventSlug); const existing = [...room.participants.values()]; room.participants.set(participant.participantId, participant); return existing; } export function leaveRoom(eventSlug: string, participantId: string) { const room = rooms.get(eventSlug); if (!room) return; room.participants.delete(participantId); if (room.participants.size === 0) rooms.delete(eventSlug); } export function kickFromRoom(eventSlug: string, participantId: string) { const room = rooms.get(eventSlug); if (!room) return; room.kickedParticipantIds.add(participantId); room.participants.delete(participantId); } export function getParticipant(eventSlug: string, participantId: string): Participant | undefined { return rooms.get(eventSlug)?.participants.get(participantId); } export function getRoomParticipants(eventSlug: string): Participant[] { return [...(rooms.get(eventSlug)?.participants.values() ?? [])]; } export function broadcast( eventSlug: string, message: unknown, opts: { exclude?: string } = {}, ) { const room = rooms.get(eventSlug); if (!room) return; const data = JSON.stringify(message); for (const p of room.participants.values()) { if (opts.exclude && p.participantId === opts.exclude) continue; if (p.ws.readyState === p.ws.OPEN) p.ws.send(data); } }