From 0f33b4dafc65cc5a00c19c19a5904a34183096bd Mon Sep 17 00:00:00 2001 From: ekko <152005280+EKKOLearnAI@users.noreply.github.com> Date: Thu, 16 Jul 2026 10:03:38 +0800 Subject: [PATCH] Make desktop update downloads optional (#2094) --- packages/desktop/src/main/desktop-i18n.ts | 10 +++++++ packages/desktop/src/main/updater.ts | 33 ++++++++++++++--------- tests/desktop/updater.test.ts | 15 +++++++++++ 3 files changed, 45 insertions(+), 13 deletions(-) diff --git a/packages/desktop/src/main/desktop-i18n.ts b/packages/desktop/src/main/desktop-i18n.ts index caa8b66f..02a4e95b 100644 --- a/packages/desktop/src/main/desktop-i18n.ts +++ b/packages/desktop/src/main/desktop-i18n.ts @@ -16,6 +16,7 @@ type TranslationKey = | 'update.currentVersion' | 'update.availableTitle' | 'update.availableMessage' + | 'update.availableDetail' | 'update.downloading' | 'update.readyTitle' | 'update.readyMessage' @@ -71,6 +72,7 @@ const translations: Record> = { 'update.currentVersion': 'Current version: {version}', 'update.availableTitle': 'Update available', 'update.availableMessage': 'Hermes Studio {version} is available.', + 'update.availableDetail': 'Choose whether to download this update.', 'update.downloading': 'The update is downloading in the background.', 'update.readyTitle': 'Update ready', 'update.readyMessage': 'Hermes Studio {version} is ready to install.', @@ -123,6 +125,7 @@ const translations: Record> = { 'update.currentVersion': '当前版本:{version}', 'update.availableTitle': '发现新版本', 'update.availableMessage': 'Hermes Studio {version} 可用。', + 'update.availableDetail': '请选择是否下载此更新。', 'update.downloading': '更新正在后台下载。', 'update.readyTitle': '更新已就绪', 'update.readyMessage': 'Hermes Studio {version} 已准备好安装。', @@ -175,6 +178,7 @@ const translations: Record> = { 'update.currentVersion': '目前版本:{version}', 'update.availableTitle': '發現新版本', 'update.availableMessage': 'Hermes Studio {version} 可用。', + 'update.availableDetail': '請選擇是否下載此更新。', 'update.downloading': '更新正在背景下載。', 'update.readyTitle': '更新已就緒', 'update.readyMessage': 'Hermes Studio {version} 已準備好安裝。', @@ -227,6 +231,7 @@ const translations: Record> = { 'update.currentVersion': '現在のバージョン: {version}', 'update.availableTitle': 'アップデートがあります', 'update.availableMessage': 'Hermes Studio {version} が利用できます。', + 'update.availableDetail': 'このアップデートをダウンロードするか選択してください。', 'update.downloading': 'アップデートをバックグラウンドでダウンロードしています。', 'update.readyTitle': 'アップデートの準備ができました', 'update.readyMessage': 'Hermes Studio {version} をインストールできます。', @@ -279,6 +284,7 @@ const translations: Record> = { 'update.currentVersion': '현재 버전: {version}', 'update.availableTitle': '업데이트 사용 가능', 'update.availableMessage': 'Hermes Studio {version}을 사용할 수 있습니다.', + 'update.availableDetail': '이 업데이트를 다운로드할지 선택하세요.', 'update.downloading': '업데이트를 백그라운드에서 다운로드하고 있습니다.', 'update.readyTitle': '업데이트 준비 완료', 'update.readyMessage': 'Hermes Studio {version}을 설치할 준비가 되었습니다.', @@ -331,6 +337,7 @@ const translations: Record> = { 'update.currentVersion': 'Version actuelle : {version}', 'update.availableTitle': 'Mise a jour disponible', 'update.availableMessage': 'Hermes Studio {version} est disponible.', + 'update.availableDetail': 'Choisissez si vous souhaitez telecharger cette mise a jour.', 'update.downloading': 'La mise a jour se telecharge en arriere-plan.', 'update.readyTitle': 'Mise a jour prete', 'update.readyMessage': 'Hermes Studio {version} est pret a etre installe.', @@ -383,6 +390,7 @@ const translations: Record> = { 'update.currentVersion': 'Version actual: {version}', 'update.availableTitle': 'Actualizacion disponible', 'update.availableMessage': 'Hermes Studio {version} esta disponible.', + 'update.availableDetail': 'Elige si deseas descargar esta actualizacion.', 'update.downloading': 'La actualizacion se esta descargando en segundo plano.', 'update.readyTitle': 'Actualizacion lista', 'update.readyMessage': 'Hermes Studio {version} esta listo para instalarse.', @@ -435,6 +443,7 @@ const translations: Record> = { 'update.currentVersion': 'Aktuelle Version: {version}', 'update.availableTitle': 'Update verfugbar', 'update.availableMessage': 'Hermes Studio {version} ist verfugbar.', + 'update.availableDetail': 'Wahlen Sie, ob Sie dieses Update herunterladen mochten.', 'update.downloading': 'Das Update wird im Hintergrund heruntergeladen.', 'update.readyTitle': 'Update bereit', 'update.readyMessage': 'Hermes Studio {version} ist zur Installation bereit.', @@ -487,6 +496,7 @@ const translations: Record> = { 'update.currentVersion': 'Versao atual: {version}', 'update.availableTitle': 'Atualizacao disponivel', 'update.availableMessage': 'Hermes Studio {version} esta disponivel.', + 'update.availableDetail': 'Escolha se deseja baixar esta atualizacao.', 'update.downloading': 'A atualizacao esta sendo baixada em segundo plano.', 'update.readyTitle': 'Atualizacao pronta', 'update.readyMessage': 'Hermes Studio {version} esta pronto para instalar.', diff --git a/packages/desktop/src/main/updater.ts b/packages/desktop/src/main/updater.ts index 5f4ceeed..650eacdf 100644 --- a/packages/desktop/src/main/updater.ts +++ b/packages/desktop/src/main/updater.ts @@ -167,6 +167,21 @@ async function promptInstallDownloadedUpdate(info: UpdateInfo): Promise { } } +async function promptDownloadAvailableUpdate(info: UpdateInfo): Promise { + const { response } = await dialog.showMessageBox({ + type: 'info', + title: t('update.availableTitle'), + message: t('update.availableMessage', { version: info.version }), + detail: t('update.availableDetail'), + buttons: [t('update.download'), t('update.later')], + defaultId: 0, + cancelId: 1, + }) + if (response === 0) { + await autoUpdater.downloadUpdate() + } +} + export function initAutoUpdater(nextOptions: AutoUpdaterOptions = {}) { options = { ...options, ...nextOptions } if (initialized) return @@ -174,18 +189,15 @@ export function initAutoUpdater(nextOptions: AutoUpdaterOptions = {}) { if (!app.isPackaged) return // dev mode: skip - autoUpdater.autoDownload = true + autoUpdater.autoDownload = false autoUpdater.autoInstallOnAppQuit = true autoUpdater.on('update-available', info => { console.log(`[updater] update available: ${info.version}`) - dialog.showMessageBox({ - type: 'info', - title: t('update.availableTitle'), - message: t('update.availableMessage', { version: info.version }), - detail: t('update.downloading'), - buttons: [t('common.ok')], - }).catch(() => undefined) + promptDownloadAvailableUpdate(info).catch(err => { + console.error('[updater] update download failed:', err) + showUpdateCheckFailed() + }) }) autoUpdater.on('update-not-available', info => { console.log('[updater] up to date') @@ -211,11 +223,6 @@ export function initAutoUpdater(nextOptions: AutoUpdaterOptions = {}) { console.error('[updater] initial check failed:', err) }) } - - // Recheck every 6h while app is running - setInterval(() => { - checkForDesktopUpdates(false).catch(() => undefined) - }, 6 * 60 * 60 * 1000) } export async function checkForDesktopUpdates(manual: boolean): Promise { diff --git a/tests/desktop/updater.test.ts b/tests/desktop/updater.test.ts index 687b1df1..f4558eb0 100644 --- a/tests/desktop/updater.test.ts +++ b/tests/desktop/updater.test.ts @@ -1,3 +1,5 @@ +import { readFileSync } from 'node:fs' +import { resolve } from 'node:path' import { describe, expect, it } from 'vitest' import { isWindowsUpdaterLockError, pendingUpdateDirectories } from '../../packages/desktop/src/main/updater-helpers' @@ -19,4 +21,17 @@ describe('desktop updater helpers', () => { 'C:\\Users\\A\\AppData\\Roaming/hermes-studio-updater/pending', ])) }) + + it('checks on startup and from the tray without forcing an update', () => { + const updaterSource = readFileSync(resolve('packages/desktop/src/main/updater.ts'), 'utf-8') + const mainSource = readFileSync(resolve('packages/desktop/src/main/index.ts'), 'utf-8') + + expect(mainSource).toContain('checkForDesktopUpdates(true)') + expect(updaterSource).toContain('checkForDesktopUpdates(false)') + expect(updaterSource).toContain('autoUpdater.autoDownload = false') + expect(updaterSource).toContain('autoUpdater.autoInstallOnAppQuit = true') + expect(updaterSource).toContain("buttons: [t('update.download'), t('update.later')]") + expect(updaterSource).toContain('if (response === 0) {\n await autoUpdater.downloadUpdate()') + expect(updaterSource).not.toContain('setInterval(') + }) })