Full-featured SIP router with multi-provider trunking, browser softphone via WebRTC, real-time Opus/G.722/PCM transcoding in Rust, RNNoise ML noise suppression, Kokoro neural TTS announcements, and a Lit-based web dashboard with live call monitoring and REST API.
125 lines
4.0 KiB
TypeScript
125 lines
4.0 KiB
TypeScript
/**
|
|
* 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<WebSocket, string>();
|
|
const deviceIdToWs = new Map<string, WebSocket>();
|
|
|
|
// ---------------------------------------------------------------------------
|
|
// 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 */ }
|
|
}
|