From 5706355c5f2732b92bbf97c8c8161f5f9c544955 Mon Sep 17 00:00:00 2001 From: Ivan Fontosh Date: Tue, 19 May 2026 08:24:52 +0800 Subject: [PATCH] fix(mac): prevent updater hang on manual install Bump to 1.0.20. Disable autoDownload during manual check and fix Squirrel.Mac quitAndInstall race on darwin. Co-authored-by: Cursor --- app/main/update/installAutoUpdater.ts | 61 ++++++++++++++++++++++++--- package-lock.json | 4 +- package.json | 2 +- 3 files changed, 59 insertions(+), 8 deletions(-) diff --git a/app/main/update/installAutoUpdater.ts b/app/main/update/installAutoUpdater.ts index dda4668..3211d9c 100644 --- a/app/main/update/installAutoUpdater.ts +++ b/app/main/update/installAutoUpdater.ts @@ -14,6 +14,43 @@ import type { LicenseService } from '../license/licenseService'; const STARTUP_CHECK_DELAY_MS = 12_000; /** Не дёргать сервер чаще (смена лицензии / повторные emit). */ const RE_CHECK_COOLDOWN_MS = 30_000; +/** Ручная загрузка из модалки — не зависать бесконечно на «Загрузка…». */ +const MANUAL_DOWNLOAD_TIMEOUT_MS = 30 * 60 * 1000; + +function withTimeout(promise: Promise, ms: number, code: string): Promise { + return new Promise((resolve, reject) => { + const timer = setTimeout(() => reject(new Error(code)), ms); + promise.then( + (v) => { + clearTimeout(timer); + resolve(v); + }, + (e: unknown) => { + clearTimeout(timer); + reject(e instanceof Error ? e : new Error(String(e))); + }, + ); + }); +} + +/** + * На macOS Squirrel.Mac может уже получить update-downloaded к моменту quitAndInstall. + * При autoInstallOnAppQuit=true MacUpdater не вызывает checkForUpdates повторно и зависает. + */ +function quitAndInstallForPlatform(): void { + if (process.platform === 'darwin') { + autoUpdater.autoInstallOnAppQuit = false; + } + autoUpdater.quitAndInstall(false, true); +} + +function formatUpdaterError(e: unknown): string { + const raw = e instanceof Error ? e.message : String(e); + if (raw === 'UPDATE_DOWNLOAD_TIMEOUT') { + return 'Превышено время ожидания загрузки обновления'; + } + return raw; +} let lastCheckAt = 0; /** Ручная установка: не показывать второй диалог из `update-downloaded`. */ @@ -42,6 +79,8 @@ async function runManualUpdaterCheck(licenseService: LicenseService): Promise { if (!app.isPackaged) { return { ok: false, message: 'NOT_PACKAGED' }; } + const prevAutoInstallOnAppQuit = autoUpdater.autoInstallOnAppQuit; try { suppressAutoInstallDialog = true; - await autoUpdater.downloadUpdate(); - autoUpdater.quitAndInstall(false, true); + if (process.platform === 'darwin') { + autoUpdater.autoInstallOnAppQuit = false; + } + await withTimeout( + autoUpdater.downloadUpdate(), + MANUAL_DOWNLOAD_TIMEOUT_MS, + 'UPDATE_DOWNLOAD_TIMEOUT', + ); + quitAndInstallForPlatform(); return { ok: true }; } catch (e) { suppressAutoInstallDialog = false; - const message = e instanceof Error ? e.message : String(e); - return { ok: false, message }; + if (process.platform === 'darwin') { + autoUpdater.autoInstallOnAppQuit = prevAutoInstallOnAppQuit; + } + return { ok: false, message: formatUpdaterError(e) }; } } @@ -119,7 +170,7 @@ export function installAutoUpdater(licenseService: LicenseService, register: Reg }) .then((r) => { if (r.response === 0) { - autoUpdater.quitAndInstall(false, true); + quitAndInstallForPlatform(); } }); }); diff --git a/package-lock.json b/package-lock.json index 393e873..0694197 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "TTRPGPlayer", - "version": "1.0.18", + "version": "1.0.20", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "TTRPGPlayer", - "version": "1.0.18", + "version": "1.0.20", "hasInstallScript": true, "license": "ISC", "dependencies": { diff --git a/package.json b/package.json index a6bb499..4ad044d 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "TTRPGPlayer", - "version": "1.0.19", + "version": "1.0.20", "description": "TTRPG Player — редактор и проигрыватель НРИ", "main": "dist/main/index.cjs", "scripts": {