feat(runtime): refactor runtime state and proxy event handling for typed WebRTC linking and shared status models

This commit is contained in:
2026-04-14 10:45:59 +00:00
parent 5a280c5c41
commit 51f7560730
15 changed files with 1105 additions and 813 deletions

View File

@@ -14,12 +14,36 @@ import { WebSocketServer, WebSocket } from 'ws';
import { handleWebRtcSignaling } from './webrtcbridge.ts';
import type { VoiceboxManager } from './voicebox.ts';
// CallManager was previously used for WebRTC call handling. Now replaced by Rust proxy-engine.
// Kept as `any` type for backward compat with the function signature until full WebRTC port.
type CallManager = any;
const CONFIG_PATH = path.join(process.cwd(), '.nogit', 'config.json');
interface IHandleRequestContext {
getStatus: () => unknown;
log: (msg: string) => void;
onStartCall: (number: string, deviceId?: string, providerId?: string) => { id: string } | null;
onHangupCall: (callId: string) => boolean;
onConfigSaved?: () => void | Promise<void>;
voiceboxManager?: VoiceboxManager;
}
interface IWebUiOptions extends IHandleRequestContext {
port: number;
onWebRtcOffer?: (sessionId: string, sdp: string, ws: WebSocket) => Promise<void>;
onWebRtcIce?: (sessionId: string, candidate: unknown) => Promise<void>;
onWebRtcClose?: (sessionId: string) => Promise<void>;
onWebRtcAccept?: (callId: string, sessionId: string) => void;
}
interface IWebRtcSocketMessage {
type?: string;
sessionId?: string;
callId?: string;
sdp?: string;
candidate?: unknown;
userAgent?: string;
_remoteIp?: string | null;
[key: string]: unknown;
}
// ---------------------------------------------------------------------------
// WebSocket broadcast
// ---------------------------------------------------------------------------
@@ -82,14 +106,9 @@ function loadStaticFiles(): void {
async function handleRequest(
req: http.IncomingMessage,
res: http.ServerResponse,
getStatus: () => unknown,
log: (msg: string) => void,
onStartCall: (number: string, deviceId?: string, providerId?: string) => { id: string } | null,
onHangupCall: (callId: string) => boolean,
onConfigSaved?: () => void,
callManager?: CallManager,
voiceboxManager?: VoiceboxManager,
context: IHandleRequestContext,
): Promise<void> {
const { getStatus, log, onStartCall, onHangupCall, onConfigSaved, voiceboxManager } = context;
const url = new URL(req.url || '/', `http://${req.headers.host || 'localhost'}`);
const method = req.method || 'GET';
@@ -258,7 +277,7 @@ async function handleRequest(
fs.writeFileSync(CONFIG_PATH, JSON.stringify(cfg, null, 2) + '\n');
log('[config] updated config.json');
onConfigSaved?.();
await onConfigSaved?.();
return sendJson(res, { ok: true });
} catch (e: any) {
return sendJson(res, { ok: false, error: e.message }, 400);
@@ -339,21 +358,21 @@ async function handleRequest(
// ---------------------------------------------------------------------------
export function initWebUi(
getStatus: () => unknown,
log: (msg: string) => void,
onStartCall: (number: string, deviceId?: string, providerId?: string) => { id: string } | null,
onHangupCall: (callId: string) => boolean,
onConfigSaved?: () => void,
callManager?: CallManager,
voiceboxManager?: VoiceboxManager,
/** WebRTC signaling handlers — forwarded to Rust proxy-engine. */
onWebRtcOffer?: (sessionId: string, sdp: string, ws: WebSocket) => Promise<void>,
onWebRtcIce?: (sessionId: string, candidate: any) => Promise<void>,
onWebRtcClose?: (sessionId: string) => Promise<void>,
/** Called when browser sends webrtc-accept (callId + sessionId linking). */
onWebRtcAccept?: (callId: string, sessionId: string) => void,
options: IWebUiOptions,
): void {
const WEB_PORT = 3060;
const {
port,
getStatus,
log,
onStartCall,
onHangupCall,
onConfigSaved,
voiceboxManager,
onWebRtcOffer,
onWebRtcIce,
onWebRtcClose,
onWebRtcAccept,
} = options;
loadStaticFiles();
@@ -367,12 +386,12 @@ export function initWebUi(
const cert = fs.readFileSync(certPath, 'utf8');
const key = fs.readFileSync(keyPath, 'utf8');
server = https.createServer({ cert, key }, (req, res) =>
handleRequest(req, res, getStatus, log, onStartCall, onHangupCall, onConfigSaved, callManager, voiceboxManager).catch(() => { res.writeHead(500); res.end(); }),
handleRequest(req, res, { getStatus, log, onStartCall, onHangupCall, onConfigSaved, voiceboxManager }).catch(() => { res.writeHead(500); res.end(); }),
);
useTls = true;
} catch {
server = http.createServer((req, res) =>
handleRequest(req, res, getStatus, log, onStartCall, onHangupCall, onConfigSaved, callManager, voiceboxManager).catch(() => { res.writeHead(500); res.end(); }),
handleRequest(req, res, { getStatus, log, onStartCall, onHangupCall, onConfigSaved, voiceboxManager }).catch(() => { res.writeHead(500); res.end(); }),
);
}
@@ -386,12 +405,12 @@ export function initWebUi(
socket.on('message', (raw) => {
try {
const msg = JSON.parse(raw.toString());
const msg = JSON.parse(raw.toString()) as IWebRtcSocketMessage;
if (msg.type === 'webrtc-offer' && msg.sessionId) {
// Forward to Rust proxy-engine for WebRTC handling.
if (onWebRtcOffer) {
if (onWebRtcOffer && typeof msg.sdp === 'string') {
log(`[webrtc-ws] offer msg keys: ${Object.keys(msg).join(',')}, sdp type: ${typeof msg.sdp}, sdp len: ${msg.sdp?.length || 0}`);
onWebRtcOffer(msg.sessionId, msg.sdp, socket as any).catch((e: any) =>
onWebRtcOffer(msg.sessionId, msg.sdp, socket).catch((e: any) =>
log(`[webrtc] offer error: ${e.message}`));
}
} else if (msg.type === 'webrtc-ice' && msg.sessionId) {
@@ -409,7 +428,7 @@ export function initWebUi(
}
} else if (msg.type?.startsWith('webrtc-')) {
msg._remoteIp = remoteIp;
handleWebRtcSignaling(socket as any, msg);
handleWebRtcSignaling(socket, msg);
}
} catch { /* ignore */ }
});
@@ -418,8 +437,8 @@ export function initWebUi(
socket.on('error', () => wsClients.delete(socket));
});
server.listen(WEB_PORT, '0.0.0.0', () => {
log(`web ui listening on ${useTls ? 'https' : 'http'}://0.0.0.0:${WEB_PORT}`);
server.listen(port, '0.0.0.0', () => {
log(`web ui listening on ${useTls ? 'https' : 'http'}://0.0.0.0:${port}`);
});
setInterval(() => broadcastWs('status', getStatus()), 1000);