import { useEffect, useRef } from 'react'; import type { MapPoint } from '../components/map/types'; export interface DeviceStreamEvent { deviceId: string; lastSeenAt?: string; isActive?: boolean; disabledReason?: string | null; position?: MapPoint; job?: { id: string; ticketNumber: string; title: string }; } // Subscribes to live device updates (position pings, log points, and // admin enable/disable) for an org over the backend WebSocket, so the // devices page reflects device activity without a manual refresh. export function useDevicesStream(orgId: string | null, onEvent: (event: DeviceStreamEvent) => void) { const eventRef = useRef(onEvent); eventRef.current = onEvent; useEffect(() => { if (!orgId) { return; } let socket: WebSocket | null = null; let closed = false; let attempt = 0; let timer: ReturnType | null = null; const channel = `org:${orgId}:devices`; 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 })); }; socket.onmessage = (event) => { try { const msg = JSON.parse(event.data); if (msg.type === 'device') { eventRef.current(msg as DeviceStreamEvent); } } 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(); }; }, [orgId]); }