fix(voice): authenticate page-close terminate beacon
The previous beacon used navigator.sendBeacon, which can't set custom request headers — and Chatwoot's API requires devise-token-auth headers (access-token / client / uid) on every call. So the beacon hit the terminate endpoint, got 401, and Meta kept the call alive until the carrier-side timeout (~60s) — exactly the bug reported: refresh during an in-progress call leaves it ringing on Meta's side. Switch to fetch with keepalive:true so the request survives page unload AND lets us rehydrate the auth headers from the cw_d_session_info cookie the dashboard sets at login. credentials:'same-origin' keeps any session cookies along for the ride. Empty body is well under the 64KB keepalive quota so it actually flushes before the tab dies.
This commit is contained in:
@@ -1,4 +1,5 @@
|
||||
import { ref } from 'vue';
|
||||
import Cookies from 'js-cookie';
|
||||
import WhatsappCallsAPI from 'dashboard/api/channel/whatsapp/whatsappCallsAPI';
|
||||
|
||||
// Browser ↔ Meta WebRTC is a singleton — only one PeerConnection at a time can
|
||||
@@ -329,18 +330,51 @@ export const isWhatsappCallMuted = () => {
|
||||
return !tracks[0].enabled;
|
||||
};
|
||||
|
||||
// devise-token-auth requires access-token / client / uid headers on every
|
||||
// request — navigator.sendBeacon can't set custom headers, so the dashboard
|
||||
// stashes the auth payload in the cw_d_session_info cookie at login and we
|
||||
// rehydrate it here for unload-time requests.
|
||||
const getDeviseAuthHeaders = () => {
|
||||
try {
|
||||
const raw = Cookies.get('cw_d_session_info');
|
||||
if (!raw) return null;
|
||||
const session = JSON.parse(raw);
|
||||
return {
|
||||
'access-token': session['access-token'] || '',
|
||||
client: session.client || '',
|
||||
uid: session.uid || '',
|
||||
expiry: session.expiry || '',
|
||||
'token-type': session['token-type'] || 'Bearer',
|
||||
};
|
||||
} catch (_) {
|
||||
return null;
|
||||
}
|
||||
};
|
||||
|
||||
// Best-effort terminate beacon for any WhatsApp call — the backend's terminate
|
||||
// endpoint handles both ringing and in_progress states (rejecting via terminate
|
||||
// endpoint handles both ringing and in_progress states (terminate while ringing
|
||||
// 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.
|
||||
// Browser-direct WebRTC has no rejoin path, so the only sensible thing on page
|
||||
// close is to release the call on Meta's side.
|
||||
//
|
||||
// Uses fetch+keepalive instead of navigator.sendBeacon so we can attach the
|
||||
// devise-token-auth headers — without them the request 401s and the call
|
||||
// stays open on Meta until the carrier-side timeout (~60s).
|
||||
export const sendWhatsappCallBeacon = callId => {
|
||||
if (!callId) return;
|
||||
const accountId = window.location.pathname.split('/')[3];
|
||||
if (!accountId) return;
|
||||
const headers = getDeviseAuthHeaders();
|
||||
if (!headers) return;
|
||||
const url = `/api/v1/accounts/${accountId}/whatsapp_calls/${callId}/terminate`;
|
||||
try {
|
||||
navigator.sendBeacon(url);
|
||||
fetch(url, {
|
||||
method: 'POST',
|
||||
keepalive: true,
|
||||
credentials: 'same-origin',
|
||||
headers: { 'Content-Type': 'application/json', ...headers },
|
||||
body: '{}',
|
||||
}).catch(() => {});
|
||||
} catch (_) {
|
||||
/* noop */
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user