feat: add WebSocket proxy for plugin backends#553
Conversation
Adds /plugin-ws/:name route that proxies authenticated WebSocket connections to plugin server subprocesses, enabling real-time bidirectional communication for plugins like web-terminal.
📝 WalkthroughWalkthroughAdds 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
Sequence DiagramsequenceDiagram
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
Poem
🚥 Pre-merge checks | ✅ 3✅ Passed checks (3 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches🧪 Generate unit tests (beta)
📝 Coding Plan
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. Comment Tip You can disable the changed files summary in the walkthrough.Disable the |
There was a problem hiding this comment.
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
⛔ Files ignored due to path filters (1)
package-lock.jsonis excluded by!**/package-lock.json
📒 Files selected for processing (2)
server/index.jsserver/routes/plugins.js
| 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); | ||
| }); |
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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 |").
| | 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| |
There was a problem hiding this comment.
🧩 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 --webRepository: 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.
| | **[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 |").
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
Bug Fixes
Documentation