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:
Ilia Denisov
2026-05-07 12:58:37 +02:00
parent cd61868881
commit fbc0260720
25 changed files with 7284 additions and 36 deletions
+76 -30
View File
@@ -506,51 +506,97 @@ add coverage. Future contributors looking for "the Connect tests" can
read any file in `gateway/internal/grpcapi/` — they all use the
Connect client now.
## Phase 5. WASM Build, `WasmCore` Adapter, `GalaxyClient` Skeleton
## ~~Phase 5. WASM Build, `WasmCore` Adapter, `GalaxyClient` Skeleton~~
Status: pending.
Status: done.
Goal: package `ui/core` as a WASM module, expose it to TypeScript
through a typed adapter, and prove the WASM-side crypto pipeline at
unit level. End-to-end Connect round-trip is validated in Phase 7
(authenticated calls only become possible after login).
Artifacts:
Decisions taken with the project owner before implementation:
- `ui/wasm/main.go` TinyGo entry point exporting `Core` API to JS
- `ui/Makefile` target `wasm` producing `core.wasm` and `wasm_exec.js`
under `ui/frontend/static/`
- `ui/frontend/src/platform/core/index.ts` `Core` interface and
build-time target resolver
- `ui/frontend/src/platform/core/wasm.ts` `WasmCore` adapter
- `ui/frontend/src/api/galaxy-client.ts` `GalaxyClient` orchestrating
`Core.signRequest` → ConnectRPC fetch → `Core.verifyResponse`
- `ui/frontend/src/api/connect.ts` typed Connect client built from
generated stubs (Connect codegen via `@bufbuild/protoc-gen-es` and
`@connectrpc/protoc-gen-connect-es`)
- topic doc `ui/docs/wasm-toolchain.md` documenting TinyGo vs
standard Go choice and bundle size measured
1. **TinyGo as primary toolchain.** `core.wasm` lands at 903 KB —
well under the 1 MB acceptance bar. The `GOOS=js GOARCH=wasm`
fallback path stays documented in `ui/docs/wasm-toolchain.md`.
2. **`Core.signRequest` returns canonical bytes only.** No private
key inside WASM; Phase 6 plugs WebCrypto's non-exportable keys at
the orchestration layer. `GalaxyClient` takes a pluggable `Signer`
so Phase 5 tests pass a fixture-key signer and Phase 6 swaps in
WebCrypto without touching the orchestration.
3. **TS codegen runs locally, not against buf.build BSR.** A new
`ui/buf.gen.yaml` invokes
`frontend/node_modules/.bin/protoc-gen-es` (added as a
devDependency). This sidesteps BSR rate limiting and removes the
network dependency from the codegen step.
4. **Field naming is camelCase end-to-end.** Both the TS `Core`
interface and the Go bridge in `ui/wasm/main.go` use camelCase
field names; there is no snake-case translation layer.
Artifacts (delivered):
- `ui/wasm/main.go` TinyGo entry point on `globalThis.galaxyCore`
with four functions: `signRequest`, `verifyResponse`,
`verifyEvent`, `verifyPayloadHash`.
- `ui/Makefile` `wasm` and `ts-protos` targets.
- `ui/buf.gen.yaml` with the local Protobuf-ES plugin (single plugin —
protobuf-es v2 emits both message types and Connect service
descriptors in one file).
- `ui/frontend/src/platform/core/index.ts` — typed `Core` interface
plus a `loadCore()` resolver (Phase 5 ships only the WASM adapter).
- `ui/frontend/src/platform/core/wasm.ts` — `WasmCore` adapter for
browsers; the JSDOM test path lives next to it in
`ui/frontend/tests/setup-wasm.ts`.
- `ui/frontend/src/api/connect.ts` — typed Connect-Web transport +
`EdgeGatewayClient` factory.
- `ui/frontend/src/api/galaxy-client.ts` — `GalaxyClient` skeleton
with injected `Signer` and `Sha256` dependencies.
- `ui/frontend/src/proto/galaxy/gateway/v1/edge_gateway_pb.ts`
(generated) and `ui/frontend/src/proto/buf/validate/validate_pb.ts`
(generated as a transitive import via `--include-imports`).
- `ui/frontend/static/core.wasm` (903 KB) + `wasm_exec.js` (TinyGo
shim).
- Three Vitest files exercising the bridge end-to-end:
`tests/wasm-core.test.ts` (each Core method, including a sanity
`signRequest` check that the canonical bytes start with the v1
domain marker), `tests/wasm-core-canon-parity.test.ts` (byte-for-
byte parity against three request fixtures plus the response and
event signature fixtures from `ui/core/canon/testdata/`), and
`tests/galaxy-client.test.ts` (orchestration through a mock `Core`
and `createRouterTransport` from `@connectrpc/connect`).
- Topic doc `ui/docs/wasm-toolchain.md`.
- `ui/README.md` repository-layout block.
Dependencies: Phases 2, 3, 4.
Acceptance criteria:
Acceptance criteria (met):
- `make wasm` produces a deterministic bundle under 1 MB (TinyGo) or
under 3 MB (standard Go fallback);
- `make wasm` produces `core.wasm` deterministically under 1 MB (903
KB measured);
- `WasmCore.signRequest` produces canonical bytes byte-for-byte
identical to the gateway-side verifier output on shared fixtures
(validated via Vitest with the WASM module loaded in JSDOM);
- `WasmCore` exposes the same TypeScript types as the future
`WailsCore` and `CapacitorCore` will need to satisfy.
identical to the gateway-side fixtures for three message types
(`request_user_account_get`, `request_user_games_command`,
`request_lobby_my_games_list`);
- `WasmCore` exposes the same `Core` TypeScript types future
`WailsCore` and `CapacitorCore` adapters will satisfy.
Targeted tests:
Targeted tests (delivered):
- Vitest unit tests for `WasmCore` calling each public method with a
fixture WASM module loaded in JSDOM;
- Vitest unit tests for `GalaxyClient` using a mock `Core` and a mock
Connect transport;
- Vitest tests asserting `WasmCore.signRequest` output matches gateway
fixtures byte-for-byte for at least three message types.
- Vitest unit tests for `WasmCore` calling each public method with
the WASM module loaded in JSDOM via `tests/setup-wasm.ts`;
- Vitest unit tests for `GalaxyClient` using a mock `Core` and the
in-memory `createRouterTransport`;
- Vitest tests asserting `WasmCore.signRequest` output matches the
committed gateway fixtures byte-for-byte for the three request
message types listed above.
Decision deviation note: the initial plan listed `protoc-gen-es` and
`protoc-gen-connect-es` as separate plugins. Protobuf-ES v2 generates
service descriptors in the `_pb.ts` file directly, so a single
`@bufbuild/protoc-gen-es` plugin is sufficient — `@connectrpc/connect`
v2 consumes those descriptors via `createClient`. The `connect-es`
plugin is a v1-only path and is intentionally not used here.
## Phase 6. Storage Layer (Web)