electron-updater-for-render 1.1.2-beta.7 → 1.1.3

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.
@@ -35,7 +35,6 @@ __export(main_exports, {
35
35
  module.exports = __toCommonJS(main_exports);
36
36
  var import_path2 = __toESM(require("path"), 1);
37
37
  var import_original_fs = __toESM(require("original-fs"), 1);
38
- var import_crypto = __toESM(require("crypto"), 1);
39
38
  var import_semver = __toESM(require("semver"), 1);
40
39
  var import_electron2 = require("electron");
41
40
  var import_promises = require("stream/promises");
@@ -158,7 +157,45 @@ var RouterHandler = class _RouterHandler {
158
157
  }
159
158
  };
160
159
 
160
+ // src/main/utils.ts
161
+ var import_fs2 = require("fs");
162
+ var import_crypto = __toESM(require("crypto"), 1);
163
+ function verifyFileStream(filePath, info, publicKey) {
164
+ return new Promise((resolve, reject) => {
165
+ const hash = import_crypto.default.createHash("sha256");
166
+ const verify = publicKey && info.signature ? import_crypto.default.createVerify("RSA-SHA256") : null;
167
+ const stream = (0, import_fs2.createReadStream)(filePath);
168
+ stream.on("data", (chunk) => {
169
+ hash.update(chunk);
170
+ if (verify) {
171
+ verify.update(chunk);
172
+ }
173
+ });
174
+ stream.on("end", () => {
175
+ const sha256 = hash.digest("hex");
176
+ if (info.sha256 && sha256 !== info.sha256) {
177
+ console.error(`[RenderUpdater] SHA256 mismatch! Expected ${info.sha256}, got ${sha256}`);
178
+ return resolve(false);
179
+ }
180
+ if (verify && publicKey && info.signature) {
181
+ verify.end();
182
+ const isValid = verify.verify(publicKey, info.signature, "base64");
183
+ if (!isValid) {
184
+ console.error("[RenderUpdater] RSA Signature verification failed.");
185
+ return resolve(false);
186
+ }
187
+ }
188
+ resolve(true);
189
+ });
190
+ stream.on("error", (err) => {
191
+ console.error("[RenderUpdater] Stream error during file verification:", err);
192
+ reject(err);
193
+ });
194
+ });
195
+ }
196
+
161
197
  // src/main/index.ts
198
+ var fsPromises = import_original_fs.default.promises;
162
199
  var RenderUpdater = class {
163
200
  versionsDir;
164
201
  currentVersionFile;
@@ -206,7 +243,7 @@ var RenderUpdater = class {
206
243
  if (!import_original_fs.default.existsSync(this.versionsDir)) {
207
244
  import_original_fs.default.mkdirSync(this.versionsDir, { recursive: true });
208
245
  }
209
- const savedVersion = this.readCurrentVersionFromFile();
246
+ const savedVersion = this.readCurrentVersionFromFileSync();
210
247
  this._activeVersion = savedVersion || "0.0.0";
211
248
  this._diskVersion = savedVersion || "0.0.0";
212
249
  console.log("[RenderUpdater] Initialized. Active version:", this._activeVersion);
@@ -221,7 +258,8 @@ var RenderUpdater = class {
221
258
  });
222
259
  }
223
260
  }
