electron-incremental-update 2.0.0-beta.8 → 2.0.0

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,57 +1,54 @@
1
- import { isDev, getEntryVersion, getAppVersion, getPathFromAppNameAsar, restartApp } from './chunk-4MH6ZXCY.js';
2
- import { isUpdateJSON, __require } from './chunk-72ZAJ7AF.js';
3
- import path from 'node:path';
1
+ import { isDev, getEntryVersion, getAppVersion, getPathFromAppNameAsar, restartApp } from './chunk-IABBXJFB.js';
2
+ import { isUpdateJSON, __require } from './chunk-RCRKUKFX.js';
4
3
  import fs2 from 'node:fs';
5
- import { app } from 'electron';
6
4
  import { EventEmitter } from 'node:events';
5
+ import { app } from 'electron';
6
+ import path from 'node:path';
7
7
 
8
- // src/updater/types.ts
8
+ // src/entry/types.ts
9
9
  var ErrorInfo = {
10
- download: "Download failed",
11
- validate: "Validate failed",
12
- param: "Missing params",
13
- network: "Network error"
10
+ download: "Download Failed",
11
+ validate: "Validate Failed",
12
+ param: "Missing Params",
13
+ network: "Network Error"
14
14
  };
15
15
  var UpdaterError = class extends Error {
16
16
  code;
17
17
  constructor(msg, info) {
18
- super(ErrorInfo[msg] + ": " + info);
18
+ super(`[${ErrorInfo[msg]}] ${info}`);
19
19
  this.code = msg;
20
20
  }
21
21
  };
22
22
 
