@@ -47,60 +47,83 @@ class WebSocketService {
4747 private ws : WebSocket | null = null ;
4848 private reconnectAttempts = 0 ;
4949 private maxReconnectAttempts = 5 ;
50- private reconnectDelay = 1000 ;
50+ private reconnectDelay = 12000 ; // Changed from 1000ms to 12000ms (12 seconds)
5151 private listeners : Map < string , Set < ( message : StreamMessage ) => void > > = new Map ( ) ;
5252 private planSubscriptions : Set < string > = new Set ( ) ;
53+ private reconnectTimer : NodeJS . Timeout | null = null ; // Add timer tracking
54+ private isConnecting = false ; // Add connection state tracking
5355
5456 /**
5557 * Connect to WebSocket server
5658 */
5759 connect ( ) : Promise < void > {
5860 return new Promise ( ( resolve , reject ) => {
61+ // Prevent multiple simultaneous connection attempts
62+ if ( this . isConnecting ) {
63+ console . log ( 'Connection attempt already in progress' ) ;
64+ return ;
65+ }
66+
67+ // Clear any existing reconnection timer
68+ if ( this . reconnectTimer ) {
69+ clearTimeout ( this . reconnectTimer ) ;
70+ this . reconnectTimer = null ;
71+ }
72+
5973 try {
74+ this . isConnecting = true ;
75+
6076 // Get WebSocket URL from environment or default to localhost
6177 const wsProtocol = window . location . protocol === 'https:' ? 'wss:' : 'ws:' ;
6278 const wsHost = process . env . REACT_APP_WS_HOST || '127.0.0.1:8000' ;
6379 const processId = crypto . randomUUID ( ) ; // Generate unique process ID for this session
6480
65- // const wsUrl = `${wsProtocol}//${wsHost}/api/v3/socket/${processId}`;
6681 // Build WebSocket URL with authentication headers as query parameters
67- const userId = getUserId ( ) ; // Import this from config
68- const wsUrl = `${ wsProtocol } //${ wsHost } /api/v3/socket/${ processId } ?user_id=${ encodeURIComponent ( userId ) } ` ;
82+ const userId = getUserId ( ) ; // Import this from config
83+ const wsUrl = `${ wsProtocol } //${ wsHost } /api/v3/socket/${ processId } ?user_id=${ encodeURIComponent ( userId ) } ` ;
6984
7085 console . log ( 'Connecting to WebSocket:' , wsUrl ) ;
7186
7287 this . ws = new WebSocket ( wsUrl ) ;
7388
7489 this . ws . onopen = ( ) => {
75- console . log ( 'WebSocket connected' ) ;
90+ console . log ( 'WebSocket connected successfully ' ) ;
7691 this . reconnectAttempts = 0 ;
92+ this . isConnecting = false ;
7793 this . emit ( 'connection_status' , { connected : true } ) ;
7894 resolve ( ) ;
7995 } ;
8096
8197 this . ws . onmessage = ( event ) => {
8298 try {
83- const message : StreamMessage = JSON . parse ( event . data ) ;
99+ const message : StreamMessage = JSON . parse ( event . data ) ;
84100 this . handleMessage ( message ) ;
85101 } catch ( error ) {
86102 console . error ( 'Error parsing WebSocket message:' , error , 'Raw data:' , event . data ) ;
87103 this . emit ( 'error' , { error : 'Failed to parse WebSocket message' } ) ;
88104 }
89105 } ;
90106
91- this . ws . onclose = ( ) => {
92- console . log ( 'WebSocket disconnected' ) ;
107+ this . ws . onclose = ( event ) => {
108+ console . log ( 'WebSocket disconnected' , event . code , event . reason ) ;
109+ this . isConnecting = false ;
93110 this . emit ( 'connection_status' , { connected : false } ) ;
94- this . attemptReconnect ( ) ;
111+
112+ // Only attempt reconnect if it wasn't a manual disconnect
113+ if ( event . code !== 1000 ) { // 1000 = normal closure
114+ this . attemptReconnect ( ) ;
115+ }
95116 } ;
96117
97118 this . ws . onerror = ( error ) => {
98119 console . error ( 'WebSocket error:' , error ) ;
120+ this . isConnecting = false ;
99121 this . emit ( 'error' , { error : 'WebSocket connection failed' } ) ;
100122 reject ( error ) ;
101123 } ;
102124
103125 } catch ( error ) {
126+ this . isConnecting = false ;
104127 reject ( error ) ;
105128 }
106129 } ) ;
@@ -110,11 +133,22 @@ class WebSocketService {
110133 * Disconnect from WebSocket server
111134 */
112135 disconnect ( ) : void {
136+ console . log ( 'Manually disconnecting WebSocket' ) ;
137+
138+ // Clear any pending reconnection attempts
139+ if ( this . reconnectTimer ) {
140+ clearTimeout ( this . reconnectTimer ) ;
141+ this . reconnectTimer = null ;
142+ }
143+
144+ this . reconnectAttempts = this . maxReconnectAttempts ; // Prevent reconnection
145+
113146 if ( this . ws ) {
114- this . ws . close ( ) ;
147+ this . ws . close ( 1000 , 'Manual disconnect' ) ; // Use normal closure code
115148 this . ws = null ;
116149 }
117150 this . planSubscriptions . clear ( ) ;
151+ this . isConnecting = false ;
118152 }
119153
120154 /**
@@ -209,9 +243,6 @@ class WebSocketService {
209243 /**
210244 * Handle incoming WebSocket messages
211245 */
212- /**
213- * Handle incoming WebSocket messages
214- */
215246 private handleMessage ( message : StreamMessage ) : void {
216247 console . log ( 'WebSocket message received:' , message ) ;
217248
@@ -234,26 +265,39 @@ class WebSocketService {
234265 */
235266 private attemptReconnect ( ) : void {
236267 if ( this . reconnectAttempts >= this . maxReconnectAttempts ) {
237- console . log ( 'Max reconnection attempts reached' ) ;
268+ console . log ( 'Max reconnection attempts reached - stopping reconnect attempts ' ) ;
238269 this . emit ( 'error' , { error : 'Max reconnection attempts reached' } ) ;
239270 return ;
240271 }
241272
273+ // Prevent multiple simultaneous reconnection attempts
274+ if ( this . isConnecting || this . reconnectTimer ) {
275+ console . log ( 'Reconnection attempt already in progress' ) ;
276+ return ;
277+ }
278+
242279 this . reconnectAttempts ++ ;
280+ // Use exponential backoff: 12s, 24s, 48s, 96s, 192s
243281 const delay = this . reconnectDelay * Math . pow ( 2 , this . reconnectAttempts - 1 ) ;
244282
245- console . log ( `Attempting to reconnect in ${ delay } ms (attempt ${ this . reconnectAttempts } ) ` ) ;
283+ console . log ( `Scheduling reconnection attempt ${ this . reconnectAttempts } / ${ this . maxReconnectAttempts } in ${ delay / 1000 } s ` ) ;
246284
247- setTimeout ( ( ) => {
285+ this . reconnectTimer = setTimeout ( ( ) => {
286+ this . reconnectTimer = null ;
287+ console . log ( `Attempting reconnection (attempt ${ this . reconnectAttempts } )` ) ;
288+
248289 this . connect ( )
249290 . then ( ( ) => {
291+ console . log ( 'Reconnection successful - re-subscribing to plans' ) ;
250292 // Re-subscribe to all plans
251293 this . planSubscriptions . forEach ( planId => {
252294 this . subscribeToPlan ( planId ) ;
253295 } ) ;
254296 } )
255297 . catch ( ( error ) => {
256298 console . error ( 'Reconnection failed:' , error ) ;
299+ // The connect() method will trigger another reconnection attempt
300+ // through the onclose handler if needed
257301 } ) ;
258302 } , delay ) ;
259303 }
@@ -277,28 +321,28 @@ class WebSocketService {
277321 }
278322
279323 /**
280- * Send plan approval response
281- */
324+ * Send plan approval response
325+ */
282326 sendPlanApprovalResponse ( response : PlanApprovalResponseData ) : void {
283- if ( ! this . ws || this . ws . readyState !== WebSocket . OPEN ) {
284- console . error ( 'WebSocket not connected - cannot send plan approval response' ) ;
285- this . emit ( 'error' , { error : 'Cannot send plan approval response - WebSocket not connected' } ) ;
286- return ;
287- }
327+ if ( ! this . ws || this . ws . readyState !== WebSocket . OPEN ) {
328+ console . error ( 'WebSocket not connected - cannot send plan approval response' ) ;
329+ this . emit ( 'error' , { error : 'Cannot send plan approval response - WebSocket not connected' } ) ;
330+ return ;
331+ }
288332
289- try {
290- const message = {
291- type : 'plan_approval_response' ,
292- data : response
293- } ;
294- this . ws . send ( JSON . stringify ( message ) ) ;
295- console . log ( 'Plan approval response sent:' , response ) ;
296- } catch ( error ) {
297- console . error ( 'Failed to send plan approval response:' , error ) ;
298- this . emit ( 'error' , { error : 'Failed to send plan approval response' } ) ;
333+ try {
334+ const message = {
335+ type : 'plan_approval_response' ,
336+ data : response
337+ } ;
338+ this . ws . send ( JSON . stringify ( message ) ) ;
339+ console . log ( 'Plan approval response sent:' , response ) ;
340+ } catch ( error ) {
341+ console . error ( 'Failed to send plan approval response:' , error ) ;
342+ this . emit ( 'error' , { error : 'Failed to send plan approval response' } ) ;
343+ }
299344 }
300345}
301- }
302346
303347// Export singleton instance
304348export const webSocketService = new WebSocketService ( ) ;
0 commit comments