@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
@@ -129,6 +129,19 @@ class GoTrueClient {
129
129
  * Keep extra care to never reject or throw uncaught errors
130
130
  */
131
131
  this.initializePromise = null;
132
+ /**
133
+ * Non-null only while `initialize()` is running. While open,
134
+ * `_notifyAllSubscribers` enqueues init-chain notifications (those fired with
135
+ * `broadcast = true`, i.e. from `_recoverAndRefresh`) into this array instead
136
+ * of firing directly, so that `initializePromise` is guaranteed to be
137
+ * resolved before any subscriber callback runs. Callbacks that call
138
+ * `getSession()` / `getUser()` etc. would otherwise deadlock because those
139
+ * methods await `initializePromise`. Notifications from the incoming
140
+ * BroadcastChannel handler (`broadcast = false`) are not enqueued — they fire
141
+ * immediately. Flushed (in order) by `initialize()` after `initializePromise`
142
+ * settles.
143
+ */
144
+ this._pendingInitNotifications = null;
132
145
  this.detectSessionInUrl = true;
133
146
  this.hasCustomAuthorizationHeader = false;
134
147
  this.suppressGetSessionWarning = false;
@@ -318,9 +331,19 @@ class GoTrueClient {
318
331
  * @category Auth
319
332
  */
320
333
  async initialize() {
334
+ var _a;
321
335
  if (this.initializePromise) {
322
336
  return await this.initializePromise;
323
337
  }
338
+ // Open the notification queue before _initialize() runs so that every
339
+ // _notifyAllSubscribers call inside the init chain enqueues instead of
340
+ // firing. Without this, a callback receiving SIGNED_IN (or TOKEN_REFRESHED
341
+ // / SIGNED_OUT) during _recoverAndRefresh would deadlock if it called
342
+ // getSession() / getUser() — those methods await initializePromise, which
343
+ // can only resolve after the callback returns, which can only return after
344
+ // getSession() resolves. The queue is flushed below after initializePromise
345
+ // has settled, so callbacks run with a fully resolved initializePromise.
346
+ this._pendingInitNotifications = [];
324
347
  this.initializePromise = (async () => {
325
348
  if (this.lock != null) {
326
349
  // TODO(v3): remove legacy lock path
@@ -330,7 +353,15 @@ class GoTrueClient {
330
353
  }
331
354
  return await this._initialize();
332
355
  })();
333
- return await this.initializePromise;
356
+ const result = await this.initializePromise;
357
+ // initializePromise is now resolved — flush queued notifications in order.
358
+ // Callbacks can safely call getSession() / getUser() / signOut() etc.
359
+ const queue = (_a = this._pendingInitNotifications) !== null && _a !== void 0 ? _a : [];
360
+ this._pendingInitNotifications = null;
361
+ for (const n of queue) {
362
+ await this._notifyAllSubscribers(n.event, n.session, n.broadcast);
363
+ }
364
+ return result;
334
365
  }
335
366
  /**
336
367
  * IMPORTANT:
@@ -686,6 +717,7 @@ class GoTrueClient {
686
717
  */
687
718
  async signUp(credentials) {
688
719
  var _a, _b, _c;
720
+ let flowId = null;
689
721
  try {
690
722
  let res;
691
723
  if ('email' in credentials) {
@@ -694,11 +726,11 @@ class GoTrueClient {
694
726
  let codeChallengeMethod = null;
695
727
  if (this.flowType === 'pkce') {
696
728
  ;
697
- [codeChallenge, codeChallengeMethod] = await (0, helpers_1.getCodeChallengeAndMethod)(this.storage, this.storageKey);
729
+ [codeChallenge, codeChallengeMethod, flowId] = await this._getCodeChallengeAndMethod();
698
730
  }
699
731
  res = await (0, fetch_1._request)(this.fetch, 'POST', `${this.url}/signup`, {
700
732
  headers: this.headers,
701
- redirectTo: options === null || options === void 0 ? void 0 : options.emailRedirectTo,
733
+ redirectTo: this._maybeAppendFlowIdToRedirect(options === null || options === void 0 ? void 0 : options.emailRedirectTo, flowId),
702
734
  body: {
703
735
  email,
704
736
  password,
@@ -729,7 +761,7 @@ class GoTrueClient {
729
761
  }
730
762
  const { data, error } = res;
731
763
  if (error || !data) {
732
- await (0, helpers_1.removeItemAsync)(this.storage, `${this.storageKey}-code-verifier`);
764
+ await (0, helpers_1.removePKCEVerifier)(this.storage, this.storageKey, flowId);
733
765
  return this._returnResult({ data: { user: null, session: null }, error: error });
734
766
  }
735
767
  const session = data.session;
@@ -741,7 +773,7 @@ class GoTrueClient {
741
773
  return this._returnResult({ data: { user, session }, error: null });
742
774
  }
743
775
  catch (error) {
744
- await (0, helpers_1.removeItemAsync)(this.storage, `${this.storageKey}-code-verifier`);
776
+ await (0, helpers_1.removePKCEVerifier)(this.storage, this.storageKey, flowId);
745
777
  if ((0, errors_1.isAuthError)(error)) {
746
778
  return this._returnResult({ data: { user: null, session: null }, error });
747
779
  }
@@ -956,7 +988,8 @@ class GoTrueClient {
956
988
  * {
957
989
  * data: {
958
990
  * provider: 'github',
959
- * url: <PROVIDER_URL_TO_REDIRECT_TO>
991
+ * url: <PROVIDER_URL_TO_REDIRECT_TO>,
992
+ * flowId: <PKCE_FLOW_ID_OR_NULL>
960
993
  * },
961
994
  * error: null
962
995
  * }
@@ -1029,12 +1062,29 @@ class GoTrueClient {
1029
1062
  *
1030
1063
  * @remarks
1031
1064
  * - Used when `flowType` is set to `pkce` in client options.
1065
+ * - When several PKCE flows are in flight at once, pass `options.flowId` so
1066
+ * the code is exchanged with the verifier created by that specific flow.
1067
+ * The flow id is returned by `signInWithOAuth`, and with
1068
+ * `experimental.appendPkceFlowIdToRedirects` enabled it also arrives on
1069
+ * your callback URL as the reserved `sb_flow_id` query parameter (read
1070
+ * automatically in a browser).
1071
+ * - When a flow id is present but its stored verifier is gone (evicted,
1072
+ * already used, or from another device), the call fails with a verifier
1073
+ * missing error instead of trying another flow's verifier — a mismatched
1074
+ * verifier would consume the single-use code. Without any flow id the
1075
+ * most recently stored verifier is used, as before.
1032
1076
  *
1033
1077
  * @example Exchange Auth Code
1034
1078
  * ```js
1035
1079
  * supabase.auth.exchangeCodeForSession('34e770dd-9ff9-416c-87fa-43b31d7ef225')
1036
1080
  * ```
1037
1081
  *
1082
+ * @example Exchange Auth Code for a specific flow (e.g. in a server-side callback handler)
1083
+ * ```js
1084
+ * const flowId = requestUrl.searchParams.get('sb_flow_id')
1085
+ * supabase.auth.exchangeCodeForSession(code, flowId ? { flowId } : undefined)
1086
+ * ```
1087
+ *
1038
1088
  * @exampleResponse Exchange Auth Code
1039
1089
  * ```json
1040
1090
  * {
@@ -1192,15 +1242,15 @@ class GoTrueClient {
1192
1242
  * }
1193
1243
  * ```
1194
1244
  */
1195
- async exchangeCodeForSession(authCode) {
1245
+ async exchangeCodeForSession(authCode, options) {
1196
1246
  await this.initializePromise;
1197
1247
  if (this.lock != null) {
1198
1248
  // TODO(v3): remove legacy lock path
1199
1249
  return this._acquireLock(this.lockAcquireTimeout, async () => {
1200
- return this._exchangeCodeForSession(authCode);
1250
+ return this._exchangeCodeForSession(authCode, options);
1201
1251
  });
1202
1252
  }
1203
- return this._exchangeCodeForSession(authCode);
1253
+ return this._exchangeCodeForSession(authCode, options);
1204
1254
  }
1205
1255
  /**
1206
1256
  * Signs in a user by verifying a message signed by the user's private key.
@@ -1542,8 +1592,23 @@ class GoTrueClient {
1542
1592
  throw error;
1543
1593
  }
1544
1594
  }
1545
- async _exchangeCodeForSession(authCode) {
1546
- const storageItem = await (0, helpers_1.getItemAsync)(this.storage, `${this.storageKey}-code-verifier`);
1595
+ async _exchangeCodeForSession(authCode, options) {
1596
+ const hasExplicitFlowId = (options === null || options === void 0 ? void 0 : options.flowId) != null;
1597
+ const requestedFlowId = hasExplicitFlowId
1598
+ ? (0, helpers_1.validatePKCEFlowId)(options === null || options === void 0 ? void 0 : options.flowId)
1599
+ : (0, helpers_1.isBrowser)()
1600
+ ? (0, helpers_1.validatePKCEFlowId)((0, helpers_1.parseParametersFromURL)(window.location.href)[constants_1.PKCE_FLOW_ID_PARAM])
1601
+ : null;
1602
+ if (hasExplicitFlowId && !requestedFlowId) {
1603
+ this._debug('#_exchangeCodeForSession()', 'provided flowId is not a valid flow id', options === null || options === void 0 ? void 0 : options.flowId);
1604
+ }
1605
+ // With a flow id (explicit or from the callback URL) the lookup is
1606
+ // slot-only and a miss fails fast — see retrievePKCEVerifier. An invalid
1607
+ // explicit flow id also fails fast rather than borrowing another flow's
1608
+ // verifier.
1609
+ const { verifier: storageItem, flowId } = hasExplicitFlowId && !requestedFlowId
1610
+ ? { verifier: null, flowId: null }
1611
+ : await (0, helpers_1.retrievePKCEVerifier)(this.storage, this.storageKey, requestedFlowId);
1547
1612
  const [codeVerifier, redirectType] = (storageItem !== null && storageItem !== void 0 ? storageItem : '').split('/');
1548
1613
  try {
1549
1614
  if (!codeVerifier && this.flowType === 'pkce') {
@@ -1557,7 +1622,7 @@ class GoTrueClient {
1557
1622
  },
1558
1623
  xform: fetch_1._sessionResponse,
1559
1624
  });
1560
- await (0, helpers_1.removeItemAsync)(this.storage, `${this.storageKey}-code-verifier`);
1625
+ await (0, helpers_1.removePKCEVerifier)(this.storage, this.storageKey, flowId);
1561
1626
  if (error) {
1562
1627
  throw error;
1563
1628
  }
@@ -1575,7 +1640,7 @@ class GoTrueClient {
1575
1640
  return this._returnResult({ data: Object.assign(Object.assign({}, data), { redirectType: redirectType !== null && redirectType !== void 0 ? redirectType : null }), error });
1576
1641
  }
1577
1642
  catch (error) {
1578
- await (0, helpers_1.removeItemAsync)(this.storage, `${this.storageKey}-code-verifier`);
1643
+ await (0, helpers_1.removePKCEVerifier)(this.storage, this.storageKey, flowId);
1579
1644
  if ((0, errors_1.isAuthError)(error)) {
1580
1645
  return this._returnResult({
1581
1646
  data: { user: null, session: null, redirectType: null },
@@ -1774,6 +1839,7 @@ class GoTrueClient {
1774
1839
  */
1775
1840
  async signInWithOtp(credentials) {
1776
1841
  var _a, _b, _c, _d, _f;
1842
+ let flowId = null;
1777
1843
  try {
1778
1844
  if ('email' in credentials) {
1779
1845
  const { email, options } = credentials;
@@ -1781,7 +1847,7 @@ class GoTrueClient {
1781
1847
  let codeChallengeMethod = null;
1782
1848
  if (this.flowType === 'pkce') {
1783
1849
  ;
1784
- [codeChallenge, codeChallengeMethod] = await (0, helpers_1.getCodeChallengeAndMethod)(this.storage, this.storageKey);
1850
+ [codeChallenge, codeChallengeMethod, flowId] = await this._getCodeChallengeAndMethod();
1785
1851
  }
1786
1852
  const { error } = await (0, fetch_1._request)(this.fetch, 'POST', `${this.url}/otp`, {
1787
1853
  headers: this.headers,
@@ -1793,7 +1859,7 @@ class GoTrueClient {
1793
1859
  code_challenge: codeChallenge,
1794
1860
  code_challenge_method: codeChallengeMethod,
1795
1861
  },
1796
- redirectTo: options === null || options === void 0 ? void 0 : options.emailRedirectTo,
1862
+ redirectTo: this._maybeAppendFlowIdToRedirect(options === null || options === void 0 ? void 0 : options.emailRedirectTo, flowId),
1797
1863
  });
1798
1864
  return this._returnResult({ data: { user: null, session: null }, error });
1799
1865
  }
@@ -1817,7 +1883,7 @@ class GoTrueClient {
1817
1883
  throw new errors_1.AuthInvalidCredentialsError('You must provide either an email or phone number.');
1818
1884
  }
1819
1885
  catch (error) {
1820
- await (0, helpers_1.removeItemAsync)(this.storage, `${this.storageKey}-code-verifier`);
1886
+ await (0, helpers_1.removePKCEVerifier)(this.storage, this.storageKey, flowId);
1821
1887
  if ((0, errors_1.isAuthError)(error)) {
1822
1888
  return this._returnResult({ data: { user: null, session: null }, error });
1823
1889
  }
@@ -2052,29 +2118,30 @@ class GoTrueClient {
2052
2118
  * ```
2053
2119
  */
2054
2120
  async signInWithSSO(params) {
2055
- var _a, _b, _c, _d, _f;
2121
+ var _a, _b, _c, _d;
2122
+ let flowId = null;
2056
2123
  try {
2057
2124
  let codeChallenge = null;
2058
2125
  let codeChallengeMethod = null;
2059
2126
  if (this.flowType === 'pkce') {
2060
2127
  ;
2061
- [codeChallenge, codeChallengeMethod] = await (0, helpers_1.getCodeChallengeAndMethod)(this.storage, this.storageKey);
2128
+ [codeChallenge, codeChallengeMethod, flowId] = await this._getCodeChallengeAndMethod();
2062
2129
  }
2063
2130
  const result = await (0, fetch_1._request)(this.fetch, 'POST', `${this.url}/sso`, {
2064
- 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)
2131
+ 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)
2065
2132
  ? { gotrue_meta_security: { captcha_token: params.options.captchaToken } }
2066
2133
  : null)), { skip_http_redirect: true, code_challenge: codeChallenge, code_challenge_method: codeChallengeMethod }),
2067
2134
  headers: this.headers,
2068
2135
  xform: fetch_1._ssoResponse,
2069
2136
  });
2070
2137
  // Automatically redirect in browser unless skipBrowserRedirect is true
2071
- if (((_d = result.data) === null || _d === void 0 ? void 0 : _d.url) && (0, helpers_1.isBrowser)() && !((_f = params.options) === null || _f === void 0 ? void 0 : _f.skipBrowserRedirect)) {
2138
+ if (((_c = result.data) === null || _c === void 0 ? void 0 : _c.url) && (0, helpers_1.isBrowser)() && !((_d = params.options) === null || _d === void 0 ? void 0 : _d.skipBrowserRedirect)) {
2072
2139
  window.location.assign(result.data.url);
2073
2140
  }
2074
2141
  return this._returnResult(result);
2075
2142
  }
2076
2143
  catch (error) {
2077
- await (0, helpers_1.removeItemAsync)(this.storage, `${this.storageKey}-code-verifier`);
2144
+ await (0, helpers_1.removePKCEVerifier)(this.storage, this.storageKey, flowId);
2078
2145
  if ((0, errors_1.isAuthError)(error)) {
2079
2146
  return this._returnResult({ data: null, error });
2080
2147
  }
@@ -2194,6 +2261,7 @@ class GoTrueClient {
2194
2261
  * ```
2195
2262
  */
2196
2263
  async resend(credentials) {
2264
+ let flowId = null;
2197
2265
  try {
2198
2266
  const endpoint = `${this.url}/resend`;
2199
2267
  if ('email' in credentials) {
@@ -2202,7 +2270,7 @@ class GoTrueClient {
2202
2270
  let codeChallengeMethod = null;
2203
2271
  if (this.flowType === 'pkce') {
2204
2272
  ;
2205
- [codeChallenge, codeChallengeMethod] = await (0, helpers_1.getCodeChallengeAndMethod)(this.storage, this.storageKey);
2273
+ [codeChallenge, codeChallengeMethod, flowId] = await this._getCodeChallengeAndMethod();
2206
2274
  }
2207
2275
  const { error } = await (0, fetch_1._request)(this.fetch, 'POST', endpoint, {
2208
2276
  headers: this.headers,
@@ -2213,10 +2281,10 @@ class GoTrueClient {
2213
2281
  code_challenge: codeChallenge,
2214
2282
  code_challenge_method: codeChallengeMethod,
2215
2283
  },
2216
- redirectTo: options === null || options === void 0 ? void 0 : options.emailRedirectTo,
2284
+ redirectTo: this._maybeAppendFlowIdToRedirect(options === null || options === void 0 ? void 0 : options.emailRedirectTo, flowId),
2217
2285
  });
2218
2286
  if (error) {
2219
- await (0, helpers_1.removeItemAsync)(this.storage, `${this.storageKey}-code-verifier`);
2287
+ await (0, helpers_1.removePKCEVerifier)(this.storage, this.storageKey, flowId);
2220
2288
  }
2221
2289
  return this._returnResult({ data: { user: null, session: null }, error });
2222
2290
  }
@@ -2238,7 +2306,7 @@ class GoTrueClient {
2238
2306
  throw new errors_1.AuthInvalidCredentialsError('You must provide either an email or phone number and a type');
2239
2307
  }
2240
2308
  catch (error) {
2241
- await (0, helpers_1.removeItemAsync)(this.storage, `${this.storageKey}-code-verifier`);
2309
+ await (0, helpers_1.removePKCEVerifier)(this.storage, this.storageKey, flowId);
2242
2310
  if ((0, errors_1.isAuthError)(error)) {
2243
2311
  return this._returnResult({ data: { user: null, session: null }, error });
2244
2312
  }
@@ -2254,7 +2322,7 @@ class GoTrueClient {
2254
2322
  * to the client. If that storage is based on request cookies for example,
2255
2323
  * the values in it may not be authentic and therefore it's strongly advised
2256
2324
  * against using this method and its results in such circumstances. A warning
2257
- * will be emitted if this is detected. Use {@link #getUser()} instead.
2325
+ * will be emitted if this is detected. Use {@link GoTrueClient.getUser} instead.
2258
2326
  *
2259
2327
  * @category Auth
2260
2328
  *
@@ -2405,7 +2473,7 @@ class GoTrueClient {
2405
2473
  }
2406
2474
  }
2407
2475
  /**
2408
- * Use instead of {@link #getSession} inside the library. Loads the session
2476
+ * Use instead of {@link GoTrueClient.getSession} inside the library. Loads the session
2409
2477
  * via `__loadSession` (which may trigger a refresh if the access token is
2410
2478
  * within the expiry margin) and runs `fn` with the result.
2411
2479
  */
@@ -2426,7 +2494,7 @@ class GoTrueClient {
2426
2494
  /**
2427
2495
  * NEVER USE DIRECTLY!
2428
2496
  *
2429
- * Always use {@link #_useSession}.
2497
+ * Always use `_useSession`.
2430
2498
  */
2431
2499
  async __loadSession() {
2432
2500
  this._debug('#__loadSession()', 'begin');
@@ -2640,7 +2708,6 @@ class GoTrueClient {
2640
2708
  // JWT contains a `session_id` which does not correspond to an active
2641
2709
  // session in the database, indicating the user is signed out.
2642
2710
  await this._removeSession();
2643
- await (0, helpers_1.removeItemAsync)(this.storage, `${this.storageKey}-code-verifier`);
2644
2711
  }
2645
2712
  return this._returnResult({ data: { user: null }, error });
2646
2713
  }
@@ -2772,6 +2839,7 @@ class GoTrueClient {
2772
2839
  return await this._updateUser(attributes, options);
2773
2840
  }
2774
2841
  async _updateUser(attributes, options = {}) {
2842
+ let flowId = null;
2775
2843
  try {
2776
2844
  return await this._useSession(async (result) => {
2777
2845
  const { data: sessionData, error: sessionError } = result;
@@ -2786,11 +2854,11 @@ class GoTrueClient {
2786
2854
  let codeChallengeMethod = null;
2787
2855
  if (this.flowType === 'pkce' && attributes.email != null) {
2788
2856
  ;
2789
- [codeChallenge, codeChallengeMethod] = await (0, helpers_1.getCodeChallengeAndMethod)(this.storage, this.storageKey);
2857
+ [codeChallenge, codeChallengeMethod, flowId] = await this._getCodeChallengeAndMethod();
2790
2858
  }
2791
2859
  const { data, error: userError } = await (0, fetch_1._request)(this.fetch, 'PUT', `${this.url}/user`, {
2792
2860
  headers: this.headers,
2793
- redirectTo: options === null || options === void 0 ? void 0 : options.emailRedirectTo,
2861
+ redirectTo: this._maybeAppendFlowIdToRedirect(options === null || options === void 0 ? void 0 : options.emailRedirectTo, flowId),
2794
2862
  body: Object.assign(Object.assign({}, attributes), { code_challenge: codeChallenge, code_challenge_method: codeChallengeMethod }),
2795
2863
  jwt: session.access_token,
2796
2864
  xform: fetch_1._userResponse,
@@ -2805,7 +2873,7 @@ class GoTrueClient {
2805
2873
  });
2806
2874
  }
2807
2875
  catch (error) {
2808
- await (0, helpers_1.removeItemAsync)(this.storage, `${this.storageKey}-code-verifier`);
2876
+ await (0, helpers_1.removePKCEVerifier)(this.storage, this.storageKey, flowId);
2809
2877
  if ((0, errors_1.isAuthError)(error)) {
2810
2878
  return this._returnResult({ data: { user: null }, error });
2811
2879
  }
@@ -3198,11 +3266,14 @@ class GoTrueClient {
3198
3266
  this._debug('#_initialize()', 'begin', 'is PKCE flow', true);
3199
3267
  if (!params.code)
3200
3268
  throw new errors_1.AuthPKCEGrantCodeExchangeError('No code detected.');
3201
- const { data, error } = await this._exchangeCodeForSession(params.code);
3269
+ const { data, error } = await this._exchangeCodeForSession(params.code, {
3270
+ flowId: params[constants_1.PKCE_FLOW_ID_PARAM],
3271
+ });
3202
3272
  if (error)
3203
3273
  throw error;
3204
3274
  const url = new URL(window.location.href);
3205
3275
  url.searchParams.delete('code');
3276
+ url.searchParams.delete(constants_1.PKCE_FLOW_ID_PARAM);
3206
3277
  window.history.replaceState(window.history.state, '', url.toString());
3207
3278
  return {
3208
3279
  data: { session: data.session, redirectType: (_a = data.redirectType) !== null && _a !== void 0 ? _a : null },
@@ -3272,8 +3343,16 @@ class GoTrueClient {
3272
3343
  * Checks if the current URL and backing storage contain parameters given by a PKCE flow
3273
3344
  */
3274
3345
  async _isPKCECallback(params) {
3346
+ if (!params.code) {
3347
+ return false;
3348
+ }
3349
+ const flowId = (0, helpers_1.validatePKCEFlowId)(params[constants_1.PKCE_FLOW_ID_PARAM]);
3350
+ if (flowId &&
3351
+ (await (0, helpers_1.getItemAsync)(this.storage, (0, helpers_1.pkceVerifierSlotKey)(this.storageKey, flowId)))) {
3352
+ return true;
3353
+ }
3275
3354
  const currentStorageContent = await (0, helpers_1.getItemAsync)(this.storage, `${this.storageKey}-code-verifier`);
3276
- return !!(params.code && currentStorageContent);
3355
+ return !!currentStorageContent;
3277
3356
  }
3278
3357
  /**
3279
3358
  * 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.
@@ -3329,6 +3408,9 @@ class GoTrueClient {
3329
3408
  async _signOut({ scope } = { scope: 'global' }) {
3330
3409
  return await this._useSession(async (result) => {
3331
3410
  var _a;
3411
+ const removeCurrentSession = async () => {
3412
+ await this._removeSession();
3413
+ };
3332
3414
  const { data, error: sessionError } = result;
3333
3415
  if (sessionError && !(0, errors_1.isAuthSessionMissingError)(sessionError)) {
3334
3416
  return this._returnResult({ error: sessionError });
@@ -3342,13 +3424,15 @@ class GoTrueClient {
3342
3424
  if (!(((0, errors_1.isAuthApiError)(error) &&
3343
3425
  (error.status === 404 || error.status === 401 || error.status === 403)) ||
3344
3426
  (0, errors_1.isAuthSessionMissingError)(error))) {
3427
+ if (scope !== 'others') {
3428
+ await removeCurrentSession();
3429
+ }
3345
3430
  return this._returnResult({ error });
3346
3431
  }
3347
3432
  }
3348
3433
  }
3349
3434
  if (scope !== 'others') {
3350
- await this._removeSession();
3351
- await (0, helpers_1.removeItemAsync)(this.storage, `${this.storageKey}-code-verifier`);
3435
+ await removeCurrentSession();
3352
3436
  }
3353
3437
  return this._returnResult({ error: null });
3354
3438
  });
@@ -3569,7 +3653,16 @@ class GoTrueClient {
3569
3653
  catch (err) {
3570
3654
  await ((_b = this.stateChangeEmitters.get(id)) === null || _b === void 0 ? void 0 : _b.callback('INITIAL_SESSION', null));
3571
3655
  this._debug('INITIAL_SESSION', 'callback id', id, 'error', err);
3572
- if ((0, errors_1.isAuthSessionMissingError)(err)) {
3656
+ if ((0, errors_1.isAuthSessionMissingError)(err) ||
3657
+ (0, errors_1.isAuthRetryableFetchError)(err) ||
3658
+ ((0, errors_1.isAuthApiError)(err) &&
3659
+ (err.code === 'refresh_token_not_found' ||
3660
+ err.code === 'refresh_token_already_used' ||
3661
+ err.code === 'session_expired'))) {
3662
+ // A missing session, a transient/aborted network failure (e.g. a
3663
+ // superseded page navigation cancelling the in-flight request), or
3664
+ // a dead refresh token (e.g. stale SSR cookies) is not an
3665
+ // application error — warn rather than surface it raw.
3573
3666
  console.warn(err);
3574
3667
  }
3575
3668
  else {
@@ -3648,9 +3741,10 @@ class GoTrueClient {
3648
3741
  async resetPasswordForEmail(email, options = {}) {
3649
3742
  let codeChallenge = null;
3650
3743
  let codeChallengeMethod = null;
3744
+ let flowId = null;
3651
3745
  if (this.flowType === 'pkce') {
3652
3746
  ;
3653
- [codeChallenge, codeChallengeMethod] = await (0, helpers_1.getCodeChallengeAndMethod)(this.storage, this.storageKey, true // isPasswordRecovery
3747
+ [codeChallenge, codeChallengeMethod, flowId] = await this._getCodeChallengeAndMethod(true // isPasswordRecovery
3654
3748
  );
3655
3749
  }
3656
3750
  try {
@@ -3662,11 +3756,11 @@ class GoTrueClient {
3662
3756
  gotrue_meta_security: { captcha_token: options.captchaToken },
3663
3757
  },
3664
3758
  headers: this.headers,
3665
- redirectTo: options.redirectTo,
3759
+ redirectTo: this._maybeAppendFlowIdToRedirect(options.redirectTo, flowId),
3666
3760
  });
3667
3761
  }
3668
3762
  catch (error) {
3669
- await (0, helpers_1.removeItemAsync)(this.storage, `${this.storageKey}-code-verifier`);
3763
+ await (0, helpers_1.removePKCEVerifier)(this.storage, this.storageKey, flowId);
3670
3764
  if ((0, errors_1.isAuthError)(error)) {
3671
3765
  return this._returnResult({ data: null, error });
3672
3766
  }
@@ -3749,7 +3843,8 @@ class GoTrueClient {
3749
3843
  * {
3750
3844
  * data: {
3751
3845
  * provider: 'github',
3752
- * url: <PROVIDER_URL_TO_REDIRECT_TO>
3846
+ * url: <PROVIDER_URL_TO_REDIRECT_TO>,
3847
+ * flowId: <PKCE_FLOW_ID_OR_NULL>
3753
3848
  * },
3754
3849
  * error: null
3755
3850
  * }
@@ -3763,18 +3858,20 @@ class GoTrueClient {
3763
3858
  }
3764
3859
  async linkIdentityOAuth(credentials) {
3765
3860
  var _a;
3861
+ let flowId = null;
3766
3862
  try {
3767
3863
  const { data, error } = await this._useSession(async (result) => {
3768
3864
  var _a, _b, _c, _d, _f;
3769
3865
  const { data, error } = result;
3770
3866
  if (error)
3771
3867
  throw error;
3772
- const url = await this._getUrlForProvider(`${this.url}/user/identities/authorize`, credentials.provider, {
3868
+ const { url, flowId: urlFlowId } = await this._getUrlForProvider(`${this.url}/user/identities/authorize`, credentials.provider, {
3773
3869
  redirectTo: (_a = credentials.options) === null || _a === void 0 ? void 0 : _a.redirectTo,
3774
3870
  scopes: (_b = credentials.options) === null || _b === void 0 ? void 0 : _b.scopes,
3775
3871
  queryParams: (_c = credentials.options) === null || _c === void 0 ? void 0 : _c.queryParams,
3776
3872
  skipBrowserRedirect: true,
3777
3873
  });
3874
+ flowId = urlFlowId;
3778
3875
  return await (0, fetch_1._request)(this.fetch, 'GET', url, {
3779
3876
  headers: this.headers,
3780
3877
  jwt: (_f = (_d = data.session) === null || _d === void 0 ? void 0 : _d.access_token) !== null && _f !== void 0 ? _f : undefined,
@@ -3786,13 +3883,16 @@ class GoTrueClient {
3786
3883
  window.location.assign(data === null || data === void 0 ? void 0 : data.url);
3787
3884
  }
3788
3885
  return this._returnResult({
3789
- data: { provider: credentials.provider, url: data === null || data === void 0 ? void 0 : data.url },
3886
+ data: { provider: credentials.provider, url: data === null || data === void 0 ? void 0 : data.url, flowId },
3790
3887
  error: null,
3791
3888
  });
3792
3889
  }
3793
3890
  catch (error) {
3794
3891
  if ((0, errors_1.isAuthError)(error)) {
3795
- return this._returnResult({ data: { provider: credentials.provider, url: null }, error });
3892
+ return this._returnResult({
3893
+ data: { provider: credentials.provider, url: null, flowId },
3894
+ error,
3895
+ });
3796
3896
  }
3797
3897
  throw error;
3798
3898
  }
@@ -3835,7 +3935,7 @@ class GoTrueClient {
3835
3935
  return this._returnResult({ data, error });
3836
3936
  }
3837
3937
  catch (error) {
3838
- await (0, helpers_1.removeItemAsync)(this.storage, `${this.storageKey}-code-verifier`);
3938
+ await (0, helpers_1.removePKCEVerifier)(this.storage, this.storageKey, null);
3839
3939
  if ((0, errors_1.isAuthError)(error)) {
3840
3940
  return this._returnResult({ data: { user: null, session: null }, error });
3841
3941
  }
@@ -3940,7 +4040,7 @@ class GoTrueClient {
3940
4040
  return isValidSession;
3941
4041
  }
3942
4042
  async _handleProviderSignIn(provider, options) {
3943
- const url = await this._getUrlForProvider(`${this.url}/authorize`, provider, {
4043
+ const { url, flowId } = await this._getUrlForProvider(`${this.url}/authorize`, provider, {
3944
4044
  redirectTo: options.redirectTo,
3945
4045
  scopes: options.scopes,
3946
4046
  queryParams: options.queryParams,
@@ -3950,7 +4050,7 @@ class GoTrueClient {
3950
4050
  if ((0, helpers_1.isBrowser)() && !options.skipBrowserRedirect) {
3951
4051
  window.location.assign(url);
3952
4052
  }
3953
- return { data: { provider, url }, error: null };
4053
+ return { data: { provider, url, flowId }, error: null };
3954
4054
  }
3955
4055
  /**
3956
4056
  * Recovers the session from LocalStorage and refreshes the token
@@ -4047,7 +4147,16 @@ class GoTrueClient {
4047
4147
  }
4048
4148
  catch (err) {
4049
4149
  this._debug(debugName, 'error', err);
4050
- console.error(err);
4150
+ if ((0, errors_1.isAuthRetryableFetchError)(err)) {
4151
+ // Transient/aborted network failure during session recovery (e.g. a
4152
+ // superseded page navigation cancelling the in-flight request). Warn
4153
+ // rather than surface it raw; the session is untouched and recovery
4154
+ // will run again on the next load.
4155
+ console.warn(err);
4156
+ }
4157
+ else {
4158
+ console.error(err);
4159
+ }
4051
4160
  return;
4052
4161
  }
4053
4162
  finally {
@@ -4197,6 +4306,19 @@ class GoTrueClient {
4197
4306
  }
4198
4307
  }
4199
4308
  async _notifyAllSubscribers(event, session, broadcast = true) {
4309
+ if (this._pendingInitNotifications !== null && broadcast) {
4310
+ // We're inside initialize() before initializePromise has resolved, and
4311
+ // this notification originates from the init chain (_recoverAndRefresh),
4312
+ // which always fires with broadcast = true. Enqueue instead of firing so
4313
+ // that callbacks can safely call getSession() / getUser() without
4314
+ // deadlocking on initializePromise.
4315
+ //
4316
+ // Notifications with broadcast = false come from the incoming
4317
+ // BroadcastChannel handler (a cross-tab event), not the init chain, so
4318
+ // they are fired immediately to preserve multi-tab ordering.
4319
+ this._pendingInitNotifications.push({ event, session, broadcast });
4320
+ return;
4321
+ }
4200
4322
  const debugName = `#_notifyAllSubscribers(${event})`;
4201
4323
  this._debug(debugName, 'begin', session, `broadcast = ${broadcast}`);
4202
4324
  try {
@@ -4233,7 +4355,6 @@ class GoTrueClient {
4233
4355
  // _saveSession is always called whenever a new session has been acquired
4234
4356
  // so we can safely suppress the warning returned by future getSession calls
4235
4357
  this.suppressGetSessionWarning = true;
4236
- await (0, helpers_1.removeItemAsync)(this.storage, `${this.storageKey}-code-verifier`);
4237
4358
  // Create a shallow copy to work with, to avoid mutating the original session object if it's used elsewhere
4238
4359
  const sessionToProcess = Object.assign({}, session);
4239
4360
  const userIsProxy = sessionToProcess.user && sessionToProcess.user.__isUserNotAvailableProxy === true;
@@ -4277,7 +4398,7 @@ class GoTrueClient {
4277
4398
  this.lastRefreshFailure = null;
4278
4399
  this.suppressGetSessionWarning = false;
4279
4400
  await (0, helpers_1.removeItemAsync)(this.storage, this.storageKey);
4280
- await (0, helpers_1.removeItemAsync)(this.storage, this.storageKey + '-code-verifier');
4401
+ await (0, helpers_1.removeAllPKCEVerifiers)(this.storage, this.storageKey);
4281
4402
  await (0, helpers_1.removeItemAsync)(this.storage, this.storageKey + '-user');
4282
4403
  if (this.userStorage) {
4283
4404
  await (0, helpers_1.removeItemAsync)(this.userStorage, this.storageKey + '-user');
@@ -4287,8 +4408,8 @@ class GoTrueClient {
4287
4408
  /**
4288
4409
  * Removes any registered visibilitychange callback.
4289
4410
  *
4290
- * {@see #startAutoRefresh}
4291
- * {@see #stopAutoRefresh}
4411
+ * {@link GoTrueClient.startAutoRefresh}
4412
+ * {@link GoTrueClient.stopAutoRefresh}
4292
4413
  */
4293
4414
  _removeVisibilityChangedCallback() {
4294
4415
  this._debug('#_removeVisibilityChangedCallback()');
@@ -4304,7 +4425,7 @@ class GoTrueClient {
4304
4425
  }
4305
4426
  }
4306
4427
  /**
4307
- * This is the private implementation of {@link #startAutoRefresh}. Use this
4428
+ * This is the private implementation of {@link GoTrueClient.startAutoRefresh}. Use this
4308
4429
  * within the library.
4309
4430
  */
4310
4431
  async _startAutoRefresh() {
@@ -4346,7 +4467,7 @@ class GoTrueClient {
4346
4467
  }
4347
4468
  }
4348
4469
  /**
4349
- * This is the private implementation of {@link #stopAutoRefresh}. Use this
4470
+ * This is the private implementation of {@link GoTrueClient.stopAutoRefresh}. Use this
4350
4471
  * within the library.
4351
4472
  */
4352
4473
  async _stopAutoRefresh() {
@@ -4382,7 +4503,7 @@ class GoTrueClient {
4382
4503
  * platform's foreground indication mechanism and call these methods
4383
4504
  * appropriately to conserve resources.
4384
4505
  *
4385
- * {@see #stopAutoRefresh}
4506
+ * {@link GoTrueClient.stopAutoRefresh}
4386
4507
  *
4387
4508
  * @category Auth
4388
4509
  *
@@ -4416,7 +4537,7 @@ class GoTrueClient {
4416
4537
  * If you call this method any managed visibility change callback will be
4417
4538
  * removed and you must manage visibility changes on your own.
4418
4539
  *
4419
- * See {@link #startAutoRefresh} for more details.
4540
+ * See {@link GoTrueClient.startAutoRefresh} for more details.
4420
4541
  *
4421
4542
  * @category Auth
4422
4543
  *
@@ -4643,15 +4764,23 @@ class GoTrueClient {
4643
4764
  * @param options.queryParams An object of key-value pairs containing query parameters granted to the OAuth application.
4644
4765
  */
4645
4766
  async _getUrlForProvider(url, provider, options) {
4767
+ let redirectTo = options === null || options === void 0 ? void 0 : options.redirectTo;
4768
+ let codeChallenge = null;
4769
+ let codeChallengeMethod = null;
4770
+ let flowId = null;
4771
+ if (this.flowType === 'pkce') {
4772
+ ;
4773
+ [codeChallenge, codeChallengeMethod, flowId] = await this._getCodeChallengeAndMethod();
4774
+ redirectTo = this._maybeAppendFlowIdToRedirect(redirectTo, flowId);
4775
+ }
4646
4776
  const urlParams = [`provider=${encodeURIComponent(provider)}`];
4647
- if (options === null || options === void 0 ? void 0 : options.redirectTo) {
4648
- urlParams.push(`redirect_to=${encodeURIComponent(options.redirectTo)}`);
4777
+ if (redirectTo) {
4778
+ urlParams.push(`redirect_to=${encodeURIComponent(redirectTo)}`);
4649
4779
  }
4650
4780
  if (options === null || options === void 0 ? void 0 : options.scopes) {
4651
4781
  urlParams.push(`scopes=${encodeURIComponent(options.scopes)}`);
4652
4782
  }
4653
- if (this.flowType === 'pkce') {
4654
- const [codeChallenge, codeChallengeMethod] = await (0, helpers_1.getCodeChallengeAndMethod)(this.storage, this.storageKey);
4783
+ if (codeChallenge != null && codeChallengeMethod != null) {
4655
4784
  const flowParams = new URLSearchParams({
4656
4785
  code_challenge: `${encodeURIComponent(codeChallenge)}`,
4657
4786
  code_challenge_method: `${encodeURIComponent(codeChallengeMethod)}`,
@@ -4665,7 +4794,27 @@ class GoTrueClient {
4665
4794
  if (options === null || options === void 0 ? void 0 : options.skipBrowserRedirect) {
4666
4795
  urlParams.push(`skip_http_redirect=${options.skipBrowserRedirect}`);
4667
4796
  }
4668
- return `${url}?${urlParams.join('&')}`;
4797
+ return { url: `${url}?${urlParams.join('&')}`, flowId };
4798
+ }
4799
+ /**
4800
+ * Appends the reserved flow id parameter to a redirect URL so the callback
4801
+ * can be matched to the verifier stored for its flow. Opt-in via
4802
+ * `experimental.appendPkceFlowIdToRedirects`: redirect URLs are validated
4803
+ * against the project's allow list including the query string, so an extra
4804
+ * parameter can stop exact (non-wildcard) entries from matching.
4805
+ */
4806
+ _maybeAppendFlowIdToRedirect(redirectTo, flowId) {
4807
+ if (!redirectTo || !flowId || !this.experimental.appendPkceFlowIdToRedirects) {
4808
+ return redirectTo !== null && redirectTo !== void 0 ? redirectTo : undefined;
4809
+ }
4810
+ return (0, helpers_1.appendFlowIdToRedirectTo)(redirectTo, flowId);
4811
+ }
4812
+ /**
4813
+ * Generates and stores a PKCE challenge/verifier pair for a new flow,
4814
+ * logging any pending verifier the bounded slot ring evicts.
4815
+ */
4816
+ async _getCodeChallengeAndMethod(isPasswordRecovery = false) {
4817
+ return (0, helpers_1.getCodeChallengeAndMethod)(this.storage, this.storageKey, isPasswordRecovery, (evictedFlowId) => this._debug('#_getCodeChallengeAndMethod()', 'evicted oldest pending PKCE verifier slot', evictedFlowId));
4669
4818
  }
4670
4819
  async _unenroll(params) {
4671
4820
  try {
@@ -4813,7 +4962,7 @@ class GoTrueClient {
4813
4962
  return run();
4814
4963
  }
4815
4964
  /**
4816
- * {@see GoTrueMFAApi#challengeAndVerify}
4965
+ * {@link GoTrueMFAApi#challengeAndVerify}
4817
4966
  */
4818
4967
  async _challengeAndVerify(params) {
4819
4968
  const { data: challengeData, error: challengeError } = await this._challenge({
@@ -4829,7 +4978,7 @@ class GoTrueClient {
4829
4978
  });
4830
4979
  }
4831
4980
  /**
4832
- * {@see GoTrueMFAApi#listFactors}
4981
+ * {@link GoTrueMFAApi#listFactors}
4833
4982
  */
4834
4983
  async _listFactors() {
4835
4984
  var _a;
@@ -4857,7 +5006,7 @@ class GoTrueClient {
4857
5006
  };
4858
5007
  }
4859
5008
  /**
4860
- * {@see GoTrueMFAApi#getAuthenticatorAssuranceLevel}
5009
+ * {@link GoTrueMFAApi#getAuthenticatorAssuranceLevel}
4861
5010
  */
4862
5011
  async _getAuthenticatorAssuranceLevel(jwt) {
4863
5012
  var _a, _b, _c, _d;
@@ -5108,15 +5257,15 @@ class GoTrueClient {
5108
5257
  * Extracts the JWT claims present in the access token by first verifying the
5109
5258
  * JWT against the server's JSON Web Key Set endpoint
5110
5259
  * `/.well-known/jwks.json` which is often cached, resulting in significantly
5111
- * faster responses. Prefer this method over {@link #getUser} which always
5260
+ * faster responses. Prefer this method over {@link GoTrueClient.getUser} which always
5112
5261
  * sends a request to the Auth server for each JWT.
5113
5262
  *
5114
5263
  * If the project is not using an asymmetric JWT signing key (like ECC or
5115
- * RSA) it always sends a request to the Auth server (similar to {@link
5116
- * #getUser}) to verify the JWT.
5264
+ * RSA) it always sends a request to the Auth server (similar to
5265
+ * {@link GoTrueClient.getUser}) to verify the JWT.
5117
5266
  *
5118
5267
  * @param jwt An optional specific JWT you wish to verify, not the one you
5119
- * can obtain from {@link #getSession}.
5268
+ * can obtain from {@link GoTrueClient.getSession}.
5120
5269
  * @param options Various additional options that allow you to customize the
5121
5270
  * behavior of this method.
5122
5271
  *