"use strict";
/**
 * Copyright 2017 Google Inc. All rights reserved.
 * Modifications copyright (c) Microsoft Corporation.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
    if (k2 === undefined) k2 = k;
    Object.defineProperty(o, k2, { enumerable: true, get: function() { return m[k]; } });
}) : (function(o, m, k, k2) {
    if (k2 === undefined) k2 = k;
    o[k2] = m[k];
}));
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
    Object.defineProperty(o, "default", { enumerable: true, value: v });
}) : function(o, v) {
    o["default"] = v;
});
var __importStar = (this && this.__importStar) || function (mod) {
    if (mod && mod.__esModule) return mod;
    var result = {};
    if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);
    __setModuleDefault(result, mod);
    return result;
};
var __importDefault = (this && this.__importDefault) || function (mod) {
    return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.logPolitely = exports.downloadBrowserWithProgressBar = void 0;
const extract_zip_1 = __importDefault(require("extract-zip"));
const fs_1 = __importDefault(require("fs"));
const os_1 = __importDefault(require("os"));
const path_1 = __importDefault(require("path"));
const progress_1 = __importDefault(require("progress"));
const proxy_from_env_1 = require("proxy-from-env");
const URL = __importStar(require("url"));
const util = __importStar(require("util"));
const registry_1 = require("../utils/registry");
const debugLogger_1 = require("../utils/debugLogger");
// `https-proxy-agent` v5 is written in TypeScript and exposes generated types.
// However, as of June 2020, its types are generated with tsconfig that enables
// `esModuleInterop` option.
//
// As a result, we can't depend on the package unless we enable the option
// for our codebase. Instead of doing this, we abuse "require" to import module
// without types.
const ProxyAgent = require('https-proxy-agent');
const unlinkAsync = util.promisify(fs_1.default.unlink.bind(fs_1.default));
const chmodAsync = util.promisify(fs_1.default.chmod.bind(fs_1.default));
const existsAsync = (path) => new Promise(resolve => fs_1.default.stat(path, err => resolve(!err)));
async function downloadBrowserWithProgressBar(registry, browserName) {
    const browserDirectory = registry.browserDirectory(browserName);
    const progressBarName = `${browserName} v${registry.revision(browserName)}`;
    if (await existsAsync(browserDirectory)) {
        // Already downloaded.
        debugLogger_1.debugLogger.log('install', `browser ${browserName} is already downloaded.`);
        return false;
    }
    let progressBar;
    let lastDownloadedBytes = 0;
    function progress(downloadedBytes, totalBytes) {
        if (!process.stderr.isTTY)
            return;
        if (!progressBar) {
            progressBar = new progress_1.default(`Downloading ${progressBarName} - ${toMegabytes(totalBytes)} [:bar] :percent :etas `, {
                complete: '=',
                incomplete: ' ',
                width: 20,
                total: totalBytes,
            });
        }
        const delta = downloadedBytes - lastDownloadedBytes;
        lastDownloadedBytes = downloadedBytes;
        progressBar.tick(delta);
    }
    const url = registry.downloadURL(browserName);
    const zipPath = path_1.default.join(os_1.default.tmpdir(), `playwright-download-${browserName}-${registry_1.hostPlatform}-${registry.revision(browserName)}.zip`);
    try {
        for (let attempt = 1, N = 3; attempt <= N; ++attempt) {
            debugLogger_1.debugLogger.log('install', `downloading ${progressBarName} - attempt #${attempt}`);
            const { error } = await downloadFile(url, zipPath, progress);
            if (!error) {
                debugLogger_1.debugLogger.log('install', `SUCCESS downloading ${progressBarName}`);
                break;
            }
            const errorMessage = typeof error === 'object' && typeof error.message === 'string' ? error.message : '';
            debugLogger_1.debugLogger.log('install', `attempt #${attempt} - ERROR: ${errorMessage}`);
            if (attempt < N && (errorMessage.includes('ECONNRESET') || errorMessage.includes('ETIMEDOUT'))) {
                // Maximum delay is 3rd retry: 1337.5ms
                const millis = (Math.random() * 200) + (250 * Math.pow(1.5, attempt));
                debugLogger_1.debugLogger.log('install', `sleeping ${millis}ms before retry...`);
                await new Promise(c => setTimeout(c, millis));
            }
            else {
                throw error;
            }
        }
        debugLogger_1.debugLogger.log('install', `extracting archive`);
        debugLogger_1.debugLogger.log('install', `-- zip: ${zipPath}`);
        debugLogger_1.debugLogger.log('install', `-- location: ${browserDirectory}`);
        await extract_zip_1.default(zipPath, { dir: browserDirectory });
        const executablePath = registry.executablePath(browserName);
        debugLogger_1.debugLogger.log('install', `fixing permissions at ${executablePath}`);
        await chmodAsync(executablePath, 0o755);
    }
    catch (e) {
        debugLogger_1.debugLogger.log('install', `FAILED installation ${progressBarName} with error: ${e}`);
        process.exitCode = 1;
        throw e;
    }
    finally {
        if (await existsAsync(zipPath))
            await unlinkAsync(zipPath);
    }
    logPolitely(`${progressBarName} downloaded to ${browserDirectory}`);
    return true;
}
exports.downloadBrowserWithProgressBar = downloadBrowserWithProgressBar;
function toMegabytes(bytes) {
    const mb = bytes / 1024 / 1024;
    return `${Math.round(mb * 10) / 10} Mb`;
}
function downloadFile(url, destinationPath, progressCallback) {
    debugLogger_1.debugLogger.log('install', `running download:`);
    debugLogger_1.debugLogger.log('install', `-- from url: ${url}`);
    debugLogger_1.debugLogger.log('install', `-- to location: ${destinationPath}`);
    let fulfill = ({ error }) => { };
    let downloadedBytes = 0;
    let totalBytes = 0;
    const promise = new Promise(x => { fulfill = x; });
    const request = httpRequest(url, 'GET', response => {
        if (response.statusCode !== 200) {
            const error = new Error(`Download failed: server returned code ${response.statusCode}. URL: ${url}`);
            // consume response data to free up memory
            response.resume();
            fulfill({ error });
            return;
        }
        const file = fs_1.default.createWriteStream(destinationPath);
        file.on('finish', () => fulfill({ error: null }));
        file.on('error', error => fulfill({ error }));
        response.pipe(file);
        totalBytes = parseInt(response.headers['content-length'], 10);
        debugLogger_1.debugLogger.log('install', `-- total bytes: ${totalBytes}`);
        if (progressCallback)
            response.on('data', onData);
    });
    request.on('error', (error) => fulfill({ error }));
    return promise;
    function onData(chunk) {
        downloadedBytes += chunk.length;
        progressCallback(downloadedBytes, totalBytes);
    }
}
function httpRequest(url, method, response) {
    let options = URL.parse(url);
    options.method = method;
    const proxyURL = proxy_from_env_1.getProxyForUrl(url);
    if (proxyURL) {
        if (url.startsWith('http:')) {
            const proxy = URL.parse(proxyURL);
            options = {
                path: options.href,
                host: proxy.hostname,
                port: proxy.port,
            };
        }
        else {
            const parsedProxyURL = URL.parse(proxyURL);
            parsedProxyURL.secureProxy = parsedProxyURL.protocol === 'https:';
            options.agent = new ProxyAgent(parsedProxyURL);
            options.rejectUnauthorized = false;
        }
    }
    const requestCallback = (res) => {
        if (res.statusCode >= 300 && res.statusCode < 400 && res.headers.location)
            httpRequest(res.headers.location, method, response);
        else
            response(res);
    };
    const request = options.protocol === 'https:' ?
        require('https').request(options, requestCallback) :
        require('http').request(options, requestCallback);
    request.end();
    return request;
}
function logPolitely(toBeLogged) {
    const logLevel = process.env.npm_config_loglevel;
    const logLevelDisplay = ['silent', 'error', 'warn'].indexOf(logLevel || '') > -1;
    if (!logLevelDisplay)
        console.log(toBeLogged); // eslint-disable-line no-console
}
exports.logPolitely = logPolitely;
//# sourceMappingURL=browserFetcher.js.map