import { useState, useEffect, useRef, useCallback } from 'react' import type { WsInbound, WsOutbound, ConnectionStatus } from '../types/protocol' interface UseWebSocketOptions { url: string onMessage?: (message: WsOutbound) => void onConnect?: () => void onDisconnect?: () => void onError?: (error: Event) => void reconnectInterval?: number maxReconnectAttempts?: number } interface UseWebSocketReturn { status: ConnectionStatus sendMessage: (message: WsInbound) => boolean connect: () => void disconnect: () => void } export function useWebSocket({ url, onMessage, onConnect, onDisconnect, onError, reconnectInterval = 3000, maxReconnectAttempts = 5, }: UseWebSocketOptions): UseWebSocketReturn { const [status, setStatus] = useState('disconnected') const wsRef = useRef(null) const reconnectAttemptsRef = useRef(0) const reconnectTimerRef = useRef | null>(null) const isManualDisconnectRef = useRef(false) const connect = useCallback(() => { if (wsRef.current?.readyState === WebSocket.OPEN) { return } isManualDisconnectRef.current = false setStatus('connecting') try { const ws = new WebSocket(url) wsRef.current = ws ws.onopen = () => { setStatus('connected') reconnectAttemptsRef.current = 0 onConnect?.() } ws.onmessage = (event) => { try { const message = JSON.parse(event.data) as WsOutbound onMessage?.(message) } catch (error) { console.error('Failed to parse message:', error) } } ws.onerror = (error) => { setStatus('error') onError?.(error) } ws.onclose = () => { setStatus('disconnected') onDisconnect?.() // Auto reconnect if not manually disconnected if (!isManualDisconnectRef.current && reconnectAttemptsRef.current < maxReconnectAttempts) { reconnectAttemptsRef.current += 1 reconnectTimerRef.current = setTimeout(() => { connect() }, reconnectInterval) } } } catch (error) { setStatus('error') console.error('WebSocket connection error:', error) } }, [url, onMessage, onConnect, onDisconnect, onError, reconnectInterval, maxReconnectAttempts]) const disconnect = useCallback(() => { isManualDisconnectRef.current = true if (reconnectTimerRef.current) { clearTimeout(reconnectTimerRef.current) reconnectTimerRef.current = null } if (wsRef.current) { wsRef.current.close() wsRef.current = null } setStatus('disconnected') }, []) const sendMessage = useCallback((message: WsInbound): boolean => { if (wsRef.current?.readyState === WebSocket.OPEN) { wsRef.current.send(JSON.stringify(message)) return true } console.warn('WebSocket is not connected') return false }, []) // Auto connect on mount useEffect(() => { connect() // Cleanup on unmount return () => { disconnect() } }, [connect, disconnect]) return { status, sendMessage, connect, disconnect, } }