@supabase/realtime-js 2.99.3-canary.0 → 2.99.3
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.
- package/dist/main/RealtimeChannel.d.ts +28 -35
- package/dist/main/RealtimeChannel.d.ts.map +1 -1
- package/dist/main/RealtimeChannel.js +301 -140
- package/dist/main/RealtimeChannel.js.map +1 -1
- package/dist/main/RealtimeClient.d.ts +57 -38
- package/dist/main/RealtimeClient.d.ts.map +1 -1
- package/dist/main/RealtimeClient.js +520 -232
- package/dist/main/RealtimeClient.js.map +1 -1
- package/dist/main/RealtimePresence.d.ts +24 -8
- package/dist/main/RealtimePresence.d.ts.map +1 -1
- package/dist/main/RealtimePresence.js +202 -6
- package/dist/main/RealtimePresence.js.map +1 -1
- package/dist/main/lib/constants.d.ts +35 -39
- package/dist/main/lib/constants.d.ts.map +1 -1
- package/dist/main/lib/constants.js +35 -30
- package/dist/main/lib/constants.js.map +1 -1
- package/dist/main/lib/push.d.ts +48 -0
- package/dist/main/lib/push.d.ts.map +1 -0
- package/dist/main/lib/push.js +102 -0
- package/dist/main/lib/push.js.map +1 -0
- package/dist/main/lib/timer.d.ts +22 -0
- package/dist/main/lib/timer.d.ts.map +1 -0
- package/dist/main/lib/timer.js +39 -0
- package/dist/main/lib/timer.js.map +1 -0
- package/dist/main/lib/version.d.ts +1 -1
- package/dist/main/lib/version.d.ts.map +1 -1
- package/dist/main/lib/version.js +1 -1
- package/dist/main/lib/version.js.map +1 -1
- package/dist/main/lib/websocket-factory.d.ts +9 -0
- package/dist/main/lib/websocket-factory.d.ts.map +1 -1
- package/dist/main/lib/websocket-factory.js +12 -0
- package/dist/main/lib/websocket-factory.js.map +1 -1
- package/dist/module/RealtimeChannel.d.ts +28 -35
- package/dist/module/RealtimeChannel.d.ts.map +1 -1
- package/dist/module/RealtimeChannel.js +302 -141
- package/dist/module/RealtimeChannel.js.map +1 -1
- package/dist/module/RealtimeClient.d.ts +57 -38
- package/dist/module/RealtimeClient.d.ts.map +1 -1
- package/dist/module/RealtimeClient.js +521 -233
- package/dist/module/RealtimeClient.js.map +1 -1
- package/dist/module/RealtimePresence.d.ts +24 -8
- package/dist/module/RealtimePresence.d.ts.map +1 -1
- package/dist/module/RealtimePresence.js +202 -5
- package/dist/module/RealtimePresence.js.map +1 -1
- package/dist/module/lib/constants.d.ts +35 -39
- package/dist/module/lib/constants.d.ts.map +1 -1
- package/dist/module/lib/constants.js +35 -30
- package/dist/module/lib/constants.js.map +1 -1
- package/dist/module/lib/push.d.ts +48 -0
- package/dist/module/lib/push.d.ts.map +1 -0
- package/dist/module/lib/push.js +99 -0
- package/dist/module/lib/push.js.map +1 -0
- package/dist/module/lib/timer.d.ts +22 -0
- package/dist/module/lib/timer.d.ts.map +1 -0
- package/dist/module/lib/timer.js +36 -0
- package/dist/module/lib/timer.js.map +1 -0
- package/dist/module/lib/version.d.ts +1 -1
- package/dist/module/lib/version.d.ts.map +1 -1
- package/dist/module/lib/version.js +1 -1
- package/dist/module/lib/version.js.map +1 -1
- package/dist/module/lib/websocket-factory.d.ts +9 -0
- package/dist/module/lib/websocket-factory.d.ts.map +1 -1
- package/dist/module/lib/websocket-factory.js +12 -0
- package/dist/module/lib/websocket-factory.js.map +1 -1
- package/dist/tsconfig.module.tsbuildinfo +1 -1
- package/dist/tsconfig.tsbuildinfo +1 -1
- package/package.json +3 -3
- package/src/RealtimeChannel.ts +364 -201
- package/src/RealtimeClient.ts +583 -296
- package/src/RealtimePresence.ts +287 -10
- package/src/lib/constants.ts +37 -50
- package/src/lib/push.ts +121 -0
- package/src/lib/timer.ts +43 -0
- package/src/lib/version.ts +1 -1
- package/src/lib/websocket-factory.ts +13 -0
- package/dist/main/phoenix/channelAdapter.d.ts +0 -32
- package/dist/main/phoenix/channelAdapter.d.ts.map +0 -1
- package/dist/main/phoenix/channelAdapter.js +0 -103
- package/dist/main/phoenix/channelAdapter.js.map +0 -1
- package/dist/main/phoenix/presenceAdapter.d.ts +0 -53
- package/dist/main/phoenix/presenceAdapter.d.ts.map +0 -1
- package/dist/main/phoenix/presenceAdapter.js +0 -93
- package/dist/main/phoenix/presenceAdapter.js.map +0 -1
- package/dist/main/phoenix/socketAdapter.d.ts +0 -38
- package/dist/main/phoenix/socketAdapter.d.ts.map +0 -1
- package/dist/main/phoenix/socketAdapter.js +0 -114
- package/dist/main/phoenix/socketAdapter.js.map +0 -1
- package/dist/main/phoenix/types.d.ts +0 -5
- package/dist/main/phoenix/types.d.ts.map +0 -1
- package/dist/main/phoenix/types.js +0 -3
- package/dist/main/phoenix/types.js.map +0 -1
- package/dist/module/phoenix/channelAdapter.d.ts +0 -32
- package/dist/module/phoenix/channelAdapter.d.ts.map +0 -1
- package/dist/module/phoenix/channelAdapter.js +0 -100
- package/dist/module/phoenix/channelAdapter.js.map +0 -1
- package/dist/module/phoenix/presenceAdapter.d.ts +0 -53
- package/dist/module/phoenix/presenceAdapter.d.ts.map +0 -1
- package/dist/module/phoenix/presenceAdapter.js +0 -90
- package/dist/module/phoenix/presenceAdapter.js.map +0 -1
- package/dist/module/phoenix/socketAdapter.d.ts +0 -38
- package/dist/module/phoenix/socketAdapter.d.ts.map +0 -1
- package/dist/module/phoenix/socketAdapter.js +0 -111
- package/dist/module/phoenix/socketAdapter.js.map +0 -1
- package/dist/module/phoenix/types.d.ts +0 -5
- package/dist/module/phoenix/types.d.ts.map +0 -1
- package/dist/module/phoenix/types.js +0 -2
- package/dist/module/phoenix/types.js.map +0 -1
- package/src/phoenix/channelAdapter.ts +0 -147
- package/src/phoenix/presenceAdapter.ts +0 -116
- package/src/phoenix/socketAdapter.ts +0 -168
- package/src/phoenix/types.ts +0 -32
package/src/RealtimeChannel.ts
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
|
-
import { CHANNEL_EVENTS, CHANNEL_STATES } from './lib/constants'
|
|
2
|
-
import
|
|
1
|
+
import { CHANNEL_EVENTS, CHANNEL_STATES, MAX_PUSH_BUFFER_SIZE } from './lib/constants'
|
|
2
|
+
import Push from './lib/push'
|
|
3
3
|
import type RealtimeClient from './RealtimeClient'
|
|
4
|
+
import Timer from './lib/timer'
|
|
4
5
|
import RealtimePresence, { REALTIME_PRESENCE_LISTEN_EVENTS } from './RealtimePresence'
|
|
5
6
|
import type {
|
|
6
7
|
RealtimePresenceJoinPayload,
|
|
@@ -9,8 +10,6 @@ import type {
|
|
|
9
10
|
} from './RealtimePresence'
|
|
10
11
|
import * as Transformers from './lib/transformers'
|
|
11
12
|
import { httpEndpointURL } from './lib/transformers'
|
|
12
|
-
import ChannelAdapter from './phoenix/channelAdapter'
|
|
13
|
-
import { ChannelBindingCallback, ChannelOnErrorCallback } from './phoenix/types'
|
|
14
13
|
|
|
15
14
|
type ReplayOption = {
|
|
16
15
|
since: number
|
|
@@ -148,7 +147,7 @@ export enum REALTIME_SUBSCRIBE_STATES {
|
|
|
148
147
|
|
|
149
148
|
export const REALTIME_CHANNEL_STATES = CHANNEL_STATES
|
|
150
149
|
|
|
151
|
-
|
|
150
|
+
interface PostgresChangesFilters {
|
|
152
151
|
postgres_changes: {
|
|
153
152
|
id: string
|
|
154
153
|
event: string
|
|
@@ -157,52 +156,30 @@ type PostgresChangesFilters = {
|
|
|
157
156
|
filter?: string
|
|
158
157
|
}[]
|
|
159
158
|
}
|
|
160
|
-
|
|
161
|
-
type Binding = {
|
|
162
|
-
type: string
|
|
163
|
-
filter: { [key: string]: any }
|
|
164
|
-
callback: ChannelBindingCallback
|
|
165
|
-
ref: number
|
|
166
|
-
id?: string
|
|
167
|
-
}
|
|
168
|
-
|
|
169
159
|
/** A channel is the basic building block of Realtime
|
|
170
160
|
* and narrows the scope of data flow to subscribed clients.
|
|
171
161
|
* You can think of a channel as a chatroom where participants are able to see who's online
|
|
172
162
|
* and send and receive messages.
|
|
173
163
|
*/
|
|
174
164
|
export default class RealtimeChannel {
|
|
175
|
-
bindings:
|
|
176
|
-
|
|
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
|
|
177
180
|
broadcastEndpointURL: string
|
|
181
|
+
subTopic: string
|
|
178
182
|
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
|
-
}
|
|
206
183
|
|
|
207
184
|
/**
|
|
208
185
|
* Creates a channel that can broadcast messages, sync presence, and listen to Postgres changes.
|
|
@@ -235,17 +212,53 @@ export default class RealtimeChannel {
|
|
|
235
212
|
},
|
|
236
213
|
...params.config,
|
|
237
214
|
}
|
|
238
|
-
|
|
239
|
-
this.
|
|
240
|
-
this.
|
|
241
|
-
|
|
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
|
+
})
|
|
242
224
|
this._onClose(() => {
|
|
225
|
+
this.rejoinTimer.reset()
|
|
226
|
+
this.socket.log('channel', `close ${this.topic} ${this._joinRef()}`)
|
|
227
|
+
this.state = CHANNEL_STATES.closed
|
|
243
228
|
this.socket._remove(this)
|
|
244
229
|
})
|
|
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
|
+
})
|
|
245
246
|
|
|
246
|
-
this.
|
|
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)
|
|
247
260
|
|
|
248
|
-
this.broadcastEndpointURL = httpEndpointURL(this.socket.
|
|
261
|
+
this.broadcastEndpointURL = httpEndpointURL(this.socket.endPoint)
|
|
249
262
|
this.private = this.params.config.private || false
|
|
250
263
|
|
|
251
264
|
if (!this.private && this.params.config?.broadcast?.replay) {
|
|
@@ -261,7 +274,7 @@ export default class RealtimeChannel {
|
|
|
261
274
|
if (!this.socket.isConnected()) {
|
|
262
275
|
this.socket.connect()
|
|
263
276
|
}
|
|
264
|
-
if (this.
|
|
277
|
+
if (this.state == CHANNEL_STATES.closed) {
|
|
265
278
|
const {
|
|
266
279
|
config: { broadcast, presence, private: isPrivate },
|
|
267
280
|
} = this.params
|
|
@@ -284,18 +297,16 @@ export default class RealtimeChannel {
|
|
|
284
297
|
accessTokenPayload.access_token = this.socket.accessTokenValue
|
|
285
298
|
}
|
|
286
299
|
|
|
287
|
-
this._onError((
|
|
288
|
-
callback?.(REALTIME_SUBSCRIBE_STATES.CHANNEL_ERROR, reason as Error)
|
|
289
|
-
})
|
|
300
|
+
this._onError((e: Error) => callback?.(REALTIME_SUBSCRIBE_STATES.CHANNEL_ERROR, e))
|
|
290
301
|
|
|
291
302
|
this._onClose(() => callback?.(REALTIME_SUBSCRIBE_STATES.CLOSED))
|
|
292
303
|
|
|
293
304
|
this.updateJoinPayload({ ...{ config }, ...accessTokenPayload })
|
|
294
305
|
|
|
295
|
-
this.
|
|
306
|
+
this.joinedOnce = true
|
|
307
|
+
this._rejoin(timeout)
|
|
296
308
|
|
|
297
|
-
this.
|
|
298
|
-
.subscribe(timeout)
|
|
309
|
+
this.joinPush
|
|
299
310
|
.receive('ok', async ({ postgres_changes }: PostgresChangesFilters) => {
|
|
300
311
|
// Only refresh auth if using callback-based tokens
|
|
301
312
|
if (!this.socket._isManualToken()) {
|
|
@@ -304,9 +315,46 @@ export default class RealtimeChannel {
|
|
|
304
315
|
if (postgres_changes === undefined) {
|
|
305
316
|
callback?.(REALTIME_SUBSCRIBE_STATES.SUBSCRIBED)
|
|
306
317
|
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
|
|
307
357
|
}
|
|
308
|
-
|
|
309
|
-
this._updatePostgresBindings(postgres_changes, callback)
|
|
310
358
|
})
|
|
311
359
|
.receive('error', (error: { [key: string]: any }) => {
|
|
312
360
|
this.state = CHANNEL_STATES.errored
|
|
@@ -314,59 +362,16 @@ export default class RealtimeChannel {
|
|
|
314
362
|
REALTIME_SUBSCRIBE_STATES.CHANNEL_ERROR,
|
|
315
363
|
new Error(JSON.stringify(Object.values(error).join(', ') || 'error'))
|
|
316
364
|
)
|
|
365
|
+
return
|
|
317
366
|
})
|
|
318
367
|
.receive('timeout', () => {
|
|
319
368
|
callback?.(REALTIME_SUBSCRIBE_STATES.TIMED_OUT)
|
|
369
|
+
return
|
|
320
370
|
})
|
|
321
371
|
}
|
|
322
372
|
return this
|
|
323
373
|
}
|
|
324
374
|
|
|
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
|
-
|
|
370
375
|
/**
|
|
371
376
|
* Returns the current presence state for this channel.
|
|
372
377
|
*
|
|
@@ -426,11 +431,6 @@ export default class RealtimeChannel {
|
|
|
426
431
|
filter: { event: `${REALTIME_PRESENCE_LISTEN_EVENTS.LEAVE}` },
|
|
427
432
|
callback: (payload: RealtimePresenceLeavePayload<T>) => void
|
|
428
433
|
): 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,9 +534,12 @@ export default class RealtimeChannel {
|
|
|
534
534
|
filter: { event: string; [key: string]: string },
|
|
535
535
|
callback: (payload: any) => void
|
|
536
536
|
): RealtimeChannel {
|
|
537
|
-
if (this.
|
|
538
|
-
this.socket.log(
|
|
539
|
-
|
|
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())
|
|
540
543
|
}
|
|
541
544
|
return this._on(type, filter, callback)
|
|
542
545
|
}
|
|
@@ -621,7 +624,7 @@ export default class RealtimeChannel {
|
|
|
621
624
|
},
|
|
622
625
|
opts: { [key: string]: any } = {}
|
|
623
626
|
): Promise<RealtimeChannelSendResponse> {
|
|
624
|
-
if (!this.
|
|
627
|
+
if (!this._canPush() && args.type === 'broadcast') {
|
|
625
628
|
console.warn(
|
|
626
629
|
'Realtime send() is automatically falling back to REST API. ' +
|
|
627
630
|
'This behavior will be deprecated in the future. ' +
|
|
@@ -671,7 +674,7 @@ export default class RealtimeChannel {
|
|
|
671
674
|
}
|
|
672
675
|
} else {
|
|
673
676
|
return new Promise((resolve) => {
|
|
674
|
-
const push = this.
|
|
677
|
+
const push = this._push(args.type, args, opts.timeout || this.timeout)
|
|
675
678
|
|
|
676
679
|
if (args.type === 'broadcast' && !this.params?.config?.broadcast?.ack) {
|
|
677
680
|
resolve('ok')
|
|
@@ -688,8 +691,8 @@ export default class RealtimeChannel {
|
|
|
688
691
|
* Updates the payload that will be sent the next time the channel joins (reconnects).
|
|
689
692
|
* Useful for rotating access tokens or updating config without re-creating the channel.
|
|
690
693
|
*/
|
|
691
|
-
updateJoinPayload(payload:
|
|
692
|
-
this.
|
|
694
|
+
updateJoinPayload(payload: { [key: string]: any }): void {
|
|
695
|
+
this.joinPush.updatePayload(payload)
|
|
693
696
|
}
|
|
694
697
|
|
|
695
698
|
/**
|
|
@@ -701,24 +704,56 @@ export default class RealtimeChannel {
|
|
|
701
704
|
* To receive leave acknowledgements, use the a `receive` hook to bind to the server ack, ie:
|
|
702
705
|
* channel.unsubscribe().receive("ok", () => alert("left!") )
|
|
703
706
|
*/
|
|
704
|
-
|
|
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
|
+
|
|
705
718
|
return new Promise<RealtimeChannelSendResponse>((resolve) => {
|
|
706
|
-
this.
|
|
707
|
-
|
|
708
|
-
.receive('ok', () =>
|
|
709
|
-
|
|
710
|
-
|
|
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()
|
|
711
739
|
})
|
|
712
740
|
}
|
|
713
|
-
|
|
714
741
|
/**
|
|
742
|
+
* Teardown the channel.
|
|
743
|
+
*
|
|
715
744
|
* Destroys and stops related timers.
|
|
716
745
|
*/
|
|
717
746
|
teardown() {
|
|
718
|
-
this.
|
|
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 = {}
|
|
719
753
|
}
|
|
720
754
|
|
|
721
755
|
/** @internal */
|
|
756
|
+
|
|
722
757
|
async _fetchWithTimeout(url: string, options: { [key: string]: any }, timeout: number) {
|
|
723
758
|
const controller = new AbortController()
|
|
724
759
|
const id = setTimeout(() => controller.abort(), timeout)
|
|
@@ -734,112 +769,195 @@ export default class RealtimeChannel {
|
|
|
734
769
|
}
|
|
735
770
|
|
|
736
771
|
/** @internal */
|
|
737
|
-
|
|
738
|
-
|
|
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,
|
|
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`
|
|
747
775
|
}
|
|
748
|
-
|
|
749
|
-
if (this.
|
|
750
|
-
|
|
776
|
+
let pushEvent = new Push(this, event, payload, timeout)
|
|
777
|
+
if (this._canPush()) {
|
|
778
|
+
pushEvent.send()
|
|
751
779
|
} else {
|
|
752
|
-
this.
|
|
780
|
+
this._addToPushBuffer(pushEvent)
|
|
753
781
|
}
|
|
754
782
|
|
|
755
|
-
|
|
783
|
+
return pushEvent
|
|
784
|
+
}
|
|
756
785
|
|
|
757
|
-
|
|
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
|
+
}
|
|
758
803
|
}
|
|
759
804
|
|
|
760
805
|
/**
|
|
761
|
-
*
|
|
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.
|
|
762
810
|
*
|
|
763
811
|
* @internal
|
|
764
812
|
*/
|
|
765
|
-
|
|
766
|
-
|
|
813
|
+
_onMessage(_event: string, payload: any, _ref?: string) {
|
|
814
|
+
return payload
|
|
767
815
|
}
|
|
768
816
|
|
|
769
|
-
/**
|
|
770
|
-
|
|
771
|
-
|
|
772
|
-
* @internal
|
|
773
|
-
*/
|
|
774
|
-
private _onError(callback: ChannelOnErrorCallback) {
|
|
775
|
-
this.channelAdapter.onError(callback)
|
|
817
|
+
/** @internal */
|
|
818
|
+
_isMember(topic: string): boolean {
|
|
819
|
+
return this.topic === topic
|
|
776
820
|
}
|
|
777
821
|
|
|
778
822
|
/** @internal */
|
|
779
|
-
|
|
780
|
-
this.
|
|
781
|
-
|
|
823
|
+
_joinRef(): string {
|
|
824
|
+
return this.joinPush.ref
|
|
825
|
+
}
|
|
782
826
|
|
|
783
|
-
|
|
784
|
-
|
|
785
|
-
|
|
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
|
+
}
|
|
786
839
|
|
|
787
|
-
|
|
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
|
+
}
|
|
788
889
|
|
|
789
|
-
|
|
790
|
-
|
|
791
|
-
|
|
890
|
+
/** @internal */
|
|
891
|
+
_isClosed(): boolean {
|
|
892
|
+
return this.state === CHANNEL_STATES.closed
|
|
893
|
+
}
|
|
792
894
|
|
|
793
|
-
|
|
794
|
-
|
|
795
|
-
|
|
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
|
-
})
|
|
895
|
+
/** @internal */
|
|
896
|
+
_isJoined(): boolean {
|
|
897
|
+
return this.state === CHANNEL_STATES.joined
|
|
811
898
|
}
|
|
812
899
|
|
|
813
900
|
/** @internal */
|
|
814
|
-
|
|
815
|
-
|
|
816
|
-
const events: string[] = [close, error, leave, join]
|
|
817
|
-
return ref && events.includes(event) && ref !== this.joinPush.ref
|
|
901
|
+
_isJoining(): boolean {
|
|
902
|
+
return this.state === CHANNEL_STATES.joining
|
|
818
903
|
}
|
|
819
904
|
|
|
820
905
|
/** @internal */
|
|
821
|
-
|
|
822
|
-
this.
|
|
823
|
-
|
|
824
|
-
|
|
825
|
-
|
|
826
|
-
|
|
827
|
-
|
|
828
|
-
|
|
829
|
-
|
|
830
|
-
|
|
831
|
-
|
|
832
|
-
|
|
833
|
-
|
|
834
|
-
|
|
835
|
-
|
|
836
|
-
|
|
837
|
-
|
|
838
|
-
|
|
906
|
+
_isLeaving(): boolean {
|
|
907
|
+
return this.state === CHANNEL_STATES.leaving
|
|
908
|
+
}
|
|
909
|
+
|
|
910
|
+
/** @internal */
|
|
911
|
+
_replyEventName(ref: string): string {
|
|
912
|
+
return `chan_reply_${ref}`
|
|
913
|
+
}
|
|
914
|
+
|
|
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
|
+
}
|
|
923
|
+
|
|
924
|
+
if (this.bindings[typeLower]) {
|
|
925
|
+
this.bindings[typeLower].push(binding)
|
|
926
|
+
} else {
|
|
927
|
+
this.bindings[typeLower] = [binding]
|
|
928
|
+
}
|
|
929
|
+
|
|
930
|
+
return this
|
|
931
|
+
}
|
|
932
|
+
|
|
933
|
+
/** @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
|
|
946
|
+
}
|
|
947
|
+
|
|
948
|
+
/** @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
|
|
839
957
|
}
|
|
958
|
+
}
|
|
840
959
|
|
|
841
|
-
|
|
842
|
-
})
|
|
960
|
+
return true
|
|
843
961
|
}
|
|
844
962
|
|
|
845
963
|
/**
|
|
@@ -856,6 +974,51 @@ export default class RealtimeChannel {
|
|
|
856
974
|
return normalizedServer === normalizedClient
|
|
857
975
|
}
|
|
858
976
|
|
|
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
|
+
|
|
859
1022
|
/** @internal */
|
|
860
1023
|
private _getPayloadRecords(payload: any) {
|
|
861
1024
|
const records = {
|