/** * CallManager - Singleton to manage active voice calls * * Ensures only ONE voice call can be active at a time per device. * If a new call is started while another is active, the old one is disconnected first. */ type DisconnectCallback = () => Promise; class CallManager { private static instance: CallManager; private activeCallId: string | null = null; private disconnectCallback: DisconnectCallback | null = null; private constructor() { // Singleton } static getInstance(): CallManager { if (!CallManager.instance) { CallManager.instance = new CallManager(); } return CallManager.instance; } /** * Register a new call. If there's an existing call, disconnect it first. * @param callId Unique ID for this call * @param onDisconnect Callback to disconnect this call * @returns true if this call can proceed */ async registerCall( callId: string, onDisconnect: DisconnectCallback ): Promise { console.log(`[CallManager] Registering call: ${callId}`); // If there's an active call, disconnect it first if (this.activeCallId && this.activeCallId !== callId) { console.log( `[CallManager] Active call exists (${this.activeCallId}), disconnecting...` ); if (this.disconnectCallback) { try { await this.disconnectCallback(); console.log(`[CallManager] Previous call disconnected`); } catch (err) { console.warn(`[CallManager] Error disconnecting previous call:`, err); } } } // Register the new call this.activeCallId = callId; this.disconnectCallback = onDisconnect; console.log(`[CallManager] Call ${callId} is now active`); return true; } /** * Unregister a call when it ends * @param callId The call ID to unregister */ unregisterCall(callId: string): void { if (this.activeCallId === callId) { console.log(`[CallManager] Unregistering call: ${callId}`); this.activeCallId = null; this.disconnectCallback = null; } else { console.log( `[CallManager] Call ${callId} is not active, ignoring unregister` ); } } /** * Check if there's an active call */ hasActiveCall(): boolean { return this.activeCallId !== null; } /** * Get the current active call ID */ getActiveCallId(): string | null { return this.activeCallId; } /** * Force disconnect the active call (if any) */ async forceDisconnect(): Promise { if (this.activeCallId && this.disconnectCallback) { console.log(`[CallManager] Force disconnecting call: ${this.activeCallId}`); try { await this.disconnectCallback(); } catch (err) { console.warn(`[CallManager] Error force disconnecting:`, err); } this.activeCallId = null; this.disconnectCallback = null; } } } export const callManager = CallManager.getInstance();