Initialize generic Eve Electron runtime

This commit is contained in:
Meta Vens 2026-07-10 07:40:43 +01:00
commit 53b38f1778
15 changed files with 825 additions and 0 deletions

3
.gitignore vendored Normal file
View file

@ -0,0 +1,3 @@
node_modules/
artifacts/
*.tgz

5
LICENSE Normal file
View file

@ -0,0 +1,5 @@
Mozilla Public License Version 2.0
This Source Code Form is subject to the terms of the Mozilla Public License,
v. 2.0. If a copy of the MPL was not distributed with this file, You can
obtain one at https://mozilla.org/MPL/2.0/.

15
README.md Normal file
View file

@ -0,0 +1,15 @@
# EveElectron
EveElectron is the generic Electron lowering runtime for Eve/CultUI surfaces.
It owns secure window construction, the narrow provider preload bridge, shell
projection, command-intent emission, runtime packaging, and capture evidence.
Providers supply advertisements, surface and embedded-document reads, command
submission, asset resolution, and any product daemon lifecycle. EveElectron
does not import provider state or plugin semantic implementations.
```powershell
npm install
npm test
npm run pack:check
```

125
eve-runtime-capability.json Normal file
View file

@ -0,0 +1,125 @@
{
"schema": "gamecult.eve.runtime_capability.v1",
"runtimeId": "electron-shell",
"title": "Electron shell",
"owner": "EveElectron",
"supportedFeatures": [
"providerAdvertisements",
"commandTransport",
"surfaceTreeProjection",
"embeddedDocuments",
"pluginProjection",
"securePreloadBridge",
"windowLifecycle"
],
"supportedPlugins": [
{
"pluginId": "sai.vn",
"capabilities": ["vn.stage", "story.choose", "story.continue", "story.jump"],
"projectionAdapter": "EveElectronShell.buildPluginProjection"
},
{
"pluginId": "norn.graph",
"capabilities": ["embed.norn"],
"projectionAdapter": "EveElectronShell.buildPluginProjection"
},
{
"pluginId": "tex.math",
"capabilities": ["embed.tex", "tex.inline", "tex.block"],
"projectionAdapter": "EveElectronShell.buildPluginProjection"
}
],
"unsupportedPlugins": [],
"worldSurfaceLowering": [
{
"targetId": "electron-shell",
"surfaceKinds": ["interactive-world", "interactive-world-editor"],
"projectionKinds": ["provider-authored-world-surface", "provider-authored-world-editor-surface"],
"supportLevel": "provider-shell-surface-tree-command-surface",
"ownership": "runtime-lowers-provider-world-surface-without-owning-world-state",
"evidencePaths": [
"src/eve-electron-shell.mjs",
"src/window-host.mjs",
"src/eve-provider-preload.cjs",
"test/shell.test.mjs",
"test/window-host.test.mjs",
"test/preload.test.cjs"
]
}
],
"commandTransport": {
"schema": "gamecult.eve.command_invocation.v1",
"status": "active",
"emitter": "EveElectronShell.createCommandIntent",
"clientId": "electron-shell",
"authority": "Electron emits command intents through provider-advertised boundaries; providers accept, deny, and publish receipts."
},
"lifecycle": {
"release": {
"ownerRepo": "EveElectron",
"status": "owner-npm-package",
"releaseContract": {
"ownerRepo": "EveElectron",
"packageName": "@gamecult/eve-electron",
"packageRoot": ".",
"versionSource": "package.json",
"tagPattern": "eveelectron-v{version}",
"artifactKind": "npm-package",
"requestSchema": "gamecult.eve.runtime_release_request.v1",
"requestBuilder": "npm pack",
"artifactPattern": "gamecult-eve-electron-{version}.tgz",
"publishProof": "npm pack is produced from the EveElectron owner repository."
},
"evidencePaths": ["package.json", "package-lock.json", "src/index.mjs", "src/index.d.ts"],
"pendingProofs": []
},
"test": {
"ownerRepo": "EveElectron",
"status": "owner-package-tests-and-aetheria-live-smoke",
"testContract": {
"ownerRepo": "EveElectron",
"runnerKind": "node-and-provider-live-smoke",
"runnerScript": "npm test",
"consumerProject": "E:\\Projects\\Aetheria",
"packageName": "@gamecult/eve-electron",
"testAssembly": "test/*.test.*",
"testPlatform": "Node contract tests plus Electron",
"resultsArtifact": "Aetheria.Rts.Web Stage 7C smoke result",
"logArtifact": "Aetheria.Rts.Web Stage 7C log",
"manifestMutation": "Aetheria consumes the package through a file dependency; provider state remains Aetheria-owned."
},
"evidencePaths": ["test/shell.test.mjs", "test/window-host.test.mjs", "test/preload.test.cjs"],
"pendingProofs": []
},
"capture": {
"ownerRepo": "EveElectron",
"status": "pending-owner-window-capture",
"captureContract": {
"ownerRepo": "EveElectron",
"runtimeId": "electron-shell",
"targetId": "electron-shell",
"requestSchema": "gamecult.eve.runtime_capture_request.v1",
"requestBuilder": "pending",
"advertisementPath": "provider-supplied",
"captureKind": "electron-window-png",
"artifactKind": "png",
"artifactPattern": "artifacts/capture/{providerId}/{surfaceId}.png",
"conformanceAttachment": "runtime.captureArtifacts[]",
"requiredProvider": "aetheria",
"requiredSurface": "aetheria.game",
"additionalProviderSurfaces": [
{ "providerId": "eve.world-smoke", "surfaceId": "eve.world-smoke.surface", "purpose": "Non-product control." }
],
"authority": "Capture observes Electron projection only; provider state and receipts remain provider-owned.",
"publishProof": "Owner-repo Electron PNGs attach to Eve conformance."
},
"evidencePaths": [],
"pendingProofs": ["Capture one non-product and one Aetheria window from the owner runtime."]
}
},
"capture": {
"status": "pending-owner-window-capture",
"note": "Aetheria live lowering passes through the owner package; portable owner-repo PNG capture remains pending.",
"artifacts": []
}
}

