1
1
'use strict' ;
2
+ // @ts -check
2
3
3
- const extractZip = require ( 'extract-zip' ) ;
4
4
const fs = require ( 'fs' ) ;
5
5
const helper = require ( './lib/chromedriver' ) ;
6
- const request = require ( 'request' ) ;
6
+ const axios = require ( 'axios' ) . default ;
7
7
const mkdirp = require ( 'mkdirp' ) ;
8
8
const path = require ( 'path' ) ;
9
9
const del = require ( 'del' ) ;
10
10
const child_process = require ( 'child_process' ) ;
11
11
const os = require ( 'os' ) ;
12
+ const url = require ( 'url' ) ;
13
+ const https = require ( 'https' ) ;
14
+ const { promisify } = require ( 'util' ) ;
15
+ const extractZip = promisify ( require ( 'extract-zip' ) ) ;
12
16
const { getChromeVersion } = require ( '@testim/chrome-version' ) ;
13
17
14
18
const skipDownload = process . env . npm_config_chromedriver_skip_download || process . env . CHROMEDRIVER_SKIP_DOWNLOAD ;
@@ -23,115 +27,110 @@ const configuredfilePath = process.env.npm_config_chromedriver_filepath || proce
23
27
24
28
// adapt http://chromedriver.storage.googleapis.com/
25
29
cdnUrl = cdnUrl . replace ( / \/ + $ / , '' ) ;
26
- let platform = process . platform ;
27
-
30
+ const platform = validatePlatform ( ) ;
28
31
const detect_chromedriver_version = process . env . npm_config_detect_chromedriver_version || process . env . DETECT_CHROMEDRIVER_VERSION ;
29
32
let chromedriver_version = process . env . npm_config_chromedriver_version || process . env . CHROMEDRIVER_VERSION || helper . version ;
30
- if ( platform === 'linux' ) {
31
- if ( process . arch === 'arm64' || process . arch === 'x64' ) {
32
- platform += '64' ;
33
- } else {
34
- console . log ( 'Only Linux 64 bits supported.' ) ;
35
- process . exit ( 1 ) ;
36
- }
37
- } else if ( platform === 'darwin' || platform === 'freebsd' ) {
38
- if ( process . arch === 'x64' ) {
39
- // @ts -ignore
40
- platform = 'mac64' ;
41
- } else {
42
- console . log ( 'Only Mac 64 bits supported.' ) ;
43
- process . exit ( 1 ) ;
44
- }
45
- } else if ( platform !== 'win32' ) {
46
- console . log ( 'Unexpected platform or architecture:' , process . platform , process . arch ) ;
47
- process . exit ( 1 ) ;
48
- }
49
- let tmpPath ;
50
- const chromedriverBinaryFileName = process . platform === 'win32' ? 'chromedriver.exe' : 'chromedriver' ;
51
33
let chromedriverBinaryFilePath ;
52
34
let downloadedFile = '' ;
53
35
54
- Promise . resolve ( ) . then ( function ( ) {
55
- if ( detect_chromedriver_version === 'true' ) {
56
- // Refer http://chromedriver.chromium.org/downloads/version-selection
57
- return getChromeVersion ( ) . then ( function ( chromeVersion ) {
36
+ ( async function install ( ) {
37
+ try {
38
+ if ( detect_chromedriver_version === 'true' ) {
39
+ // Refer http://chromedriver.chromium.org/downloads/version-selection
40
+ const chromeVersion = await getChromeVersion ( ) ;
58
41
console . log ( "Your Chrome version is " + chromeVersion ) ;
59
42
const chromeVersionWithoutPatch = / ^ ( .* ?) \. \d + $ / . exec ( chromeVersion ) [ 1 ] ;
60
- return getChromeDriverVersion ( getRequestOptions ( cdnUrl + '/LATEST_RELEASE_' + chromeVersionWithoutPatch ) ) ;
61
- } ) . then ( function ( ) {
43
+ await getChromeDriverVersion ( getRequestOptions ( cdnUrl + '/LATEST_RELEASE_' + chromeVersionWithoutPatch ) ) ;
62
44
console . log ( "Compatible ChromeDriver version is " + chromedriver_version ) ;
63
- } ) ;
64
- }
65
- if ( chromedriver_version === 'LATEST' ) {
66
- return getChromeDriverVersion ( getRequestOptions ( `${ cdnUrl } /LATEST_RELEASE` ) ) ;
67
- } else {
68
- const latestReleaseForVersionMatch = chromedriver_version . match ( / L A T E S T _ ( \d + ) / ) ;
69
- if ( latestReleaseForVersionMatch ) {
70
- const majorVersion = latestReleaseForVersionMatch [ 1 ] ;
71
- return getChromeDriverVersion ( getRequestOptions ( `${ cdnUrl } /LATEST_RELEASE_${ majorVersion } ` ) ) ;
72
45
}
73
- }
74
- } )
75
- . then ( ( ) => {
76
- tmpPath = findSuitableTempDirectory ( ) ;
46
+ if ( chromedriver_version === 'LATEST' ) {
47
+ await getChromeDriverVersion ( getRequestOptions ( `${ cdnUrl } /LATEST_RELEASE` ) ) ;
48
+ } else {
49
+ const latestReleaseForVersionMatch = chromedriver_version . match ( / L A T E S T _ ( \d + ) / ) ;
50
+ if ( latestReleaseForVersionMatch ) {
51
+ const majorVersion = latestReleaseForVersionMatch [ 1 ] ;
52
+ await getChromeDriverVersion ( getRequestOptions ( `${ cdnUrl } /LATEST_RELEASE_${ majorVersion } ` ) ) ;
53
+ }
54
+ }
55
+ const tmpPath = findSuitableTempDirectory ( ) ;
56
+ const chromedriverBinaryFileName = process . platform === 'win32' ? 'chromedriver.exe' : 'chromedriver' ;
77
57
chromedriverBinaryFilePath = path . resolve ( tmpPath , chromedriverBinaryFileName ) ;
78
- } )
79
- . then ( verifyIfChromedriverIsAvailableAndHasCorrectVersion )
80
- . then ( chromedriverIsAvailable => {
81
- if ( chromedriverIsAvailable ) return ;
82
- console . log ( 'Current existing ChromeDriver binary is unavailable, proceeding with download and extraction.' ) ;
83
- return downloadFile ( ) . then ( extractDownload ) ;
84
- } )
85
- . then ( ( ) => copyIntoPlace ( tmpPath , libPath ) )
86
- . then ( fixFilePermissions )
87
- . then ( ( ) => console . log ( 'Done. ChromeDriver binary available at' , helper . path ) )
88
- . catch ( function ( err ) {
58
+ const chromedriverIsAvailable = await verifyIfChromedriverIsAvailableAndHasCorrectVersion ( ) ;
59
+ if ( ! chromedriverIsAvailable ) {
60
+ console . log ( 'Current existing ChromeDriver binary is unavailable, proceeding with download and extraction.' ) ;
61
+ await downloadFile ( tmpPath ) ;
62
+ await extractDownload ( tmpPath ) ;
63
+ }
64
+ await copyIntoPlace ( tmpPath , libPath ) ;
65
+ fixFilePermissions ( ) ;
66
+ console . log ( 'Done. ChromeDriver binary available at' , helper . path ) ;
67
+ } catch ( err ) {
89
68
console . error ( 'ChromeDriver installation failed' , err ) ;
90
69
process . exit ( 1 ) ;
91
- } ) ;
70
+ }
71
+ } ) ( ) ;
72
+
73
+ function validatePlatform ( ) {
74
+ /** @type string */
75
+ let thePlatform = process . platform ;
76
+ if ( thePlatform === 'linux' ) {
77
+ if ( process . arch === 'arm64' || process . arch === 'x64' ) {
78
+ thePlatform += '64' ;
79
+ } else {
80
+ console . log ( 'Only Linux 64 bits supported.' ) ;
81
+ process . exit ( 1 ) ;
82
+ }
83
+ } else if ( thePlatform === 'darwin' || thePlatform === 'freebsd' ) {
84
+ if ( process . arch === 'x64' ) {
85
+ thePlatform = 'mac64' ;
86
+ } else {
87
+ console . log ( 'Only Mac 64 bits supported.' ) ;
88
+ process . exit ( 1 ) ;
89
+ }
90
+ } else if ( thePlatform !== 'win32' ) {
91
+ console . log ( 'Unexpected platform or architecture:' , process . platform , process . arch ) ;
92
+ process . exit ( 1 ) ;
93
+ }
94
+ return thePlatform ;
95
+ }
92
96
93
- function downloadFile ( ) {
97
+ async function downloadFile ( dirToLoadTo ) {
94
98
if ( detect_chromedriver_version !== 'true' && configuredfilePath ) {
95
99
downloadedFile = configuredfilePath ;
96
100
console . log ( 'Using file: ' , downloadedFile ) ;
97
- return Promise . resolve ( ) ;
101
+ return ;
98
102
} else {
99
103
const fileName = `chromedriver_${ platform } .zip` ;
100
- const tempDownloadedFile = path . resolve ( tmpPath , fileName ) ;
104
+ const tempDownloadedFile = path . resolve ( dirToLoadTo , fileName ) ;
101
105
downloadedFile = tempDownloadedFile ;
102
106
const formattedDownloadUrl = `${ cdnUrl } /${ chromedriver_version } /${ fileName } ` ;
103
107
console . log ( 'Downloading from file: ' , formattedDownloadUrl ) ;
104
108
console . log ( 'Saving to file:' , downloadedFile ) ;
105
- return requestBinary ( getRequestOptions ( formattedDownloadUrl ) , downloadedFile ) ;
109
+ await requestBinary ( getRequestOptions ( formattedDownloadUrl ) , downloadedFile ) ;
106
110
}
107
111
}
108
112
109
113
function verifyIfChromedriverIsAvailableAndHasCorrectVersion ( ) {
110
114
if ( ! fs . existsSync ( chromedriverBinaryFilePath ) )
111
- return false ;
115
+ return Promise . resolve ( false ) ;
112
116
const forceDownload = process . env . npm_config_chromedriver_force_download === 'true' || process . env . CHROMEDRIVER_FORCE_DOWNLOAD === 'true' ;
113
117
if ( forceDownload )
114
- return false ;
118
+ return Promise . resolve ( false ) ;
115
119
console . log ( 'ChromeDriver binary exists. Validating...' ) ;
116
120
const deferred = new Deferred ( ) ;
117
121
try {
118
122
fs . accessSync ( chromedriverBinaryFilePath , fs . constants . X_OK ) ;
119
123
const cp = child_process . spawn ( chromedriverBinaryFilePath , [ '--version' ] ) ;
120
124
let str = '' ;
121
- cp . stdout . on ( 'data' , function ( data ) {
122
- str += data ;
123
- } ) ;
124
- cp . on ( 'error' , function ( ) {
125
- deferred . resolve ( false ) ;
126
- } ) ;
127
- cp . on ( 'close' , function ( code ) {
125
+ cp . stdout . on ( 'data' , data => str += data ) ;
126
+ cp . on ( 'error' , ( ) => deferred . resolve ( false ) ) ;
127
+ cp . on ( 'close' , code => {
128
128
if ( code !== 0 )
129
129
return deferred . resolve ( false ) ;
130
130
const parts = str . split ( ' ' ) ;
131
131
if ( parts . length < 3 )
132
132
return deferred . resolve ( false ) ;
133
133
if ( parts [ 1 ] . startsWith ( chromedriver_version ) ) {
134
- console . log ( str ) ;
135
134
console . log ( `ChromeDriver is already available at '${ chromedriverBinaryFilePath } '.` ) ;
136
135
return deferred . resolve ( true ) ;
137
136
}
@@ -169,59 +168,46 @@ function findSuitableTempDirectory() {
169
168
console . log ( candidatePath , 'is not writable:' , e . message ) ;
170
169
}
171
170
}
172
-
173
171
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.' ) ;
174
172
process . exit ( 1 ) ;
175
173
}
176
174
177
-
178
175
function getRequestOptions ( downloadPath ) {
179
- const options = { uri : downloadPath , method : 'GET' } ;
180
- const protocol = options . uri . substring ( 0 , options . uri . indexOf ( '//' ) ) ;
181
- const proxyUrl = protocol === 'https:'
176
+ /** @type import('axios').AxiosRequestConfig */
177
+ const options = { url : downloadPath , method : "GET" } ;
178
+ const urlParts = url . parse ( downloadPath ) ;
179
+ const isHttps = urlParts . protocol === 'https:' ;
180
+ const proxyUrl = isHttps
182
181
? process . env . npm_config_https_proxy
183
182
: ( process . env . npm_config_proxy || process . env . npm_config_http_proxy ) ;
184
183
if ( proxyUrl ) {
185
- options . proxy = proxyUrl ;
186
- }
187
-
188
- options . strictSSL = ! ! process . env . npm_config_strict_ssl ;
189
-
190
- // Use certificate authority settings from npm
191
- let ca = process . env . npm_config_ca ;
192
-
193
- // Parse ca string like npm does
194
- if ( ca && ca . match ( / ^ " .* " $ / ) ) {
195
- try {
196
- ca = JSON . parse ( ca . trim ( ) ) ;
197
- } catch ( e ) {
198
- console . error ( 'Could not parse ca string' , process . env . npm_config_ca , e ) ;
199
- }
184
+ const proxyUrlParts = url . parse ( proxyUrl ) ;
185
+ options . proxy = {
186
+ host : proxyUrlParts . hostname ,
187
+ port : proxyUrlParts . port ? parseInt ( proxyUrlParts . port ) : 80 ,
188
+ protocol : proxyUrlParts . protocol
189
+ } ;
200
190
}
201
191
202
- if ( ! ca && process . env . npm_config_cafile ) {
203
- try {
204
- ca = fs . readFileSync ( process . env . npm_config_cafile , { encoding : 'utf8' } )
205
- . split ( / \n (? = - - - - - B E G I N C E R T I F I C A T E - - - - - ) / g) ;
206
-
207
- // Comments at the beginning of the file result in the first
208
- // item not containing a certificate - in this case the
209
- // download will fail
210
- if ( ca . length > 0 && ! / - - - - - B E G I N C E R T I F I C A T E - - - - - / . test ( ca [ 0 ] ) ) {
211
- ca . shift ( ) ;
192
+ if ( isHttps ) {
193
+ // Use certificate authority settings from npm
194
+ let ca = process . env . npm_config_ca ;
195
+ if ( ca )
196
+ console . log ( 'Using npmconf ca.' ) ;
197
+
198
+ if ( ! ca && process . env . npm_config_cafile ) {
199
+ try {
200
+ ca = fs . readFileSync ( process . env . npm_config_cafile , { encoding : 'utf8' } ) ;
201
+ } catch ( e ) {
202
+ console . error ( 'Could not read cafile' , process . env . npm_config_cafile , e ) ;
212
203
}
213
-
214
- } catch ( e ) {
215
- console . error ( 'Could not read cafile' , process . env . npm_config_cafile , e ) ;
204
+ console . log ( 'Using npmconf cafile.' ) ;
216
205
}
217
- }
218
206
219
- if ( ca ) {
220
- console . log ( 'Using npmconf ca' ) ;
221
- options . agentOptions = {
207
+ options . httpsAgent = new https . Agent ( {
208
+ rejectUnauthorized : ! ! process . env . npm_config_strict_ssl ,
222
209
ca : ca
223
- } ;
224
- options . ca = ca ;
210
+ } ) ;
225
211
}
226
212
227
213
// Use specific User-Agent
@@ -232,94 +218,90 @@ function getRequestOptions(downloadPath) {
232
218
return options ;
233
219
}
234
220
235
- function getChromeDriverVersion ( requestOptions ) {
236
- const deferred = new Deferred ( ) ;
237
- request ( requestOptions , function ( err , response , data ) {
238
- if ( err ) {
239
- deferred . reject ( 'Error with http(s) request: ' + err ) ;
240
- } else {
241
- chromedriver_version = data . trim ( ) ;
242
- deferred . resolve ( true ) ;
243
- }
244
- } ) ;
245
- return deferred . promise ;
221
+ /**
222
+ *
223
+ * @param {import('axios').AxiosRequestConfig } requestOptions
224
+ */
225
+ async function getChromeDriverVersion ( requestOptions ) {
226
+ console . log ( 'Finding Chromedriver version.' ) ;
227
+ const response = await axios ( requestOptions ) ;
228
+ chromedriver_version = response . data . trim ( ) ;
229
+ console . log ( `Chromedriver version is ${ chromedriver_version } .` ) ;
246
230
}
247
231
248
- function requestBinary ( requestOptions , filePath ) {
249
- const deferred = new Deferred ( ) ;
250
-
232
+ /**
233
+ *
234
+ * @param {import('axios').AxiosRequestConfig } requestOptions
235
+ * @param {string } filePath
236
+ */
237
+ async function requestBinary ( requestOptions , filePath ) {
238
+ const outFile = fs . createWriteStream ( filePath ) ;
239
+ let response ;
240
+ try {
241
+ response = await axios . create ( requestOptions ) ( { responseType : 'stream' } ) ;
242
+ } catch ( error ) {
243
+ if ( error && error . response ) {
244
+ if ( error . response . status )
245
+ console . error ( 'Error status code:' , error . response . status ) ;
246
+ if ( error . response . data ) {
247
+ error . response . data . on ( 'data' , data => console . error ( data . toString ( 'utf8' ) ) ) ;
248
+ await new Promise ( ( resolve ) => {
249
+ error . response . data . on ( 'finish' , resolve ) ;
250
+ error . response . data . on ( 'error' , resolve ) ;
251
+ } ) ;
252
+ }
253
+ }
254
+ throw new Error ( 'Error with http(s) request: ' + error ) ;
255
+ }
251
256
let count = 0 ;
252
257
let notifiedCount = 0 ;
253
- const outFile = fs . openSync ( filePath , 'w' ) ;
254
-
255
- const client = request ( requestOptions ) ;
256
-
257
- client . on ( 'error' , function ( err ) {
258
- deferred . reject ( 'Error with http(s) request: ' + err ) ;
259
- } ) ;
260
-
261
- client . on ( 'data' , function ( data ) {
262
- fs . writeSync ( outFile , data , 0 , data . length , null ) ;
258
+ response . data . on ( 'data' , data => {
263
259
count += data . length ;
264
- if ( ( count - notifiedCount ) > 800000 ) {
260
+ if ( ( count - notifiedCount ) > 1024 * 1024 ) {
265
261
console . log ( 'Received ' + Math . floor ( count / 1024 ) + 'K...' ) ;
266
262
notifiedCount = count ;
267
263
}
268
264
} ) ;
269
-
270
- client . on ( 'end' , function ( ) {
271
- console . log ( 'Received ' + Math . floor ( count / 1024 ) + 'K total.' ) ;
272
- fs . closeSync ( outFile ) ;
273
- deferred . resolve ( true ) ;
265
+ response . data . on ( 'end' , ( ) => console . log ( 'Received ' + Math . floor ( count / 1024 ) + 'K total.' ) ) ;
266
+ const pipe = response . data . pipe ( outFile ) ;
267
+ await new Promise ( ( resolve , reject ) => {
268
+ pipe . on ( 'finish' , resolve ) ;
269
+ pipe . on ( 'error' , reject ) ;
274
270
} ) ;
275
-
276
- return deferred . promise ;
277
271
}
278
272
279
- function extractDownload ( ) {
273
+ async function extractDownload ( dirToExtractTo ) {
280
274
if ( path . extname ( downloadedFile ) !== '.zip' ) {
281
275
fs . copyFileSync ( downloadedFile , chromedriverBinaryFilePath ) ;
282
276
console . log ( 'Skipping zip extraction - binary file found.' ) ;
283
- return Promise . resolve ( ) ;
277
+ return ;
284
278
}
285
- const deferred = new Deferred ( ) ;
286
279
console . log ( 'Extracting zip contents' ) ;
287
- extractZip ( path . resolve ( downloadedFile ) , { dir : tmpPath } , function ( err ) {
288
- if ( err ) {
289
- deferred . reject ( 'Error extracting archive: ' + err ) ;
290
- } else {
291
- deferred . resolve ( true ) ;
292
- }
293
- } ) ;
294
- return deferred . promise ;
280
+ try {
281
+ await extractZip ( path . resolve ( downloadedFile ) , { dir : dirToExtractTo } ) ;
282
+ } catch ( error ) {
283
+ throw new Error ( 'Error extracting archive: ' + error ) ;
284
+ }
295
285
}
296
286
297
-
298
- function copyIntoPlace ( originPath , targetPath ) {
299
- return del ( targetPath )
300
- . then ( function ( ) {
301
- console . log ( "Copying to target path" , targetPath ) ;
302
- fs . mkdirSync ( targetPath ) ;
303
-
304
- // Look for the extracted directory, so we can rename it.
305
- const files = fs . readdirSync ( originPath ) ;
306
- const promises = files . map ( function ( name ) {
307
- const deferred = new Deferred ( ) ;
308
-
309
- const file = path . join ( originPath , name ) ;
310
- const reader = fs . createReadStream ( file ) ;
311
-
312
- const targetFile = path . join ( targetPath , name ) ;
313
- const writer = fs . createWriteStream ( targetFile ) ;
314
- writer . on ( "close" , function ( ) {
315
- deferred . resolve ( true ) ;
316
- } ) ;
317
-
318
- reader . pipe ( writer ) ;
319
- return deferred . promise ;
320
- } ) ;
321
- return Promise . all ( promises ) ;
287
+ async function copyIntoPlace ( originPath , targetPath ) {
288
+ await del ( targetPath ) ;
289
+ console . log ( "Copying to target path" , targetPath ) ;
290
+ fs . mkdirSync ( targetPath ) ;
291
+
292
+ // Look for the extracted directory, so we can rename it.
293
+ const files = fs . readdirSync ( originPath ) ;
294
+ const promises = files . map ( name => {
295
+ return new Promise ( ( resolve ) => {
296
+ const file = path . join ( originPath , name ) ;
297
+ const reader = fs . createReadStream ( file ) ;
298
+ const targetFile = path . join ( targetPath , name ) ;
299
+ const writer = fs . createWriteStream ( targetFile ) ;
300
+ writer . on ( "close" , ( ) => resolve ( ) ) ;
301
+ reader . pipe ( writer ) ;
322
302
} ) ;
303
+ } ) ;
304
+ await Promise . all ( promises ) ;
323
305
}
324
306
325
307
2 commit comments
byang183 commentedon Mar 6, 2020
Per issue from axios, if the installation happens within a corp environment, which force to use proxy, the installation will fail.
axios/axios#925
byang183 commentedon Mar 6, 2020
Just to clarify, same config works for 80.0.0, and get 400 from proxy server at 80.0.1