@supabase/gotrue-js 2.108.2 → 2.112.3

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 (67) hide show
  1. package/README.md +6 -2
  2. package/dist/main/GoTrueAdminApi.d.ts +1 -4
  3. package/dist/main/GoTrueAdminApi.d.ts.map +1 -1
  4. package/dist/main/GoTrueAdminApi.js +1 -4
  5. package/dist/main/GoTrueAdminApi.js.map +1 -1
  6. package/dist/main/GoTrueClient.d.ts +67 -21
  7. package/dist/main/GoTrueClient.d.ts.map +1 -1
  8. package/dist/main/GoTrueClient.js +217 -68
  9. package/dist/main/GoTrueClient.js.map +1 -1
  10. package/dist/main/lib/constants.d.ts +13 -0
  11. package/dist/main/lib/constants.d.ts.map +1 -1
  12. package/dist/main/lib/constants.js +14 -1
  13. package/dist/main/lib/constants.js.map +1 -1
  14. package/dist/main/lib/errors.d.ts +1 -1
  15. package/dist/main/lib/fetch.d.ts.map +1 -1
  16. package/dist/main/lib/fetch.js +12 -6
  17. package/dist/main/lib/fetch.js.map +1 -1
  18. package/dist/main/lib/helpers.d.ts +50 -1
  19. package/dist/main/lib/helpers.d.ts.map +1 -1
  20. package/dist/main/lib/helpers.js +161 -5
  21. package/dist/main/lib/helpers.js.map +1 -1
  22. package/dist/main/lib/locks.d.ts +1 -1
  23. package/dist/main/lib/locks.js +1 -1
  24. package/dist/main/lib/types.d.ts +71 -9
  25. package/dist/main/lib/types.d.ts.map +1 -1
  26. package/dist/main/lib/types.js.map +1 -1
  27. package/dist/main/lib/version.d.ts +1 -1
  28. package/dist/main/lib/version.js +1 -1
  29. package/dist/module/GoTrueAdminApi.d.ts +1 -4
  30. package/dist/module/GoTrueAdminApi.d.ts.map +1 -1
  31. package/dist/module/GoTrueAdminApi.js +1 -4
  32. package/dist/module/GoTrueAdminApi.js.map +1 -1
  33. package/dist/module/GoTrueClient.d.ts +67 -21
  34. package/dist/module/GoTrueClient.d.ts.map +1 -1
  35. package/dist/module/GoTrueClient.js +219 -70
  36. package/dist/module/GoTrueClient.js.map +1 -1
  37. package/dist/module/lib/constants.d.ts +13 -0
  38. package/dist/module/lib/constants.d.ts.map +1 -1
  39. package/dist/module/lib/constants.js +13 -0
  40. package/dist/module/lib/constants.js.map +1 -1
  41. package/dist/module/lib/errors.d.ts +1 -1
  42. package/dist/module/lib/fetch.d.ts.map +1 -1
  43. package/dist/module/lib/fetch.js +12 -6
  44. package/dist/module/lib/fetch.js.map +1 -1
  45. package/dist/module/lib/helpers.d.ts +50 -1
  46. package/dist/module/lib/helpers.d.ts.map +1 -1
  47. package/dist/module/lib/helpers.js +153 -5
  48. package/dist/module/lib/helpers.js.map +1 -1
  49. package/dist/module/lib/locks.d.ts +1 -1
  50. package/dist/module/lib/locks.js +1 -1
  51. package/dist/module/lib/types.d.ts +71 -9
  52. package/dist/module/lib/types.d.ts.map +1 -1
  53. package/dist/module/lib/types.js.map +1 -1
  54. package/dist/module/lib/version.d.ts +1 -1
  55. package/dist/module/lib/version.js +1 -1
  56. package/dist/tsconfig.module.tsbuildinfo +1 -1
  57. package/dist/tsconfig.tsbuildinfo +1 -1
  58. package/package.json +4 -4
  59. package/src/GoTrueAdminApi.ts +1 -4
  60. package/src/GoTrueClient.ts +268 -91
  61. package/src/lib/constants.ts +15 -0
  62. package/src/lib/errors.ts +1 -1
  63. package/src/lib/fetch.ts +13 -8
  64. package/src/lib/helpers.ts +198 -6
  65. package/src/lib/locks.ts +1 -1
  66. package/src/lib/types.ts +71 -9
  67. package/src/lib/version.ts +1 -1
@@ -1,8 +1,8 @@
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, REFRESH_FAILURE_COOLDOWN_MS, STORAGE_KEY, } from './lib/constants';
2
+ import { AUTO_REFRESH_TICK_DURATION_MS, AUTO_REFRESH_TICK_THRESHOLD, DEFAULT_HEADERS, EXPIRY_MARGIN_MS, GOTRUE_URL, JWKS_TTL, PKCE_FLOW_ID_PARAM, REFRESH_FAILURE_COOLDOWN_MS, STORAGE_KEY, } from './lib/constants';
3
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 { assertPasskeyExperimentalEnabled, decodeJWT, deepClone, Deferred, generateCallbackId, getAlgorithm, getCodeChallengeAndMethod, getItemAsync, insecureUserWarningProxy, isBrowser, parseParametersFromURL, removeItemAsync, resolveFetch, retryable, setItemAsync, sleep, supportsLocalStorage, userNotAvailableProxy, validateExp, } from './lib/helpers';
5
+ import { appendFlowIdToRedirectTo, assertPasskeyExperimentalEnabled, decodeJWT, deepClone, Deferred, generateCallbackId, getAlgorithm, getCodeChallengeAndMethod, getItemAsync, insecureUserWarningProxy, isBrowser, parseParametersFromURL, pkceVerifierSlotKey, removeAllPKCEVerifiers, removeItemAsync, removePKCEVerifier, resolveFetch, retrievePKCEVerifier, retryable, setItemAsync, sleep, supportsLocalStorage, userNotAvailableProxy, validateExp, validatePKCEFlowId, } from './lib/helpers';
6
6
  import { memoryLocalStorageAdapter } from './lib/local-storage';
7
7
  import { LockAcquireTimeoutError } from './lib/locks';
