electron-incremental-update 0.7.8 → 0.7.10

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -20,6 +20,8 @@ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: tru
20
20
  // src/index.ts
21
21
  var src_exports = {};
22
22
  __export(src_exports, {
23
+ DownloadError: () => DownloadError,
24
+ IncrementalUpdater: () => IncrementalUpdater,
23
25
  MinimumVersionError: () => MinimumVersionError,
24
26
  VerifyFailedError: () => VerifyFailedError,
25
27
  createUpdater: () => createUpdater,
@@ -31,34 +33,10 @@ var import_node_fs3 = require("fs");
31
33
  var import_electron3 = require("electron");
32
34
 
33
35
  // src/updater/index.ts
34
- var import_node_events = require("events");
35
- var import_node_buffer3 = require("buffer");
36
36
  var import_node_fs2 = require("fs");
37
+ var import_node_buffer3 = require("buffer");
37
38
  var import_promises = require("fs/promises");
38
39
 
39
- // src/crypto.ts
40
- var import_node_crypto = require("crypto");
41
- var import_node_buffer = require("buffer");
42
- function decrypt(encryptedText, key2, iv) {
43
- const decipher = (0, import_node_crypto.createDecipheriv)("aes-256-cbc", key2, iv);
44
- let decrypted = decipher.update(encryptedText, "base64url", "utf8");
45
- decrypted += decipher.final("utf8");
46
- return decrypted;
47
- }
48
- function key(data, length) {
49
- const hash = (0, import_node_crypto.createHash)("SHA256").update(data).digest("binary");
50
- return import_node_buffer.Buffer.from(hash).subarray(0, length);
51
- }
52
- var verify = (buffer, signature, cert) => {
53
- try {
54
- const [sig, version] = decrypt(signature, key(cert, 32), key(buffer, 16)).split("%");
55
- const result = (0, import_node_crypto.createVerify)("RSA-SHA256").update(buffer).verify(cert, sig, "base64");
56
- return result ? version : false;
57
- } catch (error) {
58
- return false;
59
- }
60
- };
61
-
62
40
  // src/utils.ts
63
41
  var import_node_fs = require("fs");
64
42
  var import_node_path = require("path");
@@ -80,11 +58,11 @@ function waitAppReady(duration = 1e3) {
80
58
  }, duration);
81
59
  import_electron.app.whenReady().then(() => {
82
60
  clearTimeout(timeout);
83
- resolve2(null);
61
+ resolve2();
84
62
  });
85
63
  });
86
64
  }
