feat(desktop): self-update appimages in place (#2843)

This commit is contained in:
Hampus authored and GitHub committed 2026-09-19 19:06:53 +02:00
1 parent 98a42f612b
commit c4f0b2ece0
20 files changed
+2210 -57

No files matched your search

+12 -2
View File
@@ -696,10 +696,14 @@ class Updater {
} finally {
this.transition({type: 'manualDownload.finished'});
}
await this.downloadManualNativeUpdateOrOpen(currentOption.url, currentOption.suggestedName);
await this.downloadManualNativeUpdateOrOpen(currentOption.url, currentOption.suggestedName, currentOption.sha256);
}
private async downloadManualNativeUpdateOrOpen(url: string, suggestedName?: string): Promise<void> {
private async downloadManualNativeUpdateOrOpen(
url: string,
suggestedName?: string,
sha256?: string | null,
): Promise<void> {
if (this.manualNativeDownloadInFlight) {
return;
}
@@ -708,10 +712,16 @@ class Updater {
const outcome = await downloadWithNative({
url,
suggestedName: suggestedName ?? this.getManualUpdateSuggestedName(url),
sha256,
});
if (outcome === 'success' || outcome === 'canceled') {
return;
}
if (outcome === 'checksum-mismatch') {
logger.error('Native manual update download did not match its published checksum', {url});
pushDesktopUpdateDownloadFailedModal();
return;
}
logger.warn('Native manual update download unavailable; opening update URL externally', {outcome});
await openExternalUrl(url);
} finally {
@@ -5,5 +5,5 @@ export function shouldShowNativeDesktopUpdateInApp(platform: string | null | und
}
export function shouldShowNativeDesktopUpdateDownloadProgress(platform: string | null | undefined): boolean {
return platform === 'win32';
return platform === 'win32' || platform === 'linux';
}
@@ -212,6 +212,7 @@ export interface DownloadFileOptions {
export interface DownloadFileResult {
success: boolean;
canceled?: boolean;
checksumMismatch?: boolean;
path?: string;
error?: string;
}
@@ -357,7 +358,7 @@ export interface ElectronAPI {
requestInputMonitoringPermission?: () => Promise<InputMonitoringPermissionStatus>;
getScreenRecordingPermissionStatus?: () => Promise<InputMonitoringPermissionStatus>;
requestScreenRecordingPermission?: () => Promise<InputMonitoringPermissionStatus>;
downloadFile: (url: string, defaultPath: string) => Promise<DownloadFileResult>;
downloadFile: (url: string, defaultPath: string, sha256?: string | null) => Promise<DownloadFileResult>;
toggleDevTools: () => void;
showNotification: (options: NotificationOptions) => Promise<NotificationResult>;
shouldPlayNotificationSound?: () => Promise<boolean>;
@@ -273,21 +273,27 @@ export function attachExternalLinkInterceptor() {
};
}
export type NativeDownloadOutcome = 'success' | 'canceled' | 'failed' | 'unavailable';
export type NativeDownloadOutcome = 'success' | 'canceled' | 'checksum-mismatch' | 'failed' | 'unavailable';
export async function downloadWithNative(options: {
url: string;
suggestedName?: string;
title?: string;
sha256?: string | null;
}): Promise<NativeDownloadOutcome> {
const electronApi = getElectronAPI();
if (!electronApi) {
return 'unavailable';
}
try {
const result = await electronApi.downloadFile(options.url, options.suggestedName ?? 'download');
const result = await electronApi.downloadFile(
options.url,
options.suggestedName ?? 'download',
options.sha256 ?? null,
);
if (result.success) return 'success';
if (result.canceled) return 'canceled';
if (result.checksumMismatch) return 'checksum-mismatch';
return 'failed';
} catch (error) {
logger.error(' Native download failed, falling back to browser', error);
+2 -1
View File
@@ -283,6 +283,7 @@ export interface UpdaterDownloadOption {
export interface DownloadResult {
success: boolean;
canceled?: boolean;
checksumMismatch?: boolean;
path?: string;
error?: string;
}
@@ -349,7 +350,7 @@ export interface ElectronAPI {
platform: 'darwin' | 'win32' | 'linux' | string;
buildChannel: 'stable' | 'canary';
openExternal(url: string): Promise<void>;
downloadFile(url: string, suggestedName: string): Promise<DownloadResult>;
downloadFile(url: string, suggestedName: string, sha256?: string | null): Promise<DownloadResult>;
onUpdaterEvent(callback: (event: UpdaterEvent) => void): () => void;
updaterCheck(context: 'user' | 'background'): Promise<void>;
updaterDownload(context: 'user' | 'background'): Promise<void>;