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 { SSE_EVENT_TYPES, SystemRole, type PermissionOverrides } from "@capakraken/shared";
|
||||||
import { auth } from "~/server/auth.js";
|
import { auth } from "~/server/auth.js";
|
||||||
|
|
||||||
// Start the reminder scheduler (idempotent — only starts once)
|
|
||||||
startReminderScheduler();
|
|
||||||
|
|
||||||
export const dynamic = "force-dynamic";
|
export const dynamic = "force-dynamic";
|
||||||
export const runtime = "nodejs";
|
export const runtime = "nodejs";
|
||||||
|
|
||||||
export async function GET() {
|
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();
|
const session = await auth();
|
||||||
|
|
||||||
if (!session?.user) {
|
if (!session?.user) {
|
||||||
|
|||||||
@@ -15,10 +15,27 @@ export function useTimelineSSE() {
|
|||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
let es: EventSource | null = null;
|
let es: EventSource | null = null;
|
||||||
let reconnectAttempts = 0;
|
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() {
|
function connect() {
|
||||||
|
if (isDisposed) return;
|
||||||
es = new EventSource("/api/sse/timeline");
|
es = new EventSource("/api/sse/timeline");
|
||||||
|
|
||||||
|
es.onopen = () => {
|
||||||
|
reconnectAttempts = 0;
|
||||||
|
};
|
||||||
|
|
||||||
es.onmessage = (event) => {
|
es.onmessage = (event) => {
|
||||||
try {
|
try {
|
||||||
const data = JSON.parse(event.data as string) as { type: string };
|
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", "getMyEntriesView"]] });
|
||||||
void queryClient.invalidateQueries({ queryKey: [["timeline", "getHolidayOverlays"]] });
|
void queryClient.invalidateQueries({ queryKey: [["timeline", "getHolidayOverlays"]] });
|
||||||
void queryClient.invalidateQueries({ queryKey: [["timeline", "getMyHolidayOverlays"]] });
|
void queryClient.invalidateQueries({ queryKey: [["timeline", "getMyHolidayOverlays"]] });
|
||||||
|
void queryClient.invalidateQueries({ queryKey: [["vacation", "list"]] });
|
||||||
void queryClient.invalidateQueries({ queryKey: [["allocation", "list"]] });
|
void queryClient.invalidateQueries({ queryKey: [["allocation", "list"]] });
|
||||||
break;
|
break;
|
||||||
|
|
||||||
@@ -82,18 +100,18 @@ export function useTimelineSSE() {
|
|||||||
|
|
||||||
es.onerror = () => {
|
es.onerror = () => {
|
||||||
es?.close();
|
es?.close();
|
||||||
reconnectAttempts++;
|
scheduleReconnect();
|
||||||
const delay = Math.min(1000 * Math.pow(2, reconnectAttempts), 30000);
|
|
||||||
reconnectTimeout.current = setTimeout(connect, delay);
|
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
connect();
|
connect();
|
||||||
|
|
||||||
return () => {
|
return () => {
|
||||||
|
isDisposed = true;
|
||||||
es?.close();
|
es?.close();
|
||||||
if (reconnectTimeout.current) {
|
if (reconnectTimeout.current) {
|
||||||
clearTimeout(reconnectTimeout.current);
|
clearTimeout(reconnectTimeout.current);
|
||||||
|
reconnectTimeout.current = null;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
}, [queryClient]);
|
}, [queryClient]);
|
||||||
|
|||||||
Reference in New Issue
Block a user