87
- async function unzipFile(gzipPath, targetFilePath) {
65
+ async function unzipFile(gzipPath, targetFilePath = gzipPath.slice(0, -3)) {
88
66
  if (!(0, import_node_fs.existsSync)(gzipPath)) {
89
67
  throw new Error(`path to zipped file not exist: ${gzipPath}`);
90
68
  }
@@ -107,12 +85,47 @@ function parseVersion(version) {
107
85
  throw new TypeError(`invalid version: ${version}`);
108
86
  }
109
87
  const [major, minor, patch] = match.slice(1, 4).map(Number);
110
- if (isNaN(major) || isNaN(minor) || isNaN(patch)) {
88
+ const ret = {
89
+ major,
90
+ minor,
91
+ patch,
92
+ stage: "",
93
+ stageVersion: -1
94
+ };
95
+ if (match[4]) {
96
+ let [stage, _v] = match[4].split(".");
97
+ ret.stage = stage;
98
+ ret.stageVersion = Number(_v) || -1;
99
+ }
100
+ if (isNaN(major) || isNaN(minor) || isNaN(patch) || isNaN(ret.stageVersion)) {
111
101
  throw new TypeError(`invalid version: ${version}`);
112
102
  }
113
- return { major, minor, patch, stage: match[4] };
103
+ return ret;
114
104
  }
115
105
 
106
+ // src/crypto.ts
107
+ var import_node_crypto = require("crypto");
108
+ var import_node_buffer = require("buffer");
109
+ function decrypt(encryptedText, key2, iv) {
110
+ const decipher = (0, import_node_crypto.createDecipheriv)("aes-256-cbc", key2, iv);
111
+ let decrypted = decipher.update(encryptedText, "base64url", "utf8");
112
+ decrypted += decipher.final("utf8");
113
+ return decrypted;
114
+ }
115
+ function key(data, length) {
116
+ const hash = (0, import_node_crypto.createHash)("SHA256").update(data).digest("binary");
117
+ return import_node_buffer.Buffer.from(hash).subarray(0, length);
118
+ }
119
+ var verify = (buffer, signature, cert) => {
120
+ try {
121
+ const [sig, version] = decrypt(signature, key(cert, 32), key(buffer, 16)).split("%");
122
+ const result = (0, import_node_crypto.createVerify)("RSA-SHA256").update(buffer).verify(cert, sig, "base64");
123
+ return result ? version : false;
124
+ } catch (error) {
125
+ return false;
126
+ }
127
+ };
128
+
116
129
  // src/updateJson.ts
117
130
  function isUpdateJSON(json) {
118
131
  const is = (j) => "signature" in j && "version" in j && "size" in j && "minimumVersion" in j;
@@ -122,7 +135,7 @@ function isUpdateJSON(json) {
122
135
  // src/updater/defaultFunctions.ts
123
136
  var import_node_buffer2 = require("buffer");
124
137
  var import_electron2 = require("electron");
125
- async function downloadJSONDefault(url, updater, headers) {
138
+ var downloadJSONDefault = async (url, headers) => {
126
139
  await waitAppReady();
127
140
  return new Promise((resolve2, reject) => {
128
141
  const request = import_electron2.net.request({
@@ -154,10 +167,10 @@ async function downloadJSONDefault(url, updater, headers) {
154
167
  });
155
168
  request.end();
156
169
  });
157
- }
158
- async function downloadBufferDefault(url, updater, headers) {
170
+ };
171
+ var downloadBufferDefault = async (url, headers, total, onDownloading) => {
159
172
  await waitAppReady();
160
- let progress = 0;
173
+ let current = 0;
161
174
  return new Promise((resolve2, reject) => {
162
175
  const request = import_electron2.net.request({
163
176
  url,
@@ -170,8 +183,12 @@ async function downloadBufferDefault(url, updater, headers) {
170
183
  request.on("response", (res) => {
171
184
  let data = [];
172
185
  res.on("data", (chunk) => {
173
- progress += chunk.length;
174
- updater.emit("downloading", progress);
186
+ current += chunk.length;
187
+ onDownloading?.({
188
+ percent: `${+(current / total).toFixed(2) * 100}%`,
189
+ total,
190
+ current
191
+ });
175
192
  data.push(chunk);
176
193
  });
177
194
  res.on("end", () => {
@@ -182,15 +199,24 @@ async function downloadBufferDefault(url, updater, headers) {
182
199
  });
183
200
  request.end();
184
201
  });
185
- }
186
- var compareVersionDefault = (oldVersion, newVersion) => {
187
- const oldV = parseVersion(oldVersion);
188
- const newV = parseVersion(newVersion);
189
- if (oldV.major < newV.major || oldV.major === newV.major && oldV.minor < newV.minor || oldV.major === newV.major && oldV.minor === newV.minor && oldV.patch < newV.patch) {
190
- return true;
202
+ };
203
+ var compareVersionDefault = (version1, version2) => {
204
+ const oldV = parseVersion(version1);
205
+ const newV = parseVersion(version2);
206
+ function compareStrings(str1, str2) {
207
+ if (str1 === "") {
208
+ return str2 !== "";
209
+ } else if (str2 === "") {
210
+ return true;
211
+ }
212
+ return str1 < str2;
191
213
  }
192
- if (oldV.stage < newV.stage || !newV.stage && oldV.stage) {
193
- return true;
214
+ for (let key2 of Object.keys(oldV)) {
215
+ if (key2 === "stage" && compareStrings(oldV[key2], newV[key2])) {
216
+ return true;
217
+ } else if (oldV[key2] !== newV[key2]) {
218
+ return oldV[key2] < newV[key2];
219
+ }
194
220
  }
195
221
  return false;
196
222
  };
@@ -214,158 +240,160 @@ var VerifyFailedError = class extends Error {
214
240
  this.cert = cert;
215
241
  }
216
242
  };
217
- function createUpdater(updaterOptions) {
218
- const {
219
- SIGNATURE_CERT,
220
- repository,
221
- productName,
222
- releaseAsarURL: _release,
223
- updateJsonURL: _update,
224
- debug = false,
225
- receiveBeta = false,
226
- downloadConfig: { extraHeader, userAgent } = {},
227
- overrideFunctions: {
228
- compareVersion,
229
- verifySignaure,
230
- downloadBuffer,
231
- downloadJSON
232
- } = {}
233
- } = updaterOptions;
234
- const updater = new import_node_events.EventEmitter();
235
- let signature;
236
- let version;
237
- const asarPath = getProductAsarPath(productName);
238
- const gzipPath = `${asarPath}.gz`;
239
- const tmpFilePath = `${asarPath}.tmp`;
240
- function log(msg) {
241
- debug && updater.emit("debug", msg);
243
+ var DownloadError = class extends Error {
244
+ constructor() {
245
+ super("download update error");
246
+ }
247
+ };
248
+ var IncrementalUpdater = class {
249
+ info;
250
+ option;
251
+ asarPath;
252
+ gzipPath;
253
+ tmpFilePath;
254
+ logger;
255
+ onDownloading;
256
+ get productName() {
257
+ return this.option.productName;
258
+ }
259
+ set productName(name) {
260
+ this.option.productName = name;
242
261
  }
243
- async function needUpdate(version2, minVersion) {
244
- const compare = compareVersion ?? compareVersionDefault;
245
- const productVersion = getProductVersion(productName);
262
+ get receiveBeta() {
263
+ return !!this.option.receiveBeta;
264
+ }
265
+ set receiveBeta(receiveBeta) {
266
+ this.option.receiveBeta = receiveBeta;
267
+ }
268
+ constructor(option) {
269
+ this.option = option;
270
+ this.asarPath = getProductAsarPath(this.productName);
271
+ this.gzipPath = `${this.asarPath}.gz`;
272
+ this.tmpFilePath = `${this.asarPath}.tmp`;
273
+ }
274
+ async needUpdate(version, minVersion) {
275
+ const compare = this.option.overrideFunctions?.compareVersion ?? compareVersionDefault;
276
+ const productVersion = getProductVersion(this.option.productName);
246
277
  const entryVersion = getEntryVersion();
247
278
  if (await compare(entryVersion, minVersion)) {
248
279
  throw new MinimumVersionError(entryVersion, minVersion);
249
280
  }
250
- log(`check update: current version is ${productVersion}, new version is ${version2}`);
251
- return await compare(productVersion, version2);
281
+ this.logger?.info(`check update: current version is ${productVersion}, new version is ${version}`);
282
+ return await compare(productVersion, version);
252
283
  }
253
- async function parseData(format, data, version2) {
254
- if ((0, import_node_fs2.existsSync)(tmpFilePath)) {
255
- log(`remove tmp file: ${tmpFilePath}`);
256
- await (0, import_promises.rm)(tmpFilePath);
284
+ async parseData(format, data) {
285
+ if ((0, import_node_fs2.existsSync)(this.tmpFilePath)) {
286
+ this.logger?.warn(`remove tmp file: ${this.tmpFilePath}`);
287
+ await (0, import_promises.rm)(this.tmpFilePath);
288
+ }
289
+ if ((0, import_node_fs2.existsSync)(this.gzipPath)) {
290
+ this.logger?.warn(`remove .gz file: ${this.gzipPath}`);
291
+ await (0, import_promises.rm)(this.gzipPath);
292
+ }
293
+ if (!["string", "object", "undefined"].includes(typeof data)) {
294
+ throw new TypeError(`invalid type at format '${format}': ${data}`);
257
295
  }
258
- if ((0, import_node_fs2.existsSync)(gzipPath)) {
259
- log(`remove .gz file: ${gzipPath}`);
260
- await (0, import_promises.rm)(gzipPath);
296
+ if (typeof data === "object" && (format === "json" && isUpdateJSON(data) || format === "buffer" && import_node_buffer3.Buffer.isBuffer(data))) {
297
+ return data;
261
298
  }
262
299
  if (typeof data === "object") {
263
- if (format === "json" && isUpdateJSON(data) || format === "buffer" && import_node_buffer3.Buffer.isBuffer(data)) {
264
- return data;
265
- } else {
266
- throw new TypeError(`invalid type at format '${format}': ${data}`);
267
- }
268
- } else if (["string", "undefined"].includes(typeof data)) {
269
- const ua = userAgent || "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/86.0.4240.183 Safari/537.36";
270
- const headers = {
271
- Accept: `application/${format === "json" ? "json" : "octet-stream"}`,
272
- UserAgent: ua,
273
- ...extraHeader
274
- };
275
- log(`download headers: ${JSON.stringify(headers, null, 2)}`);
276
- const info = format === "json" ? {
277
- name: "updateJsonURL",
278
- url: _update,
279
- repoFallback: `${repository.replace("github.com", "raw.githubusercontent.com")}/master/version.json`,
280
- fn: downloadJSON ?? downloadJSONDefault
281
- } : {
282
- name: "releaseAsarURL",
283
- url: _release,
284
- repoFallback: `${repository}/releases/download/v${version2}/${productName}-${version2}.asar.gz`,
285
- fn: downloadBuffer ?? downloadBufferDefault
286
- };
287
- data ??= info.url;
288
- if (!data) {
289
- log(`no ${info.name}, fallback to use repository`);
290
- if (!repository) {
291
- throw new Error(`${info.name} or repository are not set`);
292
- }
293
- if (format === "buffer" && !version2) {
294
- throw new Error("version are not set");
295
- }
296
- data = info.repoFallback;
300
+ throw new TypeError(`invalid type at format '${format}': ${data}`);
301
+ }
302
+ const ua = this.option.downloadConfig?.userAgent || "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/86.0.4240.183 Safari/537.36";
303
+ const headers = {
304
+ Accept: `application/${format === "json" ? "json" : "octet-stream"}`,
305
+ UserAgent: ua,
306
+ ...this.option.downloadConfig?.extraHeader
307
+ };
308
+ this.logger?.info(`download headers: ${JSON.stringify(headers, null, 2)}`);
309
+ const config = format === "json" ? {
310
+ name: "updateJsonURL",
311
+ url: this.option.updateJsonURL,
312
+ repoFallback: `${this.option.repository.replace("github.com", "raw.githubusercontent.com")}/master/version.json`,
313
+ fn: this.option.overrideFunctions?.downloadJSON ?? downloadJSONDefault
314
+ } : {
315
+ name: "releaseAsarURL",
316
+ url: this.option.releaseAsarURL,
317
+ repoFallback: `${this.option.repository}/releases/download/v${this.info?.version}/${this.productName}-${this.info?.version}.asar.gz`,
318
+ fn: this.option.overrideFunctions?.downloadBuffer ?? downloadBufferDefault
319
+ };
320
+ data ??= config.url;
321
+ if (!data) {
322
+ this.logger?.debug(`no ${config.name}, fallback to use repository`);
323
+ if (!this.option.repository) {
324
+ throw new Error(`${config.name} or repository are not set`);
297
325
  }
298
- log(`download ${format} from ${data}`);
299
- const ret = await info.fn(data, updater, headers);
300
- log(`download ${format} success${format === "buffer" ? `, file size: ${ret.length}` : ""}`);
301
- if (format === "buffer") {
302
- updater.emit("downloadBuffer", ret);
326
+ if (format === "buffer" && !this.info?.version) {
327
+ throw new Error("version are not set");
303
328
  }
329
+ data = config.repoFallback;
330
+ }
331
+ this.logger?.info(`download ${format} from ${data}`);
332
+ try {
333
+ const ret = format === "json" ? await config.fn(data, headers) : await config.fn(data, headers, this.info.size, this.onDownloading);
334
+ this.logger?.info(`download ${format} success${format === "buffer" ? `, file size: ${ret.length}` : ""}`);
304
335
  return ret;
305
- } else {
306
- throw new TypeError(`invalid type at format '${format}': ${data}`);
336
+ } catch (e) {
337
+ throw new DownloadError();
307
338
  }
308
339
  }
309
- updater.productName = productName;
310
- updater.debug = debug;
311
- updater.receiveBeta = receiveBeta;
312
- updater.checkUpdate = async (data) => {
340
+ async checkUpdate(data) {
313
341
  try {
314
- let {
315
- signature: _sig,
316
- size,
317
- version: _ver,
318
- minimumVersion,
319
- beta
320
- } = await parseData("json", data);
321
- if (receiveBeta) {
322
- _ver = beta.version;
323
- _sig = beta.signature;
342
+ let { signature, size, version, minimumVersion, beta } = await this.parseData("json", data);
343
+ if (this.receiveBeta) {
344
+ version = beta.version;
345
+ signature = beta.signature;
324
346
  minimumVersion = beta.minimumVersion;
325
347
  size = beta.size;
326
348
  }
327
- log(`checked version: ${_ver}, size: ${size}, signature: ${_sig}`);
328
- if (!await needUpdate(_ver, minimumVersion)) {
329
- log(`update unavailable: ${_ver}`);
349
+ this.logger?.info(`checked version: ${version}, size: ${size}, signature: ${signature}`);
350
+ if (!await this.needUpdate(version, minimumVersion)) {
351
+ this.logger?.info(`update unavailable: ${version} is the latest version`);
330
352
  return void 0;
331
353
  } else {
332
- log(`update available: ${_ver}`);
333
- signature = _sig;
334
- version = _ver;
335
- return { size, version: _ver };
354
+ this.logger?.info(`update available: ${version}`);
355
+ this.info = {
356
+ signature,
357
+ minimumVersion,
358
+ version,
359
+ size
360
+ };
361
+ return { size, version };
336
362
  }
337
363
  } catch (error) {
338
- log(error);
364
+ this.logger?.error("check update failed", error);
339
365
  return error;
340
366
  }
341
- };
342
- updater.download = async (data, sig) => {
367
+ }
368
+ async download(data, sig) {
343
369
  try {
344
- const _sig = sig ?? signature;
370
+ const _sig = sig ?? this.info?.signature;
345
371
  if (!_sig) {
346
372
  throw new Error("signature are not set, please checkUpdate first or set the second parameter");
347
373
  }
348
- const buffer = await parseData("buffer", data, version);
349
- log("verify start");
350
- const _verify = verifySignaure ?? verify;
351
- const _ver = await _verify(buffer, _sig, SIGNATURE_CERT);
374
+ const buffer = await this.parseData("buffer", data);
375
+ this.logger?.info("verify start");
376
+ const _verify = this.option.overrideFunctions?.verifySignaure ?? verify;
377
+ const _ver = await _verify(buffer, _sig, this.option.SIGNATURE_CERT);
352
378
  if (!_ver) {
353
- throw new VerifyFailedError(_sig, SIGNATURE_CERT);
379
+ throw new VerifyFailedError(_sig, this.option.SIGNATURE_CERT);
354
380
  }
355
- log("verify success");
356
- log(`write to ${gzipPath}`);
357
- await (0, import_promises.writeFile)(gzipPath, buffer);
358
- log(`extract to ${tmpFilePath}`);
359
- await unzipFile(gzipPath, tmpFilePath);
360
- log(`download success, version: ${_ver}`);
361
- signature = "";
381
+ this.logger?.info("verify success");
382
+ this.logger?.info(`write to ${this.gzipPath}`);
383
+ await (0, import_promises.writeFile)(this.gzipPath, buffer);
384
+ this.logger?.info(`extract to ${this.tmpFilePath}`);
385
+ await unzipFile(this.gzipPath, this.tmpFilePath);
386
+ this.logger?.info(`download success${typeof _ver === "string" ? `, version: ${_ver}` : ""}`);
387
+ this.info = void 0;
362
388
  return true;
363
389
  } catch (error) {
364
- log(error);
390
+ this.logger?.error("download asar failed", error);
365
391
  return error;
366
392
  }
367
- };
368
- return updater;
393
+ }
394
+ };
395
+ function createUpdater(option) {
396
+ return new IncrementalUpdater(option);
369
397
  }
370
398
 
371
399
  // src/index.ts
@@ -412,6 +440,8 @@ function initApp(appOptions) {
412
440
  }
413
441
  // Annotate the CommonJS export names for ESM import in node:
414
442
  0 && (module.exports = {
443
+ DownloadError,
444
+ IncrementalUpdater,
415
445
  MinimumVersionError,
416
446
  VerifyFailedError,
417
447
  createUpdater,