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

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,207 +1,210 @@
1
- import {
2
- __require,
3
- getAppVersion,
4
- getEntryVersion,
5
- getPathFromAppNameAsar,
6
- isDev,
7
- isUpdateJSON,
8
- restartApp,
9
- unzipFile
10
- } from "./chunk-RSLOPAIZ.js";
1
+ import { isDev, getEntryVersion, getAppVersion, getPathFromAppNameAsar, restartApp } from './chunk-IABBXJFB.js';
2
+ import { isUpdateJSON, __require } from './chunk-RCRKUKFX.js';
3
+ import fs2 from 'node:fs';
4
+ import { EventEmitter } from 'node:events';
5
+ import { app } from 'electron';
6
+ import path from 'node:path';
11
7
 
12
- // src/entry.ts
13
- import { join } from "node:path";
14
- import { existsSync as existsSync2, renameSync } from "node:fs";
15
- import { app as app2 } from "electron";
16
-
17
- // src/updater/core.ts
18
- import { existsSync, rmSync, writeFileSync } from "node:fs";
19
- import { app } from "electron";
20
-
21
- // src/updater/types.ts
8
+ // src/entry/types.ts
22
9
  var ErrorInfo = {
23
- download: "Download failed",
24
- validate: "Validate failed",
25
- param: "Missing params",
26
- version: "Unsatisfied version"
10
+ download: "Download Failed",
11
+ validate: "Validate Failed",
12
+ param: "Missing Params",
13
+ network: "Network Error"
27
14
  };
28
15
  var UpdaterError = class extends Error {
16
+ code;
29
17
  constructor(msg, info) {
30
- super(msg + ": " + info);
18
+ super("[" + ErrorInfo[msg] + "] " + info);
19
+ this.code = msg;
31
20
  }
32
21
  };
33
22
 