View file

@ -0,0 +1,197 @@
{
"type": "surface-state",
"schema": "gamecult.eve.surface.v1",
"providerKind": "sai.visual_novel",
"providerId": "gamecult.home.vn",
"title": "GameCult Compound VN",
"version": 1,
"updatedAt": "2026-05-31T00:00:00.000Z",
"surface": {
"id": "sai.visual_novel.surface",
"styles": {
"themeId": "gamecult.vn",
"tokens": {
"colorBackground": "#05080d",
"colorPanel": "rgba(5, 8, 13, 0.92)",
"colorPanelAlt": "rgba(9, 14, 22, 0.82)",
"colorText": "#f6f1e2",
"colorMuted": "#b7c7d9",
"colorAccent": "#ff8a2a",
"colorLink": "#59b7ff",
"radiusPanel": 8,
"fontBody": "Ubuntu",
"fontTitle": "Montserrat",
"pixelArt": true,
"motion": "subtle"
},
"controls": [
{
"id": "theme.accent",
"kind": "color",
"label": "Accent",
"token": "colorAccent",
"command": "style.patch"
}
]
},
"root": {
"id": "sai.root",
"kind": "vn.stage",
"props": {
"mode": "visual-novel",
"storyId": "gamecult-compound",
"currentPath": "hub",
"style": "gamecult.vn"
},
"children": [
{
"id": "sai.background",
"kind": "image.background",
"props": {
"src": "/static/interactive/cotsc-praxis/backgrounds/compound.png",
"label": "Front Gate",
"fit": "cover",
"pixelArt": true
}
},
{
"id": "sai.graph",
"kind": "embed.norn",
"props": {
"label": "GameCult Ink Knot Graph",
"engine": {
"id": "norn",
"contract": "gamecult.norn.surface.v1",
"web": {
"wasm": "/static/interactive/norn/norn-rs.wasm",
"package": "@gamecult/norn-viewer"
},
"native": {
"rustCrate": "norn-rs",
"direct2dAdapter": "fensalir.norn.direct2d"
}
},
"layout": {
"mode": "norn.2d",
"iterations": 220,
"rankGap": 150,
"nodeGap": 88,
"edgeLength": 132
},
"graph": {
"nodes": [
{ "id": "hub", "label": "Hub", "target": "hub", "x": 0.2, "y": 0.2, "current": true },
{ "id": "eve", "label": "Eve", "target": "eve", "x": 0.72, "y": 0.24 },
{ "id": "sai", "label": "Sai", "target": "sai", "x": 0.48, "y": 0.55 }
],
"edges": [
{ "source": "hub", "target": "eve" },
{ "source": "hub", "target": "sai" },
{ "source": "sai", "target": "eve" }
]
},
"interaction": {
"nodeAction": "story.jump",
"focusFollowsStory": true,
"rendererMustUseNorn": true
},
"placement": {
"space": "scene",
"anchor": "whiteboard",
"mode": "keystone",
"quad": [[0.08, 0.10], [0.55, 0.06], [0.59, 0.52], [0.07, 0.58]],
"zIndex": 4
}
}
},
{
"id": "sai.embeds",
"kind": "layer.embedded-surfaces",
"children": [
{
"id": "sai.tex.log-power",
"kind": "embed.tex",
"props": {
"label": "Bifrost voting weight",
"source": "\\\\mathrm{votes}(p)=1+\\\\lfloor\\\\log_b(1+p)\\\\rfloor",
"format": "latex",
"display": "block",
"renderer": {
"web": "katex",
"native": "tex-renderer",
"direct2d": "tex-to-directwrite"
},
"placement": {
"space": "scene",
"anchor": "whiteboard-equation",
"mode": "keystone",
"quad": [[0.16, 0.60], [0.54, 0.57], [0.56, 0.70], [0.15, 0.73]],
"zIndex": 5
}
}
}
]
},
{
"id": "sai.cards",
"kind": "layer.cards",
"children": [
{
"id": "sai.card.projects",
"kind": "card.external",
"props": {
"key": "projects",
"title": "Project Atlas",
"selector": "#gamecult-vn-projects",
"mode": "provider-owned-fragment"
}
}
]
},
{
"id": "sai.dialogue",
"kind": "panel.dialogue",
"props": {
"speaker": "Void",
"text": "Pick a door. The map will keep accusing us of being real.",
"scene": "Front Gate"
},
"children": [
{
"id": "sai.dialogue.text",
"kind": "text.dialogue",
"props": {
"speaker": "Void",
"text": "Pick a door. The map will keep accusing us of being real."
}
}
]
},
{
"id": "sai.choices",
"kind": "rail.actions",
"children": [
{
"id": "sai.choice.eve",
"kind": "control.button",
"props": {
"label": "What is Eve trying to turn the web into?",
"action": {
"schema": "gamecult.eve.command.v1",
"command": "story.choose",
"payload": { "index": 0, "targetPath": "eve" }
}
}
}
]
}
]
}
},
"commands": [
{ "schema": "gamecult.eve.command.v1", "command": "story.continue", "label": "Continue" },
{ "schema": "gamecult.eve.command.v1", "command": "story.choose", "label": "Choose" },
{ "schema": "gamecult.eve.command.v1", "command": "story.jump", "label": "Jump to knot" },
{ "schema": "gamecult.eve.command.v1", "command": "style.patch", "label": "Patch style" }
]
}

