electron-incremental-update 0.7.8 → 0.7.9

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