"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;