erp5_web_renderjs_ui: Replace appcache by a service worker
Chrome will deprecate appcache in the coming weeks. As appcache has been dropped on Firefox, this change will improve the speed on Firefox. The service worker only provide a cache for all ERP5JS gadgets. ERP5JS will still be usable on browser without service worker API. The list of files to cache is directly added inside the service worker code, in order to be able to skip the usage of the fetch method (which does not yet cover all browser HTTP usage). This list is configurable on the web site level. No need to change the service worker code directly. The service worker will be updated as soon as the web site modification date changes. End user does not need to reload its browser tab. Only browsing ERP5JS will trigger the service worker update. When a new update is found, ERP5JS will automatically reload itself if there is no risk of losing user data input. An empty appcache is kept, to allow migration of existing client to the worker automatically.
/*jslint indent: 2*/ | /*jslint indent: 2*/ | ||
/*global self, caches, fetch*/ | /*global self, caches, fetch, Promise, URL, location, JSON*/ | ||
(function (self, caches, fetch) { | (function (self, caches, fetch, Promise, URL, location, JSON) { | ||
"use strict"; | "use strict"; | ||
var CACHE_NAME = 'Thu, 12 July 2016 12:00:00 GMT', | var prefix = location.toString() + '_', | ||
// Files required to make this app work offline | CACHE_NAME = prefix + '${modification_date}', | ||
REQUIRED_FILES = [ | REQUIRED_FILES = JSON.parse('${required_url_list}'), | ||
'./', | required_url_list = [], | ||
'https://netdna.bootstrapcdn.com/font-awesome/4.2.0/css/font-awesome.min.css', | i, | ||
'https://netdna.bootstrapcdn.com/font-awesome/4.2.0/fonts/fontawesome-webfont.eot?v=4.2.0', | len = REQUIRED_FILES.length; | ||
'https://netdna.bootstrapcdn.com/font-awesome/4.2.0/fonts/fontawesome-webfont.eot?#iefix&v=4.2.0', | |||
'https://netdna.bootstrapcdn.com/font-awesome/4.2.0/fonts/fontawesome-webfont.woff?v=4.2.0', | for (i = 0; i < len; i += 1) { | ||
'https://netdna.bootstrapcdn.com/font-awesome/4.2.0/fonts/fontawesome-webfont.ttf?v=4.2.0', | required_url_list.push( | ||
'https://netdna.bootstrapcdn.com/font-awesome/4.2.0/fonts/fontawesome-webfont.svg?v=4.2.0#fontawesomeregular', | new URL(REQUIRED_FILES[i], location.toString()).toString() | ||
'URI.js', | ); | ||
'erp5_launcher.js', | } | ||
'gadget_erp5.css', | |||
'gadget_erp5_editor_panel.html', | |||
'gadget_erp5_editor_panel.js', | |||
'gadget_erp5_field_checkbox.html', | |||
'gadget_erp5_field_checkbox.js', | |||
'gadget_erp5_field_datetime.html', | |||
'gadget_erp5_field_datetime.js', | |||
'gadget_erp5_field_email.html', | |||
'gadget_erp5_field_email.js', | |||
'gadget_erp5_field_file.html', | |||
'gadget_erp5_field_file.js', | |||
'gadget_erp5_field_float.html', | |||
'gadget_erp5_field_float.js', | |||
'gadget_erp5_field_gadget.html', | |||
'gadget_erp5_field_gadget.js', | |||
'gadget_erp5_field_image.html', | |||
'gadget_erp5_field_image.js', | |||
'gadget_erp5_field_integer.html', | |||
'gadget_erp5_field_integer.js', | |||
'gadget_erp5_field_list.html', | |||
'gadget_erp5_field_list.js', | |||
'gadget_erp5_field_listbox.html', | |||
'gadget_erp5_field_listbox.js', | |||
'gadget_erp5_field_multicheckbox.html', | |||
'gadget_erp5_field_multicheckbox.js', | |||
'gadget_erp5_field_multilist.html', | |||
'gadget_erp5_field_multilist.js', | |||
'gadget_erp5_field_multirelationstring.html', | |||
'gadget_erp5_field_multirelationstring.js', | |||
'gadget_erp5_field_radio.html', | |||
'gadget_erp5_field_radio.js', | |||
'gadget_erp5_field_readonly.html', | |||
'gadget_erp5_field_readonly.js', | |||
'gadget_erp5_field_relationstring.html', | |||
'gadget_erp5_field_relationstring.js', | |||
'gadget_erp5_field_string.html', | |||
'gadget_erp5_field_string.js', | |||
'gadget_erp5_field_password.html', | |||
'gadget_erp5_field_password.js', | |||
'gadget_erp5_field_textarea.html', | |||
'gadget_erp5_field_textarea.js', | |||
'gadget_erp5_form.html', | |||
'gadget_erp5_form.js', | |||
'gadget_erp5_header.html', | |||
'gadget_erp5_header.js', | |||
'gadget_erp5_jio.html', | |||
'gadget_erp5_jio.js', | |||
'gadget_erp5_page_action.html', | |||
'gadget_erp5_page_action.js', | |||
'gadget_erp5_page_form.html', | |||
'gadget_erp5_page_form.js', | |||
'gadget_erp5_page_front.html', | |||
'gadget_erp5_page_front.js', | |||
'gadget_erp5_page_history.html', | |||
'gadget_erp5_page_history.js', | |||
'gadget_erp5_page_jump.html', | |||
'gadget_erp5_page_jump.js', | |||
'gadget_erp5_page_logout.html', | |||
'gadget_erp5_page_logout.js', | |||
'gadget_erp5_page_preference.html', | |||
'gadget_erp5_page_preference.js', | |||
'gadget_erp5_page_relation_search.html', | |||
'gadget_erp5_page_relation_search.js', | |||
'gadget_erp5_page_search.html', | |||
'gadget_erp5_page_search.js', | |||
'gadget_erp5_page_tab.html', | |||
'gadget_erp5_page_tab.js', | |||
'gadget_erp5_page_worklist.html', | |||
'gadget_erp5_page_worklist.js', | |||
'gadget_erp5_panel.html', | |||
'gadget_erp5_panel.js', | |||
'gadget_erp5_pt_form_dialog.html', | |||
'gadget_erp5_pt_form_dialog.js', | |||
'gadget_erp5_pt_form_list.html', | |||
'gadget_erp5_pt_form_list.js', | |||
'gadget_erp5_pt_form_view.html', | |||
'gadget_erp5_pt_form_view.js', | |||
'gadget_erp5_pt_form_view_editable.html', | |||
'gadget_erp5_pt_form_view_editable.js', | |||
'gadget_erp5_pt_report_view.html', | |||
'gadget_erp5_pt_report_view.js', | |||
'gadget_erp5_router.html', | |||
'gadget_erp5_router.js', | |||
'gadget_erp5_relation_input.html', | |||
'gadget_erp5_relation_input.js', | |||
'gadget_erp5_search_editor.html', | |||
'gadget_erp5_search_editor.js', | |||
'gadget_erp5_searchfield.html', | |||
'gadget_erp5_searchfield.js', | |||
'gadget_erp5_sort_editor.html', | |||
'gadget_erp5_sort_editor.js', | |||
'gadget_global.js', | |||
'gadget_erp5_global.js', | |||
'gadget_jio.html', | |||
'gadget_jio.js', | |||
'gadget_translation.html', | |||
'gadget_translation.js', | |||
'gadget_translation_data.js', | |||
'handlebars.js', | |||
'i18next.js', | |||
'jiodev.js', | |||
'renderjs.js', | |||
'rsvp.js' | |||
]; | |||
self.addEventListener('install', function (event) { | self.addEventListener('install', function (event) { | ||
// Perform install step: loading each required file into cache | // Perform install step: loading each required file into cache | ||
event.waitUntil( | event.waitUntil( | ||
caches.open(CACHE_NAME) | caches.open(CACHE_NAME) | ||
.then(function (cache) { | .then(function (cache) { | ||
// Add all offline dependencies to the cache | var promise = Promise.resolve(); | ||
return cache.addAll(REQUIRED_FILES); | |||
function append(url_to_cache) { | |||
promise = promise | |||
.then(function () { | |||
// Use cache.add because safari does not support cache.addAll. | |||
|
|||
return cache.add(url_to_cache); | |||
}); | |||
} | |||
len = required_url_list.length; | |||
for (i = 0; i < len; i += 1) { | |||
// Add all offline dependencies to the cache | |||
// One by one, to not hammer zopes | |||
append(required_url_list[i]); | |||
} | |||
return promise; | |||
}) | }) | ||
.then(function () { | .then(function () { | ||
// At this point everything has been cached | // When user accesses ERP5JS web site first time, service worker is | ||
// installed but it is not activated yet, service worker is activated | |||
// when the page is refreshed or when a new tab opens the site again. | |||
// If user does not refresh the page and continue to use the site, | |||
// user can't use cache, so everything becomes slow. We must avoid this | |||
// situation. | |||
// So, we want to activate the new service worker immediately if it was | |||
// the first one. | |||
return self.skipWaiting(); | return self.skipWaiting(); | ||
}) | }) | ||
.catch(function (error) { | |||
// Since we do not allow to override existing cache, if cache installation | |||
// failed, we need to delete the cache completely. | |||
caches.delete(CACHE_NAME); | |||
throw error; | |||
}) | |||
); | ); | ||
}); | }); | ||
self.addEventListener('fetch', function (event) { | self.addEventListener('fetch', function (event) { | ||
event.respondWith( | var url = new URL(event.request.url); | ||
caches.match(event.request) | url.hash = ''; | ||
if ((event.request.method !== 'GET') || | |||
(required_url_list.indexOf(url.toString()) === -1)) { | |||
// Try not to use the untrustable fetch function | |||
// It can only be skip synchronously | |||
return; | |||
} | |||
return event.respondWith( | |||
caches.open(CACHE_NAME) | |||
.then(function (cache) { | |||
// Don't give request object itself. Firefox's Cache Storage | |||
// does not work properly when VARY contains Accept-Language. | |||
// Give URL string instead, then cache.match works on both Firefox and Chrome. | |||
return cache.match(event.request.url); | |||
}) | |||
.then(function (response) { | .then(function (response) { | ||
// Cache hit - return the response from the cached version | // Cache hit - return the response from the cached version | ||
if (response) { | if (response) { | ||
return response; | return response; | ||
} | } | ||
// Not in cache - return the result from the live server | // Not in cache - return the result from the live server | ||
// `fetch` is essentially a "fallback" | // `fetch` is essentially a "fallback" | ||
return fetch(event.request); | return fetch(event.request); | ||
... | @@ -168,7 +105,8 @@ | ... | @@ -168,7 +105,8 @@ |
.filter(function (key) { | .filter(function (key) { | ||
// Filter by keys that don't start with the latest version prefix. | // Filter by keys that don't start with the latest version prefix. | ||
// return !key.startsWith(version); | // return !key.startsWith(version); | ||
return key !== CACHE_NAME; | return ((key !== CACHE_NAME) && | ||
key.startsWith(prefix)); | |||
}) | }) | ||
.map(function (key) { | .map(function (key) { | ||
/* Return a promise that's fulfilled | /* Return a promise that's fulfilled | ||
... | @@ -179,11 +117,21 @@ | ... | @@ -179,11 +117,21 @@ |
); | ); | ||
}) | }) | ||
.then(function () { | .then(function () { | ||
self.clients.claim(); | return self.clients.claim(); | ||
}) | |||
.then(function () { | |||
return self.clients.matchAll(); | |||
}) | |||
.then(function (client_list) { | |||
// Notify all clients that they can reload the page | |||
var j, | |||
client_len = client_list.length; | |||
for (j = 0; j < client_len; j += 1) { | |||
client_list[j].postMessage('claim'); | |||
} | |||
}) | }) | ||
); | ); | ||
}); | }); | ||
}(self, caches, fetch, Promise, URL, location, JSON)); | |||
\ No newline at end of file | |||
}(self, caches, fetch)); |