Browser cockpit: serve the buzz desktop frontend as a NIP-07 web client #16
Labels
No labels
bug
duplicate
enhancement
help wanted
invalid
question
wontfix
bug
duplicate
enhancement
help wanted
invalid
question
wontfix
No milestone
No project
No assignees
1 participant
Notifications
Due date
No due date set.
Dependencies
No dependencies set.
Reference
allod/archetypes#16
Loading…
Add table
Add a link
Reference in a new issue
No description provided.
Delete branch "%!s()"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
Serve Block's buzz desktop frontend as a browser cockpit: a plain web page against the buzz relay, signing through a NIP-07 browser extension, with no Tauri backend and no page-held private key. This is the public half of the work — a small adapter patch against the pinned upstream source, a reproducible package, and an opt-in static-serving NixOS interface. Deployment composition lives downstream.
The relay already carries the whole protocol client-side:
desktop/src/shared/api/relayClientSession.tsis a complete NIP-01 + NIP-42 client in TypeScript, every read isPOST /queryand every writePOST /events, anddesktop/src/testing/e2eBridge.tscovers 195 of 247 Tauri commands with a real browserWebSocket, realfetch, and real secp256k1 in its relay mode. The Rust backend supplies only socket transport, event signing, and OS keyring — so a browser adapter is thin, and upstream CI keeps the seam it rides from rotting.Milestones
M1 — adapter patch (this issue's first PR). A patch set against the pinned upstream revision, plus its test coverage:
VITE_BUZZ_COCKPIT=1layered onbuild:e2e) and a pre-render bootstrap that fetches same-origin/buzz-cockpit-config.json, discovers a NIP-07 provider, validates the signer, seeds the required localStorage records, and renders an actionable fatal gate before app render when config or signer is unavailable. Unflaggedbuild:e2estays upstream's test harness andbuildstill tree-shakes the bridge out.window.nostr, re-checks the provider pubkey per call, and validates the returned event byte-for-byte against the unsigned template, expected pubkey, recomputed event ID, and Schnorr signature.27235event over the exact URL, method, exact body digest, and a nonce, sent asAuthorization: Nostr <base64(event)>. No relay-mode request relies on the dev-modeX-Pubkeyheader.get_nsecandimport_identityreject as extension-managed,persist_current_identitynever creates a page key, sign-out clears page state without claiming to delete the extension key, and the baked fixture key is compiled out of cockpit bundles.7reaction publication and kind5removal,is_shared_identityreturning false without a warning, and a stringdescriptionat every relay-mode channel boundary where the native converter promises one.plugin:websocket|*relay transport; browser-native and custody commands are answered by the dispatcher itself; everything else rejectsunavailable in browser cockpit, and native-only UI entry points are hidden rather than left to dead-end.M2 — package and static-service interface. A
packages.<system>.buzz-cockpitoutput built from the same pinned source object as the relay and CLI with the locked pnpm graph, installed atshare/buzz-cockpit/; a required canonicalservices.buzz.relayUrlrendered intoRELAY_URLwith the HTTP origin and media base derived from it; and an opt-inservices.buzz.cockpitserving the immutable package plus generated non-secret runtime JSON at an origin root through its own nginx listener, defaulting to127.0.0.1:3001and adding no firewall port. Positive and mutation-style negative checks cover pin equality, required config, URL derivation, loopback binding, firewall absence, asset 404 behavior, and generated unit text.M2 coordinates with #15 so there is one upstream pin and one buzz module: extend its accepted contract if it has not merged, consume its landed names if it has.
Notable constraints
The allowlist admission rule is subtle. The cockpit dispatcher runs before the bridge switch, so allowlisting a command merely forwards to whatever case exists there. A command may join the allowlist only when that branch is already real in relay mode. Anything whose only case is a mock must be answered by the dispatcher itself or denied — allowlisting a mock-only command reintroduces fabricated native success, which is exactly what this boundary exists to prevent. A set-equality test against the switch parsed out of the bridge source makes a newly added upstream command fail review rather than become silently reachable.
Firefox only, by dependency, and it is a tripwire not a preference. The relay emits a literal
Access-Control-Allow-Headers: *. The Fetch spec excludesAuthorizationfrom that wildcard; Chromium and Safari enforce the exclusion and Firefox currently does not, so the cross-origin NIP-98 header only reaches the relay in Firefox. If a Firefox update starts enforcing it,/queryand/eventspreflight will fail; the remedy is an explicit relay allow-header list or a same-origin proxy, and no cockpit-side workaround exists. The Firefox project's NIP-98 assertions are how that regression gets caught.Deliberately absent. Huddle/voice, managed agents and ACP runtimes, projects/git, mesh compute, local archive, and OS-keyring custody are Rust-native and stay unavailable — for a cockpit that is the point. Browser media upload, native pickers and downloads, and message deletion are also out of scope: the bridge's existing upload descriptors are fabricated and must not be presented as successful uploads. Relay-side agent participants and their messages remain ordinary, visible chat data.
Out of scope. No relay protocol or router changes, no reuse of the relay's own SPA serving, no new web client or fork of the desktop UI, and no cleanup of the 11k-line bridge beyond the narrow adapter seams.
Open upstream defect
An intermittent
TypeError: Cannot read properties of null (reading 'trim')has been reported near channel creation and is not reproduced deterministically. The string-contract correction in item 6 is interface hardening, not a diagnosis: a bounded instrumented reproduction run is the gate for calling anything a crash fix, and a clean run leaves the intermittent cause open rather than closed.