21
package-lock.json generated Normal file
View file

@ -0,0 +1,21 @@
{
"name": "@gamecult/eve-electron",
"version": "0.1.0",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "@gamecult/eve-electron",
"version": "0.1.0",
"license": "MPL-2.0",
"peerDependencies": {
"electron": ">=30"
},
"peerDependenciesMeta": {
"electron": {
"optional": true
}
}
}
}
}

27
package.json Normal file
View file

@ -0,0 +1,27 @@
{
"name": "@gamecult/eve-electron",
"version": "0.1.0",
"type": "module",
"description": "Provider-agnostic Electron runtime for Eve surfaces.",
"license": "MPL-2.0",
"main": "src/index.mjs",
"types": "src/index.d.ts",
"exports": {
".": {
"types": "./src/index.d.ts",
"import": "./src/index.mjs"
},
"./preload": "./src/eve-provider-preload.cjs"
},
"files": ["src", "eve-runtime-capability.json", "README.md", "LICENSE"],
"scripts": {
"test": "node --test test/*.test.mjs",
"pack:check": "npm pack --dry-run"
},
"peerDependencies": {
"electron": ">=30"
},
"peerDependenciesMeta": {
"electron": { "optional": true }
}
}

233
src/eve-electron-shell.mjs Normal file
View file

