mirror of
https://github.com/openclaw/openclaw.git
synced 2026-05-13 02:22:55 +00:00
refactor(gateway): hard-break plugin wildcard http handlers
This commit is contained in:
@@ -13,7 +13,6 @@ export function createMockPluginRegistry(
|
||||
source: "test",
|
||||
})),
|
||||
tools: [],
|
||||
httpHandlers: [],
|
||||
httpRoutes: [],
|
||||
channelRegistrations: [],
|
||||
gatewayHandlers: {},
|
||||
|
||||
@@ -16,6 +16,8 @@ describe("registerPluginHttpRoute", () => {
|
||||
expect(registry.httpRoutes).toHaveLength(1);
|
||||
expect(registry.httpRoutes[0]?.path).toBe("/plugins/demo");
|
||||
expect(registry.httpRoutes[0]?.handler).toBe(handler);
|
||||
expect(registry.httpRoutes[0]?.auth).toBe("gateway");
|
||||
expect(registry.httpRoutes[0]?.match).toBe("exact");
|
||||
|
||||
unregister();
|
||||
expect(registry.httpRoutes).toHaveLength(0);
|
||||
@@ -64,7 +66,7 @@ describe("registerPluginHttpRoute", () => {
|
||||
expect(registry.httpRoutes).toHaveLength(1);
|
||||
expect(registry.httpRoutes[0]?.handler).toBe(secondHandler);
|
||||
expect(logs).toContain(
|
||||
'plugin: replacing stale webhook path /plugins/synology for account "default" (synology-chat)',
|
||||
'plugin: replacing stale webhook path /plugins/synology (exact) for account "default" (synology-chat)',
|
||||
);
|
||||
|
||||
// Old unregister must not remove the replacement route.
|
||||
|
||||
@@ -6,12 +6,14 @@ import { requireActivePluginRegistry } from "./runtime.js";
|
||||
export type PluginHttpRouteHandler = (
|
||||
req: IncomingMessage,
|
||||
res: ServerResponse,
|
||||
) => Promise<void> | void;
|
||||
) => Promise<boolean | void> | boolean | void;
|
||||
|
||||
export function registerPluginHttpRoute(params: {
|
||||
path?: string | null;
|
||||
fallbackPath?: string | null;
|
||||
handler: PluginHttpRouteHandler;
|
||||
auth?: PluginHttpRouteRegistration["auth"];
|
||||
match?: PluginHttpRouteRegistration["match"];
|
||||
pluginId?: string;
|
||||
source?: string;
|
||||
accountId?: string;
|
||||
@@ -29,16 +31,23 @@ export function registerPluginHttpRoute(params: {
|
||||
return () => {};
|
||||
}
|
||||
|
||||
const existingIndex = routes.findIndex((entry) => entry.path === normalizedPath);
|
||||
const routeMatch = params.match ?? "exact";
|
||||
const existingIndex = routes.findIndex(
|
||||
(entry) => entry.path === normalizedPath && entry.match === routeMatch,
|
||||
);
|
||||
if (existingIndex >= 0) {
|
||||
const pluginHint = params.pluginId ? ` (${params.pluginId})` : "";
|
||||
params.log?.(`plugin: replacing stale webhook path ${normalizedPath}${suffix}${pluginHint}`);
|
||||
params.log?.(
|
||||
`plugin: replacing stale webhook path ${normalizedPath} (${routeMatch})${suffix}${pluginHint}`,
|
||||
);
|
||||
routes.splice(existingIndex, 1);
|
||||
}
|
||||
|
||||
const entry: PluginHttpRouteRegistration = {
|
||||
path: normalizedPath,
|
||||
handler: params.handler,
|
||||
auth: params.auth ?? "gateway",
|
||||
match: routeMatch,
|
||||
pluginId: params.pluginId,
|
||||
source: params.source,
|
||||
};
|
||||
|
||||
@@ -518,13 +518,18 @@ describe("loadOpenClawPlugins", () => {
|
||||
expect(channel).toBeDefined();
|
||||
});
|
||||
|
||||
it("registers http handlers", () => {
|
||||
it("registers http routes with auth and match options", () => {
|
||||
useNoBundledPlugins();
|
||||
const plugin = writePlugin({
|
||||
id: "http-demo",
|
||||
filename: "http-demo.cjs",
|
||||
body: `module.exports = { id: "http-demo", register(api) {
|
||||
api.registerHttpHandler(async () => false);
|
||||
api.registerHttpRoute({
|
||||
path: "/webhook",
|
||||
auth: "plugin",
|
||||
match: "prefix",
|
||||
handler: async () => false
|
||||
});
|
||||
} };`,
|
||||
});
|
||||
|
||||
@@ -535,10 +540,13 @@ describe("loadOpenClawPlugins", () => {
|
||||
},
|
||||
});
|
||||
|
||||
const handler = registry.httpHandlers.find((entry) => entry.pluginId === "http-demo");
|
||||
expect(handler).toBeDefined();
|
||||
const route = registry.httpRoutes.find((entry) => entry.pluginId === "http-demo");
|
||||
expect(route).toBeDefined();
|
||||
expect(route?.path).toBe("/webhook");
|
||||
expect(route?.auth).toBe("plugin");
|
||||
expect(route?.match).toBe("prefix");
|
||||
const httpPlugin = registry.plugins.find((entry) => entry.id === "http-demo");
|
||||
expect(httpPlugin?.httpHandlers).toBe(1);
|
||||
expect(httpPlugin?.httpRoutes).toBe(1);
|
||||
});
|
||||
|
||||
it("registers http routes", () => {
|
||||
@@ -561,8 +569,10 @@ describe("loadOpenClawPlugins", () => {
|
||||
const route = registry.httpRoutes.find((entry) => entry.pluginId === "http-route-demo");
|
||||
expect(route).toBeDefined();
|
||||
expect(route?.path).toBe("/demo");
|
||||
expect(route?.auth).toBe("gateway");
|
||||
expect(route?.match).toBe("exact");
|
||||
const httpPlugin = registry.plugins.find((entry) => entry.id === "http-route-demo");
|
||||
expect(httpPlugin?.httpHandlers).toBe(1);
|
||||
expect(httpPlugin?.httpRoutes).toBe(1);
|
||||
});
|
||||
|
||||
it("respects explicit disable in config", () => {
|
||||
|
||||
@@ -176,7 +176,7 @@ function createPluginRecord(params: {
|
||||
cliCommands: [],
|
||||
services: [],
|
||||
commands: [],
|
||||
httpHandlers: 0,
|
||||
httpRoutes: 0,
|
||||
hookCount: 0,
|
||||
configSchema: params.configSchema,
|
||||
configUiHints: undefined,
|
||||
|
||||
@@ -17,8 +17,10 @@ import type {
|
||||
OpenClawPluginChannelRegistration,
|
||||
OpenClawPluginCliRegistrar,
|
||||
OpenClawPluginCommandDefinition,
|
||||
OpenClawPluginHttpHandler,
|
||||
OpenClawPluginHttpRouteAuth,
|
||||
OpenClawPluginHttpRouteMatch,
|
||||
OpenClawPluginHttpRouteHandler,
|
||||
OpenClawPluginHttpRouteParams,
|
||||
OpenClawPluginHookOptions,
|
||||
ProviderPlugin,
|
||||
OpenClawPluginService,
|
||||
@@ -49,16 +51,12 @@ export type PluginCliRegistration = {
|
||||
source: string;
|
||||
};
|
||||
|
||||
export type PluginHttpRegistration = {
|
||||
pluginId: string;
|
||||
handler: OpenClawPluginHttpHandler;
|
||||
source: string;
|
||||
};
|
||||
|
||||
export type PluginHttpRouteRegistration = {
|
||||
pluginId?: string;
|
||||
path: string;
|
||||
handler: OpenClawPluginHttpRouteHandler;
|
||||
auth: OpenClawPluginHttpRouteAuth;
|
||||
match: OpenClawPluginHttpRouteMatch;
|
||||
source?: string;
|
||||
};
|
||||
|
||||
@@ -114,7 +112,7 @@ export type PluginRecord = {
|
||||
cliCommands: string[];
|
||||
services: string[];
|
||||
commands: string[];
|
||||
httpHandlers: number;
|
||||
httpRoutes: number;
|
||||
hookCount: number;
|
||||
configSchema: boolean;
|
||||
configUiHints?: Record<string, PluginConfigUiHint>;
|
||||
@@ -129,7 +127,6 @@ export type PluginRegistry = {
|
||||
channels: PluginChannelRegistration[];
|
||||
providers: PluginProviderRegistration[];
|
||||
gatewayHandlers: GatewayRequestHandlers;
|
||||
httpHandlers: PluginHttpRegistration[];
|
||||
httpRoutes: PluginHttpRouteRegistration[];
|
||||
cliRegistrars: PluginCliRegistration[];
|
||||
services: PluginServiceRegistration[];
|
||||
@@ -152,7 +149,6 @@ export function createEmptyPluginRegistry(): PluginRegistry {
|
||||
channels: [],
|
||||
providers: [],
|
||||
gatewayHandlers: {},
|
||||
httpHandlers: [],
|
||||
httpRoutes: [],
|
||||
cliRegistrars: [],
|
||||
services: [],
|
||||
@@ -288,19 +284,7 @@ export function createPluginRegistry(registryParams: PluginRegistryParams) {
|
||||
record.gatewayMethods.push(trimmed);
|
||||
};
|
||||
|
||||
const registerHttpHandler = (record: PluginRecord, handler: OpenClawPluginHttpHandler) => {
|
||||
record.httpHandlers += 1;
|
||||
registry.httpHandlers.push({
|
||||
pluginId: record.id,
|
||||
handler,
|
||||
source: record.source,
|
||||
});
|
||||
};
|
||||
|
||||
const registerHttpRoute = (
|
||||
record: PluginRecord,
|
||||
params: { path: string; handler: OpenClawPluginHttpRouteHandler },
|
||||
) => {
|
||||
const registerHttpRoute = (record: PluginRecord, params: OpenClawPluginHttpRouteParams) => {
|
||||
const normalizedPath = normalizePluginHttpPath(params.path);
|
||||
if (!normalizedPath) {
|
||||
pushDiagnostic({
|
||||
@@ -311,20 +295,25 @@ export function createPluginRegistry(registryParams: PluginRegistryParams) {
|
||||
});
|
||||
return;
|
||||
}
|
||||
if (registry.httpRoutes.some((entry) => entry.path === normalizedPath)) {
|
||||
const match = params.match ?? "exact";
|
||||
if (
|
||||
registry.httpRoutes.some((entry) => entry.path === normalizedPath && entry.match === match)
|
||||
) {
|
||||
pushDiagnostic({
|
||||
level: "error",
|
||||
pluginId: record.id,
|
||||
source: record.source,
|
||||
message: `http route already registered: ${normalizedPath}`,
|
||||
message: `http route already registered: ${normalizedPath} (${match})`,
|
||||
});
|
||||
return;
|
||||
}
|
||||
record.httpHandlers += 1;
|
||||
record.httpRoutes += 1;
|
||||
registry.httpRoutes.push({
|
||||
pluginId: record.id,
|
||||
path: normalizedPath,
|
||||
handler: params.handler,
|
||||
auth: params.auth ?? "gateway",
|
||||
match,
|
||||
source: record.source,
|
||||
});
|
||||
};
|
||||
@@ -489,7 +478,6 @@ export function createPluginRegistry(registryParams: PluginRegistryParams) {
|
||||
registerTool: (tool, opts) => registerTool(record, tool, opts),
|
||||
registerHook: (events, handler, opts) =>
|
||||
registerHook(record, events, handler, opts, params.config),
|
||||
registerHttpHandler: (handler) => registerHttpHandler(record, handler),
|
||||
registerHttpRoute: (params) => registerHttpRoute(record, params),
|
||||
registerChannel: (registration) => registerChannel(record, registration),
|
||||
registerProvider: (provider) => registerProvider(record, provider),
|
||||
|
||||
@@ -194,15 +194,20 @@ export type OpenClawPluginCommandDefinition = {
|
||||
handler: PluginCommandHandler;
|
||||
};
|
||||
|
||||
export type OpenClawPluginHttpHandler = (
|
||||
req: IncomingMessage,
|
||||
res: ServerResponse,
|
||||
) => Promise<boolean> | boolean;
|
||||
export type OpenClawPluginHttpRouteAuth = "gateway" | "plugin";
|
||||
export type OpenClawPluginHttpRouteMatch = "exact" | "prefix";
|
||||
|
||||
export type OpenClawPluginHttpRouteHandler = (
|
||||
req: IncomingMessage,
|
||||
res: ServerResponse,
|
||||
) => Promise<void> | void;
|
||||
) => Promise<boolean | void> | boolean | void;
|
||||
|
||||
export type OpenClawPluginHttpRouteParams = {
|
||||
path: string;
|
||||
handler: OpenClawPluginHttpRouteHandler;
|
||||
auth?: OpenClawPluginHttpRouteAuth;
|
||||
match?: OpenClawPluginHttpRouteMatch;
|
||||
};
|
||||
|
||||
export type OpenClawPluginCliContext = {
|
||||
program: Command;
|
||||
@@ -265,8 +270,7 @@ export type OpenClawPluginApi = {
|
||||
handler: InternalHookHandler,
|
||||
opts?: OpenClawPluginHookOptions,
|
||||
) => void;
|
||||
registerHttpHandler: (handler: OpenClawPluginHttpHandler) => void;
|
||||
registerHttpRoute: (params: { path: string; handler: OpenClawPluginHttpRouteHandler }) => void;
|
||||
registerHttpRoute: (params: OpenClawPluginHttpRouteParams) => void;
|
||||
registerChannel: (registration: OpenClawPluginChannelRegistration | ChannelPlugin) => void;
|
||||
registerGatewayMethod: (method: string, handler: GatewayRequestHandler) => void;
|
||||
registerCli: (registrar: OpenClawPluginCliRegistrar, opts?: { commands?: string[] }) => void;
|
||||
|
||||
Reference in New Issue
Block a user