a plugin can serve a websocket
Bun's route table is fixed at serve() and WS_ROUTES is read once before it, so a plugin installed afterwards could never add a socket route. That was a hard ceiling, not a preference: /minesweeper had to live in the platform and plugins/desktop could not stream a display at all. The way out was already in the tree and had one consumer. dev-proxy serves a socket with no WS_ROUTES entry, through a WILDCARD route whose handler calls server.upgrade itself. Nothing has to become dynamic, because a plugin name is a path segment rather than a route — one wildcard declared at boot serves every plugin that will ever be installed. Authorisation is the PLUGIN's own capability, asked as if the socket were a GET against its HTTP prefix. That is the safety argument: there is no second permission to grant, forget, or drift, so granting a plugin grants its socket and revoking it closes it. It is also why plugin-ws is exempt from the boot totality check rather than carrying a registry entry — one entry covering every plugin's socket would mean granting one plugin granted them all. A target is a TCP address and never a URL, which keeps the relay ignorant of what it carries. That ignorance is the only reason one relay can serve an RFB display and a line-oriented sidecar. Loopback only and not configurable: a plugin naming an arbitrary host would be a request-forgery primitive handed to every plugin author. sidecarPort() is new because createSidecarProxy learned announced ports per-proxy and that state was unreachable from anywhere else — a second subscription would have been a second thing to keep in step. Verified: boots past the totality gate, and both an existing and a non-existent plugin refuse 401 before revealing which. NOT yet verified: that it relays a byte. Minesweeper is about to be the first caller, which is what proves it. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Officer-Session: 656f5c85-e49a-4f1f-b9f3-042a04b4d043
This commit is contained in:
+15
-1
@@ -30,6 +30,7 @@ import './servers/api/chat/opencode/sidecar-server'; // subscribe to the opencod
|
||||
import type { SidecarRegistration } from './servers/sidecar/registration-protocol';
|
||||
import { toShellUsername } from './servers/data-path';
|
||||
import { devProxyWebsocket, upgradeDevProxyWs } from './servers/api/dev-proxy/websocket';
|
||||
import { pluginWsWebsocket, upgradePluginWs } from './servers/api/plugin-ws/websocket';
|
||||
|
||||
// Build static file routes from public/
|
||||
// A snapshot of ./public, taken once at boot: one exact route per file, each holding a `Bun.file`
|
||||
@@ -156,6 +157,7 @@ async function handleSidecarQueueCommand(ws: ServerWebSocket<WSData>, msg: Recor
|
||||
const handlers: Record<string, any> = {
|
||||
terminal: terminalWebsocket,
|
||||
'dev-proxy': devProxyWebsocket,
|
||||
'plugin-ws': pluginWsWebsocket,
|
||||
chat: chatWebsocket,
|
||||
'chat-notify': chatNotifyWebsocket,
|
||||
'task-runner': taskRunnerWebsocket,
|
||||
@@ -195,7 +197,7 @@ const WS_ROUTES: Record<string, UpgradableProvider> = {
|
||||
// Every provider named by a served route, plus the two that upgrade through their own handlers rather than
|
||||
// `upgradeWs`: `sidecar` (loopback process-to-process, its own route below) and `dev-proxy` (upgraded from
|
||||
// inside the proxy router). Named explicitly so their absence from WS_ROUTES cannot read as an oversight.
|
||||
const SERVED_WS_PROVIDERS = [...new Set([...Object.values(WS_ROUTES), 'sidecar', 'dev-proxy'])];
|
||||
const SERVED_WS_PROVIDERS = [...new Set([...Object.values(WS_ROUTES), 'sidecar', 'dev-proxy', 'plugin-ws'])];
|
||||
|
||||
/*
|
||||
Both doors into the platform, checked against the permission registry before either opens.
|
||||
@@ -487,6 +489,18 @@ const server = serve({
|
||||
if (req.headers.get('upgrade') === 'websocket') return upgradeDevProxyWs(req, server);
|
||||
return honoServer.fetch(req, server);
|
||||
},
|
||||
/*
|
||||
Every plugin's socket, through one wildcard declared at boot.
|
||||
|
||||
This is the route that made a plugin websocket possible at all. `WS_ROUTES` is read once before
|
||||
`serve()`, so nothing installed later can add an entry to it — but a wildcard does not need to,
|
||||
because the plugin name is a path segment rather than a route. The same trick `dev-proxy` above has
|
||||
been using all along; it simply had no second consumer until now.
|
||||
*/
|
||||
'/api/plugin-ws/*': (req: Request, server: any) => {
|
||||
if (req.headers.get('upgrade') === 'websocket') return upgradePluginWs(req, server);
|
||||
return honoServer.fetch(req, server);
|
||||
},
|
||||
'/api/sidecar/register': (req: Request, server: any) => {
|
||||
const ok = server.upgrade(req, {
|
||||
data: { provider: 'sidecar', userId: 0, email: '', username: '' },
|
||||
|
||||
Reference in New Issue
Block a user