@ -0,0 +1,233 @@
export class EveElectronShell {
constructor({ clientId = "electron-shell" } = {}) {
this.clientId = clientId;
}
selectSurface(providerAdvertisement, requestedSurfaceId = "") {
const provider = normalizeProviderAdvertisement(providerAdvertisement);
const surfaces = provider.surfaces;
const surface = requestedSurfaceId
? surfaces.find(candidate => candidate.surfaceId === requestedSurfaceId)
: surfaces[0];
if (!surface) {
throw new Error(`Provider ${provider.providerId} does not advertise surface ${requestedSurfaceId || "(first)"}`);
}
return {
providerId: provider.providerId,
surfaceId: surface.surfaceId,
transport: surface.transport || "",
url: surface.url || surface.key || "",
worldInteraction: normalizeWorldInteraction(surface.worldInteraction),
};
}
lowerSurface(surfaceDocument, providerAdvertisement, requestedSurfaceId = "") {
const selected = this.selectSurface(providerAdvertisement, requestedSurfaceId);
const document = normalizeSurfaceDocument(surfaceDocument);
if (document.surfaceId !== selected.surfaceId) {
throw new Error(`Surface document ${document.surfaceId} does not match advertised Electron surface ${selected.surfaceId}.`);
}
return {
type: "electron-shell-projection",
schema: "gamecult.eve.electron_shell_projection.v1",
providerId: selected.providerId,
surfaceId: selected.surfaceId,
projectionKind: selected.worldInteraction.projectionKind,
commandBoundary: selected.worldInteraction.commandBoundary,
receiptSchema: selected.worldInteraction.receiptSchema,
ownership: selected.worldInteraction.ownership,
root: buildShellNode(document.root),
};
}
createCommandIntent(providerAdvertisement, requestedSurfaceId, command, payload = {}) {
if (!command) throw new Error("Command is required.");
const selected = this.selectSurface(providerAdvertisement, requestedSurfaceId);
const action = objectValue(payload.action);
const commandBoundary = firstString(
selected.worldInteraction.commandBoundary,
payload.commandBoundary,
action.commandBoundary,
action.target,
);
const receiptSchema = firstString(
selected.worldInteraction.receiptSchema,
payload.receiptSchema,
action.receiptSchema,
);
const intent = {
type: "surface-command",
schema: "gamecult.eve.command_invocation.v1",
providerId: selected.providerId,
surfaceId: selected.surfaceId,
command,
payload: {
...payload,
transport: payload.transport ?? null,
},
issuedAt: new Date().toISOString(),
clientId: this.clientId,
};
if (commandBoundary) intent.commandBoundary = commandBoundary;
if (receiptSchema) intent.receiptSchema = receiptSchema;
return intent;
}
}
export function normalizeProviderAdvertisement(providerAdvertisement) {
if (!providerAdvertisement || typeof providerAdvertisement !== "object") {
throw new Error("Provider advertisement is required.");
}
const providerId = String(providerAdvertisement.providerId || "");
if (!providerId) throw new Error("Provider advertisement missing providerId.");
const surfaces = Array.isArray(providerAdvertisement.surfaces)
? providerAdvertisement.surfaces
.filter(surface => surface && typeof surface === "object" && surface.surfaceId)
.map(surface => ({
...surface,
surfaceId: String(surface.surfaceId),
}))
: [];
if (!surfaces.length) throw new Error(`Provider ${providerId} advertises no surfaces.`);
return { ...providerAdvertisement, providerId, surfaces };
}
export function normalizeSurfaceDocument(surfaceDocument) {
if (!surfaceDocument || typeof surfaceDocument !== "object") {
throw new Error("Surface document is required.");
}
if (surfaceDocument.schema !== "gamecult.eve.surface.v1") {
throw new Error(`Unexpected surface schema: ${surfaceDocument.schema || ""}`);
}
const surface = objectValue(surfaceDocument.surface);
const surfaceId = firstString(surface.id);
if (!surfaceId) throw new Error("Surface document missing surface.id.");
const root = objectValue(surface.root);
if (!root.id) throw new Error(`Surface document ${surfaceId} missing surface.root.`);
return { ...surfaceDocument, surfaceId, root };
}
function buildShellNode(component) {
const source = objectValue(component);
const children = Array.isArray(source.children) ? source.children.map(buildShellNode) : [];
const embeddedDocuments = normalizeEmbeddedDocuments(source.embeddedDocuments);
const componentKind = firstString(source.kind);
const pluginProjection = buildPluginProjection(componentKind, source);
return {
id: firstString(source.id),
componentKind,
shellElementKind: shellElementKind(componentKind),
props: objectValue(source.props),
layout: objectValue(source.layout),
style: objectValue(source.style),
stateBindingCount: Array.isArray(source.stateBindings) ? source.stateBindings.length : 0,
embeddedDocumentCount: embeddedDocuments.length,
embeddedDocuments,
...(pluginProjection ? { pluginProjection } : {}),
children,
};
}
function normalizeEmbeddedDocuments(value) {
return Array.isArray(value)
? value
.filter(document => document && typeof document === "object")
.map(document => ({
slotId: firstString(document.slotId, document.id),
documentId: firstString(document.documentId, document.href, document.url),
schemaId: firstString(document.schemaId, document.schema),
presentationKind: firstString(document.presentationKind, document.kind),
}))
.filter(document => document.slotId || document.documentId)
: [];
}
function shellElementKind(componentKind) {
if (!componentKind) return "empty";
if (componentKind === "vn.stage") return "sai-vn-stage-shell";
if (componentKind === "embed.norn") return "norn-graph-shell";
if (componentKind === "embed.tex") return "tex-math-shell";
if (componentKind.startsWith("control.")) return "command-control";
if (componentKind.startsWith("embed.")) return "plugin-placeholder";
if (componentKind === "surface.slot") return "embedded-surface-slot";
if (componentKind.startsWith("field.") || componentKind.startsWith("world.")) return "world-projection-node";
if (componentKind.startsWith("text.") || componentKind === "text" || componentKind === "label") return "text";
return "shell-node";
}
function buildPluginProjection(componentKind, component) {
if (componentKind === "vn.stage") {
return {
pluginId: "sai.vn",
projectionKind: "sai-vn-electron-stage-shell",
abiSchema: "gamecult.eve.plugin_abi.v1",
commandBoundary: "sidecar-advertised-plugin-abi",
capabilities: ["vn.stage", "story.choose", "story.continue", "story.jump"],
documentId: firstString(component.props?.storyId, component.id),
semanticOwner: "Sai",
availability: "required",
};
}
if (componentKind === "embed.norn") {
return {
pluginId: "norn.graph",
projectionKind: "norn-graph-electron-overlay-shell",
abiSchema: "gamecult.eve.plugin_abi.v1",
commandBoundary: "sidecar-advertised-plugin-abi",
capabilities: ["embed.norn"],
documentId: firstString(component.props?.sourceUri, component.props?.documentId, component.id),
semanticOwner: "Norn",
availability: "optional-nested",
};
}
if (componentKind === "embed.tex") {
return {
pluginId: "tex.math",
projectionKind: texProjectionKind(component),
abiSchema: "gamecult.eve.plugin_abi.v1",
commandBoundary: "sidecar-advertised-plugin-abi",
capabilities: ["embed.tex", "tex.inline", "tex.block"],
documentId: firstString(component.props?.sourceUri, component.props?.source, component.id),
semanticOwner: "EvePlugins",
availability: "optional-nested",
};
}
return null;
}
function texProjectionKind(component) {
const display = firstString(component.props?.display, "inline");
if (display === "block") return "tex-math-electron-block-source-shell";
if (display === "page") return "tex-math-electron-page-source-shell";
return "tex-math-electron-inline-source-shell";
}
function normalizeWorldInteraction(value) {
const source = objectValue(value);
return {
projectionKind: firstString(source.projectionKind),
commandBoundary: firstString(source.commandBoundary),
receiptSchema: firstString(source.receiptSchema),
ownership: firstString(source.ownership),
};
}
function objectValue(value) {
return value && typeof value === "object" && !Array.isArray(value) ? value : {};
}
function firstString(...values) {
for (const value of values) {
if (typeof value === "string" && value.trim()) return value;
if (typeof value === "number" && Number.isFinite(value)) return String(value);
}
return "";
}

