Skip to content

Commit 1981e97

Browse files
authored
fix: Set auth on connect and reconnect flows (#497)
* fix: Set auth on connect and reconnect flows * remove from timer as we are already setAuth on connect() * handle exceptions on setAuth errors
1 parent 613b990 commit 1981e97

File tree

3 files changed

+88
-16
lines changed

3 files changed

+88
-16
lines changed

src/RealtimeClient.ts

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -216,6 +216,12 @@ export default class RealtimeClient {
216216
}
217217
this.conn = new this.transport(this.endpointURL()) as WebSocketLike
218218
this.setupConnection()
219+
// Set the token on connect
220+
setTimeout(() => {
221+
this.setAuth().catch((e) => {
222+
this.log('error', 'error setting auth', e)
223+
})
224+
}, 0)
219225
}
220226

221227
/**

test/RealtimeChannel.test.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1017,7 +1017,7 @@ describe('on', () => {
10171017

10181018
channel.on('presence', { event: 'join' }, sinon.spy())
10191019

1020-
await new Promise((resolve) => setTimeout(resolve, 3000))
1020+
await new Promise((resolve) => setTimeout(resolve, 100))
10211021

10221022
assert.deepEqual(channel.joinPush.payload, {
10231023
config: {

test/RealtimeClient.test.ts

Lines changed: 81 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -131,6 +131,35 @@ describe('connect with WebSocket', () => {
131131
socket.connect()
132132
assert.deepStrictEqual(conn, socket.conn)
133133
})
134+
135+
test('handles setAuth errors gracefully during connection', async () => {
136+
const errorMessage = 'Token fetch failed'
137+
const accessToken = sinon.spy(() => Promise.reject(new Error(errorMessage)))
138+
const logSpy = sinon.spy()
139+
140+
const socketWithError = new RealtimeClient(url, {
141+
transport: MockWebSocket,
142+
accessToken,
143+
logger: logSpy,
144+
})
145+
146+
socketWithError.connect()
147+
148+
await new Promise((resolve) => setTimeout(() => resolve(undefined), 100))
149+
150+
// Verify that the error was logged
151+
assert.ok(
152+
logSpy.calledWith(
153+
'error',
154+
'error setting auth',
155+
sinon.match.instanceOf(Error)
156+
)
157+
)
158+
159+
// Verify that the connection was still established despite the error
160+
assert.ok(socketWithError.conn, 'connection should still exist')
161+
assert.equal(socketWithError.conn!.url, socketWithError.endpointURL())
162+
})
134163
})
135164

136165
describe('disconnect', () => {
@@ -210,7 +239,7 @@ describe('channel', () => {
210239
let channel
211240

212241
test('returns channel with given topic and params', () => {
213-
channel = socket.channel('topic', { one: 'two' })
242+
channel = socket.channel('topic')
214243

215244
assert.deepStrictEqual(channel.socket, socket)
216245
assert.equal(channel.topic, 'realtime:topic')
@@ -220,12 +249,11 @@ describe('channel', () => {
220249
presence: { key: '', enabled: false },
221250
private: false,
222251
},
223-
one: 'two',
224252
})
225253
})
226254

227255
test('returns channel with given topic and params for a private channel', () => {
228-
channel = socket.channel('topic', { config: { private: true }, one: 'two' })
256+
channel = socket.channel('topic', { config: { private: true } })
229257

230258
assert.deepStrictEqual(channel.socket, socket)
231259
assert.equal(channel.topic, 'realtime:topic')
@@ -235,7 +263,6 @@ describe('channel', () => {
235263
presence: { key: '', enabled: false },
236264
private: true,
237265
},
238-
one: 'two',
239266
})
240267
})
241268

@@ -540,7 +567,7 @@ describe('setAuth', () => {
540567
let new_token = generateJWT('1h')
541568
let new_socket = new RealtimeClient(url, {
542569
transport: MockWebSocket,
543-
accessToken: () => Promise.resolve(token),
570+
accessToken: () => Promise.resolve(new_token),
544571
})
545572

546573
const channel1 = new_socket.channel('test-topic1')
@@ -725,15 +752,18 @@ describe('flushSendBuffer', () => {
725752
})
726753
})
727754

728-
describe('_onConnClose', () => {
729-
beforeEach(() => {
730-
socket.connect()
731-
})
755+
describe('socket close event', () => {
756+
beforeEach(() => socket.connect())
732757

733758
test('schedules reconnectTimer timeout', () => {
734759
const spy = sinon.spy(socket.reconnectTimer, 'scheduleTimeout')
735760

736-
socket._onConnClose()
761+
const closeEvent = new CloseEvent('close', {
762+
code: 1000,
763+
reason: '',
764+
wasClean: true,
765+
})
766+
socket.conn?.onclose?.(closeEvent)
737767

738768
assert.ok(spy.calledOnce)
739769
})
@@ -742,10 +772,39 @@ describe('_onConnClose', () => {
742772
const channel = socket.channel('topic')
743773
const spy = sinon.spy(channel, '_trigger')
744774

745-
socket._onConnClose()
775+
const closeEvent = new CloseEvent('close', {
776+
code: 1000,
777+
reason: '',
778+
wasClean: true,
779+
})
780+
socket.conn?.onclose?.(closeEvent)
746781

747782
assert.ok(spy.calledWith('phx_error'))
748783
})
784+
785+
test('should use a new token after reconnect', async () => {
786+
const tokens = ['initial-token', 'refreshed-token']
787+
788+
let callCount = 0
789+
const accessToken = sinon.spy(() => Promise.resolve(tokens[callCount++]))
790+
791+
const socket = new RealtimeClient(url, {
792+
transport: MockWebSocket,
793+
accessToken,
794+
})
795+
socket.connect()
796+
797+
// Wait for the async setAuth call to complete
798+
await new Promise((resolve) => setTimeout(resolve, 100))
799+
assert.strictEqual(accessToken.callCount, 1)
800+
expect(socket.accessTokenValue).toBe(tokens[0])
801+
802+
// Call the callback and wait for async operations to complete
803+
await socket.reconnectTimer.callback()
804+
await new Promise((resolve) => setTimeout(resolve, 100))
805+
expect(socket.accessTokenValue).toBe(tokens[1])
806+
assert.strictEqual(accessToken.callCount, 2)
807+
})
749808
})
750809

751810
describe('_onConnError', () => {
@@ -757,7 +816,7 @@ describe('_onConnError', () => {
757816
const channel = socket.channel('topic')
758817
const spy = sinon.spy(channel, '_trigger')
759818

760-
socket._onConnError('error')
819+
socket.conn?.onerror?.(new Event('error'))
761820

762821
assert.ok(spy.calledWith('phx_error'))
763822
})
@@ -780,7 +839,8 @@ describe('onConnMessage', () => {
780839
const otherSpy = sinon.spy(otherChannel, '_trigger')
781840

782841
socket.pendingHeartbeatRef = '3'
783-
socket._onConnMessage(data)
842+
const messageEvent = new MessageEvent('message', { data: message })
843+
socket.conn?.onmessage?.(messageEvent)
784844

785845
// assert.ok(targetSpy.calledWith('INSERT', {type: 'INSERT'}, 'ref'))
786846
assert.strictEqual(targetSpy.callCount, 1)
@@ -792,12 +852,15 @@ describe('onConnMessage', () => {
792852
let socket = new RealtimeClient(url)
793853
socket.onHeartbeat((message: HeartbeatStatus) => (called = message == 'ok'))
794854

855+
socket.connect()
856+
795857
const message =
796858
'{"ref":"1","event":"phx_reply","payload":{"status":"ok","response":{}},"topic":"phoenix"}'
797859
const data = { data: message }
798860

799861
socket.pendingHeartbeatRef = '3'
800-
socket._onConnMessage(data)
862+
const messageEvent = new MessageEvent('message', { data: message })
863+
socket.conn?.onmessage?.(messageEvent)
801864

802865
assert.strictEqual(called, true)
803866
})
@@ -808,12 +871,15 @@ describe('onConnMessage', () => {
808871
(message: HeartbeatStatus) => (called = message == 'error')
809872
)
810873

874+
socket.connect()
875+
811876
const message =
812877
'{"ref":"1","event":"phx_reply","payload":{"status":"error","response":{}},"topic":"phoenix"}'
813878
const data = { data: message }
814879

815880
socket.pendingHeartbeatRef = '3'
816-
socket._onConnMessage(data)
881+
const messageEvent = new MessageEvent('message', { data: message })
882+
socket.conn?.onmessage?.(messageEvent)
817883

818884
assert.strictEqual(called, true)
819885
})

0 commit comments

Comments
 (0)