diff --git a/static/sw.js b/static/sw.js new file mode 100644 index 0000000..3ed96cd --- /dev/null +++ b/static/sw.js @@ -0,0 +1,400 @@ +const CACHE_VERSION = 2.3; + +const BASE_CACHE_FILES = [ + '/js/fuse.min.94c78ad70b02749822921660cf4e9f0b3701bc0680c421afb784a78228de0275.js', + '/js/helper/getParents.min.1618c696be7c98933f9a92677f518b512a74e55bdbb976b09936b4182e93181b.js', + '/css/main.min.css', + '/js/helper/fadeinout.min.93a331f96194789a542f33690bbe4f0c102c7e78ffc018217f5a1c33010bad91.js', + '/logo.png', + '/js/lazysizes.min.31dd6a2d3a1ec0f78a8df007535cf23f03aeb5c70f026e6d6a19dac3b3acc340.js', + '/fonts/montserrat-bold.woff2', + '/js/enquire.min.dfb99dee1e029d51d6cfb672d847929890b1585402de17f5ed092edd72a688b4.js', + '/fonts/montserrat-regular.woff2', + '/fonts/merriweather-regular.woff2', + +]; + +const OFFLINE_CACHE_FILES = [ + '/js/fuse.min.94c78ad70b02749822921660cf4e9f0b3701bc0680c421afb784a78228de0275.js', + '/js/helper/getParents.min.1618c696be7c98933f9a92677f518b512a74e55bdbb976b09936b4182e93181b.js', + '/css/main.min.css', + '/js/helper/fadeinout.min.93a331f96194789a542f33690bbe4f0c102c7e78ffc018217f5a1c33010bad91.js', + '/logo.png', + '/js/lazysizes.min.31dd6a2d3a1ec0f78a8df007535cf23f03aeb5c70f026e6d6a19dac3b3acc340.js', + '/fonts/montserrat-bold.woff2', + '/offline/' +]; + +const NOT_FOUND_CACHE_FILES = [ + '/js/fuse.min.94c78ad70b02749822921660cf4e9f0b3701bc0680c421afb784a78228de0275.js', + '/js/helper/getParents.min.1618c696be7c98933f9a92677f518b512a74e55bdbb976b09936b4182e93181b.js', + '/css/main.min.css', + '/js/helper/fadeinout.min.93a331f96194789a542f33690bbe4f0c102c7e78ffc018217f5a1c33010bad91.js', + '/logo.png', + '/js/lazysizes.min.31dd6a2d3a1ec0f78a8df007535cf23f03aeb5c70f026e6d6a19dac3b3acc340.js', + '/fonts/montserrat-bold.woff2', + '/404.html' +]; + +const OFFLINE_PAGE = '/offline/'; +const NOT_FOUND_PAGE = '/404.html'; + +const CACHE_VERSIONS = { + assets: 'assets-v' + CACHE_VERSION, + content: 'content-v' + CACHE_VERSION, + offline: 'offline-v' + CACHE_VERSION, + notFound: '404-v' + CACHE_VERSION, +}; + +// Define MAX_TTL's in SECONDS for specific file extensions +const MAX_TTL = { + '/': 3600, + html: 3600, + json: 86400, + js: 86400, + css: 86400, +}; + +const CACHE_BLACKLIST = [ + //(str) => { + // return !str.startsWith('https://yourwebsite.com'); + //}, +]; + +const SUPPORTED_METHODS = [ + 'GET', +]; + +/** + * isBlackListed + * @param {string} url + * @returns {boolean} + */ +function isBlacklisted(url) { + return (CACHE_BLACKLIST.length > 0) ? !CACHE_BLACKLIST.filter((rule) => { + if(typeof rule === 'function') { + return !rule(url); + } else { + return false; + } + }).length : false +} + +/** + * getFileExtension + * @param {string} url + * @returns {string} + */ +function getFileExtension(url) { + let extension = url.split('.').reverse()[0].split('?')[0]; + return (extension.endsWith('/')) ? '/' : extension; +} + +/** + * getTTL + * @param {string} url + */ +function getTTL(url) { + if (typeof url === 'string') { + let extension = getFileExtension(url); + if (typeof MAX_TTL[extension] === 'number') { + return MAX_TTL[extension]; + } else { + return null; + } + } else { + return null; + } +} + +/** + * installServiceWorker + * @returns {Promise} + */ +function installServiceWorker() { + return Promise.all( + [ + caches.open(CACHE_VERSIONS.assets) + .then( + (cache) => { + return cache.addAll(BASE_CACHE_FILES); + } + ), + caches.open(CACHE_VERSIONS.offline) + .then( + (cache) => { + return cache.addAll(OFFLINE_CACHE_FILES); + } + ), + caches.open(CACHE_VERSIONS.notFound) + .then( + (cache) => { + return cache.addAll(NOT_FOUND_CACHE_FILES); + } + ) + ] + ) + .then(() => { + return self.skipWaiting(); + }); +} + +/** + * cleanupLegacyCache + * @returns {Promise} + */ +function cleanupLegacyCache() { + + let currentCaches = Object.keys(CACHE_VERSIONS) + .map( + (key) => { + return CACHE_VERSIONS[key]; + } + ); + + return new Promise( + (resolve, reject) => { + + caches.keys() + .then( + (keys) => { + return legacyKeys = keys.filter( + (key) => { + return !~currentCaches.indexOf(key); + } + ); + } + ) + .then( + (legacy) => { + if (legacy.length) { + Promise.all( + legacy.map( + (legacyKey) => { + return caches.delete(legacyKey) + } + ) + ) + .then( + () => { + resolve() + } + ) + .catch( + (err) => { + reject(err); + } + ); + } else { + resolve(); + } + } + ) + .catch( + () => { + reject(); + } + ); + + } + ); +} + +function precacheUrl(url) { + if(!isBlacklisted(url)) { + caches.open(CACHE_VERSIONS.content) + .then((cache) => { + cache.match(url) + .then((response) => { + if(!response) { + return fetch(url) + } else { + // already in cache, nothing to do. + return null + } + }) + .then((response) => { + if(response) { + return cache.put(url, response.clone()); + } else { + return null; + } + }); + }) + } +} + + + +self.addEventListener( + 'install', event => { + event.waitUntil( + Promise.all([ + installServiceWorker(), + self.skipWaiting(), + ]) + ); + } +); + +// The activate handler takes care of cleaning up old caches. +self.addEventListener( + 'activate', event => { + event.waitUntil( + Promise.all( + [ + cleanupLegacyCache(), + self.clients.claim(), + self.skipWaiting(), + ] + ) + .catch( + (err) => { + event.skipWaiting(); + } + ) + ); + } +); + +self.addEventListener( + 'fetch', event => { + + event.respondWith( + caches.open(CACHE_VERSIONS.content) + .then( + (cache) => { + + return cache.match(event.request) + .then( + (response) => { + + if (response) { + + let headers = response.headers.entries(); + let date = null; + + for (let pair of headers) { + if (pair[0] === 'date') { + date = new Date(pair[1]); + } + } + + if (date) { + let age = parseInt((new Date().getTime() - date.getTime()) / 1000); + let ttl = getTTL(event.request.url); + + if (ttl && age > ttl) { + + return new Promise( + (resolve) => { + + return fetch(event.request.clone()) + .then( + (updatedResponse) => { + if (updatedResponse) { + cache.put(event.request, updatedResponse.clone()); + resolve(updatedResponse); + } else { + resolve(response) + } + } + ) + .catch( + () => { + resolve(response); + } + ); + + } + ) + .catch( + (err) => { + return response; + } + ); + } else { + return response; + } + + } else { + return response; + } + + } else { + return null; + } + } + ) + .then( + (response) => { + if (response) { + return response; + } else { + return fetch(event.request.clone()) + .then( + (response) => { + + if(response.status < 400) { + if (~SUPPORTED_METHODS.indexOf(event.request.method) && !isBlacklisted(event.request.url)) { + cache.put(event.request, response.clone()); + } + return response; + } else { + return caches.open(CACHE_VERSIONS.notFound).then((cache) => { + return cache.match(NOT_FOUND_PAGE); + }) + } + } + ) + .then((response) => { + if(response) { + return response; + } + }) + .catch( + () => { + + return caches.open(CACHE_VERSIONS.offline) + .then( + (offlineCache) => { + return offlineCache.match(OFFLINE_PAGE) + } + ) + + } + ); + } + } + ) + .catch( + (error) => { + console.error(' Error in fetch handler:', error); + throw error; + } + ); + } + ) + ); + + } +); + + +self.addEventListener('message', (event) => { + + if( + typeof event.data === 'object' && + typeof event.data.action === 'string' + ) { + switch(event.data.action) { + case 'cache' : + precacheUrl(event.data.url); + break; + default : + console.log('Unknown action: ' + event.data.action); + break; + } + } + +});