@hexclave/next 1.0.49 → 1.0.52

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 (164) hide show
  1. package/dist/{admin-app-impl-Cmhx-Gnk.d.ts → admin-app-impl-Bga0o6cL.d.ts} +5 -2
  2. package/dist/{admin-app-impl-Cmhx-Gnk.d.ts.map → admin-app-impl-Bga0o6cL.d.ts.map} +1 -1
  3. package/dist/clickmap/clickmap-core.d.ts +1 -1
  4. package/dist/clickmap/index.d.ts +1 -1
  5. package/dist/{client-app-Majjw8Xp.d.ts → client-app-B0kBC67Z.d.ts} +2 -1
  6. package/dist/{client-app-Majjw8Xp.d.ts.map → client-app-B0kBC67Z.d.ts.map} +1 -1
  7. package/dist/common-BK_sydxF.d.ts.map +1 -1
  8. package/dist/components/selected-team-switcher.d.ts +1 -1
  9. package/dist/components/team-icon.d.ts +1 -1
  10. package/dist/components/team-switcher.d.ts +1 -1
  11. package/dist/components-page/account-settings/active-sessions/active-sessions-page.js +1 -1
  12. package/dist/components-page/account-settings/api-keys/api-keys-page.js +2 -2
  13. package/dist/components-page/account-settings/payments/payments-page.d.ts +1 -1
  14. package/dist/components-page/account-settings/teams/leave-team-section.d.ts +1 -1
  15. package/dist/components-page/account-settings/teams/team-api-keys-section.d.ts +1 -1
  16. package/dist/components-page/account-settings/teams/team-api-keys-section.js +2 -2
  17. package/dist/components-page/account-settings/teams/team-display-name-section.d.ts +1 -1
  18. package/dist/components-page/account-settings/teams/team-member-invitation-section.d.ts +1 -1
  19. package/dist/components-page/account-settings/teams/team-member-list-section.d.ts +1 -1
  20. package/dist/components-page/account-settings/teams/team-page.d.ts +1 -1
  21. package/dist/components-page/account-settings/teams/team-profile-image-section.d.ts +1 -1
  22. package/dist/components-page/account-settings/teams/team-profile-user-section.d.ts +1 -1
  23. package/dist/components-page/hexclave-handler-client.d.ts +1 -1
  24. package/dist/components-page/hexclave-handler.d.ts +1 -1
  25. package/dist/components-page/sign-out.js +11 -2
  26. package/dist/components-page/sign-out.js.map +1 -1
  27. package/dist/components-page/sign-out.test.d.ts +1 -0
  28. package/dist/components-page/sign-out.test.js +157 -0
  29. package/dist/components-page/sign-out.test.js.map +1 -0
  30. package/dist/dev-tool/dev-tool-core.d.ts +1 -1
  31. package/dist/dev-tool/dev-tool-core.d.ts.map +1 -1
  32. package/dist/dev-tool/dev-tool-core.js +58 -28
  33. package/dist/dev-tool/dev-tool-core.js.map +1 -1
  34. package/dist/dev-tool/dev-tool-styles.js +35 -0
  35. package/dist/dev-tool/dev-tool-styles.js.map +1 -1
  36. package/dist/dev-tool/index.d.ts +1 -1
  37. package/dist/esm/components-page/account-settings/active-sessions/active-sessions-page.js +1 -1
  38. package/dist/esm/components-page/account-settings/api-keys/api-keys-page.js +2 -2
  39. package/dist/esm/components-page/account-settings/teams/team-api-keys-section.js +2 -2
  40. package/dist/esm/components-page/sign-out.d.ts.map +1 -1
  41. package/dist/esm/components-page/sign-out.js +12 -3
  42. package/dist/esm/components-page/sign-out.js.map +1 -1
  43. package/dist/esm/components-page/sign-out.test.d.ts +1 -0
  44. package/dist/esm/components-page/sign-out.test.js +156 -0
  45. package/dist/esm/components-page/sign-out.test.js.map +1 -0
  46. package/dist/esm/dev-tool/dev-tool-core.d.ts.map +1 -1
  47. package/dist/esm/dev-tool/dev-tool-core.js +58 -28
  48. package/dist/esm/dev-tool/dev-tool-core.js.map +1 -1
  49. package/dist/esm/dev-tool/dev-tool-styles.js +35 -0
  50. package/dist/esm/dev-tool/dev-tool-styles.js.map +1 -1
  51. package/dist/esm/generated/global-css.d.ts +1 -1
  52. package/dist/esm/generated/global-css.js +1 -1
  53. package/dist/esm/generated/global-css.js.map +1 -1
  54. package/dist/esm/lib/hexclave-app/apps/implementations/admin-app-impl.d.ts +1 -0
  55. package/dist/esm/lib/hexclave-app/apps/implementations/admin-app-impl.d.ts.map +1 -1
  56. package/dist/esm/lib/hexclave-app/apps/implementations/client-app-impl.cross-domain.test.js +97 -0
  57. package/dist/esm/lib/hexclave-app/apps/implementations/client-app-impl.cross-domain.test.js.map +1 -1
  58. package/dist/esm/lib/hexclave-app/apps/implementations/client-app-impl.d.ts +1 -0
  59. package/dist/esm/lib/hexclave-app/apps/implementations/client-app-impl.d.ts.map +1 -1
  60. package/dist/esm/lib/hexclave-app/apps/implementations/client-app-impl.js +26 -1
  61. package/dist/esm/lib/hexclave-app/apps/implementations/client-app-impl.js.map +1 -1
  62. package/dist/esm/lib/hexclave-app/apps/implementations/common.d.ts.map +1 -1
  63. package/dist/esm/lib/hexclave-app/apps/implementations/common.js +21 -4
  64. package/dist/esm/lib/hexclave-app/apps/implementations/common.js.map +1 -1
  65. package/dist/esm/lib/hexclave-app/apps/implementations/redirect-back-state.d.ts +37 -0
  66. package/dist/esm/lib/hexclave-app/apps/implementations/redirect-back-state.d.ts.map +1 -0
  67. package/dist/esm/lib/hexclave-app/apps/implementations/redirect-back-state.js +89 -0
  68. package/dist/esm/lib/hexclave-app/apps/implementations/redirect-back-state.js.map +1 -0
  69. package/dist/esm/lib/hexclave-app/apps/implementations/redirect-back-state.test.d.ts +1 -0
  70. package/dist/esm/lib/hexclave-app/apps/implementations/redirect-back-state.test.js +134 -0
  71. package/dist/esm/lib/hexclave-app/apps/implementations/redirect-back-state.test.js.map +1 -0
  72. package/dist/esm/lib/hexclave-app/apps/implementations/redirect-loop-breaker.d.ts +8 -0
  73. package/dist/esm/lib/hexclave-app/apps/implementations/redirect-loop-breaker.d.ts.map +1 -0
  74. package/dist/esm/lib/hexclave-app/apps/implementations/redirect-loop-breaker.js +73 -0
  75. package/dist/esm/lib/hexclave-app/apps/implementations/redirect-loop-breaker.js.map +1 -0
  76. package/dist/esm/lib/hexclave-app/apps/implementations/redirect-loop-breaker.test.d.ts +1 -0
  77. package/dist/esm/lib/hexclave-app/apps/implementations/redirect-loop-breaker.test.js +92 -0
  78. package/dist/esm/lib/hexclave-app/apps/implementations/redirect-loop-breaker.test.js.map +1 -0
  79. package/dist/esm/lib/hexclave-app/apps/implementations/server-app-impl.d.ts +1 -0
  80. package/dist/esm/lib/hexclave-app/apps/implementations/server-app-impl.d.ts.map +1 -1
  81. package/dist/esm/lib/hexclave-app/apps/implementations/server-app-impl.js +27 -6
  82. package/dist/esm/lib/hexclave-app/apps/implementations/server-app-impl.js.map +1 -1
  83. package/dist/esm/lib/hexclave-app/apps/interfaces/client-app.d.ts +1 -0
  84. package/dist/esm/lib/hexclave-app/apps/interfaces/client-app.d.ts.map +1 -1
  85. package/dist/esm/lib/hexclave-app/apps/interfaces/client-app.js.map +1 -1
  86. package/dist/esm/pushed-config-error-overlay/index.js +1 -1
  87. package/dist/esm/pushed-config-error-overlay/index.js.map +1 -1
  88. package/dist/esm/utils/session-storage.d.ts +12 -0
  89. package/dist/esm/utils/session-storage.d.ts.map +1 -0
  90. package/dist/esm/utils/session-storage.js +30 -0
  91. package/dist/esm/utils/session-storage.js.map +1 -0
  92. package/dist/generated/global-css.d.ts +1 -1
  93. package/dist/generated/global-css.js +1 -1
  94. package/dist/generated/global-css.js.map +1 -1
  95. package/dist/{hexclave-provider-IAd7nlbi.d.ts → hexclave-provider-BQ9wrtBt.d.ts} +2 -2
  96. package/dist/{hexclave-provider-IAd7nlbi.d.ts.map → hexclave-provider-BQ9wrtBt.d.ts.map} +1 -1
  97. package/dist/{hooks-BKXOF6jh.d.ts → hooks-ykQ7Wsco.d.ts} +2 -2
  98. package/dist/{hooks-BKXOF6jh.d.ts.map → hooks-ykQ7Wsco.d.ts.map} +1 -1
  99. package/dist/{index-Ck1qKoin.d.ts → index-DwE1cNLH.d.ts} +2 -2
  100. package/dist/{index-Ck1qKoin.d.ts.map → index-DwE1cNLH.d.ts.map} +1 -1
  101. package/dist/index.d.ts +4 -4
  102. package/dist/lib/hexclave-app/apps/implementations/admin-app-impl.d.ts +1 -1
  103. package/dist/lib/hexclave-app/apps/implementations/client-app-impl.cross-domain.test.js +97 -0
  104. package/dist/lib/hexclave-app/apps/implementations/client-app-impl.cross-domain.test.js.map +1 -1
  105. package/dist/lib/hexclave-app/apps/implementations/client-app-impl.d.ts +1 -1
  106. package/dist/lib/hexclave-app/apps/implementations/client-app-impl.js +26 -1
  107. package/dist/lib/hexclave-app/apps/implementations/client-app-impl.js.map +1 -1
  108. package/dist/lib/hexclave-app/apps/implementations/common.js +21 -4
  109. package/dist/lib/hexclave-app/apps/implementations/common.js.map +1 -1
  110. package/dist/lib/hexclave-app/apps/implementations/index.d.ts +1 -1
  111. package/dist/lib/hexclave-app/apps/implementations/redirect-back-state.d.ts +37 -0
  112. package/dist/lib/hexclave-app/apps/implementations/redirect-back-state.d.ts.map +1 -0
  113. package/dist/lib/hexclave-app/apps/implementations/redirect-back-state.js +92 -0
  114. package/dist/lib/hexclave-app/apps/implementations/redirect-back-state.js.map +1 -0
  115. package/dist/lib/hexclave-app/apps/implementations/redirect-back-state.test.d.ts +1 -0
  116. package/dist/lib/hexclave-app/apps/implementations/redirect-back-state.test.js +133 -0
  117. package/dist/lib/hexclave-app/apps/implementations/redirect-back-state.test.js.map +1 -0
  118. package/dist/lib/hexclave-app/apps/implementations/redirect-loop-breaker.d.ts +8 -0
  119. package/dist/lib/hexclave-app/apps/implementations/redirect-loop-breaker.d.ts.map +1 -0
  120. package/dist/lib/hexclave-app/apps/implementations/redirect-loop-breaker.js +74 -0
  121. package/dist/lib/hexclave-app/apps/implementations/redirect-loop-breaker.js.map +1 -0
  122. package/dist/lib/hexclave-app/apps/implementations/redirect-loop-breaker.test.d.ts +1 -0
  123. package/dist/lib/hexclave-app/apps/implementations/redirect-loop-breaker.test.js +91 -0
  124. package/dist/lib/hexclave-app/apps/implementations/redirect-loop-breaker.test.js.map +1 -0
  125. package/dist/lib/hexclave-app/apps/implementations/server-app-impl.d.ts +1 -1
  126. package/dist/lib/hexclave-app/apps/implementations/server-app-impl.js +27 -6
  127. package/dist/lib/hexclave-app/apps/implementations/server-app-impl.js.map +1 -1
  128. package/dist/lib/hexclave-app/apps/index.d.ts +1 -1
  129. package/dist/lib/hexclave-app/apps/interfaces/admin-app.d.ts +1 -1
  130. package/dist/lib/hexclave-app/apps/interfaces/client-app.d.ts +1 -1
  131. package/dist/lib/hexclave-app/apps/interfaces/client-app.js.map +1 -1
  132. package/dist/lib/hexclave-app/apps/interfaces/server-app.d.ts +1 -1
  133. package/dist/lib/hexclave-app/index.d.ts +1 -1
  134. package/dist/lib/hexclave-app/projects/index.d.ts +1 -1
  135. package/dist/lib/hexclave-app/teams/index.d.ts +1 -1
  136. package/dist/lib/hexclave-app/users/index.d.ts +1 -1
  137. package/dist/lib/hooks.d.ts +1 -1
  138. package/dist/providers/hexclave-context.d.ts +1 -1
  139. package/dist/providers/hexclave-provider-client.d.ts +1 -1
  140. package/dist/providers/hexclave-provider.d.ts +1 -1
  141. package/dist/pushed-config-error-overlay/index.d.ts +1 -1
  142. package/dist/pushed-config-error-overlay/index.js +1 -1
  143. package/dist/pushed-config-error-overlay/index.js.map +1 -1
  144. package/dist/sign-out-DBnSkfDz.d.ts.map +1 -1
  145. package/dist/utils/session-storage.d.ts +12 -0
  146. package/dist/utils/session-storage.d.ts.map +1 -0
  147. package/dist/utils/session-storage.js +33 -0
  148. package/dist/utils/session-storage.js.map +1 -0
  149. package/package.json +4 -4
  150. package/src/components-page/sign-out.test.tsx +190 -0
  151. package/src/components-page/sign-out.tsx +22 -3
  152. package/src/dev-tool/dev-tool-core.ts +100 -35
  153. package/src/dev-tool/dev-tool-styles.ts +35 -0
  154. package/src/lib/hexclave-app/apps/implementations/client-app-impl.cross-domain.test.ts +127 -0
  155. package/src/lib/hexclave-app/apps/implementations/client-app-impl.ts +34 -1
  156. package/src/lib/hexclave-app/apps/implementations/common.ts +18 -1
  157. package/src/lib/hexclave-app/apps/implementations/redirect-back-state.test.ts +134 -0
  158. package/src/lib/hexclave-app/apps/implementations/redirect-back-state.ts +140 -0
  159. package/src/lib/hexclave-app/apps/implementations/redirect-loop-breaker.test.ts +127 -0
  160. package/src/lib/hexclave-app/apps/implementations/redirect-loop-breaker.ts +117 -0
  161. package/src/lib/hexclave-app/apps/implementations/server-app-impl.ts +31 -5
  162. package/src/lib/hexclave-app/apps/interfaces/client-app.ts +1 -0
  163. package/src/pushed-config-error-overlay/index.ts +1 -1
  164. package/src/utils/session-storage.ts +45 -0
