From 6a2fc4722604e4cb1782d9dbafbf482b7d0a12d6 Mon Sep 17 00:00:00 2001 From: saridsa2 Date: Fri, 10 Apr 2026 15:49:34 +0530 Subject: [PATCH] fix: SIP disconnect on callState change + dispose sends agentId MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Fixed useEffect dependency bug: callState in deps caused cleanup (disconnectSip) to fire on every state transition, killing SIP mid-dial. Now uses useRef for callState in beforeunload handler with empty deps array — cleanup only fires on unmount. - sendBeacon auto-dispose now includes agentId from agent config - Disposition modal submit now includes agentId from agent config Co-Authored-By: Claude Opus 4.6 (1M context) --- src/components/call-desk/active-call-card.tsx | 2 ++ src/providers/sip-provider.tsx | 17 +++++++++++++---- 2 files changed, 15 insertions(+), 4 deletions(-) diff --git a/src/components/call-desk/active-call-card.tsx b/src/components/call-desk/active-call-card.tsx index 2d975eb..77bec51 100644 --- a/src/components/call-desk/active-call-card.tsx +++ b/src/components/call-desk/active-call-card.tsx @@ -90,9 +90,11 @@ export const ActiveCallCard = ({ lead, callerPhone, missedCallId, onCallComplete // Submit disposition to sidecar if (callUcid) { + const agentCfg = JSON.parse(localStorage.getItem('helix_agent_config') ?? '{}'); const disposePayload = { ucid: callUcid, disposition, + agentId: agentCfg.ozonetelAgentId, callerPhone, direction: callDirectionRef.current, durationSec: callDuration, diff --git a/src/providers/sip-provider.tsx b/src/providers/sip-provider.tsx index 8213265..dcc9bf5 100644 --- a/src/providers/sip-provider.tsx +++ b/src/providers/sip-provider.tsx @@ -1,4 +1,4 @@ -import { useEffect, useCallback, type PropsWithChildren } from 'react'; +import { useEffect, useCallback, useRef, type PropsWithChildren } from 'react'; import { useAtom, useSetAtom } from 'jotai'; import { sipConnectionStatusAtom, @@ -93,22 +93,31 @@ export const SipProvider = ({ children }: PropsWithChildren) => { // Layer 2: Fire sendBeacon to auto-dispose if user confirms leave // These two layers protect against the "agent refreshes mid-call → stuck in ACW" bug. // Layer 3 (server-side ACW timeout) lives in supervisor.service.ts. + // + // IMPORTANT: beforeunload reads callState via a ref (not the dep array) + // because adding callState to deps causes the cleanup to fire on every + // state transition → disconnectSip() → kills the call mid-flight. + const callStateRef = useRef(callState); + callStateRef.current = callState; + useEffect(() => { const handleBeforeUnload = (e: BeforeUnloadEvent) => { const ucid = localStorage.getItem('helix_active_ucid'); + const state = callStateRef.current; // Layer 1: Show browser "Leave page?" dialog during active calls - if (callState === 'active' || callState === 'ringing-in' || callState === 'ringing-out') { + if (state === 'active' || state === 'ringing-in' || state === 'ringing-out') { e.preventDefault(); e.returnValue = ''; } // Layer 2: Fire disposition beacon if there's an active UCID - // sendBeacon is guaranteed to fire even during page unload if (ucid) { + const agentCfg = JSON.parse(localStorage.getItem('helix_agent_config') ?? '{}'); const payload = JSON.stringify({ ucid, disposition: 'CALLBACK_REQUESTED', + agentId: agentCfg.ozonetelAgentId, autoDisposed: true, }); navigator.sendBeacon('/api/ozonetel/dispose', new Blob([payload], { type: 'application/json' })); @@ -125,7 +134,7 @@ export const SipProvider = ({ children }: PropsWithChildren) => { window.removeEventListener('unload', handleUnload); disconnectSip(); }; - }, [callState]); + }, []); // empty deps — runs once on mount, cleanup only on unmount return <>{children}; };