mirror of
https://github.com/siteboon/claudecodeui.git
synced 2026-03-11 17:07:40 +00:00
feat(plugins): add backend subprocess support via RPC bridge
Extend the plugin system so plugins can optionally declare a entry in their manifest. The host spawns a Node.js subprocess for that script, assigns it a random local port, and exposes an RPC proxy route () that the sandboxed iframe can call via postMessage — avoiding the need for the iframe to hold auth tokens. Changes: - Add plugin process manager to spawn/stop server subprocesses - Wire subprocess lifecycle into enable/disable and uninstall routes - Add RPC proxy route on the host server - Extend PluginsContext and PluginTabContent to handle ccui:rpc and ccui:rpc-response postMessage events - Add hello-world server.js as a reference subprocess implementation - Update manifest.json and README with server field documentation
This commit is contained in:
@@ -1,5 +1,6 @@
|
||||
import { useEffect, useRef } from 'react';
|
||||
import { useTheme } from '../../contexts/ThemeContext';
|
||||
import { authenticatedFetch } from '../../utils/api';
|
||||
import type { Project, ProjectSession } from '../../types/app';
|
||||
|
||||
type PluginTabContentProps = {
|
||||
@@ -82,6 +83,34 @@ export default function PluginTabContent({
|
||||
);
|
||||
break;
|
||||
}
|
||||
case 'ccui:rpc': {
|
||||
// Plugin is making an RPC call to its server subprocess.
|
||||
// We bridge this because the sandboxed iframe has no auth token.
|
||||
const { requestId, method, path: rpcPath, body } = event.data;
|
||||
if (!requestId || !rpcPath) break;
|
||||
|
||||
const cleanPath = String(rpcPath).replace(/^\//, '');
|
||||
const url = `/api/plugins/${encodeURIComponent(pluginName)}/rpc/${cleanPath}`;
|
||||
|
||||
authenticatedFetch(url, {
|
||||
method: method || 'GET',
|
||||
...(body ? { body: JSON.stringify(body) } : {}),
|
||||
})
|
||||
.then(async (res) => {
|
||||
const data = await res.json().catch(() => null);
|
||||
iframeRef.current?.contentWindow?.postMessage(
|
||||
{ type: 'ccui:rpc-response', requestId, status: res.status, data },
|
||||
'*',
|
||||
);
|
||||
})
|
||||
.catch((err) => {
|
||||
iframeRef.current?.contentWindow?.postMessage(
|
||||
{ type: 'ccui:rpc-response', requestId, status: 500, error: (err as Error).message },
|
||||
'*',
|
||||
);
|
||||
});
|
||||
break;
|
||||
}
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
@@ -12,8 +12,10 @@ export type Plugin = {
|
||||
type: 'iframe' | 'react';
|
||||
slot: 'tab';
|
||||
entry: string;
|
||||
server: string | null;
|
||||
permissions: string[];
|
||||
enabled: boolean;
|
||||
serverRunning: boolean;
|
||||
dirName: string;
|
||||
};
|
||||
|
||||
|
||||
Reference in New Issue
Block a user