@supabase/auth-js 2.107.0-beta.1 → 2.107.0-canary.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/main/GoTrueClient.d.ts +14 -68
- package/dist/main/GoTrueClient.d.ts.map +1 -1
- package/dist/main/GoTrueClient.js +116 -334
- package/dist/main/GoTrueClient.js.map +1 -1
- package/dist/main/lib/errors.d.ts +0 -24
- package/dist/main/lib/errors.d.ts.map +1 -1
- package/dist/main/lib/errors.js +1 -31
- package/dist/main/lib/errors.js.map +1 -1
- package/dist/main/lib/locks.d.ts +34 -28
- package/dist/main/lib/locks.d.ts.map +1 -1
- package/dist/main/lib/locks.js +34 -28
- package/dist/main/lib/locks.js.map +1 -1
- package/dist/main/lib/types.d.ts +27 -16
- package/dist/main/lib/types.d.ts.map +1 -1
- package/dist/main/lib/types.js.map +1 -1
- package/dist/main/lib/version.d.ts +1 -1
- package/dist/main/lib/version.d.ts.map +1 -1
- package/dist/main/lib/version.js +1 -1
- package/dist/main/lib/version.js.map +1 -1
- package/dist/module/GoTrueClient.d.ts +14 -68
- package/dist/module/GoTrueClient.d.ts.map +1 -1
- package/dist/module/GoTrueClient.js +118 -336
- package/dist/module/GoTrueClient.js.map +1 -1
- package/dist/module/lib/errors.d.ts +0 -24
- package/dist/module/lib/errors.d.ts.map +1 -1
- package/dist/module/lib/errors.js +0 -28
- package/dist/module/lib/errors.js.map +1 -1
- package/dist/module/lib/locks.d.ts +34 -28
- package/dist/module/lib/locks.d.ts.map +1 -1
- package/dist/module/lib/locks.js +34 -28
- package/dist/module/lib/locks.js.map +1 -1
- package/dist/module/lib/types.d.ts +27 -16
- package/dist/module/lib/types.d.ts.map +1 -1
- package/dist/module/lib/types.js.map +1 -1
- package/dist/module/lib/version.d.ts +1 -1
- package/dist/module/lib/version.d.ts.map +1 -1
- package/dist/module/lib/version.js +1 -1
- package/dist/module/lib/version.js.map +1 -1
- package/dist/tsconfig.module.tsbuildinfo +1 -1
- package/dist/tsconfig.tsbuildinfo +1 -1
- package/package.json +1 -1
- package/src/GoTrueClient.ts +147 -400
- package/src/lib/errors.ts +0 -32
- package/src/lib/locks.ts +34 -29
- package/src/lib/types.ts +27 -16
- package/src/lib/version.ts +1 -1
- package/migrations/lockless-coordination.md +0 -89
package/src/GoTrueClient.ts
CHANGED
|
@@ -16,13 +16,11 @@ import {
|
|
|
16
16
|
AuthInvalidTokenResponseError,
|
|
17
17
|
AuthPKCECodeVerifierMissingError,
|
|
18
18
|
AuthPKCEGrantCodeExchangeError,
|
|
19
|
-
AuthRefreshDiscardedError,
|
|
20
19
|
AuthSessionMissingError,
|
|
21
20
|
AuthUnknownError,
|
|
22
21
|
isAuthApiError,
|
|
23
22
|
isAuthError,
|
|
24
23
|
isAuthImplicitGrantRedirectError,
|
|
25
|
-
isAuthRefreshDiscardedError,
|
|
26
24
|
isAuthRetryableFetchError,
|
|
27
25
|
isAuthSessionMissingError,
|
|
28
26
|
} from './lib/errors'
|
|
@@ -197,17 +195,11 @@ const DEFAULT_OPTIONS: Omit<
|
|
|
197
195
|
debug: false,
|
|
198
196
|
hasCustomAuthorizationHeader: false,
|
|
199
197
|
throwOnError: false,
|
|
200
|
-
lockAcquireTimeout: 5000, // 5 seconds
|
|
198
|
+
lockAcquireTimeout: 5000, // 5 seconds
|
|
201
199
|
skipAutoInitialize: false,
|
|
202
200
|
experimental: {},
|
|
203
201
|
}
|
|
204
202
|
|
|
205
|
-
/**
|
|
206
|
-
* No-op lock used internally as a placeholder. Kept so older test setups that
|
|
207
|
-
* inject this exact reference do not break; new code never sees it because
|
|
208
|
-
* `this.lock` stays `null` when no custom lock is supplied (lockless path).
|
|
209
|
-
* TODO(v3): remove with the legacy lock path.
|
|
210
|
-
*/
|
|
211
203
|
async function lockNoOp<R>(name: string, acquireTimeout: number, fn: () => Promise<R>): Promise<R> {
|
|
212
204
|
return await fn()
|
|
213
205
|
}
|
|
@@ -288,14 +280,6 @@ export default class GoTrueClient {
|
|
|
288
280
|
protected autoRefreshTickTimeout: ReturnType<typeof setTimeout> | null = null
|
|
289
281
|
protected visibilityChangedCallback: (() => Promise<any>) | null = null
|
|
290
282
|
protected refreshingDeferred: Deferred<CallRefreshTokenResult> | null = null
|
|
291
|
-
/**
|
|
292
|
-
* Monotonic counter incremented at the top of `_removeSession`, before any
|
|
293
|
-
* `await`. The commit guard inside `_callRefreshToken` captures this value
|
|
294
|
-
* before `_saveSession` and re-checks it after, so a `signOut` that
|
|
295
|
-
* interleaves inside `_saveSession`'s storage-write awaits is still caught
|
|
296
|
-
* (the post-fetch storage snapshot alone misses that window).
|
|
297
|
-
*/
|
|
298
|
-
protected _sessionRemovalEpoch = 0
|
|
299
283
|
/**
|
|
300
284
|
* Keeps track of the async client initialization.
|
|
301
285
|
* When null or not yet resolved the auth state is `unknown`
|
|
@@ -313,19 +297,10 @@ export default class GoTrueClient {
|
|
|
313
297
|
protected hasCustomAuthorizationHeader = false
|
|
314
298
|
protected suppressGetSessionWarning = false
|
|
315
299
|
protected fetch: Fetch
|
|
316
|
-
|
|
317
|
-
* Custom lock function passed via `settings.lock`. When non-null, every auth
|
|
318
|
-
* operation runs inside `_acquireLock`. When null (the default), the client
|
|
319
|
-
* uses its lockless coordination (refresh single-flight + commit guard).
|
|
320
|
-
* TODO(v3): remove along with the legacy lock path.
|
|
321
|
-
*/
|
|
322
|
-
protected lock: LockFunc | null = null
|
|
300
|
+
protected lock: LockFunc
|
|
323
301
|
protected lockAcquired = false
|
|
324
302
|
protected pendingInLock: Promise<any>[] = []
|
|
325
303
|
protected throwOnError: boolean
|
|
326
|
-
/**
|
|
327
|
-
* Only consulted when a custom `lock` is supplied. TODO(v3): remove.
|
|
328
|
-
*/
|
|
329
304
|
protected lockAcquireTimeout: number
|
|
330
305
|
/**
|
|
331
306
|
* Opt-in flags for experimental features. Defaults to an empty object.
|
|
@@ -396,23 +371,19 @@ export default class GoTrueClient {
|
|
|
396
371
|
this.url = settings.url
|
|
397
372
|
this.headers = settings.headers
|
|
398
373
|
this.fetch = resolveFetch(settings.fetch)
|
|
374
|
+
this.lock = settings.lock || lockNoOp
|
|
399
375
|
this.detectSessionInUrl = settings.detectSessionInUrl
|
|
400
376
|
this.flowType = settings.flowType
|
|
401
377
|
this.hasCustomAuthorizationHeader = settings.hasCustomAuthorizationHeader
|
|
402
378
|
this.throwOnError = settings.throwOnError
|
|
403
|
-
|
|
404
|
-
// Always wire `lockAcquireTimeout` even on the lockless path: consumers
|
|
405
|
-
// (including supabase-js tests) read it off the client to verify option
|
|
406
|
-
// flow-through.
|
|
407
379
|
this.lockAcquireTimeout = settings.lockAcquireTimeout
|
|
408
380
|
|
|
409
|
-
|
|
410
|
-
// compatibility with callers passing a custom `lock` (typically React
|
|
411
|
-
// Native `processLock` or Node multi-process setups). When `settings.lock`
|
|
412
|
-
// is null the client uses its lockless coordination — no `navigator.locks`
|
|
413
|
-
// by default, no implicit `processLock`.
|
|
414
|
-
if (settings.lock != null) {
|
|
381
|
+
if (settings.lock) {
|
|
415
382
|
this.lock = settings.lock
|
|
383
|
+
} else if (this.persistSession && isBrowser() && globalThis?.navigator?.locks) {
|
|
384
|
+
this.lock = navigatorLock
|
|
385
|
+
} else {
|
|
386
|
+
this.lock = lockNoOp
|
|
416
387
|
}
|
|
417
388
|
|
|
418
389
|
if (!this.jwks) {
|
|
@@ -547,13 +518,9 @@ export default class GoTrueClient {
|
|
|
547
518
|
}
|
|
548
519
|
|
|
549
520
|
this.initializePromise = (async () => {
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
return await this._initialize()
|
|
554
|
-
})
|
|
555
|
-
}
|
|
556
|
-
return await this._initialize()
|
|
521
|
+
return await this._acquireLock(this.lockAcquireTimeout, async () => {
|
|
522
|
+
return await this._initialize()
|
|
523
|
+
})
|
|
557
524
|
})()
|
|
558
525
|
|
|
559
526
|
return await this.initializePromise
|
|
@@ -1438,14 +1405,9 @@ export default class GoTrueClient {
|
|
|
1438
1405
|
async exchangeCodeForSession(authCode: string): Promise<AuthTokenResponse> {
|
|
1439
1406
|
await this.initializePromise
|
|
1440
1407
|
|
|
1441
|
-
|
|
1442
|
-
|
|
1443
|
-
|
|
1444
|
-
return this._exchangeCodeForSession(authCode)
|
|
1445
|
-
})
|
|
1446
|
-
}
|
|
1447
|
-
|
|
1448
|
-
return this._exchangeCodeForSession(authCode)
|
|
1408
|
+
return this._acquireLock(this.lockAcquireTimeout, async () => {
|
|
1409
|
+
return this._exchangeCodeForSession(authCode)
|
|
1410
|
+
})
|
|
1449
1411
|
}
|
|
1450
1412
|
|
|
1451
1413
|
/**
|
|
@@ -2482,14 +2444,9 @@ export default class GoTrueClient {
|
|
|
2482
2444
|
async reauthenticate(): Promise<AuthResponse> {
|
|
2483
2445
|
await this.initializePromise
|
|
2484
2446
|
|
|
2485
|
-
|
|
2486
|
-
|
|
2487
|
-
|
|
2488
|
-
return await this._reauthenticate()
|
|
2489
|
-
})
|
|
2490
|
-
}
|
|
2491
|
-
|
|
2492
|
-
return await this._reauthenticate()
|
|
2447
|
+
return await this._acquireLock(this.lockAcquireTimeout, async () => {
|
|
2448
|
+
return await this._reauthenticate()
|
|
2449
|
+
})
|
|
2493
2450
|
}
|
|
2494
2451
|
|
|
2495
2452
|
private async _reauthenticate(): Promise<AuthResponse> {
|
|
@@ -2636,7 +2593,7 @@ export default class GoTrueClient {
|
|
|
2636
2593
|
* - If the session's access token is expired or is about to expire, this method will use the refresh token to refresh the session.
|
|
2637
2594
|
* - When using in a browser, or you've called `startAutoRefresh()` in your environment (React Native, etc.) this function always returns a valid access token without refreshing the session itself, as this is done in the background. This function returns very fast.
|
|
2638
2595
|
* - **IMPORTANT SECURITY NOTICE:** If using an insecure storage medium, such as cookies or request headers, the user object returned by this function **must not be trusted**. Always verify the JWT using `getClaims()` or your own JWT verification library to securely establish the user's identity and access. You can also use `getUser()` to fetch the user object directly from the Auth server for this purpose.
|
|
2639
|
-
* -
|
|
2596
|
+
* - When using in a browser, this function is synchronized across all tabs using the [LockManager](https://developer.mozilla.org/en-US/docs/Web/API/LockManager) API. In other environments make sure you've defined a proper `lock` property, if necessary, to make sure there are no race conditions while the session is being refreshed.
|
|
2640
2597
|
*
|
|
2641
2598
|
* @example Get the session data
|
|
2642
2599
|
* ```js
|
|
@@ -2704,26 +2661,17 @@ export default class GoTrueClient {
|
|
|
2704
2661
|
async getSession() {
|
|
2705
2662
|
await this.initializePromise
|
|
2706
2663
|
|
|
2707
|
-
|
|
2708
|
-
|
|
2709
|
-
|
|
2710
|
-
return this._useSession(async (result) => {
|
|
2711
|
-
return result
|
|
2712
|
-
})
|
|
2664
|
+
const result = await this._acquireLock(this.lockAcquireTimeout, async () => {
|
|
2665
|
+
return this._useSession(async (result) => {
|
|
2666
|
+
return result
|
|
2713
2667
|
})
|
|
2714
|
-
}
|
|
2715
|
-
|
|
2716
|
-
return await this._useSession(async (result) => {
|
|
2717
|
-
return result
|
|
2718
2668
|
})
|
|
2669
|
+
|
|
2670
|
+
return result
|
|
2719
2671
|
}
|
|
2720
2672
|
|
|
2721
2673
|
/**
|
|
2722
2674
|
* Acquires a global lock based on the storage key.
|
|
2723
|
-
*
|
|
2724
|
-
* TODO(v3): remove along with the legacy lock path. Only called when
|
|
2725
|
-
* `this.lock` is non-null (custom lock supplied via constructor). The
|
|
2726
|
-
* default lockless path bypasses this entirely.
|
|
2727
2675
|
*/
|
|
2728
2676
|
private async _acquireLock<R>(acquireTimeout: number, fn: () => Promise<R>): Promise<R> {
|
|
2729
2677
|
this._debug('#_acquireLock', 'begin', acquireTimeout)
|
|
@@ -2752,7 +2700,7 @@ export default class GoTrueClient {
|
|
|
2752
2700
|
return result
|
|
2753
2701
|
}
|
|
2754
2702
|
|
|
2755
|
-
return await this.lock
|
|
2703
|
+
return await this.lock(`lock:${this.storageKey}`, acquireTimeout, async () => {
|
|
2756
2704
|
this._debug('#_acquireLock', 'lock acquired for storage key', this.storageKey)
|
|
2757
2705
|
|
|
2758
2706
|
try {
|
|
@@ -2794,9 +2742,10 @@ export default class GoTrueClient {
|
|
|
2794
2742
|
}
|
|
2795
2743
|
|
|
2796
2744
|
/**
|
|
2797
|
-
* Use instead of {@link #getSession} inside the library.
|
|
2798
|
-
*
|
|
2799
|
-
*
|
|
2745
|
+
* Use instead of {@link #getSession} inside the library. It is
|
|
2746
|
+
* semantically usually what you want, as getting a session involves some
|
|
2747
|
+
* processing afterwards that requires only one client operating on the
|
|
2748
|
+
* session at once across multiple tabs or processes.
|
|
2800
2749
|
*/
|
|
2801
2750
|
private async _useSession<R>(
|
|
2802
2751
|
fn: (
|
|
@@ -2824,10 +2773,7 @@ export default class GoTrueClient {
|
|
|
2824
2773
|
this._debug('#_useSession', 'begin')
|
|
2825
2774
|
|
|
2826
2775
|
try {
|
|
2827
|
-
//
|
|
2828
|
-
// idempotent, and the only write path inside it (refresh) is
|
|
2829
|
-
// single-flighted downstream by `refreshingDeferred` in
|
|
2830
|
-
// `_callRefreshToken`. No serialization is needed at this layer.
|
|
2776
|
+
// the use of __loadSession here is the only correct use of the function!
|
|
2831
2777
|
const result = await this.__loadSession()
|
|
2832
2778
|
|
|
2833
2779
|
return await fn(result)
|
|
@@ -2863,8 +2809,7 @@ export default class GoTrueClient {
|
|
|
2863
2809
|
> {
|
|
2864
2810
|
this._debug('#__loadSession()', 'begin')
|
|
2865
2811
|
|
|
2866
|
-
if (
|
|
2867
|
-
// TODO(v3): remove. Only meaningful on the legacy lock path.
|
|
2812
|
+
if (!this.lockAcquired) {
|
|
2868
2813
|
this._debug('#__loadSession()', 'used outside of an acquired lock!', new Error().stack)
|
|
2869
2814
|
}
|
|
2870
2815
|
|
|
@@ -3031,15 +2976,9 @@ export default class GoTrueClient {
|
|
|
3031
2976
|
|
|
3032
2977
|
await this.initializePromise
|
|
3033
2978
|
|
|
3034
|
-
|
|
3035
|
-
|
|
3036
|
-
|
|
3037
|
-
result = await this._acquireLock(this.lockAcquireTimeout, async () => {
|
|
3038
|
-
return await this._getUser()
|
|
3039
|
-
})
|
|
3040
|
-
} else {
|
|
3041
|
-
result = await this._getUser()
|
|
3042
|
-
}
|
|
2979
|
+
const result = await this._acquireLock(this.lockAcquireTimeout, async () => {
|
|
2980
|
+
return await this._getUser()
|
|
2981
|
+
})
|
|
3043
2982
|
|
|
3044
2983
|
if (result.data.user) {
|
|
3045
2984
|
this.suppressGetSessionWarning = true
|
|
@@ -3214,14 +3153,9 @@ export default class GoTrueClient {
|
|
|
3214
3153
|
): Promise<UserResponse> {
|
|
3215
3154
|
await this.initializePromise
|
|
3216
3155
|
|
|
3217
|
-
|
|
3218
|
-
|
|
3219
|
-
|
|
3220
|
-
return await this._updateUser(attributes, options)
|
|
3221
|
-
})
|
|
3222
|
-
}
|
|
3223
|
-
|
|
3224
|
-
return await this._updateUser(attributes, options)
|
|
3156
|
+
return await this._acquireLock(this.lockAcquireTimeout, async () => {
|
|
3157
|
+
return await this._updateUser(attributes, options)
|
|
3158
|
+
})
|
|
3225
3159
|
}
|
|
3226
3160
|
|
|
3227
3161
|
protected async _updateUser(
|
|
@@ -3408,14 +3342,9 @@ export default class GoTrueClient {
|
|
|
3408
3342
|
}): Promise<AuthResponse> {
|
|
3409
3343
|
await this.initializePromise
|
|
3410
3344
|
|
|
3411
|
-
|
|
3412
|
-
|
|
3413
|
-
|
|
3414
|
-
return await this._setSession(currentSession)
|
|
3415
|
-
})
|
|
3416
|
-
}
|
|
3417
|
-
|
|
3418
|
-
return await this._setSession(currentSession)
|
|
3345
|
+
return await this._acquireLock(this.lockAcquireTimeout, async () => {
|
|
3346
|
+
return await this._setSession(currentSession)
|
|
3347
|
+
})
|
|
3419
3348
|
}
|
|
3420
3349
|
|
|
3421
3350
|
protected async _setSession(currentSession: {
|
|
@@ -3604,14 +3533,9 @@ export default class GoTrueClient {
|
|
|
3604
3533
|
async refreshSession(currentSession?: { refresh_token: string }): Promise<AuthResponse> {
|
|
3605
3534
|
await this.initializePromise
|
|
3606
3535
|
|
|
3607
|
-
|
|
3608
|
-
|
|
3609
|
-
|
|
3610
|
-
return await this._refreshSession(currentSession)
|
|
3611
|
-
})
|
|
3612
|
-
}
|
|
3613
|
-
|
|
3614
|
-
return await this._refreshSession(currentSession)
|
|
3536
|
+
return await this._acquireLock(this.lockAcquireTimeout, async () => {
|
|
3537
|
+
return await this._refreshSession(currentSession)
|
|
3538
|
+
})
|
|
3615
3539
|
}
|
|
3616
3540
|
|
|
3617
3541
|
protected async _refreshSession(currentSession?: {
|
|
@@ -3800,7 +3724,9 @@ export default class GoTrueClient {
|
|
|
3800
3724
|
if (typeof this.detectSessionInUrl === 'function') {
|
|
3801
3725
|
return this.detectSessionInUrl(new URL(window.location.href), params)
|
|
3802
3726
|
}
|
|
3803
|
-
return Boolean(
|
|
3727
|
+
return Boolean(
|
|
3728
|
+
params.access_token || params.error || params.error_description || params.error_code
|
|
3729
|
+
)
|
|
3804
3730
|
}
|
|
3805
3731
|
|
|
3806
3732
|
/**
|
|
@@ -3859,14 +3785,9 @@ export default class GoTrueClient {
|
|
|
3859
3785
|
async signOut(options: SignOut = { scope: 'global' }): Promise<{ error: AuthError | null }> {
|
|
3860
3786
|
await this.initializePromise
|
|
3861
3787
|
|
|
3862
|
-
|
|
3863
|
-
|
|
3864
|
-
|
|
3865
|
-
return await this._signOut(options)
|
|
3866
|
-
})
|
|
3867
|
-
}
|
|
3868
|
-
|
|
3869
|
-
return await this._signOut(options)
|
|
3788
|
+
return await this._acquireLock(this.lockAcquireTimeout, async () => {
|
|
3789
|
+
return await this._signOut(options)
|
|
3790
|
+
})
|
|
3870
3791
|
}
|
|
3871
3792
|
|
|
3872
3793
|
protected async _signOut(
|
|
@@ -3913,19 +3834,16 @@ export default class GoTrueClient {
|
|
|
3913
3834
|
}
|
|
3914
3835
|
|
|
3915
3836
|
/**
|
|
3916
|
-
*
|
|
3917
|
-
*
|
|
3918
|
-
*
|
|
3919
|
-
*
|
|
3920
|
-
*
|
|
3921
|
-
* `_notifyAllSubscribers` returns, so the inner refresh dedupes onto the
|
|
3922
|
-
* outer's unresolved promise and the two wait on each other.
|
|
3837
|
+
* Avoid using an async function inside `onAuthStateChange` as you might end
|
|
3838
|
+
* up with a deadlock. The callback function runs inside an exclusive lock,
|
|
3839
|
+
* so calling other Supabase Client APIs that also try to acquire the
|
|
3840
|
+
* exclusive lock, might cause a deadlock. This behavior is observable across
|
|
3841
|
+
* tabs. In the next major library version, this behavior will not be supported.
|
|
3923
3842
|
*
|
|
3924
|
-
*
|
|
3843
|
+
* Receive a notification every time an auth event happens.
|
|
3925
3844
|
*
|
|
3926
|
-
* @
|
|
3927
|
-
*
|
|
3928
|
-
* refresh-triggering work outside the callback.
|
|
3845
|
+
* @param callback A callback function to be invoked when an auth event happens.
|
|
3846
|
+
* @deprecated Due to the possibility of deadlocks with async functions as callbacks, use the version without an async function.
|
|
3929
3847
|
*/
|
|
3930
3848
|
onAuthStateChange(callback: (event: AuthChangeEvent, session: Session | null) => Promise<void>): {
|
|
3931
3849
|
data: { subscription: Subscription }
|
|
@@ -3938,8 +3856,18 @@ export default class GoTrueClient {
|
|
|
3938
3856
|
* - Subscribes to important events occurring on the user's session.
|
|
3939
3857
|
* - Use on the frontend/client. It is less useful on the server.
|
|
3940
3858
|
* - Events are emitted across tabs to keep your application's UI up-to-date. Some events can fire very frequently, based on the number of tabs open. Use a quick and efficient callback function, and defer or debounce as many operations as you can to be performed outside of the callback.
|
|
3941
|
-
* -
|
|
3942
|
-
*
|
|
3859
|
+
* - **Important:** A callback can be an `async` function and it runs synchronously during the processing of the changes causing the event. You can easily create a dead-lock by using `await` on a call to another method of the Supabase library.
|
|
3860
|
+
* - Avoid using `async` functions as callbacks.
|
|
3861
|
+
* - Limit the number of `await` calls in `async` callbacks.
|
|
3862
|
+
* - Do not use other Supabase functions in the callback function. If you must, dispatch the functions once the callback has finished executing. Use this as a quick way to achieve this:
|
|
3863
|
+
* ```js
|
|
3864
|
+
* supabase.auth.onAuthStateChange((event, session) => {
|
|
3865
|
+
* setTimeout(async () => {
|
|
3866
|
+
* // await on other Supabase function here
|
|
3867
|
+
* // this runs right after the callback has finished
|
|
3868
|
+
* }, 0)
|
|
3869
|
+
* })
|
|
3870
|
+
* ```
|
|
3943
3871
|
* - Emitted events:
|
|
3944
3872
|
* - `INITIAL_SESSION`
|
|
3945
3873
|
* - Emitted right after the Supabase client is constructed and the initial session from storage is loaded.
|
|
@@ -4130,14 +4058,9 @@ export default class GoTrueClient {
|
|
|
4130
4058
|
;(async () => {
|
|
4131
4059
|
await this.initializePromise
|
|
4132
4060
|
|
|
4133
|
-
|
|
4134
|
-
|
|
4135
|
-
|
|
4136
|
-
this._emitInitialSession(id)
|
|
4137
|
-
})
|
|
4138
|
-
} else {
|
|
4139
|
-
await this._emitInitialSession(id)
|
|
4140
|
-
}
|
|
4061
|
+
await this._acquireLock(this.lockAcquireTimeout, async () => {
|
|
4062
|
+
this._emitInitialSession(id)
|
|
4063
|
+
})
|
|
4141
4064
|
})()
|
|
4142
4065
|
|
|
4143
4066
|
return { data: { subscription } }
|
|
@@ -4532,10 +4455,7 @@ export default class GoTrueClient {
|
|
|
4532
4455
|
* @param refreshToken A valid refresh token that was returned on login.
|
|
4533
4456
|
*/
|
|
4534
4457
|
private async _refreshAccessToken(refreshToken: string): Promise<AuthResponse> {
|
|
4535
|
-
|
|
4536
|
-
// fragment of the token in the debug tag, even when `debug: true` is
|
|
4537
|
-
// enabled (logs may be forwarded to third-party services).
|
|
4538
|
-
const debugName = `#_refreshAccessToken()`
|
|
4458
|
+
const debugName = `#_refreshAccessToken(${refreshToken.substring(0, 5)}...)`
|
|
4539
4459
|
this._debug(debugName, 'begin')
|
|
4540
4460
|
|
|
4541
4461
|
try {
|
|
@@ -4688,22 +4608,15 @@ export default class GoTrueClient {
|
|
|
4688
4608
|
const { error } = await this._callRefreshToken(currentSession.refresh_token)
|
|
4689
4609
|
|
|
4690
4610
|
if (error) {
|
|
4691
|
-
|
|
4692
|
-
|
|
4693
|
-
|
|
4694
|
-
|
|
4695
|
-
|
|
4696
|
-
|
|
4697
|
-
|
|
4698
|
-
|
|
4699
|
-
|
|
4700
|
-
this._debug(
|
|
4701
|
-
debugName,
|
|
4702
|
-
'refresh failed with a non-retryable error, removing the session',
|
|
4703
|
-
error
|
|
4704
|
-
)
|
|
4705
|
-
await this._removeSession()
|
|
4706
|
-
}
|
|
4611
|
+
console.error(error)
|
|
4612
|
+
|
|
4613
|
+
if (!isAuthRetryableFetchError(error)) {
|
|
4614
|
+
this._debug(
|
|
4615
|
+
debugName,
|
|
4616
|
+
'refresh failed with a non-retryable error, removing the session',
|
|
4617
|
+
error
|
|
4618
|
+
)
|
|
4619
|
+
await this._removeSession()
|
|
4707
4620
|
}
|
|
4708
4621
|
}
|
|
4709
4622
|
}
|
|
@@ -4756,85 +4669,18 @@ export default class GoTrueClient {
|
|
|
4756
4669
|
return this.refreshingDeferred.promise
|
|
4757
4670
|
}
|
|
4758
4671
|
|
|
4759
|
-
|
|
4760
|
-
// fragment of the token in the debug tag, even when `debug: true` is
|
|
4761
|
-
// enabled (logs may be forwarded to third-party services).
|
|
4762
|
-
const debugName = `#_callRefreshToken()`
|
|
4672
|
+
const debugName = `#_callRefreshToken(${refreshToken.substring(0, 5)}...)`
|
|
4763
4673
|
|
|
4764
4674
|
this._debug(debugName, 'begin')
|
|
4765
4675
|
|
|
4766
4676
|
try {
|
|
4767
4677
|
this.refreshingDeferred = new Deferred<CallRefreshTokenResult>()
|
|
4768
4678
|
|
|
4769
|
-
// Snapshot storage before the fetch. The commit guard discards the
|
|
4770
|
-
// rotated tokens only when a non-null pre-fetch snapshot changed under
|
|
4771
|
-
// us — typical case: a concurrent `signOut` ran `_removeSession`, or
|
|
4772
|
-
// another tab's refresh rewrote the slot. Callers passing
|
|
4773
|
-
// externally-sourced tokens (SSR cookie handoff, multi-account
|
|
4774
|
-
// switching, `setSession`/`refreshSession({ refresh_token })`) may
|
|
4775
|
-
// start from a null snapshot OR from a non-null snapshot whose
|
|
4776
|
-
// refresh_token differs from the one they're hydrating; in both
|
|
4777
|
-
// cases the guard fires only when storage was *modified between
|
|
4778
|
-
// snapshots*, not when the input token disagrees with what's stored.
|
|
4779
|
-
const storedAtStart = (await getItemAsync(this.storage, this.storageKey)) as Session | null
|
|
4780
|
-
|
|
4781
4679
|
const { data, error } = await this._refreshAccessToken(refreshToken)
|
|
4782
4680
|
if (error) throw error
|
|
4783
4681
|
if (!data.session) throw new AuthSessionMissingError()
|
|
4784
4682
|
|
|
4785
|
-
const storedAfter = (await getItemAsync(this.storage, this.storageKey)) as Session | null
|
|
4786
|
-
const storageChangedUnderUs =
|
|
4787
|
-
storedAtStart !== null &&
|
|
4788
|
-
(storedAfter === null || storedAfter.refresh_token !== storedAtStart.refresh_token)
|
|
4789
|
-
|
|
4790
|
-
if (storageChangedUnderUs) {
|
|
4791
|
-
this._debug(
|
|
4792
|
-
debugName,
|
|
4793
|
-
'commit guard: storage changed since refresh started, discarding rotated tokens',
|
|
4794
|
-
{
|
|
4795
|
-
// Presence indicators only — never log refresh token fragments,
|
|
4796
|
-
// even partial. Logs may be forwarded to third-party services.
|
|
4797
|
-
startedWith: 'present',
|
|
4798
|
-
nowHolds: storedAfter ? 'replaced' : 'cleared',
|
|
4799
|
-
}
|
|
4800
|
-
)
|
|
4801
|
-
const discarded: CallRefreshTokenResult = {
|
|
4802
|
-
data: null,
|
|
4803
|
-
error: new AuthRefreshDiscardedError(),
|
|
4804
|
-
}
|
|
4805
|
-
this.refreshingDeferred.resolve(discarded)
|
|
4806
|
-
return discarded
|
|
4807
|
-
}
|
|
4808
|
-
|
|
4809
|
-
// Second leg of the commit guard: close the TOCTOU window between the
|
|
4810
|
-
// synchronous `storageChangedUnderUs` check and the actual storage
|
|
4811
|
-
// writes inside `_saveSession`. A concurrent `signOut → _removeSession`
|
|
4812
|
-
// can land inside `_saveSession`'s `await setItemAsync(...)` yields and
|
|
4813
|
-
// clear storage just before we overwrite it. Capture the epoch BEFORE
|
|
4814
|
-
// the save and re-check after; if it advanced, undo the write directly
|
|
4815
|
-
// (do NOT call `_removeSession` — that would emit a duplicate
|
|
4816
|
-
// SIGNED_OUT for the concurrent signOut that already fired one).
|
|
4817
|
-
const epochBeforeSave = this._sessionRemovalEpoch
|
|
4818
|
-
|
|
4819
4683
|
await this._saveSession(data.session)
|
|
4820
|
-
|
|
4821
|
-
if (this._sessionRemovalEpoch !== epochBeforeSave) {
|
|
4822
|
-
this._debug(
|
|
4823
|
-
debugName,
|
|
4824
|
-
'commit guard (post-save): _removeSession ran during _saveSession, undoing write'
|
|
4825
|
-
)
|
|
4826
|
-
await removeItemAsync(this.storage, this.storageKey)
|
|
4827
|
-
if (this.userStorage) {
|
|
4828
|
-
await removeItemAsync(this.userStorage, this.storageKey + '-user')
|
|
4829
|
-
}
|
|
4830
|
-
const discarded: CallRefreshTokenResult = {
|
|
4831
|
-
data: null,
|
|
4832
|
-
error: new AuthRefreshDiscardedError(),
|
|
4833
|
-
}
|
|
4834
|
-
this.refreshingDeferred.resolve(discarded)
|
|
4835
|
-
return discarded
|
|
4836
|
-
}
|
|
4837
|
-
|
|
4838
4684
|
await this._notifyAllSubscribers('TOKEN_REFRESHED', data.session)
|
|
4839
4685
|
|
|
4840
4686
|
const result = { data: data.session, error: null }
|
|
@@ -4946,11 +4792,6 @@ export default class GoTrueClient {
|
|
|
4946
4792
|
}
|
|
4947
4793
|
|
|
4948
4794
|
private async _removeSession() {
|
|
4949
|
-
// Bump synchronously, BEFORE any `await`, so that `_callRefreshToken`'s
|
|
4950
|
-
// post-save check sees the increment whenever this method has started —
|
|
4951
|
-
// even if it hasn't finished. Pairs with the epoch check in
|
|
4952
|
-
// `_callRefreshToken`. See `_sessionRemovalEpoch` field doc.
|
|
4953
|
-
this._sessionRemovalEpoch += 1
|
|
4954
4795
|
this._debug('#_removeSession()')
|
|
4955
4796
|
|
|
4956
4797
|
this.suppressGetSessionWarning = false
|
|
@@ -5139,144 +4980,58 @@ export default class GoTrueClient {
|
|
|
5139
4980
|
await this._stopAutoRefresh()
|
|
5140
4981
|
}
|
|
5141
4982
|
|
|
5142
|
-
/**
|
|
5143
|
-
* Tears down the client's background work: stops the auto-refresh interval,
|
|
5144
|
-
* removes the `visibilitychange` listener, closes the cross-tab
|
|
5145
|
-
* `BroadcastChannel`, and clears registered `onAuthStateChange` subscribers.
|
|
5146
|
-
*
|
|
5147
|
-
* Call this from cleanup hooks when the client is being replaced before
|
|
5148
|
-
* its JS realm is destroyed. React Strict Mode and HMR are the common
|
|
5149
|
-
* cases. Any in-flight `fetch` calls continue to completion and may still
|
|
5150
|
-
* write to storage; dispose doesn't abort them or erase storage.
|
|
5151
|
-
*
|
|
5152
|
-
* Lifecycle caveat: because in-flight refreshes are not aborted, a
|
|
5153
|
-
* disposed instance can still persist a rotated session to storage after
|
|
5154
|
-
* `dispose()` returns. A subsequent `createClient` against the same
|
|
5155
|
-
* `storageKey` will pick up that session on its next read. If you need
|
|
5156
|
-
* strict isolation between client lifecycles, await any pending auth
|
|
5157
|
-
* operation before calling `dispose()` (or change the `storageKey` for
|
|
5158
|
-
* the replacement client).
|
|
5159
|
-
*
|
|
5160
|
-
* Safe to call repeatedly.
|
|
5161
|
-
*
|
|
5162
|
-
* @category Auth
|
|
5163
|
-
*
|
|
5164
|
-
* @example Cleanup on React unmount
|
|
5165
|
-
* ```ts
|
|
5166
|
-
* useEffect(() => {
|
|
5167
|
-
* const client = createClient(...)
|
|
5168
|
-
* return () => { client.auth.dispose() }
|
|
5169
|
-
* }, [])
|
|
5170
|
-
* ```
|
|
5171
|
-
*/
|
|
5172
|
-
async dispose(): Promise<void> {
|
|
5173
|
-
this._removeVisibilityChangedCallback()
|
|
5174
|
-
await this._stopAutoRefresh()
|
|
5175
|
-
this.broadcastChannel?.close()
|
|
5176
|
-
this.broadcastChannel = null
|
|
5177
|
-
this.stateChangeEmitters.clear()
|
|
5178
|
-
}
|
|
5179
|
-
|
|
5180
4983
|
/**
|
|
5181
4984
|
* Runs the auto refresh token tick.
|
|
5182
4985
|
*/
|
|
5183
4986
|
private async _autoRefreshTokenTick() {
|
|
5184
4987
|
this._debug('#_autoRefreshTokenTick()', 'begin')
|
|
5185
4988
|
|
|
5186
|
-
if (this.lock != null) {
|
|
5187
|
-
// TODO(v3): remove legacy lock path. Uses `_acquireLock(0, ...)` which
|
|
5188
|
-
// throws `LockAcquireTimeoutError` immediately if the lock is held —
|
|
5189
|
-
// that's the fail-fast skip path that lets the tick bail out instead
|
|
5190
|
-
// of queuing behind a long-running operation.
|
|
5191
|
-
try {
|
|
5192
|
-
await this._acquireLock(0, async () => {
|
|
5193
|
-
try {
|
|
5194
|
-
const now = Date.now()
|
|
5195
|
-
try {
|
|
5196
|
-
return await this._useSession(async (result) => {
|
|
5197
|
-
const {
|
|
5198
|
-
data: { session },
|
|
5199
|
-
} = result
|
|
5200
|
-
|
|
5201
|
-
if (!session || !session.refresh_token || !session.expires_at) {
|
|
5202
|
-
this._debug('#_autoRefreshTokenTick()', 'no session')
|
|
5203
|
-
return
|
|
5204
|
-
}
|
|
5205
|
-
|
|
5206
|
-
const expiresInTicks = Math.floor(
|
|
5207
|
-
(session.expires_at * 1000 - now) / AUTO_REFRESH_TICK_DURATION_MS
|
|
5208
|
-
)
|
|
5209
|
-
|
|
5210
|
-
this._debug(
|
|
5211
|
-
'#_autoRefreshTokenTick()',
|
|
5212
|
-
`access token expires in ${expiresInTicks} ticks, a tick lasts ${AUTO_REFRESH_TICK_DURATION_MS}ms, refresh threshold is ${AUTO_REFRESH_TICK_THRESHOLD} ticks`
|
|
5213
|
-
)
|
|
5214
|
-
|
|
5215
|
-
if (expiresInTicks <= AUTO_REFRESH_TICK_THRESHOLD) {
|
|
5216
|
-
await this._callRefreshToken(session.refresh_token)
|
|
5217
|
-
}
|
|
5218
|
-
})
|
|
5219
|
-
} catch (e) {
|
|
5220
|
-
console.error(
|
|
5221
|
-
'Auto refresh tick failed with error. This is likely a transient error.',
|
|
5222
|
-
e
|
|
5223
|
-
)
|
|
5224
|
-
}
|
|
5225
|
-
} finally {
|
|
5226
|
-
this._debug('#_autoRefreshTokenTick()', 'end')
|
|
5227
|
-
}
|
|
5228
|
-
})
|
|
5229
|
-
} catch (e) {
|
|
5230
|
-
if (e instanceof LockAcquireTimeoutError) {
|
|
5231
|
-
this._debug('auto refresh token tick lock not available')
|
|
5232
|
-
} else {
|
|
5233
|
-
throw e
|
|
5234
|
-
}
|
|
5235
|
-
}
|
|
5236
|
-
return
|
|
5237
|
-
}
|
|
5238
|
-
|
|
5239
|
-
// Lockless default: skip if a refresh is already in flight.
|
|
5240
|
-
// `_callRefreshToken` also dedupes via the same field; this is just a
|
|
5241
|
-
// fast-path skip to avoid an unnecessary storage read.
|
|
5242
|
-
if (this.refreshingDeferred !== null) {
|
|
5243
|
-
this._debug('#_autoRefreshTokenTick()', 'refresh already in flight, skipping')
|
|
5244
|
-
return
|
|
5245
|
-
}
|
|
5246
|
-
|
|
5247
4989
|
try {
|
|
5248
|
-
|
|
5249
|
-
|
|
5250
|
-
|
|
5251
|
-
await this._useSession(async (result) => {
|
|
5252
|
-
const {
|
|
5253
|
-
data: { session },
|
|
5254
|
-
} = result
|
|
4990
|
+
await this._acquireLock(0, async () => {
|
|
4991
|
+
try {
|
|
4992
|
+
const now = Date.now()
|
|
5255
4993
|
|
|
5256
|
-
|
|
5257
|
-
this.
|
|
5258
|
-
|
|
5259
|
-
|
|
4994
|
+
try {
|
|
4995
|
+
return await this._useSession(async (result) => {
|
|
4996
|
+
const {
|
|
4997
|
+
data: { session },
|
|
4998
|
+
} = result
|
|
4999
|
+
|
|
5000
|
+
if (!session || !session.refresh_token || !session.expires_at) {
|
|
5001
|
+
this._debug('#_autoRefreshTokenTick()', 'no session')
|
|
5002
|
+
return
|
|
5003
|
+
}
|
|
5260
5004
|
|
|
5261
|
-
|
|
5262
|
-
|
|
5263
|
-
|
|
5264
|
-
|
|
5005
|
+
// session will expire in this many ticks (or has already expired if <= 0)
|
|
5006
|
+
const expiresInTicks = Math.floor(
|
|
5007
|
+
(session.expires_at * 1000 - now) / AUTO_REFRESH_TICK_DURATION_MS
|
|
5008
|
+
)
|
|
5265
5009
|
|
|
5266
|
-
|
|
5267
|
-
|
|
5268
|
-
|
|
5269
|
-
|
|
5010
|
+
this._debug(
|
|
5011
|
+
'#_autoRefreshTokenTick()',
|
|
5012
|
+
`access token expires in ${expiresInTicks} ticks, a tick lasts ${AUTO_REFRESH_TICK_DURATION_MS}ms, refresh threshold is ${AUTO_REFRESH_TICK_THRESHOLD} ticks`
|
|
5013
|
+
)
|
|
5270
5014
|
|
|
5271
|
-
|
|
5272
|
-
|
|
5015
|
+
if (expiresInTicks <= AUTO_REFRESH_TICK_THRESHOLD) {
|
|
5016
|
+
await this._callRefreshToken(session.refresh_token)
|
|
5017
|
+
}
|
|
5018
|
+
})
|
|
5019
|
+
} catch (e) {
|
|
5020
|
+
console.error(
|
|
5021
|
+
'Auto refresh tick failed with error. This is likely a transient error.',
|
|
5022
|
+
e
|
|
5023
|
+
)
|
|
5273
5024
|
}
|
|
5274
|
-
}
|
|
5275
|
-
|
|
5276
|
-
|
|
5025
|
+
} finally {
|
|
5026
|
+
this._debug('#_autoRefreshTokenTick()', 'end')
|
|
5027
|
+
}
|
|
5028
|
+
})
|
|
5029
|
+
} catch (e) {
|
|
5030
|
+
if (e instanceof LockAcquireTimeoutError) {
|
|
5031
|
+
this._debug('auto refresh token tick lock not available')
|
|
5032
|
+
} else {
|
|
5033
|
+
throw e
|
|
5277
5034
|
}
|
|
5278
|
-
} finally {
|
|
5279
|
-
this._debug('#_autoRefreshTokenTick()', 'end')
|
|
5280
5035
|
}
|
|
5281
5036
|
}
|
|
5282
5037
|
|
|
@@ -5333,29 +5088,24 @@ export default class GoTrueClient {
|
|
|
5333
5088
|
if (!calledFromInitialize) {
|
|
5334
5089
|
// called when the visibility has changed, i.e. the browser
|
|
5335
5090
|
// transitioned from hidden -> visible so we need to see if the session
|
|
5336
|
-
// should be recovered
|
|
5091
|
+
// should be recovered immediately... but to do that we need to acquire
|
|
5092
|
+
// the lock first asynchronously
|
|
5337
5093
|
await this.initializePromise
|
|
5338
5094
|
|
|
5339
|
-
|
|
5340
|
-
// TODO(v3): remove legacy lock path
|
|
5341
|
-
await this._acquireLock(this.lockAcquireTimeout, async () => {
|
|
5342
|
-
if (document.visibilityState !== 'visible') {
|
|
5343
|
-
this._debug(
|
|
5344
|
-
methodName,
|
|
5345
|
-
'acquired the lock to recover the session, but the browser visibilityState is no longer visible, aborting'
|
|
5346
|
-
)
|
|
5347
|
-
return
|
|
5348
|
-
}
|
|
5349
|
-
await this._recoverAndRefresh()
|
|
5350
|
-
})
|
|
5351
|
-
} else {
|
|
5095
|
+
await this._acquireLock(this.lockAcquireTimeout, async () => {
|
|
5352
5096
|
if (document.visibilityState !== 'visible') {
|
|
5353
|
-
this._debug(
|
|
5097
|
+
this._debug(
|
|
5098
|
+
methodName,
|
|
5099
|
+
'acquired the lock to recover the session, but the browser visibilityState is no longer visible, aborting'
|
|
5100
|
+
)
|
|
5101
|
+
|
|
5102
|
+
// visibility has changed while waiting for the lock, abort
|
|
5354
5103
|
return
|
|
5355
5104
|
}
|
|
5105
|
+
|
|
5356
5106
|
// recover the session
|
|
5357
5107
|
await this._recoverAndRefresh()
|
|
5358
|
-
}
|
|
5108
|
+
})
|
|
5359
5109
|
}
|
|
5360
5110
|
} else if (document.visibilityState === 'hidden') {
|
|
5361
5111
|
if (this.autoRefreshToken) {
|
|
@@ -5487,7 +5237,7 @@ export default class GoTrueClient {
|
|
|
5487
5237
|
params: MFAVerifyWebauthnParams<T>
|
|
5488
5238
|
): Promise<AuthMFAVerifyResponse>
|
|
5489
5239
|
private async _verify(params: MFAVerifyParams): Promise<AuthMFAVerifyResponse> {
|
|
5490
|
-
|
|
5240
|
+
return this._acquireLock(this.lockAcquireTimeout, async () => {
|
|
5491
5241
|
try {
|
|
5492
5242
|
return await this._useSession(async (result) => {
|
|
5493
5243
|
const { data: sessionData, error: sessionError } = result
|
|
@@ -5558,13 +5308,7 @@ export default class GoTrueClient {
|
|
|
5558
5308
|
}
|
|
5559
5309
|
throw error
|
|
5560
5310
|
}
|
|
5561
|
-
}
|
|
5562
|
-
|
|
5563
|
-
if (this.lock != null) {
|
|
5564
|
-
// TODO(v3): remove legacy lock path
|
|
5565
|
-
return this._acquireLock(this.lockAcquireTimeout, run)
|
|
5566
|
-
}
|
|
5567
|
-
return run()
|
|
5311
|
+
})
|
|
5568
5312
|
}
|
|
5569
5313
|
|
|
5570
5314
|
/**
|
|
@@ -5580,7 +5324,7 @@ export default class GoTrueClient {
|
|
|
5580
5324
|
params: MFAChallengeWebauthnParams
|
|
5581
5325
|
): Promise<Prettify<AuthMFAChallengeWebauthnResponse>>
|
|
5582
5326
|
private async _challenge(params: MFAChallengeParams): Promise<AuthMFAChallengeResponse> {
|
|
5583
|
-
|
|
5327
|
+
return this._acquireLock(this.lockAcquireTimeout, async () => {
|
|
5584
5328
|
try {
|
|
5585
5329
|
return await this._useSession(async (result) => {
|
|
5586
5330
|
const { data: sessionData, error: sessionError } = result
|
|
@@ -5653,13 +5397,7 @@ export default class GoTrueClient {
|
|
|
5653
5397
|
}
|
|
5654
5398
|
throw error
|
|
5655
5399
|
}
|
|
5656
|
-
}
|
|
5657
|
-
|
|
5658
|
-
if (this.lock != null) {
|
|
5659
|
-
// TODO(v3): remove legacy lock path
|
|
5660
|
-
return this._acquireLock(this.lockAcquireTimeout, run)
|
|
5661
|
-
}
|
|
5662
|
-
return run()
|
|
5400
|
+
})
|
|
5663
5401
|
}
|
|
5664
5402
|
|
|
5665
5403
|
/**
|
|
@@ -5668,6 +5406,9 @@ export default class GoTrueClient {
|
|
|
5668
5406
|
private async _challengeAndVerify(
|
|
5669
5407
|
params: MFAChallengeAndVerifyParams
|
|
5670
5408
|
): Promise<AuthMFAVerifyResponse> {
|
|
5409
|
+
// both _challenge and _verify independently acquire the lock, so no need
|
|
5410
|
+
// to acquire it here
|
|
5411
|
+
|
|
5671
5412
|
const { data: challengeData, error: challengeError } = await this._challenge({
|
|
5672
5413
|
factorId: params.factorId,
|
|
5673
5414
|
})
|
|
@@ -5686,6 +5427,7 @@ export default class GoTrueClient {
|
|
|
5686
5427
|
* {@see GoTrueMFAApi#listFactors}
|
|
5687
5428
|
*/
|
|
5688
5429
|
private async _listFactors(): Promise<AuthMFAListFactorsResponse> {
|
|
5430
|
+
// use #getUser instead of #_getUser as the former acquires a lock
|
|
5689
5431
|
const {
|
|
5690
5432
|
data: { user },
|
|
5691
5433
|
error: userError,
|
|
@@ -6165,8 +5907,13 @@ export default class GoTrueClient {
|
|
|
6165
5907
|
} = decodeJWT(token)
|
|
6166
5908
|
|
|
6167
5909
|
if (!options?.allowExpired) {
|
|
6168
|
-
// Reject expired JWTs should only happen if jwt argument was passed
|
|
6169
|
-
|
|
5910
|
+
// Reject expired JWTs should only happen if jwt argument was passed.
|
|
5911
|
+
// Rethrow as AuthInvalidJwtError so the outer catch converts it to { data, error }.
|
|
5912
|
+
try {
|
|
5913
|
+
validateExp(payload.exp)
|
|
5914
|
+
} catch (e) {
|
|
5915
|
+
throw new AuthInvalidJwtError(e instanceof Error ? e.message : 'JWT validation failed')
|
|
5916
|
+
}
|
|
6170
5917
|
}
|
|
6171
5918
|
|
|
6172
5919
|
const signingKey =
|