@supabase/realtime-js 2.99.3 → 2.100.0-canary.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (111) hide show
  1. package/dist/main/RealtimeChannel.d.ts +35 -28
  2. package/dist/main/RealtimeChannel.d.ts.map +1 -1
  3. package/dist/main/RealtimeChannel.js +140 -301
  4. package/dist/main/RealtimeChannel.js.map +1 -1
  5. package/dist/main/RealtimeClient.d.ts +37 -56
  6. package/dist/main/RealtimeClient.d.ts.map +1 -1
  7. package/dist/main/RealtimeClient.js +233 -520
  8. package/dist/main/RealtimeClient.js.map +1 -1
  9. package/dist/main/RealtimePresence.d.ts +8 -24
  10. package/dist/main/RealtimePresence.d.ts.map +1 -1
  11. package/dist/main/RealtimePresence.js +6 -202
  12. package/dist/main/RealtimePresence.js.map +1 -1
  13. package/dist/main/lib/constants.d.ts +39 -35
  14. package/dist/main/lib/constants.d.ts.map +1 -1
  15. package/dist/main/lib/constants.js +30 -35
  16. package/dist/main/lib/constants.js.map +1 -1
  17. package/dist/main/lib/version.d.ts +1 -1
  18. package/dist/main/lib/version.d.ts.map +1 -1
  19. package/dist/main/lib/version.js +1 -1
  20. package/dist/main/lib/version.js.map +1 -1
  21. package/dist/main/lib/websocket-factory.d.ts +0 -9
  22. package/dist/main/lib/websocket-factory.d.ts.map +1 -1
  23. package/dist/main/lib/websocket-factory.js +0 -12
  24. package/dist/main/lib/websocket-factory.js.map +1 -1
  25. package/dist/main/phoenix/channelAdapter.d.ts +32 -0
  26. package/dist/main/phoenix/channelAdapter.d.ts.map +1 -0
  27. package/dist/main/phoenix/channelAdapter.js +103 -0
  28. package/dist/main/phoenix/channelAdapter.js.map +1 -0
  29. package/dist/main/phoenix/presenceAdapter.d.ts +53 -0
  30. package/dist/main/phoenix/presenceAdapter.d.ts.map +1 -0
  31. package/dist/main/phoenix/presenceAdapter.js +93 -0
  32. package/dist/main/phoenix/presenceAdapter.js.map +1 -0
  33. package/dist/main/phoenix/socketAdapter.d.ts +38 -0
  34. package/dist/main/phoenix/socketAdapter.d.ts.map +1 -0
  35. package/dist/main/phoenix/socketAdapter.js +114 -0
  36. package/dist/main/phoenix/socketAdapter.js.map +1 -0
  37. package/dist/main/phoenix/types.d.ts +5 -0
  38. package/dist/main/phoenix/types.d.ts.map +1 -0
  39. package/dist/main/phoenix/types.js +3 -0
  40. package/dist/main/phoenix/types.js.map +1 -0
  41. package/dist/module/RealtimeChannel.d.ts +35 -28
  42. package/dist/module/RealtimeChannel.d.ts.map +1 -1
  43. package/dist/module/RealtimeChannel.js +141 -302
  44. package/dist/module/RealtimeChannel.js.map +1 -1
  45. package/dist/module/RealtimeClient.d.ts +37 -56
  46. package/dist/module/RealtimeClient.d.ts.map +1 -1
  47. package/dist/module/RealtimeClient.js +234 -521
  48. package/dist/module/RealtimeClient.js.map +1 -1
  49. package/dist/module/RealtimePresence.d.ts +8 -24
  50. package/dist/module/RealtimePresence.d.ts.map +1 -1
  51. package/dist/module/RealtimePresence.js +5 -202
  52. package/dist/module/RealtimePresence.js.map +1 -1
  53. package/dist/module/lib/constants.d.ts +39 -35
  54. package/dist/module/lib/constants.d.ts.map +1 -1
  55. package/dist/module/lib/constants.js +30 -35
  56. package/dist/module/lib/constants.js.map +1 -1
  57. package/dist/module/lib/version.d.ts +1 -1
  58. package/dist/module/lib/version.d.ts.map +1 -1
  59. package/dist/module/lib/version.js +1 -1
  60. package/dist/module/lib/version.js.map +1 -1
  61. package/dist/module/lib/websocket-factory.d.ts +0 -9
  62. package/dist/module/lib/websocket-factory.d.ts.map +1 -1
  63. package/dist/module/lib/websocket-factory.js +0 -12
  64. package/dist/module/lib/websocket-factory.js.map +1 -1
  65. package/dist/module/phoenix/channelAdapter.d.ts +32 -0
  66. package/dist/module/phoenix/channelAdapter.d.ts.map +1 -0
  67. package/dist/module/phoenix/channelAdapter.js +100 -0
  68. package/dist/module/phoenix/channelAdapter.js.map +1 -0
  69. package/dist/module/phoenix/presenceAdapter.d.ts +53 -0
  70. package/dist/module/phoenix/presenceAdapter.d.ts.map +1 -0
  71. package/dist/module/phoenix/presenceAdapter.js +90 -0
  72. package/dist/module/phoenix/presenceAdapter.js.map +1 -0
  73. package/dist/module/phoenix/socketAdapter.d.ts +38 -0
  74. package/dist/module/phoenix/socketAdapter.d.ts.map +1 -0
  75. package/dist/module/phoenix/socketAdapter.js +111 -0
  76. package/dist/module/phoenix/socketAdapter.js.map +1 -0
  77. package/dist/module/phoenix/types.d.ts +5 -0
  78. package/dist/module/phoenix/types.d.ts.map +1 -0
  79. package/dist/module/phoenix/types.js +2 -0
  80. package/dist/module/phoenix/types.js.map +1 -0
  81. package/dist/tsconfig.module.tsbuildinfo +1 -1
  82. package/dist/tsconfig.tsbuildinfo +1 -1
  83. package/package.json +2 -2
  84. package/src/RealtimeChannel.ts +201 -364
  85. package/src/RealtimeClient.ts +290 -581
  86. package/src/RealtimePresence.ts +10 -287
  87. package/src/lib/constants.ts +50 -37
  88. package/src/lib/version.ts +1 -1
  89. package/src/lib/websocket-factory.ts +0 -13
  90. package/src/phoenix/channelAdapter.ts +147 -0
  91. package/src/phoenix/presenceAdapter.ts +116 -0
  92. package/src/phoenix/socketAdapter.ts +168 -0
  93. package/src/phoenix/types.ts +32 -0
  94. package/dist/main/lib/push.d.ts +0 -48
  95. package/dist/main/lib/push.d.ts.map +0 -1
  96. package/dist/main/lib/push.js +0 -102
  97. package/dist/main/lib/push.js.map +0 -1
  98. package/dist/main/lib/timer.d.ts +0 -22
  99. package/dist/main/lib/timer.d.ts.map +0 -1
  100. package/dist/main/lib/timer.js +0 -39
  101. package/dist/main/lib/timer.js.map +0 -1
  102. package/dist/module/lib/push.d.ts +0 -48
  103. package/dist/module/lib/push.d.ts.map +0 -1
  104. package/dist/module/lib/push.js +0 -99
  105. package/dist/module/lib/push.js.map +0 -1
  106. package/dist/module/lib/timer.d.ts +0 -22
  107. package/dist/module/lib/timer.d.ts.map +0 -1
  108. package/dist/module/lib/timer.js +0 -36
  109. package/dist/module/lib/timer.js.map +0 -1
  110. package/src/lib/push.ts +0 -121
  111. package/src/lib/timer.ts +0 -43
