Consume generic EveElectron host

This commit is contained in:
Meta Vens 2026-07-10 07:41:03 +01:00
commit 8975d7ebbb
7 changed files with 86 additions and 64 deletions

View file

@ -14,6 +14,13 @@ export {};
declare global {
interface Window {
aetheriaRts: AetheriaRtsApi;
eveProvider: {
providerAdvertisement: AetheriaRtsApi["eveProviderAdvertisement"];
surface: AetheriaRtsApi["eveSurface"];
document: AetheriaRtsApi["eveDocument"];
submitCommand: AetheriaRtsApi["submitEveCommand"];
windowControl: AetheriaRtsApi["windowControl"];
};
}
}
@ -38,8 +45,8 @@ async function showDaemonEveSurface(): Promise<void> {
host.setAttribute("aria-label", "Aetheria daemon Eve surface");
const requestedSurfaceId = new URLSearchParams(location.search).get("surface") || "";
const providerHost = new EveBrowserProviderHost(host, {
providerAdvertisement: () => window.aetheriaRts.eveProviderAdvertisement(),
surface: surface => window.aetheriaRts.eveSurface(surfaceRequest(surface)),
providerAdvertisement: () => window.eveProvider.providerAdvertisement(),
surface: surface => window.eveProvider.surface(surfaceRequest(surface)),
submitCommand: submitEveCommand,
resolveDocument: resolveEveDocument,
resolveAssetUrl: resolveAetheriaAssetUrl,
@ -64,7 +71,7 @@ async function resolveEveDocument(
request: EveEmbeddedDocumentRequest,
component: EveSurfaceComponent,
): ReturnType<AetheriaRtsApi["eveDocument"]> {
return window.aetheriaRts.eveDocument({
return window.eveProvider.document({
...request,
context: { viewport: viewportFromComponent(component) },
});
@ -103,7 +110,7 @@ function resolveAetheriaAssetUrl(uri: string): string {
async function submitEveCommand(intent: EveCommandIntent): Promise<void> {
try {
const receipt = await window.aetheriaRts.submitEveCommand({
const receipt = await window.eveProvider.submitCommand({
providerId: intent.providerId,
surfaceId: intent.surfaceId,
command: intent.command,
@ -126,7 +133,7 @@ function wireWindowControls(): void {
button.addEventListener("click", () => {
const action = button.dataset.windowControl as "minimize" | "maximize" | "close" | undefined;
if (action) {
void window.aetheriaRts.windowControl(action);
void window.eveProvider.windowControl(action);
}
});
});

View file

@ -8,6 +8,7 @@ import {
AetheriaCultMeshClient,
} from "./aetheria-cultmesh.js";
import { registerAetheriaRtsIpcHandlers } from "./aetheria-rts-generated-bindings.js";
import { createEveElectronWindow, registerEveWindowControls } from "@gamecult/eve-electron";
const __dirname = dirname(fileURLToPath(import.meta.url));
const projectRoot = resolve(__dirname, "..");
@ -116,29 +117,12 @@ app.on("before-quit", () => {
});
function createWindow(): BrowserWindow {
const window = new BrowserWindow({
width: 1440,
height: 900,
minWidth: 1000,
minHeight: 640,
return createEveElectronWindow({
show: !electronSmoke,
backgroundColor: "#0b1016",
title: "Aetheria Starbridge",
frame: false,
autoHideMenuBar: true,
webPreferences: {
contextIsolation: true,
nodeIntegration: false,
preload: resolve(__dirname, "preload.cjs"),
},
});
window.webContents.setWindowOpenHandler(({ url }) => {
void shell.openExternal(url);
return { action: "deny" };
});
return window;
preload: resolve(__dirname, "preload.cjs"),
}, { BrowserWindow, shell });
}
async function runElectronSmoke(window: BrowserWindow): Promise<Record<string, unknown>> {
@ -149,14 +133,15 @@ async function runElectronSmoke(window: BrowserWindow): Promise<Record<string, u
(async () => {
await new Promise(resolve => setTimeout(resolve, 100));
const api = window.aetheriaRts;
const eveProvider = window.eveProvider;
const status = document.querySelector("#status")?.textContent ?? "";
const bodyMode = document.body.className;
const eveHostText = document.querySelector("#eve-surface-host")?.textContent ?? "";
const worldScene = document.querySelector(".cultui-world-scene");
const worldEntityCount = worldScene?.querySelectorAll(".cultui-world-entity").length ?? 0;
const controlledWorldEntityCount = worldScene?.querySelectorAll('.cultui-world-entity[data-controlled="true"]').length ?? 0;
const providerAdvertisement = api ? await api.eveProviderAdvertisement() : null;
const eveSurface = api ? await api.eveSurface({ recordKey: "eve:surface:aetheria.daemon.game" }) : null;
const providerAdvertisement = eveProvider ? await eveProvider.providerAdvertisement() : null;
const eveSurface = eveProvider ? await eveProvider.surface({ recordKey: "eve:surface:aetheria.daemon.game" }) : null;
const health = api ? await api.daemonHealth() : null;
const authority = api ? await api.authorityStatus() : null;
const starbridge = api ? await api.starbridgeSession() : null;
@ -177,7 +162,7 @@ async function runElectronSmoke(window: BrowserWindow): Promise<Record<string, u
};
const eveFieldSurface = findComponent(eveSurface?.surface?.root, component =>
component.kind === "field.surface2d" || component.kind === "gravity.surface");
const eveReceipt = api ? await api.submitEveCommand({
const eveReceipt = eveProvider ? await eveProvider.submitCommand({
providerId: "aetheria.daemon",
surfaceId: "aetheria.game",
command: "sensor_ping",
@ -198,10 +183,11 @@ async function runElectronSmoke(window: BrowserWindow): Promise<Record<string, u
typeof api.assetManifest === "function" &&
typeof api.surfaceCatalog === "function" &&
typeof api.surfaceCatalogIndex === "function" &&
typeof api.eveProviderAdvertisement === "function" &&
typeof api.eveDocument === "function" &&
typeof api.eveSurface === "function" &&
typeof api.submitEveCommand === "function",
!!eveProvider &&
typeof eveProvider.providerAdvertisement === "function" &&
typeof eveProvider.document === "function" &&
typeof eveProvider.surface === "function" &&
typeof eveProvider.submitCommand === "function",
status,
bodyMode,
eveHostText,
@ -331,28 +317,7 @@ function registerIpc(): void {
daemonRunning: daemonProcess != null && !daemonProcess.killed,
daemonMode: launchLocalDaemon ? "local" : "remote",
}));
ipcMain.handle("aetheria-rts:window-control", (event, action: string) => {
const window = BrowserWindow.fromWebContents(event.sender);
if (!window)
return;
switch (action) {
case "minimize":
window.minimize();
return;
case "maximize":
if (window.isMaximized())
window.unmaximize();
else
window.maximize();
return;
case "close":
window.close();
return;
default:
throw new Error(`Unknown window control '${action}'.`);
}
});
registerEveWindowControls(ipcMain, BrowserWindow, "aetheria-rts:window-control");
}
function registerAssetProtocol(): void {

View file

@ -2,6 +2,7 @@
// Generated by scripts/generate-rts-bindings.mjs from the RTS IPC surface contract.
const { contextBridge, ipcRenderer } = require("electron");
const { installEveProviderBridge } = require("@gamecult/eve-electron/preload");
const channels = {
mapViewport: "aetheria-rts:map-viewport",
@ -58,10 +59,17 @@ contextBridge.exposeInMainWorld("aetheriaRts", {
setTarget: request => ipcRenderer.invoke(channels.setTarget, request),
surfaceCatalog: () => ipcRenderer.invoke(channels.surfaceCatalog),
surfaceCatalogIndex: () => ipcRenderer.invoke(channels.surfaceCatalogIndex),
eveSurface: request => ipcRenderer.invoke(channels.eveSurface, request),
eveProviderAdvertisement: request => ipcRenderer.invoke(channels.eveProviderAdvertisement, request),
eveDocument: request => ipcRenderer.invoke(channels.eveDocument, request),
submitEveCommand: request => ipcRenderer.invoke(channels.submitEveCommand, request),
windowControl: request => ipcRenderer.invoke(channels.windowControl, request),
health: () => ipcRenderer.invoke(channels.health),
});
installEveProviderBridge({
contextBridge,
ipcRenderer,
channels: {
providerAdvertisement: channels.eveProviderAdvertisement,
surface: channels.eveSurface,
document: channels.eveDocument,
submitCommand: channels.submitEveCommand,
windowControl: channels.windowControl,
},
});

View file

@ -9,6 +9,7 @@
"version": "0.1.0",
"dependencies": {
"@gamecult/eve-browser-lowering": "file:../../Eve/packages/eve-browser-lowering",
"@gamecult/eve-electron": "file:../../EveElectron",
"@msgpack/msgpack": "^3.1.2",
"cultcache-ts": "file:../../CultLib/packages/cultcache-ts",
"cultmesh-ts": "file:../../CultLib/packages/cultmesh-ts",
@ -68,6 +69,19 @@
"typescript": "^5.6.3"
}
},
"../../EveElectron": {
"name": "@gamecult/eve-electron",
"version": "0.1.0",
"license": "MPL-2.0",
"peerDependencies": {
"electron": ">=30"
},
"peerDependenciesMeta": {
"electron": {
"optional": true
}
}
},
"node_modules/@electron-internal/extract-zip": {
"version": "1.0.3",
"resolved": "https://registry.npmjs.org/@electron-internal/extract-zip/-/extract-zip-1.0.3.tgz",
@ -103,6 +117,10 @@
"resolved": "../../Eve/packages/eve-browser-lowering",
"link": true
},
"node_modules/@gamecult/eve-electron": {
"resolved": "../../EveElectron",
"link": true
},
"node_modules/@msgpack/msgpack": {
"version": "3.1.3",
"resolved": "https://registry.npmjs.org/@msgpack/msgpack/-/msgpack-3.1.3.tgz",

View file

@ -25,6 +25,7 @@
"dependencies": {
"@msgpack/msgpack": "^3.1.2",
"@gamecult/eve-browser-lowering": "file:../../Eve/packages/eve-browser-lowering",
"@gamecult/eve-electron": "file:../../EveElectron",
"cultcache-ts": "file:../../CultLib/packages/cultcache-ts",
"cultmesh-ts": "file:../../CultLib/packages/cultmesh-ts",
"cultnet-ts": "file:../../CultLib/packages/cultnet-ts"

View file

@ -1591,9 +1591,17 @@ function renderIpcChannelLines() {
function renderPreload() {
const channelLines = renderIpcChannelLines();
const eveProviderMethods = new Set([
"eveSurface",
"eveProviderAdvertisement",
"eveDocument",
"submitEveCommand",
"windowControl",
]);
const apiLines = ipcChannels
.filter(([name]) => name !== "viewportFeedUpdate" &&
name !== "viewportFeedStop")
name !== "viewportFeedStop" &&
!eveProviderMethods.has(name))
.map(([name]) => {
if (name === "viewportFeed") {
return ` watchViewportFeed: (request, callback) => {
@ -1628,6 +1636,7 @@ function renderPreload() {
// Generated by scripts/generate-rts-bindings.mjs from the RTS IPC surface contract.
const { contextBridge, ipcRenderer } = require("electron");
const { installEveProviderBridge } = require("@gamecult/eve-electron/preload");
const channels = {
${channelLines}
@ -1636,5 +1645,17 @@ ${channelLines}
contextBridge.exposeInMainWorld("aetheriaRts", {
${apiLines}
});
installEveProviderBridge({
contextBridge,
ipcRenderer,
channels: {
providerAdvertisement: channels.eveProviderAdvertisement,
surface: channels.eveSurface,
document: channels.eveDocument,
submitCommand: channels.submitEveCommand,
windowControl: channels.windowControl,
},
});
`;
}

View file

@ -13,9 +13,11 @@ if (-not (Get-Content wwwroot\index.html -Raw).Contains('eve-surface-host')) {
}
$appText = Get-Content wwwroot\app.js -Raw
if (-not $appText.Contains('window.aetheriaRts.eveProviderAdvertisement') -or
if (-not $appText.Contains('window.eveProvider.providerAdvertisement') -or
-not $appText.Contains('EveBrowserProviderHost') -or
-not $appText.Contains('window.aetheriaRts.eveDocument') -or
-not $appText.Contains('window.eveProvider.document') -or
$appText.Contains('window.aetheriaRts.eveProviderAdvertisement') -or
$appText.Contains('window.aetheriaRts.eveDocument') -or
$appText.Contains('eve:surface:aetheria.daemon.game') -or
$appText.Contains('gamecult.aetheria.gravity_viewport.v1') -or
$appText.Contains('gamecult.aetheria.render_splats_viewport.v1') -or
@ -34,7 +36,7 @@ $mainText = Get-Content Electron\main.ts -Raw
if (-not $mainText.Contains('await api.surfaceCatalog()') -or -not $mainText.Contains('await api.surfaceCatalogIndex()')) {
Write-Error "Stage 7C Electron RTS verifier failed: Electron smoke does not call the preload CultMesh surface catalog APIs."
}
if (-not $mainText.Contains('await api.eveProviderAdvertisement()')) {
if (-not $mainText.Contains('await eveProvider.providerAdvertisement()')) {
Write-Error "Stage 7C Electron RTS verifier failed: Electron smoke does not verify provider advertisement discovery through preload."
}
@ -45,7 +47,7 @@ if (-not $mainText.Contains('await api.renderSplatsViewport') -or
Write-Error "Stage 7C Electron RTS verifier failed: Electron smoke does not verify daemon Eve field document lowering."
}
if (-not $mainText.Contains('await api.submitEveCommand') -or
if (-not $mainText.Contains('await eveProvider.submitCommand') -or
-not $mainText.Contains('eveReceipt?.commandId') -or
-not $mainText.Contains('eveReceipt?.accepted') -or
-not $mainText.Contains('eveReceipt?.route')) {