Files
Termix/src/backend/plugins/protocol.ts
T
LukeGus c58ccbfc3f fix: harden plugin auth and add per-request identity for ctx calls
Require auth on /plugin-api, gate plugin enable/grant/revoke routes
behind admin.plugins.manage, and thread the calling user through
worker ctx.hosts/ctx.ssh calls instead of always using the install
owner. Also adds the plugin permissions grant/revoke UI.
2026-09-21 14:32:59 -05:00

74 lines
2.4 KiB
TypeScript

/**
* Wire format between a plugin worker and the main-thread broker.
*
* Everything crossing this boundary must be structured-cloneable. That is not
* just a serialisation detail: it is the reason a plugin cannot be handed a db
* handle, an ssh2 Client or a live socket by accident. Live objects stay in the
* main thread and the worker refers to them by opaque handle strings.
*/
export interface PluginRequest {
/** Correlates a reply to its request. Worker-generated, monotonic. */
id: number;
/** Dotted ctx path, e.g. "hosts.get" or "storage.set". */
method: string;
args: unknown[];
/**
* The user whose HTTP request is currently executing inside the worker, if
* any. Set by the worker's own call() from its AsyncLocalStorage context,
* never by plugin code -- no ctx method accepts a userId argument, so a
* plugin has no way to set or forge this itself. Absent for a ctx call made
* from a timer or event listener, which has no inbound request to derive an
* actor from; the broker falls back to the plugin's ownerUserId then.
*/
callerUserId?: string;
}
export interface PluginResponseOk {
id: number;
ok: true;
value: unknown;
}
export interface PluginResponseErr {
id: number;
ok: false;
error: { message: string; code?: string };
}
export type PluginResponse = PluginResponseOk | PluginResponseErr;
/** Main thread -> worker, pushed rather than requested. */
export interface PluginPush {
kind: "event" | "http" | "timer" | "deactivate";
/** Event topic, route key, or timer id depending on kind. */
key: string;
payload: unknown;
/** Set when the push expects a reply (an HTTP route invocation). */
replyTo?: number;
}
/** First message the worker receives, before any plugin code runs. */
export interface PluginBootstrapData {
pluginId: string;
pluginDir: string;
entryPath: string;
manifest: unknown;
}
export function isPluginRequest(value: unknown): value is PluginRequest {
if (!value || typeof value !== "object") return false;
const v = value as Record<string, unknown>;
return (
typeof v.id === "number" &&
typeof v.method === "string" &&
Array.isArray(v.args)
);
}
export function isPluginResponse(value: unknown): value is PluginResponse {
if (!value || typeof value !== "object") return false;
const v = value as Record<string, unknown>;
return typeof v.id === "number" && typeof v.ok === "boolean";
}