@@ -38,6 +38,12 @@ export type RealtimeMessage = {
38
38
}
39
39
40
40
export type RealtimeRemoveChannelResponse = 'ok' | 'timed out' | 'error'
41
+ export type HeartbeatStatus =
42
+ | 'sent'
43
+ | 'ok'
44
+ | 'error'
45
+ | 'timeout'
46
+ | 'disconnected'
41
47
42
48
const noop = ( ) => { }
43
49
@@ -86,7 +92,7 @@ const WORKER_SCRIPT = `
86
92
export default class RealtimeClient {
87
93
accessTokenValue : string | null = null
88
94
apiKey : string | null = null
89
- channels : RealtimeChannel [ ] = [ ]
95
+ channels : Set < RealtimeChannel > = new Set ( )
90
96
endPoint : string = ''
91
97
httpEndpoint : string = ''
92
98
headers ?: { [ key : string ] : string } = DEFAULT_HEADERS
@@ -96,6 +102,7 @@ export default class RealtimeClient {
96
102
heartbeatIntervalMs : number = 25000
97
103
heartbeatTimer : ReturnType < typeof setInterval > | undefined = undefined
98
104
pendingHeartbeatRef : string | null = null
105
+ heartbeatCallback : ( status : HeartbeatStatus ) => void = noop
99
106
ref : number = 0
100
107
reconnectTimer : Timer
101
108
logger : Function = noop
@@ -268,7 +275,7 @@ export default class RealtimeClient {
268
275
* Returns all created channels
269
276
*/
270
277
getChannels ( ) : RealtimeChannel [ ] {
271
- return this . channels
278
+ return Array . from ( this . channels )
272
279
}
273
280
274
281
/**
@@ -279,7 +286,7 @@ export default class RealtimeClient {
279
286
channel : RealtimeChannel
280
287
) : Promise < RealtimeRemoveChannelResponse > {
281
288
const status = await channel . unsubscribe ( )
282
- if ( this . channels . length === 0 ) {
289
+ if ( this . channels . size === 0 ) {
283
290
this . disconnect ( )
284
291
}
285
292
return status
@@ -290,9 +297,13 @@ export default class RealtimeClient {
290
297
*/
291
298
async removeAllChannels ( ) : Promise < RealtimeRemoveChannelResponse [ ] > {
292
299
const values_1 = await Promise . all (
293
- this . channels . map ( ( channel ) => channel . unsubscribe ( ) )
300
+ Array . from ( this . channels ) . map ( ( channel ) => {
301
+ this . channels . delete ( channel )
302
+ return channel . unsubscribe ( )
303
+ } )
294
304
)
295
305
this . disconnect ( )
306
+
296
307
return values_1
297
308
}
298
309
@@ -332,9 +343,18 @@ export default class RealtimeClient {
332
343
topic : string ,
333
344
params : RealtimeChannelOptions = { config : { } }
334
345
) : RealtimeChannel {
335
- const chan = new RealtimeChannel ( `realtime:${ topic } ` , params , this )
336
- this . channels . push ( chan )
337
- return chan
346
+ const realtimeTopic = `realtime:${ topic } `
347
+ const exists = this . getChannels ( ) . find (
348
+ ( c : RealtimeChannel ) => c . topic === realtimeTopic
349
+ )
350
+
351
+ if ( ! exists ) {
352
+ const chan = new RealtimeChannel ( `realtime:${ topic } ` , params , this )
353
+ this . channels . add ( chan )
354
+ return chan
355
+ } else {
356
+ return exists
357
+ }
338
358
}
339
359
340
360
/**
@@ -394,6 +414,7 @@ export default class RealtimeClient {
394
414
*/
395
415
async sendHeartbeat ( ) {
396
416
if ( ! this . isConnected ( ) ) {
417
+ this . heartbeatCallback ( 'disconnected' )
397
418
return
398
419
}
399
420
if ( this . pendingHeartbeatRef ) {
@@ -402,6 +423,7 @@ export default class RealtimeClient {
402
423
'transport' ,
403
424
'heartbeat timeout. Attempting to re-establish connection'
404
425
)
426
+ this . heartbeatCallback ( 'timeout' )
405
427
this . conn ?. close ( WS_CLOSE_NORMAL , 'hearbeat timeout' )
406
428
return
407
429
}
@@ -412,9 +434,13 @@ export default class RealtimeClient {
412
434
payload : { } ,
413
435
ref : this . pendingHeartbeatRef ,
414
436
} )
437
+ this . heartbeatCallback ( 'sent' )
415
438
await this . setAuth ( )
416
439
}
417
440
441
+ onHeartbeat ( callback : ( status : HeartbeatStatus ) => void ) : void {
442
+ this . heartbeatCallback = callback
443
+ }
418
444
/**
419
445
* Flushes send buffer
420
446
*/
@@ -467,7 +493,7 @@ export default class RealtimeClient {
467
493
* @internal
468
494
*/
469
495
_leaveOpenTopic ( topic : string ) : void {
470
- let dupChannel = this . channels . find (
496
+ let dupChannel = Array . from ( this . channels ) . find (
471
497
( c ) => c . topic === topic && ( c . _isJoined ( ) || c . _isJoining ( ) )
472
498
)
473
499
if ( dupChannel ) {
@@ -484,9 +510,7 @@ export default class RealtimeClient {
484
510
* @internal
485
511
*/
486
512
_remove ( channel : RealtimeChannel ) {
487
- this . channels = this . channels . filter (
488
- ( c : RealtimeChannel ) => c . _joinRef ( ) !== channel . _joinRef ( )
489
- )
513
+ this . channels . delete ( channel )
490
514
}
491
515
492
516
/**
@@ -510,6 +534,10 @@ export default class RealtimeClient {
510
534
this . decode ( rawMessage . data , ( msg : RealtimeMessage ) => {
511
535
let { topic, event, payload, ref } = msg
512
536
537
+ if ( topic === 'phoenix' && event === 'phx_reply' ) {
538
+ this . heartbeatCallback ( msg . payload . status == 'ok' ? 'ok' : 'error' )
539
+ }
540
+
513
541
if ( ref && ref === this . pendingHeartbeatRef ) {
514
542
this . pendingHeartbeatRef = null
515
543
}
@@ -521,11 +549,13 @@ export default class RealtimeClient {
521
549
} `,
522
550
payload
523
551
)
524
- this . channels
552
+
553
+ Array . from ( this . channels )
525
554
. filter ( ( channel : RealtimeChannel ) => channel . _isMember ( topic ) )
526
555
. forEach ( ( channel : RealtimeChannel ) =>
527
556
channel . _trigger ( event , payload , ref )
528
557
)
558
+
529
559
this . stateChangeCallbacks . message . forEach ( ( callback ) => callback ( msg ) )
530
560
} )
531
561
}
0 commit comments