import { useEffect, useRef, useState, useCallback } from 'react' import type { MeshHealth, Alert, EnvEvent } from '@/lib/api' interface WebSocketMessage { type: string data?: unknown event?: EnvEvent } interface UseWebSocketReturn { connected: boolean lastHealth: MeshHealth | null lastAlert: Alert | null lastMessage: WebSocketMessage | null } export function useWebSocket(): UseWebSocketReturn { const [connected, setConnected] = useState(false) const [lastHealth, setLastHealth] = useState(null) const [lastAlert, setLastAlert] = useState(null) const [lastMessage, setLastMessage] = useState(null) const wsRef = useRef(null) const reconnectTimeoutRef = useRef(null) const reconnectDelayRef = useRef(1000) const connect = useCallback(() => { if (wsRef.current?.readyState === WebSocket.OPEN) { return } const protocol = window.location.protocol === 'https:' ? 'wss:' : 'ws:' const wsUrl = `${protocol}//${window.location.host}/ws/live` try { const ws = new WebSocket(wsUrl) wsRef.current = ws ws.onopen = () => { setConnected(true) reconnectDelayRef.current = 1000 // Reset backoff on successful connection } ws.onmessage = (event) => { try { const message: WebSocketMessage = JSON.parse(event.data) // Store all messages for generic handling setLastMessage(message) switch (message.type) { case 'health_update': setLastHealth(message.data as MeshHealth) break case 'alert_fired': setLastAlert(message.data as Alert) break // env_update messages are handled via lastMessage } } catch (e) { console.error('Failed to parse WebSocket message:', e) } } ws.onclose = () => { setConnected(false) wsRef.current = null // Schedule reconnect with exponential backoff const delay = Math.min(reconnectDelayRef.current, 30000) reconnectTimeoutRef.current = window.setTimeout(() => { reconnectDelayRef.current = Math.min(delay * 2, 30000) connect() }, delay) } ws.onerror = () => { ws.close() } // Keepalive ping every 30 seconds const pingInterval = setInterval(() => { if (ws.readyState === WebSocket.OPEN) { ws.send('ping') } }, 30000) ws.addEventListener('close', () => { clearInterval(pingInterval) }) } catch (e) { console.error('Failed to create WebSocket:', e) } }, []) useEffect(() => { connect() return () => { if (reconnectTimeoutRef.current) { clearTimeout(reconnectTimeoutRef.current) } if (wsRef.current) { wsRef.current.close() } } }, [connect]) return { connected, lastHealth, lastAlert, lastMessage } }