View file

@ -0,0 +1,25 @@
const defaultChannels = Object.freeze({
providerAdvertisement: "eve:provider-advertisement",
surface: "eve:surface",
document: "eve:embedded-document",
submitCommand: "eve:submit-command",
windowControl: "eve-electron:window-control",
});
function installEveProviderBridge({ contextBridge, ipcRenderer, namespace = "eveProvider", channels = {} }) {
if (!contextBridge?.exposeInMainWorld || !ipcRenderer?.invoke) {
throw new Error("Electron contextBridge and ipcRenderer are required.");
}
const names = { ...defaultChannels, ...channels };
const api = Object.freeze({
providerAdvertisement: request => ipcRenderer.invoke(names.providerAdvertisement, request),
surface: request => ipcRenderer.invoke(names.surface, request),
document: request => ipcRenderer.invoke(names.document, request),
submitCommand: request => ipcRenderer.invoke(names.submitCommand, request),
windowControl: action => ipcRenderer.invoke(names.windowControl, action),
});
contextBridge.exposeInMainWorld(namespace, api);
return api;
}
module.exports = { defaultChannels, installEveProviderBridge };

27
src/index.d.ts vendored Normal file
View file

@ -0,0 +1,27 @@
import type { BrowserWindow, BrowserWindowConstructorOptions, IpcMain, Shell } from "electron";
export interface EveElectronWindowOptions extends BrowserWindowConstructorOptions {
browserWindow?: BrowserWindowConstructorOptions;
openExternal?: boolean;
preload?: string;
sandbox?: boolean;
webPreferences?: BrowserWindowConstructorOptions["webPreferences"];
}
export declare const defaultWindowControlChannel: string;
export declare function createEveElectronWindow(
options: EveElectronWindowOptions,
electron: { BrowserWindow: typeof BrowserWindow; shell: Shell },
): BrowserWindow;
export declare function registerEveWindowControls(
ipcMain: IpcMain,
BrowserWindow: typeof import("electron").BrowserWindow,
channel?: string,
): () => void;
export declare class EveElectronShell {
constructor(options?: { clientId?: string });
selectSurface(providerAdvertisement: object, requestedSurfaceId?: string): object;
lowerSurface(surfaceDocument: object, providerAdvertisement: object, requestedSurfaceId?: string): object;
createCommandIntent(providerAdvertisement: object, requestedSurfaceId: string, command: string, payload?: object): object;
}

