"use client"; import { useEffect, useRef, useState } from "react"; export type SseEvent = { type: string; data: T[]; received_at: number }; /** Subscribe to the market-data SSE relay. Handlers are kept in a ref so re-renders don't reconnect. */ export function useMarketStream(symbols: string[], channels: string[], handlers: Partial>) => void>>) { const [connected, setConnected] = useState(false); const ref = useRef(handlers); useEffect(() => { ref.current = handlers; }); const key = `${symbols.join(",")}|${channels.join(",")}`; useEffect(() => { if (!symbols.length) return; const url = `/api/market/stream?symbols=${encodeURIComponent(symbols.join(","))}&channels=${encodeURIComponent(channels.join(","))}`; const es = new EventSource(url, { withCredentials: true }); es.onopen = () => setConnected(true); es.onerror = () => setConnected(false); const listeners: [string, (e: MessageEvent) => void][] = []; for (const ch of channels) { const fn = (e: MessageEvent) => { try { const parsed = JSON.parse(e.data) as SseEvent>; ref.current[ch]?.(parsed); } catch { /* ignore malformed */ } }; es.addEventListener(ch, fn); listeners.push([ch, fn]); } return () => { for (const [ch, fn] of listeners) es.removeEventListener(ch, fn); es.close(); setConnected(false); }; // eslint-disable-next-line react-hooks/exhaustive-deps }, [key]); return connected; }