@depup/supabase__auth-js 2.103.0-depup.0 → 2.110.8-depup.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.
Files changed (106) hide show
  1. package/AGENTS.md +11 -0
  2. package/README.md +2 -2
  3. package/changes.json +1 -1
  4. package/dist/main/GoTrueAdminApi.d.ts +45 -8
  5. package/dist/main/GoTrueAdminApi.d.ts.map +1 -1
  6. package/dist/main/GoTrueAdminApi.js +67 -7
  7. package/dist/main/GoTrueAdminApi.js.map +1 -1
  8. package/dist/main/GoTrueClient.d.ts +239 -42
  9. package/dist/main/GoTrueClient.d.ts.map +1 -1
  10. package/dist/main/GoTrueClient.js +946 -164
  11. package/dist/main/GoTrueClient.js.map +1 -1
  12. package/dist/main/lib/constants.d.ts +8 -0
  13. package/dist/main/lib/constants.d.ts.map +1 -1
  14. package/dist/main/lib/constants.js +9 -1
  15. package/dist/main/lib/constants.js.map +1 -1
  16. package/dist/main/lib/errors.d.ts +42 -3
  17. package/dist/main/lib/errors.d.ts.map +1 -1
  18. package/dist/main/lib/errors.js +44 -13
  19. package/dist/main/lib/errors.js.map +1 -1
  20. package/dist/main/lib/fetch.d.ts +28 -8
  21. package/dist/main/lib/fetch.d.ts.map +1 -1
  22. package/dist/main/lib/fetch.js +28 -6
  23. package/dist/main/lib/fetch.js.map +1 -1
  24. package/dist/main/lib/helpers.d.ts +4 -1
  25. package/dist/main/lib/helpers.d.ts.map +1 -1
  26. package/dist/main/lib/helpers.js +15 -4
  27. package/dist/main/lib/helpers.js.map +1 -1
  28. package/dist/main/lib/locks.d.ts +29 -35
  29. package/dist/main/lib/locks.d.ts.map +1 -1
  30. package/dist/main/lib/locks.js +37 -38
  31. package/dist/main/lib/locks.js.map +1 -1
  32. package/dist/main/lib/types.d.ts +305 -64
  33. package/dist/main/lib/types.d.ts.map +1 -1
  34. package/dist/main/lib/types.js.map +1 -1
  35. package/dist/main/lib/version.d.ts +1 -1
  36. package/dist/main/lib/version.js +1 -1
  37. package/dist/main/lib/webauthn.d.ts +8 -0
  38. package/dist/main/lib/webauthn.d.ts.map +1 -1
  39. package/dist/main/lib/webauthn.dom.d.ts +3 -3
  40. package/dist/main/lib/webauthn.dom.d.ts.map +1 -1
  41. package/dist/main/lib/webauthn.errors.d.ts +5 -0
  42. package/dist/main/lib/webauthn.errors.d.ts.map +1 -1
  43. package/dist/main/lib/webauthn.errors.js +7 -0
  44. package/dist/main/lib/webauthn.errors.js.map +1 -1
  45. package/dist/main/lib/webauthn.js +1 -0
  46. package/dist/main/lib/webauthn.js.map +1 -1
  47. package/dist/module/GoTrueAdminApi.d.ts +45 -8
  48. package/dist/module/GoTrueAdminApi.d.ts.map +1 -1
  49. package/dist/module/GoTrueAdminApi.js +68 -8
  50. package/dist/module/GoTrueAdminApi.js.map +1 -1
  51. package/dist/module/GoTrueClient.d.ts +239 -42
  52. package/dist/module/GoTrueClient.d.ts.map +1 -1
  53. package/dist/module/GoTrueClient.js +951 -169
  54. package/dist/module/GoTrueClient.js.map +1 -1
  55. package/dist/module/lib/constants.d.ts +8 -0
  56. package/dist/module/lib/constants.d.ts.map +1 -1
  57. package/dist/module/lib/constants.js +8 -0
  58. package/dist/module/lib/constants.js.map +1 -1
  59. package/dist/module/lib/errors.d.ts +42 -3
  60. package/dist/module/lib/errors.d.ts.map +1 -1
  61. package/dist/module/lib/errors.js +41 -12
  62. package/dist/module/lib/errors.js.map +1 -1
  63. package/dist/module/lib/fetch.d.ts +28 -8
  64. package/dist/module/lib/fetch.d.ts.map +1 -1
  65. package/dist/module/lib/fetch.js +28 -6
  66. package/dist/module/lib/fetch.js.map +1 -1
  67. package/dist/module/lib/helpers.d.ts +4 -1
  68. package/dist/module/lib/helpers.d.ts.map +1 -1
  69. package/dist/module/lib/helpers.js +14 -4
  70. package/dist/module/lib/helpers.js.map +1 -1
  71. package/dist/module/lib/locks.d.ts +29 -35
  72. package/dist/module/lib/locks.d.ts.map +1 -1
  73. package/dist/module/lib/locks.js +37 -38
  74. package/dist/module/lib/locks.js.map +1 -1
  75. package/dist/module/lib/types.d.ts +305 -64
  76. package/dist/module/lib/types.d.ts.map +1 -1
  77. package/dist/module/lib/types.js.map +1 -1
  78. package/dist/module/lib/version.d.ts +1 -1
  79. package/dist/module/lib/version.js +1 -1
  80. package/dist/module/lib/webauthn.d.ts +8 -0
  81. package/dist/module/lib/webauthn.d.ts.map +1 -1
  82. package/dist/module/lib/webauthn.dom.d.ts +3 -3
  83. package/dist/module/lib/webauthn.dom.d.ts.map +1 -1
  84. package/dist/module/lib/webauthn.errors.d.ts +5 -0
  85. package/dist/module/lib/webauthn.errors.d.ts.map +1 -1
  86. package/dist/module/lib/webauthn.errors.js +7 -0
  87. package/dist/module/lib/webauthn.errors.js.map +1 -1
  88. package/dist/module/lib/webauthn.js +1 -1
  89. package/dist/module/lib/webauthn.js.map +1 -1
  90. package/dist/tsconfig.module.tsbuildinfo +1 -1
  91. package/dist/tsconfig.tsbuildinfo +1 -1
  92. package/migrations/README.md +25 -0
  93. package/migrations/lockless-coordination.md +155 -0
  94. package/package.json +25 -12
  95. package/src/GoTrueAdminApi.ts +99 -7
  96. package/src/GoTrueClient.ts +1117 -181
  97. package/src/lib/constants.ts +9 -0
  98. package/src/lib/errors.ts +76 -9
  99. package/src/lib/fetch.ts +66 -24
  100. package/src/lib/helpers.ts +19 -6
  101. package/src/lib/locks.ts +44 -42
  102. package/src/lib/types.ts +374 -82
  103. package/src/lib/version.ts +1 -1
  104. package/src/lib/webauthn.dom.ts +3 -3
  105. package/src/lib/webauthn.errors.ts +12 -0
  106. package/src/lib/webauthn.ts +1 -1
@@ -1,15 +1,15 @@
1
1
  import GoTrueAdminApi from './GoTrueAdminApi';
2
- import { AUTO_REFRESH_TICK_DURATION_MS, AUTO_REFRESH_TICK_THRESHOLD, DEFAULT_HEADERS, EXPIRY_MARGIN_MS, GOTRUE_URL, JWKS_TTL, STORAGE_KEY, } from './lib/constants';
3
- import { AuthImplicitGrantRedirectError, AuthInvalidCredentialsError, AuthInvalidJwtError, AuthInvalidTokenResponseError, AuthPKCECodeVerifierMissingError, AuthPKCEGrantCodeExchangeError, AuthSessionMissingError, AuthUnknownError, isAuthApiError, isAuthError, isAuthImplicitGrantRedirectError, isAuthRetryableFetchError, isAuthSessionMissingError, } from './lib/errors';
2
+ import { AUTO_REFRESH_TICK_DURATION_MS, AUTO_REFRESH_TICK_THRESHOLD, DEFAULT_HEADERS, EXPIRY_MARGIN_MS, GOTRUE_URL, JWKS_TTL, REFRESH_FAILURE_COOLDOWN_MS, STORAGE_KEY, } from './lib/constants';
3
+ import { AuthImplicitGrantRedirectError, AuthInvalidCredentialsError, AuthInvalidJwtError, AuthInvalidTokenResponseError, AuthPKCECodeVerifierMissingError, AuthPKCEGrantCodeExchangeError, AuthRefreshDiscardedError, AuthSessionMissingError, AuthUnknownError, isAuthApiError, isAuthError, isAuthImplicitGrantRedirectError, isAuthRefreshDiscardedError, isAuthRetryableFetchError, isAuthSessionMissingError, } from './lib/errors';
4
4
  import { _request, _sessionResponse, _sessionResponsePassword, _ssoResponse, _userResponse, } from './lib/fetch';
5
- import { decodeJWT, deepClone, Deferred, generateCallbackId, getAlgorithm, getCodeChallengeAndMethod, getItemAsync, insecureUserWarningProxy, isBrowser, parseParametersFromURL, removeItemAsync, resolveFetch, retryable, setItemAsync, sleep, supportsLocalStorage, userNotAvailableProxy, validateExp, } from './lib/helpers';
5
+ import { assertPasskeyExperimentalEnabled, decodeJWT, deepClone, Deferred, generateCallbackId, getAlgorithm, getCodeChallengeAndMethod, getItemAsync, insecureUserWarningProxy, isBrowser, parseParametersFromURL, removeItemAsync, resolveFetch, retryable, setItemAsync, sleep, supportsLocalStorage, userNotAvailableProxy, validateExp, } from './lib/helpers';
6
6
  import { memoryLocalStorageAdapter } from './lib/local-storage';
7
- import { LockAcquireTimeoutError, navigatorLock } from './lib/locks';
7
+ import { LockAcquireTimeoutError } from './lib/locks';
8
8
  import { polyfillGlobalThis } from './lib/polyfills';
9
9
  import { version } from './lib/version';
10
10
  import { bytesToBase64URL, stringToUint8Array } from './lib/base64url';
11
11
  import { createSiweMessage, fromHex, getAddress, toHex, } from './lib/web3/ethereum';
12
- import { deserializeCredentialCreationOptions, deserializeCredentialRequestOptions, serializeCredentialCreationResponse, serializeCredentialRequestResponse, WebAuthnApi, } from './lib/webauthn';
12
+ import { createCredential, deserializeCredentialCreationOptions, deserializeCredentialRequestOptions, getCredential, serializeCredentialCreationResponse, serializeCredentialRequestResponse, browserSupportsWebAuthn, webAuthnAbortService, WebAuthnApi, } from './lib/webauthn';
13
13
  polyfillGlobalThis(); // Make "globalThis" available
