"use strict";
/**
 * NonceStore -- Persistent, TTL-based nonce tracking for replay protection.
 *
 * Survives process restarts by persisting nonces to disk.
 * Used by AgentAuth to prevent handoff replay attacks across sessions.
 *
 * Format: JSONL file with { nonce, timestamp } entries.
 * Prunes expired entries on load and periodically during use.
 *
 * @connexum/ai-governance
 */
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
    if (k2 === undefined) k2 = k;
    var desc = Object.getOwnPropertyDescriptor(m, k);
    if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
      desc = { enumerable: true, get: function() { return m[k]; } };
    }
    Object.defineProperty(o, k2, desc);
}) : (function(o, m, k, k2) {
    if (k2 === undefined) k2 = k;
    o[k2] = m[k];
}));
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
    Object.defineProperty(o, "default", { enumerable: true, value: v });
}) : function(o, v) {
    o["default"] = v;
});
var __importStar = (this && this.__importStar) || (function () {
    var ownKeys = function(o) {
        ownKeys = Object.getOwnPropertyNames || function (o) {
            var ar = [];
            for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
            return ar;
        };
        return ownKeys(o);
    };
    return function (mod) {
        if (mod && mod.__esModule) return mod;
        var result = {};
        if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
        __setModuleDefault(result, mod);
        return result;
    };
})();
Object.defineProperty(exports, "__esModule", { value: true });
exports.NonceStore = void 0;
const fs = __importStar(require("fs"));
const path = __importStar(require("path"));
class NonceStore {
    config;
    nonces = new Map();
    dirty = false;
    constructor(config) {
        this.config = {
            ttlMs: 600000,
            maxEntries: 10000,
            ...config,
        };
        const dir = path.dirname(config.storePath);
        if (!fs.existsSync(dir)) {
            fs.mkdirSync(dir, { recursive: true });
        }
        this.loadFromDisk();
    }
    /**
     * Check if a nonce has been seen. Returns true if it's a replay.
     */
    has(nonce) {
        this.pruneIfNeeded();
        return this.nonces.has(nonce);
    }
    /**
     * Record a nonce as used. Returns false if already used (replay).
     */
    add(nonce) {
        this.pruneIfNeeded();
        if (this.nonces.has(nonce)) {
            return false; // Replay
        }
        this.nonces.set(nonce, Date.now());
        this.dirty = true;
        this.flush();
        return true;
    }
    /**
     * Get count of active (non-expired) nonces.
     */
    size() {
        this.pruneIfNeeded();
        return this.nonces.size;
    }
    /**
     * Force flush to disk.
     */
    flush() {
        if (!this.dirty && fs.existsSync(this.config.storePath))
            return;
        try {
            const entries = [];
            for (const [nonce, ts] of this.nonces) {
                entries.push({ nonce, ts });
            }
            fs.writeFileSync(this.config.storePath, entries.map(e => JSON.stringify(e)).join('\n') + '\n');
            this.dirty = false;
        }
        catch (err) {
            process.stderr.write(`[NONCE STORE] Failed to persist: ${err.message}\n`);
        }
    }
    /**
     * Clear all nonces (for testing).
     */
    clear() {
        this.nonces.clear();
        this.dirty = true;
        this.flush();
    }
    // --- Private ---
    pruneIfNeeded() {
        const now = Date.now();
        let pruned = false;
        for (const [nonce, ts] of this.nonces) {
            if (now - ts > this.config.ttlMs) {
                this.nonces.delete(nonce);
                pruned = true;
            }
        }
        // Force prune if over max
        if (this.nonces.size > this.config.maxEntries) {
            const sorted = Array.from(this.nonces.entries())
                .sort((a, b) => a[1] - b[1]);
            const toRemove = sorted.slice(0, sorted.length - this.config.maxEntries);
            for (const [nonce] of toRemove) {
                this.nonces.delete(nonce);
            }
            pruned = true;
        }
        if (pruned) {
            this.dirty = true;
            this.flush();
        }
    }
    loadFromDisk() {
        if (!fs.existsSync(this.config.storePath))
            return;
        try {
            const content = fs.readFileSync(this.config.storePath, 'utf-8');
            const now = Date.now();
            const lines = content.trim().split('\n').filter(Boolean);
            for (const line of lines) {
                try {
                    const entry = JSON.parse(line);
                    // Only load non-expired nonces
                    if (now - entry.ts <= this.config.ttlMs) {
                        this.nonces.set(entry.nonce, entry.ts);
                    }
                }
                catch {
                    // Skip malformed lines
                }
            }
        }
        catch (err) {
            process.stderr.write(`[NONCE STORE] Failed to load: ${err.message}\n`);
        }
    }
}
exports.NonceStore = NonceStore;
//# sourceMappingURL=nonce-store.js.map