electron-incremental-update 1.3.0 → 2.0.0-beta.1

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.cjs CHANGED
@@ -17,69 +17,53 @@ var __copyProps = (to, from, except, desc) => {
17
17
  };
18
18
  var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
19
19
 
20
- // src/index.ts
21
- var src_exports = {};
22
- __export(src_exports, {
20
+ // src/entry.ts
21
+ var entry_exports = {};
22
+ __export(entry_exports, {
23
23
  ErrorInfo: () => ErrorInfo,
24
24
  Updater: () => Updater,
25
25
  UpdaterError: () => UpdaterError,
26
- createUpdater: () => createUpdater,
27
- downloadBufferDefault: () => downloadBufferDefault,
28
- downloadJSONDefault: () => downloadJSONDefault,
29
26
  initApp: () => initApp,
30
- isLowerVersionDefault: () => isLowerVersionDefault,
31
27
  startupWithUpdater: () => startupWithUpdater
32
28
  });
33
- module.exports = __toCommonJS(src_exports);
29
+ module.exports = __toCommonJS(entry_exports);
34
30
  var import_node_path2 = require("path");
35
- var import_node_fs4 = require("fs");
31
+ var import_node_fs5 = require("fs");
36
32
  var import_electron4 = require("electron");
37
33
 
38
34
  // src/updater/core.ts
39
35
  var import_node_fs3 = require("fs");
40
- var import_electron3 = require("electron");
36
+ var import_electron2 = require("electron");
37
+
38
+ // src/utils/version.ts
39
+ function isUpdateJSON(json) {
40
+ const is = (j) => !!(j && j.minimumVersion && j.signature && j.size && j.version);
41
+ return is(json) && is(json?.beta);
42
+ }
41
43
 
42
44
  // src/utils/electron.ts
43
45
  var import_node_fs = require("fs");
44
46
  var import_node_path = require("path");
45
- var import_node_os = require("os");
46
47
  var import_electron = require("electron");
47
- var is = {
48
- dev: !import_electron.app.isPackaged,
49
- win: process.platform === "win32",
50
- mac: process.platform === "darwin",
51
- linux: process.platform === "linux"
52
- };
48
+ var isDev = __EIU_IS_DEV__;
49
+ var isWin = process.platform === "win32";
50
+ var isMac = process.platform === "darwin";
51
+ var isLinux = process.platform === "linux";
53
52
  function getPathFromAppNameAsar(...path) {
54
- return is.dev ? "DEV.asar" : (0, import_node_path.join)((0, import_node_path.dirname)(import_electron.app.getAppPath()), `${import_electron.app.name}.asar`, ...path);
53
+ return isDev ? "DEV.asar" : (0, import_node_path.join)((0, import_node_path.dirname)(import_electron.app.getAppPath()), `${import_electron.app.name}.asar`, ...path);
54
+ }
55
+ function getAppVersion() {
56
+ return isDev ? getEntryVersion() : (0, import_node_fs.readFileSync)(getPathFromAppNameAsar("version"), "utf-8");
55
57
  }
56
- function getVersions() {
57
- const platform = is.win ? "Windows" : is.mac ? "MacOS" : process.platform.toUpperCase();
58
- return {
59
- appVersion: is.dev ? import_electron.app.getVersion() : (0, import_node_fs.readFileSync)(getPathFromAppNameAsar("version"), "utf-8"),
60
- entryVersion: import_electron.app.getVersion(),
61
- electronVersion: process.versions.electron,
62
- nodeVersion: process.versions.node,
63
- systemVersion: `${platform} ${(0, import_node_os.release)()}`
64
- };
58
+ function getEntryVersion() {
59
+ return import_electron.app.getVersion();
65
60
  }
66
61
  function restartApp() {
67
62
  import_electron.app.relaunch();
68
63
  import_electron.app.quit();
69
64
  }
70
- function waitAppReady(timeout = 1e3) {
71
- return import_electron.app.isReady() ? Promise.resolve() : new Promise((resolve2, reject) => {
72
- const _ = setTimeout(() => {
73
- reject(new Error("app is not ready"));
74
- }, timeout);
75
- import_electron.app.whenReady().then(() => {
76
- clearTimeout(_);
77
- resolve2();
78
- });
79
- });
80
- }
81
65
 
82
- // src/utils/zip.ts
66
+ // src/utils/unzip.ts
83
67
  var import_node_fs2 = require("fs");
84
68
  var import_node_zlib = require("zlib");
85
69
  async function unzipFile(gzipPath, targetFilePath = gzipPath.slice(0, -3)) {
@@ -87,80 +71,21 @@ async function unzipFile(gzipPath, targetFilePath = gzipPath.slice(0, -3)) {
87
71
  throw new Error(`path to zipped file not exist: ${gzipPath}`);
88
72
  }
89
73
  const compressedBuffer = (0, import_node_fs2.readFileSync)(gzipPath);
90
- return new Promise((resolve2, reject) => {
74
+ return new Promise((resolve, reject) => {
91
75
  (0, import_node_zlib.brotliDecompress)(compressedBuffer, (err, buffer) => {
92
76
  (0, import_node_fs2.rmSync)(gzipPath);
93
77
  if (err) {
94
78
  reject(err);
95
79
  }
96
80
  (0, import_node_fs2.writeFileSync)(targetFilePath, buffer);
97
- resolve2(null);
81
+ resolve(null);
98
82
  });
99
83
  });
100
84
  }
101
85
 
102
- // src/utils/pure.ts
103
- function parseVersion(version) {
104
- const match = /^(\d+)\.(\d+)\.(\d+)(?:-([a-z0-9.-]+))?/i.exec(version);
105
- if (!match) {
106
- throw new TypeError(`invalid version: ${version}`);
107
- }
108
- const [major, minor, patch] = match.slice(1, 4).map(Number);
109
- const ret = {
110
- major,
111
- minor,
112
- patch,
113
- stage: "",
114
- stageVersion: -1
115
- };
116
- if (match[4]) {
117
- let [stage, _v] = match[4].split(".");
118
- ret.stage = stage;
119
- ret.stageVersion = Number(_v) || -1;
120
- }
121
- if (Number.isNaN(major) || Number.isNaN(minor) || Number.isNaN(patch) || Number.isNaN(ret.stageVersion)) {
122
- throw new TypeError(`invalid version: ${version}`);
123
- }
124
- return ret;
125
- }
126
- function isUpdateJSON(json) {
127
- const is2 = (j) => !!(j && j.minimumVersion && j.signature && j.size && j.version);
128
- return is2(json) && is2(json?.beta);
129
- }
130
-
131
- // src/crypto/dec.ts
132
- var import_node_crypto2 = require("crypto");
133
-
134
- // src/crypto/utils.ts
135
- var import_node_crypto = require("crypto");
136
- function hashString(data, length) {
137
- const hash = (0, import_node_crypto.createHash)("SHA256").update(data).digest("binary");
138
- return Buffer.from(hash).subarray(0, length);
139
- }
140
-
141
- // src/crypto/dec.ts
142
- function decrypt(encryptedText, key, iv) {
143
- const decipher = (0, import_node_crypto2.createDecipheriv)("aes-256-cbc", key, iv);
144
- let decrypted = decipher.update(encryptedText, "base64url", "utf8");
145
- decrypted += decipher.final("utf8");
146
- return decrypted;
147
- }
148
- var verify = (buffer, signature, cert) => {
149
- try {
150
- const [sig, version] = decrypt(signature, hashString(cert, 32), hashString(buffer, 16)).split("%");
151
- const result = (0, import_node_crypto2.createVerify)("RSA-SHA256").update(buffer).verify(cert, sig, "base64");
152
- return result ? version : false;
153
- } catch (error) {
154
- return false;
155
- }
156
- };
157
-
158
- // src/crypto/enc.ts
159
- var import_node_crypto3 = require("crypto");
160
-
161
86
  // src/updater/types.ts
162
87
  var ErrorInfo = {
163
- downlaod: "Download failed",
88
+ download: "Download failed",
164
89
  validate: "Validate failed",
165
90
  param: "Missing params",
166
91
  version: "Unsatisfied version"
@@ -171,83 +96,15 @@ var UpdaterError = class extends Error {
171
96
  }
172
97
  };
173
98
 
174
- // src/updater/defaultFunctions/download.ts
175
- var import_electron2 = require("electron");
176
- async function downlaodFn(url, headers, onResponse) {
177
- await waitAppReady();
178
- return new Promise((resolve2, reject) => {
179
- const request = import_electron2.net.request({ url, method: "GET", redirect: "follow" });
180
- Object.keys(headers).forEach((key) => request.setHeader(key, headers[key]));
181
- request.on("response", (res) => onResponse(res, resolve2, reject));
182
- request.on("error", reject);
183
- request.end();
184
- });
185
- }
186
- var downloadJSONDefault = async (url, headers) => {
187
- return await downlaodFn(url, headers, (resp, resolve2, reject) => {
188
- let data = "";
189
- resp.on("data", (chunk) => data += chunk);
190
- resp.on("end", () => {
191
- try {
192
- const json = JSON.parse(data);
193
- if (isUpdateJSON(json)) {
194
- resolve2(json);
195
- } else {
196
- throw Error;
197
- }
198
- } catch (ignore) {
199
- reject(new Error("invalid update json"));
200
- }
201
- });
202
- resp.on("aborted", () => reject(new Error("aborted")));
203
- resp.on("error", () => reject(new Error("download error")));
204
- });
205
- };
206
- var downloadBufferDefault = async (url, headers, total, onDownloading) => {
207
- let current = 0;
208
- return await downlaodFn(url, headers, (resp, resolve2, reject) => {
209
- let data = [];
210
- resp.on("data", (chunk) => {
211
- current += chunk.length;
212
- onDownloading?.({ percent: `${+(current / total).toFixed(2) * 100}%`, total, current });
213
- data.push(chunk);
214
- });
215
- resp.on("end", () => resolve2(Buffer.concat(data)));
216
- resp.on("aborted", () => reject(new Error("aborted")));
217
- resp.on("error", () => reject(new Error("download error")));
218
- });
219
- };
220
-
221
- // src/updater/defaultFunctions/compareVersion.ts
222
- var isLowerVersionDefault = (version1, version2) => {
223
- const oldV = parseVersion(version1);
224
- const newV = parseVersion(version2);
225
- function compareStrings(str1, str2) {
226
- if (str1 === "") {
227
- return str2 !== "";
228
- } else if (str2 === "") {
229
- return true;
230
- }
231
- return str1 < str2;
232
- }
233
- for (let key of Object.keys(oldV)) {
234
- if (key === "stage" && compareStrings(oldV[key], newV[key])) {
235
- return true;
236
- } else if (oldV[key] !== newV[key]) {
237
- return oldV[key] < newV[key];
238
- }
239
- }
240
- return false;
241
- };
242
-
243
99
  // src/updater/core.ts
244
100
  var Updater = class {
245
- CERT = __SIGNATURE_CERT__;
101
+ CERT = __EIU_SIGNATURE_CERT__;
246
102
  info;
247
- option;
103
+ options;
248
104
  asarPath;
249
105
  gzipPath;
250
106
  tmpFilePath;
107
+ provider;
251
108
  /**
252
109
  * updater logger
253
110
  */
@@ -261,31 +118,49 @@ var Updater = class {
261
118
  * }
262
119
  */
263
120
  onDownloading;
121
+ /**
122
+ * URL handler hook
123
+ *
124
+ * for Github, there are some {@link https://github.com/XIU2/UserScript/blob/master/GithubEnhanced-High-Speed-Download.user.js#L34 public CDN links}
125
+ * @param url source url
126
+ * @param isDownloadAsar whether is download asar
127
+ */
128
+ handleURL;
264
129
  /**
265
130
  * whether receive beta version
266
131
  */
267
132
  get receiveBeta() {
268
- return !!this.option.receiveBeta;
133
+ return !!this.options.receiveBeta;
269
134
  }
270
135
  set receiveBeta(receiveBeta) {
271
- this.option.receiveBeta = receiveBeta;
136
+ this.options.receiveBeta = receiveBeta;
272
137
  }
273
138
  /**
274
139
  * initialize incremental updater
140
+ * @param provider update provider
275
141
  * @param option UpdaterOption
276
142
  */
277
- constructor(option = {}) {
278
- this.option = option;
143
+ constructor(provider, option = {}) {
144
+ this.provider = provider;
145
+ this.options = option;
279
146
  if (option.SIGNATURE_CERT) {
280
147
  this.CERT = option.SIGNATURE_CERT;
281
148
  }
149
+ if (option.logger) {
150
+ this.logger = option.logger;
151
+ }
282
152
  this.asarPath = getPathFromAppNameAsar();
283
153
  this.gzipPath = `${this.asarPath}.gz`;
284
154
  this.tmpFilePath = `${this.asarPath}.tmp`;
285
155
  }
286
156
  async needUpdate(version, minVersion) {
287
- const isLowerVersion = this.option.overrideFunctions?.isLowerVersion ?? isLowerVersionDefault;
288
- const { appVersion, entryVersion } = getVersions();
157
+ if (isDev) {
158
+ this.logger?.warn(`in dev mode, skip check update`);
159
+ return false;
160
+ }
161
+ const isLowerVersion = this.provider.isLowerVersion;
162
+ const entryVersion = getEntryVersion();
163
+ const appVersion = getAppVersion();
289
164
  if (await isLowerVersion(entryVersion, minVersion)) {
290
165
  throw new UpdaterError(ErrorInfo.version, `entry version (${entryVersion}) < minimumVersion (${minVersion})`);
291
166
  }
@@ -308,42 +183,14 @@ var Updater = class {
308
183
  throw new UpdaterError(ErrorInfo.param, `invalid type at format '${format}': ${JSON.stringify(data)}`);
309
184
  }
310
185
  }
311
- 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";
312
- const headers = {
313
- Accept: `application/${format === "json" ? "json" : "octet-stream"}`,
314
- UserAgent: ua,
315
- ...this.option.downloadConfig?.extraHeader
316
- };
317
- this.logger?.debug(`download headers: ${JSON.stringify(headers)}`);
318
- const config = format === "json" ? {
319
- name: "updateJsonURL",
320
- url: this.option.updateJsonURL,
321
- repoFallback: `${this.option.repository?.replace("github.com", "raw.githubusercontent.com")}/master/version.json`,
322
- fn: this.option.overrideFunctions?.downloadJSON ?? downloadJSONDefault
323
- } : {
324
- name: "releaseAsarURL",
325
- url: this.option.releaseAsarURL,
326
- repoFallback: `${this.option.repository}/releases/download/v${this.info?.version}/${import_electron3.app.name}-${this.info?.version}.asar.gz`,
327
- fn: this.option.overrideFunctions?.downloadBuffer ?? downloadBufferDefault
328
- };
329
- data ??= config.url;
330
- if (!data) {
331
- this.logger?.debug(`no ${config.name}, fallback to use repository`);
332
- if (!this.option.repository) {
333
- throw new UpdaterError(ErrorInfo.param, `${config.name} or repository is not set`);
334
- }
335
- if (format === "buffer" && !this.info?.version) {
336
- throw new UpdaterError(ErrorInfo.param, "version is not set");
337
- }
338
- data = config.repoFallback;
339
- }
340
- this.logger?.debug(`download ${format} from ${data}`);
186
+ this.logger?.debug(`download from ${this.provider.name}`);
341
187
  try {
342
- const ret = format === "json" ? await config.fn(data, headers) : await config.fn(data, headers, this.info.size, this.onDownloading);
343
- this.logger?.debug(`download ${format} success${format === "buffer" ? `, file size: ${ret.length}` : ""}`);
344
- return ret;
188
+ const result = format === "json" ? await this.provider.downloadJSON(data ?? __EIU_VERSION_PATH__) : await this.provider.downloadBuffer(import_electron2.app.name, this.info, this.onDownloading);
189
+ this.logger?.debug(`download ${format} success${format === "buffer" ? `, file size: ${result.length}` : ""}`);
190
+ return result;
345
191
  } catch (e) {
346
- throw new UpdaterError(ErrorInfo.downlaod, e.toString());
192
+ this.logger?.warn(`download ${format} failed: ${e}`);
193
+ throw new UpdaterError(ErrorInfo.download, `download ${format} failed: ${e}`);
347
194
  }
348
195
  }
349
196
  async checkUpdate(data) {
@@ -355,35 +202,32 @@ var Updater = class {
355
202
  minimumVersion = beta.minimumVersion;
356
203
  size = beta.size;
357
204
  }
358
- this.logger?.debug(`checked version: ${version}, size: ${size}, signature: ${signature}`);
205
+ this.logger?.debug(`checked update, version: ${version}, size: ${size}, signature: ${signature}`);
359
206
  if (!await this.needUpdate(version, minimumVersion)) {
360
207
  this.logger?.info(`update unavailable: ${version} is the latest version`);
361
- return void 0;
208
+ return { success: false, data: version };
362
209
  } else {
363
210
  this.logger?.info(`update available: ${version}`);
364
- this.info = {
365
- signature,
366
- minimumVersion,
367
- version,
368
- size
369
- };
370
- return this.info;
211
+ this.info = { signature, minimumVersion, version, size };
212
+ return { success: true, data: this.info };
371
213
  }
372
214
  } catch (error) {
373
215
  this.logger?.error("check update failed", error);
374
- return error;
216
+ return {
217
+ success: false,
218
+ data: error instanceof UpdaterError ? error : new UpdaterError(ErrorInfo.download, error.toString())
219
+ };
375
220
  }
376
221
  }
377
222
  async download(data, sig) {
378
223
  try {
379
- const _sig = sig ?? this.info?.signature;
380
- if (!_sig) {
381
- throw new UpdaterError(ErrorInfo.param, "signature is empty");
224
+ if (!this.info) {
225
+ throw new UpdaterError(ErrorInfo.param, "no update info");
382
226
  }
227
+ const _sig = sig ?? this.info.signature;
383
228
  const buffer = await this.parseData("buffer", data);
384
229
  this.logger?.debug("verify start");
385
- const _verify = this.option.overrideFunctions?.verifySignaure ?? verify;
386
- const _ver = await _verify(buffer, _sig, this.CERT);
230
+ const _ver = await this.provider.verifySignaure(buffer, _sig, this.CERT);
387
231
  if (!_ver) {
388
232
  throw new UpdaterError(ErrorInfo.validate, "invalid signature or certificate");
389
233
  }
@@ -394,10 +238,13 @@ var Updater = class {
394
238
  await unzipFile(this.gzipPath, this.tmpFilePath);
395
239
  this.logger?.info(`download success, version: ${_ver}`);
396
240
  this.info = void 0;
397
- return true;
241
+ return { success: true };
398
242
  } catch (error) {
399
243
  this.logger?.error("download asar failed", error);
400
- return error;
244
+ return {
245
+ success: false,
246
+ data: error instanceof UpdaterError ? error : new UpdaterError(ErrorInfo.download, error.toString())
247
+ };
401
248
  }
402
249
  }
403
250
  /**
@@ -409,56 +256,60 @@ var Updater = class {
409
256
  }
410
257
  };
411
258
 
412
- // src/updater/index.ts
413
- function createUpdater(option) {
414
- return new Updater(option);
415
- }
259
+ // src/utils/zip.ts
260
+ var import_node_fs4 = require("fs");
261
+ var import_node_zlib2 = require("zlib");
262
+
263
+ // src/utils/crypto/decrypt.ts
264
+ var import_node_crypto2 = require("crypto");
416
265
 
417
- // src/index.ts
266
+ // src/utils/crypto/utils.ts
267
+ var import_node_crypto = require("crypto");
268
+
269
+ // src/utils/crypto/encrypt.ts
270
+ var import_node_crypto3 = require("crypto");
271
+
272
+ // src/entry.ts
418
273
  function startupWithUpdater(fn) {
419
274
  return fn;
420
275
  }
421
276
  var defaultOnInstall = (install, _, __, logger) => {
422
277
  install();
423
- logger?.info(`update success!`);
278
+ logger.info(`update success!`);
424
279
  };
425
- async function initApp(appOptions = {}) {
280
+ async function initApp(appOptions) {
426
281
  const {
282
+ provider,
427
283
  updater,
428
- electronDevDistPath = "../dist-electron",
429
- mainPath = "main/index.js",
430
- hooks
431
- } = appOptions || {};
432
- const {
433
284
  onInstall = defaultOnInstall,
434
285
  beforeStart,
435
286
  onStartError
436
- } = hooks || {};
437
- function handleError(err, logger2) {
438
- console.error(err);
439
- onStartError?.(err, logger2);
440
- import_electron4.app.quit();
441
- }
287
+ } = appOptions;
442
288
  let updaterInstance;
443
289
  if (typeof updater === "object" || !updater) {
444
- updaterInstance = createUpdater(updater);
290
+ updaterInstance = new Updater(provider, updater);
445
291
  } else {
446
292
  updaterInstance = await updater();
447
293
  }
448
- const logger = updaterInstance.logger;
294
+ const logger = updaterInstance.logger || console;
449
295
  try {
450
296
  const appNameAsarPath = getPathFromAppNameAsar();
451
297
  const tempAsarPath = `${appNameAsarPath}.tmp`;
452
- if ((0, import_node_fs4.existsSync)(tempAsarPath)) {
453
- logger?.info(`installing new asar: ${tempAsarPath}`);
454
- await onInstall(() => (0, import_node_fs4.renameSync)(tempAsarPath, appNameAsarPath), tempAsarPath, appNameAsarPath, logger);
298
+ if ((0, import_node_fs5.existsSync)(tempAsarPath)) {
299
+ logger.info(`installing new asar: ${tempAsarPath}`);
300
+ await onInstall(() => (0, import_node_fs5.renameSync)(tempAsarPath, appNameAsarPath), tempAsarPath, appNameAsarPath, logger);
455
301
  }
456
- const mainDir = is.dev ? electronDevDistPath : appNameAsarPath;
457
- const entry = (0, import_node_path2.resolve)(__dirname, mainDir, mainPath);
458
- await beforeStart?.(entry, logger);
459
- require(entry)(updaterInstance);
302
+ const mainFilePath = (0, import_node_path2.join)(
303
+ isDev ? (0, import_node_path2.join)(import_electron4.app.getAppPath(), __EIU_MAIN_DEV_DIR__) : appNameAsarPath,
304
+ "main",
305
+ __EIU_MAIN_FILE__
306
+ );
307
+ await beforeStart?.(mainFilePath, logger);
308
+ require(mainFilePath)(updaterInstance);
460
309
  } catch (error) {
461
- handleError(error, logger);
310
+ logger.error("startup error", error);
311
+ onStartError?.(error, logger);
312
+ import_electron4.app.quit();
462
313
  }
463
314
  }
464
315
  // Annotate the CommonJS export names for ESM import in node:
@@ -466,10 +317,6 @@ async function initApp(appOptions = {}) {
466
317
  ErrorInfo,
467
318
  Updater,
468
319
  UpdaterError,
469
- createUpdater,
470
- downloadBufferDefault,
471
- downloadJSONDefault,
472
320
  initApp,
473
- isLowerVersionDefault,
474
321
  startupWithUpdater
475
322
  });