| 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
|
| };
|
| };
|
|
|