feat(FN-3407): normalize shell host bootstrap

Introduces a canonical shell host context and normalized bootstrap flow for the dashboard, extracting the host initialization logic into a dedicated `shell-host.ts` module with a `ShellHostContext` provider; updates `App.tsx` and `Header` to use the new context, stabilizes related tests, and documen

Fusion-Task-Id: FN-3407
This commit is contained in:
Fusion
2026-05-07 22:56:16 -07:00
committed by gsxdsm
parent 301a3a77ac
commit a6d019d70a
11 changed files with 432 additions and 23 deletions

View File

@@ -0,0 +1,179 @@
export type ShellHostKind = "browser" | "desktop-shell" | "mobile-shell";
export type ShellHostMode = "local" | "remote";
export type ShellHostContext =
| { kind: "browser" }
| {
kind: "desktop-shell" | "mobile-shell";
mode?: ShellHostMode;
connectionId?: string;
serverUrl?: string;
canOpenConnectionManager?: boolean;
};
export const SHELL_HOST_QUERY_KEYS = [
"shellKind",
"shellMode",
"profileId",
"serverBaseUrl",
"serverLabel",
"shellCanOpenConnectionManager",
"hostKind",
"mode",
"connectionId",
"serverUrl",
"canOpenConnectionManager",
] as const;
const BOOTSTRAP_GLOBAL_KEYS = [
"__FUSION_SHELL_HOST_CONTEXT__",
"__fusionShellHostContext",
"__FUSION_SHELL_CONTEXT__",
] as const;
let cachedContext: ShellHostContext | null = null;
let bootstrapped = false;
function parseBoolean(value: unknown): boolean | undefined {
if (typeof value === "boolean") return value;
if (typeof value !== "string") return undefined;
if (value === "1" || value === "true") return true;
if (value === "0" || value === "false") return false;
return undefined;
}
function normalizeServerUrl(value: unknown): string | undefined {
if (typeof value !== "string" || value.trim().length === 0) return undefined;
try {
const parsed = new URL(value);
if (parsed.protocol !== "http:" && parsed.protocol !== "https:") {
return undefined;
}
return parsed.toString().replace(/\/$/, "");
} catch {
return undefined;
}
}
function normalizeKind(value: unknown): ShellHostKind | undefined {
if (value === "browser" || value === "desktop-shell" || value === "mobile-shell") return value;
if (value === "desktop") return "desktop-shell";
if (value === "mobile") return "mobile-shell";
return undefined;
}
function normalizeMode(value: unknown): ShellHostMode | undefined {
return value === "local" || value === "remote" ? value : undefined;
}
function normalizeNativeHost(input: {
kind: "desktop-shell" | "mobile-shell";
mode?: unknown;
connectionId?: unknown;
serverUrl?: unknown;
canOpenConnectionManager?: unknown;
}): ShellHostContext {
const mode = normalizeMode(input.mode);
const connectionId = typeof input.connectionId === "string" && input.connectionId.trim() ? input.connectionId : undefined;
const serverUrl = normalizeServerUrl(input.serverUrl);
const canOpenConnectionManager = parseBoolean(input.canOpenConnectionManager);
return {
kind: input.kind,
...(mode ? { mode } : {}),
...(connectionId ? { connectionId } : {}),
...(serverUrl ? { serverUrl } : {}),
...(canOpenConnectionManager !== undefined ? { canOpenConnectionManager } : {}),
};
}
function fromBootstrapGlobal(target: Window): ShellHostContext | null {
for (const key of BOOTSTRAP_GLOBAL_KEYS) {
const raw = (target as Window & Record<string, unknown>)[key];
if (!raw || typeof raw !== "object") continue;
const record = raw as Record<string, unknown>;
const kind = normalizeKind(record.kind ?? record.shellKind ?? record.hostKind);
if (!kind) continue;
if (kind === "browser") return { kind };
return normalizeNativeHost({
kind,
mode: record.mode ?? record.shellMode,
connectionId: record.connectionId ?? record.profileId,
serverUrl: record.serverUrl ?? record.serverBaseUrl,
canOpenConnectionManager: record.canOpenConnectionManager ?? record.shellCanOpenConnectionManager,
});
}
return null;
}
function fromQuery(target: Window): ShellHostContext | null {
let url: URL;
try {
url = new URL(target.location.href);
} catch {
return null;
}
const params = url.searchParams;
const kind = normalizeKind(params.get("hostKind") ?? params.get("kind") ?? params.get("shellKind"));
if (!kind) return null;
if (kind === "browser") return { kind };
return normalizeNativeHost({
kind,
mode: params.get("mode") ?? params.get("shellMode"),
connectionId: params.get("connectionId") ?? params.get("profileId"),
serverUrl: params.get("serverUrl") ?? params.get("serverBaseUrl"),
canOpenConnectionManager: params.get("canOpenConnectionManager") ?? params.get("shellCanOpenConnectionManager"),
});
}
function stripShellQueryParams(target: Window): void {
try {
const url = new URL(target.location.href);
let changed = false;
for (const key of SHELL_HOST_QUERY_KEYS) {
if (url.searchParams.has(key)) {
url.searchParams.delete(key);
changed = true;
}
}
if (!changed) return;
const cleaned = url.pathname + (url.search ? url.search : "") + url.hash;
target.history.replaceState(target.history.state, "", cleaned);
} catch {
// no-op
}
}
export function detectShellHostContext(target: Window = window): ShellHostContext {
const globalContext = fromBootstrapGlobal(target);
if (globalContext) return globalContext;
const queryContext = fromQuery(target);
if (queryContext) return queryContext;
if (typeof (target as Window & { fusionAPI?: unknown }).fusionAPI !== "undefined") {
return { kind: "desktop-shell" };
}
return { kind: "browser" };
}
export function bootstrapShellHostContext(target: Window = window): ShellHostContext {
if (!bootstrapped && typeof window !== "undefined") {
cachedContext = detectShellHostContext(target);
stripShellQueryParams(target);
bootstrapped = true;
}
return cachedContext ?? { kind: "browser" };
}
export function getShellHostContext(): ShellHostContext {
if (cachedContext) return cachedContext;
if (typeof window === "undefined") return { kind: "browser" };
return bootstrapShellHostContext(window);
}
export function __resetShellHostContextForTests(): void {
cachedContext = null;
bootstrapped = false;
}