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:
Tanmay Deep Sharma
2026-05-03 16:07:55 +07:00
parent 77c57acffa
commit 772734840d
2 changed files with 23 additions and 6 deletions
@@ -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);
};