From aff383cb6df5bb0ece965660f06ad2ab703d0e1c Mon Sep 17 00:00:00 2001 From: saridsa2 Date: Tue, 17 Mar 2026 21:31:53 +0530 Subject: [PATCH] fix: session reset after call end, add reject/decline for incoming calls, extract caller ID from SIP headers (P-Asserted-Identity, Remote-Party-ID) --- src/components/call-desk/call-widget.tsx | 3 +- src/hooks/use-sip-phone.ts | 5 ++ src/lib/sip-client.ts | 82 ++++++++++++++++++++---- 3 files changed, 76 insertions(+), 14 deletions(-) diff --git a/src/components/call-desk/call-widget.tsx b/src/components/call-desk/call-widget.tsx index b3f7c4b..d95303d 100644 --- a/src/components/call-desk/call-widget.tsx +++ b/src/components/call-desk/call-widget.tsx @@ -92,6 +92,7 @@ export const CallWidget = () => { isOnHold, callDuration, answer, + reject, hangup, toggleMute, toggleHold, @@ -171,7 +172,7 @@ export const CallWidget = () => { - diff --git a/src/hooks/use-sip-phone.ts b/src/hooks/use-sip-phone.ts index 49cda38..abc4811 100644 --- a/src/hooks/use-sip-phone.ts +++ b/src/hooks/use-sip-phone.ts @@ -110,6 +110,10 @@ export const useSipPhone = (config?: Partial) => { sipClientRef.current?.answer(); }, []); + const reject = useCallback(() => { + sipClientRef.current?.reject(); + }, []); + const hangup = useCallback(() => { sipClientRef.current?.hangup(); }, []); @@ -159,6 +163,7 @@ export const useSipPhone = (config?: Partial) => { disconnect, makeCall, answer, + reject, hangup, toggleMute, toggleHold, diff --git a/src/lib/sip-client.ts b/src/lib/sip-client.ts index fe7a020..129a491 100644 --- a/src/lib/sip-client.ts +++ b/src/lib/sip-client.ts @@ -15,13 +15,9 @@ export class SIPClient { ) {} connect(): void { - // Enable JsSIP debug logging to diagnose connection issues JsSIP.debug.enable('JsSIP:*'); const socket = new JsSIP.WebSocketInterface(this.config.wsServer); - - // Extract SIP ID from URI for authorization_user - // URI format: sip:521814@blr-pub-rtc4.ozonetel.com const sipId = this.config.uri.replace('sip:', '').split('@')[0]; const configuration: UAConfiguration = { @@ -61,13 +57,19 @@ export class SIPClient { this.ua.on('newRTCSession', (data: RTCSessionEvent) => { const session = data.session; + + // If we already have an active session, reject the new one + if (this.currentSession && this.currentSession !== session) { + session.terminate(); + return; + } + this.currentSession = session; - // Extract caller number - const remoteUri = session.remote_identity?.uri?.toString() ?? ''; - const callerNumber = remoteUri.replace('sip:', '').split('@')[0] || 'Unknown'; + // Extract caller number — try multiple SIP headers + const callerNumber = this.extractCallerNumber(session); - // Setup audio + // Setup audio for this session session.on('peerconnection', (e: PeerConnectionEvent) => { const pc = e.peerconnection; pc.ontrack = (event: RTCTrackEvent) => { @@ -95,15 +97,13 @@ export class SIPClient { }) as CallListener); session.on('failed', (_e: EndEvent) => { + this.resetSession(); this.onCallStateChange('failed'); - this.currentSession = null; - this.cleanupAudio(); }); session.on('ended', (_e: EndEvent) => { + this.resetSession(); this.onCallStateChange('ended'); - this.currentSession = null; - this.cleanupAudio(); }); if (session.direction === 'incoming') { @@ -158,10 +158,22 @@ export class SIPClient { } } + reject(): void { + if (this.currentSession && this.currentSession.direction === 'incoming') { + // Use 486 Busy Here for rejecting incoming calls + this.currentSession.terminate({ + status_code: 486, + reason_phrase: 'Busy Here', + }); + this.resetSession(); + this.onCallStateChange('ended'); + } + } + hangup(): void { if (this.currentSession) { this.currentSession.terminate(); - this.currentSession = null; + this.resetSession(); } } @@ -197,6 +209,11 @@ export class SIPClient { return this.ua?.isRegistered() ?? false; } + private resetSession(): void { + this.currentSession = null; + this.cleanupAudio(); + } + private cleanupAudio(): void { if (this.audioElement) { this.audioElement.srcObject = null; @@ -205,6 +222,45 @@ export class SIPClient { } } + private extractCallerNumber(session: RTCSession): string { + // Try P-Asserted-Identity header first (most reliable for real caller ID) + try { + const request = session.direction === 'incoming' ? (session as any)._request : null; + if (request) { + // Check P-Asserted-Identity + const pai = request.getHeader('P-Asserted-Identity'); + if (pai) { + const match = pai.match(/sip:(\+?\d+)@/); + if (match) return match[1]; + } + + // Check Remote-Party-ID + const rpid = request.getHeader('Remote-Party-ID'); + if (rpid) { + const match = rpid.match(/sip:(\+?\d+)@/); + if (match) return match[1]; + } + + // Check X-Original-CallerID (custom header some PBX systems use) + const xCid = request.getHeader('X-Original-CallerID'); + if (xCid) return xCid; + + // Check From header display name (sometimes contains the real number) + const fromDisplay = request.from?.display_name; + if (fromDisplay && /^\+?\d{7,}$/.test(fromDisplay)) { + return fromDisplay; + } + } + } catch (e) { + console.warn('Error extracting caller ID from SIP headers:', e); + } + + // Fallback to remote_identity URI + const remoteUri = session.remote_identity?.uri?.toString() ?? ''; + const number = remoteUri.replace('sip:', '').split('@')[0] || 'Unknown'; + return number; + } + private parseStunServers(stunConfig: string): RTCIceServer[] { const servers: RTCIceServer[] = []; const lines = stunConfig.split('\n').filter((line) => line.trim());