({ children }: { children: ReactNode })
| 33 | const REFRESH_DEBOUNCE = 500; |
| 34 | |
| 35 | export const WebSocketProvider = ({ children }: { children: ReactNode }) => { |
| 36 | const router = useRouter(); |
| 37 | const { user } = useAppMode(); |
| 38 | const [isConnected, setIsConnected] = useState(false); |
| 39 | const wsRef = useRef<WebSocket | null>(null); |
| 40 | const connectionIdRef = useRef<string | null>(null); |
| 41 | const reconnectDelayRef = useRef(INITIAL_RECONNECT_DELAY); |
| 42 | const reconnectTimerRef = useRef<ReturnType<typeof setTimeout> | null>(null); |
| 43 | const hasPendingUpdates = useRef(false); |
| 44 | const refreshTimerRef = useRef<ReturnType<typeof setTimeout> | null>(null); |
| 45 | const mountedRef = useRef(true); |
| 46 | const listenersRef = useRef<Set<(event: WsEvent) => void>>(new Set()); |
| 47 | |
| 48 | const isEditorActive = useEditorActivityStore((s) => s.isActive); |
| 49 | |
| 50 | const subscribe = useCallback((handler: (event: WsEvent) => void) => { |
| 51 | listenersRef.current.add(handler); |
| 52 | return () => { listenersRef.current.delete(handler); }; |
| 53 | }, []); |
| 54 | |
| 55 | const debouncedRefresh = useCallback(() => { |
| 56 | if (refreshTimerRef.current) clearTimeout(refreshTimerRef.current); |
| 57 | refreshTimerRef.current = setTimeout(() => { |
| 58 | router.refresh(); |
| 59 | }, REFRESH_DEBOUNCE); |
| 60 | }, [router]); |
| 61 | |
| 62 | const handleMessage = useCallback( |
| 63 | (event: MessageEvent) => { |
| 64 | try { |
| 65 | const raw = JSON.parse(event.data) as Record<string, unknown>; |
| 66 | |
| 67 | if (raw.type === "connected") { |
| 68 | connectionIdRef.current = (raw.connectionId as string) ?? null; |
| 69 | return; |
| 70 | } |
| 71 | |
| 72 | const data = raw as unknown as WsEvent; |
| 73 | listenersRef.current.forEach((handler) => handler(data)); |
| 74 | |
| 75 | if (data.type === "notification") return; |
| 76 | |
| 77 | if (isEditorActive()) { |
| 78 | hasPendingUpdates.current = true; |
| 79 | return; |
| 80 | } |
| 81 | |
| 82 | debouncedRefresh(); |
| 83 | } catch {} |
| 84 | }, |
| 85 | [isEditorActive, debouncedRefresh], |
| 86 | ); |
| 87 | |
| 88 | const connect = useCallback(() => { |
| 89 | if (!mountedRef.current) return; |
| 90 | if (!user) return; |
| 91 | if (wsRef.current?.readyState === WebSocket.OPEN) return; |
| 92 |
nothing calls this directly
no test coverage detected