Amir Mahla
Init CUA2
af1ae43
raw
history blame
5.4 kB
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; // Only try three times, then stop
const baseReconnectDelay = 3000; // Start with 3 seconds
const maxReconnectDelay = 5000; // Max 5 seconds
const lastErrorTimeRef = useRef(0);
const errorThrottleMs = 5000; // Only show error toast once every 5 seconds
const isInitialConnectionRef = useRef(true); // Track if this is the first connection attempt
const getReconnectDelay = () => {
// Exponential backoff with jitter
const delay = Math.min(
baseReconnectDelay * Math.pow(2, reconnectAttemptsRef.current),
maxReconnectDelay
);
return delay + Math.random() * 1000; // Add jitter
};
const connect = useCallback(() => {
if (wsRef.current?.readyState === WebSocket.OPEN || wsRef.current?.readyState === WebSocket.CONNECTING) {
return; // Already connected or connecting
}
try {
setConnectionState('connecting');
const ws = new WebSocket(url);
ws.onopen = () => {
console.log('WebSocket connected');
setIsConnected(true);
setConnectionState('connected');
reconnectAttemptsRef.current = 0; // Reset attempts on successful connection
isInitialConnectionRef.current = false; // Mark that we've had a successful connection
};
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');
// Don't show error toasts on initial connection failure
// Only show toasts after we've had a successful connection before
if (!isInitialConnectionRef.current) {
// Throttle error notifications
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');
// Only attempt to reconnect if it wasn't a manual close (code 1000) and we haven't exceeded max attempts
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) {
// Normal closure - don't reconnect
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; // Allow error toasts on manual reconnect
setTimeout(() => connect(), 1000); // Small delay before reconnecting
}, [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]); // Only depend on url, not the functions
return {
isConnected,
connectionState,
sendMessage,
reconnect: connect,
disconnect,
manualReconnect
};
};