Skip to content
Open
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 2 additions & 5 deletions packages/core/supabase-js/src/SupabaseClient.ts
Original file line number Diff line number Diff line change
Expand Up @@ -370,11 +370,8 @@ export default class SupabaseClient<
...settings.realtime,
})
if (this.accessToken) {
// Start auth immediately to avoid race condition with channel subscriptions
// Wrap Promise to avoid Firefox extension cross-context Promise access errors
Promise.resolve(this.accessToken())
.then((token) => this.realtime.setAuth(token))
.catch((e) => console.warn('Failed to set initial Realtime auth token:', e))
// Start auth immediately to avoid a race with channel subscriptions
this.realtime.setAuth()
}

this.rest = new PostgrestClient(new URL('rest/v1', baseUrl).href, {
Expand Down
26 changes: 11 additions & 15 deletions packages/core/supabase-js/test/unit/SupabaseClient.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -263,21 +263,21 @@ describe('SupabaseClient', () => {
jest.clearAllMocks()
})

test('should automatically call setAuth() when accessToken option is provided', async () => {
test('should automatically call setAuth() in callback mode when accessToken option is provided', async () => {
const customToken = 'custom-jwt-token'
const customAccessTokenFn = jest.fn().mockResolvedValue(customToken)

const client = createClient(URL, KEY, { accessToken: customAccessTokenFn })
const setAuthSpy = jest.spyOn(client.realtime, 'setAuth')

// Wait for the constructor's async operation to complete
await Promise.resolve()
// Flush microtask queue to wait for constructor setting auth
await new Promise((resolve) => setTimeout(resolve))

expect(setAuthSpy).toHaveBeenCalledWith(customToken)
// Realtime acquires auth token without dropping to manual mode
expect(customAccessTokenFn).toHaveBeenCalled()
expect(client.realtime.accessTokenValue).toBe(customToken)
expect((client.realtime as any)._isManualToken()).toBe(false)

// Clean up
setAuthSpy.mockRestore()
client.realtime.disconnect()
})

Expand All @@ -296,24 +296,20 @@ describe('SupabaseClient', () => {
})

test('should handle errors gracefully when accessToken callback fails', async () => {
const consoleWarnSpy = jest.spyOn(console, 'warn').mockImplementation(() => {})
const error = new Error('Token fetch failed')
const failingAccessTokenFn = jest.fn().mockRejectedValue(error)

const client = createClient(URL, KEY, { accessToken: failingAccessTokenFn })

// Wait for the promise to reject and warning to be logged
await Promise.resolve()
await Promise.resolve()
// Flush microtask queue to wait for constructor setting auth
await new Promise((resolve) => setTimeout(resolve))

expect(consoleWarnSpy).toHaveBeenCalledWith(
'Failed to set initial Realtime auth token:',
error
)
expect(failingAccessTokenFn).toHaveBeenCalled()
expect(client).toBeDefined()
expect(client.realtime).toBeDefined()
// Stays in callback mode so a later setAuth() retries the callback
expect((client.realtime as any)._isManualToken()).toBe(false)

consoleWarnSpy.mockRestore()
client.realtime.disconnect()
})

Expand Down