@@ -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