import { router } from "expo-router"; import { useEffect, useRef } from "react"; import { fetchAPI } from "@/lib/fetch"; import type { CallRecord, ChatActiveRide } from "@/types/type"; // Listens for an incoming WebRTC call (a 'ringing' call row this user did not // place) and routes the user to the call screen — regardless of which tab is // open. Rendered once at the root layout level; emits no UI. // // It only polls while an active ride exists (the only window in which a call // can happen). To avoid re-navigating on every poll, it remembers the call id // it already handed off to the call screen and resets once that call goes // terminal. const ACTIVE_POLL_MS = 5000; const CALL_POLL_MS = 3000; const CallWatcher = () => { // The ride we're watching for an incoming call on. const rideIdRef = useRef(null); // The call id we've already navigated to, so we don't re-push the screen. const handledCallIdRef = useRef(null); useEffect(() => { let cancelled = false; // Refresh which ride (if any) is active for this user, then poll its call // row. Both run on intervals; the call poll no-ops until a rideId is known. const activeTimer = setInterval(async () => { try { const res = await fetchAPI("/(api)/chat/active"); const active = (res.data ?? null) as ChatActiveRide | null; if (cancelled) return; rideIdRef.current = active?.ride_id ?? null; } catch (err) { console.log("[CALL_WATCHER_ACTIVE]: ", err); } }, ACTIVE_POLL_MS); const callTimer = setInterval(async () => { const rideId = rideIdRef.current; if (rideId === null) return; try { const res = await fetchAPI(`/(api)/ride/${rideId}/call`); const call = (res.data ?? null) as CallRecord | null; if (cancelled || !call) return; // A terminal call clears the handled marker so the next incoming call // can navigate again. if ( call.status === "ended" || call.status === "declined" || call.status === "missed" ) { if (handledCallIdRef.current === call.id) { handledCallIdRef.current = null; } return; } // An incoming ringing call we didn't place: hand off to the call // screen, once per call id. if (call.status === "ringing" && !call.is_caller) { if (handledCallIdRef.current === call.id) return; handledCallIdRef.current = call.id; router.push({ pathname: "/(root)/call", params: { rideId: String(rideId), mode: "incoming" }, }); } } catch (err) { console.log("[CALL_WATCHER_CALL]: ", err); } }, CALL_POLL_MS); // Kick the active poll immediately so an incoming call on a freshly // matched ride is noticed without waiting for the first interval. void (async () => { try { const res = await fetchAPI("/(api)/chat/active"); if (cancelled) return; rideIdRef.current = ((res.data ?? null) as ChatActiveRide | null)?.ride_id ?? null; } catch { // ignore — the interval will retry } })(); return () => { cancelled = true; clearInterval(activeTimer); clearInterval(callTimer); }; }, []); return null; }; export default CallWatcher;