Skip to content

Commit c5ea629

Browse files
committed
Fix Bitfinex OrderBook WebSocket reconnection with resilient network handling
1 parent 724c4bf commit c5ea629

1 file changed

Lines changed: 116 additions & 19 deletions

File tree

src/hooks/useOrderBookWebSocket.ts

Lines changed: 116 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -254,12 +254,41 @@ export function useOrderBookWebSocket(
254254
};
255255

256256
// Main effect for initializing WebSocket connection to real exchange APIs
257+
// Define reconnect refs at the top level of the hook (outside useEffect)
258+
const reconnectAttemptsRef = useRef<number>(0);
259+
const reconnectTimeoutRef = useRef<NodeJS.Timeout | null>(null);
260+
257261
useEffect(() => {
258262
// Only run on the client side
259263
if (typeof window === 'undefined') {
260264
return;
261265
}
262266

267+
// Add network connectivity change monitoring
268+
const handleOnline = () => {
269+
console.log('[OrderBook] Network connection restored, attempting to reconnect WebSocket');
270+
// If we were disconnected due to network issues, try to reconnect
271+
if (connectionStatus === 'disconnected' || connectionStatus === 'error' ||
272+
connectionStatus === 'fallback_mock' || connectionStatus === 'reconnecting') {
273+
// Reset reconnection attempts counter to give a fresh start
274+
reconnectAttemptsRef.current = 0;
275+
// Reconnect immediately
276+
if (connectWebSocketRef.current) {
277+
connectWebSocketRef.current();
278+
}
279+
}
280+
};
281+
282+
const handleOffline = () => {
283+
console.log('[OrderBook] Network connection lost, WebSocket will disconnect');
284+
// When network is offline, update status but keep existing data
285+
setConnectionStatus('disconnected');
286+
};
287+
288+
// Add event listeners for online/offline events
289+
window.addEventListener('online', handleOnline);
290+
window.addEventListener('offline', handleOffline);
291+
263292
console.log(`[OrderBook] Initializing ${exchange} order book WebSocket connection`);
264293

265294
try {
@@ -277,6 +306,15 @@ export function useOrderBookWebSocket(
277306
console.log(`[OrderBook] Connected to ${exchange} WebSocket`);
278307
setConnectionStatus('connected');
279308

309+
// Reset reconnection attempts counter on successful connection
310+
reconnectAttemptsRef.current = 0;
311+
312+
// Clear any pending reconnection attempts
313+
if (reconnectTimeoutRef.current !== null) {
314+
clearTimeout(reconnectTimeoutRef.current);
315+
reconnectTimeoutRef.current = null;
316+
}
317+
280318
const formattedSymbol = exchange === 'binance' ? 'btcusdt' :
281319
exchange === 'coinbase' ? 'BTC-USD' : 'tBTCUSD';
282320

@@ -623,29 +661,75 @@ export function useOrderBookWebSocket(
623661
}
624662
};
625663

626-
// Handle errors - simply log and set status, we'll fall back to mock data
627-
socketRef.current.onerror = () => {
628-
console.log(`[OrderBook] WebSocket connection issue detected - will use fallback data`);
629-
setConnectionStatus('fallback_mock');
664+
// Handle WebSocket errors with proper reconnection strategy
665+
const attemptReconnect = () => {
666+
// Implement exponential backoff for reconnection attempts
667+
const delay = Math.min(
668+
options.initialReconnectDelayMs * Math.pow(2, reconnectAttemptsRef.current),
669+
options.maxReconnectDelayMs
670+
);
630671

631-
// Ensure we have fallback data
632-
if (!internalOrderBookRef.current) {
633-
const mockData = getMockOrderBook(exchange);
634-
internalOrderBookRef.current = mockData;
635-
setOrderBook(mockData);
636-
setLastUpdated(new Date());
672+
console.log(`[OrderBook] Scheduling reconnection attempt ${reconnectAttemptsRef.current + 1} in ${delay}ms`);
673+
setConnectionStatus('reconnecting');
674+
675+
// Clear any existing reconnection timeout
676+
if (reconnectTimeoutRef.current) {
677+
clearTimeout(reconnectTimeoutRef.current);
637678
}
679+
680+
// Schedule reconnection attempt
681+
reconnectTimeoutRef.current = setTimeout(() => {
682+
reconnectAttemptsRef.current++;
683+
684+
// Check if we've exceeded max reconnection attempts
685+
if (reconnectAttemptsRef.current > options.maxReconnectAttempts) {
686+
console.log(`[OrderBook] Max reconnection attempts (${options.maxReconnectAttempts}) exceeded, using fallback data`);
687+
setConnectionStatus('fallback_mock');
688+
689+
// Ensure we have fallback data
690+
const mockData = getMockOrderBook(exchange);
691+
internalOrderBookRef.current = mockData;
692+
setOrderBook(mockData);
693+
setLastUpdated(new Date());
694+
return;
695+
}
696+
697+
// Attempt to reconnect
698+
console.log(`[OrderBook] Attempting reconnection #${reconnectAttemptsRef.current}`);
699+
if (connectWebSocketRef.current) {
700+
connectWebSocketRef.current();
701+
}
702+
}, delay);
638703
};
639704

640-
socketRef.current.onclose = () => {
641-
console.log(`[OrderBook] WebSocket closed - using fallback data`);
642-
setConnectionStatus('fallback_mock');
705+
// Handle WebSocket errors
706+
socketRef.current.onerror = (event) => {
707+
console.log(`[OrderBook] WebSocket error detected:`, event);
708+
// Don't immediately fall back to mock data - let the onclose handler handle reconnection
709+
// This avoids duplicate reconnection attempts since an error is usually followed by a close
710+
};
711+
712+
// Handle WebSocket close events
713+
socketRef.current.onclose = (event) => {
714+
console.log(`[OrderBook] WebSocket closed with code ${event.code}, reason: ${event.reason}`);
643715

644-
// Always ensure we have good data
645-
const mockData = getMockOrderBook(exchange);
646-
internalOrderBookRef.current = mockData;
647-
setOrderBook(mockData);
648-
setLastUpdated(new Date());
716+
// If this is an abnormal closure (not intentional), attempt to reconnect
717+
if (event.code !== 1000) { // 1000 = normal closure
718+
// First ensure we have usable data while reconnecting
719+
if (!internalOrderBookRef.current) {
720+
const tempData = getMockOrderBook(exchange);
721+
internalOrderBookRef.current = tempData;
722+
setOrderBook(tempData);
723+
setLastUpdated(new Date());
724+
}
725+
726+
// Then attempt to reconnect
727+
attemptReconnect();
728+
} else {
729+
// This was a normal closure (e.g., component unmounting)
730+
console.log(`[OrderBook] WebSocket closed normally - no reconnection needed`);
731+
setConnectionStatus('disconnected');
732+
}
649733
};
650734
// Only set up simulated updates if we're in simulation mode
651735
const useSimulation = false;
@@ -668,19 +752,32 @@ export function useOrderBookWebSocket(
668752

669753
// Clean up function
670754
return () => {
755+
// Close WebSocket with normal closure code
671756
if (socketRef.current) {
672757
try {
673-
socketRef.current.close();
758+
// Use code 1000 (normal closure) to indicate intentional disconnect
759+
socketRef.current.close(1000, "Component unmounting");
674760
} catch (err) {
675761
console.error('[OrderBook] Error closing socket on cleanup:', err);
676762
}
677763
socketRef.current = null;
678764
}
679765

766+
// Clear any pending reconnection attempts
767+
if (reconnectTimeoutRef.current !== null) {
768+
clearTimeout(reconnectTimeoutRef.current);
769+
reconnectTimeoutRef.current = null;
770+
}
771+
772+
// Clear any update intervals
680773
if (updateTimeoutRef.current !== null) {
681774
clearInterval(updateTimeoutRef.current);
682775
updateTimeoutRef.current = null;
683776
}
777+
778+
// Remove network event listeners
779+
window.removeEventListener('online', handleOnline);
780+
window.removeEventListener('offline', handleOffline);
684781
};
685782
}, [exchange, symbol, updateInternalOrderBook, setupMockData]);
686783

0 commit comments

Comments
 (0)