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
@@ -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();