"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.peertubeGot = exports.findLatestRedirection = exports.getAgent = exports.isBinaryResponse = exports.doRequestAndSaveToFile = exports.doJSONRequest = exports.doRequest = void 0;
const tslib_1 = require("tslib");
const fs_extra_1 = require("fs-extra");
const got_1 = tslib_1.__importDefault(require("got"));
const hpagent_1 = require("hpagent");
const constants_1 = require("../initializers/constants");
const core_utils_1 = require("./core-utils");
const logger_1 = require("./logger");
const proxy_1 = require("./proxy");
const lTags = (0, logger_1.loggerTagsFactory)('request');
const httpSignature = require('@peertube/http-signature');
const peertubeGot = got_1.default.extend(Object.assign(Object.assign({}, getAgent()), { headers: {
        'user-agent': getUserAgent()
    }, handlers: [
        (options, next) => {
            var _a;
            const promiseOrStream = next(options);
            const bodyKBLimit = (_a = options.context) === null || _a === void 0 ? void 0 : _a.bodyKBLimit;
            if (!bodyKBLimit)
                throw new Error('No KB limit for this request');
            const bodyLimit = bodyKBLimit * 1000;
            promiseOrStream.on('downloadProgress', progress => {
                if (progress.transferred > bodyLimit && progress.percent !== 1) {
                    const message = `Exceeded the download limit of ${bodyLimit} B`;
                    logger_1.logger.warn(message, lTags());
                    if (promiseOrStream.cancel) {
                        promiseOrStream.cancel();
                        return;
                    }
                    promiseOrStream.destroy();
                }
            });
            return promiseOrStream;
        }
    ], hooks: {
        beforeRequest: [
            options => {
                const headers = options.headers || {};
                headers['host'] = options.url.host;
            },
            options => {
                var _a, _b, _c;
                const httpSignatureOptions = (_a = options.context) === null || _a === void 0 ? void 0 : _a.httpSignature;
                if (httpSignatureOptions) {
                    const method = (_b = options.method) !== null && _b !== void 0 ? _b : 'GET';
                    const path = (_c = options.path) !== null && _c !== void 0 ? _c : options.url.pathname;
                    if (!method || !path) {
                        throw new Error(`Cannot sign request without method (${method}) or path (${path}) ${options}`);
                    }
                    httpSignature.signRequest({
                        getHeader: function (header) {
                            const value = options.headers[header.toLowerCase()];
                            if (!value)
                                logger_1.logger.warn('Unknown header requested by http-signature.', { headers: options.headers, header });
                            return value;
                        },
                        setHeader: function (header, value) {
                            options.headers[header] = value;
                        },
                        method,
                        path
                    }, httpSignatureOptions);
                }
            }
        ],
        beforeRetry: [
            (_options, error, retryCount) => {
                logger_1.logger.debug('Retrying request to %s.', error.request.requestUrl, Object.assign({ retryCount, error: buildRequestError(error) }, lTags()));
            }
        ]
    } }));
exports.peertubeGot = peertubeGot;
function doRequest(url, options = {}) {
    const gotOptions = buildGotOptions(options);
    return peertubeGot(url, gotOptions)
        .catch(err => { throw buildRequestError(err); });
}
exports.doRequest = doRequest;
function doJSONRequest(url, options = {}) {
    const gotOptions = buildGotOptions(options);
    return peertubeGot(url, Object.assign(Object.assign({}, gotOptions), { responseType: 'json' }))
        .catch(err => { throw buildRequestError(err); });
}
exports.doJSONRequest = doJSONRequest;
function doRequestAndSaveToFile(url, destPath, options = {}) {
    var _a;
    return tslib_1.__awaiter(this, void 0, void 0, function* () {
        const gotOptions = buildGotOptions(Object.assign(Object.assign({}, options), { timeout: (_a = options.timeout) !== null && _a !== void 0 ? _a : constants_1.REQUEST_TIMEOUTS.FILE }));
        const outFile = (0, fs_extra_1.createWriteStream)(destPath);
        try {
            yield (0, core_utils_1.pipelinePromise)(peertubeGot.stream(url, gotOptions), outFile);
        }
        catch (err) {
            (0, fs_extra_1.remove)(destPath)
                .catch(err => logger_1.logger.error('Cannot remove %s after request failure.', destPath, Object.assign({ err }, lTags())));
            throw buildRequestError(err);
        }
    });
}
exports.doRequestAndSaveToFile = doRequestAndSaveToFile;
function getAgent() {
    if (!(0, proxy_1.isProxyEnabled)())
        return {};
    const proxy = (0, proxy_1.getProxy)();
    logger_1.logger.info('Using proxy %s.', proxy, lTags());
    const proxyAgentOptions = {
        keepAlive: true,
        keepAliveMsecs: 1000,
        maxSockets: 256,
        maxFreeSockets: 256,
        scheduling: 'lifo',
        proxy
    };
    return {
        agent: {
            http: new hpagent_1.HttpProxyAgent(proxyAgentOptions),
            https: new hpagent_1.HttpsProxyAgent(proxyAgentOptions)
        }
    };
}
exports.getAgent = getAgent;
function getUserAgent() {
    return `PeerTube/${constants_1.PEERTUBE_VERSION} (+${constants_1.WEBSERVER.URL})`;
}
function isBinaryResponse(result) {
    return constants_1.BINARY_CONTENT_TYPES.has(result.headers['content-type']);
}
exports.isBinaryResponse = isBinaryResponse;
function findLatestRedirection(url, options, iteration = 1) {
    return tslib_1.__awaiter(this, void 0, void 0, function* () {
        if (iteration > 10)
            throw new Error('Too much iterations to find final URL ' + url);
        const { headers } = yield peertubeGot(url, Object.assign({ followRedirect: false }, buildGotOptions(options)));
        if (headers.location)
            return findLatestRedirection(headers.location, options, iteration + 1);
        return url;
    });
}
exports.findLatestRedirection = findLatestRedirection;
function buildGotOptions(options) {
    var _a;
    const { activityPub, bodyKBLimit = 1000 } = options;
    const context = { bodyKBLimit, httpSignature: options.httpSignature };
    let headers = options.headers || {};
    if (!headers.date) {
        headers = Object.assign(Object.assign({}, headers), { date: new Date().toUTCString() });
    }
    if (activityPub && !headers.accept) {
        headers = Object.assign(Object.assign({}, headers), { accept: constants_1.ACTIVITY_PUB.ACCEPT_HEADER });
    }
    return {
        method: options.method,
        dnsCache: true,
        timeout: (_a = options.timeout) !== null && _a !== void 0 ? _a : constants_1.REQUEST_TIMEOUTS.DEFAULT,
        json: options.json,
        searchParams: options.searchParams,
        retry: 2,
        headers,
        context
    };
}
function buildRequestError(error) {
    const newError = new Error(error.message);
    newError.name = error.name;
    newError.stack = error.stack;
    if (error.response) {
        newError.responseBody = error.response.body;
        newError.responseHeaders = error.response.headers;
        newError.statusCode = error.response.statusCode;
    }
    return newError;
}
//# sourceMappingURL=requests.js.map