fix(msteams): replace deprecated HttpPlugin with httpServerAdapter

Migrate from the deprecated `plugins: [noOpHttpPlugin]` pattern to the
SDK's public `httpServerAdapter` option. This eliminates the
"[DEPRECATED] HttpPlugin in plugins array" warning that fires every
~60 seconds in the gateway error log.

The old NoOpHttpPlugin was a ~40-line class that used fragile
reflect-metadata hacks against internal SDK decorator paths. The
replacement is a simple 3-line IHttpServerAdapter stub with an empty
registerRoute() — the only method the interface requires.

Fixes: https://github.com/openclaw/openclaw/issues/60732
See also: https://github.com/openclaw/openclaw/issues/55161

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
RamuKaka
2026-04-04 07:54:51 -07:00
committed by Brad Groux
parent 37301cbc3b
commit 4eb0034738
2 changed files with 15 additions and 62 deletions

View File

@@ -81,9 +81,8 @@ function createSdkStub(): MSTeamsTeamsSdk {
describe("createMSTeamsApp", () => {
it("does not crash with express 5 path-to-regexp (#55161)", async () => {
// Regression test for: https://github.com/openclaw/openclaw/issues/55161
// The default HttpPlugin in @microsoft/teams.apps uses `express().use('/api*', ...)`
// which throws in express 5 (path-to-regexp v8+). createMSTeamsApp injects a no-op
// HTTP plugin stub to prevent the SDK from creating the default HttpPlugin.
// createMSTeamsApp passes a no-op httpServerAdapter to prevent the SDK from
// creating its default HttpPlugin (which registers `/api*` — invalid in Express 5).
const { App } = await import("@microsoft/teams.apps");
const { Client } = await import("@microsoft/teams.api");
const sdk: MSTeamsTeamsSdk = { App, Client };

View File

@@ -1,3 +1,4 @@
import type { IHttpServerAdapter } from "@microsoft/teams.apps/dist/http/index.js";
import { formatUnknownError } from "./errors.js";
import type { MSTeamsAdapter } from "./messenger.js";
import type { MSTeamsCredentials } from "./token.js";
@@ -55,65 +56,22 @@ export async function loadMSTeamsSdk(): Promise<MSTeamsTeamsSdk> {
}
/**
* Create a lightweight no-op HTTP plugin stub that satisfies the Teams SDK's
* plugin discovery by name ("http") but does NOT spin up an Express server.
*
* The default HttpPlugin in @microsoft/teams.apps registers an Express
* middleware with the pattern `/api*`. When the host application (OpenClaw)
* uses Express 5 — which depends on path-to-regexp v8 — that pattern is
* invalid and throws:
*
* Missing parameter name at index 5: /api*
* Create a no-op HTTP server adapter that satisfies the Teams SDK's
* IHttpServerAdapter interface without spinning up an Express server.
*
* OpenClaw manages its own Express server for the Teams webhook endpoint, so
* the SDK's built-in HTTP server is unnecessary. Passing this stub prevents
* the SDK from creating the default HttpPlugin and avoids the crash.
* the SDK's built-in HTTP server is unnecessary. Passing this adapter via the
* `httpServerAdapter` option prevents the SDK from creating the default
* HttpPlugin (which uses the deprecated `plugins` array and registers an
* Express middleware with the pattern `/api*` — invalid in Express 5).
*
* See: https://github.com/openclaw/openclaw/issues/55161
* See: https://github.com/openclaw/openclaw/issues/60732
*/
async function createNoOpHttpPlugin(): Promise<unknown> {
// Lazy-import reflect-metadata (required by the Teams SDK decorator system)
// and the decorator key constants so we can tag the stub class correctly.
//
// FRAGILE: these are internal SDK paths (not public API). If
// @microsoft/teams.apps changes its dist layout, these imports will break.
// Pin the SDK version and re-verify after any upgrade.
await import("reflect-metadata");
const { PLUGIN_METADATA_KEY } =
await import("@microsoft/teams.apps/dist/types/plugin/decorators/plugin.js");
const { PLUGIN_DEPENDENCIES_METADATA_KEY } =
await import("@microsoft/teams.apps/dist/types/plugin/decorators/dependency.js");
const { PLUGIN_EVENTS_METADATA_KEY } =
await import("@microsoft/teams.apps/dist/types/plugin/decorators/event.js");
class NoOpHttpPlugin {
onInit() {}
async onStart() {}
onStop() {}
asServer() {
return {
onRequest: undefined as unknown,
initialize: async () => {},
start: async () => {},
stop: async () => {},
} as {
onRequest: unknown;
initialize: (opts?: unknown) => Promise<void>;
start: (port?: number | string) => Promise<void>;
stop: () => Promise<void>;
};
}
}
Reflect.defineMetadata(
PLUGIN_METADATA_KEY,
{ name: "http", version: "0.0.0", description: "no-op stub (express 5 compat)" },
NoOpHttpPlugin,
);
Reflect.defineMetadata(PLUGIN_DEPENDENCIES_METADATA_KEY, [], NoOpHttpPlugin);
Reflect.defineMetadata(PLUGIN_EVENTS_METADATA_KEY, [], NoOpHttpPlugin);
return new NoOpHttpPlugin();
function createNoOpHttpServerAdapter(): IHttpServerAdapter {
return {
registerRoute() {},
};
}
/**
@@ -127,15 +85,11 @@ export async function createMSTeamsApp(
creds: MSTeamsCredentials,
sdk: MSTeamsTeamsSdk,
): Promise<MSTeamsApp> {
const noOpHttp = await createNoOpHttpPlugin();
// Use type assertion: the SDK's AppOptions generic narrows `plugins` to
// Array<TPlugin>, but our no-op stub satisfies the runtime contract without
// matching the decorator-heavy IPlugin type at compile time.
return new sdk.App({
clientId: creds.appId,
clientSecret: creds.appPassword,
tenantId: creds.tenantId,
plugins: [noOpHttp],
httpServerAdapter: createNoOpHttpServerAdapter(),
} as ConstructorParameters<MSTeamsTeamsSdk["App"]>[0]);
}