From 9b8659a40b8830fa364d7b6c56d1200c81a966ad Mon Sep 17 00:00:00 2001 From: Hampus Date: Sat, 29 Aug 2026 17:44:43 +0200 Subject: [PATCH] fix(desktop): stop the updater button flickering after download (#2089) --- .../updater/state/UpdaterStateMachine.test.ts | 30 +++++++++++++++++++ .../updater/state/UpdaterStateMachine.ts | 7 +++++ fluxer_desktop/src/main/Updater.ts | 24 ++++++++++++++- 3 files changed, 60 insertions(+), 1 deletion(-) diff --git a/fluxer_app/src/features/updater/state/UpdaterStateMachine.test.ts b/fluxer_app/src/features/updater/state/UpdaterStateMachine.test.ts index b5e79c265..42412458a 100644 --- a/fluxer_app/src/features/updater/state/UpdaterStateMachine.test.ts +++ b/fluxer_app/src/features/updater/state/UpdaterStateMachine.test.ts @@ -45,6 +45,36 @@ describe('updaterStateMachine', () => { expect(snapshot.context.updateInfo.web.available).toBe(false); }); + it('keeps a downloaded native update pending restart when checks re-emit available or not-available', () => { + let snapshot = createUpdaterMachineSnapshot(); + snapshot = transition(snapshot, { + type: 'native.available', + version: '2.0.0', + downloadSize: 1000, + downloadStarted: true, + downloadUrl: null, + downloadOptions: [], + }); + snapshot = transition(snapshot, {type: 'native.downloaded', version: '2.0.0'}); + expect(snapshot.context.updateInfo.native.downloaded).toBe(true); + + snapshot = transition(snapshot, {type: 'native.notAvailable', now: NOW}); + expect(snapshot.context.updateInfo.native.downloaded).toBe(true); + expect(snapshot.context.updateInfo.native.available).toBe(true); + + snapshot = transition(snapshot, { + type: 'native.available', + version: null, + downloadSize: null, + downloadStarted: true, + downloadUrl: null, + downloadOptions: [], + }); + expect(snapshot.context.updateInfo.native.downloaded).toBe(true); + expect(snapshot.context.updateInfo.native.downloading).toBe(false); + expect(snapshot.context.updateInfo.native.version).toBe('2.0.0'); + }); + it('tracks check start and finish separately from available updates', () => { let snapshot = createUpdaterMachineSnapshot(); snapshot = transition(snapshot, {type: 'check.started'}); diff --git a/fluxer_app/src/features/updater/state/UpdaterStateMachine.ts b/fluxer_app/src/features/updater/state/UpdaterStateMachine.ts index 06d270540..7edf9b3b9 100644 --- a/fluxer_app/src/features/updater/state/UpdaterStateMachine.ts +++ b/fluxer_app/src/features/updater/state/UpdaterStateMachine.ts @@ -166,6 +166,13 @@ export const updaterStateMachine = setup({ }), applyNativeAvailable: assign(({context, event}) => { if (event.type !== 'native.available') return {}; + const currentNative = context.updateInfo.native; + if (currentNative.downloaded && (event.version == null || event.version === currentNative.version)) { + return { + isChecking: false, + nativeUnsupported: null, + }; + } return { updateInfo: { ...context.updateInfo, diff --git a/fluxer_desktop/src/main/Updater.ts b/fluxer_desktop/src/main/Updater.ts index 05b00057b..198b5122f 100644 --- a/fluxer_desktop/src/main/Updater.ts +++ b/fluxer_desktop/src/main/Updater.ts @@ -308,6 +308,8 @@ function registerVelopackUpdater(getMainWindow: () => BrowserWindow | null): voi function registerElectronUpdater(getMainWindow: () => BrowserWindow | null): void { let electronUpdateDownloading = false; let electronDownloadRetries = 0; + let electronUpdateDownloaded = false; + let electronDownloadedVersion: string | null = null; const {UpdateSourceType, updateElectronApp} = requireModule( 'update-electron-app', ) as typeof import('update-electron-app'); @@ -323,7 +325,14 @@ function registerElectronUpdater(getMainWindow: () => BrowserWindow | null): voi autoUpdater.on('checking-for-update', () => { send(getMainWindow(), {type: 'checking', context: lastContext}); }); + const sendPendingRestart = () => { + send(getMainWindow(), {type: 'downloaded', context: lastContext, version: electronDownloadedVersion}); + }; autoUpdater.on('update-available', () => { + if (electronUpdateDownloaded) { + sendPendingRestart(); + return; + } electronUpdateDownloading = true; send(getMainWindow(), { type: 'available', @@ -334,10 +343,19 @@ function registerElectronUpdater(getMainWindow: () => BrowserWindow | null): voi }); }); autoUpdater.on('update-not-available', () => { + if (electronUpdateDownloaded) { + sendPendingRestart(); + return; + } send(getMainWindow(), {type: 'not-available', context: lastContext}); }); autoUpdater.on('update-downloaded', (_event, _releaseNotes, releaseName) => { - send(getMainWindow(), {type: 'downloaded', context: lastContext, version: releaseName ?? null}); + electronUpdateDownloading = false; + electronUpdateDownloaded = true; + if (releaseName) { + electronDownloadedVersion = releaseName; + } + sendPendingRestart(); }); autoUpdater.on('error', (err: Error) => { const message = err?.message ?? String(err); @@ -360,6 +378,10 @@ function registerElectronUpdater(getMainWindow: () => BrowserWindow | null): voi return; } electronUpdateDownloading = false; + if (electronUpdateDownloaded) { + sendPendingRestart(); + return; + } send(getMainWindow(), {type: 'error', context: lastContext, phase, message}); }); ipcMain.handle('updater-check', async (_e, context: UpdaterContext) => {