import { useEffect, useRef } from 'react'; import type { MapPoint } from '../components/map/types'; // Subscribes to live points for a job over the backend WebSocket. // Reconnects with capped exponential backoff; resubscribes on reconnect. export function useJobStream(jobId: string | null, onPoints: (points: MapPoint[]) => void) { const handlerRef = useRef(onPoints); handlerRef.current = onPoints; useEffect(() => { if (!jobId) { return; } let socket: WebSocket | null = null; let closed = false; let attempt = 0; let timer: ReturnType | null = null; const connect = () => { const proto = window.location.protocol === 'https:' ? 'wss' : 'ws'; socket = new WebSocket(`${proto}://${window.location.host}/api/ws`); socket.onopen = () => { attempt = 0; socket?.send(JSON.stringify({ type: 'subscribe', channel: `job:${jobId}` })); }; socket.onmessage = (event) => { try { const msg = JSON.parse(event.data); if (msg.type === 'points' && msg.jobId === jobId) { handlerRef.current(msg.points); } } catch { // ignore malformed frames } }; socket.onclose = () => { if (closed) { return; } attempt += 1; const delay = Math.min(1000 * 2 ** attempt, 15000); timer = setTimeout(connect, delay); }; }; connect(); return () => { closed = true; if (timer) { clearTimeout(timer); } socket?.close(); }; }, [jobId]); }