import { useEffect, useState, type PropsWithChildren } from "react"; import type { ShellConnectionState } from "../types/native-shell"; import "./DesktopLaunchGate.css"; type Phase = | { kind: "loading" } | { kind: "chooser"; state: ShellConnectionState } | { kind: "starting-local"; message: string } | { kind: "local-error"; message: string } | { kind: "ready"; serverBaseUrl?: string } | { kind: "bypass" }; function getFusionShell() { if (typeof window === "undefined") return null; const shell = window.fusionShell ?? null; if (!shell) { console.log("[DesktopLaunchGate] window.fusionShell unavailable; bypassing"); } return shell; } function isDesktopShell(state: ShellConnectionState | null): boolean { return state?.host === "desktop-shell"; } function needsChooser(state: ShellConnectionState): boolean { const modeState = state.desktopModeState; if (modeState) { return modeState.isFirstRun || modeState.desktopMode === null; } return !state.desktopMode; } async function waitForLocalRuntime( shell: NonNullable>, timeoutMs = 30_000, ): Promise<{ baseUrl: string }> { const deadline = Date.now() + timeoutMs; // The runtime is started by main when setDesktopMode("local") fires; just // poll shell:getState until localRuntime reports running. while (Date.now() < deadline) { const state = await shell.getState(); const rt = state.localRuntime; if (rt?.state === "running" && (rt.baseUrl || rt.port)) { const baseUrl = rt.baseUrl ?? `http://127.0.0.1:${rt.port}`; return { baseUrl }; } if (rt?.state === "error") { throw new Error(rt.error ?? "Local runtime failed to start"); } await new Promise((resolve) => setTimeout(resolve, 250)); } throw new Error("Local runtime did not become ready in time"); } function applyServerBaseUrl(baseUrl: string): void { // Reload the page with the local runtime URL so shell-host bootstrap reads // it and routes API calls through it (the page itself is loaded via file:// // so relative /api fetches would otherwise fail). const url = new URL(window.location.href); url.searchParams.set("serverBaseUrl", baseUrl); url.searchParams.set("shellKind", "desktop-shell"); url.searchParams.set("shellMode", "local"); window.location.replace(url.toString()); } export function DesktopLaunchGate({ children }: PropsWithChildren) { const [phase, setPhase] = useState({ kind: "loading" }); useEffect(() => { const shell = getFusionShell(); if (!shell) { setPhase({ kind: "bypass" }); return; } let cancelled = false; void (async () => { try { console.log("[DesktopLaunchGate] fetching shell state…"); const state = await shell.getState(); if (cancelled) return; console.log("[DesktopLaunchGate] shell state:", state); if (!isDesktopShell(state)) { console.log("[DesktopLaunchGate] not desktop-shell host; bypassing"); setPhase({ kind: "bypass" }); return; } if (needsChooser(state)) { console.log("[DesktopLaunchGate] first run / no mode chosen — showing chooser"); setPhase({ kind: "chooser", state }); return; } // Mode already chosen. If local, ensure runtime URL is in the // current page URL; if remote, App handles the redirect to the // active profile already. if (state.desktopMode === "local") { const params = new URLSearchParams(window.location.search); if (params.has("serverBaseUrl")) { setPhase({ kind: "ready", serverBaseUrl: params.get("serverBaseUrl") ?? undefined }); return; } setPhase({ kind: "starting-local", message: "Starting local Fusion runtime…" }); const { baseUrl } = await waitForLocalRuntime(shell); if (cancelled) return; applyServerBaseUrl(baseUrl); return; } setPhase({ kind: "ready" }); } catch (error) { if (cancelled) return; setPhase({ kind: "local-error", message: error instanceof Error ? error.message : String(error), }); } })(); return () => { cancelled = true; }; }, []); useEffect(() => { const shell = getFusionShell(); if (!shell?.onResetDesktopModeRequest || !shell.resetDesktopMode) { return; } return shell.onResetDesktopModeRequest(() => { void (async () => { try { await shell.resetDesktopMode?.(); } catch { // Reset best-effort; we still reload to give the user a fresh // chooser even if the IPC failed. } const url = new URL(window.location.href); url.searchParams.delete("serverBaseUrl"); url.searchParams.delete("shellMode"); window.location.replace(url.toString()); })(); }); }, []); if (phase.kind === "loading" || phase.kind === "starting-local") { const message = phase.kind === "loading" ? "Loading Fusion…" : phase.message; return (

{message}

); } if (phase.kind === "local-error") { return (

Couldn't start local Fusion

{phase.message}

); } if (phase.kind === "chooser") { return ( { const shell = getFusionShell(); if (!shell) return; setPhase({ kind: "starting-local", message: mode === "local" ? "Starting local Fusion runtime…" : "Setting up remote connection…", }); try { await shell.setDesktopMode(mode); if (mode === "local") { const { baseUrl } = await waitForLocalRuntime(shell); applyServerBaseUrl(baseUrl); return; } await shell.openConnectionManager(); // After remote setup the App will pick up the active profile and // redirect; reload to flush bootstrap state. window.location.reload(); } catch (error) { setPhase({ kind: "local-error", message: error instanceof Error ? error.message : String(error), }); } }} /> ); } return <>{children}; } function DesktopModeChooser({ onPick }: { onPick: (mode: "local" | "remote") => void }) { const [pending, setPending] = useState<"local" | "remote" | null>(null); return (

How do you want to run Fusion?

Run Fusion locally in this app, or connect to a Fusion server you're already running somewhere else.

); }