Mattermost: refresh interaction callback URL cache

This commit is contained in:
Muhammed Mukhthar CM
2026-03-06 09:53:04 +00:00
parent 9d8950a051
commit 4d593731be
3 changed files with 33 additions and 9 deletions

View File

@@ -5,6 +5,7 @@ import { resolveMattermostAccount } from "./accounts.js";
import type { MattermostClient } from "./client.js"; import type { MattermostClient } from "./client.js";
import { import {
buildButtonAttachments, buildButtonAttachments,
computeInteractionCallbackUrl,
createMattermostInteractionHandler, createMattermostInteractionHandler,
generateInteractionToken, generateInteractionToken,
getInteractionCallbackUrl, getInteractionCallbackUrl,
@@ -136,7 +137,9 @@ describe("callback URL registry", () => {
describe("resolveInteractionCallbackUrl", () => { describe("resolveInteractionCallbackUrl", () => {
afterEach(() => { afterEach(() => {
setInteractionCallbackUrl("resolve-test", ""); for (const accountId of ["cached", "default", "acct", "myaccount"]) {
setInteractionCallbackUrl(accountId, "");
}
}); });
it("prefers cached URL from registry", () => { it("prefers cached URL from registry", () => {
@@ -144,6 +147,14 @@ describe("resolveInteractionCallbackUrl", () => {
expect(resolveInteractionCallbackUrl("cached")).toBe("http://cached:1234/path"); expect(resolveInteractionCallbackUrl("cached")).toBe("http://cached:1234/path");
}); });
it("recomputes from config when bypassing the cache explicitly", () => {
setInteractionCallbackUrl("acct", "http://cached:1234/path");
const url = computeInteractionCallbackUrl("acct", {
gateway: { port: 9999, customBindHost: "gateway.internal" },
});
expect(url).toBe("http://gateway.internal:9999/mattermost/interactions/acct");
});
it("uses interactions.callbackBaseUrl when configured", () => { it("uses interactions.callbackBaseUrl when configured", () => {
const url = resolveInteractionCallbackUrl("default", { const url = resolveInteractionCallbackUrl("default", {
channels: { channels: {

View File

@@ -67,17 +67,12 @@ function normalizeCallbackBaseUrl(baseUrl: string): string {
/** /**
* Resolve the interaction callback URL for an account. * Resolve the interaction callback URL for an account.
* Prefers the in-memory registered URL (set by the gateway monitor).
* Falls back to computing it from interactions.callbackBaseUrl or gateway host config. * Falls back to computing it from interactions.callbackBaseUrl or gateway host config.
*/ */
export function resolveInteractionCallbackUrl( export function computeInteractionCallbackUrl(
accountId: string, accountId: string,
cfg?: InteractionCallbackConfig, cfg?: InteractionCallbackConfig,
): string { ): string {
const cached = callbackUrls.get(accountId);
if (cached) {
return cached;
}
const path = resolveInteractionCallbackPath(accountId); const path = resolveInteractionCallbackPath(accountId);
// Prefer merged per-account config when available, but keep the top-level path for // Prefer merged per-account config when available, but keep the top-level path for
// callers/tests that still pass the root Mattermost config shape directly. // callers/tests that still pass the root Mattermost config shape directly.
@@ -101,6 +96,22 @@ export function resolveInteractionCallbackUrl(
return `http://${host}:${port}${path}`; return `http://${host}:${port}${path}`;
} }
/**
* Resolve the interaction callback URL for an account.
* Prefers the in-memory registered URL (set by the gateway monitor) so callers outside the
* monitor lifecycle can reuse the runtime-validated callback destination.
*/
export function resolveInteractionCallbackUrl(
accountId: string,
cfg?: InteractionCallbackConfig,
): string {
const cached = callbackUrls.get(accountId);
if (cached) {
return cached;
}
return computeInteractionCallbackUrl(accountId, cfg);
}
// ── HMAC token management ────────────────────────────────────────────── // ── HMAC token management ──────────────────────────────────────────────
// Secret is derived from the bot token so it's stable across CLI and gateway processes. // Secret is derived from the bot token so it's stable across CLI and gateway processes.

View File

@@ -44,8 +44,8 @@ import {
type MattermostUser, type MattermostUser,
} from "./client.js"; } from "./client.js";
import { import {
computeInteractionCallbackUrl,
createMattermostInteractionHandler, createMattermostInteractionHandler,
resolveInteractionCallbackUrl,
resolveInteractionCallbackPath, resolveInteractionCallbackPath,
setInteractionCallbackUrl, setInteractionCallbackUrl,
setInteractionSecret, setInteractionSecret,
@@ -456,7 +456,9 @@ export async function monitorMattermostProvider(opts: MonitorMattermostOpts = {}
// Register HTTP callback endpoint for interactive button clicks. // Register HTTP callback endpoint for interactive button clicks.
// Mattermost POSTs to this URL when a user clicks a button action. // Mattermost POSTs to this URL when a user clicks a button action.
const interactionPath = resolveInteractionCallbackPath(account.accountId); const interactionPath = resolveInteractionCallbackPath(account.accountId);
const callbackUrl = resolveInteractionCallbackUrl(account.accountId, { // Recompute from config on each monitor start so reconnects or config reloads can refresh the
// cached callback URL for downstream callers such as `message action=send`.
const callbackUrl = computeInteractionCallbackUrl(account.accountId, {
gateway: cfg.gateway, gateway: cfg.gateway,
interactions: account.config.interactions, interactions: account.config.interactions,
}); });