diff --git a/src/Uno.Wasm.Bootstrap/Embedded/service-worker-classic.js b/src/Uno.Wasm.Bootstrap/Embedded/service-worker-classic.js new file mode 100644 index 000000000..0b1e37f39 --- /dev/null +++ b/src/Uno.Wasm.Bootstrap/Embedded/service-worker-classic.js @@ -0,0 +1,87 @@ +// As of Dec 2024, Firefox does not support ES6 modules in service workers, so we need to use importScripts +// https://developer.mozilla.org/en-US/docs/Web/API/ServiceWorker#browser_compatibility +importScripts("$(REMOTE_WEBAPP_PATH)$(REMOTE_BASE_PATH)/uno-config-script.js"); + +if (config.environmentVariables["UNO_BOOTSTRAP_DEBUGGER_ENABLED"] !== "True") { + console.debug("[ServiceWorker] Initializing"); + let uno_enable_tracing = config.uno_enable_tracing; + + self.addEventListener('install', function (e) { + console.debug('[ServiceWorker] Installing offline worker'); + e.waitUntil( + caches.open('$(CACHE_KEY)').then(async function (cache) { + console.debug('[ServiceWorker] Caching app binaries and content'); + + // Add files one by one to avoid failed downloads to prevent the + // worker to fail installing. + for (var i = 0; i < config.offline_files.length; i++) { + try { + if (uno_enable_tracing) { + console.debug(`[ServiceWorker] cache ${key}`); + } + + await cache.add(config.offline_files[i]); + } + catch (e) { + console.debug(`[ServiceWorker] Failed to fetch ${config.offline_files[i]}`); + } + } + + // Add the runtime's own files to the cache. We cannot use the + // existing cached content from the runtime as the keys contain a + // hash we cannot reliably compute. + var c = await fetch("$(REMOTE_WEBAPP_PATH)_framework/blazor.boot.json"); + const monoConfigResources = (await c.json()).resources; + + var entries = { + ...(monoConfigResources.coreAssembly || {}) + , ...(monoConfigResources.assembly || {}) + , ...(monoConfigResources.lazyAssembly || {}) + , ...(monoConfigResources.jsModuleWorker || {}) + , ...(monoConfigResources.jsModuleGlobalization || {}) + , ...(monoConfigResources.jsModuleNative || {}) + , ...(monoConfigResources.jsModuleRuntime || {}) + , ...(monoConfigResources.wasmNative || {}) + , ...(monoConfigResources.icu || {}) + , ...(monoConfigResources.coreAssembly || {}) + }; + + for (var key in entries) { + var uri = `$(REMOTE_WEBAPP_PATH)_framework/${key}`; + + if (uno_enable_tracing) { + console.debug(`[ServiceWorker] cache ${uri}`); + } + + await cache.add(uri); + } + }) + ); + }); + + self.addEventListener('activate', event => { + event.waitUntil(self.clients.claim()); + }); + + self.addEventListener('fetch', event => { + event.respondWith(async function () { + try { + // Network first mode to get fresh content every time, then fallback to + // cache content if needed. + return await fetch(event.request); + } catch (err) { + return caches.match(event.request).then(response => { + return response || fetch(event.request); + }); + } + }()); + }); +} +else { + // In development, always fetch from the network and do not enable offline support. + // This is because caching would make development more difficult (changes would not + // be reflected on the first load after each change). + // It also breaks the hot reload feature because VS's browserlink is not always able to + // inject its own framework in the served scripts and pages. + self.addEventListener('fetch', () => { }); +} diff --git a/src/Uno.Wasm.Bootstrap/Embedded/service-worker.js b/src/Uno.Wasm.Bootstrap/Embedded/service-worker.js index 0b1e37f39..98f1f8d6d 100644 --- a/src/Uno.Wasm.Bootstrap/Embedded/service-worker.js +++ b/src/Uno.Wasm.Bootstrap/Embedded/service-worker.js @@ -1,10 +1,9 @@ -// As of Dec 2024, Firefox does not support ES6 modules in service workers, so we need to use importScripts -// https://developer.mozilla.org/en-US/docs/Web/API/ServiceWorker#browser_compatibility -importScripts("$(REMOTE_WEBAPP_PATH)$(REMOTE_BASE_PATH)/uno-config-script.js"); +import { config as unoConfig } from "$(REMOTE_WEBAPP_PATH)$(REMOTE_BASE_PATH)/uno-config.js"; -if (config.environmentVariables["UNO_BOOTSTRAP_DEBUGGER_ENABLED"] !== "True") { + +if (unoConfig.environmentVariables["UNO_BOOTSTRAP_DEBUGGER_ENABLED"] !== "True") { console.debug("[ServiceWorker] Initializing"); - let uno_enable_tracing = config.uno_enable_tracing; + let uno_enable_tracing = unoConfig.uno_enable_tracing; self.addEventListener('install', function (e) { console.debug('[ServiceWorker] Installing offline worker'); @@ -14,16 +13,16 @@ if (config.environmentVariables["UNO_BOOTSTRAP_DEBUGGER_ENABLED"] !== "True") { // Add files one by one to avoid failed downloads to prevent the // worker to fail installing. - for (var i = 0; i < config.offline_files.length; i++) { + for (var i = 0; i < unoConfig.offline_files.length; i++) { try { if (uno_enable_tracing) { console.debug(`[ServiceWorker] cache ${key}`); } - await cache.add(config.offline_files[i]); + await cache.add(unoConfig.offline_files[i]); } catch (e) { - console.debug(`[ServiceWorker] Failed to fetch ${config.offline_files[i]}`); + console.debug(`[ServiceWorker] Failed to fetch ${unoConfig.offline_files[i]}`); } } diff --git a/src/Uno.Wasm.Bootstrap/ShellTask.cs b/src/Uno.Wasm.Bootstrap/ShellTask.cs index 7c5520048..2751b1240 100644 --- a/src/Uno.Wasm.Bootstrap/ShellTask.cs +++ b/src/Uno.Wasm.Bootstrap/ShellTask.cs @@ -151,6 +151,7 @@ public override bool Execute() RemoveDuplicateAssets(); GeneratePackageFolder(); BuildServiceWorker(); + BuildServiceWorkerClassic(); GenerateEmbeddedJs(); GenerateIndexHtml(); GenerateConfig(); @@ -311,6 +312,24 @@ private void BuildServiceWorker() CopyStreamToOutput("service-worker.js", memoryStream, DeployMode.Root); } + // Case for browsers that do not support modules for service workers: Firefox for example + private void BuildServiceWorkerClassic() + { + using var resourceStream = GetType().Assembly.GetManifestResourceStream("Uno.Wasm.Bootstrap.v0.Embedded.service-worker-classic.js"); + using var reader = new StreamReader(resourceStream); + + var worker = TouchServiceWorker(reader.ReadToEnd()); + var memoryStream = new MemoryStream(); + + using var writer = new StreamWriter(memoryStream, Encoding.UTF8); + writer.Write(worker); + writer.Flush(); + + memoryStream.Position = 0; + + CopyStreamToOutput("service-worker-classic.js", memoryStream, DeployMode.Root); + } + private void ExtractAdditionalJS() { BuildResourceSearchList(); @@ -534,7 +553,8 @@ private void GenerateConfig() .Where(d => !d.EndsWith("require.js") && !d.EndsWith("uno-bootstrap.js") - && !d.EndsWith("service-worker.js")) + && !d.EndsWith("service-worker.js") + && !d.EndsWith("service-worker-classic.js")) .Select(dep => BuildDependencyPath(dep, baseLookup))); var config = new StringBuilder(); @@ -636,7 +656,8 @@ private void GenerateConfigScript() .Where(d => !d.EndsWith("require.js") && !d.EndsWith("uno-bootstrap.js") - && !d.EndsWith("service-worker.js")) + && !d.EndsWith("service-worker.js") + && !d.EndsWith("service-worker-classic.js")) .Select(dep => BuildDependencyPath(dep, baseLookup))); var config = new StringBuilder(); diff --git a/src/Uno.Wasm.Bootstrap/ts/Uno/WebAssembly/Bootstrapper.ts b/src/Uno.Wasm.Bootstrap/ts/Uno/WebAssembly/Bootstrapper.ts index af2a3d3c5..7fdc549b7 100644 --- a/src/Uno.Wasm.Bootstrap/ts/Uno/WebAssembly/Bootstrapper.ts +++ b/src/Uno.Wasm.Bootstrap/ts/Uno/WebAssembly/Bootstrapper.ts @@ -259,7 +259,7 @@ namespace Uno.WebAssembly.Bootstrap { this._runMain(this._unoConfig.uno_main, []); - this.initializePWA(); + await this.initializePWA(); } catch (e) { console.error(e); @@ -510,7 +510,7 @@ namespace Uno.WebAssembly.Bootstrap { link.click(); } - private initializePWA() { + private async initializePWA(): Promise { if (typeof window === 'object' /* ENVIRONMENT_IS_WEB */) { @@ -522,15 +522,17 @@ namespace Uno.WebAssembly.Bootstrap { console.debug(`Registering service worker for ${_webAppBasePath}`); - navigator.serviceWorker - .register( - `${_webAppBasePath}service-worker.js`, { + try { + await navigator.serviceWorker.register(`${_webAppBasePath}service-worker.js`, { scope: _webAppBasePath, type: 'module' - }) - .then(function () { - console.debug('Service Worker Registered'); }); + console.debug('Service Worker Registered'); + } catch (e) { + console.debug('Service Worker registration failed. falling back to classic service worker', e); + + await navigator.serviceWorker.register(`${_webAppBasePath}service-worker-classic.js`, {scope: _webAppBasePath}); + } } } }