From 3cc46c492b0cfcbf8b59a654d3fa97de61b0bdb1 Mon Sep 17 00:00:00 2001 From: 4gray Date: Sun, 28 Jun 2026 18:26:49 +0200 Subject: [PATCH] fix(updater): address final review feedback --- .../app/services/app-update.service.spec.ts | 29 +++++++++++++++++++ .../src/app/services/app-update.service.ts | 14 +++++++++ .../settings-about-section.component.spec.ts | 15 ++++++++++ .../settings-about-section.component.ts | 2 +- 4 files changed, 59 insertions(+), 1 deletion(-) diff --git a/apps/electron-backend/src/app/services/app-update.service.spec.ts b/apps/electron-backend/src/app/services/app-update.service.spec.ts index f32405542..352ca5264 100644 --- a/apps/electron-backend/src/app/services/app-update.service.spec.ts +++ b/apps/electron-backend/src/app/services/app-update.service.spec.ts @@ -145,6 +145,35 @@ describe('AppUpdateService', () => { ); }); + it('deduplicates concurrent update checks', async () => { + const { service, updater } = createService(); + let resolveCheck: ((value: unknown) => void) | undefined; + updater.checkForUpdates.mockImplementationOnce( + () => + new Promise((resolve) => { + resolveCheck = resolve; + }) + ); + + const firstCheck = service.checkForUpdates(); + const secondCheck = service.checkForUpdates(); + + expect(updater.checkForUpdates).toHaveBeenCalledTimes(1); + expect(service.getStatus().status).toBe( + ELECTRON_BRIDGE_APP_UPDATE_STATUSES.Checking + ); + + resolveCheck?.(null); + await expect(Promise.all([firstCheck, secondCheck])).resolves.toEqual([ + service.getStatus(), + service.getStatus(), + ]); + + await service.checkForUpdates(); + + expect(updater.checkForUpdates).toHaveBeenCalledTimes(2); + }); + it('checks packaged non-AppImage Linux builds through GitHub manual fallback', async () => { const fetcher = createReleaseFetcher(); const { service, updater } = createService({ diff --git a/apps/electron-backend/src/app/services/app-update.service.ts b/apps/electron-backend/src/app/services/app-update.service.ts index ffb628578..6efc736b3 100644 --- a/apps/electron-backend/src/app/services/app-update.service.ts +++ b/apps/electron-backend/src/app/services/app-update.service.ts @@ -226,6 +226,8 @@ export class AppUpdateService { private readonly releases: CachedGitHubRelease[] = []; private loadedReleasePages = 0; private loadedAllReleases = false; + private checkForUpdatesPromise: Promise | null = + null; private status: ElectronBridgeAppUpdateStatus; constructor(private readonly options: AppUpdateServiceOptions) { @@ -256,6 +258,18 @@ export class AppUpdateService { } async checkForUpdates(): Promise { + if (this.checkForUpdatesPromise) { + return this.checkForUpdatesPromise; + } + + this.checkForUpdatesPromise = this.runCheckForUpdates().finally(() => { + this.checkForUpdatesPromise = null; + }); + + return this.checkForUpdatesPromise; + } + + private async runCheckForUpdates(): Promise { if (!this.isPackaged) { this.setStatus({ status: ELECTRON_BRIDGE_APP_UPDATE_STATUSES.Unsupported, diff --git a/apps/web/src/app/settings/settings-about-section.component.spec.ts b/apps/web/src/app/settings/settings-about-section.component.spec.ts index 0c4fb35ac..b74b3d016 100644 --- a/apps/web/src/app/settings/settings-about-section.component.spec.ts +++ b/apps/web/src/app/settings/settings-about-section.component.spec.ts @@ -98,4 +98,19 @@ describe('SettingsAboutSectionComponent app updates', () => { expect(getButton(fixture, 'app-update-open-release')).toBeTruthy(); expect(openManual).toHaveBeenCalledTimes(1); }); + + it('uses a neutral status label before unsupported packages have checked GitHub releases', () => { + configureComponent(fixture, { + currentVersion: '0.22.0', + manualDownloadUrl: + 'https://github.com/4gray/iptvnator/releases/latest', + status: ELECTRON_BRIDGE_APP_UPDATE_STATUSES.Unsupported, + supportedSelfUpdate: false, + }); + + expect(fixture.componentInstance.appUpdateStatusLabelKey()).toBe( + 'SETTINGS.APP_UPDATE_IDLE' + ); + expect(getButton(fixture, 'app-update-open-release')).toBeTruthy(); + }); }); diff --git a/apps/web/src/app/settings/settings-about-section.component.ts b/apps/web/src/app/settings/settings-about-section.component.ts index ecc9dbbea..0c67a5783 100644 --- a/apps/web/src/app/settings/settings-about-section.component.ts +++ b/apps/web/src/app/settings/settings-about-section.component.ts @@ -97,7 +97,7 @@ export class SettingsAboutSectionComponent { case ELECTRON_BRIDGE_APP_UPDATE_STATUSES.Downloaded: return 'SETTINGS.APP_UPDATE_DOWNLOADED'; case ELECTRON_BRIDGE_APP_UPDATE_STATUSES.Unsupported: - return 'SETTINGS.APP_UPDATE_MANUAL_AVAILABLE'; + return 'SETTINGS.APP_UPDATE_IDLE'; case ELECTRON_BRIDGE_APP_UPDATE_STATUSES.Error: return 'SETTINGS.APP_UPDATE_ERROR'; default: