electron-incremental-update 1.2.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.js CHANGED
@@ -1,17 +1,16 @@
1
1
  import {
2
2
  __require,
3
+ getAppVersion,
4
+ getEntryVersion,
3
5
  getPathFromAppNameAsar,
4
- getVersions,
5
- is,
6
+ isDev,
6
7
  isUpdateJSON,
7
- parseVersion,
8
8
  restartApp,
9
- unzipFile,
10
- waitAppReady
11
- } from "./chunk-RQCTJY4L.js";
9
+ unzipFile
10
+ } from "./chunk-RSLOPAIZ.js";
12
11
 
13
- // src/index.ts
14
- import { resolve } from "node:path";
12
+ // src/entry.ts
13
+ import { join } from "node:path";
15
14
  import { existsSync as existsSync2, renameSync } from "node:fs";
16
15
  import { app as app2 } from "electron";
17
16
 
@@ -19,156 +18,28 @@ import { app as app2 } from "electron";
19
18
  import { existsSync, rmSync, writeFileSync } from "node:fs";
20
19
  import { app } from "electron";
21
20
 
22
- // src/crypto/dec.ts
23
- import { createDecipheriv, createVerify } from "node:crypto";
24
-
25
- // src/crypto/utils.ts
26
- import { createHash } from "node:crypto";
27
- function hashString(data, length) {
28
- const hash = createHash("SHA256").update(data).digest("binary");
29
- return Buffer.from(hash).subarray(0, length);
30
- }
31
-
32
- // src/crypto/dec.ts
33
- function decrypt(encryptedText, key, iv) {
34
- const decipher = createDecipheriv("aes-256-cbc", key, iv);
35
- let decrypted = decipher.update(encryptedText, "base64url", "utf8");
36
- decrypted += decipher.final("utf8");
37
- return decrypted;
38
- }
39
- var verify = (buffer, signature, cert) => {
40
- try {
41
- const [sig, version] = decrypt(signature, hashString(cert, 32), hashString(buffer, 16)).split("%");
42
- const result = createVerify("RSA-SHA256").update(buffer).verify(cert, sig, "base64");
43
- return result ? version : false;
44
- } catch (error) {
45
- return false;
46
- }
47
- };
48
-
49
21
  // src/updater/types.ts
