| | import { WebSocketEvent } from '@/types/agent';
|
| | import { useCallback, useEffect, useRef, useState } from 'react';
|
| |
|
| | interface UseWebSocketProps {
|
| | url: string;
|
| | onMessage: (event: WebSocketEvent) => void;
|
| | onError?: (error: Event) => void;
|
| | }
|
| |
|
| | export const useWebSocket = ({ url, onMessage, onError }: UseWebSocketProps) => {
|
| | const [isConnected, setIsConnected] = useState(false);
|
| | const [connectionState, setConnectionState] = useState<'connecting' | 'connected' | 'disconnected' | 'error'>('disconnected');
|
| | const wsRef = useRef<WebSocket | null>(null);
|
| | const reconnectTimeoutRef = useRef<NodeJS.Timeout>();
|
| | const reconnectAttemptsRef = useRef(0);
|
| | const maxReconnectAttempts = 3;
|
| | const baseReconnectDelay = 3000;
|
| | const maxReconnectDelay = 5000;
|
| | const lastErrorTimeRef = useRef(0);
|
| | const errorThrottleMs = 5000;
|
| | const isInitialConnectionRef = useRef(true);
|
| |
|
| | const getReconnectDelay = () => {
|
| |
|
| | const delay = Math.min(
|
| | baseReconnectDelay * Math.pow(2, reconnectAttemptsRef.current),
|
| | maxReconnectDelay
|
| | );
|
| | return delay + Math.random() * 1000;
|
| | };
|
| |
|
| | const connect = useCallback(() => {
|
| | if (wsRef.current?.readyState === WebSocket.OPEN || wsRef.current?.readyState === WebSocket.CONNECTING) {
|
| | return;
|
| | }
|
| |
|
| | try {
|
| | setConnectionState('connecting');
|
| | const ws = new WebSocket(url);
|
| |
|
| | ws.onopen = () => {
|
| | console.log('WebSocket connected');
|
| | setIsConnected(true);
|
| | setConnectionState('connected');
|
| | reconnectAttemptsRef.current = 0;
|
| | isInitialConnectionRef.current = false;
|
| | };
|
| |
|
| | ws.onmessage = (event) => {
|
| | try {
|
| | const data = JSON.parse(event.data) as WebSocketEvent;
|
| | onMessage(data);
|
| | } catch (error) {
|
| | console.error('Failed to parse WebSocket message:', error);
|
| | }
|
| | };
|
| |
|
| | ws.onerror = (error) => {
|
| | console.error('WebSocket error:', error);
|
| | setConnectionState('error');
|
| |
|
| |
|
| |
|
| | if (!isInitialConnectionRef.current) {
|
| |
|
| | const now = Date.now();
|
| | if (now - lastErrorTimeRef.current > errorThrottleMs) {
|
| | lastErrorTimeRef.current = now;
|
| | onError?.(error);
|
| | }
|
| | }
|
| | };
|
| |
|
| | ws.onclose = (event) => {
|
| | console.log('WebSocket disconnected', { code: event.code, reason: event.reason });
|
| | setIsConnected(false);
|
| | setConnectionState('disconnected');
|
| |
|
| |
|
| | if (event.code !== 1000 && reconnectAttemptsRef.current < maxReconnectAttempts) {
|
| | const delay = getReconnectDelay();
|
| | console.log(`Attempting to reconnect in ${Math.round(delay)}ms (attempt ${reconnectAttemptsRef.current + 1}/${maxReconnectAttempts})`);
|
| |
|
| | reconnectTimeoutRef.current = setTimeout(() => {
|
| | reconnectAttemptsRef.current++;
|
| | connect();
|
| | }, delay);
|
| | } else if (reconnectAttemptsRef.current >= maxReconnectAttempts) {
|
| | console.log('Max reconnection attempts reached');
|
| | setConnectionState('error');
|
| | } else if (event.code === 1000) {
|
| |
|
| | setConnectionState('disconnected');
|
| | console.log('WebSocket closed normally, not reconnecting');
|
| | }
|
| | };
|
| |
|
| | wsRef.current = ws;
|
| | } catch (error) {
|
| | console.error('Failed to create WebSocket connection:', error);
|
| | setConnectionState('error');
|
| | }
|
| | }, [url, onMessage, onError]);
|
| |
|
| | const disconnect = useCallback(() => {
|
| | if (reconnectTimeoutRef.current) {
|
| | clearTimeout(reconnectTimeoutRef.current);
|
| | }
|
| | if (wsRef.current) {
|
| | wsRef.current.close(1000, 'Manual disconnect');
|
| | wsRef.current = null;
|
| | }
|
| | setIsConnected(false);
|
| | setConnectionState('disconnected');
|
| | reconnectAttemptsRef.current = 0;
|
| | }, []);
|
| |
|
| | const manualReconnect = useCallback(() => {
|
| | console.log('Manual reconnect requested');
|
| | disconnect();
|
| | reconnectAttemptsRef.current = 0;
|
| | isInitialConnectionRef.current = false;
|
| | setTimeout(() => connect(), 1000);
|
| | }, [disconnect, connect]);
|
| |
|
| | const sendMessage = (message: unknown) => {
|
| | if (wsRef.current?.readyState === WebSocket.OPEN) {
|
| | try {
|
| | wsRef.current.send(JSON.stringify(message));
|
| | } catch (error) {
|
| | console.error('Failed to send WebSocket message:', error);
|
| | }
|
| | } else {
|
| | console.warn('WebSocket is not connected');
|
| | }
|
| | };
|
| |
|
| | useEffect(() => {
|
| | connect();
|
| |
|
| | return () => {
|
| | disconnect();
|
| | };
|
| | }, [url]);
|
| |
|
| | return {
|
| | isConnected,
|
| | connectionState,
|
| | sendMessage,
|
| | reconnect: connect,
|
| | disconnect,
|
| | manualReconnect
|
| | };
|
| | };
|
| |
|