Plugins: harden interactive handler registration

This commit is contained in:
Vincent Koc 2026-03-15 15:52:28 -07:00
parent 61f8466390
commit 8ffa9769f7
1 changed files with 26 additions and 6 deletions

View File

@ -73,6 +73,13 @@ function normalizeNamespace(namespace: string): string {
return namespace.trim();
}
function normalizeInteractiveChannel(channel: unknown): "telegram" | "discord" | null {
if (channel === "telegram" || channel === "discord") {
return channel;
}
return null;
}
function validateNamespace(namespace: string): string | null {
if (!namespace.trim()) {
return "Interactive handler namespace cannot be empty";
@ -112,12 +119,22 @@ export function registerPluginInteractiveHandler(
registration: PluginInteractiveHandlerRegistration,
opts?: { pluginName?: string; pluginRoot?: string },
): InteractiveRegistrationResult {
const channel = normalizeInteractiveChannel(registration.channel);
if (!channel) {
return {
ok: false,
error: 'Interactive handler channel must be either "telegram" or "discord"',
};
}
const namespace = normalizeNamespace(registration.namespace);
const validationError = validateNamespace(namespace);
if (validationError) {
return { ok: false, error: validationError };
}
const key = toRegistryKey(registration.channel, namespace);
if (typeof registration.handler !== "function") {
return { ok: false, error: "Interactive handler must be a function" };
}
const key = toRegistryKey(channel, namespace);
const existing = interactiveHandlers.get(key);
if (existing) {
return {
@ -125,9 +142,10 @@ export function registerPluginInteractiveHandler(
error: `Interactive handler namespace "${namespace}" already registered by plugin "${existing.pluginId}"`,
};
}
if (registration.channel === "telegram") {
if (channel === "telegram") {
const telegramRegistration = registration as PluginInteractiveTelegramHandlerRegistration;
interactiveHandlers.set(key, {
...registration,
...telegramRegistration,
namespace,
channel: "telegram",
pluginId,
@ -135,8 +153,9 @@ export function registerPluginInteractiveHandler(
pluginRoot: opts?.pluginRoot,
});
} else {
const discordRegistration = registration as PluginInteractiveDiscordHandlerRegistration;
interactiveHandlers.set(key, {
...registration,
...discordRegistration,
namespace,
channel: "discord",
pluginId,
@ -204,8 +223,9 @@ export async function dispatchPluginInteractiveHandler(params: {
return { matched: false, handled: false, duplicate: false };
}
const dedupeKey =
const dedupeId =
params.channel === "telegram" ? params.callbackId?.trim() : params.interactionId?.trim();
const dedupeKey = dedupeId ? `${params.channel}:${match.namespace}:${dedupeId}` : undefined;
if (dedupeKey && callbackDedupe.peek(dedupeKey)) {
return { matched: true, handled: true, duplicate: true };
}
@ -354,7 +374,7 @@ export async function dispatchPluginInteractiveHandler(params: {
});
}
const resolved = await result;
if (dedupeKey) {
if (dedupeKey && (resolved?.handled ?? true)) {
callbackDedupe.check(dedupeKey);
}