fix: prevent StrictMode double-mount from killing SIP WebSocket connection

This commit is contained in:
2026-03-17 20:08:22 +05:30
parent b2cc3d7012
commit d54d54f5f3
3 changed files with 30 additions and 5 deletions

View File

@@ -71,6 +71,11 @@ export const useSipPhone = (config?: Partial<SIPConfig>) => {
return; return;
} }
// Don't reconnect if already connected or connecting
if (sipClientRef.current?.isConnected() || sipClientRef.current?.isRegistered()) {
return;
}
if (sipClientRef.current) { if (sipClientRef.current) {
sipClientRef.current.disconnect(); sipClientRef.current.disconnect();
} }
@@ -127,11 +132,15 @@ export const useSipPhone = (config?: Partial<SIPConfig>) => {
setIsOnHold(!isOnHold); setIsOnHold(!isOnHold);
}, [isOnHold]); }, [isOnHold]);
// Cleanup on unmount // Cleanup only on actual page unload, not StrictMode remount
useEffect(() => { useEffect(() => {
return () => { const handleUnload = () => {
sipClientRef.current?.disconnect(); sipClientRef.current?.disconnect();
}; };
window.addEventListener('beforeunload', handleUnload);
return () => {
window.removeEventListener('beforeunload', handleUnload);
};
}, []); }, []);
return { return {

View File

@@ -15,15 +15,26 @@ export class SIPClient {
) {} ) {}
connect(): void { connect(): void {
// Enable JsSIP debug logging to diagnose connection issues
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 configuration: UAConfiguration = { const configuration: UAConfiguration = {
sockets: [socket], sockets: [socket],
uri: this.config.uri, uri: this.config.uri,
password: this.config.password, password: this.config.password,
authorization_user: sipId,
display_name: this.config.displayName, display_name: this.config.displayName,
register: true, register: true,
register_expires: 120, register_expires: 120,
session_timers: false,
connection_recovery_min_interval: 2,
connection_recovery_max_interval: 30,
}; };
this.ua = new JsSIP.UA(configuration); this.ua = new JsSIP.UA(configuration);

View File

@@ -1,4 +1,4 @@
import { createContext, useContext, useEffect, type PropsWithChildren } from 'react'; import { createContext, useContext, useEffect, useRef, type PropsWithChildren } from 'react';
import { useSipPhone } from '@/hooks/use-sip-phone'; import { useSipPhone } from '@/hooks/use-sip-phone';
type SipContextType = ReturnType<typeof useSipPhone>; type SipContextType = ReturnType<typeof useSipPhone>;
@@ -7,10 +7,15 @@ const SipContext = createContext<SipContextType | null>(null);
export const SipProvider = ({ children }: PropsWithChildren) => { export const SipProvider = ({ children }: PropsWithChildren) => {
const sipPhone = useSipPhone(); const sipPhone = useSipPhone();
const hasConnected = useRef(false);
// Auto-connect on mount // Auto-connect on mount — skip StrictMode double-fire
useEffect(() => { useEffect(() => {
sipPhone.connect(); if (!hasConnected.current) {
hasConnected.current = true;
sipPhone.connect();
}
// Do NOT disconnect on cleanup — the SIP connection should persist
// eslint-disable-next-line react-hooks/exhaustive-deps // eslint-disable-next-line react-hooks/exhaustive-deps
}, []); }, []);