@@ -22,6 +22,8 @@ const _connections: Array<Connection> = []
2222const _state : Record < string , unknown > = { }
2323const _keySubscribers : Record < string , string [ ] > = { } // key -> [connectionId...] in subscription order
2424const _keyHosts : Record < string , string > = { } // key -> hostConnectionId
25+ const _pendingUnsubscribes : Record < string , NodeJS . Timeout > = { } // "connectionId:cleanKey" -> timeout
26+ const UNSUBSCRIBE_DELAY_MS = 150 // Debounce delay to handle React StrictMode double-firing
2527let _wss : WebSocketServer | undefined
2628
2729function getSocketServer ( ) {
@@ -195,6 +197,21 @@ function handleMessage(connection: Connection, message: string) {
195197 }
196198
197199 case 'on' : {
200+ // Cancel any pending unsubscribe for this connection+key (handles StrictMode remount)
201+ const pendingKey = `${ connection . id } :${ cleanKey } `
202+ if ( _pendingUnsubscribes [ pendingKey ] ) {
203+ clearTimeout ( _pendingUnsubscribes [ pendingKey ] )
204+ delete _pendingUnsubscribes [ pendingKey ]
205+ syncLogger . debug ( `Cancelled pending unsubscribe for ${ connection . id } on ${ cleanKey } ` )
206+
207+ // Already subscribed, just send current state if exists
208+ if ( _state [ cleanKey ] ) {
209+ const response : MessagePayload = { data : _state [ cleanKey ] , key, type : 'update' }
210+ connection . ws . send ( JSON . stringify ( response ) )
211+ }
212+ break
213+ }
214+
198215 const isNewSubscriber = ! connection . watch . includes ( cleanKey )
199216
200217 if ( isNewSubscriber ) {
@@ -231,14 +248,24 @@ function handleMessage(connection: Connection, message: string) {
231248 }
232249
233250 case 'off' : {
234- const index = connection . watch . indexOf ( cleanKey )
235- if ( index > - 1 ) {
236- connection . watch . splice ( index , 1 )
237- syncLogger . debug ( `Connection ${ connection . id } stopped watching:` , cleanKey )
251+ const pendingKey = `${ connection . id } :${ cleanKey } `
238252
239- // Handle unsubscription with host migration
240- handleUnsubscribe ( connection , cleanKey , key )
253+ // Clear any existing pending unsubscribe for this connection+key
254+ if ( _pendingUnsubscribes [ pendingKey ] ) {
255+ clearTimeout ( _pendingUnsubscribes [ pendingKey ] )
241256 }
257+
258+ // Schedule delayed unsubscribe (handles React StrictMode double-firing)
259+ _pendingUnsubscribes [ pendingKey ] = setTimeout ( ( ) => {
260+ delete _pendingUnsubscribes [ pendingKey ]
261+
262+ const index = connection . watch . indexOf ( cleanKey )
263+ if ( index > - 1 ) {
264+ connection . watch . splice ( index , 1 )
265+ syncLogger . debug ( `Connection ${ connection . id } stopped watching:` , cleanKey )
266+ handleUnsubscribe ( connection , cleanKey , key )
267+ }
268+ } , UNSUBSCRIBE_DELAY_MS )
242269 break
243270 }
244271
@@ -349,6 +376,14 @@ function handleConnection(ws: WebSocket) {
349376 ws . on ( 'close' , ( ) => {
350377 syncLogger . debug ( `Connection ${ connection . id } closed. Removing...` )
351378
379+ // Clear any pending unsubscribes for this connection (they're closing anyway)
380+ Object . keys ( _pendingUnsubscribes ) . forEach ( ( pendingKey ) => {
381+ if ( pendingKey . startsWith ( `${ connection . id } :` ) ) {
382+ clearTimeout ( _pendingUnsubscribes [ pendingKey ] )
383+ delete _pendingUnsubscribes [ pendingKey ]
384+ }
385+ } )
386+
352387 // Handle unsubscription for all watched keys with host migration
353388 connection . watch . forEach ( ( cleanKey ) => {
354389 handleUnsubscribe ( connection , cleanKey )
0 commit comments