"use strict";
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
    return new (P || (P = Promise))(function (resolve, reject) {
        function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
        function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
        function step(result) { result.done ? resolve(result.value) : new P(function (resolve) { resolve(result.value); }).then(fulfilled, rejected); }
        step((generator = generator.apply(thisArg, _arguments || [])).next());
    });
};
Object.defineProperty(exports, "__esModule", { value: true });
const getCredentialsByURI = require("credentials-by-uri");
const createWriteStreamAtomic = require("fs-write-stream-atomic");
const mkdirp = require("mkdirp-promise");
const normalizeRegistryUrl = require("normalize-registry-url");
const PQueue = require("p-queue");
const path = require("path");
const R = require("ramda");
const retry = require("retry");
const ssri = require("ssri");
const unpackStream = require("unpack-stream");
const urlLib = require("url");
const errorTypes_1 = require("../errorTypes");
exports.default = (client, gotOpts) => {
    gotOpts.rawNpmConfig.registry = normalizeRegistryUrl(gotOpts.rawNpmConfig.registry || gotOpts.registry);
    const retryOpts = {
        factor: gotOpts.factor,
        maxTimeout: gotOpts.maxTimeout,
        minTimeout: gotOpts.minTimeout,
        randomize: gotOpts.randomize,
        retries: gotOpts.retries,
    };
    let counter = 0;
    const networkConcurrency = gotOpts.networkConcurrency || 16;
    const rawNpmConfig = gotOpts.rawNpmConfig || {};
    const requestsQueue = new PQueue({
        concurrency: networkConcurrency,
    });
    function getJSON(url, registry, priority) {
        return __awaiter(this, void 0, void 0, function* () {
            return requestsQueue.add(() => new Promise((resolve, reject) => {
                const getOpts = {
                    auth: getCredentialsByURI(registry, rawNpmConfig),
                    fullMetadata: false,
                };
                client.get(url, getOpts, (err, data, raw, res) => {
                    if (err) {
                        reject(err);
                        return;
                    }
                    resolve(data);
                });
            }), { priority });
        });
    }
    function download(url, saveto, opts) {
        // Tarballs are requested first because they are bigger than metadata files.
        // However, when one line is left available, allow it to be picked up by a metadata request.
        // This is done in order to avoid situations when tarballs are downloaded in chunks
        // As much tarballs should be downloaded simultaneously as possible.
        const priority = (++counter % networkConcurrency === 0 ? -1 : 1) * 1000;
        return requestsQueue.add(() => __awaiter(this, void 0, void 0, function* () {
            yield mkdirp(path.dirname(saveto));
            const auth = opts.registry && getCredentialsByURI(opts.registry, rawNpmConfig);
            // If a tarball is hosted on a different place than the manifest, only send
            // credentials on `alwaysAuth`
            const shouldAuth = auth && (auth.alwaysAuth ||
                !opts.registry ||
                urlLib.parse(url).host === urlLib.parse(opts.registry).host);
            const op = retry.operation(retryOpts);
            return new Promise((resolve, reject) => {
                op.attempt((currentAttempt) => {
                    fetch(currentAttempt)
                        .then(resolve)
                        .catch((err) => {
                        if (op.retry(err)) {
                            return;
                        }
                        reject(op.mainError());
                    });
                });
            });
            function fetch(currentAttempt) {
                return new Promise((resolve, reject) => {
                    client.fetch(url, { auth: shouldAuth && auth || undefined }, (err, res) => __awaiter(this, void 0, void 0, function* () {
                        if (err)
                            return reject(err);
                        if (res.statusCode !== 200) {
                            return reject(new Error(`Invalid response: ${res.statusCode}`));
                        }
                        // Is saved to a variable only because TypeScript 5.3 errors otherwise
                        const contentLength = res.headers['content-length'];
                        const size = typeof contentLength === 'string'
                            ? parseInt(contentLength, 10)
                            : null;
                        if (opts.onStart) {
                            opts.onStart(size, currentAttempt);
                        }
                        const onProgress = opts.onProgress;
                        let downloaded = 0;
                        res.on('data', (chunk) => {
                            downloaded += chunk.length;
                            if (onProgress)
                                onProgress(downloaded);
                        });
                        const writeStream = createWriteStreamAtomic(saveto);
                        const stream = res
                            .on('error', reject)
                            .pipe(writeStream)
                            .on('error', reject);
                        Promise.all([
                            opts.integrity && ssri.checkStream(res, opts.integrity),
                            unpackStream.local(res, opts.unpackTo, {
                                generateIntegrity: opts.generatePackageIntegrity,
                            }),
                            waitTillClosed({ stream, size, getDownloaded: () => downloaded, url }),
                        ])
                            .then((vals) => resolve(vals[1]))
                            .catch(reject);
                    }));
                })
                    .catch((err) => {
                    err.attempts = currentAttempt;
                    err.resource = url;
                    throw err;
                });
            }
        }), { priority });
    }
    return {
        download,
        getJSON: R.memoize(getJSON),
    };
};
function waitTillClosed(opts) {
    return new Promise((resolve, reject) => {
        opts.stream.on('close', () => {
            const downloaded = opts.getDownloaded();
            if (opts.size !== null && opts.size !== downloaded) {
                const err = new errorTypes_1.BadTarballError({
                    expectedSize: opts.size,
                    receivedSize: downloaded,
                    tarballUrl: opts.url,
                });
                reject(err);
                return;
            }
            resolve();
        });
    });
}
//# sourceMappingURL=got.js.map