Files
flxn-app/src/hooks/use-server-events.ts
2025-09-29 21:28:22 -05:00

117 lines
3.3 KiB
TypeScript

import { useEffect, useRef } from "react";
import { useQueryClient } from "@tanstack/react-query";
import { Logger } from "@/lib/logger";
import { useAuth } from "@/contexts/auth-context";
import { tournamentQueries } from "@/features/tournaments/queries";
import { reactionKeys, reactionQueries } from "@/features/reactions/queries";
const logger = new Logger('ServerEvents');
type SSEEvent = {
type: string;
[key: string]: any;
};
type EventHandler = (event: SSEEvent, queryClient: ReturnType<typeof useQueryClient>, currentSessionId?: string) => void;
const eventHandlers: Record<string, EventHandler> = {
"connected": () => {
logger.info("New Connection");
},
"ping": () => {},
"heartbeat": () => {},
"match": (event, queryClient) => {
queryClient.invalidateQueries(tournamentQueries.details(event.tournamentId))
queryClient.invalidateQueries(tournamentQueries.current())
},
"reaction": (event, queryClient) => {
queryClient.invalidateQueries(reactionQueries.match(event.matchId));
queryClient.setQueryData(reactionKeys.match(event.matchId), () => event.reactions);
}
};
export function useServerEvents() {
const queryClient = useQueryClient();
const { user } = useAuth();
const retryCountRef = useRef(0);
const shouldConnectRef = useRef(true);
const timeoutRef = useRef<NodeJS.Timeout | null>(null);
useEffect(() => {
if (typeof window === 'undefined') return;
if (!user?.id) return;
shouldConnectRef.current = true;
retryCountRef.current = 0;
const connectEventSource = () => {
if (!shouldConnectRef.current) return;
const eventSource = new EventSource(`/api/events/$`);
eventSource.onopen = () => {
retryCountRef.current = 0;
};
eventSource.onmessage = (event) => {
try {
const data: SSEEvent = JSON.parse(event.data);
logger.info("Event received", data);
const handler = eventHandlers[data.type];
if (handler) {
handler(data, queryClient, user?.id);
} else {
logger.warn(`Unhandled SSE event type: ${data.type}`);
}
} catch (error) {
logger.error("Error parsing SSE message", error);
}
};
eventSource.onerror = (error) => {
logger.error("SSE connection error", error);
eventSource.close();
if (shouldConnectRef.current && retryCountRef.current < 10) {
retryCountRef.current += 1;
const delay = Math.min(
1000 * Math.pow(1.5, retryCountRef.current - 1),
15000
);
logger.info(
`SSE reconnection attempt ${retryCountRef.current}/10 in ${delay}ms`
);
timeoutRef.current = setTimeout(() => {
if (shouldConnectRef.current) {
connectEventSource();
}
}, delay);
} else if (retryCountRef.current >= 10) {
logger.error("SSE max reconnection attempts reached");
}
};
return eventSource;
};
const eventSource = connectEventSource();
return () => {
logger.info("Closing SSE connection");
shouldConnectRef.current = false;
if (timeoutRef.current) {
clearTimeout(timeoutRef.current);
timeoutRef.current = null;
}
if (eventSource) {
eventSource.close();
}
};
}, [user?.id]);
}