14
14
  const DEFAULT_OPTIONS = {
15
15
  url: GOTRUE_URL,
@@ -22,9 +22,16 @@ const DEFAULT_OPTIONS = {
22
22
  debug: false,
23
23
  hasCustomAuthorizationHeader: false,
24
24
  throwOnError: false,
25
- lockAcquireTimeout: 5000, // 5 seconds
25
+ lockAcquireTimeout: 5000, // 5 seconds. Only used when a custom `lock` is supplied. TODO(v3): remove.
26
26
  skipAutoInitialize: false,
27
+ experimental: {},
27
28
  };
29
+ /**
30
+ * No-op lock used internally as a placeholder. Kept so older test setups that
31
+ * inject this exact reference do not break; new code never sees it because
32
+ * `this.lock` stays `null` when no custom lock is supplied (lockless path).
33
+ * TODO(v3): remove with the legacy lock path.
34
+ */
28
35
  async function lockNoOp(name, acquireTimeout, fn) {
29
36
  return await fn();
30
37
  }
@@ -58,13 +65,21 @@ class GoTrueClient {
58
65
  /**
59
66
  * Create a new client for use in the browser.
60
67
  *
61
- * @example
68
+ * @example Using supabase-js (recommended)
69
+ * ```ts
70
+ * import { createClient } from '@supabase/supabase-js'
71
+ *
72
+ * const supabase = createClient('https://xyzcompany.supabase.co', 'your-publishable-key')
73
+ * const { data, error } = await supabase.auth.getUser()
74
+ * ```
75
+ *
76
+ * @example Standalone import for bundle-sensitive environments
62
77
  * ```ts
63
78
  * import { GoTrueClient } from '@supabase/auth-js'
64
79
  *
65
80
  * const auth = new GoTrueClient({
66
81
  * url: 'https://xyzcompany.supabase.co/auth/v1',
67
- * headers: { apikey: 'public-anon-key' },
82
+ * headers: { apikey: 'your-publishable-key' },
68
83
  * storageKey: 'supabase-auth',
69
84
  * })
70
85
  * ```
@@ -81,6 +96,29 @@ class GoTrueClient {
81
96
  this.autoRefreshTickTimeout = null;
82
97
  this.visibilityChangedCallback = null;
83
98
  this.refreshingDeferred = null;
99
+ /**
100
+ * Cache of the most recent refresh failure, keyed by the refresh token
101
+ * that failed. Serial callers passing the *same* token within
102
+ * `REFRESH_FAILURE_COOLDOWN_MS` (including subsequent auto-refresh ticks)
103
+ * receive this cached result instead of firing another `/token` request.
104
+ * Callers passing a *different* token (token rotation pickup, explicit
105
+ * `setSession`/`refreshSession({ refresh_token })`, multi-account switch)
106
+ * bypass the cache and attempt a fresh refresh as they should.
107
+ * Cleared on any successful refresh (locally or via BroadcastChannel from
108
+ * another tab) and on `_removeSession`.
109
+ *
110
+ * Pairs with `refreshingDeferred`: concurrent callers share the in-flight
111
+ * promise, serial callers within the cooldown share the failure result.
112
+ */
113
+ this.lastRefreshFailure = null;
114
+ /**
115
+ * Monotonic counter incremented at the top of `_removeSession`, before any
116
+ * `await`. The commit guard inside `_callRefreshToken` captures this value
117
+ * before `_saveSession` and re-checks it after, so a `signOut` that
118
+ * interleaves inside `_saveSession`'s storage-write awaits is still caught
119
+ * (the post-fetch storage snapshot alone misses that window).
120
+ */
121
+ this._sessionRemovalEpoch = 0;
84
122
  /**
85
123
  * Keeps track of the async client initialization.
86
124
  * When null or not yet resolved the auth state is `unknown`
@@ -88,9 +126,29 @@ class GoTrueClient {
88
126
  * Keep extra care to never reject or throw uncaught errors
89
127
  */
90
128
  this.initializePromise = null;
129
+ /**
130
+ * Non-null only while `initialize()` is running. While open,
131
+ * `_notifyAllSubscribers` enqueues init-chain notifications (those fired with
132
+ * `broadcast = true`, i.e. from `_recoverAndRefresh`) into this array instead
133
+ * of firing directly, so that `initializePromise` is guaranteed to be
134
+ * resolved before any subscriber callback runs. Callbacks that call
135
+ * `getSession()` / `getUser()` etc. would otherwise deadlock because those
136
+ * methods await `initializePromise`. Notifications from the incoming
137
+ * BroadcastChannel handler (`broadcast = false`) are not enqueued — they fire
138
+ * immediately. Flushed (in order) by `initialize()` after `initializePromise`
139
+ * settles.
140
+ */
141
+ this._pendingInitNotifications = null;
91
142
  this.detectSessionInUrl = true;
92
143
  this.hasCustomAuthorizationHeader = false;
93
144
  this.suppressGetSessionWarning = false;
145
+ /**
146
+ * Custom lock function passed via `settings.lock`. When non-null, every auth
147
+ * operation runs inside `_acquireLock`. When null (the default), the client
148
+ * uses its lockless coordination (refresh single-flight + commit guard).
149
+ * TODO(v3): remove along with the legacy lock path.
150
+ */
151
+ this.lock = null;
94
152
  this.lockAcquired = false;
95
153
  this.pendingInLock = [];
96
154
  /**
@@ -115,29 +173,32 @@ class GoTrueClient {
115
173
  }
116
174
  this.persistSession = settings.persistSession;
117
175
  this.autoRefreshToken = settings.autoRefreshToken;
176
+ this.experimental = (_b = settings.experimental) !== null && _b !== void 0 ? _b : {};
118
177
  this.admin = new GoTrueAdminApi({
119
178
  url: settings.url,
120
179
  headers: settings.headers,
121
180
  fetch: settings.fetch,
181
+ experimental: this.experimental,
122
182
  });
123
183
  this.url = settings.url;
124
184
  this.headers = settings.headers;
125
185
  this.fetch = resolveFetch(settings.fetch);
126
- this.lock = settings.lock || lockNoOp;
127
186
  this.detectSessionInUrl = settings.detectSessionInUrl;
128
187
  this.flowType = settings.flowType;
129
188
  this.hasCustomAuthorizationHeader = settings.hasCustomAuthorizationHeader;
130
189
  this.throwOnError = settings.throwOnError;
190
+ // Always wire `lockAcquireTimeout` even on the lockless path: consumers
191
+ // (including supabase-js tests) read it off the client to verify option
192
+ // flow-through.
131
193
  this.lockAcquireTimeout = settings.lockAcquireTimeout;
132
- if (settings.lock) {
194
+ // TODO(v3): remove. Legacy opt-in path preserved for backwards
195
+ // compatibility with callers passing a custom `lock` (typically React
196
+ // Native `processLock` or Node multi-process setups). When `settings.lock`
197
+ // is null the client uses its lockless coordination — no `navigator.locks`
198
+ // by default, no implicit `processLock`.
199
+ if (settings.lock != null) {
133
200
  this.lock = settings.lock;
134
201
  }
135
- else if (this.persistSession && isBrowser() && ((_b = globalThis === null || globalThis === void 0 ? void 0 : globalThis.navigator) === null || _b === void 0 ? void 0 : _b.locks)) {
136
- this.lock = navigatorLock;
137
- }
138
- else {
139
- this.lock = lockNoOp;
140
- }
141
202
  if (!this.jwks) {
142
203
  this.jwks = { keys: [] };
143
204
  this.jwks_cached_at = Number.MIN_SAFE_INTEGER;
@@ -159,6 +220,15 @@ class GoTrueClient {
159
220
  listGrants: this._listOAuthGrants.bind(this),
160
221
  revokeGrant: this._revokeOAuthGrant.bind(this),
161
222
  };
223
+ this.passkey = {
224
+ startRegistration: this._startPasskeyRegistration.bind(this),
225
+ verifyRegistration: this._verifyPasskeyRegistration.bind(this),
226
+ startAuthentication: this._startPasskeyAuthentication.bind(this),
227
+ verifyAuthentication: this._verifyPasskeyAuthentication.bind(this),
228
+ list: this._listPasskeys.bind(this),
229
+ update: this._updatePasskey.bind(this),
230
+ delete: this._deletePasskey.bind(this),
231
+ };
162
232
  if (this.persistSession) {
163
233
  if (settings.storage) {
164
234
  this.storage = settings.storage;
@@ -189,6 +259,12 @@ class GoTrueClient {
189
259
  }
190
260
  (_c = this.broadcastChannel) === null || _c === void 0 ? void 0 : _c.addEventListener('message', async (event) => {
191
261
  this._debug('received broadcast notification from other tab or client', event);
262
+ // Another tab successfully refreshed or signed in — any cached
263
+ // failure in this tab is stale and should not block the next
264
+ // refresh attempt.
265
+ if (event.data.event === 'TOKEN_REFRESHED' || event.data.event === 'SIGNED_IN') {
266
+ this.lastRefreshFailure = null;
267
+ }
192
268
  try {
193
269
  await this._notifyAllSubscribers(event.data.event, event.data.session, false); // broadcast = false so we don't get an endless loop of messages
194
270
  }
@@ -234,22 +310,55 @@ class GoTrueClient {
234
310
  return this;
235
311
  }
236
312
  /**
237
- * Initializes the client session either from the url or from storage.
238
- * This method is automatically called when instantiating the client, but should also be called
239
- * manually when checking for an error from an auth redirect (oauth, magiclink, password recovery, etc).
313
+ * Initialize the auth client by loading the session from storage or
314
+ * detecting it from the URL after an OAuth, magic-link, or password-recovery
315
+ * redirect.
316
+ *
317
+ * **Most callers do not need to invoke this directly.** The client calls it
318
+ * automatically during construction, and to react to sign-in events (including
319
+ * post-redirect events) you should subscribe to `onAuthStateChange` rather
320
+ * than awaiting `initialize()`.
321
+ *
322
+ * You only need to call it manually when you have opted out of the automatic
323
+ * call by passing `skipAutoInitialize: true` — for example, in an SSR context
324
+ * where you need to control initialization timing. In that case, awaiting
325
+ * `initialize()` returns the resolved session result (or any error encountered
326
+ * while detecting it from the URL).
240
327
  *
241
328
  * @category Auth
242
329
  */
243
330
  async initialize() {
331
+ var _a;
244
332
  if (this.initializePromise) {
245
333
  return await this.initializePromise;
246
334
  }
335
+ // Open the notification queue before _initialize() runs so that every
336
+ // _notifyAllSubscribers call inside the init chain enqueues instead of
337
+ // firing. Without this, a callback receiving SIGNED_IN (or TOKEN_REFRESHED
338
+ // / SIGNED_OUT) during _recoverAndRefresh would deadlock if it called
339
+ // getSession() / getUser() — those methods await initializePromise, which
340
+ // can only resolve after the callback returns, which can only return after
341
+ // getSession() resolves. The queue is flushed below after initializePromise
342
+ // has settled, so callbacks run with a fully resolved initializePromise.
343
+ this._pendingInitNotifications = [];
247
344
  this.initializePromise = (async () => {
248
- return await this._acquireLock(this.lockAcquireTimeout, async () => {
249
- return await this._initialize();
250
- });
345
+ if (this.lock != null) {
346
+ // TODO(v3): remove legacy lock path
347
+ return await this._acquireLock(this.lockAcquireTimeout, async () => {
348
+ return await this._initialize();
349
+ });
350
+ }
351
+ return await this._initialize();
251
352
  })();
252
- return await this.initializePromise;
353
+ const result = await this.initializePromise;
354
+ // initializePromise is now resolved — flush queued notifications in order.
355
+ // Callbacks can safely call getSession() / getUser() / signOut() etc.
356
+ const queue = (_a = this._pendingInitNotifications) !== null && _a !== void 0 ? _a : [];
357
+ this._pendingInitNotifications = null;
358
+ for (const n of queue) {
359
+ await this._notifyAllSubscribers(n.event, n.session, n.broadcast);
360
+ }
361
+ return result;
253
362
  }
254
363
  /**
255
364
  * IMPORTANT:
@@ -783,6 +892,21 @@ class GoTrueClient {
783
892
  * password: 'some-password',
784
893
  * })
785
894
  * ```
895
+ *
896
+ * @exampleDescription Handling errors
897
+ * Log the full `error` object so fields like `code`, `status`, and `name` aren't hidden. The `error.code` (e.g. `'invalid_credentials'`, `'email_not_confirmed'`) is often more useful for branching than `error.message`, and the full object surfaces both.
898
+ *
899
+ * @example Handling errors
900
+ * ```js
901
+ * const { data, error } = await supabase.auth.signInWithPassword({
902
+ * email: 'example@email.com',
903
+ * password: 'example-password',
904
+ * })
905
+ * if (error) {
906
+ * console.error(error)
907
+ * return
908
+ * }
909
+ * ```
786
910
  */
787
911
  async signInWithPassword(credentials) {
788
912
  try {
@@ -1098,9 +1222,13 @@ class GoTrueClient {
1098
1222
  */
1099
1223
  async exchangeCodeForSession(authCode) {
1100
1224
  await this.initializePromise;
1101
- return this._acquireLock(this.lockAcquireTimeout, async () => {
1102
- return this._exchangeCodeForSession(authCode);
1103
- });
1225
+ if (this.lock != null) {
1226
+ // TODO(v3): remove legacy lock path
1227
+ return this._acquireLock(this.lockAcquireTimeout, async () => {
1228
+ return this._exchangeCodeForSession(authCode);
1229
+ });
1230
+ }
1231
+ return this._exchangeCodeForSession(authCode);
1104
1232
  }
1105
1233
  /**
1106
1234
  * Signs in a user by verifying a message signed by the user's private key.
@@ -1202,7 +1330,7 @@ class GoTrueClient {
1202
1330
  }
1203
1331
  }
1204
1332
  async signInWithEthereum(credentials) {
1205
- var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k, _l;
1333
+ var _a, _b, _c, _d, _f, _g, _h, _j, _k, _l, _m;
1206
1334
  // TODO: flatten type
1207
1335
  let message;
1208
1336
  let signature;
@@ -1262,11 +1390,11 @@ class GoTrueClient {
1262
1390
  version: '1',
1263
1391
  chainId: chainId,
1264
1392
  nonce: (_c = options === null || options === void 0 ? void 0 : options.signInWithEthereum) === null || _c === void 0 ? void 0 : _c.nonce,
1265
- issuedAt: (_e = (_d = options === null || options === void 0 ? void 0 : options.signInWithEthereum) === null || _d === void 0 ? void 0 : _d.issuedAt) !== null && _e !== void 0 ? _e : new Date(),
1266
- expirationTime: (_f = options === null || options === void 0 ? void 0 : options.signInWithEthereum) === null || _f === void 0 ? void 0 : _f.expirationTime,
1267
- notBefore: (_g = options === null || options === void 0 ? void 0 : options.signInWithEthereum) === null || _g === void 0 ? void 0 : _g.notBefore,
1268
- requestId: (_h = options === null || options === void 0 ? void 0 : options.signInWithEthereum) === null || _h === void 0 ? void 0 : _h.requestId,
1269
- resources: (_j = options === null || options === void 0 ? void 0 : options.signInWithEthereum) === null || _j === void 0 ? void 0 : _j.resources,
1393
+ issuedAt: (_f = (_d = options === null || options === void 0 ? void 0 : options.signInWithEthereum) === null || _d === void 0 ? void 0 : _d.issuedAt) !== null && _f !== void 0 ? _f : new Date(),
1394
+ expirationTime: (_g = options === null || options === void 0 ? void 0 : options.signInWithEthereum) === null || _g === void 0 ? void 0 : _g.expirationTime,
1395
+ notBefore: (_h = options === null || options === void 0 ? void 0 : options.signInWithEthereum) === null || _h === void 0 ? void 0 : _h.notBefore,
1396
+ requestId: (_j = options === null || options === void 0 ? void 0 : options.signInWithEthereum) === null || _j === void 0 ? void 0 : _j.requestId,
1397
+ resources: (_k = options === null || options === void 0 ? void 0 : options.signInWithEthereum) === null || _k === void 0 ? void 0 : _k.resources,
1270
1398
  };
1271
1399
  message = createSiweMessage(siweMessage);
1272
1400
  // Sign message
@@ -1279,8 +1407,8 @@ class GoTrueClient {
1279
1407
  const { data, error } = await _request(this.fetch, 'POST', `${this.url}/token?grant_type=web3`, {
1280
1408
  headers: this.headers,
1281
1409
  body: Object.assign({ chain: 'ethereum', message,
1282
- signature }, (((_k = credentials.options) === null || _k === void 0 ? void 0 : _k.captchaToken)
1283
- ? { gotrue_meta_security: { captcha_token: (_l = credentials.options) === null || _l === void 0 ? void 0 : _l.captchaToken } }
1410
+ signature }, (((_l = credentials.options) === null || _l === void 0 ? void 0 : _l.captchaToken)
1411
+ ? { gotrue_meta_security: { captcha_token: (_m = credentials.options) === null || _m === void 0 ? void 0 : _m.captchaToken } }
1284
1412
  : null)),
1285
1413
  xform: _sessionResponse,
1286
1414
  });
@@ -1305,7 +1433,7 @@ class GoTrueClient {
1305
1433
  }
1306
1434
  }
1307
1435
  async signInWithSolana(credentials) {
1308
- var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k, _l, _m;
1436
+ var _a, _b, _c, _d, _f, _g, _h, _j, _k, _l, _m, _o;
1309
1437
  let message;
1310
1438
  let signature;
1311
1439
  if ('message' in credentials) {
@@ -1390,17 +1518,17 @@ class GoTrueClient {
1390
1518
  ...(((_d = options === null || options === void 0 ? void 0 : options.signInWithSolana) === null || _d === void 0 ? void 0 : _d.notBefore)
1391
1519
  ? [`Not Before: ${options.signInWithSolana.notBefore}`]
1392
1520
  : []),
1393
- ...(((_e = options === null || options === void 0 ? void 0 : options.signInWithSolana) === null || _e === void 0 ? void 0 : _e.expirationTime)
1521
+ ...(((_f = options === null || options === void 0 ? void 0 : options.signInWithSolana) === null || _f === void 0 ? void 0 : _f.expirationTime)
1394
1522
  ? [`Expiration Time: ${options.signInWithSolana.expirationTime}`]
1395
1523
  : []),
1396
- ...(((_f = options === null || options === void 0 ? void 0 : options.signInWithSolana) === null || _f === void 0 ? void 0 : _f.chainId)
1524
+ ...(((_g = options === null || options === void 0 ? void 0 : options.signInWithSolana) === null || _g === void 0 ? void 0 : _g.chainId)
1397
1525
  ? [`Chain ID: ${options.signInWithSolana.chainId}`]
1398
1526
  : []),
1399
- ...(((_g = options === null || options === void 0 ? void 0 : options.signInWithSolana) === null || _g === void 0 ? void 0 : _g.nonce) ? [`Nonce: ${options.signInWithSolana.nonce}`] : []),
1400
- ...(((_h = options === null || options === void 0 ? void 0 : options.signInWithSolana) === null || _h === void 0 ? void 0 : _h.requestId)
1527
+ ...(((_h = options === null || options === void 0 ? void 0 : options.signInWithSolana) === null || _h === void 0 ? void 0 : _h.nonce) ? [`Nonce: ${options.signInWithSolana.nonce}`] : []),
1528
+ ...(((_j = options === null || options === void 0 ? void 0 : options.signInWithSolana) === null || _j === void 0 ? void 0 : _j.requestId)
1401
1529
  ? [`Request ID: ${options.signInWithSolana.requestId}`]
1402
1530
  : []),
1403
- ...(((_k = (_j = options === null || options === void 0 ? void 0 : options.signInWithSolana) === null || _j === void 0 ? void 0 : _j.resources) === null || _k === void 0 ? void 0 : _k.length)
1531
+ ...(((_l = (_k = options === null || options === void 0 ? void 0 : options.signInWithSolana) === null || _k === void 0 ? void 0 : _k.resources) === null || _l === void 0 ? void 0 : _l.length)
1404
1532
  ? [
1405
1533
  'Resources',
1406
1534
  ...options.signInWithSolana.resources.map((resource) => `- ${resource}`),
@@ -1417,8 +1545,8 @@ class GoTrueClient {
1417
1545
  try {
1418
1546
  const { data, error } = await _request(this.fetch, 'POST', `${this.url}/token?grant_type=web3`, {
1419
1547
  headers: this.headers,
1420
- body: Object.assign({ chain: 'solana', message, signature: bytesToBase64URL(signature) }, (((_l = credentials.options) === null || _l === void 0 ? void 0 : _l.captchaToken)
1421
- ? { gotrue_meta_security: { captcha_token: (_m = credentials.options) === null || _m === void 0 ? void 0 : _m.captchaToken } }
1548
+ body: Object.assign({ chain: 'solana', message, signature: bytesToBase64URL(signature) }, (((_m = credentials.options) === null || _m === void 0 ? void 0 : _m.captchaToken)
1549
+ ? { gotrue_meta_security: { captcha_token: (_o = credentials.options) === null || _o === void 0 ? void 0 : _o.captchaToken } }
1422
1550
  : null)),
1423
1551
  xform: _sessionResponse,
1424
1552
  });
@@ -1470,7 +1598,7 @@ class GoTrueClient {
1470
1598
  }
1471
1599
  if (data.session) {
1472
1600
  await this._saveSession(data.session);
1473
- await this._notifyAllSubscribers('SIGNED_IN', data.session);
1601
+ await this._notifyAllSubscribers(redirectType === 'recovery' ? 'PASSWORD_RECOVERY' : 'SIGNED_IN', data.session);
1474
1602
  }
1475
1603
  return this._returnResult({ data: Object.assign(Object.assign({}, data), { redirectType: redirectType !== null && redirectType !== void 0 ? redirectType : null }), error });
1476
1604
  }
@@ -1673,7 +1801,7 @@ class GoTrueClient {
1673
1801
  * ```
1674
1802
  */
1675
1803
  async signInWithOtp(credentials) {
1676
- var _a, _b, _c, _d, _e;
1804
+ var _a, _b, _c, _d, _f;
1677
1805
  try {
1678
1806
  if ('email' in credentials) {
1679
1807
  const { email, options } = credentials;
@@ -1706,7 +1834,7 @@ class GoTrueClient {
1706
1834
  data: (_c = options === null || options === void 0 ? void 0 : options.data) !== null && _c !== void 0 ? _c : {},
1707
1835
  create_user: (_d = options === null || options === void 0 ? void 0 : options.shouldCreateUser) !== null && _d !== void 0 ? _d : true,
1708
1836
  gotrue_meta_security: { captcha_token: options === null || options === void 0 ? void 0 : options.captchaToken },
1709
- channel: (_e = options === null || options === void 0 ? void 0 : options.channel) !== null && _e !== void 0 ? _e : 'sms',
1837
+ channel: (_f = options === null || options === void 0 ? void 0 : options.channel) !== null && _f !== void 0 ? _f : 'sms',
1710
1838
  },
1711
1839
  });
1712
1840
  return this._returnResult({
@@ -1952,7 +2080,7 @@ class GoTrueClient {
1952
2080
  * ```
1953
2081
  */
1954
2082
  async signInWithSSO(params) {
1955
- var _a, _b, _c, _d, _e;
2083
+ var _a, _b, _c, _d, _f;
1956
2084
  try {
1957
2085
  let codeChallenge = null;
1958
2086
  let codeChallengeMethod = null;
@@ -1968,7 +2096,7 @@ class GoTrueClient {
1968
2096
  xform: _ssoResponse,
1969
2097
  });
1970
2098
  // Automatically redirect in browser unless skipBrowserRedirect is true
1971
- if (((_d = result.data) === null || _d === void 0 ? void 0 : _d.url) && isBrowser() && !((_e = params.options) === null || _e === void 0 ? void 0 : _e.skipBrowserRedirect)) {
2099
+ if (((_d = result.data) === null || _d === void 0 ? void 0 : _d.url) && isBrowser() && !((_f = params.options) === null || _f === void 0 ? void 0 : _f.skipBrowserRedirect)) {
1972
2100
  window.location.assign(result.data.url);
1973
2101
  }
1974
2102
  return this._returnResult(result);
@@ -2004,9 +2132,13 @@ class GoTrueClient {
2004
2132
  */
2005
2133
  async reauthenticate() {
2006
2134
  await this.initializePromise;
2007
- return await this._acquireLock(this.lockAcquireTimeout, async () => {
2008
- return await this._reauthenticate();
2009
- });
2135
+ if (this.lock != null) {
2136
+ // TODO(v3): remove legacy lock path
2137
+ return await this._acquireLock(this.lockAcquireTimeout, async () => {
2138
+ return await this._reauthenticate();
2139
+ });
2140
+ }
2141
+ return await this._reauthenticate();
2010
2142
  }
2011
2143
  async _reauthenticate() {
2012
2144
  try {
@@ -2094,15 +2226,26 @@ class GoTrueClient {
2094
2226
  const endpoint = `${this.url}/resend`;
2095
2227
  if ('email' in credentials) {
2096
2228
  const { email, type, options } = credentials;
2229
+ let codeChallenge = null;
2230
+ let codeChallengeMethod = null;
2231
+ if (this.flowType === 'pkce') {
2232
+ ;
2233
+ [codeChallenge, codeChallengeMethod] = await getCodeChallengeAndMethod(this.storage, this.storageKey);
2234
+ }
2097
2235
  const { error } = await _request(this.fetch, 'POST', endpoint, {
2098
2236
  headers: this.headers,
2099
2237
  body: {
2100
2238
  email,
2101
2239
  type,
2102
2240
  gotrue_meta_security: { captcha_token: options === null || options === void 0 ? void 0 : options.captchaToken },
2241
+ code_challenge: codeChallenge,
2242
+ code_challenge_method: codeChallengeMethod,
2103
2243
  },
2104
2244
  redirectTo: options === null || options === void 0 ? void 0 : options.emailRedirectTo,
2105
2245
  });
2246
+ if (error) {
2247
+ await removeItemAsync(this.storage, `${this.storageKey}-code-verifier`);
2248
+ }
2106
2249
  return this._returnResult({ data: { user: null, session: null }, error });
2107
2250
  }
2108
2251
  else if ('phone' in credentials) {
@@ -2123,6 +2266,7 @@ class GoTrueClient {
2123
2266
  throw new AuthInvalidCredentialsError('You must provide either an email or phone number and a type');
2124
2267
  }
2125
2268
  catch (error) {
2269
+ await removeItemAsync(this.storage, `${this.storageKey}-code-verifier`);
2126
2270
  if (isAuthError(error)) {
2127
2271
  return this._returnResult({ data: { user: null, session: null }, error });
2128
2272
  }
@@ -2138,7 +2282,7 @@ class GoTrueClient {
2138
2282
  * to the client. If that storage is based on request cookies for example,
2139
2283
  * the values in it may not be authentic and therefore it's strongly advised
2140
2284
  * against using this method and its results in such circumstances. A warning
2141
- * will be emitted if this is detected. Use {@link #getUser()} instead.
2285
+ * will be emitted if this is detected. Use {@link GoTrueClient.getUser} instead.
2142
2286
  *
2143
2287
  * @category Auth
2144
2288
  *
@@ -2149,7 +2293,7 @@ class GoTrueClient {
2149
2293
  * - If the session's access token is expired or is about to expire, this method will use the refresh token to refresh the session.
2150
2294
  * - 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.
2151
2295
  * - **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.
2152
- * - 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.
2296
+ * - Cross-tab refresh races are handled by the GoTrue server (the rotated token from the first tab is returned to subsequent tabs via the parent-of-active mechanism), so no client-side serialization is needed.
2153
2297
  *
2154
2298
  * @example Get the session data
2155
2299
  * ```js
@@ -2216,15 +2360,24 @@ class GoTrueClient {
2216
2360
  */
2217
2361
  async getSession() {
2218
2362
  await this.initializePromise;
2219
- const result = await this._acquireLock(this.lockAcquireTimeout, async () => {
2220
- return this._useSession(async (result) => {
2221
- return result;
2363
+ if (this.lock != null) {
2364
+ // TODO(v3): remove legacy lock path
2365
+ return await this._acquireLock(this.lockAcquireTimeout, async () => {
2366
+ return this._useSession(async (result) => {
2367
+ return result;
2368
+ });
2222
2369
  });
2370
+ }
2371
+ return await this._useSession(async (result) => {
2372
+ return result;
2223
2373
  });
2224
- return result;
2225
2374
  }
2226
2375
  /**
2227
2376
  * Acquires a global lock based on the storage key.
2377
+ *
2378
+ * TODO(v3): remove along with the legacy lock path. Only called when
2379
+ * `this.lock` is non-null (custom lock supplied via constructor). The
2380
+ * default lockless path bypasses this entirely.
2228
2381
  */
2229
2382
  async _acquireLock(acquireTimeout, fn) {
2230
2383
  this._debug('#_acquireLock', 'begin', acquireTimeout);
@@ -2241,7 +2394,7 @@ class GoTrueClient {
2241
2394
  try {
2242
2395
  await result;
2243
2396
  }
2244
- catch (e) {
2397
+ catch (_e) {
2245
2398
  // we just care if it finished
2246
2399
  }
2247
2400
  })());
@@ -2280,15 +2433,17 @@ class GoTrueClient {
2280
2433
  }
2281
2434
  }
2282
2435
  /**
2283
- * Use instead of {@link #getSession} inside the library. It is
2284
- * semantically usually what you want, as getting a session involves some
2285
- * processing afterwards that requires only one client operating on the
2286
- * session at once across multiple tabs or processes.
2436
+ * Use instead of {@link GoTrueClient.getSession} inside the library. Loads the session
2437
+ * via `__loadSession` (which may trigger a refresh if the access token is
2438
+ * within the expiry margin) and runs `fn` with the result.
2287
2439
  */
2288
2440
  async _useSession(fn) {
2289
2441
  this._debug('#_useSession', 'begin');
2290
2442
  try {
2291
- // the use of __loadSession here is the only correct use of the function!
2443
+ // Concurrent callers may both reach __loadSession; storage reads are
2444
+ // idempotent, and the only write path inside it (refresh) is
2445
+ // single-flighted downstream by `refreshingDeferred` in
2446
+ // `_callRefreshToken`. No serialization is needed at this layer.
2292
2447
  const result = await this.__loadSession();
2293
2448
  return await fn(result);
2294
2449
  }
@@ -2299,11 +2454,12 @@ class GoTrueClient {
2299
2454
  /**
2300
2455
  * NEVER USE DIRECTLY!
2301
2456
  *
2302
- * Always use {@link #_useSession}.
2457
+ * Always use `_useSession`.
2303
2458
  */
2304
2459
  async __loadSession() {
2305
2460
  this._debug('#__loadSession()', 'begin');
2306
- if (!this.lockAcquired) {
2461
+ if (this.lock != null && !this.lockAcquired) {
2462
+ // TODO(v3): remove. Only meaningful on the legacy lock path.
2307
2463
  this._debug('#__loadSession()', 'used outside of an acquired lock!', new Error().stack);
2308
2464
  }
2309
2465
  try {
@@ -2357,6 +2513,25 @@ class GoTrueClient {
2357
2513
  }
2358
2514
  const { data: session, error } = await this._callRefreshToken(currentSession.refresh_token);
2359
2515
  if (error) {
2516
+ // Proactive-preserve mirror: `_callRefreshToken` keeps the session
2517
+ // in storage when refresh fails non-retryably but the access token
2518
+ // is still inside its real expiry window. Hand the caller the
2519
+ // still-valid session instead of translating the refresh error
2520
+ // into `session: null`. If the access token has actually expired,
2521
+ // the session is genuinely dead and the error stands. Explicit
2522
+ // refresh entry points (`refreshSession`, `setSession`)
2523
+ // intentionally bypass this fallback — they want to know the
2524
+ // refresh failed.
2525
+ const accessTokenStillValid = !!(currentSession.expires_at && currentSession.expires_at * 1000 > Date.now());
2526
+ if (accessTokenStillValid) {
2527
+ // Race guard: a concurrent `signOut` may have cleared storage
2528
+ // during the refresh attempt. Don't hand back a session that no
2529
+ // longer exists on disk.
2530
+ const stillStored = (await getItemAsync(this.storage, this.storageKey));
2531
+ if (stillStored && stillStored.refresh_token === currentSession.refresh_token) {
2532
+ return this._returnResult({ data: { session: currentSession }, error: null });
2533
+ }
2534
+ }
2360
2535
  return this._returnResult({ data: { session: null }, error });
2361
2536
  }
2362
2537
  return this._returnResult({ data: { session }, error: null });
@@ -2446,9 +2621,16 @@ class GoTrueClient {
2446
2621
  return await this._getUser(jwt);
2447
2622
  }
2448
2623
  await this.initializePromise;
2449
- const result = await this._acquireLock(this.lockAcquireTimeout, async () => {
2450
- return await this._getUser();
2451
- });
2624
+ let result;
2625
+ if (this.lock != null) {
2626
+ // TODO(v3): remove legacy lock path
2627
+ result = await this._acquireLock(this.lockAcquireTimeout, async () => {
2628
+ return await this._getUser();
2629
+ });
2630
+ }
2631
+ else {
2632
+ result = await this._getUser();
2633
+ }
2452
2634
  if (result.data.user) {
2453
2635
  this.suppressGetSessionWarning = true;
2454
2636
  }
@@ -2609,9 +2791,13 @@ class GoTrueClient {
2609
2791
  */
2610
2792
  async updateUser(attributes, options = {}) {
2611
2793
  await this.initializePromise;
2612
- return await this._acquireLock(this.lockAcquireTimeout, async () => {
2613
- return await this._updateUser(attributes, options);
2614
- });
2794
+ if (this.lock != null) {
2795
+ // TODO(v3): remove legacy lock path
2796
+ return await this._acquireLock(this.lockAcquireTimeout, async () => {
2797
+ return await this._updateUser(attributes, options);
2798
+ });
2799
+ }
2800
+ return await this._updateUser(attributes, options);
2615
2801
  }
2616
2802
  async _updateUser(attributes, options = {}) {
2617
2803
  try {
@@ -2780,9 +2966,13 @@ class GoTrueClient {
2780
2966
  */
2781
2967
  async setSession(currentSession) {
2782
2968
  await this.initializePromise;
2783
- return await this._acquireLock(this.lockAcquireTimeout, async () => {
2784
- return await this._setSession(currentSession);
2785
- });
2969
+ if (this.lock != null) {
2970
+ // TODO(v3): remove legacy lock path
2971
+ return await this._acquireLock(this.lockAcquireTimeout, async () => {
2972
+ return await this._setSession(currentSession);
2973
+ });
2974
+ }
2975
+ return await this._setSession(currentSession);
2786
2976
  }
2787
2977
  async _setSession(currentSession) {
2788
2978
  try {
@@ -2960,9 +3150,13 @@ class GoTrueClient {
2960
3150
  */
2961
3151
  async refreshSession(currentSession) {
2962
3152
  await this.initializePromise;
2963
- return await this._acquireLock(this.lockAcquireTimeout, async () => {
2964
- return await this._refreshSession(currentSession);
2965
- });
3153
+ if (this.lock != null) {
3154
+ // TODO(v3): remove legacy lock path
3155
+ return await this._acquireLock(this.lockAcquireTimeout, async () => {
3156
+ return await this._refreshSession(currentSession);
3157
+ });
3158
+ }
3159
+ return await this._refreshSession(currentSession);
2966
3160
  }
2967
3161
  async _refreshSession(currentSession) {
2968
3162
  try {
@@ -2999,6 +3193,7 @@ class GoTrueClient {
2999
3193
  * Gets the session data from a URL string
3000
3194
  */
3001
3195
  async _getSessionFromURL(params, callbackUrlType) {
3196
+ var _a;
3002
3197
  try {
3003
3198
  if (!isBrowser())
3004
3199
  throw new AuthImplicitGrantRedirectError('No browser detected.');
@@ -3037,7 +3232,10 @@ class GoTrueClient {
3037
3232
  const url = new URL(window.location.href);
3038
3233
  url.searchParams.delete('code');
3039
3234
  window.history.replaceState(window.history.state, '', url.toString());
3040
- return { data: { session: data.session, redirectType: null }, error: null };
3235
+ return {
3236
+ data: { session: data.session, redirectType: (_a = data.redirectType) !== null && _a !== void 0 ? _a : null },
3237
+ error: null,
3238
+ };
3041
3239
  }
3042
3240
  const { provider_token, provider_refresh_token, access_token, refresh_token, expires_in, expires_at, token_type, } = params;
3043
3241
  if (!access_token || !expires_in || !refresh_token || !token_type) {
@@ -3096,7 +3294,7 @@ class GoTrueClient {
3096
3294
  if (typeof this.detectSessionInUrl === 'function') {
3097
3295
  return this.detectSessionInUrl(new URL(window.location.href), params);
3098
3296
  }
3099
- return Boolean(params.access_token || params.error_description);
3297
+ return Boolean(params.access_token || params.error || params.error_description || params.error_code);
3100
3298
  }
3101
3299
  /**
3102
3300
  * Checks if the current URL and backing storage contain parameters given by a PKCE flow
@@ -3113,37 +3311,56 @@ class GoTrueClient {
3113
3311
  *
3114
3312
  * If using `others` scope, no `SIGNED_OUT` event is fired!
3115
3313
  *
3314
+ * **Warning:** the default `scope` is `'global'`. This signs the user out of
3315
+ * **every device they are currently signed in on**, not just the current
3316
+ * tab/session. If you only want to sign the user out of the current session
3317
+ * (the behavior most other auth libraries default to), pass
3318
+ * `{ scope: 'local' }` explicitly.
3319
+ *
3116
3320
  * @category Auth
3117
3321
  *
3118
3322
  * @remarks
3119
3323
  * - In order to use the `signOut()` method, the user needs to be signed in first.
3120
- * - By default, `signOut()` uses the global scope, which signs out all other sessions that the user is logged into as well. Customize this behavior by passing a scope parameter.
3324
+ * - By default, `signOut()` uses the **global** scope, which signs out the user
3325
+ * on every device they are signed in on (not just the current one). Pass
3326
+ * `{ scope: 'local' }` to only sign out the current session. This is
3327
+ * usually what apps want on a "Sign out" button, especially when users
3328
+ * sign in from multiple devices and do not expect signing out of one to
3329
+ * terminate the others.
3121
3330
  * - Since Supabase Auth uses JWTs for authentication, the access token JWT will be valid until it's expired. When the user signs out, Supabase revokes the refresh token and deletes the JWT from the client-side. This does not revoke the JWT and it will still be valid until it expires.
3122
3331
  *
3123
- * @example Sign out (all sessions)
3332
+ * @example Sign out of every device (global – default)
3124
3333
  * ```js
3125
3334
  * const { error } = await supabase.auth.signOut()
3126
3335
  * ```
3127
3336
  *
3128
- * @example Sign out (current session)
3337
+ * @example Sign out only the current session (recommended for most apps)
3129
3338
  * ```js
3130
3339
  * const { error } = await supabase.auth.signOut({ scope: 'local' })
3131
3340
  * ```
3132
3341
  *
3133
- * @example Sign out (other sessions)
3342
+ * @example Sign out of all other sessions, keep the current one
3134
3343
  * ```js
3135
3344
  * const { error } = await supabase.auth.signOut({ scope: 'others' })
3136
3345
  * ```
3137
3346
  */
3138
3347
  async signOut(options = { scope: 'global' }) {
3139
3348
  await this.initializePromise;
3140
- return await this._acquireLock(this.lockAcquireTimeout, async () => {
3141
- return await this._signOut(options);
3142
- });
3349
+ if (this.lock != null) {
3350
+ // TODO(v3): remove legacy lock path
3351
+ return await this._acquireLock(this.lockAcquireTimeout, async () => {
3352
+ return await this._signOut(options);
3353
+ });
3354
+ }
3355
+ return await this._signOut(options);
3143
3356
  }
3144
3357
  async _signOut({ scope } = { scope: 'global' }) {
3145
3358
  return await this._useSession(async (result) => {
3146
3359
  var _a;
3360
+ const removeCurrentSession = async () => {
3361
+ await this._removeSession();
3362
+ await removeItemAsync(this.storage, `${this.storageKey}-code-verifier`);
3363
+ };
3147
3364
  const { data, error: sessionError } = result;
3148
3365
  if (sessionError && !isAuthSessionMissingError(sessionError)) {
3149
3366
  return this._returnResult({ error: sessionError });
@@ -3157,13 +3374,15 @@ class GoTrueClient {
3157
3374
  if (!((isAuthApiError(error) &&
3158
3375
  (error.status === 404 || error.status === 401 || error.status === 403)) ||
3159
3376
  isAuthSessionMissingError(error))) {
3377
+ if (scope !== 'others') {
3378
+ await removeCurrentSession();
3379
+ }
3160
3380
  return this._returnResult({ error });
3161
3381
  }
3162
3382
  }
3163
3383
  }
3164
3384
  if (scope !== 'others') {
3165
- await this._removeSession();
3166
- await removeItemAsync(this.storage, `${this.storageKey}-code-verifier`);
3385
+ await removeCurrentSession();
3167
3386
  }
3168
3387
  return this._returnResult({ error: null });
3169
3388
  });
@@ -3175,18 +3394,8 @@ class GoTrueClient {
3175
3394
  * - Subscribes to important events occurring on the user's session.
3176
3395
  * - Use on the frontend/client. It is less useful on the server.
3177
3396
  * - 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.
3178
- * - **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.
3179
- * - Avoid using `async` functions as callbacks.
3180
- * - Limit the number of `await` calls in `async` callbacks.
3181
- * - 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:
3182
- * ```js
3183
- * supabase.auth.onAuthStateChange((event, session) => {
3184
- * setTimeout(async () => {
3185
- * // await on other Supabase function here
3186
- * // this runs right after the callback has finished
3187
- * }, 0)
3188
- * })
3189
- * ```
3397
+ * - Callbacks can be `async` and can safely call other Supabase auth methods (`getUser`, `setSession`, etc.) from inside the callback.
3398
+ * - Keep callbacks quick. Events are awaited in order, so a slow callback delays subsequent events to subscribers in this tab.
3190
3399
  * - Emitted events:
3191
3400
  * - `INITIAL_SESSION`
3192
3401
  * - Emitted right after the Supabase client is constructed and the initial session from storage is loaded.
@@ -3369,9 +3578,15 @@ class GoTrueClient {
3369
3578
  this.stateChangeEmitters.set(id, subscription);
3370
3579
  (async () => {
3371
3580
  await this.initializePromise;
3372
- await this._acquireLock(this.lockAcquireTimeout, async () => {
3373
- this._emitInitialSession(id);
3374
- });
3581
+ if (this.lock != null) {
3582
+ // TODO(v3): remove legacy lock path
3583
+ await this._acquireLock(this.lockAcquireTimeout, async () => {
3584
+ this._emitInitialSession(id);
3585
+ });
3586
+ }
3587
+ else {
3588
+ await this._emitInitialSession(id);
3589
+ }
3375
3590
  })();
3376
3591
  return { data: { subscription } };
3377
3592
  }
@@ -3388,7 +3603,10 @@ class GoTrueClient {
3388
3603
  catch (err) {
3389
3604
  await ((_b = this.stateChangeEmitters.get(id)) === null || _b === void 0 ? void 0 : _b.callback('INITIAL_SESSION', null));
3390
3605
  this._debug('INITIAL_SESSION', 'callback id', id, 'error', err);
3391
- if (isAuthSessionMissingError(err)) {
3606
+ if (isAuthSessionMissingError(err) || isAuthRetryableFetchError(err)) {
3607
+ // A missing session or a transient/aborted network failure (e.g. a
3608
+ // superseded page navigation cancelling the in-flight request) is
3609
+ // not an application error — warn rather than surface it raw.
3392
3610
  console.warn(err);
3393
3611
  }
3394
3612
  else {
@@ -3584,7 +3802,7 @@ class GoTrueClient {
3584
3802
  var _a;
3585
3803
  try {
3586
3804
  const { data, error } = await this._useSession(async (result) => {
3587
- var _a, _b, _c, _d, _e;
3805
+ var _a, _b, _c, _d, _f;
3588
3806
  const { data, error } = result;
3589
3807
  if (error)
3590
3808
  throw error;
@@ -3596,7 +3814,7 @@ class GoTrueClient {
3596
3814
  });
3597
3815
  return await _request(this.fetch, 'GET', url, {
3598
3816
  headers: this.headers,
3599
- jwt: (_e = (_d = data.session) === null || _d === void 0 ? void 0 : _d.access_token) !== null && _e !== void 0 ? _e : undefined,
3817
+ jwt: (_f = (_d = data.session) === null || _d === void 0 ? void 0 : _d.access_token) !== null && _f !== void 0 ? _f : undefined,
3600
3818
  });
3601
3819
  });
3602
3820
  if (error)
@@ -3713,7 +3931,10 @@ class GoTrueClient {
3713
3931
  * @param refreshToken A valid refresh token that was returned on login.
3714
3932
  */
3715
3933
  async _refreshAccessToken(refreshToken) {
3716
- const debugName = `#_refreshAccessToken(${refreshToken.substring(0, 5)}...)`;
3934
+ // Refresh tokens are long-lived bearer credentials; do NOT include any
3935
+ // fragment of the token in the debug tag, even when `debug: true` is
3936
+ // enabled (logs may be forwarded to third-party services).
3937
+ const debugName = `#_refreshAccessToken()`;
3717
3938
  this._debug(debugName, 'begin');
3718
3939
  try {
3719
3940
  const startedAt = Date.now();
@@ -3820,10 +4041,17 @@ class GoTrueClient {
3820
4041
  if (this.autoRefreshToken && currentSession.refresh_token) {
3821
4042
  const { error } = await this._callRefreshToken(currentSession.refresh_token);
3822
4043
  if (error) {
3823
- console.error(error);
3824
- if (!isAuthRetryableFetchError(error)) {
3825
- this._debug(debugName, 'refresh failed with a non-retryable error, removing the session', error);
3826
- await this._removeSession();
4044
+ // `_callRefreshToken` is the single source of truth for refresh
4045
+ // outcomes: it removes the session itself when the access token
4046
+ // is actually expired, and preserves it when the token is still
4047
+ // valid (proactive-preserve). Don't second-guess that here — a
4048
+ // local `_removeSession` would emit a duplicate `SIGNED_OUT` on
4049
+ // genuine failures and undo the proactive-preserve at init time.
4050
+ if (isAuthRefreshDiscardedError(error)) {
4051
+ this._debug(debugName, 'refresh discarded by commit guard', error);
4052
+ }
4053
+ else {
4054
+ this._debug(debugName, 'refresh failed', error);
3827
4055
  }
3828
4056
  }
3829
4057
  }
@@ -3856,7 +4084,16 @@ class GoTrueClient {
3856
4084
  }
3857
4085
  catch (err) {
3858
4086
  this._debug(debugName, 'error', err);
3859
- console.error(err);
4087
+ if (isAuthRetryableFetchError(err)) {
4088
+ // Transient/aborted network failure during session recovery (e.g. a
4089
+ // superseded page navigation cancelling the in-flight request). Warn
4090
+ // rather than surface it raw; the session is untouched and recovery
4091
+ // will run again on the next load.
4092
+ console.warn(err);
4093
+ }
4094
+ else {
4095
+ console.error(err);
4096
+ }
3860
4097
  return;
3861
4098
  }
3862
4099
  finally {
@@ -3872,18 +4109,91 @@ class GoTrueClient {
3872
4109
  if (this.refreshingDeferred) {
3873
4110
  return this.refreshingDeferred.promise;
3874
4111
  }
3875
- const debugName = `#_callRefreshToken(${refreshToken.substring(0, 5)}...)`;
4112
+ // Serial failure cooldown: callers passing the *same* refresh token
4113
+ // after a recent failure receive the cached result instead of firing
4114
+ // another `/token` request. This caps the proactive-refresh storm
4115
+ // where every `getSession()` call inside the 90s EXPIRY_MARGIN_MS
4116
+ // window kept re-firing against the same broken refresh token during
4117
+ // outages. Concurrent callers already share `refreshingDeferred`; this
4118
+ // cache covers serial callers spaced across cooldown windows.
4119
+ //
4120
+ // Token-keyed so callers with a fresh refresh token (rotation pickup
4121
+ // from another tab, explicit `setSession`/`refreshSession({ refresh_token })`,
4122
+ // multi-account switch) bypass the cache and attempt a real refresh.
4123
+ if (this.lastRefreshFailure &&
4124
+ this.lastRefreshFailure.refreshToken === refreshToken &&
4125
+ Date.now() < this.lastRefreshFailure.expiresAt) {
4126
+ this._debug('#_callRefreshToken()', 'returning cached failure (cooldown active)');
4127
+ return this.lastRefreshFailure.result;
4128
+ }
4129
+ // Refresh tokens are long-lived bearer credentials; do NOT include any
4130
+ // fragment of the token in the debug tag, even when `debug: true` is
4131
+ // enabled (logs may be forwarded to third-party services).
4132
+ const debugName = `#_callRefreshToken()`;
3876
4133
  this._debug(debugName, 'begin');
3877
4134
  try {
3878
4135
  this.refreshingDeferred = new Deferred();
4136
+ // Snapshot storage before the fetch. The commit guard discards the
4137
+ // rotated tokens only when a non-null pre-fetch snapshot changed under
4138
+ // us — typical case: a concurrent `signOut` ran `_removeSession`, or
4139
+ // another tab's refresh rewrote the slot. Callers passing
4140
+ // externally-sourced tokens (SSR cookie handoff, multi-account
4141
+ // switching, `setSession`/`refreshSession({ refresh_token })`) may
4142
+ // start from a null snapshot OR from a non-null snapshot whose
4143
+ // refresh_token differs from the one they're hydrating; in both
4144
+ // cases the guard fires only when storage was *modified between
4145
+ // snapshots*, not when the input token disagrees with what's stored.
4146
+ const storedAtStart = (await getItemAsync(this.storage, this.storageKey));
3879
4147
  const { data, error } = await this._refreshAccessToken(refreshToken);
3880
4148
  if (error)
3881
4149
  throw error;
3882
4150
  if (!data.session)
3883
4151
  throw new AuthSessionMissingError();
4152
+ const storedAfter = (await getItemAsync(this.storage, this.storageKey));
4153
+ const storageChangedUnderUs = storedAtStart !== null &&
4154
+ (storedAfter === null || storedAfter.refresh_token !== storedAtStart.refresh_token);
4155
+ if (storageChangedUnderUs) {
4156
+ this._debug(debugName, 'commit guard: storage changed since refresh started, discarding rotated tokens', {
4157
+ // Presence indicators only — never log refresh token fragments,
4158
+ // even partial. Logs may be forwarded to third-party services.
4159
+ startedWith: 'present',
4160
+ nowHolds: storedAfter ? 'replaced' : 'cleared',
4161
+ });
4162
+ const discarded = {
4163
+ data: null,
4164
+ error: new AuthRefreshDiscardedError(),
4165
+ };
4166
+ this.refreshingDeferred.resolve(discarded);
4167
+ return discarded;
4168
+ }
4169
+ // Second leg of the commit guard: close the TOCTOU window between the
4170
+ // synchronous `storageChangedUnderUs` check and the actual storage
4171
+ // writes inside `_saveSession`. A concurrent `signOut → _removeSession`
4172
+ // can land inside `_saveSession`'s `await setItemAsync(...)` yields and
4173
+ // clear storage just before we overwrite it. Capture the epoch BEFORE
4174
+ // the save and re-check after; if it advanced, undo the write directly
4175
+ // (do NOT call `_removeSession` — that would emit a duplicate
4176
+ // SIGNED_OUT for the concurrent signOut that already fired one).
4177
+ const epochBeforeSave = this._sessionRemovalEpoch;
3884
4178
  await this._saveSession(data.session);
4179
+ if (this._sessionRemovalEpoch !== epochBeforeSave) {
4180
+ this._debug(debugName, 'commit guard (post-save): _removeSession ran during _saveSession, undoing write');
4181
+ await removeItemAsync(this.storage, this.storageKey);
4182
+ if (this.userStorage) {
4183
+ await removeItemAsync(this.userStorage, this.storageKey + '-user');
4184
+ }
4185
+ const discarded = {
4186
+ data: null,
4187
+ error: new AuthRefreshDiscardedError(),
4188
+ };
4189
+ this.refreshingDeferred.resolve(discarded);
4190
+ return discarded;
4191
+ }
3885
4192
  await this._notifyAllSubscribers('TOKEN_REFRESHED', data.session);
3886
4193
  const result = { data: data.session, error: null };
4194
+ // Refresh succeeded — clear any cached failure so the next caller
4195
+ // (including the auto-refresh ticker) attempts a real refresh again.
4196
+ this.lastRefreshFailure = null;
3887
4197
  this.refreshingDeferred.resolve(result);
3888
4198
  return result;
3889
4199
  }
@@ -3892,8 +4202,35 @@ class GoTrueClient {
3892
4202
  if (isAuthError(error)) {
3893
4203
  const result = { data: null, error };
3894
4204
  if (!isAuthRetryableFetchError(error)) {
3895
- await this._removeSession();
4205
+ // Proactive vs reactive distinction: a refresh fires whenever
4206
+ // the access token is within EXPIRY_MARGIN_MS of expiry. If the
4207
+ // access token is *still valid* at this moment, the refresh was
4208
+ // proactive and the existing session is still usable until its
4209
+ // real expiry — destroying it now would log out a user whose
4210
+ // access token works. If the access token has actually expired,
4211
+ // the refresh token is the only credential left and it just got
4212
+ // rejected — the session is genuinely dead. `__loadSession`
4213
+ // mirrors this distinction on the read path so callers see the
4214
+ // preserved session instead of `session: null`.
4215
+ const storedNow = (await getItemAsync(this.storage, this.storageKey));
4216
+ const accessTokenStillValid = !!((storedNow === null || storedNow === void 0 ? void 0 : storedNow.expires_at) && storedNow.expires_at * 1000 > Date.now());
4217
+ if (accessTokenStillValid) {
4218
+ this._debug(debugName, 'proactive refresh failed, access token still valid — preserving session');
4219
+ }
4220
+ else {
4221
+ await this._removeSession();
4222
+ }
3896
4223
  }
4224
+ // Cache the failure so serial callers (and the next auto-refresh
4225
+ // tick) passing the same refresh token within the cooldown window
4226
+ // receive it synchronously instead of firing another `/token`
4227
+ // call. Set after the optional `_removeSession` above (which
4228
+ // clears the cache as part of teardown) so the cache survives.
4229
+ this.lastRefreshFailure = {
4230
+ refreshToken,
4231
+ result,
4232
+ expiresAt: Date.now() + REFRESH_FAILURE_COOLDOWN_MS,
4233
+ };
3897
4234
  (_a = this.refreshingDeferred) === null || _a === void 0 ? void 0 : _a.resolve(result);
3898
4235
  return result;
3899
4236
  }
@@ -3906,6 +4243,19 @@ class GoTrueClient {
3906
4243
  }
3907
4244
  }
3908
4245
  async _notifyAllSubscribers(event, session, broadcast = true) {
4246
+ if (this._pendingInitNotifications !== null && broadcast) {
4247
+ // We're inside initialize() before initializePromise has resolved, and
4248
+ // this notification originates from the init chain (_recoverAndRefresh),
4249
+ // which always fires with broadcast = true. Enqueue instead of firing so
4250
+ // that callbacks can safely call getSession() / getUser() without
4251
+ // deadlocking on initializePromise.
4252
+ //
4253
+ // Notifications with broadcast = false come from the incoming
4254
+ // BroadcastChannel handler (a cross-tab event), not the init chain, so
4255
+ // they are fired immediately to preserve multi-tab ordering.
4256
+ this._pendingInitNotifications.push({ event, session, broadcast });
4257
+ return;
4258
+ }
3909
4259
  const debugName = `#_notifyAllSubscribers(${event})`;
3910
4260
  this._debug(debugName, 'begin', session, `broadcast = ${broadcast}`);
3911
4261
  try {
@@ -3942,7 +4292,6 @@ class GoTrueClient {
3942
4292
  // _saveSession is always called whenever a new session has been acquired
3943
4293
  // so we can safely suppress the warning returned by future getSession calls
3944
4294
  this.suppressGetSessionWarning = true;
3945
- await removeItemAsync(this.storage, `${this.storageKey}-code-verifier`);
3946
4295
  // Create a shallow copy to work with, to avoid mutating the original session object if it's used elsewhere
3947
4296
  const sessionToProcess = Object.assign({}, session);
3948
4297
  const userIsProxy = sessionToProcess.user && sessionToProcess.user.__isUserNotAvailableProxy === true;
@@ -3975,7 +4324,15 @@ class GoTrueClient {
3975
4324
  }
3976
4325
  }
3977
4326
  async _removeSession() {
4327
+ // Bump synchronously, BEFORE any `await`, so that `_callRefreshToken`'s
4328
+ // post-save check sees the increment whenever this method has started —
4329
+ // even if it hasn't finished. Pairs with the epoch check in
4330
+ // `_callRefreshToken`. See `_sessionRemovalEpoch` field doc.
4331
+ this._sessionRemovalEpoch += 1;
3978
4332
  this._debug('#_removeSession()');
4333
+ // The session is gone — no point holding on to a cached refresh failure
4334
+ // for a token that no longer exists. Synchronous, before any `await`.
4335
+ this.lastRefreshFailure = null;
3979
4336
  this.suppressGetSessionWarning = false;
3980
4337
  await removeItemAsync(this.storage, this.storageKey);
3981
4338
  await removeItemAsync(this.storage, this.storageKey + '-code-verifier');
@@ -3988,8 +4345,8 @@ class GoTrueClient {
3988
4345
  /**
3989
4346
  * Removes any registered visibilitychange callback.
3990
4347
  *
3991
- * {@see #startAutoRefresh}
3992
- * {@see #stopAutoRefresh}
4348
+ * {@link GoTrueClient.startAutoRefresh}
4349
+ * {@link GoTrueClient.stopAutoRefresh}
3993
4350
  */
3994
4351
  _removeVisibilityChangedCallback() {
3995
4352
  this._debug('#_removeVisibilityChangedCallback()');
@@ -4005,7 +4362,7 @@ class GoTrueClient {
4005
4362
  }
4006
4363
  }
4007
4364
  /**
4008
- * This is the private implementation of {@link #startAutoRefresh}. Use this
4365
+ * This is the private implementation of {@link GoTrueClient.startAutoRefresh}. Use this
4009
4366
  * within the library.
4010
4367
  */
4011
4368
  async _startAutoRefresh() {
@@ -4047,7 +4404,7 @@ class GoTrueClient {
4047
4404
  }
4048
4405
  }
4049
4406
  /**
4050
- * This is the private implementation of {@link #stopAutoRefresh}. Use this
4407
+ * This is the private implementation of {@link GoTrueClient.stopAutoRefresh}. Use this
4051
4408
  * within the library.
4052
4409
  */
4053
4410
  async _stopAutoRefresh() {
@@ -4083,7 +4440,7 @@ class GoTrueClient {
4083
4440
  * platform's foreground indication mechanism and call these methods
4084
4441
  * appropriately to conserve resources.
4085
4442
  *
4086
- * {@see #stopAutoRefresh}
4443
+ * {@link GoTrueClient.stopAutoRefresh}
4087
4444
  *
4088
4445
  * @category Auth
4089
4446
  *
@@ -4117,7 +4474,7 @@ class GoTrueClient {
4117
4474
  * If you call this method any managed visibility change callback will be
4118
4475
  * removed and you must manage visibility changes on your own.
4119
4476
  *
4120
- * See {@link #startAutoRefresh} for more details.
4477
+ * See {@link GoTrueClient.startAutoRefresh} for more details.
4121
4478
  *
4122
4479
  * @category Auth
4123
4480
  *
@@ -4145,47 +4502,122 @@ class GoTrueClient {
4145
4502
  this._removeVisibilityChangedCallback();
4146
4503
  await this._stopAutoRefresh();
4147
4504
  }
4505
+ /**
4506
+ * Tears down the client's background work: stops the auto-refresh interval,
4507
+ * removes the `visibilitychange` listener, closes the cross-tab
4508
+ * `BroadcastChannel`, and clears registered `onAuthStateChange` subscribers.
4509
+ *
4510
+ * Call this from cleanup hooks when the client is being replaced before
4511
+ * its JS realm is destroyed. React Strict Mode and HMR are the common
4512
+ * cases. Any in-flight `fetch` calls continue to completion and may still
4513
+ * write to storage; dispose doesn't abort them or erase storage.
4514
+ *
4515
+ * Lifecycle caveat: because in-flight refreshes are not aborted, a
4516
+ * disposed instance can still persist a rotated session to storage after
4517
+ * `dispose()` returns. A subsequent `createClient` against the same
4518
+ * `storageKey` will pick up that session on its next read. If you need
4519
+ * strict isolation between client lifecycles, await any pending auth
4520
+ * operation before calling `dispose()` (or change the `storageKey` for
4521
+ * the replacement client).
4522
+ *
4523
+ * Safe to call repeatedly.
4524
+ *
4525
+ * @category Auth
4526
+ *
4527
+ * @example Cleanup on React unmount
4528
+ * ```ts
4529
+ * useEffect(() => {
4530
+ * const client = createClient(...)
4531
+ * return () => { client.auth.dispose() }
4532
+ * }, [])
4533
+ * ```
4534
+ */
4535
+ async dispose() {
4536
+ var _a;
4537
+ this._removeVisibilityChangedCallback();
4538
+ await this._stopAutoRefresh();
4539
+ (_a = this.broadcastChannel) === null || _a === void 0 ? void 0 : _a.close();
4540
+ this.broadcastChannel = null;
4541
+ this.stateChangeEmitters.clear();
4542
+ }
4148
4543
  /**
4149
4544
  * Runs the auto refresh token tick.
4150
4545
  */
4151
4546
  async _autoRefreshTokenTick() {
4152
4547
  this._debug('#_autoRefreshTokenTick()', 'begin');
4153
- try {
4154
- await this._acquireLock(0, async () => {
4155
- try {
4156
- const now = Date.now();
4548
+ if (this.lock != null) {
4549
+ // TODO(v3): remove legacy lock path. Uses `_acquireLock(0, ...)` which
4550
+ // throws `LockAcquireTimeoutError` immediately if the lock is held —
4551
+ // that's the fail-fast skip path that lets the tick bail out instead
4552
+ // of queuing behind a long-running operation.
4553
+ try {
4554
+ await this._acquireLock(0, async () => {
4157
4555
  try {
4158
- return await this._useSession(async (result) => {
4159
- const { data: { session }, } = result;
4160
- if (!session || !session.refresh_token || !session.expires_at) {
4161
- this._debug('#_autoRefreshTokenTick()', 'no session');
4162
- return;
4163
- }
4164
- // session will expire in this many ticks (or has already expired if <= 0)
4165
- const expiresInTicks = Math.floor((session.expires_at * 1000 - now) / AUTO_REFRESH_TICK_DURATION_MS);
4166
- this._debug('#_autoRefreshTokenTick()', `access token expires in ${expiresInTicks} ticks, a tick lasts ${AUTO_REFRESH_TICK_DURATION_MS}ms, refresh threshold is ${AUTO_REFRESH_TICK_THRESHOLD} ticks`);
4167
- if (expiresInTicks <= AUTO_REFRESH_TICK_THRESHOLD) {
4168
- await this._callRefreshToken(session.refresh_token);
4169
- }
4170
- });
4556
+ const now = Date.now();
4557
+ try {
4558
+ return await this._useSession(async (result) => {
4559
+ const { data: { session }, } = result;
4560
+ if (!session || !session.refresh_token || !session.expires_at) {
4561
+ this._debug('#_autoRefreshTokenTick()', 'no session');
4562
+ return;
4563
+ }
4564
+ const expiresInTicks = Math.floor((session.expires_at * 1000 - now) / AUTO_REFRESH_TICK_DURATION_MS);
4565
+ this._debug('#_autoRefreshTokenTick()', `access token expires in ${expiresInTicks} ticks, a tick lasts ${AUTO_REFRESH_TICK_DURATION_MS}ms, refresh threshold is ${AUTO_REFRESH_TICK_THRESHOLD} ticks`);
4566
+ if (expiresInTicks <= AUTO_REFRESH_TICK_THRESHOLD) {
4567
+ await this._callRefreshToken(session.refresh_token);
4568
+ }
4569
+ });
4570
+ }
4571
+ catch (e) {
4572
+ console.error('Auto refresh tick failed with error. This is likely a transient error.', e);
4573
+ }
4171
4574
  }
4172
- catch (e) {
4173
- console.error('Auto refresh tick failed with error. This is likely a transient error.', e);
4575
+ finally {
4576
+ this._debug('#_autoRefreshTokenTick()', 'end');
4174
4577
  }
4578
+ });
4579
+ }
4580
+ catch (e) {
4581
+ if (e instanceof LockAcquireTimeoutError) {
4582
+ this._debug('auto refresh token tick lock not available');
4175
4583
  }
4176
- finally {
4177
- this._debug('#_autoRefreshTokenTick()', 'end');
4584
+ else {
4585
+ throw e;
4178
4586
  }
4179
- });
4587
+ }
4588
+ return;
4180
4589
  }
4181
- catch (e) {
4182
- if (e.isAcquireTimeout || e instanceof LockAcquireTimeoutError) {
4183
- this._debug('auto refresh token tick lock not available');
4590
+ // Lockless default: skip if a refresh is already in flight.
4591
+ // `_callRefreshToken` also dedupes via the same field; this is just a
4592
+ // fast-path skip to avoid an unnecessary storage read.
4593
+ if (this.refreshingDeferred !== null) {
4594
+ this._debug('#_autoRefreshTokenTick()', 'refresh already in flight, skipping');
4595
+ return;
4596
+ }
4597
+ try {
4598
+ const now = Date.now();
4599
+ try {
4600
+ await this._useSession(async (result) => {
4601
+ const { data: { session }, } = result;
4602
+ if (!session || !session.refresh_token || !session.expires_at) {
4603
+ this._debug('#_autoRefreshTokenTick()', 'no session');
4604
+ return;
4605
+ }
4606
+ // session will expire in this many ticks (or has already expired if <= 0)
4607
+ const expiresInTicks = Math.floor((session.expires_at * 1000 - now) / AUTO_REFRESH_TICK_DURATION_MS);
4608
+ this._debug('#_autoRefreshTokenTick()', `access token expires in ${expiresInTicks} ticks, a tick lasts ${AUTO_REFRESH_TICK_DURATION_MS}ms, refresh threshold is ${AUTO_REFRESH_TICK_THRESHOLD} ticks`);
4609
+ if (expiresInTicks <= AUTO_REFRESH_TICK_THRESHOLD) {
4610
+ await this._callRefreshToken(session.refresh_token);
4611
+ }
4612
+ });
4184
4613
  }
4185
- else {
4186
- throw e;
4614
+ catch (e) {
4615
+ console.error('Auto refresh tick failed with error. This is likely a transient error.', e);
4187
4616
  }
4188
4617
  }
4618
+ finally {
4619
+ this._debug('#_autoRefreshTokenTick()', 'end');
4620
+ }
4189
4621
  }
4190
4622
  /**
4191
4623
  * Registers callbacks on the browser / platform, which in-turn run
@@ -4234,18 +4666,26 @@ class GoTrueClient {
4234
4666
  if (!calledFromInitialize) {
4235
4667
  // called when the visibility has changed, i.e. the browser
4236
4668
  // transitioned from hidden -> visible so we need to see if the session
4237
- // should be recovered immediately... but to do that we need to acquire
4238
- // the lock first asynchronously
4669
+ // should be recovered
4239
4670
  await this.initializePromise;
4240
- await this._acquireLock(this.lockAcquireTimeout, async () => {
4671
+ if (this.lock != null) {
4672
+ // TODO(v3): remove legacy lock path
4673
+ await this._acquireLock(this.lockAcquireTimeout, async () => {
4674
+ if (document.visibilityState !== 'visible') {
4675
+ this._debug(methodName, 'acquired the lock to recover the session, but the browser visibilityState is no longer visible, aborting');
4676
+ return;
4677
+ }
4678
+ await this._recoverAndRefresh();
4679
+ });
4680
+ }
4681
+ else {
4241
4682
  if (document.visibilityState !== 'visible') {
4242
- this._debug(methodName, 'acquired the lock to recover the session, but the browser visibilityState is no longer visible, aborting');
4243
- // visibility has changed while waiting for the lock, abort
4683
+ this._debug(methodName, 'visibilityState is no longer visible, skipping recovery');
4244
4684
  return;
4245
4685
  }
4246
4686
  // recover the session
4247
4687
  await this._recoverAndRefresh();
4248
- });
4688
+ }
4249
4689
  }
4250
4690
  }
4251
4691
  else if (document.visibilityState === 'hidden') {
@@ -4341,7 +4781,7 @@ class GoTrueClient {
4341
4781
  }
4342
4782
  }
4343
4783
  async _verify(params) {
4344
- return this._acquireLock(this.lockAcquireTimeout, async () => {
4784
+ const run = async () => {
4345
4785
  try {
4346
4786
  return await this._useSession(async (result) => {
4347
4787
  var _a;
@@ -4375,10 +4815,15 @@ class GoTrueClient {
4375
4815
  }
4376
4816
  throw error;
4377
4817
  }
4378
- });
4818
+ };
4819
+ if (this.lock != null) {
4820
+ // TODO(v3): remove legacy lock path
4821
+ return this._acquireLock(this.lockAcquireTimeout, run);
4822
+ }
4823
+ return run();
4379
4824
  }
4380
4825
  async _challenge(params) {
4381
- return this._acquireLock(this.lockAcquireTimeout, async () => {
4826
+ const run = async () => {
4382
4827
  try {
4383
4828
  return await this._useSession(async (result) => {
4384
4829
  var _a;
@@ -4418,14 +4863,17 @@ class GoTrueClient {
4418
4863
  }
4419
4864
  throw error;
4420
4865
  }
4421
- });
4866
+ };
4867
+ if (this.lock != null) {
4868
+ // TODO(v3): remove legacy lock path
4869
+ return this._acquireLock(this.lockAcquireTimeout, run);
4870
+ }
4871
+ return run();
4422
4872
  }
4423
4873
  /**
4424
- * {@see GoTrueMFAApi#challengeAndVerify}
4874
+ * {@link GoTrueMFAApi#challengeAndVerify}
4425
4875
  */
4426
4876
  async _challengeAndVerify(params) {
4427
- // both _challenge and _verify independently acquire the lock, so no need
4428
- // to acquire it here
4429
4877
  const { data: challengeData, error: challengeError } = await this._challenge({
4430
4878
  factorId: params.factorId,
4431
4879
  });
@@ -4439,11 +4887,10 @@ class GoTrueClient {
4439
4887
  });
4440
4888
  }
4441
4889
  /**
4442
- * {@see GoTrueMFAApi#listFactors}
4890
+ * {@link GoTrueMFAApi#listFactors}
4443
4891
  */
4444
4892
  async _listFactors() {
4445
4893
  var _a;
4446
- // use #getUser instead of #_getUser as the former acquires a lock
4447
4894
  const { data: { user }, error: userError, } = await this.getUser();
4448
4895
  if (userError) {
4449
4896
  return { data: null, error: userError };
@@ -4468,7 +4915,7 @@ class GoTrueClient {
4468
4915
  };
4469
4916
  }
4470
4917
  /**
4471
- * {@see GoTrueMFAApi#getAuthenticatorAssuranceLevel}
4918
+ * {@link GoTrueMFAApi#getAuthenticatorAssuranceLevel}
4472
4919
  */
4473
4920
  async _getAuthenticatorAssuranceLevel(jwt) {
4474
4921
  var _a, _b, _c, _d;
@@ -4719,15 +5166,15 @@ class GoTrueClient {
4719
5166
  * Extracts the JWT claims present in the access token by first verifying the
4720
5167
  * JWT against the server's JSON Web Key Set endpoint
4721
5168
  * `/.well-known/jwks.json` which is often cached, resulting in significantly
4722
- * faster responses. Prefer this method over {@link #getUser} which always
5169
+ * faster responses. Prefer this method over {@link GoTrueClient.getUser} which always
4723
5170
  * sends a request to the Auth server for each JWT.
4724
5171
  *
4725
5172
  * If the project is not using an asymmetric JWT signing key (like ECC or
4726
- * RSA) it always sends a request to the Auth server (similar to {@link
4727
- * #getUser}) to verify the JWT.
5173
+ * RSA) it always sends a request to the Auth server (similar to
5174
+ * {@link GoTrueClient.getUser}) to verify the JWT.
4728
5175
  *
4729
5176
  * @param jwt An optional specific JWT you wish to verify, not the one you
4730
- * can obtain from {@link #getSession}.
5177
+ * can obtain from {@link GoTrueClient.getSession}.
4731
5178
  * @param options Various additional options that allow you to customize the
4732
5179
  * behavior of this method.
4733
5180
  *
@@ -4792,8 +5239,14 @@ class GoTrueClient {
4792
5239
  }
4793
5240
  const { header, payload, signature, raw: { header: rawHeader, payload: rawPayload }, } = decodeJWT(token);
4794
5241
  if (!(options === null || options === void 0 ? void 0 : options.allowExpired)) {
4795
- // Reject expired JWTs should only happen if jwt argument was passed
4796
- validateExp(payload.exp);
5242
+ // Reject expired JWTs should only happen if jwt argument was passed.
5243
+ // Rethrow as AuthInvalidJwtError so the outer catch converts it to { data, error }.
5244
+ try {
5245
+ validateExp(payload.exp);
5246
+ }
5247
+ catch (e) {
5248
+ throw new AuthInvalidJwtError(e instanceof Error ? e.message : 'JWT validation failed');
5249
+ }
4797
5250
  }
4798
5251
  const signingKey = !header.alg ||
4799
5252
  header.alg.startsWith('HS') ||
@@ -4844,6 +5297,335 @@ class GoTrueClient {
4844
5297
  throw error;
4845
5298
  }
4846
5299
  }
5300
+ // --- Passkey Methods ---
5301
+ /**
5302
+ * Sign in with a passkey. Handles the full WebAuthn ceremony:
5303
+ * 1. Fetches authentication challenge from server
5304
+ * 2. Prompts user via navigator.credentials.get()
5305
+ * 3. Verifies credential with server and creates session
5306
+ *
5307
+ * Requires `auth.experimental.passkey: true`.
5308
+ *
5309
+ * @category Auth
5310
+ */
5311
+ async signInWithPasskey(credentials) {
5312
+ var _a, _b, _c;
5313
+ assertPasskeyExperimentalEnabled(this.experimental);
5314
+ try {
5315
+ if (!browserSupportsWebAuthn()) {
5316
+ return this._returnResult({
5317
+ data: null,
5318
+ error: new AuthUnknownError('Browser does not support WebAuthn', null),
5319
+ });
5320
+ }
5321
+ // 1. Get challenge options from server
5322
+ const { data: options, error: optionsError } = await this._startPasskeyAuthentication({
5323
+ options: { captchaToken: (_a = credentials === null || credentials === void 0 ? void 0 : credentials.options) === null || _a === void 0 ? void 0 : _a.captchaToken },
5324
+ });
5325
+ if (optionsError || !options) {
5326
+ return this._returnResult({ data: null, error: optionsError });
5327
+ }
5328
+ // 2. Deserialize and prompt user via browser WebAuthn API
5329
+ const publicKeyOptions = deserializeCredentialRequestOptions(options.options);
5330
+ const signal = (_c = (_b = credentials === null || credentials === void 0 ? void 0 : credentials.options) === null || _b === void 0 ? void 0 : _b.signal) !== null && _c !== void 0 ? _c : webAuthnAbortService.createNewAbortSignal();
5331
+ const { data: credential, error: credentialError } = await getCredential({
5332
+ publicKey: publicKeyOptions,
5333
+ signal,
5334
+ });
5335
+ if (credentialError || !credential) {
5336
+ return this._returnResult({
5337
+ data: null,
5338
+ error: credentialError !== null && credentialError !== void 0 ? credentialError : new AuthUnknownError('WebAuthn ceremony failed', null),
5339
+ });
5340
+ }
5341
+ // 3. Serialize and verify with server
5342
+ const serialized = serializeCredentialRequestResponse(credential);
5343
+ return this._verifyPasskeyAuthentication({
5344
+ challengeId: options.challenge_id,
5345
+ credential: serialized,
5346
+ });
5347
+ }
5348
+ catch (error) {
5349
+ if (isAuthError(error)) {
5350
+ return this._returnResult({ data: null, error });
5351
+ }
5352
+ throw error;
5353
+ }
5354
+ }
5355
+ /**
5356
+ * Register a passkey for the current authenticated user. Handles the full WebAuthn ceremony:
5357
+ * 1. Fetches registration challenge from server
5358
+ * 2. Prompts user via navigator.credentials.create()
5359
+ * 3. Verifies credential with server
5360
+ *
5361
+ * Requires an active session. Requires `auth.experimental.passkey: true`.
5362
+ *
5363
+ * @category Auth
5364
+ */
5365
+ async registerPasskey(credentials) {
5366
+ var _a, _b;
5367
+ assertPasskeyExperimentalEnabled(this.experimental);
5368
+ try {
5369
+ if (!browserSupportsWebAuthn()) {
5370
+ return this._returnResult({
5371
+ data: null,
5372
+ error: new AuthUnknownError('Browser does not support WebAuthn', null),
5373
+ });
5374
+ }
5375
+ // 1. Get challenge options from server
5376
+ const { data: options, error: optionsError } = await this._startPasskeyRegistration();
5377
+ if (optionsError || !options) {
5378
+ return this._returnResult({ data: null, error: optionsError });
5379
+ }
5380
+ // 2. Deserialize and prompt user via browser WebAuthn API
5381
+ const publicKeyOptions = deserializeCredentialCreationOptions(options.options);
5382
+ const signal = (_b = (_a = credentials === null || credentials === void 0 ? void 0 : credentials.options) === null || _a === void 0 ? void 0 : _a.signal) !== null && _b !== void 0 ? _b : webAuthnAbortService.createNewAbortSignal();
5383
+ const { data: credential, error: credentialError } = await createCredential({
5384
+ publicKey: publicKeyOptions,
5385
+ signal,
5386
+ });
5387
+ if (credentialError || !credential) {
5388
+ return this._returnResult({
5389
+ data: null,
5390
+ error: credentialError !== null && credentialError !== void 0 ? credentialError : new AuthUnknownError('WebAuthn ceremony failed', null),
5391
+ });
5392
+ }
5393
+ // 3. Serialize and verify with server
5394
+ const serialized = serializeCredentialCreationResponse(credential);
5395
+ return this._verifyPasskeyRegistration({
5396
+ challengeId: options.challenge_id,
5397
+ credential: serialized,
5398
+ });
5399
+ }
5400
+ catch (error) {
5401
+ if (isAuthError(error)) {
5402
+ return this._returnResult({ data: null, error });
5403
+ }
5404
+ throw error;
5405
+ }
5406
+ }
5407
+ /**
5408
+ * Start passkey registration for the current authenticated user.
5409
+ * Returns WebAuthn credential creation options to pass to navigator.credentials.create().
5410
+ */
5411
+ async _startPasskeyRegistration() {
5412
+ assertPasskeyExperimentalEnabled(this.experimental);
5413
+ try {
5414
+ return await this._useSession(async (result) => {
5415
+ const { data: { session }, error: sessionError, } = result;
5416
+ if (sessionError) {
5417
+ return this._returnResult({ data: null, error: sessionError });
5418
+ }
5419
+ if (!session) {
5420
+ return this._returnResult({ data: null, error: new AuthSessionMissingError() });
5421
+ }
5422
+ const { data, error } = await _request(this.fetch, 'POST', `${this.url}/passkeys/registration/options`, {
5423
+ headers: this.headers,
5424
+ jwt: session.access_token,
5425
+ body: {},
5426
+ });
5427
+ if (error) {
5428
+ return this._returnResult({ data: null, error });
5429
+ }
5430
+ return this._returnResult({ data, error: null });
5431
+ });
5432
+ }
5433
+ catch (error) {
5434
+ if (isAuthError(error)) {
5435
+ return this._returnResult({ data: null, error });
5436
+ }
5437
+ throw error;
5438
+ }
5439
+ }
5440
+ /**
5441
+ * Verify passkey registration with the credential response.
5442
+ * The credentialResponse should be the serialized output of navigator.credentials.create().
5443
+ */
5444
+ async _verifyPasskeyRegistration(params) {
5445
+ assertPasskeyExperimentalEnabled(this.experimental);
5446
+ try {
5447
+ return await this._useSession(async (result) => {
5448
+ const { data: { session }, error: sessionError, } = result;
5449
+ if (sessionError) {
5450
+ return this._returnResult({ data: null, error: sessionError });
5451
+ }
5452
+ if (!session) {
5453
+ return this._returnResult({ data: null, error: new AuthSessionMissingError() });
5454
+ }
5455
+ const { data, error } = await _request(this.fetch, 'POST', `${this.url}/passkeys/registration/verify`, {
5456
+ headers: this.headers,
5457
+ jwt: session.access_token,
5458
+ body: {
5459
+ challenge_id: params.challengeId,
5460
+ credential: params.credential,
5461
+ },
5462
+ });
5463
+ if (error) {
5464
+ return this._returnResult({ data: null, error });
5465
+ }
5466
+ return this._returnResult({ data, error: null });
5467
+ });
5468
+ }
5469
+ catch (error) {
5470
+ if (isAuthError(error)) {
5471
+ return this._returnResult({ data: null, error });
5472
+ }
5473
+ throw error;
5474
+ }
5475
+ }
5476
+ /**
5477
+ * Start passkey authentication.
5478
+ * Returns WebAuthn credential request options to pass to navigator.credentials.get().
5479
+ */
5480
+ async _startPasskeyAuthentication(params) {
5481
+ var _a;
5482
+ assertPasskeyExperimentalEnabled(this.experimental);
5483
+ try {
5484
+ const { data, error } = await _request(this.fetch, 'POST', `${this.url}/passkeys/authentication/options`, {
5485
+ headers: this.headers,
5486
+ body: {
5487
+ gotrue_meta_security: { captcha_token: (_a = params === null || params === void 0 ? void 0 : params.options) === null || _a === void 0 ? void 0 : _a.captchaToken },
5488
+ },
5489
+ });
5490
+ if (error) {
5491
+ return this._returnResult({ data: null, error });
5492
+ }
5493
+ return this._returnResult({ data, error: null });
5494
+ }
5495
+ catch (error) {
5496
+ if (isAuthError(error)) {
5497
+ return this._returnResult({ data: null, error });
5498
+ }
5499
+ throw error;
5500
+ }
5501
+ }
5502
+ /**
5503
+ * Verify passkey authentication and create a session.
5504
+ * The credential should be the serialized output of navigator.credentials.get().
5505
+ */
5506
+ async _verifyPasskeyAuthentication(params) {
5507
+ assertPasskeyExperimentalEnabled(this.experimental);
5508
+ try {
5509
+ const { data, error } = await _request(this.fetch, 'POST', `${this.url}/passkeys/authentication/verify`, {
5510
+ headers: this.headers,
5511
+ body: {
5512
+ challenge_id: params.challengeId,
5513
+ credential: params.credential,
5514
+ },
5515
+ xform: _sessionResponse,
5516
+ });
5517
+ if (error) {
5518
+ return this._returnResult({ data: null, error });
5519
+ }
5520
+ if (data.session) {
5521
+ await this._saveSession(data.session);
5522
+ await this._notifyAllSubscribers('SIGNED_IN', data.session);
5523
+ }
5524
+ return this._returnResult({ data, error: null });
5525
+ }
5526
+ catch (error) {
5527
+ if (isAuthError(error)) {
5528
+ return this._returnResult({ data: null, error });
5529
+ }
5530
+ throw error;
5531
+ }
5532
+ }
5533
+ /**
5534
+ * List all passkeys for the current user.
5535
+ */
5536
+ async _listPasskeys() {
5537
+ assertPasskeyExperimentalEnabled(this.experimental);
5538
+ try {
5539
+ return await this._useSession(async (result) => {
5540
+ const { data: { session }, error: sessionError, } = result;
5541
+ if (sessionError) {
5542
+ return this._returnResult({ data: null, error: sessionError });
5543
+ }
5544
+ if (!session) {
5545
+ return this._returnResult({ data: null, error: new AuthSessionMissingError() });
5546
+ }
5547
+ const { data, error } = await _request(this.fetch, 'GET', `${this.url}/passkeys`, {
5548
+ headers: this.headers,
5549
+ jwt: session.access_token,
5550
+ xform: (data) => ({ data, error: null }),
5551
+ });
5552
+ if (error) {
5553
+ return this._returnResult({ data: null, error });
5554
+ }
5555
+ return this._returnResult({ data, error: null });
5556
+ });
5557
+ }
5558
+ catch (error) {
5559
+ if (isAuthError(error)) {
5560
+ return this._returnResult({ data: null, error });
5561
+ }
5562
+ throw error;
5563
+ }
5564
+ }
5565
+ /**
5566
+ * Update a passkey.
5567
+ */
5568
+ async _updatePasskey(params) {
5569
+ assertPasskeyExperimentalEnabled(this.experimental);
5570
+ try {
5571
+ return await this._useSession(async (result) => {
5572
+ const { data: { session }, error: sessionError, } = result;
5573
+ if (sessionError) {
5574
+ return this._returnResult({ data: null, error: sessionError });
5575
+ }
5576
+ if (!session) {
5577
+ return this._returnResult({ data: null, error: new AuthSessionMissingError() });
5578
+ }
5579
+ const { data, error } = await _request(this.fetch, 'PATCH', `${this.url}/passkeys/${params.passkeyId}`, {
5580
+ headers: this.headers,
5581
+ jwt: session.access_token,
5582
+ body: { friendly_name: params.friendlyName },
5583
+ });
5584
+ if (error) {
5585
+ return this._returnResult({ data: null, error });
5586
+ }
5587
+ return this._returnResult({ data, error: null });
5588
+ });
5589
+ }
5590
+ catch (error) {
5591
+ if (isAuthError(error)) {
5592
+ return this._returnResult({ data: null, error });
5593
+ }
5594
+ throw error;
5595
+ }
5596
+ }
5597
+ /**
5598
+ * Delete a passkey.
5599
+ */
5600
+ async _deletePasskey(params) {
5601
+ assertPasskeyExperimentalEnabled(this.experimental);
5602
+ try {
5603
+ return await this._useSession(async (result) => {
5604
+ const { data: { session }, error: sessionError, } = result;
5605
+ if (sessionError) {
5606
+ return this._returnResult({ data: null, error: sessionError });
5607
+ }
5608
+ if (!session) {
5609
+ return this._returnResult({ data: null, error: new AuthSessionMissingError() });
5610
+ }
5611
+ const { error } = await _request(this.fetch, 'DELETE', `${this.url}/passkeys/${params.passkeyId}`, {
5612
+ headers: this.headers,
5613
+ jwt: session.access_token,
5614
+ noResolveJson: true,
5615
+ });
5616
+ if (error) {
5617
+ return this._returnResult({ data: null, error });
5618
+ }
5619
+ return this._returnResult({ data: null, error: null });
5620
+ });
5621
+ }
5622
+ catch (error) {
5623
+ if (isAuthError(error)) {
5624
+ return this._returnResult({ data: null, error });
5625
+ }
5626
+ throw error;
5627
+ }
5628
+ }
4847
5629
  }
4848
5630
  GoTrueClient.nextInstanceID = {};
4849
5631
  export default GoTrueClient;