From 0ee36282c3af5396b15e74339d8da52a9137076c Mon Sep 17 00:00:00 2001 From: Lennart Goedhart Date: Thu, 10 Sep 2020 00:32:13 +1000 Subject: [PATCH] Bug fixes for Service Worker (#5970) * Fix service worker refresh for Firefox * Improve logging for service worker initialisation * Skip browser cache for service worker precaching * Prevent `Partial Content` responses in `fetch` by stripping `Range` header * Prevent calling `filter` on an undefined property when service worker cache is empty * Bug fixes for Service Worker * Fixes https://github.com/nightscout/cgm-remote-monitor/issues/5920 * Fixes https://github.com/nightscout/cgm-remote-monitor/issues/5943 * Update service-worker.js Removed the MP3 files from the preload to fix issues with webkit-based browsers Co-authored-by: p5nbTgip0r Co-authored-by: Sulka Haro --- views/index.html | 6 +- views/service-worker.js | 143 +++++++++++++++++++++++++++++----------- 2 files changed, 109 insertions(+), 40 deletions(-) diff --git a/views/index.html b/views/index.html index 1adb2f50e81..dbff801392f 100644 --- a/views/index.html +++ b/views/index.html @@ -733,15 +733,15 @@ const newWorker = reg.installing; newWorker.addEventListener('statechange', (state) => { console.log('New worker state change', state); - //reg.unregister().then(function() { - window.location.reload(true); - // }); + window.location.reload(true); }); }); }).catch(function(error) { console.log('Registration failed with ' + error); }); }); + } else { + console.log('Browser does not support service workers.'); }; diff --git a/views/service-worker.js b/views/service-worker.js index 50e720b1aba..0711889012b 100644 --- a/views/service-worker.js +++ b/views/service-worker.js @@ -23,8 +23,6 @@ const CACHE_LIST = [ '/images/mstile-144x144.png', '/css/ui-darkness/jquery-ui.min.css', '/css/jquery.tooltips.css', - '/audio/alarm.mp3', - '/audio/alarm2.mp3', '/css/ui-darkness/images/ui-icons_ffffff_256x240.png', '/css/ui-darkness/images/ui-icons_cccccc_256x240.png', '/css/ui-darkness/images/ui-bg_inset-soft_25_000000_1x100.png', @@ -38,44 +36,111 @@ const CACHE_LIST = [ '/images/logo2.png' ]; -// Open a cache and use `addAll()` with an array of assets to add all of them -// to the cache. Return a promise resolving when all the assets are added. +function returnRangeRequest(request) { + return caches + .open(CACHE) + .then((cache) => { + return cache.match(request.url); + }) + .then((res) => { + if (!res) { + return fetch(request) + .then(res => { + const clonedRes = res.clone(); + return caches + .open(CACHE) + .then(cache => cache.put(request, clonedRes)) + .then(() => res); + }) + .then(res => { + return res.arrayBuffer(); + }); + } + return res.arrayBuffer(); + }) + .then((arrayBuffer) => { + const bytes = /^bytes=(\d+)-(\d+)?$/g.exec( + request.headers.get('range') + ); + if (bytes) { + const start = Number(bytes[1]); + const end = Number(bytes[2]) || arrayBuffer.byteLength - 1; + return new Response(arrayBuffer.slice(start, end + 1), { + status: 206, + statusText: 'Partial Content', + headers: [ + ['Content-Range', `bytes ${start}-${end}/${arrayBuffer.byteLength}`] + ] + }); + } else { + return new Response(null, { + status: 416, + statusText: 'Range Not Satisfiable', + headers: [['Content-Range', `*/${arrayBuffer.byteLength}`]] + }); + } + }); +} + +// Open a cache and `put()` the assets to the cache. +// Return a promise resolving when all the assets are added. function precache() { - return caches.open(CACHE).then(function (cache) { - return cache.addAll(CACHE_LIST); + return caches.open(CACHE) + .then((cache) => { + // if any cache requests fail, don't interrupt other requests in progress + return Promise.allSettled( + CACHE_LIST.map((url) => { + // `no-store` in case of partial content responses and + // because we're making our own cache + let request = new Request(url, { cache: 'no-store' }); + return fetch(request).then((response) => { + // console.log('Caching response', url, response); + cache.put(url, response); + }).catch((err) => { + console.log('Could not precache asset', url, err); + }); + }) + ); }); } -// Open the cache where the assets were stored and search for the requested -// resource. Notice that in case of no matching, the promise still resolves -// but it does with `undefined` as value. +// Try to read the requested resource from cache. +// If the requested resource does not exist in the cache, fetch it from +// network and cache the response. function fromCache(request) { - return caches.open(CACHE).then(function (cache) { - return cache.match(request).then(function (matching) { - return matching || Promise.reject('no-match'); - }); - }); -} + return caches.open(CACHE).then((cache) => { + return cache.match(request).then((matching) => { + console.log(matching); + if(matching){ + return matching; + } + + return fetch(request).then((response) => { + // console.log('Response from network is:', response); + cache.put(request, response.clone()); + return response; + }).catch((error) => { + // This catch() will handle exceptions thrown from the fetch() operation. + // Note that a HTTP error response (e.g. 404) will NOT trigger an exception. + // It will return a normal response object that has the appropriate error code set. + console.error('Fetching failed:', error); -// Update consists in opening the cache, performing a network request and -// storing the new response data. -function update(request) { - return caches.open(CACHE).then(function (cache) { - return fetch(request).then(function (response) { - return cache.put(request, response); + throw error; + }); }); }); } // On install, cache some resources. -self.addEventListener('install', function(evt) { - //console.log('The service worker is being installed.'); +self.addEventListener('install', (evt) => { + // console.log('The service worker is being installed.'); + self.skipWaiting(); evt.waitUntil(precache()); }); function inCache(request) { let found = false; - CACHE_LIST.forEach( function (e) { + CACHE_LIST.forEach((e) => { if (request.url.endsWith(e)) { found = true; } @@ -83,25 +148,29 @@ function inCache(request) { return found; } -self.addEventListener('fetch', function(evt) { +self.addEventListener('fetch', (evt) => { if (!evt.request.url.startsWith(self.location.origin) || CACHE === 'developmentMode' || !inCache(evt.request) || evt.request.method !== 'GET') { //console.log('Skipping cache for ', evt.request.url); return void evt.respondWith(fetch(evt.request)); } - //console.log('Returning cached for ', evt.request.url); - evt.respondWith(fromCache(evt.request)); - evt.waitUntil(update(evt.request)); + if (evt.request.headers.get('range')) { + evt.respondWith(returnRangeRequest(evt.request)); + } else { + evt.respondWith(fromCache(evt.request)); + } }); self.addEventListener('activate', (event) => { event.waitUntil( caches.keys().then((cacheNames) => { - return cacheNames.filter((cacheName) => CACHE !== cacheName); - }).then((unusedCaches) => { - //console.log('DESTROYING CACHE', unusedCaches.join(',')); - return Promise.all(unusedCaches.map((unusedCache) => { - return caches.delete(unusedCache); - })); - }).then(() => self.clients.claim()) - ); -}); \ No newline at end of file + return Promise.all( + cacheNames.map((cacheName) => { + if (cacheName !== CACHE) { + // console.log('Deleting out of date cache:', cacheName); + return caches.delete(cacheName); + } + }) + ); + })); + +});