LetsBeBiz-Redesign/openclaw/extensions/nextcloud-talk/src/replay-guard.ts

66 lines
1.9 KiB
TypeScript

import path from "node:path";
import { createPersistentDedupe } from "openclaw/plugin-sdk";
const DEFAULT_REPLAY_TTL_MS = 24 * 60 * 60 * 1000;
const DEFAULT_MEMORY_MAX_SIZE = 1_000;
const DEFAULT_FILE_MAX_ENTRIES = 10_000;
function sanitizeSegment(value: string): string {
const trimmed = value.trim();
if (!trimmed) {
return "default";
}
return trimmed.replace(/[^a-zA-Z0-9_-]/g, "_");
}
function buildReplayKey(params: { roomToken: string; messageId: string }): string | null {
const roomToken = params.roomToken.trim();
const messageId = params.messageId.trim();
if (!roomToken || !messageId) {
return null;
}
return `${roomToken}:${messageId}`;
}
export type NextcloudTalkReplayGuardOptions = {
stateDir: string;
ttlMs?: number;
memoryMaxSize?: number;
fileMaxEntries?: number;
onDiskError?: (error: unknown) => void;
};
export type NextcloudTalkReplayGuard = {
shouldProcessMessage: (params: {
accountId: string;
roomToken: string;
messageId: string;
}) => Promise<boolean>;
};
export function createNextcloudTalkReplayGuard(
options: NextcloudTalkReplayGuardOptions,
): NextcloudTalkReplayGuard {
const stateDir = options.stateDir.trim();
const persistentDedupe = createPersistentDedupe({
ttlMs: options.ttlMs ?? DEFAULT_REPLAY_TTL_MS,
memoryMaxSize: options.memoryMaxSize ?? DEFAULT_MEMORY_MAX_SIZE,
fileMaxEntries: options.fileMaxEntries ?? DEFAULT_FILE_MAX_ENTRIES,
resolveFilePath: (namespace) =>
path.join(stateDir, "nextcloud-talk", "replay-dedupe", `${sanitizeSegment(namespace)}.json`),
});
return {
shouldProcessMessage: async ({ accountId, roomToken, messageId }) => {
const replayKey = buildReplayKey({ roomToken, messageId });
if (!replayKey) {
return true;
}
return await persistentDedupe.checkAndRecord(replayKey, {
namespace: accountId,
onDiskError: options.onDiskError,
});
},
};
}