Skip to content

feat: add WebSocket proxy for plugin backends#553

Merged
blackmammoth merged 3 commits into
mainfrom
feat/plugin-websockets
Mar 18, 2026
Merged

feat: add WebSocket proxy for plugin backends#553
blackmammoth merged 3 commits into
mainfrom
feat/plugin-websockets

Conversation

@viper151
Copy link
Copy Markdown
Contributor

@viper151 viper151 commented Mar 17, 2026

Adds /plugin-ws/:name route that proxies authenticated WebSocket connections to plugin server subprocesses, enabling real-time bidirectional communication for plugins like web-terminal.

Summary by CodeRabbit

  • New Features

    • Proxied authenticated WebSocket connections to plugin internal servers, routing requests under /plugin-ws/.
  • Bug Fixes

    • Plugin assets served with no-cache headers to ensure fresh content delivery.
  • Documentation

    • Added a new plugin entry "Web Terminal" to the Plugins list.

Adds /plugin-ws/:name route that proxies authenticated WebSocket
connections to plugin server subprocesses, enabling real-time
bidirectional communication for plugins like web-terminal.
@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented Mar 17, 2026

📝 Walkthrough

Walkthrough

Adds a WebSocket proxy in the server to route authenticated client WS connections to plugin internal WS servers, applies no-cache headers to served plugin assets, and updates plugin-listing content and a comment about per-plugin user-configured secrets.

Changes

Cohort / File(s) Summary
Plugin WebSocket Proxy
server/index.js
Imported getPluginPort and added an internal handlePluginWsProxy(clientWs, pathname) that validates plugin names, obtains plugin ports, establishes an upstream WS to ws://127.0.0.1:<port>/ws, and relays messages/errors/close events. Integrated routing for /plugin-ws/ paths.
Plugin Asset & RPC headers
server/routes/plugins.js
Added no-cache response headers (Cache-Control: no-store, no-cache, must-revalidate, Pragma: no-cache, Expires: 0) for plugin assets. Updated comment wording to "per-plugin user-configured secrets" in RPC header injection logic.
Docs: Plugins list
README.md
Added a new "Web Terminal" entry to the Plugins table (content addition).

Sequence Diagram

sequenceDiagram
    actor Client
    participant Server
    participant PluginManager
    participant Plugin

    Client->>Server: WS connect to /plugin-ws/[plugin-name]
    Server->>Server: Validate plugin name from pathname
    Server->>PluginManager: getPluginPort(plugin-name)
    PluginManager-->>Server: return port
    Server->>Plugin: Open upstream WS to ws://127.0.0.1:[port]/ws
    Plugin-->>Server: Upstream WS open

    rect rgba(100, 150, 200, 0.5)
    Client->>Server: send message
    Server->>Plugin: forward message
    Plugin-->>Server: send response
    Server-->>Client: forward response
    end

    Client->>Server: close
    Server->>Plugin: close upstream
    Plugin-->>Server: upstream closed
    Server-->>Client: closed
Loading

Poem

🐰
Tiny paws tap the server floor,
Tunnels open to plugin doors,
I ferry messages light and fleet,
No-cache bits and a fresh plugin sheet,
Hops of joy—connections complete.

