Files
siprouter/ts/webrtcbridge.ts
Juergen Kunz f3e1c96872 initial commit — SIP B2BUA + WebRTC bridge with Rust codec engine
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.
2026-04-09 23:03:55 +00:00

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 */ }
}