diff --git a/packages/web/src/index.html b/packages/web/src/index.html index bb112e0c9c2..e7f65ee5e5e 100644 --- a/packages/web/src/index.html +++ b/packages/web/src/index.html @@ -30,7 +30,7 @@ - + diff --git a/packages/web/src/sw.js b/packages/web/src/sw.js new file mode 100644 index 00000000000..8f37e51ea42 --- /dev/null +++ b/packages/web/src/sw.js @@ -0,0 +1,88 @@ +const CACHE_NAME = 'sn-app-v1' + +// keep in sync with webpack copy patterns in web.webpack.config.js +const APP_SHELL = [ + '/', + '/index.html', + '/app.js', + '/app.css', + '/favicon/favicon.ico', + '/favicon/favicon-32x32.png', + '/favicon/apple-touch-icon.png', + '/manifest.webmanifest', +] + +self.addEventListener('install', (event) => { + event.waitUntil( + caches.open(CACHE_NAME).then((cache) => { + return cache.addAll(APP_SHELL) + }) + ) + // dont wait for old tabs to close + self.skipWaiting() +}) + +self.addEventListener('activate', (event) => { + event.waitUntil( + caches.keys().then((names) => { + return Promise.all( + names + .filter((name) => name !== CACHE_NAME) + .map((name) => caches.delete(name)) + ) + }) + ) + self.clients.claim() +}) + +self.addEventListener('fetch', (event) => { + const { request } = event + + // let API calls and websocket stuff go straight to network + if (request.url.includes('/api/') || request.url.includes('/socket')) { + return + } + + // navigation requests: network first, fall back to cached shell + if (request.mode === 'navigate') { + event.respondWith( + fetch(request) + .then((resp) => { + // stash a fresh copy if it's good + if (resp.ok) { + const clone = resp.clone() + caches.open(CACHE_NAME).then((cache) => cache.put(request, clone)) + } + return resp + }) + .catch(() => caches.match('/index.html')) + ) + return + } + + // everything else: cache first, then network + event.respondWith( + caches.match(request).then((cached) => { + if (cached) { + // refresh cache in bg + fetch(request) + .then((resp) => { + if (resp.ok) { + caches.open(CACHE_NAME).then((cache) => cache.put(request, resp)) + } + }) + .catch(() => {}) + return cached + } + + return fetch(request).then((resp) => { + // only cache same-origin stuff + if (resp.ok && new URL(request.url).origin === self.location.origin) { + const clone = resp.clone() + caches.open(CACHE_NAME).then((cache) => cache.put(request, clone)) + } + return resp + }) + }) + ) +}) diff --git a/packages/web/web.webpack.config.js b/packages/web/web.webpack.config.js index a2cf68aba4b..ae11c124dac 100644 --- a/packages/web/web.webpack.config.js +++ b/packages/web/web.webpack.config.js @@ -20,6 +20,7 @@ module.exports = (env) => { { from: 'src/500.html' }, { from: 'src/index.html' }, { from: 'src/manifest.webmanifest' }, + { from: 'src/sw.js' }, { from: 'src/robots.txt' }, { from: 'src/.well-known', to: '.well-known' }, ] diff --git a/packages/web/web.webpack.dev.js b/packages/web/web.webpack.dev.js index 2084f86a60e..f37a08a6602 100644 --- a/packages/web/web.webpack.dev.js +++ b/packages/web/web.webpack.dev.js @@ -20,7 +20,7 @@ module.exports = (env, argv) => { headers: { 'Access-Control-Allow-Origin': '*', 'Content-Security-Policy': - "default-src https: 'self'; base-uri 'self'; child-src * blob:; connect-src * blob:; font-src * data:; form-action 'self'; frame-ancestors * file:; frame-src * blob:; img-src 'self' * data: blob:; manifest-src 'self'; media-src 'self' blob: *.standardnotes.com; object-src 'self' blob: *.standardnotes.com; script-src 'self' 'sha256-r26E+iPOhx7KM7cKn4trOSoD8u5E7wL7wwJ8UrR+rGs=' 'unsafe-eval' 'wasm-unsafe-eval'; style-src *;", + "default-src https: 'self'; base-uri 'self'; child-src * blob:; connect-src * blob:; font-src * data:; form-action 'self'; frame-ancestors * file:; frame-src * blob:; img-src 'self' * data: blob:; manifest-src 'self'; media-src 'self' blob: *.standardnotes.com; object-src 'self' blob: *.standardnotes.com; script-src 'self' 'sha256-r26E+iPOhx7KM7cKn4trOSoD8u5E7wL7wwJ8UrR+rGs=' 'sha256-ZRgzGkk7dE5Vafy/x+4fOkq6Ysvyn2dpddLN4XBd9hI=' 'unsafe-eval' 'wasm-unsafe-eval'; worker-src 'self'; style-src *;", }, hot: true, static: './dist',