Skip to content
This repository was archived by the owner on Oct 9, 2025. It is now read-only.

Commit e70fa7e

Browse files
feat: Implement token callback; fix CI testing (#439)
setAuth will automatically use a accessToken callback to fetch the token at the right time instead of assuming that the right token is present --------- Co-authored-by: Kamil Ogórek <[email protected]>
1 parent 9c8a5d3 commit e70fa7e

File tree

7 files changed

+465
-688
lines changed

7 files changed

+465
-688
lines changed

package-lock.json

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -52,7 +52,7 @@
5252
"jsdom": "^16.7.0",
5353
"jsdom-global": "3.0.0",
5454
"jsonwebtoken": "^9.0.2",
55-
"mock-socket": "^9.0.3",
55+
"mock-socket": "^9.3.1",
5656
"npm-run-all": "^4.1.5",
5757
"nyc": "^15.1.0",
5858
"prettier": "^2.1.2",

src/RealtimeChannel.ts

Lines changed: 70 additions & 78 deletions
Original file line numberDiff line numberDiff line change
@@ -110,6 +110,15 @@ export enum REALTIME_SUBSCRIBE_STATES {
110110

111111
export const REALTIME_CHANNEL_STATES = CHANNEL_STATES
112112

113+
interface PostgresChangesFilters {
114+
postgres_changes: {
115+
id: string
116+
event: string
117+
schema?: string
118+
table?: string
119+
filter?: string
120+
}[]
121+
}
113122
/** A channel is the basic building block of Realtime
114123
* and narrows the scope of data flow to subscribed clients.
115124
* You can think of a channel as a chatroom where participants are able to see who's online
@@ -202,21 +211,23 @@ export default class RealtimeChannel {
202211

203212
/** Subscribe registers your client with the server */
204213
subscribe(
205-
callback?: (status: `${REALTIME_SUBSCRIBE_STATES}`, err?: Error) => void,
214+
callback?: (status: REALTIME_SUBSCRIBE_STATES, err?: Error) => void,
206215
timeout = this.timeout
207216
): RealtimeChannel {
208217
if (!this.socket.isConnected()) {
209218
this.socket.connect()
210219
}
211-
212220
if (this.joinedOnce) {
213221
throw `tried to subscribe multiple times. 'subscribe' can only be called a single time per channel instance`
214222
} else {
215223
const {
216224
config: { broadcast, presence, private: isPrivate },
217225
} = this.params
218-
this._onError((e: Error) => callback && callback('CHANNEL_ERROR', e))
219-
this._onClose(() => callback && callback('CLOSED'))
226+
227+
this._onError((e: Error) =>
228+
callback?.(REALTIME_SUBSCRIBE_STATES.CHANNEL_ERROR, e)
229+
)
230+
this._onClose(() => callback?.(REALTIME_SUBSCRIBE_STATES.CLOSED))
220231

221232
const accessTokenPayload: { access_token?: string } = {}
222233
const config = {
@@ -227,8 +238,8 @@ export default class RealtimeChannel {
227238
private: isPrivate,
228239
}
229240

230-
if (this.socket.accessToken) {
231-
accessTokenPayload.access_token = this.socket.accessToken
241+
if (this.socket.accessTokenValue) {
242+
accessTokenPayload.access_token = this.socket.accessTokenValue
232243
}
233244

234245
this.updateJoinPayload({ ...{ config }, ...accessTokenPayload })
@@ -237,85 +248,67 @@ export default class RealtimeChannel {
237248
this._rejoin(timeout)
238249

239250
this.joinPush
240-
.receive(
241-
'ok',
242-
({
243-
postgres_changes: serverPostgresFilters,
244-
}: {
245-
postgres_changes: {
246-
id: string
247-
event: string
248-
schema?: string
249-
table?: string
250-
filter?: string
251-
}[]
252-
}) => {
253-
this.socket.accessToken &&
254-
this.socket.setAuth(this.socket.accessToken)
255-
256-
if (serverPostgresFilters === undefined) {
257-
callback && callback('SUBSCRIBED')
258-
return
259-
} else {
260-
const clientPostgresBindings = this.bindings.postgres_changes
261-
const bindingsLen = clientPostgresBindings?.length ?? 0
262-
const newPostgresBindings = []
263-
264-
for (let i = 0; i < bindingsLen; i++) {
265-
const clientPostgresBinding = clientPostgresBindings[i]
266-
const {
267-
filter: { event, schema, table, filter },
268-
} = clientPostgresBinding
269-
const serverPostgresFilter =
270-
serverPostgresFilters && serverPostgresFilters[i]
271-
272-
if (
273-
serverPostgresFilter &&
274-
serverPostgresFilter.event === event &&
275-
serverPostgresFilter.schema === schema &&
276-
serverPostgresFilter.table === table &&
277-
serverPostgresFilter.filter === filter
278-
) {
279-
newPostgresBindings.push({
280-
...clientPostgresBinding,
281-
id: serverPostgresFilter.id,
282-
})
283-
} else {
284-
this.unsubscribe()
285-
callback &&
286-
callback(
287-
'CHANNEL_ERROR',
288-
new Error(
289-
'mismatch between server and client bindings for postgres changes'
290-
)
291-
)
292-
return
293-
}
251+
.receive('ok', async ({ postgres_changes }: PostgresChangesFilters) => {
252+
this.socket.setAuth()
253+
if (postgres_changes === undefined) {
254+
callback?.(REALTIME_SUBSCRIBE_STATES.SUBSCRIBED)
255+
return
256+
} else {
257+
const clientPostgresBindings = this.bindings.postgres_changes
258+
const bindingsLen = clientPostgresBindings?.length ?? 0
259+
const newPostgresBindings = []
260+
261+
for (let i = 0; i < bindingsLen; i++) {
262+
const clientPostgresBinding = clientPostgresBindings[i]
263+
const {
264+
filter: { event, schema, table, filter },
265+
} = clientPostgresBinding
266+
const serverPostgresFilter =
267+
postgres_changes && postgres_changes[i]
268+
269+
if (
270+
serverPostgresFilter &&
271+
serverPostgresFilter.event === event &&
272+
serverPostgresFilter.schema === schema &&
273+
serverPostgresFilter.table === table &&
274+
serverPostgresFilter.filter === filter
275+
) {
276+
newPostgresBindings.push({
277+
...clientPostgresBinding,
278+
id: serverPostgresFilter.id,
279+
})
280+
} else {
281+
this.unsubscribe()
282+
callback?.(
283+
REALTIME_SUBSCRIBE_STATES.CHANNEL_ERROR,
284+
new Error(
285+
'mismatch between server and client bindings for postgres changes'
286+
)
287+
)
288+
return
294289
}
290+
}
295291

296-
this.bindings.postgres_changes = newPostgresBindings
292+
this.bindings.postgres_changes = newPostgresBindings
297293

298-
callback && callback('SUBSCRIBED')
299-
return
300-
}
294+
callback && callback(REALTIME_SUBSCRIBE_STATES.SUBSCRIBED)
295+
return
301296
}
302-
)
297+
})
303298
.receive('error', (error: { [key: string]: any }) => {
304-
callback &&
305-
callback(
306-
'CHANNEL_ERROR',
307-
new Error(
308-
JSON.stringify(Object.values(error).join(', ') || 'error')
309-
)
299+
callback?.(
300+
REALTIME_SUBSCRIBE_STATES.CHANNEL_ERROR,
301+
new Error(
302+
JSON.stringify(Object.values(error).join(', ') || 'error')
310303
)
304+
)
311305
return
312306
})
313307
.receive('timeout', () => {
314-
callback && callback('TIMED_OUT')
308+
callback?.(REALTIME_SUBSCRIBE_STATES.TIMED_OUT)
315309
return
316310
})
317311
}
318-
319312
return this
320313
}
321314

@@ -445,12 +438,13 @@ export default class RealtimeChannel {
445438
): Promise<RealtimeChannelSendResponse> {
446439
if (!this._canPush() && args.type === 'broadcast') {
447440
const { event, payload: endpoint_payload } = args
441+
const authorization = this.socket.accessTokenValue
442+
? `Bearer ${this.socket.accessTokenValue}`
443+
: ''
448444
const options = {
449445
method: 'POST',
450446
headers: {
451-
Authorization: this.socket.accessToken
452-
? `Bearer ${this.socket.accessToken}`
453-
: '',
447+
Authorization: authorization,
454448
apikey: this.socket.apiKey ? this.socket.apiKey : '',
455449
'Content-Type': 'application/json',
456450
},
@@ -523,7 +517,6 @@ export default class RealtimeChannel {
523517

524518
return new Promise((resolve) => {
525519
const leavePush = new Push(this, CHANNEL_EVENTS.leave, {}, timeout)
526-
527520
leavePush
528521
.receive('ok', () => {
529522
onClose()
@@ -538,7 +531,6 @@ export default class RealtimeChannel {
538531
})
539532

540533
leavePush.send()
541-
542534
if (!this._canPush()) {
543535
leavePush.trigger('ok', {})
544536
}

0 commit comments

Comments
 (0)