2
src/index.mjs Normal file
View file

@ -0,0 +1,2 @@
export * from "./eve-electron-shell.mjs";
export * from "./window-host.mjs";

49
src/window-host.mjs Normal file
View file

@ -0,0 +1,49 @@
export const defaultWindowControlChannel = "eve-electron:window-control";
export function createEveElectronWindow(options = {}, electron) {
const { BrowserWindow, shell } = requiredElectron(electron);
const window = new BrowserWindow({
width: options.width ?? 1440,
height: options.height ?? 900,
minWidth: options.minWidth ?? 800,
minHeight: options.minHeight ?? 560,
show: options.show ?? true,
backgroundColor: options.backgroundColor ?? "#101214",
title: options.title ?? "Eve",
frame: options.frame ?? false,
autoHideMenuBar: options.autoHideMenuBar ?? true,
...(options.browserWindow ?? {}),
webPreferences: {
contextIsolation: true,
nodeIntegration: false,
sandbox: options.sandbox ?? false,
preload: options.preload,
...(options.webPreferences ?? {}),
},
});
window.webContents.setWindowOpenHandler(({ url }) => {
if (options.openExternal !== false) void shell.openExternal(url);
return { action: "deny" };
});
return window;
}
export function registerEveWindowControls(ipcMain, BrowserWindow, channel = defaultWindowControlChannel) {
ipcMain.handle(channel, (event, action) => {
const window = BrowserWindow.fromWebContents(event.sender);
if (!window) return;
if (action === "minimize") window.minimize();
else if (action === "maximize") window.isMaximized() ? window.unmaximize() : window.maximize();
else if (action === "close") window.close();
else throw new Error(`Unknown Eve window control '${action}'.`);
});
return () => ipcMain.removeHandler(channel);
}
function requiredElectron(electron) {
if (!electron?.BrowserWindow || !electron?.shell) {
throw new Error("Electron BrowserWindow and shell are required.");
}
return electron;
}

