mirror of
https://github.com/Termix-SSH/Termix.git
synced 2026-10-29 06:35:24 +00:00
ctx.http.route now registers a real router on the /plugin-api/:pluginId dispatcher, replacing its stub 404. A plugin never touches the Express req/res: it gets a plain summary and returns a plain object, so sockets and session cookies stay on this side of the boundary. Authorization and cookie headers are not forwarded. Plugins load last in the start-up sequence, after the server is fully wired, and are terminated before the database on shutdown so none can outlive it. A plugin that fails to load cannot stop the backend. fix: correct the nginx plugin-api location regex The pattern was ^/plugin-api/(/.*)?$, which needs a double slash and so never matched /plugin-api/<id>/<route>. Every other block in the file uses ^/prefix(/.*)?$. Left alone, plugin routes would work in dev and 404 only behind Docker.
88 lines
2.9 KiB
TypeScript
88 lines
2.9 KiB
TypeScript
// Mounted at /plugin-api. Every installed plugin that exposes backend routes
|
|
// registers a sub-router here under its own plugin id, and this dispatcher
|
|
// forwards matching requests to it. The plugin runtime populates the map via
|
|
// registerPluginRouter when a plugin activates, and clears it on deactivate,
|
|
// so an inactive plugin falls back to the 404 below.
|
|
//
|
|
// WebSocket note: plugin WS traffic does NOT get its own port from the
|
|
// 30001-30006 range. It rides the existing WS servers (e.g. the terminal or
|
|
// tunnel WS servers) using a named-channel envelope, the same way other
|
|
// features multiplex over a shared connection. This is a settled decision,
|
|
// not a placeholder, so it should not need revisiting when plugin WS support
|
|
// is implemented.
|
|
|
|
import express, { type Request, type Response, type Router } from "express";
|
|
import { databaseLogger } from "../../utils/logger.js";
|
|
|
|
const router = express.Router();
|
|
|
|
const activePluginRouters = new Map<string, Router>();
|
|
|
|
/**
|
|
* Called by the plugin runtime when a plugin activates. Replacing an existing
|
|
* entry is normal: a crash-restart re-registers under the same id.
|
|
*/
|
|
export function registerPluginRouter(
|
|
pluginId: string,
|
|
pluginRouter: Router,
|
|
): void {
|
|
activePluginRouters.set(pluginId, pluginRouter);
|
|
databaseLogger.info("Registered plugin API router", {
|
|
operation: "plugin_api_register",
|
|
pluginId,
|
|
});
|
|
}
|
|
|
|
export function unregisterPluginRouter(pluginId: string): void {
|
|
if (!activePluginRouters.delete(pluginId)) return;
|
|
databaseLogger.info("Unregistered plugin API router", {
|
|
operation: "plugin_api_register",
|
|
pluginId,
|
|
});
|
|
}
|
|
|
|
export function getRegisteredPluginIds(): string[] {
|
|
return [...activePluginRouters.keys()];
|
|
}
|
|
|
|
/**
|
|
* @openapi
|
|
* /plugin-api/{pluginId}/{path}:
|
|
* get:
|
|
* summary: Dispatch a request to an installed plugin's backend router
|
|
* description: >
|
|
* Forwards the request to the sub-router registered by the plugin with
|
|
* the given id. Returns 404 if the plugin is not installed or not
|
|
* currently running. All HTTP methods are dispatched the same way.
|
|
* tags:
|
|
* - Plugins
|
|
* parameters:
|
|
* - in: path
|
|
* name: pluginId
|
|
* required: true
|
|
* schema:
|
|
* type: string
|
|
* responses:
|
|
* 200:
|
|
* description: Response from the plugin's router.
|
|
* 404:
|
|
* description: Plugin not installed or not running.
|
|
*/
|
|
router.use("/:pluginId", (req: Request, res: Response, next) => {
|
|
const pluginId = String(req.params.pluginId);
|
|
const pluginRouter = activePluginRouters.get(pluginId);
|
|
|
|
if (!pluginRouter) {
|
|
databaseLogger.warn("Plugin API request for unregistered plugin", {
|
|
operation: "plugin_api_dispatch",
|
|
pluginId,
|
|
});
|
|
res.status(404).json({ error: "Plugin not installed or not running" });
|
|
return;
|
|
}
|
|
|
|
pluginRouter(req, res, next);
|
|
});
|
|
|
|
export default router;
|