import { contextBridge, ipcRenderer } from "electron"; import type { DeepLinkResult, FusionAPI, SystemInfo, UpdateCheckResult } from "./types"; interface ShellConnectionProfile { id: string; name: string; serverUrl: string; authToken?: string | null; createdAt: string; updatedAt: string; lastUsedAt?: string | null; } interface ShellConnectionProfileInput { id?: string; name: string; serverUrl: string; authToken?: string | null; } interface ShellConnectionState { host: "web" | "mobile-shell" | "desktop-shell"; desktopModeState?: { isFirstRun: boolean; desktopMode: "local" | "remote" | null; }; desktopMode?: "local" | "remote"; activeProfileId: string | null; profiles: ShellConnectionProfile[]; localRuntime?: { source: "embedded-local" | "external-cli" | "none"; state: "stopped" | "starting" | "running" | "error"; port?: number; baseUrl?: string; error?: string; }; } export type FusionDesktopAPI = FusionAPI; type WindowControlAction = "minimize" | "maximize" | "close" | "isMaximized"; const electronApi = { // Window control minimize: (): Promise => ipcRenderer.invoke("window:minimize"), maximize: (): Promise => ipcRenderer.invoke("window:maximize"), close: (): Promise => ipcRenderer.invoke("window:close"), isMaximized: (): Promise => ipcRenderer.invoke("window:isMaximized"), // App info getSystemInfo: (): Promise => ipcRenderer.invoke("app:getSystemInfo"), checkForUpdates: (): Promise => ipcRenderer.invoke("app:checkForUpdates"), getServerPort: (): Promise => ipcRenderer.invoke("app:getServerPort"), getDesktopRuntimeStatus: (): Promise => ipcRenderer.invoke("desktopRuntime:getStatus"), startDesktopLocalRuntime: (): Promise => ipcRenderer.invoke("desktopRuntime:startLocal"), stopDesktopLocalRuntime: (): Promise => ipcRenderer.invoke("desktopRuntime:stopLocal"), getDesktopLaunchMode: (): Promise<"choose" | "local" | "remote"> => ipcRenderer.invoke("desktopLaunchMode:getMode"), setDesktopLaunchMode: (mode: "choose" | "local" | "remote"): Promise<"choose" | "local" | "remote"> => ipcRenderer.invoke("desktopLaunchMode:setMode", mode), getDesktopLaunchContext: (): Promise<{ mode: "remote"; profileId: string; serverBaseUrl: string; serverLabel?: string; authToken?: string } | null> => ipcRenderer.invoke("desktopLaunchMode:getContext"), openConnectionManager: (): Promise => ipcRenderer.invoke("shell:openConnectionManager"), // Tray status updateTrayStatus: (status: string): Promise => ipcRenderer.invoke("tray:updateStatus", status), // Native dialogs showExportDialog: (): Promise => ipcRenderer.invoke("native:showExportDialog"), showImportDialog: (): Promise => ipcRenderer.invoke("native:showImportDialog"), // Deep link events (main → renderer) onDeepLink: (callback: (result: DeepLinkResult) => void): (() => void) => { const handler = (_event: Electron.IpcRendererEvent, result: DeepLinkResult) => callback(result); ipcRenderer.on("deep-link", handler); return () => ipcRenderer.removeListener("deep-link", handler); }, windowControl: async (action: WindowControlAction): Promise => { switch (action) { case "minimize": return ipcRenderer.invoke("window:minimize"); case "maximize": return ipcRenderer.invoke("window:maximize"); case "close": return ipcRenderer.invoke("window:close"); case "isMaximized": return ipcRenderer.invoke("window:isMaximized"); } }, getPlatform: (): Promise<"darwin" | "win32" | "linux"> => ipcRenderer.invoke("platform:get"), apiRequest: (method: string, path: string, body?: unknown): Promise => ipcRenderer.invoke("api-request", { method, path, body }), // Auto-updater events (main → renderer) onUpdateAvailable: (callback: (info: { version: string }) => void): (() => void) => { const handler = (_event: Electron.IpcRendererEvent, info: { version: string }) => callback(info); ipcRenderer.on("update-available", handler); return () => ipcRenderer.removeListener("update-available", handler); }, onUpdateDownloaded: (callback: () => void): (() => void) => { const handler = () => callback(); ipcRenderer.on("update-downloaded", handler); return () => ipcRenderer.removeListener("update-downloaded", handler); }, invoke: (channel: string, payload?: unknown): Promise => ipcRenderer.invoke(channel, payload), }; const fusionShell = { getState: (): Promise => ipcRenderer.invoke("shell:getState"), listProfiles: (): Promise => ipcRenderer.invoke("shell:listProfiles"), saveProfile: (profile: ShellConnectionProfileInput): Promise => ipcRenderer.invoke("shell:saveProfile", profile), deleteProfile: (profileId: string): Promise => ipcRenderer.invoke("shell:deleteProfile", profileId), setActiveProfile: (profileId: string | null): Promise => ipcRenderer.invoke("shell:setActiveProfile", profileId), getDesktopModeState: (): Promise<{ isFirstRun: boolean; desktopMode: "local" | "remote" | null }> => ipcRenderer.invoke("shell:getDesktopModeState"), setDesktopMode: (mode: "local" | "remote"): Promise => ipcRenderer.invoke("shell:setDesktopMode", mode), startQrScan: (): Promise<{ serverUrl: string; authToken?: string | null }> => ipcRenderer.invoke("shell:startQrScan"), openConnectionManager: (): Promise => ipcRenderer.invoke("shell:openConnectionManager"), subscribe: (listener: (state: ShellConnectionState) => void): (() => void) => { const handler = (_event: Electron.IpcRendererEvent, state: ShellConnectionState) => listener(state); ipcRenderer.on("shell:state", handler); return () => ipcRenderer.removeListener("shell:state", handler); }, }; contextBridge.exposeInMainWorld("electronAPI", electronApi); contextBridge.exposeInMainWorld("fusionAPI", electronApi); contextBridge.exposeInMainWorld("fusionShell", fusionShell);