fix: session reset after call end, add reject/decline for incoming calls, extract caller ID from SIP headers (P-Asserted-Identity, Remote-Party-ID)

This commit is contained in:
2026-03-17 21:31:53 +05:30
parent 36b3a5d34d
commit aff383cb6d
3 changed files with 76 additions and 14 deletions

View File

@@ -92,6 +92,7 @@ export const CallWidget = () => {
isOnHold, isOnHold,
callDuration, callDuration,
answer, answer,
reject,
hangup, hangup,
toggleMute, toggleMute,
toggleHold, toggleHold,
@@ -171,7 +172,7 @@ export const CallWidget = () => {
<Button size="md" color="primary" iconLeading={Phone01} onClick={answer}> <Button size="md" color="primary" iconLeading={Phone01} onClick={answer}>
Answer Answer
</Button> </Button>
<Button size="md" color="primary-destructive" iconLeading={PhoneX} onClick={hangup}> <Button size="md" color="primary-destructive" iconLeading={PhoneX} onClick={reject}>
Decline Decline
</Button> </Button>
</div> </div>

View File

@@ -110,6 +110,10 @@ export const useSipPhone = (config?: Partial<SIPConfig>) => {
sipClientRef.current?.answer(); sipClientRef.current?.answer();
}, []); }, []);
const reject = useCallback(() => {
sipClientRef.current?.reject();
}, []);
const hangup = useCallback(() => { const hangup = useCallback(() => {
sipClientRef.current?.hangup(); sipClientRef.current?.hangup();
}, []); }, []);
@@ -159,6 +163,7 @@ export const useSipPhone = (config?: Partial<SIPConfig>) => {
disconnect, disconnect,
makeCall, makeCall,
answer, answer,
reject,
hangup, hangup,
toggleMute, toggleMute,
toggleHold, toggleHold,

View File

@@ -15,13 +15,9 @@ export class SIPClient {
) {} ) {}
connect(): void { connect(): void {
// Enable JsSIP debug logging to diagnose connection issues
JsSIP.debug.enable('JsSIP:*'); JsSIP.debug.enable('JsSIP:*');
const socket = new JsSIP.WebSocketInterface(this.config.wsServer); 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 sipId = this.config.uri.replace('sip:', '').split('@')[0];
const configuration: UAConfiguration = { const configuration: UAConfiguration = {
@@ -61,13 +57,19 @@ export class SIPClient {
this.ua.on('newRTCSession', (data: RTCSessionEvent) => { this.ua.on('newRTCSession', (data: RTCSessionEvent) => {
const session = data.session; 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; this.currentSession = session;
// Extract caller number // Extract caller number — try multiple SIP headers
const remoteUri = session.remote_identity?.uri?.toString() ?? ''; const callerNumber = this.extractCallerNumber(session);
const callerNumber = remoteUri.replace('sip:', '').split('@')[0] || 'Unknown';
// Setup audio // Setup audio for this session
session.on('peerconnection', (e: PeerConnectionEvent) => { session.on('peerconnection', (e: PeerConnectionEvent) => {
const pc = e.peerconnection; const pc = e.peerconnection;
pc.ontrack = (event: RTCTrackEvent) => { pc.ontrack = (event: RTCTrackEvent) => {
@@ -95,15 +97,13 @@ export class SIPClient {
}) as CallListener); }) as CallListener);
session.on('failed', (_e: EndEvent) => { session.on('failed', (_e: EndEvent) => {
this.resetSession();
this.onCallStateChange('failed'); this.onCallStateChange('failed');
this.currentSession = null;
this.cleanupAudio();
}); });
session.on('ended', (_e: EndEvent) => { session.on('ended', (_e: EndEvent) => {
this.resetSession();
this.onCallStateChange('ended'); this.onCallStateChange('ended');
this.currentSession = null;
this.cleanupAudio();
}); });
if (session.direction === 'incoming') { 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 { hangup(): void {
if (this.currentSession) { if (this.currentSession) {
this.currentSession.terminate(); this.currentSession.terminate();
this.currentSession = null; this.resetSession();
} }
} }
@@ -197,6 +209,11 @@ export class SIPClient {
return this.ua?.isRegistered() ?? false; return this.ua?.isRegistered() ?? false;
} }
private resetSession(): void {
this.currentSession = null;
this.cleanupAudio();
}
private cleanupAudio(): void { private cleanupAudio(): void {
if (this.audioElement) { if (this.audioElement) {
this.audioElement.srcObject = null; 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[] { private parseStunServers(stunConfig: string): RTCIceServer[] {
const servers: RTCIceServer[] = []; const servers: RTCIceServer[] = [];
const lines = stunConfig.split('\n').filter((line) => line.trim()); const lines = stunConfig.split('\n').filter((line) => line.trim());