17
test/preload.test.cjs Normal file
View file

@ -0,0 +1,17 @@
const assert = require("node:assert/strict");
const test = require("node:test");
const { installEveProviderBridge } = require("../src/eve-provider-preload.cjs");
test("exposes only the generic Eve provider bridge", async () => {
let exposed;
const calls = [];
const api = installEveProviderBridge({
contextBridge: { exposeInMainWorld: (name, value) => { exposed = { name, value }; } },
ipcRenderer: { invoke: (...args) => { calls.push(args); return Promise.resolve(args); } },
channels: { surface: "provider:surface" },
});
assert.equal(exposed.name, "eveProvider");
assert.deepEqual(Object.keys(api), ["providerAdvertisement", "surface", "document", "submitCommand", "windowControl"]);
await api.surface({ surfaceId: "fixture" });
assert.deepEqual(calls, [["provider:surface", { surfaceId: "fixture" }]]);
});

31
test/shell.test.mjs Normal file
View file

@ -0,0 +1,31 @@
import assert from "node:assert/strict";
import test from "node:test";
import { EveElectronShell } from "../src/eve-electron-shell.mjs";
const advertisement = {
schema: "gamecult.eve.provider_advertisement.v1",
providerId: "fixture.world",
surfaces: [{
surfaceId: "fixture.world.surface",
worldInteraction: {
projectionKind: "provider-authored-world-surface",
commandBoundary: "fixture.world.commands",
receiptSchema: "gamecult.eve.command_receipt.v1",
ownership: "provider-owns-world-state",
},
}],
};
const surface = {
schema: "gamecult.eve.surface.v1",
surface: { id: "fixture.world.surface", root: { id: "root", kind: "world.scene3d", children: [] } },
};
test("lowers a non-product interactive world and preserves provider command authority", () => {
const shell = new EveElectronShell({ clientId: "fixture-electron" });
const projection = shell.lowerSurface(surface, advertisement, "fixture.world.surface");
assert.equal(projection.providerId, "fixture.world");
assert.equal(projection.root.shellElementKind, "world-projection-node");
const command = shell.createCommandIntent(advertisement, "fixture.world.surface", "fixture.world.commands", {});
assert.equal(command.commandBoundary, "fixture.world.commands");
assert.equal(command.clientId, "fixture-electron");
});

48
test/window-host.test.mjs Normal file
View file

@ -0,0 +1,48 @@
import assert from "node:assert/strict";
import test from "node:test";
import { createEveElectronWindow, registerEveWindowControls } from "../src/window-host.mjs";
test("creates a secure provider-agnostic Eve window", () => {
let options;
let opened;
class BrowserWindow {
constructor(value) {
options = value;
this.webContents = { setWindowOpenHandler: handler => { this.handler = handler; } };
BrowserWindow.instance = this;
}
}
createEveElectronWindow({ title: "Fixture", preload: "preload.cjs", show: false }, {
BrowserWindow,
shell: { openExternal: url => { opened = url; } },
});
assert.equal(options.title, "Fixture");
assert.equal(options.show, false);
assert.equal(options.webPreferences.contextIsolation, true);
assert.equal(options.webPreferences.nodeIntegration, false);
assert.deepEqual(BrowserWindow.instance.handler({ url: "https://example.test" }), { action: "deny" });
assert.equal(opened, "https://example.test");
});
test("routes window controls through one generic handler", () => {
const calls = [];
let handler;
const window = {
minimize: () => calls.push("minimize"),
maximize: () => calls.push("maximize"),
unmaximize: () => calls.push("unmaximize"),
close: () => calls.push("close"),
isMaximized: () => false,
};
const ipcMain = {
handle: (_channel, value) => { handler = value; },
removeHandler: () => calls.push("removed"),
};
const BrowserWindow = { fromWebContents: () => window };
const dispose = registerEveWindowControls(ipcMain, BrowserWindow);
handler({ sender: {} }, "minimize");
handler({ sender: {} }, "maximize");
handler({ sender: {} }, "close");
dispose();
assert.deepEqual(calls, ["minimize", "maximize", "close", "removed"]);
});