feat: ts/packages/cloudflare-hub, a Worker deployment of a hub node - #14
Merged
Conversation
Mearman
marked this pull request as ready for review
September 10, 2026 16:24
Codex Review SummaryThis comment shows the latest Codex review activity on this pull request.
ℹ️ About Codex in GitHubYour team has set up Codex to review pull requests in this repo. Reviews are triggered when you
Codex reacts with 👀 while any review is running, comments if it has suggestions, and reacts with 👍 once all reviews finish with no findings. |
Mearman
force-pushed
the
feat/cloudflare-hub
branch
from
September 10, 2026 17:37
f999ca5 to
8812b41
Compare
A reference Cloudflare Worker running a public, always-on hub, depending on core as an ordinary workspace consumer. The ports architecture is what makes a Worker possible at all -- no net.Server, no full node:crypto -- so this package supplies Worker-shaped adapters for the same contracts: a WebSocket-message Transport (each binary message is one self-delimiting CBOR frame; upgrades arrive through the fetch handler via acceptPair rather than a bound listener) and a Web Crypto Identity deriving device-id from the raw public-key bytes, with signature interop proven against core's Node adapter in both directions. The hub logic itself is deliberately thin: relay-connect pairing via gossiped device-ids, bidirectional relay-data forwarding, and cleanup on disconnect. Hostile input mirrors core's TCP adapter: undecodable bytes reject that connection; a decodable but schema-invalid frame drops without disconnecting. Deferred and documented in the package README: raw-TCP ingress via cloudflare:sockets, the announcer role (needs a KV/Durable-Object Storage adapter), and a real deploy (wrangler deploy --dry-run validates the bundle in CI without credentials).
turbo runs workspace-wide, so the renamed ts-verify job already linted/typechecked/tested the hub; adding the _build step also exercises its wrangler deploy --dry-run bundle validation. README's Implementations section records the hub alongside core.
… relayed a frame The pull-based for-await over receive() parks on a pure-JS waiter, and workerd's hang detection cancels any plain-Worker request whose promise chain does that -- so the old entry logged "the Workers runtime canceled this request because it detected that your Worker's code had hung" once per connection and relayed zero frames, while looking alive (the socket-level listeners still fired, and wrangler deploy --dry-run passed because bundling executes nothing). A Durable Object is the documented home for exactly this lifetime shape: the DO class is defined in the entrypoint (wrangler resolves the binding against the entrypoint's own exports) and owns the hub state, accepting each upgrade's server socket and driving the hub per connection; the default export becomes a thin router forwarding upgrades to the single named instance. The SQLite-backed class per current wrangler guidance makes hibernation available as a follow-up without a migration later. The transport adapter loses its now-dead listen/acceptPair factory (the DO drives wrapWebSocket directly) and keeps the Connection contract as its whole surface. scripts/live-check.mjs drives two genuine WebSocket clients through gossip -> relay-connect -> relay-inbound -> bidirectional relay-data against wrangler dev -- the check dry-run bundling cannot make -- verified passing three consecutive runs with zero hang-cancellation errors in the dev log. Plain-JS scripts join the eslint ignores (no TS project to type them against); .wrangler/ joins .gitignore.
…rings both ways A rejecting receive iteration (undecodable bytes or a non-binary message -- the adapter's own hostile-input behaviour) used to reject handleConnection, which the entry voids: an unhandled rejection on every hostile client. The rejection is the disconnect signal and the adapter already closed the socket, so handleConnection now treats it exactly like a clean end: registry and pairing cleanup run identically. A second relay-connect from the same initiator used to overwrite only the initiator-side pairing entry, leaving the old target's side live: the old target's relay-data kept flowing to the initiator on a pipe the initiator believed was its new pairing -- mis-attributed traffic in the exact role the package exists to serve. A new relay-connect now tears any pairing either side already belongs to down in both directions before installing the new one. Tests grow accordingly, with FakeWebSocket extracted to a shared helper: the rejection-cleanup path, the stale-pairing case (B's data must not reach A after A re-pairs to C), text-message and send-after-close handling on the adapter, a byte-identical re-encode assertion, and -- the seam where the entry bug hid -- hub tests driving the real wrapWebSocket adapter rather than fakes alone, including hostile bytes closing only the offending connection.
The teardown only covered the same-role entries: a connection already the TARGET of one pairing (X->A) that then initiated its own (A->B) kept X's pipe half-alive -- X's relay-data kept flowing to A on what A believed was its pipe with B -- and symmetrically for a target holding its own outbound pipe. The teardown is now a single forgetPairingsOf helper removing every pairing a connection belongs to in either role, both directions of each, applied to both the initiator and the target before installing a new pairing; forgetConnection reuses the same helper. Cross-role tests pin both directions: an old opposite-end's data arrives nowhere after either re-pair shape.
…check claim The adapter and wrangler.toml headers still referenced src/durable-hub.ts, folded into src/worker.ts when the Durable Object class moved into the entrypoint. The README's verification prose now states what the live check proves structurally (exits non-zero naming the failing step on regression) rather than a point-in-time run count. Also pins the identity adapter's unsupported-alg behaviour with a test: an unimplemented COSE algorithm throws loudly rather than mis-verifying.
Mearman
force-pushed
the
feat/cloudflare-hub
branch
from
September 10, 2026 18:02
8812b41 to
3171ee4
Compare
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Plan task #6: a reference Cloudflare Worker deployment of a public, always-on hub node, depending on
ts/packages/coreas an ordinary workspace consumer.The ports architecture is what makes a Worker possible at all — no
net.Server, no fullnode:crypto— so rather than reinventing protocol logic, this package supplies Worker-shaped adapters for core's existing port contracts:acceptPair();listen()registers the handler. Undecodable bytes reject that connection; a decodable but schema-invalid frame drops without disconnecting — mirroring core's TCP adapter's split.The hub logic itself is deliberately thin:
relay-connectpairing via gossiped device-ids, bidirectionalrelay-dataforwarding, forget-on-disconnect, and newest-gossip-wins for a device's mapping. 15 tests cover the pairing logic, the transport adapter's hostile-input behaviour, and identity interop.CI: the ts job (renamed
ts-verify, turbo runs workspace-wide) now also runs_build, which for this package iswrangler deploy --dry-run --outdir=dist— validating the Worker bundle with no Cloudflare credentials. Deferred and documented in the package README: raw-TCP ingress viacloudflare:sockets, the announcer role (needs a KV/DO Storage adapter over core's Storage port), and a real authenticated deploy.