34
- // src/updater/core.ts
35
- var Updater = class {
36
- CERT = __EIU_SIGNATURE_CERT__;
23
+ // src/entry/updater.ts
24
+ var Updater = class extends EventEmitter {
25
+ CERT;
26
+ controller;
37
27
  info;
38
- options;
39
- asarPath;
40
- gzipPath;
41
- tmpFilePath;
42
28
  provider;
43
29
  /**
44
- * updater logger
30
+ * Updater logger
45
31
  */
46
32
  logger;
47
33
  /**
48
- * downloading progress hook
49
- * @param progress download progress
50
- * @example
51
- * updater.onDownloading = ({ percent, total, current }) => {
52
- * console.log(`download progress: ${percent}, total: ${total}, current: ${current}`)
53
- * }
34
+ * Whether to receive beta update
54
35
  */
55
- onDownloading;
36
+ receiveBeta;
56
37
  /**
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
38
+ * Whether force update in DEV
62
39
  */
63
- handleURL;
40
+ forceUpdate;
64
41
  /**
65
- * whether receive beta version
42
+ * Initialize incremental updater
43
+ * @param options UpdaterOption
66
44
  */
67
- get receiveBeta() {
68
- return !!this.options.receiveBeta;
69
- }
70
- set receiveBeta(receiveBeta) {
71
- this.options.receiveBeta = receiveBeta;
72
- }
73
- /**
74
- * initialize incremental updater
75
- * @param provider update provider
76
- * @param option UpdaterOption
77
- */
78
- constructor(provider, option = {}) {
79
- this.provider = provider;
80
- this.options = option;
81
- if (option.SIGNATURE_CERT) {
82
- this.CERT = option.SIGNATURE_CERT;
83
- }
84
- if (option.logger) {
85
- this.logger = option.logger;
86
- }
87
- this.asarPath = getPathFromAppNameAsar();
88
- this.gzipPath = `${this.asarPath}.gz`;
89
- this.tmpFilePath = `${this.asarPath}.tmp`;
90
- }
91
- async needUpdate(version, minVersion) {
92
- if (isDev) {
93
- this.logger?.warn(`in dev mode, skip check update`);
94
- return false;
45
+ constructor(options = {}) {
46
+ super();
47
+ this.provider = options.provider;
48
+ this.receiveBeta = options.receiveBeta;
49
+ this.CERT = options.SIGNATURE_CERT || __EIU_SIGNATURE_CERT__;
50
+ this.logger = options.logger;
51
+ this.controller = new AbortController();
52
+ if (isDev && !this.logger) {
53
+ this.logger = {
54
+ info: (...args) => console.log("[EIU-INFO ]", ...args),
55
+ debug: (...args) => console.log("[EIU-DEBUG]", ...args),
56
+ warn: (...args) => console.log("[EIU-WARN ]", ...args),
57
+ error: (...args) => console.error("[EIU-ERROR]", ...args)
58
+ };
59
+ this.logger.info("No logger set, enable dev-only logger");
95
60
  }
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})`);
61
+ if (!this.provider) {
62
+ this.logger?.debug("WARN: No update provider");
101
63
  }
102
- this.logger?.info(`check update: current version is ${appVersion}, new version is ${version}`);
103
- return await isLowerVersion(appVersion, version);
104
64
  }
105
- async parseData(format, data) {
106
- if (existsSync(this.tmpFilePath)) {
107
- this.logger?.warn(`remove tmp file: ${this.tmpFilePath}`);
108
- rmSync(this.tmpFilePath);
109
- }
110
- if (existsSync(this.gzipPath)) {
111
- this.logger?.warn(`remove .gz file: ${this.gzipPath}`);
112
- rmSync(this.gzipPath);
113
- }
65
+ async fetch(format, data) {
114
66
  if (typeof data === "object") {
115
67
  if (format === "json" && isUpdateJSON(data) || format === "buffer" && Buffer.isBuffer(data)) {
116
68
  return data;
117
69
  } else {
118
- throw new UpdaterError(ErrorInfo.param, `invalid type at format '${format}': ${JSON.stringify(data)}`);
70
+ this.err("Invalid type", "param", `Invalid type at format '${format}': ${JSON.stringify(data)}`);
71
+ return;
119
72
  }
120
73
  }
121
- this.logger?.debug(`download from ${this.provider.name}`);
74
+ this.logger?.debug(`Download from \`${this.provider.name}\``);
122
75
  try {
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}` : ""}`);
76
+ const result = format === "json" ? await this.provider.downloadJSON(__EIU_VERSION_PATH__, this.controller.signal) : await this.provider.downloadAsar(app.name, this.info, this.controller.signal, (info) => this.emit("download-progress", info));
77
+ this.logger?.debug(`Download ${format} success${format === "buffer" ? `, file size: ${result.length}` : ""}`);
125
78
  return result;
126
79
  } catch (e) {
127
- this.logger?.warn(`download ${format} failed: ${e}`);
128
- throw new UpdaterError(ErrorInfo.download, `download ${format} failed: ${e}`);
80
+ this.err(`Fetch ${format} failed`, "network", `Download ${format} failed: ${e}`);
129
81
  }
130
82
  }
