mirror of
https://dev.azure.com/globalhealthx/EMR/_git/helix-engage
synced 2026-04-11 18:28:15 +00:00
fix: SIP disconnect on callState change + dispose sends agentId
- 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) <noreply@anthropic.com>
This commit is contained in:
@@ -90,9 +90,11 @@ export const ActiveCallCard = ({ lead, callerPhone, missedCallId, onCallComplete
|
|||||||
|
|
||||||
// Submit disposition to sidecar
|
// Submit disposition to sidecar
|
||||||
if (callUcid) {
|
if (callUcid) {
|
||||||
|
const agentCfg = JSON.parse(localStorage.getItem('helix_agent_config') ?? '{}');
|
||||||
const disposePayload = {
|
const disposePayload = {
|
||||||
ucid: callUcid,
|
ucid: callUcid,
|
||||||
disposition,
|
disposition,
|
||||||
|
agentId: agentCfg.ozonetelAgentId,
|
||||||
callerPhone,
|
callerPhone,
|
||||||
direction: callDirectionRef.current,
|
direction: callDirectionRef.current,
|
||||||
durationSec: callDuration,
|
durationSec: callDuration,
|
||||||
|
|||||||
@@ -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 { useAtom, useSetAtom } from 'jotai';
|
||||||
import {
|
import {
|
||||||
sipConnectionStatusAtom,
|
sipConnectionStatusAtom,
|
||||||
@@ -93,22 +93,31 @@ export const SipProvider = ({ children }: PropsWithChildren) => {
|
|||||||
// Layer 2: Fire sendBeacon to auto-dispose if user confirms leave
|
// 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.
|
// 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.
|
// 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(() => {
|
useEffect(() => {
|
||||||
const handleBeforeUnload = (e: BeforeUnloadEvent) => {
|
const handleBeforeUnload = (e: BeforeUnloadEvent) => {
|
||||||
const ucid = localStorage.getItem('helix_active_ucid');
|
const ucid = localStorage.getItem('helix_active_ucid');
|
||||||
|
const state = callStateRef.current;
|
||||||
|
|
||||||
// Layer 1: Show browser "Leave page?" dialog during active calls
|
// 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.preventDefault();
|
||||||
e.returnValue = '';
|
e.returnValue = '';
|
||||||
}
|
}
|
||||||
|
|
||||||
// Layer 2: Fire disposition beacon if there's an active UCID
|
// Layer 2: Fire disposition beacon if there's an active UCID
|
||||||
// sendBeacon is guaranteed to fire even during page unload
|
|
||||||
if (ucid) {
|
if (ucid) {
|
||||||
|
const agentCfg = JSON.parse(localStorage.getItem('helix_agent_config') ?? '{}');
|
||||||
const payload = JSON.stringify({
|
const payload = JSON.stringify({
|
||||||
ucid,
|
ucid,
|
||||||
disposition: 'CALLBACK_REQUESTED',
|
disposition: 'CALLBACK_REQUESTED',
|
||||||
|
agentId: agentCfg.ozonetelAgentId,
|
||||||
autoDisposed: true,
|
autoDisposed: true,
|
||||||
});
|
});
|
||||||
navigator.sendBeacon('/api/ozonetel/dispose', new Blob([payload], { type: 'application/json' }));
|
navigator.sendBeacon('/api/ozonetel/dispose', new Blob([payload], { type: 'application/json' }));
|
||||||
@@ -125,7 +134,7 @@ export const SipProvider = ({ children }: PropsWithChildren) => {
|
|||||||
window.removeEventListener('unload', handleUnload);
|
window.removeEventListener('unload', handleUnload);
|
||||||
disconnectSip();
|
disconnectSip();
|
||||||
};
|
};
|
||||||
}, [callState]);
|
}, []); // empty deps — runs once on mount, cleanup only on unmount
|
||||||
|
|
||||||
return <>{children}</>;
|
return <>{children}</>;
|
||||||
};
|
};
|
||||||
|
|||||||
Reference in New Issue
Block a user