UNPKG

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