electron-incremental-update 0.3.0 → 0.4.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/README.md CHANGED
@@ -16,6 +16,8 @@ using RSA + Signature to sign the new `main.asar` downloaded from remote and rep
16
16
 
17
17
  develop with [vite-plugin-electron](https://github.com/electron-vite/vite-plugin-electron), and may be effect in other electron vite frameworks
18
18
 
19
+ **all options are documented in the jsdoc**
20
+
19
21
  ## install
20
22
 
21
23
  ### npm
@@ -50,21 +52,28 @@ src
50
52
 
51
53
  ### setup app
52
54
 
53
- more example see comment on `initApp()`
54
55
 
55
56
  ```ts
56
57
  // electron/app.ts
57
- import { createUpdater, initApp } from 'electron-incremental-update'
58
+ import { createUpdater, getGithubReleaseCdnGroup, initApp, parseGithubCdnURL } from 'electron-incremental-update'
58
59
  import { name, repository } from '../package.json'
59
60
 
60
61
  const SIGNATURE_PUB = '' // auto generate RSA public key when start app
61
62
 
63
+ // create updater when init, no need to set productName
64
+ initApp({ name }, { SIGNATURE_PUB, repository })
65
+
66
+ // or create updater manually
67
+ const { cdnPrefix } = getGithubReleaseCdnGroup()[0]
62
68
  const updater = createUpdater({
63
69
  SIGNATURE_PUB,
64
- repository,
65
70
  productName: name,
71
+ repository,
72
+ updateJsonURL: parseGithubCdnURL(repository, 'fastly.jsdelivr.net/gh', 'version.json'),
73
+ releaseAsarURL: parseGithubCdnURL(repository, cdnPrefix, `download/latest/${name}.asar.gz`),
74
+ debug: true,
66
75
  })
67
- initApp(name, updater)
76
+ initApp({ name }).setUpdater(updater)
68
77
  ```
69
78
 
70
79
  ### setup main
@@ -72,43 +81,43 @@ initApp(name, updater)
72
81
  ```ts
73
82
  // electron/main/index.ts
74
83
  import type { Updater } from 'electron-incremental-update'
75
- import { getAppAsarPath, getAppVersion, getElectronVersion } from 'electron-incremental-update'
84
+ import { getAppAsarPath, getAppVersion, getEntryVersion } from 'electron-incremental-update'
76
85
  import { app } from 'electron'
77
86
  import { name } from '../../package.json'
78
87
 
79
88
  export default function (updater: Updater) {
80
89
  console.log('\ncurrent:')
81
- console.log(`\telectron: ${getElectronVersion()}`)
82
90
  console.log(`\tasar path: ${getAppAsarPath(name)}`)
91
+ console.log(`\tentry: ${getEntryVersion()}`)
83
92
  console.log(`\tapp: ${getAppVersion(name)}`)
84
-
85
- updater.checkUpdate()
86
- updater.on('checkResult', async (result, err) => {
87
- switch (result) {
88
- case 'success':
89
- await dialog.showMessageBox({
90
- type: 'info',
91
- buttons: ['Restart', 'Later'],
92
- message: 'Application successfully updated!',
93
- }).then(({ response }) => {
94
- if (response === 0) {
95
- app.relaunch()
96
- app.quit()
97
- }
98
- })
99
- break
100
- case 'unavailable':
101
- console.log('Update Unavailable')
102
- break
103
- case 'fail':
104
- console.error(err)
105
- break
93
+ let size = 0
94
+ let currentSize = 0
95
+ updater.on('checkResult', async (result) => {
96
+ if (result === false) {
97
+ console.log('Update Unavailable')
98
+ } else if (result instanceof Error) {
99
+ console.error(result)
100
+ } else {
101
+ size = result.size
102
+ console.log('new version: ', result.version)
103
+ const { response } = await dialog.showMessageBox({
104
+ type: 'info',
105
+ buttons: ['Download', 'Later'],
106
+ message: 'Application update available!',
107
+ })
108
+ response === 0 && await updater.downloadUpdate()
106
109
  }
107
110
  })
108
- updater.on('downloadStart', console.log)
109
- updater.on('downloading', console.log)
110
- updater.on('downloadEnd', console.log)
111
+ updater.on('download', () => console.log('download start'))
112
+ updater.on('downloading', (len) => {
113
+ currentSize += len
114
+ console.log(`${(currentSize / size).toFixed(2)}%`)
115
+ })
116
+ updater.on('downloaded', () => console.log('download end'))
111
117
  updater.on('donwnloadError', console.error)
118
+ // to debug, it need to set debug to true in updater options
119
+ updater.on('debug', data => console.log('[updater]:', data))
120
+ updater.checkUpdate()
112
121
 
113
122
  // app logics
114
123
  app.whenReady().then(() => {
@@ -144,7 +153,12 @@ db.close()
144
153
 
145
154
  ### setup vite.config.ts
146
155
 
156
+ make sure the plugin is set in the **last** build task plugin option
157
+
158
+ - set it to preload task plugin, as the end of build task
159
+
147
160
  ```ts
161
+ // vite.config.ts
148
162
  export default defineConfig(({ command }) => {
149
163
 
150
164
  const isBuild = command === 'build'
@@ -162,7 +176,7 @@ export default defineConfig(({ command }) => {
162
176
  // ...
163
177
  vite: {
164
178
  plugins: [
165
- updater({ // !make sure the plugin run pack asar after all build finish
179
+ updater({
166
180
  productName: pkg.name,
167
181
  version: pkg.version,
168
182
  isBuild,
@@ -179,90 +193,6 @@ export default defineConfig(({ command }) => {
179
193
  })
180
194
  ```
181
195
 
182
- #### plugin options
183
-
184
- ```ts
185
- type Options = {
186
- /**
187
- * whether is in build mode
188
- */
189
- isBuild: boolean
190
- /**
191
- * the name of you application
192
- *
193
- * you can set as 'name' in package.json
194
- */
195
- productName: string
196
- /**
197
- * the version of you application
198
- *
199
- * you can set as 'version' in package.json
200
- */
201
- version: string
202
- /**
203
- * Whether to minify
204
- */
205
- minify?: boolean
206
- /**
207
- * path config
208
- */
209
- paths?: {
210
- /**
211
- * Path to app entry file
212
- * @default 'electron/app.ts'
213
- */
214
- entryPath?: string
215
- /**
216
- * Path to app entry output file
217
- * @default 'app.js'
218
- */
219
- entryOutputPath?: string
220
- /**
221
- * Path to asar file
222
- * @default `release/${ProductName}.asar`
223
- */
224
- asarOutputPath?: string
225
- /**
226
- * Path to electron build output
227
- * @default `dist-electron`
228
- */
229
- electronDistPath?: string
230
- /**
231
- * Path to renderer build output
232
- * @default `dist`
233
- */
234
- rendererDistPath?: string
235
- /**
236
- * Path to version info output
237
- * @default `version.json`
238
- */
239
- versionPath?: string
240
- }
241
- /**
242
- * signature config
243
- */
244
- keys?: {
245
- /**
246
- * Path to the pem file that contains private key
247
- * if not ended with .pem, it will be appended
248
- * @default 'public/private.pem'
249
- */
250
- privateKeyPath?: string
251
- /**
252
- * Path to the pem file that contains public key
253
- * if not ended with .pem, it will be appended
254
- * @default 'public/public.pem'
255
- */
256
- publicKeyPath?: string
257
- /**
258
- * Length of the key
259
- * @default 2048
260
- */
261
- keyLength?: number
262
- }
263
- }
264
- ```
265
-
266
196
  ### electron-builder config
267
197
 
268
198
  ```js
package/dist/index.cjs CHANGED
@@ -63,11 +63,15 @@ function downloadJSONDefault(url, updater, headers) {
63
63
  res.headers = headers;
64
64
  res.on("data", (chunk) => data += chunk);
65
65
  res.on("end", () => {
66
- const json = JSON.parse(data);
67
- if ("signature" in json && "version" in json && "size" in json) {
68
- resolve2(json);
69
- } else {
70
- throw new Error("invalid update json");
66
+ try {
67
+ const json = JSON.parse(data);
68
+ if ("signature" in json && "version" in json && "size" in json) {
69
+ resolve2(json);
70
+ } else {
71
+ throw Error;
72
+ }
73
+ } catch (e) {
74
+ reject(new Error("invalid json"));
71
75
  }
72
76
  });
73
77
  }).on("error", (e) => {
@@ -177,6 +181,7 @@ function createUpdater({
177
181
  }) {
178
182
  const updater = new import_node_events.EventEmitter();
179
183
  let signature = "";
184
+ let version = "";
180
185
  const gzipPath = `../${productName}.asar.gz`;
181
186
  const tmpFile = gzipPath.replace(".asar.gz", ".tmp.gz");
182
187
  const { downloadBuffer, downloadJSON, extraHeader, userAgent } = downloadConfig || {};
@@ -190,12 +195,16 @@ function createUpdater({
190
195
  UserAgent: ua,
191
196
  ...extraHeader
192
197
  };
193
- log(`headers: ${headers}`);
198
+ log(`download headers: ${JSON.stringify(headers, null, 2)}`);
194
199
  const downloadFn = format === "json" ? downloadJSON ?? downloadJSONDefault : downloadBuffer ?? downloadBufferDefault;
195
- return await downloadFn(url, updater, headers);
200
+ log(`download ${format} from ${url}`);
201
+ const ret = await downloadFn(url, updater, headers);
202
+ log(`download ${format} success`);
203
+ return ret;
196
204
  }
197
205
  async function extractFile(gzipFilePath) {
198
206
  if (!gzipFilePath.endsWith(".asar.gz") || !(0, import_node_fs2.existsSync)(gzipFilePath)) {
207
+ log("update .asar.gz file not exist");
199
208
  return;
200
209
  }
201
210
  gzipFilePath = gzipFilePath.replace(".asar.gz", ".tmp.gz");
@@ -207,55 +216,61 @@ function createUpdater({
207
216
  log(`outputFilePath: ${outputFilePath}`);
208
217
  input.pipe(gunzip).pipe(output).on("finish", async () => {
209
218
  await (0, import_promises.rm)(gzipFilePath);
210
- log("finish");
219
+ log(`${gzipFilePath} unzipped`);
211
220
  resolve2(outputFilePath);
212
221
  }).on("error", async (err) => {
213
222
  await (0, import_promises.rm)(gzipFilePath);
214
- log(`error: ${err}`);
215
223
  output.destroy(err);
216
224
  reject(err);
217
225
  });
218
226
  });
219
227
  }
220
228
  function verify(buffer, signature2) {
221
- log(`signature: ${signature2}`);
222
229
  return (0, import_node_crypto.createVerify)("RSA-SHA256").update(buffer).verify(SIGNATURE_PUB, signature2, "base64");
223
230
  }
224
- function needUpdate(version) {
225
- if (!version || !import_electron2.app.isPackaged) {
231
+ function needUpdate(version2) {
232
+ if (!import_electron2.app.isPackaged) {
233
+ log("in dev mode, no need to update");
226
234
  return false;
227
235
  }
228
236
  const currentVersion = getEntryVersion();
229
- log(`currentVersion: ${currentVersion}`);
230
- log(`newVersion: ${version}`);
237
+ log(`check update:
238
+ current version is ${currentVersion},
239
+ new version is ${version2}`);
231
240
  const _compare = compareVersion ?? compareVersionDefault;
232
- return _compare(currentVersion, version);
241
+ return _compare(currentVersion, version2);
233
242
  }
234
243
  async function checkUpdate(url) {
235
244
  url ??= _update;
236
245
  if (!url) {
237
- log("no updateJsonURL, use repository");
246
+ log("no updateJsonURL, fallback to use repository");
238
247
  if (!repository) {
239
248
  throw new Error("updateJsonURL or repository are not set");
240
249
  }
241
- url = `${repository.replace("github.com", "raw.githubusercontent.com")}/version.json`;
250
+ url = `${repository.replace("github.com", "raw.githubusercontent.com")}/master/version.json`;
242
251
  }
243
- log(`updateJsonURL: ${url}`);
244
252
  if ((0, import_node_fs2.existsSync)(tmpFile)) {
245
253
  log(`remove tmp file: ${tmpFile}`);
246
254
  await (0, import_promises.rm)(tmpFile);
247
255
  }
256
+ if ((0, import_node_fs2.existsSync)(gzipPath)) {
257
+ log(`remove .gz file: ${gzipPath}`);
258
+ await (0, import_promises.rm)(gzipPath);
259
+ }
248
260
  const json = await download(url, "json");
249
261
  const {
250
262
  signature: _sig,
251
- version,
263
+ version: _v,
252
264
  size
253
265
  } = json;
254
- log(`UpdateJSON: ${JSON.stringify(json, null, 2)}`);
255
- if (!needUpdate(version)) {
266
+ log(`update info: ${JSON.stringify(json, null, 2)}`);
267
+ if (!await needUpdate(_v)) {
268
+ log(`update unavailable: ${_v}`);
256
269
  return false;
257
270
  } else {
271
+ log(`update available: ${_v}`);
258
272
  signature = _sig;
273
+ version = _v;
259
274
  return { size, version };
260
275
  }
261
276
  }
@@ -263,23 +278,25 @@ function createUpdater({
263
278
  if (typeof src !== "object") {
264
279
  let _url = src ?? _release;
265
280
  if (!_url) {
266
- log("no releaseAsarURL, use repository");
281
+ log("no releaseAsarURL, fallback to use repository");
267
282
  if (!repository) {
268
283
  throw new Error("releaseAsarURL or repository are not set");
269
284
  }
270
285
  _url = `${repository}/releases/download/latest/${productName}.asar.gz`;
271
286
  }
272
- log(`releaseAsarURL: ${_url}`);
273
287
  src = await download(_url, "buffer");
274
288
  }
275
- log("start verify");
289
+ log("verify start");
276
290
  if (!verify(src, signature)) {
277
- throw new Error("file broken, invalid signature!");
291
+ log("verify failed");
292
+ throw new Error("invalid signature");
278
293
  }
294
+ log("verify success");
279
295
  log(`write file: ${gzipPath}`);
280
296
  await (0, import_promises.writeFile)(gzipPath, src);
281
297
  log(`extract file: ${gzipPath}`);
282
298
  await extractFile(gzipPath);
299
+ log(`update success, version: ${version}`);
283
300
  updater.emit("downloaded");
284
301
  }
285
302
  const onCheck = async (url) => {
@@ -307,21 +324,25 @@ function createUpdater({
307
324
  }
308
325
 
309
326
  // src/index.ts
310
- function initApp(productName, updater, option) {
327
+ function initApp(appOptions, updaterOptions) {
311
328
  const {
329
+ name: productName,
312
330
  electronDistPath = "dist-electron",
313
331
  mainPath = "main/index.js"
314
- } = option ?? {};
332
+ } = appOptions ?? {};
315
333
  const mainDir = import_electron3.app.isPackaged ? `../${productName}.asar` : electronDistPath;
316
334
  const entry = (0, import_node_path2.resolve)(__dirname, mainDir, mainPath);
317
- let _updater;
318
- if ("SIGNATURE_PUB" in updater) {
319
- const _option = updater.productName ? updater : { ...updater, productName };
320
- _updater = createUpdater(_option);
335
+ if (updaterOptions) {
336
+ require(entry)(
337
+ createUpdater({ ...updaterOptions, productName })
338
+ );
321
339
  } else {
322
- _updater = updater;
340
+ return {
341
+ setUpdater(updater) {
342
+ require(entry)(updater);
343
+ }
344
+ };
323
345
  }
324
- return require(entry)(_updater);
325
346
  }
326
347
  // Annotate the CommonJS export names for ESM import in node:
327
348
  0 && (module.exports = {
package/dist/index.d.ts CHANGED
@@ -2,9 +2,9 @@ import { Buffer } from 'node:buffer';
2
2
 
3
3
  type CheckResultType = Error | false | Omit<UpdateJSON, 'signature'>;
4
4
  type UpdateEvents = {
5
- check: null;
5
+ check: [url?: string];
6
6
  checkResult: [data: CheckResultType];
7
- download: null;
7
+ download: [src?: string | Buffer];
8
8
  downloading: [current: number];
9
9
  downloaded: null;
10
10
  donwnloadError: [error: unknown];
@@ -16,20 +16,6 @@ type UpdateJSON = {
16
16
  size: number;
17
17
  };
18
18
  type MaybeArray<T> = T extends undefined | null | never ? [] : T extends any[] ? T['length'] extends 1 ? [data: T[0]] : T : [data: T];
19
- interface BaseOption {
20
- /**
21
- * URL of version info json
22
- * @default `${repository.replace('github.com', 'raw.githubusercontent.com')}/version.json`
23
- * @throws if `updateJsonURL` and `repository` are all not set
24
- */
25
- updateJsonURL?: string;
26
- /**
27
- * URL of release asar.gz
28
- * @default `${repository}/releases/download/latest/${productName}.asar.gz`
29
- * @throws if `releaseAsarURL` and `repository` are all not set
30
- */
31
- releaseAsarURL?: string;
32
- }
33
19
  interface TypedUpdater<T extends Record<string | symbol, MaybeArray<any>>, Event extends Exclude<keyof T, number> = Exclude<keyof T, number>> {
34
20
  removeAllListeners<E extends Event>(event?: E): this;
35
21
  listeners<E extends Event>(eventName: E): Function[];
@@ -43,11 +29,11 @@ interface TypedUpdater<T extends Record<string | symbol, MaybeArray<any>>, Event
43
29
  * - `false`: unavailable
44
30
  * - `{size: number, version: string}`: success
45
31
  */
46
- checkUpdate(url?: BaseOption['updateJsonURL']): Promise<void>;
47
- downloadUpdate(url?: BaseOption['releaseAsarURL'] | Buffer): Promise<void>;
32
+ checkUpdate(url?: string): Promise<void>;
33
+ downloadUpdate(url?: string | Buffer): Promise<void>;
48
34
  }
49
35
  type Updater = TypedUpdater<UpdateEvents>;
50
- interface UpdaterOption extends BaseOption {
36
+ interface UpdaterOption {
51
37
  /**
52
38
  * public key of signature
53
39
  *
@@ -65,7 +51,7 @@ interface UpdaterOption extends BaseOption {
65
51
  */
66
52
  SIGNATURE_PUB: string;
67
53
  /**
68
- * product name
54
+ * name of your application
69
55
  *
70
56
  * you can use the `name` in `package.json`
71
57
  */
@@ -79,8 +65,29 @@ interface UpdaterOption extends BaseOption {
79
65
  * `repository` will be used to determine the url
80
66
  */
81
67
  repository?: string;
68
+ /**
69
+ * URL of version info json
70
+ * @default `${repository.replace('github.com', 'raw.githubusercontent.com')}/master/version.json`
71
+ * @throws if `updateJsonURL` and `repository` are all not set
72
+ */
73
+ updateJsonURL?: string;
74
+ /**
75
+ * URL of release asar.gz
76
+ * @default `${repository}/releases/download/latest/${productName}.asar.gz`
77
+ * @throws if `releaseAsarURL` and `repository` are all not set
78
+ */
79
+ releaseAsarURL?: string;
80
+ /**
81
+ * whether to enable debug listener
82
+ */
82
83
  debug?: boolean;
83
- compareVersion?: (oldVersion: string, newVersion: string) => boolean;
84
+ /**
85
+ * custom version compare function
86
+ * @param oldVersion old version string
87
+ * @param newVersion new version string
88
+ * @returns whether to update
89
+ */
90
+ compareVersion?: (oldVersion: string, newVersion: string) => boolean | Promise<boolean>;
84
91
  downloadConfig?: {
85
92
  /**
86
93
  * download user agent
@@ -94,7 +101,7 @@ interface UpdaterOption extends BaseOption {
94
101
  /**
95
102
  * download JSON function
96
103
  * @param url download url
97
- * @param updater updater, emit events
104
+ * @param updater updater, to trigger events
98
105
  * @param header download header
99
106
  * @returns `UpdateJSON`
100
107
  */
@@ -102,7 +109,7 @@ interface UpdaterOption extends BaseOption {
102
109
  /**
103
110
  * download buffer function
104
111
  * @param url download url
105
- * @param updater updater, emit events
112
+ * @param updater updater, to trigger events
106
113
  * @param header download header
107
114
  * @returns `Buffer`
108
115
  */
@@ -144,61 +151,63 @@ declare function restartApp(): void;
144
151
 
145
152
  declare function createUpdater({ SIGNATURE_PUB, repository, productName, releaseAsarURL: _release, updateJsonURL: _update, debug, downloadConfig, compareVersion, }: UpdaterOption): Updater;
146
153
 
147
- interface AppOption {
154
+ type AppOption = {
155
+ /**
156
+ * name of your application
157
+ *
158
+ * you can use the `name` in `package.json`
159
+ */
160
+ name: string;
148
161
  /**
149
162
  * path of electron output dist
150
163
  * @default 'dist-electron'
151
- */
164
+ */
152
165
  electronDistPath?: string;
153
166
  /**
154
167
  * relative path of main entry in electron dist
155
168
  * @default 'main/index.js'
156
- */
169
+ */
157
170
  mainPath?: string;
158
- }
171
+ };
172
+ type OptionalProperty<T, K extends keyof T> = Omit<T, K> & Partial<Pick<T, K>>;
173
+ type InitUpdaterOptions = OptionalProperty<UpdaterOption, 'productName'>;
159
174
  /**
160
- * Initialize application
161
- * @param productName name of your application
162
- * @param updater updater instance or updater options
163
- * @param option options for entry, will be used to generate electron main path, default target path: `dist-electron/main/index.js`
164
- * @returns a function to init your application with a updater
165
- *
175
+ * create updater manually
166
176
  * @example
167
- * **manual** generate updater
168
177
  * ```ts
169
- * import { initApp } from 'electron-incremental-updater'
178
+ * import { createUpdater, getGithubReleaseCdnGroup, initApp, parseGithubCdnURL } from 'electron-incremental-update'
170
179
  * import { name, repository } from '../package.json'
171
180
  *
172
- * const SIGNATURE_PUB = '' // auto generate RSA public key when start app
181
+ * const SIGNATURE_PUB = '' // auto generate
182
+ *
183
+ * const { cdnPrefix } = getGithubReleaseCdnGroup()[0]
173
184
  * const updater = createUpdater({
174
185
  * SIGNATURE_PUB,
175
186
  * productName: name,
176
187
  * repository,
188
+ * updateJsonURL: parseGithubCdnURL(repository, 'fastly.jsdelivr.net/gh', 'version.json'),
189
+ * releaseAsarURL: parseGithubCdnURL(repository, cdnPrefix, `download/latest/${name}.asar.gz`),
190
+ * debug: true,
177
191
  * })
178
- * initApp(name, updater)
192
+ * initApp({ name }).setUpdater(updater)
179
193
  * ```
180
- * @example
181
- * **auto** generate updater and set update URL
194
+ */
195
+ declare function initApp(appOptions: AppOption): {
196
+ setUpdater: (updater: Updater) => void;
197
+ };
198
+ /**
199
+ * create updater when init, no need to set productName
182
200
  *
201
+ * @example
183
202
  * ```ts
184
- * import { getReleaseDnsPrefix, initApp } from 'electron-incremental-update'
203
+ * import { initApp } from 'electron-incremental-update'
185
204
  * import { name, repository } from '../package.json'
186
205
  *
187
- * const SIGNATURE_PUB = '' // auto generate RSA public key when start app
206
+ * const SIGNATURE_PUB = '' // auto generate
188
207
  *
189
- * const { urlPrefix } = getReleaseCdnPrefix()[0]
190
- * initApp(name, {
191
- * SIGNATURE_PUB,
192
- * repository,
193
- * updateJsonURL: `https://cdn.jsdelivr.net/gh/${repository.replace('https://github.com', '')}/version.json`,
194
- * releaseAsarURL: `${urlPrefix}/download/latest/${name}.asar.gz`,
195
- * }, {
196
- * // options for main entry
197
- * })
208
+ * initApp({ name }, { SIGNATURE_PUB, repository })
198
209
  * ```
199
- */
200
- declare function initApp(productName: string, updater: Updater | Omit<UpdaterOption, 'productName'> & {
201
- productName?: string;
202
- }, option?: AppOption): any;
210
+ */
211
+ declare function initApp(appOptions: AppOption, updaterOptions: InitUpdaterOptions): undefined;
203
212
 
204
- export { BaseOption, CheckResultType, UpdateJSON, Updater, UpdaterOption, createUpdater, getAppAsarPath, getAppVersion, getEntryVersion, getGithubReleaseCdnGroup, initApp, parseGithubCdnURL, requireNative, restartApp };
213
+ export { AppOption, CheckResultType, InitUpdaterOptions, UpdateJSON, Updater, UpdaterOption, createUpdater, getAppAsarPath, getAppVersion, getEntryVersion, getGithubReleaseCdnGroup, initApp, parseGithubCdnURL, requireNative, restartApp };
package/dist/index.mjs CHANGED
@@ -25,11 +25,15 @@ function downloadJSONDefault(url, updater, headers) {
25
25
  res.headers = headers;
26
26
  res.on("data", (chunk) => data += chunk);
27
27
  res.on("end", () => {
28
- const json = JSON.parse(data);
29
- if ("signature" in json && "version" in json && "size" in json) {
30
- resolve2(json);
31
- } else {
32
- throw new Error("invalid update json");
28
+ try {
29
+ const json = JSON.parse(data);
30
+ if ("signature" in json && "version" in json && "size" in json) {
31
+ resolve2(json);
32
+ } else {
33
+ throw Error;
34
+ }
35
+ } catch (e) {
36
+ reject(new Error("invalid json"));
33
37
  }
34
38
  });
35
39
  }).on("error", (e) => {
@@ -139,6 +143,7 @@ function createUpdater({
139
143
  }) {
140
144
  const updater = new EventEmitter();
141
145
  let signature = "";
146
+ let version = "";
142
147
  const gzipPath = `../${productName}.asar.gz`;
143
148
  const tmpFile = gzipPath.replace(".asar.gz", ".tmp.gz");
144
149
  const { downloadBuffer, downloadJSON, extraHeader, userAgent } = downloadConfig || {};
@@ -152,12 +157,16 @@ function createUpdater({
152
157
  UserAgent: ua,
153
158
  ...extraHeader
154
159
  };
155
- log(`headers: ${headers}`);
160
+ log(`download headers: ${JSON.stringify(headers, null, 2)}`);
156
161
  const downloadFn = format === "json" ? downloadJSON ?? downloadJSONDefault : downloadBuffer ?? downloadBufferDefault;
157
- return await downloadFn(url, updater, headers);
162
+ log(`download ${format} from ${url}`);
163
+ const ret = await downloadFn(url, updater, headers);
164
+ log(`download ${format} success`);
165
+ return ret;
158
166
  }
159
167
  async function extractFile(gzipFilePath) {
160
168
  if (!gzipFilePath.endsWith(".asar.gz") || !existsSync(gzipFilePath)) {
169
+ log("update .asar.gz file not exist");
161
170
  return;
162
171
  }
163
172
  gzipFilePath = gzipFilePath.replace(".asar.gz", ".tmp.gz");
@@ -169,55 +178,61 @@ function createUpdater({
169
178
  log(`outputFilePath: ${outputFilePath}`);
170
179
  input.pipe(gunzip).pipe(output).on("finish", async () => {
171
180
  await rm(gzipFilePath);
172
- log("finish");
181
+ log(`${gzipFilePath} unzipped`);
173
182
  resolve2(outputFilePath);
174
183
  }).on("error", async (err) => {
175
184
  await rm(gzipFilePath);
176
- log(`error: ${err}`);
177
185
  output.destroy(err);
178
186
  reject(err);
179
187
  });
180
188
  });
181
189
  }
182
190
  function verify(buffer, signature2) {
183
- log(`signature: ${signature2}`);
184
191
  return createVerify("RSA-SHA256").update(buffer).verify(SIGNATURE_PUB, signature2, "base64");
185
192
  }
186
- function needUpdate(version) {
187
- if (!version || !app2.isPackaged) {
193
+ function needUpdate(version2) {
194
+ if (!app2.isPackaged) {
195
+ log("in dev mode, no need to update");
188
196
  return false;
189
197
  }
190
198
  const currentVersion = getEntryVersion();
191
- log(`currentVersion: ${currentVersion}`);
192
- log(`newVersion: ${version}`);
199
+ log(`check update:
200
+ current version is ${currentVersion},
201
+ new version is ${version2}`);
193
202
  const _compare = compareVersion ?? compareVersionDefault;
194
- return _compare(currentVersion, version);
203
+ return _compare(currentVersion, version2);
195
204
  }
196
205
  async function checkUpdate(url) {
197
206
  url ??= _update;
198
207
  if (!url) {
199
- log("no updateJsonURL, use repository");
208
+ log("no updateJsonURL, fallback to use repository");
200
209
  if (!repository) {
201
210
  throw new Error("updateJsonURL or repository are not set");
202
211
  }
203
- url = `${repository.replace("github.com", "raw.githubusercontent.com")}/version.json`;
212
+ url = `${repository.replace("github.com", "raw.githubusercontent.com")}/master/version.json`;
204
213
  }
205
- log(`updateJsonURL: ${url}`);
206
214
  if (existsSync(tmpFile)) {
207
215
  log(`remove tmp file: ${tmpFile}`);
208
216
  await rm(tmpFile);
209
217
  }
218
+ if (existsSync(gzipPath)) {
219
+ log(`remove .gz file: ${gzipPath}`);
220
+ await rm(gzipPath);
221
+ }
210
222
  const json = await download(url, "json");
211
223
  const {
212
224
  signature: _sig,
213
- version,
225
+ version: _v,
214
226
  size
215
227
  } = json;
216
- log(`UpdateJSON: ${JSON.stringify(json, null, 2)}`);
217
- if (!needUpdate(version)) {
228
+ log(`update info: ${JSON.stringify(json, null, 2)}`);
229
+ if (!await needUpdate(_v)) {
230
+ log(`update unavailable: ${_v}`);
218
231
  return false;
219
232
  } else {
233
+ log(`update available: ${_v}`);
220
234
  signature = _sig;
235
+ version = _v;
221
236
  return { size, version };
222
237
  }
223
238
  }
@@ -225,23 +240,25 @@ function createUpdater({
225
240
  if (typeof src !== "object") {
226
241
  let _url = src ?? _release;
227
242
  if (!_url) {
228
- log("no releaseAsarURL, use repository");
243
+ log("no releaseAsarURL, fallback to use repository");
229
244
  if (!repository) {
230
245
  throw new Error("releaseAsarURL or repository are not set");
231
246
  }
232
247
  _url = `${repository}/releases/download/latest/${productName}.asar.gz`;
233
248
  }
234
- log(`releaseAsarURL: ${_url}`);
235
249
  src = await download(_url, "buffer");
236
250
  }
237
- log("start verify");
251
+ log("verify start");
238
252
  if (!verify(src, signature)) {
239
- throw new Error("file broken, invalid signature!");
253
+ log("verify failed");
254
+ throw new Error("invalid signature");
240
255
  }
256
+ log("verify success");
241
257
  log(`write file: ${gzipPath}`);
242
258
  await writeFile(gzipPath, src);
243
259
  log(`extract file: ${gzipPath}`);
244
260
  await extractFile(gzipPath);
261
+ log(`update success, version: ${version}`);
245
262
  updater.emit("downloaded");
246
263
  }
247
264
  const onCheck = async (url) => {
@@ -269,21 +286,25 @@ function createUpdater({
269
286
  }
270
287
 
271
288
  // src/index.ts
272
- function initApp(productName, updater, option) {
289
+ function initApp(appOptions, updaterOptions) {
273
290
  const {
291
+ name: productName,
274
292
  electronDistPath = "dist-electron",
275
293
  mainPath = "main/index.js"
276
- } = option ?? {};
294
+ } = appOptions ?? {};
277
295
  const mainDir = app3.isPackaged ? `../${productName}.asar` : electronDistPath;
278
296
  const entry = resolve(__dirname, mainDir, mainPath);
279
- let _updater;
280
- if ("SIGNATURE_PUB" in updater) {
281
- const _option = updater.productName ? updater : { ...updater, productName };
282
- _updater = createUpdater(_option);
297
+ if (updaterOptions) {
298
+ __require(entry)(
299
+ createUpdater({ ...updaterOptions, productName })
300
+ );
283
301
  } else {
284
- _updater = updater;
302
+ return {
303
+ setUpdater(updater) {
304
+ __require(entry)(updater);
305
+ }
306
+ };
285
307
  }
286
- return __require(entry)(_updater);
287
308
  }
288
309
  export {
289
310
  createUpdater,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "electron-incremental-update",
3
- "version": "0.3.0",
3
+ "version": "0.4.0",
4
4
  "description": "electron incremental update tools, powered by vite",
5
5
  "scripts": {
6
6
  "build": "tsup",
@@ -63,4 +63,4 @@
63
63
  "dependencies": {
64
64
  "ci-info": "^3.8.0"
65
65
  }
66
- }
66
+ }