From 3c3e61975642197c756720b90be08df30f9f1782 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tobias=20Br=C3=A4utigam?= Date: Sat, 15 Apr 2017 13:42:37 +0200 Subject: [PATCH 1/6] enable caching via service worker --- config.json | 14 ++--- source/ServiceWorker.js | 101 +++++++++++++++++++++++++++++++++ source/class/cv/Application.js | 32 +++++++++++ source/class/cv/Config.js | 9 ++- 4 files changed, 145 insertions(+), 11 deletions(-) create mode 100644 source/ServiceWorker.js diff --git a/config.json b/config.json index 4ff6c4dd4cd..dede6191f6e 100644 --- a/config.json +++ b/config.json @@ -83,9 +83,7 @@ "${APPLICATION}.ui.structure.pure.*", "${APPLICATION}.transforms.*", "${APPLICATION}.plugins.*", - "${APPLICATION}.parser.*", - "${APPLICATION}.core.*", - "${QXTHEME}" + "${APPLICATION}.parser.*" ], "lint-check" : { @@ -95,9 +93,7 @@ "THREE", "sprintf", "replayLog", - "svg4everybody", - "EVENT_RECORDER", - "Favico" + "svg4everybody" ] }, @@ -111,9 +107,6 @@ }, "add-script" : [ - { - "uri" : "resource/libs/EventRecorder.js" - }, { "uri" : "resource/libs/sprintf.js" }, @@ -150,7 +143,8 @@ "version", "library_version.inc.php", "manifest.json", - "../node_modules/monaco-editor" + "../node_modules/monaco-editor", + "ServiceWorker.js" ] } }, diff --git a/source/ServiceWorker.js b/source/ServiceWorker.js new file mode 100644 index 00000000000..f7da320a4a4 --- /dev/null +++ b/source/ServiceWorker.js @@ -0,0 +1,101 @@ +/** + * ServiceWorker for the CometVisu + * + * @author Tobias Bräutigam + * @since (0.11.0) 2017 + */ + +var CACHE = "cv-cache-v1"; +var NO_CACHE_TEST = /.+\.php$/i; +var config = {}; + +self.addEventListener('message', function(event) { + var data = event.data; + + if (data.command === "configure") { + config = data.message; + } +}); + +self.addEventListener('fetch', function(ev) { + if (config.disableCache === true || !ev.request.url.startsWith(this.registration.scope)) { + return; + } + + if (!NO_CACHE_TEST.test(ev.request.url) && + (!ev.request.headers.pragma || ev.request.headers.pragma !== "no-cache") && + (!ev.request.headers['Cache-Control'] || ev.request.headers['Cache-Control'] !== "no-cache") + ) { + + ev.respondWith(fromCache(ev.request).then(function(response){ + // console.log(ev.request.url+" from cache"); + return response; + }).catch(function () { + // not cached -> do now + // console.log("caching " + ev.request.url); + return fetchAndUpdate(ev.request); + })); + + if (config.forceReload === true) { + // always update cache + ev.waitUntil(update(ev.request)); + } + } else { + ev.respondWith(fromNetwork(ev.request)); + } +}); + +function fromNetwork(request, timeout) { + return new Promise(function (resolve, reject) { + if (timeout) { + var timeoutId = setTimeout(reject, timeout); + } + + fetch(request).then(function (response) { + if (timeoutId) { + clearTimeout(timeoutId); + } + resolve(response); + }, reject); + }); +} + +/** + * Get response from cache + * @param request {Request} + * @return {Response} + */ +function fromCache(request) { + return caches.open(CACHE).then(function (cache) { + return cache.match(request).then(function (matching) { + return matching || Promise.reject('no-match'); + }); + }); +} + +/** + * Fetch request from network and update the cache + * @param request {Request} + * @return {Promise} + */ +function update(request) { + return caches.open(CACHE).then(function (cache) { + return fetch(request).then(function (response) { + return cache.put(request, response); + }); + }); +} + +/** + * Fetch request from network, update the cache and return the response + * @param request {Request} + * @return {Response} + */ +function fetchAndUpdate(request) { + return caches.open(CACHE).then(function (cache) { + return fetch(request).then(function (response) { + cache.put(request, response.clone()); + return response; + }); + }); +} \ No newline at end of file diff --git a/source/class/cv/Application.js b/source/class/cv/Application.js index 6a7b5087904..6d568467331 100644 --- a/source/class/cv/Application.js +++ b/source/class/cv/Application.js @@ -148,6 +148,8 @@ qx.Class.define("cv.Application", console.log(info); + this.registerServiceWorker(); + if (qx.core.Environment.get("qx.aspects")) { qx.dev.Profile.stop(); qx.dev.Profile.start(); @@ -564,6 +566,36 @@ qx.Class.define("cv.Application", } }, + /** + * Install the service-worker if possible + */ + registerServiceWorker: function() { + if (cv.Config.useServiceWorker === true) { + navigator.serviceWorker.register('ServiceWorker.js').then(function(reg) { + this.debug("ServiceWorker successfully registered for scope "+reg.scope); + + // configure service worker + var configMessage = { + "command": "configure", + "message": { + forceReload: cv.Config.forceReload, + disableCache: qx.core.Environment.get("qx.debug") + } + }; + + if (reg.active) { + reg.active.postMessage(configMessage); + } else { + navigator.serviceWorker.ready.then(function(ev) { + ev.active.postMessage(configMessage); + }); + } + }.bind(this)).catch(function(err) { + this.error("Error registering service-worker: ", err); + }.bind(this)); + } + }, + /** * Handle errors that occur during loading ot the config file * @param textStatus {String} error status diff --git a/source/class/cv/Config.js b/source/class/cv/Config.js index bc7100b2033..9d1db231cee 100644 --- a/source/class/cv/Config.js +++ b/source/class/cv/Config.js @@ -174,6 +174,11 @@ qx.Class.define('cv.Config', { */ configServer: null, + /** + * If the CometVisu can use service workers + */ + useServiceWorker: false, + /** * Get the structure that is related to this design * @param design {String?} name of the design @@ -331,5 +336,7 @@ qx.Class.define('cv.Config', { if (isNaN(cv.Config.use_maturity)) { cv.Config.use_maturity = statics.Maturity.release; // default to release } + + cv.Config.useServiceWorker = 'serviceWorker' in navigator && (req.protocol === "https" || req.host === "localhost"); } -}); \ No newline at end of file +}); From a4f4341785866ab8223b84fa95d42604a511a55c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tobias=20Br=C3=A4utigam?= Date: Sun, 16 Apr 2017 11:00:02 +0200 Subject: [PATCH 2/6] improve service worker: - forceReload is false by default now (but configs are always prevented from beeing cached by the browser) - forceReload updates the cached responses (need to reload twice to see the changed file) - disabled by default in source version (can be overridden by URL-parameter worker=true) --- source/ServiceWorker.js | 43 ++++++++++++++++++++++++++++------ source/class/cv/Application.js | 5 ++-- source/class/cv/Config.js | 9 +++++++ 3 files changed, 47 insertions(+), 10 deletions(-) diff --git a/source/ServiceWorker.js b/source/ServiceWorker.js index f7da320a4a4..4aab14ed028 100644 --- a/source/ServiceWorker.js +++ b/source/ServiceWorker.js @@ -7,18 +7,46 @@ var CACHE = "cv-cache-v1"; var NO_CACHE_TEST = /.+\.php$/i; +var CONFIG_TEST = /.+visu_config.*\.xml.*/i; var config = {}; self.addEventListener('message', function(event) { var data = event.data; + console.log(data); if (data.command === "configure") { config = data.message; } }); +self.addEventListener('install', function(event) { + // take over right now + console.log("install"); +}); + +// delete old caches after activation +self.addEventListener('activate', function(event) { + console.log("activate"); + var cacheWhitelist = [CACHE]; + event.waitUntil( + caches.keys().then(function(cacheNames) { + return Promise.all( + cacheNames.map(function(cacheName) { + if (cacheWhitelist.indexOf(cacheName) === -1) { + return caches.delete(cacheName); + } + }) + ); + }) + ); +}); + self.addEventListener('fetch', function(ev) { - if (config.disableCache === true || !ev.request.url.startsWith(this.registration.scope)) { + if (config.disableCache === true || + !ev.request.url.startsWith(this.registration.scope) || + CONFIG_TEST.test(ev.request.url) + ) { + // fallback to "normal" behaviour without serviceWorker -> sends HTTP request return; } @@ -29,17 +57,16 @@ self.addEventListener('fetch', function(ev) { ev.respondWith(fromCache(ev.request).then(function(response){ // console.log(ev.request.url+" from cache"); + if (config.forceReload === true) { + update(ev.request); + } + return response; }).catch(function () { // not cached -> do now // console.log("caching " + ev.request.url); return fetchAndUpdate(ev.request); })); - - if (config.forceReload === true) { - // always update cache - ev.waitUntil(update(ev.request)); - } } else { ev.respondWith(fromNetwork(ev.request)); } @@ -94,7 +121,9 @@ function update(request) { function fetchAndUpdate(request) { return caches.open(CACHE).then(function (cache) { return fetch(request).then(function (response) { - cache.put(request, response.clone()); + if (response.status < 400) { + cache.put(request, response.clone()); + } return response; }); }); diff --git a/source/class/cv/Application.js b/source/class/cv/Application.js index 6d568467331..6b204e76170 100644 --- a/source/class/cv/Application.js +++ b/source/class/cv/Application.js @@ -324,7 +324,7 @@ qx.Class.define("cv.Application", var ajaxRequest = new qx.io.request.Xhr(uri); ajaxRequest.set({ accept: "application/xml", - cache: !cv.Config.forceReload + cache: false }); ajaxRequest.setUserData("noDemo", true); ajaxRequest.addListenerOnce("success", function (e) { @@ -578,8 +578,7 @@ qx.Class.define("cv.Application", var configMessage = { "command": "configure", "message": { - forceReload: cv.Config.forceReload, - disableCache: qx.core.Environment.get("qx.debug") + forceReload: cv.Config.forceReload } }; diff --git a/source/class/cv/Config.js b/source/class/cv/Config.js index 9d1db231cee..683db6e105f 100644 --- a/source/class/cv/Config.js +++ b/source/class/cv/Config.js @@ -179,6 +179,8 @@ qx.Class.define('cv.Config', { */ useServiceWorker: false, + enableServiceWorkerCache : true, + /** * Get the structure that is related to this design * @param design {String?} name of the design @@ -338,5 +340,12 @@ qx.Class.define('cv.Config', { } cv.Config.useServiceWorker = 'serviceWorker' in navigator && (req.protocol === "https" || req.host === "localhost"); + + if (cv.Config.useServiceWorker) { + if (qx.core.Environment.get("qx.debug")) { + // disable service worker in dev environment unless the user wants it + cv.Config.useServiceWorker = req.queryKey.worker === "true"; + } + } } }); From 98b1a4be85fb37b58a68b82735f77a932bd54128 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tobias=20Br=C3=A4utigam?= Date: Sun, 16 Apr 2017 14:30:25 +0200 Subject: [PATCH 3/6] add documentation for worker parameter --- doc/manual/de/config/url-params.rst | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/doc/manual/de/config/url-params.rst b/doc/manual/de/config/url-params.rst index ac86f82ff4d..62f9a25fd1f 100644 --- a/doc/manual/de/config/url-params.rst +++ b/doc/manual/de/config/url-params.rst @@ -217,3 +217,17 @@ In der Entwicklerversion sind diese standardmäßig eingeschaltet in einem Relea Default: false im Release, true in Entwicklerversion Options: true (log=true), false (log=false) + + +.. _worker: + +*worker* - ServiceWorker Cache in der Entwicklerversion einschalten + +In der Entwicklerversion ist der ServiceWorker zum Caching der Dateien abgeschaltet, damit man Änderungen +während des Entwickelns beim neu Laden direkt testen kann. Mit diesem URL-Parameter kann der ServiceWorker +trotzdem eingeschaltet werden + +.. code:: + + Default: false (worker=false) + Options: true (worker=true) [nur in Entwicklungsversion, in einem Release hat dieser Parameter eine Funktion] From 7fe13a9cce8e87e9ce4a1431a7905cf58a852b7d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tobias=20Br=C3=A4utigam?= Date: Sat, 22 Apr 2017 16:43:10 +0200 Subject: [PATCH 4/6] async updating cache via queue --- source/ServiceWorker.js | 25 +++++++++++++++++++++---- 1 file changed, 21 insertions(+), 4 deletions(-) diff --git a/source/ServiceWorker.js b/source/ServiceWorker.js index 4aab14ed028..81006f5006d 100644 --- a/source/ServiceWorker.js +++ b/source/ServiceWorker.js @@ -5,10 +5,12 @@ * @since (0.11.0) 2017 */ -var CACHE = "cv-cache-v1"; +var CACHE = "cv-cache-v2"; var NO_CACHE_TEST = /.+\.php$/i; var CONFIG_TEST = /.+visu_config.*\.xml.*/i; var config = {}; +var updateQueue = []; +var queueTid = null; self.addEventListener('message', function(event) { var data = event.data; @@ -59,6 +61,12 @@ self.addEventListener('fetch', function(ev) { // console.log(ev.request.url+" from cache"); if (config.forceReload === true) { update(ev.request); + } else { + updateQueue.push(ev.request); + if (queueTid) { + clearTimeout(queueTid); + } + queueTid = setTimeout(processQueue, 1000); } return response; @@ -87,6 +95,13 @@ function fromNetwork(request, timeout) { }); } +function processQueue() { + while (updateQueue.length) { + var request = updateQueue.shift(); + update(request); + } +} + /** * Get response from cache * @param request {Request} @@ -106,9 +121,11 @@ function fromCache(request) { * @return {Promise} */ function update(request) { - return caches.open(CACHE).then(function (cache) { - return fetch(request).then(function (response) { - return cache.put(request, response); + caches.open(CACHE).then(function (cache) { + fetch(request).then(function (response) { + if (response.status < 400) { + cache.put(request, response); + } }); }); } From 70dba7fbed07d747da4fda2be7fc9e388aed4393 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tobias=20Br=C3=A4utigam?= Date: Sat, 22 Apr 2017 16:52:37 +0200 Subject: [PATCH 5/6] update cache version in build --- config.json | 9 ++++++++- utils/update_version.py | 14 ++++++++++++-- 2 files changed, 20 insertions(+), 3 deletions(-) diff --git a/config.json b/config.json index dede6191f6e..46067ab8605 100644 --- a/config.json +++ b/config.json @@ -301,6 +301,13 @@ } }, + "update-version-cache": { + "shell" : + { + "command" : "utils/update_version.py worker" + } + }, + "build" : { "extend" : [ "parts-config" ], @@ -312,7 +319,7 @@ "run" : [ "build-resources", - "update-version", + "update-version-cache", "build-script", "build-files", "build-libs", diff --git a/utils/update_version.py b/utils/update_version.py index 0e5b84967ce..143693341cf 100755 --- a/utils/update_version.py +++ b/utils/update_version.py @@ -5,11 +5,13 @@ import datetime import json import os +import sys +import re root_dir = os.path.abspath(os.path.join(os.path.realpath(os.path.dirname(__file__)), '..')) -def update_version(): +def update_version(service_worker_cache=False): # gather information from git data = { "revision": subprocess.check_output(["git", "rev-parse", "HEAD"]).strip("\n"), @@ -34,5 +36,13 @@ def update_version(): }); ''' % data) + if service_worker_cache is True: + regex = re.compile("var CACHE = \"(.+)\";", re.IGNORECASE) + with open(os.path.join(root_dir, "source", "ServiceWorker.js"), 'r+') as f: + content = f.read() + content = regex.sub("var CACHE = \"%s\";" % data['revision'], content) + f.seek(0) + f.write(content) + if __name__ == '__main__': - update_version() \ No newline at end of file + update_version(len(sys.argv) == 2 and sys.argv[1] == "worker") From 42625d5d0e96cf10b78856e0c69020e5d1998a8f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tobias=20Br=C3=A4utigam?= Date: Mon, 18 Sep 2017 19:02:26 +0200 Subject: [PATCH 6/6] fix merge errors --- config.json | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/config.json b/config.json index 46067ab8605..a87dadd8ec7 100644 --- a/config.json +++ b/config.json @@ -83,7 +83,9 @@ "${APPLICATION}.ui.structure.pure.*", "${APPLICATION}.transforms.*", "${APPLICATION}.plugins.*", - "${APPLICATION}.parser.*" + "${APPLICATION}.parser.*", + "${APPLICATION}.core.*", + "${QXTHEME}" ], "lint-check" : { @@ -93,7 +95,9 @@ "THREE", "sprintf", "replayLog", - "svg4everybody" + "svg4everybody", + "EVENT_RECORDER", + "Favico" ] }, @@ -107,6 +111,9 @@ }, "add-script" : [ + { + "uri" : "resource/libs/EventRecorder.js" + }, { "uri" : "resource/libs/sprintf.js" },