fix(updater): avoid settings status startup race

This commit is contained in:
4gray committed 2026-06-28 17:36:55 +02:00
1 parent 3db409cb5b
commit f7eb34ddba
3 files changed
+61 -19

No files matched your search

+7 -6
View File
@@ -86,6 +86,13 @@ export default class Main {
trace('startup', 'bootstrap-events:start');
}
const appUpdateService = new AppUpdateService({
app,
getMainWindow: () => App.mainWindow,
updater: autoUpdater,
});
AppUpdateEvents.bootstrapAppUpdateEvents(appUpdateService);
// Initialize database before other events
await initDatabase();
@@ -105,12 +112,6 @@ export default class Main {
DatabaseEvents.bootstrapDatabaseEvents();
EpgEvents.bootstrapEpgEvents();
RemoteControlEvents.bootstrapRemoteControlEvents();
const appUpdateService = new AppUpdateService({
app,
getMainWindow: () => App.mainWindow,
updater: autoUpdater,
});
AppUpdateEvents.bootstrapAppUpdateEvents(appUpdateService);
void appUpdateService.checkForUpdatesOnStartup();
// Set main window for downloads and reset stale downloads
@@ -422,6 +422,35 @@ describe('SettingsComponent', () => {
expect(component.appUpdateStatus()).toEqual(retriedStatus);
});
it('waits for the desktop app update bridge method before loading status', async () => {
const retriedStatus: ElectronBridgeAppUpdateStatus = {
...DEFAULT_APP_UPDATE_STATUS,
status: ELECTRON_BRIDGE_APP_UPDATE_STATUSES.Unsupported,
supportedSelfUpdate: false,
};
await fixture.whenStable();
delete (window.electron as Partial<typeof window.electron>)
.getAppUpdateStatus;
let retryCount = 0;
const getStatus = jest.fn().mockResolvedValue(retriedStatus);
jest.spyOn(
privateApi(component),
'waitForAppUpdateStatusRetry'
).mockImplementation(async () => {
retryCount += 1;
if (retryCount === 1) {
window.electron.getAppUpdateStatus = getStatus;
}
});
await privateApi(component).loadAppUpdateStatus();
expect(retryCount).toBe(1);
expect(getStatus).toHaveBeenCalledTimes(1);
expect(component.appUpdateStatus()).toEqual(retriedStatus);
});
it('forwards app update actions to the desktop bridge', async () => {
await component.checkForAppUpdate();
await component.downloadAppUpdate();
+25 -13
View File
@@ -85,8 +85,8 @@ import {
import { SettingsSnackbarService } from './settings-snackbar.service';
import { AppUpdateReleaseNotesDialogComponent } from './app-update-release-notes-dialog.component';
const APP_UPDATE_STATUS_LOAD_ATTEMPTS = 5;
const APP_UPDATE_STATUS_LOAD_RETRY_DELAY_MS = 100;
const APP_UPDATE_STATUS_LOAD_ATTEMPTS = 60;
const APP_UPDATE_STATUS_LOAD_RETRY_DELAY_MS = 250;
@Component({
templateUrl: './settings.component.html',
@@ -320,28 +320,40 @@ export class SettingsComponent implements OnInit, OnDestroy {
}
private async loadAppUpdateStatus(): Promise<void> {
if (!this.isDesktop || !window.electron?.getAppUpdateStatus) {
if (!this.isDesktop) {
return;
}
let lastError: unknown;
for (
let attempt = 1;
attempt <= APP_UPDATE_STATUS_LOAD_ATTEMPTS;
attempt += 1
) {
try {
this.appUpdateStatus.set(
await window.electron.getAppUpdateStatus()
const electron = window.electron;
if (electron?.getAppUpdateStatus) {
try {
this.appUpdateStatus.set(
await electron.getAppUpdateStatus()
);
return;
} catch (error) {
lastError = error;
}
}
if (attempt === APP_UPDATE_STATUS_LOAD_ATTEMPTS) {
console.warn(
'Failed to load app update status:',
lastError ??
new Error('Desktop app update bridge is unavailable')
);
return;
} catch (error) {
if (attempt === APP_UPDATE_STATUS_LOAD_ATTEMPTS) {
console.warn('Failed to load app update status:', error);
return;
}
await this.waitForAppUpdateStatusRetry();
}
await this.waitForAppUpdateStatusRetry();
}
}