@hexclave/next 1.0.21 → 1.0.23
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/README.md +187 -7
- package/dist/components/elements/sidebar-layout.js +1 -1
- package/dist/components-page/oauth-callback.js +14 -19
- package/dist/components-page/oauth-callback.js.map +1 -1
- package/dist/components-page/oauth-callback.test.d.ts +1 -0
- package/dist/components-page/oauth-callback.test.js +90 -0
- package/dist/components-page/oauth-callback.test.js.map +1 -0
- package/dist/dev-tool/index.d.ts +6 -5
- package/dist/dev-tool/index.d.ts.map +1 -1
- package/dist/dev-tool/index.js +16 -6
- package/dist/dev-tool/index.js.map +1 -1
- package/dist/esm/components/elements/sidebar-layout.js +1 -1
- package/dist/esm/components-page/oauth-callback.js +14 -19
- package/dist/esm/components-page/oauth-callback.js.map +1 -1
- package/dist/esm/components-page/oauth-callback.test.d.ts +1 -0
- package/dist/esm/components-page/oauth-callback.test.js +89 -0
- package/dist/esm/components-page/oauth-callback.test.js.map +1 -0
- package/dist/esm/dev-tool/index.d.ts +6 -5
- package/dist/esm/dev-tool/index.d.ts.map +1 -1
- package/dist/esm/dev-tool/index.js +16 -6
- package/dist/esm/dev-tool/index.js.map +1 -1
- package/dist/esm/generated/quetzal-translations.d.ts +2 -2
- package/dist/esm/lib/auth.d.ts.map +1 -1
- package/dist/esm/lib/auth.js +32 -11
- package/dist/esm/lib/auth.js.map +1 -1
- package/dist/esm/lib/auth.test.js +25 -10
- package/dist/esm/lib/auth.test.js.map +1 -1
- package/dist/esm/lib/hexclave-app/apps/implementations/admin-app-impl.d.ts +3 -2
- package/dist/esm/lib/hexclave-app/apps/implementations/admin-app-impl.d.ts.map +1 -1
- package/dist/esm/lib/hexclave-app/apps/implementations/admin-app-impl.js +1 -0
- package/dist/esm/lib/hexclave-app/apps/implementations/admin-app-impl.js.map +1 -1
- package/dist/esm/lib/hexclave-app/apps/implementations/client-app-impl.cross-domain.test.js +54 -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 +5 -2
- 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 +29 -5
- package/dist/esm/lib/hexclave-app/apps/implementations/client-app-impl.js.map +1 -1
- package/dist/esm/lib/hexclave-app/apps/implementations/common.js +1 -1
- package/dist/esm/lib/hexclave-app/apps/implementations/server-app-impl.d.ts +1 -1
- package/dist/esm/lib/hexclave-app/apps/interfaces/admin-app.d.ts +1 -0
- package/dist/esm/lib/hexclave-app/apps/interfaces/admin-app.d.ts.map +1 -1
- package/dist/esm/lib/hexclave-app/apps/interfaces/admin-app.js.map +1 -1
- package/dist/esm/lib/hexclave-app/apps/interfaces/client-app.d.ts +5 -3
- 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/generated/quetzal-translations.d.ts +2 -2
- package/dist/lib/auth.d.ts.map +1 -1
- package/dist/lib/auth.js +31 -10
- package/dist/lib/auth.js.map +1 -1
- package/dist/lib/auth.test.js +23 -8
- package/dist/lib/auth.test.js.map +1 -1
- package/dist/lib/hexclave-app/apps/implementations/admin-app-impl.d.ts +3 -2
- package/dist/lib/hexclave-app/apps/implementations/admin-app-impl.d.ts.map +1 -1
- package/dist/lib/hexclave-app/apps/implementations/admin-app-impl.js +1 -0
- package/dist/lib/hexclave-app/apps/implementations/admin-app-impl.js.map +1 -1
- package/dist/lib/hexclave-app/apps/implementations/client-app-impl.cross-domain.test.js +54 -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 +5 -2
- package/dist/lib/hexclave-app/apps/implementations/client-app-impl.d.ts.map +1 -1
- package/dist/lib/hexclave-app/apps/implementations/client-app-impl.js +28 -4
- package/dist/lib/hexclave-app/apps/implementations/client-app-impl.js.map +1 -1
- package/dist/lib/hexclave-app/apps/implementations/common.js +1 -1
- package/dist/lib/hexclave-app/apps/implementations/server-app-impl.d.ts +1 -1
- package/dist/lib/hexclave-app/apps/interfaces/admin-app.d.ts +1 -0
- package/dist/lib/hexclave-app/apps/interfaces/admin-app.d.ts.map +1 -1
- package/dist/lib/hexclave-app/apps/interfaces/admin-app.js.map +1 -1
- package/dist/lib/hexclave-app/apps/interfaces/client-app.d.ts +5 -3
- package/dist/lib/hexclave-app/apps/interfaces/client-app.d.ts.map +1 -1
- package/dist/lib/hexclave-app/apps/interfaces/client-app.js.map +1 -1
- package/package.json +4 -4
- package/src/components-page/oauth-callback.test.tsx +109 -0
- package/src/components-page/oauth-callback.tsx +14 -19
- package/src/dev-tool/index.ts +27 -6
- package/src/lib/auth.test.ts +32 -10
- package/src/lib/auth.ts +41 -7
- package/src/lib/hexclave-app/apps/implementations/admin-app-impl.ts +2 -1
- package/src/lib/hexclave-app/apps/implementations/client-app-impl.cross-domain.test.ts +66 -0
- package/src/lib/hexclave-app/apps/implementations/client-app-impl.ts +40 -4
- package/src/lib/hexclave-app/apps/interfaces/admin-app.ts +1 -0
- package/src/lib/hexclave-app/apps/interfaces/client-app.ts +5 -3
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"oauth-callback.js","names":[],"sources":["../../../src/components-page/oauth-callback.tsx"],"sourcesContent":["'use client';\n\n\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 { KnownError } from \"@hexclave/shared\";\nimport { captureError } from \"@hexclave/shared/dist/utils/errors\";\nimport { runAsynchronously } from \"@hexclave/shared/dist/utils/promises\";\nimport { Spinner, cn } from \"@hexclave/ui\";\nimport { useEffect, useRef, useState } from \"react\";\nimport { useStackApp } from \"..\";\nimport { MaybeFullPage } from \"../components/elements/maybe-full-page\";\nimport { StyledLink } from \"../components/link\";\nimport {
|
|
1
|
+
{"version":3,"file":"oauth-callback.js","names":[],"sources":["../../../src/components-page/oauth-callback.tsx"],"sourcesContent":["'use client';\n\n\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 { KnownError } from \"@hexclave/shared\";\nimport { captureError } from \"@hexclave/shared/dist/utils/errors\";\nimport { runAsynchronously } from \"@hexclave/shared/dist/utils/promises\";\nimport { Spinner, cn } from \"@hexclave/ui\";\nimport { useEffect, useRef, useState } from \"react\";\nimport { useStackApp } from \"..\";\nimport { MaybeFullPage } from \"../components/elements/maybe-full-page\";\nimport { StyledLink } from \"../components/link\";\nimport { useTranslation } from \"../lib/translations\";\nimport { ErrorPage } from \"./error-page\";\n\nexport function OAuthCallback({ fullPage }: { fullPage?: boolean }) {\n const { t } = useTranslation();\n const app = useStackApp();\n const called = useRef(false);\n const [showRedirectLink, setShowRedirectLink] = useState(false);\n const [errorSearchParams, setErrorSearchParams] = useState<Record<string, string> | null>(null);\n\n useEffect(() => runAsynchronously(async () => {\n if (called.current) return;\n called.current = true;\n try {\n const hasRedirected = await app.callOAuthCallback();\n if (!hasRedirected) {\n await app.redirectToSignIn({ noRedirectBack: true });\n }\n } catch (e) {\n if (KnownError.isKnownError(e)) {\n setErrorSearchParams({\n errorCode: e.errorCode,\n message: e.message,\n details: JSON.stringify(e.details ?? {}),\n });\n return;\n }\n captureError(\"<OAuthCallback />\", e);\n setErrorSearchParams({});\n }\n }), [app]);\n\n useEffect(() => {\n setTimeout(() => setShowRedirectLink(true), 3000);\n }, []);\n\n if (errorSearchParams != null) {\n return <ErrorPage searchParams={errorSearchParams} fullPage={fullPage} />;\n }\n\n return (\n <MaybeFullPage\n fullPage={fullPage ?? false}\n containerClassName=\"flex items-center justify-center\"\n >\n <div\n className={cn(\n \"text-center justify-center items-center stack-scope flex flex-col gap-4 max-w-[380px]\",\n fullPage ? \"p-4\" : \"p-0\"\n )}\n >\n <div className=\"flex flex-col justify-center items-center gap-4\">\n <Spinner size={20} />\n </div>\n {showRedirectLink ? <p>{t('If you are not redirected automatically, ')}<StyledLink\n className=\"whitespace-nowrap\"\n href=\"#\"\n onClick={(e) => {\n e.preventDefault();\n runAsynchronously(app.redirectToHome());\n }}\n >{t(\"click here\")}</StyledLink></p> : null}\n </div>\n </MaybeFullPage>\n );\n}\n"],"mappings":";;;;;;;;;;;;;;;AAkBA,SAAgB,cAAc,EAAE,YAAoC;CAClE,MAAM,EAAE,MAAM,gBAAgB;CAC9B,MAAM,MAAM,aAAa;CACzB,MAAM,SAAS,OAAO,MAAM;CAC5B,MAAM,CAAC,kBAAkB,uBAAuB,SAAS,MAAM;CAC/D,MAAM,CAAC,mBAAmB,wBAAwB,SAAwC,KAAK;AAE/F,iBAAgB,kBAAkB,YAAY;AAC5C,MAAI,OAAO,QAAS;AACpB,SAAO,UAAU;AACjB,MAAI;AAEF,OAAI,CADkB,MAAM,IAAI,mBAAmB,CAEjD,OAAM,IAAI,iBAAiB,EAAE,gBAAgB,MAAM,CAAC;WAE/C,GAAG;AACV,OAAI,WAAW,aAAa,EAAE,EAAE;AAC9B,yBAAqB;KACnB,WAAW,EAAE;KACb,SAAS,EAAE;KACX,SAAS,KAAK,UAAU,EAAE,WAAW,EAAE,CAAC;KACzC,CAAC;AACF;;AAEF,gBAAa,qBAAqB,EAAE;AACpC,wBAAqB,EAAE,CAAC;;GAE1B,EAAE,CAAC,IAAI,CAAC;AAEV,iBAAgB;AACd,mBAAiB,oBAAoB,KAAK,EAAE,IAAK;IAChD,EAAE,CAAC;AAEN,KAAI,qBAAqB,KACvB,QAAO,oBAAC;EAAU,cAAc;EAA6B;GAAY;AAG3E,QACE,oBAAC;EACC,UAAU,YAAY;EACtB,oBAAmB;YAEnB,qBAAC;GACC,WAAW,GACT,yFACA,WAAW,QAAQ,MACpB;cAED,oBAAC;IAAI,WAAU;cACb,oBAAC,WAAQ,MAAM,KAAM;KACjB,EACL,mBAAmB,qBAAC,kBAAG,EAAE,4CAA4C,EAAC,oBAAC;IACtE,WAAU;IACV,MAAK;IACL,UAAU,MAAM;AACd,OAAE,gBAAgB;AAClB,uBAAkB,IAAI,gBAAgB,CAAC;;cAEzC,EAAE,aAAa;KAAc,IAAI,GAAG;IAClC;GACQ"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { };
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
import React, { act } from "react";
|
|
2
|
+
import { jsx } from "react/jsx-runtime";
|
|
3
|
+
import { KnownErrors } from "@hexclave/shared";
|
|
4
|
+
import { createRoot } from "react-dom/client";
|
|
5
|
+
import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
|
|
6
|
+
import { OAuthCallback } from "./oauth-callback.js";
|
|
7
|
+
import { TranslationProviderClient } from "../providers/translation-provider-client.js";
|
|
8
|
+
|
|
9
|
+
//#region src/components-page/oauth-callback.test.tsx
|
|
10
|
+
// @vitest-environment jsdom
|
|
11
|
+
const appMockState = vi.hoisted(() => ({ app: null }));
|
|
12
|
+
vi.mock("..", () => ({ useStackApp: () => {
|
|
13
|
+
if (appMockState.app == null) throw new Error("Expected test app to be set before rendering.");
|
|
14
|
+
return appMockState.app;
|
|
15
|
+
} }));
|
|
16
|
+
vi.mock("@hexclave/ui", async () => {
|
|
17
|
+
await import("react");
|
|
18
|
+
return {
|
|
19
|
+
Button: (props) => /* @__PURE__ */ jsx("button", {
|
|
20
|
+
type: "button",
|
|
21
|
+
onClick: props.onClick,
|
|
22
|
+
children: props.children
|
|
23
|
+
}),
|
|
24
|
+
Spinner: () => /* @__PURE__ */ jsx("div", { "data-testid": "spinner" }),
|
|
25
|
+
Typography: (props) => /* @__PURE__ */ jsx("div", { children: props.children }),
|
|
26
|
+
cn: (...classes) => classes.filter(Boolean).join(" ")
|
|
27
|
+
};
|
|
28
|
+
});
|
|
29
|
+
const previousActEnvironment = Reflect.get(globalThis, "IS_REACT_ACT_ENVIRONMENT");
|
|
30
|
+
function createAppTestDouble(options) {
|
|
31
|
+
return {
|
|
32
|
+
callOAuthCallback: options.callOAuthCallback,
|
|
33
|
+
redirectToSignIn: vi.fn(async () => {}),
|
|
34
|
+
redirectToHome: vi.fn(async () => {})
|
|
35
|
+
};
|
|
36
|
+
}
|
|
37
|
+
let root = null;
|
|
38
|
+
let container = null;
|
|
39
|
+
async function renderWithApp(app) {
|
|
40
|
+
appMockState.app = app;
|
|
41
|
+
container = document.createElement("div");
|
|
42
|
+
document.body.append(container);
|
|
43
|
+
root = createRoot(container);
|
|
44
|
+
await act(async () => {
|
|
45
|
+
root?.render(/* @__PURE__ */ jsx(TranslationProviderClient, {
|
|
46
|
+
quetzalKeys: /* @__PURE__ */ new Map(),
|
|
47
|
+
quetzalLocale: /* @__PURE__ */ new Map(),
|
|
48
|
+
children: /* @__PURE__ */ jsx(OAuthCallback, {})
|
|
49
|
+
}));
|
|
50
|
+
});
|
|
51
|
+
}
|
|
52
|
+
async function flushEffects() {
|
|
53
|
+
await act(async () => {
|
|
54
|
+
await new Promise((resolve) => setTimeout(resolve, 0));
|
|
55
|
+
});
|
|
56
|
+
}
|
|
57
|
+
describe("OAuthCallback", () => {
|
|
58
|
+
beforeEach(() => {
|
|
59
|
+
Reflect.set(globalThis, "IS_REACT_ACT_ENVIRONMENT", true);
|
|
60
|
+
});
|
|
61
|
+
afterEach(() => {
|
|
62
|
+
act(() => {
|
|
63
|
+
root?.unmount();
|
|
64
|
+
});
|
|
65
|
+
container?.remove();
|
|
66
|
+
root = null;
|
|
67
|
+
container = null;
|
|
68
|
+
appMockState.app = null;
|
|
69
|
+
vi.restoreAllMocks();
|
|
70
|
+
Reflect.set(globalThis, "IS_REACT_ACT_ENVIRONMENT", previousActEnvironment);
|
|
71
|
+
});
|
|
72
|
+
it("renders backend-encoded OAuth callback errors on the callback page", async () => {
|
|
73
|
+
const errorMessage = "Your sign up was rejected by an administrator's sign-up rule.";
|
|
74
|
+
const callOAuthCallback = vi.fn(async () => {
|
|
75
|
+
throw new KnownErrors.SignUpRejected(errorMessage);
|
|
76
|
+
});
|
|
77
|
+
const app = createAppTestDouble({ callOAuthCallback });
|
|
78
|
+
await renderWithApp(app);
|
|
79
|
+
await flushEffects();
|
|
80
|
+
expect(callOAuthCallback).toHaveBeenCalledOnce();
|
|
81
|
+
expect(container?.textContent).toContain("SIGN_UP_REJECTED");
|
|
82
|
+
expect(container?.textContent).toContain(errorMessage);
|
|
83
|
+
expect(app.redirectToSignIn).not.toHaveBeenCalled();
|
|
84
|
+
});
|
|
85
|
+
});
|
|
86
|
+
|
|
87
|
+
//#endregion
|
|
88
|
+
export { };
|
|
89
|
+
//# sourceMappingURL=oauth-callback.test.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"oauth-callback.test.js","names":[],"sources":["../../../src/components-page/oauth-callback.test.tsx"],"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// @vitest-environment jsdom\n\nimport { KnownErrors } from \"@hexclave/shared\";\nimport React, { act } from \"react\";\nimport { createRoot, type Root } from \"react-dom/client\";\nimport { afterEach, beforeEach, describe, expect, it, vi } from \"vitest\";\nimport type { StackClientApp } from \"../lib/hexclave-app/apps/interfaces/client-app\";\nimport { TranslationProviderClient } from \"../providers/translation-provider-client\";\nimport { OAuthCallback } from \"./oauth-callback\";\n\nconst appMockState = vi.hoisted(() => ({ app: null as unknown }));\n\nvi.mock(\"..\", () => ({\n useStackApp: () => {\n if (appMockState.app == null) {\n throw new Error(\"Expected test app to be set before rendering.\");\n }\n return appMockState.app;\n },\n}));\n\nvi.mock(\"@hexclave/ui\", async () => {\n const React = await import(\"react\");\n return {\n Button: (props: { children: React.ReactNode, onClick?: () => void }) => (\n <button type=\"button\" onClick={props.onClick}>{props.children}</button>\n ),\n Spinner: () => <div data-testid=\"spinner\" />,\n Typography: (props: { children: React.ReactNode }) => <div>{props.children}</div>,\n cn: (...classes: (string | false | null | undefined)[]) => classes.filter(Boolean).join(\" \"),\n };\n});\n\nconst previousActEnvironment = Reflect.get(globalThis, \"IS_REACT_ACT_ENVIRONMENT\");\n\nfunction createAppTestDouble(options: {\n callOAuthCallback: () => Promise<boolean>,\n}) {\n const app = {\n callOAuthCallback: options.callOAuthCallback,\n redirectToSignIn: vi.fn(async () => {}),\n redirectToHome: vi.fn(async () => {}),\n };\n\n // This test double intentionally implements only the StackClientApp surface\n // that OAuthCallback and the rendered error card touch.\n return app as unknown as StackClientApp<true>;\n}\n\nlet root: Root | null = null;\nlet container: HTMLDivElement | null = null;\n\nasync function renderWithApp(app: StackClientApp<true>) {\n appMockState.app = app;\n container = document.createElement(\"div\");\n document.body.append(container);\n root = createRoot(container);\n await act(async () => {\n root?.render(\n <TranslationProviderClient quetzalKeys={new Map()} quetzalLocale={new Map()}>\n <OAuthCallback />\n </TranslationProviderClient>\n );\n });\n}\n\nasync function flushEffects() {\n await act(async () => {\n await new Promise((resolve) => setTimeout(resolve, 0));\n });\n}\n\ndescribe(\"OAuthCallback\", () => {\n beforeEach(() => {\n Reflect.set(globalThis, \"IS_REACT_ACT_ENVIRONMENT\", true);\n });\n\n afterEach(() => {\n act(() => {\n root?.unmount();\n });\n container?.remove();\n root = null;\n container = null;\n appMockState.app = null;\n vi.restoreAllMocks();\n Reflect.set(globalThis, \"IS_REACT_ACT_ENVIRONMENT\", previousActEnvironment);\n });\n\n it(\"renders backend-encoded OAuth callback errors on the callback page\", async () => {\n const errorMessage = \"Your sign up was rejected by an administrator's sign-up rule.\";\n const callOAuthCallback = vi.fn(async () => {\n throw new KnownErrors.SignUpRejected(errorMessage);\n });\n const app = createAppTestDouble({ callOAuthCallback });\n\n await renderWithApp(app);\n await flushEffects();\n\n expect(callOAuthCallback).toHaveBeenCalledOnce();\n expect(container?.textContent).toContain(\"SIGN_UP_REJECTED\");\n expect(container?.textContent).toContain(errorMessage);\n expect(app.redirectToSignIn).not.toHaveBeenCalled();\n });\n});\n"],"mappings":";;;;;;;;;;AAcA,MAAM,eAAe,GAAG,eAAe,EAAE,KAAK,MAAiB,EAAE;AAEjE,GAAG,KAAK,aAAa,EACnB,mBAAmB;AACjB,KAAI,aAAa,OAAO,KACtB,OAAM,IAAI,MAAM,gDAAgD;AAElE,QAAO,aAAa;GAEvB,EAAE;AAEH,GAAG,KAAK,gBAAgB,YAAY;AACpB,OAAM,OAAO;AAC3B,QAAO;EACL,SAAS,UACP,oBAAC;GAAO,MAAK;GAAS,SAAS,MAAM;aAAU,MAAM;IAAkB;EAEzE,eAAe,oBAAC,SAAI,eAAY,YAAY;EAC5C,aAAa,UAAyC,oBAAC,mBAAK,MAAM,WAAe;EACjF,KAAK,GAAG,YAAmD,QAAQ,OAAO,QAAQ,CAAC,KAAK,IAAI;EAC7F;EACD;AAEF,MAAM,yBAAyB,QAAQ,IAAI,YAAY,2BAA2B;AAElF,SAAS,oBAAoB,SAE1B;AASD,QARY;EACV,mBAAmB,QAAQ;EAC3B,kBAAkB,GAAG,GAAG,YAAY,GAAG;EACvC,gBAAgB,GAAG,GAAG,YAAY,GAAG;EACtC;;AAOH,IAAI,OAAoB;AACxB,IAAI,YAAmC;AAEvC,eAAe,cAAc,KAA2B;AACtD,cAAa,MAAM;AACnB,aAAY,SAAS,cAAc,MAAM;AACzC,UAAS,KAAK,OAAO,UAAU;AAC/B,QAAO,WAAW,UAAU;AAC5B,OAAM,IAAI,YAAY;AACpB,QAAM,OACJ,oBAAC;GAA0B,6BAAa,IAAI,KAAK;GAAE,+BAAe,IAAI,KAAK;aACzE,oBAAC,kBAAgB;IACS,CAC7B;GACD;;AAGJ,eAAe,eAAe;AAC5B,OAAM,IAAI,YAAY;AACpB,QAAM,IAAI,SAAS,YAAY,WAAW,SAAS,EAAE,CAAC;GACtD;;AAGJ,SAAS,uBAAuB;AAC9B,kBAAiB;AACf,UAAQ,IAAI,YAAY,4BAA4B,KAAK;GACzD;AAEF,iBAAgB;AACd,YAAU;AACR,SAAM,SAAS;IACf;AACF,aAAW,QAAQ;AACnB,SAAO;AACP,cAAY;AACZ,eAAa,MAAM;AACnB,KAAG,iBAAiB;AACpB,UAAQ,IAAI,YAAY,4BAA4B,uBAAuB;GAC3E;AAEF,IAAG,sEAAsE,YAAY;EACnF,MAAM,eAAe;EACrB,MAAM,oBAAoB,GAAG,GAAG,YAAY;AAC1C,SAAM,IAAI,YAAY,eAAe,aAAa;IAClD;EACF,MAAM,MAAM,oBAAoB,EAAE,mBAAmB,CAAC;AAEtD,QAAM,cAAc,IAAI;AACxB,QAAM,cAAc;AAEpB,SAAO,kBAAkB,CAAC,sBAAsB;AAChD,SAAO,WAAW,YAAY,CAAC,UAAU,mBAAmB;AAC5D,SAAO,WAAW,YAAY,CAAC,UAAU,aAAa;AACtD,SAAO,IAAI,iBAAiB,CAAC,IAAI,kBAAkB;GACnD;EACF"}
|
|
@@ -4,16 +4,17 @@ import { StackClientApp } from "../lib/hexclave-app";
|
|
|
4
4
|
/**
|
|
5
5
|
* Mounts the Hexclave dev tool on the page.
|
|
6
6
|
*
|
|
7
|
-
*
|
|
8
|
-
* -
|
|
9
|
-
* -
|
|
7
|
+
* Visibility is determined by the `devTool` option:
|
|
8
|
+
* - `true`: always show
|
|
9
|
+
* - `false`: never show (caller gates this — mountDevTool won't be called)
|
|
10
|
+
* - `"auto"` / `undefined`: show based on NODE_ENV or localhost/file: heuristics
|
|
10
11
|
*
|
|
11
12
|
* Console commands (also work in production):
|
|
12
13
|
* HexclaveDevTool.enable() — force-show the dev tool
|
|
13
14
|
* HexclaveDevTool.disable() — force-hide the dev tool
|
|
14
|
-
* HexclaveDevTool.reset() — revert to
|
|
15
|
+
* HexclaveDevTool.reset() — revert to auto behavior
|
|
15
16
|
*/
|
|
16
|
-
declare function mountDevTool(app: StackClientApp<true
|
|
17
|
+
declare function mountDevTool(app: StackClientApp<true>, devToolOption?: boolean | "auto"): () => void;
|
|
17
18
|
//#endregion
|
|
18
19
|
export { mountDevTool };
|
|
19
20
|
//# sourceMappingURL=index.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","names":[],"sources":["../../../src/dev-tool/index.ts"],"mappings":";;;;;
|
|
1
|
+
{"version":3,"file":"index.d.ts","names":[],"sources":["../../../src/dev-tool/index.ts"],"mappings":";;;;;AAsGA;;;;;;;;;;;iBAAgB,YAAA,CAAa,GAAA,EAAK,cAAA,QAAsB,aAAA"}
|
|
@@ -2,6 +2,7 @@ import { runAsynchronously } from "@hexclave/shared/dist/utils/promises";
|
|
|
2
2
|
import { canMountIntoDom } from "../in-page-ui/dom.js";
|
|
3
3
|
import { captureError } from "@hexclave/shared/dist/utils/errors";
|
|
4
4
|
import { isLocalhost } from "@hexclave/shared/dist/utils/urls";
|
|
5
|
+
import { envVars } from "../generated/env.js";
|
|
5
6
|
|
|
6
7
|
//#region src/dev-tool/index.ts
|
|
7
8
|
const OVERRIDE_KEY = "__hexclave-dev-tool-override";
|
|
@@ -13,10 +14,17 @@ function getOverride() {
|
|
|
13
14
|
} catch {}
|
|
14
15
|
return null;
|
|
15
16
|
}
|
|
17
|
+
let activeDevToolOption = void 0;
|
|
16
18
|
function shouldShow() {
|
|
17
19
|
const override = getOverride();
|
|
18
20
|
if (override !== null) return override;
|
|
19
21
|
if (!canMountIntoDom()) return false;
|
|
22
|
+
if (activeDevToolOption === true) return true;
|
|
23
|
+
const nodeEnv = envVars.NODE_ENV;
|
|
24
|
+
if (nodeEnv !== void 0) return nodeEnv === "development";
|
|
25
|
+
try {
|
|
26
|
+
if (new URL(window.location.href).protocol === "file:") return true;
|
|
27
|
+
} catch {}
|
|
20
28
|
return isLocalhost(window.location.href);
|
|
21
29
|
}
|
|
22
30
|
let activeCleanup = null;
|
|
@@ -53,17 +61,19 @@ function tryMount() {
|
|
|
53
61
|
/**
|
|
54
62
|
* Mounts the Hexclave dev tool on the page.
|
|
55
63
|
*
|
|
56
|
-
*
|
|
57
|
-
* -
|
|
58
|
-
* -
|
|
64
|
+
* Visibility is determined by the `devTool` option:
|
|
65
|
+
* - `true`: always show
|
|
66
|
+
* - `false`: never show (caller gates this — mountDevTool won't be called)
|
|
67
|
+
* - `"auto"` / `undefined`: show based on NODE_ENV or localhost/file: heuristics
|
|
59
68
|
*
|
|
60
69
|
* Console commands (also work in production):
|
|
61
70
|
* HexclaveDevTool.enable() — force-show the dev tool
|
|
62
71
|
* HexclaveDevTool.disable() — force-hide the dev tool
|
|
63
|
-
* HexclaveDevTool.reset() — revert to
|
|
72
|
+
* HexclaveDevTool.reset() — revert to auto behavior
|
|
64
73
|
*/
|
|
65
|
-
function mountDevTool(app) {
|
|
74
|
+
function mountDevTool(app, devToolOption) {
|
|
66
75
|
activeApp = app;
|
|
76
|
+
activeDevToolOption = devToolOption === false ? void 0 : devToolOption ?? void 0;
|
|
67
77
|
tryMount();
|
|
68
78
|
const myCleanup = activeCleanup;
|
|
69
79
|
return () => {
|
|
@@ -101,7 +111,7 @@ if (typeof window !== "undefined") window.HexclaveDevTool = window.HexclaveDevTo
|
|
|
101
111
|
activeCleanup();
|
|
102
112
|
activeCleanup = null;
|
|
103
113
|
}
|
|
104
|
-
console.log("[Stack DevTool] Reset to default (
|
|
114
|
+
console.log("[Stack DevTool] Reset to default (auto-detect based on NODE_ENV or localhost/file: origin).");
|
|
105
115
|
}
|
|
106
116
|
};
|
|
107
117
|
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","names":[],"sources":["../../../src/dev-tool/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 type { StackClientApp } from \"../lib/hexclave-app\";\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 { canMountIntoDom } from \"../in-page-ui/dom\";\nimport type { createDevTool as CreateDevToolFn } from \"./dev-tool-core\";\n\n// Hexclave rebrand: UI-only local pref — straight rename (one-time reset is harmless)\nconst OVERRIDE_KEY = '__hexclave-dev-tool-override';\n\nfunction getOverride(): boolean | null {\n try {\n const val = localStorage.getItem(OVERRIDE_KEY);\n if (val === 'true') return true;\n if (val === 'false') return false;\n } catch {}\n return null;\n}\n\nfunction shouldShow(): boolean {\n const override = getOverride();\n if (override !== null) return override;\n if (!canMountIntoDom()) return false;\n return isLocalhost(window.location.href);\n}\n\nlet activeCleanup: (() => void) | null = null;\nlet activeApp: StackClientApp<true> | null = null;\nlet mountGeneration = 0;\n\nlet createDevToolPromise: Promise<typeof CreateDevToolFn> | null = null;\nfunction loadCreateDevTool(): Promise<typeof CreateDevToolFn> {\n if (!createDevToolPromise) {\n createDevToolPromise = import(\"./dev-tool-core\").then(m => m.createDevTool).catch((err) => {\n createDevToolPromise = null;\n throw err;\n });\n }\n return createDevToolPromise;\n}\n\nfunction tryMount() {\n if (activeCleanup) {\n activeCleanup();\n activeCleanup = null;\n }\n\n if (!shouldShow() || !activeApp || !canMountIntoDom()) return;\n\n const generation = ++mountGeneration;\n const app = activeApp;\n\n runAsynchronously(async () => {\n const createDevTool = await loadCreateDevTool();\n if (generation !== mountGeneration) return;\n if (!shouldShow() || activeApp !== app || !canMountIntoDom()) return;\n activeCleanup = createDevTool(app);\n }, {\n noErrorLogging: true,\n onError: (error) => {\n captureError(\"dev-tool-mount\", error);\n },\n });\n}\n\n/**\n * Mounts the Hexclave dev tool on the page.\n *\n *
|
|
1
|
+
{"version":3,"file":"index.js","names":[],"sources":["../../../src/dev-tool/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 type { StackClientApp } from \"../lib/hexclave-app\";\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 { canMountIntoDom } from \"../in-page-ui/dom\";\nimport { envVars } from \"../generated/env\";\nimport type { createDevTool as CreateDevToolFn } from \"./dev-tool-core\";\n\n// Hexclave rebrand: UI-only local pref — straight rename (one-time reset is harmless)\nconst OVERRIDE_KEY = '__hexclave-dev-tool-override';\n\nfunction getOverride(): boolean | null {\n try {\n const val = localStorage.getItem(OVERRIDE_KEY);\n if (val === 'true') return true;\n if (val === 'false') return false;\n } catch {}\n return null;\n}\n\nlet activeDevToolOption: true | \"auto\" | undefined = undefined;\n\nfunction shouldShow(): boolean {\n const override = getOverride();\n if (override !== null) return override;\n if (!canMountIntoDom()) return false;\n\n // If devTool was explicitly set to true, always show\n if (activeDevToolOption === true) return true;\n\n // \"auto\" behavior (the default):\n const nodeEnv = envVars.NODE_ENV;\n if (nodeEnv !== undefined) {\n // NODE_ENV is available (bundler/process env exists) — only show in development\n return nodeEnv === \"development\";\n }\n\n // NODE_ENV not found (no process.env/import.meta) — show on localhost or file: protocol\n try {\n const url = new URL(window.location.href);\n if (url.protocol === \"file:\") return true;\n } catch {}\n return isLocalhost(window.location.href);\n}\n\nlet activeCleanup: (() => void) | null = null;\nlet activeApp: StackClientApp<true> | null = null;\nlet mountGeneration = 0;\n\nlet createDevToolPromise: Promise<typeof CreateDevToolFn> | null = null;\nfunction loadCreateDevTool(): Promise<typeof CreateDevToolFn> {\n if (!createDevToolPromise) {\n createDevToolPromise = import(\"./dev-tool-core\").then(m => m.createDevTool).catch((err) => {\n createDevToolPromise = null;\n throw err;\n });\n }\n return createDevToolPromise;\n}\n\nfunction tryMount() {\n if (activeCleanup) {\n activeCleanup();\n activeCleanup = null;\n }\n\n if (!shouldShow() || !activeApp || !canMountIntoDom()) return;\n\n const generation = ++mountGeneration;\n const app = activeApp;\n\n runAsynchronously(async () => {\n const createDevTool = await loadCreateDevTool();\n if (generation !== mountGeneration) return;\n if (!shouldShow() || activeApp !== app || !canMountIntoDom()) return;\n activeCleanup = createDevTool(app);\n }, {\n noErrorLogging: true,\n onError: (error) => {\n captureError(\"dev-tool-mount\", error);\n },\n });\n}\n\n/**\n * Mounts the Hexclave dev tool on the page.\n *\n * Visibility is determined by the `devTool` option:\n * - `true`: always show\n * - `false`: never show (caller gates this — mountDevTool won't be called)\n * - `\"auto\"` / `undefined`: show based on NODE_ENV or localhost/file: heuristics\n *\n * Console commands (also work in production):\n * HexclaveDevTool.enable() — force-show the dev tool\n * HexclaveDevTool.disable() — force-hide the dev tool\n * HexclaveDevTool.reset() — revert to auto behavior\n */\nexport function mountDevTool(app: StackClientApp<true>, devToolOption?: boolean | \"auto\"): () => void {\n activeApp = app;\n activeDevToolOption = devToolOption === false ? undefined : devToolOption ?? undefined;\n tryMount();\n\n // Capture the cleanup created by THIS specific mount call so that React\n // StrictMode's double-invoke doesn't let the first effect's cleanup tear\n // down the second mount (which would cause the tool to disappear silently).\n const myCleanup = activeCleanup;\n\n return () => {\n activeApp = null;\n if (activeCleanup === myCleanup && myCleanup != null) {\n activeCleanup = null;\n myCleanup();\n }\n };\n}\n\n// Expose console commands: HexclaveDevTool.enable() / .disable() / .reset()\nif (typeof window !== 'undefined') {\n // Hexclave rebrand: expose under both the legacy and new global names.\n (window as any).HexclaveDevTool = (window as any).HexclaveDevTool = {\n enable() {\n try {\n localStorage.setItem(OVERRIDE_KEY, 'true');\n } catch {}\n tryMount();\n console.log('[Stack DevTool] Enabled. Refresh if the panel does not appear.');\n },\n disable() {\n try {\n localStorage.setItem(OVERRIDE_KEY, 'false');\n } catch {}\n if (activeCleanup) {\n activeCleanup();\n activeCleanup = null;\n }\n console.log('[Stack DevTool] Disabled.');\n },\n reset() {\n try {\n localStorage.removeItem(OVERRIDE_KEY);\n } catch {}\n if (shouldShow()) {\n tryMount();\n } else if (activeCleanup) {\n activeCleanup();\n activeCleanup = null;\n }\n console.log('[Stack DevTool] Reset to default (auto-detect based on NODE_ENV or localhost/file: origin).');\n },\n };\n}\n\n"],"mappings":";;;;;;;AAcA,MAAM,eAAe;AAErB,SAAS,cAA8B;AACrC,KAAI;EACF,MAAM,MAAM,aAAa,QAAQ,aAAa;AAC9C,MAAI,QAAQ,OAAQ,QAAO;AAC3B,MAAI,QAAQ,QAAS,QAAO;SACtB;AACR,QAAO;;AAGT,IAAI,sBAAiD;AAErD,SAAS,aAAsB;CAC7B,MAAM,WAAW,aAAa;AAC9B,KAAI,aAAa,KAAM,QAAO;AAC9B,KAAI,CAAC,iBAAiB,CAAE,QAAO;AAG/B,KAAI,wBAAwB,KAAM,QAAO;CAGzC,MAAM,UAAU,QAAQ;AACxB,KAAI,YAAY,OAEd,QAAO,YAAY;AAIrB,KAAI;AAEF,MADY,IAAI,IAAI,OAAO,SAAS,KAAK,CACjC,aAAa,QAAS,QAAO;SAC/B;AACR,QAAO,YAAY,OAAO,SAAS,KAAK;;AAG1C,IAAI,gBAAqC;AACzC,IAAI,YAAyC;AAC7C,IAAI,kBAAkB;AAEtB,IAAI,uBAA+D;AACnE,SAAS,oBAAqD;AAC5D,KAAI,CAAC,qBACH,wBAAuB,OAAO,sBAAmB,MAAK,MAAK,EAAE,cAAc,CAAC,OAAO,QAAQ;AACzF,yBAAuB;AACvB,QAAM;GACN;AAEJ,QAAO;;AAGT,SAAS,WAAW;AAClB,KAAI,eAAe;AACjB,iBAAe;AACf,kBAAgB;;AAGlB,KAAI,CAAC,YAAY,IAAI,CAAC,aAAa,CAAC,iBAAiB,CAAE;CAEvD,MAAM,aAAa,EAAE;CACrB,MAAM,MAAM;AAEZ,mBAAkB,YAAY;EAC5B,MAAM,gBAAgB,MAAM,mBAAmB;AAC/C,MAAI,eAAe,gBAAiB;AACpC,MAAI,CAAC,YAAY,IAAI,cAAc,OAAO,CAAC,iBAAiB,CAAE;AAC9D,kBAAgB,cAAc,IAAI;IACjC;EACD,gBAAgB;EAChB,UAAU,UAAU;AAClB,gBAAa,kBAAkB,MAAM;;EAExC,CAAC;;;;;;;;;;;;;;;AAgBJ,SAAgB,aAAa,KAA2B,eAA8C;AACpG,aAAY;AACZ,uBAAsB,kBAAkB,QAAQ,SAAY,iBAAiB;AAC7E,WAAU;CAKV,MAAM,YAAY;AAElB,cAAa;AACX,cAAY;AACZ,MAAI,kBAAkB,aAAa,aAAa,MAAM;AACpD,mBAAgB;AAChB,cAAW;;;;AAMjB,IAAI,OAAO,WAAW,YAEpB,CAAC,OAAe,kBAAkB,AAAC,OAAe,kBAAkB;CAClE,SAAS;AACP,MAAI;AACF,gBAAa,QAAQ,cAAc,OAAO;UACpC;AACR,YAAU;AACV,UAAQ,IAAI,iEAAiE;;CAE/E,UAAU;AACR,MAAI;AACF,gBAAa,QAAQ,cAAc,QAAQ;UACrC;AACR,MAAI,eAAe;AACjB,kBAAe;AACf,mBAAgB;;AAElB,UAAQ,IAAI,4BAA4B;;CAE1C,QAAQ;AACN,MAAI;AACF,gBAAa,WAAW,aAAa;UAC/B;AACR,MAAI,YAAY,CACd,WAAU;WACD,eAAe;AACxB,kBAAe;AACf,mBAAgB;;AAElB,UAAQ,IAAI,8FAA8F;;CAE7G"}
|