1 | 'use strict'
|
2 |
|
3 | const debug = require('debug')('electron-download')
|
4 | const envPaths = require('env-paths')
|
5 | const fs = require('fs-extra')
|
6 | const rc = require('rc')
|
7 | const nugget = require('nugget')
|
8 | const os = require('os')
|
9 | const path = require('path')
|
10 | const pathExists = require('path-exists')
|
11 | const semver = require('semver')
|
12 | const sumchecker = require('sumchecker')
|
13 |
|
14 | class ElectronDownloader {
|
15 | constructor (opts) {
|
16 |
|
17 | this.opts = Object.assign({}, opts)
|
18 | if (!this.opts.cache) {
|
19 | this.opts.cache = process.env.ELECTRON_CACHE || envPaths('electron', {suffix: ''}).cache
|
20 | }
|
21 |
|
22 | this.npmrc = {}
|
23 | try {
|
24 | rc('npm', this.npmrc)
|
25 | } catch (error) {
|
26 | console.error(`Error reading npm configuration: ${error.message}`)
|
27 | }
|
28 | }
|
29 |
|
30 | get baseUrl () {
|
31 | return process.env.NPM_CONFIG_ELECTRON_MIRROR ||
|
32 | process.env.npm_config_electron_mirror ||
|
33 | process.env.ELECTRON_MIRROR ||
|
34 | this.opts.mirror ||
|
35 | 'https://github.com/electron/electron/releases/download/v'
|
36 | }
|
37 |
|
38 | get middleUrl () {
|
39 | return process.env.ELECTRON_CUSTOM_DIR || this.opts.customDir || this.version
|
40 | }
|
41 |
|
42 | get urlSuffix () {
|
43 | return process.env.ELECTRON_CUSTOM_FILENAME || this.opts.customFilename || this.filename
|
44 | }
|
45 |
|
46 | get arch () {
|
47 | return this.opts.arch || os.arch()
|
48 | }
|
49 |
|
50 | get cache () {
|
51 | return this.opts.cache
|
52 | }
|
53 |
|
54 | get cachedChecksum () {
|
55 | return path.join(this.cache, `${this.checksumFilename}-${this.version}`)
|
56 | }
|
57 |
|
58 | get cachedZip () {
|
59 | return path.join(this.cache, this.opts.customFilename || this.filename)
|
60 | }
|
61 |
|
62 | get checksumFilename () {
|
63 | return 'SHASUMS256.txt'
|
64 | }
|
65 |
|
66 | get checksumUrl () {
|
67 | return `${this.baseUrl}${this.middleUrl}/${this.checksumFilename}`
|
68 | }
|
69 |
|
70 | get filename () {
|
71 | const type = `${this.platform}-${this.arch}`
|
72 | const suffix = `v${this.version}-${type}`
|
73 |
|
74 | if (this.chromedriver) {
|
75 | return `chromedriver-v2.21-${type}.zip`
|
76 | } else if (this.mksnapshot) {
|
77 | return `mksnapshot-${suffix}.zip`
|
78 | } else if (this.ffmpeg) {
|
79 | return `ffmpeg-${suffix}.zip`
|
80 | } else if (this.symbols) {
|
81 | return `electron-${suffix}-symbols.zip`
|
82 | } else if (this.dsym) {
|
83 | return `electron-${suffix}-dsym.zip`
|
84 | } else {
|
85 | return `electron-${suffix}.zip`
|
86 | }
|
87 | }
|
88 |
|
89 | get platform () {
|
90 | return this.opts.platform || os.platform()
|
91 | }
|
92 |
|
93 | get proxy () {
|
94 | let proxy
|
95 | if (this.npmrc && this.npmrc.proxy) proxy = this.npmrc.proxy
|
96 | if (this.npmrc && this.npmrc['https-proxy']) proxy = this.npmrc['https-proxy']
|
97 |
|
98 | return proxy
|
99 | }
|
100 |
|
101 | get quiet () {
|
102 | return this.opts.quiet || process.stdout.rows < 1
|
103 | }
|
104 |
|
105 | get strictSSL () {
|
106 | let strictSSL = true
|
107 | if (this.opts.strictSSL === false || this.npmrc['strict-ssl'] === false) {
|
108 | strictSSL = false
|
109 | }
|
110 |
|
111 | return strictSSL
|
112 | }
|
113 |
|
114 | get force () {
|
115 | return this.opts.force || false
|
116 | }
|
117 |
|
118 | get symbols () {
|
119 | return this.opts.symbols || false
|
120 | }
|
121 |
|
122 | get dsym () {
|
123 | return this.opts.dsym || false
|
124 | }
|
125 |
|
126 | get chromedriver () {
|
127 | return this.opts.chromedriver || false
|
128 | }
|
129 |
|
130 | get mksnapshot () {
|
131 | return this.opts.mksnapshot || false
|
132 | }
|
133 |
|
134 | get ffmpeg () {
|
135 | return this.opts.ffmpeg || false
|
136 | }
|
137 |
|
138 | get url () {
|
139 | return `${this.baseUrl}${this.middleUrl}/${this.urlSuffix}`
|
140 | }
|
141 |
|
142 | get verifyChecksumNeeded () {
|
143 | return this.opts.verifyChecksum !== false && semver.gte(this.version, '1.3.2')
|
144 | }
|
145 |
|
146 | get version () {
|
147 | return this.opts.version
|
148 | }
|
149 |
|
150 | checkForCachedChecksum (cb) {
|
151 | pathExists(this.cachedChecksum)
|
152 | .then(exists => {
|
153 | if (exists && !this.force) {
|
154 | this.verifyChecksum(cb)
|
155 | } else {
|
156 | this.downloadChecksum(cb)
|
157 | }
|
158 | })
|
159 | }
|
160 |
|
161 | checkForCachedZip (cb) {
|
162 | pathExists(this.cachedZip).then(exists => {
|
163 | if (exists && !this.force) {
|
164 | debug('zip exists', this.cachedZip)
|
165 | this.checkIfZipNeedsVerifying(cb)
|
166 | } else {
|
167 | this.ensureCacheDir(cb)
|
168 | }
|
169 | })
|
170 | }
|
171 |
|
172 | checkIfZipNeedsVerifying (cb) {
|
173 | if (this.verifyChecksumNeeded) {
|
174 | debug('Verifying zip with checksum')
|
175 | return this.checkForCachedChecksum(cb)
|
176 | }
|
177 | return cb(null, this.cachedZip)
|
178 | }
|
179 |
|
180 | createCacheDir (cb) {
|
181 | fs.mkdirs(this.cache, (err) => {
|
182 | if (err) {
|
183 | if (err.code !== 'EACCES') return cb(err)
|
184 |
|
185 | let localCache = path.resolve('./.electron')
|
186 | return fs.mkdirs(localCache, function (err) {
|
187 | if (err) return cb(err)
|
188 | cb(null, localCache)
|
189 | })
|
190 | }
|
191 | cb(null, this.cache)
|
192 | })
|
193 | }
|
194 |
|
195 | downloadChecksum (cb) {
|
196 | this.downloadFile(this.checksumUrl, this.cachedChecksum, cb, this.verifyChecksum.bind(this))
|
197 | }
|
198 |
|
199 | downloadFile (url, cacheFilename, cb, onSuccess) {
|
200 | const tempFileName = `tmp-${process.pid}-${(ElectronDownloader.tmpFileCounter++).toString(16)}-${path.basename(cacheFilename)}`
|
201 | debug('downloading', url, 'to', this.cache)
|
202 | let nuggetOpts = {
|
203 | target: tempFileName,
|
204 | dir: this.cache,
|
205 | resume: true,
|
206 | quiet: this.quiet,
|
207 | strictSSL: this.strictSSL,
|
208 | proxy: this.proxy
|
209 | }
|
210 | nugget(url, nuggetOpts, (errors) => {
|
211 | if (errors) {
|
212 |
|
213 | return this.handleDownloadError(cb, errors[0])
|
214 | }
|
215 |
|
216 | this.moveFileToCache(tempFileName, cacheFilename, cb, onSuccess)
|
217 | })
|
218 | }
|
219 |
|
220 | downloadIfNotCached (cb) {
|
221 | if (!this.version) return cb(new Error('must specify version'))
|
222 | debug('info', {cache: this.cache, filename: this.filename, url: this.url})
|
223 | this.checkForCachedZip(cb)
|
224 | }
|
225 |
|
226 | downloadZip (cb) {
|
227 | this.downloadFile(this.url, this.cachedZip, cb, this.checkIfZipNeedsVerifying.bind(this))
|
228 | }
|
229 |
|
230 | ensureCacheDir (cb) {
|
231 | debug('creating cache dir')
|
232 | this.createCacheDir((err, actualCache) => {
|
233 | if (err) return cb(err)
|
234 | this.opts.cache = actualCache
|
235 | this.downloadZip(cb)
|
236 | })
|
237 | }
|
238 |
|
239 | handleDownloadError (cb, error) {
|
240 | if (error.message.indexOf('404') === -1) return cb(error)
|
241 | if (this.symbols) {
|
242 | error.message = `Failed to find Electron symbols v${this.version} for ${this.platform}-${this.arch} at ${this.url}`
|
243 | } else {
|
244 | error.message = `Failed to find Electron v${this.version} for ${this.platform}-${this.arch} at ${this.url}`
|
245 | }
|
246 |
|
247 | return cb(error)
|
248 | }
|
249 |
|
250 | moveFileToCache (filename, target, cb, onSuccess) {
|
251 | const cache = this.cache
|
252 | debug('moving', filename, 'from', cache, 'to', target)
|
253 | fs.rename(path.join(cache, filename), target, (err) => {
|
254 | if (err) {
|
255 | fs.unlink(cache, cleanupError => {
|
256 | try {
|
257 | if (cleanupError) {
|
258 | console.error(`Error deleting cache file: ${cleanupError.message}`)
|
259 | }
|
260 | } finally {
|
261 | cb(err)
|
262 | }
|
263 | })
|
264 | } else {
|
265 | onSuccess(cb)
|
266 | }
|
267 | })
|
268 | }
|
269 |
|
270 | verifyChecksum (cb) {
|
271 | let options = {}
|
272 | if (semver.lt(this.version, '1.3.5')) {
|
273 | options.defaultTextEncoding = 'binary'
|
274 | }
|
275 | let checker = new sumchecker.ChecksumValidator('sha256', this.cachedChecksum, options)
|
276 | checker.validate(this.cache, this.filename).then(() => {
|
277 | cb(null, this.cachedZip)
|
278 | }, (err) => {
|
279 | fs.unlink(this.cachedZip, (fsErr) => {
|
280 | if (fsErr) return cb(fsErr)
|
281 | cb(err)
|
282 | })
|
283 | })
|
284 | }
|
285 | }
|
286 |
|
287 | ElectronDownloader.tmpFileCounter = 0
|
288 |
|
289 | module.exports = function download (opts, cb) {
|
290 | let downloader = new ElectronDownloader(opts)
|
291 | downloader.downloadIfNotCached(cb)
|
292 | }
|