Skip to content

feat: add fromNodeUpgradeHandler util + socket.io example#185

Merged
pi0 merged 5 commits intomainfrom
feat/node-handler-adapter
Apr 10, 2026
Merged

feat: add fromNodeUpgradeHandler util + socket.io example#185
pi0 merged 5 commits intomainfrom
feat/node-handler-adapter

Conversation

@pi0
Copy link
Copy Markdown
Member

@pi0 pi0 commented Apr 10, 2026

resolves #138

  • Adds fromNodeUpgradeHandler(handler) — wraps a Node.js (req, socket, head) upgrade handler (e.g. from ws, socket.io, express-ws) as a crossws hooks object, so it can be mounted via crossws/server/node while delegating the socket to the underlying library.
  • Pulls the real req/socket/head out of request.runtime.node.{req, upgrade} (exposed by srvx's NodeRequest from src/server/node.ts:22) — no polyfill needed, we just hand the triple to the user's handler.
  • Adds a handled?: boolean sentinel to Hooks.upgrade's return type. When the hook has taken ownership of the socket, the node adapter bails out before wss.handleUpgrade so the same socket isn't upgraded twice. Other runtimes ignore the flag.
  • Exported from the crossws/adapters/node subpath (not the main entry), and documented in docs/2.adapters/node.md.

The wrapped handler takes ownership of the socket, so crossws's other lifecycle hooks (open/message/close/error) are not invoked for connections routed through it — the wrapped library manages the WebSocket lifecycle as usual.

Example

import { WebSocketServer } from "ws";
import { fromNodeUpgradeHandler } from "crossws/adapters/node";
import { serve } from "crossws/server/node";

const wss = new WebSocketServer({ noServer: true });
wss.on("connection", (ws) => {
  ws.on("message", (data) => ws.send(data));
});

serve({
  fetch: () => new Response("ok"),
  websocket: fromNodeUpgradeHandler((req, socket, head) => {
    wss.handleUpgrade(req, socket, head, (ws) => {
      wss.emit("connection", ws, req);
    });
  }),
});

Test plan

  • pnpm vitest run test/node-handler.test.ts — new regression tests pass.
  • Regression-first: temporarily removed the handled bailout in the node adapter and confirmed both tests fail hard (via an unhandledRejection guard) with server.handleUpgrade() was called more than once with the same socket.
  • pnpm vitest run — full suite (117 passed, 6 skipped).
  • pnpm typecheck.
  • pnpm lint.

Summary by CodeRabbit

  • New Features

    • Delegate WebSocket upgrades to external Node.js handlers so third-party servers can own the socket and complete the upgrade.
  • Behavior Changes

    • Connections delegated to external handlers bypass crossws lifecycle hooks (open, message, close, error).
  • Documentation

    • New Node.js integration guide and Socket.IO integration section with usage patterns and runtime notes.
  • Examples

    • Added a runnable Socket.IO example and browser client UI demonstrating combined transports.
  • Tests

    • New tests validating delegated upgrade behavior and ensuring only the external handler performs the upgrade.
  • Chores

    • Workspace updated to include examples in development tooling.

Wraps a Node.js `(req, socket, head)` upgrade handler (e.g. from `ws`,
`socket.io`) as a crossws hooks object, delegating the socket to the
underlying library while keeping crossws's upgrade-time routing. Adds
a `handled` sentinel to `Hooks.upgrade`'s return so the node adapter
bails out of its own `wss.handleUpgrade` when the hook has taken over
the socket.
@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented Apr 10, 2026

📝 Walkthrough

Walkthrough

Adds a Node.js upgrade adapter fromNodeUpgradeHandler, extends the upgrade hook to return handled: true when an external handler performs the upgrade, and updates adapter logic to skip internal upgrade processing for delegated sockets. Includes docs, tests, and a Socket.IO example.

Changes

Cohort / File(s) Summary
Hook System Extension
src/hooks.ts
Widened Hooks.upgrade return object and AdapterHookable.upgrade() to include optional handled?: boolean; JSDoc updated to document { handled: true } as a signal that the hook performed the upgrade.
Adapter Integration
src/adapters/node.ts
handleUpgrade now checks handled from the upgrade hook result and returns early when true, avoiding mutation of augmented request fields and skipping wss.handleUpgrade(...). Re-exports fromNodeUpgradeHandler and NodeUpgradeHandler type.
Node-style Handler
src/node-handler.ts
New module exporting NodeUpgradeHandler type and fromNodeUpgradeHandler(handler) which invokes the provided (req, socket, head) handler using request.runtime.node.upgrade and returns { handled: true } after delegation; throws if runtime data is missing.
Tests
test/node-handler.test.ts
New tests validating fromNodeUpgradeHandler behavior: successful delegated WebSocket connections, echo behavior, and asserting only the upstream handler performs the upgrade (no duplicate adapter upgrade).
Documentation
docs/2.adapters/node.md
Added Node.js docs describing fromNodeUpgradeHandler, example usage with ws-style servers and Socket.IO, control-flow implications (adapter lifecycle hooks not invoked for delegated sockets), and runtime mounting constraints.
Examples & Workspace
examples/socket.io/server.mjs, examples/socket.io/public/index.html, examples/socket.io/package.json, pnpm-workspace.yaml
Added a Socket.IO + crossws example (server, client HTML, package.json) and included examples/* in workspace globs to treat examples as workspace packages.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Suggested labels

enhancement

Poem

🐇 I hopped a packet through Node's upgrade door,
Delegated the socket, then hopped off the floor.
"Handled!" I cried — the adapter stayed still,
Upstream took over, and echoed my thrill.
Hooray for integrations — more webs, less chore! 🥕

🚥 Pre-merge checks | ✅ 2 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 50.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (2 passed)
Check name Status Explanation
Title check ✅ Passed The title accurately reflects the main changes: introducing a new fromNodeUpgradeHandler utility function and providing a Socket.IO example demonstrating its usage.
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat/node-handler-adapter

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@docs/2.adapters/node.md`:
- Line 68: The docs note for fromNodeUpgradeHandler is incomplete: update the
sentence to state that it requires both request.runtime.node.req and the upgrade
tuple runtime.node.upgrade.{socket, head} from the Node.js runtime (since
fromNodeUpgradeHandler reads request.runtime.node.{req, upgrade} and extracts
req plus upgrade.socket and upgrade.head); mention it only works on Node.js and
must be used via the crossws node server plugin so those runtime fields are
present.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 112acbd4-77ed-4ecf-8383-4b96b6f95977

📥 Commits

Reviewing files that changed from the base of the PR and between 3a13250 and 250e04b.

📒 Files selected for processing (3)
  • docs/2.adapters/node.md
  • src/adapters/node.ts
  • src/node-handler.ts
✅ Files skipped from review due to trivial changes (1)
  • src/node-handler.ts
🚧 Files skipped from review as they are similar to previous changes (1)
  • src/adapters/node.ts

Comment thread docs/2.adapters/node.md
@pi0 pi0 changed the title feat: add fromNodeUpgradeHandler util feat: add fromNodeUpgradeHandler util + socket.io example Apr 10, 2026
@pi0 pi0 merged commit 86fb6f1 into main Apr 10, 2026
3 of 4 checks passed
@pi0 pi0 deleted the feat/node-handler-adapter branch April 10, 2026 21:48
@codecov
Copy link
Copy Markdown

codecov Bot commented Apr 10, 2026

Codecov Report

✅ All modified and coverable lines are covered by tests.

📢 Thoughts on this report? Let us know!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

usage with Socket.io

1 participant