Files
Netcatty/electron/plugins/pluginBridge.cjs
T
ryan-wong-coder 7a47187e37 feat(plugins): add isolated plugin host runtime (#2303)
* feat(plugins): add isolated plugin host runtime

* test(plugins): build runtime dependencies before tests

* fix(plugins): harden runtime lifecycle and recovery

* fix(plugins): harden runtime extension boundaries

* fix(plugins): enforce runtime integrity boundaries

* fix(plugins): await utility process exit

* fix(plugins): harden utility process containment

* fix(plugins): close runtime recovery gaps

* fix(plugins): harden runtime lifecycle boundaries
2026-07-19 08:22:17 +08:00

74 lines
2.7 KiB
JavaScript

"use strict";
const { isPluginDevelopmentEnabled } = require("./constants.cjs");
const CHANNELS = Object.freeze({
status: "netcatty:plugins:status",
list: "netcatty:plugins:list",
install: "netcatty:plugins:install",
setEnabled: "netcatty:plugins:set-enabled",
restart: "netcatty:plugins:restart",
uninstall: "netcatty:plugins:uninstall",
});
function createTrustedPluginBridgeSender(options = {}) {
const devServerOrigin = options.devServerUrl ? new URL(options.devServerUrl).origin : null;
return (event) => {
const senderUrl = event?.senderFrame?.url || event?.sender?.getURL?.() || "";
try {
const url = new URL(senderUrl);
if (url.protocol === "app:" && url.hostname === "netcatty") return true;
return Boolean(devServerOrigin && url.origin === devServerOrigin);
} catch {
return false;
}
};
}
function registerPluginBridge(ipcMain, options) {
const manager = options.manager;
const env = options.env ?? process.env;
const isTrustedSender = options.isTrustedSender;
const configured = isPluginDevelopmentEnabled(env)
&& Boolean(manager)
&& typeof manager.initialize === "function";
const resolveManager = async () => {
if (!configured) throw new Error("Plugin development runtime is disabled or unavailable");
try {
await manager.initialize();
} catch (cause) {
throw new Error("Plugin development runtime is disabled or unavailable", { cause });
}
return manager;
};
const handle = (channel, callback) => {
ipcMain.handle(channel, async (event, payload) => {
if (!isTrustedSender(event)) throw new Error("Untrusted plugin management sender");
const activeManager = await resolveManager();
return callback(activeManager, payload);
});
};
ipcMain.handle(CHANNELS.status, async (event) => {
if (!isTrustedSender(event)) throw new Error("Untrusted plugin management sender");
let available = false;
try {
await resolveManager();
available = true;
} catch {}
return { available, experimental: true };
});
handle(CHANNELS.list, async (activeManager) => activeManager.list());
handle(CHANNELS.install, async (activeManager, payload) => activeManager.install(
payload?.archivePath,
{ enable: payload?.enable === true },
));
handle(CHANNELS.setEnabled, async (activeManager, payload) => activeManager.setEnabled(
payload?.pluginId,
payload?.enabled === true,
));
handle(CHANNELS.restart, async (activeManager, payload) => activeManager.restart(payload?.pluginId));
handle(CHANNELS.uninstall, async (activeManager, payload) => activeManager.uninstall(payload?.pluginId));
}
module.exports = { CHANNELS, createTrustedPluginBridgeSender, registerPluginBridge };