23
- // src/updater/core.ts
23
+ // src/entry/updater.ts
24
24
  var Updater = class extends EventEmitter {
25
- CERT = __EIU_SIGNATURE_CERT__;
25
+ CERT;
26
+ controller;
26
27
  info;
27
28
  provider;
28
29
  /**
29
- * updater logger
30
+ * Updater logger
30
31
  */
31
32
  logger;
32
33
  /**
33
- * whether to receive beta update
34
+ * Whether to receive beta update
34
35
  */
35
36
  receiveBeta;
36
37
  /**
37
- * whether force update in DEV
38
+ * Whether force update in DEV
38
39
  */
39
40
  forceUpdate;
40
41
  /**
41
- * initialize incremental updater
42
- * @param provider update provider
43
- * @param option UpdaterOption
42
+ * Initialize incremental updater
43
+ * @param options UpdaterOption
44
44
  */
45
- constructor(provider, option = {}) {
45
+ constructor(options = {}) {
46
46
  super();
47
- this.provider = provider;
48
- this.receiveBeta = option.receiveBeta;
49
- if (option.SIGNATURE_CERT) {
50
- this.CERT = option.SIGNATURE_CERT;
51
- }
52
- if (option.logger) {
53
- this.logger = option.logger;
54
- }
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();
55
52
  if (isDev && !this.logger) {
56
53
  this.logger = {
57
54
  info: (...args) => console.log("[EIU-INFO ]", ...args),
@@ -59,7 +56,10 @@ var Updater = class extends EventEmitter {
59
56
  warn: (...args) => console.log("[EIU-WARN ]", ...args),
60
57
  error: (...args) => console.error("[EIU-ERROR]", ...args)
61
58
  };
62
- this.logger.info("no logger set, enable dev-only logger");
59
+ this.logger.info("No logger set, enable dev-only logger");
60
+ }
61
+ if (!this.provider) {
62
+ this.logger?.debug("WARN: No update provider");
63
63
  }
64
64
  }
65
65
  async fetch(format, data) {
@@ -67,90 +67,99 @@ var Updater = class extends EventEmitter {
67
67
  if (format === "json" && isUpdateJSON(data) || format === "buffer" && Buffer.isBuffer(data)) {
68
68
  return data;
69
69
  } else {
70
- this.err("invalid type", "param", `invalid type at format '${format}': ${JSON.stringify(data)}`);
70
+ this.err("Invalid type", "param", `Invalid type at format '${format}': ${JSON.stringify(data)}`);
71
71
  return;
72
72
  }
73
73
  }
74
- this.logger?.debug(`download from ${this.provider.name}`);
74
+ this.logger?.debug(`Download from \`${this.provider.name}\``);
75
75
  try {
76
- const result = format === "json" ? await this.provider.downloadJSON(data ?? __EIU_VERSION_PATH__) : await this.provider.downloadAsar(app.name, this.info, (data2) => this.emit("download-progress", data2));
77
- 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}` : ""}`);
78
78
  return result;
79
79
  } catch (e) {
80
- this.err(`fetch ${format} failed`, "network", `download ${format} failed: ${e}`);
80
+ this.err(`Fetch ${format} failed`, "network", e instanceof Error ? e.message : e.toString());
81
81
  }
82
82
  }
83
83
  /**
84
- * handle error message and emit error event
84
+ * Handle error message and emit error event
85
85
  */
86
86
  err(msg, code, errorInfo) {
87
87
  const err = new UpdaterError(code, errorInfo);
88
88
  this.logger?.error(msg, err);
89
89
  this.emit("error", err);
90
90
  }
91
- async checkUpdate(data) {
92
- const emitUnavailable = (msg) => {
91
+ async checkForUpdates(data) {
92
+ const emitUnavailable = (msg, info2) => {
93
93
  this.logger?.info(msg);
94
- this.emit("update-unavailable", msg);
94
+ this.emit("update-not-available", msg, info2);
95
95
  return false;
96
96
  };
97
+ if (!data && !this.provider) {
98
+ this.err("Check update failed", "param", "No update json or provider");
99
+ return false;
100
+ }
97
101
  const _data = await this.fetch("json", data);
98
102
  if (!_data) {
99
- return emitUnavailable("failed to get update info");
103
+ return emitUnavailable("Failed to get update info");
100
104
  }
101
- let { signature, version, minimumVersion, beta } = _data;
102
- if (this.receiveBeta) {
103
- version = beta.version;
104
- signature = beta.signature;
105
- minimumVersion = beta.minimumVersion;
106
- }
107
- this.logger?.debug(`checked update, version: ${version}, signature: ${signature}`);
105
+ const { signature, version, minimumVersion } = this.receiveBeta ? _data.beta : _data;
106
+ const info = { signature, minimumVersion, version };
107
+ this.logger?.debug(`Checked update, version: ${version}, signature: ${signature}`);
108
108
  if (isDev && !this.forceUpdate && !data) {
109
- return emitUnavailable("skip check update in dev mode, to force update, set `updater.forceUpdate` to true or call checkUpdate with UpdateJSON");
109
+ return emitUnavailable("Skip check update in dev mode. To force update, set `updater.forceUpdate` to true or call checkUpdate with UpdateJSON", info);
110
110
  }
111
111
  const isLowerVersion = this.provider.isLowerVersion;
112
112
  const entryVersion = getEntryVersion();
113
113
  const appVersion = getAppVersion();
114
- if (isLowerVersion(entryVersion, minimumVersion)) {
115
- return emitUnavailable(`entry version (${entryVersion}) < minimumVersion (${minimumVersion})`);
116
- }
117
- this.logger?.info(`check update: current version is ${appVersion}, new version is ${version}`);
118
- if (!isLowerVersion(appVersion, version)) {
119
- return emitUnavailable(`current version (${appVersion}) < new version (${version})`);
114
+ try {
115
+ if (isLowerVersion(entryVersion, minimumVersion)) {
116
+ return emitUnavailable(`Entry Version (${entryVersion}) < MinimumVersion (${minimumVersion})`, info);
117
+ }
118
+ this.logger?.info(`Check update: current version is ${appVersion}, new version is ${version}`);
119
+ if (!isLowerVersion(appVersion, version)) {
120
+ return emitUnavailable(`Current version (${appVersion}) < New version (${version})`, info);
121
+ }
122
+ this.logger?.info(`Update available: ${version}`);
123
+ this.emit("update-available", info);
124
+ this.info = info;
125
+ return true;
126
+ } catch {
127
+ this.err("Fail to parse version", "validate", "Fail to parse version string");
128
+ return false;
120
129
  }
121
- this.logger?.info(`update available: ${version}`);
122
- this.info = { signature, minimumVersion, version };
123
- this.emit("update-available", this.info);
124
- return true;
125
130
  }
126
131
  async downloadUpdate(data, info) {
127
132
  const _sig = info?.signature ?? this.info?.signature;
128
133
  const _version = info?.version ?? this.info?.version;
129
134
  if (!_sig || !_version) {
130
- this.err("download failed", "param", "no update signature, please call `checkUpdate` first or manually setup params");
135
+ this.err("Download failed", "param", "No update signature, please call `checkUpdate` first or manually setup params");
136
+ return false;
137
+ }
138
+ if (!data && !this.provider) {
139
+ this.err("Download failed", "param", "No update asar buffer and provider");
131
140
  return false;
132
141
  }
133
142
  const buffer = await this.fetch("buffer", data ? Buffer.from(data) : void 0);
134
143
  if (!buffer) {
135
- this.err("download failed", "param", "no update asar file buffer");
144
+ this.err("Download failed", "param", "No update asar file buffer");
136
145
  return false;
137
146
  }
138
147
  this.logger?.debug("verify start");
139
148
  if (!await this.provider.verifySignaure(buffer, _version, _sig, this.CERT)) {
140
- this.err("download failed", "validate", "invalid update asar file");
149
+ this.err("Download failed", "validate", "Invalid update asar file");
141
150
  return false;
142
151
  }
143
- this.logger?.debug("verify success");
152
+ this.logger?.debug("Verify success");
144
153
  try {
145
- const tmpFilePath = getPathFromAppNameAsar() + ".tmp";
146
- this.logger?.debug(`install to ${tmpFilePath}`);
154
+ const tmpFilePath = `${getPathFromAppNameAsar()}.tmp`;
155
+ this.logger?.debug(`Install to ${tmpFilePath}`);
147
156
  fs2.writeFileSync(tmpFilePath, await this.provider.unzipFile(buffer));
148
- this.logger?.info(`download success, version: ${_version}`);
157
+ this.logger?.info(`Download success, version: ${_version}`);
149
158
  this.info = void 0;
150
159
  this.emit("update-downloaded");
151
160
  return true;
152
161
  } catch (error) {
153
- this.err("download failed", "download", `fail to unwrap asar file, ${error}`);
162
+ this.err("Download failed", "download", `Fail to unwrap asar file, ${error}`);
154
163
  return false;
155
164
  }
156
165
  }
@@ -158,26 +167,24 @@ var Updater = class extends EventEmitter {
158
167
  * quit App and install
159
168
  */
160
169
  quitAndInstall() {
161
- this.logger?.info("quit and install");
170
+ this.logger?.info("Quit and install");
162
171
  restartApp();
163
172
  }
164
- /**
165
- * setup provider URL handler
166
- *
167
- * @example
168
- * updater.setURLHandler((url, isDownloadingAsar) => {
169
- * if (isDownloadingAsar) {
170
- * url.hostname = 'https://cdn.jsdelivr.net/gh'
171
- * return url
172
- * }
173
- * })
174
- */
175
- setURLHandler(handler) {
176
- this.provider.urlHandler = handler;
173
+ cancel() {
174
+ if (this.controller.signal.aborted) {
175
+ return;
176
+ }
177
+ this.controller.abort();
178
+ this.logger?.info("Cancel update");
179
+ this.emit("update-cancelled");
180
+ this.controller = new AbortController();
177
181
  }
178
182
  };
179
-
180
- // src/entry.ts
183
+ async function autoUpdate(updater) {
184
+ if (await updater.checkForUpdates() && await updater.downloadUpdate()) {
185
+ updater.quitAndInstall();
186
+ }
187
+ }
181
188
  function startupWithUpdater(fn) {
182
189
  return fn;
183
190
  }
@@ -185,9 +192,14 @@ var defaultOnInstall = (install, _, __, logger) => {
185
192
  install();
186
193
  logger?.info(`update success!`);
187
194
  };
188
- async function initApp(appOptions) {
195
+ async function createElectronApp(appOptions = {}) {
196
+ const appNameAsarPath = getPathFromAppNameAsar();
189
197
  const {
190
- provider,
198
+ mainPath = path.join(
199
+ isDev ? path.join(app.getAppPath(), __EIU_MAIN_DEV_DIR__) : appNameAsarPath,
200
+ "main",
201
+ __EIU_MAIN_FILE__
202
+ ),
191
203
  updater,
192
204
  onInstall = defaultOnInstall,
193
205
  beforeStart,
@@ -195,38 +207,29 @@ async function initApp(appOptions) {
195
207
  } = appOptions;
196
208
  let updaterInstance;
197
209
  if (typeof updater === "object" || !updater) {
198
- updaterInstance = new Updater(provider, updater);
210
+ updaterInstance = new Updater(updater);
199
211
  } else {
200
212
  updaterInstance = await updater();
201
213
  }
202
- let logger = updaterInstance.logger;
203
- if (isDev && !logger) {
204
- logger = {
205
- info: (...args) => console.log("[EIU-INFO ]", ...args),
206
- debug: (...args) => console.log("[EIU-DEBUG]", ...args),
207
- warn: (...args) => console.log("[EIU-WARN ]", ...args),
208
- error: (...args) => console.error("[EIU-ERROR]", ...args)
209
- };
210
- }
214
+ const logger = updaterInstance.logger;
211
215
  try {
212
- const appNameAsarPath = getPathFromAppNameAsar();
213
216
  const tempAsarPath = `${appNameAsarPath}.tmp`;
214
217
  if (fs2.existsSync(tempAsarPath)) {
215
- logger?.info(`installing new asar: ${tempAsarPath}`);
218
+ logger?.info(`Installing new asar from ${tempAsarPath}`);
216
219
  await onInstall(() => fs2.renameSync(tempAsarPath, appNameAsarPath), tempAsarPath, appNameAsarPath, logger);
217
220
  }
218
- const mainFilePath = path.join(
219
- isDev ? path.join(app.getAppPath(), __EIU_MAIN_DEV_DIR__) : appNameAsarPath,
220
- "main",
221
- __EIU_MAIN_FILE__
222
- );
223
- await beforeStart?.(mainFilePath, logger);
224
- __require(mainFilePath)(updaterInstance);
221
+ await beforeStart?.(mainPath, logger);
222
+ if (__EIU_IS_ESM__) {
223
+ (await import(`file://${mainPath}`)).default(updaterInstance);
224
+ } else {
225
+ __require(mainPath)(updaterInstance);
226
+ }
225
227
  } catch (error) {
226
228
  logger?.error("startup error", error);
227
229
  onStartError?.(error, logger);
228
230
  app.quit();
229
231
  }
230
232
  }
233
+ var initApp = createElectronApp;
231
234
 
232
- export { ErrorInfo, Updater, UpdaterError, initApp, startupWithUpdater };
235
+ export { ErrorInfo, Updater, UpdaterError, autoUpdate, createElectronApp, initApp, startupWithUpdater };
package/dist/provider.cjs CHANGED
@@ -7,6 +7,7 @@ var zlib = require('zlib');
7
7
 
8
8
  function _interopDefault (e) { return e && e.__esModule ? e : { default: e }; }
9
9
 
10
+ var electron__default = /*#__PURE__*/_interopDefault(electron);
10
11
  var crypto__default = /*#__PURE__*/_interopDefault(crypto);
11
12
  var zlib__default = /*#__PURE__*/_interopDefault(zlib);
12
13
 
@@ -32,7 +33,7 @@ function parseVersion(version) {
32
33
  ret.stageVersion = Number(_v) || -1;
33
34
  }
34
35
  if (Number.isNaN(major) || Number.isNaN(minor) || Number.isNaN(patch) || Number.isNaN(ret.stageVersion)) {
35
- throw new TypeError(`invalid version: ${version}`);
36
+ throw new TypeError(`Invalid version: ${version}`);
36
37
  }
37
38
  return ret;
38
39
  }
@@ -62,19 +63,19 @@ function isUpdateJSON(json) {
62
63
  }
63
64
 
64
65
  // src/provider/download.ts
65
- function getHeader(response, headerKey) {
66
- const value = response.headers[headerKey];
66
+ function getHeader(headers, key) {
67
+ const value = headers[key];
67
68
  if (Array.isArray(value)) {
68
69
  return value.length === 0 ? null : value[value.length - 1];
69
70
  } else {
70
71
  return value;
71
72
  }
72
73
  }
73
- async function downloadFn(url, headers, onResponse) {
74
- await electron.app.whenReady();
74
+ async function downloadFn(url, headers, signal, onResponse) {
75
+ await electron__default.default.app.whenReady();
75
76
  return new Promise((resolve, reject) => {
76
- const request = electron.net.request({ url, method: "GET", redirect: "follow" });
77
- Object.keys(headers).forEach((key) => request.setHeader(key, headers[key]));
77
+ const request = electron__default.default.net.request({ url, method: "GET", redirect: "follow", headers, cache: "no-cache" });
78
+ signal.addEventListener("abort", () => request.abort(), { once: true });
78
79
  request.on("response", (resp) => {
79
80
  resp.on("aborted", () => reject(new Error("aborted")));
80
81
  resp.on("error", () => reject(new Error("download error")));
@@ -84,46 +85,56 @@ async function downloadFn(url, headers, onResponse) {
84
85
  request.end();
85
86
  });
86
87
  }
87
- async function defaultDownloadUpdateJSON(url, headers) {
88
- return await downloadFn(url, headers, (resp, resolve, reject) => {
89
- let data = "";
90
- resp.on("data", (chunk) => data += chunk);
91
- resp.on("end", () => {
92
- try {
93
- const json = JSON.parse(data);
94
- if (isUpdateJSON(json)) {
95
- resolve(json);
96
- } else {
97
- throw Error;
88
+ async function defaultDownloadUpdateJSON(url, headers, signal) {
89
+ return await downloadFn(
90
+ url,
91
+ headers,
92
+ signal,
93
+ (resp, resolve, reject) => {
94
+ let data = "";
95
+ resp.on("data", (chunk) => data += chunk);
96
+ resp.on("end", () => {
97
+ try {
98
+ const json = JSON.parse(data);
99
+ if (isUpdateJSON(json)) {
100
+ resolve(json);
101
+ } else {
102
+ throw Error;
103
+ }
104
+ } catch {
105
+ reject(new Error(`Invalid update json, "${data}"`));
98
106
  }
99
- } catch {
100
- reject(new Error("invalid update json"));
101
- }
102
- });
103
- });
107
+ });
108
+ }
109
+ );
104
110
  }
105
- async function defaultDownloadAsar(url, headers, onDownloading) {
111
+ async function defaultDownloadAsar(url, headers, signal, onDownloading) {
106
112
  let transferred = 0;
107
113
  let time = Date.now();
108
- return await downloadFn(url, headers, (resp, resolve) => {
109
- const total = getHeader(resp.headers, "content-length") || -1;
110
- let data = [];
111
- resp.on("data", (chunk) => {
112
- const delta = chunk.length;
113
- transferred += delta;
114
- const current = Date.now();
115
- onDownloading?.({
116
- percent: +(transferred / total).toFixed(2) * 100,
117
- total,
118
- transferred,
119
- delta,
120
- bps: delta / ((current - time) * 1e3)
114
+ return await downloadFn(
115
+ url,
116
+ headers,
117
+ signal,
118
+ (resp, resolve) => {
119
+ const total = +getHeader(resp.headers, "content-length") || -1;
120
+ const data = [];
121
+ resp.on("data", (chunk) => {
122
+ const delta = chunk.length;
123
+ transferred += delta;
124
+ const current = Date.now();
125
+ onDownloading?.({
126
+ percent: total ? +(transferred / total).toFixed(2) * 100 : -1,
127
+ total,
128
+ transferred,
129
+ delta,
130
+ bps: delta / (current - time)
131
+ });
132
+ time = current;
133
+ data.push(chunk);
121
134
  });
122
- time = current;
123
- data.push(chunk);
124
- });
125
- resp.on("end", () => resolve(Buffer.concat(data)));
126
- });
135
+ resp.on("end", () => resolve(Buffer.concat(data)));
136
+ }
137
+ );
127
138
  }
128
139
  function hashBuffer(data, length) {
129
140
  const hash = crypto__default.default.createHash("SHA256").update(data).digest("binary");
@@ -159,8 +170,17 @@ async function defaultUnzipFile(buffer) {
159
170
  // src/provider/base.ts
160
171
  var BaseProvider = class {
161
172
  name = "BaseProvider";
173
+ /**
174
+ * @inheritdoc
175
+ */
162
176
  isLowerVersion = defaultIsLowerVersion;
177
+ /**
178
+ * @inheritdoc
179
+ */
163
180
  verifySignaure = defaultVerifySignature;
181
+ /**
182
+ * @inheritdoc
183
+ */
164
184
  unzipFile = defaultUnzipFile;
165
185
  };
166
186
 
@@ -170,15 +190,18 @@ var GitHubProvider = class extends BaseProvider {
170
190
  options;
171
191
  /**
172
192
  * Update Provider for Github repo
173
- * - download update json from `https://raw.githubusercontent.com/{user}/{repo}/HEAD/{versionPath}`
193
+ * - download update json from `https://github.com/{user}/{repo}/raw/HEAD/{versionPath}`
174
194
  * - download update asar from `https://github.com/{user}/{repo}/releases/download/v{version}/{name}-{version}.asar.gz`
175
195
  *
176
- * you can setup `urlHandler` in {@link GitHubProviderOptions} or `Updater` to modify url before request
196
+ * you can setup `urlHandler` in {@link GitHubProviderOptions} to modify url before request
177
197
  * @param options provider options
178
198
  */
179
199
  constructor(options) {
180
200
  super();
181
201
  this.options = options;
202
+ if (!options.branch) {
203
+ this.options.branch = "HEAD";
204
+ }
182
205
  }
183
206
  get urlHandler() {
184
207
  return this.options.urlHandler;
@@ -186,23 +209,31 @@ var GitHubProvider = class extends BaseProvider {
186
209
  set urlHandler(handler) {
187
210
  this.options.urlHandler = handler;
188
211
  }
189
- async parseURL(isDownloadAsar, extraPath) {
212
+ async parseURL(extraPath) {
190
213
  const url$1 = new url.URL(
191
- `/${this.options.username}/${this.options.repo}/${extraPath}`,
192
- "https://" + (isDownloadAsar ? "github.com" : "raw.githubusercontent.com")
214
+ `/${this.options.user}/${this.options.repo}/${extraPath}`,
215
+ "https://github.com"
193
216
  );
194
- return (await this.urlHandler?.(url$1, isDownloadAsar) || url$1).toString();
217
+ return (await this.urlHandler?.(url$1) || url$1).toString();
195
218
  }
196
- async downloadJSON(versionPath) {
219
+ /**
220
+ * @inheritdoc
221
+ */
222
+ async downloadJSON(versionPath, signal) {
197
223
  return await defaultDownloadUpdateJSON(
198
- await this.parseURL(false, `${this.options.branch ?? "HEAD"}/${versionPath}`),
199
- { accept: "application/json", ...this.options.extraHeaders }
224
+ await this.parseURL(`raw/${this.options.branch}/${versionPath}`),
225
+ { Accept: "application/json", ...this.options.extraHeaders },
226
+ signal
200
227
  );
201
228
  }
202
- async downloadAsar(name, info, onDownloading) {
229
+ /**
230
+ * @inheritdoc
231
+ */
232
+ async downloadAsar(name, info, signal, onDownloading) {
203
233
  return await defaultDownloadAsar(
204
- await this.parseURL(true, `releases/download/v${info.version}/${name}-${info.version}.asar.gz`),
205
- { accept: "application/octet-stream", ...this.options.extraHeaders },
234
+ await this.parseURL(`releases/download/v${info.version}/${name}-${info.version}.asar.gz`),
235
+ { Accept: "application/octet-stream", ...this.options.extraHeaders },
236
+ signal,
206
237
  onDownloading
207
238
  );
208
239
  }