openclaw/extensions/discord/src/components-registry.ts

107 lines
3.1 KiB
TypeScript

import { resolveGlobalMap } from "openclaw/plugin-sdk/global-singleton";
import type { DiscordComponentEntry, DiscordModalEntry } from "./components.js";
const DEFAULT_COMPONENT_TTL_MS = 30 * 60 * 1000;
const DISCORD_COMPONENT_ENTRIES_KEY = Symbol.for("openclaw.discord.componentEntries");
const DISCORD_MODAL_ENTRIES_KEY = Symbol.for("openclaw.discord.modalEntries");
let componentEntries: Map<string, DiscordComponentEntry> | undefined;
let modalEntries: Map<string, DiscordModalEntry> | undefined;
function getComponentEntries(): Map<string, DiscordComponentEntry> {
componentEntries ??= resolveGlobalMap<string, DiscordComponentEntry>(
DISCORD_COMPONENT_ENTRIES_KEY,
);
return componentEntries;
}
function getModalEntries(): Map<string, DiscordModalEntry> {
modalEntries ??= resolveGlobalMap<string, DiscordModalEntry>(DISCORD_MODAL_ENTRIES_KEY);
return modalEntries;
}
function isExpired(entry: { expiresAt?: number }, now: number) {
return typeof entry.expiresAt === "number" && entry.expiresAt <= now;
}
function normalizeEntryTimestamps<T extends { createdAt?: number; expiresAt?: number }>(
entry: T,
now: number,
ttlMs: number,
): T {
const createdAt = entry.createdAt ?? now;
const expiresAt = entry.expiresAt ?? createdAt + ttlMs;
return { ...entry, createdAt, expiresAt };
}
function registerEntries<
T extends { id: string; messageId?: string; createdAt?: number; expiresAt?: number },
>(
entries: T[],
store: Map<string, T>,
params: { now: number; ttlMs: number; messageId?: string },
): void {
for (const entry of entries) {
const normalized = normalizeEntryTimestamps(
{ ...entry, messageId: params.messageId ?? entry.messageId },
params.now,
params.ttlMs,
);
store.set(entry.id, normalized);
}
}
function resolveEntry<T extends { expiresAt?: number }>(
store: Map<string, T>,
params: { id: string; consume?: boolean },
): T | null {
const entry = store.get(params.id);
if (!entry) {
return null;
}
const now = Date.now();
if (isExpired(entry, now)) {
store.delete(params.id);
return null;
}
if (params.consume !== false) {
store.delete(params.id);
}
return entry;
}
export function registerDiscordComponentEntries(params: {
entries: DiscordComponentEntry[];
modals: DiscordModalEntry[];
ttlMs?: number;
messageId?: string;
}): void {
const now = Date.now();
const ttlMs = params.ttlMs ?? DEFAULT_COMPONENT_TTL_MS;
registerEntries(params.entries, getComponentEntries(), {
now,
ttlMs,
messageId: params.messageId,
});
registerEntries(params.modals, getModalEntries(), { now, ttlMs, messageId: params.messageId });
}
export function resolveDiscordComponentEntry(params: {
id: string;
consume?: boolean;
}): DiscordComponentEntry | null {
return resolveEntry(getComponentEntries(), params);
}
export function resolveDiscordModalEntry(params: {
id: string;
consume?: boolean;
}): DiscordModalEntry | null {
return resolveEntry(getModalEntries(), params);
}
export function clearDiscordComponentEntries(): void {
getComponentEntries().clear();
getModalEntries().clear();
}