🚥 Pre-merge checks | ✅ 3
✅ Passed checks (3 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title 'feat: add WebSocket proxy for plugin backends' directly and clearly describes the main change: adding WebSocket proxy functionality for plugin backends.
Docstring Coverage ✅ Passed Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%.

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

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat/plugin-websockets
📝 Coding Plan
  • Generate coding plan for human review comments

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.

Tip

You can disable the changed files summary in the walkthrough.

Disable the reviews.changed_files_summary setting to disable the changed files summary in the walkthrough.

Copy link
Copy Markdown
Contributor

@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 `@server/index.js`:
- Around line 1416-1428: The client messages can be dropped while the upstream
WebSocket (upstream) is still connecting; implement a small buffer/queue to
store messages received in clientWs.on('message') until upstream emits 'open',
then flush the queue by sending each buffered message via upstream.send; ensure
you still check upstream.readyState before sending and clear the buffer on
upstream 'close'/'error' (or forward an error to clientWs) to avoid memory
leaks; reference the upstream and clientWs event handlers and the
pluginName/port logging when adding the buffer and flush logic.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro

Run ID: b61a80f4-e953-489a-96b9-32f835e9bfda

📥 Commits

Reviewing files that changed from the base of the PR and between 4de8b78 and bde554f.

⛔ Files ignored due to path filters (1)
  • package-lock.json is excluded by !**/package-lock.json
📒 Files selected for processing (2)
  • server/index.js
  • server/routes/plugins.js

Comment thread server/index.js
Comment on lines +1416 to +1428
const upstream = new WebSocket(`ws://127.0.0.1:${port}/ws`);

upstream.on('open', () => {
console.log(`[Plugins] WS proxy connected to "${pluginName}" on port ${port}`);
});

// Relay messages bidirectionally
upstream.on('message', (data) => {
if (clientWs.readyState === WebSocket.OPEN) clientWs.send(data);
});
clientWs.on('message', (data) => {
if (upstream.readyState === WebSocket.OPEN) upstream.send(data);
});
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Potential message loss during upstream connection phase.

Messages sent by the client before the upstream WebSocket reaches the OPEN state will be silently dropped due to the readyState check on line 1427. This could cause issues if the client immediately sends messages after connecting.

Consider buffering client messages until the upstream connection is established:

🔧 Proposed fix to buffer messages until upstream is ready
 function handlePluginWsProxy(clientWs, pathname) {
     const pluginName = pathname.replace('/plugin-ws/', '');
     if (!pluginName || /[^a-zA-Z0-9_-]/.test(pluginName)) {
         clientWs.close(4400, 'Invalid plugin name');
         return;
     }

     const port = getPluginPort(pluginName);
     if (!port) {
         clientWs.close(4404, 'Plugin not running');
         return;
     }

     const upstream = new WebSocket(`ws://127.0.0.1:${port}/ws`);
+    const pendingMessages = [];

     upstream.on('open', () => {
         console.log(`[Plugins] WS proxy connected to "${pluginName}" on port ${port}`);
+        // Flush any messages that arrived before upstream was ready
+        for (const msg of pendingMessages) {
+            upstream.send(msg);
+        }
+        pendingMessages.length = 0;
     });

     // Relay messages bidirectionally
     upstream.on('message', (data) => {
         if (clientWs.readyState === WebSocket.OPEN) clientWs.send(data);
     });
     clientWs.on('message', (data) => {
-        if (upstream.readyState === WebSocket.OPEN) upstream.send(data);
+        if (upstream.readyState === WebSocket.OPEN) {
+            upstream.send(data);
+        } else if (upstream.readyState === WebSocket.CONNECTING) {
+            pendingMessages.push(data);
+        }
+        // If upstream is CLOSING or CLOSED, drop the message (connection is terminating)
     });
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@server/index.js` around lines 1416 - 1428, The client messages can be dropped
while the upstream WebSocket (upstream) is still connecting; implement a small
buffer/queue to store messages received in clientWs.on('message') until upstream
emits 'open', then flush the queue by sending each buffered message via
upstream.send; ensure you still check upstream.readyState before sending and
clear the buffer on upstream 'close'/'error' (or forward an error to clientWs)
to avoid memory leaks; reference the upstream and clientWs event handlers and
the pluginName/port logging when adding the buffer and flush logic.

@viper151 viper151 requested a review from blackmammoth March 17, 2026 11:27
Copy link
Copy Markdown
Contributor

@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 `@README.md`:
- Line 156: The table row for "Web Terminal" is missing a space before the
closing pipe causing inconsistent table alignment; edit the Markdown table row
containing the text "**[Web
Terminal](https://github.com/cloudcli-ai/cloudcli-plugin-terminal)** | Full
xterm.js terminal with multi-tab support|" and add a single space before the
final pipe so it matches other rows (i.e., change the trailing "...support|" to
"...support |").

ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro

Run ID: 0cfd5c6c-eeeb-401b-8d02-4cb0080a4c1e

📥 Commits

Reviewing files that changed from the base of the PR and between bde554f and c4f0d46.

📒 Files selected for processing (1)
  • README.md

Comment thread README.md
| Plugin | Description |
|---|---|
| **[Project Stats](https://github.com/cloudcli-ai/cloudcli-plugin-starter)** | Shows file counts, lines of code, file-type breakdown, largest files, and recently modified files for your current project |
| **[Web Terminal](https://github.com/cloudcli-ai/cloudcli-plugin-terminal)** | Full xterm.js terminal with multi-tab support|
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Description: Verify the Web Terminal plugin repository exists and is accessible

# Check if the repository exists
gh repo view cloudcli-ai/cloudcli-plugin-terminal --json name,description,url

# Check the README for feature description
gh repo view cloudcli-ai/cloudcli-plugin-terminal --web

Repository: siteboon/claudecodeui

Length of output: 262


🌐 Web query:

cloudcli-ai cloudcli-plugin-terminal GitHub repository

💡 Result:

The GitHub repository for cloudcli-ai / cloudcli-plugin-terminal is:

  • cloudcli-ai/cloudcli-plugin-terminal (CloudCLI Plugin — Web Terminal) [1]

CloudCLI GitHub org (shows the repo in its repository list): [2]

References:
[1] https://github.com/cloudcli-ai/cloudcli-plugin-terminal
[2] https://github.com/cloudcli-ai


🏁 Script executed:

#!/bin/bash
# Try to access the repository URL via HTTP to verify it exists
curl -s -o /dev/null -w "HTTP Status: %{http_code}\n" "https://github.com/cloudcli-ai/cloudcli-plugin-terminal"

# Also check the organization page
curl -s -o /dev/null -w "HTTP Status: %{http_code}\n" "https://github.com/cloudcli-ai"

Repository: siteboon/claudecodeui

Length of output: 98


Add space before the closing pipe for consistent table formatting.

The plugin entry follows the existing format correctly, and the repository is publicly accessible. However, there's a minor spacing inconsistency: add a space before the closing pipe to match the formatting of other rows in the table.

Formatting fix
-| **[Web Terminal](https://github.com/cloudcli-ai/cloudcli-plugin-terminal)** | Full xterm.js terminal with multi-tab support|
+| **[Web Terminal](https://github.com/cloudcli-ai/cloudcli-plugin-terminal)** | Full xterm.js terminal with multi-tab support |
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
| **[Web Terminal](https://github.com/cloudcli-ai/cloudcli-plugin-terminal)** | Full xterm.js terminal with multi-tab support|
| **[Web Terminal](https://github.com/cloudcli-ai/cloudcli-plugin-terminal)** | Full xterm.js terminal with multi-tab support |
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@README.md` at line 156, The table row for "Web Terminal" is missing a space
before the closing pipe causing inconsistent table alignment; edit the Markdown
table row containing the text "**[Web
Terminal](https://github.com/cloudcli-ai/cloudcli-plugin-terminal)** | Full
xterm.js terminal with multi-tab support|" and add a single space before the
final pipe so it matches other rows (i.e., change the trailing "...support|" to
"...support |").

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.

2 participants