Implements a Pion Go B2BUA media server sidecar that sits between Meta's WhatsApp Cloud API and the agent's browser, enabling call persistence across page reloads, server-side recording, multi-participant support, audio injection, and AI integration readiness. Go Media Server (enterprise/media-server/): - Pion WebRTC v4 B2BUA with Peer A (Meta) and Peer B (Agent) connections - Real-time OGG/Opus recording with crash recovery - Audio bridge with multi-peer fan-out and AudioConsumer plugin interface - Audio injection from OGG files with loop support for hold music - Session manager with graceful shutdown and orphaned recording recovery - HTTP API with Bearer token auth, health checks, and metrics Rails Integration: - Whatsapp::MediaServerClient HTTP client for Go sidecar communication - Dual-mode CallService: legacy browser-direct and server-relay paths - Media server callback controller for agent disconnect/recording/terminate - CallRecordingFetchJob: downloads OGG from Go server to ActiveStorage/S3 - CallCleanupJob: sweeps stale ringing and in-progress calls - New endpoints: active, agent_answer, reconnect, join, play_audio - DB migration: media_session_id column with indexes Frontend: - Dual-mode composable auto-detecting legacy vs server-relay - handleAgentOffer() for receiving SDP from media server - useCallReconnection composable for page reload recovery - Removed terminateCallOnUnload in server-relay mode - Simplified outbound call flow in ConversationHeader - New ActionCable event: whatsapp_call.agent_offer Documentation: - Server-side WebRTC architecture spec (1505 lines) - Implementation plan with 119 trackable checklist items - Feature spec, PR breakdown, and relay architecture docs
64 lines
2.4 KiB
JavaScript
64 lines
2.4 KiB
JavaScript
import { onMounted, computed } from 'vue';
|
|
import { useWhatsappCallsStore } from 'dashboard/stores/whatsappCalls';
|
|
import WhatsappCallsAPI from 'dashboard/api/whatsappCalls';
|
|
|
|
/**
|
|
* Checks for an active WhatsApp call on page load and reconnects if found.
|
|
* This handles the server-relay scenario where the call persists on the media
|
|
* server even after the agent's browser reloads.
|
|
*
|
|
* NOTE: This composable intentionally does NOT call useWhatsappCallSession()
|
|
* to avoid creating duplicate side effects (beforeunload handlers, cleanup
|
|
* callbacks, timers). The WhatsappCallWidget owns the useWhatsappCallSession
|
|
* instance. This composable only sets store state and triggers the reconnect
|
|
* API call — the actual WebRTC setup happens when the ActionCable agent_offer
|
|
* event arrives and is handled by handleAgentOffer.
|
|
*
|
|
* Usage: call `useCallReconnection()` in the app-level layout component that
|
|
* mounts once on page load.
|
|
*/
|
|
export function useCallReconnection() {
|
|
const callsStore = useWhatsappCallsStore();
|
|
|
|
const isReconnecting = computed(() => callsStore.isReconnecting);
|
|
|
|
onMounted(async () => {
|
|
// Skip if there's already an active or incoming call in the store
|
|
if (callsStore.hasActiveCall || callsStore.hasIncomingCall) return;
|
|
|
|
try {
|
|
const { data } = await WhatsappCallsAPI.active();
|
|
if (!data?.call) return;
|
|
|
|
const activeCallData = data.call;
|
|
|
|
callsStore.setReconnecting(true);
|
|
callsStore.setActiveCall({
|
|
id: activeCallData.id,
|
|
callId: activeCallData.call_id,
|
|
direction: activeCallData.direction,
|
|
conversationId: activeCallData.conversation_id,
|
|
status: 'reconnecting',
|
|
serverRelay: true,
|
|
caller: activeCallData.caller,
|
|
});
|
|
|
|
// Set timer offset so the timer resumes from the correct elapsed time
|
|
if (activeCallData.elapsed_seconds) {
|
|
callsStore.setTimerOffset(activeCallData.elapsed_seconds);
|
|
}
|
|
|
|
// Tell the server to create a new Peer B and send us a fresh SDP offer.
|
|
// The server will broadcast whatsapp_call.agent_offer via ActionCable,
|
|
// which is handled by handleAgentOffer in actionCable.js.
|
|
await WhatsappCallsAPI.reconnect(activeCallData.id);
|
|
} catch {
|
|
// No active call or API/reconnect error — clear state and silent fail.
|
|
// clearActiveCall() also resets isReconnecting and callTimerOffset.
|
|
callsStore.clearActiveCall();
|
|
}
|
|
});
|
|
|
|
return { isReconnecting };
|
|
}
|