mirror of
https://github.com/Termix-SSH/Termix.git
synced 2026-10-29 22:55:24 +00:00
feat: ship the ssh terminal as a first-party in-process plugin
Adds a hardcoded process:transport-owner tier so the terminal can own its WebSocket server and ssh2 clients, which cannot cross the worker boundary.
This commit is contained in:
1 parent
e040b12045
commit
c94a453f1e
43 files changed
+2426
-38
No files matched your search
@@ -0,0 +1,121 @@
|
||||
/**
|
||||
* Worker plugins gained an optional deactivate() hook. It is best-effort by
|
||||
* design: a plugin that ignores it or hangs must not be able to block a
|
||||
* disable, it just loses its cleanup.
|
||||
*/
|
||||
|
||||
import { afterEach, describe, expect, it, vi } from "vitest";
|
||||
import fs from "node:fs";
|
||||
import path from "node:path";
|
||||
import { PluginLoader } from "../../plugins/loader.js";
|
||||
import { PluginBroker } from "../../plugins/broker.js";
|
||||
import { createFixturePlugin, type Fixture } from "./fixture-plugin.js";
|
||||
|
||||
vi.mock("../../utils/logger.js", () => ({
|
||||
pluginLogger: {
|
||||
debug: vi.fn(),
|
||||
info: vi.fn(),
|
||||
warn: vi.fn(),
|
||||
error: vi.fn(),
|
||||
success: vi.fn(),
|
||||
},
|
||||
}));
|
||||
|
||||
function buildLoader(broker: PluginBroker): PluginLoader {
|
||||
return new PluginLoader({
|
||||
onWorkerReady: (plugin, worker) => broker.attach(plugin, worker),
|
||||
onWorkerGone: (plugin) => broker.detach(plugin.id),
|
||||
onBeforeTerminate: async (plugin) => {
|
||||
const runtime = broker.get(plugin.id);
|
||||
if (runtime) await broker.requestDeactivate(runtime, 2000);
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
function newBroker(): PluginBroker {
|
||||
return new PluginBroker({
|
||||
listHosts: async () => [],
|
||||
resolveHost: async () => null,
|
||||
});
|
||||
}
|
||||
|
||||
describe("worker deactivate hook", () => {
|
||||
let fixture: Fixture | null = null;
|
||||
let loader: PluginLoader | null = null;
|
||||
|
||||
afterEach(async () => {
|
||||
await loader?.shutdown();
|
||||
loader = null;
|
||||
fixture?.cleanup();
|
||||
fixture = null;
|
||||
});
|
||||
|
||||
it("runs the plugin's deactivate() before the worker is terminated", async () => {
|
||||
fixture = createFixturePlugin();
|
||||
const marker = path.join(fixture.root, "marker.txt");
|
||||
|
||||
fs.writeFileSync(
|
||||
path.join(fixture.dir, "backend", "index.mjs"),
|
||||
`
|
||||
import fs from "node:fs";
|
||||
export async function activate() {}
|
||||
export async function deactivate() {
|
||||
fs.writeFileSync(${JSON.stringify(marker)}, "cleaned");
|
||||
}
|
||||
`,
|
||||
);
|
||||
|
||||
loader = buildLoader(newBroker());
|
||||
const plugin = await loader.load(fixture.dir);
|
||||
await loader.activate(plugin.id, "user-1");
|
||||
await loader.deactivate(plugin.id);
|
||||
|
||||
expect(plugin.state).toBe("stopped");
|
||||
expect(fs.readFileSync(marker, "utf8")).toBe("cleaned");
|
||||
});
|
||||
|
||||
it("still stops a plugin that exports no deactivate", async () => {
|
||||
fixture = createFixturePlugin();
|
||||
loader = buildLoader(newBroker());
|
||||
|
||||
const plugin = await loader.load(fixture.dir);
|
||||
await loader.activate(plugin.id, "user-1");
|
||||
await loader.deactivate(plugin.id);
|
||||
|
||||
expect(plugin.state).toBe("stopped");
|
||||
expect(plugin.worker).toBeNull();
|
||||
});
|
||||
|
||||
it("does not let a hanging deactivate block the disable", async () => {
|
||||
fixture = createFixturePlugin();
|
||||
fs.writeFileSync(
|
||||
path.join(fixture.dir, "backend", "index.mjs"),
|
||||
`
|
||||
export async function activate() {}
|
||||
export function deactivate() {
|
||||
return new Promise(() => {});
|
||||
}
|
||||
`,
|
||||
);
|
||||
|
||||
const broker = newBroker();
|
||||
loader = new PluginLoader({
|
||||
onWorkerReady: (plugin, worker) => broker.attach(plugin, worker),
|
||||
onWorkerGone: (plugin) => broker.detach(plugin.id),
|
||||
onBeforeTerminate: async (plugin) => {
|
||||
const runtime = broker.get(plugin.id);
|
||||
// Short timeout so the test does not wait out the real one.
|
||||
if (runtime) await broker.requestDeactivate(runtime, 150);
|
||||
},
|
||||
});
|
||||
|
||||
const plugin = await loader.load(fixture.dir);
|
||||
await loader.activate(plugin.id, "user-1");
|
||||
|
||||
const started = Date.now();
|
||||
await loader.deactivate(plugin.id);
|
||||
|
||||
expect(plugin.state).toBe("stopped");
|
||||
expect(Date.now() - started).toBeLessThan(2000);
|
||||
});
|
||||
});
|
||||
Reference in new issue
Block a user