Skip to content

Commit

Permalink
[IMP] website_event_track_online: PWA adds prefetch for offline use
Browse files Browse the repository at this point in the history
This commit adds the prefetch of the current page's children (cf. under
the same scope) to be available for offline use.

For performance reason, only the first-level children are added to the
cache to avoid a huge amount of requests and/or resources used by simply
loading the event website's pages. Also, for the same reason, this
process is delegated to the ServiceWorker to avoid cluttering the Main
Thread.

Note: due to some security restriction introduced in the Fetch spec,
some redirects may prevent offline-requests from being properly
fulfilled from the cache. See references for further details.

References:
- whatwg/fetch#763
- GoogleChromeLabs/sw-precache#220
- https://fetch.spec.whatwg.org/#concept-request-redirect-mode

closes #56159

X-original-commit: bc9a306
Signed-off-by: Adrien Dieudonné (adr) <adr@odoo.com>
Signed-off-by: Pierre Paridans <pparidans@users.noreply.github.com>
  • Loading branch information
pparidans committed Aug 19, 2020
1 parent d1779ff commit 334bb2d
Show file tree
Hide file tree
Showing 2 changed files with 95 additions and 5 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down Expand Up @@ -217,6 +220,50 @@ const processPendingRequests = async () => {
}
};

/**
* Add given urls to the Cache, skipping the ones already present
* @param {Array<string>} 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));
});
Expand All @@ -227,3 +274,7 @@ self.addEventListener("sync", (event) => {
event.waitUntil(processPendingRequests());
}
});

self.addEventListener("message", (event) => {
event.waitUntil(processMessage(event.data));
});
Original file line number Diff line number Diff line change
Expand Up @@ -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));
},

/**
Expand Down Expand Up @@ -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
*/
Expand All @@ -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
Expand All @@ -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);
})
Expand Down

0 comments on commit 334bb2d

Please sign in to comment.