import { useEffect, useRef, useState } from "react"; interface UseWebSocketOptions { onMessage?: (data: unknown) => void; onError?: (error: Event) => void; onClose?: () => void; sinceEventId?: number; } export const useWebSocket = (traceId: string | null, options: UseWebSocketOptions = {}) => { const wsRef = useRef(null); const [connected, setConnected] = useState(false); const { onMessage, onError, onClose, sinceEventId = 0 } = options; useEffect(() => { if (!traceId) return; const httpBase = (typeof window !== "undefined" ? (window as unknown as { CONFIG?: { API_BASE_URL?: string } }).CONFIG?.API_BASE_URL : undefined) || (typeof import.meta !== "undefined" && import.meta.env && import.meta.env.VITE_API_BASE_URL ? import.meta.env.VITE_API_BASE_URL : `${window.location.protocol}//${window.location.hostname}:8000`); const wsBase = httpBase.replace(/^http(s?):\/\//, "ws$1://").replace(/\/+$/, ""); const url = `${wsBase}/api/traces/${traceId}/watch?since_event_id=${sinceEventId}`; const ws = new WebSocket(url); let pingTimer: number | null = null; ws.onopen = () => { setConnected(true); pingTimer = window.setInterval(() => { if (ws.readyState === WebSocket.OPEN) { ws.send("ping"); } }, 15000); }; ws.onmessage = (event) => { try { const data = JSON.parse(event.data); onMessage?.(data); } catch { onMessage?.(event.data); } }; ws.onerror = (error) => { onError?.(error); }; ws.onclose = () => { setConnected(false); if (pingTimer) { window.clearInterval(pingTimer); pingTimer = null; } onClose?.(); }; wsRef.current = ws; return () => { if (pingTimer) { window.clearInterval(pingTimer); pingTimer = null; } ws.close(); }; }, [traceId, onMessage, onError, onClose, sinceEventId]); return { connected, ws: wsRef.current }; };