/** * In-memory nonce cache for anti-replay. * TTL defines how long a nonce is considered "seen". */ export class NonceCache { cache = new Map(); ttlMs; constructor(ttlMs = 3600000) { this.ttlMs = ttlMs; } /** * Returns true if nonce is valid (not seen within TTL). Records nonce on success. */ isValid(nonce, timestamp) { const now = Date.now(); const cached = this.cache.get(nonce); if (cached !== undefined) { if (now - cached < this.ttlMs) { return false; } this.cache.delete(nonce); } this.cache.set(nonce, timestamp); this.cleanup(now); return true; } cleanup(now) { for (const [n, ts] of this.cache.entries()) { if (now - ts >= this.ttlMs) { this.cache.delete(n); } } } clear() { this.cache.clear(); } }