50
- var MinimumVersionError = class extends Error {
51
- currentVersion;
52
- minVersion;
53
- constructor(version, minimumVersion) {
54
- super(`current entry version is ${version}, less than the minimumVersion ${minimumVersion}`);
55
- this.currentVersion = version;
56
- this.minVersion = minimumVersion;
57
- }
58
- };
59
- var VerifyFailedError = class extends Error {
60
- signature;
61
- cert;
62
- constructor(signature, cert) {
63
- super("verify failed, invalid signature or certificate");
64
- this.signature = signature;
65
- this.cert = cert;
66
- }
22
+ var ErrorInfo = {
23
+ download: "Download failed",
24
+ validate: "Validate failed",
25
+ param: "Missing params",
26
+ version: "Unsatisfied version"
67
27
  };
68
- var DownloadError = class extends Error {
69
- constructor(msg) {
70
- super(`download update error, ${msg}`);
28
+ var UpdaterError = class extends Error {
29
+ constructor(msg, info) {
30
+ super(msg + ": " + info);
71
31
  }
72
32
  };
73
33
 
74
- // src/updater/defaultFunctions/download.ts
75
- import { net } from "electron";
76
- var downloadJSONDefault = async (url, headers) => {
77
- await waitAppReady();
78
- return new Promise((resolve2, reject) => {
79
- const request = net.request({
80
- url,
81
- method: "GET",
82
- redirect: "follow"
83
- });
84
- Object.keys(headers).forEach((key) => {
85
- request.setHeader(key, headers[key]);
86
- });
87
- request.on("response", (res) => {
88
- let data = "";
89
- res.on("data", (chunk) => data += chunk);
90
- res.on("end", () => {
91
- try {
92
- const json = JSON.parse(data);
93
- if (isUpdateJSON(json)) {
94
- resolve2(json);
95
- } else {
96
- throw Error;
97
- }
98
- } catch (e) {
99
- reject(new Error("invalid json"));
100
- }
101
- });
102
- });
103
- request.on("error", (e) => {
104
- reject(e);
105
- });
106
- request.end();
107
- });
108
- };
109
- var downloadBufferDefault = async (url, headers, total, onDownloading) => {
110
- await waitAppReady();
111
- let current = 0;
112
- return new Promise((resolve2, reject) => {
113
- const request = net.request({
114
- url,
115
- method: "GET",
116
- redirect: "follow"
117
- });
118
- Object.keys(headers).forEach((key) => {
119
- request.setHeader(key, headers[key]);
120
- });
121
- request.on("response", (res) => {
122
- let data = [];
123
- res.on("data", (chunk) => {
124
- current += chunk.length;
125
- onDownloading?.({
126
- percent: `${+(current / total).toFixed(2) * 100}%`,
127
- total,
128
- current
129
- });
130
- data.push(chunk);
131
- });
132
- res.on("end", () => {
133
- resolve2(Buffer.concat(data));
134
- });
135
- }).on("error", (e) => {
136
- reject(e);
137
- });
138
- request.end();
139
- });
140
- };
141
-
142
- // src/updater/defaultFunctions/compareVersion.ts
143
- var compareVersionDefault = (version1, version2) => {
144
- const oldV = parseVersion(version1);
145
- const newV = parseVersion(version2);
146
- function compareStrings(str1, str2) {
147
- if (str1 === "") {
148
- return str2 !== "";
149
- } else if (str2 === "") {
150
- return true;
151
- }
152
- return str1 < str2;
153
- }
154
- for (let key of Object.keys(oldV)) {
155
- if (key === "stage" && compareStrings(oldV[key], newV[key])) {
156
- return true;
157
- } else if (oldV[key] !== newV[key]) {
158
- return oldV[key] < newV[key];
159
- }
160
- }
161
- return false;
162
- };
163
-
164
34
  // src/updater/core.ts
165
35
  var Updater = class {
166
- CERT = __SIGNATURE_CERT__;
36
+ CERT = __EIU_SIGNATURE_CERT__;
167
37
  info;
168
- option;
38
+ options;
169
39
  asarPath;
170
40
  gzipPath;
171
41
  tmpFilePath;
42
+ provider;
172
43
  /**
173
44
  * updater logger
174
45
  */
@@ -182,36 +53,54 @@ var Updater = class {
182
53
  * }
183
54
  */
184
55
  onDownloading;
56
+ /**
57
+ * URL handler hook
58
+ *
59
+ * for Github, there are some {@link https://github.com/XIU2/UserScript/blob/master/GithubEnhanced-High-Speed-Download.user.js#L34 public CDN links}
60
+ * @param url source url
61
+ * @param isDownloadAsar whether is download asar
62
+ */
63
+ handleURL;
185
64
  /**
186
65
  * whether receive beta version
187
66
  */
188
67
  get receiveBeta() {
189
- return !!this.option.receiveBeta;
68
+ return !!this.options.receiveBeta;
190
69
  }
191
70
  set receiveBeta(receiveBeta) {
192
- this.option.receiveBeta = receiveBeta;
71
+ this.options.receiveBeta = receiveBeta;
193
72
  }
194
73
  /**
195
74
  * initialize incremental updater
75
+ * @param provider update provider
196
76
  * @param option UpdaterOption
197
77
  */
198
- constructor(option = {}) {
199
- this.option = option;
78
+ constructor(provider, option = {}) {
79
+ this.provider = provider;
80
+ this.options = option;
200
81
  if (option.SIGNATURE_CERT) {
201
82
  this.CERT = option.SIGNATURE_CERT;
202
83
  }
84
+ if (option.logger) {
85
+ this.logger = option.logger;
86
+ }
203
87
  this.asarPath = getPathFromAppNameAsar();
204
88
  this.gzipPath = `${this.asarPath}.gz`;
205
89
  this.tmpFilePath = `${this.asarPath}.tmp`;
206
90
  }
207
91
  async needUpdate(version, minVersion) {
208
- const compare = this.option.overrideFunctions?.compareVersion ?? compareVersionDefault;
209
- const { appVersion, entryVersion } = getVersions();
210
- if (await compare(entryVersion, minVersion)) {
211
- throw new MinimumVersionError(entryVersion, minVersion);
92
+ if (isDev) {
93
+ this.logger?.warn(`in dev mode, skip check update`);
94
+ return false;
95
+ }
96
+ const isLowerVersion = this.provider.isLowerVersion;
97
+ const entryVersion = getEntryVersion();
98
+ const appVersion = getAppVersion();
99
+ if (await isLowerVersion(entryVersion, minVersion)) {
100
+ throw new UpdaterError(ErrorInfo.version, `entry version (${entryVersion}) < minimumVersion (${minVersion})`);
212
101
  }
213
102
  this.logger?.info(`check update: current version is ${appVersion}, new version is ${version}`);
214
- return await compare(appVersion, version);
103
+ return await isLowerVersion(appVersion, version);
215
104
  }
216
105
  async parseData(format, data) {
217
106
  if (existsSync(this.tmpFilePath)) {
@@ -222,63 +111,23 @@ var Updater = class {
222
111
  this.logger?.warn(`remove .gz file: ${this.gzipPath}`);
223
112
  rmSync(this.gzipPath);
224
113
  }
225
- if (!["string", "object", "undefined"].includes(typeof data)) {
226
- throw new TypeError(`invalid type at format '${format}': ${data}`);
227
- }
228
- if (typeof data === "object" && (format === "json" && isUpdateJSON(data) || format === "buffer" && Buffer.isBuffer(data))) {
229
- return data;
230
- }
231
114
  if (typeof data === "object") {
232
- throw new TypeError(`invalid type at format '${format}': ${data}`);
233
- }
234
- 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";
235
- const headers = {
236
- Accept: `application/${format === "json" ? "json" : "octet-stream"}`,
237
- UserAgent: ua,
238
- ...this.option.downloadConfig?.extraHeader
239
- };
240
- this.logger?.info(`download headers: ${JSON.stringify(headers, null, 2)}`);
241
- const config = format === "json" ? {
242
- name: "updateJsonURL",
243
- url: this.option.updateJsonURL,
244
- repoFallback: `${this.option.repository.replace("github.com", "raw.githubusercontent.com")}/master/version.json`,
245
- fn: this.option.overrideFunctions?.downloadJSON ?? downloadJSONDefault
246
- } : {
247
- name: "releaseAsarURL",
248
- url: this.option.releaseAsarURL,
249
- repoFallback: `${this.option.repository}/releases/download/v${this.info?.version}/${app.name}-${this.info?.version}.asar.gz`,
250
- fn: this.option.overrideFunctions?.downloadBuffer ?? downloadBufferDefault
251
- };
252
- data ??= config.url;
253
- if (!data) {
254
- this.logger?.debug(`no ${config.name}, fallback to use repository`);
255
- if (!this.option.repository) {
256
- throw new Error(`${config.name} or repository are not set`);
257
- }
258
- if (format === "buffer" && !this.info?.version) {
259
- throw new Error("version are not set");
115
+ if (format === "json" && isUpdateJSON(data) || format === "buffer" && Buffer.isBuffer(data)) {
116
+ return data;
117
+ } else {
118
+ throw new UpdaterError(ErrorInfo.param, `invalid type at format '${format}': ${JSON.stringify(data)}`);
260
119
  }
261
- data = config.repoFallback;
262
120
  }
263
- this.logger?.info(`download ${format} from ${data}`);
121
+ this.logger?.debug(`download from ${this.provider.name}`);
264
122
  try {
265
- const ret = format === "json" ? await config.fn(data, headers) : await config.fn(data, headers, this.info.size, this.onDownloading);
266
- this.logger?.info(`download ${format} success${format === "buffer" ? `, file size: ${ret.length}` : ""}`);
267
- return ret;
123
+ const result = format === "json" ? await this.provider.downloadJSON(data ?? __EIU_VERSION_PATH__) : await this.provider.downloadBuffer(app.name, this.info, this.onDownloading);
124
+ this.logger?.debug(`download ${format} success${format === "buffer" ? `, file size: ${result.length}` : ""}`);
125
+ return result;
268
126
  } catch (e) {
269
- throw new DownloadError(e.toString());
127
+ this.logger?.warn(`download ${format} failed: ${e}`);
128
+ throw new UpdaterError(ErrorInfo.download, `download ${format} failed: ${e}`);
270
129
  }
271
130
  }
272
- /**
273
- * check update info
274
- *
275
- * if you want to update **offline**, you can set `data` and `sig` add update info
276
- * @param data custom download URL of `updatejson` or existing update json
277
- * @returns
278
- * - Available:`{size: number, version: string}`
279
- * - Unavailable: `undefined`
280
- * - Fail: `CheckResultError`
281
- */
282
131
  async checkUpdate(data) {
283
132
  try {
284
133
  let { signature, size, version, minimumVersion, beta } = await this.parseData("json", data);
@@ -288,59 +137,49 @@ var Updater = class {
288
137
  minimumVersion = beta.minimumVersion;
289
138
  size = beta.size;
290
139
  }
291
- this.logger?.info(`checked version: ${version}, size: ${size}, signature: ${signature}`);
140
+ this.logger?.debug(`checked update, version: ${version}, size: ${size}, signature: ${signature}`);
292
141
  if (!await this.needUpdate(version, minimumVersion)) {
293
142
  this.logger?.info(`update unavailable: ${version} is the latest version`);
294
- return void 0;
143
+ return { success: false, data: version };
295
144
  } else {
296
145
  this.logger?.info(`update available: ${version}`);
297
- this.info = {
298
- signature,
299
- minimumVersion,
300
- version,
301
- size
302
- };
303
- return this.info;
146
+ this.info = { signature, minimumVersion, version, size };
147
+ return { success: true, data: this.info };
304
148
  }
305
149
  } catch (error) {
306
150
  this.logger?.error("check update failed", error);
307
- return error;
151
+ return {
152
+ success: false,
153
+ data: error instanceof UpdaterError ? error : new UpdaterError(ErrorInfo.download, error.toString())
154
+ };
308
155
  }
309
156
  }
310
- /**
311
- * download update
312
- *
313
- * if you want to update **offline**, you can set both `data` and `sig` to verify and install
314
- * @param data custom download URL of `asar.gz` or existing `asar.gz` buffer
315
- * @param sig signature
316
- * @returns
317
- * - `true`: success
318
- * - `DownloadResultError`: fail
319
- */
320
157
  async download(data, sig) {
321
158
  try {
322
- const _sig = sig ?? this.info?.signature;
323
- if (!_sig) {
324
- throw new Error("signature are not set, please checkUpdate first or set the second parameter");
159
+ if (!this.info) {
160
+ throw new UpdaterError(ErrorInfo.param, "no update info");
325
161
  }
162
+ const _sig = sig ?? this.info.signature;
326
163
  const buffer = await this.parseData("buffer", data);
327
- this.logger?.info("verify start");
328
- const _verify = this.option.overrideFunctions?.verifySignaure ?? verify;
329
- const _ver = await _verify(buffer, _sig, this.CERT);
164
+ this.logger?.debug("verify start");
165
+ const _ver = await this.provider.verifySignaure(buffer, _sig, this.CERT);
330
166
  if (!_ver) {
331
- throw new VerifyFailedError(_sig, this.CERT);
167
+ throw new UpdaterError(ErrorInfo.validate, "invalid signature or certificate");
332
168
  }
333
- this.logger?.info("verify success");
334
- this.logger?.info(`write to ${this.gzipPath}`);
169
+ this.logger?.debug("verify success");
170
+ this.logger?.debug(`write to ${this.gzipPath}`);
335
171
  writeFileSync(this.gzipPath, buffer);
336
- this.logger?.info(`extract to ${this.tmpFilePath}`);
172
+ this.logger?.debug(`extract to ${this.tmpFilePath}`);
337
173
  await unzipFile(this.gzipPath, this.tmpFilePath);
338
174
  this.logger?.info(`download success, version: ${_ver}`);
339
175
  this.info = void 0;
340
- return true;
176
+ return { success: true };
341
177
  } catch (error) {
342
178
  this.logger?.error("download asar failed", error);
343
- return error;
179
+ return {
180
+ success: false,
181
+ data: error instanceof UpdaterError ? error : new UpdaterError(ErrorInfo.download, error.toString())
182
+ };
344
183
  }
345
184
  }
346
185
  /**
@@ -352,64 +191,53 @@ var Updater = class {
352
191
  }
353
192
  };
354
193
 
355
- // src/updater/index.ts
356
- function createUpdater(option) {
357
- return new Updater(option);
358
- }
359
-
360
- // src/index.ts
194
+ // src/entry.ts
361
195
  function startupWithUpdater(fn) {
362
196
  return fn;
363
197
  }
364
198
  var defaultOnInstall = (install, _, __, logger) => {
365
199
  install();
366
- logger?.info(`update success!`);
200
+ logger.info(`update success!`);
367
201
  };
368
- async function initApp(appOptions = {}) {
202
+ async function initApp(appOptions) {
369
203
  const {
204
+ provider,
370
205
  updater,
371
- electronDevDistPath = "../dist-electron",
372
- mainPath = "main/index.js",
373
- hooks
374
- } = appOptions || {};
375
- const {
376
206
  onInstall = defaultOnInstall,
377
207
  beforeStart,
378
208
  onStartError
379
- } = hooks || {};
380
- function handleError(err, logger2) {
381
- console.error(err);
382
- onStartError?.(err, logger2);
383
- app2.quit();
384
- }
209
+ } = appOptions;
385
210
  let updaterInstance;
386
211
  if (typeof updater === "object" || !updater) {
387
- updaterInstance = createUpdater(updater);
212
+ updaterInstance = new Updater(provider, updater);
388
213
  } else {
389
214
  updaterInstance = await updater();
390
215
  }
391
- const logger = updaterInstance.logger;
216
+ const logger = updaterInstance.logger || console;
392
217
  try {
393
218
  const appNameAsarPath = getPathFromAppNameAsar();
394
219
  const tempAsarPath = `${appNameAsarPath}.tmp`;
395
220
  if (existsSync2(tempAsarPath)) {
396
- logger?.info(`installing new asar: ${tempAsarPath}`);
221
+ logger.info(`installing new asar: ${tempAsarPath}`);
397
222
  await onInstall(() => renameSync(tempAsarPath, appNameAsarPath), tempAsarPath, appNameAsarPath, logger);
398
223
  }
399
- const mainDir = is.dev ? electronDevDistPath : appNameAsarPath;
400
- const entry = resolve(__dirname, mainDir, mainPath);
401
- await beforeStart?.(entry, logger);
402
- __require(entry)(updaterInstance);
224
+ const mainFilePath = join(
225
+ isDev ? join(app2.getAppPath(), __EIU_MAIN_DEV_DIR__) : appNameAsarPath,
226
+ "main",
227
+ __EIU_MAIN_FILE__
228
+ );
229
+ await beforeStart?.(mainFilePath, logger);
230
+ __require(mainFilePath)(updaterInstance);
403
231
  } catch (error) {
404
- handleError(error, logger);
232
+ logger.error("startup error", error);
233
+ onStartError?.(error, logger);
234
+ app2.quit();
405
235
  }
406
236
  }
407
237
  export {
408
- DownloadError,
409
- MinimumVersionError,
238
+ ErrorInfo,
410
239
  Updater,
411
- VerifyFailedError,
412
- createUpdater,
240
+ UpdaterError,
413
241
  initApp,
414
242
  startupWithUpdater
415
243
  };