From 36b3a5d34d005df2bed38548daf57d1ba6303882 Mon Sep 17 00:00:00 2001 From: saridsa2 Date: Tue, 17 Mar 2026 21:20:32 +0530 Subject: [PATCH] =?UTF-8?q?feat:=20Ozonetel=20SIP=20integration=20?= =?UTF-8?q?=E2=80=94=20agent=20login,=20SIP=20registration,=20inbound=20ca?= =?UTF-8?q?ll=20flow=20working?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Known issues to fix: - Must refresh page after each call (SIP session not resetting to idle properly) - Decline/reject call not working - Caller ID shows DID instead of original caller (Ozonetel IVR config issue with call:cid) - Socket.IO reconnect noise in console (sidecar not running) --- src/providers/sip-provider.tsx | 91 ++++++++++++++++++++++++++++++++-- 1 file changed, 86 insertions(+), 5 deletions(-) diff --git a/src/providers/sip-provider.tsx b/src/providers/sip-provider.tsx index 467279b..29b6076 100644 --- a/src/providers/sip-provider.tsx +++ b/src/providers/sip-provider.tsx @@ -1,25 +1,106 @@ -import { createContext, useContext, useEffect, useRef, type PropsWithChildren } from 'react'; +import { createContext, useContext, useEffect, useRef, useState, type PropsWithChildren } from 'react'; import { useSipPhone } from '@/hooks/use-sip-phone'; -type SipContextType = ReturnType; +const SIDECAR_URL = import.meta.env.VITE_SIDECAR_URL ?? 'http://localhost:4100'; + +type SipContextType = ReturnType & { + ozonetelStatus: 'idle' | 'logging-in' | 'logged-in' | 'error'; + ozonetelError: string | null; +}; const SipContext = createContext(null); export const SipProvider = ({ children }: PropsWithChildren) => { const sipPhone = useSipPhone(); const hasConnected = useRef(false); + const [ozonetelStatus, setOzonetelStatus] = useState<'idle' | 'logging-in' | 'logged-in' | 'error'>('idle'); + const [ozonetelError, setOzonetelError] = useState(null); - // Auto-connect on mount — skip StrictMode double-fire + // Auto-connect SIP + login to Ozonetel on mount useEffect(() => { if (!hasConnected.current) { hasConnected.current = true; + + // 1. Connect SIP (WebSocket → Ozonetel SIP server) sipPhone.connect(); + + // 2. Login agent to Ozonetel's routing system + // Try sidecar first, fallback to direct Ozonetel API call + const sipId = (import.meta.env.VITE_SIP_URI ?? '').replace('sip:', '').split('@')[0]; + const agentId = import.meta.env.VITE_OZONETEL_AGENT_ID ?? 'Agent3'; + const agentPassword = import.meta.env.VITE_OZONETEL_AGENT_PASSWORD ?? 'Test123$'; + const accountId = import.meta.env.VITE_OZONETEL_ACCOUNT_ID ?? 'global_demo'; + const apiKey = import.meta.env.VITE_OZONETEL_API_KEY ?? ''; + + if (sipId) { + setOzonetelStatus('logging-in'); + + const loginViaOzonetelDirect = async () => { + const params = new URLSearchParams({ + userName: accountId, + apiKey: apiKey, + phoneNumber: sipId, + action: 'login', + mode: 'blended', + state: 'Ready', + }); + + const response = await fetch( + `https://in1-ccaas-api.ozonetel.com/CAServices/AgentAuthenticationV2/index.php`, + { + method: 'POST', + headers: { + 'Content-Type': 'application/x-www-form-urlencoded', + 'Authorization': 'Basic ' + btoa(`${agentId}:${agentPassword}`), + }, + body: params.toString(), + }, + ); + return response.json(); + }; + + const loginViaSidecar = async () => { + const response = await fetch(`${SIDECAR_URL}/api/ozonetel/agent-login`, { + method: 'POST', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify({ + agentId, + password: agentPassword, + phoneNumber: sipId, + mode: 'blended', + }), + }); + return response.json(); + }; + + // Try sidecar first, fallback to direct + loginViaSidecar() + .catch(() => loginViaOzonetelDirect()) + .then(data => { + if (data.status === 'success' || data.message?.includes('logged in')) { + setOzonetelStatus('logged-in'); + console.log('Ozonetel agent login:', data.message); + } else { + setOzonetelStatus('error'); + setOzonetelError(data.message ?? 'Unknown error'); + console.warn('Ozonetel agent login issue:', data); + } + }) + .catch(err => { + setOzonetelStatus('error'); + setOzonetelError(err.message); + console.error('Ozonetel agent login failed:', err); + }); + } } - // Do NOT disconnect on cleanup — the SIP connection should persist // eslint-disable-next-line react-hooks/exhaustive-deps }, []); - return {children}; + return ( + + {children} + + ); }; export const useSip = (): SipContextType => {