import { onMounted, onUnmounted, ref } from "vue"; import { getToken } from "../api"; export interface SacDashboardStreamMessage { type?: string; last_event_id?: number | null; } /** SSE /api/v1/stream/events — вызывает onNewEvent при новом last_event_id (после первого тика). */ export function useSacLiveEventStream(onNewEvent: () => void) { const live = ref(false); let eventSource: EventSource | null = null; let lastKnownEventDbId: number | null = null; function handleStreamMessage(msg: SacDashboardStreamMessage) { if (msg.type !== "dashboard") return; const incoming = msg.last_event_id; if (incoming === undefined) return; if (incoming === null) { if (lastKnownEventDbId !== null) { onNewEvent(); } lastKnownEventDbId = null; return; } if (lastKnownEventDbId === null) { lastKnownEventDbId = incoming; return; } if (incoming !== lastKnownEventDbId) { lastKnownEventDbId = incoming; onNewEvent(); } } function connect() { const token = getToken(); if (!token) return; eventSource?.close(); const url = `/api/v1/stream/events`; eventSource = new EventSource(url); eventSource.onopen = () => { live.value = true; }; eventSource.onmessage = (ev) => { try { handleStreamMessage(JSON.parse(ev.data) as SacDashboardStreamMessage); } catch { /* ignore malformed SSE */ } }; eventSource.onerror = () => { live.value = false; }; } onMounted(() => connect()); onUnmounted(() => { eventSource?.close(); eventSource = null; live.value = false; }); return { live }; }