import { useEffect } from 'react'; import { sessionEvents } from './sessionEvents'; const RECONNECT_INITIAL_MS = 1000; const RECONNECT_MAX_MS = 30000; export function useUserEvents(): void { useEffect(() => { let ws: WebSocket | null = null; let reconnectTimer: ReturnType | null = null; let reconnectDelay = RECONNECT_INITIAL_MS; let unmounted = false; const connect = () => { if (unmounted) return; const url = new URL('/api/ws/user', window.location.href); url.protocol = url.protocol === 'https:' ? 'wss:' : 'ws:'; ws = new WebSocket(url.toString()); ws.onopen = () => { reconnectDelay = RECONNECT_INITIAL_MS; }; ws.onmessage = (ev) => { try { const parsed: unknown = JSON.parse(ev.data); if (parsed && typeof (parsed as { type?: unknown }).type === 'string') { sessionEvents.emit(parsed as import('./sessionEvents').SessionEvent); } } catch (err) { console.warn('useUserEvents: failed to parse frame', err); } }; ws.onclose = () => { if (unmounted) return; const delay = reconnectDelay; reconnectDelay = Math.min(reconnectDelay * 2, RECONNECT_MAX_MS); reconnectTimer = setTimeout(connect, delay); }; ws.onerror = () => { // close handler will trigger reconnect; best-effort, ignore failure // because the socket may already be closing try { ws?.close(); } catch {} }; }; connect(); return () => { unmounted = true; if (reconnectTimer) clearTimeout(reconnectTimer); // best-effort cleanup; ignore failure because the socket may already be closed if (ws) try { ws.close(); } catch {} }; }, []); }