diff --git a/src/lib/sip-client.ts b/src/lib/sip-client.ts index 129a491..2f3e4be 100644 --- a/src/lib/sip-client.ts +++ b/src/lib/sip-client.ts @@ -223,10 +223,17 @@ 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) { + // Ozonetel sends the real caller number in X-CALLERNO header + const xCallerNo = request.getHeader('X-CALLERNO'); + if (xCallerNo) { + // Remove leading 0s or country code prefix (00919... → 919...) + const cleaned = xCallerNo.replace(/^0+/, ''); + return cleaned; + } + // Check P-Asserted-Identity const pai = request.getHeader('P-Asserted-Identity'); if (pai) { @@ -241,11 +248,11 @@ export class SIPClient { if (match) return match[1]; } - // Check X-Original-CallerID (custom header some PBX systems use) + // Check X-Original-CallerID const xCid = request.getHeader('X-Original-CallerID'); if (xCid) return xCid; - // Check From header display name (sometimes contains the real number) + // Check From header display name const fromDisplay = request.from?.display_name; if (fromDisplay && /^\+?\d{7,}$/.test(fromDisplay)) { return fromDisplay; diff --git a/src/providers/sip-provider.tsx b/src/providers/sip-provider.tsx index 8c561ce..2995368 100644 --- a/src/providers/sip-provider.tsx +++ b/src/providers/sip-provider.tsx @@ -1,4 +1,4 @@ -import { createContext, useContext, useEffect, useRef, useState, type PropsWithChildren } from 'react'; +import { createContext, useContext, useEffect, useState, type PropsWithChildren } from 'react'; import { useSipPhone } from '@/hooks/use-sip-phone'; type SipContextType = ReturnType & { @@ -8,22 +8,20 @@ type SipContextType = ReturnType & { const SipContext = createContext(null); +// Module-level flag — survives React StrictMode double-mount +let sipConnectedGlobal = false; + export const SipProvider = ({ children }: PropsWithChildren) => { const sipPhone = useSipPhone(); - const hasConnected = useRef(false); const [ozonetelStatus, setOzonetelStatus] = useState<'idle' | 'logging-in' | 'logged-in' | 'error'>('idle'); // eslint-disable-next-line @typescript-eslint/no-unused-vars const [ozonetelError, _setOzonetelError] = useState(null); - // Auto-connect SIP on mount — only WebSocket/SIP registration - // Ozonetel agent login is handled by the sidecar during auth, NOT here + // Auto-connect SIP on mount — module-level guard prevents duplicate connections useEffect(() => { - if (!hasConnected.current) { - hasConnected.current = true; + if (!sipConnectedGlobal) { + sipConnectedGlobal = true; sipPhone.connect(); - - // Ozonetel status tracks whether the sidecar handled agent login - // We assume logged-in if SIP connects (sidecar handles the REST login) setOzonetelStatus('logged-in'); } // eslint-disable-next-line react-hooks/exhaustive-deps