diff --git a/install.js b/install.js index ffd0624..3c8f5e6 100644 --- a/install.js +++ b/install.js @@ -1,340 +1,43 @@ 'use strict'; // @ts-check -const fs = require('fs'); const helper = require('./lib/chromedriver'); -const axios = require('axios').default; -const mkdirp = require('mkdirp'); -const path = require('path'); -const del = require('del'); -const child_process = require('child_process'); -const os = require('os'); -const url = require('url'); -const https = require('https'); -const extractZip = require('extract-zip'); -const { getChromeVersion } = require('@testim/chrome-version'); -const HttpsProxyAgent = require('https-proxy-agent'); +const utils = require('./lib/install-utils') -const skipDownload = process.env.npm_config_chromedriver_skip_download || process.env.CHROMEDRIVER_SKIP_DOWNLOAD; +let skipDownload = process.env.npm_config_chromedriver_skip_download || process.env.CHROMEDRIVER_SKIP_DOWNLOAD; if (skipDownload === 'true') { console.log('Found CHROMEDRIVER_SKIP_DOWNLOAD variable, skipping installation.'); process.exit(0); } -const libPath = path.join(__dirname, 'lib', 'chromedriver'); let cdnUrl = process.env.npm_config_chromedriver_cdnurl || process.env.CHROMEDRIVER_CDNURL || 'https://chromedriver.storage.googleapis.com'; -const configuredfilePath = process.env.npm_config_chromedriver_filepath || process.env.CHROMEDRIVER_FILEPATH; - // adapt http://chromedriver.storage.googleapis.com/ cdnUrl = cdnUrl.replace(/\/+$/, ''); -const platform = validatePlatform(); -const detect_chromedriver_version = process.env.npm_config_detect_chromedriver_version || process.env.DETECT_CHROMEDRIVER_VERSION; +let detect_chromedriver_version = process.env.npm_config_detect_chromedriver_version || process.env.DETECT_CHROMEDRIVER_VERSION; let chromedriver_version = process.env.npm_config_chromedriver_version || process.env.CHROMEDRIVER_VERSION || helper.version; -let chromedriverBinaryFilePath; -let downloadedFile = ''; +let force_download = process.env.npm_config_chromedriver_force_download === 'true' || process.env.CHROMEDRIVER_FORCE_DOWNLOAD === 'true'; (async function install() { - try { - if (detect_chromedriver_version === 'true') { - // Refer http://chromedriver.chromium.org/downloads/version-selection - const chromeVersion = await getChromeVersion(); - console.log("Your Chrome version is " + chromeVersion); - const chromeVersionWithoutPatch = /^(.*?)\.\d+$/.exec(chromeVersion)[1]; - await getChromeDriverVersion(getRequestOptions(cdnUrl + '/LATEST_RELEASE_' + chromeVersionWithoutPatch)); - console.log("Compatible ChromeDriver version is " + chromedriver_version); - } - if (chromedriver_version === 'LATEST') { - await getChromeDriverVersion(getRequestOptions(`${cdnUrl}/LATEST_RELEASE`)); - } else { - const latestReleaseForVersionMatch = chromedriver_version.match(/LATEST_(\d+)/); - if (latestReleaseForVersionMatch) { - const majorVersion = latestReleaseForVersionMatch[1]; - await getChromeDriverVersion(getRequestOptions(`${cdnUrl}/LATEST_RELEASE_${majorVersion}`)); - } - } - const tmpPath = findSuitableTempDirectory(); - const chromedriverBinaryFileName = process.platform === 'win32' ? 'chromedriver.exe' : 'chromedriver'; - chromedriverBinaryFilePath = path.resolve(tmpPath, chromedriverBinaryFileName); - const chromedriverIsAvailable = await verifyIfChromedriverIsAvailableAndHasCorrectVersion(); - if (!chromedriverIsAvailable) { - console.log('Current existing ChromeDriver binary is unavailable, proceeding with download and extraction.'); - await downloadFile(tmpPath); - await extractDownload(tmpPath); - } - await copyIntoPlace(tmpPath, libPath); - fixFilePermissions(); - console.log('Done. ChromeDriver binary available at', helper.path); - } catch (err) { - console.error('ChromeDriver installation failed', err); - process.exit(1); - } -})(); - -function validatePlatform() { - /** @type string */ - let thePlatform = process.platform; - if (thePlatform === 'linux') { - if (process.arch === 'arm64' || process.arch === 'x64') { - thePlatform += '64'; - } else { - console.log('Only Linux 64 bits supported.'); - process.exit(1); - } - } else if (thePlatform === 'darwin' || thePlatform === 'freebsd') { - if (process.arch === 'x64') { - thePlatform = 'mac64'; - } else { - console.log('Only Mac 64 bits supported.'); - process.exit(1); - } - } else if (thePlatform !== 'win32') { - console.log('Unexpected platform or architecture:', process.platform, process.arch); - process.exit(1); - } - return thePlatform; -} - -async function downloadFile(dirToLoadTo) { - if (detect_chromedriver_version !== 'true' && configuredfilePath) { - downloadedFile = configuredfilePath; - console.log('Using file: ', downloadedFile); - return; - } else { - const fileName = `chromedriver_${platform}.zip`; - const tempDownloadedFile = path.resolve(dirToLoadTo, fileName); - downloadedFile = tempDownloadedFile; - const formattedDownloadUrl = `${cdnUrl}/${chromedriver_version}/${fileName}`; - console.log('Downloading from file: ', formattedDownloadUrl); - console.log('Saving to file:', downloadedFile); - await requestBinary(getRequestOptions(formattedDownloadUrl), downloadedFile); - } -} + let options = { + cdn_url: cdnUrl + }; -function verifyIfChromedriverIsAvailableAndHasCorrectVersion() { - if (!fs.existsSync(chromedriverBinaryFilePath)) - return Promise.resolve(false); - const forceDownload = process.env.npm_config_chromedriver_force_download === 'true' || process.env.CHROMEDRIVER_FORCE_DOWNLOAD === 'true'; - if (forceDownload) - return Promise.resolve(false); - console.log('ChromeDriver binary exists. Validating...'); - const deferred = new Deferred(); - try { - fs.accessSync(chromedriverBinaryFilePath, fs.constants.X_OK); - const cp = child_process.spawn(chromedriverBinaryFilePath, ['--version']); - let str = ''; - cp.stdout.on('data', data => str += data); - cp.on('error', () => deferred.resolve(false)); - cp.on('close', code => { - if (code !== 0) - return deferred.resolve(false); - const parts = str.split(' '); - if (parts.length < 3) - return deferred.resolve(false); - if (parts[1].startsWith(chromedriver_version)) { - console.log(`ChromeDriver is already available at '${chromedriverBinaryFilePath}'.`); - return deferred.resolve(true); - } - deferred.resolve(false); - }); - } - catch (error) { - deferred.resolve(false); + if (force_download) { + options.force_download = "true"; } - return deferred.promise; -} - -function findSuitableTempDirectory() { - const now = Date.now(); - const candidateTmpDirs = [ - process.env.npm_config_tmp, - process.env.XDG_CACHE_HOME, - // Platform specific default, including TMPDIR/TMP/TEMP env - os.tmpdir(), - path.join(process.cwd(), 'tmp') - ]; - - for (let i = 0; i < candidateTmpDirs.length; i++) { - if (!candidateTmpDirs[i]) continue; - // Prevent collision with other versions in the dependency tree - const namespace = chromedriver_version; - const candidatePath = path.join(candidateTmpDirs[i], namespace, 'chromedriver'); - try { - mkdirp.sync(candidatePath, '0777'); - const testFile = path.join(candidatePath, now + '.tmp'); - fs.writeFileSync(testFile, 'test'); - fs.unlinkSync(testFile); - return candidatePath; - } catch (e) { - console.log(candidatePath, 'is not writable:', e.message); - } - } - console.error('Can not find a writable tmp directory, please report issue on https://github.com/giggio/chromedriver/issues/ with as much information as possible.'); - process.exit(1); -} - -function getRequestOptions(downloadPath) { - /** @type import('axios').AxiosRequestConfig */ - const options = { url: downloadPath, method: "GET" }; - const urlParts = url.parse(downloadPath); - const isHttps = urlParts.protocol === 'https:'; - const proxyUrl = isHttps - ? process.env.npm_config_https_proxy - : (process.env.npm_config_proxy || process.env.npm_config_http_proxy); - if (proxyUrl) { - const proxyUrlParts = url.parse(proxyUrl); - options.proxy = { - host: proxyUrlParts.hostname, - port: proxyUrlParts.port ? parseInt(proxyUrlParts.port) : 80, - protocol: proxyUrlParts.protocol - }; - } - - if (isHttps) { - // Use certificate authority settings from npm - let ca = process.env.npm_config_ca; - if (ca) - console.log('Using npmconf ca.'); - if (!ca && process.env.npm_config_cafile) { - try { - ca = fs.readFileSync(process.env.npm_config_cafile, { encoding: 'utf8' }); - } catch (e) { - console.error('Could not read cafile', process.env.npm_config_cafile, e); - } - console.log('Using npmconf cafile.'); - } - - if (proxyUrl) { - console.log('Using workaround for https-url combined with a proxy.'); - const httpsProxyAgentOptions = url.parse(proxyUrl); - // @ts-ignore - httpsProxyAgentOptions.ca = ca; - // @ts-ignore - httpsProxyAgentOptions.rejectUnauthorized = !!process.env.npm_config_strict_ssl; - // @ts-ignore - options.httpsAgent = new HttpsProxyAgent(httpsProxyAgentOptions); - options.proxy = false; + if (detect_chromedriver_version !== 'true') { + if (chromedriver_version === 'LATEST') { + chromedriver_version = await utils.getChromeDriverVersionFromUrl(`${cdnUrl}/LATEST_RELEASE`); } else { - options.httpsAgent = new https.Agent({ - rejectUnauthorized: !!process.env.npm_config_strict_ssl, - ca: ca - }); - } - } - - // Use specific User-Agent - if (process.env.npm_config_user_agent) { - options.headers = { 'User-Agent': process.env.npm_config_user_agent }; - } - - return options; -} - -/** - * - * @param {import('axios').AxiosRequestConfig} requestOptions - */ -async function getChromeDriverVersion(requestOptions) { - console.log('Finding Chromedriver version.'); - const response = await axios(requestOptions); - chromedriver_version = response.data.trim(); - console.log(`Chromedriver version is ${chromedriver_version}.`); -} - -/** - * - * @param {import('axios').AxiosRequestConfig} requestOptions - * @param {string} filePath - */ -async function requestBinary(requestOptions, filePath) { - const outFile = fs.createWriteStream(filePath); - let response; - try { - response = await axios.create(requestOptions)({ responseType: 'stream' }); - } catch (error) { - if (error && error.response) { - if (error.response.status) - console.error('Error status code:', error.response.status); - if (error.response.data) { - error.response.data.on('data', data => console.error(data.toString('utf8'))); - await new Promise((resolve) => { - error.response.data.on('finish', resolve); - error.response.data.on('error', resolve); - }); + let latestReleaseForVersionMatch = chromedriver_version.match(/LATEST_(\d+)/); + if (latestReleaseForVersionMatch) { + let majorVersion = latestReleaseForVersionMatch[1]; + chromedriver_version = await utils.getChromeDriverVersionFromUrl(`${cdnUrl}/LATEST_RELEASE_${majorVersion}`); } } - throw new Error('Error with http(s) request: ' + error); + options.download_version = chromedriver_version; } - let count = 0; - let notifiedCount = 0; - response.data.on('data', data => { - count += data.length; - if ((count - notifiedCount) > 1024 * 1024) { - console.log('Received ' + Math.floor(count / 1024) + 'K...'); - notifiedCount = count; - } - }); - response.data.on('end', () => console.log('Received ' + Math.floor(count / 1024) + 'K total.')); - const pipe = response.data.pipe(outFile); - await new Promise((resolve, reject) => { - pipe.on('finish', resolve); - pipe.on('error', reject); - }); -} - -async function extractDownload(dirToExtractTo) { - if (path.extname(downloadedFile) !== '.zip') { - fs.copyFileSync(downloadedFile, chromedriverBinaryFilePath); - console.log('Skipping zip extraction - binary file found.'); - return; - } - console.log(`Extracting zip contents to ${dirToExtractTo}.`); - try { - await extractZip(path.resolve(downloadedFile), { dir: dirToExtractTo }); - } catch (error) { - throw new Error('Error extracting archive: ' + error); - } -} - -async function copyIntoPlace(originPath, targetPath) { - await del(targetPath); - console.log("Copying to target path", targetPath); - fs.mkdirSync(targetPath); - - // Look for the extracted directory, so we can rename it. - const files = fs.readdirSync(originPath); - const promises = files.map(name => { - return new Promise((resolve) => { - const file = path.join(originPath, name); - const reader = fs.createReadStream(file); - const targetFile = path.join(targetPath, name); - const writer = fs.createWriteStream(targetFile); - writer.on("close", () => resolve()); - reader.pipe(writer); - }); - }); - await Promise.all(promises); -} - -function fixFilePermissions() { - // Check that the binary is user-executable and fix it if it isn't (problems with unzip library) - if (process.platform != 'win32') { - const stat = fs.statSync(helper.path); - // 64 == 0100 (no octal literal in strict mode) - if (!(stat.mode & 64)) { - console.log('Fixing file permissions.'); - fs.chmodSync(helper.path, '755'); - } - } -} - -function Deferred() { - this.resolve = null; - this.reject = null; - this.promise = new Promise(function (resolve, reject) { - this.resolve = resolve; - this.reject = reject; - }.bind(this)); - Object.freeze(this); -} + await (helper.download(options)) +})(); \ No newline at end of file diff --git a/lib/chromedriver.js b/lib/chromedriver.js index c680d40..4e3602b 100644 --- a/lib/chromedriver.js +++ b/lib/chromedriver.js @@ -1,24 +1,40 @@ const fs = require('fs'); const path = require('path'); const tcpPortUsed = require('tcp-port-used'); +const utils = require('./install-utils'); +const { getChromeVersion } = require('@testim/chrome-version'); + +// Default to Google's CDN +let cdnUrl = process.env.npm_config_chromedriver_cdnurl || process.env.CHROMEDRIVER_CDNURL || 'https://chromedriver.storage.googleapis.com'; +// adapt http://chromedriver.storage.googleapis.com/ +cdnUrl = cdnUrl.replace(/\/+$/, ''); + +const libPath = path.join(__dirname, 'chromedriver'); + function getPortFromArgs(args) { let port = 9515; - if (!args){ + if (!args) { return port; } const portRegexp = /--port=(\d*)/; const portArg = args.find(function (arg) { return portRegexp.test(arg); }); - if (portArg){ + if (portArg) { port = parseInt(portRegexp.exec(portArg)[1]); } return port; } + process.env.PATH = path.join(__dirname, 'chromedriver') + path.delimiter + process.env.PATH; -exports.path = process.platform === 'win32' ? path.join(__dirname, 'chromedriver', 'chromedriver.exe') : path.join(__dirname, 'chromedriver', 'chromedriver'); + +exports.path = process.platform === 'win32' ? + path.join(__dirname, 'chromedriver', 'chromedriver.exe') : + path.join(__dirname, 'chromedriver', 'chromedriver'); + exports.version = '84.0.4147.30'; -exports.start = function(args, returnPromise) { + +exports.start = function (args, returnPromise) { let command = exports.path; if (!fs.existsSync(command)) { console.log('Could not find chromedriver in default path: ', command); @@ -40,8 +56,77 @@ exports.start = function(args, returnPromise) { return cp; }); }; + exports.stop = function () { - if (exports.defaultInstance != null){ + if (exports.defaultInstance != null) { exports.defaultInstance.kill(); } }; + +exports.download = async function (options) { + if (!options) { + // this allows us to handle non-existent properties more easily + options = {} + } + // allow for a custom cdn url to be set + if (options.cdn_url) { + cdnUrl = options.cdn_url; + } + // adapt http://chromedriver.storage.googleapis.com/ + cdnUrl = cdnUrl.replace(/\/+$/, ''); + try { + // allow for a version to be picked, but default to current installed version + let chromeVersion; + if (!options.download_version) { + chromeVersion = await getChromeVersion(); + console.log("Installed Chrome version is " + chromeVersion); + } else { + chromeVersion = options.download_version; + console.log("Selected version is " + chromeVersion); + } + + let chromeVersionWithoutPatch; + // allow both full version and major version as input + if ((chromeVersion + "").includes(".")) { + chromeVersionWithoutPatch = /^(.*?)\.\d+$/.exec(chromeVersion)[1]; + } else { + chromeVersionWithoutPatch = chromeVersion; + } + chromedriver_version = await utils.getChromeDriverVersionFromUrl(`${cdnUrl}/LATEST_RELEASE_${chromeVersionWithoutPatch}`); + console.log("Compatible ChromeDriver version is " + chromedriver_version); + + let latestReleaseForVersionMatch = chromedriver_version.match(/LATEST_(\d+)/); + if (latestReleaseForVersionMatch) { + let majorVersion = latestReleaseForVersionMatch[1]; + chromedriver_version = await utils.getChromeDriverVersionFromUrl(`${cdnUrl}/LATEST_RELEASE_${majorVersion}`); + } + let tmpPath = utils.findSuitableTempDirectory(chromedriver_version); + let chromedriverIsAvailable = await utils.verifyIfChromedriverIsAvailableAndHasCorrectVersion(chromedriver_version); + if (!chromedriverIsAvailable || options.force_download === "true") { + console.log('Current existing ChromeDriver binary is unavailable, proceeding with download and extraction.'); + await utils.downloadChromedriver(cdnUrl, chromedriver_version, tmpPath); + await utils.extractDownload(tmpPath); + } + await utils.copyIntoPlace(tmpPath, libPath); + utils.fixFilePermissions(); + console.log('Done. ChromeDriver binary available at', exports.path); + } catch (err) { + console.error('ChromeDriver installation failed', err); + process.exit(1); + } +}; + +exports.is_proper_version_installed = async function () { + let chromeVersion = await getChromeVersion(); + let chromeVersionWithoutPatch = /^(.*?)\.\d+$/.exec(chromeVersion)[1]; + + chromedriver_version = await utils.getChromeDriverVersionFromUrl(`${cdnUrl}/LATEST_RELEASE_${chromeVersionWithoutPatch}`); + + let latestReleaseForVersionMatch = chromedriver_version.match(/LATEST_(\d+)/); + if (latestReleaseForVersionMatch) { + let majorVersion = latestReleaseForVersionMatch[1]; + chromedriver_version = await utils.getChromeDriverVersionFromUrl(`${cdnUrl}/LATEST_RELEASE_${majorVersion}`); + } + let chromedriverIsAvailable = await utils.verifyIfChromedriverIsAvailableAndHasCorrectVersion(chromedriver_version); + return chromedriverIsAvailable; +} \ No newline at end of file diff --git a/lib/install-utils.js b/lib/install-utils.js new file mode 100644 index 0000000..26808da --- /dev/null +++ b/lib/install-utils.js @@ -0,0 +1,292 @@ +'use strict'; +// @ts-check + +const fs = require('fs'); +const helper = require('./chromedriver'); +const axios = require('axios').default; +const mkdirp = require('mkdirp'); +const path = require('path'); +const del = require('del'); +const child_process = require('child_process'); +const os = require('os'); +const url = require('url'); +const https = require('https'); +const extractZip = require('extract-zip'); +const HttpsProxyAgent = require('https-proxy-agent'); + +const platform = validatePlatform(); +const configuredfilePath = process.env.npm_config_chromedriver_filepath || process.env.CHROMEDRIVER_FILEPATH; +const detect_chromedriver_version = process.env.npm_config_detect_chromedriver_version || process.env.DETECT_CHROMEDRIVER_VERSION; +let downloadedFile = ''; + +function validatePlatform() { + /** @type string */ + let thePlatform = process.platform; + if (thePlatform === 'linux') { + if (process.arch === 'arm64' || process.arch === 'x64') { + thePlatform += '64'; + } else { + console.log('Only Linux 64 bits supported.'); + process.exit(1); + } + } else if (thePlatform === 'darwin' || thePlatform === 'freebsd') { + if (process.arch === 'x64') { + thePlatform = 'mac64'; + } else { + console.log('Only Mac 64 bits supported.'); + process.exit(1); + } + } else if (thePlatform !== 'win32') { + console.log('Unexpected platform or architecture:', process.platform, process.arch); + process.exit(1); + } + return thePlatform; +} + +exports.downloadChromedriver = async function (cdnUrl, chromedriver_version, dirToLoadTo) { + if (detect_chromedriver_version !== 'true' && configuredfilePath) { + downloadedFile = configuredfilePath; + console.log('Using file: ', downloadedFile); + return; + } else { + const fileName = `chromedriver_${platform}.zip`; + const tempDownloadedFile = path.resolve(dirToLoadTo, fileName); + downloadedFile = tempDownloadedFile; + const formattedDownloadUrl = `${cdnUrl}/${chromedriver_version}/${fileName}`; + console.log('Downloading from file: ', formattedDownloadUrl); + console.log('Saving to file:', downloadedFile); + await exports.requestBinary(exports.getRequestOptions(formattedDownloadUrl), downloadedFile); + } +} + +exports.verifyIfChromedriverIsAvailableAndHasCorrectVersion = function (chromedriver_version) { + let tmpPath = exports.findSuitableTempDirectory(chromedriver_version); + let chromedriverBinaryFileName = process.platform === 'win32' ? 'chromedriver.exe' : 'chromedriver'; + let chromedriverBinaryFilePath = path.resolve(tmpPath, chromedriverBinaryFileName); + if (!fs.existsSync(chromedriverBinaryFilePath)) + return Promise.resolve(false); + console.log('ChromeDriver binary exists. Validating...'); + const deferred = new Deferred(); + try { + fs.accessSync(chromedriverBinaryFilePath, fs.constants.X_OK); + const cp = child_process.spawn(chromedriverBinaryFilePath, ['--version']); + let str = ''; + cp.stdout.on('data', data => str += data); + cp.on('error', () => deferred.resolve(false)); + cp.on('close', code => { + if (code !== 0) + return deferred.resolve(false); + const parts = str.split(' '); + if (parts.length < 3) + return deferred.resolve(false); + if (parts[1].startsWith(chromedriver_version)) { + console.log(`ChromeDriver is already available at '${chromedriverBinaryFilePath}'.`); + return deferred.resolve(true); + } + deferred.resolve(false); + }); + } + catch (error) { + deferred.resolve(false); + } + return deferred.promise; +} + +exports.findSuitableTempDirectory = function (chromedriver_version) { + const now = Date.now(); + const candidateTmpDirs = [ + process.env.npm_config_tmp, + process.env.XDG_CACHE_HOME, + // Platform specific default, including TMPDIR/TMP/TEMP env + os.tmpdir(), + path.join(process.cwd(), 'tmp') + ]; + + for (let i = 0; i < candidateTmpDirs.length; i++) { + if (!candidateTmpDirs[i]) continue; + // Prevent collision with other versions in the dependency tree + const namespace = chromedriver_version; + const candidatePath = path.join(candidateTmpDirs[i], namespace, 'chromedriver'); + try { + mkdirp.sync(candidatePath, '0777'); + const testFile = path.join(candidatePath, now + '.tmp'); + fs.writeFileSync(testFile, 'test'); + fs.unlinkSync(testFile); + return candidatePath; + } catch (e) { + console.log(candidatePath, 'is not writable:', e.message); + } + } + console.error('Can not find a writable tmp directory, please report issue on https://github.com/giggio/chromedriver/issues/ with as much information as possible.'); + process.exit(1); +} + +exports.getRequestOptions = function (downloadPath) { + /** @type import('axios').AxiosRequestConfig */ + const options = { url: downloadPath, method: "GET" }; + const urlParts = url.parse(downloadPath); + const isHttps = urlParts.protocol === 'https:'; + const proxyUrl = isHttps + ? process.env.npm_config_https_proxy + : (process.env.npm_config_proxy || process.env.npm_config_http_proxy); + if (proxyUrl) { + const proxyUrlParts = url.parse(proxyUrl); + options.proxy = { + host: proxyUrlParts.hostname, + port: proxyUrlParts.port ? parseInt(proxyUrlParts.port) : 80, + protocol: proxyUrlParts.protocol + }; + } + + if (isHttps) { + // Use certificate authority settings from npm + let ca = process.env.npm_config_ca; + if (ca) + console.log('Using npmconf ca.'); + + if (!ca && process.env.npm_config_cafile) { + try { + ca = fs.readFileSync(process.env.npm_config_cafile, { encoding: 'utf8' }); + } catch (e) { + console.error('Could not read cafile', process.env.npm_config_cafile, e); + } + console.log('Using npmconf cafile.'); + } + + if (proxyUrl) { + console.log('Using workaround for https-url combined with a proxy.'); + const httpsProxyAgentOptions = url.parse(proxyUrl); + // @ts-ignore + httpsProxyAgentOptions.ca = ca; + // @ts-ignore + httpsProxyAgentOptions.rejectUnauthorized = !!process.env.npm_config_strict_ssl; + // @ts-ignore + options.httpsAgent = new HttpsProxyAgent(httpsProxyAgentOptions); + options.proxy = false; + } else { + options.httpsAgent = new https.Agent({ + rejectUnauthorized: !!process.env.npm_config_strict_ssl, + ca: ca + }); + } + } + + // Use specific User-Agent + if (process.env.npm_config_user_agent) { + options.headers = { 'User-Agent': process.env.npm_config_user_agent }; + } + + return options; +} + +/** + * + * @param {import('axios').AxiosRequestConfig} requestOptions + */ +exports.getChromeDriverVersion = async function (requestOptions) { + console.log('Finding Chromedriver version.'); + const response = await axios(requestOptions); + return response.data.trim(); +} + +exports.getChromeDriverVersionFromUrl = async function (downloadPath) { + let requestOptions = exports.getRequestOptions(downloadPath) + return await exports.getChromeDriverVersion(requestOptions) +} + +/** + * + * @param {import('axios').AxiosRequestConfig} requestOptions + * @param {string} filePath + */ +exports.requestBinary = async function (requestOptions, filePath) { + const outFile = fs.createWriteStream(filePath); + let response; + try { + response = await axios.create(requestOptions)({ responseType: 'stream' }); + } catch (error) { + if (error && error.response) { + if (error.response.status) + console.error('Error status code:', error.response.status); + if (error.response.data) { + error.response.data.on('data', data => console.error(data.toString('utf8'))); + await new Promise((resolve) => { + error.response.data.on('finish', resolve); + error.response.data.on('error', resolve); + }); + } + } + throw new Error('Error with http(s) request: ' + error); + } + let count = 0; + let notifiedCount = 0; + response.data.on('data', data => { + count += data.length; + if ((count - notifiedCount) > 1024 * 1024) { + console.log('Received ' + Math.floor(count / 1024) + 'K...'); + notifiedCount = count; + } + }); + response.data.on('end', () => console.log('Received ' + Math.floor(count / 1024) + 'K total.')); + const pipe = response.data.pipe(outFile); + await new Promise((resolve, reject) => { + pipe.on('finish', resolve); + pipe.on('error', reject); + }); +} + +exports.extractDownload = async function (dirToExtractTo) { + if (path.extname(downloadedFile) !== '.zip') { + fs.copyFileSync(downloadedFile, chromedriverBinaryFilePath); + console.log('Skipping zip extraction - binary file found.'); + return; + } + console.log(`Extracting zip contents to ${dirToExtractTo}.`); + try { + await extractZip(path.resolve(downloadedFile), { dir: dirToExtractTo }); + } catch (error) { + throw new Error('Error extracting archive: ' + error); + } +} + +exports.copyIntoPlace = async function (originPath, targetPath) { + await del(targetPath); + console.log("Copying to target path", targetPath); + fs.mkdirSync(targetPath); + + // Look for the extracted directory, so we can rename it. + const files = fs.readdirSync(originPath); + const promises = files.map(name => { + return new Promise((resolve) => { + const file = path.join(originPath, name); + const reader = fs.createReadStream(file); + const targetFile = path.join(targetPath, name); + const writer = fs.createWriteStream(targetFile); + writer.on("close", () => resolve()); + reader.pipe(writer); + }); + }); + await Promise.all(promises); +} + +exports.fixFilePermissions = function () { + // Check that the binary is user-executable and fix it if it isn't (problems with unzip library) + if (process.platform != 'win32') { + const stat = fs.statSync(helper.path); + // 64 == 0100 (no octal literal in strict mode) + if (!(stat.mode & 64)) { + console.log('Fixing file permissions.'); + fs.chmodSync(helper.path, '755'); + } + } +} + +function Deferred() { + this.resolve = null; + this.reject = null; + this.promise = new Promise(function (resolve, reject) { + this.resolve = resolve; + this.reject = reject; + }.bind(this)); + Object.freeze(this); +}