Skip to content

feat: Add WebSocket support for single-app deployments - #893

Merged
aXenDeveloper merged 2 commits into
canaryfrom
claude/websocket-single-app
Oct 21, 2026
Merged

aXenDeveloper merged 2 commits into
canaryfrom
claude/websocket-single-app

Conversation

@aXenDeveloper

Copy link
Copy Markdown
Owner

Description

What?

This PR adds full WebSocket support for single-app deployments (Nitro-based apps that embed the VitNode API), bringing feature parity with standalone API servers. Previously, WebSockets were only available when the API ran separately.

Key additions:

  • createVitNodeWebSocketHooks: New crossws adapter that bridges Nitro's WebSocket handler to VitNode's protocol, running handshakes through the embedded API so they get the same session, Origin checks, and middleware as standalone APIs
  • upgradeWebSocket: Hono helper for the /ws route that coordinates with the crossws hooks
  • Realtime pub/sub readiness: Added waitForRealtimePubSub() to delay handshakes until Redis subscription is ready, preventing clients from missing messages after reconnects
  • Connection IDs: Each socket now gets a unique connectionId that changes on reconnect, allowing clients to detect state loss and restore rooms/reload data
  • Documentation: Setup guide for single apps, reconnect behavior guide, and Vercel deployment clarification
  • Test coverage: Comprehensive tests for the crossws adapter, pub/sub recovery, and connection ID lifecycle

Why?

Single-app deployments are the default for new VitNode projects. WebSocket support completes the feature set for this deployment model, enabling real-time features like live editing, notifications, and collaborative tools without requiring a separate API server. The implementation reuses VitNode's existing protocol and handlers, ensuring consistent behavior across deployment types.

The connection ID mechanism solves a critical issue: clients need to know when to restore state after a reconnect, since pub/sub has no replay and rooms are lost. This is especially important for features like live editing that depend on accurate room membership.

Realtime readiness checking prevents a race condition in serverless environments where a socket could open before Redis subscription completes, causing missed messages.

https://claude.ai/code/session_01SkDZQZ5eahXVkPDEnVB5P1

One deployment now serves the site, the HTTP API and WebSockets, Vercel
included. Nitro owns `/api/ws`; each handshake runs through the embedded
Hono API (Origin check, rate limiter, session), and the connection uses the
same `handleVitNodeWebSocket` protocol as the standalone API via the new
`@vitnode/core/ws/crossws.server` adapter.

- Reconnects restore state: `useVitNodeWebSocket` returns a `connectionId`
  that changes on every open, in leader and follower tabs; live editing
  rejoins its room on it.
- Redis: handshakes answer 503 until the instance is subscribed; a
  resubscribe after an outage closes local sockets with 1012 so clients
  reload missed state; publishes are kept alive with the request's
  `waitUntil`.
- `vitnode({ webSocket: true })` turns the browser connection on in an app
  that embeds the API; the single-app template ships the route.
- The WebSocket Origin check also accepts the handshake's own origin.
- Docs: WebSocket setup and reconnects pages; Vercel page no longer says
  WebSockets are unsupported.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01SkDZQZ5eahXVkPDEnVB5P1
@chatgpt-codex-connector

chatgpt-codex-connector Bot commented Oct 21, 2026 •

Copy link
Copy Markdown

Codex Review Summary

This comment shows the latest Codex review activity on this pull request.

Review Status Commit Review trigger
📝 Code Review ✅ Completed 2026-10-21T13:55:32.658835Z a47addc New commits
ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review" or "@codex security review".

Codex reacts with 👀 while any review is running, comments if it has suggestions, and reacts with 👍 once all reviews finish with no findings.

@github-actions

github-actions Bot commented Oct 21, 2026 •

Copy link
Copy Markdown

React Doctor found 7 issues in 6 files · 7 warnings · score 77 / 100 (Needs work) · vs canary

7 warnings

src/pages/blog-index.tsx

  • ⚠️ L108 React function has high control-flow complexity no-high-complexity-react-function

src/pages/boards-page.tsx

  • ⚠️ L88 Duplicated JSX structure duplicate-jsx-subtree
  • ⚠️ L92 Heading has no content heading-has-content

src/pages/discussion-edit-page.tsx

  • ⚠️ L171 Heading has no content heading-has-content

src/pages/discussion-new-page.tsx

  • ⚠️ L113 Heading has no content heading-has-content

src/pages/thread.tsx

  • ⚠️ L454 Heading has no content heading-has-content

src/ws/handle.ts

  • ⚠️ L45 await before an early-return guard async-defer-await
⚠️ Warning: .github/workflows/react-doctor.yml is configured incorrectly. See below to fix.

React Doctor compares against canary to report only the issues this pull request introduces. This run couldn't complete that comparison (usually a shallow CI checkout with no merge base), so it listed every issue in the changed files, including ones that already existed on canary.

Add fetch-depth: 0 to the actions/checkout step in .github/workflows/react-doctor.yml so the checkout includes the history React Doctor needs:

 jobs:
   react-doctor:
     steps:
       - uses: actions/checkout@v5
+        with:
+          fetch-depth: 0

       - uses: millionco/react-doctor@v2

To silence this warning, set silence-missing-baseline-warning: true on the React Doctor action.

Reviewed by React Doctor for commit a47addc. See inline comments for fixes.

@aXenDeveloper aXenDeveloper changed the title Add WebSocket support for single-app deployments feat: Add WebSocket support for single-app deployments Oct 21, 2026
@github-actions github-actions Bot added the 💡 Feature A new feature label Oct 21, 2026

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: f043d11d54

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment on lines +405 to +407
.catch(() => {
// Realtime stays single-instance; local clients still get their messages.
})

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P1 Badge Retry failed Redis subscriptions without awaiting reconnect

If the initial SUBSCRIBE command rejects while the Redis connection remains open, this catch only swallows the error and leaves subscribed false. The ready event denotes the connection becoming ready and is not re-emitted for an ordinary command failure (node-redis connection docs), so subscribe() is never called again; every subsequent /api/ws handshake then waits five seconds and returns 503 until the process or Redis connection restarts. Schedule another subscription attempt or deliberately reconnect after this failure.

Useful? React with 👍 / 👎.

@@ -37,6 +43,8 @@ const sendNotificationState = async (
userId: number,
): Promise<void> => {
const state = await getNotificationState(c.get("db"), userId);

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

React Doctor · react-doctor/async-defer-await (warning)

This await blocks the function before an early-return that doesn't use the awaited value, so the skip path waits for nothing. Move the await below the guard so it only runs when you need the data

Fix → Move the await below the early-return guard so the skip path stays fast and avoids unnecessary async work.

Docs

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: a47addc6b1

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

})
```

A web app pointed at that API with `VITNODE_API_URL` connects to it on its own; it needs no `webSocket` option and no Nitro route.

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Fix automatic connection for split-host APIs

This setup claim fails for a common production configuration such as web https://www.example.com/ and API https://api.example.com/: shouldConnectWebSocket() compares only pathname and port, so with both URLs using / and the default HTTPS port it returns false and the provider never opens the socket. Compare the origins/hostnames as well, or document the additional configuration required for split-host deployments.

Useful? React with 👍 / 👎.

@aXenDeveloper
aXenDeveloper merged commit 0681419 into canary Oct 21, 2026
7 checks passed
@aXenDeveloper
aXenDeveloper deleted the claude/websocket-single-app branch October 21, 2026 13:56
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

💡 Feature A new feature

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants