phase 5: wasm core, GalaxyClient skeleton, Connect-Web stubs
Compile `ui/core` to WebAssembly via TinyGo (903 KB) and expose four canonical-bytes / signature-verification functions on `globalThis.galaxyCore` from `ui/wasm/main.go`. The TypeScript-side `Core` interface plus a `WasmCore` adapter (browser + JSDOM loader) bridge those into a typed shape, and a `GalaxyClient` skeleton wires `Core.signRequest` → injected `Signer` → typed Connect client → `Core.verifyPayloadHash` / `verifyResponse`. Wire `ui/buf.gen.yaml` against the local `@bufbuild/protoc-gen-es` v2 binary (devDependency) so the codegen step does not depend on the buf.build BSR. Vitest covers the bridge end-to-end: per-method WasmCore tests under JSDOM, byte-for-byte canon parity against the gateway fixtures committed in Phase 3, and a `GalaxyClient` orchestration test using `createRouterTransport`. The committed `core.wasm` snapshot tracks TinyGo output so contributors run `make wasm` only when `ui/core/` changes; CI consumes the snapshot directly. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,51 @@
|
||||
// Boots the TinyGo `core.wasm` module under Vitest/JSDOM by reading
|
||||
// the artefact and the matching wasm_exec.js shim from disk, evaluating
|
||||
// the shim into the test's global scope, then instantiating WebAssembly
|
||||
// against `(new Go()).importObject`. Returns a `Core` ready to use in
|
||||
// tests; subsequent calls return the cached instance so each Vitest
|
||||
// file pays the boot cost once.
|
||||
|
||||
import { readFileSync } from "node:fs";
|
||||
import { resolve } from "node:path";
|
||||
import { runInThisContext } from "node:vm";
|
||||
import { adaptBridge, requireBridge } from "../src/platform/core/wasm";
|
||||
import type { Core } from "../src/platform/core/index";
|
||||
|
||||
// Vitest is launched from ui/frontend, so the static artefacts produced
|
||||
// by `make wasm` sit at <cwd>/static/. Resolving via process.cwd avoids
|
||||
// the JSDOM-specific "URL must be of scheme file" error that
|
||||
// import.meta.url triggers under the jsdom test environment.
|
||||
const STATIC_DIR = resolve(process.cwd(), "static") + "/";
|
||||
|
||||
let cached: Promise<Core> | undefined;
|
||||
|
||||
export function loadWasmCoreForTest(): Promise<Core> {
|
||||
if (!cached) {
|
||||
cached = boot();
|
||||
}
|
||||
return cached;
|
||||
}
|
||||
|
||||
async function boot(): Promise<Core> {
|
||||
if (typeof globalThis.Go === "undefined") {
|
||||
const shim = readFileSync(`${STATIC_DIR}wasm_exec.js`, "utf8");
|
||||
runInThisContext(shim);
|
||||
}
|
||||
const Go = globalThis.Go;
|
||||
if (!Go) {
|
||||
throw new Error("setup-wasm: Go runtime missing after wasm_exec.js eval");
|
||||
}
|
||||
const go = new Go();
|
||||
const wasm = readFileSync(`${STATIC_DIR}core.wasm`);
|
||||
const { instance } = await WebAssembly.instantiate(
|
||||
wasm,
|
||||
go.importObject,
|
||||
);
|
||||
void go.run(instance);
|
||||
// `go.run` is async but the Go side registers `globalThis.galaxyCore`
|
||||
// and then blocks on `select {}`; the registration happens
|
||||
// synchronously before the first await, so by the time the next
|
||||
// microtask runs the bridge is already available.
|
||||
await new Promise<void>((resolve) => setTimeout(resolve, 0));
|
||||
return adaptBridge(requireBridge());
|
||||
}
|
||||
Reference in New Issue
Block a user