/** * WebSocket hook for real-time updates from the backend. */ import { useEffect, useRef, useCallback, useState } from 'react'; export interface WSMessage { type: 'agent_status' | 'log' | 'project_update' | 'pong'; data: Record; } type WSCallback = (msg: WSMessage) => void; export function useWebSocket(onMessage: WSCallback) { const wsRef = useRef(null); const [connected, setConnected] = useState(false); const reconnectTimer = useRef | undefined>(undefined); const connect = useCallback(() => { const protocol = window.location.protocol === 'https:' ? 'wss:' : 'ws:'; const url = `${protocol}//${window.location.host}/ws/live`; const ws = new WebSocket(url); wsRef.current = ws; ws.onopen = () => { setConnected(true); // Start ping interval const pingInterval = setInterval(() => { if (ws.readyState === WebSocket.OPEN) ws.send('ping'); }, 30000); ws.addEventListener('close', () => clearInterval(pingInterval)); }; ws.onmessage = (evt) => { try { const msg = JSON.parse(evt.data) as WSMessage; onMessage(msg); } catch { // ignore parse errors } }; ws.onclose = () => { setConnected(false); // Auto-reconnect after 3s reconnectTimer.current = setTimeout(connect, 3000); }; ws.onerror = () => { ws.close(); }; }, [onMessage]); useEffect(() => { connect(); return () => { clearTimeout(reconnectTimer.current); wsRef.current?.close(); }; }, [connect]); return { connected }; }