@hexclave/js 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.
- package/dist/{admin-app-impl-Du4dsPO9.d.ts → admin-app-impl-DHs4il4z.d.ts} +5 -2
- package/dist/{admin-app-impl-Du4dsPO9.d.ts.map → admin-app-impl-DHs4il4z.d.ts.map} +1 -1
- package/dist/clickmap/clickmap-core.d.ts +1 -1
- package/dist/clickmap/index.d.ts +1 -1
- package/dist/{client-app-BgdvwktS.d.ts → client-app-BoOM8rxo.d.ts} +2 -1
- package/dist/{client-app-BgdvwktS.d.ts.map → client-app-BoOM8rxo.d.ts.map} +1 -1
- package/dist/common-CRu2FsO9.d.ts.map +1 -1
- package/dist/dev-tool/dev-tool-core.d.ts +1 -1
- package/dist/dev-tool/dev-tool-core.d.ts.map +1 -1
- package/dist/dev-tool/dev-tool-core.js +58 -28
- package/dist/dev-tool/dev-tool-core.js.map +1 -1
- package/dist/dev-tool/dev-tool-styles.js +35 -0
- package/dist/dev-tool/dev-tool-styles.js.map +1 -1
- package/dist/dev-tool/index.d.ts +1 -1
- package/dist/esm/dev-tool/dev-tool-core.d.ts.map +1 -1
- package/dist/esm/dev-tool/dev-tool-core.js +58 -28
- package/dist/esm/dev-tool/dev-tool-core.js.map +1 -1
- package/dist/esm/dev-tool/dev-tool-styles.js +35 -0
- package/dist/esm/dev-tool/dev-tool-styles.js.map +1 -1
- package/dist/esm/lib/hexclave-app/apps/implementations/admin-app-impl.d.ts +1 -0
- package/dist/esm/lib/hexclave-app/apps/implementations/admin-app-impl.d.ts.map +1 -1
- package/dist/esm/lib/hexclave-app/apps/implementations/client-app-impl.cross-domain.test.js +97 -0
- package/dist/esm/lib/hexclave-app/apps/implementations/client-app-impl.cross-domain.test.js.map +1 -1
- package/dist/esm/lib/hexclave-app/apps/implementations/client-app-impl.d.ts +1 -0
- package/dist/esm/lib/hexclave-app/apps/implementations/client-app-impl.d.ts.map +1 -1
- package/dist/esm/lib/hexclave-app/apps/implementations/client-app-impl.js +26 -1
- package/dist/esm/lib/hexclave-app/apps/implementations/client-app-impl.js.map +1 -1
- package/dist/esm/lib/hexclave-app/apps/implementations/common.d.ts.map +1 -1
- package/dist/esm/lib/hexclave-app/apps/implementations/common.js +21 -4
- package/dist/esm/lib/hexclave-app/apps/implementations/common.js.map +1 -1
- package/dist/esm/lib/hexclave-app/apps/implementations/redirect-back-state.d.ts +37 -0
- package/dist/esm/lib/hexclave-app/apps/implementations/redirect-back-state.d.ts.map +1 -0
- package/dist/esm/lib/hexclave-app/apps/implementations/redirect-back-state.js +89 -0
- package/dist/esm/lib/hexclave-app/apps/implementations/redirect-back-state.js.map +1 -0
- package/dist/esm/lib/hexclave-app/apps/implementations/redirect-back-state.test.d.ts +1 -0
- package/dist/esm/lib/hexclave-app/apps/implementations/redirect-back-state.test.js +134 -0
- package/dist/esm/lib/hexclave-app/apps/implementations/redirect-back-state.test.js.map +1 -0
- package/dist/esm/lib/hexclave-app/apps/implementations/redirect-loop-breaker.d.ts +8 -0
- package/dist/esm/lib/hexclave-app/apps/implementations/redirect-loop-breaker.d.ts.map +1 -0
- package/dist/esm/lib/hexclave-app/apps/implementations/redirect-loop-breaker.js +73 -0
- package/dist/esm/lib/hexclave-app/apps/implementations/redirect-loop-breaker.js.map +1 -0
- package/dist/esm/lib/hexclave-app/apps/implementations/redirect-loop-breaker.test.d.ts +1 -0
- package/dist/esm/lib/hexclave-app/apps/implementations/redirect-loop-breaker.test.js +92 -0
- package/dist/esm/lib/hexclave-app/apps/implementations/redirect-loop-breaker.test.js.map +1 -0
- package/dist/esm/lib/hexclave-app/apps/implementations/server-app-impl.d.ts +1 -0
- package/dist/esm/lib/hexclave-app/apps/implementations/server-app-impl.d.ts.map +1 -1
- package/dist/esm/lib/hexclave-app/apps/implementations/server-app-impl.js +24 -3
- package/dist/esm/lib/hexclave-app/apps/implementations/server-app-impl.js.map +1 -1
- package/dist/esm/lib/hexclave-app/apps/interfaces/client-app.d.ts +1 -0
- package/dist/esm/lib/hexclave-app/apps/interfaces/client-app.d.ts.map +1 -1
- package/dist/esm/lib/hexclave-app/apps/interfaces/client-app.js.map +1 -1
- package/dist/esm/pushed-config-error-overlay/index.js +1 -1
- package/dist/esm/pushed-config-error-overlay/index.js.map +1 -1
- package/dist/esm/pushed-config-error-overlay/index.test.js +1 -1
- package/dist/esm/utils/session-storage.d.ts +12 -0
- package/dist/esm/utils/session-storage.d.ts.map +1 -0
- package/dist/esm/utils/session-storage.js +30 -0
- package/dist/esm/utils/session-storage.js.map +1 -0
- package/dist/index.d.ts +1 -1
- package/dist/lib/hexclave-app/apps/implementations/admin-app-impl.d.ts +1 -1
- package/dist/lib/hexclave-app/apps/implementations/client-app-impl.cross-domain.test.js +97 -0
- package/dist/lib/hexclave-app/apps/implementations/client-app-impl.cross-domain.test.js.map +1 -1
- package/dist/lib/hexclave-app/apps/implementations/client-app-impl.d.ts +1 -1
- package/dist/lib/hexclave-app/apps/implementations/client-app-impl.js +26 -1
- package/dist/lib/hexclave-app/apps/implementations/client-app-impl.js.map +1 -1
- package/dist/lib/hexclave-app/apps/implementations/common.js +21 -4
- package/dist/lib/hexclave-app/apps/implementations/common.js.map +1 -1
- package/dist/lib/hexclave-app/apps/implementations/index.d.ts +1 -1
- package/dist/lib/hexclave-app/apps/implementations/redirect-back-state.d.ts +37 -0
- package/dist/lib/hexclave-app/apps/implementations/redirect-back-state.d.ts.map +1 -0
- package/dist/lib/hexclave-app/apps/implementations/redirect-back-state.js +92 -0
- package/dist/lib/hexclave-app/apps/implementations/redirect-back-state.js.map +1 -0
- package/dist/lib/hexclave-app/apps/implementations/redirect-back-state.test.d.ts +1 -0
- package/dist/lib/hexclave-app/apps/implementations/redirect-back-state.test.js +133 -0
- package/dist/lib/hexclave-app/apps/implementations/redirect-back-state.test.js.map +1 -0
- package/dist/lib/hexclave-app/apps/implementations/redirect-loop-breaker.d.ts +8 -0
- package/dist/lib/hexclave-app/apps/implementations/redirect-loop-breaker.d.ts.map +1 -0
- package/dist/lib/hexclave-app/apps/implementations/redirect-loop-breaker.js +74 -0
- package/dist/lib/hexclave-app/apps/implementations/redirect-loop-breaker.js.map +1 -0
- package/dist/lib/hexclave-app/apps/implementations/redirect-loop-breaker.test.d.ts +1 -0
- package/dist/lib/hexclave-app/apps/implementations/redirect-loop-breaker.test.js +91 -0
- package/dist/lib/hexclave-app/apps/implementations/redirect-loop-breaker.test.js.map +1 -0
- package/dist/lib/hexclave-app/apps/implementations/server-app-impl.d.ts +1 -1
- package/dist/lib/hexclave-app/apps/implementations/server-app-impl.js +24 -3
- package/dist/lib/hexclave-app/apps/implementations/server-app-impl.js.map +1 -1
- package/dist/lib/hexclave-app/apps/index.d.ts +1 -1
- package/dist/lib/hexclave-app/apps/interfaces/admin-app.d.ts +1 -1
- package/dist/lib/hexclave-app/apps/interfaces/client-app.d.ts +1 -1
- package/dist/lib/hexclave-app/apps/interfaces/client-app.js.map +1 -1
- package/dist/lib/hexclave-app/apps/interfaces/server-app.d.ts +1 -1
- package/dist/lib/hexclave-app/index.d.ts +1 -1
- package/dist/lib/hexclave-app/projects/index.d.ts +1 -1
- package/dist/lib/hexclave-app/teams/index.d.ts +1 -1
- package/dist/lib/hexclave-app/users/index.d.ts +1 -1
- package/dist/pushed-config-error-overlay/index.d.ts +1 -1
- package/dist/pushed-config-error-overlay/index.js +1 -1
- package/dist/pushed-config-error-overlay/index.js.map +1 -1
- package/dist/pushed-config-error-overlay/index.test.js +1 -1
- package/dist/utils/session-storage.d.ts +12 -0
- package/dist/utils/session-storage.d.ts.map +1 -0
- package/dist/utils/session-storage.js +33 -0
- package/dist/utils/session-storage.js.map +1 -0
- package/package.json +2 -2
- package/src/dev-tool/dev-tool-core.ts +100 -35
- package/src/dev-tool/dev-tool-styles.ts +35 -0
- package/src/lib/hexclave-app/apps/implementations/client-app-impl.cross-domain.test.ts +127 -0
- package/src/lib/hexclave-app/apps/implementations/client-app-impl.ts +34 -1
- package/src/lib/hexclave-app/apps/implementations/common.ts +18 -1
- package/src/lib/hexclave-app/apps/implementations/redirect-back-state.test.ts +134 -0
- package/src/lib/hexclave-app/apps/implementations/redirect-back-state.ts +140 -0
- package/src/lib/hexclave-app/apps/implementations/redirect-loop-breaker.test.ts +127 -0
- package/src/lib/hexclave-app/apps/implementations/redirect-loop-breaker.ts +117 -0
- package/src/lib/hexclave-app/apps/implementations/server-app-impl.ts +27 -2
- package/src/lib/hexclave-app/apps/interfaces/client-app.ts +1 -0
- package/src/pushed-config-error-overlay/index.ts +1 -1
- package/src/utils/session-storage.ts +45 -0
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import { _ as adminProjectUpdateOptionsToCrud, d as AdminProjectCreateOptions, f as AdminProjectUpdateOptions, g as adminProjectCreateOptionsToCrud, h as PushedConfigSource, l as AdminOwnedProject, m as PushConfigOptions, p as Project, u as AdminProject } from "../../../client-app-
|
|
1
|
+
import { _ as adminProjectUpdateOptionsToCrud, d as AdminProjectCreateOptions, f as AdminProjectUpdateOptions, g as adminProjectCreateOptionsToCrud, h as PushedConfigSource, l as AdminOwnedProject, m as PushConfigOptions, p as Project, u as AdminProject } from "../../../client-app-BoOM8rxo.js";
|
|
2
2
|
export { AdminOwnedProject, AdminProject, AdminProjectCreateOptions, AdminProjectUpdateOptions, Project, PushConfigOptions, PushedConfigSource, adminProjectCreateOptionsToCrud, adminProjectUpdateOptionsToCrud };
|
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import { $ as serverTeamCreateOptionsToCrud, B as ServerListUsersOptions, G as ServerTeamUser, H as ServerTeamCreateOptions, I as EditableTeamMemberProfile, J as TeamInvitation, K as Team, L as ReceivedTeamInvitation, Q as TeamUser, R as SentTeamInvitation, U as ServerTeamMemberProfile, V as ServerTeam, W as ServerTeamUpdateOptions, X as TeamMemberProfileUpdateOptions, Y as TeamMemberProfile, Z as TeamUpdateOptions, et as serverTeamUpdateOptionsToCrud, nt as teamUpdateOptionsToCrud, q as TeamCreateOptions, tt as teamCreateOptionsToCrud, z as ServerListTeamsOptions } from "../../../client-app-
|
|
1
|
+
import { $ as serverTeamCreateOptionsToCrud, B as ServerListUsersOptions, G as ServerTeamUser, H as ServerTeamCreateOptions, I as EditableTeamMemberProfile, J as TeamInvitation, K as Team, L as ReceivedTeamInvitation, Q as TeamUser, R as SentTeamInvitation, U as ServerTeamMemberProfile, V as ServerTeam, W as ServerTeamUpdateOptions, X as TeamMemberProfileUpdateOptions, Y as TeamMemberProfile, Z as TeamUpdateOptions, et as serverTeamUpdateOptionsToCrud, nt as teamUpdateOptionsToCrud, q as TeamCreateOptions, tt as teamCreateOptionsToCrud, z as ServerListTeamsOptions } from "../../../client-app-BoOM8rxo.js";
|
|
2
2
|
export { EditableTeamMemberProfile, ReceivedTeamInvitation, SentTeamInvitation, ServerListTeamsOptions, ServerListUsersOptions, ServerTeam, ServerTeamCreateOptions, ServerTeamMemberProfile, ServerTeamUpdateOptions, ServerTeamUser, Team, TeamCreateOptions, TeamInvitation, TeamMemberProfile, TeamMemberProfileUpdateOptions, TeamUpdateOptions, TeamUser, serverTeamCreateOptionsToCrud, serverTeamUpdateOptionsToCrud, teamCreateOptionsToCrud, teamUpdateOptionsToCrud };
|
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import { Ct as UserExtra, Dt as userUpdateOptionsToCrud, Et as serverUserUpdateOptionsToCrud, Ot as withUserDestructureGuard, St as User, Tt as serverUserCreateOptionsToCrud, _t as ServerUserCreateOptions, at as BaseUser, bt as SyncedPartialUser, ct as CurrentServerUser, dt as OAuthProvider, ft as ProjectCurrentServerUser, gt as ServerUser, ht as ServerOAuthProvider, it as Auth, lt as CurrentUser, mt as ServerBaseUser, ot as CurrentInternalServerUser, pt as ProjectCurrentUser, rt as ActiveSession, st as CurrentInternalUser, ut as InternalUserExtra, vt as ServerUserUpdateOptions, wt as UserUpdateOptions, xt as TokenPartialUser, yt as SyncedPartialServerUser } from "../../../client-app-
|
|
1
|
+
import { Ct as UserExtra, Dt as userUpdateOptionsToCrud, Et as serverUserUpdateOptionsToCrud, Ot as withUserDestructureGuard, St as User, Tt as serverUserCreateOptionsToCrud, _t as ServerUserCreateOptions, at as BaseUser, bt as SyncedPartialUser, ct as CurrentServerUser, dt as OAuthProvider, ft as ProjectCurrentServerUser, gt as ServerUser, ht as ServerOAuthProvider, it as Auth, lt as CurrentUser, mt as ServerBaseUser, ot as CurrentInternalServerUser, pt as ProjectCurrentUser, rt as ActiveSession, st as CurrentInternalUser, ut as InternalUserExtra, vt as ServerUserUpdateOptions, wt as UserUpdateOptions, xt as TokenPartialUser, yt as SyncedPartialServerUser } from "../../../client-app-BoOM8rxo.js";
|
|
2
2
|
export { ActiveSession, Auth, BaseUser, CurrentInternalServerUser, CurrentInternalUser, CurrentServerUser, CurrentUser, InternalUserExtra, OAuthProvider, ProjectCurrentServerUser, ProjectCurrentUser, ServerBaseUser, ServerOAuthProvider, ServerUser, ServerUserCreateOptions, ServerUserUpdateOptions, SyncedPartialServerUser, SyncedPartialUser, TokenPartialUser, User, UserExtra, UserUpdateOptions, serverUserCreateOptionsToCrud, serverUserUpdateOptionsToCrud, userUpdateOptionsToCrud, withUserDestructureGuard };
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { a as StackClientApp } from "../client-app-
|
|
1
|
+
import { a as StackClientApp } from "../client-app-BoOM8rxo.js";
|
|
2
2
|
//#region src/pushed-config-error-overlay/index.d.ts
|
|
3
3
|
declare function mountPushedConfigErrorOverlay(app: StackClientApp<true>): () => void;
|
|
4
4
|
//#endregion
|
|
@@ -329,7 +329,7 @@ function mountPushedConfigErrorOverlay(app) {
|
|
|
329
329
|
minimized = false;
|
|
330
330
|
return;
|
|
331
331
|
}
|
|
332
|
-
const issueMessage = issue.messages.join("\n");
|
|
332
|
+
const issueMessage = "Hexclave config " + issue.kind + ": " + issue.messages.join("\n");
|
|
333
333
|
const issueKey = `${app.projectId}:${issue.kind}:${issueMessage}`;
|
|
334
334
|
const issueLabel = issue.kind === "error" ? "error" : "warning";
|
|
335
335
|
const issueTitle = issue.kind === "error" ? "Your Hexclave config has been saved, but contains errors" : "Your Hexclave config has been saved, but has warnings";
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","names":["envVars"],"sources":["../../src/pushed-config-error-overlay/index.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//===========================================\n\nimport { captureError } from \"@hexclave/shared/dist/utils/errors\";\nimport { runAsynchronously } from \"@hexclave/shared/dist/utils/promises\";\nimport { isLocalhost } from \"@hexclave/shared/dist/utils/urls\";\nimport { envVars } from \"../generated/env\";\nimport { getInPageUiBaseCSS } from \"../in-page-ui/base-styles\";\nimport { canMountIntoDom, getGlobalUiInstance, h, setGlobalUiInstance, setHtml } from \"../in-page-ui/dom\";\nimport type { StackClientApp } from \"../lib/hexclave-app\";\n\nconst GLOBAL_INSTANCE_KEY = \"__hexclave-pushed-config-error-overlay\";\nconst MINIMIZED_STORAGE_KEY = \"hexclave-pushed-config-error-minimized-key\";\nconst REFRESH_INTERVAL_MS = 5_000;\nconst HEXCLAVE_LOGO_SVG = '<svg width=\"16\" height=\"16\" viewBox=\"0 0 48 48\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"3\" stroke-linejoin=\"miter\"><path d=\"M 24 4 L 41.32 14 L 41.32 34 L 24 44 L 6.68 34 L 6.68 14 Z\"/><path d=\"M 11 16.87 L 14 15.13 L 14 32.87 L 11 31.13 Z\" fill=\"currentColor\" stroke=\"none\"/><path d=\"M 11 16.87 L 14 15.13 L 14 32.87 L 11 31.13 Z\" fill=\"currentColor\" stroke=\"none\" transform=\"rotate(120 24 24)\"/><path d=\"M 11 16.87 L 14 15.13 L 14 32.87 L 11 31.13 Z\" fill=\"currentColor\" stroke=\"none\" transform=\"rotate(240 24 24)\"/></svg>';\nconst COPY_ICON_SVG = '<svg width=\"12\" height=\"12\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2.25\" stroke-linecap=\"round\" stroke-linejoin=\"round\"><rect x=\"9\" y=\"9\" width=\"13\" height=\"13\" rx=\"2\"/><path d=\"M5 15H4a2 2 0 0 1-2-2V4a2 2 0 0 1 2-2h9a2 2 0 0 1 2 2v1\"/></svg>';\n\ntype ConfigIssue = {\n kind: \"error\" | \"warning\",\n messages: string[],\n};\n\nconst css = getInPageUiBaseCSS(\".hexclave-config-error-overlay\") + `\n .hexclave-config-error-overlay .hce-backdrop {\n position: fixed;\n inset: 0;\n z-index: 2147483647;\n display: flex;\n align-items: center;\n justify-content: center;\n padding: 24px;\n background: rgba(0, 0, 0, 0.46);\n backdrop-filter: blur(6px);\n overflow: auto;\n }\n\n .hexclave-config-error-overlay .hce-card {\n --hce-status: var(--sdt-error);\n width: min(720px, calc(100vw - 32px));\n max-height: min(640px, calc(100dvh - 48px));\n border: 1px solid color-mix(in srgb, var(--hce-status) 35%, var(--sdt-border));\n border-radius: 18px;\n background: var(--sdt-overlay-bg);\n box-shadow: var(--sdt-shadow);\n backdrop-filter: blur(18px);\n display: flex;\n overflow: hidden;\n }\n\n .hexclave-config-error-overlay .hce-card-warning {\n --hce-status: var(--sdt-warning);\n }\n\n .hexclave-config-error-overlay .hce-card-inner {\n padding: 18px;\n width: 100%;\n overflow: auto;\n }\n\n .hexclave-config-error-overlay .hce-header {\n display: flex;\n align-items: flex-start;\n justify-content: space-between;\n gap: 12px;\n margin-bottom: 12px;\n }\n\n .hexclave-config-error-overlay .hce-title-row {\n display: flex;\n align-items: flex-start;\n gap: 10px;\n min-width: 0;\n }\n\n .hexclave-config-error-overlay .hce-logo {\n flex-shrink: 0;\n display: flex;\n align-items: center;\n justify-content: center;\n width: 34px;\n height: 34px;\n border-radius: 10px;\n background: var(--hce-status);\n color: white;\n box-shadow: 0 10px 30px color-mix(in srgb, var(--hce-status) 32%, transparent);\n }\n\n .hexclave-config-error-overlay .hce-badge {\n display: inline-flex;\n flex-shrink: 0;\n padding: 2px 6px;\n border-radius: 999px;\n background: var(--hce-status);\n color: white;\n font-size: 10px;\n font-weight: 700;\n letter-spacing: 0.06em;\n text-transform: uppercase;\n }\n\n .hexclave-config-error-overlay .hce-title {\n color: var(--sdt-text);\n margin-top: 4px;\n font-size: 18px;\n font-weight: 700;\n line-height: 1.25;\n }\n\n .hexclave-config-error-overlay .hce-actions {\n display: flex;\n gap: 4px;\n }\n\n .hexclave-config-error-overlay .hce-icon-btn {\n display: inline-flex;\n align-items: center;\n justify-content: center;\n width: 28px;\n height: 28px;\n border: 1px solid var(--sdt-border);\n border-radius: 8px;\n background: var(--sdt-bg-elevated);\n color: var(--sdt-text-secondary);\n cursor: pointer;\n font: inherit;\n line-height: 1;\n vertical-align: top;\n }\n\n .hexclave-config-error-overlay .hce-icon-btn svg {\n display: block;\n flex-shrink: 0;\n }\n\n .hexclave-config-error-overlay .hce-text-btn {\n align-items: center;\n gap: 6px;\n min-height: 28px;\n padding: 0 10px;\n width: auto;\n font-size: 12px;\n line-height: 1;\n }\n\n .hexclave-config-error-overlay .hce-icon-btn:hover {\n background: var(--sdt-bg-hover);\n color: var(--sdt-text);\n }\n\n .hexclave-config-error-overlay .hce-body {\n color: var(--sdt-text-secondary);\n font-size: 14px;\n line-height: 1.5;\n }\n\n .hexclave-config-error-overlay .hce-message-header {\n display: flex;\n align-items: center;\n justify-content: space-between;\n gap: 10px;\n margin-top: 14px;\n margin-bottom: 8px;\n }\n\n .hexclave-config-error-overlay .hce-message-label {\n color: var(--sdt-text);\n font-size: 12px;\n font-weight: 650;\n }\n\n .hexclave-config-error-overlay .hce-message {\n padding: 12px;\n max-height: min(260px, max(96px, 30dvh));\n overflow: auto;\n border: 1px solid var(--sdt-border-subtle);\n border-radius: 10px;\n background: var(--sdt-bg-subtle);\n color: var(--sdt-text);\n font-family: var(--sdt-font-mono);\n font-size: 12px;\n white-space: pre-wrap;\n overflow-wrap: anywhere;\n }\n\n .hexclave-config-error-overlay .hce-footer {\n margin-top: 10px;\n color: var(--sdt-text-tertiary);\n font-size: 12px;\n }\n\n .hexclave-config-error-overlay .hce-pill {\n position: fixed;\n right: 18px;\n bottom: 18px;\n z-index: 2147483647;\n display: flex;\n align-items: center;\n gap: 8px;\n padding: 8px 12px 8px 8px;\n --hce-status: var(--sdt-error);\n border: 1px solid color-mix(in srgb, var(--hce-status) 35%, var(--sdt-border));\n border-radius: 999px;\n background: var(--sdt-overlay-bg);\n box-shadow: var(--sdt-trigger-shadow);\n color: var(--sdt-text);\n cursor: pointer;\n font: inherit;\n backdrop-filter: blur(18px);\n }\n\n .hexclave-config-error-overlay .hce-pill-warning {\n --hce-status: var(--sdt-warning);\n }\n\n .hexclave-config-error-overlay .hce-pill-logo {\n display: flex;\n align-items: center;\n justify-content: center;\n width: 26px;\n height: 26px;\n border-radius: 999px;\n background: var(--hce-status);\n color: white;\n }\n\n @media (max-height: 520px) {\n .hexclave-config-error-overlay .hce-backdrop {\n align-items: flex-start;\n padding: 12px;\n }\n\n .hexclave-config-error-overlay .hce-card {\n width: min(720px, calc(100vw - 24px));\n max-height: calc(100dvh - 24px);\n }\n\n .hexclave-config-error-overlay .hce-card-inner {\n padding: 12px;\n }\n\n .hexclave-config-error-overlay .hce-header {\n margin-bottom: 8px;\n }\n\n .hexclave-config-error-overlay .hce-title {\n font-size: 16px;\n }\n\n .hexclave-config-error-overlay .hce-body {\n font-size: 13px;\n }\n\n .hexclave-config-error-overlay .hce-message {\n max-height: max(80px, 24dvh);\n }\n }\n`;\n\nfunction storageGet(key: string): string | null {\n try {\n return localStorage.getItem(key);\n } catch {\n return null;\n }\n}\n\nfunction storageSet(key: string, value: string): void {\n try {\n localStorage.setItem(key, value);\n } catch {\n // Storage may be unavailable in private or embedded contexts.\n }\n}\n\nfunction storageRemove(key: string): void {\n try {\n localStorage.removeItem(key);\n } catch {\n // Storage may be unavailable in private or embedded contexts.\n }\n}\n\nfunction shouldMount(): boolean {\n if (!canMountIntoDom()) {\n return false;\n }\n\n const nodeEnv = envVars.NODE_ENV;\n if (nodeEnv !== undefined) {\n return nodeEnv === \"development\";\n }\n\n try {\n const url = new URL(window.location.href);\n if (url.protocol === \"file:\") {\n return true;\n }\n } catch {\n return false;\n }\n return isLocalhost(window.location.href);\n}\n\nasync function copyTextToClipboard(text: string): Promise<void> {\n const clipboard: unknown = Reflect.get(navigator, \"clipboard\");\n const writeText = clipboard != null && typeof clipboard === \"object\"\n ? Reflect.get(clipboard, \"writeText\")\n : null;\n if (typeof writeText === \"function\") {\n await writeText.call(clipboard, text);\n return;\n }\n\n const textarea = h(\"textarea\", {\n style: {\n position: \"fixed\",\n left: \"-9999px\",\n top: \"0\",\n opacity: \"0\",\n },\n readonly: \"true\",\n }) as HTMLTextAreaElement;\n textarea.value = text;\n document.body.appendChild(textarea);\n textarea.select();\n const copied = document.execCommand(\"copy\");\n textarea.remove();\n if (!copied) {\n throw new Error(\"Browser refused to copy the config error message.\");\n }\n}\n\nfunction buildConfigIssueAiPrompt(issue: ConfigIssue): string {\n const issueLabel = issue.kind === \"error\" ? \"error\" : \"warning\";\n return [\n `Help me fix this Hexclave config ${issueLabel}.`,\n \"\",\n \"Hexclave reminder:\",\n `This ${issueLabel} comes from a pushed Hexclave config file. The app may keep running with the synced Hexclave config, but I need to fix the config file mentioned in the message and save it again so Hexclave can sync the config successfully and clear the dialog. Use your web fetch tool to read https://skill.hexclave.com to learn more about Hexclave.`,\n \"\",\n issue.kind === \"error\" ? \"Error message:\" : \"Warning message:\",\n issue.messages.join(\"\\n\"),\n \"\",\n \"Please explain the likely cause, identify the config key or value I should change, and suggest the smallest safe fix.\",\n ].join(\"\\n\");\n}\n\nexport function mountPushedConfigErrorOverlay(app: StackClientApp<true>): () => void {\n if (!shouldMount()) {\n return () => {};\n }\n\n getGlobalUiInstance(GLOBAL_INSTANCE_KEY)?.cleanup();\n\n const root = h(\"div\", { className: \"hexclave-config-error-overlay\" });\n const style = h(\"style\", null, css);\n root.appendChild(style);\n document.body.appendChild(root);\n\n let disposed = false;\n let lastErrorKey: string | null = null;\n let lastConsoleErrorKey: string | null = null;\n let minimized = false;\n\n const render = (issue: ConfigIssue | null) => {\n root.replaceChildren(style);\n if (issue == null) {\n lastErrorKey = null;\n minimized = false;\n return;\n }\n\n const issueMessage = issue.messages.join(\"\\n\");\n const issueKey = `${app.projectId}:${issue.kind}:${issueMessage}`;\n const issueLabel = issue.kind === \"error\" ? \"error\" : \"warning\";\n const issueTitle = issue.kind === \"error\"\n ? \"Your Hexclave config has been saved, but contains errors\"\n : \"Your Hexclave config has been saved, but has warnings\";\n const bodyText = issue.kind === \"error\"\n ? \"Your app can keep running, but Hexclave is still using the last valid config until this is fixed.\"\n : \"Your app can keep running, but part of your Hexclave config may not behave the way you expect until this is fixed.\";\n const footerText = issue.kind === \"error\"\n ? \"Fix the config file mentioned above and save it again. This message will disappear after the config sync succeeds.\"\n : \"Fix the config file mentioned above and save it again. This warning will disappear after Hexclave syncs a config without warnings.\";\n if (issueKey !== lastConsoleErrorKey) {\n lastConsoleErrorKey = issueKey;\n const consoleMessage = `[Hexclave] Config ${issueLabel}: ${issueMessage}`;\n if (issue.kind === \"error\") {\n console.error(consoleMessage);\n } else {\n console.warn(consoleMessage);\n }\n }\n\n if (issueKey !== lastErrorKey) {\n lastErrorKey = issueKey;\n minimized = storageGet(MINIMIZED_STORAGE_KEY) === issueKey;\n }\n\n if (minimized) {\n const logoSpan = h(\"span\", { className: \"hce-pill-logo\" });\n setHtml(logoSpan, HEXCLAVE_LOGO_SVG);\n root.appendChild(h(\"button\", {\n className: issue.kind === \"error\" ? \"hce-pill\" : \"hce-pill hce-pill-warning\",\n type: \"button\",\n onClick: () => {\n minimized = false;\n storageRemove(MINIMIZED_STORAGE_KEY);\n render(issue);\n },\n },\n logoSpan,\n h(\"span\", null, issue.kind === \"error\" ? \"Config error\" : \"Config warning\")));\n return;\n }\n\n const logoSpan = h(\"span\", { className: \"hce-logo\" });\n setHtml(logoSpan, HEXCLAVE_LOGO_SVG);\n const copyButton = h(\"button\", {\n className: \"hce-icon-btn hce-text-btn\",\n type: \"button\",\n title: issue.kind === \"error\" ? \"Copy error message\" : \"Copy warning message\",\n \"aria-label\": issue.kind === \"error\" ? \"Copy config error message\" : \"Copy config warning message\",\n onClick: () => {\n runAsynchronously(async () => {\n await copyTextToClipboard(issueMessage);\n copyButton.textContent = \"Copied\";\n setTimeout(() => {\n setHtml(copyButton, `${COPY_ICON_SVG}Copy`);\n }, 1500);\n }, {\n noErrorLogging: true,\n onError: (copyError) => {\n captureError(\"pushed-config-error-overlay-copy\", copyError);\n copyButton.textContent = \"Copy failed\";\n setTimeout(() => {\n setHtml(copyButton, `${COPY_ICON_SVG}Copy`);\n }, 1500);\n },\n });\n },\n });\n setHtml(copyButton, `${COPY_ICON_SVG}Copy`);\n\n const aiPromptCopyButton = h(\"button\", {\n className: \"hce-icon-btn\",\n type: \"button\",\n title: \"Copy AI prompt\",\n \"aria-label\": issue.kind === \"error\" ? \"Copy AI prompt for config error\" : \"Copy AI prompt for config warning\",\n onClick: () => {\n runAsynchronously(async () => {\n await copyTextToClipboard(buildConfigIssueAiPrompt(issue));\n aiPromptCopyButton.textContent = \"✓\";\n setTimeout(() => {\n setHtml(aiPromptCopyButton, COPY_ICON_SVG);\n }, 1500);\n }, {\n noErrorLogging: true,\n onError: (copyError) => {\n captureError(\"pushed-config-error-overlay-copy-ai-prompt\", copyError);\n aiPromptCopyButton.textContent = \"!\";\n setTimeout(() => {\n setHtml(aiPromptCopyButton, COPY_ICON_SVG);\n }, 1500);\n },\n });\n },\n });\n setHtml(aiPromptCopyButton, COPY_ICON_SVG);\n\n root.appendChild(h(\"div\", { className: \"hce-backdrop\" },\n h(\"div\", { className: issue.kind === \"error\" ? \"hce-card\" : \"hce-card hce-card-warning\", role: \"alertdialog\", \"aria-modal\": \"true\", \"aria-label\": `Hexclave config ${issueLabel}` },\n h(\"div\", { className: \"hce-card-inner\" },\n h(\"div\", { className: \"hce-header\" },\n h(\"div\", { className: \"hce-title-row\" },\n logoSpan,\n h(\"div\", null,\n h(\"span\", { className: \"hce-badge\" }, `Config ${issueLabel}`),\n h(\"div\", { className: \"hce-title\" }, issueTitle),\n ),\n ),\n h(\"div\", { className: \"hce-actions\" },\n aiPromptCopyButton,\n h(\"button\", {\n className: \"hce-icon-btn\",\n type: \"button\",\n title: \"Minimize\",\n \"aria-label\": issue.kind === \"error\" ? \"Minimize config error\" : \"Minimize config warning\",\n onClick: () => {\n minimized = true;\n storageSet(MINIMIZED_STORAGE_KEY, issueKey);\n render(issue);\n },\n }, \"–\"),\n ),\n ),\n h(\"div\", { className: \"hce-body\" },\n bodyText,\n h(\"div\", { className: \"hce-message-header\" },\n h(\"div\", { className: \"hce-message-label\" }, issue.kind === \"error\" ? \"Error message\" : \"Warning message\"),\n copyButton,\n ),\n h(\"div\", { className: \"hce-message\" }, issueMessage),\n h(\"div\", { className: \"hce-footer\" }, footerText),\n ),\n ),\n )));\n };\n\n const refresh = () => {\n if (disposed || !canMountIntoDom()) {\n return;\n }\n runAsynchronously(async () => {\n const project = await app.getProject();\n if (disposed) {\n return;\n }\n render(project.pushedConfigError == null\n ? project.configWarnings.length === 0\n ? null\n : { kind: \"warning\", messages: project.configWarnings.map((warning) => warning.message) }\n : { kind: \"error\", messages: [project.pushedConfigError.message] });\n }, {\n noErrorLogging: true,\n onError: (error) => {\n captureError(\"pushed-config-error-overlay-refresh\", error);\n },\n });\n };\n\n // This is mounted from the base client-app constructor, which also runs\n // before subclass field initializers. Defer the first app call so overridden\n // methods like adminApp.getProject() can safely touch subclass caches.\n queueMicrotask(refresh);\n const interval = setInterval(refresh, REFRESH_INTERVAL_MS);\n\n const cleanup = () => {\n disposed = true;\n clearInterval(interval);\n root.remove();\n if (getGlobalUiInstance(GLOBAL_INSTANCE_KEY)?.cleanup === cleanup) {\n setGlobalUiInstance(GLOBAL_INSTANCE_KEY, null);\n }\n };\n setGlobalUiInstance(GLOBAL_INSTANCE_KEY, { cleanup });\n return cleanup;\n}\n\n"],"mappings":";;;;;;;;AAaA,MAAM,sBAAsB;AAC5B,MAAM,wBAAwB;AAC9B,MAAM,sBAAsB;AAC5B,MAAM,oBAAoB;AAC1B,MAAM,gBAAgB;AAOtB,MAAM,OAAA,GAAA,6BAAA,mBAAA,CAAyB,gCAAgC,IAAI;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AA4OnE,SAAS,WAAW,KAA4B;CAC9C,IAAI;EACF,OAAO,aAAa,QAAQ,GAAG;CACjC,QAAQ;EACN,OAAO;CACT;AACF;AAEA,SAAS,WAAW,KAAa,OAAqB;CACpD,IAAI;EACF,aAAa,QAAQ,KAAK,KAAK;CACjC,QAAQ,CAER;AACF;AAEA,SAAS,cAAc,KAAmB;CACxC,IAAI;EACF,aAAa,WAAW,GAAG;CAC7B,QAAQ,CAER;AACF;AAEA,SAAS,cAAuB;CAC9B,IAAI,EAAA,GAAA,qBAAA,gBAAA,CAAiB,GACnB,OAAO;CAGT,MAAM,UAAUA,oBAAAA,QAAQ;CACxB,IAAI,YAAY,KAAA,GACd,OAAO,YAAY;CAGrB,IAAI;EAEF,IAAI,IADY,IAAI,OAAO,SAAS,IAC9B,CAAC,CAAC,aAAa,SACnB,OAAO;CAEX,QAAQ;EACN,OAAO;CACT;CACA,QAAA,GAAA,iCAAA,YAAA,CAAmB,OAAO,SAAS,IAAI;AACzC;AAEA,eAAe,oBAAoB,MAA6B;CAC9D,MAAM,YAAqB,QAAQ,IAAI,WAAW,WAAW;CAC7D,MAAM,YAAY,aAAa,QAAQ,OAAO,cAAc,WACxD,QAAQ,IAAI,WAAW,WAAW,IAClC;CACJ,IAAI,OAAO,cAAc,YAAY;EACnC,MAAM,UAAU,KAAK,WAAW,IAAI;EACpC;CACF;CAEA,MAAM,YAAA,GAAA,qBAAA,EAAA,CAAa,YAAY;EAC7B,OAAO;GACL,UAAU;GACV,MAAM;GACN,KAAK;GACL,SAAS;EACX;EACA,UAAU;CACZ,CAAC;CACD,SAAS,QAAQ;CACjB,SAAS,KAAK,YAAY,QAAQ;CAClC,SAAS,OAAO;CAChB,MAAM,SAAS,SAAS,YAAY,MAAM;CAC1C,SAAS,OAAO;CAChB,IAAI,CAAC,QACH,MAAM,IAAI,MAAM,mDAAmD;AAEvE;AAEA,SAAS,yBAAyB,OAA4B;CAC5D,MAAM,aAAa,MAAM,SAAS,UAAU,UAAU;CACtD,OAAO;EACL,oCAAoC,WAAW;EAC/C;EACA;EACA,QAAQ,WAAW;EACnB;EACA,MAAM,SAAS,UAAU,mBAAmB;EAC5C,MAAM,SAAS,KAAK,IAAI;EACxB;EACA;CACF,CAAC,CAAC,KAAK,IAAI;AACb;AAEA,SAAgB,8BAA8B,KAAuC;CACnF,IAAI,CAAC,YAAY,GACf,aAAa,CAAC;CAGhB,CAAA,GAAA,qBAAA,oBAAA,CAAoB,mBAAmB,CAAC,EAAE,QAAQ;CAElD,MAAM,QAAA,GAAA,qBAAA,EAAA,CAAS,OAAO,EAAE,WAAW,gCAAgC,CAAC;CACpE,MAAM,SAAA,GAAA,qBAAA,EAAA,CAAU,SAAS,MAAM,GAAG;CAClC,KAAK,YAAY,KAAK;CACtB,SAAS,KAAK,YAAY,IAAI;CAE9B,IAAI,WAAW;CACf,IAAI,eAA8B;CAClC,IAAI,sBAAqC;CACzC,IAAI,YAAY;CAEhB,MAAM,UAAU,UAA8B;EAC5C,KAAK,gBAAgB,KAAK;EAC1B,IAAI,SAAS,MAAM;GACjB,eAAe;GACf,YAAY;GACZ;EACF;EAEA,MAAM,eAAe,MAAM,SAAS,KAAK,IAAI;EAC7C,MAAM,WAAW,GAAG,IAAI,UAAU,GAAG,MAAM,KAAK,GAAG;EACnD,MAAM,aAAa,MAAM,SAAS,UAAU,UAAU;EACtD,MAAM,aAAa,MAAM,SAAS,UAC9B,6DACA;EACJ,MAAM,WAAW,MAAM,SAAS,UAC5B,sGACA;EACJ,MAAM,aAAa,MAAM,SAAS,UAC9B,uHACA;EACJ,IAAI,aAAa,qBAAqB;GACpC,sBAAsB;GACtB,MAAM,iBAAiB,qBAAqB,WAAW,IAAI;GAC3D,IAAI,MAAM,SAAS,SACjB,QAAQ,MAAM,cAAc;QAE5B,QAAQ,KAAK,cAAc;EAE/B;EAEA,IAAI,aAAa,cAAc;GAC7B,eAAe;GACf,YAAY,WAAW,qBAAqB,MAAM;EACpD;EAEA,IAAI,WAAW;GACb,MAAM,YAAA,GAAA,qBAAA,EAAA,CAAa,QAAQ,EAAE,WAAW,gBAAgB,CAAC;GACzD,CAAA,GAAA,qBAAA,QAAA,CAAQ,UAAU,iBAAiB;GACnC,KAAK,aAAA,GAAA,qBAAA,EAAA,CAAc,UAAU;IAC3B,WAAW,MAAM,SAAS,UAAU,aAAa;IACjD,MAAM;IACN,eAAe;KACb,YAAY;KACZ,cAAc,qBAAqB;KACnC,OAAO,KAAK;IACd;GACF,GACA,WAAA,GAAA,qBAAA,EAAA,CACE,QAAQ,MAAM,MAAM,SAAS,UAAU,iBAAiB,gBAAgB,CAAC,CAAC;GAC5E;EACF;EAEA,MAAM,YAAA,GAAA,qBAAA,EAAA,CAAa,QAAQ,EAAE,WAAW,WAAW,CAAC;EACpD,CAAA,GAAA,qBAAA,QAAA,CAAQ,UAAU,iBAAiB;EACnC,MAAM,cAAA,GAAA,qBAAA,EAAA,CAAe,UAAU;GAC7B,WAAW;GACX,MAAM;GACN,OAAO,MAAM,SAAS,UAAU,uBAAuB;GACvD,cAAc,MAAM,SAAS,UAAU,8BAA8B;GACrE,eAAe;IACb,CAAA,GAAA,qCAAA,kBAAA,CAAkB,YAAY;KAC5B,MAAM,oBAAoB,YAAY;KACtC,WAAW,cAAc;KACzB,iBAAiB;MACf,CAAA,GAAA,qBAAA,QAAA,CAAQ,YAAY,GAAG,cAAc,KAAK;KAC5C,GAAG,IAAI;IACT,GAAG;KACD,gBAAgB;KAChB,UAAU,cAAc;MACtB,CAAA,GAAA,mCAAA,aAAA,CAAa,oCAAoC,SAAS;MAC1D,WAAW,cAAc;MACzB,iBAAiB;OACf,CAAA,GAAA,qBAAA,QAAA,CAAQ,YAAY,GAAG,cAAc,KAAK;MAC5C,GAAG,IAAI;KACT;IACF,CAAC;GACH;EACF,CAAC;EACD,CAAA,GAAA,qBAAA,QAAA,CAAQ,YAAY,GAAG,cAAc,KAAK;EAE1C,MAAM,sBAAA,GAAA,qBAAA,EAAA,CAAuB,UAAU;GACrC,WAAW;GACX,MAAM;GACN,OAAO;GACP,cAAc,MAAM,SAAS,UAAU,oCAAoC;GAC3E,eAAe;IACb,CAAA,GAAA,qCAAA,kBAAA,CAAkB,YAAY;KAC5B,MAAM,oBAAoB,yBAAyB,KAAK,CAAC;KACzD,mBAAmB,cAAc;KACjC,iBAAiB;MACf,CAAA,GAAA,qBAAA,QAAA,CAAQ,oBAAoB,aAAa;KAC3C,GAAG,IAAI;IACT,GAAG;KACD,gBAAgB;KAChB,UAAU,cAAc;MACtB,CAAA,GAAA,mCAAA,aAAA,CAAa,8CAA8C,SAAS;MACpE,mBAAmB,cAAc;MACjC,iBAAiB;OACf,CAAA,GAAA,qBAAA,QAAA,CAAQ,oBAAoB,aAAa;MAC3C,GAAG,IAAI;KACT;IACF,CAAC;GACH;EACF,CAAC;EACD,CAAA,GAAA,qBAAA,QAAA,CAAQ,oBAAoB,aAAa;EAEzC,KAAK,aAAA,GAAA,qBAAA,EAAA,CAAc,OAAO,EAAE,WAAW,eAAe,IAAA,GAAA,qBAAA,EAAA,CAClD,OAAO;GAAE,WAAW,MAAM,SAAS,UAAU,aAAa;GAA6B,MAAM;GAAe,cAAc;GAAQ,cAAc,mBAAmB;EAAa,IAAA,GAAA,qBAAA,EAAA,CAChL,OAAO,EAAE,WAAW,iBAAiB,IAAA,GAAA,qBAAA,EAAA,CACnC,OAAO,EAAE,WAAW,aAAa,IAAA,GAAA,qBAAA,EAAA,CAC/B,OAAO,EAAE,WAAW,gBAAgB,GACpC,WAAA,GAAA,qBAAA,EAAA,CACE,OAAO,OAAA,GAAA,qBAAA,EAAA,CACL,QAAQ,EAAE,WAAW,YAAY,GAAG,UAAU,YAAY,IAAA,GAAA,qBAAA,EAAA,CAC1D,OAAO,EAAE,WAAW,YAAY,GAAG,UAAU,CACjD,CACF,IAAA,GAAA,qBAAA,EAAA,CACE,OAAO,EAAE,WAAW,cAAc,GAClC,qBAAA,GAAA,qBAAA,EAAA,CACE,UAAU;GACV,WAAW;GACX,MAAM;GACN,OAAO;GACP,cAAc,MAAM,SAAS,UAAU,0BAA0B;GACjE,eAAe;IACb,YAAY;IACZ,WAAW,uBAAuB,QAAQ;IAC1C,OAAO,KAAK;GACd;EACF,GAAG,GAAG,CACR,CACF,IAAA,GAAA,qBAAA,EAAA,CACE,OAAO,EAAE,WAAW,WAAW,GAC/B,WAAA,GAAA,qBAAA,EAAA,CACE,OAAO,EAAE,WAAW,qBAAqB,IAAA,GAAA,qBAAA,EAAA,CACvC,OAAO,EAAE,WAAW,oBAAoB,GAAG,MAAM,SAAS,UAAU,kBAAkB,iBAAiB,GACzG,UACF,IAAA,GAAA,qBAAA,EAAA,CACE,OAAO,EAAE,WAAW,cAAc,GAAG,YAAY,IAAA,GAAA,qBAAA,EAAA,CACjD,OAAO,EAAE,WAAW,aAAa,GAAG,UAAU,CAClD,CACF,CACF,CAAC,CAAC;CACJ;CAEA,MAAM,gBAAgB;EACpB,IAAI,YAAY,EAAA,GAAA,qBAAA,gBAAA,CAAiB,GAC/B;EAEF,CAAA,GAAA,qCAAA,kBAAA,CAAkB,YAAY;GAC5B,MAAM,UAAU,MAAM,IAAI,WAAW;GACrC,IAAI,UACF;GAEF,OAAO,QAAQ,qBAAqB,OAChC,QAAQ,eAAe,WAAW,IAChC,OACA;IAAE,MAAM;IAAW,UAAU,QAAQ,eAAe,KAAK,YAAY,QAAQ,OAAO;GAAE,IACxF;IAAE,MAAM;IAAS,UAAU,CAAC,QAAQ,kBAAkB,OAAO;GAAE,CAAC;EACtE,GAAG;GACD,gBAAgB;GAChB,UAAU,UAAU;IAClB,CAAA,GAAA,mCAAA,aAAA,CAAa,uCAAuC,KAAK;GAC3D;EACF,CAAC;CACH;CAKA,eAAe,OAAO;CACtB,MAAM,WAAW,YAAY,SAAS,mBAAmB;CAEzD,MAAM,gBAAgB;EACpB,WAAW;EACX,cAAc,QAAQ;EACtB,KAAK,OAAO;EACZ,KAAA,GAAA,qBAAA,oBAAA,CAAwB,mBAAmB,CAAC,EAAE,YAAY,SACxD,CAAA,GAAA,qBAAA,oBAAA,CAAoB,qBAAqB,IAAI;CAEjD;CACA,CAAA,GAAA,qBAAA,oBAAA,CAAoB,qBAAqB,EAAE,QAAQ,CAAC;CACpD,OAAO;AACT"}
|
|
1
|
+
{"version":3,"file":"index.js","names":["envVars"],"sources":["../../src/pushed-config-error-overlay/index.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//===========================================\n\nimport { captureError } from \"@hexclave/shared/dist/utils/errors\";\nimport { runAsynchronously } from \"@hexclave/shared/dist/utils/promises\";\nimport { isLocalhost } from \"@hexclave/shared/dist/utils/urls\";\nimport { envVars } from \"../generated/env\";\nimport { getInPageUiBaseCSS } from \"../in-page-ui/base-styles\";\nimport { canMountIntoDom, getGlobalUiInstance, h, setGlobalUiInstance, setHtml } from \"../in-page-ui/dom\";\nimport type { StackClientApp } from \"../lib/hexclave-app\";\n\nconst GLOBAL_INSTANCE_KEY = \"__hexclave-pushed-config-error-overlay\";\nconst MINIMIZED_STORAGE_KEY = \"hexclave-pushed-config-error-minimized-key\";\nconst REFRESH_INTERVAL_MS = 5_000;\nconst HEXCLAVE_LOGO_SVG = '<svg width=\"16\" height=\"16\" viewBox=\"0 0 48 48\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"3\" stroke-linejoin=\"miter\"><path d=\"M 24 4 L 41.32 14 L 41.32 34 L 24 44 L 6.68 34 L 6.68 14 Z\"/><path d=\"M 11 16.87 L 14 15.13 L 14 32.87 L 11 31.13 Z\" fill=\"currentColor\" stroke=\"none\"/><path d=\"M 11 16.87 L 14 15.13 L 14 32.87 L 11 31.13 Z\" fill=\"currentColor\" stroke=\"none\" transform=\"rotate(120 24 24)\"/><path d=\"M 11 16.87 L 14 15.13 L 14 32.87 L 11 31.13 Z\" fill=\"currentColor\" stroke=\"none\" transform=\"rotate(240 24 24)\"/></svg>';\nconst COPY_ICON_SVG = '<svg width=\"12\" height=\"12\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2.25\" stroke-linecap=\"round\" stroke-linejoin=\"round\"><rect x=\"9\" y=\"9\" width=\"13\" height=\"13\" rx=\"2\"/><path d=\"M5 15H4a2 2 0 0 1-2-2V4a2 2 0 0 1 2-2h9a2 2 0 0 1 2 2v1\"/></svg>';\n\ntype ConfigIssue = {\n kind: \"error\" | \"warning\",\n messages: string[],\n};\n\nconst css = getInPageUiBaseCSS(\".hexclave-config-error-overlay\") + `\n .hexclave-config-error-overlay .hce-backdrop {\n position: fixed;\n inset: 0;\n z-index: 2147483647;\n display: flex;\n align-items: center;\n justify-content: center;\n padding: 24px;\n background: rgba(0, 0, 0, 0.46);\n backdrop-filter: blur(6px);\n overflow: auto;\n }\n\n .hexclave-config-error-overlay .hce-card {\n --hce-status: var(--sdt-error);\n width: min(720px, calc(100vw - 32px));\n max-height: min(640px, calc(100dvh - 48px));\n border: 1px solid color-mix(in srgb, var(--hce-status) 35%, var(--sdt-border));\n border-radius: 18px;\n background: var(--sdt-overlay-bg);\n box-shadow: var(--sdt-shadow);\n backdrop-filter: blur(18px);\n display: flex;\n overflow: hidden;\n }\n\n .hexclave-config-error-overlay .hce-card-warning {\n --hce-status: var(--sdt-warning);\n }\n\n .hexclave-config-error-overlay .hce-card-inner {\n padding: 18px;\n width: 100%;\n overflow: auto;\n }\n\n .hexclave-config-error-overlay .hce-header {\n display: flex;\n align-items: flex-start;\n justify-content: space-between;\n gap: 12px;\n margin-bottom: 12px;\n }\n\n .hexclave-config-error-overlay .hce-title-row {\n display: flex;\n align-items: flex-start;\n gap: 10px;\n min-width: 0;\n }\n\n .hexclave-config-error-overlay .hce-logo {\n flex-shrink: 0;\n display: flex;\n align-items: center;\n justify-content: center;\n width: 34px;\n height: 34px;\n border-radius: 10px;\n background: var(--hce-status);\n color: white;\n box-shadow: 0 10px 30px color-mix(in srgb, var(--hce-status) 32%, transparent);\n }\n\n .hexclave-config-error-overlay .hce-badge {\n display: inline-flex;\n flex-shrink: 0;\n padding: 2px 6px;\n border-radius: 999px;\n background: var(--hce-status);\n color: white;\n font-size: 10px;\n font-weight: 700;\n letter-spacing: 0.06em;\n text-transform: uppercase;\n }\n\n .hexclave-config-error-overlay .hce-title {\n color: var(--sdt-text);\n margin-top: 4px;\n font-size: 18px;\n font-weight: 700;\n line-height: 1.25;\n }\n\n .hexclave-config-error-overlay .hce-actions {\n display: flex;\n gap: 4px;\n }\n\n .hexclave-config-error-overlay .hce-icon-btn {\n display: inline-flex;\n align-items: center;\n justify-content: center;\n width: 28px;\n height: 28px;\n border: 1px solid var(--sdt-border);\n border-radius: 8px;\n background: var(--sdt-bg-elevated);\n color: var(--sdt-text-secondary);\n cursor: pointer;\n font: inherit;\n line-height: 1;\n vertical-align: top;\n }\n\n .hexclave-config-error-overlay .hce-icon-btn svg {\n display: block;\n flex-shrink: 0;\n }\n\n .hexclave-config-error-overlay .hce-text-btn {\n align-items: center;\n gap: 6px;\n min-height: 28px;\n padding: 0 10px;\n width: auto;\n font-size: 12px;\n line-height: 1;\n }\n\n .hexclave-config-error-overlay .hce-icon-btn:hover {\n background: var(--sdt-bg-hover);\n color: var(--sdt-text);\n }\n\n .hexclave-config-error-overlay .hce-body {\n color: var(--sdt-text-secondary);\n font-size: 14px;\n line-height: 1.5;\n }\n\n .hexclave-config-error-overlay .hce-message-header {\n display: flex;\n align-items: center;\n justify-content: space-between;\n gap: 10px;\n margin-top: 14px;\n margin-bottom: 8px;\n }\n\n .hexclave-config-error-overlay .hce-message-label {\n color: var(--sdt-text);\n font-size: 12px;\n font-weight: 650;\n }\n\n .hexclave-config-error-overlay .hce-message {\n padding: 12px;\n max-height: min(260px, max(96px, 30dvh));\n overflow: auto;\n border: 1px solid var(--sdt-border-subtle);\n border-radius: 10px;\n background: var(--sdt-bg-subtle);\n color: var(--sdt-text);\n font-family: var(--sdt-font-mono);\n font-size: 12px;\n white-space: pre-wrap;\n overflow-wrap: anywhere;\n }\n\n .hexclave-config-error-overlay .hce-footer {\n margin-top: 10px;\n color: var(--sdt-text-tertiary);\n font-size: 12px;\n }\n\n .hexclave-config-error-overlay .hce-pill {\n position: fixed;\n right: 18px;\n bottom: 18px;\n z-index: 2147483647;\n display: flex;\n align-items: center;\n gap: 8px;\n padding: 8px 12px 8px 8px;\n --hce-status: var(--sdt-error);\n border: 1px solid color-mix(in srgb, var(--hce-status) 35%, var(--sdt-border));\n border-radius: 999px;\n background: var(--sdt-overlay-bg);\n box-shadow: var(--sdt-trigger-shadow);\n color: var(--sdt-text);\n cursor: pointer;\n font: inherit;\n backdrop-filter: blur(18px);\n }\n\n .hexclave-config-error-overlay .hce-pill-warning {\n --hce-status: var(--sdt-warning);\n }\n\n .hexclave-config-error-overlay .hce-pill-logo {\n display: flex;\n align-items: center;\n justify-content: center;\n width: 26px;\n height: 26px;\n border-radius: 999px;\n background: var(--hce-status);\n color: white;\n }\n\n @media (max-height: 520px) {\n .hexclave-config-error-overlay .hce-backdrop {\n align-items: flex-start;\n padding: 12px;\n }\n\n .hexclave-config-error-overlay .hce-card {\n width: min(720px, calc(100vw - 24px));\n max-height: calc(100dvh - 24px);\n }\n\n .hexclave-config-error-overlay .hce-card-inner {\n padding: 12px;\n }\n\n .hexclave-config-error-overlay .hce-header {\n margin-bottom: 8px;\n }\n\n .hexclave-config-error-overlay .hce-title {\n font-size: 16px;\n }\n\n .hexclave-config-error-overlay .hce-body {\n font-size: 13px;\n }\n\n .hexclave-config-error-overlay .hce-message {\n max-height: max(80px, 24dvh);\n }\n }\n`;\n\nfunction storageGet(key: string): string | null {\n try {\n return localStorage.getItem(key);\n } catch {\n return null;\n }\n}\n\nfunction storageSet(key: string, value: string): void {\n try {\n localStorage.setItem(key, value);\n } catch {\n // Storage may be unavailable in private or embedded contexts.\n }\n}\n\nfunction storageRemove(key: string): void {\n try {\n localStorage.removeItem(key);\n } catch {\n // Storage may be unavailable in private or embedded contexts.\n }\n}\n\nfunction shouldMount(): boolean {\n if (!canMountIntoDom()) {\n return false;\n }\n\n const nodeEnv = envVars.NODE_ENV;\n if (nodeEnv !== undefined) {\n return nodeEnv === \"development\";\n }\n\n try {\n const url = new URL(window.location.href);\n if (url.protocol === \"file:\") {\n return true;\n }\n } catch {\n return false;\n }\n return isLocalhost(window.location.href);\n}\n\nasync function copyTextToClipboard(text: string): Promise<void> {\n const clipboard: unknown = Reflect.get(navigator, \"clipboard\");\n const writeText = clipboard != null && typeof clipboard === \"object\"\n ? Reflect.get(clipboard, \"writeText\")\n : null;\n if (typeof writeText === \"function\") {\n await writeText.call(clipboard, text);\n return;\n }\n\n const textarea = h(\"textarea\", {\n style: {\n position: \"fixed\",\n left: \"-9999px\",\n top: \"0\",\n opacity: \"0\",\n },\n readonly: \"true\",\n }) as HTMLTextAreaElement;\n textarea.value = text;\n document.body.appendChild(textarea);\n textarea.select();\n const copied = document.execCommand(\"copy\");\n textarea.remove();\n if (!copied) {\n throw new Error(\"Browser refused to copy the config error message.\");\n }\n}\n\nfunction buildConfigIssueAiPrompt(issue: ConfigIssue): string {\n const issueLabel = issue.kind === \"error\" ? \"error\" : \"warning\";\n return [\n `Help me fix this Hexclave config ${issueLabel}.`,\n \"\",\n \"Hexclave reminder:\",\n `This ${issueLabel} comes from a pushed Hexclave config file. The app may keep running with the synced Hexclave config, but I need to fix the config file mentioned in the message and save it again so Hexclave can sync the config successfully and clear the dialog. Use your web fetch tool to read https://skill.hexclave.com to learn more about Hexclave.`,\n \"\",\n issue.kind === \"error\" ? \"Error message:\" : \"Warning message:\",\n issue.messages.join(\"\\n\"),\n \"\",\n \"Please explain the likely cause, identify the config key or value I should change, and suggest the smallest safe fix.\",\n ].join(\"\\n\");\n}\n\nexport function mountPushedConfigErrorOverlay(app: StackClientApp<true>): () => void {\n if (!shouldMount()) {\n return () => {};\n }\n\n getGlobalUiInstance(GLOBAL_INSTANCE_KEY)?.cleanup();\n\n const root = h(\"div\", { className: \"hexclave-config-error-overlay\" });\n const style = h(\"style\", null, css);\n root.appendChild(style);\n document.body.appendChild(root);\n\n let disposed = false;\n let lastErrorKey: string | null = null;\n let lastConsoleErrorKey: string | null = null;\n let minimized = false;\n\n const render = (issue: ConfigIssue | null) => {\n root.replaceChildren(style);\n if (issue == null) {\n lastErrorKey = null;\n minimized = false;\n return;\n }\n\n const issueMessage = \"Hexclave config \" + issue.kind + \": \" + issue.messages.join(\"\\n\");\n const issueKey = `${app.projectId}:${issue.kind}:${issueMessage}`;\n const issueLabel = issue.kind === \"error\" ? \"error\" : \"warning\";\n const issueTitle = issue.kind === \"error\"\n ? \"Your Hexclave config has been saved, but contains errors\"\n : \"Your Hexclave config has been saved, but has warnings\";\n const bodyText = issue.kind === \"error\"\n ? \"Your app can keep running, but Hexclave is still using the last valid config until this is fixed.\"\n : \"Your app can keep running, but part of your Hexclave config may not behave the way you expect until this is fixed.\";\n const footerText = issue.kind === \"error\"\n ? \"Fix the config file mentioned above and save it again. This message will disappear after the config sync succeeds.\"\n : \"Fix the config file mentioned above and save it again. This warning will disappear after Hexclave syncs a config without warnings.\";\n if (issueKey !== lastConsoleErrorKey) {\n lastConsoleErrorKey = issueKey;\n const consoleMessage = `[Hexclave] Config ${issueLabel}: ${issueMessage}`;\n if (issue.kind === \"error\") {\n console.error(consoleMessage);\n } else {\n console.warn(consoleMessage);\n }\n }\n\n if (issueKey !== lastErrorKey) {\n lastErrorKey = issueKey;\n minimized = storageGet(MINIMIZED_STORAGE_KEY) === issueKey;\n }\n\n if (minimized) {\n const logoSpan = h(\"span\", { className: \"hce-pill-logo\" });\n setHtml(logoSpan, HEXCLAVE_LOGO_SVG);\n root.appendChild(h(\"button\", {\n className: issue.kind === \"error\" ? \"hce-pill\" : \"hce-pill hce-pill-warning\",\n type: \"button\",\n onClick: () => {\n minimized = false;\n storageRemove(MINIMIZED_STORAGE_KEY);\n render(issue);\n },\n },\n logoSpan,\n h(\"span\", null, issue.kind === \"error\" ? \"Config error\" : \"Config warning\")));\n return;\n }\n\n const logoSpan = h(\"span\", { className: \"hce-logo\" });\n setHtml(logoSpan, HEXCLAVE_LOGO_SVG);\n const copyButton = h(\"button\", {\n className: \"hce-icon-btn hce-text-btn\",\n type: \"button\",\n title: issue.kind === \"error\" ? \"Copy error message\" : \"Copy warning message\",\n \"aria-label\": issue.kind === \"error\" ? \"Copy config error message\" : \"Copy config warning message\",\n onClick: () => {\n runAsynchronously(async () => {\n await copyTextToClipboard(issueMessage);\n copyButton.textContent = \"Copied\";\n setTimeout(() => {\n setHtml(copyButton, `${COPY_ICON_SVG}Copy`);\n }, 1500);\n }, {\n noErrorLogging: true,\n onError: (copyError) => {\n captureError(\"pushed-config-error-overlay-copy\", copyError);\n copyButton.textContent = \"Copy failed\";\n setTimeout(() => {\n setHtml(copyButton, `${COPY_ICON_SVG}Copy`);\n }, 1500);\n },\n });\n },\n });\n setHtml(copyButton, `${COPY_ICON_SVG}Copy`);\n\n const aiPromptCopyButton = h(\"button\", {\n className: \"hce-icon-btn\",\n type: \"button\",\n title: \"Copy AI prompt\",\n \"aria-label\": issue.kind === \"error\" ? \"Copy AI prompt for config error\" : \"Copy AI prompt for config warning\",\n onClick: () => {\n runAsynchronously(async () => {\n await copyTextToClipboard(buildConfigIssueAiPrompt(issue));\n aiPromptCopyButton.textContent = \"✓\";\n setTimeout(() => {\n setHtml(aiPromptCopyButton, COPY_ICON_SVG);\n }, 1500);\n }, {\n noErrorLogging: true,\n onError: (copyError) => {\n captureError(\"pushed-config-error-overlay-copy-ai-prompt\", copyError);\n aiPromptCopyButton.textContent = \"!\";\n setTimeout(() => {\n setHtml(aiPromptCopyButton, COPY_ICON_SVG);\n }, 1500);\n },\n });\n },\n });\n setHtml(aiPromptCopyButton, COPY_ICON_SVG);\n\n root.appendChild(h(\"div\", { className: \"hce-backdrop\" },\n h(\"div\", { className: issue.kind === \"error\" ? \"hce-card\" : \"hce-card hce-card-warning\", role: \"alertdialog\", \"aria-modal\": \"true\", \"aria-label\": `Hexclave config ${issueLabel}` },\n h(\"div\", { className: \"hce-card-inner\" },\n h(\"div\", { className: \"hce-header\" },\n h(\"div\", { className: \"hce-title-row\" },\n logoSpan,\n h(\"div\", null,\n h(\"span\", { className: \"hce-badge\" }, `Config ${issueLabel}`),\n h(\"div\", { className: \"hce-title\" }, issueTitle),\n ),\n ),\n h(\"div\", { className: \"hce-actions\" },\n aiPromptCopyButton,\n h(\"button\", {\n className: \"hce-icon-btn\",\n type: \"button\",\n title: \"Minimize\",\n \"aria-label\": issue.kind === \"error\" ? \"Minimize config error\" : \"Minimize config warning\",\n onClick: () => {\n minimized = true;\n storageSet(MINIMIZED_STORAGE_KEY, issueKey);\n render(issue);\n },\n }, \"–\"),\n ),\n ),\n h(\"div\", { className: \"hce-body\" },\n bodyText,\n h(\"div\", { className: \"hce-message-header\" },\n h(\"div\", { className: \"hce-message-label\" }, issue.kind === \"error\" ? \"Error message\" : \"Warning message\"),\n copyButton,\n ),\n h(\"div\", { className: \"hce-message\" }, issueMessage),\n h(\"div\", { className: \"hce-footer\" }, footerText),\n ),\n ),\n )));\n };\n\n const refresh = () => {\n if (disposed || !canMountIntoDom()) {\n return;\n }\n runAsynchronously(async () => {\n const project = await app.getProject();\n if (disposed) {\n return;\n }\n render(project.pushedConfigError == null\n ? project.configWarnings.length === 0\n ? null\n : { kind: \"warning\", messages: project.configWarnings.map((warning) => warning.message) }\n : { kind: \"error\", messages: [project.pushedConfigError.message] });\n }, {\n noErrorLogging: true,\n onError: (error) => {\n captureError(\"pushed-config-error-overlay-refresh\", error);\n },\n });\n };\n\n // This is mounted from the base client-app constructor, which also runs\n // before subclass field initializers. Defer the first app call so overridden\n // methods like adminApp.getProject() can safely touch subclass caches.\n queueMicrotask(refresh);\n const interval = setInterval(refresh, REFRESH_INTERVAL_MS);\n\n const cleanup = () => {\n disposed = true;\n clearInterval(interval);\n root.remove();\n if (getGlobalUiInstance(GLOBAL_INSTANCE_KEY)?.cleanup === cleanup) {\n setGlobalUiInstance(GLOBAL_INSTANCE_KEY, null);\n }\n };\n setGlobalUiInstance(GLOBAL_INSTANCE_KEY, { cleanup });\n return cleanup;\n}\n\n"],"mappings":"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"}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
let ___generated_env_js = require("../generated/env.js");
|
|
2
1
|
let vitest = require("vitest");
|
|
2
|
+
let ___generated_env_js = require("../generated/env.js");
|
|
3
3
|
let ___lib_hexclave_app_index_js = require("../lib/hexclave-app/index.js");
|
|
4
4
|
let __index_js = require("./index.js");
|
|
5
5
|
//#region src/pushed-config-error-overlay/index.test.ts
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
//#region src/utils/session-storage.d.ts
|
|
2
|
+
/**
|
|
3
|
+
* Guarded sessionStorage access for resilience layers (redirect loop breaking, redirect-back state
|
|
4
|
+
* mirroring). These layers must degrade to "no stored value" when storage is unavailable, instead
|
|
5
|
+
* of breaking the auth flow they are trying to protect.
|
|
6
|
+
*/
|
|
7
|
+
declare function readSessionStorageItem(key: string): string | null;
|
|
8
|
+
declare function writeSessionStorageItem(key: string, value: string): void;
|
|
9
|
+
declare function removeSessionStorageItem(key: string): void;
|
|
10
|
+
//#endregion
|
|
11
|
+
export { readSessionStorageItem, removeSessionStorageItem, writeSessionStorageItem };
|
|
12
|
+
//# sourceMappingURL=session-storage.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"session-storage.d.ts","names":[],"sources":["../../src/utils/session-storage.ts"],"mappings":";;AAUA;;;;iBAAgB,sBAAA,CAAuB,GAAW;AAAA,iBAalC,uBAAA,CAAwB,GAAA,UAAa,KAAa;AAAA,iBAYlD,wBAAA,CAAyB,GAAW"}
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
|
|
2
|
+
//#region src/utils/session-storage.ts
|
|
3
|
+
/**
|
|
4
|
+
* Guarded sessionStorage access for resilience layers (redirect loop breaking, redirect-back state
|
|
5
|
+
* mirroring). These layers must degrade to "no stored value" when storage is unavailable, instead
|
|
6
|
+
* of breaking the auth flow they are trying to protect.
|
|
7
|
+
*/
|
|
8
|
+
function readSessionStorageItem(key) {
|
|
9
|
+
if (typeof window === "undefined") return null;
|
|
10
|
+
try {
|
|
11
|
+
return window.sessionStorage.getItem(key);
|
|
12
|
+
} catch {
|
|
13
|
+
return null;
|
|
14
|
+
}
|
|
15
|
+
}
|
|
16
|
+
function writeSessionStorageItem(key, value) {
|
|
17
|
+
if (typeof window === "undefined") return;
|
|
18
|
+
try {
|
|
19
|
+
window.sessionStorage.setItem(key, value);
|
|
20
|
+
} catch {}
|
|
21
|
+
}
|
|
22
|
+
function removeSessionStorageItem(key) {
|
|
23
|
+
if (typeof window === "undefined") return;
|
|
24
|
+
try {
|
|
25
|
+
window.sessionStorage.removeItem(key);
|
|
26
|
+
} catch {}
|
|
27
|
+
}
|
|
28
|
+
//#endregion
|
|
29
|
+
exports.readSessionStorageItem = readSessionStorageItem;
|
|
30
|
+
exports.removeSessionStorageItem = removeSessionStorageItem;
|
|
31
|
+
exports.writeSessionStorageItem = writeSessionStorageItem;
|
|
32
|
+
|
|
33
|
+
//# sourceMappingURL=session-storage.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"session-storage.js","names":[],"sources":["../../src/utils/session-storage.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//===========================================\n/**\n * Guarded sessionStorage access for resilience layers (redirect loop breaking, redirect-back state\n * mirroring). These layers must degrade to \"no stored value\" when storage is unavailable, instead\n * of breaking the auth flow they are trying to protect.\n */\n\nexport function readSessionStorageItem(key: string): string | null {\n if (typeof window === \"undefined\") {\n return null;\n }\n try {\n return window.sessionStorage.getItem(key);\n } catch {\n // Storage can be blocked entirely (private browsing, sandboxed iframes, disabled cookies), in\n // which case accessing window.sessionStorage throws. Treat that as \"nothing stored\".\n return null;\n }\n}\n\nexport function writeSessionStorageItem(key: string, value: string): void {\n if (typeof window === \"undefined\") {\n return;\n }\n try {\n window.sessionStorage.setItem(key, value);\n } catch {\n // Storage can be blocked or full; see readSessionStorageItem. Values written here are only\n // used as a fallback, so failing to persist must not fail the caller.\n }\n}\n\nexport function removeSessionStorageItem(key: string): void {\n if (typeof window === \"undefined\") {\n return;\n }\n try {\n window.sessionStorage.removeItem(key);\n } catch {\n // See readSessionStorageItem.\n }\n}\n"],"mappings":";;;;;;;AAUA,SAAgB,uBAAuB,KAA4B;CACjE,IAAI,OAAO,WAAW,aACpB,OAAO;CAET,IAAI;EACF,OAAO,OAAO,eAAe,QAAQ,GAAG;CAC1C,QAAQ;EAGN,OAAO;CACT;AACF;AAEA,SAAgB,wBAAwB,KAAa,OAAqB;CACxE,IAAI,OAAO,WAAW,aACpB;CAEF,IAAI;EACF,OAAO,eAAe,QAAQ,KAAK,KAAK;CAC1C,QAAQ,CAGR;AACF;AAEA,SAAgB,yBAAyB,KAAmB;CAC1D,IAAI,OAAO,WAAW,aACpB;CAEF,IAAI;EACF,OAAO,eAAe,WAAW,GAAG;CACtC,QAAQ,CAER;AACF"}
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"//": "THIS FILE IS AUTO-GENERATED FROM TEMPLATE. DO NOT EDIT IT DIRECTLY UNLESS YOU ALSO EDIT THE CORRESPONDING FILE IN packages/template (FOR package.json FILES, PLEASE EDIT package-template.json)",
|
|
3
3
|
"name": "@hexclave/js",
|
|
4
|
-
"version": "1.0.
|
|
4
|
+
"version": "1.0.52",
|
|
5
5
|
"repository": "https://github.com/hexclave/hexclave",
|
|
6
6
|
"sideEffects": false,
|
|
7
7
|
"main": "./dist/index.js",
|
|
@@ -65,7 +65,7 @@
|
|
|
65
65
|
"qrcode": "^1.5.4",
|
|
66
66
|
"rrweb": "^1.1.3",
|
|
67
67
|
"yup": "^1.7.1",
|
|
68
|
-
"@hexclave/shared": "1.0.
|
|
68
|
+
"@hexclave/shared": "1.0.52"
|
|
69
69
|
},
|
|
70
70
|
"devDependencies": {
|
|
71
71
|
"@quetzallabs/i18n": "^0.1.19",
|
|
@@ -8,7 +8,6 @@ import { DEV_TOOL_ROOT_ID } from "@hexclave/shared/dist/utils/dev-tool";
|
|
|
8
8
|
import { runAsynchronously, runAsynchronouslyWithAlert } from "@hexclave/shared/dist/utils/promises";
|
|
9
9
|
import { isLocalhost } from "@hexclave/shared/dist/utils/urls";
|
|
10
10
|
import type { StackClientApp } from "../lib/hexclave-app";
|
|
11
|
-
import { envVars } from "../generated/env";
|
|
12
11
|
import { getGlobalUiInstance, h, hasAppendChild, setGlobalUiInstance, setHtml, type UiGlobalInstance } from "../in-page-ui/dom";
|
|
13
12
|
import { getBaseUrl } from "../lib/hexclave-app/apps/implementations/common";
|
|
14
13
|
import type { HandlerUrlOptions, HandlerUrls, HandlerUrlTarget } from "../lib/hexclave-app/common";
|
|
@@ -63,6 +62,11 @@ const MAX_LOG_ENTRIES = 500;
|
|
|
63
62
|
const CONSOLE_LOG_BATCH_SIZE = 100;
|
|
64
63
|
const DRAG_THRESHOLD = 5;
|
|
65
64
|
const DOCS_URL = 'https://docs.hexclave.com';
|
|
65
|
+
// The local development-environment dashboard always listens on this loopback
|
|
66
|
+
// port (see the CLI's DEFAULT_DASHBOARD_PORT). The dev tool runs inside the
|
|
67
|
+
// customer's own app, so it can't read the CLI's env vars — the port is fixed.
|
|
68
|
+
const LOCAL_DASHBOARD_ORIGIN = 'http://localhost:26700';
|
|
69
|
+
const HOSTED_DASHBOARD_URL = 'https://app.hexclave.com';
|
|
66
70
|
|
|
67
71
|
const TABS: { id: TabId; label: string; icon: string }[] = [
|
|
68
72
|
{ id: 'overview', label: 'Overview', icon: '<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><rect x="3" y="3" width="7" height="7"/><rect x="14" y="3" width="7" height="7"/><rect x="14" y="14" width="7" height="7"/><rect x="3" y="14" width="7" height="7"/></svg>' },
|
|
@@ -193,42 +197,60 @@ function resolveApiBaseUrl(app: StackClientApp<true>): string {
|
|
|
193
197
|
return getBaseUrl(opts.baseUrl);
|
|
194
198
|
}
|
|
195
199
|
|
|
196
|
-
function
|
|
197
|
-
return
|
|
200
|
+
function localDashboardProjectUrl(app: StackClientApp<true>): string {
|
|
201
|
+
return `${LOCAL_DASHBOARD_ORIGIN}/projects/${encodeURIComponent(app.projectId)}`;
|
|
198
202
|
}
|
|
199
203
|
|
|
200
|
-
function
|
|
201
|
-
if (
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
204
|
+
function parseProjectAvailabilityResponse(body: unknown): boolean {
|
|
205
|
+
if (typeof body !== 'object' || body === null) return false;
|
|
206
|
+
return 'project_available' in body && body.project_available === true;
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
// How long to wait for the availability probe before giving up. A refused
|
|
210
|
+
// connection rejects almost immediately, but a host that accepts the socket yet
|
|
211
|
+
// never responds would otherwise leave the tab stuck on "Checking…" forever.
|
|
212
|
+
const LOCAL_DASHBOARD_PROBE_TIMEOUT_MS = 3000;
|
|
213
|
+
|
|
214
|
+
// Whether the current page can even talk to the HTTP-only local dashboard.
|
|
215
|
+
// LOCAL_DASHBOARD_ORIGIN is `http://localhost:26700`, so the page itself must be
|
|
216
|
+
// an http: localhost origin: an https: page (e.g. an HTTPS dev server) would
|
|
217
|
+
// have both the probe fetch and the iframe embed blocked as mixed content, so
|
|
218
|
+
// we treat that as "not available" and fall back to the hosted dashboard.
|
|
219
|
+
function canReachLocalDashboard(): boolean {
|
|
220
|
+
return isLocalhost(window.location.href) && window.location.protocol === 'http:';
|
|
205
221
|
}
|
|
206
222
|
|
|
207
|
-
|
|
223
|
+
// Decides whether the Dashboard tab can embed the locally-running
|
|
224
|
+
// development-environment dashboard. Rather than inferring from build-time env
|
|
225
|
+
// vars (which don't reflect whether the dev environment is actually up right
|
|
226
|
+
// now), we ask the running development environment directly:
|
|
227
|
+
// 1. We must be viewing the app from an http: localhost origin — the HTTP-only
|
|
228
|
+
// dashboard on :26700 is only reachable from, and only relevant to, a local
|
|
229
|
+
// browser (and an https: page can't reach it without mixed-content errors).
|
|
230
|
+
// 2. The development environment must be running AND already own this exact
|
|
231
|
+
// project (see the project-availability route it answers).
|
|
232
|
+
// Any failure — dev environment not running (connection refused), probe timeout,
|
|
233
|
+
// project unknown, or a malformed response — resolves to `false`, which shows
|
|
234
|
+
// the hosted-dashboard hint instead.
|
|
235
|
+
async function isLocalDashboardProjectAvailable(app: StackClientApp<true>): Promise<boolean> {
|
|
236
|
+
if (!canReachLocalDashboard()) return false;
|
|
237
|
+
const url = `${LOCAL_DASHBOARD_ORIGIN}/api/development-environment/project-availability?project_id=${encodeURIComponent(app.projectId)}`;
|
|
238
|
+
const controller = new AbortController();
|
|
239
|
+
const timeout = setTimeout(() => controller.abort(), LOCAL_DASHBOARD_PROBE_TIMEOUT_MS);
|
|
208
240
|
try {
|
|
209
|
-
const
|
|
210
|
-
if (
|
|
211
|
-
|
|
212
|
-
if (port && port.endsWith('02')) {
|
|
213
|
-
url.port = port.slice(0, -2) + '01';
|
|
214
|
-
}
|
|
215
|
-
return url.origin;
|
|
216
|
-
}
|
|
217
|
-
if (url.hostname.startsWith('api.')) {
|
|
218
|
-
url.hostname = 'app.' + url.hostname.slice(4);
|
|
219
|
-
return url.origin;
|
|
220
|
-
}
|
|
221
|
-
return url.origin;
|
|
241
|
+
const response = await fetch(url, { method: 'GET', signal: controller.signal });
|
|
242
|
+
if (!response.ok) return false;
|
|
243
|
+
return parseProjectAvailabilityResponse(await response.json());
|
|
222
244
|
} catch {
|
|
223
|
-
|
|
245
|
+
// Reachability probe: when the local development environment isn't running,
|
|
246
|
+
// the fetch rejects (connection refused); when it hangs, the AbortController
|
|
247
|
+
// aborts it. Either way that simply means "not available".
|
|
248
|
+
return false;
|
|
249
|
+
} finally {
|
|
250
|
+
clearTimeout(timeout);
|
|
224
251
|
}
|
|
225
252
|
}
|
|
226
253
|
|
|
227
|
-
function resolveDashboardUrl(app: StackClientApp<true>): string {
|
|
228
|
-
const base = deriveDashboardBaseUrl(resolveApiBaseUrl(app));
|
|
229
|
-
return `${base}/projects/${encodeURIComponent(app.projectId)}`;
|
|
230
|
-
}
|
|
231
|
-
|
|
232
254
|
function formatTimestamp(ts: number): string {
|
|
233
255
|
return new Date(ts).toLocaleTimeString([], {
|
|
234
256
|
hour: '2-digit',
|
|
@@ -563,8 +585,8 @@ function createTabBar(
|
|
|
563
585
|
// Iframe helper
|
|
564
586
|
// ---------------------------------------------------------------------------
|
|
565
587
|
|
|
566
|
-
function
|
|
567
|
-
|
|
588
|
+
function populateIframeTab(container: HTMLElement, src: string, title: string, loadingMsg = 'Loading\u2026', errorMsg = 'Unable to load content', errorDetail?: string, openExternallyLabel?: string): void {
|
|
589
|
+
container.innerHTML = '';
|
|
568
590
|
if (openExternallyLabel != null) {
|
|
569
591
|
container.appendChild(h('div', { className: 'sdt-iframe-toolbar' },
|
|
570
592
|
h('a', { href: src, target: '_blank', rel: 'noopener noreferrer', className: 'sdt-iframe-open-link' }, openExternallyLabel),
|
|
@@ -594,7 +616,7 @@ function createIframeTab(src: string, title: string, loadingMsg = 'Loading\u2026
|
|
|
594
616
|
}
|
|
595
617
|
const retryBtn = h('button', { className: 'sdt-iframe-error-btn' }, 'Retry');
|
|
596
618
|
retryBtn.addEventListener('click', () => {
|
|
597
|
-
container
|
|
619
|
+
populateIframeTab(container, src, title, loadingMsg, errorMsg, errorDetail, openExternallyLabel);
|
|
598
620
|
});
|
|
599
621
|
errDiv.appendChild(retryBtn);
|
|
600
622
|
const link = h('a', { href: src, target: '_blank', rel: 'noopener noreferrer', style: { color: 'var(--sdt-accent)', fontSize: '12px', textDecoration: 'none' } }, 'Open in new tab');
|
|
@@ -603,7 +625,6 @@ function createIframeTab(src: string, title: string, loadingMsg = 'Loading\u2026
|
|
|
603
625
|
});
|
|
604
626
|
|
|
605
627
|
container.appendChild(iframe);
|
|
606
|
-
return container;
|
|
607
628
|
}
|
|
608
629
|
|
|
609
630
|
// ===========================================================================================
|
|
@@ -1681,8 +1702,52 @@ function createAITab(app: StackClientApp<true>): HTMLElement {
|
|
|
1681
1702
|
// ---------------------------------------------------------------------------
|
|
1682
1703
|
|
|
1683
1704
|
function createDashboardTab(app: StackClientApp<true>): HTMLElement {
|
|
1684
|
-
const
|
|
1685
|
-
|
|
1705
|
+
const container = h('div', { className: 'sdt-iframe-container' });
|
|
1706
|
+
|
|
1707
|
+
function showChecking() {
|
|
1708
|
+
container.innerHTML = '';
|
|
1709
|
+
container.appendChild(h('div', { className: 'sdt-iframe-loading' }, 'Checking development environment\u2026'));
|
|
1710
|
+
}
|
|
1711
|
+
|
|
1712
|
+
function showUnavailable() {
|
|
1713
|
+
container.innerHTML = '';
|
|
1714
|
+
const message = h('div', { className: 'sdt-dashboard-unavailable' });
|
|
1715
|
+
const text = h('div', { className: 'sdt-dashboard-unavailable-text' });
|
|
1716
|
+
text.appendChild(document.createTextNode('Navigate to '));
|
|
1717
|
+
text.appendChild(h('a', {
|
|
1718
|
+
className: 'sdt-dashboard-unavailable-link',
|
|
1719
|
+
href: HOSTED_DASHBOARD_URL,
|
|
1720
|
+
target: '_blank',
|
|
1721
|
+
rel: 'noopener noreferrer',
|
|
1722
|
+
}, HOSTED_DASHBOARD_URL));
|
|
1723
|
+
text.appendChild(document.createTextNode(" to view this project's dashboard"));
|
|
1724
|
+
message.appendChild(text);
|
|
1725
|
+
container.appendChild(message);
|
|
1726
|
+
}
|
|
1727
|
+
|
|
1728
|
+
async function check() {
|
|
1729
|
+
showChecking();
|
|
1730
|
+
const available = await isLocalDashboardProjectAvailable(app);
|
|
1731
|
+
// The dev tool may have been torn down while we were probing; only mutate
|
|
1732
|
+
// the DOM if this container is still mounted.
|
|
1733
|
+
if (!container.isConnected) return;
|
|
1734
|
+
if (available) {
|
|
1735
|
+
populateIframeTab(
|
|
1736
|
+
container,
|
|
1737
|
+
localDashboardProjectUrl(app),
|
|
1738
|
+
'Hexclave Dashboard',
|
|
1739
|
+
'Loading dashboard\u2026',
|
|
1740
|
+
'Unable to load dashboard',
|
|
1741
|
+
'The dashboard may require authentication or block framing',
|
|
1742
|
+
'Open in New Tab',
|
|
1743
|
+
);
|
|
1744
|
+
} else {
|
|
1745
|
+
showUnavailable();
|
|
1746
|
+
}
|
|
1747
|
+
}
|
|
1748
|
+
|
|
1749
|
+
runAsynchronously(check);
|
|
1750
|
+
return container;
|
|
1686
1751
|
}
|
|
1687
1752
|
|
|
1688
1753
|
// ---------------------------------------------------------------------------
|
|
@@ -2112,7 +2177,7 @@ function createPanel(
|
|
|
2112
2177
|
panel.style.height = state.get().panelHeight + 'px';
|
|
2113
2178
|
}
|
|
2114
2179
|
|
|
2115
|
-
const tabs =
|
|
2180
|
+
const tabs = TABS;
|
|
2116
2181
|
const storedActiveTab = state.get().activeTab;
|
|
2117
2182
|
const activeTab = tabs.some((tab) => tab.id === storedActiveTab) ? storedActiveTab : DEFAULT_STATE.activeTab;
|
|
2118
2183
|
|
|
@@ -328,6 +328,14 @@ export const devToolCSS = getInPageUiBaseCSS('.hexclave-devtool') + `
|
|
|
328
328
|
z-index: 1;
|
|
329
329
|
}
|
|
330
330
|
|
|
331
|
+
/* Iframe panes lay their content out as a flex column so the embedded
|
|
332
|
+
dashboard (or the fallback message) fills the pane height. Uses two
|
|
333
|
+
classes to win over the plain .sdt-tab-pane-active { display: block }. */
|
|
334
|
+
.hexclave-devtool .sdt-tab-pane-iframe.sdt-tab-pane-active {
|
|
335
|
+
display: flex;
|
|
336
|
+
flex-direction: column;
|
|
337
|
+
}
|
|
338
|
+
|
|
331
339
|
/* ===== Overview tab — single column ===== */
|
|
332
340
|
|
|
333
341
|
.hexclave-devtool .sdt-ov {
|
|
@@ -1235,6 +1243,33 @@ export const devToolCSS = getInPageUiBaseCSS('.hexclave-devtool') + `
|
|
|
1235
1243
|
background: var(--sdt-accent-hover);
|
|
1236
1244
|
}
|
|
1237
1245
|
|
|
1246
|
+
.hexclave-devtool .sdt-dashboard-unavailable {
|
|
1247
|
+
flex: 1;
|
|
1248
|
+
display: flex;
|
|
1249
|
+
flex-direction: column;
|
|
1250
|
+
align-items: center;
|
|
1251
|
+
justify-content: center;
|
|
1252
|
+
gap: 12px;
|
|
1253
|
+
padding: 24px;
|
|
1254
|
+
text-align: center;
|
|
1255
|
+
color: var(--sdt-text-secondary);
|
|
1256
|
+
font-size: 14px;
|
|
1257
|
+
}
|
|
1258
|
+
|
|
1259
|
+
.hexclave-devtool .sdt-dashboard-unavailable-text {
|
|
1260
|
+
max-width: 420px;
|
|
1261
|
+
line-height: 1.5;
|
|
1262
|
+
}
|
|
1263
|
+
|
|
1264
|
+
.hexclave-devtool .sdt-dashboard-unavailable-link {
|
|
1265
|
+
color: var(--sdt-accent);
|
|
1266
|
+
text-decoration: none;
|
|
1267
|
+
}
|
|
1268
|
+
|
|
1269
|
+
.hexclave-devtool .sdt-dashboard-unavailable-link:hover {
|
|
1270
|
+
text-decoration: underline;
|
|
1271
|
+
}
|
|
1272
|
+
|
|
1238
1273
|
/* Shared content fade animation */
|
|
1239
1274
|
.hexclave-devtool .sdt-tab-content-fade {
|
|
1240
1275
|
animation: sdt-tab-fade-in 0.15s ease-out;
|
|
@@ -648,6 +648,133 @@ describe("StackClientApp cross-domain auth", () => {
|
|
|
648
648
|
expect(redirectedUrl).toBe("/settings?tab=profile");
|
|
649
649
|
});
|
|
650
650
|
|
|
651
|
+
it("restores a dropped after_auth_return_to from the sessionStorage mirror when redirecting after sign-in", async () => {
|
|
652
|
+
const previousWindow = globalThis.window;
|
|
653
|
+
const previousDocument = globalThis.document;
|
|
654
|
+
const hadPreviousWindow = Reflect.has(globalThis, "window");
|
|
655
|
+
const sessionStorageMap = new Map<string, string>();
|
|
656
|
+
const windowMock = {
|
|
657
|
+
location: {
|
|
658
|
+
href: "https://demo.example.test/handler/sign-in?after_auth_return_to=%2Fmusic%3Ftrack%3D1",
|
|
659
|
+
},
|
|
660
|
+
sessionStorage: {
|
|
661
|
+
getItem: (key: string) => sessionStorageMap.get(key) ?? null,
|
|
662
|
+
setItem: (key: string, value: string) => {
|
|
663
|
+
sessionStorageMap.set(key, value);
|
|
664
|
+
},
|
|
665
|
+
removeItem: (key: string) => {
|
|
666
|
+
sessionStorageMap.delete(key);
|
|
667
|
+
},
|
|
668
|
+
},
|
|
669
|
+
};
|
|
670
|
+
Reflect.set(globalThis, "window", windowMock);
|
|
671
|
+
globalThis.document = createMockDocument();
|
|
672
|
+
|
|
673
|
+
try {
|
|
674
|
+
// The constructor mirrors the redirect-back state from the arrival URL into sessionStorage.
|
|
675
|
+
const clientApp = new StackClientApp({
|
|
676
|
+
baseUrl: "http://localhost:12345",
|
|
677
|
+
projectId: "00000000-0000-4000-8000-000000000011",
|
|
678
|
+
publishableClientKey: "stack-pk-test",
|
|
679
|
+
tokenStore: "memory",
|
|
680
|
+
redirectMethod: "window",
|
|
681
|
+
noAutomaticPrefetch: true,
|
|
682
|
+
});
|
|
683
|
+
|
|
684
|
+
// Intermediate hops (MFA, magic link, OAuth round-trips, ...) dropped the query params.
|
|
685
|
+
windowMock.location.href = "https://demo.example.test/handler/mfa";
|
|
686
|
+
|
|
687
|
+
const redirectUrl = await clientApp[hexclaveAppInternalsSymbol].getRedirectToHandlerUrl("afterSignIn");
|
|
688
|
+
expect(redirectUrl).toBe("/music?track=1");
|
|
689
|
+
|
|
690
|
+
// noRedirectBack opts out of the mirror just like it opts out of the query param.
|
|
691
|
+
const noRedirectBackUrl = await clientApp[hexclaveAppInternalsSymbol].getRedirectToHandlerUrl("afterSignIn", { noRedirectBack: true });
|
|
692
|
+
expect(new URL(noRedirectBackUrl, "https://demo.example.test").pathname).toBe("/");
|
|
693
|
+
} finally {
|
|
694
|
+
globalThis.document = previousDocument;
|
|
695
|
+
if (hadPreviousWindow) {
|
|
696
|
+
Reflect.set(globalThis, "window", previousWindow);
|
|
697
|
+
} else {
|
|
698
|
+
Reflect.deleteProperty(globalThis, "window");
|
|
699
|
+
}
|
|
700
|
+
}
|
|
701
|
+
});
|
|
702
|
+
|
|
703
|
+
it("restores dropped cross-domain handoff state from the sessionStorage mirror when redirecting after sign-in", async () => {
|
|
704
|
+
const projectId = "00000000-0000-4000-8000-000000000012";
|
|
705
|
+
const previousWindow = globalThis.window;
|
|
706
|
+
const hadPreviousWindow = Reflect.has(globalThis, "window");
|
|
707
|
+
const sessionStorageMap = new Map<string, string>();
|
|
708
|
+
|
|
709
|
+
const handoffState = "mirror-handoff-state";
|
|
710
|
+
const handoffCodeChallenge = "abcdefghijklmnopqrstuvwxyzABCDEFG_0123456789-._~";
|
|
711
|
+
const handoffAfterCallbackRedirect = "https://demo.example.test/dashboard";
|
|
712
|
+
const redirectBackUrl = new URL("https://demo.example.test/handler/oauth-callback");
|
|
713
|
+
redirectBackUrl.searchParams.set("hexclave_cross_domain_auth", "1");
|
|
714
|
+
redirectBackUrl.searchParams.set("hexclave_cross_domain_state", handoffState);
|
|
715
|
+
redirectBackUrl.searchParams.set("hexclave_cross_domain_code_challenge", handoffCodeChallenge);
|
|
716
|
+
redirectBackUrl.searchParams.set("hexclave_cross_domain_after_callback_redirect_url", handoffAfterCallbackRedirect);
|
|
717
|
+
|
|
718
|
+
const arrivalUrl = new URL(`https://${projectId}.built-with-stack-auth.com/handler/sign-in`);
|
|
719
|
+
arrivalUrl.searchParams.set("after_auth_return_to", redirectBackUrl.toString());
|
|
720
|
+
arrivalUrl.searchParams.set("hexclave_cross_domain_state", handoffState);
|
|
721
|
+
arrivalUrl.searchParams.set("hexclave_cross_domain_code_challenge", handoffCodeChallenge);
|
|
722
|
+
arrivalUrl.searchParams.set("hexclave_cross_domain_after_callback_redirect_url", handoffAfterCallbackRedirect);
|
|
723
|
+
|
|
724
|
+
const previousDocument = globalThis.document;
|
|
725
|
+
const windowMock = {
|
|
726
|
+
location: {
|
|
727
|
+
href: arrivalUrl.toString(),
|
|
728
|
+
},
|
|
729
|
+
sessionStorage: {
|
|
730
|
+
getItem: (key: string) => sessionStorageMap.get(key) ?? null,
|
|
731
|
+
setItem: (key: string, value: string) => {
|
|
732
|
+
sessionStorageMap.set(key, value);
|
|
733
|
+
},
|
|
734
|
+
removeItem: (key: string) => {
|
|
735
|
+
sessionStorageMap.delete(key);
|
|
736
|
+
},
|
|
737
|
+
},
|
|
738
|
+
};
|
|
739
|
+
Reflect.set(globalThis, "window", windowMock);
|
|
740
|
+
globalThis.document = createMockDocument();
|
|
741
|
+
|
|
742
|
+
try {
|
|
743
|
+
const clientApp = new StackClientApp({
|
|
744
|
+
baseUrl: "http://localhost:12345",
|
|
745
|
+
projectId,
|
|
746
|
+
publishableClientKey: "stack-pk-test",
|
|
747
|
+
tokenStore: "memory",
|
|
748
|
+
redirectMethod: "window",
|
|
749
|
+
noAutomaticPrefetch: true,
|
|
750
|
+
});
|
|
751
|
+
const crossDomainAuthorizeRedirect = "https://demo.example.test/handler/oauth-callback?code=minted-code&state=mirror-handoff-state";
|
|
752
|
+
const createCrossDomainAuthRedirectUrlSpy = vi
|
|
753
|
+
.spyOn(clientApp as any, "_createCrossDomainAuthRedirectUrl")
|
|
754
|
+
.mockResolvedValue(crossDomainAuthorizeRedirect);
|
|
755
|
+
|
|
756
|
+
// All redirect-back query params were dropped before the after-sign-in redirect.
|
|
757
|
+
windowMock.location.href = `https://${projectId}.built-with-stack-auth.com/handler/sign-in`;
|
|
758
|
+
|
|
759
|
+
const redirectUrl = await clientApp[hexclaveAppInternalsSymbol].getRedirectToHandlerUrl("afterSignIn");
|
|
760
|
+
|
|
761
|
+
expect(createCrossDomainAuthRedirectUrlSpy).toHaveBeenCalledWith(expect.objectContaining({
|
|
762
|
+
redirectUri: redirectBackUrl.toString(),
|
|
763
|
+
state: handoffState,
|
|
764
|
+
codeChallenge: handoffCodeChallenge,
|
|
765
|
+
afterCallbackRedirectUrl: handoffAfterCallbackRedirect,
|
|
766
|
+
}));
|
|
767
|
+
expect(redirectUrl).toBe(crossDomainAuthorizeRedirect);
|
|
768
|
+
} finally {
|
|
769
|
+
globalThis.document = previousDocument;
|
|
770
|
+
if (hadPreviousWindow) {
|
|
771
|
+
Reflect.set(globalThis, "window", previousWindow);
|
|
772
|
+
} else {
|
|
773
|
+
Reflect.deleteProperty(globalThis, "window");
|
|
774
|
+
}
|
|
775
|
+
}
|
|
776
|
+
});
|
|
777
|
+
|
|
651
778
|
it("ignores stale session callbacks after a newer refresh token owns the token store", async () => {
|
|
652
779
|
const clientApp = new StackClientApp({
|
|
653
780
|
baseUrl: "http://localhost:12345",
|