// SPDX-License-Identifier: AGPL-3.0-or-later import {createRequire} from 'node:module'; import {WINDOWS_APP_USER_MODEL_ID, WINDOWS_TOAST_ACTIVATOR_CLSID} from '@electron/common/DesktopIdentity'; import {type ModulePollPlan, nextModulePollDelay, resolveModulePollPlan} from '@electron/common/ModulePollPlan'; import { decideModuleSystemDisableRequest, hasOfflineRenderer, ModuleSystemDisableDecision, ModuleSystemLaunchDecision, resolveModuleSystemLaunch, } from '@electron/common/ModuleSystem'; import {armOpenUrlForwarding, observeRendererLaunchConfirmed} from '@electron/main/ModuleBootHandoff'; import {watchModuleRendererReload} from '@electron/main/ModuleRendererReload'; import type {ModuleLaunchAttempt, ModuleStore as ModuleStoreInstance} from '@electron/main/ModuleStore'; import {offerPendingModuleUpdate} from '@electron/main/ModuleUpdateGate'; import type { ModuleUpdater as ModuleUpdaterInstance, ModuleUpdaterOutcome, ModuleUpdaterSplashState, ModuleUpdaterStatus, } from '@electron/main/ModuleUpdater'; import type {SplashState, SplashStatus} from '@electron/main/SplashWindow'; import {repairWindowsShortcuts} from '@electron/main/WindowsShortcuts'; import {type BrowserWindow, app as electronApp} from 'electron'; declare const __FLUXER_MAIN_APP_OUTPUT_FILE__: string; const requireModule = createRequire(import.meta.url); if (process.platform === 'win32') { const {VelopackApp} = requireModule('velopack') as typeof import('velopack'); VelopackApp.build() .onAfterInstallFastCallback(() => { repairWindowsShortcuts(); }) .onAfterUpdateFastCallback(() => { repairWindowsShortcuts(); }) .onFirstRun(() => { repairWindowsShortcuts(); }) .onRestarted(() => { repairWindowsShortcuts(); }) .run(); repairWindowsShortcuts(); electronApp.setToastActivatorCLSID(WINDOWS_TOAST_ACTIVATOR_CLSID); electronApp.setAppUserModelId(WINDOWS_APP_USER_MODEL_ID); } armOpenUrlForwarding(); const MAIN_PROCESS_ENTRY_URL = new URL(`./${__FLUXER_MAIN_APP_OUTPUT_FILE__}`, import.meta.url).href; const DEV_MODULE_POLL_INTERVAL_ENV = 'FLUXER_DEV_MODULE_POLL_MS'; function readDevModulePollInterval(): number | null { const raw = Number.parseInt(process.env[DEV_MODULE_POLL_INTERVAL_ENV] ?? '', 10); return Number.isSafeInteger(raw) && raw > 0 ? raw : null; } const HAS_OFFLINE_RENDERER = hasOfflineRenderer(import.meta.url); const MODULE_SYSTEM_LAUNCH = resolveModuleSystemLaunch({hasOfflineRenderer: HAS_OFFLINE_RENDERER}); const MODULE_LAUNCH_PERMIT = Symbol('fluxer.desktop.moduleLaunchPermit'); const UPDATE_SERVER_UNREACHABLE_MESSAGE = "Fluxer couldn't reach its update server to download app files. Check your internet connection, proxy or firewall. Fluxer keeps retrying."; interface ModuleLaunchPermit { readonly [MODULE_LAUNCH_PERMIT]: true; readonly unreachable: boolean; readonly rolledBack: boolean; readonly belowFloor: ReadonlyArray; readonly launchAttempt: ModuleLaunchAttempt; } type BootstrapLogger = typeof import('@electron/common/Logger').Logger; const SPLASH_STATUS_BY_UPDATER_STATUS: Readonly> = Object.freeze({ checking: 'checking-for-updates', downloading: 'downloading-updates', installing: 'installing-updates', verifying: 'verifying', 'retry-wait': 'update-failure', 'blocked-update-required': 'blocked-update-required', 'blocked-shell-update': 'blocked-shell-update', launching: 'launching', 'unreachable-launch': 'unreachable-launch', }); function toSplashState(state: ModuleUpdaterSplashState): SplashState { return { status: state.stalled ? 'download-stalled' : SPLASH_STATUS_BY_UPDATER_STATUS[state.status], requiredSecurityUpdate: state.requiredSecurityUpdate, current: state.current, total: state.total, progress: state.progress, seconds: state.seconds, receivedBytes: state.receivedBytes, totalBytes: state.totalBytes, bytesPerSecond: state.bytesPerSecond, message: state.detail, }; } function unhandledModuleUpdaterOutcome(outcome: never): never { throw new Error(`Unhandled module updater outcome: ${JSON.stringify(outcome)}`); } async function launchMainApp(permit: ModuleLaunchPermit, logger: BootstrapLogger): Promise { logger.info('Module convergence granted a launch permit', { unreachable: permit.unreachable, rolledBack: permit.rolledBack, belowFloor: permit.belowFloor, }); if (permit.belowFloor.length > 0) { logger.warn( 'Launching a module set the persisted floor forbids, because the rollback path deliberately outranks the forced update', {modules: permit.belowFloor}, ); } await import(MAIN_PROCESS_ENTRY_URL); } function armModulePoll( plan: ModulePollPlan, updater: ModuleUpdaterInstance, refreshModuleRoots: (modules: Readonly>) => Promise, logger: BootstrapLogger, ): void { let mainWindow: BrowserWindow | null = null; let inFlight = false; let failing = false; void import('@electron/main/ModuleBootHandoff').then(({observeMainWindow, onMainWindowReady}) => { observeMainWindow((window) => { mainWindow = window; }); onMainWindowReady(() => { logger.info('Polling the module manifest', { intervalMs: plan.intervalMs, jitterRatio: plan.jitterRatio, }); const schedule = (): void => { const timer = setTimeout(tick, nextModulePollDelay(plan)); timer.unref(); }; const tick = (): void => { const window = mainWindow; if (inFlight || window == null || window.isDestroyed()) { schedule(); return; } inFlight = true; void updater .refreshInstalledModules() .then(async (refresh) => { if (failing) { failing = false; logger.info('The module poll recovered'); } if (refresh.status !== 'activated') { return; } logger.info('A new module set landed, offering it to the renderer', {modules: refresh.modules}); try { if (window.isDestroyed()) { throw new Error('the main window closed before the activated module set could be offered'); } const releasePendingLaunch = (): void => { updater.abandonPendingLaunch(refresh.launchAttempt); }; const reloadRenderer = (): void => { if (window.isDestroyed()) { logger.warn('The main window closed before the activated module set could reload'); releasePendingLaunch(); return; } const webContents = window.webContents; const markReloadSucceeded = (): void => { void updater.markLaunchSucceeded(refresh.launchAttempt).catch((error: unknown) => { logger.error('Failed to record a successful module reload', error); }); }; void refreshModuleRoots(refresh.launchAttempt.committed).then( () => { if (window.isDestroyed()) { logger.warn('The main window closed before the activated module set could reload'); releasePendingLaunch(); return; } const stopWatchingReload = watchModuleRendererReload({ target: webContents, observeLaunchConfirmed: observeRendererLaunchConfirmed, onLoaded: markReloadSucceeded, onAbandoned: (reason) => { logger.warn('The renderer never confirmed the module reload, releasing the pending launch', { reason, }); releasePendingLaunch(); }, }); try { webContents.reloadIgnoringCache(); } catch (error) { stopWatchingReload(); releasePendingLaunch(); logger.error('The activated module set could not reload the renderer', error); } }, (error: unknown) => { releasePendingLaunch(); logger.error('The activated module set could not be served to the renderer', error); }, ); }; offerPendingModuleUpdate({ update: {modules: refresh.modules}, apply: reloadRenderer, discard: releasePendingLaunch, }); } catch (error) { updater.abandonPendingLaunch(refresh.launchAttempt); throw error; } }) .catch((error: unknown) => { if (!failing) { failing = true; logger.warn('The module poll failed, staying quiet until it recovers', error); } }) .finally(() => { inFlight = false; schedule(); }); }; if (process.platform === 'linux') { tick(); } else { schedule(); } }); }); } async function refuseUnsupportedBuild(reason: string): Promise { const {app} = await import('electron'); const {createChildLogger} = await import('@electron/common/Logger'); const {onSplashQuit, openSplashWindow, setSplashState, SplashAction} = await import('@electron/main/SplashWindow'); const logger = createChildLogger('Bootstrap'); logger.error('This build cannot start a renderer, refusing to launch', {reason}); process.stderr.write(`Fluxer cannot start: ${reason}\n`); await app.whenReady(); openSplashWindow(); setSplashState({status: 'blocked-unsupported-build', action: SplashAction.QUIT}); onSplashQuit(() => { app.quit(); }); return new Promise(() => {}); } async function runModuleBootstrap(): Promise { const {app, clipboard, session, shell} = await import('electron'); const {BUILD_CHANNEL} = await import('@electron/common/BuildChannel'); const {createChildLogger} = await import('@electron/common/Logger'); const {loadDesktopConfig} = await import('@electron/common/DesktopConfig'); const {appendWindowsGpuDriverWorkaroundSwitches} = await import('@electron/main/ChromiumRuntime'); const {applyPreReadyChromiumConfiguration} = await import('@electron/main/PreReadyChromium'); const {configureUserDataPath} = await import('@electron/common/UserDataPath'); const {armNativeProbeCache} = await import('@electron/main/NativeProbeCache'); const {isStartMinimizedLaunch} = await import('@electron/main/AutostartLaunch'); const {relaunchStableLaunchPath} = await import('@electron/main/LinuxLaunchPath'); const {getDesktopLocalAppProtocol} = await import('@electron/main/LocalAppProtocol'); const { armSecondInstanceForwarding, onMainWindowCreated, onMainWindowReady, setCommittedModuleFiles, setOnDemandModuleInstaller, setSecondInstanceSink, } = await import('@electron/main/ModuleBootHandoff'); const {instanceTurnedModulesOff} = await import('@electron/main/InstanceModulePreference'); const {createOnDemandModuleInstaller} = await import('@electron/main/ModuleOnDemand'); const {getModuleStoreRoot, ModuleStore} = await import('@electron/main/ModuleStore'); const {ModuleUpdater} = await import('@electron/main/ModuleUpdater'); const {moduleNetworkFetch} = await import('@electron/main/ModuleNetworkFetch'); const {resolveDesktopPackageOrigin} = await import('@electron/main/ShellDownloadFormats'); const {LocalNetworkSplashHint} = await import('@electron/main/LocalNetworkSplashHint'); const {resolveShellUpdatePlan, ShellUpdateCapability} = await import('@electron/main/ShellUpdateCapability'); const {armBlockedShellUpdate} = await import('@electron/main/ShellUpdateSplash'); const {UpdateServerRetry} = await import('@electron/main/UpdateServerRetry'); const {buildSplashDiagnosticsText} = await import('@electron/main/SplashDiagnostics'); const { closeSplashWindow, focusSplashWindow, markSplashLaunching, onSplashCopyDiagnostics, onSplashNetworkOnline, onSplashOpenLogs, onSplashQuit, onSplashRetry, openSplashWindow, releaseSplashAffordance, setSplashState, SplashAction, } = await import('@electron/main/SplashWindow'); const logger = createChildLogger('Bootstrap'); if (MODULE_SYSTEM_LAUNCH.kind === ModuleSystemLaunchDecision.ENABLED_WITH_IGNORED_DISABLE_REQUEST) { logger.warn( 'Ignoring the --fluxer-no-module-system override because this build carries no offline renderer, so turning the module system off would leave nothing to render', ); } const userDataConfig = configureUserDataPath(); armNativeProbeCache(userDataConfig.base); if (!app.requestSingleInstanceLock()) { logger.info('Another instance already holds the single instance lock, forwarding this launch and quitting'); app.quit(); return; } armSecondInstanceForwarding(); const instancePreference = decideModuleSystemDisableRequest( instanceTurnedModulesOff(userDataConfig.base), HAS_OFFLINE_RENDERER, ); switch (instancePreference.kind) { case ModuleSystemDisableDecision.NOT_REQUESTED: break; case ModuleSystemDisableDecision.IGNORED_WITHOUT_OFFLINE_RENDERER: logger.warn( 'Ignoring the instance desktop module preference because this build carries no offline renderer, so turning the module system off would leave nothing to render', ); break; case ModuleSystemDisableDecision.HONOURED: logger.warn( 'The instance turned the desktop module system off on an earlier boot, launching the offline renderer instead', ); await import(MAIN_PROCESS_ENTRY_URL); return; } loadDesktopConfig(userDataConfig.base); applyPreReadyChromiumConfiguration(userDataConfig.channel, process.argv); try { getDesktopLocalAppProtocol().registerSchemes(); } catch (error) { logger.error('Failed to register the local app scheme privileges', error); app.exit(1); process.exit(1); } await appendWindowsGpuDriverWorkaroundSwitches(); await app.whenReady(); if (isStartMinimizedLaunch()) { logger.info('Launched at login with start minimized, converging the modules without the splash'); } else { openSplashWindow(); } setSplashState({status: 'checking-for-updates'}); onSplashQuit(() => { logger.info('The splash requested a quit'); app.quit(); }); onSplashRetry(() => { logger.info('The splash requested a retry, relaunching'); relaunchStableLaunchPath(); app.exit(0); }); const updateServerRetry = new UpdateServerRetry(); onSplashNetworkOnline(() => { logger.info('The splash reported the network is back'); updateServerRetry.networkReturned(); }); const splashOpenedAt = Date.now(); let diagnosticsSource: { readonly store: ModuleStoreInstance; readonly updater: ModuleUpdaterInstance; } | null = null; const resolveLogsPath = (): string | null => { try { return app.getPath('logs'); } catch { return null; } }; const collectSplashDiagnostics = async (): Promise => { const updaterDiagnostics = diagnosticsSource?.updater.diagnostics() ?? null; const packageOrigin = updaterDiagnostics?.packageOrigin ?? resolveDesktopPackageOrigin(); let proxyRoute: string | null = null; try { proxyRoute = await session.defaultSession.resolveProxy(packageOrigin); } catch (error) { logger.warn('Failed to resolve the proxy route for diagnostics', error); } const state = updaterDiagnostics?.state ?? null; return buildSplashDiagnosticsText({ generatedAt: Date.now(), splashOpenedAt, appVersion: app.getVersion(), channel: BUILD_CHANNEL, platform: process.platform, arch: process.arch, osVersion: process.getSystemVersion(), electronVersion: process.versions.electron ?? 'unknown', logsPath: resolveLogsPath(), userDataPath: userDataConfig.base, packageOrigin, proxyRoute, splashStatus: state == null ? null : (toSplashState(state).status ?? null), updaterStatus: state?.status ?? null, pendingModule: state?.moduleName ?? null, receivedBytes: state?.receivedBytes ?? null, totalBytes: state?.totalBytes ?? null, bytesPerSecond: state?.bytesPerSecond ?? null, committed: diagnosticsSource?.store.getCommitted() ?? {}, lastError: updaterDiagnostics?.lastError ?? null, lastErrorAt: updaterDiagnostics?.lastErrorAt ?? null, }); }; onSplashOpenLogs(() => { const logsPath = resolveLogsPath(); if (logsPath == null) return; void shell.openPath(logsPath).then((failure) => { if (failure.length > 0) { logger.warn('Failed to open the logs folder from the splash', {failure}); } }); }); onSplashCopyDiagnostics(() => { void collectSplashDiagnostics() .then((text) => { clipboard.writeText(text); logger.info('Copied updater diagnostics from the splash'); }) .catch((error: unknown) => { logger.error('Failed to copy updater diagnostics', error); }); }); const showBlockedSplash = (status: SplashStatus, message: string | null = null): void => { openSplashWindow(); setSplashState({status, action: SplashAction.RETRY, message}); setSecondInstanceSink(focusSplashWindow); }; const blockUntilRelaunch = (status: SplashStatus, message: string | null = null): Promise => { showBlockedSplash(status, message); return new Promise(() => {}); }; try { let store: ModuleStoreInstance; try { store = await ModuleStore.open({ root: getModuleStoreRoot(userDataConfig.base), shellVersion: app.getVersion(), releaseChannel: BUILD_CHANNEL, }); } catch (error) { logger.error('Failed to open the module store', error); await blockUntilRelaunch('blocked-update-required'); return; } const localNetworkHint = new LocalNetworkSplashHint({ apiBaseUrl: resolveDesktopPackageOrigin(), platform: process.platform, onHint: () => { logger.info('The module manifest host is on the local network and the fetch is still pending'); setSplashState({status: 'waiting-local-network'}); }, }); const updater = new ModuleUpdater({ store, shellVersion: app.getVersion(), hasOfflineRenderer: HAS_OFFLINE_RENDERER, fetch: moduleNetworkFetch, sleep: updateServerRetry.sleep, onState: (state) => { localNetworkHint.armWhileChecking(state.status === 'checking'); if (updateServerRetry.holding) { if (!updateServerRetry.endsHold(state.status)) { return; } releaseSplashAffordance(); } setSplashState(toSplashState(state)); }, report: (report) => { logger.warn('Module updater report', { type: report.type, module: report.module, message: report.message, sampleRate: report.sampleRate, error: report.error, }); }, }); diagnosticsSource = {store, updater}; const runModuleUpdateAttempt = async (): Promise => { let outcome: ModuleUpdaterOutcome; try { outcome = await updater.run(); } catch (error) { logger.error('The module updater failed', error); return await blockUntilRelaunch( updater.getLastState().requiredSecurityUpdate ? 'blocked-security-update-required' : 'blocked-update-required', ); } switch (outcome.status) { case 'launching': return { [MODULE_LAUNCH_PERMIT]: true, unreachable: false, rolledBack: outcome.rolledBack, belowFloor: outcome.belowFloor, launchAttempt: outcome.launchAttempt, }; case 'unreachable-launch': return { [MODULE_LAUNCH_PERMIT]: true, unreachable: true, rolledBack: false, belowFloor: [], launchAttempt: outcome.launchAttempt, }; case 'blocked-shell-update': { const plan = resolveShellUpdatePlan(); logger.warn('A shell update is required before the modules can converge', { latestVersion: outcome.latestVersion, minimumVersion: outcome.minimumVersion, capability: plan.capability, }); if (plan.capability === ShellUpdateCapability.SELF_UPDATE) { const {runShellSelfUpdate} = await import('@electron/main/ShellSelfUpdate'); const failure = await runShellSelfUpdate(plan, { onDownloading: (progress) => { setSplashState({ status: 'shell-update-downloading', requiredSecurityUpdate: outcome.requiredSecurityUpdate, progress, }); }, onRestarting: () => { setSplashState({ status: 'shell-update-restarting', requiredSecurityUpdate: outcome.requiredSecurityUpdate, }); }, }); logger.error('The shell self update failed, falling back to a manual download', failure); } openSplashWindow(); armBlockedShellUpdate(plan, outcome.latestVersion, outcome.requiredSecurityUpdate); setSecondInstanceSink(focusSplashWindow); return await new Promise(() => {}); } case 'blocked-update-required': logger.warn('The module update is blocked', { reason: outcome.reason, modules: outcome.modules, message: outcome.message, updateServerUnreachable: outcome.updateServerUnreachable, }); if (outcome.updateServerUnreachable) { showBlockedSplash( outcome.requiredSecurityUpdate ? 'blocked-security-update-required' : 'blocked-update-unreachable', UPDATE_SERVER_UNREACHABLE_MESSAGE, ); await updateServerRetry.holdUntilNextAttempt(); logger.info('Retrying the update server'); return null; } return await blockUntilRelaunch( outcome.requiredSecurityUpdate ? 'blocked-security-update-required' : 'blocked-update-required', ); default: return unhandledModuleUpdaterOutcome(outcome); } }; const runModuleUpdateLoop = async (): Promise => { for (;;) { const permit = await runModuleUpdateAttempt(); if (permit !== null) { return permit; } } }; let servedModules: Readonly> = {}; let servedModulesUpdate: Promise = Promise.resolve(); const serveModules = (resolveModules: () => Readonly>): Promise => { const update = async (): Promise => { const modules = resolveModules(); setCommittedModuleFiles(store.storeRoot, await store.buildModuleIndex(modules)); servedModules = modules; }; const started = servedModulesUpdate.then(update, update); servedModulesUpdate = started.catch(() => undefined); return started; }; const refreshModuleRoots = (modules: Readonly>): Promise => serveModules(() => modules); const serveOnDemandModules = (): Promise => serveModules(() => { const added = Object.entries(store.getCommitted()).filter(([name]) => servedModules[name] === undefined); return {...servedModules, ...Object.fromEntries(added)}; }); const armMainWindowHandoff = ( updaterInstance: ModuleUpdaterInstance, launchAttempt: ModuleLaunchAttempt, onLaunchSettled: () => void, ): void => { onMainWindowReady(() => { closeSplashWindow(); }); const stopObservingLaunch = observeRendererLaunchConfirmed(() => { stopObservingLaunch(); void updaterInstance .markLaunchSucceeded(launchAttempt) .catch((error: unknown) => { logger.error('Failed to record a successful module launch', error); }) .finally(onLaunchSettled); }); onMainWindowCreated((window) => { if (window == null || window.isDestroyed()) { closeSplashWindow(); return; } window.once('closed', closeSplashWindow); }); }; const permit = await runModuleUpdateLoop(); localNetworkHint.disarm(); await refreshModuleRoots(permit.launchAttempt.committed); setOnDemandModuleInstaller( createOnDemandModuleInstaller({ ensure: (moduleName) => updater.ensureModule(moduleName), refresh: serveOnDemandModules, onFailure: (moduleName, error) => { logger.error('Failed to install a module on demand', {module: moduleName, error}); }, }), ); markSplashLaunching(); armMainWindowHandoff(updater, permit.launchAttempt, () => { armModulePoll(resolveModulePollPlan(readDevModulePollInterval()), updater, refreshModuleRoots, logger); }); app.once('before-quit', () => { closeSplashWindow(); }); await launchMainApp(permit, logger); } catch (error) { logger.error('The module bootstrap failed unexpectedly', error); await blockUntilRelaunch('blocked-update-required'); } } switch (MODULE_SYSTEM_LAUNCH.kind) { case ModuleSystemLaunchDecision.ENABLED: case ModuleSystemLaunchDecision.ENABLED_WITH_IGNORED_DISABLE_REQUEST: void runModuleBootstrap().catch((error: unknown) => { process.stderr.write(`The module bootstrap rejected before it could report: ${String(error)}\n`); process.exit(1); }); break; case ModuleSystemLaunchDecision.DISABLED_BY_BUILD: case ModuleSystemLaunchDecision.DISABLED_BY_REQUEST: if (HAS_OFFLINE_RENDERER) { await import(MAIN_PROCESS_ENTRY_URL); } else { void refuseUnsupportedBuild('the module system is turned off and this build carries no offline renderer').catch( (error: unknown) => { process.stderr.write(`The module bootstrap rejected before it could report: ${String(error)}\n`); process.exit(1); }, ); } break; }