@@ -1,6 +1,7 @@
1
1
  Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
2
2
  const require_rolldown_runtime = require("../../../../rolldown-runtime-D6vf50IK.js");
3
3
  let _hexclave_shared_dist_utils_promises = require("@hexclave/shared/dist/utils/promises");
4
+ let _hexclave_shared_dist_utils_strings = require("@hexclave/shared/dist/utils/strings");
4
5
  let _hexclave_shared_dist_utils_errors = require("@hexclave/shared/dist/utils/errors");
5
6
  let react = require("react");
6
7
  react = require_rolldown_runtime.__toESM(react);
@@ -16,8 +17,8 @@ let _hexclave_shared_dist_utils_stores = require("@hexclave/shared/dist/utils/st
16
17
  let ____________generated_env_js = require("../../../../generated/env.js");
17
18
  let ______url_targets_js = require("../../url-targets.js");
18
19
  //#region src/lib/hexclave-app/apps/implementations/common.ts
19
- const clientVersion = "js @hexclave/next@1.0.49";
20
- if ("js @hexclave/next@1.0.49".startsWith("STACK_COMPILE_TIME"));
20
+ const clientVersion = "js @hexclave/next@1.0.52";
21
+ if ("js @hexclave/next@1.0.52".startsWith("STACK_COMPILE_TIME"));
21
22
  const replaceHexclavePortPrefix = (input) => {
22
23
  if (!input) return input;
23
24
  const prefix = ____________generated_env_js.envVars.HEXCLAVE_PORT_PREFIX;
@@ -33,9 +34,25 @@ const showMissingConfigAlertInBrowser = (message) => {
33
34
  if (typeof alertFn === "function") alertFn(message);
34
35
  };
35
36
  const throwMissingProjectIdError = () => {
36
- const message = "Welcome to Hexclave! It seems that you haven't provided a project ID. Please create a project on the Hexclave dashboard at https://app.hexclave.com and put it in the HEXCLAVE_PROJECT_ID environment variable.";
37
+ const message = _hexclave_shared_dist_utils_strings.deindent`
38
+ Welcome to Hexclave!
39
+
40
+ It seems that you haven't provided a project ID.
41
+
42
+ ${____________generated_env_js.envVars.NODE_ENV?.includes("dev") ? _hexclave_shared_dist_utils_strings.deindent`
43
+ You are running this app in development mode. Make sure that your start command is wrapped in a call to the Hexclave CLI: \`npx @hexclave/cli dev --config-file <file> -- <command>\`
44
+
45
+ Alternatively, you can also connect to a Cloud project's environment variables instead by creating a project on the Hexclave dashboard at https://app.hexclave.com and put its information in the HEXCLAVE_PROJECT_ID environment variable.
46
+ ` : ____________generated_env_js.envVars.NODE_ENV?.includes("prod") ? _hexclave_shared_dist_utils_strings.deindent`
47
+ It seems you are running a project in production. Please create a project on the Hexclave dashboard at https://app.hexclave.com and put it in the HEXCLAVE_PROJECT_ID environment variable.
48
+ ` : _hexclave_shared_dist_utils_strings.deindent`
49
+ - If you are running a development project and would like to connect the local Hexclave dashboard, wrap your start command in: \`npx @hexclave/cli dev --config-file <file> -- <command>\`
50
+
51
+ - If you are running a project in production and would like to connect to Hexclave Cloud, create a project on the Hexclave dashboard at https://app.hexclave.com and put its information in the HEXCLAVE_PROJECT_ID environment variable.
52
+ `}
53
+ `;
37
54
  showMissingConfigAlertInBrowser(message);
38
- return (0, _hexclave_shared_dist_utils_errors.throwErr)(/* @__PURE__ */ new Error(message));
55
+ return (0, _hexclave_shared_dist_utils_errors.throwErr)(new Error(message));
39
56
  };
40
57
  const createCache = (fetcher) => {
41
58
  return new _hexclave_shared_dist_utils_caches.AsyncCache(async (dependencies) => await _hexclave_shared_dist_utils_results.Result.fromThrowingAsync(async () => await fetcher(dependencies)), {});
@@ -1 +1 @@
1
- {"version":3,"file":"common.js","names":["HexclaveAssertionError","envVars","AsyncCache","Result","hexclaveAppInternalsSymbol","Store","React"],"sources":["../../../../../src/lib/hexclave-app/apps/implementations/common.ts"],"sourcesContent":["\n//===========================================\n// THIS FILE IS AUTO-GENERATED FROM TEMPLATE. DO NOT EDIT IT DIRECTLY UNLESS YOU ALSO EDIT THE CORRESPONDING FILE IN packages/template\n//===========================================\nimport { InternalSession } from \"@hexclave/shared/dist/sessions\";\nimport { AsyncCache } from \"@hexclave/shared/dist/utils/caches\";\nimport { isBrowserLike } from \"@hexclave/shared/dist/utils/env\";\nimport { HexclaveAssertionError, captureError, concatStacktraces, throwErr } from \"@hexclave/shared/dist/utils/errors\";\nimport { createGlobal, getGlobal } from \"@hexclave/shared/dist/utils/globals\";\nimport { filterUndefined, omit } from \"@hexclave/shared/dist/utils/objects\";\nimport { ReactPromise, runAsynchronously } from \"@hexclave/shared/dist/utils/promises\";\nimport { suspendIfSsr, use } from \"@hexclave/shared/dist/utils/react\";\nimport { Result } from \"@hexclave/shared/dist/utils/results\";\nimport { Store } from \"@hexclave/shared/dist/utils/stores\";\nimport { getDefaultApiUrls } from \"@hexclave/shared/dist/utils/urls\";\nimport React, { useCallback } from \"react\"; // THIS_LINE_PLATFORM react-like\nimport { envVars } from \"../../../../generated/env\";\nimport { HandlerUrlOptions, ResolvedHandlerUrls, hexclaveAppInternalsSymbol } from \"../../common\";\nimport { resolveHandlerUrls } from \"../../url-targets\";\n\nexport const clientVersion = \"STACK_COMPILE_TIME_CLIENT_PACKAGE_VERSION_SENTINEL\";\nif (clientVersion.startsWith(\"STACK_COMPILE_TIME\")) {\n throw new HexclaveAssertionError(\"Client version was not replaced. Something went wrong during build!\");\n}\n\nconst replaceHexclavePortPrefix = <T extends string | undefined>(input: T): T => {\n if (!input) return input;\n const prefix = envVars.HEXCLAVE_PORT_PREFIX;\n return prefix ? input.replace(/\\$\\{NEXT_PUBLIC_HEXCLAVE_PORT_PREFIX:-81\\}/g, prefix) as T : input;\n};\n\nconst showMissingConfigAlertInBrowser = (message: string) => {\n if (!isBrowserLike()) return;\n\n const global = globalThis as any;\n const alertAlreadyShownKey = \"__hexclave_missing_config_alert_already_shown\";\n if (global[alertAlreadyShownKey]) return;\n global[alertAlreadyShownKey] = true;\n\n const alertFn = global.alert;\n if (typeof alertFn === \"function\") {\n alertFn(message);\n }\n};\n\nconst throwMissingProjectIdError = (): never => {\n const message = \"Welcome to Hexclave! It seems that you haven't provided a project ID. Please create a project on the Hexclave dashboard at https://app.hexclave.com and put it in the HEXCLAVE_PROJECT_ID environment variable.\";\n showMissingConfigAlertInBrowser(message);\n return throwErr(new Error(message));\n};\n\n\nexport const createCache = <D extends any[], T>(fetcher: (dependencies: D) => Promise<T>) => {\n return new AsyncCache<D, Result<T>>(\n async (dependencies) => await Result.fromThrowingAsync(async () => await fetcher(dependencies)),\n {},\n );\n};\n\nexport const createCacheBySession = <D extends any[], T>(fetcher: (session: InternalSession, extraDependencies: D) => Promise<T> ) => {\n return new AsyncCache<[InternalSession, ...D], Result<T>>(\n async ([session, ...extraDependencies]) => await Result.fromThrowingAsync(async () => await fetcher(session, extraDependencies)),\n {\n onSubscribe: ([session], refresh) => {\n const handler = session.onInvalidate(() => refresh());\n return () => handler.unsubscribe();\n },\n },\n );\n};\n\n\ntype AppLike = { [hexclaveAppInternalsSymbol]: { getConstructorOptions: () => any } };\nexport function resolveConstructorOptions<T extends { inheritsFrom?: AppLike }>(options: T): T & { inheritsFrom?: undefined } {\n return {\n ...options.inheritsFrom?.[hexclaveAppInternalsSymbol].getConstructorOptions() ?? {},\n ...filterUndefined(omit(options, [\"inheritsFrom\"])),\n };\n}\n\nexport function getUrls(partial: HandlerUrlOptions, options: { projectId: string }): ResolvedHandlerUrls {\n return resolveHandlerUrls({\n urls: partial,\n projectId: options.projectId,\n });\n}\n\nexport function getDefaultProjectId() {\n return envVars.HEXCLAVE_PROJECT_ID || throwMissingProjectIdError();\n}\n\nexport function getDefaultPublishableClientKey() {\n return envVars.HEXCLAVE_PUBLISHABLE_CLIENT_KEY;\n}\n\nexport function getDefaultSecretServerKey() {\n return envVars.HEXCLAVE_SECRET_SERVER_KEY || throwErr(new Error(\"No secret server key provided. Please copy your key from the Hexclave dashboard and put it in the HEXCLAVE_SECRET_SERVER_KEY environment variable.\"));\n}\n\nexport function getDefaultSuperSecretAdminKey() {\n return envVars.HEXCLAVE_SUPER_SECRET_ADMIN_KEY || throwErr(new Error(\"No super secret admin key provided. Please copy your key from the Hexclave dashboard and put it in the HEXCLAVE_SUPER_SECRET_ADMIN_KEY environment variable.\"));\n}\n\nexport function getDefaultExtraRequestHeaders() {\n return JSON.parse(envVars.HEXCLAVE_EXTRA_REQUEST_HEADERS || '{}');\n}\n\n/**\n * Returns the base URL for the Stack API.\n *\n * The URL can be specified in several ways, in order of precedence:\n * 1. Directly through userSpecifiedBaseUrl parameter as string or browser/server object\n * 2. Through environment variables:\n * - Browser: NEXT_PUBLIC_HEXCLAVE_API_URL_BROWSER/VITE_HEXCLAVE_API_URL_BROWSER\n * - Server: HEXCLAVE_API_URL_SERVER\n * - Default: HEXCLAVE_API_URL\n * 3. Default base URL if none of the above are specified\n *\n * The function also ensures the URL doesn't end with a trailing slash\n * by removing it if present.\n *\n * @param userSpecifiedBaseUrl - Optional URL override as string or {browser, server} object\n * @returns The configured base URL without trailing slash\n\n */\nexport function getBaseUrl(userSpecifiedBaseUrl: string | { browser: string, server: string } | undefined) {\n let url;\n if (userSpecifiedBaseUrl) {\n if (typeof userSpecifiedBaseUrl === \"string\") {\n url = userSpecifiedBaseUrl;\n } else {\n if (isBrowserLike()) {\n url = userSpecifiedBaseUrl.browser;\n } else {\n url = userSpecifiedBaseUrl.server;\n }\n }\n } else {\n if (isBrowserLike()) {\n url = envVars.HEXCLAVE_API_URL_BROWSER;\n } else {\n url = envVars.HEXCLAVE_API_URL_SERVER;\n }\n url = url || envVars.HEXCLAVE_API_URL || defaultBaseUrl;\n }\n\n return replaceHexclavePortPrefix(url.endsWith('/') ? url.slice(0, -1) : url);\n}\n\nexport const defaultBaseUrl = \"https://api.hexclave.com\";\nexport const defaultAnalyticsBaseUrl = \"https://r.hexclave.com\";\n\nconst analyticsBaseUrlsByApiBaseUrl = new Map<string, string>([\n [defaultBaseUrl, defaultAnalyticsBaseUrl],\n [\"https://api.stack-auth.com\", \"https://r.stack-auth.com\"],\n]);\n\nexport function getAnalyticsBaseUrl(regularBaseUrl: string): string {\n return analyticsBaseUrlsByApiBaseUrl.get(regularBaseUrl) ?? regularBaseUrl;\n}\n\n\nfunction fetchBackendUrlsInBackground(primaryBaseUrl: string): void {\n createGlobal('__stack-fetch-backend-urls-started', () => {\n runAsynchronously(async () => {\n try {\n const res = await fetch(`${primaryBaseUrl}/api/v1/internal/backend-urls`);\n if (!res.ok) {\n return;\n }\n const data = await res.json();\n if (!Array.isArray(data.urls) || !data.urls.every((u: unknown) => typeof u === 'string')) {\n return;\n }\n createGlobal('__stack-fetched-backend-urls', () => data.urls as string[]);\n } catch (e) {\n captureError('fetch-backend-urls-in-background', e);\n }\n });\n return true;\n });\n}\n\nexport function resolveApiUrls(userExplicitBaseUrl: string | { browser: string, server: string } | undefined): () => string[] {\n return () => {\n if (userExplicitBaseUrl != null) {\n return [getBaseUrl(userExplicitBaseUrl)];\n }\n const primary = getBaseUrl(undefined);\n // Always try to fetch server-configured URLs (supports custom domains via\n // STACK_BACKEND_URLS_CONFIG). Hardcoded fallbacks are used as a default\n // until the background fetch completes.\n fetchBackendUrlsInBackground(primary);\n return getGlobal('__stack-fetched-backend-urls') ?? getDefaultApiUrls(primary);\n };\n}\n\nexport type TokenObject = {\n accessToken: string | null,\n refreshToken: string | null,\n};\n\nexport function createEmptyTokenStore() {\n return new Store<TokenObject>({\n refreshToken: null,\n accessToken: null,\n });\n}\n\n\nconst cachePromiseByHookId = new Map<string, ReactPromise<Result<unknown>>>();\nexport function useAsyncCache<D extends any[], T>(cache: AsyncCache<D, Result<T>>, dependencies: D, caller: string): T {\n // we explicitly don't want to run this hook in SSR\n suspendIfSsr(caller);\n\n // on the dashboard, we do some perf monitoring for pre-fetching which should hook right in here\n const asyncCacheHooks: any[] = getGlobal(\"use-async-cache-execution-hooks\") ?? [];\n for (const hook of asyncCacheHooks) {\n hook({ cache, caller, dependencies });\n }\n\n const id = React.useId();\n\n // whenever the dependencies change, we need to refresh the promise cache\n React.useEffect(() => {\n cachePromiseByHookId.delete(id);\n }, [...dependencies, id]);\n\n const subscribe = useCallback((cb: () => void) => {\n const { unsubscribe } = cache.onStateChange(dependencies, () => {\n cachePromiseByHookId.delete(id);\n cb();\n });\n return unsubscribe;\n }, [cache, ...dependencies]);\n\n const getSnapshot = useCallback(() => {\n // React checks whether a promise passed to `use` is still the same as the previous one by comparing the reference.\n // If we didn't cache here, this wouldn't work because the promise would be recreated every time the value changes.\n if (!cachePromiseByHookId.has(id)) {\n cachePromiseByHookId.set(id, cache.getOrWait(dependencies, \"read-write\"));\n }\n return cachePromiseByHookId.get(id) as ReactPromise<Result<T>>;\n }, [cache, ...dependencies]);\n\n // note: we must use React.useSyncExternalStore instead of importing the function directly, as it will otherwise\n // throw an error on Next.js (\"can't import useSyncExternalStore from the server\")\n const promise = React.useSyncExternalStore(\n subscribe,\n getSnapshot,\n getSnapshot,\n );\n\n const result = use(promise);\n if (result.status === \"error\") {\n const error = result.error;\n if (error instanceof Error && !(error as any).__stackHasConcatenatedStacktraces) {\n concatStacktraces(error, new Error());\n (error as any).__stackHasConcatenatedStacktraces = true;\n }\n throw error;\n }\n return result.data;\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"common.js","names":["HexclaveAssertionError","envVars","AsyncCache","Result","hexclaveAppInternalsSymbol","Store","React"],"sources":["../../../../../src/lib/hexclave-app/apps/implementations/common.ts"],"sourcesContent":["\n//===========================================\n// THIS FILE IS AUTO-GENERATED FROM TEMPLATE. DO NOT EDIT IT DIRECTLY UNLESS YOU ALSO EDIT THE CORRESPONDING FILE IN packages/template\n//===========================================\nimport { InternalSession } from \"@hexclave/shared/dist/sessions\";\nimport { AsyncCache } from \"@hexclave/shared/dist/utils/caches\";\nimport { isBrowserLike } from \"@hexclave/shared/dist/utils/env\";\nimport { HexclaveAssertionError, captureError, concatStacktraces, throwErr } from \"@hexclave/shared/dist/utils/errors\";\nimport { createGlobal, getGlobal } from \"@hexclave/shared/dist/utils/globals\";\nimport { filterUndefined, omit } from \"@hexclave/shared/dist/utils/objects\";\nimport { ReactPromise, runAsynchronously } from \"@hexclave/shared/dist/utils/promises\";\nimport { suspendIfSsr, use } from \"@hexclave/shared/dist/utils/react\";\nimport { Result } from \"@hexclave/shared/dist/utils/results\";\nimport { Store } from \"@hexclave/shared/dist/utils/stores\";\nimport { deindent } from \"@hexclave/shared/dist/utils/strings\";\nimport { getDefaultApiUrls } from \"@hexclave/shared/dist/utils/urls\";\nimport React, { useCallback } from \"react\"; // THIS_LINE_PLATFORM react-like\nimport { envVars } from \"../../../../generated/env\";\nimport { HandlerUrlOptions, ResolvedHandlerUrls, hexclaveAppInternalsSymbol } from \"../../common\";\nimport { resolveHandlerUrls } from \"../../url-targets\";\n\nexport const clientVersion = \"STACK_COMPILE_TIME_CLIENT_PACKAGE_VERSION_SENTINEL\";\nif (clientVersion.startsWith(\"STACK_COMPILE_TIME\")) {\n throw new HexclaveAssertionError(\"Client version was not replaced. Something went wrong during build!\");\n}\n\nconst replaceHexclavePortPrefix = <T extends string | undefined>(input: T): T => {\n if (!input) return input;\n const prefix = envVars.HEXCLAVE_PORT_PREFIX;\n return prefix ? input.replace(/\\$\\{NEXT_PUBLIC_HEXCLAVE_PORT_PREFIX:-81\\}/g, prefix) as T : input;\n};\n\nconst showMissingConfigAlertInBrowser = (message: string) => {\n if (!isBrowserLike()) return;\n\n const global = globalThis as any;\n const alertAlreadyShownKey = \"__hexclave_missing_config_alert_already_shown\";\n if (global[alertAlreadyShownKey]) return;\n global[alertAlreadyShownKey] = true;\n\n const alertFn = global.alert;\n if (typeof alertFn === \"function\") {\n alertFn(message);\n }\n};\n\nconst throwMissingProjectIdError = (): never => {\n const message = deindent`\n Welcome to Hexclave!\n \n It seems that you haven't provided a project ID.\n\n ${envVars.NODE_ENV?.includes(\"dev\") ? deindent`\n You are running this app in development mode. Make sure that your start command is wrapped in a call to the Hexclave CLI: \\`npx @hexclave/cli dev --config-file <file> -- <command>\\`\n\n Alternatively, you can also connect to a Cloud project's environment variables instead by creating a project on the Hexclave dashboard at https://app.hexclave.com and put its information in the HEXCLAVE_PROJECT_ID environment variable.\n ` : envVars.NODE_ENV?.includes(\"prod\") ? deindent`\n It seems you are running a project in production. Please create a project on the Hexclave dashboard at https://app.hexclave.com and put it in the HEXCLAVE_PROJECT_ID environment variable.\n ` : deindent`\n - If you are running a development project and would like to connect the local Hexclave dashboard, wrap your start command in: \\`npx @hexclave/cli dev --config-file <file> -- <command>\\`\n\n - If you are running a project in production and would like to connect to Hexclave Cloud, create a project on the Hexclave dashboard at https://app.hexclave.com and put its information in the HEXCLAVE_PROJECT_ID environment variable.\n `}\n `;\n showMissingConfigAlertInBrowser(message);\n return throwErr(new Error(message));\n};\n\n\nexport const createCache = <D extends any[], T>(fetcher: (dependencies: D) => Promise<T>) => {\n return new AsyncCache<D, Result<T>>(\n async (dependencies) => await Result.fromThrowingAsync(async () => await fetcher(dependencies)),\n {},\n );\n};\n\nexport const createCacheBySession = <D extends any[], T>(fetcher: (session: InternalSession, extraDependencies: D) => Promise<T> ) => {\n return new AsyncCache<[InternalSession, ...D], Result<T>>(\n async ([session, ...extraDependencies]) => await Result.fromThrowingAsync(async () => await fetcher(session, extraDependencies)),\n {\n onSubscribe: ([session], refresh) => {\n const handler = session.onInvalidate(() => refresh());\n return () => handler.unsubscribe();\n },\n },\n );\n};\n\n\ntype AppLike = { [hexclaveAppInternalsSymbol]: { getConstructorOptions: () => any } };\nexport function resolveConstructorOptions<T extends { inheritsFrom?: AppLike }>(options: T): T & { inheritsFrom?: undefined } {\n return {\n ...options.inheritsFrom?.[hexclaveAppInternalsSymbol].getConstructorOptions() ?? {},\n ...filterUndefined(omit(options, [\"inheritsFrom\"])),\n };\n}\n\nexport function getUrls(partial: HandlerUrlOptions, options: { projectId: string }): ResolvedHandlerUrls {\n return resolveHandlerUrls({\n urls: partial,\n projectId: options.projectId,\n });\n}\n\nexport function getDefaultProjectId() {\n return envVars.HEXCLAVE_PROJECT_ID || throwMissingProjectIdError();\n}\n\nexport function getDefaultPublishableClientKey() {\n return envVars.HEXCLAVE_PUBLISHABLE_CLIENT_KEY;\n}\n\nexport function getDefaultSecretServerKey() {\n return envVars.HEXCLAVE_SECRET_SERVER_KEY || throwErr(new Error(\"No secret server key provided. Please copy your key from the Hexclave dashboard and put it in the HEXCLAVE_SECRET_SERVER_KEY environment variable.\"));\n}\n\nexport function getDefaultSuperSecretAdminKey() {\n return envVars.HEXCLAVE_SUPER_SECRET_ADMIN_KEY || throwErr(new Error(\"No super secret admin key provided. Please copy your key from the Hexclave dashboard and put it in the HEXCLAVE_SUPER_SECRET_ADMIN_KEY environment variable.\"));\n}\n\nexport function getDefaultExtraRequestHeaders() {\n return JSON.parse(envVars.HEXCLAVE_EXTRA_REQUEST_HEADERS || '{}');\n}\n\n/**\n * Returns the base URL for the Stack API.\n *\n * The URL can be specified in several ways, in order of precedence:\n * 1. Directly through userSpecifiedBaseUrl parameter as string or browser/server object\n * 2. Through environment variables:\n * - Browser: NEXT_PUBLIC_HEXCLAVE_API_URL_BROWSER/VITE_HEXCLAVE_API_URL_BROWSER\n * - Server: HEXCLAVE_API_URL_SERVER\n * - Default: HEXCLAVE_API_URL\n * 3. Default base URL if none of the above are specified\n *\n * The function also ensures the URL doesn't end with a trailing slash\n * by removing it if present.\n *\n * @param userSpecifiedBaseUrl - Optional URL override as string or {browser, server} object\n * @returns The configured base URL without trailing slash\n\n */\nexport function getBaseUrl(userSpecifiedBaseUrl: string | { browser: string, server: string } | undefined) {\n let url;\n if (userSpecifiedBaseUrl) {\n if (typeof userSpecifiedBaseUrl === \"string\") {\n url = userSpecifiedBaseUrl;\n } else {\n if (isBrowserLike()) {\n url = userSpecifiedBaseUrl.browser;\n } else {\n url = userSpecifiedBaseUrl.server;\n }\n }\n } else {\n if (isBrowserLike()) {\n url = envVars.HEXCLAVE_API_URL_BROWSER;\n } else {\n url = envVars.HEXCLAVE_API_URL_SERVER;\n }\n url = url || envVars.HEXCLAVE_API_URL || defaultBaseUrl;\n }\n\n return replaceHexclavePortPrefix(url.endsWith('/') ? url.slice(0, -1) : url);\n}\n\nexport const defaultBaseUrl = \"https://api.hexclave.com\";\nexport const defaultAnalyticsBaseUrl = \"https://r.hexclave.com\";\n\nconst analyticsBaseUrlsByApiBaseUrl = new Map<string, string>([\n [defaultBaseUrl, defaultAnalyticsBaseUrl],\n [\"https://api.stack-auth.com\", \"https://r.stack-auth.com\"],\n]);\n\nexport function getAnalyticsBaseUrl(regularBaseUrl: string): string {\n return analyticsBaseUrlsByApiBaseUrl.get(regularBaseUrl) ?? regularBaseUrl;\n}\n\n\nfunction fetchBackendUrlsInBackground(primaryBaseUrl: string): void {\n createGlobal('__stack-fetch-backend-urls-started', () => {\n runAsynchronously(async () => {\n try {\n const res = await fetch(`${primaryBaseUrl}/api/v1/internal/backend-urls`);\n if (!res.ok) {\n return;\n }\n const data = await res.json();\n if (!Array.isArray(data.urls) || !data.urls.every((u: unknown) => typeof u === 'string')) {\n return;\n }\n createGlobal('__stack-fetched-backend-urls', () => data.urls as string[]);\n } catch (e) {\n captureError('fetch-backend-urls-in-background', e);\n }\n });\n return true;\n });\n}\n\nexport function resolveApiUrls(userExplicitBaseUrl: string | { browser: string, server: string } | undefined): () => string[] {\n return () => {\n if (userExplicitBaseUrl != null) {\n return [getBaseUrl(userExplicitBaseUrl)];\n }\n const primary = getBaseUrl(undefined);\n // Always try to fetch server-configured URLs (supports custom domains via\n // STACK_BACKEND_URLS_CONFIG). Hardcoded fallbacks are used as a default\n // until the background fetch completes.\n fetchBackendUrlsInBackground(primary);\n return getGlobal('__stack-fetched-backend-urls') ?? getDefaultApiUrls(primary);\n };\n}\n\nexport type TokenObject = {\n accessToken: string | null,\n refreshToken: string | null,\n};\n\nexport function createEmptyTokenStore() {\n return new Store<TokenObject>({\n refreshToken: null,\n accessToken: null,\n });\n}\n\n\nconst cachePromiseByHookId = new Map<string, ReactPromise<Result<unknown>>>();\nexport function useAsyncCache<D extends any[], T>(cache: AsyncCache<D, Result<T>>, dependencies: D, caller: string): T {\n // we explicitly don't want to run this hook in SSR\n suspendIfSsr(caller);\n\n // on the dashboard, we do some perf monitoring for pre-fetching which should hook right in here\n const asyncCacheHooks: any[] = getGlobal(\"use-async-cache-execution-hooks\") ?? [];\n for (const hook of asyncCacheHooks) {\n hook({ cache, caller, dependencies });\n }\n\n const id = React.useId();\n\n // whenever the dependencies change, we need to refresh the promise cache\n React.useEffect(() => {\n cachePromiseByHookId.delete(id);\n }, [...dependencies, id]);\n\n const subscribe = useCallback((cb: () => void) => {\n const { unsubscribe } = cache.onStateChange(dependencies, () => {\n cachePromiseByHookId.delete(id);\n cb();\n });\n return unsubscribe;\n }, [cache, ...dependencies]);\n\n const getSnapshot = useCallback(() => {\n // React checks whether a promise passed to `use` is still the same as the previous one by comparing the reference.\n // If we didn't cache here, this wouldn't work because the promise would be recreated every time the value changes.\n if (!cachePromiseByHookId.has(id)) {\n cachePromiseByHookId.set(id, cache.getOrWait(dependencies, \"read-write\"));\n }\n return cachePromiseByHookId.get(id) as ReactPromise<Result<T>>;\n }, [cache, ...dependencies]);\n\n // note: we must use React.useSyncExternalStore instead of importing the function directly, as it will otherwise\n // throw an error on Next.js (\"can't import useSyncExternalStore from the server\")\n const promise = React.useSyncExternalStore(\n subscribe,\n getSnapshot,\n getSnapshot,\n );\n\n const result = use(promise);\n if (result.status === \"error\") {\n const error = result.error;\n if (error instanceof Error && !(error as any).__stackHasConcatenatedStacktraces) {\n concatStacktraces(error, new Error());\n (error as any).__stackHasConcatenatedStacktraces = true;\n }\n throw error;\n }\n return result.data;\n}\n"],"mappings":"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"}
@@ -1,4 +1,4 @@
1
- import { n as _HexclaveServerAppImplIncomplete, r as _HexclaveClientAppImplIncomplete, t as _HexclaveAdminAppImplIncomplete } from "../../../../admin-app-impl-Cmhx-Gnk.js";
1
+ import { n as _HexclaveServerAppImplIncomplete, r as _HexclaveClientAppImplIncomplete, t as _HexclaveAdminAppImplIncomplete } from "../../../../admin-app-impl-Bga0o6cL.js";
2
2
 
3
3
  //#region src/lib/hexclave-app/apps/implementations/index.d.ts
4
4
  declare const _HexclaveAdminAppImpl: typeof _HexclaveAdminAppImplIncomplete, _HexclaveClientAppImpl: typeof _HexclaveClientAppImplIncomplete, _HexclaveServerAppImpl: typeof _HexclaveServerAppImplIncomplete;
@@ -0,0 +1,37 @@
1
+ //#region src/lib/hexclave-app/apps/implementations/redirect-back-state.d.ts
2
+ type PersistedRedirectBackState = {
3
+ /**
4
+ * The `after_auth_return_to` value exactly as it appeared in the URL (possibly relative).
5
+ * sessionStorage is per-origin, so restoring it into a URL later always resolves it against the
6
+ * same origin it was saved from.
7
+ */
8
+ afterAuthReturnTo: string;
9
+ crossDomainState: string | null;
10
+ crossDomainCodeChallenge: string | null;
11
+ crossDomainAfterCallbackRedirectUrl: string | null; /** Wall-clock ms (Date.now()); must survive page loads, so performance.now() is not usable. */
12
+ savedAtMillis: number;
13
+ };
14
+ /**
15
+ * Mirrors the redirect-back state from the given URL into sessionStorage, if it has any. Called
16
+ * with the page URL at app construction time (full page loads) and with the target URL of every
17
+ * SDK-driven redirect (client-side navigations, which don't re-run the constructor).
18
+ */
19
+ declare function saveRedirectBackStateFromUrl(options: {
20
+ url: URL;
21
+ projectId: string;
22
+ }): void;
23
+ declare function readRedirectBackState(options: {
24
+ projectId: string;
25
+ }): PersistedRedirectBackState | null;
26
+ /**
27
+ * Returns a copy of `currentUrl` with any redirect-back query params that were dropped along the
28
+ * way restored from the sessionStorage mirror. If `currentUrl` already carries
29
+ * `after_auth_return_to`, it is returned unchanged — explicit query params always win.
30
+ */
31
+ declare function augmentUrlWithPersistedRedirectBackState(options: {
32
+ currentUrl: URL;
33
+ projectId: string;
34
+ }): URL;
35
+ //#endregion
36
+ export { PersistedRedirectBackState, augmentUrlWithPersistedRedirectBackState, readRedirectBackState, saveRedirectBackStateFromUrl };
37
+ //# sourceMappingURL=redirect-back-state.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"redirect-back-state.d.ts","names":[],"sources":["../../../../../src/lib/hexclave-app/apps/implementations/redirect-back-state.ts"],"mappings":";KAoCY,0BAAA;EAAA;;;;;EAMV,iBAAA;EACA,gBAAA;EACA,wBAAA;EACA,mCAAA,iBAEa;EAAb,aAAA;AAAA;;;;;;iBAwBc,4BAAA,CAA6B,OAAA;EAAW,GAAA,EAAK,GAAG;EAAE,SAAA;AAAA;AAAA,iBAelD,qBAAA,CAAsB,OAAA;EAAW,SAAA;AAAA,IAAsB,0BAA0B;;;;;;iBAgCjF,wCAAA,CAAyC,OAAA;EAAW,UAAA,EAAY,GAAA;EAAK,SAAA;AAAA,IAAsB,GAAG"}
@@ -0,0 +1,92 @@
1
+ Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
2
+ let __redirect_page_urls_js = require("./redirect-page-urls.js");
3
+ let ____________utils_session_storage_js = require("../../../../utils/session-storage.js");
4
+ //#region src/lib/hexclave-app/apps/implementations/redirect-back-state.ts
5
+ /**
6
+ * Resilience layer for the redirect-back ("return to where you came from") state of auth flows.
7
+ *
8
+ * The primary transport for this state is query params (`after_auth_return_to` +
9
+ * `hexclave_cross_domain_*`), which must survive every hop of the auth flow (sign-in page, OAuth
10
+ * provider round-trips, MFA, magic links, ...). Historically, any hop that dropped these params
11
+ * stranded the user on the wrong page after auth — on hosted components, that meant the hosted
12
+ * welcome page instead of the customer's app.
13
+ *
14
+ * This module mirrors the state into sessionStorage whenever it appears in a URL, so
15
+ * `redirectToAfterSignIn`/`redirectToAfterSignUp` can restore it if the params were dropped along
16
+ * the way. Query params always take precedence; the mirror is only a fallback. It is scoped
17
+ * per-tab (sessionStorage), per-origin (sessionStorage again), and per-project, and it expires
18
+ * after a TTL. Restored URLs still go through the exact same trust validation as URLs read from
19
+ * query params (the `_isTrusted` gate client-side and the cross-domain authorize endpoint
20
+ * server-side), so the mirror never widens the set of allowed destinations.
21
+ */
22
+ const redirectBackStateTtlMillis = 1800 * 1e3;
23
+ function getStorageKey(projectId) {
24
+ return `hexclave-redirect-back-state-${projectId}`;
25
+ }
26
+ function isPersistedRedirectBackState(value) {
27
+ return typeof value === "object" && value !== null && "afterAuthReturnTo" in value && typeof value.afterAuthReturnTo === "string" && "crossDomainState" in value && (value.crossDomainState === null || typeof value.crossDomainState === "string") && "crossDomainCodeChallenge" in value && (value.crossDomainCodeChallenge === null || typeof value.crossDomainCodeChallenge === "string") && "crossDomainAfterCallbackRedirectUrl" in value && (value.crossDomainAfterCallbackRedirectUrl === null || typeof value.crossDomainAfterCallbackRedirectUrl === "string") && "savedAtMillis" in value && typeof value.savedAtMillis === "number";
28
+ }
29
+ /**
30
+ * Mirrors the redirect-back state from the given URL into sessionStorage, if it has any. Called
31
+ * with the page URL at app construction time (full page loads) and with the target URL of every
32
+ * SDK-driven redirect (client-side navigations, which don't re-run the constructor).
33
+ */
34
+ function saveRedirectBackStateFromUrl(options) {
35
+ const afterAuthReturnTo = options.url.searchParams.get("after_auth_return_to");
36
+ if (afterAuthReturnTo == null) return;
37
+ const state = {
38
+ afterAuthReturnTo,
39
+ crossDomainState: options.url.searchParams.get(__redirect_page_urls_js.crossDomainAuthQueryParams.state),
40
+ crossDomainCodeChallenge: options.url.searchParams.get(__redirect_page_urls_js.crossDomainAuthQueryParams.codeChallenge),
41
+ crossDomainAfterCallbackRedirectUrl: options.url.searchParams.get(__redirect_page_urls_js.crossDomainAuthQueryParams.afterCallbackRedirectUrl),
42
+ savedAtMillis: Date.now()
43
+ };
44
+ (0, ____________utils_session_storage_js.writeSessionStorageItem)(getStorageKey(options.projectId), JSON.stringify(state));
45
+ }
46
+ function readRedirectBackState(options) {
47
+ const storageKey = getStorageKey(options.projectId);
48
+ const raw = (0, ____________utils_session_storage_js.readSessionStorageItem)(storageKey);
49
+ if (raw == null) return null;
50
+ let parsed;
51
+ try {
52
+ parsed = JSON.parse(raw);
53
+ } catch {
54
+ (0, ____________utils_session_storage_js.removeSessionStorageItem)(storageKey);
55
+ return null;
56
+ }
57
+ if (!isPersistedRedirectBackState(parsed)) {
58
+ (0, ____________utils_session_storage_js.removeSessionStorageItem)(storageKey);
59
+ return null;
60
+ }
61
+ const ageMillis = Date.now() - parsed.savedAtMillis;
62
+ if (ageMillis < 0 || ageMillis > redirectBackStateTtlMillis) {
63
+ (0, ____________utils_session_storage_js.removeSessionStorageItem)(storageKey);
64
+ return null;
65
+ }
66
+ return parsed;
67
+ }
68
+ /**
69
+ * Returns a copy of `currentUrl` with any redirect-back query params that were dropped along the
70
+ * way restored from the sessionStorage mirror. If `currentUrl` already carries
71
+ * `after_auth_return_to`, it is returned unchanged — explicit query params always win.
72
+ */
73
+ function augmentUrlWithPersistedRedirectBackState(options) {
74
+ if (options.currentUrl.searchParams.has("after_auth_return_to")) return options.currentUrl;
75
+ const persisted = readRedirectBackState({ projectId: options.projectId });
76
+ if (persisted == null) return options.currentUrl;
77
+ const augmented = new URL(options.currentUrl.toString());
78
+ augmented.searchParams.set("after_auth_return_to", persisted.afterAuthReturnTo);
79
+ const crossDomainParamValues = [
80
+ [__redirect_page_urls_js.crossDomainAuthQueryParams.state, persisted.crossDomainState],
81
+ [__redirect_page_urls_js.crossDomainAuthQueryParams.codeChallenge, persisted.crossDomainCodeChallenge],
82
+ [__redirect_page_urls_js.crossDomainAuthQueryParams.afterCallbackRedirectUrl, persisted.crossDomainAfterCallbackRedirectUrl]
83
+ ];
84
+ for (const [paramName, persistedValue] of crossDomainParamValues) if (persistedValue != null && !augmented.searchParams.has(paramName)) augmented.searchParams.set(paramName, persistedValue);
85
+ return augmented;
86
+ }
87
+ //#endregion
88
+ exports.augmentUrlWithPersistedRedirectBackState = augmentUrlWithPersistedRedirectBackState;
89
+ exports.readRedirectBackState = readRedirectBackState;
90
+ exports.saveRedirectBackStateFromUrl = saveRedirectBackStateFromUrl;
91
+
92
+ //# sourceMappingURL=redirect-back-state.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"redirect-back-state.js","names":["crossDomainAuthQueryParams"],"sources":["../../../../../src/lib/hexclave-app/apps/implementations/redirect-back-state.ts"],"sourcesContent":["\n//===========================================\n// THIS FILE IS AUTO-GENERATED FROM TEMPLATE. DO NOT EDIT IT DIRECTLY UNLESS YOU ALSO EDIT THE CORRESPONDING FILE IN packages/template\n//===========================================\nimport { readSessionStorageItem, removeSessionStorageItem, writeSessionStorageItem } from \"../../../../utils/session-storage\";\nimport { crossDomainAuthQueryParams } from \"./redirect-page-urls\";\n\n/**\n * Resilience layer for the redirect-back (\"return to where you came from\") state of auth flows.\n *\n * The primary transport for this state is query params (`after_auth_return_to` +\n * `hexclave_cross_domain_*`), which must survive every hop of the auth flow (sign-in page, OAuth\n * provider round-trips, MFA, magic links, ...). Historically, any hop that dropped these params\n * stranded the user on the wrong page after auth — on hosted components, that meant the hosted\n * welcome page instead of the customer's app.\n *\n * This module mirrors the state into sessionStorage whenever it appears in a URL, so\n * `redirectToAfterSignIn`/`redirectToAfterSignUp` can restore it if the params were dropped along\n * the way. Query params always take precedence; the mirror is only a fallback. It is scoped\n * per-tab (sessionStorage), per-origin (sessionStorage again), and per-project, and it expires\n * after a TTL. Restored URLs still go through the exact same trust validation as URLs read from\n * query params (the `_isTrusted` gate client-side and the cross-domain authorize endpoint\n * server-side), so the mirror never widens the set of allowed destinations.\n */\n\n// Deliberately NOT consumed/cleared on use: React can invoke the after-sign-in redirect twice\n// (strict mode, remounts), and consuming on first read would make the second invocation fall back\n// to the default post-auth page and race the correct redirect. Staleness is bounded by the TTL\n// instead, and a stale entry merely returns the user to the last page that started an auth flow\n// in this tab — which is also what they'd expect.\n//\n// The TTL must stay below the 1h lifetime of the outer PKCE verifier cookie (see\n// saveVerifierAndState in cookie.ts): the mirrored cross-domain params are useless once their\n// verifier cookie on the app domain has expired.\nconst redirectBackStateTtlMillis = 30 * 60 * 1000;\n\nexport type PersistedRedirectBackState = {\n /**\n * The `after_auth_return_to` value exactly as it appeared in the URL (possibly relative).\n * sessionStorage is per-origin, so restoring it into a URL later always resolves it against the\n * same origin it was saved from.\n */\n afterAuthReturnTo: string,\n crossDomainState: string | null,\n crossDomainCodeChallenge: string | null,\n crossDomainAfterCallbackRedirectUrl: string | null,\n /** Wall-clock ms (Date.now()); must survive page loads, so performance.now() is not usable. */\n savedAtMillis: number,\n};\n\nfunction getStorageKey(projectId: string): string {\n return `hexclave-redirect-back-state-${projectId}`;\n}\n\nfunction isPersistedRedirectBackState(value: unknown): value is PersistedRedirectBackState {\n return (\n typeof value === \"object\"\n && value !== null\n && \"afterAuthReturnTo\" in value && typeof value.afterAuthReturnTo === \"string\"\n && \"crossDomainState\" in value && (value.crossDomainState === null || typeof value.crossDomainState === \"string\")\n && \"crossDomainCodeChallenge\" in value && (value.crossDomainCodeChallenge === null || typeof value.crossDomainCodeChallenge === \"string\")\n && \"crossDomainAfterCallbackRedirectUrl\" in value && (value.crossDomainAfterCallbackRedirectUrl === null || typeof value.crossDomainAfterCallbackRedirectUrl === \"string\")\n && \"savedAtMillis\" in value && typeof value.savedAtMillis === \"number\"\n );\n}\n\n/**\n * Mirrors the redirect-back state from the given URL into sessionStorage, if it has any. Called\n * with the page URL at app construction time (full page loads) and with the target URL of every\n * SDK-driven redirect (client-side navigations, which don't re-run the constructor).\n */\nexport function saveRedirectBackStateFromUrl(options: { url: URL, projectId: string }): void {\n const afterAuthReturnTo = options.url.searchParams.get(\"after_auth_return_to\");\n if (afterAuthReturnTo == null) {\n return;\n }\n const state: PersistedRedirectBackState = {\n afterAuthReturnTo,\n crossDomainState: options.url.searchParams.get(crossDomainAuthQueryParams.state),\n crossDomainCodeChallenge: options.url.searchParams.get(crossDomainAuthQueryParams.codeChallenge),\n crossDomainAfterCallbackRedirectUrl: options.url.searchParams.get(crossDomainAuthQueryParams.afterCallbackRedirectUrl),\n savedAtMillis: Date.now(),\n };\n writeSessionStorageItem(getStorageKey(options.projectId), JSON.stringify(state));\n}\n\nexport function readRedirectBackState(options: { projectId: string }): PersistedRedirectBackState | null {\n const storageKey = getStorageKey(options.projectId);\n const raw = readSessionStorageItem(storageKey);\n if (raw == null) {\n return null;\n }\n let parsed: unknown;\n try {\n parsed = JSON.parse(raw);\n } catch {\n // Corrupted value (eg. written by a different SDK version); losing the fallback for this flow\n // is the pre-existing behavior, so just discard it.\n removeSessionStorageItem(storageKey);\n return null;\n }\n if (!isPersistedRedirectBackState(parsed)) {\n removeSessionStorageItem(storageKey);\n return null;\n }\n const ageMillis = Date.now() - parsed.savedAtMillis;\n if (ageMillis < 0 || ageMillis > redirectBackStateTtlMillis) {\n removeSessionStorageItem(storageKey);\n return null;\n }\n return parsed;\n}\n\n/**\n * Returns a copy of `currentUrl` with any redirect-back query params that were dropped along the\n * way restored from the sessionStorage mirror. If `currentUrl` already carries\n * `after_auth_return_to`, it is returned unchanged — explicit query params always win.\n */\nexport function augmentUrlWithPersistedRedirectBackState(options: { currentUrl: URL, projectId: string }): URL {\n if (options.currentUrl.searchParams.has(\"after_auth_return_to\")) {\n return options.currentUrl;\n }\n const persisted = readRedirectBackState({ projectId: options.projectId });\n if (persisted == null) {\n return options.currentUrl;\n }\n const augmented = new URL(options.currentUrl.toString());\n augmented.searchParams.set(\"after_auth_return_to\", persisted.afterAuthReturnTo);\n const crossDomainParamValues = [\n [crossDomainAuthQueryParams.state, persisted.crossDomainState],\n [crossDomainAuthQueryParams.codeChallenge, persisted.crossDomainCodeChallenge],\n [crossDomainAuthQueryParams.afterCallbackRedirectUrl, persisted.crossDomainAfterCallbackRedirectUrl],\n ] as const;\n for (const [paramName, persistedValue] of crossDomainParamValues) {\n if (persistedValue != null && !augmented.searchParams.has(paramName)) {\n augmented.searchParams.set(paramName, persistedValue);\n }\n }\n return augmented;\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;AAkCA,MAAM,6BAA6B,OAAU;AAgB7C,SAAS,cAAc,WAA2B;CAChD,OAAO,gCAAgC;AACzC;AAEA,SAAS,6BAA6B,OAAqD;CACzF,OACE,OAAO,UAAU,YACd,UAAU,QACV,uBAAuB,SAAS,OAAO,MAAM,sBAAsB,YACnE,sBAAsB,UAAU,MAAM,qBAAqB,QAAQ,OAAO,MAAM,qBAAqB,aACrG,8BAA8B,UAAU,MAAM,6BAA6B,QAAQ,OAAO,MAAM,6BAA6B,aAC7H,yCAAyC,UAAU,MAAM,wCAAwC,QAAQ,OAAO,MAAM,wCAAwC,aAC9J,mBAAmB,SAAS,OAAO,MAAM,kBAAkB;AAElE;;;;;;AAOA,SAAgB,6BAA6B,SAAgD;CAC3F,MAAM,oBAAoB,QAAQ,IAAI,aAAa,IAAI,sBAAsB;CAC7E,IAAI,qBAAqB,MACvB;CAEF,MAAM,QAAoC;EACxC;EACA,kBAAkB,QAAQ,IAAI,aAAa,IAAIA,wBAAAA,2BAA2B,KAAK;EAC/E,0BAA0B,QAAQ,IAAI,aAAa,IAAIA,wBAAAA,2BAA2B,aAAa;EAC/F,qCAAqC,QAAQ,IAAI,aAAa,IAAIA,wBAAAA,2BAA2B,wBAAwB;EACrH,eAAe,KAAK,IAAI;CAC1B;CACA,CAAA,GAAA,qCAAA,wBAAA,CAAwB,cAAc,QAAQ,SAAS,GAAG,KAAK,UAAU,KAAK,CAAC;AACjF;AAEA,SAAgB,sBAAsB,SAAmE;CACvG,MAAM,aAAa,cAAc,QAAQ,SAAS;CAClD,MAAM,OAAA,GAAA,qCAAA,uBAAA,CAA6B,UAAU;CAC7C,IAAI,OAAO,MACT,OAAO;CAET,IAAI;CACJ,IAAI;EACF,SAAS,KAAK,MAAM,GAAG;CACzB,QAAQ;EAGN,CAAA,GAAA,qCAAA,yBAAA,CAAyB,UAAU;EACnC,OAAO;CACT;CACA,IAAI,CAAC,6BAA6B,MAAM,GAAG;EACzC,CAAA,GAAA,qCAAA,yBAAA,CAAyB,UAAU;EACnC,OAAO;CACT;CACA,MAAM,YAAY,KAAK,IAAI,IAAI,OAAO;CACtC,IAAI,YAAY,KAAK,YAAY,4BAA4B;EAC3D,CAAA,GAAA,qCAAA,yBAAA,CAAyB,UAAU;EACnC,OAAO;CACT;CACA,OAAO;AACT;;;;;;AAOA,SAAgB,yCAAyC,SAAsD;CAC7G,IAAI,QAAQ,WAAW,aAAa,IAAI,sBAAsB,GAC5D,OAAO,QAAQ;CAEjB,MAAM,YAAY,sBAAsB,EAAE,WAAW,QAAQ,UAAU,CAAC;CACxE,IAAI,aAAa,MACf,OAAO,QAAQ;CAEjB,MAAM,YAAY,IAAI,IAAI,QAAQ,WAAW,SAAS,CAAC;CACvD,UAAU,aAAa,IAAI,wBAAwB,UAAU,iBAAiB;CAC9E,MAAM,yBAAyB;EAC7B,CAACA,wBAAAA,2BAA2B,OAAO,UAAU,gBAAgB;EAC7D,CAACA,wBAAAA,2BAA2B,eAAe,UAAU,wBAAwB;EAC7E,CAACA,wBAAAA,2BAA2B,0BAA0B,UAAU,mCAAmC;CACrG;CACA,KAAK,MAAM,CAAC,WAAW,mBAAmB,wBACxC,IAAI,kBAAkB,QAAQ,CAAC,UAAU,aAAa,IAAI,SAAS,GACjE,UAAU,aAAa,IAAI,WAAW,cAAc;CAGxD,OAAO;AACT"}
@@ -0,0 +1,133 @@
1
+ let vitest = require("vitest");
2
+ let __redirect_back_state_js = require("./redirect-back-state.js");
3
+ //#region src/lib/hexclave-app/apps/implementations/redirect-back-state.test.ts
4
+ function createMockSessionStorage() {
5
+ const map = /* @__PURE__ */ new Map();
6
+ return {
7
+ getItem: (key) => map.get(key) ?? null,
8
+ setItem: (key, value) => {
9
+ map.set(key, value);
10
+ },
11
+ removeItem: (key) => {
12
+ map.delete(key);
13
+ }
14
+ };
15
+ }
16
+ const previousWindow = Reflect.get(globalThis, "window");
17
+ const hadPreviousWindow = Reflect.has(globalThis, "window");
18
+ const projectId = "00000000-0000-4000-8000-000000000000";
19
+ (0, vitest.describe)("redirect-back state mirror", () => {
20
+ (0, vitest.beforeEach)(() => {
21
+ Reflect.set(globalThis, "window", { sessionStorage: createMockSessionStorage() });
22
+ vitest.vi.useFakeTimers();
23
+ vitest.vi.setSystemTime(/* @__PURE__ */ new Date("2026-01-01T00:00:00Z"));
24
+ });
25
+ (0, vitest.afterEach)(() => {
26
+ vitest.vi.useRealTimers();
27
+ if (hadPreviousWindow) Reflect.set(globalThis, "window", previousWindow);
28
+ else Reflect.deleteProperty(globalThis, "window");
29
+ });
30
+ (0, vitest.it)("round-trips redirect-back state including the cross-domain handoff params", () => {
31
+ const url = new URL("https://hosted.example.test/handler/sign-in");
32
+ url.searchParams.set("after_auth_return_to", "https://app.example.test/handler/oauth-callback?hexclave_cross_domain_auth=1");
33
+ url.searchParams.set("hexclave_cross_domain_state", "the-state");
34
+ url.searchParams.set("hexclave_cross_domain_code_challenge", "the-code-challenge");
35
+ url.searchParams.set("hexclave_cross_domain_after_callback_redirect_url", "https://app.example.test/dashboard");
36
+ (0, __redirect_back_state_js.saveRedirectBackStateFromUrl)({
37
+ url,
38
+ projectId
39
+ });
40
+ (0, vitest.expect)((0, __redirect_back_state_js.readRedirectBackState)({ projectId })).toEqual({
41
+ afterAuthReturnTo: "https://app.example.test/handler/oauth-callback?hexclave_cross_domain_auth=1",
42
+ crossDomainState: "the-state",
43
+ crossDomainCodeChallenge: "the-code-challenge",
44
+ crossDomainAfterCallbackRedirectUrl: "https://app.example.test/dashboard",
45
+ savedAtMillis: Date.now()
46
+ });
47
+ });
48
+ (0, vitest.it)("returns null when nothing was saved and does not save URLs without after_auth_return_to", () => {
49
+ (0, vitest.expect)((0, __redirect_back_state_js.readRedirectBackState)({ projectId })).toBeNull();
50
+ (0, __redirect_back_state_js.saveRedirectBackStateFromUrl)({
51
+ url: new URL("https://hosted.example.test/handler/sign-in?foo=bar"),
52
+ projectId
53
+ });
54
+ (0, vitest.expect)((0, __redirect_back_state_js.readRedirectBackState)({ projectId })).toBeNull();
55
+ });
56
+ (0, vitest.it)("expires the state after the TTL", () => {
57
+ (0, __redirect_back_state_js.saveRedirectBackStateFromUrl)({
58
+ url: new URL("https://hosted.example.test/handler/sign-in?after_auth_return_to=/music"),
59
+ projectId
60
+ });
61
+ vitest.vi.advanceTimersByTime(1740 * 1e3);
62
+ (0, vitest.expect)((0, __redirect_back_state_js.readRedirectBackState)({ projectId })).not.toBeNull();
63
+ vitest.vi.advanceTimersByTime(120 * 1e3);
64
+ (0, vitest.expect)((0, __redirect_back_state_js.readRedirectBackState)({ projectId })).toBeNull();
65
+ });
66
+ (0, vitest.it)("scopes the state per project", () => {
67
+ (0, __redirect_back_state_js.saveRedirectBackStateFromUrl)({
68
+ url: new URL("https://hosted.example.test/handler/sign-in?after_auth_return_to=/music"),
69
+ projectId
70
+ });
71
+ (0, vitest.expect)((0, __redirect_back_state_js.readRedirectBackState)({ projectId: "11111111-1111-4111-8111-111111111111" })).toBeNull();
72
+ (0, vitest.expect)((0, __redirect_back_state_js.readRedirectBackState)({ projectId })).not.toBeNull();
73
+ });
74
+ (0, vitest.it)("discards malformed stored values", () => {
75
+ const storage = createMockSessionStorage();
76
+ storage.setItem(`hexclave-redirect-back-state-${projectId}`, "{not json");
77
+ Reflect.set(globalThis, "window", { sessionStorage: storage });
78
+ (0, vitest.expect)((0, __redirect_back_state_js.readRedirectBackState)({ projectId })).toBeNull();
79
+ storage.setItem(`hexclave-redirect-back-state-${projectId}`, JSON.stringify({ some: "other-shape" }));
80
+ (0, vitest.expect)((0, __redirect_back_state_js.readRedirectBackState)({ projectId })).toBeNull();
81
+ });
82
+ (0, vitest.it)("restores dropped params onto a URL, but never overrides an explicit after_auth_return_to", () => {
83
+ const arrivalUrl = new URL("https://hosted.example.test/handler/sign-in");
84
+ arrivalUrl.searchParams.set("after_auth_return_to", "https://app.example.test/handler/oauth-callback");
85
+ arrivalUrl.searchParams.set("hexclave_cross_domain_state", "the-state");
86
+ arrivalUrl.searchParams.set("hexclave_cross_domain_code_challenge", "the-code-challenge");
87
+ arrivalUrl.searchParams.set("hexclave_cross_domain_after_callback_redirect_url", "https://app.example.test/dashboard");
88
+ (0, __redirect_back_state_js.saveRedirectBackStateFromUrl)({
89
+ url: arrivalUrl,
90
+ projectId
91
+ });
92
+ const strippedUrl = new URL("https://hosted.example.test/handler/mfa");
93
+ const augmented = (0, __redirect_back_state_js.augmentUrlWithPersistedRedirectBackState)({
94
+ currentUrl: strippedUrl,
95
+ projectId
96
+ });
97
+ (0, vitest.expect)(augmented.searchParams.get("after_auth_return_to")).toBe("https://app.example.test/handler/oauth-callback");
98
+ (0, vitest.expect)(augmented.searchParams.get("hexclave_cross_domain_state")).toBe("the-state");
99
+ (0, vitest.expect)(augmented.searchParams.get("hexclave_cross_domain_code_challenge")).toBe("the-code-challenge");
100
+ (0, vitest.expect)(augmented.searchParams.get("hexclave_cross_domain_after_callback_redirect_url")).toBe("https://app.example.test/dashboard");
101
+ (0, vitest.expect)(strippedUrl.searchParams.has("after_auth_return_to")).toBe(false);
102
+ const explicitUrl = new URL("https://hosted.example.test/handler/sign-in?after_auth_return_to=/somewhere-else");
103
+ (0, vitest.expect)((0, __redirect_back_state_js.augmentUrlWithPersistedRedirectBackState)({
104
+ currentUrl: explicitUrl,
105
+ projectId
106
+ }).toString()).toBe(explicitUrl.toString());
107
+ });
108
+ (0, vitest.it)("leaves the URL unchanged when there is no mirrored state", () => {
109
+ const url = new URL("https://hosted.example.test/handler/sign-in");
110
+ (0, vitest.expect)((0, __redirect_back_state_js.augmentUrlWithPersistedRedirectBackState)({
111
+ currentUrl: url,
112
+ projectId
113
+ }).toString()).toBe(url.toString());
114
+ });
115
+ (0, vitest.it)("degrades to a no-op when sessionStorage is unavailable", () => {
116
+ Reflect.set(globalThis, "window", { get sessionStorage() {
117
+ throw new Error("Storage is disabled");
118
+ } });
119
+ const url = new URL("https://hosted.example.test/handler/sign-in?after_auth_return_to=/music");
120
+ (0, __redirect_back_state_js.saveRedirectBackStateFromUrl)({
121
+ url,
122
+ projectId
123
+ });
124
+ (0, vitest.expect)((0, __redirect_back_state_js.readRedirectBackState)({ projectId })).toBeNull();
125
+ (0, vitest.expect)((0, __redirect_back_state_js.augmentUrlWithPersistedRedirectBackState)({
126
+ currentUrl: url,
127
+ projectId
128
+ }).toString()).toBe(url.toString());
129
+ });
130
+ });
131
+ //#endregion
132
+
133
+ //# sourceMappingURL=redirect-back-state.test.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"redirect-back-state.test.js","names":[],"sources":["../../../../../src/lib/hexclave-app/apps/implementations/redirect-back-state.test.ts"],"sourcesContent":["\n//===========================================\n// THIS FILE IS AUTO-GENERATED FROM TEMPLATE. DO NOT EDIT IT DIRECTLY UNLESS YOU ALSO EDIT THE CORRESPONDING FILE IN packages/template\n//===========================================\nimport { afterEach, beforeEach, describe, expect, it, vi } from \"vitest\";\nimport { augmentUrlWithPersistedRedirectBackState, readRedirectBackState, saveRedirectBackStateFromUrl } from \"./redirect-back-state\";\n\nfunction createMockSessionStorage() {\n const map = new Map<string, string>();\n return {\n getItem: (key: string) => map.get(key) ?? null,\n setItem: (key: string, value: string) => {\n map.set(key, value);\n },\n removeItem: (key: string) => {\n map.delete(key);\n },\n };\n}\n\nconst previousWindow = Reflect.get(globalThis, \"window\");\nconst hadPreviousWindow = Reflect.has(globalThis, \"window\");\nconst projectId = \"00000000-0000-4000-8000-000000000000\";\n\ndescribe(\"redirect-back state mirror\", () => {\n beforeEach(() => {\n Reflect.set(globalThis, \"window\", { sessionStorage: createMockSessionStorage() });\n vi.useFakeTimers();\n vi.setSystemTime(new Date(\"2026-01-01T00:00:00Z\"));\n });\n\n afterEach(() => {\n vi.useRealTimers();\n if (hadPreviousWindow) {\n Reflect.set(globalThis, \"window\", previousWindow);\n } else {\n Reflect.deleteProperty(globalThis, \"window\");\n }\n });\n\n it(\"round-trips redirect-back state including the cross-domain handoff params\", () => {\n const url = new URL(\"https://hosted.example.test/handler/sign-in\");\n url.searchParams.set(\"after_auth_return_to\", \"https://app.example.test/handler/oauth-callback?hexclave_cross_domain_auth=1\");\n url.searchParams.set(\"hexclave_cross_domain_state\", \"the-state\");\n url.searchParams.set(\"hexclave_cross_domain_code_challenge\", \"the-code-challenge\");\n url.searchParams.set(\"hexclave_cross_domain_after_callback_redirect_url\", \"https://app.example.test/dashboard\");\n\n saveRedirectBackStateFromUrl({ url, projectId });\n\n expect(readRedirectBackState({ projectId })).toEqual({\n afterAuthReturnTo: \"https://app.example.test/handler/oauth-callback?hexclave_cross_domain_auth=1\",\n crossDomainState: \"the-state\",\n crossDomainCodeChallenge: \"the-code-challenge\",\n crossDomainAfterCallbackRedirectUrl: \"https://app.example.test/dashboard\",\n savedAtMillis: Date.now(),\n });\n });\n\n it(\"returns null when nothing was saved and does not save URLs without after_auth_return_to\", () => {\n expect(readRedirectBackState({ projectId })).toBeNull();\n saveRedirectBackStateFromUrl({ url: new URL(\"https://hosted.example.test/handler/sign-in?foo=bar\"), projectId });\n expect(readRedirectBackState({ projectId })).toBeNull();\n });\n\n it(\"expires the state after the TTL\", () => {\n const url = new URL(\"https://hosted.example.test/handler/sign-in?after_auth_return_to=/music\");\n saveRedirectBackStateFromUrl({ url, projectId });\n\n vi.advanceTimersByTime(29 * 60 * 1000);\n expect(readRedirectBackState({ projectId })).not.toBeNull();\n\n vi.advanceTimersByTime(2 * 60 * 1000);\n expect(readRedirectBackState({ projectId })).toBeNull();\n });\n\n it(\"scopes the state per project\", () => {\n const url = new URL(\"https://hosted.example.test/handler/sign-in?after_auth_return_to=/music\");\n saveRedirectBackStateFromUrl({ url, projectId });\n expect(readRedirectBackState({ projectId: \"11111111-1111-4111-8111-111111111111\" })).toBeNull();\n expect(readRedirectBackState({ projectId })).not.toBeNull();\n });\n\n it(\"discards malformed stored values\", () => {\n const storage = createMockSessionStorage();\n storage.setItem(`hexclave-redirect-back-state-${projectId}`, \"{not json\");\n Reflect.set(globalThis, \"window\", { sessionStorage: storage });\n expect(readRedirectBackState({ projectId })).toBeNull();\n\n storage.setItem(`hexclave-redirect-back-state-${projectId}`, JSON.stringify({ some: \"other-shape\" }));\n expect(readRedirectBackState({ projectId })).toBeNull();\n });\n\n it(\"restores dropped params onto a URL, but never overrides an explicit after_auth_return_to\", () => {\n const arrivalUrl = new URL(\"https://hosted.example.test/handler/sign-in\");\n arrivalUrl.searchParams.set(\"after_auth_return_to\", \"https://app.example.test/handler/oauth-callback\");\n arrivalUrl.searchParams.set(\"hexclave_cross_domain_state\", \"the-state\");\n arrivalUrl.searchParams.set(\"hexclave_cross_domain_code_challenge\", \"the-code-challenge\");\n arrivalUrl.searchParams.set(\"hexclave_cross_domain_after_callback_redirect_url\", \"https://app.example.test/dashboard\");\n saveRedirectBackStateFromUrl({ url: arrivalUrl, projectId });\n\n // Params were dropped along the way: restore all of them.\n const strippedUrl = new URL(\"https://hosted.example.test/handler/mfa\");\n const augmented = augmentUrlWithPersistedRedirectBackState({ currentUrl: strippedUrl, projectId });\n expect(augmented.searchParams.get(\"after_auth_return_to\")).toBe(\"https://app.example.test/handler/oauth-callback\");\n expect(augmented.searchParams.get(\"hexclave_cross_domain_state\")).toBe(\"the-state\");\n expect(augmented.searchParams.get(\"hexclave_cross_domain_code_challenge\")).toBe(\"the-code-challenge\");\n expect(augmented.searchParams.get(\"hexclave_cross_domain_after_callback_redirect_url\")).toBe(\"https://app.example.test/dashboard\");\n // The input URL is not mutated.\n expect(strippedUrl.searchParams.has(\"after_auth_return_to\")).toBe(false);\n\n // Explicit query params always win over the mirror.\n const explicitUrl = new URL(\"https://hosted.example.test/handler/sign-in?after_auth_return_to=/somewhere-else\");\n const unchanged = augmentUrlWithPersistedRedirectBackState({ currentUrl: explicitUrl, projectId });\n expect(unchanged.toString()).toBe(explicitUrl.toString());\n });\n\n it(\"leaves the URL unchanged when there is no mirrored state\", () => {\n const url = new URL(\"https://hosted.example.test/handler/sign-in\");\n const augmented = augmentUrlWithPersistedRedirectBackState({ currentUrl: url, projectId });\n expect(augmented.toString()).toBe(url.toString());\n });\n\n it(\"degrades to a no-op when sessionStorage is unavailable\", () => {\n Reflect.set(globalThis, \"window\", {\n get sessionStorage(): Storage {\n throw new Error(\"Storage is disabled\");\n },\n });\n const url = new URL(\"https://hosted.example.test/handler/sign-in?after_auth_return_to=/music\");\n saveRedirectBackStateFromUrl({ url, projectId });\n expect(readRedirectBackState({ projectId })).toBeNull();\n expect(augmentUrlWithPersistedRedirectBackState({ currentUrl: url, projectId }).toString()).toBe(url.toString());\n });\n});\n"],"mappings":"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"}
@@ -0,0 +1,8 @@
1
+ //#region src/lib/hexclave-app/apps/implementations/redirect-loop-breaker.d.ts
2
+ declare function recordRedirectAndThrowIfLoopDetected(options: {
3
+ currentUrl: URL;
4
+ targetUrl: URL;
5
+ }): void;
6
+ //#endregion
7
+ export { recordRedirectAndThrowIfLoopDetected };
8
+ //# sourceMappingURL=redirect-loop-breaker.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"redirect-loop-breaker.d.ts","names":[],"sources":["../../../../../src/lib/hexclave-app/apps/implementations/redirect-loop-breaker.ts"],"mappings":";iBAqFgB,oCAAA,CAAqC,OAAA;EAAW,UAAA,EAAY,GAAA;EAAK,SAAA,EAAW,GAAG;AAAA"}
@@ -0,0 +1,74 @@
1
+ Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
2
+ let _hexclave_shared_dist_utils_errors = require("@hexclave/shared/dist/utils/errors");
3
+ let ____________utils_session_storage_js = require("../../../../utils/session-storage.js");
4
+ //#region src/lib/hexclave-app/apps/implementations/redirect-loop-breaker.ts
5
+ /**
6
+ * Last line of defense against redirect loops (most importantly in hosted components flows, where
7
+ * redirects bounce between the app domain and the hosted domain and a bug anywhere in the chain
8
+ * can bounce the user forever).
9
+ *
10
+ * Every SDK-driven redirect records a breadcrumb, and when the exact same redirect keeps repeating
11
+ * within a short window, the redirect throws instead of navigating. The page that initiated the
12
+ * redirect then renders its error state (eg. the auth page's error card), which both stops the
13
+ * loop and surfaces the bug loudly.
14
+ */
15
+ const breadcrumbsStorageKey = "hexclave-redirect-loop-breadcrumbs";
16
+ const loopWindowMillis = 3e4;
17
+ const maxIdenticalRedirectsPerWindow = 5;
18
+ function isRedirectBreadcrumb(value) {
19
+ return typeof value === "object" && value !== null && "from" in value && typeof value.from === "string" && "to" in value && typeof value.to === "string" && "at" in value && typeof value.at === "number";
20
+ }
21
+ function readBreadcrumbs() {
22
+ const raw = (0, ____________utils_session_storage_js.readSessionStorageItem)(breadcrumbsStorageKey);
23
+ if (raw == null) return [];
24
+ let parsed;
25
+ try {
26
+ parsed = JSON.parse(raw);
27
+ } catch {
28
+ (0, ____________utils_session_storage_js.removeSessionStorageItem)(breadcrumbsStorageKey);
29
+ return [];
30
+ }
31
+ if (!Array.isArray(parsed)) {
32
+ (0, ____________utils_session_storage_js.removeSessionStorageItem)(breadcrumbsStorageKey);
33
+ return [];
34
+ }
35
+ return parsed.filter(isRedirectBreadcrumb);
36
+ }
37
+ function writeBreadcrumbs(breadcrumbs) {
38
+ (0, ____________utils_session_storage_js.writeSessionStorageItem)(breadcrumbsStorageKey, JSON.stringify(breadcrumbs));
39
+ }
40
+ /**
41
+ * Loop iterations usually differ only in nonce-style query params (`code`, `state`,
42
+ * `after_auth_return_to`, ...), so redirects are compared by origin + pathname only.
43
+ */
44
+ function getComparableRedirectLocation(url) {
45
+ return `${url.origin}${url.pathname}`;
46
+ }
47
+ function recordRedirectAndThrowIfLoopDetected(options) {
48
+ const from = getComparableRedirectLocation(options.currentUrl);
49
+ const to = getComparableRedirectLocation(options.targetUrl);
50
+ const now = Date.now();
51
+ const recentBreadcrumbs = readBreadcrumbs().filter((b) => now - b.at >= 0 && now - b.at < loopWindowMillis);
52
+ const identicalRedirectCount = recentBreadcrumbs.filter((b) => b.from === from && b.to === to).length + 1;
53
+ if (identicalRedirectCount >= maxIdenticalRedirectsPerWindow) {
54
+ writeBreadcrumbs(recentBreadcrumbs.filter((b) => !(b.from === from && b.to === to)));
55
+ const error = new _hexclave_shared_dist_utils_errors.HexclaveAssertionError(`Redirect loop detected: the redirect ${from} -> ${to} was attempted ${identicalRedirectCount} times within ${loopWindowMillis / 1e3}s, so it was blocked. This is a bug in the auth flow; the recent redirect chain is included in the error details.`, {
56
+ from,
57
+ to,
58
+ currentUrl: options.currentUrl.toString(),
59
+ targetUrl: options.targetUrl.toString(),
60
+ recentRedirects: recentBreadcrumbs.map((b) => `${b.from} -> ${b.to}`)
61
+ });
62
+ (0, _hexclave_shared_dist_utils_errors.captureError)("redirect-loop-detected", error);
63
+ throw error;
64
+ }
65
+ writeBreadcrumbs([...recentBreadcrumbs, {
66
+ from,
67
+ to,
68
+ at: now
69
+ }].slice(-50));
70
+ }
71
+ //#endregion
72
+ exports.recordRedirectAndThrowIfLoopDetected = recordRedirectAndThrowIfLoopDetected;
73
+
74
+ //# sourceMappingURL=redirect-loop-breaker.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"redirect-loop-breaker.js","names":["HexclaveAssertionError"],"sources":["../../../../../src/lib/hexclave-app/apps/implementations/redirect-loop-breaker.ts"],"sourcesContent":["\n//===========================================\n// THIS FILE IS AUTO-GENERATED FROM TEMPLATE. DO NOT EDIT IT DIRECTLY UNLESS YOU ALSO EDIT THE CORRESPONDING FILE IN packages/template\n//===========================================\nimport { HexclaveAssertionError, captureError } from \"@hexclave/shared/dist/utils/errors\";\nimport { readSessionStorageItem, removeSessionStorageItem, writeSessionStorageItem } from \"../../../../utils/session-storage\";\n\n/**\n * Last line of defense against redirect loops (most importantly in hosted components flows, where\n * redirects bounce between the app domain and the hosted domain and a bug anywhere in the chain\n * can bounce the user forever).\n *\n * Every SDK-driven redirect records a breadcrumb, and when the exact same redirect keeps repeating\n * within a short window, the redirect throws instead of navigating. The page that initiated the\n * redirect then renders its error state (eg. the auth page's error card), which both stops the\n * loop and surfaces the bug loudly.\n */\n\n// sessionStorage (not module state) because most redirect loops involve full page loads, so each\n// loop iteration runs in a fresh JS context. sessionStorage is also per-tab, which is exactly the\n// scope of a redirect loop.\nconst breadcrumbsStorageKey = \"hexclave-redirect-loop-breadcrumbs\";\n\n// A redirect loop cycles within a few seconds per iteration even with full cross-domain page\n// loads, so a real loop reaches 5 identical redirects well within 30 seconds. A human bouncing\n// between pages (eg. toggling sign-in <-> sign-up) produces the same (from, to) pair far less\n// often, which keeps false positives implausible.\nconst loopWindowMillis = 30_000;\nconst maxIdenticalRedirectsPerWindow = 5;\nconst maxStoredBreadcrumbs = 50;\n\ntype RedirectBreadcrumb = {\n from: string,\n to: string,\n /**\n * Wall-clock milliseconds (Date.now()). Although this is used to measure elapsed time (where we\n * would normally use performance.now()), breadcrumbs must be comparable across full page loads,\n * and performance.now() restarts at zero on every navigation.\n */\n at: number,\n};\n\nfunction isRedirectBreadcrumb(value: unknown): value is RedirectBreadcrumb {\n return (\n typeof value === \"object\"\n && value !== null\n && \"from\" in value && typeof value.from === \"string\"\n && \"to\" in value && typeof value.to === \"string\"\n && \"at\" in value && typeof value.at === \"number\"\n );\n}\n\nfunction readBreadcrumbs(): RedirectBreadcrumb[] {\n const raw = readSessionStorageItem(breadcrumbsStorageKey);\n if (raw == null) {\n return [];\n }\n let parsed: unknown;\n try {\n parsed = JSON.parse(raw);\n } catch {\n // A corrupted value (eg. written by an older SDK version) just means we lose loop protection\n // for this one redirect; start over with a fresh list.\n removeSessionStorageItem(breadcrumbsStorageKey);\n return [];\n }\n if (!Array.isArray(parsed)) {\n removeSessionStorageItem(breadcrumbsStorageKey);\n return [];\n }\n return parsed.filter(isRedirectBreadcrumb);\n}\n\nfunction writeBreadcrumbs(breadcrumbs: RedirectBreadcrumb[]): void {\n writeSessionStorageItem(breadcrumbsStorageKey, JSON.stringify(breadcrumbs));\n}\n\n/**\n * Loop iterations usually differ only in nonce-style query params (`code`, `state`,\n * `after_auth_return_to`, ...), so redirects are compared by origin + pathname only.\n */\nfunction getComparableRedirectLocation(url: URL): string {\n return `${url.origin}${url.pathname}`;\n}\n\nexport function recordRedirectAndThrowIfLoopDetected(options: { currentUrl: URL, targetUrl: URL }): void {\n const from = getComparableRedirectLocation(options.currentUrl);\n const to = getComparableRedirectLocation(options.targetUrl);\n const now = Date.now();\n\n // Clock jumps backwards (eg. NTP sync) would make breadcrumbs appear to be from the future;\n // treat those as expired rather than counting them towards a loop.\n const recentBreadcrumbs = readBreadcrumbs().filter((b) => now - b.at >= 0 && now - b.at < loopWindowMillis);\n const identicalRedirectCount = recentBreadcrumbs.filter((b) => b.from === from && b.to === to).length + 1;\n\n if (identicalRedirectCount >= maxIdenticalRedirectsPerWindow) {\n // Drop the matching breadcrumbs so a manual retry (eg. the user reloading and clicking sign-in\n // again) isn't instantly blocked by the history of the loop we just broke.\n writeBreadcrumbs(recentBreadcrumbs.filter((b) => !(b.from === from && b.to === to)));\n const error = new HexclaveAssertionError(\n `Redirect loop detected: the redirect ${from} -> ${to} was attempted ${identicalRedirectCount} times within ${loopWindowMillis / 1000}s, so it was blocked. This is a bug in the auth flow; the recent redirect chain is included in the error details.`,\n {\n from,\n to,\n currentUrl: options.currentUrl.toString(),\n targetUrl: options.targetUrl.toString(),\n recentRedirects: recentBreadcrumbs.map((b) => `${b.from} -> ${b.to}`),\n },\n );\n // Also captured (not just thrown) so the loop is reported even if a caller maps the thrown\n // error to UI without reporting it.\n captureError(\"redirect-loop-detected\", error);\n throw error;\n }\n\n writeBreadcrumbs([...recentBreadcrumbs, { from, to, at: now }].slice(-maxStoredBreadcrumbs));\n}\n"],"mappings":";;;;;;;;;;;;;;AAqBA,MAAM,wBAAwB;AAM9B,MAAM,mBAAmB;AACzB,MAAM,iCAAiC;AAcvC,SAAS,qBAAqB,OAA6C;CACzE,OACE,OAAO,UAAU,YACd,UAAU,QACV,UAAU,SAAS,OAAO,MAAM,SAAS,YACzC,QAAQ,SAAS,OAAO,MAAM,OAAO,YACrC,QAAQ,SAAS,OAAO,MAAM,OAAO;AAE5C;AAEA,SAAS,kBAAwC;CAC/C,MAAM,OAAA,GAAA,qCAAA,uBAAA,CAA6B,qBAAqB;CACxD,IAAI,OAAO,MACT,OAAO,CAAC;CAEV,IAAI;CACJ,IAAI;EACF,SAAS,KAAK,MAAM,GAAG;CACzB,QAAQ;EAGN,CAAA,GAAA,qCAAA,yBAAA,CAAyB,qBAAqB;EAC9C,OAAO,CAAC;CACV;CACA,IAAI,CAAC,MAAM,QAAQ,MAAM,GAAG;EAC1B,CAAA,GAAA,qCAAA,yBAAA,CAAyB,qBAAqB;EAC9C,OAAO,CAAC;CACV;CACA,OAAO,OAAO,OAAO,oBAAoB;AAC3C;AAEA,SAAS,iBAAiB,aAAyC;CACjE,CAAA,GAAA,qCAAA,wBAAA,CAAwB,uBAAuB,KAAK,UAAU,WAAW,CAAC;AAC5E;;;;;AAMA,SAAS,8BAA8B,KAAkB;CACvD,OAAO,GAAG,IAAI,SAAS,IAAI;AAC7B;AAEA,SAAgB,qCAAqC,SAAoD;CACvG,MAAM,OAAO,8BAA8B,QAAQ,UAAU;CAC7D,MAAM,KAAK,8BAA8B,QAAQ,SAAS;CAC1D,MAAM,MAAM,KAAK,IAAI;CAIrB,MAAM,oBAAoB,gBAAgB,CAAC,CAAC,QAAQ,MAAM,MAAM,EAAE,MAAM,KAAK,MAAM,EAAE,KAAK,gBAAgB;CAC1G,MAAM,yBAAyB,kBAAkB,QAAQ,MAAM,EAAE,SAAS,QAAQ,EAAE,OAAO,EAAE,CAAC,CAAC,SAAS;CAExG,IAAI,0BAA0B,gCAAgC;EAG5D,iBAAiB,kBAAkB,QAAQ,MAAM,EAAE,EAAE,SAAS,QAAQ,EAAE,OAAO,GAAG,CAAC;EACnF,MAAM,QAAQ,IAAIA,mCAAAA,uBAChB,wCAAwC,KAAK,MAAM,GAAG,iBAAiB,uBAAuB,gBAAgB,mBAAmB,IAAK,oHACtI;GACE;GACA;GACA,YAAY,QAAQ,WAAW,SAAS;GACxC,WAAW,QAAQ,UAAU,SAAS;GACtC,iBAAiB,kBAAkB,KAAK,MAAM,GAAG,EAAE,KAAK,MAAM,EAAE,IAAI;EACtE,CACF;EAGA,CAAA,GAAA,mCAAA,aAAA,CAAa,0BAA0B,KAAK;EAC5C,MAAM;CACR;CAEA,iBAAiB,CAAC,GAAG,mBAAmB;EAAE;EAAM;EAAI,IAAI;CAAI,CAAC,CAAC,CAAC,MAAM,GAAqB,CAAC;AAC7F"}