[Init] Initial commit - NetMesh terminal manager
Some checks failed
build-packages / resolve bundled mosh-client (push) Has been cancelled
build-packages / resolve bundled et-client (push) Has been cancelled
build-packages / build-macos (push) Has been cancelled
build-packages / build-windows (push) Has been cancelled
build-packages / build-linux-x64 (push) Has been cancelled
build-packages / build-linux-arm64 (push) Has been cancelled
build-packages / release (push) Has been cancelled
build-packages / update Nix release metadata (push) Has been cancelled
build-packages / bump homebrew tap (push) Has been cancelled
test / lint-and-test (push) Has been cancelled
AI automation / Route event (push) Has been cancelled
AI automation / Hand reopened issue to maintainers (push) Has been cancelled
AI automation / Clean source issue state (push) Has been cancelled
AI automation / Reconcile handoffs (push) Has been cancelled
AI automation / Classify issue (push) Has been cancelled
AI automation / Claude Code smoke (push) Has been cancelled
AI automation / Review issue follow-up (push) Has been cancelled
AI automation / Publish issue follow-up (push) Has been cancelled
AI automation / Implement with Claude Code (push) Has been cancelled
AI automation / Publish implement PR (push) Has been cancelled
AI automation / Continue queued issue comments (push) Has been cancelled
AI automation / Codex review loop (push) Has been cancelled
AI automation / Publish Codex fix (push) Has been cancelled
AI automation / Clear Codex dispatch marker (push) Has been cancelled
AI automation / Own PR re-request Codex (push) Has been cancelled
AI automation / External PR re-request Codex (push) Has been cancelled
AI automation / Poll Codex reaction / retry (push) Has been cancelled
build-et-binaries / build-linux-x64 (push) Has been cancelled
build-et-binaries / build-linux-arm64 (push) Has been cancelled
build-et-binaries / build-macos-universal (push) Has been cancelled
build-et-binaries / build-windows-x64 (push) Has been cancelled
build-et-binaries / release (push) Has been cancelled

This commit is contained in:
2026-09-13 18:24:01 +08:00
commit 3c72efcb7f
3255 changed files with 907009 additions and 0 deletions

View File

