/**
 * @license
 * Copyright 2023 Google Inc.
 * SPDX-License-Identifier: Apache-2.0
 */
import { createHash } from 'node:crypto';
import { createWriteStream, unlinkSync } from 'node:fs';
import * as http from 'node:http';
import * as https from 'node:https';
import { URL, urlToHttpOptions } from 'node:url';
export async function headHttpRequest(url) {
    return await new Promise(resolve => {
        httpRequest(url, 'HEAD', response => {
            // consume response data free node process
            response.resume();
            resolve(response.statusCode === 200);
        }, false)
            .then(request => {
            request.on('error', () => {
                resolve(false);
            });
        })
            .catch(() => {
            resolve(false);
        });
    });
}
export async function httpRequest(url, method, response, keepAlive = true) {
    let agent;
    try {
        const { ProxyAgent } = await import('proxy-agent');
        agent = new ProxyAgent();
    }
    catch {
        // Standard Node.js agents will be used.
    }
    const options = {
        protocol: url.protocol,
        hostname: url.hostname,
        port: url.port,
        path: url.pathname + url.search,
        method,
        headers: keepAlive ? { Connection: 'keep-alive' } : undefined,
        auth: urlToHttpOptions(url).auth,
        agent,
    };
    const requestCallback = (res) => {
        if (res.statusCode &&
            res.statusCode >= 300 &&
            res.statusCode < 400 &&
            res.headers.location) {
            void httpRequest(new URL(res.headers.location), method, response);
            // consume response data to free up memory
            // And prevents the connection from being kept alive
            res.resume();
        }
        else {
            response(res);
        }
    };
    const request = options.protocol === 'https:'
        ? https.request(options, requestCallback)
        : http.request(options, requestCallback);
    request.end();
    return request;
}
class HashVerifier {
    #hash = createHash('sha256');
    update(chunk) {
        this.#hash.update(chunk);
    }
    verify(url, destinationPath, expectedHash) {
        const actualHash = this.#hash.digest('hex');
        if (actualHash !== expectedHash.toLowerCase()) {
            try {
                unlinkSync(destinationPath);
            }
            catch { }
            throw new Error(`Integrity check failed for downloaded browser archive.\n` +
                `  URL:      ${url}\n` +
                `  Expected: ${expectedHash.toLowerCase()}\n` +
                `  Actual:   ${actualHash}`);
        }
    }
}
/**
 * @internal
 */
export function downloadFile(url, destinationPath, progressCallback, expectedHash) {
    return new Promise(async (resolve, reject) => {
        let downloadedBytes = 0;
        let totalBytes = 0;
        const verifier = expectedHash ? new HashVerifier() : null;
        try {
            const request = await 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();
                    reject(error);
                    return;
                }
                const file = createWriteStream(destinationPath);
                file.on('close', () => {
                    if (verifier && expectedHash) {
                        try {
                            verifier.verify(url, destinationPath, expectedHash);
                        }
                        catch (err) {
                            reject(err);
                            return;
                        }
                    }
                    return resolve();
                });
                file.on('error', error => {
                    return reject(error);
                });
                totalBytes = parseInt(response.headers['content-length'], 10);
                response.on('data', (chunk) => {
                    downloadedBytes += chunk.length;
                    verifier?.update(chunk);
                    if (progressCallback) {
                        progressCallback(downloadedBytes, totalBytes);
                    }
                });
                response.pipe(file);
            });
            request.on('error', error => {
                return reject(error);
            });
        }
        catch (error) {
            reject(error);
        }
    });
}
export async function getJSON(url) {
    const text = await getText(url);
    try {
        return JSON.parse(text);
    }
    catch {
        throw new Error('Could not parse JSON from ' + url.toString());
    }
}
export function getText(url) {
    return new Promise(async (resolve, reject) => {
        try {
            const request = await httpRequest(url, 'GET', response => {
                let data = '';
                if (response.statusCode && response.statusCode >= 400) {
                    return reject(new Error(`Got status code ${response.statusCode}`));
                }
                response.on('data', chunk => {
                    data += chunk;
                });
                response.on('end', () => {
                    try {
                        return resolve(String(data));
                    }
                    catch {
                        return reject(new Error(`Failed to read text response from ${url}`));
                    }
                });
            }, false);
            request.on('error', err => {
                reject(err);
            });
        }
        catch (err) {
            reject(err);
        }
    });
}
//# sourceMappingURL=httpUtil.js.map