'use client'; import { useEffect, useState, useRef } from 'react'; import { useRouter } from 'next/navigation'; export type LiveEvent = { type: string; payload: Record; userId?: string; user?: { id: string; name: string | null; email: string }; timestamp: string; }; interface UseLiveOptions { sessionId: string; apiPath: string; // e.g., 'sessions', 'motivators', 'year-review' currentUserId?: string; enabled?: boolean; onEvent?: (event: LiveEvent) => void; } interface UseLiveReturn { isConnected: boolean; lastEvent: LiveEvent | null; error: string | null; } export function useLive({ sessionId, apiPath, currentUserId, enabled = true, onEvent, }: UseLiveOptions): UseLiveReturn { const [isConnected, setIsConnected] = useState(false); const [lastEvent, setLastEvent] = useState(null); const [error, setError] = useState(null); const router = useRouter(); const eventSourceRef = useRef(null); const reconnectTimeoutRef = useRef(null); const reconnectAttemptsRef = useRef(0); const onEventRef = useRef(onEvent); const currentUserIdRef = useRef(currentUserId); // Keep refs updated useEffect(() => { onEventRef.current = onEvent; }, [onEvent]); useEffect(() => { currentUserIdRef.current = currentUserId; }, [currentUserId]); useEffect(() => { if (!enabled || typeof window === 'undefined') return; function connect() { // Close existing connection if (eventSourceRef.current) { eventSourceRef.current.close(); } try { const eventSource = new EventSource(`/api/${apiPath}/${sessionId}/subscribe`); eventSourceRef.current = eventSource; eventSource.onopen = () => { setIsConnected(true); setError(null); reconnectAttemptsRef.current = 0; }; eventSource.onmessage = (event) => { try { const data = JSON.parse(event.data) as LiveEvent; // Handle connection event if (data.type === 'connected') { return; } // Client-side filter: ignore events created by current user // This prevents duplicates when revalidatePath already refreshed the data if (currentUserIdRef.current && data.userId === currentUserIdRef.current) { return; } setLastEvent(data); onEventRef.current?.(data); // Refresh the page data when we receive an event from another user router.refresh(); } catch (e) { console.error('Failed to parse SSE event:', e); } }; eventSource.onerror = () => { setIsConnected(false); eventSource.close(); // Exponential backoff reconnect const delay = Math.min(1000 * Math.pow(2, reconnectAttemptsRef.current), 30000); reconnectAttemptsRef.current++; if (reconnectAttemptsRef.current <= 5) { reconnectTimeoutRef.current = setTimeout(connect, delay); } else { setError('Connexion perdue. Rechargez la page.'); } }; } catch (e) { setError('Impossible de se connecter au mode live'); console.error('Failed to create EventSource:', e); } } connect(); return () => { if (eventSourceRef.current) { eventSourceRef.current.close(); eventSourceRef.current = null; } if (reconnectTimeoutRef.current) { clearTimeout(reconnectTimeoutRef.current); reconnectTimeoutRef.current = null; } }; }, [sessionId, apiPath, enabled, router]); return { isConnected, lastEvent, error }; }