@stackframe/tanstack-start 2.8.93 → 2.8.94

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 (31) hide show
  1. package/dist/esm/lib/auth.d.ts +3 -1
  2. package/dist/esm/lib/auth.d.ts.map +1 -1
  3. package/dist/esm/lib/auth.js +4 -4
  4. package/dist/esm/lib/auth.js.map +1 -1
  5. package/dist/esm/lib/stack-app/apps/implementations/admin-app-impl.d.ts.map +1 -1
  6. package/dist/esm/lib/stack-app/apps/implementations/client-app-impl.d.ts +30 -3
  7. package/dist/esm/lib/stack-app/apps/implementations/client-app-impl.d.ts.map +1 -1
  8. package/dist/esm/lib/stack-app/apps/implementations/client-app-impl.js +193 -30
  9. package/dist/esm/lib/stack-app/apps/implementations/client-app-impl.js.map +1 -1
  10. package/dist/esm/lib/stack-app/apps/implementations/common.js +1 -1
  11. package/dist/esm/lib/stack-app/url-targets.d.ts.map +1 -1
  12. package/dist/esm/lib/stack-app/url-targets.js +10 -1
  13. package/dist/esm/lib/stack-app/url-targets.js.map +1 -1
  14. package/dist/esm/lib/stack-app/url-targets.test.js +32 -0
  15. package/dist/esm/lib/stack-app/url-targets.test.js.map +1 -1
  16. package/dist/lib/auth.d.ts +3 -1
  17. package/dist/lib/auth.d.ts.map +1 -1
  18. package/dist/lib/auth.js +4 -4
  19. package/dist/lib/auth.js.map +1 -1
  20. package/dist/lib/stack-app/apps/implementations/admin-app-impl.d.ts.map +1 -1
  21. package/dist/lib/stack-app/apps/implementations/client-app-impl.d.ts +30 -3
  22. package/dist/lib/stack-app/apps/implementations/client-app-impl.d.ts.map +1 -1
  23. package/dist/lib/stack-app/apps/implementations/client-app-impl.js +191 -28
  24. package/dist/lib/stack-app/apps/implementations/client-app-impl.js.map +1 -1
  25. package/dist/lib/stack-app/apps/implementations/common.js +1 -1
  26. package/dist/lib/stack-app/url-targets.d.ts.map +1 -1
  27. package/dist/lib/stack-app/url-targets.js +10 -1
  28. package/dist/lib/stack-app/url-targets.js.map +1 -1
  29. package/dist/lib/stack-app/url-targets.test.js +32 -0
  30. package/dist/lib/stack-app/url-targets.test.js.map +1 -1
  31. package/package.json +3 -3
@@ -4,7 +4,7 @@ import React, { useCallback, useMemo } from "react";
4
4
  import { KnownErrors, StackClientInterface } from "@stackframe/stack-shared";
5
5
  import { suspend, suspendIfSsr, use } from "@stackframe/stack-shared/dist/utils/react";
6
6
  import { deepPlainEquals, omit } from "@stackframe/stack-shared/dist/utils/objects";
7
- import { isRelative } from "@stackframe/stack-shared/dist/utils/urls";
7
+ import { createUrlIfValid, isRelative } from "@stackframe/stack-shared/dist/utils/urls";
8
8
  import { Result } from "@stackframe/stack-shared/dist/utils/results";
9
9
  import { deindent, mergeScopeStrings } from "@stackframe/stack-shared/dist/utils/strings";
10
10
  import { isBrowserLike } from "@stackframe/stack-shared/dist/utils/env";
@@ -17,6 +17,7 @@ import { WebAuthnError, startAuthentication, startRegistration } from "@simplewe
17
17
  import { InternalSession } from "@stackframe/stack-shared/dist/sessions";
18
18
  import { parseJson } from "@stackframe/stack-shared/dist/utils/json";
19
19
  import { DependenciesMap } from "@stackframe/stack-shared/dist/utils/maps";
20
+ import { getTrustedParentDomain, validateRedirectUrl } from "@stackframe/stack-shared/dist/utils/redirect-urls";
20
21
  import { Store, storeLock } from "@stackframe/stack-shared/dist/utils/stores";
