diff --git a/apps/server/AliasVault.Client/nginx.conf b/apps/server/AliasVault.Client/nginx.conf index bc4c4ca10..f240c5fb9 100644 --- a/apps/server/AliasVault.Client/nginx.conf +++ b/apps/server/AliasVault.Client/nginx.conf @@ -33,8 +33,10 @@ http { try_files $uri =404; } - # Never cache index.html and appsettings.json + # Never cache index.html, appsettings.json, and Blazor framework bootstrap files # These files must always be fresh to ensure users get the latest version + # The _framework/*.js and *.json files are critical for Blazor initialization and must not be cached + # to prevent issues when upgrading .NET versions (e.g., .NET 9 -> .NET 10) location ~* ^/(index\.html|appsettings\.json)$ { root /usr/share/nginx/html; expires -1; @@ -43,6 +45,16 @@ http { try_files $uri =404; } + # Never cache Blazor framework JavaScript and JSON files + # These files change between .NET versions and caching them causes boot failures + location ~* ^/_framework/.*\.(js|json)$ { + root /usr/share/nginx/html; + expires -1; + add_header Cache-Control "no-store, no-cache, must-revalidate, proxy-revalidate, max-age=0"; + add_header Pragma "no-cache"; + try_files $uri =404; + } + # Default location for all other files location / { root /usr/share/nginx/html; diff --git a/apps/server/AliasVault.Client/wwwroot/index.template.html b/apps/server/AliasVault.Client/wwwroot/index.template.html index 0f3e61ef1..95bee17b4 100644 --- a/apps/server/AliasVault.Client/wwwroot/index.template.html +++ b/apps/server/AliasVault.Client/wwwroot/index.template.html @@ -31,7 +31,7 @@ - + @@ -284,20 +284,72 @@ const errorMessageElement = document.getElementById('error-message'); const showError = (message) => { - errorMessageElement.textContent = message; - errorMessageElement.classList.remove('hidden'); - document.querySelector('.loading-progress-text').classList.add('hidden'); - document.querySelector('svg.animate-spin').classList.add('hidden'); + if (errorMessageElement) { + errorMessageElement.textContent = message; + errorMessageElement.classList.remove('hidden'); + } + const progressText = document.querySelector('.loading-progress-text'); + if (progressText) progressText.classList.add('hidden'); }; + + // Detect Blazor boot failures (e.g., after .NET version upgrades) + // This handles transitions between major .NET versions where boot format changes + let bootFailureHandled = false; + async function handleBlazorBootFailure() { + if (bootFailureHandled) return; + bootFailureHandled = true; + + console.info('AliasVault: Blazor boot failure detected. Refreshing cached files...'); + + try { + // Clear service worker caches + if ('caches' in window) { + const cacheKeys = await caches.keys(); + await Promise.all(cacheKeys.map(key => caches.delete(key))); + } + + // Unregister service workers + if ('serviceWorker' in navigator) { + const registrations = await navigator.serviceWorker.getRegistrations(); + await Promise.all(registrations.map(r => r.unregister())); + } + + // Force browser to fetch fresh versions of critical files + // cache: 'reload' bypasses HTTP cache and updates it with fresh content + const criticalFiles = [ + '/', + '/_framework/blazor.webassembly.js', + '/_framework/dotnet.js' + ]; + await Promise.all(criticalFiles.map(url => + fetch(url, { cache: 'reload' }).catch(() => {}) + )); + } catch (e) { + console.error('AliasVault: Cache refresh failed:', e); + } + + // Navigate to cache-busted URL to force complete reload with fresh resources + const url = new URL(window.location.href); + url.searchParams.set('_r', Date.now().toString()); + window.location.replace(url.toString()); + } + window.addEventListener('error', function(event) { if (event.error && event.error.message && event.error.message.includes('WebAssembly')) { showError(getTranslation('errors.webAssemblyError')); } }); window.addEventListener('unhandledrejection', function(event) { - if (event.reason && event.reason.message && event.reason.message.includes('WebAssembly')) { + const errorMessage = event.reason?.message || String(event.reason) || ''; + + if (errorMessage.includes('WebAssembly')) { showError(getTranslation('errors.webAssemblyError')); } + // Detect Blazor boot failures + else if (errorMessage.includes('blazor.boot.json') || errorMessage.includes('Failed to load config file') || errorMessage.includes('Failed to start platform')) { + event.preventDefault(); + handleBlazorBootFailure(); + } }); window.addEventListener('load', manageLoadingScreen); window.blazorCulture = {