Skip to content

Commit 3d3193f

Browse files
committed
fix(auth): only warn if multiple clients share a storage-key
Allow use of multiple clients with different storage-keys, without the `Multiple GoTrueClient instances detected in the same browser context. It is not an error, but this should be avoided as it may produce undefined behavior when used concurrently under the same storage key.` warning appearing. supabase/auth-js#725 (comment)
1 parent 30d8caa commit 3d3193f

File tree

3 files changed

+129
-17
lines changed

3 files changed

+129
-17
lines changed

packages/core/auth-js/src/GoTrueClient.ts

Lines changed: 21 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -183,7 +183,7 @@ async function lockNoOp<R>(name: string, acquireTimeout: number, fn: () => Promi
183183
const GLOBAL_JWKS: { [storageKey: string]: { cachedAt: number; jwks: { keys: JWK[] } } } = {}
184184

185185
export default class GoTrueClient {
186-
private static nextInstanceID = 0
186+
private static nextInstanceID: Record<string, number> = {}
187187

188188
private instanceID: number
189189

@@ -265,24 +265,26 @@ export default class GoTrueClient {
265265
* Create a new client for use in the browser.
266266
*/
267267
constructor(options: GoTrueClientOptions) {
268-
this.instanceID = GoTrueClient.nextInstanceID
269-
GoTrueClient.nextInstanceID += 1
270-
271-
if (this.instanceID > 0 && isBrowser()) {
272-
console.warn(
273-
'Multiple GoTrueClient instances detected in the same browser context. It is not an error, but this should be avoided as it may produce undefined behavior when used concurrently under the same storage key.'
274-
)
275-
}
276-
277268
const settings = { ...DEFAULT_OPTIONS, ...options }
269+
this.storageKey = settings.storageKey
270+
271+
this.instanceID = GoTrueClient.nextInstanceID[this.storageKey] ?? 0
272+
GoTrueClient.nextInstanceID[this.storageKey] = this.instanceID + 1
278273

279274
this.logDebugMessages = !!settings.debug
280275
if (typeof settings.debug === 'function') {
281276
this.logger = settings.debug
282277
}
283278

279+
if (this.instanceID > 0 && isBrowser()) {
280+
const message = `${this._logPrefix()} Multiple GoTrueClient instances detected in the same browser context. It is not an error, but this should be avoided as it may produce undefined behavior when used concurrently under the same storage key.`
281+
console.warn(message)
282+
if (this.logDebugMessages) {
283+
console.trace(message)
284+
}
285+
}
286+
284287
this.persistSession = settings.persistSession
285-
this.storageKey = settings.storageKey
286288
this.autoRefreshToken = settings.autoRefreshToken
287289
this.admin = new GoTrueAdminApi({
288290
url: settings.url,
@@ -362,12 +364,16 @@ export default class GoTrueClient {
362364
this.initialize()
363365
}
364366

367+
private _logPrefix(): string {
368+
return (
369+
'GoTrueClient@' +
370+
`${this.storageKey}:${this.instanceID} (${version}) ${new Date().toISOString()}`
371+
)
372+
}
373+
365374
private _debug(...args: any[]): GoTrueClient {
366375
if (this.logDebugMessages) {
367-
this.logger(
368-
`GoTrueClient@${this.instanceID} (${version}) ${new Date().toISOString()}`,
369-
...args
370-
)
376+
this.logger(this._logPrefix(), ...args)
371377
}
372378

373379
return this

packages/core/auth-js/test/GoTrueClient.browser.test.ts

Lines changed: 94 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,12 @@
22
* @jest-environment jsdom
33
*/
44

5-
import { autoRefreshClient, getClientWithSpecificStorage, pkceClient } from './lib/clients'
5+
import {
6+
autoRefreshClient,
7+
getClientWithSpecificStorage,
8+
getClientWithSpecificStorageKey,
9+
pkceClient,
10+
} from './lib/clients'
611
import { mockUserCredentials } from './lib/utils'
712
import {
813
supportsLocalStorage,
@@ -174,6 +179,94 @@ describe('Fetch resolution in browser environment', () => {
174179
const resolvedFetch = resolveFetch(customFetch)
175180
expect(typeof resolvedFetch).toBe('function')
176181
})
182+
183+
it('should warn when two clients are created with the same storage key', () => {
184+
let consoleWarnSpy
185+
let consoleTraceSpy
186+
try {
187+
consoleWarnSpy = jest.spyOn(console, 'warn')
188+
consoleTraceSpy = jest.spyOn(console, 'trace')
189+
getClientWithSpecificStorageKey('same-storage-key')
190+
getClientWithSpecificStorageKey('same-storage-key')
191+
expect(consoleWarnSpy).toHaveBeenCalledTimes(1)
192+
expect(consoleWarnSpy).toHaveBeenCalledWith(
193+
expect.stringMatching(
194+
/GoTrueClient@same-storage-key:1 .* Multiple GoTrueClient instances detected in the same browser context. It is not an error, but this should be avoided as it may produce undefined behavior when used concurrently under the same storage key./
195+
)
196+
)
197+
expect(consoleTraceSpy).not.toHaveBeenCalled()
198+
} finally {
199+
consoleWarnSpy?.mockRestore()
200+
consoleTraceSpy?.mockRestore()
201+
}
202+
})
203+
204+
it('should warn & trace when two clients are created with the same storage key and debug is enabled', () => {
205+
let consoleWarnSpy
206+
let consoleTraceSpy
207+
try {
208+
consoleWarnSpy = jest.spyOn(console, 'warn')
209+
consoleTraceSpy = jest.spyOn(console, 'trace')
210+
getClientWithSpecificStorageKey('identical-storage-key')
211+
getClientWithSpecificStorageKey('identical-storage-key', { debug: true })
212+
expect(consoleWarnSpy).toHaveBeenCalledTimes(1)
213+
expect(consoleWarnSpy).toHaveBeenCalledWith(
214+
expect.stringMatching(
215+
/GoTrueClient@identical-storage-key:1 .* Multiple GoTrueClient instances detected in the same browser context. It is not an error, but this should be avoided as it may produce undefined behavior when used concurrently under the same storage key./
216+
)
217+
)
218+
expect(consoleTraceSpy).toHaveBeenCalledWith(
219+
expect.stringMatching(
220+
/GoTrueClient@identical-storage-key:1 .* Multiple GoTrueClient instances detected in the same browser context. It is not an error, but this should be avoided as it may produce undefined behavior when used concurrently under the same storage key./
221+
)
222+
)
223+
} finally {
224+
consoleWarnSpy?.mockRestore()
225+
consoleTraceSpy?.mockRestore()
226+
}
227+
})
228+
229+
it('should not warn when two clients are created with differing storage keys', () => {
230+
let consoleWarnSpy
231+
let consoleTraceSpy
232+
try {
233+
consoleWarnSpy = jest.spyOn(console, 'warn')
234+
consoleTraceSpy = jest.spyOn(console, 'trace')
235+
getClientWithSpecificStorageKey('first-storage-key')
236+
getClientWithSpecificStorageKey('second-storage-key')
237+
expect(consoleWarnSpy).not.toHaveBeenCalled()
238+
expect(consoleTraceSpy).not.toHaveBeenCalled()
239+
} finally {
240+
consoleWarnSpy?.mockRestore()
241+
consoleTraceSpy?.mockRestore()
242+
}
243+
})
244+
245+
it('should warn only when a second client with a duplicate key is created', () => {
246+
let consoleWarnSpy
247+
let consoleTraceSpy
248+
try {
249+
consoleWarnSpy = jest.spyOn(console, 'warn')
250+
consoleTraceSpy = jest.spyOn(console, 'trace')
251+
getClientWithSpecificStorageKey('test-storage-key1')
252+
expect(consoleWarnSpy).not.toHaveBeenCalled()
253+
getClientWithSpecificStorageKey('test-storage-key2')
254+
expect(consoleWarnSpy).not.toHaveBeenCalled()
255+
getClientWithSpecificStorageKey('test-storage-key3')
256+
expect(consoleWarnSpy).not.toHaveBeenCalled()
257+
getClientWithSpecificStorageKey('test-storage-key2')
258+
expect(consoleWarnSpy).toHaveBeenCalledTimes(1)
259+
expect(consoleWarnSpy).toHaveBeenCalledWith(
260+
expect.stringMatching(
261+
/GoTrueClient@test-storage-key2:1 .* Multiple GoTrueClient instances detected in the same browser context. It is not an error, but this should be avoided as it may produce undefined behavior when used concurrently under the same storage key./
262+
)
263+
)
264+
expect(consoleTraceSpy).not.toHaveBeenCalled()
265+
} finally {
266+
consoleWarnSpy?.mockRestore()
267+
consoleTraceSpy?.mockRestore()
268+
}
269+
})
177270
})
178271

179272
describe('Callback URL handling', () => {

packages/core/auth-js/test/lib/clients.ts

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import jwt from 'jsonwebtoken'
2-
import { GoTrueAdminApi, GoTrueClient } from '../../src/index'
2+
import { GoTrueAdminApi, GoTrueClient, type GoTrueClientOptions } from '../../src/index'
33
import { SupportedStorage } from '../../src/lib/types'
44

55
export const SIGNUP_ENABLED_AUTO_CONFIRM_OFF_PORT = 9999
@@ -156,3 +156,16 @@ export function getClientWithSpecificStorage(storage: SupportedStorage) {
156156
storage,
157157
})
158158
}
159+
160+
export function getClientWithSpecificStorageKey(
161+
storageKey: string,
162+
opts: GoTrueClientOptions = {}
163+
) {
164+
return new GoTrueClient({
165+
url: GOTRUE_URL_SIGNUP_ENABLED_AUTO_CONFIRM_ON,
166+
autoRefreshToken: false,
167+
persistSession: true,
168+
storageKey,
169+
...opts,
170+
})
171+
}

0 commit comments

Comments
 (0)