Skip to content

Commit 008f584

Browse files
committed
fix: Only warn if multiple clients share a storage-key
This allows 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. The storageKey has also been added to the prefix of the debug messages to help with tracing. The same prefix has been added to the warning for consistency If debug messages are enabled, the warning will also output a trace in addition to the warning to assist the user in tracking down where their multiple instances are being created from.
1 parent 5cd695c commit 008f584

File tree

3 files changed

+129
-17
lines changed

3 files changed

+129
-17
lines changed

src/GoTrueClient.ts

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

158158
export default class GoTrueClient {
159-
private static nextInstanceID = 0
159+
private static nextInstanceID: Record<string, number> = {}
160160

161161
private instanceID: number
162162

@@ -238,24 +238,26 @@ export default class GoTrueClient {
238238
* Create a new client for use in the browser.
239239
*/
240240
constructor(options: GoTrueClientOptions) {
241-
this.instanceID = GoTrueClient.nextInstanceID
242-
GoTrueClient.nextInstanceID += 1
243-
244-
if (this.instanceID > 0 && isBrowser()) {
245-
console.warn(
246-
'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.'
247-
)
248-
}
249-
250241
const settings = { ...DEFAULT_OPTIONS, ...options }
242+
this.storageKey = settings.storageKey
243+
244+
this.instanceID = GoTrueClient.nextInstanceID[this.storageKey] ?? 0
245+
GoTrueClient.nextInstanceID[this.storageKey] = this.instanceID + 1
251246

252247
this.logDebugMessages = !!settings.debug
253248
if (typeof settings.debug === 'function') {
254249
this.logger = settings.debug
255250
}
256251

252+
if (this.instanceID > 0 && isBrowser()) {
253+
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.`
254+
console.warn(message)
255+
if (this.logDebugMessages) {
256+
console.trace(message)
257+
}
258+
}
259+
257260
this.persistSession = settings.persistSession
258-
this.storageKey = settings.storageKey
259261
this.autoRefreshToken = settings.autoRefreshToken
260262
this.admin = new GoTrueAdminApi({
261263
url: settings.url,
@@ -334,12 +336,16 @@ export default class GoTrueClient {
334336
this.initialize()
335337
}
336338

339+
private _logPrefix(): string {
340+
return (
341+
'GoTrueClient@' +
342+
`${this.storageKey}:${this.instanceID} (${version}) ${new Date().toISOString()}`
343+
)
344+
}
345+
337346
private _debug(...args: any[]): GoTrueClient {
338347
if (this.logDebugMessages) {
339-
this.logger(
340-
`GoTrueClient@${this.instanceID} (${version}) ${new Date().toISOString()}`,
341-
...args
342-
)
348+
this.logger(this._logPrefix(), ...args)
343349
}
344350

345351
return this

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

813
// Add structuredClone polyfill for jsdom
@@ -98,6 +103,94 @@ describe('GoTrueClient in browser environment', () => {
98103
expect(signinError).toBeNull()
99104
expect(signinData?.session).toBeDefined()
100105
})
106+
107+
it('should warn when two clients are created with the same storage key', () => {
108+
let consoleWarnSpy
109+
let consoleTraceSpy
110+
try {
111+
consoleWarnSpy = jest.spyOn(console, 'warn')
112+
consoleTraceSpy = jest.spyOn(console, 'trace')
113+
getClientWithSpecificStorageKey('test-storage-key')
114+
getClientWithSpecificStorageKey('test-storage-key')
115+
expect(consoleWarnSpy).toHaveBeenCalledTimes(1)
116+
expect(consoleWarnSpy).toHaveBeenCalledWith(
117+
expect.stringMatching(
118+
/GoTrueClient@test-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./
119+
)
120+
)
121+
expect(consoleTraceSpy).not.toHaveBeenCalled()
122+
} finally {
123+
consoleWarnSpy?.mockRestore()
124+
consoleTraceSpy?.mockRestore()
125+
}
126+
})
127+
128+
it('should warn & trace when two clients are created with the same storage key and debug is enabled', () => {
129+
let consoleWarnSpy
130+
let consoleTraceSpy
131+
try {
132+
consoleWarnSpy = jest.spyOn(console, 'warn')
133+
consoleTraceSpy = jest.spyOn(console, 'trace')
134+
getClientWithSpecificStorageKey('test-storage-key')
135+
getClientWithSpecificStorageKey('test-storage-key', { debug: true })
136+
expect(consoleWarnSpy).toHaveBeenCalledTimes(1)
137+
expect(consoleWarnSpy).toHaveBeenCalledWith(
138+
expect.stringMatching(
139+
/GoTrueClient@test-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./
140+
)
141+
)
142+
expect(consoleTraceSpy).toHaveBeenCalledWith(
143+
expect.stringMatching(
144+
/GoTrueClient@test-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./
145+
)
146+
)
147+
} finally {
148+
consoleWarnSpy?.mockRestore()
149+
consoleTraceSpy?.mockRestore()
150+
}
151+
})
152+
153+
it('should not warn when two clients are created with differing storage keys', () => {
154+
let consoleWarnSpy
155+
let consoleTraceSpy
156+
try {
157+
consoleWarnSpy = jest.spyOn(console, 'warn')
158+
consoleTraceSpy = jest.spyOn(console, 'trace')
159+
getClientWithSpecificStorageKey('test-storage-key1')
160+
getClientWithSpecificStorageKey('test-storage-key2')
161+
expect(consoleWarnSpy).not.toHaveBeenCalled()
162+
expect(consoleTraceSpy).not.toHaveBeenCalled()
163+
} finally {
164+
consoleWarnSpy?.mockRestore()
165+
consoleTraceSpy?.mockRestore()
166+
}
167+
})
168+
169+
it('should not warn only when a second client with a duplicate key is created', () => {
170+
let consoleWarnSpy
171+
let consoleTraceSpy
172+
try {
173+
consoleWarnSpy = jest.spyOn(console, 'warn')
174+
consoleTraceSpy = jest.spyOn(console, 'trace')
175+
getClientWithSpecificStorageKey('test-storage-key1')
176+
expect(consoleWarnSpy).not.toHaveBeenCalled()
177+
getClientWithSpecificStorageKey('test-storage-key2')
178+
expect(consoleWarnSpy).not.toHaveBeenCalled()
179+
getClientWithSpecificStorageKey('test-storage-key3')
180+
expect(consoleWarnSpy).not.toHaveBeenCalled()
181+
getClientWithSpecificStorageKey('test-storage-key2')
182+
expect(consoleWarnSpy).toHaveBeenCalledTimes(1)
183+
expect(consoleWarnSpy).toHaveBeenCalledWith(
184+
expect.stringMatching(
185+
/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./
186+
)
187+
)
188+
expect(consoleTraceSpy).not.toHaveBeenCalled()
189+
} finally {
190+
consoleWarnSpy?.mockRestore()
191+
consoleTraceSpy?.mockRestore()
192+
}
193+
})
101194
})
102195

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

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)