UNPKG

7.64 kBJavaScriptView Raw
1'use strict'
2
3const debug = require('debug')('electron-download')
4const envPaths = require('env-paths')
5const fs = require('fs-extra')
6const rc = require('rc')
7const nugget = require('nugget')
8const os = require('os')
9const path = require('path')
10const pathExists = require('path-exists')
11const semver = require('semver')
12const sumchecker = require('sumchecker')
13
14class ElectronDownloader {
15 constructor (opts) {
16 // use passed argument or XDG environment variable fallback to OS default
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 // try local folder if homedir is off limits (e.g. some linuxes return '/' as homedir)
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 // nugget returns an array of errors but we only need 1st because we only have 1 url
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 // in case cache dir changed
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
287ElectronDownloader.tmpFileCounter = 0
288
289module.exports = function download (opts, cb) {
290 let downloader = new ElectronDownloader(opts)
291 downloader.downloadIfNotCached(cb)
292}