/** @jsxImportSource react */
import { setCurrentPluginTarget } from "../../../plugins/current-target";
import { createRoot } from "react-dom/client";
import { App } from "../../../app";
import { applyLanguageFromConfig } from "../../../i18n";
import { UiHostProvider } from "../../../ui/host";
import { debugLog } from "../../../utils/debug-log";
import { measurePerfAsync } from "../../../utils/perf-marks";
import {
backendRequest,
initElectrobunBackend,
replaceElectrobunCapabilityManifests,
setElectrobunRemoteRequestHandler,
} from "./backend-rpc";
import { installElectrobunCapabilityStreamClient } from "./capability-stream-client";
import { installFocusScopeRelease } from "./host/focus-scope";
import { installElectrobunBrokerRemoteClient } from "./broker-remote-client";
import { installElectrobunConfigStoreHost } from "./config-host";
import { WebDialogHostProvider } from "./dialog-host";
import {
installElectrobunCloudApiFetchTransport,
installElectrobunHttpFetchTransport,
} from "./http-fetch";
import { installElectrobunUpdateHost } from "./update-host";
import { installScreenshotWatermark } from "./screenshot-watermark";
import { installElectrobunWindowFullscreenTracking } from "./window-fullscreen";
import { installDomMarketDataFrames } from "./data-frames";
import { DesktopFatalScreen, ElectrobunErrorBoundary } from "./fatal-screen";
import { WebInputHostProvider } from "./input-host";
import { webNativeRenderer } from "./native-renderer";
import { WebToastHostProvider } from "./toast-host";
import { createWebUiHost, webRendererHost } from "./ui-host";
import { createApplicationMenuBridge } from "./application-menu-bridge";
import { createDesktopDeepLinkBridge } from "./desktop-deeplink-bridge";
import {
initializeDesktopResearchActivity,
observeDesktopDeepLinks,
} from "../../../api-client/research-activity";
import { createDesktopWindowBridge } from "./desktop/window/bridge";
import { prepareDetachedSnapshot } from "./desktop/window/snapshot";
import { createElectrobunAppServices } from "./app-services";
import { getRendererPlugins } from "../../../plugins/catalog-ui";
import { loadDesktopExternalPlugin, loadDesktopExternalPlugins } from "./external-plugins";
import { setPluginManager } from "../../../plugins/builtin/plugin-marketplace/store";
// Declared here rather than sniffed: the desktop view and the hosted browser
// app are both browser contexts but differ in what plugins may do.
setCurrentPluginTarget("desktop");
const rootElement = document.getElementById("root");
if (!rootElement) {
throw new Error("Missing root element");
}
const appRootElement = rootElement;
const root = createRoot(appRootElement);
const bootLog = debugLog.createLogger("electrobun-web-boot");
let appMounted = false;
appRootElement.tabIndex = -1;
root.render(
Starting Gloomberb...
);
function renderFatalError(error: unknown, details?: string, title = "Gloomberb failed to start"): void {
root.render(
,
);
}
window.__gloomRenderFatalError = (error, details, source) => {
if (appMounted && source === "unhandledrejection") {
return;
}
renderFatalError(error, details, "Gloomberb crashed");
};
function focusWebSurface(): void {
window.focus();
appRootElement.focus({ preventScroll: true });
}
function requestStartupFocus(): void {
focusWebSurface();
requestAnimationFrame(() => {
void backendRequest("host.focusWindow")
.catch(() => null)
.then(() => focusWebSurface());
});
}
async function boot() {
bootLog.info("boot started");
const backendInitPromise = initElectrobunBackend();
// Avoid a premature global unhandled-rejection render while UI chunks load.
void backendInitPromise.catch(() => {});
installElectrobunConfigStoreHost();
installElectrobunBrokerRemoteClient();
installElectrobunHttpFetchTransport();
installElectrobunCloudApiFetchTransport();
installElectrobunUpdateHost();
const init = await measurePerfAsync("startup.electrobun.backend-init", () => backendInitPromise);
installElectrobunCapabilityStreamClient();
installFocusScopeRelease();
installElectrobunWindowFullscreenTracking();
installDomMarketDataFrames();
installScreenshotWatermark();
const desktopSnapshot = init.windowKind === "detached" && init.paneId && init.desktopSnapshot
? prepareDetachedSnapshot(init.desktopSnapshot, init.paneId)
: init.desktopSnapshot;
const config = desktopSnapshot?.config ?? init.config;
applyLanguageFromConfig(config);
const desktopWindowBridge = createDesktopWindowBridge(init.windowKind, init.paneId);
const desktopApplicationMenuBridge = createApplicationMenuBridge();
initializeDesktopResearchActivity();
const desktopDeepLinkBridge = observeDesktopDeepLinks(createDesktopDeepLinkBridge());
const webUiHost = createWebUiHost(init.desktopPlatform);
// Compiled by the Bun process, which owns the filesystem. A failure here must
// not stop the app from starting: the marketplace reports broken plugins, and
// the built-in catalog is enough to run on.
const externalPlugins = await measurePerfAsync(
"startup.electrobun.load-external-plugins",
async () => {
try {
return await loadDesktopExternalPlugins(await backendRequest("plugins.listExternal"));
} catch (error) {
debugLog.createLogger("desktop-plugins").error(`External plugin load failed: ${error}`);
return [];
}
},
);
// The view cannot run git or bun; every operation is the Bun process doing
// it, and `load` is that process compiling the result for this renderer.
// `activate` registers the plugin over there too, where its capabilities and
// brokers actually run, and adopts the manifests that come back.
setPluginManager({
install: (repo, pin) => backendRequest("plugins.install", { ref: repo, ...(pin ? { pin } : {}) }),
update: (directory, pin) => backendRequest("plugins.update", { directory, ...(pin ? { pin } : {}) }),
remove: (directory) => backendRequest("plugins.remove", { directory }),
remoteHeads: (directories) => backendRequest("plugins.remoteHeads", { directories: [...directories] }),
load: async (directory) => {
const bundle = await backendRequest("plugins.bundle", { directory });
return bundle ? loadDesktopExternalPlugin(bundle) : null;
},
activate: async (directory) => {
const result = await backendRequest("plugins.activate", { directory });
if (!result.ok) return result;
replaceElectrobunCapabilityManifests(result.capabilityManifests);
return { ok: true };
},
deactivate: async (pluginId) => {
const result = await backendRequest("plugins.deactivate", { pluginId });
replaceElectrobunCapabilityManifests(result.capabilityManifests);
},
});
const remoteControlAdapter = init.windowKind === "main"
? { registerHandler: setElectrobunRemoteRequestHandler }
: undefined;
measurePerfAsync("startup.electrobun.root-render", async () => {
root.render(
,
);
appMounted = true;
});
requestStartupFocus();
bootLog.info("root render scheduled", {
layoutPanes: config.layout.instances.length,
floatingPanes: config.layout.floating.length,
detachedPanes: config.layout.detached.length,
brokerInstances: config.brokerInstances.length,
});
}
boot().catch((error) => {
renderFatalError(error);
});