"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.HttpTransport = exports.HttpTransportError = void 0;
const tslib_1 = require("tslib");
const agentkeepalive_1 = tslib_1.__importStar(require("agentkeepalive"));
const node_fetch_1 = tslib_1.__importDefault(require("node-fetch"));
const worker_threads_1 = require("worker_threads");
const debug_logging_1 = require("@splunkdlt/debug-logging");
const httputils_1 = require("../utils/httputils");
const stats_1 = require("../utils/stats");
const jsonrpc_1 = require("./jsonrpc");
const { debug, trace } = debug_logging_1.createModuleDebug('eth:http');
class HttpTransportError extends Error {
    constructor(message, response) {
        super(message);
        this.response = response;
    }
}
exports.HttpTransportError = HttpTransportError;
const CONFIG_DEFAULTS = {
    timeout: 60000,
    validateCertificate: true,
    requestKeepAlive: true,
    maxSockets: 256
};
const initialCounters = {
    requests: 0,
    errors: 0
};
class HttpTransport {
    constructor(url, config) {
        this.url = url;
        this.counters = { ...initialCounters };
        this.aggregates = { requestDuration: new stats_1.AggregateMetric(), batchSize: new stats_1.AggregateMetric() };
        this.config = { ...CONFIG_DEFAULTS, ...config };
        const baseAgentOptions = {
            keepAlive: true,
            maxSockets: 256
        };
        this.httpAgent = httputils_1.isHttps(url)
            ? new agentkeepalive_1.HttpsAgent({
                ...baseAgentOptions,
                rejectUnauthorized: this.config.validateCertificate
            })
            : new agentkeepalive_1.default(baseAgentOptions);
    }
    get source() {
        const u = new URL(this.url);
        return `jsonprc+${u.origin}`;
    }
    get originHost() {
        return new URL(this.url).hostname;
    }
    async send(request) {
        debug('Sending JSON RPC request: %o', request.method);
        const result = await this.sendInternal(request);
        if (Array.isArray(result)) {
            throw new HttpTransportError(`JSON RPC returned batch but expected single message`, result);
        }
        return result;
    }
    async sendBatch(request) {
        debug('Sending JSON RPC batch containing %d requests', request.length);
        const result = await this.sendInternal(request);
        if (!Array.isArray(result)) {
            throw new HttpTransportError(`JSON RPC returned single message, was expecting batch`, result);
        }
        return result;
    }
    async sendInternal(request) {
        const startTime = Date.now();
        debug(`Sending JSON RPC request over HTTP (thread ${worker_threads_1.threadId}): %O`, request);
        const body = JSON.stringify(request);
        this.counters.requests++;
        this.aggregates.batchSize.push(Array.isArray(request) ? request.length : 1);
        try {
            const response = await node_fetch_1.default(this.url, {
                method: 'POST',
                headers: {
                    'Content-Type': 'application/json',
                    'Content-Length': String(body.length)
                },
                body,
                agent: this.httpAgent,
                timeout: this.config.timeout
            });
            if (!response.ok) {
                let responseBody = null;
                try {
                    responseBody = await response.text();
                    try {
                        responseBody = JSON.parse(responseBody);
                    }
                    catch (e) {
                        // ignore
                    }
                }
                catch (e) {
                    // ignore
                }
                throw new HttpTransportError(`JSON RPC service ${this.url} responded with HTTP status ${response.status} (${response.statusText})`, responseBody);
            }
            const data = await response.json();
            debug('Received JSON RPC response (thread %d):\n%O', worker_threads_1.threadId, data);
            if (!jsonrpc_1.validateJsonRpcResponse(data)) {
                throw new HttpTransportError('UNREACHABLE: Invalid JSON RPC response', data);
            }
            this.aggregates.requestDuration.push(Date.now() - startTime);
            debug('Completed JSON RPC request in %d ms', Date.now() - startTime);
            if (Array.isArray(request) !== Array.isArray(data)) {
                jsonrpc_1.checkError(Array.isArray(data) ? data[0] : data);
                throw new HttpTransportError(Array.isArray(request)
                    ? 'JSON RPC returned single message, was expecting batch'
                    : 'JSON RPC returned batch, was expecting single message', data);
            }
            return data;
        }
        catch (e) {
            this.counters.errors++;
            this.aggregates.requestDuration.push(Date.now() - startTime);
            throw e;
        }
    }
    get stats() {
        return {
            ...this.counters,
            httpClient: stats_1.httpClientStats(this.httpAgent.getCurrentStatus())
        };
    }
    flushStats() {
        const stats = {
            ...this.counters,
            ...this.aggregates.requestDuration.flush('requestDuration'),
            ...this.aggregates.batchSize.flush('batchSize')
        };
        this.counters = { ...initialCounters };
        return stats;
    }
}
exports.HttpTransport = HttpTransport;
