"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.Cookies = void 0;
// import type {Headers} from "./headers";
const utils_1 = require("../crypto/utils");
const utils_2 = require("./utils");
const headers_1 = require("./headers");
class Cookies {
    static parseCookies(hdrs) {
        const entries = hdrs
            .filter((hdr) => hdr.trim().length > 0)
            .map((cookieDef) => {
            const [keyval, ...opts] = cookieDef.split(';');
            const [name, value] = (0, utils_2.splitN)(keyval, '=', 2).map((value) => value.trim());
            return [
                name,
                {
                    name,
                    value,
                    ...Object.fromEntries(opts.map((opt) => (0, utils_2.splitN)(opt, '=', 2).map((value) => value.trim()))),
                },
            ];
        });
        const jar = Object.fromEntries(entries);
        for (const cookie of Object.values(jar)) {
            if (typeof cookie.expires === 'string') {
                cookie.expires = new Date(cookie.expires);
            }
        }
        return jar;
    }
    static encodeCookie(data) {
        let result = '';
        result += `${data.name}=${data.value};`;
        result += Object.entries(data)
            .filter(([key]) => !['name', 'value', 'expires'].includes(key))
            .map(([key, value]) => `${key}=${value}`)
            .join('; ');
        if (data.expires) {
            result += ';';
            result += `expires=${data.expires.toUTCString()}`;
        }
        return result;
    }
    constructor(request, response, { keys = [] } = {}) {
        this.response = response;
        this.receivedCookieJar = {};
        this.outgoingCookieJar = {};
        this.keys = [];
        if (keys)
            this.keys = keys;
        const cookieReqHdr = (0, headers_1.getHeader)(request.headers, 'Cookie') ?? '';
        this.receivedCookieJar = Cookies.parseCookies(cookieReqHdr.split(';'));
        const cookieResHdr = (0, headers_1.getHeaders)(response.headers, 'Set-Cookie') ?? [];
        this.outgoingCookieJar = Cookies.parseCookies(cookieResHdr);
    }
    toHeaders() {
        return Object.values(this.outgoingCookieJar).map((cookie) => Cookies.encodeCookie(cookie));
    }
    updateHeader() {
        if (!this.response.headers) {
            this.response.headers = {};
        }
        (0, headers_1.removeHeader)(this.response.headers, 'Set-Cookie');
        this.toHeaders().map((hdr) => (0, headers_1.addHeader)(this.response.headers, 'Set-Cookie', hdr));
    }
    get(name) {
        return this.receivedCookieJar[name]?.value;
    }
    deleteCookie(name) {
        this.set(name, '', {
            path: '/',
            expires: new Date(0),
        });
    }
    async getAndVerify(name) {
        const value = this.get(name);
        if (!value)
            return undefined;
        if (!(await this.isSignedCookieValid(name))) {
            return undefined;
        }
        return value;
    }
    get canSign() {
        return this.keys?.length > 0;
    }
    set(name, value, opts = {}) {
        this.outgoingCookieJar[name] = {
            ...opts,
            name,
            value,
        };
        this.updateHeader();
    }
    async setAndSign(name, value, opts = {}) {
        if (!this.canSign) {
            throw Error('No keys provided for signing.');
        }
        this.set(name, value, opts);
        const sigName = `${name}.sig`;
        const signature = await (0, utils_1.createSHA256HMAC)(this.keys[0], value);
        this.set(sigName, signature, opts);
        this.updateHeader();
    }
    async isSignedCookieValid(cookieName) {
        const signedCookieName = `${cookieName}.sig`;
        // No cookie or no signature cookie makes the cookie it invalid.
        if (!this.get(cookieName) || !this.get(signedCookieName)) {
            this.deleteCookie(signedCookieName);
            this.deleteCookie(cookieName);
            return false;
        }
        const value = this.get(cookieName);
        const signature = this.get(signedCookieName);
        const allCheckSignatures = await Promise.all(this.keys.map((key) => (0, utils_1.createSHA256HMAC)(key, value)));
        if (!allCheckSignatures.includes(signature)) {
            this.deleteCookie(signedCookieName);
            this.deleteCookie(cookieName);
            return false;
        }
        return true;
    }
}
exports.Cookies = Cookies;
//# sourceMappingURL=cookies.js.map