8
8
  import { polyfillGlobalThis } from './lib/polyfills';
@@ -126,6 +126,19 @@ class GoTrueClient {
126
126
  * Keep extra care to never reject or throw uncaught errors
127
127
  */
128
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;
129
142
  this.detectSessionInUrl = true;
130
143
  this.hasCustomAuthorizationHeader = false;
131
144
  this.suppressGetSessionWarning = false;
@@ -315,9 +328,19 @@ class GoTrueClient {
315
328
  * @category Auth
316
329
  */
317
330
  async initialize() {
331
+ var _a;
318
332
  if (this.initializePromise) {
319
333
  return await this.initializePromise;
320
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 = [];
321
344
  this.initializePromise = (async () => {
322
345
  if (this.lock != null) {
323
346
  // TODO(v3): remove legacy lock path
@@ -327,7 +350,15 @@ class GoTrueClient {
327
350
  }
328
351
  return await this._initialize();
329
352
  })();
330
- 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;
331
362
  }
332
363
  /**
333
364
  * IMPORTANT:
@@ -683,6 +714,7 @@ class GoTrueClient {
683
714
  */
684
715
  async signUp(credentials) {
685
716
  var _a, _b, _c;
717
+ let flowId = null;
686
718
  try {
687
719
  let res;
688
720
  if ('email' in credentials) {
@@ -691,11 +723,11 @@ class GoTrueClient {
691
723
  let codeChallengeMethod = null;
692
724
  if (this.flowType === 'pkce') {
693
725
  ;
694
- [codeChallenge, codeChallengeMethod] = await getCodeChallengeAndMethod(this.storage, this.storageKey);
726
+ [codeChallenge, codeChallengeMethod, flowId] = await this._getCodeChallengeAndMethod();
695
727
  }
696
728
  res = await _request(this.fetch, 'POST', `${this.url}/signup`, {
697
729
  headers: this.headers,
698
- redirectTo: options === null || options === void 0 ? void 0 : options.emailRedirectTo,
730
+ redirectTo: this._maybeAppendFlowIdToRedirect(options === null || options === void 0 ? void 0 : options.emailRedirectTo, flowId),
699
731
  body: {
700
732
  email,
701
733
  password,
@@ -726,7 +758,7 @@ class GoTrueClient {
726
758
  }
727
759
  const { data, error } = res;
728
760
  if (error || !data) {
729
- await removeItemAsync(this.storage, `${this.storageKey}-code-verifier`);
761
+ await removePKCEVerifier(this.storage, this.storageKey, flowId);
730
762
  return this._returnResult({ data: { user: null, session: null }, error: error });
731
763
  }
732
764
  const session = data.session;
@@ -738,7 +770,7 @@ class GoTrueClient {
738
770
  return this._returnResult({ data: { user, session }, error: null });
739
771
  }
740
772
  catch (error) {
741
- await removeItemAsync(this.storage, `${this.storageKey}-code-verifier`);
773
+ await removePKCEVerifier(this.storage, this.storageKey, flowId);
742
774
  if (isAuthError(error)) {
743
775
  return this._returnResult({ data: { user: null, session: null }, error });
744
776
  }
@@ -953,7 +985,8 @@ class GoTrueClient {
953
985
  * {
954
986
  * data: {
955
987
  * provider: 'github',
956
- * url: <PROVIDER_URL_TO_REDIRECT_TO>
988
+ * url: <PROVIDER_URL_TO_REDIRECT_TO>,
989
+ * flowId: <PKCE_FLOW_ID_OR_NULL>
957
990
  * },
958
991
  * error: null
959
992
  * }
@@ -1026,12 +1059,29 @@ class GoTrueClient {
1026
1059
  *
1027
1060
  * @remarks
1028
1061
  * - Used when `flowType` is set to `pkce` in client options.
1062
+ * - When several PKCE flows are in flight at once, pass `options.flowId` so
1063
+ * the code is exchanged with the verifier created by that specific flow.
1064
+ * The flow id is returned by `signInWithOAuth`, and with
1065
+ * `experimental.appendPkceFlowIdToRedirects` enabled it also arrives on
1066
+ * your callback URL as the reserved `sb_flow_id` query parameter (read
1067
+ * automatically in a browser).
1068
+ * - When a flow id is present but its stored verifier is gone (evicted,
1069
+ * already used, or from another device), the call fails with a verifier
1070
+ * missing error instead of trying another flow's verifier — a mismatched
1071
+ * verifier would consume the single-use code. Without any flow id the
1072
+ * most recently stored verifier is used, as before.
1029
1073
  *
1030
1074
  * @example Exchange Auth Code
1031
1075
  * ```js
1032
1076
  * supabase.auth.exchangeCodeForSession('34e770dd-9ff9-416c-87fa-43b31d7ef225')
1033
1077
  * ```
1034
1078
  *
1079
+ * @example Exchange Auth Code for a specific flow (e.g. in a server-side callback handler)
1080
+ * ```js
1081
+ * const flowId = requestUrl.searchParams.get('sb_flow_id')
1082
+ * supabase.auth.exchangeCodeForSession(code, flowId ? { flowId } : undefined)
1083
+ * ```
1084
+ *
1035
1085
  * @exampleResponse Exchange Auth Code
1036
1086
  * ```json
1037
1087
  * {
@@ -1189,15 +1239,15 @@ class GoTrueClient {
1189
1239
  * }
1190
1240
  * ```
1191
1241
  */
1192
- async exchangeCodeForSession(authCode) {
1242
+ async exchangeCodeForSession(authCode, options) {
1193
1243
  await this.initializePromise;
1194
1244
  if (this.lock != null) {
1195
1245
  // TODO(v3): remove legacy lock path
1196
1246
  return this._acquireLock(this.lockAcquireTimeout, async () => {
1197
- return this._exchangeCodeForSession(authCode);
1247
+ return this._exchangeCodeForSession(authCode, options);
1198
1248
  });
1199
1249
  }
1200
- return this._exchangeCodeForSession(authCode);
1250
+ return this._exchangeCodeForSession(authCode, options);
1201
1251
  }
1202
1252
  /**
1203
1253
  * Signs in a user by verifying a message signed by the user's private key.
@@ -1539,8 +1589,23 @@ class GoTrueClient {
1539
1589
  throw error;
1540
1590
  }
1541
1591
  }
1542
- async _exchangeCodeForSession(authCode) {
1543
- const storageItem = await getItemAsync(this.storage, `${this.storageKey}-code-verifier`);
1592
+ async _exchangeCodeForSession(authCode, options) {
1593
+ const hasExplicitFlowId = (options === null || options === void 0 ? void 0 : options.flowId) != null;
1594
+ const requestedFlowId = hasExplicitFlowId
1595
+ ? validatePKCEFlowId(options === null || options === void 0 ? void 0 : options.flowId)
1596
+ : isBrowser()
1597
+ ? validatePKCEFlowId(parseParametersFromURL(window.location.href)[PKCE_FLOW_ID_PARAM])
1598
+ : null;
1599
+ if (hasExplicitFlowId && !requestedFlowId) {
1600
+ this._debug('#_exchangeCodeForSession()', 'provided flowId is not a valid flow id', options === null || options === void 0 ? void 0 : options.flowId);
1601
+ }
1602
+ // With a flow id (explicit or from the callback URL) the lookup is
1603
+ // slot-only and a miss fails fast — see retrievePKCEVerifier. An invalid
1604
+ // explicit flow id also fails fast rather than borrowing another flow's
1605
+ // verifier.
1606
+ const { verifier: storageItem, flowId } = hasExplicitFlowId && !requestedFlowId
1607
+ ? { verifier: null, flowId: null }
1608
+ : await retrievePKCEVerifier(this.storage, this.storageKey, requestedFlowId);
1544
1609
  const [codeVerifier, redirectType] = (storageItem !== null && storageItem !== void 0 ? storageItem : '').split('/');
1545
1610
  try {
1546
1611
  if (!codeVerifier && this.flowType === 'pkce') {
@@ -1554,7 +1619,7 @@ class GoTrueClient {
1554
1619
  },
1555
1620
  xform: _sessionResponse,
1556
1621
  });
1557
- await removeItemAsync(this.storage, `${this.storageKey}-code-verifier`);
1622
+ await removePKCEVerifier(this.storage, this.storageKey, flowId);
1558
1623
  if (error) {
1559
1624
  throw error;
1560
1625
  }
@@ -1572,7 +1637,7 @@ class GoTrueClient {
1572
1637
  return this._returnResult({ data: Object.assign(Object.assign({}, data), { redirectType: redirectType !== null && redirectType !== void 0 ? redirectType : null }), error });
1573
1638
  }
1574
1639
  catch (error) {
1575
- await removeItemAsync(this.storage, `${this.storageKey}-code-verifier`);
1640
+ await removePKCEVerifier(this.storage, this.storageKey, flowId);
1576
1641
  if (isAuthError(error)) {
1577
1642
  return this._returnResult({
1578
1643
  data: { user: null, session: null, redirectType: null },
@@ -1771,6 +1836,7 @@ class GoTrueClient {
1771
1836
  */
1772
1837
  async signInWithOtp(credentials) {
1773
1838
  var _a, _b, _c, _d, _f;
1839
+ let flowId = null;
1774
1840
  try {
1775
1841
  if ('email' in credentials) {
1776
1842
  const { email, options } = credentials;
@@ -1778,7 +1844,7 @@ class GoTrueClient {
1778
1844
  let codeChallengeMethod = null;
1779
1845
  if (this.flowType === 'pkce') {
1780
1846
  ;
1781
- [codeChallenge, codeChallengeMethod] = await getCodeChallengeAndMethod(this.storage, this.storageKey);
1847
+ [codeChallenge, codeChallengeMethod, flowId] = await this._getCodeChallengeAndMethod();
1782
1848
  }
1783
1849
  const { error } = await _request(this.fetch, 'POST', `${this.url}/otp`, {
1784
1850
  headers: this.headers,
@@ -1790,7 +1856,7 @@ class GoTrueClient {
1790
1856
  code_challenge: codeChallenge,
1791
1857
  code_challenge_method: codeChallengeMethod,
1792
1858
  },
1793
- redirectTo: options === null || options === void 0 ? void 0 : options.emailRedirectTo,
1859
+ redirectTo: this._maybeAppendFlowIdToRedirect(options === null || options === void 0 ? void 0 : options.emailRedirectTo, flowId),
1794
1860
  });
1795
1861
  return this._returnResult({ data: { user: null, session: null }, error });
1796
1862
  }
@@ -1814,7 +1880,7 @@ class GoTrueClient {
1814
1880
  throw new AuthInvalidCredentialsError('You must provide either an email or phone number.');
1815
1881
  }
1816
1882
  catch (error) {
1817
- await removeItemAsync(this.storage, `${this.storageKey}-code-verifier`);
1883
+ await removePKCEVerifier(this.storage, this.storageKey, flowId);
1818
1884
  if (isAuthError(error)) {
1819
1885
  return this._returnResult({ data: { user: null, session: null }, error });
1820
1886
  }
@@ -2049,29 +2115,30 @@ class GoTrueClient {
2049
2115
  * ```
2050
2116
  */
2051
2117
  async signInWithSSO(params) {
2052
- var _a, _b, _c, _d, _f;
2118
+ var _a, _b, _c, _d;
2119
+ let flowId = null;
2053
2120
  try {
2054
2121
  let codeChallenge = null;
2055
2122
  let codeChallengeMethod = null;
2056
2123
  if (this.flowType === 'pkce') {
2057
2124
  ;
2058
- [codeChallenge, codeChallengeMethod] = await getCodeChallengeAndMethod(this.storage, this.storageKey);
2125
+ [codeChallenge, codeChallengeMethod, flowId] = await this._getCodeChallengeAndMethod();
2059
2126
  }
2060
2127
  const result = await _request(this.fetch, 'POST', `${this.url}/sso`, {
2061
- body: Object.assign(Object.assign(Object.assign(Object.assign(Object.assign({}, ('providerId' in params ? { provider_id: params.providerId } : null)), ('domain' in params ? { domain: params.domain } : null)), { redirect_to: (_b = (_a = params.options) === null || _a === void 0 ? void 0 : _a.redirectTo) !== null && _b !== void 0 ? _b : undefined }), (((_c = params === null || params === void 0 ? void 0 : params.options) === null || _c === void 0 ? void 0 : _c.captchaToken)
2128
+ body: Object.assign(Object.assign(Object.assign(Object.assign(Object.assign({}, ('providerId' in params ? { provider_id: params.providerId } : null)), ('domain' in params ? { domain: params.domain } : null)), { redirect_to: this._maybeAppendFlowIdToRedirect((_a = params.options) === null || _a === void 0 ? void 0 : _a.redirectTo, flowId) }), (((_b = params === null || params === void 0 ? void 0 : params.options) === null || _b === void 0 ? void 0 : _b.captchaToken)
2062
2129
  ? { gotrue_meta_security: { captcha_token: params.options.captchaToken } }
2063
2130
  : null)), { skip_http_redirect: true, code_challenge: codeChallenge, code_challenge_method: codeChallengeMethod }),
2064
2131
  headers: this.headers,
2065
2132
  xform: _ssoResponse,
2066
2133
  });
2067
2134
  // Automatically redirect in browser unless skipBrowserRedirect is true
2068
- if (((_d = result.data) === null || _d === void 0 ? void 0 : _d.url) && isBrowser() && !((_f = params.options) === null || _f === void 0 ? void 0 : _f.skipBrowserRedirect)) {
2135
+ if (((_c = result.data) === null || _c === void 0 ? void 0 : _c.url) && isBrowser() && !((_d = params.options) === null || _d === void 0 ? void 0 : _d.skipBrowserRedirect)) {
2069
2136
  window.location.assign(result.data.url);
2070
2137
  }
2071
2138
  return this._returnResult(result);
2072
2139
  }
2073
2140
  catch (error) {
2074
- await removeItemAsync(this.storage, `${this.storageKey}-code-verifier`);
2141
+ await removePKCEVerifier(this.storage, this.storageKey, flowId);
2075
2142
  if (isAuthError(error)) {
2076
2143
  return this._returnResult({ data: null, error });
2077
2144
  }
@@ -2191,6 +2258,7 @@ class GoTrueClient {
2191
2258
  * ```
2192
2259
  */
2193
2260
  async resend(credentials) {
2261
+ let flowId = null;
2194
2262
  try {
2195
2263
  const endpoint = `${this.url}/resend`;
2196
2264
  if ('email' in credentials) {
@@ -2199,7 +2267,7 @@ class GoTrueClient {
2199
2267
  let codeChallengeMethod = null;
2200
2268
  if (this.flowType === 'pkce') {
2201
2269
  ;
2202
- [codeChallenge, codeChallengeMethod] = await getCodeChallengeAndMethod(this.storage, this.storageKey);
2270
+ [codeChallenge, codeChallengeMethod, flowId] = await this._getCodeChallengeAndMethod();
2203
2271
  }
2204
2272
  const { error } = await _request(this.fetch, 'POST', endpoint, {
2205
2273
  headers: this.headers,
@@ -2210,10 +2278,10 @@ class GoTrueClient {
2210
2278
  code_challenge: codeChallenge,
2211
2279
  code_challenge_method: codeChallengeMethod,
2212
2280
  },
2213
- redirectTo: options === null || options === void 0 ? void 0 : options.emailRedirectTo,
2281
+ redirectTo: this._maybeAppendFlowIdToRedirect(options === null || options === void 0 ? void 0 : options.emailRedirectTo, flowId),
2214
2282
  });
2215
2283
  if (error) {
2216
- await removeItemAsync(this.storage, `${this.storageKey}-code-verifier`);
2284
+ await removePKCEVerifier(this.storage, this.storageKey, flowId);
2217
2285
  }
2218
2286
  return this._returnResult({ data: { user: null, session: null }, error });
2219
2287
  }
@@ -2235,7 +2303,7 @@ class GoTrueClient {
2235
2303
  throw new AuthInvalidCredentialsError('You must provide either an email or phone number and a type');
2236
2304
  }
2237
2305
  catch (error) {
2238
- await removeItemAsync(this.storage, `${this.storageKey}-code-verifier`);
2306
+ await removePKCEVerifier(this.storage, this.storageKey, flowId);
2239
2307
  if (isAuthError(error)) {
2240
2308
  return this._returnResult({ data: { user: null, session: null }, error });
2241
2309
  }
@@ -2251,7 +2319,7 @@ class GoTrueClient {
2251
2319
  * to the client. If that storage is based on request cookies for example,
2252
2320
  * the values in it may not be authentic and therefore it's strongly advised
2253
2321
  * against using this method and its results in such circumstances. A warning
2254
- * will be emitted if this is detected. Use {@link #getUser()} instead.
2322
+ * will be emitted if this is detected. Use {@link GoTrueClient.getUser} instead.
2255
2323
  *
2256
2324
  * @category Auth
2257
2325
  *
@@ -2402,7 +2470,7 @@ class GoTrueClient {
2402
2470
  }
2403
2471
  }
2404
2472
  /**
2405
- * Use instead of {@link #getSession} inside the library. Loads the session
2473
+ * Use instead of {@link GoTrueClient.getSession} inside the library. Loads the session
2406
2474
  * via `__loadSession` (which may trigger a refresh if the access token is
2407
2475
  * within the expiry margin) and runs `fn` with the result.
2408
2476
  */
@@ -2423,7 +2491,7 @@ class GoTrueClient {
2423
2491
  /**
2424
2492
  * NEVER USE DIRECTLY!
2425
2493
  *
2426
- * Always use {@link #_useSession}.
2494
+ * Always use `_useSession`.
2427
2495
  */
2428
2496
  async __loadSession() {
2429
2497
  this._debug('#__loadSession()', 'begin');
@@ -2637,7 +2705,6 @@ class GoTrueClient {
2637
2705
  // JWT contains a `session_id` which does not correspond to an active
2638
2706
  // session in the database, indicating the user is signed out.
2639
2707
  await this._removeSession();
2640
- await removeItemAsync(this.storage, `${this.storageKey}-code-verifier`);
2641
2708
  }
2642
2709
  return this._returnResult({ data: { user: null }, error });
2643
2710
  }
@@ -2769,6 +2836,7 @@ class GoTrueClient {
2769
2836
  return await this._updateUser(attributes, options);
2770
2837
  }
2771
2838
  async _updateUser(attributes, options = {}) {
2839
+ let flowId = null;
2772
2840
  try {
2773
2841
  return await this._useSession(async (result) => {
2774
2842
  const { data: sessionData, error: sessionError } = result;
@@ -2783,11 +2851,11 @@ class GoTrueClient {
2783
2851
  let codeChallengeMethod = null;
2784
2852
  if (this.flowType === 'pkce' && attributes.email != null) {
2785
2853
  ;
2786
- [codeChallenge, codeChallengeMethod] = await getCodeChallengeAndMethod(this.storage, this.storageKey);
2854
+ [codeChallenge, codeChallengeMethod, flowId] = await this._getCodeChallengeAndMethod();
2787
2855
  }
2788
2856
  const { data, error: userError } = await _request(this.fetch, 'PUT', `${this.url}/user`, {
2789
2857
  headers: this.headers,
2790
- redirectTo: options === null || options === void 0 ? void 0 : options.emailRedirectTo,
2858
+ redirectTo: this._maybeAppendFlowIdToRedirect(options === null || options === void 0 ? void 0 : options.emailRedirectTo, flowId),
2791
2859
  body: Object.assign(Object.assign({}, attributes), { code_challenge: codeChallenge, code_challenge_method: codeChallengeMethod }),
2792
2860
  jwt: session.access_token,
2793
2861
  xform: _userResponse,
@@ -2802,7 +2870,7 @@ class GoTrueClient {
2802
2870
  });
2803
2871
  }
2804
2872
  catch (error) {
2805
- await removeItemAsync(this.storage, `${this.storageKey}-code-verifier`);
2873
+ await removePKCEVerifier(this.storage, this.storageKey, flowId);
2806
2874
  if (isAuthError(error)) {
2807
2875
  return this._returnResult({ data: { user: null }, error });
2808
2876
  }
@@ -3195,11 +3263,14 @@ class GoTrueClient {
3195
3263
  this._debug('#_initialize()', 'begin', 'is PKCE flow', true);
3196
3264
  if (!params.code)
3197
3265
  throw new AuthPKCEGrantCodeExchangeError('No code detected.');
3198
- const { data, error } = await this._exchangeCodeForSession(params.code);
3266
+ const { data, error } = await this._exchangeCodeForSession(params.code, {
3267
+ flowId: params[PKCE_FLOW_ID_PARAM],
3268
+ });
3199
3269
  if (error)
3200
3270
  throw error;
3201
3271
  const url = new URL(window.location.href);
3202
3272
  url.searchParams.delete('code');
3273
+ url.searchParams.delete(PKCE_FLOW_ID_PARAM);
3203
3274
  window.history.replaceState(window.history.state, '', url.toString());
3204
3275
  return {
3205
3276
  data: { session: data.session, redirectType: (_a = data.redirectType) !== null && _a !== void 0 ? _a : null },
@@ -3269,8 +3340,16 @@ class GoTrueClient {
3269
3340
  * Checks if the current URL and backing storage contain parameters given by a PKCE flow
3270
3341
  */
3271
3342
  async _isPKCECallback(params) {
3343
+ if (!params.code) {
3344
+ return false;
3345
+ }
3346
+ const flowId = validatePKCEFlowId(params[PKCE_FLOW_ID_PARAM]);
3347
+ if (flowId &&
3348
+ (await getItemAsync(this.storage, pkceVerifierSlotKey(this.storageKey, flowId)))) {
3349
+ return true;
3350
+ }
3272
3351
  const currentStorageContent = await getItemAsync(this.storage, `${this.storageKey}-code-verifier`);
3273
- return !!(params.code && currentStorageContent);
3352
+ return !!currentStorageContent;
3274
3353
  }
3275
3354
  /**
3276
3355
  * Inside a browser context, `signOut()` will remove the logged in user from the browser session and log them out - removing all items from localstorage and then trigger a `"SIGNED_OUT"` event.
@@ -3326,6 +3405,9 @@ class GoTrueClient {
3326
3405
  async _signOut({ scope } = { scope: 'global' }) {
3327
3406
  return await this._useSession(async (result) => {
3328
3407
  var _a;
3408
+ const removeCurrentSession = async () => {
3409
+ await this._removeSession();
3410
+ };
3329
3411
  const { data, error: sessionError } = result;
3330
3412
  if (sessionError && !isAuthSessionMissingError(sessionError)) {
3331
3413
  return this._returnResult({ error: sessionError });
@@ -3339,13 +3421,15 @@ class GoTrueClient {
3339
3421
  if (!((isAuthApiError(error) &&
3340
3422
  (error.status === 404 || error.status === 401 || error.status === 403)) ||
3341
3423
  isAuthSessionMissingError(error))) {
3424
+ if (scope !== 'others') {
3425
+ await removeCurrentSession();
3426
+ }
3342
3427
  return this._returnResult({ error });
3343
3428
  }
3344
3429
  }
3345
3430
  }
3346
3431
  if (scope !== 'others') {
3347
- await this._removeSession();
3348
- await removeItemAsync(this.storage, `${this.storageKey}-code-verifier`);
3432
+ await removeCurrentSession();
3349
3433
  }
3350
3434
  return this._returnResult({ error: null });
3351
3435
  });
@@ -3566,7 +3650,16 @@ class GoTrueClient {
3566
3650
  catch (err) {
3567
3651
  await ((_b = this.stateChangeEmitters.get(id)) === null || _b === void 0 ? void 0 : _b.callback('INITIAL_SESSION', null));
3568
3652
  this._debug('INITIAL_SESSION', 'callback id', id, 'error', err);
3569
- if (isAuthSessionMissingError(err)) {
3653
+ if (isAuthSessionMissingError(err) ||
3654
+ isAuthRetryableFetchError(err) ||
3655
+ (isAuthApiError(err) &&
3656
+ (err.code === 'refresh_token_not_found' ||
3657
+ err.code === 'refresh_token_already_used' ||
3658
+ err.code === 'session_expired'))) {
3659
+ // A missing session, a transient/aborted network failure (e.g. a
3660
+ // superseded page navigation cancelling the in-flight request), or
3661
+ // a dead refresh token (e.g. stale SSR cookies) is not an
3662
+ // application error — warn rather than surface it raw.
3570
3663
  console.warn(err);
3571
3664
  }
3572
3665
  else {
@@ -3645,9 +3738,10 @@ class GoTrueClient {
3645
3738
  async resetPasswordForEmail(email, options = {}) {
3646
3739
  let codeChallenge = null;
3647
3740
  let codeChallengeMethod = null;
3741
+ let flowId = null;
3648
3742
  if (this.flowType === 'pkce') {
3649
3743
  ;
3650
- [codeChallenge, codeChallengeMethod] = await getCodeChallengeAndMethod(this.storage, this.storageKey, true // isPasswordRecovery
3744
+ [codeChallenge, codeChallengeMethod, flowId] = await this._getCodeChallengeAndMethod(true // isPasswordRecovery
3651
3745
  );
3652
3746
  }
3653
3747
  try {
@@ -3659,11 +3753,11 @@ class GoTrueClient {
3659
3753
  gotrue_meta_security: { captcha_token: options.captchaToken },
3660
3754
  },
3661
3755
  headers: this.headers,
3662
- redirectTo: options.redirectTo,
3756
+ redirectTo: this._maybeAppendFlowIdToRedirect(options.redirectTo, flowId),
3663
3757
  });
3664
3758
  }
3665
3759
  catch (error) {
3666
- await removeItemAsync(this.storage, `${this.storageKey}-code-verifier`);
3760
+ await removePKCEVerifier(this.storage, this.storageKey, flowId);
3667
3761
  if (isAuthError(error)) {
3668
3762
  return this._returnResult({ data: null, error });
3669
3763
  }
@@ -3746,7 +3840,8 @@ class GoTrueClient {
3746
3840
  * {
3747
3841
  * data: {
3748
3842
  * provider: 'github',
3749
- * url: <PROVIDER_URL_TO_REDIRECT_TO>
3843
+ * url: <PROVIDER_URL_TO_REDIRECT_TO>,
3844
+ * flowId: <PKCE_FLOW_ID_OR_NULL>
3750
3845
  * },
3751
3846
  * error: null
3752
3847
  * }
@@ -3760,18 +3855,20 @@ class GoTrueClient {
3760
3855
  }
3761
3856
  async linkIdentityOAuth(credentials) {
3762
3857
  var _a;
3858
+ let flowId = null;
3763
3859
  try {
3764
3860
  const { data, error } = await this._useSession(async (result) => {
3765
3861
  var _a, _b, _c, _d, _f;
3766
3862
  const { data, error } = result;
3767
3863
  if (error)
3768
3864
  throw error;
3769
- const url = await this._getUrlForProvider(`${this.url}/user/identities/authorize`, credentials.provider, {
3865
+ const { url, flowId: urlFlowId } = await this._getUrlForProvider(`${this.url}/user/identities/authorize`, credentials.provider, {
3770
3866
  redirectTo: (_a = credentials.options) === null || _a === void 0 ? void 0 : _a.redirectTo,
3771
3867
  scopes: (_b = credentials.options) === null || _b === void 0 ? void 0 : _b.scopes,
3772
3868
  queryParams: (_c = credentials.options) === null || _c === void 0 ? void 0 : _c.queryParams,
3773
3869
  skipBrowserRedirect: true,
3774
3870
  });
3871
+ flowId = urlFlowId;
3775
3872
  return await _request(this.fetch, 'GET', url, {
3776
3873
  headers: this.headers,
3777
3874
  jwt: (_f = (_d = data.session) === null || _d === void 0 ? void 0 : _d.access_token) !== null && _f !== void 0 ? _f : undefined,
@@ -3783,13 +3880,16 @@ class GoTrueClient {
3783
3880
  window.location.assign(data === null || data === void 0 ? void 0 : data.url);
3784
3881
  }
3785
3882
  return this._returnResult({
3786
- data: { provider: credentials.provider, url: data === null || data === void 0 ? void 0 : data.url },
3883
+ data: { provider: credentials.provider, url: data === null || data === void 0 ? void 0 : data.url, flowId },
3787
3884
  error: null,
3788
3885
  });
3789
3886
  }
3790
3887
  catch (error) {
3791
3888
  if (isAuthError(error)) {
3792
- return this._returnResult({ data: { provider: credentials.provider, url: null }, error });
3889
+ return this._returnResult({
3890
+ data: { provider: credentials.provider, url: null, flowId },
3891
+ error,
3892
+ });
3793
3893
  }
3794
3894
  throw error;
3795
3895
  }
@@ -3832,7 +3932,7 @@ class GoTrueClient {
3832
3932
  return this._returnResult({ data, error });
3833
3933
  }
3834
3934
  catch (error) {
3835
- await removeItemAsync(this.storage, `${this.storageKey}-code-verifier`);
3935
+ await removePKCEVerifier(this.storage, this.storageKey, null);
3836
3936
  if (isAuthError(error)) {
3837
3937
  return this._returnResult({ data: { user: null, session: null }, error });
3838
3938
  }
@@ -3937,7 +4037,7 @@ class GoTrueClient {
3937
4037
  return isValidSession;
3938
4038
  }
3939
4039
  async _handleProviderSignIn(provider, options) {
3940
- const url = await this._getUrlForProvider(`${this.url}/authorize`, provider, {
4040
+ const { url, flowId } = await this._getUrlForProvider(`${this.url}/authorize`, provider, {
3941
4041
  redirectTo: options.redirectTo,
3942
4042
  scopes: options.scopes,
3943
4043
  queryParams: options.queryParams,
@@ -3947,7 +4047,7 @@ class GoTrueClient {
3947
4047
  if (isBrowser() && !options.skipBrowserRedirect) {
3948
4048
  window.location.assign(url);
3949
4049
  }
3950
- return { data: { provider, url }, error: null };
4050
+ return { data: { provider, url, flowId }, error: null };
3951
4051
  }
3952
4052
  /**
3953
4053
  * Recovers the session from LocalStorage and refreshes the token
@@ -4044,7 +4144,16 @@ class GoTrueClient {
4044
4144
  }
4045
4145
  catch (err) {
4046
4146
  this._debug(debugName, 'error', err);
4047
- console.error(err);
4147
+ if (isAuthRetryableFetchError(err)) {
4148
+ // Transient/aborted network failure during session recovery (e.g. a
4149
+ // superseded page navigation cancelling the in-flight request). Warn
4150
+ // rather than surface it raw; the session is untouched and recovery
4151
+ // will run again on the next load.
4152
+ console.warn(err);
4153
+ }
4154
+ else {
4155
+ console.error(err);
4156
+ }
4048
4157
  return;
4049
4158
  }
4050
4159
  finally {
@@ -4194,6 +4303,19 @@ class GoTrueClient {
4194
4303
  }
4195
4304
  }
4196
4305
  async _notifyAllSubscribers(event, session, broadcast = true) {
4306
+ if (this._pendingInitNotifications !== null && broadcast) {
4307
+ // We're inside initialize() before initializePromise has resolved, and
4308
+ // this notification originates from the init chain (_recoverAndRefresh),
4309
+ // which always fires with broadcast = true. Enqueue instead of firing so
4310
+ // that callbacks can safely call getSession() / getUser() without
4311
+ // deadlocking on initializePromise.
4312
+ //
4313
+ // Notifications with broadcast = false come from the incoming
4314
+ // BroadcastChannel handler (a cross-tab event), not the init chain, so
4315
+ // they are fired immediately to preserve multi-tab ordering.
4316
+ this._pendingInitNotifications.push({ event, session, broadcast });
4317
+ return;
4318
+ }
4197
4319
  const debugName = `#_notifyAllSubscribers(${event})`;
4198
4320
  this._debug(debugName, 'begin', session, `broadcast = ${broadcast}`);
4199
4321
  try {
@@ -4230,7 +4352,6 @@ class GoTrueClient {
4230
4352
  // _saveSession is always called whenever a new session has been acquired
4231
4353
  // so we can safely suppress the warning returned by future getSession calls
4232
4354
  this.suppressGetSessionWarning = true;
4233
- await removeItemAsync(this.storage, `${this.storageKey}-code-verifier`);
4234
4355
  // Create a shallow copy to work with, to avoid mutating the original session object if it's used elsewhere
4235
4356
  const sessionToProcess = Object.assign({}, session);
4236
4357
  const userIsProxy = sessionToProcess.user && sessionToProcess.user.__isUserNotAvailableProxy === true;
@@ -4274,7 +4395,7 @@ class GoTrueClient {
4274
4395
  this.lastRefreshFailure = null;
4275
4396
  this.suppressGetSessionWarning = false;
4276
4397
  await removeItemAsync(this.storage, this.storageKey);
4277
- await removeItemAsync(this.storage, this.storageKey + '-code-verifier');
4398
+ await removeAllPKCEVerifiers(this.storage, this.storageKey);
4278
4399
  await removeItemAsync(this.storage, this.storageKey + '-user');
4279
4400
  if (this.userStorage) {
4280
4401
  await removeItemAsync(this.userStorage, this.storageKey + '-user');
@@ -4284,8 +4405,8 @@ class GoTrueClient {
4284
4405
  /**
4285
4406
  * Removes any registered visibilitychange callback.
4286
4407
  *
4287
- * {@see #startAutoRefresh}
4288
- * {@see #stopAutoRefresh}
4408
+ * {@link GoTrueClient.startAutoRefresh}
4409
+ * {@link GoTrueClient.stopAutoRefresh}
4289
4410
  */
4290
4411
  _removeVisibilityChangedCallback() {
4291
4412
  this._debug('#_removeVisibilityChangedCallback()');
@@ -4301,7 +4422,7 @@ class GoTrueClient {
4301
4422
  }
4302
4423
  }
4303
4424
  /**
4304
- * This is the private implementation of {@link #startAutoRefresh}. Use this
4425
+ * This is the private implementation of {@link GoTrueClient.startAutoRefresh}. Use this
4305
4426
  * within the library.
4306
4427
  */
4307
4428
  async _startAutoRefresh() {
@@ -4343,7 +4464,7 @@ class GoTrueClient {
4343
4464
  }
4344
4465
  }
4345
4466
  /**
4346
- * This is the private implementation of {@link #stopAutoRefresh}. Use this
4467
+ * This is the private implementation of {@link GoTrueClient.stopAutoRefresh}. Use this
4347
4468
  * within the library.
4348
4469
  */
4349
4470
  async _stopAutoRefresh() {
@@ -4379,7 +4500,7 @@ class GoTrueClient {
4379
4500
  * platform's foreground indication mechanism and call these methods
4380
4501
  * appropriately to conserve resources.
4381
4502
  *
4382
- * {@see #stopAutoRefresh}
4503
+ * {@link GoTrueClient.stopAutoRefresh}
4383
4504
  *
4384
4505
  * @category Auth
4385
4506
  *
@@ -4413,7 +4534,7 @@ class GoTrueClient {
4413
4534
  * If you call this method any managed visibility change callback will be
4414
4535
  * removed and you must manage visibility changes on your own.
4415
4536
  *
4416
- * See {@link #startAutoRefresh} for more details.
4537
+ * See {@link GoTrueClient.startAutoRefresh} for more details.
4417
4538
  *
4418
4539
  * @category Auth
4419
4540
  *
@@ -4640,15 +4761,23 @@ class GoTrueClient {
4640
4761
  * @param options.queryParams An object of key-value pairs containing query parameters granted to the OAuth application.
4641
4762
  */
4642
4763
  async _getUrlForProvider(url, provider, options) {
4764
+ let redirectTo = options === null || options === void 0 ? void 0 : options.redirectTo;
4765
+ let codeChallenge = null;
4766
+ let codeChallengeMethod = null;
4767
+ let flowId = null;
4768
+ if (this.flowType === 'pkce') {
4769
+ ;
4770
+ [codeChallenge, codeChallengeMethod, flowId] = await this._getCodeChallengeAndMethod();
4771
+ redirectTo = this._maybeAppendFlowIdToRedirect(redirectTo, flowId);
4772
+ }
4643
4773
  const urlParams = [`provider=${encodeURIComponent(provider)}`];
4644
- if (options === null || options === void 0 ? void 0 : options.redirectTo) {
4645
- urlParams.push(`redirect_to=${encodeURIComponent(options.redirectTo)}`);
4774
+ if (redirectTo) {
4775
+ urlParams.push(`redirect_to=${encodeURIComponent(redirectTo)}`);
4646
4776
  }
4647
4777
  if (options === null || options === void 0 ? void 0 : options.scopes) {
4648
4778
  urlParams.push(`scopes=${encodeURIComponent(options.scopes)}`);
4649
4779
  }
4650
- if (this.flowType === 'pkce') {
4651
- const [codeChallenge, codeChallengeMethod] = await getCodeChallengeAndMethod(this.storage, this.storageKey);
4780
+ if (codeChallenge != null && codeChallengeMethod != null) {
4652
4781
  const flowParams = new URLSearchParams({
4653
4782
  code_challenge: `${encodeURIComponent(codeChallenge)}`,
4654
4783
  code_challenge_method: `${encodeURIComponent(codeChallengeMethod)}`,
@@ -4662,7 +4791,27 @@ class GoTrueClient {
4662
4791
  if (options === null || options === void 0 ? void 0 : options.skipBrowserRedirect) {
4663
4792
  urlParams.push(`skip_http_redirect=${options.skipBrowserRedirect}`);
4664
4793
  }
4665
- return `${url}?${urlParams.join('&')}`;
4794
+ return { url: `${url}?${urlParams.join('&')}`, flowId };
4795
+ }
4796
+ /**
4797
+ * Appends the reserved flow id parameter to a redirect URL so the callback
4798
+ * can be matched to the verifier stored for its flow. Opt-in via
4799
+ * `experimental.appendPkceFlowIdToRedirects`: redirect URLs are validated
4800
+ * against the project's allow list including the query string, so an extra
4801
+ * parameter can stop exact (non-wildcard) entries from matching.
4802
+ */
4803
+ _maybeAppendFlowIdToRedirect(redirectTo, flowId) {
4804
+ if (!redirectTo || !flowId || !this.experimental.appendPkceFlowIdToRedirects) {
4805
+ return redirectTo !== null && redirectTo !== void 0 ? redirectTo : undefined;
4806
+ }
4807
+ return appendFlowIdToRedirectTo(redirectTo, flowId);
4808
+ }
4809
+ /**
4810
+ * Generates and stores a PKCE challenge/verifier pair for a new flow,
4811
+ * logging any pending verifier the bounded slot ring evicts.
4812
+ */
4813
+ async _getCodeChallengeAndMethod(isPasswordRecovery = false) {
4814
+ return getCodeChallengeAndMethod(this.storage, this.storageKey, isPasswordRecovery, (evictedFlowId) => this._debug('#_getCodeChallengeAndMethod()', 'evicted oldest pending PKCE verifier slot', evictedFlowId));
4666
4815
  }
4667
4816
  async _unenroll(params) {
4668
4817
  try {
@@ -4810,7 +4959,7 @@ class GoTrueClient {
4810
4959
  return run();
4811
4960
  }
4812
4961
  /**
4813
- * {@see GoTrueMFAApi#challengeAndVerify}
4962
+ * {@link GoTrueMFAApi#challengeAndVerify}
4814
4963
  */
4815
4964
  async _challengeAndVerify(params) {
4816
4965
  const { data: challengeData, error: challengeError } = await this._challenge({
@@ -4826,7 +4975,7 @@ class GoTrueClient {
4826
4975
  });
4827
4976
  }
4828
4977
  /**
4829
- * {@see GoTrueMFAApi#listFactors}
4978
+ * {@link GoTrueMFAApi#listFactors}
4830
4979
  */
4831
4980
  async _listFactors() {
4832
4981
  var _a;
@@ -4854,7 +5003,7 @@ class GoTrueClient {
4854
5003
  };
4855
5004
  }
4856
5005
  /**
4857
- * {@see GoTrueMFAApi#getAuthenticatorAssuranceLevel}
5006
+ * {@link GoTrueMFAApi#getAuthenticatorAssuranceLevel}
4858
5007
  */
4859
5008
  async _getAuthenticatorAssuranceLevel(jwt) {
4860
5009
  var _a, _b, _c, _d;
@@ -5105,15 +5254,15 @@ class GoTrueClient {
5105
5254
  * Extracts the JWT claims present in the access token by first verifying the
5106
5255
  * JWT against the server's JSON Web Key Set endpoint
5107
5256
  * `/.well-known/jwks.json` which is often cached, resulting in significantly
5108
- * faster responses. Prefer this method over {@link #getUser} which always
5257
+ * faster responses. Prefer this method over {@link GoTrueClient.getUser} which always
5109
5258
  * sends a request to the Auth server for each JWT.
5110
5259
  *
5111
5260
  * If the project is not using an asymmetric JWT signing key (like ECC or
5112
- * RSA) it always sends a request to the Auth server (similar to {@link
5113
- * #getUser}) to verify the JWT.
5261
+ * RSA) it always sends a request to the Auth server (similar to
5262
+ * {@link GoTrueClient.getUser}) to verify the JWT.
5114
5263
  *
5115
5264
  * @param jwt An optional specific JWT you wish to verify, not the one you
5116
- * can obtain from {@link #getSession}.
5265
+ * can obtain from {@link GoTrueClient.getSession}.
5117
5266
  * @param options Various additional options that allow you to customize the
5118
5267
  * behavior of this method.
5119
5268
  *