import { emit } from "./bus.js"; /** * SSE client con reconnect exponencial y try-catch en parseo. * Si el server reinicia o un evento viene malformado, no rompe la app. */ let _es = null; let _retryDelay = 1000; let _retryTimer = null; const MAX_RETRY = 30_000; const EVENTS = [ ["conversation.upsert", "conversation:upsert"], ["run.created", "run:created"], ["takeover.created", "takeover:created"], ["order.created", "order:created"], ]; function safeParse(rawData, evName) { try { return JSON.parse(rawData); } catch (err) { console.error(`[sse] bad payload for ${evName}:`, err?.message || err); return null; } } function attach(es) { es.addEventListener("hello", () => { _retryDelay = 1000; // reset on success emit("sse:status", { ok: true }); }); for (const [serverName, busName] of EVENTS) { es.addEventListener(serverName, (e) => { const data = safeParse(e.data, serverName); if (data !== null) emit(busName, data); }); } es.onerror = () => { emit("sse:status", { ok: false }); try { es.close(); } catch (_) {} if (_es === es) _es = null; scheduleReconnect(); }; } function scheduleReconnect() { if (_retryTimer) return; const delay = _retryDelay; _retryTimer = setTimeout(() => { _retryTimer = null; connectSSE(); }, delay); _retryDelay = Math.min(_retryDelay * 2, MAX_RETRY); } export function connectSSE() { if (_retryTimer) { clearTimeout(_retryTimer); _retryTimer = null; } if (_es) { try { _es.close(); } catch (_) {} } _es = new EventSource("/stream"); attach(_es); return _es; }