diff --git a/routes/_utils/asyncModules.js b/routes/_utils/asyncModules.js new file mode 100644 index 0000000..562bf34 --- /dev/null +++ b/routes/_utils/asyncModules.js @@ -0,0 +1,14 @@ +const importURLSearchParams = () => import( + /* webpackChunkName: 'url-search-params' */ 'url-search-params' + ).then(Params => { + window.URLSearchParams = Params + Object.defineProperty(window.URL.prototype, 'searchParams', { + get() { + return new Params(this.search) + } + }) +}) + +export { + importURLSearchParams +} \ No newline at end of file diff --git a/templates/main.js b/templates/main.js index d044b23..2f3817d 100644 --- a/templates/main.js +++ b/templates/main.js @@ -1,16 +1,19 @@ -import { init } from 'sapper/runtime.js'; +import { init } from 'sapper/runtime.js' +import toast from '../routes/_utils/toast.js' // polyfills Promise.all([ - typeof URLSearchParams === 'undefined' && import(/* webpackChunkName: 'url-search-params' */ 'url-search-params').then(Params => { - window.URLSearchParams = Params - Object.defineProperty(window.URL.prototype, 'searchParams', { - get() { - return new Params(this.search) - } - }) - }) + typeof URLSearchParams === 'undefined' && importURLParams() ]).then(() => { // `routes` is an array of route objects injected by Sapper init(document.querySelector('#sapper'), __routes__) + + if (navigator.serviceWorker && navigator.serviceWorker.controller) { + navigator.serviceWorker.controller.onstatechange = (e) => { + if (e.target.state === 'redundant') { + toast.say('App update available. Reload to update.'); + } + } + } + }) \ No newline at end of file diff --git a/templates/service-worker.js b/templates/service-worker.js index dcffa32..27ddf99 100644 --- a/templates/service-worker.js +++ b/templates/service-worker.js @@ -36,8 +36,18 @@ self.addEventListener('activate', event => { ) }) +const NETWORK_ONLY = [ + '/oauth' +] + +const CACHE_FIRST = [ + '/api/v1/accounts/verify_credentials', + '/system/accounts/avatars' +] + self.addEventListener('fetch', event => { - const url = new URL(event.request.url) + const req = event.request + const url = new URL(req.url) // don't try to handle e.g. data: URIs if (!url.protocol.startsWith('http')) { @@ -46,7 +56,7 @@ self.addEventListener('fetch', event => { // always serve assets and webpack-generated files from cache if (cached.has(url.pathname)) { - event.respondWith(caches.match(event.request)) + event.respondWith(caches.match(req)) return } @@ -55,30 +65,59 @@ self.addEventListener('fetch', event => { // app, but if it's right for yours then uncomment this section if (url.origin === self.origin && routes.find(route => route.pattern.test(url.pathname))) { - event.respondWith(caches.match('/index.html')); - return; + event.respondWith(caches.match('/index.html')) + return + } + + // Non-GET and for certain endpoints (e.g. OAuth), go network-only + if (req.method !== 'GET' || + NETWORK_ONLY.some(pattern => url.pathname.startsWith(pattern))) { + console.log('Using network-only for', url.href) + event.respondWith(fetch(req)) + return + } + + // For these, go cache-first. + if (CACHE_FIRST.some(pattern => url.pathname.startsWith(pattern))) { + console.log('Using cache-first for', url.href) + event.respondWith(caches + .open(`offline${timestamp}`) + .then(async cache => { + let response = await cache.match(req) + if (response) { + // update asynchronously + fetch(req).then(response => { + cache.put(req, response.clone()) + }) + return response + } + response = await fetch(req) + cache.put(req, response.clone()) + return response + })) + return } // for everything else, try the network first, falling back to // cache if the user is offline. (If the pages never change, you // might prefer a cache-first approach to a network-first one.) - event.respondWith( - caches - .open(`offline${timestamp}`) - .then(async cache => { - try { - const response = await fetch(event.request) - cache.put(event.request, response.clone()) + event.respondWith(caches + .open(`offline${timestamp}`) + .then(async cache => { + try { + console.log('Using network-first for', url.href) + const response = await fetch(req) + cache.put(req, response.clone()) + return response + } catch (err) { + const response = await cache.match(req) + if (response) { return response - } catch (err) { - const response = await cache.match(event.request) - if (response) { - return response - } - - throw err } - }) + + throw err + } + }) ) })