"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
const fetch_from_npm_registry_1 = require("fetch-from-npm-registry");
const fs = require("graceful-fs");
const mkdirp = require("mkdirp-promise");
const path = require("path");
const pathTemp = require("path-temp");
const retry = require("retry");
const rimraf = require("rimraf");
const ssri = require("ssri");
const unpackStream = require("unpack-stream");
const urlLib = require("url");
const errorTypes_1 = require("./errorTypes");
exports.default = (gotOpts) => {
    const fetchFromNpmRegistry = fetch_from_npm_registry_1.default(gotOpts);
    const retryOpts = Object.assign({ factor: 10, maxTimeout: 6e4, minTimeout: 1e4, retries: 2 }, gotOpts.retry);
    return async function download(url, saveto, opts) {
        const saveToDir = path.dirname(saveto);
        await mkdirp(saveToDir);
        // If a tarball is hosted on a different place than the manifest, only send
        // credentials on `alwaysAuth`
        const shouldAuth = opts.auth && (opts.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 (err.code === 'E403') {
                        reject(err);
                        return;
                    }
                    if (op.retry(err)) {
                        return;
                    }
                    reject(op.mainError());
                });
            });
        });
        async function fetch(currentAttempt) {
            try {
                const res = await fetchFromNpmRegistry(url, { auth: shouldAuth && opts.auth || undefined }); // tslint:disable-line
                if (res.status !== 200) {
                    const err = new Error(`${res.status} ${res.statusText}: ${url}`);
                    // tslint:disable
                    err['code'] = `E${res.status}`;
                    err['uri'] = url;
                    err['response'] = res;
                    // tslint:enable
                    throw err;
                }
                const contentLength = res.headers.has('content-length') && res.headers.get('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.body.on('data', (chunk) => {
                    downloaded += chunk.length;
                    if (onProgress)
                        onProgress(downloaded);
                });
                const tempTarballLocation = pathTemp(saveToDir);
                const writeStream = fs.createWriteStream(tempTarballLocation);
                return await new Promise((resolve, reject) => {
                    const stream = res.body
                        .on('error', reject)
                        .pipe(writeStream)
                        .on('error', reject);
                    const tempLocation = pathTemp(opts.unpackTo);
                    Promise.all([
                        opts.integrity && safeCheckStream(res.body, opts.integrity) || true,
                        unpackStream.local(res.body, tempLocation, {
                            generateIntegrity: opts.generatePackageIntegrity,
                            ignore: opts.ignore,
                        }),
                        waitTillClosed({ stream, size, getDownloaded: () => downloaded, url }),
                    ])
                        .then(([integrityCheckResult, filesIndex]) => {
                        if (integrityCheckResult !== true) {
                            throw integrityCheckResult;
                        }
                        fs.rename(tempTarballLocation, saveto, () => {
                            // ignore errors
                        });
                        resolve({ tempLocation, filesIndex });
                    })
                        .catch((err) => {
                        rimraf(tempTarballLocation, () => {
                            // ignore errors
                        });
                        rimraf(tempLocation, () => {
                            // Just ignoring this error
                            // A redundant stage folder won't break anything
                        });
                        reject(err);
                    });
                });
            }
            catch (err) {
                err.attempts = currentAttempt;
                err.resource = url;
                throw err;
            }
        }
    };
};
async function safeCheckStream(stream, integrity) {
    try {
        await ssri.checkStream(stream, integrity);
        return true;
    }
    catch (err) {
        return err;
    }
}
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=createDownloader.js.map