224
- readCurrentVersionFromFile() {
261
+ // 初始化使用一次的同步方法(可接受)
262
+ readCurrentVersionFromFileSync() {
225
263
  if (import_original_fs.default.existsSync(this.currentVersionFile)) {
226
264
  try {
227
265
  const data = JSON.parse(import_original_fs.default.readFileSync(this.currentVersionFile, "utf-8"));
@@ -234,35 +272,33 @@ var RenderUpdater = class {
234
272
  }
235
273
  return "0.0.0";
236
274
  }
237
- cleanOldVersions() {
275
+ // 全面异步替换原同步清理操作
276
+ async cleanOldVersions() {
238
277
  try {
239
278
  if (!this.versionsDir) return;
240
- const versionDirs = import_original_fs.default.readdirSync(this.versionsDir).filter((v) => {
241
- return import_original_fs.default.statSync(import_path2.default.join(this.versionsDir, v)).isDirectory() && import_semver.default.valid(v);
242
- });
279
+ const files = await fsPromises.readdir(this.versionsDir, { withFileTypes: true });
280
+ const versionDirs = files.filter((dirent) => dirent.isDirectory() && import_semver.default.valid(dirent.name)).map((dirent) => dirent.name);
243
281
  versionDirs.sort((a, b) => import_semver.default.compare(import_semver.default.coerce(b) ?? b, import_semver.default.coerce(a) ?? a));
244
- const activeIdx = versionDirs.indexOf(this._activeVersion);
245
- const diskIdx = versionDirs.indexOf(this._diskVersion);
246
282
  const safeVersions = /* @__PURE__ */ new Set();
247
- if (activeIdx !== -1) safeVersions.add(this._activeVersion);
248
- if (diskIdx !== -1) safeVersions.add(this._diskVersion);
283
+ safeVersions.add(this._activeVersion);
284
+ safeVersions.add(this._diskVersion);
249
285
  let kept = 0;
250
286
  for (const v of versionDirs) {
251
287
  if (!safeVersions.has(v)) {
252
- console.info(`[RenderUpdater] Cleaning up old version: ${v}`);
253
- try {
254
- import_original_fs.default.rmSync(import_path2.default.join(this.versionsDir, v), { recursive: true, force: true });
255
- } catch (e) {
256
- if (e.code === "EBUSY") {
257
- console.warn(`[RenderUpdater] Version ${v} is locked, skipping.`);
258
- } else {
259
- console.error(`[RenderUpdater] Failed to remove version ${v}:`, e);
288
+ if (kept >= this.maxVersionsToKeep) {
289
+ console.info(`[RenderUpdater] Cleaning up old version: ${v}`);
290
+ try {
291
+ await fsPromises.rm(import_path2.default.join(this.versionsDir, v), { recursive: true, force: true });
292
+ } catch (e) {
293
+ if (e.code === "EBUSY") {
294
+ console.warn(`[RenderUpdater] Version ${v} is locked, skipping.`);
295
+ } else {
296
+ console.error(`[RenderUpdater] Failed to remove version ${v}:`, e);
297
+ }
260
298
  }
299
+ } else {
300
+ kept++;
261
301
  }
262
- } else {
263
- kept++;
264
- }
265
- if (kept >= this.maxVersionsToKeep && !safeVersions.has(v)) {
266
302
  }
267
303
  }
268
304
  } catch (e) {
@@ -333,25 +369,21 @@ var RenderUpdater = class {
333
369
  try {
334
370
  console.log("[RenderUpdater] download() entry. current this.versionsDir:", this.versionsDir);
335
371
  const infoToUse = providedInfo || await (await fetch(`${this.baseUrl}/latest.json?t=${Date.now()}`)).json();
336
- if (!this.versionsDir) {
337
- throw new Error("[RenderUpdater] CRITICAL: this.versionsDir lost in download.");
338
- }
339
- if (!infoToUse || !infoToUse.version) {
340
- throw new Error(`[RenderUpdater] CRITICAL: infoToUse or version missing. info: ${JSON.stringify(infoToUse)}`);
341
- }
372
+ if (!this.versionsDir) throw new Error("[RenderUpdater] CRITICAL: this.versionsDir lost in download.");
373
+ if (!infoToUse || !infoToUse.version) throw new Error(`[RenderUpdater] CRITICAL: infoToUse or version missing. info: ${JSON.stringify(infoToUse)}`);
342
374
  const versionDir = import_path2.default.join(this.versionsDir, infoToUse.version);
343
- const asarPath = import_path2.default.join(versionDir, "renderer.asar");
344
- if (import_original_fs.default.existsSync(asarPath) && this.verifyFile(asarPath, infoToUse)) {
375
+ const finalAsarPath = import_path2.default.join(versionDir, "renderer.asar");
376
+ const tmpAsarPath = import_path2.default.join(versionDir, "renderer.asar.tmp");
377
+ if (import_original_fs.default.existsSync(finalAsarPath) && await verifyFileStream(finalAsarPath, infoToUse, this.publicKey)) {
345
378
  console.log("[RenderUpdater] File already exists and verified. Skipping download.");
346
379
  onProgress?.(100);
347
380
  return;
348
381
  }
349
- if (!import_original_fs.default.existsSync(versionDir)) {
350
- import_original_fs.default.mkdirSync(versionDir, { recursive: true });
351
- }
382
+ await fsPromises.mkdir(versionDir, { recursive: true });
352
383
  let downloadedBytes = 0;
353
- if (import_original_fs.default.existsSync(asarPath)) {
354
- downloadedBytes = import_original_fs.default.statSync(asarPath).size;
384
+ if (import_original_fs.default.existsSync(tmpAsarPath)) {
385
+ const stats = await fsPromises.stat(tmpAsarPath);
386
+ downloadedBytes = stats.size;
355
387
  }
356
388
  const fetchOptions = {};
357
389
  if (downloadedBytes > 0) {
@@ -372,7 +404,7 @@ var RenderUpdater = class {
372
404
  const incomingTotal = contentLength ? parseInt(contentLength, 10) : 0;
373
405
  const total = downloadedBytes + incomingTotal;
374
406
  if (!downloadResponse.body) throw new Error("Response body is empty");
375
- const fileStream = import_original_fs.default.createWriteStream(asarPath, { flags: downloadResponse.status === 206 ? "a" : "w" });
407
+ const fileStream = import_original_fs.default.createWriteStream(tmpAsarPath, { flags: downloadResponse.status === 206 ? "a" : "w" });
376
408
  const progressTransform = new import_stream.Transform({
377
409
  transform(chunk, _encoding, callback) {
378
410
  downloadedBytes += chunk.length;
@@ -387,62 +419,34 @@ var RenderUpdater = class {
387
419
  progressTransform,
388
420
  fileStream
389
421
  );
390
- if (!this.verifyFile(asarPath, infoToUse)) {
391
- import_original_fs.default.rmSync(versionDir, { recursive: true, force: true });
422
+ if (!await verifyFileStream(tmpAsarPath, infoToUse, this.publicKey)) {
423
+ await fsPromises.rm(tmpAsarPath, { force: true });
392
424
  throw new Error("[RenderUpdater] Verification failed after download (SHA256 or RSA Mismatch)");
393
425
  }
394
- this.setUpdatePending(infoToUse.version);
395
- this.cleanOldVersions();
426
+ await fsPromises.rename(tmpAsarPath, finalAsarPath);
427
+ await this.setUpdatePending(infoToUse.version);
428
+ await this.cleanOldVersions();
396
429
  } finally {
397
430
  this.isDownloading = false;
398
431
  }
399
432
  }
400
- verifyFile(filePath, info) {
401
- const fileBuffer = import_original_fs.default.readFileSync(filePath);
402
- const hashSum = import_crypto.default.createHash("sha256");
403
- hashSum.update(fileBuffer);
404
- const sha256 = hashSum.digest("hex");
405
- if (sha256 !== info.sha256 && info.sha256 !== "") {
406
- console.error(`[RenderUpdater] SHA256 mismatch! Expected ${info.sha256}, got ${sha256}`);
407
- return false;
408
- }
409
- if (this.publicKey && info.signature) {
410
- try {
411
- const verify = import_crypto.default.createVerify("RSA-SHA256");
412
- verify.update(fileBuffer);
413
- verify.end();
414
- const isValid = verify.verify(this.publicKey, info.signature, "base64");
415
- if (!isValid) {
416
- console.error("[RenderUpdater] RSA Signature verification failed.");
417
- return false;
418
- }
419
- } catch (err) {
420
- console.error("[RenderUpdater] RSA verification error:", err);
421
- return false;
422
- }
423
- }
424
- return true;
425
- }
426
- useVersion(version) {
427
- this.updateDiskVersion(version);
428
- this.updateMemoryVersion(version);
433
+ async useVersion(version) {
434
+ await this.updateDiskVersion(version);
435
+ this._activeVersion = version;
429
436
  }
430
- setUpdatePending(version) {
431
- this.updateDiskVersion(version);
437
+ async setUpdatePending(version) {
438
+ await this.updateDiskVersion(version);
432
439
  if (this.onStatusChanged) {
433
440
  this.onStatusChanged({ status: "ready", version });
434
441
  }
435
442
  }
436
- updateDiskVersion(version) {
437
- import_original_fs.default.writeFileSync(
443
+ async updateDiskVersion(version) {
444
+ await fsPromises.writeFile(
438
445
  this.currentVersionFile,
439
446
  JSON.stringify({ version, date: (/* @__PURE__ */ new Date()).toISOString() })
440
447
  );
441
448
  this._diskVersion = version;
442
449
  }
443
- updateMemoryVersion(version) {
444
- this._activeVersion = version;
445
- }
446
450
  async installAndRestart() {
447
451
  if (this.onBeforeRestart) {
448
452
  await this.onBeforeRestart();
@@ -450,104 +454,130 @@ var RenderUpdater = class {
450
454
  import_electron2.app.relaunch();
451
455
  import_electron2.app.quit();
452
456
  }
457
+ // --- 改版后切分的上帝函数 ---
453
458
  async checkForUpdatesAndNotify() {
454
459
  try {
455
460
  const checkRes = await this.check();
456
- if (checkRes.updateAvailable && checkRes.info) {
457
- const info = checkRes.info;
458
- const isReady = checkRes.status === "ready";
459
- const doDownload = async () => {
460
- try {
461
- if (!isReady) {
462
- await this.download(info, this.onDownloadProgress);
463
- }
464
- const performInstall = async (shouldRestart) => {
465
- if (shouldRestart) {
466
- this.useVersion(info.version);
467
- await this.installAndRestart();
468
- } else {
469
- this.setUpdatePending(info.version);
470
- console.info(`[RenderUpdater] Update v${info.version} ready, will apply on next launch.`);
471
- }
472
- };
473
- if (this.onDownloadComplete) {
474
- this.onDownloadComplete(info, () => {
475
- performInstall(true).catch(console.error);
476
- });
477
- } else {
478
- if (info.forceUpdate || isReady) {
479
- if (isReady) {
480
- const { response } = await import_electron2.dialog.showMessageBox({
481
- type: "info",
482
- title: "\u66F4\u65B0\u5C31\u7EEA",
483
- message: `\u65B0\u7248\u672C v${info.version} \u5DF2\u51C6\u5907\u5C31\u7EEA\u3002\u662F\u5426\u7ACB\u5373\u91CD\u542F\u5E94\u7528\uFF1F`,
484
- buttons: ["\u7ACB\u5373\u91CD\u542F", "\u7A0D\u540E\u518D\u8BF4"],
485
- defaultId: 0,
486
- cancelId: 1
487
- });
488
- await performInstall(response === 0);
489
- } else {
490
- if (info.forceUpdate === "prompt") {
491
- import_electron2.dialog.showMessageBoxSync({
492
- type: "warning",
493
- title: "\u4E0B\u8F7D\u5B8C\u6210",
494
- message: `\u6700\u65B0\u7248\u672C v${info.version} \u5DF2\u4E0B\u8F7D\u5B8C\u6BD5\u3002\u5373\u5C06\u91CD\u542F\u7A0B\u5E8F\u4EE5\u5E94\u7528\u66F4\u65B0\u3002`,
495
- buttons: ["\u6211\u77E5\u9053\u4E86"]
496
- });
497
- }
498
- await performInstall(true);
499
- }
500
- } else if (this.autoPrompt) {
501
- const { response } = await import_electron2.dialog.showMessageBox({
502
- type: "info",
503
- title: "\u4E0B\u8F7D\u5B8C\u6210",
504
- message: `\u6700\u65B0\u7248\u672C v${info.version} \u5DF2\u4E0B\u8F7D\u5B8C\u6BD5\u3002\u662F\u5426\u7ACB\u5373\u91CD\u542F\u4EE5\u5E94\u7528\u66F4\u65B0\uFF1F`,
505
- buttons: ["\u7ACB\u5373\u91CD\u542F", "\u7A0D\u540E\u518D\u8BF4"],
506
- defaultId: 0,
507
- cancelId: 1
508
- });
509
- await performInstall(response === 0);
510
- }
511
- }
512
- } catch (e) {
513
- if (this.onError) {
514
- this.onError(e);
515
- } else {
516
- console.error("[RenderUpdater] Download/Install error:", e);
517
- }
518
- }
519
- };
520
- if (this.onUpdateAvailable) {
521
- this.onUpdateAvailable(info, doDownload);
461
+ if (!checkRes.updateAvailable || !checkRes.info) return;
462
+ const info = checkRes.info;
463
+ if (checkRes.status === "ready") {
464
+ await this.handleReadyUpdate(info);
465
+ } else {
466
+ await this.handleNewUpdate(info);
467
+ }
468
+ } catch (e) {
469
+ this.handleError(e);
470
+ }
471
+ }
472
+ async handleReadyUpdate(info) {
473
+ if (this.onUpdateAvailable) {
474
+ this.onUpdateAvailable(info, async () => {
475
+ if (this.onDownloadComplete) {
476
+ this.onDownloadComplete(info, () => this.performInstall(info, true).catch(this.handleError.bind(this)));
522
477
  } else {
523
- if (info.forceUpdate || isReady) {
524
- await doDownload();
525
- } else if (this.autoDownload) {
526
- await doDownload();
527
- } else if (this.autoPrompt) {
528
- const { response } = await import_electron2.dialog.showMessageBox({
529
- type: "info",
530
- title: "\u53D1\u73B0\u65B0\u7248\u672C",
531
- message: `\u53D1\u73B0\u53EF\u7528\u66F4\u65B0 (v${info.version})\u3002\u662F\u5426\u7ACB\u5373\u4E0B\u8F7D\u66F4\u65B0\uFF1F
532
-
533
- \u66F4\u65B0\u65E5\u5FD7\uFF1A
534
- ${info.releaseNotes || "\u65E0\u8BE6\u7EC6\u8BB0\u5F55\u3002"}`,
535
- buttons: ["\u7ACB\u5373\u66F4\u65B0", "\u7A0D\u540E\u518D\u8BF4"],
536
- cancelId: 1
537
- });
538
- if (response === 0) {
539
- await doDownload();
540
- }
541
- }
478
+ await this.performInstall(info, true);
542
479
  }
480
+ });
481
+ return;
482
+ }
483
+ if (info.forceUpdate || !this.autoPrompt) {
484
+ await this.performInstall(info, true);
485
+ return;
486
+ }
487
+ const response = await this.showReadyDialog(info.version);
488
+ await this.performInstall(info, response === 0);
489
+ }
490
+ async handleNewUpdate(info) {
491
+ if (this.onUpdateAvailable) {
492
+ this.onUpdateAvailable(info, () => this.executeAutoDownload(info));
493
+ return;
494
+ }
495
+ if (info.forceUpdate || this.autoDownload) {
496
+ await this.executeAutoDownload(info);
497
+ } else if (this.autoPrompt) {
498
+ const response = await this.showNewUpdateDialog(info);
499
+ if (response === 0) {
500
+ await this.executeAutoDownload(info);
543
501
  }
544
- } catch (e) {
545
- if (this.onError) {
546
- this.onError(e);
502
+ }
503
+ }
504
+ async executeAutoDownload(info) {
505
+ try {
506
+ await this.download(info, this.onDownloadProgress);
507
+ const doInstall = (shouldRestart) => this.performInstall(info, shouldRestart).catch(this.handleError.bind(this));
508
+ if (this.onDownloadComplete) {
509
+ this.onDownloadComplete(info, () => doInstall(true));
547
510
  } else {
548
- console.error("[RenderUpdater] Error in checkForUpdatesAndNotify:", e);
511
+ const isSilent = info.forceUpdate === "silent";
512
+ const shouldRestart = isSilent || await this.showDownloadCompleteDialog(info.version, info.forceUpdate === "prompt") === 0;
513
+ await doInstall(shouldRestart);
549
514
  }
515
+ } catch (e) {
516
+ this.handleError(e);
517
+ }
518
+ }
519
+ async performInstall(info, shouldRestart) {
520
+ if (shouldRestart) {
521
+ await this.useVersion(info.version);
522
+ await this.installAndRestart();
523
+ } else {
524
+ await this.setUpdatePending(info.version);
525
+ console.info(`[RenderUpdater] Update v${info.version} ready, will apply on next launch.`);
526
+ }
527
+ }
528
+ handleError(error) {
529
+ if (this.onError) {
530
+ this.onError(error);
531
+ } else {
532
+ console.error("[RenderUpdater] Error:", error);
533
+ }
534
+ }
535
+ // UI 分发抽离
536
+ async showReadyDialog(version) {
537
+ const { response } = await import_electron2.dialog.showMessageBox({
538
+ type: "info",
539
+ title: "\u66F4\u65B0\u5C31\u7EEA",
540
+ message: `\u65B0\u7248\u672C v${version} \u5DF2\u51C6\u5907\u5C31\u7EEA\u3002\u662F\u5426\u7ACB\u5373\u91CD\u542F\u5E94\u7528\uFF1F`,
541
+ buttons: ["\u7ACB\u5373\u91CD\u542F", "\u7A0D\u540E\u518D\u8BF4"],
542
+ defaultId: 0,
543
+ cancelId: 1
544
+ });
545
+ return response;
546
+ }
547
+ async showNewUpdateDialog(info) {
548
+ const { response } = await import_electron2.dialog.showMessageBox({
549
+ type: "info",
550
+ title: "\u53D1\u73B0\u65B0\u7248\u672C",
551
+ message: `\u53D1\u73B0\u53EF\u7528\u66F4\u65B0 (v${info.version})\u3002\u662F\u5426\u7ACB\u5373\u4E0B\u8F7D\u66F4\u65B0\uFF1F
552
+
553
+ \u66F4\u65B0\u65E5\u5FD7\uFF1A
554
+ ${info.releaseNotes || "\u65E0\u8BE6\u7EC6\u8BB0\u5F55\u3002"}`,
555
+ buttons: ["\u7ACB\u5373\u66F4\u65B0", "\u7A0D\u540E\u518D\u8BF4"],
556
+ cancelId: 1
557
+ });
558
+ return response;
559
+ }
560
+ async showDownloadCompleteDialog(version, forcePrompt) {
561
+ if (forcePrompt) {
562
+ import_electron2.dialog.showMessageBoxSync({
563
+ type: "warning",
564
+ title: "\u4E0B\u8F7D\u5B8C\u6210",
565
+ message: `\u6700\u65B0\u7248\u672C v${version} \u5DF2\u4E0B\u8F7D\u5B8C\u6BD5\u3002\u5373\u5C06\u91CD\u542F\u7A0B\u5E8F\u4EE5\u5E94\u7528\u66F4\u65B0\u3002`,
566
+ buttons: ["\u6211\u77E5\u9053\u4E86"]
567
+ });
568
+ return 0;
569
+ } else if (this.autoPrompt) {
570
+ const { response } = await import_electron2.dialog.showMessageBox({
571
+ type: "info",
572
+ title: "\u4E0B\u8F7D\u5B8C\u6210",
573
+ message: `\u6700\u65B0\u7248\u672C v${version} \u5DF2\u4E0B\u8F7D\u5B8C\u6BD5\u3002\u662F\u5426\u7ACB\u5373\u91CD\u542F\u4EE5\u5E94\u7528\u66F4\u65B0\uFF1F`,
574
+ buttons: ["\u7ACB\u5373\u91CD\u542F", "\u7A0D\u540E\u518D\u8BF4"],
575
+ defaultId: 0,
576
+ cancelId: 1
577
+ });
578
+ return response;
550
579
  }
580
+ return 0;
551
581
  }
552
582
  };
553
583
  // Annotate the CommonJS export names for ESM import in node:
@@ -22,7 +22,7 @@ export declare class RenderUpdater {
22
22
  onError?: (error: Error) => void;
23
23
  onBeforeRestart?: () => void | Promise<void>;
24
24
  constructor(options: UpdaterOptions);
25
- private readCurrentVersionFromFile;
25
+ private readCurrentVersionFromFileSync;
26
26
  private cleanOldVersions;
27
27
  get activeVersion(): string;
28
28
  get pendingVersion(): string;
@@ -35,11 +35,17 @@ export declare class RenderUpdater {
35
35
  status: 'idle' | 'available' | 'ready';
36
36
  }>;
37
37
  download(providedInfo?: UpdateInfo, onProgress?: (percent: number) => void): Promise<void>;
38
- private verifyFile;
39
- useVersion(version: string): void;
40
- setUpdatePending(version: string): void;
38
+ useVersion(version: string): Promise<void>;
39
+ setUpdatePending(version: string): Promise<void>;
41
40
  private updateDiskVersion;
42
- private updateMemoryVersion;
43
41
  installAndRestart(): Promise<void>;
44
42
  checkForUpdatesAndNotify(): Promise<void>;
43
+ private handleReadyUpdate;
44
+ private handleNewUpdate;
45
+ private executeAutoDownload;
46
+ private performInstall;
47
+ private handleError;
48
+ private showReadyDialog;
49
+ private showNewUpdateDialog;
50
+ private showDownloadCompleteDialog;
45
51
  }
@@ -1,7 +1,6 @@
1
1
  // src/main/index.ts
2
2
  import path2 from "path";
3
3
  import fs2 from "original-fs";
4
- import crypto from "crypto";
5
4
  import semver from "semver";
6
5
  import { app as app2, dialog } from "electron";
7
6
  import { pipeline } from "stream/promises";
@@ -124,7 +123,45 @@ var RouterHandler = class _RouterHandler {
124
123
  }
125
124
  };
126
125
 
126
+ // src/main/utils.ts
127
+ import { createReadStream } from "fs";
128
+ import crypto from "crypto";
129
+ function verifyFileStream(filePath, info, publicKey) {
130
+ return new Promise((resolve, reject) => {
131
+ const hash = crypto.createHash("sha256");
132
+ const verify = publicKey && info.signature ? crypto.createVerify("RSA-SHA256") : null;
133
+ const stream = createReadStream(filePath);
134
+ stream.on("data", (chunk) => {
135
+ hash.update(chunk);
136
+ if (verify) {
137
+ verify.update(chunk);
138
+ }
139
+ });
140
+ stream.on("end", () => {
141
+ const sha256 = hash.digest("hex");
142
+ if (info.sha256 && sha256 !== info.sha256) {
143
+ console.error(`[RenderUpdater] SHA256 mismatch! Expected ${info.sha256}, got ${sha256}`);
144
+ return resolve(false);
145
+ }
146
+ if (verify && publicKey && info.signature) {
147
+ verify.end();
148
+ const isValid = verify.verify(publicKey, info.signature, "base64");
149
+ if (!isValid) {
150
+ console.error("[RenderUpdater] RSA Signature verification failed.");
151
+ return resolve(false);
152
+ }
153
+ }
154
+ resolve(true);
155
+ });
156
+ stream.on("error", (err) => {
157
+ console.error("[RenderUpdater] Stream error during file verification:", err);
158
+ reject(err);
159
+ });
160
+ });
161
+ }
162
+
127
163
  // src/main/index.ts
164
+ var fsPromises = fs2.promises;
128
165
  var RenderUpdater = class {
129
166
  versionsDir;
130
167
  currentVersionFile;
@@ -172,7 +209,7 @@ var RenderUpdater = class {
172
209
  if (!fs2.existsSync(this.versionsDir)) {
173
210
  fs2.mkdirSync(this.versionsDir, { recursive: true });
174
211
  }
175
- const savedVersion = this.readCurrentVersionFromFile();
212
+ const savedVersion = this.readCurrentVersionFromFileSync();
176
213
  this._activeVersion = savedVersion || "0.0.0";
177
214
  this._diskVersion = savedVersion || "0.0.0";
178
215
  console.log("[RenderUpdater] Initialized. Active version:", this._activeVersion);
@@ -187,7 +224,8 @@ var RenderUpdater = class {
187
224
  });
188
225
  }
189
226
  }
190
- readCurrentVersionFromFile() {
227
+ // 初始化使用一次的同步方法(可接受)
228
+ readCurrentVersionFromFileSync() {
191
229
  if (fs2.existsSync(this.currentVersionFile)) {
192
230
  try {
193
231
  const data = JSON.parse(fs2.readFileSync(this.currentVersionFile, "utf-8"));
@@ -200,35 +238,33 @@ var RenderUpdater = class {
200
238
  }
201
239
  return "0.0.0";
202
240
  }
203
- cleanOldVersions() {
241
+ // 全面异步替换原同步清理操作
242
+ async cleanOldVersions() {
204
243
  try {
205
244
  if (!this.versionsDir) return;
206
- const versionDirs = fs2.readdirSync(this.versionsDir).filter((v) => {
207
- return fs2.statSync(path2.join(this.versionsDir, v)).isDirectory() && semver.valid(v);
208
- });
245
+ const files = await fsPromises.readdir(this.versionsDir, { withFileTypes: true });
246
+ const versionDirs = files.filter((dirent) => dirent.isDirectory() && semver.valid(dirent.name)).map((dirent) => dirent.name);
209
247
  versionDirs.sort((a, b) => semver.compare(semver.coerce(b) ?? b, semver.coerce(a) ?? a));
210
- const activeIdx = versionDirs.indexOf(this._activeVersion);
211
- const diskIdx = versionDirs.indexOf(this._diskVersion);
212
248
  const safeVersions = /* @__PURE__ */ new Set();
213
- if (activeIdx !== -1) safeVersions.add(this._activeVersion);
214
- if (diskIdx !== -1) safeVersions.add(this._diskVersion);
249
+ safeVersions.add(this._activeVersion);
250
+ safeVersions.add(this._diskVersion);
215
251
  let kept = 0;
216
252
  for (const v of versionDirs) {
217
253
  if (!safeVersions.has(v)) {
218
- console.info(`[RenderUpdater] Cleaning up old version: ${v}`);
219
- try {
220
- fs2.rmSync(path2.join(this.versionsDir, v), { recursive: true, force: true });
221
- } catch (e) {
222
- if (e.code === "EBUSY") {
223
- console.warn(`[RenderUpdater] Version ${v} is locked, skipping.`);
224
- } else {
225
- console.error(`[RenderUpdater] Failed to remove version ${v}:`, e);
254
+ if (kept >= this.maxVersionsToKeep) {
255
+ console.info(`[RenderUpdater] Cleaning up old version: ${v}`);
256
+ try {
257
+ await fsPromises.rm(path2.join(this.versionsDir, v), { recursive: true, force: true });
258
+ } catch (e) {
259
+ if (e.code === "EBUSY") {
260
+ console.warn(`[RenderUpdater] Version ${v} is locked, skipping.`);
261
+ } else {
262
+ console.error(`[RenderUpdater] Failed to remove version ${v}:`, e);
263
+ }
226
264
  }
265
+ } else {
266
+ kept++;
227
267
  }
228
- } else {
229
- kept++;
230
- }
231
- if (kept >= this.maxVersionsToKeep && !safeVersions.has(v)) {
232
268
  }
233
269
  }
234
270
  } catch (e) {
@@ -299,25 +335,21 @@ var RenderUpdater = class {
299
335
  try {
300
336
  console.log("[RenderUpdater] download() entry. current this.versionsDir:", this.versionsDir);
301
337
  const infoToUse = providedInfo || await (await fetch(`${this.baseUrl}/latest.json?t=${Date.now()}`)).json();
302
- if (!this.versionsDir) {
303
- throw new Error("[RenderUpdater] CRITICAL: this.versionsDir lost in download.");
304
- }
305
- if (!infoToUse || !infoToUse.version) {
306
- throw new Error(`[RenderUpdater] CRITICAL: infoToUse or version missing. info: ${JSON.stringify(infoToUse)}`);
307
- }
338
+ if (!this.versionsDir) throw new Error("[RenderUpdater] CRITICAL: this.versionsDir lost in download.");
339
+ if (!infoToUse || !infoToUse.version) throw new Error(`[RenderUpdater] CRITICAL: infoToUse or version missing. info: ${JSON.stringify(infoToUse)}`);
308
340
  const versionDir = path2.join(this.versionsDir, infoToUse.version);
309
- const asarPath = path2.join(versionDir, "renderer.asar");
310
- if (fs2.existsSync(asarPath) && this.verifyFile(asarPath, infoToUse)) {
341
+ const finalAsarPath = path2.join(versionDir, "renderer.asar");
342
+ const tmpAsarPath = path2.join(versionDir, "renderer.asar.tmp");
343
+ if (fs2.existsSync(finalAsarPath) && await verifyFileStream(finalAsarPath, infoToUse, this.publicKey)) {
311
344
  console.log("[RenderUpdater] File already exists and verified. Skipping download.");
312
345
  onProgress?.(100);
313
346
  return;
314
347
  }
315
- if (!fs2.existsSync(versionDir)) {
316
- fs2.mkdirSync(versionDir, { recursive: true });
317
- }
348
+ await fsPromises.mkdir(versionDir, { recursive: true });
318
349
  let downloadedBytes = 0;
319
- if (fs2.existsSync(asarPath)) {
320
- downloadedBytes = fs2.statSync(asarPath).size;
350
+ if (fs2.existsSync(tmpAsarPath)) {
351
+ const stats = await fsPromises.stat(tmpAsarPath);
352
+ downloadedBytes = stats.size;
321
353
  }
322
354
  const fetchOptions = {};
323
355
  if (downloadedBytes > 0) {
@@ -338,7 +370,7 @@ var RenderUpdater = class {
338
370
  const incomingTotal = contentLength ? parseInt(contentLength, 10) : 0;
339
371
  const total = downloadedBytes + incomingTotal;
340
372
  if (!downloadResponse.body) throw new Error("Response body is empty");
341
- const fileStream = fs2.createWriteStream(asarPath, { flags: downloadResponse.status === 206 ? "a" : "w" });
373
+ const fileStream = fs2.createWriteStream(tmpAsarPath, { flags: downloadResponse.status === 206 ? "a" : "w" });
342
374
  const progressTransform = new Transform({
343
375
  transform(chunk, _encoding, callback) {
344
376
  downloadedBytes += chunk.length;
@@ -353,62 +385,34 @@ var RenderUpdater = class {
353
385
  progressTransform,
354
386
  fileStream
355
387
  );
356
- if (!this.verifyFile(asarPath, infoToUse)) {
357
- fs2.rmSync(versionDir, { recursive: true, force: true });
388
+ if (!await verifyFileStream(tmpAsarPath, infoToUse, this.publicKey)) {
389
+ await fsPromises.rm(tmpAsarPath, { force: true });
358
390
  throw new Error("[RenderUpdater] Verification failed after download (SHA256 or RSA Mismatch)");
359
391
  }
360
- this.setUpdatePending(infoToUse.version);
361
- this.cleanOldVersions();
392
+ await fsPromises.rename(tmpAsarPath, finalAsarPath);
393
+ await this.setUpdatePending(infoToUse.version);
394
+ await this.cleanOldVersions();
362
395
  } finally {
363
396
  this.isDownloading = false;
364
397
  }
365
398
  }
366
- verifyFile(filePath, info) {
367
- const fileBuffer = fs2.readFileSync(filePath);
368
- const hashSum = crypto.createHash("sha256");
369
- hashSum.update(fileBuffer);
370
- const sha256 = hashSum.digest("hex");
371
- if (sha256 !== info.sha256 && info.sha256 !== "") {
372
- console.error(`[RenderUpdater] SHA256 mismatch! Expected ${info.sha256}, got ${sha256}`);
373
- return false;
374
- }
375
- if (this.publicKey && info.signature) {
376
- try {
377
- const verify = crypto.createVerify("RSA-SHA256");
378
- verify.update(fileBuffer);
379
- verify.end();
380
- const isValid = verify.verify(this.publicKey, info.signature, "base64");
381
- if (!isValid) {
382
- console.error("[RenderUpdater] RSA Signature verification failed.");
383
- return false;
384
- }
385
- } catch (err) {
386
- console.error("[RenderUpdater] RSA verification error:", err);
387
- return false;
388
- }
389
- }
390
- return true;
391
- }
392
- useVersion(version) {
393
- this.updateDiskVersion(version);
394
- this.updateMemoryVersion(version);
399
+ async useVersion(version) {
400
+ await this.updateDiskVersion(version);
401
+ this._activeVersion = version;
395
402
  }
396
- setUpdatePending(version) {
397
- this.updateDiskVersion(version);
403
+ async setUpdatePending(version) {
404
+ await this.updateDiskVersion(version);
398
405
  if (this.onStatusChanged) {
399
406
  this.onStatusChanged({ status: "ready", version });
400
407
  }
401
408
  }
402
- updateDiskVersion(version) {
403
- fs2.writeFileSync(
409
+ async updateDiskVersion(version) {
410
+ await fsPromises.writeFile(
404
411
  this.currentVersionFile,
405
412
  JSON.stringify({ version, date: (/* @__PURE__ */ new Date()).toISOString() })
406
413
  );
407
414
  this._diskVersion = version;
408
415
  }
409
- updateMemoryVersion(version) {
410
- this._activeVersion = version;
411
- }
412
416
  async installAndRestart() {
413
417
  if (this.onBeforeRestart) {
414
418
  await this.onBeforeRestart();
@@ -416,104 +420,130 @@ var RenderUpdater = class {
416
420
  app2.relaunch();
417
421
  app2.quit();
418
422
  }
423
+ // --- 改版后切分的上帝函数 ---
419
424
  async checkForUpdatesAndNotify() {
420
425
  try {
421
426
  const checkRes = await this.check();
422
- if (checkRes.updateAvailable && checkRes.info) {
423
- const info = checkRes.info;
424
- const isReady = checkRes.status === "ready";
425
- const doDownload = async () => {
426
- try {
427
- if (!isReady) {
428
- await this.download(info, this.onDownloadProgress);
429
- }
430
- const performInstall = async (shouldRestart) => {
431
- if (shouldRestart) {
432
- this.useVersion(info.version);
433
- await this.installAndRestart();
434
- } else {
435
- this.setUpdatePending(info.version);
436
- console.info(`[RenderUpdater] Update v${info.version} ready, will apply on next launch.`);
437
- }
438
- };
439
- if (this.onDownloadComplete) {
440
- this.onDownloadComplete(info, () => {
441
- performInstall(true).catch(console.error);
442
- });
443
- } else {
444
- if (info.forceUpdate || isReady) {
445
- if (isReady) {
446
- const { response } = await dialog.showMessageBox({
447
- type: "info",
448
- title: "\u66F4\u65B0\u5C31\u7EEA",
449
- message: `\u65B0\u7248\u672C v${info.version} \u5DF2\u51C6\u5907\u5C31\u7EEA\u3002\u662F\u5426\u7ACB\u5373\u91CD\u542F\u5E94\u7528\uFF1F`,
450
- buttons: ["\u7ACB\u5373\u91CD\u542F", "\u7A0D\u540E\u518D\u8BF4"],
451
- defaultId: 0,
452
- cancelId: 1
453
- });
454
- await performInstall(response === 0);
455
- } else {
456
- if (info.forceUpdate === "prompt") {
457
- dialog.showMessageBoxSync({
458
- type: "warning",
459
- title: "\u4E0B\u8F7D\u5B8C\u6210",
460
- message: `\u6700\u65B0\u7248\u672C v${info.version} \u5DF2\u4E0B\u8F7D\u5B8C\u6BD5\u3002\u5373\u5C06\u91CD\u542F\u7A0B\u5E8F\u4EE5\u5E94\u7528\u66F4\u65B0\u3002`,
461
- buttons: ["\u6211\u77E5\u9053\u4E86"]
462
- });
463
- }
464
- await performInstall(true);
465
- }
466
- } else if (this.autoPrompt) {
467
- const { response } = await dialog.showMessageBox({
468
- type: "info",
469
- title: "\u4E0B\u8F7D\u5B8C\u6210",
470
- message: `\u6700\u65B0\u7248\u672C v${info.version} \u5DF2\u4E0B\u8F7D\u5B8C\u6BD5\u3002\u662F\u5426\u7ACB\u5373\u91CD\u542F\u4EE5\u5E94\u7528\u66F4\u65B0\uFF1F`,
471
- buttons: ["\u7ACB\u5373\u91CD\u542F", "\u7A0D\u540E\u518D\u8BF4"],
472
- defaultId: 0,
473
- cancelId: 1
474
- });
475
- await performInstall(response === 0);
476
- }
477
- }
478
- } catch (e) {
479
- if (this.onError) {
480
- this.onError(e);
481
- } else {
482
- console.error("[RenderUpdater] Download/Install error:", e);
483
- }
484
- }
485
- };
486
- if (this.onUpdateAvailable) {
487
- this.onUpdateAvailable(info, doDownload);
427
+ if (!checkRes.updateAvailable || !checkRes.info) return;
428
+ const info = checkRes.info;
429
+ if (checkRes.status === "ready") {
430
+ await this.handleReadyUpdate(info);
431
+ } else {
432
+ await this.handleNewUpdate(info);
433
+ }
434
+ } catch (e) {
435
+ this.handleError(e);
436
+ }
437
+ }
438
+ async handleReadyUpdate(info) {
439
+ if (this.onUpdateAvailable) {
440
+ this.onUpdateAvailable(info, async () => {
441
+ if (this.onDownloadComplete) {
442
+ this.onDownloadComplete(info, () => this.performInstall(info, true).catch(this.handleError.bind(this)));
488
443
  } else {
489
- if (info.forceUpdate || isReady) {
490
- await doDownload();
491
- } else if (this.autoDownload) {
492
- await doDownload();
493
- } else if (this.autoPrompt) {
494
- const { response } = await dialog.showMessageBox({
495
- type: "info",
496
- title: "\u53D1\u73B0\u65B0\u7248\u672C",
497
- message: `\u53D1\u73B0\u53EF\u7528\u66F4\u65B0 (v${info.version})\u3002\u662F\u5426\u7ACB\u5373\u4E0B\u8F7D\u66F4\u65B0\uFF1F
498
-
499
- \u66F4\u65B0\u65E5\u5FD7\uFF1A
500
- ${info.releaseNotes || "\u65E0\u8BE6\u7EC6\u8BB0\u5F55\u3002"}`,
501
- buttons: ["\u7ACB\u5373\u66F4\u65B0", "\u7A0D\u540E\u518D\u8BF4"],
502
- cancelId: 1
503
- });
504
- if (response === 0) {
505
- await doDownload();
506
- }
507
- }
444
+ await this.performInstall(info, true);
508
445
  }
446
+ });
447
+ return;
448
+ }
449
+ if (info.forceUpdate || !this.autoPrompt) {
450
+ await this.performInstall(info, true);
451
+ return;
452
+ }
453
+ const response = await this.showReadyDialog(info.version);
454
+ await this.performInstall(info, response === 0);
455
+ }
456
+ async handleNewUpdate(info) {
457
+ if (this.onUpdateAvailable) {
458
+ this.onUpdateAvailable(info, () => this.executeAutoDownload(info));
459
+ return;
460
+ }
461
+ if (info.forceUpdate || this.autoDownload) {
462
+ await this.executeAutoDownload(info);
463
+ } else if (this.autoPrompt) {
464
+ const response = await this.showNewUpdateDialog(info);
465
+ if (response === 0) {
466
+ await this.executeAutoDownload(info);
509
467
  }
510
- } catch (e) {
511
- if (this.onError) {
512
- this.onError(e);
468
+ }
469
+ }
470
+ async executeAutoDownload(info) {
471
+ try {
472
+ await this.download(info, this.onDownloadProgress);
473
+ const doInstall = (shouldRestart) => this.performInstall(info, shouldRestart).catch(this.handleError.bind(this));
474
+ if (this.onDownloadComplete) {
475
+ this.onDownloadComplete(info, () => doInstall(true));
513
476
  } else {
514
- console.error("[RenderUpdater] Error in checkForUpdatesAndNotify:", e);
477
+ const isSilent = info.forceUpdate === "silent";
478
+ const shouldRestart = isSilent || await this.showDownloadCompleteDialog(info.version, info.forceUpdate === "prompt") === 0;
479
+ await doInstall(shouldRestart);
515
480
  }
481
+ } catch (e) {
482
+ this.handleError(e);
483
+ }
484
+ }
485
+ async performInstall(info, shouldRestart) {
486
+ if (shouldRestart) {
487
+ await this.useVersion(info.version);
488
+ await this.installAndRestart();
489
+ } else {
490
+ await this.setUpdatePending(info.version);
491
+ console.info(`[RenderUpdater] Update v${info.version} ready, will apply on next launch.`);
492
+ }
493
+ }
494
+ handleError(error) {
495
+ if (this.onError) {
496
+ this.onError(error);
497
+ } else {
498
+ console.error("[RenderUpdater] Error:", error);
499
+ }
500
+ }
501
+ // UI 分发抽离
502
+ async showReadyDialog(version) {
503
+ const { response } = await dialog.showMessageBox({
504
+ type: "info",
505
+ title: "\u66F4\u65B0\u5C31\u7EEA",
506
+ message: `\u65B0\u7248\u672C v${version} \u5DF2\u51C6\u5907\u5C31\u7EEA\u3002\u662F\u5426\u7ACB\u5373\u91CD\u542F\u5E94\u7528\uFF1F`,
507
+ buttons: ["\u7ACB\u5373\u91CD\u542F", "\u7A0D\u540E\u518D\u8BF4"],
508
+ defaultId: 0,
509
+ cancelId: 1
510
+ });
511
+ return response;
512
+ }
513
+ async showNewUpdateDialog(info) {
514
+ const { response } = await dialog.showMessageBox({
515
+ type: "info",
516
+ title: "\u53D1\u73B0\u65B0\u7248\u672C",
517
+ message: `\u53D1\u73B0\u53EF\u7528\u66F4\u65B0 (v${info.version})\u3002\u662F\u5426\u7ACB\u5373\u4E0B\u8F7D\u66F4\u65B0\uFF1F
518
+
519
+ \u66F4\u65B0\u65E5\u5FD7\uFF1A
520
+ ${info.releaseNotes || "\u65E0\u8BE6\u7EC6\u8BB0\u5F55\u3002"}`,
521
+ buttons: ["\u7ACB\u5373\u66F4\u65B0", "\u7A0D\u540E\u518D\u8BF4"],
522
+ cancelId: 1
523
+ });
524
+ return response;
525
+ }
526
+ async showDownloadCompleteDialog(version, forcePrompt) {
527
+ if (forcePrompt) {
528
+ dialog.showMessageBoxSync({
529
+ type: "warning",
530
+ title: "\u4E0B\u8F7D\u5B8C\u6210",
531
+ message: `\u6700\u65B0\u7248\u672C v${version} \u5DF2\u4E0B\u8F7D\u5B8C\u6BD5\u3002\u5373\u5C06\u91CD\u542F\u7A0B\u5E8F\u4EE5\u5E94\u7528\u66F4\u65B0\u3002`,
532
+ buttons: ["\u6211\u77E5\u9053\u4E86"]
533
+ });
534
+ return 0;
535
+ } else if (this.autoPrompt) {
536
+ const { response } = await dialog.showMessageBox({
537
+ type: "info",
538
+ title: "\u4E0B\u8F7D\u5B8C\u6210",
539
+ message: `\u6700\u65B0\u7248\u672C v${version} \u5DF2\u4E0B\u8F7D\u5B8C\u6BD5\u3002\u662F\u5426\u7ACB\u5373\u91CD\u542F\u4EE5\u5E94\u7528\u66F4\u65B0\uFF1F`,
540
+ buttons: ["\u7ACB\u5373\u91CD\u542F", "\u7A0D\u540E\u518D\u8BF4"],
541
+ defaultId: 0,
542
+ cancelId: 1
543
+ });
544
+ return response;
516
545
  }
546
+ return 0;
517
547
  }
518
548
  };
519
549
  export {
@@ -0,0 +1,6 @@
1
+ import type { UpdateInfo } from '../types';
2
+ /**
3
+ * 采用流式(Stream)的方式读取大文件计算 SHA256 与 RSA 签名
4
+ * 确保即使文件达到数百MB,V8内存也不会暴涨,不阻塞主线程
5
+ */
6
+ export declare function verifyFileStream(filePath: string, info: UpdateInfo, publicKey?: string): Promise<boolean>;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "electron-updater-for-render",
3
- "version": "1.1.2-beta.7",
3
+ "version": "1.1.3",
4
4
  "description": "A lightweight incremental updater for Electron renderer processes",
5
5
  "type": "module",
6
6
  "bin": {