feat(proxy-engine): add Rust-based outbound calling, WebRTC bridging, and voicemail handling

This commit is contained in:
2026-04-10 11:36:18 +00:00
parent ad253f823f
commit 239e2ac81d
42 changed files with 3360 additions and 6444 deletions

View File

@@ -157,6 +157,24 @@ export async function configureProxyEngine(config: Record<string, unknown>): Pro
}
}
/**
* Initiate an outbound call via Rust. Returns the call ID or null on failure.
*/
export async function makeCall(number: string, deviceId?: string, providerId?: string): Promise<string | null> {
if (!bridge || !initialized) return null;
try {
const result = await bridge.sendCommand('make_call', {
number,
device_id: deviceId,
provider_id: providerId,
} as any);
return (result as any)?.call_id || null;
} catch (e: any) {
logFn?.(`[proxy-engine] make_call error: ${e?.message || e}`);
return null;
}
}
/**
* Send a hangup command.
*/
@@ -170,6 +188,66 @@ export async function hangupCall(callId: string): Promise<boolean> {
}
}
/**
* Send a WebRTC offer to the proxy engine. Returns the SDP answer.
*/
export async function webrtcOffer(sessionId: string, sdp: string): Promise<{ sdp: string } | null> {
if (!bridge || !initialized) return null;
try {
const result = await bridge.sendCommand('webrtc_offer', { session_id: sessionId, sdp } as any);
return result as any;
} catch (e: any) {
logFn?.(`[proxy-engine] webrtc_offer error: ${e?.message || e}`);
return null;
}
}
/**
* Forward an ICE candidate to the proxy engine.
*/
export async function webrtcIce(sessionId: string, candidate: any): Promise<void> {
if (!bridge || !initialized) return;
try {
await bridge.sendCommand('webrtc_ice', {
session_id: sessionId,
candidate: candidate?.candidate || candidate,
sdp_mid: candidate?.sdpMid,
sdp_mline_index: candidate?.sdpMLineIndex,
} as any);
} catch { /* ignore */ }
}
/**
* Link a WebRTC session to a SIP call — enables audio bridging.
* The browser's Opus audio will be transcoded and sent to the provider.
*/
export async function webrtcLink(sessionId: string, callId: string, providerMediaAddr: string, providerMediaPort: number, sipPt: number = 9): Promise<boolean> {
if (!bridge || !initialized) return false;
try {
await bridge.sendCommand('webrtc_link', {
session_id: sessionId,
call_id: callId,
provider_media_addr: providerMediaAddr,
provider_media_port: providerMediaPort,
sip_pt: sipPt,
} as any);
return true;
} catch (e: any) {
logFn?.(`[proxy-engine] webrtc_link error: ${e?.message || e}`);
return false;
}
}
/**
* Close a WebRTC session.
*/
export async function webrtcClose(sessionId: string): Promise<void> {
if (!bridge || !initialized) return;
try {
await bridge.sendCommand('webrtc_close', { session_id: sessionId } as any);
} catch { /* ignore */ }
}
/**
* Subscribe to an event from the proxy engine.
* Event names: incoming_call, outbound_device_call, call_ringing,