diff options
Diffstat (limited to 'static/sw.js')
| -rw-r--r-- | static/sw.js | 175 |
1 files changed, 175 insertions, 0 deletions
diff --git a/static/sw.js b/static/sw.js new file mode 100644 index 0000000..4a2b0db --- /dev/null +++ b/static/sw.js @@ -0,0 +1,175 @@ +class Pwa {
+
+ constructor(self) {
+ this.scope = self;
+ const Version = new URL(location).searchParams.get("v");
+ this.CACHE_VERSION = Version;
+ //this.BASE_CACHE_FILES=['/js/theme.min.js','/js/theme_light.min.js','/abridge.css','/js/abridge.min.js','/','/404.html','/offline/','/manifest.json'];
+ this.BASE_CACHE_FILES = ['/js/theme.min.js','/js/theme_light.min.js','/abridge.css','/js/abridge.min.js','/','/404.html','/offline/','/manifest.json'];
+ this.host = `${self.location.protocol}//${self.location.host}`;
+ console.info(`Host: ${this.host}`);
+ this.OFFLINE_PAGE = '/offline/';
+ this.NOT_FOUND_PAGE = '/404.html';
+ this.CACHE_NAME = `content-v${this.CACHE_VERSION}`;
+ // 3600=1hour, 28800=8hours, 86400=1day, 604800=1week, 1209600=2weeks
+ this.NORM_TTL = 86400;// html, json, xml, anything else undefined
+ this.LONG_TTL = 604800;// js, css
+ // rarely change, may be a good idea to periodically refresh, incase I change these and forget to increment service worker version:
+ this.TTL_LONG = ["js", "css"];
+ // never change, cache forever unless service worker version is incremented:
+ this.TTL_EXEMPT = ["jpg", "jpeg", "png", "gif", "webp", "avif", "ico", "svg", "otf", "eot", "ttf", "woff", "woff2", "xml", "xsl", "txt", "mp4", "webm", "mp3", "ogg"];
+ // skip these extensions so they expire same time as html: st,wasm,json(search), xml(sitemap,atom,rss)
+ }
+
+ canCache(url) {
+ if (url.startsWith("http://localhost")) {
+ return false;
+ }
+ const result = url.toString().startsWith(this.host);
+ return result;
+ }
+
+ getFileExtension(url) {
+ const extension = url.split('.').reverse()[0].split('?')[0];
+ return (extension.endsWith('/')) ? '/' : extension;
+ }
+
+ getTTL(url) {
+ if (typeof url === 'string') {
+ const extension = this.getFileExtension(url);
+ if (this.TTL_LONG.indexOf(extension) > -1) {
+ //console.info(url + ' contains a TTL_LONG extension');
+ return this.LONG_TTL;
+ }
+ if (this.TTL_EXEMPT.indexOf(extension) > -1) {
+ //console.info(url + ' contains a TTL_EXEMPT extension');
+ return null;
+ }
+ return this.NORM_TTL;
+ }
+ return null;
+ }
+
+ async installServiceWorker() {
+ try {
+ await caches.open(this.CACHE_NAME).then((cache) => {
+ return cache.addAll(this.BASE_CACHE_FILES);
+ }, err => console.error(`Error with ${this.CACHE_NAME}`, err));
+ return this.scope.skipWaiting();
+ }
+ catch (err) {
+ return console.error("Error with installation: ", err);
+ }
+ }
+
+ cleanupLegacyCache() {
+
+ const currentCaches = [this.CACHE_NAME];
+
+ return new Promise(
+ (resolve, reject) => {
+ caches.keys()
+ .then((keys) => keys.filter((key) => !~currentCaches.indexOf(key)))
+ .then((legacy) => {
+ if (legacy.length) {
+ Promise.all(legacy.map((legacyKey) => caches.delete(legacyKey))
+ ).then(() => resolve()).catch((err) => {
+ console.error("Error in legacy cleanup: ", err);
+ reject(err);
+ });
+ } else {
+ resolve();
+ }
+ }).catch((err) => {
+ console.error("Error in legacy cleanup: ", err);
+ reject(err);
+ });
+ });
+ }
+
+ async preCacheUrl(url) {
+ const cache = await caches.open(this.CACHE_NAME);
+ const response = await cache.match(url);
+ if (!response) {
+ return fetch(url).then(resp => cache.put(url, resp.clone()));
+ }
+ return null;
+ }
+
+ register() {
+ this.scope.addEventListener('install', event => {
+ event.waitUntil(
+ Promise.all([
+ this.installServiceWorker(),
+ this.scope.skipWaiting(),
+ ]));
+ console.info('SW Installed');
+ });
+
+ this.scope.addEventListener('activate', event => {
+ event.waitUntil(Promise.all(
+ [this.cleanupLegacyCache(),
+ this.scope.clients.claim(),
+ this.scope.skipWaiting()]).catch((err) => {
+ console.error("Activation error: ", err);
+ event.skipWaiting();
+ }));
+ });
+
+ this.scope.addEventListener('fetch', event => {
+ event.respondWith(
+ caches.open(this.CACHE_NAME).then(async cache => {
+ // check if this is NOT a resource we allow cacheing (some other domain), if so fetch it instead of cache.
+ if (!this.canCache(event.request.url)) {
+ return fetch(event.request);
+ }
+ // check the cache for the requested resource
+ const response = await cache.match(event.request);
+ if (response) {
+ const headers = response.headers.entries();
+ let date = null;
+ for (let pair of headers) {
+ if (pair[0] === 'date') {
+ date = new Date(pair[1]);
+ break;
+ }
+ }
+ // date is not working, so ignore TTL and just serve the cached resource.
+ if (!date) {
+ return response;
+ }
+ const age = parseInt(((new Date().getTime() - date.getTime()) / 1000).toString());
+ const ttl = this.getTTL(event.request.url);
+ if (ttl === null || (ttl && age < ttl)) {
+ // return the resource if it is not beyond the TTL
+ return response;
+ }
+ }
+ // if we made it here then we either did not have the cache, or the TTL was expired.
+ return fetch(event.request.clone()).then(resp => {
+ if (resp.status < 400) {
+ if (this.canCache(event.request.url)) {
+ cache.put(event.request, resp.clone());
+ }
+ return resp;
+ }
+ else {
+ return cache.match(this.NOT_FOUND_PAGE);
+ }
+ }).catch(err => {
+ // if we made it here then we were unable to fetch the resource.
+ // maybe we were only fetching because of expired TTL, so use the cache regardless of TTL:
+ if (response) {
+ return response;
+ }
+ // if we made it here then we were unable to fetch the resource and do not have it cached.
+ console.error(`Error fetching ${event.request.url} resulted in offline`, err);
+ return cache.match(this.OFFLINE_PAGE);
+ })
+ }));
+ });
+ }
+}
+
+const pwa = new Pwa(self);
+pwa.register();
|