131
- async checkUpdate(data) {
83
+ /**
84
+ * Handle error message and emit error event
85
+ */
86
+ err(msg, code, errorInfo) {
87
+ const err = new UpdaterError(code, errorInfo);
88
+ this.logger?.error(msg, err);
89
+ this.emit("error", err);
90
+ }
91
+ async checkForUpdates(data) {
92
+ const emitUnavailable = (msg) => {
93
+ this.logger?.info(msg);
94
+ this.emit("update-not-available", msg);
95
+ return false;
96
+ };
97
+ if (!data && !this.provider) {
98
+ const errorInfo = "No update json or provider";
99
+ this.err("Check update failed", "param", errorInfo);
100
+ return emitUnavailable(errorInfo);
101
+ }
102
+ const _data = await this.fetch("json", data);
103
+ if (!_data) {
104
+ return emitUnavailable("Failed to get update info");
105
+ }
106
+ let { signature, version, minimumVersion, beta } = _data;
107
+ if (this.receiveBeta) {
108
+ version = beta.version;
109
+ signature = beta.signature;
110
+ minimumVersion = beta.minimumVersion;
111
+ }
112
+ this.logger?.debug(`Checked update, version: ${version}, signature: ${signature}`);
113
+ if (isDev && !this.forceUpdate && !data) {
114
+ return emitUnavailable("Skip check update in dev mode. To force update, set `updater.forceUpdate` to true or call checkUpdate with UpdateJSON");
115
+ }
116
+ const isLowerVersion = this.provider.isLowerVersion;
117
+ const entryVersion = getEntryVersion();
118
+ const appVersion = getAppVersion();
132
119
  try {
133
- let { signature, size, version, minimumVersion, beta } = await this.parseData("json", data);
134
- if (this.receiveBeta) {
135
- version = beta.version;
136
- signature = beta.signature;
137
- minimumVersion = beta.minimumVersion;
138
- size = beta.size;
120
+ if (isLowerVersion(entryVersion, minimumVersion)) {
121
+ return emitUnavailable(`Entry Version (${entryVersion}) < MinimumVersion (${minimumVersion})`);
139
122
  }
140
- this.logger?.debug(`checked update, version: ${version}, size: ${size}, signature: ${signature}`);
141
- if (!await this.needUpdate(version, minimumVersion)) {
142
- this.logger?.info(`update unavailable: ${version} is the latest version`);
143
- return { success: false, data: version };
144
- } else {
145
- this.logger?.info(`update available: ${version}`);
146
- this.info = { signature, minimumVersion, version, size };
147
- return { success: true, data: this.info };
123
+ this.logger?.info(`Check update: current version is ${appVersion}, new version is ${version}`);
124
+ if (!isLowerVersion(appVersion, version)) {
125
+ return emitUnavailable(`Current version (${appVersion}) < New version (${version})`);
148
126
  }
149
- } catch (error) {
150
- this.logger?.error("check update failed", error);
151
- return {
152
- success: false,
153
- data: error instanceof UpdaterError ? error : new UpdaterError(ErrorInfo.download, error.toString())
154
- };
127
+ this.logger?.info(`Update available: ${version}`);
128
+ this.info = { signature, minimumVersion, version };
129
+ this.emit("update-available", this.info);
130
+ return true;
131
+ } catch {
132
+ this.err("Fail to parse version", "validate", "Fail to parse version string");
133
+ return false;
155
134
  }
156
135
  }
157
- async download(data, sig) {
136
+ async downloadUpdate(data, info) {
137
+ const _sig = info?.signature ?? this.info?.signature;
138
+ const _version = info?.version ?? this.info?.version;
139
+ if (!_sig || !_version) {
140
+ this.err("Download failed", "param", "No update signature, please call `checkUpdate` first or manually setup params");
141
+ return false;
142
+ }
143
+ if (!data && !this.provider) {
144
+ this.err("Download failed", "param", "No update asar buffer and provider");
145
+ return false;
146
+ }
147
+ const buffer = await this.fetch("buffer", data ? Buffer.from(data) : void 0);
148
+ if (!buffer) {
149
+ this.err("Download failed", "param", "No update asar file buffer");
150
+ return false;
151
+ }
152
+ this.logger?.debug("verify start");
153
+ if (!await this.provider.verifySignaure(buffer, _version, _sig, this.CERT)) {
154
+ this.err("Download failed", "validate", "Invalid update asar file");
155
+ return false;
156
+ }
157
+ this.logger?.debug("Verify success");
158
158
  try {
159
- if (!this.info) {
160
- throw new UpdaterError(ErrorInfo.param, "no update info");
161
- }
162
- const _sig = sig ?? this.info.signature;
163
- const buffer = await this.parseData("buffer", data);
164
- this.logger?.debug("verify start");
165
- const _ver = await this.provider.verifySignaure(buffer, _sig, this.CERT);
166
- if (!_ver) {
167
- throw new UpdaterError(ErrorInfo.validate, "invalid signature or certificate");
168
- }
169
- this.logger?.debug("verify success");
170
- this.logger?.debug(`write to ${this.gzipPath}`);
171
- writeFileSync(this.gzipPath, buffer);
172
- this.logger?.debug(`extract to ${this.tmpFilePath}`);
173
- await unzipFile(this.gzipPath, this.tmpFilePath);
174
- this.logger?.info(`download success, version: ${_ver}`);
159
+ const tmpFilePath = getPathFromAppNameAsar() + ".tmp";
160
+ this.logger?.debug(`Install to ${tmpFilePath}`);
161
+ fs2.writeFileSync(tmpFilePath, await this.provider.unzipFile(buffer));
162
+ this.logger?.info(`Download success, version: ${_version}`);
175
163
  this.info = void 0;
176
- return { success: true };
164
+ this.emit("update-downloaded");
165
+ return true;
177
166
  } catch (error) {
178
- this.logger?.error("download asar failed", error);
179
- return {
180
- success: false,
181
- data: error instanceof UpdaterError ? error : new UpdaterError(ErrorInfo.download, error.toString())
182
- };
167
+ this.err("Download failed", "download", `Fail to unwrap asar file, ${error}`);
168
+ return false;
183
169
  }
184
170
  }
185
171
  /**
186
172
  * quit App and install
187
173
  */
188
174
  quitAndInstall() {
189
- this.logger?.info("quit and install");
175
+ this.logger?.info("Quit and install");
190
176
  restartApp();
191
177
  }
178
+ cancel() {
179
+ if (this.controller.signal.aborted) {
180
+ return;
181
+ }
182
+ this.controller.abort();
183
+ this.logger?.info("Cancel update");
184
+ this.emit("update-cancelled");
185
+ this.controller = new AbortController();
186
+ }
192
187
  };
193
-
194
- // src/entry.ts
188
+ async function autoUpdate(updater) {
189
+ if (await updater.checkForUpdates() && await updater.downloadUpdate()) {
190
+ updater.quitAndInstall();
191
+ }
192
+ }
195
193
  function startupWithUpdater(fn) {
196
194
  return fn;
197
195
  }
198
196
  var defaultOnInstall = (install, _, __, logger) => {
199
197
  install();
200
- logger.info(`update success!`);
198
+ logger?.info(`update success!`);
201
199
  };
202
- async function initApp(appOptions) {
200
+ async function createElectronApp(appOptions = {}) {
201
+ const appNameAsarPath = getPathFromAppNameAsar();
203
202
  const {
204
- provider,
203
+ mainPath = path.join(
204
+ isDev ? path.join(app.getAppPath(), __EIU_MAIN_DEV_DIR__) : appNameAsarPath,
205
+ "main",
206
+ __EIU_MAIN_FILE__
207
+ ),
205
208
  updater,
206
209
  onInstall = defaultOnInstall,
207
210
  beforeStart,
@@ -209,35 +212,29 @@ async function initApp(appOptions) {
209
212
  } = appOptions;
210
213
  let updaterInstance;
211
214
  if (typeof updater === "object" || !updater) {
212
- updaterInstance = new Updater(provider, updater);
215
+ updaterInstance = new Updater(updater);
213
216
  } else {
214
217
  updaterInstance = await updater();
215
218
  }
216
- const logger = updaterInstance.logger || console;
219
+ const logger = updaterInstance.logger;
217
220
  try {
218
- const appNameAsarPath = getPathFromAppNameAsar();
219
221
  const tempAsarPath = `${appNameAsarPath}.tmp`;
220
- if (existsSync2(tempAsarPath)) {
221
- logger.info(`installing new asar: ${tempAsarPath}`);
222
- await onInstall(() => renameSync(tempAsarPath, appNameAsarPath), tempAsarPath, appNameAsarPath, logger);
222
+ if (fs2.existsSync(tempAsarPath)) {
223
+ logger?.info(`Installing new asar from ${tempAsarPath}`);
224
+ await onInstall(() => fs2.renameSync(tempAsarPath, appNameAsarPath), tempAsarPath, appNameAsarPath, logger);
225
+ }
226
+ await beforeStart?.(mainPath, logger);
227
+ if (__EIU_IS_ESM__) {
228
+ (await import("file://" + mainPath)).default(updaterInstance);
229
+ } else {
230
+ __require(mainPath)(updaterInstance);
223
231
  }
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);
231
232
  } catch (error) {
232
- logger.error("startup error", error);
233
+ logger?.error("startup error", error);
233
234
  onStartError?.(error, logger);
234
- app2.quit();
235
+ app.quit();
235
236
  }
236
237
  }
237
- export {
238
- ErrorInfo,
239
- Updater,
240
- UpdaterError,
241
- initApp,
242
- startupWithUpdater
243
- };
238
+ var initApp = createElectronApp;
239
+
240
+ export { ErrorInfo, Updater, UpdaterError, autoUpdate, createElectronApp, initApp, startupWithUpdater };