feat: Adds a self-hostable Node.js relay#1648
Conversation
A self-hostable, protocol-compatible Node.js relay mirroring the Cloudflare relay (packages/relay) — pure `ws` behind a TLS reverse proxy, with `ws` as the only runtime dependency. - ports packages/relay/src/cloudflare-adapter.ts to a Node http + ws server, preserving the v1/v2 wire protocol (/ws + /health, control/data socket routing, frame buffering, close codes, the legacy JSON-ping COMPAT) - CLI (@getpaseo/relay-node / paseo-relay-node) + Dockerfile + docker-compose - docs/relay-node.md deploy guide; architecture.md + CLAUDE.md cross-links Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
feat(relay-node): add Node.js version of the relay
|
| Filename | Overview |
|---|---|
| packages/relay-node/src/relay-session.ts | Core per-session logic; faithful port of the Cloudflare Durable Object. Buffer overflow in bufferFrame silently discards the oldest frames without logging. |
| packages/relay-node/src/relay-server.ts | HTTP + WebSocket server entry point; clean request routing, proper upgrade validation, eviction-safe session closure guard. |
| packages/relay-node/src/relay-server.test.ts | Real-server integration tests covering health, upgrade validation, v1 forwarding, and all v2 control/data/buffer/disconnect flows. |
| packages/relay-node/src/tagged-sockets.ts | Multi-tag WebSocket index replacing the Cloudflare hibernation API; add/get/has/remove and size all look correct. |
| packages/relay-node/src/index.ts | Narrow, designed public surface — only createRelayServer, RelayServerOptions, createLogger, and logger types are exported; internals stay private. |
| packages/relay-node/Dockerfile | Two-stage build (builder + lean runner), drops to node user, omits dev-deps in runner. |
| packages/relay-node/src/config.ts | Zero-extra-dependency config using node:util parseArgs; port and frame-count validation are explicit and correctly bounded. |
Reviews (5): Last reviewed commit: "Merge branch 'main' into main" | Re-trigger Greptile
index.ts was a barrel re-exporting the whole implementation (RelaySession, TaggedSocketIndex, version helpers, loadConfig, attachment types), which both violates the repo's "no barrel index.ts" rule and couples library callers to internals. Narrow the public entry to the designed surface — createRelayServer (+ RelayServerOptions), with createLogger/Logger/LogLevel so callers can inject the optional logger. The CLI imports internals directly, so it is unaffected. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
version was optional but is set unconditionally by both connectV1 and connectV2 (the only attachment constructors), so the optionality was never exercised. The defensive `attachment.version ?? LEGACY_RELAY_VERSION` fallback in onClose was therefore unreachable dead code. Making the field required removes the false impression that an attachment can exist without a version and lets onClose read it directly. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
.dockerignore excluded tsconfig.json, but the builder stage does
`COPY package.json tsconfig.json ./` before running tsc — so a real
`docker build` failed at that COPY ("tsconfig.json not found") before
compilation. Drop it from .dockerignore; the builder needs it.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Linked issue
Closes #
Type of change
What does this PR do
Adds a self-hostable Node.js relay that mirrors the existing Cloudflare relay behavior while remaining protocol-compatible.
Changes included:
packages/relay/src/cloudflare-adapter.tsto a Node.js HTTP +wsserver implementation./wsand/healthendpoints@getpaseo/relay-nodeCLI (paseo-relay-node) for standalone deployment.docs/relay-node.mddeployment guide.architecture.mdandCLAUDE.md.This provides a lightweight self-hosted alternative to the Cloudflare relay with
wsas the only runtime dependency, making it easier to deploy on standard Node.js environments.How did you verify it
Verified locally by:
paseo-relay-nodeCLI./healthresponds successfully./ws.Checklist
npm run typecheckpassesnpm run lintpassesnpm run formatran (Biome)