feat(updater): фоновое скачивание + моментальный рестарт

Раньше после «Скачать» renderer ждал promise (`ipcRenderer.invoke`),
пока electron-updater не завершит весь download. Если пользователь
закрывал Settings и уходил на Dashboard — скачивание продолжалось,
но кнопка возвращалась в `busy=true` при следующем открытии.
Сама установка через `quitAndInstall()` без параметров поднимала
NSIS-диалог установщика — ~5-10 сек до запуска новой версии.

Что изменилось:

- IPC `updaterDownload` / `updaterInstall` — fire-and-forget через
  `ipcMain.on` / `ipcRenderer.send`. Renderer триггерит и забывает,
  прогресс приходит через `evtUpdaterStatus`. UI моментально
  переключается в kind:'downloading' и не блокируется ожиданием.
- `autoUpdater.quitAndInstall(true, true)`:
    - isSilent=true: NSIS работает без UI установщика (~1-2 сек
      вместо ~5-10), без чёрного окна на половину экрана.
    - isForceRunAfter=true: гарантия что приложение запустится
      после установки (иначе пользователь нажал «Рестарт» и остался
      без открытого приложения).
- UpdaterCard: убран `busy` для async download — статус сам
  переключается через события. Добавлена подсказка «можно закрыть
  это окно, скачивание продолжится в фоне». Подкручен subtitle на
  downloaded-state: «нажми Рестарт — приложение моментально
  откроется в новой версии».
- i18n: новый ключ `updater.downloading.hint` (RU + EN), обновлён
  `updater.downloaded.subtitle`.

`autoInstallOnAppQuit = true` уже был включён — если пользователь
не нажал «Рестарт» и просто закрыл приложение, установка
произойдёт при следующем закрытии автоматически.
This commit is contained in:
AnRil
2026-05-19 21:33:00 +07:00
parent 36085f225f
commit c5c05ee651
5 changed files with 37 additions and 15 deletions

View File

@@ -285,8 +285,13 @@ export function registerIpc(): void {
// Auto-updater
ipcMain.handle(IPC.updaterStatus, () => getUpdaterStatus())
ipcMain.handle(IPC.updaterCheck, () => checkForUpdates())
ipcMain.handle(IPC.updaterDownload, () => downloadUpdate())
ipcMain.handle(IPC.updaterInstall, () => quitAndInstall())
// download/install — fire-and-forget. Прогресс и завершение приходят в
// renderer через evtUpdaterStatus, ждать promise бессмысленно — renderer
// только зря держал бы `busy=true` весь download (минуты на медленной сети).
ipcMain.on(IPC.updaterDownload, () => {
void downloadUpdate()
})
ipcMain.on(IPC.updaterInstall, () => quitAndInstall())
// History
ipcMain.handle(IPC.getHistory, (_e, sinceMs?: number) => getHistory(sinceMs))

View File

@@ -172,5 +172,12 @@ export async function downloadUpdate(): Promise<void> {
export function quitAndInstall(): void {
if (!app.isPackaged) return
autoUpdater.quitAndInstall()
// (isSilent=true, isForceRunAfter=true):
// - isSilent: NSIS работает без UI-диалогов установки → restart занимает
// ~1-2 сек вместо ~5-10 (без чёрного окна установщика на половину экрана).
// - isForceRunAfter: гарантируем что после установки приложение запустится
// автоматически, даже если в NSIS-конфиге runAfterFinish был выключен
// для этого сценария. Без этого пользователь нажал «Рестарт» — и остался
// без открытого приложения.
autoUpdater.quitAndInstall(true, true)
}