feat(voice): WhatsApp Cloud Calling — UI
This commit is contained in:
@@ -2,10 +2,18 @@ import { computed, ref, watch, onUnmounted, onMounted } from 'vue';
|
||||
import VoiceAPI from 'dashboard/api/channel/voice/voiceAPIClient';
|
||||
import TwilioVoiceClient from 'dashboard/api/channel/voice/twilioVoiceClient';
|
||||
import { useCallsStore } from 'dashboard/stores/calls';
|
||||
import {
|
||||
useWhatsappCallSession,
|
||||
sendWhatsappTerminateBeacon,
|
||||
cleanupWhatsappSession,
|
||||
} from 'dashboard/composables/useWhatsappCallSession';
|
||||
import Timer from 'dashboard/helper/Timer';
|
||||
|
||||
const isWhatsappCall = call => call?.provider === 'whatsapp';
|
||||
|
||||
export function useCallSession() {
|
||||
const callsStore = useCallsStore();
|
||||
const whatsappSession = useWhatsappCallSession();
|
||||
const isJoining = ref(false);
|
||||
const callDuration = ref(0);
|
||||
const durationTimer = new Timer(elapsed => {
|
||||
@@ -29,20 +37,52 @@ export function useCallSession() {
|
||||
{ immediate: true }
|
||||
);
|
||||
|
||||
// Browser-native confirm prompt when reload/close happens mid-call. Reload
|
||||
// tears down the WebRTC session permanently for WhatsApp (no rejoin) and
|
||||
// drops the agent leg for Twilio, so warn either way.
|
||||
const handleBeforeUnload = event => {
|
||||
if (!hasActiveCall.value) return;
|
||||
event.preventDefault();
|
||||
event.returnValue = '';
|
||||
};
|
||||
|
||||
// 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.
|
||||
const handlePageHide = () => {
|
||||
sendWhatsappTerminateBeacon();
|
||||
};
|
||||
|
||||
const handleTwilioDisconnected = () => callsStore.clearActiveCall();
|
||||
|
||||
onMounted(() => {
|
||||
TwilioVoiceClient.addEventListener('call:disconnected', () =>
|
||||
callsStore.clearActiveCall()
|
||||
TwilioVoiceClient.addEventListener(
|
||||
'call:disconnected',
|
||||
handleTwilioDisconnected
|
||||
);
|
||||
window.addEventListener('beforeunload', handleBeforeUnload);
|
||||
window.addEventListener('pagehide', handlePageHide);
|
||||
});
|
||||
|
||||
onUnmounted(() => {
|
||||
durationTimer.stop();
|
||||
TwilioVoiceClient.removeEventListener('call:disconnected', () =>
|
||||
callsStore.clearActiveCall()
|
||||
TwilioVoiceClient.removeEventListener(
|
||||
'call:disconnected',
|
||||
handleTwilioDisconnected
|
||||
);
|
||||
window.removeEventListener('beforeunload', handleBeforeUnload);
|
||||
window.removeEventListener('pagehide', handlePageHide);
|
||||
});
|
||||
|
||||
const findCall = callSid => callsStore.calls.find(c => c.callSid === callSid);
|
||||
|
||||
const endCall = async ({ conversationId, inboxId, callSid }) => {
|
||||
if (isWhatsappCall(findCall(callSid))) {
|
||||
await whatsappSession.endActiveCall();
|
||||
durationTimer.stop();
|
||||
callsStore.clearActiveCall();
|
||||
return;
|
||||
}
|
||||
|
||||
await VoiceAPI.leaveConference({ inboxId, conversationId, callSid });
|
||||
TwilioVoiceClient.endClientCall();
|
||||
durationTimer.stop();
|
||||
@@ -54,6 +94,18 @@ export function useCallSession() {
|
||||
|
||||
isJoining.value = true;
|
||||
try {
|
||||
const call = findCall(callSid);
|
||||
if (isWhatsappCall(call)) {
|
||||
await whatsappSession.acceptIncomingCall({
|
||||
callId: call.callId,
|
||||
sdpOffer: call.sdpOffer,
|
||||
iceServers: call.iceServers,
|
||||
});
|
||||
callsStore.setCallActive(callSid);
|
||||
durationTimer.start();
|
||||
return { callId: call.callId };
|
||||
}
|
||||
|
||||
const device = await TwilioVoiceClient.initializeDevice(inboxId);
|
||||
if (!device) return null;
|
||||
|
||||
@@ -76,6 +128,10 @@ export function useCallSession() {
|
||||
} catch (error) {
|
||||
// eslint-disable-next-line no-console
|
||||
console.error('Failed to join call:', error);
|
||||
// Tear down any half-built WebRTC state so the user's next click starts
|
||||
// fresh; otherwise the leftover pc + mic stream survives and confuses
|
||||
// the second-attempt SDP exchange.
|
||||
cleanupWhatsappSession();
|
||||
return null;
|
||||
} finally {
|
||||
isJoining.value = false;
|
||||
@@ -83,7 +139,12 @@ export function useCallSession() {
|
||||
};
|
||||
|
||||
const rejectIncomingCall = callSid => {
|
||||
TwilioVoiceClient.endClientCall();
|
||||
const call = findCall(callSid);
|
||||
if (isWhatsappCall(call) && call?.callId) {
|
||||
whatsappSession.rejectIncomingCall(call.callId);
|
||||
} else {
|
||||
TwilioVoiceClient.endClientCall();
|
||||
}
|
||||
callsStore.dismissCall(callSid);
|
||||
};
|
||||
|
||||
|
||||
Reference in New Issue
Block a user