diff --git a/addons/website_event_track_online/static/src/js/service_worker.js b/addons/website_event_track_online/static/src/js/service_worker.js index cde0399cd5a..ac1a8249a15 100644 --- a/addons/website_event_track_online/static/src/js/service_worker.js +++ b/addons/website_event_track_online/static/src/js/service_worker.js @@ -151,7 +151,10 @@ const isCachableRequest = (request) => isGET(request) || isCachableURL(request.u const matchCache = async (request) => { if (isGET(request)) { const cache = await caches.open(cacheName); - return await cache.match(request); + const response = await cache.match(request.url); + if (response) { + return deserializeResponse(await serializeResponse(response.clone())); + } } if (isCachableURL(request.url)) { const serializedRequest = await serializeRequest(request); @@ -217,6 +220,50 @@ const processPendingRequests = async () => { } }; +/** + * Add given urls to the Cache, skipping the ones already present + * @param {Array} urls + */ +const prefetchUrls = async (urls = []) => { + const cache = await caches.open(cacheName); + let urlsToCache = new Set(urls); + for (let url of urlsToCache) { + (await cache.match(url)) ? urlsToCache.delete(url) : undefined; + } + return cache.addAll([...urlsToCache]); +}; + +/** + * Handle the message sent to the Worker (using the postMessage() method). + * The message is defined by the name of the action to perform and its associated parameters (optional). + * + * Actions: + * - prefetch-pages: add {Array} urls with their "alternative url" to the Cache (if not already present). + * - prefetch-assets: add {Array} urls to the Cache (if not already present). + * + * @param {Object} data + * @param {string} data.action action's name + * @param {*} data.* action's parameter(s) + * @returns {Promise} + */ +const processMessage = (data) => { + const { action } = data; + switch (action) { + case "prefetch-pages": + const { urls: pagesUrls } = data; + // To prevent redirection cached by the browser (cf. 301 Permanently Moved) from breaking the offline cache + // we also add alternative urls with the following rule: + // * if original url has a trailing "/", adds url with striped trailing "/" + // * if original url doesn't end with "/", adds url without the trailing "/" + const maybeRedirectedUrl = pagesUrls.map((url) => (url.endsWith("/") ? url.slice(0, -1) : url)); + return prefetchUrls([...pagesUrls, ...maybeRedirectedUrl]); + case "prefetch-assets": + const { urls: assetsUrls } = data; + return prefetchUrls(assetsUrls); + } + throw new Error(`Action '${action}' not found.`); +}; + self.addEventListener("fetch", (event) => { event.respondWith(processFetchEvent(event)); }); @@ -227,3 +274,7 @@ self.addEventListener("sync", (event) => { event.waitUntil(processPendingRequests()); } }); + +self.addEventListener("message", (event) => { + event.waitUntil(processMessage(event.data)); +}); diff --git a/addons/website_event_track_online/static/src/js/website_event_pwa_widget.js b/addons/website_event_track_online/static/src/js/website_event_pwa_widget.js index c9bc42a8ccd..77d12e4bb1b 100644 --- a/addons/website_event_track_online/static/src/js/website_event_pwa_widget.js +++ b/addons/website_event_track_online/static/src/js/website_event_pwa_widget.js @@ -51,7 +51,7 @@ odoo.define("website_event_track_online.website_event_pwa_widget", function (req start: function () { var superProm = this._super.apply(this, arguments); window.addEventListener("beforeinstallprompt", this.beforeInstallPromptHandler); - return superProm.then(this._registerServiceWorker.bind(this)); + return superProm.then(this._registerServiceWorker.bind(this)).then(this._prefetch.bind(this)); }, /** @@ -80,6 +80,15 @@ odoo.define("website_event_track_online.website_event_pwa_widget", function (req return ""; }, + /** + * Returns the PWA's scope + * @private + * @returns {String} + */ + _getScope: function () { + return this._getLangPrefix() + "/event"; + }, + /** * @private */ @@ -88,6 +97,36 @@ odoo.define("website_event_track_online.website_event_pwa_widget", function (req $(".o_livechat_button").css("bottom", "0"); }, + /** + * Parse the current page for first-level children pages and ask the ServiceWorker + * to already fetch them to populate the cache. + * @private + */ + _prefetch: function () { + if (!("serviceWorker" in navigator)) { + return; + } + var assetsUrls = Array.from(document.querySelectorAll('link[rel="stylesheet"], script[src]')).map(function (el) { + return el.href || el.src; + }); + navigator.serviceWorker.ready.then(function (registration) { + registration.active.postMessage({ + action: "prefetch-assets", + urls: assetsUrls, + }); + }); + var currentPageUrl = window.location.href; + var childrenPagesUrls = Array.from(document.querySelectorAll('a[href^="' + this._getScope() + '/"]')).map(function (el) { + return el.href; + }); + navigator.serviceWorker.ready.then(function (registration) { + registration.active.postMessage({ + action: "prefetch-pages", + urls: childrenPagesUrls.concat(currentPageUrl), + }); + }); + }, + /** * * @private @@ -96,9 +135,9 @@ odoo.define("website_event_track_online.website_event_pwa_widget", function (req if (!("serviceWorker" in navigator)) { return; } - var langPrefix = this._getLangPrefix(); - navigator.serviceWorker - .register(langPrefix + "/event/service-worker.js", { scope: langPrefix + "/event" }) + var scope = this._getScope(); + return navigator.serviceWorker + .register(scope + "/service-worker.js", { scope: scope }) .then(function (registration) { console.info("Registration successful, scope is:", registration.scope); })