mirror of
https://github.com/fluxerapp/fluxer
synced 2026-10-17 21:15:32 +09:00
feat(desktop): prefer the bundled renderer, own update prompts (#3341)
This commit is contained in:
1 parent
7a5b9a0ded
commit
5072488f1e
33 files changed
+2197
-113
No files matched your search
@@ -44,11 +44,12 @@ import {
|
||||
ModuleUnreachableLaunchDecision,
|
||||
ModuleUpdatePlanner,
|
||||
ModuleUpdaterBlockReason,
|
||||
type ServedModuleSelection,
|
||||
} from '@electron/main/ModuleUpdatePlanner';
|
||||
import {compareModuleVersions, type ModuleVersion, parseModuleVersion} from '@electron/main/ModuleVersion';
|
||||
import {resolveDesktopPackageOrigin} from '@electron/main/ShellDownloadFormats';
|
||||
import type {DesktopModuleEnsureResult} from '@fluxer/desktop_ipc/src/ModuleContract';
|
||||
import {DesktopModuleEnsureStatus} from '@fluxer/desktop_ipc/src/ModuleContract';
|
||||
import {DESKTOP_RENDERER_MODULE_NAME, DesktopModuleEnsureStatus} from '@fluxer/desktop_ipc/src/ModuleContract';
|
||||
|
||||
const MODULE_UPDATER_BACKOFF_BASE_MS = 1000;
|
||||
const MODULE_UPDATER_BACKOFF_CAP_MS = 30000;
|
||||
@@ -82,6 +83,7 @@ const ModuleUpdaterReportType = Object.freeze({
|
||||
STORAGE_ERROR: 'storage-error',
|
||||
ROLLBACK: 'rollback',
|
||||
ROLLBACK_FLOOR_BYPASS: 'rollback-floor-bypass',
|
||||
BUNDLED_RENDERER_PREFERRED: 'bundled-renderer-preferred',
|
||||
BLOCKED: 'blocked',
|
||||
} as const);
|
||||
|
||||
@@ -97,6 +99,7 @@ const MODULE_UPDATER_REPORT_SAMPLE_RATES: Readonly<Record<ModuleUpdaterReportTyp
|
||||
[ModuleUpdaterReportType.STORAGE_ERROR]: 1,
|
||||
[ModuleUpdaterReportType.ROLLBACK]: 1,
|
||||
[ModuleUpdaterReportType.ROLLBACK_FLOOR_BYPASS]: 1,
|
||||
[ModuleUpdaterReportType.BUNDLED_RENDERER_PREFERRED]: 1,
|
||||
[ModuleUpdaterReportType.BLOCKED]: 1,
|
||||
});
|
||||
|
||||
@@ -198,6 +201,17 @@ function isUpdateServerUnreachable(error: unknown): boolean {
|
||||
return false;
|
||||
}
|
||||
|
||||
function parseBundledRendererVersion(source: string | null): ModuleVersion | null {
|
||||
if (source == null) {
|
||||
return null;
|
||||
}
|
||||
try {
|
||||
return parseModuleVersion(source, 'bundled renderer version');
|
||||
} catch {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
function defaultSleep(ms: number): Promise<void> {
|
||||
return new Promise((resolve) => {
|
||||
setTimeout(resolve, ms);
|
||||
@@ -263,6 +277,8 @@ interface ModuleUpdaterOptions {
|
||||
readonly arch?: NodeJS.Architecture;
|
||||
readonly packageOrigin?: string;
|
||||
readonly hasOfflineRenderer?: boolean;
|
||||
readonly bundledRendererVersion?: string | null;
|
||||
readonly preferUnversionedBundle?: boolean;
|
||||
readonly forceStartupUpdate?: boolean;
|
||||
readonly selfUpdateShellFirst?: (latestVersion: string, requiredSecurityUpdate: boolean) => Promise<void>;
|
||||
readonly onState?: (state: ModuleUpdaterSplashState) => void;
|
||||
@@ -319,7 +335,13 @@ export class ModuleUpdater {
|
||||
this.manifestFeed = {releaseChannel, platform: this.platform, arch};
|
||||
const packageOrigin = (options.packageOrigin ?? resolveDesktopPackageOrigin()).replace(/\/+$/u, '');
|
||||
this.packageOrigin = packageOrigin;
|
||||
this.planner = new ModuleUpdatePlanner(this.store, this.shellVersion, this.hasOfflineRenderer);
|
||||
this.planner = new ModuleUpdatePlanner(
|
||||
this.store,
|
||||
this.shellVersion,
|
||||
this.hasOfflineRenderer,
|
||||
parseBundledRendererVersion(options.bundledRendererVersion ?? null),
|
||||
options.preferUnversionedBundle ?? false,
|
||||
);
|
||||
this.onState = options.onState ?? (() => {});
|
||||
this.reporter = options.report ?? (() => {});
|
||||
this.sleep = options.sleep ?? defaultSleep;
|
||||
@@ -395,7 +417,12 @@ export class ModuleUpdater {
|
||||
}
|
||||
|
||||
public async run(): Promise<ModuleUpdaterOutcome> {
|
||||
const attempt = await this.store.beginBootAttempt();
|
||||
let attempt = await this.store.beginBootAttempt({bundled: this.planner.bundledModules()});
|
||||
const retiredRenderer = await this.retireRendererCoveredByBundle();
|
||||
const retiredMissing = await this.retireUninstalledModules();
|
||||
if (retiredRenderer || retiredMissing) {
|
||||
attempt = {...attempt, committed: this.store.getCommitted()};
|
||||
}
|
||||
await this.restoreCachedUpdatePolicy();
|
||||
if (attempt.rolledBack) {
|
||||
this.report({
|
||||
@@ -562,6 +589,37 @@ export class ModuleUpdater {
|
||||
return {module: moduleName, status: DesktopModuleEnsureStatus.INSTALLED};
|
||||
}
|
||||
|
||||
private async retireRendererCoveredByBundle(): Promise<boolean> {
|
||||
const stale = await this.planner.staleRendererModule();
|
||||
if (stale == null) {
|
||||
return false;
|
||||
}
|
||||
if (!(await this.store.retireModule(DESKTOP_RENDERER_MODULE_NAME))) {
|
||||
return false;
|
||||
}
|
||||
this.report({
|
||||
type: ModuleUpdaterReportType.BUNDLED_RENDERER_PREFERRED,
|
||||
module: DESKTOP_RENDERER_MODULE_NAME,
|
||||
message: `retired installed renderer ${stale} because the renderer bundled with this shell is at least as new`,
|
||||
});
|
||||
return true;
|
||||
}
|
||||
|
||||
private async retireUninstalledModules(): Promise<boolean> {
|
||||
let retired = false;
|
||||
for (const moduleName of await this.planner.uninstalledCommittedModules()) {
|
||||
if (await this.store.retireModule(moduleName)) {
|
||||
retired = true;
|
||||
this.report({
|
||||
type: ModuleUpdaterReportType.PACKAGE_MISSING,
|
||||
module: moduleName,
|
||||
message: 'dropped a committed module whose files are gone, the bundled renderer does not need it to launch',
|
||||
});
|
||||
}
|
||||
}
|
||||
return retired;
|
||||
}
|
||||
|
||||
private async resolveManifestForEnsure(): Promise<DesktopModuleUpdateManifest> {
|
||||
return this.observeManifest(await this.manifestRepository.resolveMemoizedDocument());
|
||||
}
|
||||
@@ -571,7 +629,8 @@ export class ModuleUpdater {
|
||||
if (
|
||||
this.selfUpdateShellFirst == null ||
|
||||
compareModuleVersions(manifest.shell.latestVersion, this.shellVersion) <= 0 ||
|
||||
this.shellFirstAttempted.has(latestVersion)
|
||||
this.shellFirstAttempted.has(latestVersion) ||
|
||||
(this.platform === 'linux' && !this.isSecurityUpdateRequired())
|
||||
) {
|
||||
return false;
|
||||
}
|
||||
@@ -623,7 +682,7 @@ export class ModuleUpdater {
|
||||
const wanted: Array<ModulePlanItem> = [];
|
||||
for (const moduleName of Array.from(names).sort()) {
|
||||
const entry = manifest.modules[moduleName];
|
||||
if (entry == null || !planner.isShellCompatible(entry)) {
|
||||
if (entry == null || !planner.isShellCompatible(entry) || planner.bundleCoversModule(moduleName, manifest)) {
|
||||
continue;
|
||||
}
|
||||
if (committed[moduleName] === entry.sha256 && (await planner.isInstalled(moduleName, entry.sha256))) {
|
||||
@@ -668,6 +727,21 @@ export class ModuleUpdater {
|
||||
return {installed, unresolved};
|
||||
}
|
||||
|
||||
public async selectServedModules(modules: Readonly<Record<string, string>>): Promise<ServedModuleSelection> {
|
||||
return await this.planner.selectServedModules(modules);
|
||||
}
|
||||
|
||||
public async revertLaunch(attempt: ModuleLaunchAttempt): Promise<Readonly<Record<string, string>> | null> {
|
||||
const reverted = await this.store.revertLaunchAttempt(attempt, this.planner.bundledModules());
|
||||
if (reverted != null) {
|
||||
this.report({
|
||||
type: ModuleUpdaterReportType.ROLLBACK,
|
||||
message: 'reverted an in place module update whose renderer never confirmed it started',
|
||||
});
|
||||
}
|
||||
return reverted;
|
||||
}
|
||||
|
||||
public async markLaunchSucceeded(attempt: ModuleLaunchAttempt): Promise<void> {
|
||||
if (!(await this.store.markLaunchAttemptSucceeded(attempt))) {
|
||||
return;
|
||||
@@ -946,6 +1020,9 @@ export class ModuleUpdater {
|
||||
}
|
||||
} else {
|
||||
for (const moduleName of blockingModuleNames(manifest)) {
|
||||
if (this.planner.bundleCoversModule(moduleName, manifest)) {
|
||||
continue;
|
||||
}
|
||||
floor[moduleName] = manifest.modules[moduleName].sha256;
|
||||
}
|
||||
}
|
||||
|
||||
Reference in new issue
Block a user