Skip to content

Commit d5f54f0

Browse files
authored
fix(auth): only warn if multiple clients share a storage-key (#1767)
1 parent 2981692 commit d5f54f0

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
@@ -188,7 +188,7 @@ async function lockNoOp<R>(name: string, acquireTimeout: number, fn: () => Promi
188188
const GLOBAL_JWKS: { [storageKey: string]: { cachedAt: number; jwks: { keys: JWK[] } } } = {}
189189

190190
export default class GoTrueClient {
191-
private static nextInstanceID = 0
191+
private static nextInstanceID: Record<string, number> = {}
192192

193193
private instanceID: number
194194

@@ -277,24 +277,26 @@ export default class GoTrueClient {
277277
* Create a new client for use in the browser.
278278
*/
279279
constructor(options: GoTrueClientOptions) {
280-
this.instanceID = GoTrueClient.nextInstanceID
281-
GoTrueClient.nextInstanceID += 1
282-
283-
if (this.instanceID > 0 && isBrowser()) {
284-
console.warn(
285-
'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.'
286-
)
287-
}
288-
289280
const settings = { ...DEFAULT_OPTIONS, ...options }
281+
this.storageKey = settings.storageKey
282+
283+
this.instanceID = GoTrueClient.nextInstanceID[this.storageKey] ?? 0
284+
GoTrueClient.nextInstanceID[this.storageKey] = this.instanceID + 1
290285

291286
this.logDebugMessages = !!settings.debug
292287
if (typeof settings.debug === 'function') {
293288
this.logger = settings.debug
294289
}
295290

291+
if (this.instanceID > 0 && isBrowser()) {
292+
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.`
293+
console.warn(message)
294+
if (this.logDebugMessages) {
295+
console.trace(message)
296+
}
297+
}
298+
296299
this.persistSession = settings.persistSession
297-
this.storageKey = settings.storageKey
298300
this.autoRefreshToken = settings.autoRefreshToken
299301
this.admin = new GoTrueAdminApi({
300302
url: settings.url,
@@ -400,12 +402,16 @@ export default class GoTrueClient {
400402
return result
401403
}
402404

405+
private _logPrefix(): string {
406+
return (
407+
'GoTrueClient@' +
408+
`${this.storageKey}:${this.instanceID} (${version}) ${new Date().toISOString()}`
409+
)
410+
}
411+
403412
private _debug(...args: any[]): GoTrueClient {
404413
if (this.logDebugMessages) {
405-
this.logger(
406-
`GoTrueClient@${this.instanceID} (${version}) ${new Date().toISOString()}`,
407-
...args
408-
)
414+
this.logger(this._logPrefix(), ...args)
409415
}
410416

411417
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,
@@ -190,6 +195,94 @@ describe('Fetch resolution in browser environment', () => {
190195
const resolvedFetch = resolveFetch(customFetch)
191196
expect(typeof resolvedFetch).toBe('function')
192197
})
198+
199+
it('should warn when two clients are created with the same storage key', () => {
200+
let consoleWarnSpy
201+
let consoleTraceSpy
202+
try {
203+
consoleWarnSpy = jest.spyOn(console, 'warn')
204+
consoleTraceSpy = jest.spyOn(console, 'trace')
205+
getClientWithSpecificStorageKey('same-storage-key')
206+
getClientWithSpecificStorageKey('same-storage-key')
207+
expect(consoleWarnSpy).toHaveBeenCalledTimes(1)
208+
expect(consoleWarnSpy).toHaveBeenCalledWith(
209+
expect.stringMatching(
210+
/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./
211+
)
212+
)
213+
expect(consoleTraceSpy).not.toHaveBeenCalled()
214+
} finally {
215+
consoleWarnSpy?.mockRestore()
216+
consoleTraceSpy?.mockRestore()
217+
}
218+
})
219+
220+
it('should warn & trace when two clients are created with the same storage key and debug is enabled', () => {
221+
let consoleWarnSpy
222+
let consoleTraceSpy
223+
try {
224+
consoleWarnSpy = jest.spyOn(console, 'warn')
225+
consoleTraceSpy = jest.spyOn(console, 'trace')
226+
getClientWithSpecificStorageKey('identical-storage-key')
227+
getClientWithSpecificStorageKey('identical-storage-key', { debug: true })
228+
expect(consoleWarnSpy).toHaveBeenCalledTimes(1)
229+
expect(consoleWarnSpy).toHaveBeenCalledWith(
230+
expect.stringMatching(
231+
/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./
232+
)
233+
)
234+
expect(consoleTraceSpy).toHaveBeenCalledWith(
235+
expect.stringMatching(
236+
/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./
237+
)
238+
)
239+
} finally {
240+
consoleWarnSpy?.mockRestore()
241+
consoleTraceSpy?.mockRestore()
242+
}
243+
})
244+
245+
it('should not warn when two clients are created with differing storage keys', () => {
246+
let consoleWarnSpy
247+
let consoleTraceSpy
248+
try {
249+
consoleWarnSpy = jest.spyOn(console, 'warn')
250+
consoleTraceSpy = jest.spyOn(console, 'trace')
251+
getClientWithSpecificStorageKey('first-storage-key')
252+
getClientWithSpecificStorageKey('second-storage-key')
253+
expect(consoleWarnSpy).not.toHaveBeenCalled()
254+
expect(consoleTraceSpy).not.toHaveBeenCalled()
255+
} finally {
256+
consoleWarnSpy?.mockRestore()
257+
consoleTraceSpy?.mockRestore()
258+
}
259+
})
260+
261+
it('should warn only when a second client with a duplicate key is created', () => {
262+
let consoleWarnSpy
263+
let consoleTraceSpy
264+
try {
265+
consoleWarnSpy = jest.spyOn(console, 'warn')
266+
consoleTraceSpy = jest.spyOn(console, 'trace')
267+
getClientWithSpecificStorageKey('test-storage-key1')
268+
expect(consoleWarnSpy).not.toHaveBeenCalled()
269+
getClientWithSpecificStorageKey('test-storage-key2')
270+
expect(consoleWarnSpy).not.toHaveBeenCalled()
271+
getClientWithSpecificStorageKey('test-storage-key3')
272+
expect(consoleWarnSpy).not.toHaveBeenCalled()
273+
getClientWithSpecificStorageKey('test-storage-key2')
274+
expect(consoleWarnSpy).toHaveBeenCalledTimes(1)
275+
expect(consoleWarnSpy).toHaveBeenCalledWith(
276+
expect.stringMatching(
277+
/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./
278+
)
279+
)
280+
expect(consoleTraceSpy).not.toHaveBeenCalled()
281+
} finally {
282+
consoleWarnSpy?.mockRestore()
283+
consoleTraceSpy?.mockRestore()
284+
}
285+
})
193286
})
194287

195288
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)