fix(voice): terminate ringing WhatsApp calls on page close
Browser-direct WebRTC has no rejoin path, so any call that outlives the agent's tab becomes permanently orphaned on Meta's side. The existing pagehide beacon only covered the active (accepted) call — extend it to every ringing inbound too, so a hard refresh during ringing releases the call instead of leaving Meta to time it out. - useWhatsappCallSession: factor sendWhatsappCallBeacon(callId) out of the active-call wrapper so any caller can post terminate for any callId without going through the activeCallId / intentionallyClosing guard. - useCallSession.handlePageHide: after the active-call beacon, iterate the calls store for any ringing WhatsApp call and beacon /terminate for each. terminate is the right endpoint because the backend records it as 'no_answer' when the call was still ringing — accurate UX shape.
This commit is contained in:
@@ -6,6 +6,7 @@ import { useCallsStore } from 'dashboard/stores/calls';
|
||||
import {
|
||||
useWhatsappCallSession,
|
||||
sendWhatsappTerminateBeacon,
|
||||
sendWhatsappCallBeacon,
|
||||
cleanupWhatsappSession,
|
||||
} from 'dashboard/composables/useWhatsappCallSession';
|
||||
import { handleVoiceCallCreated } from 'dashboard/helper/voice';
|
||||
@@ -69,10 +70,15 @@ export function useCallSession() {
|
||||
});
|
||||
};
|
||||
|
||||
// pagehide fires after the user confirms the prompt. Let the WhatsApp session
|
||||
// best-effort sendBeacon a terminate so the server doesn't keep the call open.
|
||||
// pagehide fires after the user confirms the prompt. Beacon a terminate for
|
||||
// every WhatsApp call in the store — active OR ringing — since browser-direct
|
||||
// WebRTC has no rejoin path, so any call that survives the close becomes
|
||||
// permanently orphaned on Meta's side.
|
||||
const handlePageHide = () => {
|
||||
sendWhatsappTerminateBeacon();
|
||||
callsStore.calls
|
||||
.filter(c => c.provider === 'whatsapp' && !c.isActive && c.callId)
|
||||
.forEach(c => sendWhatsappCallBeacon(c.callId));
|
||||
};
|
||||
|
||||
const handleTwilioDisconnected = () => callsStore.clearActiveCall();
|
||||
|
||||
@@ -329,15 +329,26 @@ export const isWhatsappCallMuted = () => {
|
||||
return !tracks[0].enabled;
|
||||
};
|
||||
|
||||
// Best-effort terminate when the tab actually closes after the beforeunload prompt.
|
||||
export const sendWhatsappTerminateBeacon = () => {
|
||||
if (!activeCallId || intentionallyClosing) return;
|
||||
// Best-effort terminate beacon for any WhatsApp call — the backend's terminate
|
||||
// endpoint handles both ringing and in_progress states (rejecting via terminate
|
||||
// records 'no_answer' which is the right shape for "agent left the page").
|
||||
// Browser-direct WebRTC has no rejoin path, so the only sensible thing on
|
||||
// page close is to release the call on Meta's side.
|
||||
export const sendWhatsappCallBeacon = callId => {
|
||||
if (!callId) return;
|
||||
const accountId = window.location.pathname.split('/')[3];
|
||||
if (!accountId) return;
|
||||
const url = `/api/v1/accounts/${accountId}/whatsapp_calls/${activeCallId}/terminate`;
|
||||
const url = `/api/v1/accounts/${accountId}/whatsapp_calls/${callId}/terminate`;
|
||||
try {
|
||||
navigator.sendBeacon(url);
|
||||
} catch (_) {
|
||||
/* noop */
|
||||
}
|
||||
};
|
||||
|
||||
// Backward-compat wrapper for the active-call case — guarded by
|
||||
// intentionallyClosing so we don't double-terminate after an explicit hangup.
|
||||
export const sendWhatsappTerminateBeacon = () => {
|
||||
if (!activeCallId || intentionallyClosing) return;
|
||||
sendWhatsappCallBeacon(activeCallId);
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user