new plugins system

This commit is contained in:
KingKDot
2026-04-15 20:16:12 -05:00
parent 7367923ac5
commit fbe43e18c0
6 changed files with 349 additions and 174 deletions
+1 -2
View File
@@ -39,7 +39,7 @@ import { isAuthorizedAgentRequest } from "./server/agent-auth";
import { generateBuildMutex, sanitizeMutex, sanitizeOutputName } from "./server/build-utils";
import { detectUploadOs, normalizeClientOs, type DeployOs } from "./server/deploy-utils";
import { CORS_HEADERS } from "./server/http-security";
import { mimeType, secureHeaders, securePluginHeaders } from "./server/http-utils";
import { mimeType, secureHeaders } from "./server/http-utils";
import { sanitizePluginId } from "./server/plugin-utils";
import { dispatchAutoScriptsForConnection } from "./server/auto-script-dispatch";
import { dispatchAutoDeploysForConnection } from "./server/auto-deploy-dispatch";
@@ -421,7 +421,6 @@ async function startServer() {
enqueuePluginEvent: notificationPluginHandlers.enqueuePluginEvent,
drainPluginUIEvents: notificationPluginHandlers.drainPluginUIEvents,
secureHeaders,
securePluginHeaders,
mimeType,
},
fileShare: {
+1 -1
View File
@@ -11,7 +11,7 @@ export function securePluginHeaders() {
return {
...SECURITY_HEADERS,
"X-Frame-Options": "SAMEORIGIN",
"Content-Security-Policy": "default-src 'self'; script-src 'self'; style-src 'self' 'unsafe-inline'; img-src 'self' data:; font-src 'self'; connect-src 'none'; object-src 'none'; base-uri 'self'; frame-ancestors 'self'",
"Content-Security-Policy": "default-src 'self'; script-src 'self' 'unsafe-inline'; style-src 'self' 'unsafe-inline'; img-src 'self' data:; font-src 'self'; connect-src 'self' wss: ws:; object-src 'none'; base-uri 'self'; frame-ancestors 'self'",
};
}
@@ -46,7 +46,6 @@ type PluginRouteDeps = {
enqueuePluginEvent: (clientId: string, pluginId: string, event: string, payload: any) => void;
drainPluginUIEvents: (clientId: string, pluginId: string) => Array<{ event: string; payload: any }>;
secureHeaders: (contentType?: string) => Record<string, string>;
securePluginHeaders: () => Record<string, string>;
mimeType: (path: string) => string;
};
@@ -715,7 +714,6 @@ export async function handlePluginRoutes(
const raw = await file.text();
const baseTag = `<base href="/plugins/${pluginId}/assets/" />`;
const bridgeTag = `<script src="/assets/plugin-bridge.js"></script>`;
let injected = raw;
const headMatch = raw.match(/<head[^>]*>/i);
@@ -723,16 +721,8 @@ export async function handlePluginRoutes(
injected = raw.replace(headMatch[0], `${headMatch[0]}\n ${baseTag}`);
}
if (injected.includes("</head>")) {
injected = injected.replace("</head>", ` ${bridgeTag}\n </head>`);
} else if (injected.includes("</body>")) {
injected = injected.replace("</body>", ` ${bridgeTag}\n</body>`);
} else {
injected = `${bridgeTag}\n${injected}`;
}
return new Response(injected, {
headers: { ...deps.securePluginHeaders(), "Content-Type": "text/html; charset=utf-8" },
headers: deps.secureHeaders("text/html; charset=utf-8"),
});
}
@@ -746,9 +736,33 @@ export async function handlePluginRoutes(
}
const clientId = url.searchParams.get("clientId") || "";
const bridgeToken = uuidv4();
const origin = url.origin;
const iframeSrc = `/plugins/${pluginId}/frame?clientId=${encodeURIComponent(clientId)}&token=${encodeURIComponent(bridgeToken)}&origin=${encodeURIComponent(origin)}`;
const htmlFile = path.join(deps.PLUGIN_ROOT, pluginId, "assets", `${pluginId}.html`);
const file = Bun.file(htmlFile);
let pluginBody = "";
let pluginHeadExtras = "";
if (await file.exists()) {
const raw = await file.text();
const bodyMatch = raw.match(/<body[^>]*>([\s\S]*?)<\/body>/i);
pluginBody = bodyMatch ? bodyMatch[1] : raw;
const headMatch = raw.match(/<head[^>]*>([\s\S]*?)<\/head>/i);
if (headMatch) {
const headContent = headMatch[1];
const linkTags = headContent.match(/<link[^>]*>/gi) || [];
const styleTags = headContent.match(/<style[\s\S]*?<\/style>/gi) || [];
pluginHeadExtras = [...linkTags, ...styleTags]
.map((tag) =>
tag.replace(/href="(?!https?:\/\/|\/)/g, `href="/plugins/${pluginId}/assets/`),
)
.join("\n ");
}
}
const jsFile = Bun.file(path.join(deps.PLUGIN_ROOT, pluginId, "assets", `${pluginId}.js`));
const hasPluginJs = await jsFile.exists();
const html = `<!doctype html>
<html lang="en">
@@ -771,27 +785,19 @@ export async function handlePluginRoutes(
/>
<link rel="stylesheet" href="/assets/main.css" />
<link rel="stylesheet" href="/assets/custom.css" />
${pluginHeadExtras}
</head>
<body class="min-h-screen bg-slate-950 text-slate-100">
<header id="top-nav"></header>
<main class="px-5 py-6">
<div class="max-w-6xl mx-auto">
<div class="rounded-2xl border border-slate-800 bg-slate-900/50 overflow-hidden">
<iframe
id="plugin-frame"
src="${iframeSrc}"
sandbox="allow-scripts allow-same-origin"
class="w-full h-[calc(100vh-220px)] bg-slate-950"
></iframe>
<div id="plugin-container" class="rounded-2xl border border-slate-800 bg-slate-900/50 overflow-hidden p-4">
${pluginBody}
</div>
</div>
</main>
<div
id="plugin-host"
data-bridge-token="${bridgeToken}"
></div>
<script type="module" src="/assets/nav.js"></script>
<script src="/assets/plugin-host.js"></script>
${hasPluginJs ? `<script src="/plugins/${pluginId}/assets/${pluginId}.js"></script>` : ""}
</body>
</html>`;