fix(web): harden timeline sse reconnect lifecycle
This commit is contained in:
@@ -5,13 +5,14 @@ import { prisma } from "@capakraken/db";
|
||||
import { SSE_EVENT_TYPES, SystemRole, type PermissionOverrides } from "@capakraken/shared";
|
||||
import { auth } from "~/server/auth.js";
|
||||
|
||||
// Start the reminder scheduler (idempotent — only starts once)
|
||||
startReminderScheduler();
|
||||
|
||||
export const dynamic = "force-dynamic";
|
||||
export const runtime = "nodejs";
|
||||
|
||||
export async function GET() {
|
||||
// Start lazily on the first real SSE request so builds/import-time evaluation
|
||||
// never attempt reminder processing against a live database.
|
||||
startReminderScheduler();
|
||||
|
||||
const session = await auth();
|
||||
|
||||
if (!session?.user) {
|
||||
|
||||
@@ -15,10 +15,27 @@ export function useTimelineSSE() {
|
||||
useEffect(() => {
|
||||
let es: EventSource | null = null;
|
||||
let reconnectAttempts = 0;
|
||||
let isDisposed = false;
|
||||
|
||||
function scheduleReconnect() {
|
||||
if (isDisposed || reconnectTimeout.current) return;
|
||||
reconnectAttempts++;
|
||||
const delay = Math.min(1000 * Math.pow(2, reconnectAttempts), 30000);
|
||||
reconnectTimeout.current = setTimeout(() => {
|
||||
reconnectTimeout.current = null;
|
||||
if (isDisposed) return;
|
||||
connect();
|
||||
}, delay);
|
||||
}
|
||||
|
||||
function connect() {
|
||||
if (isDisposed) return;
|
||||
es = new EventSource("/api/sse/timeline");
|
||||
|
||||
es.onopen = () => {
|
||||
reconnectAttempts = 0;
|
||||
};
|
||||
|
||||
es.onmessage = (event) => {
|
||||
try {
|
||||
const data = JSON.parse(event.data as string) as { type: string };
|
||||
@@ -35,6 +52,7 @@ export function useTimelineSSE() {
|
||||
void queryClient.invalidateQueries({ queryKey: [["timeline", "getMyEntriesView"]] });
|
||||
void queryClient.invalidateQueries({ queryKey: [["timeline", "getHolidayOverlays"]] });
|
||||
void queryClient.invalidateQueries({ queryKey: [["timeline", "getMyHolidayOverlays"]] });
|
||||
void queryClient.invalidateQueries({ queryKey: [["vacation", "list"]] });
|
||||
void queryClient.invalidateQueries({ queryKey: [["allocation", "list"]] });
|
||||
break;
|
||||
|
||||
@@ -82,18 +100,18 @@ export function useTimelineSSE() {
|
||||
|
||||
es.onerror = () => {
|
||||
es?.close();
|
||||
reconnectAttempts++;
|
||||
const delay = Math.min(1000 * Math.pow(2, reconnectAttempts), 30000);
|
||||
reconnectTimeout.current = setTimeout(connect, delay);
|
||||
scheduleReconnect();
|
||||
};
|
||||
}
|
||||
|
||||
connect();
|
||||
|
||||
return () => {
|
||||
isDisposed = true;
|
||||
es?.close();
|
||||
if (reconnectTimeout.current) {
|
||||
clearTimeout(reconnectTimeout.current);
|
||||
reconnectTimeout.current = null;
|
||||
}
|
||||
};
|
||||
}, [queryClient]);
|
||||
|
||||
Reference in New Issue
Block a user