dialback
@johnhenry/dialback is a library for creating a reverse proxy over
WebSockets: an Agent dials out (through a NAT/firewall it’s behind), and
the Server dials back through that same connection to reach it — the
name is the mechanism, not just a label.
Request/Response <-HTTP-> [Server] <-WS-> [Agent]Provenance: previously developed as
leproxy(itself a rename of the originalproxy-socks), but never actually published to npm under either name. Renamed and adopted into the@johnhenryscope, starting fresh at0.0.0.
A Server instance is Fetch-shaped (server.fetch(request)), which is
what makes it easy to sit behind another package in this family:
@johnhenry/hostable can forward gateway traffic straight
to an agent behind a firewall with <Upstream app={dialbackServer} />, no
adapter needed — see hostable’s own API, “Ecosystem
integration.”
Install
Section titled “Install”npm install @johnhenry/dialbackdialback 0.0.5 requires Node 26 or later ("engines": { "node": ">=26.0.0" }) and ships its own TypeScript declarations for Server and Agent. Its runtime dependencies are ws and @johnhenry/webwire.
Quick example
Section titled “Quick example”// Server (Node.js)import { Server } from "@johnhenry/dialback";
const server = new Server(() => new Response("no responder", { status: 500 }), { secret: "shared-secret",});
await server.listen(8082);Server#listen(port) starts an HTTP server and a WebSocket server on the same port: plain HTTP requests go to server.fetch(), and WebSocket connections are registered as agents (and unregistered when they close). Converting Node’s req/res to and from Request/Response is done by @johnhenry/webwire, which dialback 0.0.5 depends on — so repeated response headers such as Set-Cookie reach the client as separate header lines instead of being collapsed to the last one. X-Forwarded-Host is read before Host, so the agent sees the host the original client asked for rather than the proxy’s own. If you want to run your own HTTP server instead, use server.fetch() and server.addConnection() directly, as in the Deno example.
// Agentimport { Agent } from "@johnhenry/dialback";
const { serve } = new Agent("ws://localhost:8082", { reconnect: 1000, secret: "shared-secret" });
serve(async (request, { id }) => { return new Response("Hello there!", { status: 200, headers: { "content-type": "text/plain", etag: id }, });});See API for the full Server/Agent reference,
including Deno usage and the authentication model, and the
dialback/browsermesh transport for a
real per-agent-identity alternative to the shared-secret model above.
The pages here
Section titled “The pages here”- API —
Server,Agent, utility functions, and a note on authentication - The
dialback/browsermeshtransport — optional per-agent Ed25519 identity in place of a shared secret, the handshake, the@johnhenry/dialback/handshakesubpath, and its honestly-documented limitations
Contributing
Section titled “Contributing”Contributions are welcome — see the repo for details.
License
Section titled “License”This project is licensed under the MIT License.