"use strict";
var __importDefault = (this && this.__importDefault) || function (mod) {
    return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.clearSessionsCache = exports.createResponseLike = exports.formatRequestHttp2Headers = exports.getHttp2Session = void 0;
const http2_1 = __importDefault(require("http2"));
const lru_cache_1 = __importDefault(require("lru-cache"));
const lodash_1 = require("lodash");
const connection_reset_guard_1 = require("../connection-reset-guard");
const logger_1 = __importDefault(require("../../utils/logger"));
const { HTTP2_HEADER_PATH, HTTP2_HEADER_STATUS, HTTP2_HEADER_METHOD, HTTP2_HEADER_AUTHORITY, HTTP2_HEADER_CONNECTION, HTTP2_HEADER_UPGRADE, HTTP2_HEADER_KEEP_ALIVE, HTTP2_HEADER_PROXY_CONNECTION, HTTP2_HEADER_TRANSFER_ENCODING, HTTP2_HEADER_HTTP2_SETTINGS, HTTP2_HEADER_HOST, } = http2_1.default.constants;
const HTTP2_SESSIONS_CACHE_SIZE = 100;
const HTTP2_SESSION_TIMEOUT = 60000;
const HTTP2_CONNECT_TIMEOUT = 5000;
const HTTP2_LOCAL_SETTINGS_TIMEOUT = 3000;
const HTTP2_UNSUPPORTED_HEADERS = [
    HTTP2_HEADER_CONNECTION, HTTP2_HEADER_UPGRADE, HTTP2_HEADER_HTTP2_SETTINGS, HTTP2_HEADER_KEEP_ALIVE,
    HTTP2_HEADER_PROXY_CONNECTION, HTTP2_HEADER_TRANSFER_ENCODING, HTTP2_HEADER_HOST,
];
const unsupportedOrigins = [];
const pendingSessions = new Map();
const sessionsCache = new lru_cache_1.default({
    max: HTTP2_SESSIONS_CACHE_SIZE,
    dispose: (_, session) => {
        if (!session.closed)
            session.close();
    },
});
async function getHttp2Session(requestId, origin) {
    if (sessionsCache.has(origin))
        return sessionsCache.get(origin) || null;
    if (pendingSessions.has(origin))
        return pendingSessions.get(origin) || null;
    if (unsupportedOrigins.includes(origin))
        return null;
    const pendingSession = new Promise(resolve => {
        const session = http2_1.default.connect(origin, { settings: { enablePush: false } });
        const errorHandler = (err) => {
            pendingSessions.delete(origin);
            if (err.code === 'ERR_HTTP2_ERROR' || err.code === 'ERR_HTTP2_PING_CANCEL') {
                unsupportedOrigins.push(origin);
                logger_1.default.destination.onHttp2Unsupported(requestId, origin);
            }
            session.destroy();
            resolve(null);
        };
        const connectTimeout = setTimeout(() => errorHandler({ code: 'ERR_HTTP2_ERROR' }), HTTP2_CONNECT_TIMEOUT);
        session.once('error', errorHandler);
        session.once('connect', () => {
            const localSettingsTimeout = setTimeout(() => errorHandler({ code: 'ERR_HTTP2_ERROR' }), HTTP2_LOCAL_SETTINGS_TIMEOUT);
            clearTimeout(connectTimeout);
            session.ping(lodash_1.noop);
            session.once('localSettings', () => {
                clearTimeout(localSettingsTimeout);
                pendingSessions.delete(origin);
                sessionsCache.set(origin, session);
                logger_1.default.destination.onHttp2SessionCreated(requestId, origin, sessionsCache.length, HTTP2_SESSIONS_CACHE_SIZE);
                session.once('close', () => {
                    sessionsCache.del(origin);
                    logger_1.default.destination.onHttp2SessionClosed(requestId, origin, sessionsCache.length, HTTP2_SESSIONS_CACHE_SIZE);
                });
                session.off('error', errorHandler);
                session.once('error', (err) => {
                    if (!(0, connection_reset_guard_1.isConnectionResetError)(err)) {
                        logger_1.default.destination.onHttp2Error(requestId, origin, err);
                        throw err;
                    }
                });
                session.setTimeout(HTTP2_SESSION_TIMEOUT, () => {
                    logger_1.default.destination.onHttp2SessionTimeout(origin, HTTP2_SESSION_TIMEOUT);
                    sessionsCache.del(origin);
                });
                resolve(session);
            });
        });
    });
    pendingSessions.set(origin, pendingSession);
    return pendingSession;
}
exports.getHttp2Session = getHttp2Session;
function formatRequestHttp2Headers(opts) {
    return Object.keys(opts.headers).reduce((headers, key) => {
        if (!HTTP2_UNSUPPORTED_HEADERS.includes(key))
            headers[key] = opts.headers[key];
        return headers;
    }, {
        [HTTP2_HEADER_METHOD]: opts.method,
        [HTTP2_HEADER_PATH]: opts.path,
        [HTTP2_HEADER_AUTHORITY]: opts.headers.host,
    });
}
exports.formatRequestHttp2Headers = formatRequestHttp2Headers;
function createResponseLike(stream, response) {
    const statusCode = response[HTTP2_HEADER_STATUS];
    const headers = Object.assign({}, response);
    delete headers[HTTP2_HEADER_STATUS];
    return Object.assign(stream, { trailers: {}, statusCode, headers });
}
exports.createResponseLike = createResponseLike;
function clearSessionsCache() {
    sessionsCache.reset();
}
exports.clearSessionsCache = clearSessionsCache;