Own generic CultMesh asset delivery

This commit is contained in:
Meta Vens 2026-07-10 13:55:06 +01:00
commit 56d439e84b
10 changed files with 75 additions and 5 deletions

View file

@ -25,6 +25,11 @@ receipt reconciliation, provider readiness, renderer loading, and cleanup.
Provider repos supply a resolved target, dependencies, renderer path, and any
provider-owned daemon development or asset hooks.
CultMesh-native asset references lower through the runtime-owned
`eve-asset://` protocol. The generic provider client reads
`gamecult.cultmesh.cdn.asset_blob.v1` records; provider repos publish bytes and
retain asset truth without implementing an Electron protocol.
```powershell
npm install
npm test

View file

@ -17,7 +17,8 @@
"pluginProjection",
"securePreloadBridge",
"windowLifecycle",
"liveProviderHost"
"liveProviderHost",
"providerAssets"
],
"supportedPlugins": [
{

View file

@ -23,6 +23,7 @@ export declare class EveCultMeshProviderClient {
surface(surfaceId?: string): Promise<Record<string, unknown>>;
document(request: { schemaId: string; recordRef: string }): Promise<unknown>;
resolveDocument(request: { documentId: string; schemaId: string }): Promise<{ documentId: string; schemaId: string; document?: unknown; surface?: Record<string, unknown> }>;
asset(uri: string): Promise<{ bytes: Uint8Array; mimeType: string }>;
submitCommand(request: Record<string, unknown>): Promise<EveCommandSubmission>;
receipt(submission: Record<string, unknown>): Promise<unknown>;
}

View file

@ -1,5 +1,6 @@
const providerSchema = "gamecult.eve.provider_advertisement.v1";
const commandSchema = "gamecult.eve.command_invocation.v1";
const assetBlobSchema = "gamecult.cultmesh.cdn.asset_blob.v1";
const connectionId = 0x43554c54;
export class EveCultMeshProviderClient {
@ -61,6 +62,15 @@ export class EveCultMeshProviderClient {
return { documentId, schemaId, document };
}
async asset(uri) {
const recordRef = String(uri || "").trim();
if (!recordRef.toLowerCase().startsWith("cultmesh://")) throw new Error(`Asset URI must be CultMesh-native: ${recordRef}`);
const value = await this.#document(assetBlobSchema, recordRef, "asset");
const bytes = value instanceof Uint8Array ? value : Array.isArray(value) ? Uint8Array.from(value) : null;
if (!bytes) throw new Error(`CultMesh asset ${recordRef} did not decode to bytes.`);
return { bytes, mimeType: mimeTypeFromUri(recordRef) };
}
async submitCommand(request) {
const advertisement = await this.providerAdvertisement();
const surface = selectSurface(advertisement, request?.surfaceId);
@ -261,4 +271,12 @@ function arrayField(value, name, slot) { const result = field(value, name, slot)
function recordField(value, name, slot) { const result = field(value, name, slot); return result && typeof result === "object" && !Array.isArray(result) ? result : {}; }
function objectValue(value) { return value && typeof value === "object" && !Array.isArray(value) ? value : {}; }
function stringRecord(value) { return Object.fromEntries(Object.entries(objectValue(value)).map(([key, entry]) => [key, entry == null ? "" : String(entry)])); }
function mimeTypeFromUri(uri) {
const value = uri.toLowerCase();
if (value.endsWith(".jpg") || value.endsWith(".jpeg")) return "image/jpeg";
if (value.endsWith(".svg")) return "image/svg+xml";
if (value.endsWith(".webp")) return "image/webp";
if (value.endsWith(".json")) return "application/json";
return "image/png";
}
function requireFunctionSet(value, names, label) { if (!value) throw new Error(`${label} is required.`); for (const name of names) if (typeof value[name] !== "function") throw new Error(`${label}.${name} is required.`); return value; }

View file

@ -12,10 +12,16 @@ export function createEveElectronRendererTransport(eveProvider, { resolveAssetUr
payload: intent.payload,
}),
resolveDocument: request => eveProvider.document(request),
resolveAssetUrl,
resolveAssetUrl: resolveAssetUrl || defaultAssetUrl,
};
}
function defaultAssetUrl(uri) {
return String(uri || "").startsWith("cultmesh://")
? `eve-asset://asset?uri=${encodeURIComponent(uri)}`
: uri;
}
export async function mountEveElectronProvider({
EveBrowserProviderHost,
window: windowObject = globalThis.window,

View file

@ -1,3 +1,5 @@
export declare const eveProviderIpcChannels: Readonly<Record<string, string>>;
export declare const defaultEveAssetProtocol: string;
export declare function registerEveProviderIpc(ipcMain: any, client: any, options?: { receiptTimeoutMs?: number }): () => void;
export declare function registerEveAssetProtocol(protocol: any, client: any, scheme?: string): void;
export declare function startEveElectronProviderHost(options: any): Promise<{ client: any; close(): Promise<void>; window: any }>;

View file

@ -9,6 +9,7 @@ export const eveProviderIpcChannels = Object.freeze({
submitCommand: "eve:submit-command",
windowControl: "eve-electron:window-control",
});
export const defaultEveAssetProtocol = "eve-asset";
export function registerEveProviderIpc(ipcMain, client, { receiptTimeoutMs = 5000 } = {}) {
const handlers = [
@ -28,7 +29,7 @@ export function registerEveProviderIpc(ipcMain, client, { receiptTimeoutMs = 500
}
export async function startEveElectronProviderHost(options) {
const { app, BrowserWindow, ipcMain, shell } = options.electron;
const { app, BrowserWindow, ipcMain, protocol, shell } = options.electron;
await app.whenReady();
const client = (options.createProviderClient || ((target, dependencies, clientOptions) =>
new EveCultMeshProviderClient(target, dependencies, clientOptions)))(
@ -42,12 +43,15 @@ export async function startEveElectronProviderHost(options) {
}, { BrowserWindow, shell });
const removeProviderIpc = registerEveProviderIpc(ipcMain, client, options.receipts);
const removeWindowIpc = registerEveWindowControls(ipcMain, BrowserWindow, eveProviderIpcChannels.windowControl);
const assetProtocol = options.assetProtocol === false ? "" : options.assetProtocol || defaultEveAssetProtocol;
if (assetProtocol) registerEveAssetProtocol(protocol, client, assetProtocol);
let closed = false;
const close = async () => {
if (closed) return;
closed = true;
removeProviderIpc();
removeWindowIpc();
if (assetProtocol && typeof protocol.unhandle === "function") protocol.unhandle(assetProtocol);
await client.close();
if (!window.isDestroyed?.()) window.destroy();
};
@ -63,6 +67,22 @@ export async function startEveElectronProviderHost(options) {
}
}
export function registerEveAssetProtocol(protocol, client, scheme = defaultEveAssetProtocol) {
if (!protocol?.handle) throw new Error("Electron protocol.handle is required for Eve assets.");
protocol.handle(scheme, async request => {
try {
const uri = new URL(request.url).searchParams.get("uri") || "";
const asset = await client.asset(uri);
return new Response(Uint8Array.from(asset.bytes).buffer, {
status: 200,
headers: { "content-type": asset.mimeType, "cache-control": "no-store" },
});
} catch (error) {
return new Response(error instanceof Error ? error.message : String(error), { status: 404 });
}
});
}
async function waitForProvider(client, timeoutMs) {
const started = Date.now();
let lastError;

View file

@ -16,7 +16,7 @@ function harness() {
const sent = [];
const connections = [];
const encoded = [];
const records = new Map([["records/provider", advertisement], ["records/world", surface], ["records/objects", { entities: [1] }], ["records/receipts:cmd-1", ["gamecult.eve.command_receipt.v1", "receipt-1", "cmd-1", "move", "accepted", "Provider", "daemon", "test-provider", "world", "moved", "now", 4]]]);
const records = new Map([["records/provider", advertisement], ["records/world", surface], ["records/objects", { entities: [1] }], ["cultmesh://fixture/icon", Uint8Array.from([1, 2, 3])], ["records/receipts:cmd-1", ["gamecult.eve.command_receipt.v1", "receipt-1", "cmd-1", "move", "accepted", "Provider", "daemon", "test-provider", "world", "moved", "now", 4]]]);
const peer = { send: message => sent.push(message), close() {}, on() {} };
const mesh = {
createPeerCatalog: () => ({ upsert() {} }), createAuthorityLeaseCatalog: () => ({ upsert() {} }),
@ -72,6 +72,11 @@ test("resolves nested Eve surfaces as surface projections", async () => {
assert.equal((await client.resolveDocument({ documentId: "records/world", schemaId: "gamecult.eve.surface.v1" })).surface.id, "world");
});
test("reads provider assets without provider-specific transport code", async () => {
const { client } = harness();
assert.deepEqual(await client.asset("cultmesh://fixture/icon"), { bytes: Uint8Array.from([1, 2, 3]), mimeType: "image/png" });
});
test("refuses unadvertised surfaces", async () => {
const { client } = harness();
await assert.rejects(() => client.surface("missing"), /does not advertise/);

View file

@ -1,6 +1,6 @@
import assert from "node:assert/strict";
import test from "node:test";
import { eveProviderIpcChannels, registerEveProviderIpc } from "../src/live-provider-host.mjs";
import { eveProviderIpcChannels, registerEveAssetProtocol, registerEveProviderIpc } from "../src/live-provider-host.mjs";
test("registers one generic Eve IPC authority and removes it cleanly", async () => {
const handlers = new Map();
@ -25,3 +25,14 @@ test("registers one generic Eve IPC authority and removes it cleanly", async ()
remove();
assert.equal(handlers.size, 0);
});
test("serves CultMesh assets through the runtime-owned protocol", async () => {
let handler;
registerEveAssetProtocol({ handle: (_scheme, value) => { handler = value; } }, {
asset: async uri => ({ bytes: Uint8Array.from([4, 5]), mimeType: uri.endsWith("icon") ? "image/png" : "application/octet-stream" }),
});
const response = await handler({ url: "eve-asset://asset?uri=cultmesh%3A%2F%2Ffixture%2Ficon" });
assert.equal(response.status, 200);
assert.equal(response.headers.get("content-type"), "image/png");
assert.deepEqual(new Uint8Array(await response.arrayBuffer()), Uint8Array.from([4, 5]));
});

View file

@ -11,6 +11,7 @@ test("adapts only the generic Eve provider renderer boundary", async () => {
submitCommand: async request => { calls.push(["command", request]); return { state: "accepted" }; },
};
const transport = createEveElectronRendererTransport(provider);
assert.equal(transport.resolveAssetUrl("cultmesh://fixture/icon"), "eve-asset://asset?uri=cultmesh%3A%2F%2Ffixture%2Ficon");
await transport.surface({ surfaceId: "world", recordRef: "provider-owned" });
await transport.resolveDocument({ documentId: "objects", schemaId: "objects.v1" });
await transport.submitCommand({ providerId: "fixture", surfaceId: "world", command: "move", clientId: "renderer", issuedAt: "now", payload: { x: 1 } });