import { onProxyEvent } from '../proxybridge.ts'; import type { VoiceboxManager } from '../voicebox.ts'; import type { StatusStore } from './status-store.ts'; import type { IProviderMediaInfo, WebRtcLinkManager } from './webrtc-linking.ts'; export interface IRegisterProxyEventHandlersOptions { log: (msg: string) => void; statusStore: StatusStore; voiceboxManager: VoiceboxManager; webRtcLinks: WebRtcLinkManager; getBrowserDeviceIds: () => string[]; sendToBrowserDevice: (deviceId: string, data: unknown) => boolean; broadcast: (type: string, data: unknown) => void; onLinkWebRtcSession: (callId: string, sessionId: string, media: IProviderMediaInfo) => void; onCloseWebRtcSession: (sessionId: string) => void; } export function registerProxyEventHandlers(options: IRegisterProxyEventHandlersOptions): void { const { log, statusStore, voiceboxManager, webRtcLinks, getBrowserDeviceIds, sendToBrowserDevice, broadcast, onLinkWebRtcSession, onCloseWebRtcSession, } = options; onProxyEvent('provider_registered', (data) => { const previous = statusStore.noteProviderRegistered(data); if (previous) { if (data.registered && !previous.wasRegistered) { log(`[provider:${data.provider_id}] registered (publicIp=${data.public_ip})`); } else if (!data.registered && previous.wasRegistered) { log(`[provider:${data.provider_id}] registration lost`); } } broadcast('registration', { providerId: data.provider_id, registered: data.registered }); }); onProxyEvent('device_registered', (data) => { if (statusStore.noteDeviceRegistered(data)) { log(`[registrar] ${data.display_name} registered from ${data.address}:${data.port}`); } }); onProxyEvent('incoming_call', (data) => { log(`[call] incoming: ${data.from_uri} -> ${data.to_number} via ${data.provider_id} (${data.call_id})`); statusStore.noteIncomingCall(data); if (data.ring_browsers === false) { return; } for (const deviceId of getBrowserDeviceIds()) { sendToBrowserDevice(deviceId, { type: 'webrtc-incoming', callId: data.call_id, from: data.from_uri, deviceId, }); } }); onProxyEvent('outbound_device_call', (data) => { log(`[call] outbound: device ${data.from_device} -> ${data.to_number} (${data.call_id})`); statusStore.noteOutboundDeviceCall(data); }); onProxyEvent('outbound_call_started', (data) => { log(`[call] outbound started: ${data.call_id} -> ${data.number} via ${data.provider_id}`); statusStore.noteOutboundCallStarted(data); for (const deviceId of getBrowserDeviceIds()) { sendToBrowserDevice(deviceId, { type: 'webrtc-incoming', callId: data.call_id, from: data.number, deviceId, }); } }); onProxyEvent('call_ringing', (data) => { statusStore.noteCallRinging(data); }); onProxyEvent('call_answered', (data) => { if (statusStore.noteCallAnswered(data)) { log(`[call] ${data.call_id} connected`); } if (!data.provider_media_addr || !data.provider_media_port) { return; } const target = webRtcLinks.noteCallAnswered(data.call_id, { addr: data.provider_media_addr, port: data.provider_media_port, sipPt: data.sip_pt ?? 9, }); if (!target) { log(`[webrtc] media info cached for call=${data.call_id}, waiting for session accept`); return; } onLinkWebRtcSession(data.call_id, target.sessionId, target.media); }); onProxyEvent('call_ended', (data) => { if (statusStore.noteCallEnded(data)) { log(`[call] ${data.call_id} ended: ${data.reason} (${data.duration}s)`); } broadcast('webrtc-call-ended', { callId: data.call_id }); const sessionId = webRtcLinks.cleanupCall(data.call_id); if (sessionId) { onCloseWebRtcSession(sessionId); } }); onProxyEvent('sip_unhandled', (data) => { log(`[sip] unhandled ${data.method_or_status} Call-ID=${data.call_id?.slice(0, 20)} from=${data.from_addr}:${data.from_port}`); }); onProxyEvent('leg_added', (data) => { log(`[leg] added: call=${data.call_id} leg=${data.leg_id} kind=${data.kind} state=${data.state}`); statusStore.noteLegAdded(data); }); onProxyEvent('leg_removed', (data) => { log(`[leg] removed: call=${data.call_id} leg=${data.leg_id}`); statusStore.noteLegRemoved(data); }); onProxyEvent('leg_state_changed', (data) => { log(`[leg] state: call=${data.call_id} leg=${data.leg_id} -> ${data.state}`); statusStore.noteLegStateChanged(data); }); onProxyEvent('webrtc_ice_candidate', (data) => { broadcast('webrtc-ice', { sessionId: data.session_id, candidate: { candidate: data.candidate, sdpMid: data.sdp_mid, sdpMLineIndex: data.sdp_mline_index, }, }); }); onProxyEvent('webrtc_state', (data) => { log(`[webrtc] session=${data.session_id?.slice(0, 8)} state=${data.state}`); }); onProxyEvent('webrtc_track', (data) => { log(`[webrtc] session=${data.session_id?.slice(0, 8)} track=${data.kind} codec=${data.codec}`); }); onProxyEvent('webrtc_audio_rx', (data) => { if (data.packet_count === 1 || data.packet_count === 50) { log(`[webrtc] session=${data.session_id?.slice(0, 8)} browser audio rx #${data.packet_count}`); } }); onProxyEvent('voicemail_started', (data) => { log(`[voicemail] started for call ${data.call_id} caller=${data.caller_number}`); }); onProxyEvent('recording_done', (data) => { log(`[voicemail] recording done: ${data.file_path} (${data.duration_ms}ms) caller=${data.caller_number}`); voiceboxManager.addMessage('default', { callerNumber: data.caller_number || 'Unknown', callerName: null, fileName: data.file_path, durationMs: data.duration_ms, }); }); onProxyEvent('voicemail_error', (data) => { log(`[voicemail] error: ${data.error} call=${data.call_id}`); }); }