The goal: a real host on your desk
A product developer types one command and gets a browser tab in which their product behaves exactly as it does inside the mobile host. Real signer, real product account for its own .dot name, real statement store, real permissions. No phone in the loop.
truapi-host signing-host --serve --frame-listen 127.0.0.1:9955 --product-id my-product.dot --auto-accept &
pnpm dev
That is what a dev server is for every other kind of app, with the host playing the role a database or an API mock plays elsewhere. Nothing about it is exotic, and the CLI is already 90% of the way there.
Why this matters more than it sounds
- The paths that matter most are the least tested. Product-account signing, AutoSigning grants and authorized statement proofs can only be exercised on a phone running a nightly build today, which means they are the code we iterate on least.
- The simulated host cannot catch drift.
@parity/host-api-test-sdk is good and worth keeping, but it answers with dev accounts and its own wire implementation. It did not catch the RFC-0022 DerivationIndex change. A real host caught it in thirty seconds.
- A phone in the loop taxes every cycle. Two devices per debugging round, and a clean identity, a reset, or two players at once are all genuinely painful.
The CLI fixes all three. Two small gaps stop it from being usable that way, one on each side:
| gap |
issue |
state |
| a browser cannot dial the frame socket |
this issue |
PR #438 |
| the host cannot run beside a dev server |
#436 |
PR #439 |
Both PRs are open and green. Together they turn the recipe above into something any web product can adopt in two lines.
Evidence that the rest already works
Verified against a live signing-host from a browser product (the DIM2 SPA, @parity/product-sdk-host in a plain tab), and re-verified across restarts over two weeks:
- container detection,
requestLogin answering AlreadyConnected, camera permission granted by the host
accountGet for dim2.dot/0 and /1, deterministic and stable across restarts, same username five days later
signRaw and createTransaction for our own product, refused for a foreign product id exactly as documented
resourceAllocation.request for AutoSigning returning Allocated, which is the promptless grant iOS gives and Android refuses, so this is the only place that code path can be exercised at all
statementStore.createProofAuthorized returning a real Sr25519 proof, submit accepted by the node, and a MatchAll subscription delivering the statement back
entropy.derive for the self-custody playing key
The gap this issue covers
The iOS and Android host READMEs both tell products to take the bridge URL and pass it to @parity/truapi's createWebSocketProvider(url). That function is not in the package. Grep for it and you find only the docs that promise it:
ios/truapi-host/README.md:80 and :274
ios/truapi-host/Sources/TrUAPIHost/TrUAPIHost.swift:936
android/truapi-host/README.md:58 and :256
android/truapi-host/src/main/kotlin/io/parity/truapi/TrUAPIHost.kt:25 and :533
Because it is missing, the same 30 lines are hand-written four times already:
| where |
what |
ios/.../TrUAPIHost.swift:192 |
createWebSocketMessagePort as a Swift string literal |
android/.../TrUAPIHost.kt:421 |
the same snippet again, in Kotlin |
rust/crates/truapi-host-cli/js/ws-provider.ts |
a real WireProvider, whose TCP branch uses nothing Node-specific |
dim2-spa-fable apps/web/src/dev/cliHost.ts |
our copy, written because there was nothing to import |
The practical effect: truapi-host signing-host --frame-listen says it is "required for browser clients", but a browser client cannot use it. The sandbox bootstrap builds only two transports, the iframe handover and a MessagePort already parked on window.__HOST_API_PORT__. There is no WebSocket path, so a product in a plain tab cannot reach the frame socket while it is listening.
Ask, two small parts, both in PR #438:
- Export
createWebSocketProvider(url): WireProvider from @parity/truapi, so the docs become true and the four copies can collapse onto one.
- Add
connectWebSocketHost(url) on the sandbox path, so isCorrectEnvironment() and getClientSync() also work in a plain tab. This is the half that lets a product built on @parity/product-sdk-host run against the CLI host with no product code changes at all.
Known limits, so the pitch is honest
- The only network preset is
paseo-next-v2, so a product whose descriptors target another network keeps its own chain reads while statements and host chain routes stay on nextv2.
- Host-routed chain access was unavailable until recently, because
feature_supported was hardcoded false. That is fixed on main and answers from the served chain set, so this is no longer a gap, just something to build on.
The goal: a real host on your desk
A product developer types one command and gets a browser tab in which their product behaves exactly as it does inside the mobile host. Real signer, real product account for its own
.dotname, real statement store, real permissions. No phone in the loop.truapi-host signing-host --serve --frame-listen 127.0.0.1:9955 --product-id my-product.dot --auto-accept & pnpm devThat is what a dev server is for every other kind of app, with the host playing the role a database or an API mock plays elsewhere. Nothing about it is exotic, and the CLI is already 90% of the way there.
Why this matters more than it sounds
@parity/host-api-test-sdkis good and worth keeping, but it answers with dev accounts and its own wire implementation. It did not catch the RFC-0022DerivationIndexchange. A real host caught it in thirty seconds.The CLI fixes all three. Two small gaps stop it from being usable that way, one on each side:
Both PRs are open and green. Together they turn the recipe above into something any web product can adopt in two lines.
Evidence that the rest already works
Verified against a live
signing-hostfrom a browser product (the DIM2 SPA,@parity/product-sdk-hostin a plain tab), and re-verified across restarts over two weeks:requestLoginansweringAlreadyConnected, camera permission granted by the hostaccountGetfordim2.dot/0and/1, deterministic and stable across restarts, same username five days latersignRawandcreateTransactionfor our own product, refused for a foreign product id exactly as documentedresourceAllocation.requestfor AutoSigning returningAllocated, which is the promptless grant iOS gives and Android refuses, so this is the only place that code path can be exercised at allstatementStore.createProofAuthorizedreturning a real Sr25519 proof,submitaccepted by the node, and aMatchAllsubscription delivering the statement backentropy.derivefor the self-custody playing keyThe gap this issue covers
The iOS and Android host READMEs both tell products to take the bridge URL and pass it to
@parity/truapi'screateWebSocketProvider(url). That function is not in the package. Grep for it and you find only the docs that promise it:ios/truapi-host/README.md:80and:274ios/truapi-host/Sources/TrUAPIHost/TrUAPIHost.swift:936android/truapi-host/README.md:58and:256android/truapi-host/src/main/kotlin/io/parity/truapi/TrUAPIHost.kt:25and:533Because it is missing, the same 30 lines are hand-written four times already:
ios/.../TrUAPIHost.swift:192createWebSocketMessagePortas a Swift string literalandroid/.../TrUAPIHost.kt:421rust/crates/truapi-host-cli/js/ws-provider.tsWireProvider, whose TCP branch uses nothing Node-specificdim2-spa-fable apps/web/src/dev/cliHost.tsThe practical effect:
truapi-host signing-host --frame-listensays it is "required for browser clients", but a browser client cannot use it. The sandbox bootstrap builds only two transports, the iframe handover and aMessagePortalready parked onwindow.__HOST_API_PORT__. There is no WebSocket path, so a product in a plain tab cannot reach the frame socket while it is listening.Ask, two small parts, both in PR #438:
createWebSocketProvider(url): WireProviderfrom@parity/truapi, so the docs become true and the four copies can collapse onto one.connectWebSocketHost(url)on the sandbox path, soisCorrectEnvironment()andgetClientSync()also work in a plain tab. This is the half that lets a product built on@parity/product-sdk-hostrun against the CLI host with no product code changes at all.Known limits, so the pitch is honest
paseo-next-v2, so a product whose descriptors target another network keeps its own chain reads while statements and host chain routes stay on nextv2.feature_supportedwas hardcoded false. That is fixed onmainand answers from the served chain set, so this is no longer a gap, just something to build on.