diff --git a/fluxer_app/src/features/app/state/Updater.ts b/fluxer_app/src/features/app/state/Updater.ts index 8c529c63e..68d0b8ab9 100644 --- a/fluxer_app/src/features/app/state/Updater.ts +++ b/fluxer_app/src/features/app/state/Updater.ts @@ -6,6 +6,8 @@ import { shouldShowNativeDesktopUpdateDownloadProgress, shouldShowNativeDesktopUpdateInApp, } from '@app/features/app/utils/UpdaterPlatformUtils'; +import {WORKER_NAVIGATION_CACHE_PREFIX} from '@app/features/platform/service_worker/WorkerCacheCleanup'; +import {getProtectedCacheStorage} from '@app/features/platform/state/ProtectedWebStorage'; import type {UpdaterContext, UpdaterDownloadOption, UpdaterEvent} from '@app/features/platform/types/Electron'; import {Logger} from '@app/features/platform/utils/AppLogger'; import {getClientInfo} from '@app/features/platform/utils/ClientInfo'; @@ -49,9 +51,10 @@ export const DOWNLOADING_UPDATE_DESCRIPTOR = msg({ }); const logger = new Logger('Updater'); -const CHECK_INTERVAL_MS = 30 * 60 * 1000; +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 VERSION_ENDPOINT = '/version.json'; const CURRENT_BUILD_VERSION = Config.PUBLIC_BUILD_VERSION ?? null; const ALLOWED_WEB_UPDATE_HOSTS = new Set([ @@ -61,6 +64,21 @@ const ALLOWED_WEB_UPDATE_HOSTS = new Set([ 'canary.fluxer.com', ]); +async function dropCachedAppShell(): Promise { + const browserCaches = getProtectedCacheStorage(); + if (!browserCaches) return; + try { + const cacheNames = await browserCaches.keys(); + await Promise.all( + cacheNames + .filter((cacheName) => cacheName.startsWith(WORKER_NAVIGATION_CACHE_PREFIX)) + .map((cacheName) => browserCaches.delete(cacheName)), + ); + } catch (error) { + logger.warn('Failed to drop the cached app shell before reloading', error); + } +} + function normalizeUpdaterContext(context: NativeUpdaterEvent['context']): UpdaterContext { switch (context) { case 'user': @@ -487,11 +505,9 @@ class Updater { shouldCheckNative ? this.checkNativeUpdate(checkContext) : Promise.resolve(null), this.checkWebUpdate(), ]); - this.transition({ - type: 'web.checked', - available: webResult?.available ?? false, - version: webResult?.version ?? null, - }); + if (webResult) { + this.transition({type: 'web.checked', available: webResult.available, version: webResult.version}); + } if (userInitiated && (!shouldCheckNative || (!this.isChecking && !this.nativeCheckFailed))) { this.showCurrentUpdateState(); } @@ -521,7 +537,7 @@ class Updater { private async checkWebUpdate(): Promise<{ available: boolean; version: string | null; - }> { + } | null> { if (!ALLOWED_WEB_UPDATE_HOSTS.has(window.location.host)) { return {available: false, version: null}; } @@ -529,24 +545,28 @@ class Updater { const response = await fetch(VERSION_ENDPOINT, { cache: 'no-store', headers: {'Cache-Control': 'no-cache'}, + signal: AbortSignal.timeout(WEB_CHECK_TIMEOUT_MS), }); if (!response.ok) { logger.debug('Version endpoint not available'); - return {available: false, version: null}; + return null; } const payload = (await response.json()) as { version?: string; buildVersion?: string; }; const version = payload.version ?? payload.buildVersion ?? null; - const updateAvailable = Boolean(version && CURRENT_BUILD_VERSION && version !== CURRENT_BUILD_VERSION); + if (!version) { + return null; + } + const updateAvailable = Boolean(CURRENT_BUILD_VERSION && version !== CURRENT_BUILD_VERSION); return { available: updateAvailable, version, }; } catch (error) { logger.debug('Failed to fetch version info silently:', error); - return {available: false, version: null}; + return null; } } @@ -586,6 +606,7 @@ class Updater { } if (this.updateInfo.web.available) { logger.info('Applying web update, reloading...'); + await dropCachedAppShell(); window.location.reload(); return; } diff --git a/fluxer_app/src/features/platform/service_worker/Worker.ts b/fluxer_app/src/features/platform/service_worker/Worker.ts index 17a704434..08c1855a7 100644 --- a/fluxer_app/src/features/platform/service_worker/Worker.ts +++ b/fluxer_app/src/features/platform/service_worker/Worker.ts @@ -13,7 +13,11 @@ import { precacheAssets, seedAppShell, } from '@app/features/platform/service_worker/WorkerAppShell'; -import {shouldDeleteWorkerCache, WORKER_CACHE_PREFIX} from '@app/features/platform/service_worker/WorkerCacheCleanup'; +import { + shouldDeleteWorkerCache, + WORKER_CACHE_PREFIX, + WORKER_NAVIGATION_CACHE_PREFIX, +} from '@app/features/platform/service_worker/WorkerCacheCleanup'; import {getWorkerFetchRoute} from '@app/features/platform/service_worker/WorkerFetchRouting'; import { getBadgeCount, @@ -45,7 +49,7 @@ const ensureServiceWorkerReady: Promise = Promise.resolve(); const SERVICE_WORKER_VERSION = typeof __FLUXER_SW_VERSION__ === 'string' ? __FLUXER_SW_VERSION__ : 'dev'; const PRECACHE_MANIFEST = typeof __FLUXER_PRECACHE_MANIFEST__ === 'undefined' ? [] : __FLUXER_PRECACHE_MANIFEST__; const PRECACHE_CACHE = `${WORKER_CACHE_PREFIX}-precache-${SERVICE_WORKER_VERSION}`; -const NAVIGATION_CACHE = `${WORKER_CACHE_PREFIX}-navigation-${SERVICE_WORKER_VERSION}`; +const NAVIGATION_CACHE = `${WORKER_NAVIGATION_CACHE_PREFIX}${SERVICE_WORKER_VERSION}`; const EXPECTED_CACHES = new Set([PRECACHE_CACHE, NAVIGATION_CACHE]); const NAVIGATION_NETWORK_TIMEOUT_MS = 650; const serviceWorkerCaches = self.caches; diff --git a/fluxer_app/src/features/platform/service_worker/WorkerCacheCleanup.ts b/fluxer_app/src/features/platform/service_worker/WorkerCacheCleanup.ts index d61e96354..04657c131 100644 --- a/fluxer_app/src/features/platform/service_worker/WorkerCacheCleanup.ts +++ b/fluxer_app/src/features/platform/service_worker/WorkerCacheCleanup.ts @@ -1,6 +1,7 @@ // SPDX-License-Identifier: AGPL-3.0-or-later export const WORKER_CACHE_PREFIX = 'fluxer'; +export const WORKER_NAVIGATION_CACHE_PREFIX = `${WORKER_CACHE_PREFIX}-navigation-`; export function shouldDeleteWorkerCache(cacheName: string, expectedCaches: ReadonlySet): boolean { return cacheName.startsWith(`${WORKER_CACHE_PREFIX}-`) && !expectedCaches.has(cacheName);