Files
security-alert-center/frontend/src/composables/useSacLiveEventStream.ts
T

74 lines
1.7 KiB
TypeScript

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 };
}