@@ -65,8 +65,9 @@ interface WebSocketStateNoCSS extends BaseWebSocketState {
65
65
interface BaseWebSocketState {
66
66
ws : WebSocket
67
67
roots : ConnectedRoot [ ]
68
- connected : boolean
68
+ connectionState : null | "connecting" | "connected"
69
69
mounted : boolean
70
+ closing : boolean
70
71
seenEventNames : Set < string >
71
72
}
72
73
@@ -301,8 +302,9 @@ export function handleWebSocket(
301
302
const wsStateBase : WebSocketState = {
302
303
ws,
303
304
roots : [ ] as ConnectedRoot [ ] ,
304
- connected : false ,
305
+ connectionState : null ,
305
306
mounted : false ,
307
+ closing : false ,
306
308
seenEventNames : new Set ( ) ,
307
309
hasCSS : false ,
308
310
}
@@ -321,19 +323,22 @@ export function handleWebSocket(
321
323
} )
322
324
323
325
ws . on ( "close" , async ( ) => {
324
- const promises = wsState . roots . map ( async root => {
325
- const stateTree = makeStateTree ( root . component , true )
326
- await reloadOptions . saveStateTree ( root . component . _id , stateTree )
327
- await root . component . _triggerUnmount ( root . allComponentsMap )
328
- } )
329
- if ( wsState . hasCSS ) {
330
- const cssPromise = reloadOptions . saveCSSState (
331
- wsState . cssState . id ,
332
- wsState . cssState ,
333
- )
334
- promises . push ( cssPromise )
326
+ wsState . closing = true
327
+ // Because both the "close" and "connect" events are async, we check if
328
+ // `connectionState` is set to "connected" because it could be the case
329
+ // that the "close" event fires just after the "connect" event (e.g., on
330
+ // page refresh), and the "close" event will see that the `wsState.roots`
331
+ // is an empty array due to the "connect" event still being in progress.
332
+ // This would result in an incomplete clean up of the previous
333
+ // connection's state. Hence, we return early, set the `closing` flag, and
334
+ // let the "connect" event clean up the existing state by signaling with
335
+ // `closing`.
336
+ if ( wsState . connectionState !== "connected" ) {
337
+ return
335
338
}
336
- await Promise . all ( promises )
339
+
340
+ await cleanUpWebSocketState ( wsState )
341
+ wsState . closing = false
337
342
} )
338
343
} )
339
344
@@ -372,10 +377,10 @@ async function handleMessage(
372
377
) : Promise < void > {
373
378
switch ( message . type ) {
374
379
case "connect" : {
375
- if ( wsState . connected ) {
380
+ if ( wsState . connectionState !== null ) {
376
381
break
377
382
}
378
- wsState . connected = true
383
+ wsState . connectionState = "connecting"
379
384
380
385
const cssStateID = message . cssStateID
381
386
if ( cssStateID ) {
@@ -442,7 +447,7 @@ async function handleMessage(
442
447
type : "update" ,
443
448
componentID : root . component . _id ,
444
449
pNode : toLatestPNode ( root . component . _pNode ) ,
445
- newEventNames : Array . from ( root ! . eventNames ) ,
450
+ newEventNames : Array . from ( root . eventNames ) ,
446
451
} )
447
452
448
453
// Don't wait for this, since we want wsState.mounted and wsState.roots
@@ -458,6 +463,21 @@ async function handleMessage(
458
463
deletePromises . push ( reloadOptions . deleteCSSState ( cssStateID ) )
459
464
}
460
465
await Promise . all ( deletePromises )
466
+
467
+ wsState . connectionState = "connected"
468
+ // Because both the "close" and "connect" events are async, we check if
469
+ // `closing` is set because it could be the case that the "close" event
470
+ // fires just after the "connect" event (e.g., on page refresh), and the
471
+ // "close" event will see that the `wsState.roots` is an empty array due
472
+ // to the "connect" event still being in progress. This would result in an
473
+ // incomplete cleanup of the previous connection's state. Hence, we check
474
+ // the `closing` flag and clean up any existing state that the "closing"
475
+ // event could not clean up if needed.
476
+ if ( wsState . closing ) {
477
+ await cleanUpWebSocketState ( wsState )
478
+ wsState . closing = false
479
+ }
480
+
461
481
break
462
482
}
463
483
@@ -1087,6 +1107,22 @@ function unalias(id: string, root: ConnectedRoot): string {
1087
1107
return id
1088
1108
}
1089
1109
1110
+ async function cleanUpWebSocketState ( wsState : WebSocketState ) : Promise < void > {
1111
+ const promises = wsState . roots . map ( async root => {
1112
+ const stateTree = makeStateTree ( root . component , true )
1113
+ await reloadOptions . saveStateTree ( root . component . _id , stateTree )
1114
+ await root . component . _triggerUnmount ( root . allComponentsMap )
1115
+ } )
1116
+ if ( wsState . hasCSS ) {
1117
+ const cssPromise = reloadOptions . saveCSSState (
1118
+ wsState . cssState . id ,
1119
+ wsState . cssState ,
1120
+ )
1121
+ promises . push ( cssPromise )
1122
+ }
1123
+ await Promise . all ( promises )
1124
+ }
1125
+
1090
1126
const globalStateTrees : Record < string , StateTree | undefined > = { }
1091
1127
const globalCSSState : Record < string , CSSState | undefined > = { }
1092
1128
const DELETE_INTERVAL = 60 * 1000 // 60 seconds
0 commit comments