21
22
  import { BotChallengeExecutionFailedError, BotChallengeUserCancelledError, withBotChallengeFlow } from "@stackframe/stack-shared/dist/utils/turnstile-flow";
22
23
  import { generateUuid } from "@stackframe/stack-shared/dist/utils/uuids";
@@ -24,7 +25,7 @@ import * as TanStackRouter from "@tanstack/react-router";
24
25
  import * as cookie from "cookie";
25
26
  import { constructRedirectUrl } from "../../../../utils/url.js";
26
27
  import { callOAuthCallback, getNewOAuthProviderOrScopeUrl } from "../../../auth.js";
27
- import { createBrowserCookieHelper, createCookieHelper, createPlaceholderCookieHelper, deleteCookie, deleteCookieClient, isSecure, saveVerifierAndState, setOrDeleteCookie, setOrDeleteCookieClient } from "../../../cookie.js";
28
+ import { createBrowserCookieHelper, createCookieHelper, createPlaceholderCookieHelper, deleteCookie, deleteCookieClient, getCookieClient, isSecure, saveVerifierAndState, setOrDeleteCookie, setOrDeleteCookieClient } from "../../../cookie.js";
28
29
  import { envVars } from "../../../env.js";
29
30
  import { apiKeyCreationOptionsToCrud } from "../../api-keys/index.js";
30
31
  import { contactChannelCreateOptionsToCrud, contactChannelUpdateOptionsToCrud } from "../../contact-channels/index.js";
@@ -39,6 +40,24 @@ import { mountDevTool } from "../../../../dev-tool/index.js";
39
40
 
40
41
  //#region src/lib/stack-app/apps/implementations/client-app-impl.ts
41
42
  const prefetchedCrossDomainHandoffTtlMs = 3300 * 1e3;
43
+ const nestedCrossDomainAuthQueryParams = {
44
+ refreshTokenId: "stack_nested_cross_domain_auth_refresh_token_id",
45
+ callbackUrl: "stack_nested_cross_domain_auth_callback_url",
46
+ redirectUri: "redirect_uri",
47
+ state: "state",
48
+ codeChallenge: "code_challenge",
49
+ codeChallengeMethod: "code_challenge_method",
50
+ afterCallbackRedirectUrl: "after_callback_redirect_url"
51
+ };
52
+ const oauthCallbackResponseQueryParams = [
53
+ "code",
54
+ "state",
55
+ "error",
56
+ "error_description",
57
+ "errorCode",
58
+ "message",
59
+ "details"
60
+ ];
42
61
  const allClientApps = /* @__PURE__ */ new Map();
43
62
  const STACK_AUTHORIZATION_VALUE_PREFIX = "stackauth_";
