53 lines
1.4 KiB
TypeScript
53 lines
1.4 KiB
TypeScript
"use client";
|
|
|
|
import { useState, useEffect, useRef } from "react";
|
|
import type { ActivityEvent } from "./types";
|
|
|
|
// Use same origin — Next.js rewrites /api/* to backend
|
|
|
|
export function useSSE(path: string, maxEvents: number = 30) {
|
|
const [events, setEvents] = useState<ActivityEvent[]>([]);
|
|
const retryTimeout = useRef<ReturnType<typeof setTimeout> | null>(null);
|
|
|
|
useEffect(() => {
|
|
let es: EventSource | null = null;
|
|
|
|
function connect() {
|
|
es = new EventSource(path);
|
|
|
|
// Backend sends "init" with the full batch and "update" with new events
|
|
es.addEventListener("init", (e: MessageEvent) => {
|
|
try {
|
|
const batch: ActivityEvent[] = JSON.parse(e.data);
|
|
setEvents(batch.slice(0, maxEvents));
|
|
} catch {
|
|
// ignore malformed events
|
|
}
|
|
});
|
|
|
|
es.addEventListener("update", (e: MessageEvent) => {
|
|
try {
|
|
const batch: ActivityEvent[] = JSON.parse(e.data);
|
|
setEvents((prev) => [...batch, ...prev].slice(0, maxEvents));
|
|
} catch {
|
|
// ignore malformed events
|
|
}
|
|
});
|
|
|
|
es.onerror = () => {
|
|
es?.close();
|
|
retryTimeout.current = setTimeout(connect, 5000);
|
|
};
|
|
}
|
|
|
|
connect();
|
|
|
|
return () => {
|
|
es?.close();
|
|
if (retryTimeout.current) clearTimeout(retryTimeout.current);
|
|
};
|
|
}, [path, maxEvents]);
|
|
|
|
return events;
|
|
}
|