1 | 'use strict'
|
2 |
|
3 | const debug = require('debug')('electron-download')
|
4 | const fs = require('fs-extra')
|
5 | const rc = require('rc')
|
6 | const nugget = require('nugget')
|
7 | const os = require('os')
|
8 | const path = require('path')
|
9 | const pathExists = require('path-exists')
|
10 | const semver = require('semver')
|
11 | const sumchecker = require('sumchecker')
|
12 |
|
13 | let tmpFileCounter = 0
|
14 |
|
15 | class 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 |
|
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 |
|
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
|
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 |
|
271 | module.exports = function download (opts, cb) {
|
272 | let downloader = new ElectronDownloader(opts)
|
273 | downloader.downloadIfNotCached(cb)
|
274 | }
|