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, currentSessionId?: string) => void; const eventHandlers: Record = { "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(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]); }