From bd2c51899384a338b75a77dd282cb84eedfa5fe6 Mon Sep 17 00:00:00 2001 From: Eliot Berriot Date: Wed, 8 Jan 2020 15:42:45 +0100 Subject: [PATCH] Cache API/media files with service workers --- front/src/App.vue | 11 ++++- front/src/registerServiceWorker.js | 14 ++++-- front/src/service-worker.js | 75 ++++++++++++++++++++++++++++-- 3 files changed, 89 insertions(+), 11 deletions(-) diff --git a/front/src/App.vue b/front/src/App.vue index 0a918a7c1..5417a07e1 100644 --- a/front/src/App.vue +++ b/front/src/App.vue @@ -263,7 +263,7 @@ export default { updateApp () { this.$store.commit('ui/serviceWorker', {updateAvailable: false}) if (!this.serviceWorker.registration || !this.serviceWorker.registration.waiting) { return; } - this.serviceWorker.registration.waiting.postMessage('skipWaiting'); + this.serviceWorker.registration.waiting.postMessage({command: 'skipWaiting'}) } }, computed: { @@ -317,9 +317,16 @@ export default { }, }, watch: { - '$store.state.instance.instanceUrl' () { + '$store.state.instance.instanceUrl' (v) { this.$store.dispatch('instance/fetchSettings') this.fetchNodeInfo() + if (this.serviceWorker.registration) { + let sw = this.serviceWorker.registration.active + if (sw) { + sw.postMessage({command: 'serverChosen', serverUrl: v}) + + } + } }, '$store.state.ui.theme': { immediate: true, diff --git a/front/src/registerServiceWorker.js b/front/src/registerServiceWorker.js index a182d0374..4998b6bca 100644 --- a/front/src/registerServiceWorker.js +++ b/front/src/registerServiceWorker.js @@ -6,13 +6,10 @@ import store from './store' if (process.env.NODE_ENV === 'production') { register(`${process.env.BASE_URL}service-worker.js`, { - ready () { + ready (registration) { console.log( - 'App is being served from cache by a service worker.' + 'App is being served from cache by a service worker.', registration ) - }, - registered (registration) { - console.log('Service worker has been registered.') // check for updates every 2 hours var checkInterval = 1000 * 60 * 60 * 2 // var checkInterval = 1000 * 5 @@ -20,6 +17,13 @@ if (process.env.NODE_ENV === 'production') { console.log('Checking for service worker update…') registration.update(); }, checkInterval); + store.commit('ui/serviceWorker', {registration: registration}) + if (registration.active) { + registration.active.postMessage({command: 'serverChosen', serverUrl: store.state.instance.instanceUrl}) + } + }, + registered () { + console.log('Service worker has been registered.') }, cached () { console.log('Content has been cached for offline use.') diff --git a/front/src/service-worker.js b/front/src/service-worker.js index 6fc563f72..c1bc1804d 100644 --- a/front/src/service-worker.js +++ b/front/src/service-worker.js @@ -1,14 +1,20 @@ // This is the code piece that GenerateSW mode can't provide for us. // This code listens for the user's confirmation to update the app. +workbox.loadModule('workbox-routing'); +workbox.loadModule('workbox-strategies'); +workbox.loadModule('workbox-expiration'); + self.addEventListener('message', (e) => { if (!e.data) { return; } - - switch (e.data) { + console.log('[sw] received message', e.data) + switch (e.data.command) { case 'skipWaiting': self.skipWaiting(); break; + case 'serverChosen': + self.registerServerRoutes(e.data.serverUrl) default: // NOOP break; @@ -18,7 +24,7 @@ workbox.core.clientsClaim(); // The precaching code provided by Workbox. self.__precacheManifest = [].concat(self.__precacheManifest || []); -console.log('Files to be cached by service worker [before filtering]', self.__precacheManifest.length); +console.log('[sw] Files to be cached [before filtering]', self.__precacheManifest.length); var excludedUrlsPrefix = [ '/js/locale-', '/js/moment-locale-', @@ -28,6 +34,67 @@ var excludedUrlsPrefix = [ self.__precacheManifest = self.__precacheManifest.filter((e) => { return !excludedUrlsPrefix.some(prefix => e.url.startsWith(prefix)) }); -console.log('Files to be cached by service worker [after filtering]', self.__precacheManifest.length); +console.log('[sw] Files to be cached [after filtering]', self.__precacheManifest.length); // workbox.precaching.suppressWarnings(); // Only used with Vue CLI 3 and Workbox v3. workbox.precaching.precacheAndRoute(self.__precacheManifest, {}); + +const router = new workbox.routing.Router(); +router.addCacheListener() +router.addFetchListener() + +var registeredServerRoutes = [] +self.registerServerRoutes = (serverUrl) => { + console.log('[sw] Setting up API caching for', serverUrl) + registeredServerRoutes.forEach((r) => { + console.log('[sw] Unregistering previous API route...', r) + router.unregisterRoute(r) + }) + if (!serverUrl) { + return + } + var regexReadyServerUrl = serverUrl.replace('.', '\\.') + registeredServerRoutes = [] + var networkFirstPaths = [ + 'api/v1/', + 'media/', + ] + var networkFirstExcludedPaths = [ + 'api/v1/listen' + ] + var strategy = new workbox.strategies.NetworkFirst({ + cacheName: "api-cache:" + serverUrl, + plugins: [ + new workbox.expiration.Plugin({ + maxAgeSeconds: 24 * 60 * 60 * 7, + }), + ] + }) + var networkFirstRoutes = networkFirstPaths.map((path) => { + var regex = new RegExp(regexReadyServerUrl + path) + return new workbox.routing.RegExpRoute(regex, () => {}) + }) + var matcher = ({url, event}) => { + for (let index = 0; index < networkFirstExcludedPaths.length; index++) { + const blacklistedPath = networkFirstExcludedPaths[index]; + if (url.pathname.startsWith('/' + blacklistedPath)) { + // the path is blacklisted, we don't cache it at all + console.log('[sw] Path is blacklisted, not caching', url.pathname) + return false + } + } + // we call other regex matchers + for (let index = 0; index < networkFirstRoutes.length; index++) { + const route = networkFirstRoutes[index]; + let result = route.match({url, event}) + if (result) { + return result + } + } + return false + } + + var route = new workbox.routing.Route(matcher, strategy) + console.log('[sw] registering new API route...', route) + router.registerRoute(route) + registeredServerRoutes.push(route) +}