@@ -1,7 +1,6 @@
1
- import { CHANNEL_EVENTS, CHANNEL_STATES, MAX_PUSH_BUFFER_SIZE } from './lib/constants'
2
- import Push from './lib/push'
1
+ import { CHANNEL_EVENTS, CHANNEL_STATES } from './lib/constants'
2
+ import type { ChannelState } from './lib/constants'
3
3
  import type RealtimeClient from './RealtimeClient'
4
- import Timer from './lib/timer'
5
4
  import RealtimePresence, { REALTIME_PRESENCE_LISTEN_EVENTS } from './RealtimePresence'
6
5
  import type {
7
6
  RealtimePresenceJoinPayload,
@@ -10,6 +9,8 @@ import type {
10
9
  } from './RealtimePresence'
11
10
  import * as Transformers from './lib/transformers'
12
11
  import { httpEndpointURL } from './lib/transformers'
12
+ import ChannelAdapter from './phoenix/channelAdapter'
13
+ import { ChannelBindingCallback, ChannelOnErrorCallback } from './phoenix/types'
13
14
 
14
15
  type ReplayOption = {
15
16
  since: number
@@ -147,7 +148,7 @@ export enum REALTIME_SUBSCRIBE_STATES {
147
148
 
148
149
  export const REALTIME_CHANNEL_STATES = CHANNEL_STATES
149
150
 
150
- interface PostgresChangesFilters {
151
+ type PostgresChangesFilters = {
151
152
  postgres_changes: {
152
153
  id: string
153
154
  event: string
@@ -156,30 +157,52 @@ interface PostgresChangesFilters {
156
157
  filter?: string
157
158
  }[]
158
159
  }
160
+
161
+ type Binding = {
162
+ type: string
163
+ filter: { [key: string]: any }
164
+ callback: ChannelBindingCallback
165
+ ref: number
166
+ id?: string
167
+ }
168
+
159
169
  /** A channel is the basic building block of Realtime
160
170
  * and narrows the scope of data flow to subscribed clients.
161
171
  * You can think of a channel as a chatroom where participants are able to see who's online
162
172
  * and send and receive messages.
163
173
  */
164
174
  export default class RealtimeChannel {
165
- bindings: {
166
- [key: string]: {
167
- type: string
168
- filter: { [key: string]: any }
169
- callback: Function
170
- id?: string
171
- }[]
172
- } = {}
173
- timeout: number
174
- state: CHANNEL_STATES = CHANNEL_STATES.closed
175
- joinedOnce = false
176
- joinPush: Push
177
- rejoinTimer: Timer
178
- pushBuffer: Push[] = []
179
- presence: RealtimePresence
180
- broadcastEndpointURL: string
175
+ bindings: Record<string, Binding[]> = {}
181
176
  subTopic: string
177
+ broadcastEndpointURL: string
182
178
  private: boolean
179
+ presence: RealtimePresence
180
+ /** @internal */
181
+ channelAdapter: ChannelAdapter
182
+
183
+ get state() {
184
+ return this.channelAdapter.state
185
+ }
186
+
187
+ set state(state: ChannelState) {
188
+ this.channelAdapter.state = state
189
+ }
190
+
191
+ get joinedOnce() {
192
+ return this.channelAdapter.joinedOnce
193
+ }
194
+
195
+ get timeout() {
196
+ return this.socket.timeout
197
+ }
198
+
199
+ get joinPush() {
200
+ return this.channelAdapter.joinPush
201
+ }
202
+
203
+ get rejoinTimer() {
204
+ return this.channelAdapter.rejoinTimer
205
+ }
183
206
 
184
207
  /**
185
208
  * Creates a channel that can broadcast messages, sync presence, and listen to Postgres changes.
@@ -212,53 +235,17 @@ export default class RealtimeChannel {
212
235
  },
213
236
  ...params.config,
214
237
  }
215
- this.timeout = this.socket.timeout
216
- this.joinPush = new Push(this, CHANNEL_EVENTS.join, this.params, this.timeout)
217
- this.rejoinTimer = new Timer(() => this._rejoinUntilConnected(), this.socket.reconnectAfterMs)
218
- this.joinPush.receive('ok', () => {
219
- this.state = CHANNEL_STATES.joined
220
- this.rejoinTimer.reset()
221
- this.pushBuffer.forEach((pushEvent: Push) => pushEvent.send())
222
- this.pushBuffer = []
223
- })
238
+
239
+ this.channelAdapter = new ChannelAdapter(this.socket.socketAdapter, topic, this.params)
240
+ this.presence = new RealtimePresence(this)
241
+
224
242
  this._onClose(() => {
225
- this.rejoinTimer.reset()
226
- this.socket.log('channel', `close ${this.topic} ${this._joinRef()}`)
227
- this.state = CHANNEL_STATES.closed
228
243
  this.socket._remove(this)
229
244
  })
230
- this._onError((reason: string) => {
231
- if (this._isLeaving() || this._isClosed()) {
232
- return
233
- }
234
- this.socket.log('channel', `error ${this.topic}`, reason)
235
- this.state = CHANNEL_STATES.errored
236
- this.rejoinTimer.scheduleTimeout()
237
- })
238
- this.joinPush.receive('timeout', () => {
239
- if (!this._isJoining()) {
240
- return
241
- }
242
- this.socket.log('channel', `timeout ${this.topic}`, this.joinPush.timeout)
243
- this.state = CHANNEL_STATES.errored
244
- this.rejoinTimer.scheduleTimeout()
245
- })
246
245
 
247
- this.joinPush.receive('error', (reason: any) => {
248
- if (this._isLeaving() || this._isClosed()) {
249
- return
250
- }
251
- this.socket.log('channel', `error ${this.topic}`, reason)
252
- this.state = CHANNEL_STATES.errored
253
- this.rejoinTimer.scheduleTimeout()
254
- })
255
- this._on(CHANNEL_EVENTS.reply, {}, (payload: any, ref: string) => {
256
- this._trigger(this._replyEventName(ref), payload)
257
- })
258
-
259
- this.presence = new RealtimePresence(this)
246
+ this._updateFilterTransform()
260
247
 
261
- this.broadcastEndpointURL = httpEndpointURL(this.socket.endPoint)
248
+ this.broadcastEndpointURL = httpEndpointURL(this.socket.socketAdapter.endPointURL())
262
249
  this.private = this.params.config.private || false
263
250
 
264
251
  if (!this.private && this.params.config?.broadcast?.replay) {
@@ -274,7 +261,7 @@ export default class RealtimeChannel {
274
261
  if (!this.socket.isConnected()) {
275
262
  this.socket.connect()
276
263
  }
277
- if (this.state == CHANNEL_STATES.closed) {
264
+ if (this.channelAdapter.isClosed()) {
278
265
  const {
279
266
  config: { broadcast, presence, private: isPrivate },
280
267
  } = this.params
@@ -297,16 +284,18 @@ export default class RealtimeChannel {
297
284
  accessTokenPayload.access_token = this.socket.accessTokenValue
298
285
  }
299
286
 
300
- this._onError((e: Error) => callback?.(REALTIME_SUBSCRIBE_STATES.CHANNEL_ERROR, e))
287
+ this._onError((reason: unknown) => {
288
+ callback?.(REALTIME_SUBSCRIBE_STATES.CHANNEL_ERROR, reason as Error)
289
+ })
301
290
 
302
291
  this._onClose(() => callback?.(REALTIME_SUBSCRIBE_STATES.CLOSED))
303
292
 
304
293
  this.updateJoinPayload({ ...{ config }, ...accessTokenPayload })
305
294
 
306
- this.joinedOnce = true
307
- this._rejoin(timeout)
295
+ this._updateFilterMessage()
308
296
 
309
- this.joinPush
297
+ this.channelAdapter
298
+ .subscribe(timeout)
310
299
  .receive('ok', async ({ postgres_changes }: PostgresChangesFilters) => {
311
300
  // Only refresh auth if using callback-based tokens
312
301
  if (!this.socket._isManualToken()) {
@@ -315,46 +304,9 @@ export default class RealtimeChannel {
315
304
  if (postgres_changes === undefined) {
316
305
  callback?.(REALTIME_SUBSCRIBE_STATES.SUBSCRIBED)
317
306
  return
318
- } else {
319
- const clientPostgresBindings = this.bindings.postgres_changes
320
- const bindingsLen = clientPostgresBindings?.length ?? 0
321
- const newPostgresBindings = []
322
-
323
- for (let i = 0; i < bindingsLen; i++) {
324
- const clientPostgresBinding = clientPostgresBindings[i]
325
- const {
326
- filter: { event, schema, table, filter },
327
- } = clientPostgresBinding
328
- const serverPostgresFilter = postgres_changes && postgres_changes[i]
329
-
330
- if (
331
- serverPostgresFilter &&
332
- serverPostgresFilter.event === event &&
333
- RealtimeChannel.isFilterValueEqual(serverPostgresFilter.schema, schema) &&
334
- RealtimeChannel.isFilterValueEqual(serverPostgresFilter.table, table) &&
335
- RealtimeChannel.isFilterValueEqual(serverPostgresFilter.filter, filter)
336
- ) {
337
- newPostgresBindings.push({
338
- ...clientPostgresBinding,
339
- id: serverPostgresFilter.id,
340
- })
341
- } else {
342
- this.unsubscribe()
343
- this.state = CHANNEL_STATES.errored
344
-
345
- callback?.(
346
- REALTIME_SUBSCRIBE_STATES.CHANNEL_ERROR,
347
- new Error('mismatch between server and client bindings for postgres changes')
348
- )
349
- return
350
- }
351
- }
352
-
353
- this.bindings.postgres_changes = newPostgresBindings
354
-
355
- callback && callback(REALTIME_SUBSCRIBE_STATES.SUBSCRIBED)
356
- return
357
307
  }
308
+
309
+ this._updatePostgresBindings(postgres_changes, callback)
358
310
  })
359
311
  .receive('error', (error: { [key: string]: any }) => {
360
312
  this.state = CHANNEL_STATES.errored
@@ -362,16 +314,59 @@ export default class RealtimeChannel {
362
314
  REALTIME_SUBSCRIBE_STATES.CHANNEL_ERROR,
363
315
  new Error(JSON.stringify(Object.values(error).join(', ') || 'error'))
364
316
  )
365
- return
366
317
  })
367
318
  .receive('timeout', () => {
368
319
  callback?.(REALTIME_SUBSCRIBE_STATES.TIMED_OUT)
369
- return
370
320
  })
371
321
  }
372
322
  return this
373
323
  }
374
324
 
325
+ private _updatePostgresBindings(
326
+ postgres_changes: PostgresChangesFilters['postgres_changes'],
327
+ callback?: (status: REALTIME_SUBSCRIBE_STATES, err?: Error) => void
328
+ ) {
329
+ const clientPostgresBindings = this.bindings.postgres_changes
330
+ const bindingsLen = clientPostgresBindings?.length ?? 0
331
+ const newPostgresBindings = []
332
+
333
+ for (let i = 0; i < bindingsLen; i++) {
334
+ const clientPostgresBinding = clientPostgresBindings[i]
335
+ const {
336
+ filter: { event, schema, table, filter },
337
+ } = clientPostgresBinding
338
+ const serverPostgresFilter = postgres_changes && postgres_changes[i]
339
+
340
+ if (
341
+ serverPostgresFilter &&
342
+ serverPostgresFilter.event === event &&
343
+ RealtimeChannel.isFilterValueEqual(serverPostgresFilter.schema, schema) &&
344
+ RealtimeChannel.isFilterValueEqual(serverPostgresFilter.table, table) &&
345
+ RealtimeChannel.isFilterValueEqual(serverPostgresFilter.filter, filter)
346
+ ) {
347
+ newPostgresBindings.push({
348
+ ...clientPostgresBinding,
349
+ id: serverPostgresFilter.id,
350
+ })
351
+ } else {
352
+ this.unsubscribe()
353
+ this.state = CHANNEL_STATES.errored
354
+
355
+ callback?.(
356
+ REALTIME_SUBSCRIBE_STATES.CHANNEL_ERROR,
357
+ new Error('mismatch between server and client bindings for postgres changes')
358
+ )
359
+ return
360
+ }
361
+ }
362
+
363
+ this.bindings.postgres_changes = newPostgresBindings
364
+
365
+ if (this.state != CHANNEL_STATES.errored && callback) {
366
+ callback(REALTIME_SUBSCRIBE_STATES.SUBSCRIBED)
367
+ }
368
+ }
369
+
375
370
  /**
376
371
  * Returns the current presence state for this channel.
377
372
  *
@@ -431,6 +426,11 @@ export default class RealtimeChannel {
431
426
  filter: { event: `${REALTIME_PRESENCE_LISTEN_EVENTS.LEAVE}` },
432
427
  callback: (payload: RealtimePresenceLeavePayload<T>) => void
433
428
  ): RealtimeChannel
429
+ on<T extends { [key: string]: any }>(
430
+ type: `${REALTIME_LISTEN_TYPES.PRESENCE}`,
431
+ filter: { event: '*' },
432
+ callback: (payload?: RealtimePresenceJoinPayload<T> | RealtimePresenceLeavePayload<T>) => void
433
+ ): RealtimeChannel
434
434
  on<T extends { [key: string]: any }>(
435
435
  type: `${REALTIME_LISTEN_TYPES.POSTGRES_CHANGES}`,
436
436
  filter: RealtimePostgresChangesFilter<`${REALTIME_POSTGRES_CHANGES_LISTEN_EVENT.ALL}`>,
@@ -534,12 +534,9 @@ export default class RealtimeChannel {
534
534
  filter: { event: string; [key: string]: string },
535
535
  callback: (payload: any) => void
536
536
  ): RealtimeChannel {
537
- if (this.state === CHANNEL_STATES.joined && type === REALTIME_LISTEN_TYPES.PRESENCE) {
538
- this.socket.log(
539
- 'channel',
540
- `resubscribe to ${this.topic} due to change in presence callbacks on joined channel`
541
- )
542
- this.unsubscribe().then(async () => await this.subscribe())
537
+ if (this.channelAdapter.isJoined() && type === REALTIME_LISTEN_TYPES.PRESENCE) {
538
+ this.socket.log('channel', `cannot add presence callbacks for ${this.topic} after joining.`)
539
+ throw new Error('cannot add presence callbacks after joining a channel')
543
540
  }
544
541
  return this._on(type, filter, callback)
545
542
  }
@@ -624,7 +621,7 @@ export default class RealtimeChannel {
624
621
  },
625
622
  opts: { [key: string]: any } = {}
626
623
  ): Promise<RealtimeChannelSendResponse> {
627
- if (!this._canPush() && args.type === 'broadcast') {
624
+ if (!this.channelAdapter.canPush() && args.type === 'broadcast') {
628
625
  console.warn(
629
626
  'Realtime send() is automatically falling back to REST API. ' +
630
627
  'This behavior will be deprecated in the future. ' +
@@ -674,7 +671,7 @@ export default class RealtimeChannel {
674
671
  }
675
672
  } else {
676
673
  return new Promise((resolve) => {
677
- const push = this._push(args.type, args, opts.timeout || this.timeout)
674
+ const push = this.channelAdapter.push(args.type, args, opts.timeout || this.timeout)
678
675
 
679
676
  if (args.type === 'broadcast' && !this.params?.config?.broadcast?.ack) {
680
677
  resolve('ok')
@@ -691,8 +688,8 @@ export default class RealtimeChannel {
691
688
  * Updates the payload that will be sent the next time the channel joins (reconnects).
692
689
  * Useful for rotating access tokens or updating config without re-creating the channel.
693
690
  */
694
- updateJoinPayload(payload: { [key: string]: any }): void {
695
- this.joinPush.updatePayload(payload)
691
+ updateJoinPayload(payload: Record<string, any>) {
692
+ this.channelAdapter.updateJoinPayload(payload)
696
693
  }
697
694
 
698
695
  /**
@@ -704,56 +701,24 @@ export default class RealtimeChannel {
704
701
  * To receive leave acknowledgements, use the a `receive` hook to bind to the server ack, ie:
705
702
  * channel.unsubscribe().receive("ok", () => alert("left!") )
706
703
  */
707
- unsubscribe(timeout = this.timeout): Promise<'ok' | 'timed out' | 'error'> {
708
- this.state = CHANNEL_STATES.leaving
709
- const onClose = () => {
710
- this.socket.log('channel', `leave ${this.topic}`)
711
- this._trigger(CHANNEL_EVENTS.close, 'leave', this._joinRef())
712
- }
713
-
714
- this.joinPush.destroy()
715
-
716
- let leavePush: Push | null = null
717
-
704
+ async unsubscribe(timeout = this.timeout) {
718
705
  return new Promise<RealtimeChannelSendResponse>((resolve) => {
719
- leavePush = new Push(this, CHANNEL_EVENTS.leave, {}, timeout)
720
- leavePush
721
- .receive('ok', () => {
722
- onClose()
723
- resolve('ok')
724
- })
725
- .receive('timeout', () => {
726
- onClose()
727
- resolve('timed out')
728
- })
729
- .receive('error', () => {
730
- resolve('error')
731
- })
732
-
733
- leavePush.send()
734
- if (!this._canPush()) {
735
- leavePush.trigger('ok', {})
736
- }
737
- }).finally(() => {
738
- leavePush?.destroy()
706
+ this.channelAdapter
707
+ .unsubscribe(timeout)
708
+ .receive('ok', () => resolve('ok'))
709
+ .receive('timeout', () => resolve('timed out'))
710
+ .receive('error', () => resolve('error'))
739
711
  })
740
712
  }
713
+
741
714
  /**
742
- * Teardown the channel.
743
- *
744
715
  * Destroys and stops related timers.
745
716
  */
746
717
  teardown() {
747
- this.pushBuffer.forEach((push: Push) => push.destroy())
748
- this.pushBuffer = []
749
- this.rejoinTimer.reset()
750
- this.joinPush.destroy()
751
- this.state = CHANNEL_STATES.closed
752
- this.bindings = {}
718
+ this.channelAdapter.teardown()
753
719
  }
754
720
 
755
721
  /** @internal */
756
-
757
722
  async _fetchWithTimeout(url: string, options: { [key: string]: any }, timeout: number) {
758
723
  const controller = new AbortController()
759
724
  const id = setTimeout(() => controller.abort(), timeout)
@@ -769,195 +734,112 @@ export default class RealtimeChannel {
769
734
  }
770
735
 
771
736
  /** @internal */
772
- _push(event: string, payload: { [key: string]: any }, timeout = this.timeout) {
773
- if (!this.joinedOnce) {
774
- throw `tried to push '${event}' to '${this.topic}' before joining. Use channel.subscribe() before pushing events`
737
+ _on(type: string, filter: { [key: string]: any }, callback: ChannelBindingCallback) {
738
+ const typeLower = type.toLocaleLowerCase()
739
+
740
+ const ref = this.channelAdapter.on(type, callback)
741
+
742
+ const binding: Binding = {
743
+ type: typeLower,
744
+ filter: filter,
745
+ callback: callback,
746
+ ref: ref,
775
747
  }
776
- let pushEvent = new Push(this, event, payload, timeout)
777
- if (this._canPush()) {
778
- pushEvent.send()
748
+
749
+ if (this.bindings[typeLower]) {
750
+ this.bindings[typeLower].push(binding)
779
751
  } else {
780
- this._addToPushBuffer(pushEvent)
752
+ this.bindings[typeLower] = [binding]
781
753
  }
782
754
 
783
- return pushEvent
784
- }
755
+ this._updateFilterMessage()
785
756
 
786
- /** @internal */
787
- _addToPushBuffer(pushEvent: Push) {
788
- pushEvent.startTimeout()
789
- this.pushBuffer.push(pushEvent)
790
-
791
- // Enforce buffer size limit
792
- if (this.pushBuffer.length > MAX_PUSH_BUFFER_SIZE) {
793
- const removedPush = this.pushBuffer.shift()
794
- if (removedPush) {
795
- removedPush.destroy()
796
- this.socket.log(
797
- 'channel',
798
- `discarded push due to buffer overflow: ${removedPush.event}`,
799
- removedPush.payload
800
- )
801
- }
802
- }
757
+ return this
803
758
  }
804
759
 
805
760
  /**
806
- * Overridable message hook
807
- *
808
- * Receives all events for specialized message handling before dispatching to the channel callbacks.
809
- * Must return the payload, modified or unmodified.
761
+ * Registers a callback that will be executed when the channel closes.
810
762
  *
811
763
  * @internal
812
764
  */
813
- _onMessage(_event: string, payload: any, _ref?: string) {
814
- return payload
815
- }
816
-
817
- /** @internal */
818
- _isMember(topic: string): boolean {
819
- return this.topic === topic
820
- }
821
-
822
- /** @internal */
823
- _joinRef(): string {
824
- return this.joinPush.ref
765
+ private _onClose(callback: ChannelBindingCallback) {
766
+ this.channelAdapter.onClose(callback)
825
767
  }
826
768
 
827
- /** @internal */
828
- _trigger(type: string, payload?: any, ref?: string) {
829
- const typeLower = type.toLocaleLowerCase()
830
- const { close, error, leave, join } = CHANNEL_EVENTS
831
- const events: string[] = [close, error, leave, join]
832
- if (ref && events.indexOf(typeLower) >= 0 && ref !== this._joinRef()) {
833
- return
834
- }
835
- let handledPayload = this._onMessage(typeLower, payload, ref)
836
- if (payload && !handledPayload) {
837
- throw 'channel onMessage callbacks must return the payload, modified or unmodified'
838
- }
839
-
840
- if (['insert', 'update', 'delete'].includes(typeLower)) {
841
- this.bindings.postgres_changes
842
- ?.filter((bind) => {
843
- return bind.filter?.event === '*' || bind.filter?.event?.toLocaleLowerCase() === typeLower
844
- })
845
- .map((bind) => bind.callback(handledPayload, ref))
846
- } else {
847
- this.bindings[typeLower]
848
- ?.filter((bind) => {
849
- if (['broadcast', 'presence', 'postgres_changes'].includes(typeLower)) {
850
- if ('id' in bind) {
851
- const bindId = bind.id
852
- const bindEvent = bind.filter?.event
853
- return (
854
- bindId &&
855
- payload.ids?.includes(bindId) &&
856
- (bindEvent === '*' ||
857
- bindEvent?.toLocaleLowerCase() === payload.data?.type.toLocaleLowerCase())
858
- )
859
- } else {
860
- const bindEvent = bind?.filter?.event?.toLocaleLowerCase()
861
- return bindEvent === '*' || bindEvent === payload?.event?.toLocaleLowerCase()
862
- }
863
- } else {
864
- return bind.type.toLocaleLowerCase() === typeLower
865
- }
866
- })
867
- .map((bind) => {
868
- if (typeof handledPayload === 'object' && 'ids' in handledPayload) {
869
- const postgresChanges = handledPayload.data
870
- const { schema, table, commit_timestamp, type, errors } = postgresChanges
871
- const enrichedPayload = {
872
- schema: schema,
873
- table: table,
874
- commit_timestamp: commit_timestamp,
875
- eventType: type,
876
- new: {},
877
- old: {},
878
- errors: errors,
879
- }
880
- handledPayload = {
881
- ...enrichedPayload,
882
- ...this._getPayloadRecords(postgresChanges),
883
- }
884
- }
885
- bind.callback(handledPayload, ref)
886
- })
887
- }
888
- }
889
-
890
- /** @internal */
891
- _isClosed(): boolean {
892
- return this.state === CHANNEL_STATES.closed
893
- }
894
-
895
- /** @internal */
896
- _isJoined(): boolean {
897
- return this.state === CHANNEL_STATES.joined
898
- }
899
-
900
- /** @internal */
901
- _isJoining(): boolean {
902
- return this.state === CHANNEL_STATES.joining
769
+ /**
770
+ * Registers a callback that will be executed when the channel encounteres an error.
771
+ *
772
+ * @internal
773
+ */
774
+ private _onError(callback: ChannelOnErrorCallback) {
775
+ this.channelAdapter.onError(callback)
903
776
  }
904
777
 
905
778
  /** @internal */
906
- _isLeaving(): boolean {
907
- return this.state === CHANNEL_STATES.leaving
908
- }
779
+ private _updateFilterMessage() {
780
+ this.channelAdapter.updateFilterBindings((binding, payload: any, ref) => {
781
+ const typeLower = binding.event.toLocaleLowerCase()
909
782
 
910
- /** @internal */
911
- _replyEventName(ref: string): string {
912
- return `chan_reply_${ref}`
913
- }
783
+ if (this._notThisChannelEvent(typeLower, ref)) {
784
+ return false
785
+ }
914
786
 
915
- /** @internal */
916
- _on(type: string, filter: { [key: string]: any }, callback: Function) {
917
- const typeLower = type.toLocaleLowerCase()
918
- const binding = {
919
- type: typeLower,
920
- filter: filter,
921
- callback: callback,
922
- }
787
+ const bind = this.bindings[typeLower]?.find((bind) => bind.ref === binding.ref)
923
788
 
924
- if (this.bindings[typeLower]) {
925
- this.bindings[typeLower].push(binding)
926
- } else {
927
- this.bindings[typeLower] = [binding]
928
- }
789
+ if (!bind) {
790
+ return true
791
+ }
929
792
 
930
- return this
793
+ if (['broadcast', 'presence', 'postgres_changes'].includes(typeLower)) {
794
+ if ('id' in bind) {
795
+ const bindId = bind.id
796
+ const bindEvent = bind.filter?.event
797
+ return (
798
+ bindId &&
799
+ payload.ids?.includes(bindId) &&
800
+ (bindEvent === '*' ||
801
+ bindEvent?.toLocaleLowerCase() === payload.data?.type.toLocaleLowerCase())
802
+ )
803
+ } else {
804
+ const bindEvent = bind?.filter?.event?.toLocaleLowerCase()
805
+ return bindEvent === '*' || bindEvent === payload?.event?.toLocaleLowerCase()
806
+ }
807
+ } else {
808
+ return bind.type.toLocaleLowerCase() === typeLower
809
+ }
810
+ })
931
811
  }
932
812
 
933
813
  /** @internal */
934
- _off(type: string, filter: { [key: string]: any }) {
935
- const typeLower = type.toLocaleLowerCase()
936
-
937
- if (this.bindings[typeLower]) {
938
- this.bindings[typeLower] = this.bindings[typeLower].filter((bind) => {
939
- return !(
940
- bind.type?.toLocaleLowerCase() === typeLower &&
941
- RealtimeChannel.isEqual(bind.filter, filter)
942
- )
943
- })
944
- }
945
- return this
814
+ private _notThisChannelEvent(event: string, ref?: string | null) {
815
+ const { close, error, leave, join } = CHANNEL_EVENTS
816
+ const events: string[] = [close, error, leave, join]
817
+ return ref && events.includes(event) && ref !== this.joinPush.ref
946
818
  }
947
819
 
948
820
  /** @internal */
949
- private static isEqual(obj1: { [key: string]: string }, obj2: { [key: string]: string }) {
950
- if (Object.keys(obj1).length !== Object.keys(obj2).length) {
951
- return false
952
- }
953
-
954
- for (const k in obj1) {
955
- if (obj1[k] !== obj2[k]) {
956
- return false
821
+ private _updateFilterTransform() {
822
+ this.channelAdapter.updatePayloadTransform((event, payload: any, ref) => {
823
+ if (typeof payload === 'object' && 'ids' in payload) {
824
+ const postgresChanges = payload.data
825
+ const { schema, table, commit_timestamp, type, errors } = postgresChanges
826
+ const enrichedPayload = {
827
+ schema: schema,
828
+ table: table,
829
+ commit_timestamp: commit_timestamp,
830
+ eventType: type,
831
+ new: {},
832
+ old: {},
833
+ errors: errors,
834
+ }
835
+ return {
836
+ ...enrichedPayload,
837
+ ...this._getPayloadRecords(postgresChanges),
838
+ }
957
839
  }
958
- }
959
840
 
960
- return true
841
+ return payload
842
+ })
961
843
  }
962
844
 
963
845
  /**
@@ -974,51 +856,6 @@ export default class RealtimeChannel {
974
856
  return normalizedServer === normalizedClient
975
857
  }
976
858
 
977
- /** @internal */
978
- private _rejoinUntilConnected() {
979
- this.rejoinTimer.scheduleTimeout()
980
- if (this.socket.isConnected()) {
981
- this._rejoin()
982
- }
983
- }
984
-
985
- /**
986
- * Registers a callback that will be executed when the channel closes.
987
- *
988
- * @internal
989
- */
990
- private _onClose(callback: Function) {
991
- this._on(CHANNEL_EVENTS.close, {}, callback)
992
- }
993
-
994
- /**
995
- * Registers a callback that will be executed when the channel encounteres an error.
996
- *
997
- * @internal
998
- */
999
- private _onError(callback: Function) {
1000
- this._on(CHANNEL_EVENTS.error, {}, (reason: string) => callback(reason))
1001
- }
1002
-
1003
- /**
1004
- * Returns `true` if the socket is connected and the channel has been joined.
1005
- *
1006
- * @internal
1007
- */
1008
- private _canPush(): boolean {
1009
- return this.socket.isConnected() && this._isJoined()
1010
- }
1011
-
1012
- /** @internal */
1013
- private _rejoin(timeout = this.timeout): void {
1014
- if (this._isLeaving()) {
1015
- return
1016
- }
1017
- this.socket._leaveOpenTopic(this.topic)
1018
- this.state = CHANNEL_STATES.joining
1019
- this.joinPush.resend(timeout)
1020
- }
1021
-
1022
859
  /** @internal */
1023
860
  private _getPayloadRecords(payload: any) {
1024
861
  const records = {