/** * WebRTC signaling — browser device registration and WebSocket dispatch. * * This module handles ONLY the signaling side: * - Browser device registration/unregistration via WebSocket * - WS → deviceId mapping * * All WebRTC media logic (PeerConnection, RTP, transcoding) lives in * ts/call/webrtc-leg.ts and is managed by the CallManager. */ import { WebSocket } from 'ws'; import { registerBrowserDevice, unregisterBrowserDevice, shortHash } from './registrar.ts'; // --------------------------------------------------------------------------- // Config // --------------------------------------------------------------------------- interface IWebRtcSignalingConfig { log: (msg: string) => void; } let config: IWebRtcSignalingConfig; // --------------------------------------------------------------------------- // State: WS ↔ deviceId mapping // --------------------------------------------------------------------------- const wsToSession = new WeakMap(); const deviceIdToWs = new Map(); // --------------------------------------------------------------------------- // Public API // --------------------------------------------------------------------------- export function initWebRtcSignaling(cfg: IWebRtcSignalingConfig): void { config = cfg; } /** * Handle a WebRTC signaling message from a browser client. * Only handles registration; offer/ice/hangup are routed through CallManager. */ export function handleWebRtcSignaling( ws: WebSocket, message: { type: string; sessionId?: string; [key: string]: any }, ): void { const { type } = message; if (type === 'webrtc-register') { handleRegister(ws, message.sessionId!, message.userAgent, message._remoteIp); } // Other webrtc-* types (offer, ice, hangup, accept) are handled // by the CallManager via frontend.ts WebSocket handler. } /** * Send a message to a specific browser device by its device ID. */ export function sendToBrowserDevice(deviceId: string, data: unknown): boolean { const ws = deviceIdToWs.get(deviceId); if (!ws) return false; wsSend(ws, data); return true; } /** * Get the WebSocket for a browser device (used by CallManager to create WebRtcLegs). */ export function getBrowserDeviceWs(deviceId: string): WebSocket | null { return deviceIdToWs.get(deviceId) ?? null; } /** * Get all registered browser device IDs. */ export function getAllBrowserDeviceIds(): string[] { return [...deviceIdToWs.keys()]; } // --------------------------------------------------------------------------- // Registration // --------------------------------------------------------------------------- function handleRegister(ws: WebSocket, sessionId: string, userAgent?: string, remoteIp?: string): void { // Clean up any previous browser device from this same WS connection. const prevSession = wsToSession.get(ws); if (prevSession && prevSession !== sessionId) { unregisterBrowserDevice(prevSession); } unregisterBrowserDevice(sessionId); registerBrowserDevice(sessionId, userAgent, remoteIp); wsToSession.set(ws, sessionId); config.log(`[webrtc:${sessionId.slice(0, 8)}] browser registered as device`); const deviceId = `browser-${shortHash(sessionId)}`; deviceIdToWs.set(deviceId, ws); // Echo back the assigned device ID. wsSend(ws, { type: 'webrtc-registered', deviceId }); // Only add close handler once per WS connection. if (!prevSession) { ws.on('close', () => { const sid = wsToSession.get(ws) || sessionId; config.log(`[webrtc:${sid.slice(0, 8)}] browser disconnected`); deviceIdToWs.delete(`browser-${shortHash(sid)}`); unregisterBrowserDevice(sid); }); } } // --------------------------------------------------------------------------- // Helpers // --------------------------------------------------------------------------- function wsSend(ws: WebSocket, data: unknown): void { try { if (ws.readyState === WebSocket.OPEN) { ws.send(JSON.stringify(data)); } } catch { /* ignore */ } }