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.
This commit is contained in:
124
ts/webrtcbridge.ts
Normal file
124
ts/webrtcbridge.ts
Normal file
@@ -0,0 +1,124 @@
|
||||
/**
|
||||
* 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 */ }
|
||||
}
|
||||
Reference in New Issue
Block a user