@volter/twin-tunnel
v0.1.35
Published
Local Cloudflare Quick Tunnel control-plane and Volter relay twin with a genuine loopback HTTP data path.
Readme
@volter/twin-tunnel
Legacy connector helpers: this package still has callable helpers using the retired v1
syncPullAPI. Those paths require migration before use on the current kernel; older helper descriptions below do not establish current compatibility. Check the generated index for protocol standing and use the shared model for current state semantics.
A local twin of two tunnel surfaces used by Volter worlds:
- Cloudflare Quick Tunnel allocation (
POST /tunnel) using the first-partycloudflaredquick-service field envelope. Generated field values are independent twin-local facts; the twin does not assert undocumented relationships amongid,name,hostname, oraccount_tag. - The owned
@volter/tunnelWebSocket control protocol and HTTP relay path.
The boundary is deliberate. The control plane is twinned; payload transport is real loopback
HTTP. world-tunnel quick --url ... prints the machine-readable
VOLTER_SHARE_URL=http://127.0.0.1:... handshake and genuinely forwards through that loopback
ingress. It does not claim Cloudflare edge QUIC, public DNS, TLS, reachability, or an SLA. Those
remain real deployment concerns. quick --url accepts a credential-free loopback HTTP(S) origin
only; path/query/fragment and backslash-normalized inputs are rejected rather than silently
discarded while forwarding.
# Local share rehearsal used by world cookbooks
world-tunnel quick --url http://127.0.0.1:3000
# Standalone quick API + Volter relay
world-tunnel serve --port 8787 --root .volter/world/tunnel
world-tunnel conformance
# Cloudflare control URL: http://127.0.0.1:8787/tunnel
# @volter/tunnel host: http://127.0.0.1:8787An unmodified @volter/tunnel client registers against the serve URL and forwards requests to
its configured local port. Its normal default (authRequired: true) is preserved: anonymous
requests receive 401, and the server's mintToken(tunnelId) helper creates a fake-local,
tunnel-bound HS256 token for Bearer/query/cookie rehearsal. Matching the owned relay default, a
valid signed token without tid is shared SSO while a present tid must match; start serve with
--require-tid to select the owned REQUIRE_TID=true policy. Basic-auth values remain only in the
live socket session; the append-only kernel stores a SHA-256 digest and configured bit, never the
plaintext. As upstream does, Basic auth is configured only when both user and password are
non-empty; an SDK-emitted empty value leaves that gate disabled. The twin accepts any non-empty
fake relay credential and rejects a missing one.
Browser WebSockets relay for real. A ws://<relay>/__twin/tunnels/<id>/<path> upgrade passes
the same Basic/JWT gates the HTTP path does, then bridges: the relay sends ws-upgrade with a
fresh connId, the tunnelled path and the browser’s own handshake headers; the client answers
ws-ready (or ws-error, which closes the browser socket with 1011 and the client’s own reason);
ws-message carries base64 payloads with the binary flag in both directions; ws-close bridges
either way with the vendor’s own code clamping. Frames a browser sends before ws-ready are
QUEUED and delivered in order rather than dropped, a control socket may only drive the connIds
it owns, and a control socket closing takes its bridges with it. request-abort is now sent from
ONE place for every way a request can end badly — timeout, visitor hang-up, malformed frame, owner
disconnect — so the client always stops producing for a request the relay has forgotten.
Still explicit capability todos: reliable end-to-end incremental delivery and its backpressure/cancellation (see "the streaming trade" below), account/reservation management, usage metering, inspector/replay, and Cloudflare managed tunnels.
There is no UI mirror. Both products' relevant surface is a CLI/control protocol; their hosted dashboards are incidental account and operations tooling, not an agent navigation target.
Coverage
The capability manifest in src/tunnel-capabilities.ts is the honest denominator for both the
Cloudflare Quick Tunnel allocation surface and the owned Volter relay. Run
bun scripts/twin-capabilities.ts tunnel for the current counts. Every capability is classified
as done or planned; nothing disappears behind a broad feature label.
Done (proven, verifiable)
- Cloudflare Quick Tunnel allocation envelope, unique ephemeral allocations, persistence, read-only refusal, and bounded reserved-route/loopback-origin handling.
- Real loopback forwarding through both the quick adapter and the owned relay, including method, path, body, status, the owned cache/privacy response-header rules, CORS preflight, malformed-frame containment, and single durable/live ownership under overlapping claims.
- Relay registration, replacement, disconnect, authentication, JWT bootstrap/cookie behavior, session-only Basic-auth plaintext, persistence ordering, and live status.
- An authoritative 15-discriminator census imported from
@volter/tunnel-core, with one explicit capability row per frame; implemented frames have transport proofs and unimplemented frames remain planned. Fourteen of the fifteen now carry transport proofs — everything butquota— driven over a genuine loopback WebSocket against a real local server, with the pretend client a harness shaped after the owned client'shandleWsUpgrade— it dials with the offered subprotocols, strips the hop-by-hop handshake headers, and reports a real local failure asws-error; it does not reproduce the client'sx-forwarded-*re-stamping, its 15s dial timeout orsafeClose's teardown, none of which any assertion depends on. - Visitor-disconnect abort: a browser hang-up before
response-startsendsrequest-abort, and the request is genuinely FORGOTTEN — a late response frame naming it is refused. - Read-only connector pull, mapping, idempotence, refusal-state survival, conformance, and the fail-closed persistent live-call budget.
The streaming trade, measured
The relay BUFFERS a streamed response to response-end before answering the visitor, and that is
a measured choice rather than an unfinished one. On this runtime a response body that has begun
cannot be failed THROUGH Bun.serve's response surface: erroring the ReadableStream behind a
Response ends the chunked body cleanly,
so a visitor reads a truncated payload as a successful 200 and cannot tell it from a complete one
— and a content-length that would have made the truncation detectable is dropped the moment the
body is a stream. node:http on the SAME runtime CAN signal it (src/tunnel-stream-boundary.test.ts
measures that too) — so this is a trade with a named alternative, not an impossibility: re-hosting
the HTTP plane would mean giving up server.upgrade, which the entire WebSocket bridge is built
on. The first two measurements go RED the day Bun.serve gains the ability; that is the signal to build
tunnel.volter.http.streaming, …stream_backpressure and …stream_cancel_abort and revisit the
trade. Committing the head today would buy latency and sell the ability to fail a response
honestly — which is what keeps the malformed-sequence 502 reachable at all.
stream_backpressure has a second, independent blocker: the owned protocol's fifteen
discriminators contain no pause frame, so a relay cannot ask a client to slow down.
Planned (todo — covered by default, not yet built)
- Reliable incremental streaming, backpressure and cancellation (blocked as above).
- Reconnect/backoff, quota frames, forwarded/response-header rules, rate limiting, and constant-time shared-secret comparison.
- Managed tunnel/account/reservation/usage/signup/inspector surfaces and confirmed public push.
- Cloudflare Quick Tunnel concurrency and SSE limitations, plus Cloudflare managed tunnels.
No UI mirror
The modeled products expose a CLI, HTTP API, and WebSocket control protocol. Their hosted account dashboards are operations tooling rather than an agent workflow, so this package deliberately has no React mirror and no UI capabilities. Coverage is API + connector.
