import { existsSync, mkdirSync } from "fs"; import { getGloomberbHome } from "../../../../data/config/home"; import { createAppServices, type AppServices } from "../../../../core/app-services"; import { loadDesktopBackendPlugins } from "../../../../plugins/catalog-backend"; import { restoreExtractedPlugins } from "../../../../cli/restore-plugins"; import type { AppSessionSnapshot } from "../../../../core/state/session-persistence"; import { getDataDir, initDataDir, } from "../../../../data/config/store"; import type { AppConfig } from "../../../../types/config"; import type { DesktopSharedStateSnapshot, DesktopThemePreviewState, } from "../../../../types/desktop-window"; import { loadDesktopPluginState, } from "./plugin-state"; import { createDesktopWorkspace, type DesktopWorkspace, } from "./workspace"; import { MAIN_WINDOW_RPC_KEY, paneIdFromDetachedRpcKey, } from "../window/focus"; import type { DesktopBackendRequestPayload, ElectrobunBackendInit } from "../../shared/protocol"; import type { CapabilityRegistry } from "../../../../capabilities"; interface DesktopWindowTarget { kind: "main" | "detached"; paneId?: string; } interface InitializeDesktopBackendOptions { getCurrentConfig: () => AppConfig | null; getCurrentServices: () => AppServices | null; getDesktopSnapshot: () => DesktopSharedStateSnapshot | null; getDesktopWorkspace: () => DesktopWorkspace | null; getRpcWindowKey: (rpc: TRpc) => string | undefined; getSessionSnapshot: () => AppSessionSnapshot | null; getThemePreview: () => DesktopThemePreviewState; markWindowRpcReady: (rpc: TRpc) => void; payload: DesktopBackendRequestPayload<"init">; reconcileDetachedWindows: () => void; registerCoreCapabilities: () => void; rpc: TRpc; setCurrentConfig: (config: AppConfig) => void; setDesktopWorkspace: (workspace: DesktopWorkspace) => void; setServices: (services: AppServices) => void; syncConfigAccessors: () => void; } interface InitializationPayloadOptions { desktopThemePreview: DesktopThemePreviewState; getDesktopSnapshot: () => DesktopSharedStateSnapshot | null; getSessionSnapshot: () => AppSessionSnapshot | null; } function normalizeInitWindowTarget( rpc: TRpc, payload: DesktopBackendRequestPayload<"init">, getRpcWindowKey: (rpc: TRpc) => string | undefined, ): DesktopWindowTarget { const rpcKey = getRpcWindowKey(rpc); if (rpcKey === MAIN_WINDOW_RPC_KEY) return { kind: "main" }; const detachedPaneId = paneIdFromDetachedRpcKey(rpcKey); if (detachedPaneId) { return { kind: "detached", paneId: detachedPaneId, }; } const kind = payload.kind === "detached" ? "detached" : "main"; return { kind, paneId: kind === "detached" && typeof payload.paneId === "string" && payload.paneId.length > 0 ? payload.paneId : undefined, }; } export function desktopRendererCapabilityManifests(registry: CapabilityRegistry) { return registry.manifests({ rendererOnly: true, includeDisabled: true }); } function buildInitializationPayload( config: AppConfig, services: AppServices, windowTarget: DesktopWindowTarget, options: InitializationPayloadOptions, ): ElectrobunBackendInit { return { config, sessionSnapshot: options.getSessionSnapshot(), desktopSnapshot: options.getDesktopSnapshot(), desktopThemePreview: options.desktopThemePreview, pluginState: loadDesktopPluginState(services.pluginRegistry), capabilityManifests: desktopRendererCapabilityManifests(services.pluginRegistry.capabilities), desktopPlatform: process.platform, windowKind: windowTarget.kind, paneId: windowTarget.paneId, }; } async function resolveDesktopDataDir(): Promise { const dataDir = await getDataDir() ?? getGloomberbHome(); if (!existsSync(dataDir)) { mkdirSync(dataDir, { recursive: true }); } return dataDir; } export async function initializeDesktopBackend( options: InitializeDesktopBackendOptions, ): Promise { const windowTarget = normalizeInitWindowTarget(options.rpc, options.payload, options.getRpcWindowKey); options.markWindowRpcReady(options.rpc); const currentConfig = options.getCurrentConfig(); const currentServices = options.getCurrentServices(); if (currentConfig && currentServices) { if (!options.getDesktopWorkspace()) { options.setDesktopWorkspace(createDesktopWorkspace(currentConfig, options.getSessionSnapshot())); options.reconcileDetachedWindows(); } return buildInitializationPayload(currentConfig, currentServices, windowTarget, { getDesktopSnapshot: options.getDesktopSnapshot, getSessionSnapshot: options.getSessionSnapshot, desktopThemePreview: options.getThemePreview(), }); } const initialConfig = await initDataDir(await resolveDesktopDataDir()); // Restore plugins that moved into their own repositories before the catalog // is read, the same as the terminal does at startup. Without it a // desktop-only user silently loses a feature the day it is extracted, since // nothing else installs it for them. const seededPlugins = await restoreExtractedPlugins(); options.setCurrentConfig(seededPlugins ? { ...initialConfig, seededPlugins } : initialConfig); const config = options.getCurrentConfig(); if (!config) throw new Error("Desktop config failed to initialize."); const services = createAppServices({ config, ...await loadDesktopBackendPlugins(), }); options.setServices(services); await services.ready; options.syncConfigAccessors(); options.registerCoreCapabilities(); options.setDesktopWorkspace(createDesktopWorkspace(config, options.getSessionSnapshot())); options.reconcileDetachedWindows(); return buildInitializationPayload(config, services, windowTarget, { getDesktopSnapshot: options.getDesktopSnapshot, getSessionSnapshot: options.getSessionSnapshot, desktopThemePreview: options.getThemePreview(), }); }