44
63
  function getAuthorizationHeaderValueFromAuthJson(authJson) {
@@ -106,7 +125,7 @@ var _StackClientAppImplIncomplete = class _StackClientAppImplIncomplete {
106
125
  `);
107
126
  const location = await getNewOAuthProviderOrScopeUrl(this._interface, {
108
127
  provider: options.providerId,
109
- redirectUrl: this.urls.oauthCallback,
128
+ redirectUrl: this._getOAuthCallbackRedirectUri(),
110
129
  errorRedirectUrl: this.urls.error,
111
130
  providerScope: mergeScopeStrings(options.scope || "", (this._oauthScopesOnSignIn[options.providerId] ?? []).join(" "))
112
131
  }, options.session);
@@ -241,7 +260,7 @@ var _StackClientAppImplIncomplete = class _StackClientAppImplIncomplete {
241
260
  for (const account of matchingAccounts) if ((await account.getAccessToken({ scopes })).status === "ok") return account;
242
261
  const location = await getNewOAuthProviderOrScopeUrl(this._interface, {
243
262
  provider,
244
- redirectUrl: this.urls.oauthCallback,
263
+ redirectUrl: this._getOAuthCallbackRedirectUri(),
245
264
  errorRedirectUrl: this.urls.error,
246
265
  providerScope: mergeScopeStrings(scopeString, (this._oauthScopesOnSignIn[provider] ?? []).join(" "))
247
266
  }, session);
@@ -345,6 +364,7 @@ var _StackClientAppImplIncomplete = class _StackClientAppImplIncomplete {
345
364
  this._prefetchedCrossDomainHandoffParams = null;
346
365
  this._prefetchedCrossDomainHandoffParamsFetchedAt = 0;
347
366
  this._isPrefetchingCrossDomainHandoffParams = false;
367
+ this._pendingAuthResolutionPromises = [];
348
368
  this._memoryTokenStore = createEmptyTokenStore();
349
369
  this._nextServerCookiesTokenStores = /* @__PURE__ */ new WeakMap();
350
370
  this._requestTokenStores = /* @__PURE__ */ new WeakMap();
@@ -414,6 +434,12 @@ var _StackClientAppImplIncomplete = class _StackClientAppImplIncomplete {
414
434
  });
415
435
  this._eventTracker.start();
416
436
  }
437
+ if (isBrowserLike() && this._isOAuthCallbackUrlHosted() && this._currentUrlLooksLikeStackOAuthCallback()) this._trackPendingAuthResolution(async () => {
438
+ if (isBrowserLike()) await this.callOAuthCallback({ dontWarnAboutMissingQueryParams: true });
439
+ });
440
+ if (isBrowserLike()) this._trackPendingAuthResolution(async () => {
441
+ await this._maybeHandleNestedCrossDomainAuth();
442
+ });
417
443
  if (isBrowserLike() && resolvedOptions.devTool !== false) mountDevTool(this);
418
444
  }
419
445
  _initUniqueIdentifier() {
@@ -421,6 +447,126 @@ var _StackClientAppImplIncomplete = class _StackClientAppImplIncomplete {
421
447
  if (allClientApps.has(this._uniqueIdentifier)) throw new StackAssertionError("A Stack client app with the same unique identifier already exists");
422
448
  allClientApps.set(this._uniqueIdentifier, [this._extraOptions?.checkString ?? void 0, this]);
423
449
  }
450
+ _trackPendingAuthResolution(callback) {
451
+ const promise = (async () => {
452
+ await Promise.resolve();
453
+ try {
454
+ await callback();
455
+ } catch (error) {
456
+ captureError("pending-auth-resolution-failed", error);
457
+ }
458
+ })();
459
+ this._pendingAuthResolutionPromises.push(promise);
460
+ runAsynchronously(async () => {
461
+ try {
462
+ await promise;
463
+ } finally {
464
+ this._pendingAuthResolutionPromises = this._pendingAuthResolutionPromises.filter((p) => p !== promise);
465
+ }
466
+ });
467
+ }
468
+ async _awaitPendingAuthResolutions(overrideTokenStoreInit, options) {
469
+ if (options?.awaitPendingAuthResolutions === false || overrideTokenStoreInit !== void 0 || !this._hasPersistentTokenStore() || this._pendingAuthResolutionPromises.length === 0) return;
470
+ await Promise.all(this._pendingAuthResolutionPromises);
471
+ }
472
+ _usePendingAuthResolutions(overrideTokenStoreInit) {
473
+ if (overrideTokenStoreInit !== void 0 || !this._hasPersistentTokenStore() || this._pendingAuthResolutionPromises.length === 0) return;
474
+ use(Promise.all(this._pendingAuthResolutionPromises));
475
+ }
476
+ _isOAuthCallbackUrlHosted() {
477
+ const oauthCallbackTarget = this._urlOptions.oauthCallback ?? this._urlOptions.default;
478
+ return typeof oauthCallbackTarget !== "string" && oauthCallbackTarget?.type === "hosted";
479
+ }
480
+ _currentUrlLooksLikeOAuthCallback() {
481
+ if (typeof window === "undefined") return false;
482
+ const currentUrl = new URL(window.location.href);
483
+ return currentUrl.searchParams.has("code") && currentUrl.searchParams.has("state") || currentUrl.searchParams.has("errorCode") && currentUrl.searchParams.has("message");
484
+ }
485
+ _currentUrlLooksLikeStackOAuthCallback() {
486
+ if (typeof window === "undefined") return false;
487
+ const currentUrl = new URL(window.location.href);
488
+ const state = currentUrl.searchParams.get("state");
489
+ if (!currentUrl.searchParams.has("code") || state == null) return false;
490
+ return getCookieClient(`stack-oauth-outer-${state}`) != null;
491
+ }
492
+ _getOAuthCallbackRedirectUri() {
493
+ if (!this._isOAuthCallbackUrlHosted()) return this.urls.oauthCallback;
494
+ if (typeof window === "undefined") throw new StackAssertionError("Hosted OAuth callback URLs require a browser environment to use the current URL as the redirect URI");
495
+ const currentUrl = new URL(window.location.href);
496
+ for (const param of oauthCallbackResponseQueryParams) currentUrl.searchParams.delete(param);
497
+ return currentUrl.toString();
498
+ }
499
+ async _getCurrentRefreshTokenIdIfSignedIn(options) {
500
+ const tokens = await (await this._getSession(void 0, options)).getOrFetchLikelyValidTokens(0, null);
501
+ if (tokens?.refreshToken == null) return null;
502
+ return tokens.accessToken.payload.refresh_token_id;
503
+ }
504
+ async _addNestedCrossDomainAuthParamsToRedirectUrl(options) {
505
+ const targetUrl = new URL(options.url, options.currentUrl);
506
+ if (targetUrl.origin === options.currentUrl.origin) return options.url;
507
+ const refreshTokenId = await this._getCurrentRefreshTokenIdIfSignedIn({ awaitPendingAuthResolutions: options.awaitPendingAuthResolutions });
508
+ if (refreshTokenId == null) return options.url;
509
+ targetUrl.searchParams.set(nestedCrossDomainAuthQueryParams.refreshTokenId, refreshTokenId);
510
+ targetUrl.searchParams.set(nestedCrossDomainAuthQueryParams.callbackUrl, new URL(this._getOAuthCallbackRedirectUri(), options.currentUrl).toString());
511
+ return targetUrl.toString();
512
+ }
513
+ async _maybeHandleNestedCrossDomainAuth() {
514
+ if (typeof window === "undefined") return false;
515
+ const currentUrl = new URL(window.location.href);
516
+ if (currentUrl.searchParams.has("code") && currentUrl.searchParams.has("state")) return false;
517
+ const refreshTokenId = currentUrl.searchParams.get(nestedCrossDomainAuthQueryParams.refreshTokenId);
518
+ if (refreshTokenId == null) return false;
519
+ const redirectUri = currentUrl.searchParams.get(nestedCrossDomainAuthQueryParams.redirectUri);
520
+ const state = currentUrl.searchParams.get(nestedCrossDomainAuthQueryParams.state);
521
+ const codeChallenge = currentUrl.searchParams.get(nestedCrossDomainAuthQueryParams.codeChallenge);
522
+ if (redirectUri != null || state != null || codeChallenge != null) {
523
+ if (redirectUri == null || state == null || codeChallenge == null) throw new StackAssertionError("Nested cross-domain auth callback URL is missing OAuth request parameters", {
524
+ redirectUri,
525
+ state,
526
+ codeChallenge
527
+ });
528
+ if ((currentUrl.searchParams.get(nestedCrossDomainAuthQueryParams.codeChallengeMethod) ?? "S256") !== "S256") throw new StackAssertionError("Nested cross-domain auth only supports S256 PKCE");
529
+ if (isRelative(redirectUri)) throw new Error("Nested cross-domain auth redirect URI must be absolute.");
530
+ const redirectUriUrl = new URL(redirectUri);
531
+ if (!await this._isTrusted(redirectUriUrl.toString())) throw new Error(`Nested cross-domain auth redirect URI ${redirectUri} is not trusted.`);
532
+ const afterCallbackRedirectUrlString = currentUrl.searchParams.get(nestedCrossDomainAuthQueryParams.afterCallbackRedirectUrl);
533
+ const afterCallbackRedirectUrl = afterCallbackRedirectUrlString == null ? redirectUriUrl : new URL(afterCallbackRedirectUrlString, redirectUriUrl);
534
+ if (!await this._isTrusted(afterCallbackRedirectUrl.toString())) throw new Error(`Nested cross-domain auth after-callback redirect URL ${afterCallbackRedirectUrlString} is not trusted.`);
535
+ if (await this._getCurrentRefreshTokenIdIfSignedIn({ awaitPendingAuthResolutions: false }) !== refreshTokenId) throw new Error("Nested cross-domain auth source session does not match the requested refresh token ID.");
536
+ await this._redirectTo({
537
+ url: await this._createCrossDomainAuthRedirectUrl({
538
+ redirectUri: redirectUriUrl.toString(),
539
+ state,
540
+ codeChallenge,
541
+ afterCallbackRedirectUrl: afterCallbackRedirectUrl.toString(),
542
+ awaitPendingAuthResolutions: false
543
+ }),
544
+ replace: true
545
+ });
546
+ return true;
547
+ }
548
+ if (await this._getCurrentRefreshTokenIdIfSignedIn({ awaitPendingAuthResolutions: false }) === refreshTokenId) return false;
549
+ const callbackUrlString = currentUrl.searchParams.get(nestedCrossDomainAuthQueryParams.callbackUrl);
550
+ if (callbackUrlString == null) throw new StackAssertionError("Nested cross-domain auth URL is missing callback URL");
551
+ if (isRelative(callbackUrlString)) throw new Error("Nested cross-domain auth callback URL must be absolute.");
552
+ const callbackUrl = new URL(callbackUrlString);
553
+ if (!await this._isTrusted(callbackUrl.toString())) throw new Error(`Nested cross-domain auth callback URL ${callbackUrlString} is not trusted.`);
554
+ const afterCallbackRedirectUrl = new URL(currentUrl);
555
+ afterCallbackRedirectUrl.searchParams.delete(nestedCrossDomainAuthQueryParams.refreshTokenId);
556
+ afterCallbackRedirectUrl.searchParams.delete(nestedCrossDomainAuthQueryParams.callbackUrl);
557
+ const { state: newState, codeChallenge: newCodeChallenge } = await this._getCrossDomainHandoffParamsForRedirect(currentUrl);
558
+ callbackUrl.searchParams.set(nestedCrossDomainAuthQueryParams.refreshTokenId, refreshTokenId);
559
+ callbackUrl.searchParams.set(nestedCrossDomainAuthQueryParams.redirectUri, new URL(this._getOAuthCallbackRedirectUri(), currentUrl).toString());
560
+ callbackUrl.searchParams.set(nestedCrossDomainAuthQueryParams.state, newState);
561
+ callbackUrl.searchParams.set(nestedCrossDomainAuthQueryParams.codeChallenge, newCodeChallenge);
562
+ callbackUrl.searchParams.set(nestedCrossDomainAuthQueryParams.codeChallengeMethod, "S256");
563
+ callbackUrl.searchParams.set(nestedCrossDomainAuthQueryParams.afterCallbackRedirectUrl, afterCallbackRedirectUrl.toString());
564
+ await this._redirectTo({
565
+ url: callbackUrl,
566
+ replace: true
567
+ });
568
+ return true;
569
+ }
424
570
  /**
425
571
  * Cloudflare workers does not allow use of randomness on the global scope (on which the Stack app is probably
426
572
  * initialized). For that reason, we generate the unique identifier lazily when it is first needed instead of in the
@@ -613,15 +759,15 @@ var _StackClientAppImplIncomplete = class _StackClientAppImplIncomplete {
613
759
  await setOrDeleteCookie(this._getRefreshTokenDefaultCookieNameForSecure(isSecure$1), null, cookieOptions);
614
760
  });
615
761
  }
762
+ async _getTrustedRedirectConfig() {
763
+ const project = Result.orThrow(await this._currentProjectCache.getOrWait([], "write-only"));
764
+ return {
765
+ allowLocalhost: project.config.allow_localhost,
766
+ trustedDomains: project.config.domains.map((d) => d.domain)
767
+ };
768
+ }
616
769
  async _getTrustedParentDomain(currentDomain) {
617
- const domains = Result.orThrow(await this._interface.getClientProject()).config.domains.map((d) => d.domain.trim().replace(/^https?:\/\//, "").split("/")[0]?.toLowerCase());
618
- const trustedWildcards = domains.filter((d) => d.startsWith("**."));
619
- const parts = currentDomain.split(".");
620
- for (let i = parts.length - 2; i >= 0; i--) {
621
- const parentDomain = parts.slice(i).join(".");
622
- if (domains.includes(parentDomain) && trustedWildcards.includes("**." + parentDomain)) return parentDomain;
623
- }
624
- return null;
770
+ return getTrustedParentDomain(currentDomain, (await this._getTrustedRedirectConfig()).trustedDomains);
625
771
  }
626
772
  _getBrowserCookieTokenStore() {
627
773
  if (!isBrowserLike()) throw new Error("Cannot use cookie token store on the server!");
@@ -778,11 +924,13 @@ var _StackClientAppImplIncomplete = class _StackClientAppImplIncomplete {
778
924
  sessionsBySessionKey.set(sessionKey, session);
779
925
  return session;
780
926
  }
781
- async _getSession(overrideTokenStoreInit) {
927
+ async _getSession(overrideTokenStoreInit, options) {
928
+ await this._awaitPendingAuthResolutions(overrideTokenStoreInit, options);
782
929
  const tokenStore = this._getOrCreateTokenStore(await this._createCookieHelper(overrideTokenStoreInit), overrideTokenStoreInit);
783
930
  return this._getSessionFromTokenStore(tokenStore);
784
931
  }
785
932
  _useSession(overrideTokenStoreInit) {
933
+ this._usePendingAuthResolutions(overrideTokenStoreInit);
786
934
  const tokenStore = this._useTokenStore(overrideTokenStoreInit);
787
935
  const subscribe = useCallback((cb) => {
788
936
  return subscribeSessionRefresh({
@@ -1238,7 +1386,7 @@ var _StackClientAppImplIncomplete = class _StackClientAppImplIncomplete {
1238
1386
  const scopeString = options?.scopes?.join(" ") ?? "";
1239
1387
  const location = await getNewOAuthProviderOrScopeUrl(app._interface, {
1240
1388
  provider,
1241
- redirectUrl: app.urls.oauthCallback,
1389
+ redirectUrl: app._getOAuthCallbackRedirectUri(),
1242
1390
  errorRedirectUrl: app.urls.error,
1243
1391
  providerScope: mergeScopeStrings(scopeString, (app._oauthScopesOnSignIn[provider] ?? []).join(" "))
1244
1392
  }, session);
@@ -1772,10 +1920,17 @@ var _StackClientAppImplIncomplete = class _StackClientAppImplIncomplete {
1772
1920
  }
1773
1921
  async _isTrusted(url) {
1774
1922
  if (isRelative(url)) return true;
1775
- if (typeof window !== "undefined" && window.location.origin === new URL(url).origin) return true;
1776
- return isHostedHandlerUrlForProject({
1923
+ const parsedUrl = createUrlIfValid(url);
1924
+ if (parsedUrl == null) return false;
1925
+ if (typeof window !== "undefined" && window.location.origin === parsedUrl.origin) return true;
1926
+ if (isHostedHandlerUrlForProject({
1777
1927
  url,
1778
1928
  projectId: this.projectId
1929
+ })) return true;
1930
+ const trustedRedirectConfig = await this._getTrustedRedirectConfig();
1931
+ return validateRedirectUrl(parsedUrl, {
1932
+ allowLocalhost: trustedRedirectConfig.allowLocalhost,
1933
+ trustedDomains: trustedRedirectConfig.trustedDomains
1779
1934
  });
1780
1935
  }
1781
1936
  get urls() {
@@ -1824,6 +1979,7 @@ var _StackClientAppImplIncomplete = class _StackClientAppImplIncomplete {
1824
1979
  };
1825
1980
  }
1826
1981
  _getLocalOAuthCallbackHandlerUrl() {
1982
+ if (this._isOAuthCallbackUrlHosted()) return this._getOAuthCallbackRedirectUri();
1827
1983
  return resolveHandlerUrls({
1828
1984
  urls: {
1829
1985
  ...this._urlOptions,
@@ -1834,7 +1990,7 @@ var _StackClientAppImplIncomplete = class _StackClientAppImplIncomplete {
1834
1990
  }).oauthCallback;
1835
1991
  }
1836
1992
  async _createCrossDomainAuthRedirectUrl(options) {
1837
- const session = await this._getSession();
1993
+ const session = await this._getSession(void 0, { awaitPendingAuthResolutions: options.awaitPendingAuthResolutions });
1838
1994
  const response = await this._interface.sendClientRequest("/auth/oauth/cross-domain/authorize", {
1839
1995
  method: "POST",
1840
1996
  headers: { "Content-Type": "application/json" },
@@ -1888,7 +2044,7 @@ var _StackClientAppImplIncomplete = class _StackClientAppImplIncomplete {
1888
2044
  ...options
1889
2045
  });
1890
2046
  }
1891
- async _redirectToHandler(handlerName, options) {
2047
+ async _redirectToHandler(handlerName, options, internalOptions) {
1892
2048
  const rawHandlerUrl = getUrls(this._urlOptions, { projectId: this.projectId })[handlerName];
1893
2049
  if (!rawHandlerUrl) throw new Error(`No URL for handler name ${handlerName}`);
1894
2050
  const currentUrl = typeof window === "undefined" ? null : new URL(window.location.href);
@@ -1905,7 +2061,8 @@ var _StackClientAppImplIncomplete = class _StackClientAppImplIncomplete {
1905
2061
  redirectUri: plan.redirectUri,
1906
2062
  state: plan.state,
1907
2063
  codeChallenge: plan.codeChallenge,
1908
- afterCallbackRedirectUrl: plan.afterCallbackRedirectUrl
2064
+ afterCallbackRedirectUrl: plan.afterCallbackRedirectUrl,
2065
+ awaitPendingAuthResolutions: internalOptions?.awaitPendingAuthResolutions
1909
2066
  });
1910
2067
  await this._redirectTo({
1911
2068
  url: crossDomainRedirectUrl,
@@ -1913,7 +2070,12 @@ var _StackClientAppImplIncomplete = class _StackClientAppImplIncomplete {
1913
2070
  });
1914
2071
  return;
1915
2072
  }
1916
- await this._redirectIfTrusted(plan.url, options);
2073
+ const redirectUrl = currentUrl != null && handlerName !== "signOut" && handlerName !== "afterSignOut" && handlerName !== "oauthCallback" ? await this._addNestedCrossDomainAuthParamsToRedirectUrl({
2074
+ url: plan.url,
2075
+ currentUrl,
2076
+ awaitPendingAuthResolutions: internalOptions?.awaitPendingAuthResolutions
2077
+ }) : plan.url;
2078
+ await this._redirectIfTrusted(redirectUrl, options);
1917
2079
  }
1918
2080
  _redirectToHandlerDuringRender(handlerName, options) {
1919
2081
  if (this._redirectMethod === "tanstack-start" && !isBrowserLike()) {
@@ -2170,7 +2332,7 @@ var _StackClientAppImplIncomplete = class _StackClientAppImplIncomplete {
2170
2332
  const executeOAuth = async (challenge) => {
2171
2333
  return await this._interface.authorizeOAuth({
2172
2334
  provider,
2173
- redirectUrl: constructRedirectUrl(this.urls.oauthCallback, "redirectUrl"),
2335
+ redirectUrl: constructRedirectUrl(this._getOAuthCallbackRedirectUri(), "redirectUrl"),
2174
2336
  errorRedirectUrl: constructRedirectUrl(this.urls.error, "errorRedirectUrl"),
2175
2337
  afterCallbackRedirectUrl,
2176
2338
  type: "authenticate",
@@ -2218,7 +2380,7 @@ var _StackClientAppImplIncomplete = class _StackClientAppImplIncomplete {
2218
2380
  try {
2219
2381
  return await callback();
2220
2382
  } catch (e) {
2221
- if (KnownErrors.MultiFactorAuthenticationRequired.isInstance(e)) return Result.ok(await this._experimentalMfa(e, await this._getSession()));
2383
+ if (KnownErrors.MultiFactorAuthenticationRequired.isInstance(e)) return Result.ok(await this._experimentalMfa(e, await this._getSession(void 0, { awaitPendingAuthResolutions: false })));
2222
2384
  throw e;
2223
2385
  }
2224
2386
  }
@@ -2293,8 +2455,8 @@ var _StackClientAppImplIncomplete = class _StackClientAppImplIncomplete {
2293
2455
  }
2294
2456
  if (result.status === "ok") {
2295
2457
  await this._signInToAccountWithTokens(result.data);
2296
- if (!options?.noRedirect) if (result.data.newUser) await this.redirectToAfterSignUp({ replace: true });
2297
- else await this.redirectToAfterSignIn({ replace: true });
2458
+ if (!options?.noRedirect) if (result.data.newUser) await this._redirectToHandler("afterSignUp", { replace: true }, { awaitPendingAuthResolutions: false });
2459
+ else await this._redirectToHandler("afterSignIn", { replace: true }, { awaitPendingAuthResolutions: false });
2298
2460
  return Result.ok(void 0);
2299
2461
  } else return Result.error(result.error);
2300
2462
  }
@@ -2409,10 +2571,10 @@ var _StackClientAppImplIncomplete = class _StackClientAppImplIncomplete {
2409
2571
  return Result.ok(void 0);
2410
2572
  } else return Result.error(result.error);
2411
2573
  }
2412
- async callOAuthCallback() {
2574
+ async callOAuthCallback(options) {
2413
2575
  if (typeof window === "undefined") throw new Error("callOAuthCallback can currently only be called in a browser environment");
2414
- this._ensurePersistentTokenStore();
2415
- let oauthCallbackRedirectUri = this.urls.oauthCallback;
2576
+ if (this._currentUrlLooksLikeOAuthCallback()) this._ensurePersistentTokenStore();
2577
+ let oauthCallbackRedirectUri = this._getOAuthCallbackRedirectUri();
2416
2578
  const currentUrl = new URL(window.location.href);
2417
2579
  if (currentUrl.searchParams.get(crossDomainAuthQueryParams.marker) === "1") {
2418
2580
  currentUrl.searchParams.delete("code");
@@ -2422,7 +2584,7 @@ var _StackClientAppImplIncomplete = class _StackClientAppImplIncomplete {
2422
2584
  let result;
2423
2585
  try {
2424
2586
  result = await this._catchMfaRequiredError(async () => {
2425
- return await callOAuthCallback(this._interface, oauthCallbackRedirectUri);
2587
+ return await callOAuthCallback(this._interface, oauthCallbackRedirectUri, options);
2426
2588
  });
2427
2589
  } catch (e) {
2428
2590
  if (KnownErrors.InvalidTotpCode.isInstance(e)) {
@@ -2431,6 +2593,7 @@ var _StackClientAppImplIncomplete = class _StackClientAppImplIncomplete {
2431
2593
  } else throw e;
2432
2594
  }
2433
2595
  if (result.status === "ok" && result.data) {
2596
+ this._ensurePersistentTokenStore();
2434
2597
  await this._signInToAccountWithTokens(result.data);
2435
2598
  if ("afterCallbackRedirectUrl" in result.data && result.data.afterCallbackRedirectUrl) {
2436
2599
  await this._redirectTo({
@@ -2439,10 +2602,10 @@ var _StackClientAppImplIncomplete = class _StackClientAppImplIncomplete {
2439
2602
  });
2440
2603
  return true;
2441
2604
  } else if (result.data.newUser) {
2442
- await this.redirectToAfterSignUp({ replace: true });
2605
+ await this._redirectToHandler("afterSignUp", { replace: true }, { awaitPendingAuthResolutions: false });
2443
2606
  return true;
2444
2607
  } else {
2445
- await this.redirectToAfterSignIn({ replace: true });
2608
+ await this._redirectToHandler("afterSignIn", { replace: true }, { awaitPendingAuthResolutions: false });
2446
2609
  return true;
2447
2610
  }
2448
2611
  }