@@ -0,0 +1,343 @@
"use strict";
const assert = require("node:assert/strict");
const { EventEmitter } = require("node:events");
const test = require("node:test");
const { PluginViewHost, normalizeBounds } = require("./pluginViewHost.cjs");
function fixture(options = {}) {
const ipcHandlers = new Map();
const senderOwners = new Map();
const sessions = [];
const views = [];
let nextContentsId = 10;
class FakeContents extends EventEmitter {
constructor() {
super();
this.id = nextContentsId++;
this.sent = [];
this.destroyed = false;
}
setWindowOpenHandler(handler) { this.windowOpenHandler = handler; }
async loadURL(url) {
this.loadedUrl = url;
if (options.loadURL) await options.loadURL(this);
}
send(channel, payload) { this.sent.push([channel, payload]); }
isDestroyed() { return this.destroyed; }
close() { this.destroyed = true; this.emit("destroyed"); }
}
class FakeWebContentsView {
constructor(optionsValue) {
this.options = optionsValue;
this.webContents = new FakeContents();
views.push(this);
}
setBounds(bounds) { this.bounds = bounds; }
setVisible(visible) { this.visible = visible; }
}
const electron = {
ipcMain: { handle(channel, handler) { ipcHandlers.set(channel, handler); } },
BrowserWindow: { fromWebContents(sender) { return senderOwners.get(sender) ?? null; } },
WebContentsView: FakeWebContentsView,
session: {
fromPartition(partition, sessionOptions) {
const pluginSession = new EventEmitter();
pluginSession.partition = partition;
pluginSession.options = sessionOptions;
pluginSession.permissions = {};
pluginSession.webRequest = {
onBeforeRequest(handler) { pluginSession.beforeRequest = handler; },
};
pluginSession.setProxy = options.setProxy ?? (async (proxy) => { pluginSession.proxy = proxy; });
pluginSession.setPermissionCheckHandler = (handler) => { pluginSession.permissions.check = handler; };
pluginSession.setPermissionRequestHandler = (handler) => { pluginSession.permissions.request = handler; };
pluginSession.setDevicePermissionHandler = (handler) => { pluginSession.permissions.device = handler; };
sessions.push(pluginSession);
return pluginSession;
},
},
};
const disposals = [];
const protocol = {
registerView() {
return {
token: "view-token",
url: "netcatty-plugin://view-token/package/view.html",
dispose() {
disposals.push("view");
if (options.disposeViewThrows) throw new Error("view cleanup failed");
},
};
},
registerSession() {
return { dispose() {
disposals.push("session");
if (options.disposeSessionThrows) throw new Error("session cleanup failed");
} };
},
};
let viewListener;
let changeListener;
let activationCurrent = true;
let activationChecks = 0;
const contributionService = {
runtimeSupervisor: { async notify(...args) { return args; } },
onDidPostViewMessage(listener) { viewListener = listener; return { dispose() {} }; },
onDidChange(listener) { changeListener = listener; return { dispose() {} }; },
async activateView() {
return {
plugin: { id: "com.example.view", activeVersion: "1.0.0" },
view: { id: "com.example.view.panel", entry: "view.html", location: "aside" },
identity: { runtimeId: "runtime-view-1", pluginVersion: "1.0.0" },
};
},
assertViewActivationCurrent(expected) {
activationChecks += 1;
if (!activationCurrent) throw new Error("Plugin view ownership changed while opening");
assert.deepEqual(expected, {
pluginId: "com.example.view",
pluginVersion: "1.0.0",
viewId: "com.example.view.panel",
runtimeId: "runtime-view-1",
});
return expected;
},
async executeCommand(command, args, invocation) { return { command, args, invocation }; },
};
const state = new Map();
const host = new PluginViewHost({
electron,
protocol,
packageStore: {
async preparePackageRoot() {
if (options.preparePackageRoot) await options.preparePackageRoot();
return "/package";
},
},
database: {
getViewState(pluginId, viewId, scopeId) { return state.get(`${pluginId}:${viewId}:${scopeId}`); },
setViewState(pluginId, viewId, scopeId, value) { state.set(`${pluginId}:${viewId}:${scopeId}`, value); },
},
contributionService,
preloadPath: "/runtime/viewPreload.cjs",
});
function createOwner() {
const owner = new EventEmitter();
owner.children = [];
owner.isDestroyed = () => false;
owner.contentView = {
addChildView(view) { owner.children.push(view); },
removeChildView(view) { owner.children = owner.children.filter((entry) => entry !== view); },
};
const sender = {};
senderOwners.set(sender, owner);
return { owner, sender };
}
return {
changeListener: () => changeListener,
contributionService,
createOwner,
disposals,
host,
ipcHandlers,
sessions,
activationChecks: () => activationChecks,
setActivationCurrent(value) { activationCurrent = value; },
viewListener: () => viewListener,
views,
};
}
test("plugin view bounds reject unsafe or oversized geometry", () => {
assert.deepEqual(normalizeBounds({ x: 1, y: 2, width: 640, height: 480 }), { x: 1, y: 2, width: 640, height: 480 });
assert.throws(() => normalizeBounds({ x: 0.5, y: 0, width: 1, height: 1 }), /safe integers/u);
assert.throws(() => normalizeBounds({ x: 0, y: 0, width: 20_000, height: 1 }), /invalid/u);
});
test("custom views use an ephemeral sandbox and deny direct browser capabilities", async () => {
const value = fixture();
const { owner, sender } = value.createOwner();
const opened = await value.host.open({
viewId: "com.example.view.panel",
scopeId: "window-1",
instanceId: "view-1",
bounds: { x: 5, y: 6, width: 700, height: 500 },
}, sender);
assert.deepEqual(opened, { instanceId: "view-1" });
const contentsView = value.views[0];
assert.deepEqual(contentsView.options.webPreferences, {
sandbox: true,
nodeIntegration: false,
nodeIntegrationInSubFrames: false,
nodeIntegrationInWorker: false,
contextIsolation: true,
webSecurity: true,
devTools: false,
disableDialogs: true,
navigateOnDragDrop: false,
session: value.sessions[0],
preload: "/runtime/viewPreload.cjs",
});
assert.match(value.sessions[0].partition, /^netcatty-plugin-view-/u);
assert.deepEqual(value.sessions[0].options, { cache: false });
assert.equal(value.sessions[0].permissions.check(), false);
assert.equal(value.sessions[0].permissions.device(), false);
let permissionDecision;
value.sessions[0].permissions.request(null, "camera", (decision) => { permissionDecision = decision; });
assert.equal(permissionDecision, false);
let allowed;
value.sessions[0].beforeRequest({ url: "netcatty-plugin://view-token/package/view.js" }, (decision) => { allowed = decision; });
assert.deepEqual(allowed, { cancel: false });
value.sessions[0].beforeRequest({ url: "https://example.com/track" }, (decision) => { allowed = decision; });
assert.deepEqual(allowed, { cancel: true });
assert.deepEqual(contentsView.webContents.windowOpenHandler(), { action: "deny" });
const navigation = { prevented: false, preventDefault() { this.prevented = true; } };
contentsView.webContents.emit("will-navigate", navigation, "https://example.com");
assert.equal(navigation.prevented, true);
assert.deepEqual(contentsView.bounds, { x: 5, y: 6, width: 700, height: 500 });
assert.equal(owner.children.length, 1);
assert.equal(value.activationChecks(), 2);
const environment = {
locale: "zh-CN",
theme: "dark",
reducedMotion: true,
highContrast: false,
themeTokens: { "--background": "220 10% 10%" },
};
value.host.setEnvironment(environment);
assert.deepEqual(await value.ipcHandlers.get("netcatty-plugin-view:get-environment")(
{ sender: contentsView.webContents },
), environment);
value.host.setVisible("view-1", false, sender);
assert.equal(contentsView.visible, false);
assert.deepEqual(await value.ipcHandlers.get("netcatty-plugin-view:execute-command")(
{ sender: contentsView.webContents },
{ command: "com.example.view.run", args: { selected: 1 } },
), {
command: "com.example.view.run",
args: { selected: 1 },
invocation: {
source: "view",
callerPluginId: "com.example.view",
context: { "netcatty.view": "com.example.view.panel" },
},
});
const other = value.createOwner();
assert.throws(() => value.host.setVisible("view-1", true, other.sender), /another window/u);
assert.throws(() => value.host.setBounds("view-1", { x: 0, y: 0, width: 1, height: 1 }, other.sender), /another window/u);
await assert.rejects(value.host.close("view-1", other.sender), /another window/u);
owner.emit("closed");
await new Promise((resolve) => setImmediate(resolve));
assert.equal(owner.children.length, 0);
assert.deepEqual(value.disposals.sort(), ["session", "view"]);
});
test("view setup failures dispose protocol registrations before exposing an instance", async () => {
const value = fixture({ setProxy: async () => { throw new Error("proxy unavailable"); } });
const { sender } = value.createOwner();
await assert.rejects(value.host.open({
viewId: "com.example.view.panel",
scopeId: "window-1",
bounds: { x: 0, y: 0, width: 10, height: 10 },
}, sender), /proxy unavailable/u);
assert.deepEqual(value.disposals.sort(), ["session", "view"]);
assert.equal(value.views.length, 0);
});
test("terminal runtime states close every open view owned by the plugin", async () => {
for (const reason of ["runtime-stopped", "runtime-error", "runtime-quarantined"]) {
const value = fixture();
const { owner, sender } = value.createOwner();
await value.host.open({
viewId: "com.example.view.panel",
scopeId: "window-1",
instanceId: `view-${reason}`,
bounds: { x: 0, y: 0, width: 320, height: 240 },
}, sender);
assert.equal(owner.children.length, 1);
value.changeListener()({ reason, pluginId: "com.example.view" });
await new Promise((resolve) => setImmediate(resolve));
assert.equal(owner.children.length, 0);
assert.deepEqual(value.disposals.sort(), ["session", "view"]);
}
});
test("plugin replacement during package preparation cannot publish a stale view", async () => {
let releasePackage;
const packageReady = new Promise((resolve) => { releasePackage = resolve; });
const value = fixture({ preparePackageRoot: () => packageReady });
const { owner, sender } = value.createOwner();
const opening = value.host.open({
viewId: "com.example.view.panel",
scopeId: "window-1",
instanceId: "view-replaced",
bounds: { x: 0, y: 0, width: 320, height: 240 },
}, sender);
value.setActivationCurrent(false);
releasePackage();
await assert.rejects(opening, /ownership changed/u);
assert.equal(owner.children.length, 0);
assert.equal(value.views.length, 0);
});
test("runtime shutdown cancels an in-flight view before it attaches and publishes a close event", async () => {
let releaseLoad;
const loadReady = new Promise((resolve) => { releaseLoad = resolve; });
const value = fixture({ loadURL: () => loadReady });
const { owner, sender } = value.createOwner();
const closed = [];
value.host.onDidClose((event) => closed.push(event));
const opening = value.host.open({
viewId: "com.example.view.panel",
scopeId: "window-1",
instanceId: "view-opening",
bounds: { x: 0, y: 0, width: 320, height: 240 },
}, sender);
while (value.views.length === 0) await new Promise((resolve) => setImmediate(resolve));
assert.equal(owner.children.length, 0);
value.changeListener()({ reason: "runtime-stopped", pluginId: "com.example.view" });
await new Promise((resolve) => setImmediate(resolve));
releaseLoad();
await assert.rejects(opening, /ownership changed/u);
assert.equal(owner.children.length, 0);
assert.equal(value.views[0].webContents.destroyed, true);
assert.deepEqual(closed, [{
instanceId: "view-opening",
pluginId: "com.example.view",
viewId: "com.example.view.panel",
reason: "runtime-stopped",
}]);
});
test("view cleanup failures cannot suppress owner removal or the renderer close signal", async () => {
const value = fixture({ disposeViewThrows: true, disposeSessionThrows: true });
const { owner, sender } = value.createOwner();
const closed = [];
value.host.onDidClose((event) => closed.push(event));
await value.host.open({
viewId: "com.example.view.panel",
scopeId: "window-1",
instanceId: "view-cleanup-failure",
bounds: { x: 0, y: 0, width: 320, height: 240 },
}, sender);
await value.host.close("view-cleanup-failure", sender);
assert.equal(owner.children.length, 0);
assert.equal(value.views[0].webContents.destroyed, true);
assert.deepEqual(value.disposals, ["view", "session"]);
assert.deepEqual(closed, [{
instanceId: "view-cleanup-failure",
pluginId: "com.example.view",
viewId: "com.example.view.panel",
reason: "renderer-requested",
}]);
});