mirror of
https://github.com/stack-auth/stack.git
synced 2026-06-30 21:01:54 +08:00
Some checks failed
all-good: Did all the other checks pass? / all-good (push) Has been cancelled
Ensure Prisma migrations are in sync with the schema / check_prisma_migrations (22.x) (push) Has been cancelled
Docker Server Build and Push / Docker Build and Push Server (push) Has been cancelled
Docker Server Build and Run / docker (push) Has been cancelled
Runs E2E API Tests (Local Emulator) / E2E Tests (Local Emulator, Node ${{ matrix.node-version }}) (22.x) (push) Has been cancelled
Runs E2E API Tests / E2E Tests (Node ${{ matrix.node-version }}, Freestyle ${{ matrix.freestyle-mode }}) (mock, 22.x) (push) Has been cancelled
Runs E2E API Tests / E2E Tests (Node ${{ matrix.node-version }}, Freestyle ${{ matrix.freestyle-mode }}) (prod, 22.x) (push) Has been cancelled
Runs E2E API Tests with custom port prefix / build (22.x) (push) Has been cancelled
Runs E2E Fallback Tests / E2E Fallback Tests (Node ${{ matrix.node-version }}) (22.x) (push) Has been cancelled
Lint & build / lint_and_build (24) (push) Has been cancelled
Publish npm packages / publish (push) Has been cancelled
Publish Swift SDK to prerelease repo / publish (push) Has been cancelled
TOC Generator / TOC Generator (push) Has been cancelled
156 lines
4.8 KiB
TypeScript
156 lines
4.8 KiB
TypeScript
// IF_PLATFORM js-like
|
|
|
|
import type { StackClientApp } from "../lib/hexclave-app";
|
|
import { captureError } from "@hexclave/shared/dist/utils/errors";
|
|
import { runAsynchronously } from "@hexclave/shared/dist/utils/promises";
|
|
import { isLocalhost } from "@hexclave/shared/dist/utils/urls";
|
|
import { canMountIntoDom } from "../in-page-ui/dom";
|
|
import { envVars } from "../generated/env";
|
|
import type { createDevTool as CreateDevToolFn } from "./dev-tool-core";
|
|
|
|
// Hexclave rebrand: UI-only local pref — straight rename (one-time reset is harmless)
|
|
const OVERRIDE_KEY = '__hexclave-dev-tool-override';
|
|
|
|
function getOverride(): boolean | null {
|
|
try {
|
|
const val = localStorage.getItem(OVERRIDE_KEY);
|
|
if (val === 'true') return true;
|
|
if (val === 'false') return false;
|
|
} catch {}
|
|
return null;
|
|
}
|
|
|
|
let activeDevToolOption: true | "auto" | undefined = undefined;
|
|
|
|
function shouldShow(): boolean {
|
|
const override = getOverride();
|
|
if (override !== null) return override;
|
|
if (!canMountIntoDom()) return false;
|
|
|
|
// If devTool was explicitly set to true, always show
|
|
if (activeDevToolOption === true) return true;
|
|
|
|
// "auto" behavior (the default):
|
|
const nodeEnv = envVars.NODE_ENV;
|
|
if (nodeEnv !== undefined) {
|
|
// NODE_ENV is available (bundler/process env exists) — only show in development
|
|
return nodeEnv === "development";
|
|
}
|
|
|
|
// NODE_ENV not found (no process.env/import.meta) — show on localhost or file: protocol
|
|
try {
|
|
const url = new URL(window.location.href);
|
|
if (url.protocol === "file:") return true;
|
|
} catch {}
|
|
return isLocalhost(window.location.href);
|
|
}
|
|
|
|
let activeCleanup: (() => void) | null = null;
|
|
let activeApp: StackClientApp<true> | null = null;
|
|
let mountGeneration = 0;
|
|
|
|
let createDevToolPromise: Promise<typeof CreateDevToolFn> | null = null;
|
|
function loadCreateDevTool(): Promise<typeof CreateDevToolFn> {
|
|
if (!createDevToolPromise) {
|
|
createDevToolPromise = import("./dev-tool-core").then(m => m.createDevTool).catch((err) => {
|
|
createDevToolPromise = null;
|
|
throw err;
|
|
});
|
|
}
|
|
return createDevToolPromise;
|
|
}
|
|
|
|
function tryMount() {
|
|
if (activeCleanup) {
|
|
activeCleanup();
|
|
activeCleanup = null;
|
|
}
|
|
|
|
if (!shouldShow() || !activeApp || !canMountIntoDom()) return;
|
|
|
|
const generation = ++mountGeneration;
|
|
const app = activeApp;
|
|
|
|
runAsynchronously(async () => {
|
|
const createDevTool = await loadCreateDevTool();
|
|
if (generation !== mountGeneration) return;
|
|
if (!shouldShow() || activeApp !== app || !canMountIntoDom()) return;
|
|
activeCleanup = createDevTool(app);
|
|
}, {
|
|
noErrorLogging: true,
|
|
onError: (error) => {
|
|
captureError("dev-tool-mount", error);
|
|
},
|
|
});
|
|
}
|
|
|
|
/**
|
|
* Mounts the Hexclave dev tool on the page.
|
|
*
|
|
* Visibility is determined by the `devTool` option:
|
|
* - `true`: always show
|
|
* - `false`: never show (caller gates this — mountDevTool won't be called)
|
|
* - `"auto"` / `undefined`: show based on NODE_ENV or localhost/file: heuristics
|
|
*
|
|
* Console commands (also work in production):
|
|
* HexclaveDevTool.enable() — force-show the dev tool
|
|
* HexclaveDevTool.disable() — force-hide the dev tool
|
|
* HexclaveDevTool.reset() — revert to auto behavior
|
|
*/
|
|
export function mountDevTool(app: StackClientApp<true>, devToolOption?: boolean | "auto"): () => void {
|
|
activeApp = app;
|
|
activeDevToolOption = devToolOption === false ? undefined : devToolOption ?? undefined;
|
|
tryMount();
|
|
|
|
// Capture the cleanup created by THIS specific mount call so that React
|
|
// StrictMode's double-invoke doesn't let the first effect's cleanup tear
|
|
// down the second mount (which would cause the tool to disappear silently).
|
|
const myCleanup = activeCleanup;
|
|
|
|
return () => {
|
|
activeApp = null;
|
|
if (activeCleanup === myCleanup && myCleanup != null) {
|
|
activeCleanup = null;
|
|
myCleanup();
|
|
}
|
|
};
|
|
}
|
|
|
|
// Expose console commands: HexclaveDevTool.enable() / .disable() / .reset()
|
|
if (typeof window !== 'undefined') {
|
|
// Hexclave rebrand: expose under both the legacy and new global names.
|
|
(window as any).HexclaveDevTool = (window as any).HexclaveDevTool = {
|
|
enable() {
|
|
try {
|
|
localStorage.setItem(OVERRIDE_KEY, 'true');
|
|
} catch {}
|
|
tryMount();
|
|
console.log('[Stack DevTool] Enabled. Refresh if the panel does not appear.');
|
|
},
|
|
disable() {
|
|
try {
|
|
localStorage.setItem(OVERRIDE_KEY, 'false');
|
|
} catch {}
|
|
if (activeCleanup) {
|
|
activeCleanup();
|
|
activeCleanup = null;
|
|
}
|
|
console.log('[Stack DevTool] Disabled.');
|
|
},
|
|
reset() {
|
|
try {
|
|
localStorage.removeItem(OVERRIDE_KEY);
|
|
} catch {}
|
|
if (shouldShow()) {
|
|
tryMount();
|
|
} else if (activeCleanup) {
|
|
activeCleanup();
|
|
activeCleanup = null;
|
|
}
|
|
console.log('[Stack DevTool] Reset to default (auto-detect based on NODE_ENV or localhost/file: origin).');
|
|
},
|
|
};
|
|
}
|
|
|
|
// END_PLATFORM
|