fix(desktop): shell-compatible module updates, no linux walls (#3452)

This commit is contained in:
Hampus authored and GitHub committed 2026-10-13 22:07:00 +02:00
1 parent f501d34179
commit 959b03b5f4
73 files changed
+4142 -4311

No files matched your search

+18 -109
View File
@@ -25,8 +25,7 @@ import {
type UpdaterMachineEvent,
type UpdaterMachineSnapshot,
} from '@app/features/updater/state/UpdaterStateMachine';
import {buildLinuxManualUpdateOptions} from '@app/features/updater/utils/LinuxManualUpdateOptions';
import type {UpdaterEvent as NativeUpdaterEvent, UpdaterDownloadOption} from '@app/types/electron.d';
import type {UpdaterEvent as NativeUpdaterEvent} from '@app/types/electron.d';
import {OFFICIAL_WEB_APP_HOSTS} from '@fluxer/instance_bootstrap/src/OfficialWebAppHosts';
import {makeAutoObservable, runInAction} from 'mobx';
@@ -41,7 +40,6 @@ type UpdaterEvent =
context: UpdaterContext;
version: string | null;
downloadUrl?: string;
downloadOptions?: Array<UpdaterDownloadOption>;
}
| {
type: 'not-available';
@@ -66,7 +64,6 @@ type UpdaterEvent =
const logger = new Logger('Updater');
const CHECK_INTERVAL_MS = 5 * 60 * 1000;
const MIN_CHECK_INTERVAL_MS = 60 * 1000;
const MANUAL_DOWNLOAD_REFRESH_TIMEOUT_MS = 5 * 1000;
const WEB_CHECK_TIMEOUT_MS = 15 * 1000;
const NATIVE_CHECK_TIMEOUT_MS = 30 * 1000;
const VERSION_ENDPOINT = '/version.json';
@@ -120,7 +117,6 @@ function normalizeUpdaterEvent(event: NativeUpdaterEvent): UpdaterEvent | null {
context,
version: event.version ?? null,
downloadUrl: event.downloadUrl,
downloadOptions: event.downloadOptions,
};
case 'not-available':
return {type: 'not-available', context};
@@ -151,7 +147,7 @@ class Updater {
private snapshot: UpdaterMachineSnapshot = createUpdaterMachineSnapshot();
currentVersion: string | null = null;
channel: string | null = null;
private desktopArch: string | null = null;
private flatpakAppId: string | null = null;
private isNative: boolean;
private backgroundCheckStarted = false;
private backgroundCheckInterval: number | null = null;
@@ -164,7 +160,6 @@ class Updater {
private olderShellUpdateDownloaded = false;
private olderShellInstallWhenDownloaded = false;
private desktopUpdateStarting = false;
private pendingManualDownloadRefreshes = 0;
private checkInProgress = false;
constructor() {
@@ -192,10 +187,6 @@ class Updater {
return this.snapshot.context.nativeManualDownloadUrl;
}
get nativeManualDownloadOptions(): ReadonlyArray<UpdaterDownloadOption> {
return this.snapshot.context.nativeManualDownloadOptions;
}
private get nativeCheckFailed(): boolean {
return this.snapshot.context.nativeCheckFailed;
}
@@ -260,11 +251,16 @@ class Updater {
private async bootstrapNative(): Promise<void> {
try {
const info = await getClientInfo();
const [info, desktopInfo] = await Promise.all([
getClientInfo(),
getElectronAPI()
?.getDesktopInfo()
.catch(() => null),
]);
runInAction(() => {
this.currentVersion = info.desktopVersion ?? null;
this.channel = info.desktopChannel ?? null;
this.desktopArch = info.desktopArch ?? info.arch ?? null;
this.flatpakAppId = desktopInfo?.flatpak === true ? (desktopInfo.flatpakAppId ?? null) : null;
});
} catch (error) {
logger.warn('Failed to read desktop info', error);
@@ -376,35 +372,22 @@ class Updater {
}
private handleNativeEvent(event: UpdaterEvent): void {
const isManualDownloadRefreshResult =
event.context === 'user' &&
this.pendingManualDownloadRefreshes > 0 &&
(event.type === 'available' ||
event.type === 'not-available' ||
event.type === 'error' ||
event.type === 'unsupported');
if (isManualDownloadRefreshResult) {
this.pendingManualDownloadRefreshes -= 1;
}
const isUserCheck = event.context === 'user' && !isManualDownloadRefreshResult;
const isUserCheck = event.context === 'user';
const shouldShowImmediateUserResult = isUserCheck && !this.checkInProgress;
switch (event.type) {
case 'checking':
this.transition({type: 'check.started'});
break;
case 'available': {
const manualDownloadOptions = this.resolveManualNativeDownloadOptions(event, event.downloadOptions ?? []);
case 'available':
this.transition({
type: 'native.available',
version: event.version ?? null,
downloadUrl: manualDownloadOptions[0]?.url ?? event.downloadUrl ?? null,
downloadOptions: manualDownloadOptions,
downloadUrl: getElectronAPI()?.platform === 'linux' ? DESKTOP_DOWNLOAD_URL : (event.downloadUrl ?? null),
});
if (shouldShowImmediateUserResult) {
this.showCurrentUpdateState();
}
break;
}
case 'not-available':
this.transition({type: 'native.notAvailable', now: Date.now()});
if (shouldShowImmediateUserResult) {
@@ -442,22 +425,6 @@ class Updater {
}
}
private resolveManualNativeDownloadOptions(
event: Extract<UpdaterEvent, {type: 'available'}>,
options: ReadonlyArray<UpdaterDownloadOption>,
): ReadonlyArray<UpdaterDownloadOption> {
if (getElectronAPI()?.platform !== 'linux') {
return options;
}
return buildLinuxManualUpdateOptions({
downloadUrl: event.downloadUrl ?? options[0]?.url ?? null,
channel: this.channel ?? Config.PUBLIC_RELEASE_CHANNEL,
arch: this.desktopArch,
version: event.version ?? null,
knownOptions: options,
});
}
private startBackgroundChecks(): void {
if (this.backgroundCheckStarted) return;
this.backgroundCheckStarted = true;
@@ -601,11 +568,7 @@ class Updater {
await this.startDesktopUpdate();
return;
}
if (this.isNative && this.nativeUnsupported?.reason === 'managed-package' && !this.updateInfo.web.available) {
showUpdaterModal((modals) => modals.pushUnsupportedUpdateModal('managed-package'));
return;
}
if (this.isNative && this.nativeManualUpdateAvailable && !this.nativeUnsupported) {
if (this.isNative && this.nativeManualUpdateAvailable) {
this.showManualNativeUpdateModal();
return;
}
@@ -617,15 +580,7 @@ class Updater {
}
if (this.isNative && this.updateInfo.native.available) {
logger.info('Native update is available but not installable in-app; opening desktop downloads...');
const url = this.nativeManualDownloadUrl ?? this.nativeUnsupported?.downloadUrl ?? null;
if (url) {
await openExternalUrl(url);
} else if (this.nativeUnsupported) {
const {reason, downloadUrl} = this.nativeUnsupported;
showUpdaterModal((modals) => modals.pushUnsupportedUpdateModal(reason, downloadUrl));
} else {
await openExternalUrl(DESKTOP_DOWNLOAD_URL);
}
await openExternalUrl(DESKTOP_DOWNLOAD_URL);
}
}
@@ -663,63 +618,17 @@ class Updater {
private showManualNativeUpdateModal(): void {
const version = this.updateInfo.native.version;
const options = this.nativeManualDownloadOptions;
if (options.length > 0) {
const currentVersion = this.currentVersion;
showUpdaterModal((modals) =>
modals.pushManualUpdateAvailableModal({
currentVersion,
version,
options,
onDownload: (option) => this.downloadManualNativeUpdateOption(option),
}),
);
if (getElectronAPI()?.platform === 'linux') {
const flatpakAppId = this.flatpakAppId;
showUpdaterModal((modals) => modals.pushManagedShellUpdateModal({version, flatpakAppId}));
return;
}
const url = this.nativeManualDownloadUrl ?? this.nativeUnsupported?.downloadUrl ?? DESKTOP_DOWNLOAD_URL;
const url = this.nativeManualDownloadUrl ?? DESKTOP_DOWNLOAD_URL;
showUpdaterModal((modals) =>
modals.pushUpdateAvailableModal(version, () => this.downloadManualNativeUpdateOrOpen(url)),
);
}
private async refreshManualNativeDownloadOption(option: UpdaterDownloadOption): Promise<UpdaterDownloadOption> {
if (this.checkInProgress) {
return option;
}
this.checkInProgress = true;
this.transition({type: 'check.started'});
let timeoutId: number | undefined;
const timedOut = new Promise<boolean>((resolve) => {
timeoutId = window.setTimeout(() => resolve(false), MANUAL_DOWNLOAD_REFRESH_TIMEOUT_MS);
});
try {
this.pendingManualDownloadRefreshes += 1;
const checked = await Promise.race([this.checkNativeUpdate('user'), timedOut]);
if (!checked) {
return option;
}
return this.nativeManualDownloadOptions.find((candidate) => candidate.format === option.format) ?? option;
} finally {
window.clearTimeout(timeoutId);
this.checkInProgress = false;
this.transition({type: 'check.finished', now: Date.now()});
}
}
private async downloadManualNativeUpdateOption(option: UpdaterDownloadOption): Promise<void> {
if (this.manualNativeDownloadInFlight) {
return;
}
this.transition({type: 'manualDownload.started'});
let currentOption = option;
try {
currentOption = await this.refreshManualNativeDownloadOption(option);
} finally {
this.transition({type: 'manualDownload.finished'});
}
await this.downloadManualNativeUpdateOrOpen(currentOption.url, currentOption.suggestedName, currentOption.sha256);
}
private async downloadManualNativeUpdateOrOpen(
url: string,
suggestedName?: string,