From f6afec2027e73feebe5227c304a5c5902bfc7e7e Mon Sep 17 00:00:00 2001 From: Sergey S Date: Tue, 10 Mar 2020 22:03:00 +0200 Subject: [PATCH 01/22] strore manifest info in indexeddb, implement swap cache --- dist/idb-keyval-iife.js | 76 ++++ dist/jakecache-sw.js | 777 +++++++++++++++++++++++++-------------- dist/jakecache.js | 282 ++++++++------- jakecache-sw.js | 778 ++++++++++++++++++++++++++-------------- jakecache.js | 291 ++++++++------- lib/idb-keyval-cjs.js | 73 ++++ package.json | 7 +- scripts/build.js | 34 +- 8 files changed, 1497 insertions(+), 821 deletions(-) create mode 100644 dist/idb-keyval-iife.js create mode 100644 lib/idb-keyval-cjs.js diff --git a/dist/idb-keyval-iife.js b/dist/idb-keyval-iife.js new file mode 100644 index 0000000..7990f2b --- /dev/null +++ b/dist/idb-keyval-iife.js @@ -0,0 +1,76 @@ +var idbKeyval = (function (exports) { +'use strict'; + +class Store { + constructor(dbName = 'keyval-store', storeName = 'keyval') { + this.storeName = storeName; + this._dbp = new Promise((resolve, reject) => { + const openreq = indexedDB.open(dbName, 1); + openreq.onerror = () => reject(openreq.error); + openreq.onsuccess = () => resolve(openreq.result); + // First time setup: create an empty object store + openreq.onupgradeneeded = () => { + openreq.result.createObjectStore(storeName); + }; + }); + } + _withIDBStore(type, callback) { + return this._dbp.then(db => new Promise((resolve, reject) => { + const transaction = db.transaction(this.storeName, type); + transaction.oncomplete = () => resolve(); + transaction.onabort = transaction.onerror = () => reject(transaction.error); + callback(transaction.objectStore(this.storeName)); + })); + } +} +let store; +function getDefaultStore() { + if (!store) + store = new Store(); + return store; +} +function get(key, store = getDefaultStore()) { + let req; + return store._withIDBStore('readonly', store => { + req = store.get(key); + }).then(() => req.result); +} +function set(key, value, store = getDefaultStore()) { + return store._withIDBStore('readwrite', store => { + store.put(value, key); + }); +} +function del(key, store = getDefaultStore()) { + return store._withIDBStore('readwrite', store => { + store.delete(key); + }); +} +function clear(store = getDefaultStore()) { + return store._withIDBStore('readwrite', store => { + store.clear(); + }); +} +function keys(store = getDefaultStore()) { + const keys = []; + return store._withIDBStore('readonly', store => { + // This would be store.getAllKeys(), but it isn't supported by Edge or Safari. + // And openKeyCursor isn't supported by Safari. + (store.openKeyCursor || store.openCursor).call(store).onsuccess = function () { + if (!this.result) + return; + keys.push(this.result.key); + this.result.continue(); + }; + }).then(() => keys); +} + +exports.Store = Store; +exports.get = get; +exports.set = set; +exports.del = del; +exports.clear = clear; +exports.keys = keys; + +return exports; + +}({})); diff --git a/dist/jakecache-sw.js b/dist/jakecache-sw.js index 8e8de4b..3d76492 100644 --- a/dist/jakecache-sw.js +++ b/dist/jakecache-sw.js @@ -264,134 +264,206 @@ function md5 (string, key, raw) { return raw_hmac_md5(key, string) } -class JakeCacheManifest { +self.importScripts("idb-keyval-iife.js"); + +const manifestStore = new idbKeyval.Store("manifest-db", "manifest-db"); - constructor () { - this._path = null - this._hash = null - this._isValid = false - this._fetchOptions = { credentials: "same-origin" } +class JakeCacheManifest { + constructor(data) { + this._path = null; + this._hash = null; + this._isValid = false; + this._fetchOptions = { credentials: "same-origin" }; + this._rawData = { + version: "", + cache: [], + fallback: [], + network: [] + }; + + if(data){ + this.restoreManifest(data); + } } - groupName () { - let filename = this._path.substring(this._path.lastIndexOf('/') + 1) - return filename + hash() { + return this._hash; + } + isValid() { + return this._isValid; + } + manifestData() { + return { + cacheName: this.cacheName(), + path: this._path, + hash : this._hash, + isValid : this._isValid, + rawData: this._rawData + }; } - fetchData (path, options = {}) { - this._path = path + restoreManifest(manifestData) { + if(!manifestData){ + this._isValid = false; + return; + } + this._path = manifestData.path; + this._hash = manifestData.hash; + this._rawData = manifestData.rawData - if (this._isValid && options.cache !== 'reload') { - return Promise.resolve(false) + this.restoreCache(); + } + + restoreCache() { + + this.cache = ["jakecache.js"]; + // Ignore different protocol + for (let pathname of this._rawData.cache) { + let path = new URL(pathname, location); + if (path.protocol === location.protocol) { + this.cache.push(path); + } } - // http://html5doctor.com/go-offline-with-application-cache/ - return fetch(new Request(this._path, options), this._fetchOptions).then((response) => { - if (response.type === 'opaque' || response.status === 404 || response.status === 410) { - return Promise.reject() + this.fallback = []; + for (let entry of this._rawData.fallback) { + let [pathname, fallbackPath] = entry.split(" "); + let path = new URL(pathname, location); + let fallback = new URL(fallbackPath, location); + + // Ignore cross-origin fallbacks + if (path.origin === fallback.origin) { + this.fallback.push([path, fallback]); + this.cache.push(fallback); } + } - this._rawData = { - cache: [], - fallback: [], - network: [] + this.allowNetworkFallback = false; + this.network = []; + for (let entry of this._rawData.network) { + if (entry === "*") { + this.allowNetworkFallback = true; + continue; } + let path = new URL(entry, location); + if (path.protocol === location.protocol) { + this.network.push(path); + } + } - return response.text().then((result) => { - return new Promise((resolve, reject) => { - let hash = md5(result) - if (this._hash && hash.toString() === this._hash.toString()) { - console.log('noupdate: ' + hash) - return resolve(false) - } - this._hash = hash - console.log(`update: ${hash} (was: ${this._hash})`) + this._isValid = true; + + } - let lines = result.split(/\r|\n/) - let header = 'cache' // default. + + pathName() { + return this._path; + } - let firstLine = lines.shift() - if (firstLine !== 'CACHE MANIFEST') { - return reject() - } + cacheName() { + let version = this._rawData.version; + return version + '_' + this._hash; + } - for (let line of lines) { - line = line.replace(/#.*$/, '').trim() + fetchData(path, options = {}) { + this._path = path; - if (line === '') { - continue - } + if (this._isValid && options.cache !== "reload") { + return Promise.resolve(false); + } - let res = line.match(/^([A-Z]*):/) - if (res) { - header = res[1].toLowerCase() - continue - } + // http://html5doctor.com/go-offline-with-application-cache/ + return fetch(new Request(this._path, options), this._fetchOptions).then( + response => { + if ( + response.type === "opaque" || + response.status === 404 || + response.status === 410 + ) { + return Promise.reject(); + } - if (!this._rawData[header]) { - this._rawData[header] = [] + this._hash = options.hash ? options.hash : this._hash; + + return response.text().then(result => { + return new Promise((resolve, reject) => { + let hash = md5(result); + if (this._hash && hash.toString() === this._hash.toString()) { + console.log("noupdate: " + hash); + return resolve(false); } - this._rawData[header].push(line) - } - this.cache = ['jakecache.js'] - // Ignore different protocol - for (let pathname of this._rawData.cache) { - let path = new URL(pathname, location) - if (path.protocol === location.protocol) { - this.cache.push(path) - } - } + console.log(`update: ${hash} (was: ${this._hash})`); - this.fallback = [] - for (let entry of this._rawData.fallback) { - let [pathname, fallbackPath] = entry.split(' ') - let path = new URL(pathname, location) - let fallback = new URL(fallbackPath, location) + this._hash = hash; - // Ignore cross-origin fallbacks - if (path.origin === fallback.origin) { - this.fallback.push([path, fallback]) - this.cache.push(fallback) - } - } + let lines = result.split(/\r|\n/); + let header = "cache"; // default. + let versionRegexp = /\s*(#\sVersion:)\s*([\w\.]*)/gm; - this.allowNetworkFallback = false - this.network = [] - for (let entry of this._rawData.network) { - if (entry === '*') { - this.allowNetworkFallback = true - continue + let firstLine = lines.shift(); + if (firstLine !== "CACHE MANIFEST") { + return reject(); } - let path = new URL(entry, location) - if (path.protocol === location.protocol) { - this.network.push(path) + let versionFound = false; + for (let line of lines) { + if (!versionFound) { + let match = versionRegexp.exec(line); + if (match) { + versionFound = true; + this._rawData.version = match[match.length - 1]; + } + } + + line = line.replace(/#.*$/, "").trim(); + + if (line === "") { + continue; + } + + let res = line.match(/^([A-Z]*):/); + if (res) { + header = res[1].toLowerCase(); + continue; + } + + if (!this._rawData[header]) { + this._rawData[header] = []; + } + this._rawData[header].push(line); } - } - this._isValid = true - resolve(true) - }) - }) - }) + if (!versionFound) { + this._rawData.version = "" + new Date().getTime(); + } + + this.restoreCache(); + resolve(true); + }); + }); + } + ); } } -self.addEventListener('message', function (event) { +self.addEventListener("message", function(event) { switch (event.data.command) { - case 'update': - update.call(this, event.data.pathname, event.data.options) - break - case 'abort': - postMessage({ type: 'error', message: 'Not implementable without cancellable promises.' }) - break - case 'swapCache': - swapCache() - break + case "update": + update.call(this, event.data.pathname, event.data.options); + break; + case "abort": + postMessage({ + type: "error", + message: "Not implementable without cancellable promises." + }); + break; + case "swapCache": + swapCache(); + break; } -}) +}); -let manifest = new JakeCacheManifest() +let manifest = null; const CacheStatus = { UNCACHED: 0, @@ -400,224 +472,377 @@ const CacheStatus = { DOWNLOADING: 3, UPDATEREADY: 4, OBSOLETE: 5 -} +}; -let cacheStatus = CacheStatus.UNCACHED +let cacheStatus = CacheStatus.UNCACHED; -function postMessage (msg) { +function postMessage(msg) { return self.clients.matchAll().then(clients => { - return Promise.all(clients.map(client => { - return client.postMessage(msg) - })) - }) + return Promise.all( + clients.map(client => { + return client.postMessage(msg); + }) + ); + }); } -function swapCache () { - caches.keys().then(keyList => { - return Promise.all(keyList.map(key => { - return caches.delete(key) - })) +function swapCache() { + idbKeyval.get('current', manifestStore).then(mnfstData => { + if(mnfstData){ + return caches.delete(mnfstData.cacheName); + } + }).then(() => { + return idbKeyval.get('next', manifestStore) + }).then(mnfstData => { + if(mnfstData){ + manifest = new JakeCacheManifest(mnfstData); + + return idbKeyval.set('current', mnfstData, manifestStore); + }else{ + cacheStatus = CacheStatus.UNCACHED; + } }).then(() => { - // FIXME: Add new keys. - }) + return idbKeyval.del('next', manifestStore); + }).then(_ => { + cacheStatus = CacheStatus.IDLE; + postMessage({ type: "updated" }); + }); } // 7.9.4 -function update (pathname, options = {}) { +function update(pathname, options = {}) { if (!pathname) { - console.log('No pathname!') - return Promise.reject() + console.log("No pathname!"); + return Promise.reject(); } + + let nextManifest = new JakeCacheManifest(); // *.2.2 - this.options = options - this.cacheGroup = pathname - - return caches.keys().then(cacheNames => { - this.uncached = !cacheNames.length - console.log('uncached ' + this.uncached) - return Promise.resolve(this.uncached) - }).then((uncached) => { - if (this.options.cache !== 'reload' && !uncached) { - // We have a cache and we are no doing an update check. - return Promise.reject() - } + this.options = options; + + return idbKeyval.get('current', manifestStore).then(mnfstData => { + if(!mnfstData){ + manifest = null; + this.uncached = true; + cacheStatus = CacheStatus.UNCACHED; + console.log("uncached " + this.uncached); + return Promise.resolve(this.uncached); + }else{ + manifest = new JakeCacheManifest(mnfstData); + this.options = this.options || {}; + this.options.hash = manifest.hash(); + } - // *.2.4 and *.2.6 - if (cacheStatus === CacheStatus.CHECKING) { - postMessage({ type: 'checking' }) - postMessage({ type: 'abort' }) - return Promise.reject() - } - // *.2.4, *.2.5, *.2.6 - if (cacheStatus === CacheStatus.DOWNLOADING) { - postMessage({ type: 'checking' }) - postMessage({ type: 'downloading' }) - postMessage({ type: 'abort' }) - return Promise.reject() - } - return Promise.resolve() - }).then(() => { - // *.2.7 and *.2.8 - cacheStatus = CacheStatus.CHECKING - postMessage({ type: 'checking' }) - - // FIXME: *.6: Fetch manifest, mark obsolete if fails. - return manifest.fetchData(this.cacheGroup, this.options).catch(err => { - cacheStatus = CacheStatus.OBSOLETE - postMessage({ type: 'obsolete' }) - // FIXME: *.7: Error for each existing entry. - cacheStatus = CacheStatus.IDLE - postMessage({ type: 'idle' }) - return Promise.reject(err) + return caches.open(mnfstData.cacheName).then(cache => { + if(!cache){ + manifest = null; + this.uncached = true; + cacheStatus = CacheStatus.UNCACHED; + console.log("uncached " + this.uncached); + return Promise.resolve(this.uncached); + } + + return cache.keys().then(keyData => { + this.uncached = !keyData || !keyData.length; + if(this.uncached){ + manifest = null; + cacheStatus = CacheStatus.UNCACHED; + } + console.log("uncached " + this.uncached); + return Promise.resolve(this.uncached); + }) + }) }) - }).then(modified => { - this.modified = modified - // *.2: If cache group already has an application cache in it, then - // this is an upgrade attempt. Otherwise, this is a cache attempt. - return caches.keys().then(cacheNames => { - return Promise.resolve(!!cacheNames.length) + .then(uncached => { + + if (cacheStatus === CacheStatus.UPDATEREADY) { + postMessage({ type: "updateready" }); + postMessage({ type: "abort" }); + return Promise.reject(); + } + // *.2.4 and *.2.6 + if (cacheStatus === CacheStatus.CHECKING) { + postMessage({ type: "checking" }); + postMessage({ type: "abort" }); + return Promise.reject(); + } + // *.2.4, *.2.5, *.2.6 + if (cacheStatus === CacheStatus.DOWNLOADING) { + postMessage({ type: "checking" }); + postMessage({ type: "downloading" }); + postMessage({ type: "abort" }); + return Promise.reject(); + } + return Promise.resolve(uncached); }) - }).then(upgrade => { - this.upgrade = upgrade - if (this.upgrade && !this.modified) { - cacheStatus = CacheStatus.IDLE - postMessage({ type: 'noupdate' }) - return Promise.reject() - } - - // Appcache is no-cors by default. - this.requests = manifest.cache.map(url => { - return new Request(url, { mode: 'no-cors' }) + .then(uncached => { + // *.2.7 and *.2.8 + cacheStatus = CacheStatus.CHECKING; + postMessage({ type: "checking" }); + + // FIXME: *.6: Fetch manifest, mark obsolete if fails. + + + return nextManifest.fetchData(pathname, this.options).catch(err => { + cacheStatus = CacheStatus.OBSOLETE; + postMessage({ type: "obsolete" }); + // FIXME: *.7: Error for each existing entry. + cacheStatus = CacheStatus.IDLE; + postMessage({ type: "idle" }); + return Promise.reject(err); + }); + + + }).then(modified => { + this.modified = modified; + // *.2: If cache group already has an application cache in it, then + // this is an upgrade attempt. Otherwise, this is a cache attempt. + return caches.keys().then(cacheNames => { + return Promise.resolve(!!cacheNames.length); + }); }) + .then(upgrade => { + this.upgrade = upgrade; + if (this.upgrade && !this.modified) { + cacheStatus = CacheStatus.IDLE; + postMessage({ type: "noupdate" }); + return Promise.reject(); + } - cacheStatus = CacheStatus.DOWNLOADING - postMessage({ type: 'downloading' }) - - this.loaded = 0 - this.total = this.requests.length - - return Promise.all(this.requests.map(request => { - // Manual fetch to emulate appcache behavior. - return fetch(request, manifest._fetchOptions).then(response => { - cacheStatus = CacheStatus.PROGRESS - postMessage({ - type: 'progress', - lengthComputable: true, - loaded: ++(this.loaded), - total: this.total - }) - - // section 5.6.4 of http://www.w3.org/TR/2011/WD-html5-20110525/offline.html + // Appcache is no-cors by default. + this.requests = nextManifest.cache.map(url => { + return new Request(url, { mode: "no-cors" }); + }); + + cacheStatus = CacheStatus.DOWNLOADING; + postMessage({ type: "downloading" }); + + this.loaded = 0; + this.total = this.requests.length; + + return Promise.all( + this.requests.map(request => { + // Manual fetch to emulate appcache behavior. + return fetch(request, nextManifest._fetchOptions).then(response => { + cacheStatus = CacheStatus.PROGRESS; + postMessage({ + type: "progress", + lengthComputable: true, + loaded: ++this.loaded, + total: this.total, + url: request.url.toString() + }); + + // section 5.6.4 of http://www.w3.org/TR/2011/WD-html5-20110525/offline.html + + // Redirects are fatal. + if (response.url !== request.url) { + throw Error(); + } - // Redirects are fatal. - if (response.url !== request.url) { - throw Error() - } + // FIXME: should we update this.total below? - // FIXME: should we update this.total below? + if (response.type !== "opaque") { + // If the error was a 404 or 410 HTTP response or equivalent + // Skip this resource. It is dropped from the cache. + if (response.status < 200 || response.status >= 300) { + return undefined; + } - if (response.type !== 'opaque') { - // If the error was a 404 or 410 HTTP response or equivalent - // Skip this resource. It is dropped from the cache. - if (response.status < 200 || response.status >= 300) { - return undefined - } + // HTTP caching rules, such as Cache-Control: no-store, are ignored. + if ( + (response.headers.get("cache-control") || "").match(/no-store/i) + ) { + return undefined; + } + } - // HTTP caching rules, such as Cache-Control: no-store, are ignored. - if ((response.headers.get('cache-control') || '').match(/no-store/i)) { - return undefined + return response; + }); + }) + ); + }).then(responses => { + this.responses = responses.filter(response => response); + return Promise.resolve(this.responses); + }) + .then(responses => { + console.log("Adding to cache " + nextManifest.cacheName()); + return caches + .open(nextManifest.cacheName()) + .then(cache => { + return Promise.all( + responses.map((response, index) => { + return cache.put(self.requests[index], response); + }) + ); + }).then(_ => { + let manifestVersion = 'next'; + if(!this.upgrade){ + manifest = nextManifest; + manifestVersion = 'current'; } - } - return response - }) - })) - }).then(responses => { - this.responses = responses.filter(response => response) - if (this.upgrade) { - cacheStatus = CacheStatus.UPDATEREADY - postMessage({ type: 'updateready' }) - return Promise.reject() - } else { - return Promise.resolve(this.responses) - } - }).then(responses => { - console.log('Adding to cache ' + manifest.groupName()) - return caches.open(manifest.groupName()).then(cache => { - return Promise.all(responses.map((response, index) => { - return cache.put(self.requests[index], response) - })) - }).then(_ => { - cacheStatus = CacheStatus.CACHED - postMessage({ type: 'cached' }) + return idbKeyval.set(manifestVersion, nextManifest.manifestData(), manifestStore); + }); + }).then(_ => { + if (this.upgrade) + { + cacheStatus = CacheStatus.UPDATEREADY; + postMessage({ type: "updateready" }); + } else { + cacheStatus = CacheStatus.CACHED; + postMessage({ type: "cached" }); + } + return Promise.resolve(); }) - }).catch(err => { - if (err) { - postMessage({ type: 'error' }, err) - console.log(err) - } - }) + .catch(err => { + if (err) { + postMessage({ type: "error" }, err); + console.log(err); + } + }); } -self.addEventListener('install', function (event) { - event.waitUntil(self.skipWaiting()) -}) +self.addEventListener("install", function(event) { + event.waitUntil( + self.skipWaiting() + ); +}); -self.addEventListener('activate', function (event) { - event.waitUntil(self.clients.claim()) -}) +self.addEventListener("activate", function(event) { + + event.waitUntil( + idbKeyval.get('current', manifestStore).then(mnfstData => { + if(mnfstData){ + manifest = new JakeCacheManifest(mnfstData); + cacheStatus = CacheStatus.CACHED; + } + return Promise.resolve(manifest); + }).then(mnfst => { + if(mnfst){ + return update(mnfst.pathName(), { cache : "reload" }); + } + return Promise.resolve(); + }).then(self.clients.claim()) + ); +}); + +self.addEventListener("fetch", function(event) { + if(manifest && manifest.isValid() && cacheStatus === CacheStatus.UNCACHED){ + cacheStatus = CacheStatus.CACHED; + } -self.addEventListener('fetch', function (event) { if (cacheStatus === CacheStatus.UNCACHED) { - return fetch(event.request) + return fetch(event.request); } - let url = new URL(event.request.url) + let url = new URL(event.request.url); // Ignore non-GET and different schemes. - if (event.request.method !== 'GET' || url.scheme !== location.scheme) { - return + if (!event.request.url.startsWith(self.location.origin) || + event.request.method !== "GET" || url.scheme !== location.scheme) { + return; } - // FIXME: Get data from IndexedDB instead. - event.respondWith(manifest.fetchData('test.manifest').then(_ => { - // Process network-only. - if (manifest.network.filter(entry => entry.href === url.href).length) { - return fetch(event.request) + // if (!event.request.url.startsWith(self.location.origin)) { + // // External request, or POST, ignore + // return void event.respondWith(fetch(event.request)); + // } + + // FIXME TEST: Get data from IndexedDB instead. + let mnfstPromise = manifest + ? Promise.resolve(manifest) + : idbKeyval.get("current", manifestStore).then(mnfstData => { + if (mnfstData) { + manifest = new JakeCacheManifest(mnfstData); + } + return manifest; + }); + + function checkCache(mnfst) + { + return caches + .open(mnfst.cacheName()) + .then(function (cache) { + if(!cache) + { + throw Error('Cache not found'); + } + + return cache.match(event.request).then(response => { + // Cache always wins. + if (response) { + return response; + } + + // Fallbacks consult network, and falls back on failure. + for (let [path, fallback] of mnfst.fallback) { + if (url.href.indexOf(path) === 0) { + return fetch(event.request) + .then(response => { + // Same origin only. + if (new URL(response.url).origin !== location.origin) { + throw Error(); + } + + if (response.type !== "opaque") { + if (response.status < 200 || response.status >= 300) { + throw Error(); + } + } + }) + .catch(_ => { + return cache.match(fallback); + }); + } + } + + if (mnfst.allowNetworkFallback) { + return fetch(event.request); + } + + return response; // failure. + }); + }) + .catch(err => { + if (err) { + postMessage({ type: "error" }, err); + console.log(err); + } + //error with cache remove current manifest + manifest = null; + idbKeyval.del("current", manifestStore); + cacheStatus = CacheStatus.UNCACHED; + return fetch(event.request); + }); } - return caches.match(event.request).then(response => { - // Cache always wins. - if (response) { - return response - } - - // Fallbacks consult network, and falls back on failure. - for (let [path, fallback] of manifest.fallback) { - if (url.href.indexOf(path) === 0) { - return fetch(event.request).then(response => { - // Same origin only. - if (new URL(response.url).origin !== location.origin) { - throw Error() - } - - if (response.type !== 'opaque') { - if (response.status < 200 || response.status >= 300) { - throw Error() - } - } - }).catch(_ => { - return cache.match(fallback) - }) + event.respondWith( + mnfstPromise + .then(mnfst => { + if (!mnfst) { + cacheStatus = CacheStatus.UNCACHED; + return fetch(event.request); } - } - if (manifest.allowNetworkFallback) { - return fetch(event.request) - } + // Process network-only. + if (mnfst.network.filter(entry => entry.href === url.href).length) { + return fetch(event.request); + } - return response // failure. - }) - })) -}) \ No newline at end of file + return checkCache(mnfst); + }) + .catch(err => { + if (err) { + postMessage({ type: "error" }, err); + console.log(err); + } + //error with cache remove current manifest + manifest = null; + idbKeyval.del("current", manifestStore); + cacheStatus = CacheStatus.UNCACHED; + return fetch(event.request); + }) + ); +}); \ No newline at end of file diff --git a/dist/jakecache.js b/dist/jakecache.js index b582cbd..8bb693c 100644 --- a/dist/jakecache.js +++ b/dist/jakecache.js @@ -1,199 +1,233 @@ 'use strict'; -const _eventHandlers = Symbol('eventHandlers') +const _eventHandlers = Symbol("eventHandlers"); -let CustomEvent = window.CustomEvent -let DOMException = window.DOMException -let ErrorEvent = window.ErrorEvent -let ProgressEvent = window.ProgressEvent +let CustomEvent = window.CustomEvent; +let DOMException = window.DOMException; +let ErrorEvent = window.ErrorEvent; +let ProgressEvent = window.ProgressEvent; class PolyfilledEventTarget { - constructor (names) { - this[_eventHandlers] = {} + constructor(names) { + this[_eventHandlers] = {}; names.map(name => { - this[_eventHandlers][name] = { handler: null, listeners: [] } - Object.defineProperty(this, 'on' + name, { - get: function () { - return this[_eventHandlers][name]['handler'] + this[_eventHandlers][name] = { handler: null, listeners: [] }; + Object.defineProperty(this, "on" + name, { + get: function() { + return this[_eventHandlers][name]["handler"]; }, - set: function (fn) { + set: function(fn) { if (fn === null || fn instanceof Function) { - this[_eventHandlers][name]['handler'] = fn + this[_eventHandlers][name]["handler"] = fn; } }, enumerable: false - }) - }) + }); + }); } - dispatchEvent (event) { + dispatchEvent(event) { if (this[_eventHandlers][event.type]) { - let handlers = this[_eventHandlers][event.type] - let mainFn = handlers['handler'] + let handlers = this[_eventHandlers][event.type]; + let mainFn = handlers["handler"]; if (mainFn) { - mainFn(event) + mainFn(event); } - for (let fn of handlers['listeners']) { - fn(event) + for (let fn of handlers["listeners"]) { + fn(event); } } } - addEventListener (name, fn) { + addEventListener(name, fn) { if (this[_eventHandlers][name]) { - let store = this[_eventHandlers][name]['listeners'] - let index = store.indexOf(fn) + let store = this[_eventHandlers][name]["listeners"]; + let index = store.indexOf(fn); if (index === -1) { - store.push(fn) + store.push(fn); } } } - removeEventListener (name, fn) { + removeEventListener(name, fn) { if (this[_eventHandlers][name]) { - let store = this[_eventHandlers][name]['listeners'] - let index = store.indexOf(fn) + let store = this[_eventHandlers][name]["listeners"]; + let index = store.indexOf(fn); if (index > 0) { - store.splice(index, 1) + store.splice(index, 1); } } } } -const _status = Symbol('status') +const _status = Symbol("status"); class JakeCache extends PolyfilledEventTarget { - constructor () { - super(['abort', 'cached', 'checking', - 'downloading', 'error', 'obsolete', - 'progress', 'updateready', 'noupdate']) + constructor() { + super([ + "abort", + "cached", + "checking", + "downloading", + "error", + "obsolete", + "progress", + "updateready", + "updated", + "noupdate" + ]); if (window.jakeCache) { - return window.jakeCache + return window.jakeCache; } - window.jakeCache = this - - if (('serviceWorker' in navigator) === false) { - return + window.jakeCache = this; + + if ("serviceWorker" in navigator === false) { + return; } let onload = () => { - if (document.readyState !== 'complete') { - return + if (document.readyState !== "complete") { + return; } - let html = document.querySelector('html') - this.pathname = html.getAttribute('manifest') - - if (this.pathname && 'serviceWorker' in navigator) { - navigator.serviceWorker.register('jakecache-sw.js').then(registration => { - console.log(`JakeCache installed for ${registration.scope}`) - - if (registration.active) { - // Check whether we have a cache, or cache it (no reload enforced). - console.log('cache check') - registration.active.postMessage({ - command: 'update', - pathname: this.pathname - }) - } - }).catch(err => { - console.log(`JakeCache installation failed: ${err}`) - }) + let html = document.querySelector("html"); + this.pathname = html.getAttribute("manifest"); + + if (this.pathname && "serviceWorker" in navigator) { + navigator.serviceWorker + .register("jakecache-sw.js") + .then(registration => { + console.log(`JakeCache installed for ${registration.scope}`); + + if (registration.active) { + // Check whether we have a cache, or cache it (no reload enforced). + console.log("cache check"); + registration.active.postMessage({ + command: "update", + pathname: this.pathname + }); + } + }) + .catch(err => { + console.log(`JakeCache installation failed: ${err}`); + }); } - } + }; - if (document.readyState === 'complete') { - onload() + if (document.readyState === "complete") { + onload(); } else { - document.onreadystatechange = onload + document.onreadystatechange = onload; } - this[_status] = this.UNCACHED + this[_status] = this.UNCACHED; - navigator.serviceWorker.addEventListener('message', event => { + navigator.serviceWorker.addEventListener("message", event => { switch (event.data.type) { - case 'abort': - this.dispatchEvent(new CustomEvent('abort')) - break - case 'idle': - this[_status] = this.IDLE - break - case 'checking': - this[_status] = this.CHECKING - this.dispatchEvent(new CustomEvent('checking')) - break - case 'cached': - this[_status] = this.IDLE - this.dispatchEvent(new CustomEvent('cached')) - break - case 'downloading': - this[_status] = this.DOWNLOADING - this.dispatchEvent(new CustomEvent('downloading')) - break - case 'updateready': - this[_status] = this.UPDATEREADY - this.dispatchEvent(new CustomEvent('updateready')) - break - case 'noupdate': - this[_status] = this.IDLE - this.dispatchEvent(new CustomEvent('noupdate')) - break - case 'progress': - this.dispatchEvent(new ProgressEvent('progress', event.data)) - break - case 'obsolete': - this[_status] = this.OBSOLETE - this.dispatchEvent(new CustomEvent('obsolete')) - break - case 'error': - this.dispatchEvent(new ErrorEvent('error', event.data)) - break + case "abort": + this.dispatchEvent(new CustomEvent("abort")); + break; + case "idle": + this[_status] = this.IDLE; + break; + case "checking": + this[_status] = this.CHECKING; + this.dispatchEvent(new CustomEvent("checking")); + break; + case "cached": + this[_status] = this.IDLE; + this.dispatchEvent(new CustomEvent("cached")); + break; + case "downloading": + this[_status] = this.DOWNLOADING; + this.dispatchEvent(new CustomEvent("downloading")); + break; + case "updateready": + this[_status] = this.UPDATEREADY; + this.dispatchEvent(new CustomEvent("updateready")); + break; + case "updated": + this[_status] = this.UPDATED; + this.dispatchEvent(new CustomEvent("updated")); + break; + case "noupdate": + this[_status] = this.IDLE; + this.dispatchEvent(new CustomEvent("noupdate")); + break; + case "progress": + let ev = new ProgressEvent("progress", event.data); + ev.url = event.data.srcElement; + this.dispatchEvent(ev); + break; + case "obsolete": + this[_status] = this.OBSOLETE; + this.dispatchEvent(new CustomEvent("obsolete")); + break; + case "error": + this.dispatchEvent(new ErrorEvent("error", event.data)); + break; } - }) + }); } - get UNCACHED () { return 0 } - get IDLE () { return 1 } - get CHECKING () { return 2 } - get DOWNLOADING () { return 3 } - get UPDATEREADY () { return 4 } - get OBSOLETE () { return 5 } - - get status () { - return this[_status] + get UNCACHED() { + return 0; + } + get IDLE() { + return 1; + } + get CHECKING() { + return 2; + } + get DOWNLOADING() { + return 3; + } + get UPDATEREADY() { + return 4; + } + get OBSOLETE() { + return 5; + } + get UPDATED() { + return 6; + } + get status() { + return this[_status]; } - update () { + update() { if (false) {} navigator.serviceWorker.controller.postMessage({ - command: 'update', + command: "update", pathname: this.pathname, options: { - cache: 'reload' + cache: "reload" } - }) + }); } - abort () { + abort() { if (this.status === this.DOWNLOADING) { navigator.serviceWorker.controller.postMessage({ - command: 'abort' - }) + command: "abort" + }); } } - swapCache () { + swapCache() { if (this.status !== this.UPDATEREADY) { - throw new DOMException(DOMException.INVALID_STATE_ERR, - 'there is no newer application cache to swap to.') + throw new DOMException( + DOMException.INVALID_STATE_ERR, + "there is no newer application cache to swap to." + ); } navigator.serviceWorker.controller.postMessage({ - command: 'swapCache' - }) + command: "swapCache" + }); } } -new JakeCache() \ No newline at end of file +new JakeCache(); \ No newline at end of file diff --git a/jakecache-sw.js b/jakecache-sw.js index 72d5c52..f16f55b 100644 --- a/jakecache-sw.js +++ b/jakecache-sw.js @@ -1,133 +1,204 @@ -import { md5 } from './lib/md5' +import { md5 } from "./lib/md5"; +self.importScripts("idb-keyval-iife.js"); -class JakeCacheManifest { +const manifestStore = new idbKeyval.Store("manifest-db", "manifest-db"); - constructor () { - this._path = null - this._hash = null - this._isValid = false - this._fetchOptions = { credentials: "same-origin" } +class JakeCacheManifest { + constructor(data) { + this._path = null; + this._hash = null; + this._isValid = false; + this._fetchOptions = { credentials: "same-origin" }; + this._rawData = { + version: "", + cache: [], + fallback: [], + network: [] + }; + + if(data){ + this.restoreManifest(data); + } } - groupName () { - let filename = this._path.substring(this._path.lastIndexOf('/') + 1) - return filename + hash() { + return this._hash; + } + isValid() { + return this._isValid; + } + manifestData() { + return { + cacheName: this.cacheName(), + path: this._path, + hash : this._hash, + isValid : this._isValid, + rawData: this._rawData + }; } - fetchData (path, options = {}) { - this._path = path + restoreManifest(manifestData) { + if(!manifestData){ + this._isValid = false; + return; + } + this._path = manifestData.path; + this._hash = manifestData.hash; + this._rawData = manifestData.rawData - if (this._isValid && options.cache !== 'reload') { - return Promise.resolve(false) + this.restoreCache(); + } + + restoreCache() { + + this.cache = ["jakecache.js"]; + // Ignore different protocol + for (let pathname of this._rawData.cache) { + let path = new URL(pathname, location); + if (path.protocol === location.protocol) { + this.cache.push(path); + } } - // http://html5doctor.com/go-offline-with-application-cache/ - return fetch(new Request(this._path, options), this._fetchOptions).then((response) => { - if (response.type === 'opaque' || response.status === 404 || response.status === 410) { - return Promise.reject() + this.fallback = []; + for (let entry of this._rawData.fallback) { + let [pathname, fallbackPath] = entry.split(" "); + let path = new URL(pathname, location); + let fallback = new URL(fallbackPath, location); + + // Ignore cross-origin fallbacks + if (path.origin === fallback.origin) { + this.fallback.push([path, fallback]); + this.cache.push(fallback); } + } - this._rawData = { - cache: [], - fallback: [], - network: [] + this.allowNetworkFallback = false; + this.network = []; + for (let entry of this._rawData.network) { + if (entry === "*") { + this.allowNetworkFallback = true; + continue; + } + let path = new URL(entry, location); + if (path.protocol === location.protocol) { + this.network.push(path); } + } - return response.text().then((result) => { - return new Promise((resolve, reject) => { - let hash = md5(result) - if (this._hash && hash.toString() === this._hash.toString()) { - console.log('noupdate: ' + hash) - return resolve(false) - } - this._hash = hash - console.log(`update: ${hash} (was: ${this._hash})`) + this._isValid = true; + + } - let lines = result.split(/\r|\n/) - let header = 'cache' // default. + + pathName() { + return this._path; + } - let firstLine = lines.shift() - if (firstLine !== 'CACHE MANIFEST') { - return reject() - } + cacheName() { + let version = this._rawData.version; + return version + '_' + this._hash; + } - for (let line of lines) { - line = line.replace(/#.*$/, '').trim() + fetchData(path, options = {}) { + this._path = path; - if (line === '') { - continue - } + if (this._isValid && options.cache !== "reload") { + return Promise.resolve(false); + } - let res = line.match(/^([A-Z]*):/) - if (res) { - header = res[1].toLowerCase() - continue - } + // http://html5doctor.com/go-offline-with-application-cache/ + return fetch(new Request(this._path, options), this._fetchOptions).then( + response => { + if ( + response.type === "opaque" || + response.status === 404 || + response.status === 410 + ) { + return Promise.reject(); + } - if (!this._rawData[header]) { - this._rawData[header] = [] + this._hash = options.hash ? options.hash : this._hash; + + return response.text().then(result => { + return new Promise((resolve, reject) => { + let hash = md5(result); + if (this._hash && hash.toString() === this._hash.toString()) { + console.log("noupdate: " + hash); + return resolve(false); } - this._rawData[header].push(line) - } - this.cache = ['jakecache.js'] - // Ignore different protocol - for (let pathname of this._rawData.cache) { - let path = new URL(pathname, location) - if (path.protocol === location.protocol) { - this.cache.push(path) - } - } + console.log(`update: ${hash} (was: ${this._hash})`); - this.fallback = [] - for (let entry of this._rawData.fallback) { - let [pathname, fallbackPath] = entry.split(' ') - let path = new URL(pathname, location) - let fallback = new URL(fallbackPath, location) + this._hash = hash; - // Ignore cross-origin fallbacks - if (path.origin === fallback.origin) { - this.fallback.push([path, fallback]) - this.cache.push(fallback) - } - } + let lines = result.split(/\r|\n/); + let header = "cache"; // default. + let versionRegexp = /\s*(#\sVersion:)\s*([\w\.]*)/gm; - this.allowNetworkFallback = false - this.network = [] - for (let entry of this._rawData.network) { - if (entry === '*') { - this.allowNetworkFallback = true - continue + let firstLine = lines.shift(); + if (firstLine !== "CACHE MANIFEST") { + return reject(); } - let path = new URL(entry, location) - if (path.protocol === location.protocol) { - this.network.push(path) + let versionFound = false; + for (let line of lines) { + if (!versionFound) { + let match = versionRegexp.exec(line); + if (match) { + versionFound = true; + this._rawData.version = match[match.length - 1]; + } + } + + line = line.replace(/#.*$/, "").trim(); + + if (line === "") { + continue; + } + + let res = line.match(/^([A-Z]*):/); + if (res) { + header = res[1].toLowerCase(); + continue; + } + + if (!this._rawData[header]) { + this._rawData[header] = []; + } + this._rawData[header].push(line); } - } - this._isValid = true - resolve(true) - }) - }) - }) + if (!versionFound) { + this._rawData.version = "" + new Date().getTime(); + } + + this.restoreCache(); + resolve(true); + }); + }); + } + ); } } -self.addEventListener('message', function (event) { +self.addEventListener("message", function(event) { switch (event.data.command) { - case 'update': - update.call(this, event.data.pathname, event.data.options) - break - case 'abort': - postMessage({ type: 'error', message: 'Not implementable without cancellable promises.' }) - break - case 'swapCache': - swapCache() - break + case "update": + update.call(this, event.data.pathname, event.data.options); + break; + case "abort": + postMessage({ + type: "error", + message: "Not implementable without cancellable promises." + }); + break; + case "swapCache": + swapCache(); + break; } -}) +}); -let manifest = new JakeCacheManifest() +let manifest = null; const CacheStatus = { UNCACHED: 0, @@ -136,224 +207,377 @@ const CacheStatus = { DOWNLOADING: 3, UPDATEREADY: 4, OBSOLETE: 5 -} +}; -let cacheStatus = CacheStatus.UNCACHED +let cacheStatus = CacheStatus.UNCACHED; -function postMessage (msg) { +function postMessage(msg) { return self.clients.matchAll().then(clients => { - return Promise.all(clients.map(client => { - return client.postMessage(msg) - })) - }) + return Promise.all( + clients.map(client => { + return client.postMessage(msg); + }) + ); + }); } -function swapCache () { - caches.keys().then(keyList => { - return Promise.all(keyList.map(key => { - return caches.delete(key) - })) +function swapCache() { + idbKeyval.get('current', manifestStore).then(mnfstData => { + if(mnfstData){ + return caches.delete(mnfstData.cacheName); + } + }).then(() => { + return idbKeyval.get('next', manifestStore) + }).then(mnfstData => { + if(mnfstData){ + manifest = new JakeCacheManifest(mnfstData); + + return idbKeyval.set('current', mnfstData, manifestStore); + }else{ + cacheStatus = CacheStatus.UNCACHED; + } }).then(() => { - // FIXME: Add new keys. - }) + return idbKeyval.del('next', manifestStore); + }).then(_ => { + cacheStatus = CacheStatus.IDLE; + postMessage({ type: "updated" }); + }); } // 7.9.4 -function update (pathname, options = {}) { +function update(pathname, options = {}) { if (!pathname) { - console.log('No pathname!') - return Promise.reject() + console.log("No pathname!"); + return Promise.reject(); } + + let nextManifest = new JakeCacheManifest(); // *.2.2 - this.options = options - this.cacheGroup = pathname - - return caches.keys().then(cacheNames => { - this.uncached = !cacheNames.length - console.log('uncached ' + this.uncached) - return Promise.resolve(this.uncached) - }).then((uncached) => { - if (this.options.cache !== 'reload' && !uncached) { - // We have a cache and we are no doing an update check. - return Promise.reject() - } + this.options = options; + + return idbKeyval.get('current', manifestStore).then(mnfstData => { + if(!mnfstData){ + manifest = null; + this.uncached = true; + cacheStatus = CacheStatus.UNCACHED; + console.log("uncached " + this.uncached); + return Promise.resolve(this.uncached); + }else{ + manifest = new JakeCacheManifest(mnfstData); + this.options = this.options || {}; + this.options.hash = manifest.hash(); + } - // *.2.4 and *.2.6 - if (cacheStatus === CacheStatus.CHECKING) { - postMessage({ type: 'checking' }) - postMessage({ type: 'abort' }) - return Promise.reject() - } - // *.2.4, *.2.5, *.2.6 - if (cacheStatus === CacheStatus.DOWNLOADING) { - postMessage({ type: 'checking' }) - postMessage({ type: 'downloading' }) - postMessage({ type: 'abort' }) - return Promise.reject() - } - return Promise.resolve() - }).then(() => { - // *.2.7 and *.2.8 - cacheStatus = CacheStatus.CHECKING - postMessage({ type: 'checking' }) - - // FIXME: *.6: Fetch manifest, mark obsolete if fails. - return manifest.fetchData(this.cacheGroup, this.options).catch(err => { - cacheStatus = CacheStatus.OBSOLETE - postMessage({ type: 'obsolete' }) - // FIXME: *.7: Error for each existing entry. - cacheStatus = CacheStatus.IDLE - postMessage({ type: 'idle' }) - return Promise.reject(err) + return caches.open(mnfstData.cacheName).then(cache => { + if(!cache){ + manifest = null; + this.uncached = true; + cacheStatus = CacheStatus.UNCACHED; + console.log("uncached " + this.uncached); + return Promise.resolve(this.uncached); + } + + return cache.keys().then(keyData => { + this.uncached = !keyData || !keyData.length; + if(this.uncached){ + manifest = null; + cacheStatus = CacheStatus.UNCACHED; + } + console.log("uncached " + this.uncached); + return Promise.resolve(this.uncached); + }) + }) }) - }).then(modified => { - this.modified = modified - // *.2: If cache group already has an application cache in it, then - // this is an upgrade attempt. Otherwise, this is a cache attempt. - return caches.keys().then(cacheNames => { - return Promise.resolve(!!cacheNames.length) + .then(uncached => { + + if (cacheStatus === CacheStatus.UPDATEREADY) { + postMessage({ type: "updateready" }); + postMessage({ type: "abort" }); + return Promise.reject(); + } + // *.2.4 and *.2.6 + if (cacheStatus === CacheStatus.CHECKING) { + postMessage({ type: "checking" }); + postMessage({ type: "abort" }); + return Promise.reject(); + } + // *.2.4, *.2.5, *.2.6 + if (cacheStatus === CacheStatus.DOWNLOADING) { + postMessage({ type: "checking" }); + postMessage({ type: "downloading" }); + postMessage({ type: "abort" }); + return Promise.reject(); + } + return Promise.resolve(uncached); }) - }).then(upgrade => { - this.upgrade = upgrade - if (this.upgrade && !this.modified) { - cacheStatus = CacheStatus.IDLE - postMessage({ type: 'noupdate' }) - return Promise.reject() - } - - // Appcache is no-cors by default. - this.requests = manifest.cache.map(url => { - return new Request(url, { mode: 'no-cors' }) + .then(uncached => { + // *.2.7 and *.2.8 + cacheStatus = CacheStatus.CHECKING; + postMessage({ type: "checking" }); + + // FIXME: *.6: Fetch manifest, mark obsolete if fails. + + + return nextManifest.fetchData(pathname, this.options).catch(err => { + cacheStatus = CacheStatus.OBSOLETE; + postMessage({ type: "obsolete" }); + // FIXME: *.7: Error for each existing entry. + cacheStatus = CacheStatus.IDLE; + postMessage({ type: "idle" }); + return Promise.reject(err); + }); + + + }).then(modified => { + this.modified = modified; + // *.2: If cache group already has an application cache in it, then + // this is an upgrade attempt. Otherwise, this is a cache attempt. + return caches.keys().then(cacheNames => { + return Promise.resolve(!!cacheNames.length); + }); }) + .then(upgrade => { + this.upgrade = upgrade; + if (this.upgrade && !this.modified) { + cacheStatus = CacheStatus.IDLE; + postMessage({ type: "noupdate" }); + return Promise.reject(); + } - cacheStatus = CacheStatus.DOWNLOADING - postMessage({ type: 'downloading' }) - - this.loaded = 0 - this.total = this.requests.length - - return Promise.all(this.requests.map(request => { - // Manual fetch to emulate appcache behavior. - return fetch(request, manifest._fetchOptions).then(response => { - cacheStatus = CacheStatus.PROGRESS - postMessage({ - type: 'progress', - lengthComputable: true, - loaded: ++(this.loaded), - total: this.total - }) - - // section 5.6.4 of http://www.w3.org/TR/2011/WD-html5-20110525/offline.html + // Appcache is no-cors by default. + this.requests = nextManifest.cache.map(url => { + return new Request(url, { mode: "no-cors" }); + }); + + cacheStatus = CacheStatus.DOWNLOADING; + postMessage({ type: "downloading" }); + + this.loaded = 0; + this.total = this.requests.length; + + return Promise.all( + this.requests.map(request => { + // Manual fetch to emulate appcache behavior. + return fetch(request, nextManifest._fetchOptions).then(response => { + cacheStatus = CacheStatus.PROGRESS; + postMessage({ + type: "progress", + lengthComputable: true, + loaded: ++this.loaded, + total: this.total, + url: request.url.toString() + }); + + // section 5.6.4 of http://www.w3.org/TR/2011/WD-html5-20110525/offline.html + + // Redirects are fatal. + if (response.url !== request.url) { + throw Error(); + } - // Redirects are fatal. - if (response.url !== request.url) { - throw Error() - } + // FIXME: should we update this.total below? - // FIXME: should we update this.total below? + if (response.type !== "opaque") { + // If the error was a 404 or 410 HTTP response or equivalent + // Skip this resource. It is dropped from the cache. + if (response.status < 200 || response.status >= 300) { + return undefined; + } - if (response.type !== 'opaque') { - // If the error was a 404 or 410 HTTP response or equivalent - // Skip this resource. It is dropped from the cache. - if (response.status < 200 || response.status >= 300) { - return undefined - } + // HTTP caching rules, such as Cache-Control: no-store, are ignored. + if ( + (response.headers.get("cache-control") || "").match(/no-store/i) + ) { + return undefined; + } + } - // HTTP caching rules, such as Cache-Control: no-store, are ignored. - if ((response.headers.get('cache-control') || '').match(/no-store/i)) { - return undefined + return response; + }); + }) + ); + }).then(responses => { + this.responses = responses.filter(response => response); + return Promise.resolve(this.responses); + }) + .then(responses => { + console.log("Adding to cache " + nextManifest.cacheName()); + return caches + .open(nextManifest.cacheName()) + .then(cache => { + return Promise.all( + responses.map((response, index) => { + return cache.put(self.requests[index], response); + }) + ); + }).then(_ => { + let manifestVersion = 'next'; + if(!this.upgrade){ + manifest = nextManifest; + manifestVersion = 'current'; } - } - return response - }) - })) - }).then(responses => { - this.responses = responses.filter(response => response) - if (this.upgrade) { - cacheStatus = CacheStatus.UPDATEREADY - postMessage({ type: 'updateready' }) - return Promise.reject() - } else { - return Promise.resolve(this.responses) - } - }).then(responses => { - console.log('Adding to cache ' + manifest.groupName()) - return caches.open(manifest.groupName()).then(cache => { - return Promise.all(responses.map((response, index) => { - return cache.put(self.requests[index], response) - })) - }).then(_ => { - cacheStatus = CacheStatus.CACHED - postMessage({ type: 'cached' }) + return idbKeyval.set(manifestVersion, nextManifest.manifestData(), manifestStore); + }); + }).then(_ => { + if (this.upgrade) + { + cacheStatus = CacheStatus.UPDATEREADY; + postMessage({ type: "updateready" }); + } else { + cacheStatus = CacheStatus.CACHED; + postMessage({ type: "cached" }); + } + return Promise.resolve(); }) - }).catch(err => { - if (err) { - postMessage({ type: 'error' }, err) - console.log(err) - } - }) + .catch(err => { + if (err) { + postMessage({ type: "error" }, err); + console.log(err); + } + }); } -self.addEventListener('install', function (event) { - event.waitUntil(self.skipWaiting()) -}) +self.addEventListener("install", function(event) { + event.waitUntil( + self.skipWaiting() + ); +}); -self.addEventListener('activate', function (event) { - event.waitUntil(self.clients.claim()) -}) +self.addEventListener("activate", function(event) { + + event.waitUntil( + idbKeyval.get('current', manifestStore).then(mnfstData => { + if(mnfstData){ + manifest = new JakeCacheManifest(mnfstData); + cacheStatus = CacheStatus.CACHED; + } + return Promise.resolve(manifest); + }).then(mnfst => { + if(mnfst){ + return update(mnfst.pathName(), { cache : "reload" }); + } + return Promise.resolve(); + }).then(self.clients.claim()) + ); +}); + +self.addEventListener("fetch", function(event) { + if(manifest && manifest.isValid() && cacheStatus === CacheStatus.UNCACHED){ + cacheStatus = CacheStatus.CACHED; + } -self.addEventListener('fetch', function (event) { if (cacheStatus === CacheStatus.UNCACHED) { - return fetch(event.request) + return fetch(event.request); } - let url = new URL(event.request.url) + let url = new URL(event.request.url); // Ignore non-GET and different schemes. - if (event.request.method !== 'GET' || url.scheme !== location.scheme) { - return + if (!event.request.url.startsWith(self.location.origin) || + event.request.method !== "GET" || url.scheme !== location.scheme) { + return; } - // FIXME: Get data from IndexedDB instead. - event.respondWith(manifest.fetchData('test.manifest').then(_ => { - // Process network-only. - if (manifest.network.filter(entry => entry.href === url.href).length) { - return fetch(event.request) + // if (!event.request.url.startsWith(self.location.origin)) { + // // External request, or POST, ignore + // return void event.respondWith(fetch(event.request)); + // } + + // FIXME TEST: Get data from IndexedDB instead. + let mnfstPromise = manifest + ? Promise.resolve(manifest) + : idbKeyval.get("current", manifestStore).then(mnfstData => { + if (mnfstData) { + manifest = new JakeCacheManifest(mnfstData); + } + return manifest; + }); + + function checkCache(mnfst) + { + return caches + .open(mnfst.cacheName()) + .then(function (cache) { + if(!cache) + { + throw Error('Cache not found'); + } + + return cache.match(event.request).then(response => { + // Cache always wins. + if (response) { + return response; + } + + // Fallbacks consult network, and falls back on failure. + for (let [path, fallback] of mnfst.fallback) { + if (url.href.indexOf(path) === 0) { + return fetch(event.request) + .then(response => { + // Same origin only. + if (new URL(response.url).origin !== location.origin) { + throw Error(); + } + + if (response.type !== "opaque") { + if (response.status < 200 || response.status >= 300) { + throw Error(); + } + } + }) + .catch(_ => { + return cache.match(fallback); + }); + } + } + + if (mnfst.allowNetworkFallback) { + return fetch(event.request); + } + + return response; // failure. + }); + }) + .catch(err => { + if (err) { + postMessage({ type: "error" }, err); + console.log(err); + } + //error with cache remove current manifest + manifest = null; + idbKeyval.del("current", manifestStore); + cacheStatus = CacheStatus.UNCACHED; + return fetch(event.request); + }); } - return caches.match(event.request).then(response => { - // Cache always wins. - if (response) { - return response - } - - // Fallbacks consult network, and falls back on failure. - for (let [path, fallback] of manifest.fallback) { - if (url.href.indexOf(path) === 0) { - return fetch(event.request).then(response => { - // Same origin only. - if (new URL(response.url).origin !== location.origin) { - throw Error() - } - - if (response.type !== 'opaque') { - if (response.status < 200 || response.status >= 300) { - throw Error() - } - } - }).catch(_ => { - return cache.match(fallback) - }) + event.respondWith( + mnfstPromise + .then(mnfst => { + if (!mnfst) { + cacheStatus = CacheStatus.UNCACHED; + return fetch(event.request); } - } - if (manifest.allowNetworkFallback) { - return fetch(event.request) - } + // Process network-only. + if (mnfst.network.filter(entry => entry.href === url.href).length) { + return fetch(event.request); + } - return response // failure. - }) - })) -}) + return checkCache(mnfst); + }) + .catch(err => { + if (err) { + postMessage({ type: "error" }, err); + console.log(err); + } + //error with cache remove current manifest + manifest = null; + idbKeyval.del("current", manifestStore); + cacheStatus = CacheStatus.UNCACHED; + return fetch(event.request); + }) + ); +}); diff --git a/jakecache.js b/jakecache.js index 6807f83..dfed427 100644 --- a/jakecache.js +++ b/jakecache.js @@ -1,202 +1,239 @@ -const _eventHandlers = Symbol('eventHandlers') +const _eventHandlers = Symbol("eventHandlers"); -let CustomEvent = window.CustomEvent -let DOMException = window.DOMException -let ErrorEvent = window.ErrorEvent -let ProgressEvent = window.ProgressEvent +let CustomEvent = window.CustomEvent; +let DOMException = window.DOMException; +let ErrorEvent = window.ErrorEvent; +let ProgressEvent = window.ProgressEvent; class PolyfilledEventTarget { - constructor (names) { - this[_eventHandlers] = {} + constructor(names) { + this[_eventHandlers] = {}; names.map(name => { - this[_eventHandlers][name] = { handler: null, listeners: [] } - Object.defineProperty(this, 'on' + name, { - get: function () { - return this[_eventHandlers][name]['handler'] + this[_eventHandlers][name] = { handler: null, listeners: [] }; + Object.defineProperty(this, "on" + name, { + get: function() { + return this[_eventHandlers][name]["handler"]; }, - set: function (fn) { + set: function(fn) { if (fn === null || fn instanceof Function) { - this[_eventHandlers][name]['handler'] = fn + this[_eventHandlers][name]["handler"] = fn; } }, enumerable: false - }) - }) + }); + }); } - dispatchEvent (event) { + dispatchEvent(event) { if (this[_eventHandlers][event.type]) { - let handlers = this[_eventHandlers][event.type] - let mainFn = handlers['handler'] + let handlers = this[_eventHandlers][event.type]; + let mainFn = handlers["handler"]; if (mainFn) { - mainFn(event) + mainFn(event); } - for (let fn of handlers['listeners']) { - fn(event) + for (let fn of handlers["listeners"]) { + fn(event); } } } - addEventListener (name, fn) { + addEventListener(name, fn) { if (this[_eventHandlers][name]) { - let store = this[_eventHandlers][name]['listeners'] - let index = store.indexOf(fn) + let store = this[_eventHandlers][name]["listeners"]; + let index = store.indexOf(fn); if (index === -1) { - store.push(fn) + store.push(fn); } } } - removeEventListener (name, fn) { + removeEventListener(name, fn) { if (this[_eventHandlers][name]) { - let store = this[_eventHandlers][name]['listeners'] - let index = store.indexOf(fn) + let store = this[_eventHandlers][name]["listeners"]; + let index = store.indexOf(fn); if (index > 0) { - store.splice(index, 1) + store.splice(index, 1); } } } } -const _status = Symbol('status') +const _status = Symbol("status"); class JakeCache extends PolyfilledEventTarget { - constructor () { - super(['abort', 'cached', 'checking', - 'downloading', 'error', 'obsolete', - 'progress', 'updateready', 'noupdate']) + constructor() { + super([ + "abort", + "cached", + "checking", + "downloading", + "error", + "obsolete", + "progress", + "updateready", + "updated", + "noupdate" + ]); if (window.jakeCache) { - return window.jakeCache + return window.jakeCache; } - window.jakeCache = this - - if (('serviceWorker' in navigator) === false) { - return + window.jakeCache = this; + + if ("serviceWorker" in navigator === false) { + return; } let onload = () => { - if (document.readyState !== 'complete') { - return + if (document.readyState !== "complete") { + return; } - let html = document.querySelector('html') - this.pathname = html.getAttribute('manifest') - - if (this.pathname && 'serviceWorker' in navigator) { - navigator.serviceWorker.register('jakecache-sw.js').then(registration => { - console.log(`JakeCache installed for ${registration.scope}`) - - if (registration.active) { - // Check whether we have a cache, or cache it (no reload enforced). - console.log('cache check') - registration.active.postMessage({ - command: 'update', - pathname: this.pathname - }) - } - }).catch(err => { - console.log(`JakeCache installation failed: ${err}`) - }) + let html = document.querySelector("html"); + this.pathname = html.getAttribute("manifest"); + + if (this.pathname && "serviceWorker" in navigator) { + navigator.serviceWorker + .register("jakecache-sw.js") + .then(registration => { + console.log(`JakeCache installed for ${registration.scope}`); + + if (registration.active) { + // Check whether we have a cache, or cache it (no reload enforced). + console.log("cache check"); + registration.active.postMessage({ + command: "update", + pathname: this.pathname + }); + } + }) + .catch(err => { + console.log(`JakeCache installation failed: ${err}`); + }); } - } + }; - if (document.readyState === 'complete') { - onload() + if (document.readyState === "complete") { + onload(); } else { - document.onreadystatechange = onload + document.onreadystatechange = onload; } - this[_status] = this.UNCACHED + this[_status] = this.UNCACHED; - navigator.serviceWorker.addEventListener('message', event => { + navigator.serviceWorker.addEventListener("message", event => { switch (event.data.type) { - case 'abort': - this.dispatchEvent(new CustomEvent('abort')) - break - case 'idle': - this[_status] = this.IDLE - break - case 'checking': - this[_status] = this.CHECKING - this.dispatchEvent(new CustomEvent('checking')) - break - case 'cached': - this[_status] = this.IDLE - this.dispatchEvent(new CustomEvent('cached')) - break - case 'downloading': - this[_status] = this.DOWNLOADING - this.dispatchEvent(new CustomEvent('downloading')) - break - case 'updateready': - this[_status] = this.UPDATEREADY - this.dispatchEvent(new CustomEvent('updateready')) - break - case 'noupdate': - this[_status] = this.IDLE - this.dispatchEvent(new CustomEvent('noupdate')) - break - case 'progress': - this.dispatchEvent(new ProgressEvent('progress', event.data)) - break - case 'obsolete': - this[_status] = this.OBSOLETE - this.dispatchEvent(new CustomEvent('obsolete')) - break - case 'error': - this.dispatchEvent(new ErrorEvent('error', event.data)) - break + case "abort": + this.dispatchEvent(new CustomEvent("abort")); + break; + case "idle": + this[_status] = this.IDLE; + break; + case "checking": + this[_status] = this.CHECKING; + this.dispatchEvent(new CustomEvent("checking")); + break; + case "cached": + this[_status] = this.IDLE; + this.dispatchEvent(new CustomEvent("cached")); + break; + case "downloading": + this[_status] = this.DOWNLOADING; + this.dispatchEvent(new CustomEvent("downloading")); + break; + case "updateready": + this[_status] = this.UPDATEREADY; + this.dispatchEvent(new CustomEvent("updateready")); + break; + case "updated": + this[_status] = this.UPDATED; + this.dispatchEvent(new CustomEvent("updated")); + break; + case "noupdate": + this[_status] = this.IDLE; + this.dispatchEvent(new CustomEvent("noupdate")); + break; + case "progress": + let ev = new ProgressEvent("progress", event.data); + ev.url = event.data.srcElement; + this.dispatchEvent(ev); + break; + case "obsolete": + this[_status] = this.OBSOLETE; + this.dispatchEvent(new CustomEvent("obsolete")); + break; + case "error": + this.dispatchEvent(new ErrorEvent("error", event.data)); + break; } - }) + }); } - get UNCACHED () { return 0 } - get IDLE () { return 1 } - get CHECKING () { return 2 } - get DOWNLOADING () { return 3 } - get UPDATEREADY () { return 4 } - get OBSOLETE () { return 5 } - - get status () { - return this[_status] + get UNCACHED() { + return 0; + } + get IDLE() { + return 1; + } + get CHECKING() { + return 2; + } + get DOWNLOADING() { + return 3; + } + get UPDATEREADY() { + return 4; + } + get OBSOLETE() { + return 5; + } + get UPDATED() { + return 6; + } + get status() { + return this[_status]; } - update () { - if (false) {// this.status == this.UNCACHED || this.status == this.OBSOLETE) { + update() { + if (false) { + // this.status == this.UNCACHED || this.status == this.OBSOLETE) { // If there is no such application cache, or if its // application cache group is marked as obsolete, then throw - throw new DOMException(DOMException.INVALID_STATE_ERR, - 'there is no application cache to update.') + throw new DOMException( + DOMException.INVALID_STATE_ERR, + "there is no application cache to update." + ); } navigator.serviceWorker.controller.postMessage({ - command: 'update', + command: "update", pathname: this.pathname, options: { - cache: 'reload' + cache: "reload" } - }) + }); } - abort () { + abort() { if (this.status === this.DOWNLOADING) { navigator.serviceWorker.controller.postMessage({ - command: 'abort' - }) + command: "abort" + }); } } - swapCache () { + swapCache() { if (this.status !== this.UPDATEREADY) { - throw new DOMException(DOMException.INVALID_STATE_ERR, - 'there is no newer application cache to swap to.') + throw new DOMException( + DOMException.INVALID_STATE_ERR, + "there is no newer application cache to swap to." + ); } navigator.serviceWorker.controller.postMessage({ - command: 'swapCache' - }) + command: "swapCache" + }); } } -new JakeCache() +new JakeCache(); diff --git a/lib/idb-keyval-cjs.js b/lib/idb-keyval-cjs.js new file mode 100644 index 0000000..17bdcb1 --- /dev/null +++ b/lib/idb-keyval-cjs.js @@ -0,0 +1,73 @@ +'use strict'; + +Object.defineProperty(exports, '__esModule', { value: true }); + +class Store { + constructor(dbName = 'keyval-store', storeName = 'keyval') { + this.storeName = storeName; + this._dbp = new Promise((resolve, reject) => { + const openreq = indexedDB.open(dbName, 1); + openreq.onerror = () => reject(openreq.error); + openreq.onsuccess = () => resolve(openreq.result); + // First time setup: create an empty object store + openreq.onupgradeneeded = () => { + openreq.result.createObjectStore(storeName); + }; + }); + } + _withIDBStore(type, callback) { + return this._dbp.then(db => new Promise((resolve, reject) => { + const transaction = db.transaction(this.storeName, type); + transaction.oncomplete = () => resolve(); + transaction.onabort = transaction.onerror = () => reject(transaction.error); + callback(transaction.objectStore(this.storeName)); + })); + } +} +let store; +function getDefaultStore() { + if (!store) + store = new Store(); + return store; +} +function get(key, store = getDefaultStore()) { + let req; + return store._withIDBStore('readonly', store => { + req = store.get(key); + }).then(() => req.result); +} +function set(key, value, store = getDefaultStore()) { + return store._withIDBStore('readwrite', store => { + store.put(value, key); + }); +} +function del(key, store = getDefaultStore()) { + return store._withIDBStore('readwrite', store => { + store.delete(key); + }); +} +function clear(store = getDefaultStore()) { + return store._withIDBStore('readwrite', store => { + store.clear(); + }); +} +function keys(store = getDefaultStore()) { + const keys = []; + return store._withIDBStore('readonly', store => { + // This would be store.getAllKeys(), but it isn't supported by Edge or Safari. + // And openKeyCursor isn't supported by Safari. + (store.openKeyCursor || store.openCursor).call(store).onsuccess = function () { + if (!this.result) + return; + keys.push(this.result.key); + this.result.continue(); + }; + }).then(() => keys); +} + +exports.Store = Store; +exports.get = get; +exports.set = set; +exports.del = del; +exports.clear = clear; +exports.keys = keys; diff --git a/package.json b/package.json index b3dd8b1..e48def8 100644 --- a/package.json +++ b/package.json @@ -33,6 +33,11 @@ "homepage": "https://github.com/kenchris/jakecache#readme", "devDependencies": { "rollup": "^0.25.8", + "rollup-plugin-commonjs": "^10.1.0", + "rollup-plugin-node-resolve": "^5.2.0", "tape": "^4.5.1" + }, + "dependencies": { + "idb-keyval": "^3.2.0" } -} \ No newline at end of file +} diff --git a/scripts/build.js b/scripts/build.js index a874e93..df7482b 100644 --- a/scripts/build.js +++ b/scripts/build.js @@ -1,20 +1,22 @@ -var rollup = require('rollup') +var rollup = require("rollup"); +var resolve = require("rollup-plugin-node-resolve"); +var commonjs = require("rollup-plugin-commonjs"); -var files = ['jakecache-sw.js', 'jakecache.js'] +var files = ["jakecache-sw.js", "jakecache.js"]; files.forEach(function(file) { - - rollup.rollup({ - entry: file - }).then( function ( bundle ) { - - bundle.write({ - format: 'cjs', - dest: 'dist/' + file + rollup + .rollup({ + entry: file + }) + .then(function(bundle) { + bundle.write({ + format: "cjs", + dest: "dist/" + file, + plugins: [resolve(), commonjs()] + }); + }) + .catch(function(e) { + console.error(e, e.stack); }); - - }).catch(function(e) { - console.error(e, e.stack) - }) - -}) +}); From f27ba88aef56adb5c4a1add63efab2c51d82635e Mon Sep 17 00:00:00 2001 From: Sergey S Date: Tue, 17 Mar 2020 15:26:09 +0200 Subject: [PATCH 02/22] fix url in progress event --- dist/jakecache.js | 2 +- jakecache-sw.js | 2 +- jakecache.js | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/dist/jakecache.js b/dist/jakecache.js index 8bb693c..38b5399 100644 --- a/dist/jakecache.js +++ b/dist/jakecache.js @@ -158,7 +158,7 @@ class JakeCache extends PolyfilledEventTarget { break; case "progress": let ev = new ProgressEvent("progress", event.data); - ev.url = event.data.srcElement; + ev.url = event.data.url; this.dispatchEvent(ev); break; case "obsolete": diff --git a/jakecache-sw.js b/jakecache-sw.js index f16f55b..ded3037 100644 --- a/jakecache-sw.js +++ b/jakecache-sw.js @@ -254,7 +254,7 @@ function update(pathname, options = {}) { let nextManifest = new JakeCacheManifest(); // *.2.2 - this.options = options; + (this || self).options = options; return idbKeyval.get('current', manifestStore).then(mnfstData => { if(!mnfstData){ diff --git a/jakecache.js b/jakecache.js index dfed427..e431d64 100644 --- a/jakecache.js +++ b/jakecache.js @@ -156,7 +156,7 @@ class JakeCache extends PolyfilledEventTarget { break; case "progress": let ev = new ProgressEvent("progress", event.data); - ev.url = event.data.srcElement; + ev.url = event.data.url; this.dispatchEvent(ev); break; case "obsolete": From bf00c3860c9f56a8b64564525323c40101004322 Mon Sep 17 00:00:00 2001 From: Sergey S Date: Tue, 17 Mar 2020 16:34:01 +0200 Subject: [PATCH 03/22] change this to self in update function --- dist/jakecache-sw.js | 316 ++++++++++++++++++++++--------------------- jakecache-sw.js | 316 ++++++++++++++++++++++--------------------- 2 files changed, 328 insertions(+), 304 deletions(-) diff --git a/dist/jakecache-sw.js b/dist/jakecache-sw.js index 3d76492..096f88d 100644 --- a/dist/jakecache-sw.js +++ b/dist/jakecache-sw.js @@ -281,7 +281,7 @@ class JakeCacheManifest { network: [] }; - if(data){ + if (data) { this.restoreManifest(data); } } @@ -296,26 +296,25 @@ class JakeCacheManifest { return { cacheName: this.cacheName(), path: this._path, - hash : this._hash, - isValid : this._isValid, + hash: this._hash, + isValid: this._isValid, rawData: this._rawData }; } restoreManifest(manifestData) { - if(!manifestData){ + if (!manifestData) { this._isValid = false; return; } this._path = manifestData.path; this._hash = manifestData.hash; - this._rawData = manifestData.rawData + this._rawData = manifestData.rawData; this.restoreCache(); } restoreCache() { - this.cache = ["jakecache.js"]; // Ignore different protocol for (let pathname of this._rawData.cache) { @@ -352,17 +351,15 @@ class JakeCacheManifest { } this._isValid = true; - } - pathName() { return this._path; } cacheName() { let version = this._rawData.version; - return version + '_' + this._hash; + return version + "_" + this._hash; } fetchData(path, options = {}) { @@ -384,7 +381,7 @@ class JakeCacheManifest { } this._hash = options.hash ? options.hash : this._hash; - + return response.text().then(result => { return new Promise((resolve, reject) => { let hash = md5(result); @@ -436,7 +433,7 @@ class JakeCacheManifest { if (!versionFound) { this._rawData.version = "" + new Date().getTime(); } - + this.restoreCache(); resolve(true); }); @@ -487,26 +484,32 @@ function postMessage(msg) { } function swapCache() { - idbKeyval.get('current', manifestStore).then(mnfstData => { - if(mnfstData){ + idbKeyval + .get("current", manifestStore) + .then(mnfstData => { + if (mnfstData) { return caches.delete(mnfstData.cacheName); } - }).then(() => { - return idbKeyval.get('next', manifestStore) - }).then(mnfstData => { - if(mnfstData){ - manifest = new JakeCacheManifest(mnfstData); - - return idbKeyval.set('current', mnfstData, manifestStore); - }else{ - cacheStatus = CacheStatus.UNCACHED; - } - }).then(() => { - return idbKeyval.del('next', manifestStore); - }).then(_ => { - cacheStatus = CacheStatus.IDLE; - postMessage({ type: "updated" }); - }); + }) + .then(() => { + return idbKeyval.get("next", manifestStore); + }) + .then(mnfstData => { + if (mnfstData) { + manifest = new JakeCacheManifest(mnfstData); + + return idbKeyval.set("current", mnfstData, manifestStore); + } else { + cacheStatus = CacheStatus.UNCACHED; + } + }) + .then(() => { + return idbKeyval.del("next", manifestStore); + }) + .then(_ => { + cacheStatus = CacheStatus.IDLE; + postMessage({ type: "updated" }); + }); } // 7.9.4 @@ -515,47 +518,48 @@ function update(pathname, options = {}) { console.log("No pathname!"); return Promise.reject(); } - + let nextManifest = new JakeCacheManifest(); // *.2.2 - this.options = options; + self.options = options; - return idbKeyval.get('current', manifestStore).then(mnfstData => { - if(!mnfstData){ + return idbKeyval + .get("current", manifestStore) + .then(mnfstData => { + if (!mnfstData) { manifest = null; - this.uncached = true; + self.uncached = true; cacheStatus = CacheStatus.UNCACHED; - console.log("uncached " + this.uncached); - return Promise.resolve(this.uncached); - }else{ + console.log("uncached " + self.uncached); + return Promise.resolve(self.uncached); + } else { manifest = new JakeCacheManifest(mnfstData); - this.options = this.options || {}; - this.options.hash = manifest.hash(); + self.options = self.options || {}; + self.options.hash = manifest.hash(); } return caches.open(mnfstData.cacheName).then(cache => { - if(!cache){ + if (!cache) { manifest = null; - this.uncached = true; + self.uncached = true; cacheStatus = CacheStatus.UNCACHED; - console.log("uncached " + this.uncached); - return Promise.resolve(this.uncached); + console.log("uncached " + self.uncached); + return Promise.resolve(self.uncached); } return cache.keys().then(keyData => { - this.uncached = !keyData || !keyData.length; - if(this.uncached){ + self.uncached = !keyData || !keyData.length; + if (self.uncached) { manifest = null; cacheStatus = CacheStatus.UNCACHED; } - console.log("uncached " + this.uncached); - return Promise.resolve(this.uncached); - }) - }) + console.log("uncached " + self.uncached); + return Promise.resolve(self.uncached); + }); + }); }) .then(uncached => { - if (cacheStatus === CacheStatus.UPDATEREADY) { postMessage({ type: "updateready" }); postMessage({ type: "abort" }); @@ -582,9 +586,8 @@ function update(pathname, options = {}) { postMessage({ type: "checking" }); // FIXME: *.6: Fetch manifest, mark obsolete if fails. - - return nextManifest.fetchData(pathname, this.options).catch(err => { + return nextManifest.fetchData(pathname, self.options).catch(err => { cacheStatus = CacheStatus.OBSOLETE; postMessage({ type: "obsolete" }); // FIXME: *.7: Error for each existing entry. @@ -592,10 +595,9 @@ function update(pathname, options = {}) { postMessage({ type: "idle" }); return Promise.reject(err); }); - - - }).then(modified => { - this.modified = modified; + }) + .then(modified => { + self.modified = modified; // *.2: If cache group already has an application cache in it, then // this is an upgrade attempt. Otherwise, this is a cache attempt. return caches.keys().then(cacheNames => { @@ -603,34 +605,34 @@ function update(pathname, options = {}) { }); }) .then(upgrade => { - this.upgrade = upgrade; - if (this.upgrade && !this.modified) { + self.upgrade = upgrade; + if (self.upgrade && !self.modified) { cacheStatus = CacheStatus.IDLE; postMessage({ type: "noupdate" }); return Promise.reject(); } // Appcache is no-cors by default. - this.requests = nextManifest.cache.map(url => { + self.requests = nextManifest.cache.map(url => { return new Request(url, { mode: "no-cors" }); }); cacheStatus = CacheStatus.DOWNLOADING; postMessage({ type: "downloading" }); - this.loaded = 0; - this.total = this.requests.length; + self.loaded = 0; + self.total = self.requests.length; return Promise.all( - this.requests.map(request => { + self.requests.map(request => { // Manual fetch to emulate appcache behavior. return fetch(request, nextManifest._fetchOptions).then(response => { cacheStatus = CacheStatus.PROGRESS; postMessage({ type: "progress", lengthComputable: true, - loaded: ++this.loaded, - total: this.total, + loaded: ++self.loaded, + total: self.total, url: request.url.toString() }); @@ -662,9 +664,10 @@ function update(pathname, options = {}) { }); }) ); - }).then(responses => { - this.responses = responses.filter(response => response); - return Promise.resolve(this.responses); + }) + .then(responses => { + self.responses = responses.filter(response => response); + return Promise.resolve(self.responses); }) .then(responses => { console.log("Adding to cache " + nextManifest.cacheName()); @@ -676,18 +679,23 @@ function update(pathname, options = {}) { return cache.put(self.requests[index], response); }) ); - }).then(_ => { - let manifestVersion = 'next'; - if(!this.upgrade){ + }) + .then(_ => { + let manifestVersion = "next"; + if (!self.upgrade) { manifest = nextManifest; - manifestVersion = 'current'; + manifestVersion = "current"; } - return idbKeyval.set(manifestVersion, nextManifest.manifestData(), manifestStore); + return idbKeyval.set( + manifestVersion, + nextManifest.manifestData(), + manifestStore + ); }); - }).then(_ => { - if (this.upgrade) - { + }) + .then(_ => { + if (self.upgrade) { cacheStatus = CacheStatus.UPDATEREADY; postMessage({ type: "updateready" }); } else { @@ -705,34 +713,40 @@ function update(pathname, options = {}) { } self.addEventListener("install", function(event) { - event.waitUntil( - self.skipWaiting() - ); + event.waitUntil(self.skipWaiting()); }); self.addEventListener("activate", function(event) { - event.waitUntil( - idbKeyval.get('current', manifestStore).then(mnfstData => { - if(mnfstData){ - manifest = new JakeCacheManifest(mnfstData); - cacheStatus = CacheStatus.CACHED; - } - return Promise.resolve(manifest); - }).then(mnfst => { - if(mnfst){ - return update(mnfst.pathName(), { cache : "reload" }); - } - return Promise.resolve(); - }).then(self.clients.claim()) - ); + idbKeyval + .get("current", manifestStore) + .then(mnfstData => { + if (mnfstData) { + manifest = new JakeCacheManifest(mnfstData); + cacheStatus = CacheStatus.CACHED; + } + return Promise.resolve(manifest); + }) + .then(mnfst => { + if (mnfst) { + return update(mnfst.pathName(), { cache: "reload" }); + } + return Promise.resolve(); + }) + .then(self.clients.claim()) + ); }); self.addEventListener("fetch", function(event) { - if(manifest && manifest.isValid() && cacheStatus === CacheStatus.UNCACHED){ + if (manifest && manifest.isValid() && cacheStatus === CacheStatus.UNCACHED) { cacheStatus = CacheStatus.CACHED; } + if (!event.request.url.startsWith(self.location.origin)) { + // External request, or POST, ignore + return event.respondWith(fetch(event.request)); + } + if (cacheStatus === CacheStatus.UNCACHED) { return fetch(event.request); } @@ -740,15 +754,15 @@ self.addEventListener("fetch", function(event) { let url = new URL(event.request.url); // Ignore non-GET and different schemes. - if (!event.request.url.startsWith(self.location.origin) || - event.request.method !== "GET" || url.scheme !== location.scheme) { + if ( + !event.request.url.startsWith(self.location.origin) || + event.request.method !== "GET" || + url.scheme !== location.scheme + ) { return; } - // if (!event.request.url.startsWith(self.location.origin)) { - // // External request, or POST, ignore - // return void event.respondWith(fetch(event.request)); - // } + // FIXME TEST: Get data from IndexedDB instead. let mnfstPromise = manifest @@ -758,65 +772,63 @@ self.addEventListener("fetch", function(event) { manifest = new JakeCacheManifest(mnfstData); } return manifest; - }); + }); - function checkCache(mnfst) - { - return caches - .open(mnfst.cacheName()) - .then(function (cache) { - if(!cache) - { - throw Error('Cache not found'); - } + function checkCache(mnfst) { + return caches + .open(mnfst.cacheName()) + .then(function(cache) { + if (!cache) { + throw Error("Cache not found"); + } - return cache.match(event.request).then(response => { - // Cache always wins. - if (response) { - return response; - } + return cache.match(event.request).then(response => { + // Cache always wins. + if (response) { + return response; + } - // Fallbacks consult network, and falls back on failure. - for (let [path, fallback] of mnfst.fallback) { - if (url.href.indexOf(path) === 0) { - return fetch(event.request) - .then(response => { - // Same origin only. - if (new URL(response.url).origin !== location.origin) { - throw Error(); - } - - if (response.type !== "opaque") { - if (response.status < 200 || response.status >= 300) { - throw Error(); - } - } - }) - .catch(_ => { - return cache.match(fallback); - }); - } + // Fallbacks consult network, and falls back on failure. + for (let [path, fallback] of mnfst.fallback) { + if (url.href.indexOf(path) === 0) { + return fetch(event.request) + .then(response => { + // Same origin only. + if (new URL(response.url).origin !== location.origin) { + throw Error(); + } + + if (response.type !== "opaque") { + if (response.status < 200 || response.status >= 300) { + throw Error(); } + } + }) + .catch(_ => { + return cache.match(fallback); + }); + } + } - if (mnfst.allowNetworkFallback) { - return fetch(event.request); - } + if (mnfst.allowNetworkFallback) { + return fetch(event.request); + } - return response; // failure. - }); - }) - .catch(err => { - if (err) { - postMessage({ type: "error" }, err); - console.log(err); - } - //error with cache remove current manifest - manifest = null; - idbKeyval.del("current", manifestStore); - cacheStatus = CacheStatus.UNCACHED; - return fetch(event.request); - }); - } + return response; // failure. + }); + }) + .catch(err => { + if (err) { + postMessage({ type: "error" }, err); + console.log(err); + } + //error with cache remove current manifest + manifest = null; + idbKeyval.del("current", manifestStore); + cacheStatus = CacheStatus.UNCACHED; + return fetch(event.request); + }); + } event.respondWith( mnfstPromise diff --git a/jakecache-sw.js b/jakecache-sw.js index ded3037..5799de4 100644 --- a/jakecache-sw.js +++ b/jakecache-sw.js @@ -16,7 +16,7 @@ class JakeCacheManifest { network: [] }; - if(data){ + if (data) { this.restoreManifest(data); } } @@ -31,26 +31,25 @@ class JakeCacheManifest { return { cacheName: this.cacheName(), path: this._path, - hash : this._hash, - isValid : this._isValid, + hash: this._hash, + isValid: this._isValid, rawData: this._rawData }; } restoreManifest(manifestData) { - if(!manifestData){ + if (!manifestData) { this._isValid = false; return; } this._path = manifestData.path; this._hash = manifestData.hash; - this._rawData = manifestData.rawData + this._rawData = manifestData.rawData; this.restoreCache(); } restoreCache() { - this.cache = ["jakecache.js"]; // Ignore different protocol for (let pathname of this._rawData.cache) { @@ -87,17 +86,15 @@ class JakeCacheManifest { } this._isValid = true; - } - pathName() { return this._path; } cacheName() { let version = this._rawData.version; - return version + '_' + this._hash; + return version + "_" + this._hash; } fetchData(path, options = {}) { @@ -119,7 +116,7 @@ class JakeCacheManifest { } this._hash = options.hash ? options.hash : this._hash; - + return response.text().then(result => { return new Promise((resolve, reject) => { let hash = md5(result); @@ -171,7 +168,7 @@ class JakeCacheManifest { if (!versionFound) { this._rawData.version = "" + new Date().getTime(); } - + this.restoreCache(); resolve(true); }); @@ -222,26 +219,32 @@ function postMessage(msg) { } function swapCache() { - idbKeyval.get('current', manifestStore).then(mnfstData => { - if(mnfstData){ + idbKeyval + .get("current", manifestStore) + .then(mnfstData => { + if (mnfstData) { return caches.delete(mnfstData.cacheName); } - }).then(() => { - return idbKeyval.get('next', manifestStore) - }).then(mnfstData => { - if(mnfstData){ - manifest = new JakeCacheManifest(mnfstData); - - return idbKeyval.set('current', mnfstData, manifestStore); - }else{ - cacheStatus = CacheStatus.UNCACHED; - } - }).then(() => { - return idbKeyval.del('next', manifestStore); - }).then(_ => { - cacheStatus = CacheStatus.IDLE; - postMessage({ type: "updated" }); - }); + }) + .then(() => { + return idbKeyval.get("next", manifestStore); + }) + .then(mnfstData => { + if (mnfstData) { + manifest = new JakeCacheManifest(mnfstData); + + return idbKeyval.set("current", mnfstData, manifestStore); + } else { + cacheStatus = CacheStatus.UNCACHED; + } + }) + .then(() => { + return idbKeyval.del("next", manifestStore); + }) + .then(_ => { + cacheStatus = CacheStatus.IDLE; + postMessage({ type: "updated" }); + }); } // 7.9.4 @@ -250,47 +253,48 @@ function update(pathname, options = {}) { console.log("No pathname!"); return Promise.reject(); } - + let nextManifest = new JakeCacheManifest(); // *.2.2 - (this || self).options = options; + self.options = options; - return idbKeyval.get('current', manifestStore).then(mnfstData => { - if(!mnfstData){ + return idbKeyval + .get("current", manifestStore) + .then(mnfstData => { + if (!mnfstData) { manifest = null; - this.uncached = true; + self.uncached = true; cacheStatus = CacheStatus.UNCACHED; - console.log("uncached " + this.uncached); - return Promise.resolve(this.uncached); - }else{ + console.log("uncached " + self.uncached); + return Promise.resolve(self.uncached); + } else { manifest = new JakeCacheManifest(mnfstData); - this.options = this.options || {}; - this.options.hash = manifest.hash(); + self.options = self.options || {}; + self.options.hash = manifest.hash(); } return caches.open(mnfstData.cacheName).then(cache => { - if(!cache){ + if (!cache) { manifest = null; - this.uncached = true; + self.uncached = true; cacheStatus = CacheStatus.UNCACHED; - console.log("uncached " + this.uncached); - return Promise.resolve(this.uncached); + console.log("uncached " + self.uncached); + return Promise.resolve(self.uncached); } return cache.keys().then(keyData => { - this.uncached = !keyData || !keyData.length; - if(this.uncached){ + self.uncached = !keyData || !keyData.length; + if (self.uncached) { manifest = null; cacheStatus = CacheStatus.UNCACHED; } - console.log("uncached " + this.uncached); - return Promise.resolve(this.uncached); - }) - }) + console.log("uncached " + self.uncached); + return Promise.resolve(self.uncached); + }); + }); }) .then(uncached => { - if (cacheStatus === CacheStatus.UPDATEREADY) { postMessage({ type: "updateready" }); postMessage({ type: "abort" }); @@ -317,9 +321,8 @@ function update(pathname, options = {}) { postMessage({ type: "checking" }); // FIXME: *.6: Fetch manifest, mark obsolete if fails. - - return nextManifest.fetchData(pathname, this.options).catch(err => { + return nextManifest.fetchData(pathname, self.options).catch(err => { cacheStatus = CacheStatus.OBSOLETE; postMessage({ type: "obsolete" }); // FIXME: *.7: Error for each existing entry. @@ -327,10 +330,9 @@ function update(pathname, options = {}) { postMessage({ type: "idle" }); return Promise.reject(err); }); - - - }).then(modified => { - this.modified = modified; + }) + .then(modified => { + self.modified = modified; // *.2: If cache group already has an application cache in it, then // this is an upgrade attempt. Otherwise, this is a cache attempt. return caches.keys().then(cacheNames => { @@ -338,34 +340,34 @@ function update(pathname, options = {}) { }); }) .then(upgrade => { - this.upgrade = upgrade; - if (this.upgrade && !this.modified) { + self.upgrade = upgrade; + if (self.upgrade && !self.modified) { cacheStatus = CacheStatus.IDLE; postMessage({ type: "noupdate" }); return Promise.reject(); } // Appcache is no-cors by default. - this.requests = nextManifest.cache.map(url => { + self.requests = nextManifest.cache.map(url => { return new Request(url, { mode: "no-cors" }); }); cacheStatus = CacheStatus.DOWNLOADING; postMessage({ type: "downloading" }); - this.loaded = 0; - this.total = this.requests.length; + self.loaded = 0; + self.total = self.requests.length; return Promise.all( - this.requests.map(request => { + self.requests.map(request => { // Manual fetch to emulate appcache behavior. return fetch(request, nextManifest._fetchOptions).then(response => { cacheStatus = CacheStatus.PROGRESS; postMessage({ type: "progress", lengthComputable: true, - loaded: ++this.loaded, - total: this.total, + loaded: ++self.loaded, + total: self.total, url: request.url.toString() }); @@ -397,9 +399,10 @@ function update(pathname, options = {}) { }); }) ); - }).then(responses => { - this.responses = responses.filter(response => response); - return Promise.resolve(this.responses); + }) + .then(responses => { + self.responses = responses.filter(response => response); + return Promise.resolve(self.responses); }) .then(responses => { console.log("Adding to cache " + nextManifest.cacheName()); @@ -411,18 +414,23 @@ function update(pathname, options = {}) { return cache.put(self.requests[index], response); }) ); - }).then(_ => { - let manifestVersion = 'next'; - if(!this.upgrade){ + }) + .then(_ => { + let manifestVersion = "next"; + if (!self.upgrade) { manifest = nextManifest; - manifestVersion = 'current'; + manifestVersion = "current"; } - return idbKeyval.set(manifestVersion, nextManifest.manifestData(), manifestStore); + return idbKeyval.set( + manifestVersion, + nextManifest.manifestData(), + manifestStore + ); }); - }).then(_ => { - if (this.upgrade) - { + }) + .then(_ => { + if (self.upgrade) { cacheStatus = CacheStatus.UPDATEREADY; postMessage({ type: "updateready" }); } else { @@ -440,34 +448,40 @@ function update(pathname, options = {}) { } self.addEventListener("install", function(event) { - event.waitUntil( - self.skipWaiting() - ); + event.waitUntil(self.skipWaiting()); }); self.addEventListener("activate", function(event) { - event.waitUntil( - idbKeyval.get('current', manifestStore).then(mnfstData => { - if(mnfstData){ - manifest = new JakeCacheManifest(mnfstData); - cacheStatus = CacheStatus.CACHED; - } - return Promise.resolve(manifest); - }).then(mnfst => { - if(mnfst){ - return update(mnfst.pathName(), { cache : "reload" }); - } - return Promise.resolve(); - }).then(self.clients.claim()) - ); + idbKeyval + .get("current", manifestStore) + .then(mnfstData => { + if (mnfstData) { + manifest = new JakeCacheManifest(mnfstData); + cacheStatus = CacheStatus.CACHED; + } + return Promise.resolve(manifest); + }) + .then(mnfst => { + if (mnfst) { + return update(mnfst.pathName(), { cache: "reload" }); + } + return Promise.resolve(); + }) + .then(self.clients.claim()) + ); }); self.addEventListener("fetch", function(event) { - if(manifest && manifest.isValid() && cacheStatus === CacheStatus.UNCACHED){ + if (manifest && manifest.isValid() && cacheStatus === CacheStatus.UNCACHED) { cacheStatus = CacheStatus.CACHED; } + if (!event.request.url.startsWith(self.location.origin)) { + // External request, or POST, ignore + return event.respondWith(fetch(event.request)); + } + if (cacheStatus === CacheStatus.UNCACHED) { return fetch(event.request); } @@ -475,15 +489,15 @@ self.addEventListener("fetch", function(event) { let url = new URL(event.request.url); // Ignore non-GET and different schemes. - if (!event.request.url.startsWith(self.location.origin) || - event.request.method !== "GET" || url.scheme !== location.scheme) { + if ( + !event.request.url.startsWith(self.location.origin) || + event.request.method !== "GET" || + url.scheme !== location.scheme + ) { return; } - // if (!event.request.url.startsWith(self.location.origin)) { - // // External request, or POST, ignore - // return void event.respondWith(fetch(event.request)); - // } + // FIXME TEST: Get data from IndexedDB instead. let mnfstPromise = manifest @@ -493,65 +507,63 @@ self.addEventListener("fetch", function(event) { manifest = new JakeCacheManifest(mnfstData); } return manifest; - }); + }); - function checkCache(mnfst) - { - return caches - .open(mnfst.cacheName()) - .then(function (cache) { - if(!cache) - { - throw Error('Cache not found'); - } + function checkCache(mnfst) { + return caches + .open(mnfst.cacheName()) + .then(function(cache) { + if (!cache) { + throw Error("Cache not found"); + } - return cache.match(event.request).then(response => { - // Cache always wins. - if (response) { - return response; - } + return cache.match(event.request).then(response => { + // Cache always wins. + if (response) { + return response; + } - // Fallbacks consult network, and falls back on failure. - for (let [path, fallback] of mnfst.fallback) { - if (url.href.indexOf(path) === 0) { - return fetch(event.request) - .then(response => { - // Same origin only. - if (new URL(response.url).origin !== location.origin) { - throw Error(); - } - - if (response.type !== "opaque") { - if (response.status < 200 || response.status >= 300) { - throw Error(); - } - } - }) - .catch(_ => { - return cache.match(fallback); - }); - } + // Fallbacks consult network, and falls back on failure. + for (let [path, fallback] of mnfst.fallback) { + if (url.href.indexOf(path) === 0) { + return fetch(event.request) + .then(response => { + // Same origin only. + if (new URL(response.url).origin !== location.origin) { + throw Error(); + } + + if (response.type !== "opaque") { + if (response.status < 200 || response.status >= 300) { + throw Error(); } + } + }) + .catch(_ => { + return cache.match(fallback); + }); + } + } - if (mnfst.allowNetworkFallback) { - return fetch(event.request); - } + if (mnfst.allowNetworkFallback) { + return fetch(event.request); + } - return response; // failure. - }); - }) - .catch(err => { - if (err) { - postMessage({ type: "error" }, err); - console.log(err); - } - //error with cache remove current manifest - manifest = null; - idbKeyval.del("current", manifestStore); - cacheStatus = CacheStatus.UNCACHED; - return fetch(event.request); - }); - } + return response; // failure. + }); + }) + .catch(err => { + if (err) { + postMessage({ type: "error" }, err); + console.log(err); + } + //error with cache remove current manifest + manifest = null; + idbKeyval.del("current", manifestStore); + cacheStatus = CacheStatus.UNCACHED; + return fetch(event.request); + }); + } event.respondWith( mnfstPromise From 012fbb3fa8a002d64e3813fd8233f2c08c152c16 Mon Sep 17 00:00:00 2001 From: Sergey S Date: Tue, 17 Mar 2020 16:41:01 +0200 Subject: [PATCH 04/22] check for only-if-cached flag --- jakecache-sw.js | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/jakecache-sw.js b/jakecache-sw.js index 5799de4..b468a7d 100644 --- a/jakecache-sw.js +++ b/jakecache-sw.js @@ -477,12 +477,10 @@ self.addEventListener("fetch", function(event) { cacheStatus = CacheStatus.CACHED; } - if (!event.request.url.startsWith(self.location.origin)) { - // External request, or POST, ignore - return event.respondWith(fetch(event.request)); - } - if (cacheStatus === CacheStatus.UNCACHED) { + if (event.request.cache === 'only-if-cached' && event.request.mode !== 'same-origin') { + return event.respondWith(Promise.resolve()); + } return fetch(event.request); } @@ -497,7 +495,10 @@ self.addEventListener("fetch", function(event) { return; } - + if (!event.request.url.startsWith(self.location.origin)) { + // External request, or POST, ignore + return event.respondWith(fetch(event.request)); + } // FIXME TEST: Get data from IndexedDB instead. let mnfstPromise = manifest From ba27d7943fc4d82dc98cc861e81892607b1181f6 Mon Sep 17 00:00:00 2001 From: Sergey S Date: Tue, 17 Mar 2020 17:22:41 +0200 Subject: [PATCH 05/22] chcek for only-if-cached flag, chcek for origin --- dist/jakecache-sw.js | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/dist/jakecache-sw.js b/dist/jakecache-sw.js index 096f88d..94b03ec 100644 --- a/dist/jakecache-sw.js +++ b/dist/jakecache-sw.js @@ -742,12 +742,10 @@ self.addEventListener("fetch", function(event) { cacheStatus = CacheStatus.CACHED; } - if (!event.request.url.startsWith(self.location.origin)) { - // External request, or POST, ignore - return event.respondWith(fetch(event.request)); - } - if (cacheStatus === CacheStatus.UNCACHED) { + if (event.request.cache === 'only-if-cached' && event.request.mode !== 'same-origin') { + return event.respondWith(Promise.resolve()); + } return fetch(event.request); } @@ -762,7 +760,10 @@ self.addEventListener("fetch", function(event) { return; } - + if (!event.request.url.startsWith(self.location.origin)) { + // External request, or POST, ignore + return event.respondWith(fetch(event.request)); + } // FIXME TEST: Get data from IndexedDB instead. let mnfstPromise = manifest From 6041eaa596375c63a1656082c1beada826431bdc Mon Sep 17 00:00:00 2001 From: Sergey S Date: Thu, 19 Mar 2020 20:12:42 +0200 Subject: [PATCH 06/22] combine cheks when skip request in one place --- jakecache-sw.js | 15 +++------------ 1 file changed, 3 insertions(+), 12 deletions(-) diff --git a/jakecache-sw.js b/jakecache-sw.js index b468a7d..e0487bf 100644 --- a/jakecache-sw.js +++ b/jakecache-sw.js @@ -477,17 +477,13 @@ self.addEventListener("fetch", function(event) { cacheStatus = CacheStatus.CACHED; } - if (cacheStatus === CacheStatus.UNCACHED) { - if (event.request.cache === 'only-if-cached' && event.request.mode !== 'same-origin') { - return event.respondWith(Promise.resolve()); - } - return fetch(event.request); - } - let url = new URL(event.request.url); // Ignore non-GET and different schemes. if ( + cacheStatus === CacheStatus.UNCACHED || + (event.request.cache === "only-if-cached" && + event.request.mode !== "same-origin") || !event.request.url.startsWith(self.location.origin) || event.request.method !== "GET" || url.scheme !== location.scheme @@ -495,11 +491,6 @@ self.addEventListener("fetch", function(event) { return; } - if (!event.request.url.startsWith(self.location.origin)) { - // External request, or POST, ignore - return event.respondWith(fetch(event.request)); - } - // FIXME TEST: Get data from IndexedDB instead. let mnfstPromise = manifest ? Promise.resolve(manifest) From bf565e01335f2772ffa843bc12207c1fd39317bd Mon Sep 17 00:00:00 2001 From: Sergey S Date: Thu, 19 Mar 2020 20:26:15 +0200 Subject: [PATCH 07/22] fix init cache in first install --- jakecache.js | 49 +++++++++++++++++++++++++++++++++++++++++++------ 1 file changed, 43 insertions(+), 6 deletions(-) diff --git a/jakecache.js b/jakecache.js index e431d64..d29e594 100644 --- a/jakecache.js +++ b/jakecache.js @@ -98,14 +98,28 @@ class JakeCache extends PolyfilledEventTarget { .register("jakecache-sw.js") .then(registration => { console.log(`JakeCache installed for ${registration.scope}`); + if (registration.waiting) { + console.log("waiting", registration.waiting); + registration.waiting.addEventListener( + "statechange", + onStateChange("waiting") + ); + } + + if (registration.installing) { + console.log("installing", registration.installing); + registration.installing.addEventListener( + "statechange", + onStateChange("installing") + ); + } if (registration.active) { - // Check whether we have a cache, or cache it (no reload enforced). - console.log("cache check"); - registration.active.postMessage({ - command: "update", - pathname: this.pathname - }); + console.log("active", registration.active); + registration.active.addEventListener( + "statechange", + onStateChange("active") + ); } }) .catch(err => { @@ -114,6 +128,29 @@ class JakeCache extends PolyfilledEventTarget { } }; + function onStateChange(from) { + return function(e) { + console.log( + "statechange initial state ", + from, + "changed to", + e.target.state + ); + if (e.target.state === "activated") { + // Check whether we have a cache, or cache it (no reload enforced). + console.log("cache check for update"); + + let html = document.querySelector("html"); + this.pathname = html.getAttribute("manifest"); + + navigator.serviceWorker.controller.postMessage({ + command: "update", + pathname: this.pathname + }); + } + }; + } + if (document.readyState === "complete") { onload(); } else { From fc94caa9d7b126bd48d695cb0bc3af2dc82caa96 Mon Sep 17 00:00:00 2001 From: Sergey S Date: Sat, 21 Mar 2020 16:17:00 +0200 Subject: [PATCH 08/22] update logging, call update on active --- jakecache.js | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/jakecache.js b/jakecache.js index d29e594..466bb67 100644 --- a/jakecache.js +++ b/jakecache.js @@ -99,7 +99,7 @@ class JakeCache extends PolyfilledEventTarget { .then(registration => { console.log(`JakeCache installed for ${registration.scope}`); if (registration.waiting) { - console.log("waiting", registration.waiting); + console.log("JakeCache waiting", registration.waiting); registration.waiting.addEventListener( "statechange", onStateChange("waiting") @@ -107,7 +107,7 @@ class JakeCache extends PolyfilledEventTarget { } if (registration.installing) { - console.log("installing", registration.installing); + console.log("JakeCache installing", registration.installing); registration.installing.addEventListener( "statechange", onStateChange("installing") @@ -115,11 +115,18 @@ class JakeCache extends PolyfilledEventTarget { } if (registration.active) { - console.log("active", registration.active); + console.log("JakeCache active", registration.active); registration.active.addEventListener( "statechange", onStateChange("active") ); + + // Check whether we have a cache, or cache it (no reload enforced). + console.log("JakeCache cache check"); + registration.active.postMessage({ + command: "update", + pathname: this.pathname + }); } }) .catch(err => { From 6a9eca757fe64d649d76cfe5db22e84820529693 Mon Sep 17 00:00:00 2001 From: Sergey S Date: Tue, 7 Apr 2020 22:48:12 +0300 Subject: [PATCH 09/22] refactor to async await, simplify manifest update logic --- README.md | 79 ++- dist/jakecache-sw.js | 1154 ++++++++++++++++++++---------------------- dist/jakecache.js | 107 ++-- jakecache-sw.js | 999 ++++++++++++++++++------------------ jakecache.js | 154 +++--- package-lock.json | 925 +++++++++++++++++++++++++++++++++ package.json | 10 +- rollup.config.js | 36 ++ scripts/build.js | 57 ++- 9 files changed, 2272 insertions(+), 1249 deletions(-) create mode 100644 package-lock.json create mode 100644 rollup.config.js diff --git a/README.md b/README.md index 8623e6e..bcee420 100644 --- a/README.md +++ b/README.md @@ -49,9 +49,84 @@ NETWORK: * ``` -1. Include ``jakecache.js`` on your page, maybe via `````` +1. sample setup `````` 2. Add `````` to your HTML. -3. That's it! Your website is now Jake-enabled! +3. If nam eof manifest is different cahnge it in 'jakecahce-sw.js' ```const manifestName = 'app.manifest'; +4. optional parameter in 'jakecahce-sw.js' ``` const isAutoUpdate = false; +Means autoupdate cache without message to SwapCahce +5. That's it! Your website is now Jake-enabled! ## License diff --git a/dist/jakecache-sw.js b/dist/jakecache-sw.js index 94b03ec..e783ac6 100644 --- a/dist/jakecache-sw.js +++ b/dist/jakecache-sw.js @@ -60,8 +60,8 @@ function md5_ii (a, b, c, d, x, s, t) { */ function binl_md5 (x, len) { /* append padding */ - x[len >> 5] |= 0x80 << (len % 32) - x[(((len + 64) >>> 9) << 4) + 14] = len + x[len >> 5] |= 0x80 << (len % 32); + x[(((len + 64) >>> 9) << 4) + 14] = len; let i; let olda; @@ -74,83 +74,83 @@ function binl_md5 (x, len) { let d = 271733878; for (i = 0; i < x.length; i += 16) { - olda = a - oldb = b - oldc = c - oldd = d - - a = md5_ff(a, b, c, d, x[i], 7, -680876936) - d = md5_ff(d, a, b, c, x[i + 1], 12, -389564586) - c = md5_ff(c, d, a, b, x[i + 2], 17, 606105819) - b = md5_ff(b, c, d, a, x[i + 3], 22, -1044525330) - a = md5_ff(a, b, c, d, x[i + 4], 7, -176418897) - d = md5_ff(d, a, b, c, x[i + 5], 12, 1200080426) - c = md5_ff(c, d, a, b, x[i + 6], 17, -1473231341) - b = md5_ff(b, c, d, a, x[i + 7], 22, -45705983) - a = md5_ff(a, b, c, d, x[i + 8], 7, 1770035416) - d = md5_ff(d, a, b, c, x[i + 9], 12, -1958414417) - c = md5_ff(c, d, a, b, x[i + 10], 17, -42063) - b = md5_ff(b, c, d, a, x[i + 11], 22, -1990404162) - a = md5_ff(a, b, c, d, x[i + 12], 7, 1804603682) - d = md5_ff(d, a, b, c, x[i + 13], 12, -40341101) - c = md5_ff(c, d, a, b, x[i + 14], 17, -1502002290) - b = md5_ff(b, c, d, a, x[i + 15], 22, 1236535329) - - a = md5_gg(a, b, c, d, x[i + 1], 5, -165796510) - d = md5_gg(d, a, b, c, x[i + 6], 9, -1069501632) - c = md5_gg(c, d, a, b, x[i + 11], 14, 643717713) - b = md5_gg(b, c, d, a, x[i], 20, -373897302) - a = md5_gg(a, b, c, d, x[i + 5], 5, -701558691) - d = md5_gg(d, a, b, c, x[i + 10], 9, 38016083) - c = md5_gg(c, d, a, b, x[i + 15], 14, -660478335) - b = md5_gg(b, c, d, a, x[i + 4], 20, -405537848) - a = md5_gg(a, b, c, d, x[i + 9], 5, 568446438) - d = md5_gg(d, a, b, c, x[i + 14], 9, -1019803690) - c = md5_gg(c, d, a, b, x[i + 3], 14, -187363961) - b = md5_gg(b, c, d, a, x[i + 8], 20, 1163531501) - a = md5_gg(a, b, c, d, x[i + 13], 5, -1444681467) - d = md5_gg(d, a, b, c, x[i + 2], 9, -51403784) - c = md5_gg(c, d, a, b, x[i + 7], 14, 1735328473) - b = md5_gg(b, c, d, a, x[i + 12], 20, -1926607734) - - a = md5_hh(a, b, c, d, x[i + 5], 4, -378558) - d = md5_hh(d, a, b, c, x[i + 8], 11, -2022574463) - c = md5_hh(c, d, a, b, x[i + 11], 16, 1839030562) - b = md5_hh(b, c, d, a, x[i + 14], 23, -35309556) - a = md5_hh(a, b, c, d, x[i + 1], 4, -1530992060) - d = md5_hh(d, a, b, c, x[i + 4], 11, 1272893353) - c = md5_hh(c, d, a, b, x[i + 7], 16, -155497632) - b = md5_hh(b, c, d, a, x[i + 10], 23, -1094730640) - a = md5_hh(a, b, c, d, x[i + 13], 4, 681279174) - d = md5_hh(d, a, b, c, x[i], 11, -358537222) - c = md5_hh(c, d, a, b, x[i + 3], 16, -722521979) - b = md5_hh(b, c, d, a, x[i + 6], 23, 76029189) - a = md5_hh(a, b, c, d, x[i + 9], 4, -640364487) - d = md5_hh(d, a, b, c, x[i + 12], 11, -421815835) - c = md5_hh(c, d, a, b, x[i + 15], 16, 530742520) - b = md5_hh(b, c, d, a, x[i + 2], 23, -995338651) - - a = md5_ii(a, b, c, d, x[i], 6, -198630844) - d = md5_ii(d, a, b, c, x[i + 7], 10, 1126891415) - c = md5_ii(c, d, a, b, x[i + 14], 15, -1416354905) - b = md5_ii(b, c, d, a, x[i + 5], 21, -57434055) - a = md5_ii(a, b, c, d, x[i + 12], 6, 1700485571) - d = md5_ii(d, a, b, c, x[i + 3], 10, -1894986606) - c = md5_ii(c, d, a, b, x[i + 10], 15, -1051523) - b = md5_ii(b, c, d, a, x[i + 1], 21, -2054922799) - a = md5_ii(a, b, c, d, x[i + 8], 6, 1873313359) - d = md5_ii(d, a, b, c, x[i + 15], 10, -30611744) - c = md5_ii(c, d, a, b, x[i + 6], 15, -1560198380) - b = md5_ii(b, c, d, a, x[i + 13], 21, 1309151649) - a = md5_ii(a, b, c, d, x[i + 4], 6, -145523070) - d = md5_ii(d, a, b, c, x[i + 11], 10, -1120210379) - c = md5_ii(c, d, a, b, x[i + 2], 15, 718787259) - b = md5_ii(b, c, d, a, x[i + 9], 21, -343485551) - - a = safe_add(a, olda) - b = safe_add(b, oldb) - c = safe_add(c, oldc) - d = safe_add(d, oldd) + olda = a; + oldb = b; + oldc = c; + oldd = d; + + a = md5_ff(a, b, c, d, x[i], 7, -680876936); + d = md5_ff(d, a, b, c, x[i + 1], 12, -389564586); + c = md5_ff(c, d, a, b, x[i + 2], 17, 606105819); + b = md5_ff(b, c, d, a, x[i + 3], 22, -1044525330); + a = md5_ff(a, b, c, d, x[i + 4], 7, -176418897); + d = md5_ff(d, a, b, c, x[i + 5], 12, 1200080426); + c = md5_ff(c, d, a, b, x[i + 6], 17, -1473231341); + b = md5_ff(b, c, d, a, x[i + 7], 22, -45705983); + a = md5_ff(a, b, c, d, x[i + 8], 7, 1770035416); + d = md5_ff(d, a, b, c, x[i + 9], 12, -1958414417); + c = md5_ff(c, d, a, b, x[i + 10], 17, -42063); + b = md5_ff(b, c, d, a, x[i + 11], 22, -1990404162); + a = md5_ff(a, b, c, d, x[i + 12], 7, 1804603682); + d = md5_ff(d, a, b, c, x[i + 13], 12, -40341101); + c = md5_ff(c, d, a, b, x[i + 14], 17, -1502002290); + b = md5_ff(b, c, d, a, x[i + 15], 22, 1236535329); + + a = md5_gg(a, b, c, d, x[i + 1], 5, -165796510); + d = md5_gg(d, a, b, c, x[i + 6], 9, -1069501632); + c = md5_gg(c, d, a, b, x[i + 11], 14, 643717713); + b = md5_gg(b, c, d, a, x[i], 20, -373897302); + a = md5_gg(a, b, c, d, x[i + 5], 5, -701558691); + d = md5_gg(d, a, b, c, x[i + 10], 9, 38016083); + c = md5_gg(c, d, a, b, x[i + 15], 14, -660478335); + b = md5_gg(b, c, d, a, x[i + 4], 20, -405537848); + a = md5_gg(a, b, c, d, x[i + 9], 5, 568446438); + d = md5_gg(d, a, b, c, x[i + 14], 9, -1019803690); + c = md5_gg(c, d, a, b, x[i + 3], 14, -187363961); + b = md5_gg(b, c, d, a, x[i + 8], 20, 1163531501); + a = md5_gg(a, b, c, d, x[i + 13], 5, -1444681467); + d = md5_gg(d, a, b, c, x[i + 2], 9, -51403784); + c = md5_gg(c, d, a, b, x[i + 7], 14, 1735328473); + b = md5_gg(b, c, d, a, x[i + 12], 20, -1926607734); + + a = md5_hh(a, b, c, d, x[i + 5], 4, -378558); + d = md5_hh(d, a, b, c, x[i + 8], 11, -2022574463); + c = md5_hh(c, d, a, b, x[i + 11], 16, 1839030562); + b = md5_hh(b, c, d, a, x[i + 14], 23, -35309556); + a = md5_hh(a, b, c, d, x[i + 1], 4, -1530992060); + d = md5_hh(d, a, b, c, x[i + 4], 11, 1272893353); + c = md5_hh(c, d, a, b, x[i + 7], 16, -155497632); + b = md5_hh(b, c, d, a, x[i + 10], 23, -1094730640); + a = md5_hh(a, b, c, d, x[i + 13], 4, 681279174); + d = md5_hh(d, a, b, c, x[i], 11, -358537222); + c = md5_hh(c, d, a, b, x[i + 3], 16, -722521979); + b = md5_hh(b, c, d, a, x[i + 6], 23, 76029189); + a = md5_hh(a, b, c, d, x[i + 9], 4, -640364487); + d = md5_hh(d, a, b, c, x[i + 12], 11, -421815835); + c = md5_hh(c, d, a, b, x[i + 15], 16, 530742520); + b = md5_hh(b, c, d, a, x[i + 2], 23, -995338651); + + a = md5_ii(a, b, c, d, x[i], 6, -198630844); + d = md5_ii(d, a, b, c, x[i + 7], 10, 1126891415); + c = md5_ii(c, d, a, b, x[i + 14], 15, -1416354905); + b = md5_ii(b, c, d, a, x[i + 5], 21, -57434055); + a = md5_ii(a, b, c, d, x[i + 12], 6, 1700485571); + d = md5_ii(d, a, b, c, x[i + 3], 10, -1894986606); + c = md5_ii(c, d, a, b, x[i + 10], 15, -1051523); + b = md5_ii(b, c, d, a, x[i + 1], 21, -2054922799); + a = md5_ii(a, b, c, d, x[i + 8], 6, 1873313359); + d = md5_ii(d, a, b, c, x[i + 15], 10, -30611744); + c = md5_ii(c, d, a, b, x[i + 6], 15, -1560198380); + b = md5_ii(b, c, d, a, x[i + 13], 21, 1309151649); + a = md5_ii(a, b, c, d, x[i + 4], 6, -145523070); + d = md5_ii(d, a, b, c, x[i + 11], 10, -1120210379); + c = md5_ii(c, d, a, b, x[i + 2], 15, 718787259); + b = md5_ii(b, c, d, a, x[i + 9], 21, -343485551); + + a = safe_add(a, olda); + b = safe_add(b, oldb); + c = safe_add(c, oldc); + d = safe_add(d, oldd); } return [a, b, c, d] } @@ -162,7 +162,7 @@ function binl2rstr (input) { let i; let output = ''; for (i = 0; i < input.length * 32; i += 8) { - output += String.fromCharCode((input[i >> 5] >>> (i % 32)) & 0xFF) + output += String.fromCharCode((input[i >> 5] >>> (i % 32)) & 0xFF); } return output } @@ -174,12 +174,12 @@ function binl2rstr (input) { function rstr2binl (input) { let i; const output = []; - output[(input.length >> 2) - 1] = undefined + output[(input.length >> 2) - 1] = undefined; for (i = 0; i < output.length; i += 1) { - output[i] = 0 + output[i] = 0; } for (i = 0; i < input.length * 8; i += 8) { - output[i >> 5] |= (input.charCodeAt(i / 8) & 0xFF) << (i % 32) + output[i >> 5] |= (input.charCodeAt(i / 8) & 0xFF) << (i % 32); } return output } @@ -200,15 +200,15 @@ function rstr_hmac_md5 (key, data) { const ipad = []; const opad = []; let hash; - ipad[15] = opad[15] = undefined + ipad[15] = opad[15] = undefined; if (bkey.length > 16) { - bkey = binl_md5(bkey, key.length * 8) + bkey = binl_md5(bkey, key.length * 8); } for (i = 0; i < 16; i += 1) { - ipad[i] = bkey[i] ^ 0x36363636 - opad[i] = bkey[i] ^ 0x5C5C5C5C + ipad[i] = bkey[i] ^ 0x36363636; + opad[i] = bkey[i] ^ 0x5C5C5C5C; } - hash = binl_md5(ipad.concat(rstr2binl(data)), 512 + data.length * 8) + hash = binl_md5(ipad.concat(rstr2binl(data)), 512 + data.length * 8); return binl2rstr(binl_md5(opad.concat(hash), 512 + 128)) } @@ -221,9 +221,9 @@ function rstr2hex (input) { let x; let i; for (i = 0; i < input.length; i += 1) { - x = input.charCodeAt(i) + x = input.charCodeAt(i); output += hex_tab.charAt((x >>> 4) & 0x0F) + - hex_tab.charAt(x & 0x0F) + hex_tab.charAt(x & 0x0F); } return output } @@ -269,593 +269,527 @@ self.importScripts("idb-keyval-iife.js"); const manifestStore = new idbKeyval.Store("manifest-db", "manifest-db"); class JakeCacheManifest { - constructor(data) { - this._path = null; - this._hash = null; - this._isValid = false; - this._fetchOptions = { credentials: "same-origin" }; - this._rawData = { - version: "", - cache: [], - fallback: [], - network: [] - }; - - if (data) { - this.restoreManifest(data); + constructor(data) { + this._path = null; + this._hash = null; + this._isValid = false; + this._fetchOptions = { credentials: "same-origin" }; + this._rawData = { + version: "", + cache: [], + fallback: [], + network: [] + }; + + if (data) { + this.restoreManifest(data); + } } - } - - hash() { - return this._hash; - } - isValid() { - return this._isValid; - } - manifestData() { - return { - cacheName: this.cacheName(), - path: this._path, - hash: this._hash, - isValid: this._isValid, - rawData: this._rawData - }; - } - restoreManifest(manifestData) { - if (!manifestData) { - this._isValid = false; - return; + hash() { + return this._hash; + } + isValid() { + return this._isValid; + } + manifestData() { + return { + cacheName: this.cacheName(), + path: this._path, + hash: this._hash, + isValid: this._isValid, + rawData: this._rawData + }; } - this._path = manifestData.path; - this._hash = manifestData.hash; - this._rawData = manifestData.rawData; - this.restoreCache(); - } + restoreManifest(manifestData) { + if (!manifestData) { + this._isValid = false; + return; + } + this._path = manifestData.path; + this._hash = manifestData.hash; + this._rawData = manifestData.rawData; - restoreCache() { - this.cache = ["jakecache.js"]; - // Ignore different protocol - for (let pathname of this._rawData.cache) { - let path = new URL(pathname, location); - if (path.protocol === location.protocol) { - this.cache.push(path); - } + this.restoreCache(); } - this.fallback = []; - for (let entry of this._rawData.fallback) { - let [pathname, fallbackPath] = entry.split(" "); - let path = new URL(pathname, location); - let fallback = new URL(fallbackPath, location); - - // Ignore cross-origin fallbacks - if (path.origin === fallback.origin) { - this.fallback.push([path, fallback]); - this.cache.push(fallback); - } - } + restoreCache() { + this.cache = ["jakecache.js"]; + let tmp = {}; + // Ignore different protocol + for (let pathname of this._rawData.cache) { + let path = new URL(pathname, location); + if (path.protocol === location.protocol) { + if (!tmp[path]) { + this.cache.push(path); + tmp[path] = path; + } + } + } - this.allowNetworkFallback = false; - this.network = []; - for (let entry of this._rawData.network) { - if (entry === "*") { - this.allowNetworkFallback = true; - continue; - } - let path = new URL(entry, location); - if (path.protocol === location.protocol) { - this.network.push(path); - } - } + this.fallback = []; + for (let entry of this._rawData.fallback) { + let [pathname, fallbackPath] = entry.split(" "); + let path = new URL(pathname, location); + let fallback = new URL(fallbackPath, location); - this._isValid = true; - } + // Ignore cross-origin fallbacks + if (path.origin === fallback.origin) { + this.fallback.push([path, fallback]); + this.cache.push(fallback); + } + } - pathName() { - return this._path; - } + this.allowNetworkFallback = false; + this.network = []; + for (let entry of this._rawData.network) { + if (entry === "*") { + this.allowNetworkFallback = true; + continue; + } + let path = new URL(entry, location); + if (path.protocol === location.protocol) { + this.network.push(path); + } + } - cacheName() { - let version = this._rawData.version; - return version + "_" + this._hash; - } + this._isValid = true; + } - fetchData(path, options = {}) { - this._path = path; + pathName() { + return this._path; + } - if (this._isValid && options.cache !== "reload") { - return Promise.resolve(false); + cacheName() { + let version = this._rawData.version; + return version + "_" + this._hash; } - // http://html5doctor.com/go-offline-with-application-cache/ - return fetch(new Request(this._path, options), this._fetchOptions).then( - response => { - if ( - response.type === "opaque" || - response.status === 404 || - response.status === 410 - ) { - return Promise.reject(); + fetchData(path, options = {}) { + this._path = path; + + if (this._isValid && options.cache !== "reload") { + return Promise.resolve(false); } - this._hash = options.hash ? options.hash : this._hash; + // http://html5doctor.com/go-offline-with-application-cache/ + return fetch(new Request(this._path, options), this._fetchOptions).then( + response => { + if ( + response.type === "opaque" || + response.status === 404 || + response.status === 410 + ) { + return Promise.reject(); + } - return response.text().then(result => { - return new Promise((resolve, reject) => { - let hash = md5(result); - if (this._hash && hash.toString() === this._hash.toString()) { - console.log("noupdate: " + hash); - return resolve(false); + this._hash = options.hash ? options.hash : this._hash; + + return response.text().then(result => { + return new Promise((resolve, reject) => { + let hash = md5(result); + if (this._hash && hash.toString() === this._hash.toString()) { + console.log(`JakeCache-SW noupdate: ${hash}`); + return resolve(false); + } + + console.log(`JakeCache-SW update: ${hash} (was: ${this._hash})`); + + this._hash = hash; + + let lines = result.split(/\r|\n/); + let header = "cache"; // default. + let versionRegexp = /\s*(#\sVersion:)\s*([\w\.]*)/gm; + + let firstLine = lines.shift(); + if (firstLine !== "CACHE MANIFEST") { + return reject(); + } + let versionFound = false; + for (let line of lines) { + if (!versionFound) { + let match = versionRegexp.exec(line); + if (match) { + versionFound = true; + this._rawData.version = match[match.length - 1]; + } + } + + line = line.replace(/#.*$/, "").trim(); + + if (line === "") { + continue; + } + + let res = line.match(/^([A-Z]*):/); + if (res) { + header = res[1].toLowerCase(); + continue; + } + + if (!this._rawData[header]) { + this._rawData[header] = []; + } + this._rawData[header].push(line); + } + + if (!versionFound) { + this._rawData.version = "" + new Date().getTime(); + } + + this.restoreCache(); + resolve(true); + }); + }); } + ); + } +} - console.log(`update: ${hash} (was: ${this._hash})`); +const isAutoUpdate = false; - this._hash = hash; +const CacheStatus = { + UNCACHED: 0, + IDLE: 1, + CHECKING: 2, + DOWNLOADING: 3, + UPDATEREADY: 4, + OBSOLETE: 5 +}; - let lines = result.split(/\r|\n/); - let header = "cache"; // default. - let versionRegexp = /\s*(#\sVersion:)\s*([\w\.]*)/gm; +let manifest = null; +let cacheStatus = CacheStatus.UNCACHED; - let firstLine = lines.shift(); - if (firstLine !== "CACHE MANIFEST") { - return reject(); - } - let versionFound = false; - for (let line of lines) { - if (!versionFound) { - let match = versionRegexp.exec(line); - if (match) { - versionFound = true; - this._rawData.version = match[match.length - 1]; - } - } +function postMessage(msg) { + return self.clients.matchAll().then(clients => { - line = line.replace(/#.*$/, "").trim(); + if (!clients.length) { + console.log(`JakeCache-SW no clients!! message:`, msg); + } - if (line === "") { - continue; - } + return Promise.all( + clients.map(client => { + return client.postMessage(msg); + }) + ); + }); +} - let res = line.match(/^([A-Z]*):/); - if (res) { - header = res[1].toLowerCase(); - continue; - } +async function storeManifest(newManifest, manifestVersion) { + manifestVersion = manifestVersion || "current"; - if (!this._rawData[header]) { - this._rawData[header] = []; - } - this._rawData[header].push(line); - } + await idbKeyval.set(manifestVersion, newManifest.manifestData(), manifestStore); - if (!versionFound) { - this._rawData.version = "" + new Date().getTime(); - } + return Promise.resolve(newManifest); +} - this.restoreCache(); - resolve(true); - }); - }); - } - ); - } +async function loadManifest(manifestVersion) { + try { + manifestVersion = manifestVersion || "current"; + + const mnfstData = await idbKeyval.get("current", manifestStore); + if (!mnfstData) { + return Promise.resolve(null); + } + + let manifest = new JakeCacheManifest(mnfstData); + return Promise.resolve(manifest); + } catch (err) { + console.log(`JakeCache-SW error ${err}`); + return Promise.reject(err); + } } -self.addEventListener("message", function(event) { - switch (event.data.command) { - case "update": - update.call(this, event.data.pathname, event.data.options); - break; - case "abort": - postMessage({ - type: "error", - message: "Not implementable without cancellable promises." - }); - break; - case "swapCache": - swapCache(); - break; - } -}); -let manifest = null; +async function loadCurrentManifest() { + const mnf = await loadManifest("current"); + if (!mnf) { + manifest = null; + cacheStatus = CacheStatus.UNCACHED; + console.log("JakeCache-SW uncached "); + return Promise.resolve(null); + } -const CacheStatus = { - UNCACHED: 0, - IDLE: 1, - CHECKING: 2, - DOWNLOADING: 3, - UPDATEREADY: 4, - OBSOLETE: 5 -}; + manifest = mnf; + return Promise.resolve(manifest); +} -let cacheStatus = CacheStatus.UNCACHED; +async function deleteOldCaches() { + let cacheWhitelist = []; + if (!manifest) { + manifest = await loadCurrentManifest(); + } -function postMessage(msg) { - return self.clients.matchAll().then(clients => { + if (manifest) { + cacheWhitelist.push(manifest.cacheName()); + } + + console.log('JakeCache-SW deleteing old caches except:', cacheWhitelist); + + const cacheNames = await caches.keys(); return Promise.all( - clients.map(client => { - return client.postMessage(msg); - }) - ); - }); + cacheNames.map(function (cacheName) { + if (cacheWhitelist.indexOf(cacheName) === -1) { + return caches.delete(cacheName); + } + })); } -function swapCache() { - idbKeyval - .get("current", manifestStore) - .then(mnfstData => { - if (mnfstData) { - return caches.delete(mnfstData.cacheName); - } - }) - .then(() => { - return idbKeyval.get("next", manifestStore); - }) - .then(mnfstData => { - if (mnfstData) { - manifest = new JakeCacheManifest(mnfstData); - - return idbKeyval.set("current", mnfstData, manifestStore); - } else { - cacheStatus = CacheStatus.UNCACHED; - } - }) - .then(() => { - return idbKeyval.del("next", manifestStore); - }) - .then(_ => { - cacheStatus = CacheStatus.IDLE; - postMessage({ type: "updated" }); - }); -} +let updating = false; -// 7.9.4 -function update(pathname, options = {}) { - if (!pathname) { - console.log("No pathname!"); - return Promise.reject(); - } +async function update(pathname, options = {}) { + if (!pathname) { + console.log("JakeCache-SW No pathname!"); + return Promise.reject('No pathname'); + } - let nextManifest = new JakeCacheManifest(); + if (updating) { + console.log("JakeCache-SW already updating"); + return Promise.reject('already updating'); + } - // *.2.2 - self.options = options; + updating = true; - return idbKeyval - .get("current", manifestStore) - .then(mnfstData => { - if (!mnfstData) { - manifest = null; - self.uncached = true; - cacheStatus = CacheStatus.UNCACHED; - console.log("uncached " + self.uncached); - return Promise.resolve(self.uncached); - } else { - manifest = new JakeCacheManifest(mnfstData); - self.options = self.options || {}; - self.options.hash = manifest.hash(); - } - - return caches.open(mnfstData.cacheName).then(cache => { - if (!cache) { - manifest = null; - self.uncached = true; - cacheStatus = CacheStatus.UNCACHED; - console.log("uncached " + self.uncached); - return Promise.resolve(self.uncached); + let nextManifest = new JakeCacheManifest(); + self.options = options; + + let manifestVersion = 'current'; + + try { + if (!manifest) { + manifest = await loadCurrentManifest(); } - return cache.keys().then(keyData => { - self.uncached = !keyData || !keyData.length; - if (self.uncached) { - manifest = null; - cacheStatus = CacheStatus.UNCACHED; - } - console.log("uncached " + self.uncached); - return Promise.resolve(self.uncached); - }); - }); - }) - .then(uncached => { - if (cacheStatus === CacheStatus.UPDATEREADY) { - postMessage({ type: "updateready" }); - postMessage({ type: "abort" }); - return Promise.reject(); - } - // *.2.4 and *.2.6 - if (cacheStatus === CacheStatus.CHECKING) { - postMessage({ type: "checking" }); - postMessage({ type: "abort" }); - return Promise.reject(); - } - // *.2.4, *.2.5, *.2.6 - if (cacheStatus === CacheStatus.DOWNLOADING) { - postMessage({ type: "checking" }); - postMessage({ type: "downloading" }); - postMessage({ type: "abort" }); - return Promise.reject(); - } - return Promise.resolve(uncached); - }) - .then(uncached => { - // *.2.7 and *.2.8 - cacheStatus = CacheStatus.CHECKING; - postMessage({ type: "checking" }); - - // FIXME: *.6: Fetch manifest, mark obsolete if fails. - - return nextManifest.fetchData(pathname, self.options).catch(err => { - cacheStatus = CacheStatus.OBSOLETE; - postMessage({ type: "obsolete" }); - // FIXME: *.7: Error for each existing entry. + if (manifest) { + self.options.hash = manifest.hash(); + } + + const isNeededToUpdate = await nextManifest.fetchData(pathname, self.options); + if (isNeededToUpdate) { + console.log(`JakeCache-SW storing to cache ${nextManifest.cacheName()} `); + const cache = await caches.open(nextManifest.cacheName()); + await cache.addAll(nextManifest.cache); + + let isUpgrade = manifest && !isAutoUpdate; + if (isUpgrade) { + manifestVersion = 'next'; + } + + console.log(`JakeCache-SW stored to cache ${nextManifest.cacheName()} `); + await storeManifest(nextManifest, manifestVersion); + console.log(`JakeCache-SW saved to indexed db ${nextManifest.cacheName()} `); + + if (isAutoUpdate) { + manifest = nextManifest; + try { + await deleteOldCaches(); + } catch (err) { + console.log(`JakeCache-SW deleteOldCaches error: ${err}`); + } + } + else if (isUpgrade) { + cacheStatus = CacheStatus.UPDATEREADY; + postMessage({ type: "updateready" }); + } + + updating = false; + return Promise.resolve(); + } else { + updating = false; + cacheStatus = CacheStatus.CACHED; + return Promise.resolve('JakeCache-SW noupdate needed'); + } + } + catch (err) { + updating = false; + console.log(`JakeCache-SW error: ${err}`); cacheStatus = CacheStatus.IDLE; postMessage({ type: "idle" }); return Promise.reject(err); - }); - }) - .then(modified => { - self.modified = modified; - // *.2: If cache group already has an application cache in it, then - // this is an upgrade attempt. Otherwise, this is a cache attempt. - return caches.keys().then(cacheNames => { - return Promise.resolve(!!cacheNames.length); - }); - }) - .then(upgrade => { - self.upgrade = upgrade; - if (self.upgrade && !self.modified) { - cacheStatus = CacheStatus.IDLE; - postMessage({ type: "noupdate" }); - return Promise.reject(); - } - - // Appcache is no-cors by default. - self.requests = nextManifest.cache.map(url => { - return new Request(url, { mode: "no-cors" }); - }); - - cacheStatus = CacheStatus.DOWNLOADING; - postMessage({ type: "downloading" }); - - self.loaded = 0; - self.total = self.requests.length; - - return Promise.all( - self.requests.map(request => { - // Manual fetch to emulate appcache behavior. - return fetch(request, nextManifest._fetchOptions).then(response => { - cacheStatus = CacheStatus.PROGRESS; - postMessage({ - type: "progress", - lengthComputable: true, - loaded: ++self.loaded, - total: self.total, - url: request.url.toString() - }); + } +} - // section 5.6.4 of http://www.w3.org/TR/2011/WD-html5-20110525/offline.html +async function swapCache() { - // Redirects are fatal. - if (response.url !== request.url) { - throw Error(); - } + try { + if (!manifest) { + manifest = await loadCurrentManifest(); + } + + const mnfstNextData = await idbKeyval.get("next", manifestStore); - // FIXME: should we update this.total below? - - if (response.type !== "opaque") { - // If the error was a 404 or 410 HTTP response or equivalent - // Skip this resource. It is dropped from the cache. - if (response.status < 200 || response.status >= 300) { - return undefined; - } - - // HTTP caching rules, such as Cache-Control: no-store, are ignored. - if ( - (response.headers.get("cache-control") || "").match(/no-store/i) - ) { - return undefined; - } + if (mnfstNextData) { + await idbKeyval.set("current", mnfstNextData, manifestStore); + manifest = new JakeCacheManifest(mnfstNextData); + + await idbKeyval.del("next", manifestStore); + + try { + await deleteOldCaches(); + } catch (err) { + console.log(`JakeCache-SW deleteOldCaches error: ${err}`); } - return response; - }); - }) - ); - }) - .then(responses => { - self.responses = responses.filter(response => response); - return Promise.resolve(self.responses); - }) - .then(responses => { - console.log("Adding to cache " + nextManifest.cacheName()); - return caches - .open(nextManifest.cacheName()) - .then(cache => { - return Promise.all( - responses.map((response, index) => { - return cache.put(self.requests[index], response); - }) - ); - }) - .then(_ => { - let manifestVersion = "next"; - if (!self.upgrade) { - manifest = nextManifest; - manifestVersion = "current"; - } - - return idbKeyval.set( - manifestVersion, - nextManifest.manifestData(), - manifestStore - ); - }); - }) - .then(_ => { - if (self.upgrade) { - cacheStatus = CacheStatus.UPDATEREADY; - postMessage({ type: "updateready" }); - } else { - cacheStatus = CacheStatus.CACHED; - postMessage({ type: "cached" }); - } - return Promise.resolve(); - }) - .catch(err => { - if (err) { - postMessage({ type: "error" }, err); - console.log(err); - } - }); -} + console.log(`JakeCache-SW swapCache done`); -self.addEventListener("install", function(event) { - event.waitUntil(self.skipWaiting()); -}); + postMessage({ type: "updated" }); + } else { + console.log(`JakeCache-SW no manifest to update to`); + } -self.addEventListener("activate", function(event) { - event.waitUntil( - idbKeyval - .get("current", manifestStore) - .then(mnfstData => { - if (mnfstData) { - manifest = new JakeCacheManifest(mnfstData); - cacheStatus = CacheStatus.CACHED; + if (!manifest) { + cacheStatus = CacheStatus.UNCACHED; + } else { + cacheStatus = CacheStatus.CACHED; } - return Promise.resolve(manifest); - }) - .then(mnfst => { - if (mnfst) { - return update(mnfst.pathName(), { cache: "reload" }); + } + catch (err) { + console.log(`JakeCache-SW swapCache error: ${err}`); + + if (mnfstNextData) { + cacheStatus = CacheStatus.UPDATEREADY; + postMessage({ type: "updateready" }); + } else { + cacheStatus = CacheStatus.UNCACHED; + postMessage({ type: "error" }); } - return Promise.resolve(); - }) - .then(self.clients.claim()) - ); + + return Promise.reject(err); + } +} + + +self.addEventListener("message", function (event) { + let loc = location.pathname.replace('jakecache-sw.js', 'ns.appcache'); + loc = location.pathname.replace('sw.js', 'ns.appcache'); + + switch (event.data.command) { + case "update": + let path = event.data.pathname || loc; + update.call(this, path, event.data.options); + break; + case "abort": + postMessage({ + type: "error", + message: "Not implementable without cancellable promises." + }); + break; + case "swapCache": + swapCache(); + break; + } }); -self.addEventListener("fetch", function(event) { - if (manifest && manifest.isValid() && cacheStatus === CacheStatus.UNCACHED) { - cacheStatus = CacheStatus.CACHED; - } +const manifestName = 'manifest.appcache'; - if (cacheStatus === CacheStatus.UNCACHED) { - if (event.request.cache === 'only-if-cached' && event.request.mode !== 'same-origin') { - return event.respondWith(Promise.resolve()); +self.addEventListener("install", function (event) { + let loc = location.pathname.replace(/([\w\-]+\.js)/, manifestName); + + event.waitUntil( + update(loc, { cache: "reload" }) + .catch((e) => Promise.resolve()) + .finally(() => self.skipWaiting()) + ); +}); + +self.addEventListener("activate", function (event) { + event.waitUntil( + deleteOldCaches() + .then(function () { + self.clients.claim(); + }) + ); +}); + +function fromNetwork(request) { + return fetch(request); +} + +async function fromCache(request) { + + let cacheName = ''; + if (!manifest) { + manifest = await loadCurrentManifest(); } - return fetch(event.request); - } - let url = new URL(event.request.url); + if (manifest) { + cacheName = manifest.cacheName(); + } - // Ignore non-GET and different schemes. - if ( - !event.request.url.startsWith(self.location.origin) || - event.request.method !== "GET" || - url.scheme !== location.scheme - ) { - return; - } + if (!cacheName) { + Promise.reject('no-cache'); + } - if (!event.request.url.startsWith(self.location.origin)) { - // External request, or POST, ignore - return event.respondWith(fetch(event.request)); - } + return caches.open(cacheName).then((cache) => + cache.match(request).then((matching) => + matching || Promise.reject('no-match') + )); +} - // FIXME TEST: Get data from IndexedDB instead. - let mnfstPromise = manifest - ? Promise.resolve(manifest) - : idbKeyval.get("current", manifestStore).then(mnfstData => { - if (mnfstData) { - manifest = new JakeCacheManifest(mnfstData); - } - return manifest; - }); - - function checkCache(mnfst) { - return caches - .open(mnfst.cacheName()) - .then(function(cache) { - if (!cache) { - throw Error("Cache not found"); - } +// to refresh cache +async function updateCache(request, response) { + // cach eonly js files + if (!request.url.endsWith('.js')) { + return Promise.resolve(); + } - return cache.match(event.request).then(response => { - // Cache always wins. - if (response) { - return response; - } - - // Fallbacks consult network, and falls back on failure. - for (let [path, fallback] of mnfst.fallback) { - if (url.href.indexOf(path) === 0) { - return fetch(event.request) - .then(response => { - // Same origin only. - if (new URL(response.url).origin !== location.origin) { - throw Error(); - } - - if (response.type !== "opaque") { - if (response.status < 200 || response.status >= 300) { - throw Error(); - } - } - }) - .catch(_ => { - return cache.match(fallback); - }); + let cacheName = ''; + if (!manifest) { + manifest = await loadCurrentManifest(); + } + + if (manifest) { + cacheName = manifest.cacheName(); + } + + if (!cacheName) { + Promise.reject('no-cache'); + } + + return caches.open(cacheName).then((cache) => + fetch(request).then((response) => + cache.put(request, response.clone()).then(() => response) + ) + ); +} + +self.addEventListener("fetch", function (event) { + let url = new URL(event.request.url); + // console.log(url); + + if (event.request.url.includes("sw-fetch-test")) { + event.respondWith(new Response('{"result": "ok"}', { + headers: { + "status": 200, + "statusText": "service worker response", + 'Content-Type': 'application/json' } - } - - if (mnfst.allowNetworkFallback) { - return fetch(event.request); - } - - return response; // failure. - }); - }) - .catch(err => { - if (err) { - postMessage({ type: "error" }, err); - console.log(err); - } - //error with cache remove current manifest - manifest = null; - idbKeyval.del("current", manifestStore); - cacheStatus = CacheStatus.UNCACHED; - return fetch(event.request); - }); - } + })); + return; + } - event.respondWith( - mnfstPromise - .then(mnfst => { - if (!mnfst) { - cacheStatus = CacheStatus.UNCACHED; - return fetch(event.request); - } + // Ignore non-GET and different schemes. + if ( + (event.request.cache === 'only-if-cached' && event.request.mode !== 'same-origin' || + !event.request.url.startsWith(self.location.origin) || + event.request.method !== "GET" || + url.protocol !== location.protocol) + ) { + return; + } - // Process network-only. - if (mnfst.network.filter(entry => entry.href === url.href).length) { - return fetch(event.request); - } + event.respondWith(async function () { - return checkCache(mnfst); - }) - .catch(err => { - if (err) { - postMessage({ type: "error" }, err); - console.log(err); + try { + return await fromCache(event.request); + } catch (e) { + const resp = await fromNetwork(event.request); + event.waitUntil(async function () { + await updateCache(event.request); + }()); + return resp; } - //error with cache remove current manifest - manifest = null; - idbKeyval.del("current", manifestStore); - cacheStatus = CacheStatus.UNCACHED; - return fetch(event.request); - }) - ); -}); \ No newline at end of file + }()); + +}); diff --git a/dist/jakecache.js b/dist/jakecache.js index 38b5399..5a0b4e0 100644 --- a/dist/jakecache.js +++ b/dist/jakecache.js @@ -75,7 +75,8 @@ class JakeCache extends PolyfilledEventTarget { "progress", "updateready", "updated", - "noupdate" + "noupdate", + "sw-not-attached" ]); if (window.jakeCache) { @@ -87,34 +88,47 @@ class JakeCache extends PolyfilledEventTarget { return; } + const manifestAttr = "manifest"; + let onload = () => { if (document.readyState !== "complete") { return; } - + let html = document.querySelector("html"); - this.pathname = html.getAttribute("manifest"); + this.pathname = html.getAttribute(manifestAttr); + + if (this.pathname && "serviceWorker" in navigator) { + + var self = this; - if (this.pathname && "serviceWorker" in navigator) { navigator.serviceWorker - .register("jakecache-sw.js") - .then(registration => { - console.log(`JakeCache installed for ${registration.scope}`); - - if (registration.active) { - // Check whether we have a cache, or cache it (no reload enforced). - console.log("cache check"); - registration.active.postMessage({ - command: "update", - pathname: this.pathname - }); - } - }) - .catch(err => { - console.log(`JakeCache installation failed: ${err}`); - }); + .register("jakecache-sw.js") + .then(function (reg) { + if (reg.installing) { + console.log('JakeCache Service worker installing'); + } else if (reg.waiting) { + console.log('JakeCache Service worker installed'); + } else if (reg.active) { + console.log('JakeCache Service worker active'); + } + return reg; + }) + .then(navigator.serviceWorker.ready) + .then(function (registration) { + console.log('JakeCache service worker registered'); + if (registration.active) { + registration.active.postMessage({ + command: "update", + pathname: self.pathname + }); + } + }) + .catch(function (error) { + console.error('JakeCache error when registering service worker', error, arguments); + }); } - }; + }; if (document.readyState === "complete") { onload(); @@ -122,6 +136,31 @@ class JakeCache extends PolyfilledEventTarget { document.onreadystatechange = onload; } + this.checkIfServiceWorkerAttachedToPage = function checkIfServiceWorkerAttachedToPage() + { + console.log('JakeCache checking for Service Fetch'); + fetch(`sw-fetch-test`) + .then(response => { + if (!response.ok) { + throw Error(response.statusText); + } + return response; + }) + .then(response => response.json()) + .then((data) => { + if (data.result !== 'ok') { + throw Error("Invalid response"); + } + }) + .catch(err => { + console.log('JakeCache Service worker not attached !!!'); + + { + this.dispatchEvent(new CustomEvent("sw-not-attached")); + } + }); + }; + this[_status] = this.UNCACHED; navigator.serviceWorker.addEventListener("message", event => { @@ -139,6 +178,7 @@ class JakeCache extends PolyfilledEventTarget { case "cached": this[_status] = this.IDLE; this.dispatchEvent(new CustomEvent("cached")); + this.checkIfServiceWorkerAttachedToPage(); break; case "downloading": this[_status] = this.DOWNLOADING; @@ -154,7 +194,9 @@ class JakeCache extends PolyfilledEventTarget { break; case "noupdate": this[_status] = this.IDLE; - this.dispatchEvent(new CustomEvent("noupdate")); + this.dispatchEvent(new CustomEvent("noupdate")); + console.log('JakeCache noupdate event'); + this.checkIfServiceWorkerAttachedToPage(); break; case "progress": let ev = new ProgressEvent("progress", event.data); @@ -167,6 +209,9 @@ class JakeCache extends PolyfilledEventTarget { break; case "error": this.dispatchEvent(new ErrorEvent("error", event.data)); + + // try to update + this.update(); break; } }); @@ -198,15 +243,16 @@ class JakeCache extends PolyfilledEventTarget { } update() { - if (false) {} - navigator.serviceWorker.controller.postMessage({ - command: "update", - pathname: this.pathname, - options: { - cache: "reload" + if (navigator.onLine && navigator.serviceWorker.controller) { + navigator.serviceWorker.controller.postMessage({ + command: "update", + pathname: this.pathname, + options: { + cache: "reload" + } + }); } - }); } abort() { @@ -224,10 +270,11 @@ class JakeCache extends PolyfilledEventTarget { "there is no newer application cache to swap to." ); } + navigator.serviceWorker.controller.postMessage({ command: "swapCache" }); } } -new JakeCache(); \ No newline at end of file +new JakeCache(); diff --git a/jakecache-sw.js b/jakecache-sw.js index e0487bf..bc5ef6d 100644 --- a/jakecache-sw.js +++ b/jakecache-sw.js @@ -1,587 +1,548 @@ -import { md5 } from "./lib/md5"; +'use strict'; + +// actual sources https://github.com/segios/jakecache + +import { md5 } from './lib/md5' + self.importScripts("idb-keyval-iife.js"); const manifestStore = new idbKeyval.Store("manifest-db", "manifest-db"); class JakeCacheManifest { - constructor(data) { - this._path = null; - this._hash = null; - this._isValid = false; - this._fetchOptions = { credentials: "same-origin" }; - this._rawData = { - version: "", - cache: [], - fallback: [], - network: [] - }; - - if (data) { - this.restoreManifest(data); + constructor(data) { + this._path = null; + this._hash = null; + this._isValid = false; + this._fetchOptions = { credentials: "same-origin" }; + this._rawData = { + version: "", + cache: [], + fallback: [], + network: [] + }; + + if (data) { + this.restoreManifest(data); + } } - } - - hash() { - return this._hash; - } - isValid() { - return this._isValid; - } - manifestData() { - return { - cacheName: this.cacheName(), - path: this._path, - hash: this._hash, - isValid: this._isValid, - rawData: this._rawData - }; - } - - restoreManifest(manifestData) { - if (!manifestData) { - this._isValid = false; - return; + + hash() { + return this._hash; } - this._path = manifestData.path; - this._hash = manifestData.hash; - this._rawData = manifestData.rawData; - - this.restoreCache(); - } - - restoreCache() { - this.cache = ["jakecache.js"]; - // Ignore different protocol - for (let pathname of this._rawData.cache) { - let path = new URL(pathname, location); - if (path.protocol === location.protocol) { - this.cache.push(path); - } + isValid() { + return this._isValid; } - - this.fallback = []; - for (let entry of this._rawData.fallback) { - let [pathname, fallbackPath] = entry.split(" "); - let path = new URL(pathname, location); - let fallback = new URL(fallbackPath, location); - - // Ignore cross-origin fallbacks - if (path.origin === fallback.origin) { - this.fallback.push([path, fallback]); - this.cache.push(fallback); - } + manifestData() { + return { + cacheName: this.cacheName(), + path: this._path, + hash: this._hash, + isValid: this._isValid, + rawData: this._rawData + }; } - this.allowNetworkFallback = false; - this.network = []; - for (let entry of this._rawData.network) { - if (entry === "*") { - this.allowNetworkFallback = true; - continue; - } - let path = new URL(entry, location); - if (path.protocol === location.protocol) { - this.network.push(path); - } + restoreManifest(manifestData) { + if (!manifestData) { + this._isValid = false; + return; + } + this._path = manifestData.path; + this._hash = manifestData.hash; + this._rawData = manifestData.rawData; + + this.restoreCache(); } - this._isValid = true; - } + restoreCache() { + this.cache = ["jakecache.js"]; + let tmp = {}; + // Ignore different protocol + for (let pathname of this._rawData.cache) { + let path = new URL(pathname, location); + if (path.protocol === location.protocol) { + if (!tmp[path]) { + this.cache.push(path); + tmp[path] = path; + } + } + } - pathName() { - return this._path; - } + this.fallback = []; + for (let entry of this._rawData.fallback) { + let [pathname, fallbackPath] = entry.split(" "); + let path = new URL(pathname, location); + let fallback = new URL(fallbackPath, location); - cacheName() { - let version = this._rawData.version; - return version + "_" + this._hash; - } + // Ignore cross-origin fallbacks + if (path.origin === fallback.origin) { + this.fallback.push([path, fallback]); + this.cache.push(fallback); + } + } + + this.allowNetworkFallback = false; + this.network = []; + for (let entry of this._rawData.network) { + if (entry === "*") { + this.allowNetworkFallback = true; + continue; + } + let path = new URL(entry, location); + if (path.protocol === location.protocol) { + this.network.push(path); + } + } - fetchData(path, options = {}) { - this._path = path; + this._isValid = true; + } + + pathName() { + return this._path; + } - if (this._isValid && options.cache !== "reload") { - return Promise.resolve(false); + cacheName() { + let version = this._rawData.version; + return version + "_" + this._hash; } - // http://html5doctor.com/go-offline-with-application-cache/ - return fetch(new Request(this._path, options), this._fetchOptions).then( - response => { - if ( - response.type === "opaque" || - response.status === 404 || - response.status === 410 - ) { - return Promise.reject(); + fetchData(path, options = {}) { + this._path = path; + + if (this._isValid && options.cache !== "reload") { + return Promise.resolve(false); } - this._hash = options.hash ? options.hash : this._hash; + // http://html5doctor.com/go-offline-with-application-cache/ + return fetch(new Request(this._path, options), this._fetchOptions).then( + response => { + if ( + response.type === "opaque" || + response.status === 404 || + response.status === 410 + ) { + return Promise.reject(); + } - return response.text().then(result => { - return new Promise((resolve, reject) => { - let hash = md5(result); - if (this._hash && hash.toString() === this._hash.toString()) { - console.log("noupdate: " + hash); - return resolve(false); + this._hash = options.hash ? options.hash : this._hash; + + return response.text().then(result => { + return new Promise((resolve, reject) => { + let hash = md5(result); + if (this._hash && hash.toString() === this._hash.toString()) { + console.log(`JakeCache-SW noupdate: ${hash}`); + return resolve(false); + } + + console.log(`JakeCache-SW update: ${hash} (was: ${this._hash})`); + + this._hash = hash; + + let lines = result.split(/\r|\n/); + let header = "cache"; // default. + let versionRegexp = /\s*(#\sVersion:)\s*([\w\.]*)/gm; + + let firstLine = lines.shift(); + if (firstLine !== "CACHE MANIFEST") { + return reject(); + } + let versionFound = false; + for (let line of lines) { + if (!versionFound) { + let match = versionRegexp.exec(line); + if (match) { + versionFound = true; + this._rawData.version = match[match.length - 1]; + } + } + + line = line.replace(/#.*$/, "").trim(); + + if (line === "") { + continue; + } + + let res = line.match(/^([A-Z]*):/); + if (res) { + header = res[1].toLowerCase(); + continue; + } + + if (!this._rawData[header]) { + this._rawData[header] = []; + } + this._rawData[header].push(line); + } + + if (!versionFound) { + this._rawData.version = "" + new Date().getTime(); + } + + this.restoreCache(); + resolve(true); + }); + }); } + ); + } +} - console.log(`update: ${hash} (was: ${this._hash})`); +const isAutoUpdate = false; - this._hash = hash; +const CacheStatus = { + UNCACHED: 0, + IDLE: 1, + CHECKING: 2, + DOWNLOADING: 3, + UPDATEREADY: 4, + OBSOLETE: 5 +}; - let lines = result.split(/\r|\n/); - let header = "cache"; // default. - let versionRegexp = /\s*(#\sVersion:)\s*([\w\.]*)/gm; +let manifest = null; +let cacheStatus = CacheStatus.UNCACHED; - let firstLine = lines.shift(); - if (firstLine !== "CACHE MANIFEST") { - return reject(); - } - let versionFound = false; - for (let line of lines) { - if (!versionFound) { - let match = versionRegexp.exec(line); - if (match) { - versionFound = true; - this._rawData.version = match[match.length - 1]; - } - } +function postMessage(msg) { + return self.clients.matchAll().then(clients => { - line = line.replace(/#.*$/, "").trim(); + if (!clients.length) { + console.log(`JakeCache-SW no clients!! message:`, msg); + } - if (line === "") { - continue; - } + return Promise.all( + clients.map(client => { + return client.postMessage(msg); + }) + ); + }); +} - let res = line.match(/^([A-Z]*):/); - if (res) { - header = res[1].toLowerCase(); - continue; - } +async function storeManifest(newManifest, manifestVersion) { + manifestVersion = manifestVersion || "current"; - if (!this._rawData[header]) { - this._rawData[header] = []; - } - this._rawData[header].push(line); - } + await idbKeyval.set(manifestVersion, newManifest.manifestData(), manifestStore); - if (!versionFound) { - this._rawData.version = "" + new Date().getTime(); - } + return Promise.resolve(newManifest); +} - this.restoreCache(); - resolve(true); - }); - }); - } - ); - } +async function loadManifest(manifestVersion) { + try { + manifestVersion = manifestVersion || "current"; + + const mnfstData = await idbKeyval.get("current", manifestStore); + if (!mnfstData) { + return Promise.resolve(null); + } + + let manifest = new JakeCacheManifest(mnfstData); + return Promise.resolve(manifest); + } catch (err) { + console.log(`JakeCache-SW error ${err}`); + return Promise.reject(err); + } } -self.addEventListener("message", function(event) { - switch (event.data.command) { - case "update": - update.call(this, event.data.pathname, event.data.options); - break; - case "abort": - postMessage({ - type: "error", - message: "Not implementable without cancellable promises." - }); - break; - case "swapCache": - swapCache(); - break; - } -}); -let manifest = null; +async function loadCurrentManifest() { + const mnf = await loadManifest("current"); + if (!mnf) { + manifest = null; + cacheStatus = CacheStatus.UNCACHED; + console.log("JakeCache-SW uncached "); + return Promise.resolve(null); + } -const CacheStatus = { - UNCACHED: 0, - IDLE: 1, - CHECKING: 2, - DOWNLOADING: 3, - UPDATEREADY: 4, - OBSOLETE: 5 -}; + manifest = mnf; + return Promise.resolve(manifest); +} -let cacheStatus = CacheStatus.UNCACHED; +async function deleteOldCaches() { + let cacheWhitelist = []; + if (!manifest) { + manifest = await loadCurrentManifest(); + } -function postMessage(msg) { - return self.clients.matchAll().then(clients => { + if (manifest) { + cacheWhitelist.push(manifest.cacheName()); + } + + console.log('JakeCache-SW deleteing old caches except:', cacheWhitelist); + + const cacheNames = await caches.keys(); return Promise.all( - clients.map(client => { - return client.postMessage(msg); - }) - ); - }); + cacheNames.map(function (cacheName) { + if (cacheWhitelist.indexOf(cacheName) === -1) { + return caches.delete(cacheName); + } + })); } -function swapCache() { - idbKeyval - .get("current", manifestStore) - .then(mnfstData => { - if (mnfstData) { - return caches.delete(mnfstData.cacheName); - } - }) - .then(() => { - return idbKeyval.get("next", manifestStore); - }) - .then(mnfstData => { - if (mnfstData) { - manifest = new JakeCacheManifest(mnfstData); - - return idbKeyval.set("current", mnfstData, manifestStore); - } else { - cacheStatus = CacheStatus.UNCACHED; - } - }) - .then(() => { - return idbKeyval.del("next", manifestStore); - }) - .then(_ => { - cacheStatus = CacheStatus.IDLE; - postMessage({ type: "updated" }); - }); -} +let updating = false; -// 7.9.4 -function update(pathname, options = {}) { - if (!pathname) { - console.log("No pathname!"); - return Promise.reject(); - } +async function update(pathname, options = {}) { + if (!pathname) { + console.log("JakeCache-SW No pathname!"); + return Promise.reject('No pathname'); + } - let nextManifest = new JakeCacheManifest(); + if (updating) { + console.log("JakeCache-SW already updating"); + return Promise.reject('already updating'); + } - // *.2.2 - self.options = options; + updating = true; - return idbKeyval - .get("current", manifestStore) - .then(mnfstData => { - if (!mnfstData) { - manifest = null; - self.uncached = true; - cacheStatus = CacheStatus.UNCACHED; - console.log("uncached " + self.uncached); - return Promise.resolve(self.uncached); - } else { - manifest = new JakeCacheManifest(mnfstData); - self.options = self.options || {}; - self.options.hash = manifest.hash(); - } - - return caches.open(mnfstData.cacheName).then(cache => { - if (!cache) { - manifest = null; - self.uncached = true; - cacheStatus = CacheStatus.UNCACHED; - console.log("uncached " + self.uncached); - return Promise.resolve(self.uncached); + let nextManifest = new JakeCacheManifest(); + self.options = options; + + let manifestVersion = 'current'; + + try { + if (!manifest) { + manifest = await loadCurrentManifest(); } - return cache.keys().then(keyData => { - self.uncached = !keyData || !keyData.length; - if (self.uncached) { - manifest = null; - cacheStatus = CacheStatus.UNCACHED; - } - console.log("uncached " + self.uncached); - return Promise.resolve(self.uncached); - }); - }); - }) - .then(uncached => { - if (cacheStatus === CacheStatus.UPDATEREADY) { - postMessage({ type: "updateready" }); - postMessage({ type: "abort" }); - return Promise.reject(); - } - // *.2.4 and *.2.6 - if (cacheStatus === CacheStatus.CHECKING) { - postMessage({ type: "checking" }); - postMessage({ type: "abort" }); - return Promise.reject(); - } - // *.2.4, *.2.5, *.2.6 - if (cacheStatus === CacheStatus.DOWNLOADING) { - postMessage({ type: "checking" }); - postMessage({ type: "downloading" }); - postMessage({ type: "abort" }); - return Promise.reject(); - } - return Promise.resolve(uncached); - }) - .then(uncached => { - // *.2.7 and *.2.8 - cacheStatus = CacheStatus.CHECKING; - postMessage({ type: "checking" }); - - // FIXME: *.6: Fetch manifest, mark obsolete if fails. - - return nextManifest.fetchData(pathname, self.options).catch(err => { - cacheStatus = CacheStatus.OBSOLETE; - postMessage({ type: "obsolete" }); - // FIXME: *.7: Error for each existing entry. + if (manifest) { + self.options.hash = manifest.hash(); + } + + const isNeededToUpdate = await nextManifest.fetchData(pathname, self.options); + if (isNeededToUpdate) { + console.log(`JakeCache-SW storing to cache ${nextManifest.cacheName()} `); + const cache = await caches.open(nextManifest.cacheName()); + await cache.addAll(nextManifest.cache); + + let isUpgrade = manifest && !isAutoUpdate; + if (isUpgrade) { + manifestVersion = 'next'; + } + + console.log(`JakeCache-SW stored to cache ${nextManifest.cacheName()} `); + await storeManifest(nextManifest, manifestVersion); + console.log(`JakeCache-SW saved to indexed db ${nextManifest.cacheName()} `); + + if (isAutoUpdate) { + manifest = nextManifest; + try { + await deleteOldCaches(); + } catch (err) { + console.log(`JakeCache-SW deleteOldCaches error: ${err}`); + } + } + else if (isUpgrade) { + cacheStatus = CacheStatus.UPDATEREADY; + postMessage({ type: "updateready" }); + } + + updating = false; + return Promise.resolve(); + } else { + updating = false; + cacheStatus = CacheStatus.CACHED; + return Promise.resolve('JakeCache-SW noupdate needed'); + } + } + catch (err) { + updating = false; + console.log(`JakeCache-SW error: ${err}`); cacheStatus = CacheStatus.IDLE; postMessage({ type: "idle" }); return Promise.reject(err); - }); - }) - .then(modified => { - self.modified = modified; - // *.2: If cache group already has an application cache in it, then - // this is an upgrade attempt. Otherwise, this is a cache attempt. - return caches.keys().then(cacheNames => { - return Promise.resolve(!!cacheNames.length); - }); - }) - .then(upgrade => { - self.upgrade = upgrade; - if (self.upgrade && !self.modified) { - cacheStatus = CacheStatus.IDLE; - postMessage({ type: "noupdate" }); - return Promise.reject(); - } - - // Appcache is no-cors by default. - self.requests = nextManifest.cache.map(url => { - return new Request(url, { mode: "no-cors" }); - }); - - cacheStatus = CacheStatus.DOWNLOADING; - postMessage({ type: "downloading" }); - - self.loaded = 0; - self.total = self.requests.length; - - return Promise.all( - self.requests.map(request => { - // Manual fetch to emulate appcache behavior. - return fetch(request, nextManifest._fetchOptions).then(response => { - cacheStatus = CacheStatus.PROGRESS; + } +} + +async function swapCache() { + + try { + if (!manifest) { + manifest = await loadCurrentManifest(); + } + + const mnfstNextData = await idbKeyval.get("next", manifestStore); + + if (mnfstNextData) { + await idbKeyval.set("current", mnfstNextData, manifestStore); + manifest = new JakeCacheManifest(mnfstNextData); + + await idbKeyval.del("next", manifestStore); + + try { + await deleteOldCaches(); + } catch (err) { + console.log(`JakeCache-SW deleteOldCaches error: ${err}`); + } + + console.log(`JakeCache-SW swapCache done`); + + postMessage({ type: "updated" }); + } else { + console.log(`JakeCache-SW no manifest to update to`); + } + + if (!manifest) { + cacheStatus = CacheStatus.UNCACHED; + } else { + cacheStatus = CacheStatus.CACHED; + } + } + catch (err) { + console.log(`JakeCache-SW swapCache error: ${err}`); + + if (mnfstNextData) { + cacheStatus = CacheStatus.UPDATEREADY; + postMessage({ type: "updateready" }); + } else { + cacheStatus = CacheStatus.UNCACHED; + postMessage({ type: "error" }); + } + + return Promise.reject(err); + } +} + + +self.addEventListener("message", function (event) { + let loc = location.pathname.replace('jakecache-sw.js', 'ns.appcache'); + loc = location.pathname.replace('sw.js', 'ns.appcache'); + + switch (event.data.command) { + case "update": + let path = event.data.pathname || loc; + update.call(this, path, event.data.options); + break; + case "abort": postMessage({ - type: "progress", - lengthComputable: true, - loaded: ++self.loaded, - total: self.total, - url: request.url.toString() + type: "error", + message: "Not implementable without cancellable promises." }); + break; + case "swapCache": + swapCache(); + break; + } +}); - // section 5.6.4 of http://www.w3.org/TR/2011/WD-html5-20110525/offline.html +const manifestName = 'app.manifest'; - // Redirects are fatal. - if (response.url !== request.url) { - throw Error(); - } +self.addEventListener("install", function (event) { + let loc = location.pathname.replace(/([\w\-]+\.js)/, manifestName); - // FIXME: should we update this.total below? - - if (response.type !== "opaque") { - // If the error was a 404 or 410 HTTP response or equivalent - // Skip this resource. It is dropped from the cache. - if (response.status < 200 || response.status >= 300) { - return undefined; - } - - // HTTP caching rules, such as Cache-Control: no-store, are ignored. - if ( - (response.headers.get("cache-control") || "").match(/no-store/i) - ) { - return undefined; - } - } + event.waitUntil( + update(loc, { cache: "reload" }) + .catch((e) => Promise.resolve()) + .finally(() => self.skipWaiting()) + ); +}); - return response; - }); - }) - ); - }) - .then(responses => { - self.responses = responses.filter(response => response); - return Promise.resolve(self.responses); - }) - .then(responses => { - console.log("Adding to cache " + nextManifest.cacheName()); - return caches - .open(nextManifest.cacheName()) - .then(cache => { - return Promise.all( - responses.map((response, index) => { - return cache.put(self.requests[index], response); +self.addEventListener("activate", function (event) { + event.waitUntil( + deleteOldCaches() + .then(function () { + self.clients.claim(); }) - ); - }) - .then(_ => { - let manifestVersion = "next"; - if (!self.upgrade) { - manifest = nextManifest; - manifestVersion = "current"; - } - - return idbKeyval.set( - manifestVersion, - nextManifest.manifestData(), - manifestStore - ); - }); - }) - .then(_ => { - if (self.upgrade) { - cacheStatus = CacheStatus.UPDATEREADY; - postMessage({ type: "updateready" }); - } else { - cacheStatus = CacheStatus.CACHED; - postMessage({ type: "cached" }); - } - return Promise.resolve(); - }) - .catch(err => { - if (err) { - postMessage({ type: "error" }, err); - console.log(err); - } - }); + ); +}); + +function fromNetwork(request) { + return fetch(request); } -self.addEventListener("install", function(event) { - event.waitUntil(self.skipWaiting()); -}); +async function fromCache(request) { -self.addEventListener("activate", function(event) { - event.waitUntil( - idbKeyval - .get("current", manifestStore) - .then(mnfstData => { - if (mnfstData) { - manifest = new JakeCacheManifest(mnfstData); - cacheStatus = CacheStatus.CACHED; - } - return Promise.resolve(manifest); - }) - .then(mnfst => { - if (mnfst) { - return update(mnfst.pathName(), { cache: "reload" }); - } + let cacheName = ''; + if (!manifest) { + manifest = await loadCurrentManifest(); + } + + if (manifest) { + cacheName = manifest.cacheName(); + } + + if (!cacheName) { + Promise.reject('no-cache'); + } + + return caches.open(cacheName).then((cache) => + cache.match(request).then((matching) => + matching || Promise.reject('no-match') + )); +} + +// to refresh cache +async function updateCache(request, response) { + // cache only js files + if (!request.url.endsWith('.js')) { return Promise.resolve(); - }) - .then(self.clients.claim()) - ); -}); + } -self.addEventListener("fetch", function(event) { - if (manifest && manifest.isValid() && cacheStatus === CacheStatus.UNCACHED) { - cacheStatus = CacheStatus.CACHED; - } - - let url = new URL(event.request.url); - - // Ignore non-GET and different schemes. - if ( - cacheStatus === CacheStatus.UNCACHED || - (event.request.cache === "only-if-cached" && - event.request.mode !== "same-origin") || - !event.request.url.startsWith(self.location.origin) || - event.request.method !== "GET" || - url.scheme !== location.scheme - ) { - return; - } - - // FIXME TEST: Get data from IndexedDB instead. - let mnfstPromise = manifest - ? Promise.resolve(manifest) - : idbKeyval.get("current", manifestStore).then(mnfstData => { - if (mnfstData) { - manifest = new JakeCacheManifest(mnfstData); - } - return manifest; - }); - - function checkCache(mnfst) { - return caches - .open(mnfst.cacheName()) - .then(function(cache) { - if (!cache) { - throw Error("Cache not found"); - } + let cacheName = ''; + if (!manifest) { + manifest = await loadCurrentManifest(); + } - return cache.match(event.request).then(response => { - // Cache always wins. - if (response) { - return response; - } - - // Fallbacks consult network, and falls back on failure. - for (let [path, fallback] of mnfst.fallback) { - if (url.href.indexOf(path) === 0) { - return fetch(event.request) - .then(response => { - // Same origin only. - if (new URL(response.url).origin !== location.origin) { - throw Error(); - } - - if (response.type !== "opaque") { - if (response.status < 200 || response.status >= 300) { - throw Error(); - } - } - }) - .catch(_ => { - return cache.match(fallback); - }); + if (manifest) { + cacheName = manifest.cacheName(); + } + + if (!cacheName) { + Promise.reject('no-cache'); + } + + return caches.open(cacheName).then((cache) => + // todo check if we really need fetch + fetch(request).then((response) => + cache.put(request, response.clone()).then(() => response) + ) + ); +} + +function shouldInterceptRequest(url) { + return false; +} + +self.addEventListener("fetch", function (event) { + let url = new URL(event.request.url); + // console.log(url); + + if (event.request.url.includes("sw-fetch-test")) { + event.respondWith(new Response('{"result": "ok"}', { + headers: { + "status": 200, + "statusText": "service worker response", + 'Content-Type': 'application/json' } - } - - if (mnfst.allowNetworkFallback) { - return fetch(event.request); - } - - return response; // failure. - }); - }) - .catch(err => { - if (err) { - postMessage({ type: "error" }, err); - console.log(err); - } - //error with cache remove current manifest - manifest = null; - idbKeyval.del("current", manifestStore); - cacheStatus = CacheStatus.UNCACHED; - return fetch(event.request); - }); - } - - event.respondWith( - mnfstPromise - .then(mnfst => { - if (!mnfst) { - cacheStatus = CacheStatus.UNCACHED; - return fetch(event.request); - } + })); + return; + } - // Process network-only. - if (mnfst.network.filter(entry => entry.href === url.href).length) { - return fetch(event.request); - } + const interceptRequest = shouldInterceptRequest(url); + if (interceptRequest) { + event.respondWith(async function () { + return await fromNetwork(event.request); + }()); + return; + } + + // Ignore non-GET and different schemes. + if ( + (event.request.cache === 'only-if-cached' && event.request.mode !== 'same-origin' || + !event.request.url.startsWith(self.location.origin) || + event.request.method !== "GET" || + url.protocol !== location.protocol) + ) { + return; + } + + event.respondWith(async function () { - return checkCache(mnfst); - }) - .catch(err => { - if (err) { - postMessage({ type: "error" }, err); - console.log(err); + try { + return await fromCache(event.request); + } catch (e) { + const resp = await fromNetwork(event.request); + event.waitUntil(async function () { + await updateCache(event.request, resp); + }()); + return resp; } - //error with cache remove current manifest - manifest = null; - idbKeyval.del("current", manifestStore); - cacheStatus = CacheStatus.UNCACHED; - return fetch(event.request); - }) - ); + }()); + }); diff --git a/jakecache.js b/jakecache.js index 466bb67..268c535 100644 --- a/jakecache.js +++ b/jakecache.js @@ -1,3 +1,7 @@ +'use strict'; + +// actual sources https://github.com/segios/jakecache + const _eventHandlers = Symbol("eventHandlers"); let CustomEvent = window.CustomEvent; @@ -12,10 +16,10 @@ class PolyfilledEventTarget { names.map(name => { this[_eventHandlers][name] = { handler: null, listeners: [] }; Object.defineProperty(this, "on" + name, { - get: function() { + get: function () { return this[_eventHandlers][name]["handler"]; }, - set: function(fn) { + set: function (fn) { if (fn === null || fn instanceof Function) { this[_eventHandlers][name]["handler"] = fn; } @@ -73,7 +77,8 @@ class JakeCache extends PolyfilledEventTarget { "progress", "updateready", "updated", - "noupdate" + "noupdate", + "sw-not-attached" ]); if (window.jakeCache) { @@ -85,75 +90,63 @@ class JakeCache extends PolyfilledEventTarget { return; } + const manifestAttr = "manifest"; + let onload = () => { if (document.readyState !== "complete") { return; } let html = document.querySelector("html"); - this.pathname = html.getAttribute("manifest"); + this.pathname = html.getAttribute(manifestAttr); if (this.pathname && "serviceWorker" in navigator) { + + var self = this; + navigator.serviceWorker .register("jakecache-sw.js") - .then(registration => { - console.log(`JakeCache installed for ${registration.scope}`); - if (registration.waiting) { - console.log("JakeCache waiting", registration.waiting); - registration.waiting.addEventListener( - "statechange", - onStateChange("waiting") - ); + .then(function (reg) { + if (reg.installing) { + console.log('JakeCache Service worker installing'); + } else if (reg.waiting) { + console.log('JakeCache Service worker installed'); + } else if (reg.active) { + console.log('JakeCache Service worker active'); } - - if (registration.installing) { - console.log("JakeCache installing", registration.installing); - registration.installing.addEventListener( - "statechange", - onStateChange("installing") - ); - } - + return reg; + }) + .then(navigator.serviceWorker.ready) + .then(function (registration) { + console.log('JakeCache service worker registered'); if (registration.active) { - console.log("JakeCache active", registration.active); - registration.active.addEventListener( - "statechange", - onStateChange("active") - ); - - // Check whether we have a cache, or cache it (no reload enforced). - console.log("JakeCache cache check"); registration.active.postMessage({ command: "update", - pathname: this.pathname + pathname: self.pathname }); } }) - .catch(err => { - console.log(`JakeCache installation failed: ${err}`); + .catch(function (error) { + console.error('JakeCache error when registering service worker', error, arguments) }); } }; - function onStateChange(from) { - return function(e) { - console.log( - "statechange initial state ", - from, - "changed to", - e.target.state - ); - if (e.target.state === "activated") { + function onStateChange(from, registration) { + return function (e) { + console.log('JakeCache statechange initial state ', from, 'changed to', e.target.state); + if (e.target.state === 'activated') { // Check whether we have a cache, or cache it (no reload enforced). - console.log("cache check for update"); + console.log("JakeCache cache check for update"); let html = document.querySelector("html"); - this.pathname = html.getAttribute("manifest"); - - navigator.serviceWorker.controller.postMessage({ - command: "update", - pathname: this.pathname - }); + this.pathname = html.getAttribute(manifestAttr); + if (navigator.onLine && registration.active) { + registration.active.postMessage({ + command: "update", + pathname: this.pathname + }); + } } }; } @@ -164,6 +157,36 @@ class JakeCache extends PolyfilledEventTarget { document.onreadystatechange = onload; } + // force refresh page if no service worker responsed + const forceReloadPageIfServiceWorkerNotAttachedToPage = false; + + this.checkIfServiceWorkerAttachedToPage = function checkIfServiceWorkerAttachedToPage() { + console.log('JakeCache checking for Service Fetch'); + fetch(`sw-fetch-test`) + .then(response => { + if (!response.ok) { + throw Error(response.statusText); + } + return response; + }) + .then(response => response.json()) + .then((data) => { + if (data.result !== 'ok') { + throw Error("Invalid response"); + } + }) + .catch(err => { + console.log('JakeCache Service worker not attached !!!'); + + if (forceReloadPageIfServiceWorkerNotAttachedToPage) { + location.reload(); + } + else { + this.dispatchEvent(new CustomEvent("sw-not-attached")); + } + }); + } + this[_status] = this.UNCACHED; navigator.serviceWorker.addEventListener("message", event => { @@ -181,6 +204,7 @@ class JakeCache extends PolyfilledEventTarget { case "cached": this[_status] = this.IDLE; this.dispatchEvent(new CustomEvent("cached")); + this.checkIfServiceWorkerAttachedToPage(); break; case "downloading": this[_status] = this.DOWNLOADING; @@ -197,6 +221,8 @@ class JakeCache extends PolyfilledEventTarget { case "noupdate": this[_status] = this.IDLE; this.dispatchEvent(new CustomEvent("noupdate")); + console.log('JakeCache noupdate event'); + this.checkIfServiceWorkerAttachedToPage(); break; case "progress": let ev = new ProgressEvent("progress", event.data); @@ -209,6 +235,9 @@ class JakeCache extends PolyfilledEventTarget { break; case "error": this.dispatchEvent(new ErrorEvent("error", event.data)); + + // try to update + this.update(); break; } }); @@ -240,23 +269,17 @@ class JakeCache extends PolyfilledEventTarget { } update() { - if (false) { - // this.status == this.UNCACHED || this.status == this.OBSOLETE) { - // If there is no such application cache, or if its - // application cache group is marked as obsolete, then throw - throw new DOMException( - DOMException.INVALID_STATE_ERR, - "there is no application cache to update." - ); - } + if (false) { } - navigator.serviceWorker.controller.postMessage({ - command: "update", - pathname: this.pathname, - options: { - cache: "reload" - } - }); + if (navigator.onLine && navigator.serviceWorker.controller) { + navigator.serviceWorker.controller.postMessage({ + command: "update", + pathname: this.pathname, + options: { + cache: "reload" + } + }); + } } abort() { @@ -274,10 +297,11 @@ class JakeCache extends PolyfilledEventTarget { "there is no newer application cache to swap to." ); } + navigator.serviceWorker.controller.postMessage({ command: "swapCache" }); } } -new JakeCache(); +new JakeCache(); \ No newline at end of file diff --git a/package-lock.json b/package-lock.json new file mode 100644 index 0000000..a8a88f3 --- /dev/null +++ b/package-lock.json @@ -0,0 +1,925 @@ +{ + "name": "jakecache", + "version": "1.1.1", + "lockfileVersion": 1, + "requires": true, + "dependencies": { + "@babel/code-frame": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.8.3.tgz", + "integrity": "sha512-a9gxpmdXtZEInkCSHUJDLHZVBgb1QS0jhss4cPP93EW7s+uC5bikET2twEF3KV+7rDblJcmNvTR7VJejqd2C2g==", + "dev": true, + "requires": { + "@babel/highlight": "7.9.0" + } + }, + "@babel/helper-validator-identifier": { + "version": "7.9.0", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.9.0.tgz", + "integrity": "sha512-6G8bQKjOh+of4PV/ThDm/rRqlU7+IGoJuofpagU5GlEl29Vv0RGqqt86ZGRV8ZuSOY3o+8yXl5y782SMcG7SHw==", + "dev": true + }, + "@babel/highlight": { + "version": "7.9.0", + "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.9.0.tgz", + "integrity": "sha512-lJZPilxX7Op3Nv/2cvFdnlepPXDxi29wxteT57Q965oc5R9v86ztx0jfxVrTcBk8C2kcPkkDa2Z4T3ZsPPVWsQ==", + "dev": true, + "requires": { + "@babel/helper-validator-identifier": "7.9.0", + "chalk": "2.4.2", + "js-tokens": "4.0.0" + } + }, + "@rollup/plugin-commonjs": { + "version": "11.0.2", + "resolved": "https://registry.npmjs.org/@rollup/plugin-commonjs/-/plugin-commonjs-11.0.2.tgz", + "integrity": "sha512-MPYGZr0qdbV5zZj8/2AuomVpnRVXRU5XKXb3HVniwRoRCreGlf5kOE081isNWeiLIi6IYkwTX9zE0/c7V8g81g==", + "dev": true, + "requires": { + "@rollup/pluginutils": "3.0.8", + "estree-walker": "1.0.1", + "is-reference": "1.1.4", + "magic-string": "0.25.6", + "resolve": "1.14.2" + }, + "dependencies": { + "estree-walker": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-1.0.1.tgz", + "integrity": "sha512-1fMXF3YP4pZZVozF8j/ZLfvnR8NSIljt56UhbZ5PeeDmmGHpgpdwQt7ITlGvYaQukCvuBRMLEiKiYC+oeIg4cg==", + "dev": true + } + } + }, + "@rollup/plugin-node-resolve": { + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/@rollup/plugin-node-resolve/-/plugin-node-resolve-7.1.1.tgz", + "integrity": "sha512-14ddhD7TnemeHE97a4rLOhobfYvUVcaYuqTnL8Ti7Jxi9V9Jr5LY7Gko4HZ5k4h4vqQM0gBQt6tsp9xXW94WPA==", + "dev": true, + "requires": { + "@rollup/pluginutils": "3.0.8", + "@types/resolve": "0.0.8", + "builtin-modules": "3.1.0", + "is-module": "1.0.0", + "resolve": "1.14.2" + } + }, + "@rollup/pluginutils": { + "version": "3.0.8", + "resolved": "https://registry.npmjs.org/@rollup/pluginutils/-/pluginutils-3.0.8.tgz", + "integrity": "sha512-rYGeAc4sxcZ+kPG/Tw4/fwJODC3IXHYDH4qusdN/b6aLw5LPUbzpecYbEJh4sVQGPFJxd2dBU4kc1H3oy9/bnw==", + "dev": true, + "requires": { + "estree-walker": "1.0.1" + }, + "dependencies": { + "estree-walker": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-1.0.1.tgz", + "integrity": "sha512-1fMXF3YP4pZZVozF8j/ZLfvnR8NSIljt56UhbZ5PeeDmmGHpgpdwQt7ITlGvYaQukCvuBRMLEiKiYC+oeIg4cg==", + "dev": true + } + } + }, + "@types/estree": { + "version": "0.0.39", + "resolved": "https://registry.npmjs.org/@types/estree/-/estree-0.0.39.tgz", + "integrity": "sha512-EYNwp3bU+98cpU4lAWYYL7Zz+2gryWH1qbdDTidVd6hkiR6weksdbMadyXKXNPEkQFhXM+hVO9ZygomHXp+AIw==", + "dev": true + }, + "@types/node": { + "version": "13.7.6", + "resolved": "https://registry.npmjs.org/@types/node/-/node-13.7.6.tgz", + "integrity": "sha512-eyK7MWD0R1HqVTp+PtwRgFeIsemzuj4gBFSQxfPHY5iMjS7474e5wq+VFgTcdpyHeNxyKSaetYAjdMLJlKoWqA==", + "dev": true + }, + "@types/resolve": { + "version": "0.0.8", + "resolved": "https://registry.npmjs.org/@types/resolve/-/resolve-0.0.8.tgz", + "integrity": "sha512-auApPaJf3NPfe18hSoJkp8EbZzer2ISk7o8mCC3M9he/a04+gbMF97NkpD2S8riMGvm4BMRI59/SZQSaLTKpsQ==", + "dev": true, + "requires": { + "@types/node": "13.7.6" + } + }, + "acorn": { + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-7.1.1.tgz", + "integrity": "sha512-add7dgA5ppRPxCFJoAGfMDi7PIBXq1RtGo7BhbLaxwrXPOmw8gq48Y9ozT01hUKy9byMjlR20EJhu5zlkErEkg==", + "dev": true + }, + "ansi-styles": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", + "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", + "dev": true, + "requires": { + "color-convert": "1.9.3" + } + }, + "balanced-match": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.0.tgz", + "integrity": "sha1-ibTRmasr7kneFk6gK4nORi1xt2c=", + "dev": true + }, + "brace-expansion": { + "version": "1.1.11", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", + "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", + "dev": true, + "requires": { + "balanced-match": "1.0.0", + "concat-map": "0.0.1" + } + }, + "buffer-from": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.1.tgz", + "integrity": "sha512-MQcXEUbCKtEo7bhqEs6560Hyd4XaovZlO/k9V3hjVUF/zwW7KBVdSK4gIt/bzwS9MbR5qob+F5jusZsb0YQK2A==", + "dev": true + }, + "builtin-modules": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/builtin-modules/-/builtin-modules-3.1.0.tgz", + "integrity": "sha512-k0KL0aWZuBt2lrxrcASWDfwOLMnodeQjodT/1SxEQAXsHANgo6ZC/VEaSEHCXt7aSTZ4/4H5LKa+tBXmW7Vtvw==", + "dev": true + }, + "chalk": { + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", + "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", + "dev": true, + "requires": { + "ansi-styles": "3.2.1", + "escape-string-regexp": "1.0.5", + "supports-color": "5.5.0" + } + }, + "color-convert": { + "version": "1.9.3", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", + "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", + "dev": true, + "requires": { + "color-name": "1.1.3" + } + }, + "color-name": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", + "integrity": "sha1-p9BVi9icQveV3UIyj3QIMcpTvCU=", + "dev": true + }, + "commander": { + "version": "2.20.3", + "resolved": "https://registry.npmjs.org/commander/-/commander-2.20.3.tgz", + "integrity": "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==", + "dev": true + }, + "concat-map": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", + "integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=", + "dev": true + }, + "cross-spawn": { + "version": "6.0.5", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-6.0.5.tgz", + "integrity": "sha512-eTVLrBSt7fjbDygz805pMnstIs2VTBNkRm0qxZd+M7A5XDdxVRWO5MxGBXZhjY4cqLYLdtrGqRf8mBPmzwSpWQ==", + "dev": true, + "requires": { + "nice-try": "1.0.5", + "path-key": "2.0.1", + "semver": "5.7.1", + "shebang-command": "1.2.0", + "which": "1.3.1" + } + }, + "deep-equal": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/deep-equal/-/deep-equal-1.1.1.tgz", + "integrity": "sha512-yd9c5AdiqVcR+JjcwUQb9DkhJc8ngNr0MahEBGvDiJw8puWab2yZlh+nkasOnZP+EGTAP6rRp2JzJhJZzvNF8g==", + "dev": true, + "requires": { + "is-arguments": "1.0.4", + "is-date-object": "1.0.2", + "is-regex": "1.0.5", + "object-is": "1.0.2", + "object-keys": "1.1.1", + "regexp.prototype.flags": "1.3.0" + } + }, + "define-properties": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/define-properties/-/define-properties-1.1.3.tgz", + "integrity": "sha512-3MqfYKj2lLzdMSf8ZIZE/V+Zuy+BgD6f164e8K2w7dgnpKArBDerGYpM46IYYcjnkdPNMjPk9A6VFB8+3SKlXQ==", + "dev": true, + "requires": { + "object-keys": "1.1.1" + } + }, + "defined": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/defined/-/defined-1.0.0.tgz", + "integrity": "sha1-yY2bzvdWdBiOEQlpFRGZ45sfppM=", + "dev": true + }, + "dotignore": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/dotignore/-/dotignore-0.1.2.tgz", + "integrity": "sha512-UGGGWfSauusaVJC+8fgV+NVvBXkCTmVv7sk6nojDZZvuOUNGUy0Zk4UpHQD6EDjS0jpBwcACvH4eofvyzBcRDw==", + "dev": true, + "requires": { + "minimatch": "3.0.4" + } + }, + "error-ex": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/error-ex/-/error-ex-1.3.2.tgz", + "integrity": "sha512-7dFHNmqeFSEt2ZBsCriorKnn3Z2pj+fd9kmI6QoWw4//DL+icEBfc0U7qJCisqrTsKTjw4fNFy2pW9OqStD84g==", + "dev": true, + "requires": { + "is-arrayish": "0.2.1" + } + }, + "es-abstract": { + "version": "1.17.4", + "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.17.4.tgz", + "integrity": "sha512-Ae3um/gb8F0mui/jPL+QiqmglkUsaQf7FwBEHYIFkztkneosu9imhqHpBzQ3h1vit8t5iQ74t6PEVvphBZiuiQ==", + "dev": true, + "requires": { + "es-to-primitive": "1.2.1", + "function-bind": "1.1.1", + "has": "1.0.3", + "has-symbols": "1.0.1", + "is-callable": "1.1.5", + "is-regex": "1.0.5", + "object-inspect": "1.7.0", + "object-keys": "1.1.1", + "object.assign": "4.1.0", + "string.prototype.trimleft": "2.1.1", + "string.prototype.trimright": "2.1.1" + } + }, + "es-to-primitive": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/es-to-primitive/-/es-to-primitive-1.2.1.tgz", + "integrity": "sha512-QCOllgZJtaUo9miYBcLChTUaHNjJF3PYs1VidD7AwiEj1kYxKeQTctLAezAOH5ZKRH0g2IgPn6KwB4IT8iRpvA==", + "dev": true, + "requires": { + "is-callable": "1.1.5", + "is-date-object": "1.0.2", + "is-symbol": "1.0.3" + } + }, + "escape-string-regexp": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", + "integrity": "sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ=", + "dev": true + }, + "estree-walker": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-0.6.1.tgz", + "integrity": "sha512-SqmZANLWS0mnatqbSfRP5g8OXZC12Fgg1IwNtLsyHDzJizORW4khDfjPqJZsemPWBB2uqykUah5YpQ6epsqC/w==", + "dev": true + }, + "for-each": { + "version": "0.3.3", + "resolved": "https://registry.npmjs.org/for-each/-/for-each-0.3.3.tgz", + "integrity": "sha512-jqYfLp7mo9vIyQf8ykW2v7A+2N4QjeCeI5+Dz9XraiO1ign81wjiH7Fb9vSOWvQfNtmSa4H2RoQTrrXivdUZmw==", + "dev": true, + "requires": { + "is-callable": "1.1.5" + } + }, + "fs.realpath": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", + "integrity": "sha1-FQStJSMVjKpA20onh8sBQRmU6k8=", + "dev": true + }, + "function-bind": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.1.tgz", + "integrity": "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==", + "dev": true + }, + "glob": { + "version": "7.1.6", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.6.tgz", + "integrity": "sha512-LwaxwyZ72Lk7vZINtNNrywX0ZuLyStrdDtabefZKAY5ZGJhVtgdznluResxNmPitE0SAO+O26sWTHeKSI2wMBA==", + "dev": true, + "requires": { + "fs.realpath": "1.0.0", + "inflight": "1.0.6", + "inherits": "2.0.4", + "minimatch": "3.0.4", + "once": "1.4.0", + "path-is-absolute": "1.0.1" + } + }, + "graceful-fs": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.3.tgz", + "integrity": "sha512-a30VEBm4PEdx1dRB7MFK7BejejvCvBronbLjht+sHuGYj8PHs7M/5Z+rt5lw551vZ7yfTCj4Vuyy3mSJytDWRQ==", + "dev": true + }, + "has": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/has/-/has-1.0.3.tgz", + "integrity": "sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw==", + "dev": true, + "requires": { + "function-bind": "1.1.1" + } + }, + "has-flag": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", + "integrity": "sha1-tdRU3CGZriJWmfNGfloH87lVuv0=", + "dev": true + }, + "has-symbols": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.1.tgz", + "integrity": "sha512-PLcsoqu++dmEIZB+6totNFKq/7Do+Z0u4oT0zKOJNl3lYK6vGwwu2hjHs+68OEZbTjiUE9bgOABXbP/GvrS0Kg==", + "dev": true + }, + "hosted-git-info": { + "version": "2.8.8", + "resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-2.8.8.tgz", + "integrity": "sha512-f/wzC2QaWBs7t9IYqB4T3sR1xviIViXJRJTWBlx2Gf3g0Xi5vI7Yy4koXQ1c9OYDGHN9sBy1DQ2AB8fqZBWhUg==", + "dev": true + }, + "idb-keyval": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/idb-keyval/-/idb-keyval-3.2.0.tgz", + "integrity": "sha512-slx8Q6oywCCSfKgPgL0sEsXtPVnSbTLWpyiDcu6msHOyKOLari1TD1qocXVCft80umnkk3/Qqh3lwoFt8T/BPQ==" + }, + "inflight": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", + "integrity": "sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk=", + "dev": true, + "requires": { + "once": "1.4.0", + "wrappy": "1.0.2" + } + }, + "inherits": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", + "dev": true + }, + "is-arguments": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/is-arguments/-/is-arguments-1.0.4.tgz", + "integrity": "sha512-xPh0Rmt8NE65sNzvyUmWgI1tz3mKq74lGA0mL8LYZcoIzKOzDh6HmrYm3d18k60nHerC8A9Km8kYu87zfSFnLA==", + "dev": true + }, + "is-arrayish": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.2.1.tgz", + "integrity": "sha1-d8mYQFJ6qOyxqLppe4BkWnqSap0=", + "dev": true + }, + "is-callable": { + "version": "1.1.5", + "resolved": "https://registry.npmjs.org/is-callable/-/is-callable-1.1.5.tgz", + "integrity": "sha512-ESKv5sMCJB2jnHTWZ3O5itG+O128Hsus4K4Qh1h2/cgn2vbgnLSVqfV46AeJA9D5EeeLa9w81KUXMtn34zhX+Q==", + "dev": true + }, + "is-date-object": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/is-date-object/-/is-date-object-1.0.2.tgz", + "integrity": "sha512-USlDT524woQ08aoZFzh3/Z6ch9Y/EWXEHQ/AaRN0SkKq4t2Jw2R2339tSXmwuVoY7LLlBCbOIlx2myP/L5zk0g==", + "dev": true + }, + "is-module": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-module/-/is-module-1.0.0.tgz", + "integrity": "sha1-Mlj7afeMFNW4FdZkM2tM/7ZEFZE=", + "dev": true + }, + "is-reference": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/is-reference/-/is-reference-1.1.4.tgz", + "integrity": "sha512-uJA/CDPO3Tao3GTrxYn6AwkM4nUPJiGGYu5+cB8qbC7WGFlrKZbiRo7SFKxUAEpFUfiHofWCXBUNhvYJMh+6zw==", + "dev": true, + "requires": { + "@types/estree": "0.0.39" + } + }, + "is-regex": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/is-regex/-/is-regex-1.0.5.tgz", + "integrity": "sha512-vlKW17SNq44owv5AQR3Cq0bQPEb8+kF3UKZ2fiZNOWtztYE5i0CzCZxFDwO58qAOWtxdBRVO/V5Qin1wjCqFYQ==", + "dev": true, + "requires": { + "has": "1.0.3" + } + }, + "is-symbol": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/is-symbol/-/is-symbol-1.0.3.tgz", + "integrity": "sha512-OwijhaRSgqvhm/0ZdAcXNZt9lYdKFpcRDT5ULUuYXPoT794UNOdU+gpT6Rzo7b4V2HUl/op6GqY894AZwv9faQ==", + "dev": true, + "requires": { + "has-symbols": "1.0.1" + } + }, + "isexe": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", + "integrity": "sha1-6PvzdNxVb/iUehDcsFctYz8s+hA=", + "dev": true + }, + "jest-worker": { + "version": "24.9.0", + "resolved": "https://registry.npmjs.org/jest-worker/-/jest-worker-24.9.0.tgz", + "integrity": "sha512-51PE4haMSXcHohnSMdM42anbvZANYTqMrr52tVKPqqsPJMzoP6FYYDVqahX/HrAoKEKz3uUPzSvKs9A3qR4iVw==", + "dev": true, + "requires": { + "merge-stream": "2.0.0", + "supports-color": "6.1.0" + }, + "dependencies": { + "supports-color": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-6.1.0.tgz", + "integrity": "sha512-qe1jfm1Mg7Nq/NSh6XE24gPXROEVsWHxC1LIx//XNlD9iw7YZQGjZNjYN7xGaEG6iKdA8EtNFW6R0gjnVXp+wQ==", + "dev": true, + "requires": { + "has-flag": "3.0.0" + } + } + } + }, + "js-tokens": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", + "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==", + "dev": true + }, + "json-parse-better-errors": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/json-parse-better-errors/-/json-parse-better-errors-1.0.2.tgz", + "integrity": "sha512-mrqyZKfX5EhL7hvqcV6WG1yYjnjeuYDzDhhcAAUrq8Po85NBQBJP+ZDUT75qZQ98IkUoBqdkExkukOU7Ts2wrw==", + "dev": true + }, + "load-json-file": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/load-json-file/-/load-json-file-4.0.0.tgz", + "integrity": "sha1-L19Fq5HjMhYjT9U62rZo607AmTs=", + "dev": true, + "requires": { + "graceful-fs": "4.2.3", + "parse-json": "4.0.0", + "pify": "3.0.0", + "strip-bom": "3.0.0" + } + }, + "magic-string": { + "version": "0.25.6", + "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.25.6.tgz", + "integrity": "sha512-3a5LOMSGoCTH5rbqobC2HuDNRtE2glHZ8J7pK+QZYppyWA36yuNpsX994rIY2nCuyP7CZYy7lQq/X2jygiZ89g==", + "dev": true, + "requires": { + "sourcemap-codec": "1.4.8" + } + }, + "memorystream": { + "version": "0.3.1", + "resolved": "https://registry.npmjs.org/memorystream/-/memorystream-0.3.1.tgz", + "integrity": "sha1-htcJCzDORV1j+64S3aUaR93K+bI=", + "dev": true + }, + "merge-stream": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/merge-stream/-/merge-stream-2.0.0.tgz", + "integrity": "sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w==", + "dev": true + }, + "minimatch": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz", + "integrity": "sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==", + "dev": true, + "requires": { + "brace-expansion": "1.1.11" + } + }, + "minimist": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.0.tgz", + "integrity": "sha1-o1AIsg9BOD7sH7kU9M1d95omQoQ=", + "dev": true + }, + "nice-try": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/nice-try/-/nice-try-1.0.5.tgz", + "integrity": "sha512-1nh45deeb5olNY7eX82BkPO7SSxR5SSYJiPTrTdFUVYwAl8CKMA5N9PjTYkHiRjisVcxcQ1HXdLhx2qxxJzLNQ==", + "dev": true + }, + "normalize-package-data": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/normalize-package-data/-/normalize-package-data-2.5.0.tgz", + "integrity": "sha512-/5CMN3T0R4XTj4DcGaexo+roZSdSFW/0AOOTROrjxzCG1wrWXEsGbRKevjlIL+ZDE4sZlJr5ED4YW0yqmkK+eA==", + "dev": true, + "requires": { + "hosted-git-info": "2.8.8", + "resolve": "1.14.2", + "semver": "5.7.1", + "validate-npm-package-license": "3.0.4" + } + }, + "npm-run-all": { + "version": "4.1.5", + "resolved": "https://registry.npmjs.org/npm-run-all/-/npm-run-all-4.1.5.tgz", + "integrity": "sha512-Oo82gJDAVcaMdi3nuoKFavkIHBRVqQ1qvMb+9LHk/cF4P6B2m8aP04hGf7oL6wZ9BuGwX1onlLhpuoofSyoQDQ==", + "dev": true, + "requires": { + "ansi-styles": "3.2.1", + "chalk": "2.4.2", + "cross-spawn": "6.0.5", + "memorystream": "0.3.1", + "minimatch": "3.0.4", + "pidtree": "0.3.1", + "read-pkg": "3.0.0", + "shell-quote": "1.7.2", + "string.prototype.padend": "3.1.0" + } + }, + "object-inspect": { + "version": "1.7.0", + "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.7.0.tgz", + "integrity": "sha512-a7pEHdh1xKIAgTySUGgLMx/xwDZskN1Ud6egYYN3EdRW4ZMPNEDUTF+hwy2LUC+Bl+SyLXANnwz/jyh/qutKUw==", + "dev": true + }, + "object-is": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/object-is/-/object-is-1.0.2.tgz", + "integrity": "sha512-Epah+btZd5wrrfjkJZq1AOB9O6OxUQto45hzFd7lXGrpHPGE0W1k+426yrZV+k6NJOzLNNW/nVsmZdIWsAqoOQ==", + "dev": true + }, + "object-keys": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/object-keys/-/object-keys-1.1.1.tgz", + "integrity": "sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA==", + "dev": true + }, + "object.assign": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/object.assign/-/object.assign-4.1.0.tgz", + "integrity": "sha512-exHJeq6kBKj58mqGyTQ9DFvrZC/eR6OwxzoM9YRoGBqrXYonaFyGiFMuc9VZrXf7DarreEwMpurG3dd+CNyW5w==", + "dev": true, + "requires": { + "define-properties": "1.1.3", + "function-bind": "1.1.1", + "has-symbols": "1.0.1", + "object-keys": "1.1.1" + } + }, + "once": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", + "integrity": "sha1-WDsap3WWHUsROsF9nFC6753Xa9E=", + "dev": true, + "requires": { + "wrappy": "1.0.2" + } + }, + "parse-json": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-4.0.0.tgz", + "integrity": "sha1-vjX1Qlvh9/bHRxhPmKeIy5lHfuA=", + "dev": true, + "requires": { + "error-ex": "1.3.2", + "json-parse-better-errors": "1.0.2" + } + }, + "path-is-absolute": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", + "integrity": "sha1-F0uSaHNVNP+8es5r9TpanhtcX18=", + "dev": true + }, + "path-key": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/path-key/-/path-key-2.0.1.tgz", + "integrity": "sha1-QRyttXTFoUDTpLGRDUDYDMn0C0A=", + "dev": true + }, + "path-parse": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.6.tgz", + "integrity": "sha512-GSmOT2EbHrINBf9SR7CDELwlJ8AENk3Qn7OikK4nFYAu3Ote2+JYNVvkpAEQm3/TLNEJFD/xZJjzyxg3KBWOzw==", + "dev": true + }, + "path-type": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/path-type/-/path-type-3.0.0.tgz", + "integrity": "sha512-T2ZUsdZFHgA3u4e5PfPbjd7HDDpxPnQb5jN0SrDsjNSuVXHJqtwTnWqG0B1jZrgmJ/7lj1EmVIByWt1gxGkWvg==", + "dev": true, + "requires": { + "pify": "3.0.0" + } + }, + "pidtree": { + "version": "0.3.1", + "resolved": "https://registry.npmjs.org/pidtree/-/pidtree-0.3.1.tgz", + "integrity": "sha512-qQbW94hLHEqCg7nhby4yRC7G2+jYHY4Rguc2bjw7Uug4GIJuu1tvf2uHaZv5Q8zdt+WKJ6qK1FOI6amaWUo5FA==", + "dev": true + }, + "pify": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/pify/-/pify-3.0.0.tgz", + "integrity": "sha1-5aSs0sEB/fPZpNB/DbxNtJ3SgXY=", + "dev": true + }, + "read-pkg": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/read-pkg/-/read-pkg-3.0.0.tgz", + "integrity": "sha1-nLxoaXj+5l0WwA4rGcI3/Pbjg4k=", + "dev": true, + "requires": { + "load-json-file": "4.0.0", + "normalize-package-data": "2.5.0", + "path-type": "3.0.0" + } + }, + "regexp.prototype.flags": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/regexp.prototype.flags/-/regexp.prototype.flags-1.3.0.tgz", + "integrity": "sha512-2+Q0C5g951OlYlJz6yu5/M33IcsESLlLfsyIaLJaG4FA2r4yP8MvVMJUUP/fVBkSpbbbZlS5gynbEWLipiiXiQ==", + "dev": true, + "requires": { + "define-properties": "1.1.3", + "es-abstract": "1.17.4" + } + }, + "resolve": { + "version": "1.14.2", + "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.14.2.tgz", + "integrity": "sha512-EjlOBLBO1kxsUxsKjLt7TAECyKW6fOh1VRkykQkKGzcBbjjPIxBqGh0jf7GJ3k/f5mxMqW3htMD3WdTUVtW8HQ==", + "dev": true, + "requires": { + "path-parse": "1.0.6" + } + }, + "resumer": { + "version": "0.0.0", + "resolved": "https://registry.npmjs.org/resumer/-/resumer-0.0.0.tgz", + "integrity": "sha1-8ej0YeQGS6Oegq883CqMiT0HZ1k=", + "dev": true, + "requires": { + "through": "2.3.8" + } + }, + "rollup": { + "version": "1.32.1", + "resolved": "https://registry.npmjs.org/rollup/-/rollup-1.32.1.tgz", + "integrity": "sha512-/2HA0Ec70TvQnXdzynFffkjA6XN+1e2pEv/uKS5Ulca40g2L7KuOE3riasHoNVHOsFD5KKZgDsMk1CP3Tw9s+A==", + "dev": true, + "requires": { + "@types/estree": "0.0.39", + "@types/node": "13.7.6", + "acorn": "7.1.1" + } + }, + "rollup-plugin-terser": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/rollup-plugin-terser/-/rollup-plugin-terser-5.3.0.tgz", + "integrity": "sha512-XGMJihTIO3eIBsVGq7jiNYOdDMb3pVxuzY0uhOE/FM4x/u9nQgr3+McsjzqBn3QfHIpNSZmFnpoKAwHBEcsT7g==", + "dev": true, + "requires": { + "@babel/code-frame": "7.8.3", + "jest-worker": "24.9.0", + "rollup-pluginutils": "2.8.2", + "serialize-javascript": "2.1.2", + "terser": "4.6.10" + } + }, + "rollup-pluginutils": { + "version": "2.8.2", + "resolved": "https://registry.npmjs.org/rollup-pluginutils/-/rollup-pluginutils-2.8.2.tgz", + "integrity": "sha512-EEp9NhnUkwY8aif6bxgovPHMoMoNr2FulJziTndpt5H9RdwC47GSGuII9XxpSdzVGM0GWrNPHV6ie1LTNJPaLQ==", + "dev": true, + "requires": { + "estree-walker": "0.6.1" + } + }, + "semver": { + "version": "5.7.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz", + "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==", + "dev": true + }, + "serialize-javascript": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/serialize-javascript/-/serialize-javascript-2.1.2.tgz", + "integrity": "sha512-rs9OggEUF0V4jUSecXazOYsLfu7OGK2qIn3c7IPBiffz32XniEp/TX9Xmc9LQfK2nQ2QKHvZ2oygKUGU0lG4jQ==", + "dev": true + }, + "shebang-command": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-1.2.0.tgz", + "integrity": "sha1-RKrGW2lbAzmJaMOfNj/uXer98eo=", + "dev": true, + "requires": { + "shebang-regex": "1.0.0" + } + }, + "shebang-regex": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-1.0.0.tgz", + "integrity": "sha1-2kL0l0DAtC2yypcoVxyxkMmO/qM=", + "dev": true + }, + "shell-quote": { + "version": "1.7.2", + "resolved": "https://registry.npmjs.org/shell-quote/-/shell-quote-1.7.2.tgz", + "integrity": "sha512-mRz/m/JVscCrkMyPqHc/bczi3OQHkLTqXHEFu0zDhK/qfv3UcOA4SVmRCLmos4bhjr9ekVQubj/R7waKapmiQg==", + "dev": true + }, + "source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "dev": true + }, + "source-map-support": { + "version": "0.5.16", + "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.16.tgz", + "integrity": "sha512-efyLRJDr68D9hBBNIPWFjhpFzURh+KJykQwvMyW5UiZzYwoF6l4YMMDIJJEyFWxWCqfyxLzz6tSfUFR+kXXsVQ==", + "dev": true, + "requires": { + "buffer-from": "1.1.1", + "source-map": "0.6.1" + } + }, + "sourcemap-codec": { + "version": "1.4.8", + "resolved": "https://registry.npmjs.org/sourcemap-codec/-/sourcemap-codec-1.4.8.tgz", + "integrity": "sha512-9NykojV5Uih4lgo5So5dtw+f0JgJX30KCNI8gwhz2J9A15wD0Ml6tjHKwf6fTSa6fAdVBdZeNOs9eJ71qCk8vA==", + "dev": true + }, + "spdx-correct": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/spdx-correct/-/spdx-correct-3.1.0.tgz", + "integrity": "sha512-lr2EZCctC2BNR7j7WzJ2FpDznxky1sjfxvvYEyzxNyb6lZXHODmEoJeFu4JupYlkfha1KZpJyoqiJ7pgA1qq8Q==", + "dev": true, + "requires": { + "spdx-expression-parse": "3.0.0", + "spdx-license-ids": "3.0.5" + } + }, + "spdx-exceptions": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/spdx-exceptions/-/spdx-exceptions-2.2.0.tgz", + "integrity": "sha512-2XQACfElKi9SlVb1CYadKDXvoajPgBVPn/gOQLrTvHdElaVhr7ZEbqJaRnJLVNeaI4cMEAgVCeBMKF6MWRDCRA==", + "dev": true + }, + "spdx-expression-parse": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/spdx-expression-parse/-/spdx-expression-parse-3.0.0.tgz", + "integrity": "sha512-Yg6D3XpRD4kkOmTpdgbUiEJFKghJH03fiC1OPll5h/0sO6neh2jqRDVHOQ4o/LMea0tgCkbMgea5ip/e+MkWyg==", + "dev": true, + "requires": { + "spdx-exceptions": "2.2.0", + "spdx-license-ids": "3.0.5" + } + }, + "spdx-license-ids": { + "version": "3.0.5", + "resolved": "https://registry.npmjs.org/spdx-license-ids/-/spdx-license-ids-3.0.5.tgz", + "integrity": "sha512-J+FWzZoynJEXGphVIS+XEh3kFSjZX/1i9gFBaWQcB+/tmpe2qUsSBABpcxqxnAxFdiUFEgAX1bjYGQvIZmoz9Q==", + "dev": true + }, + "string.prototype.padend": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/string.prototype.padend/-/string.prototype.padend-3.1.0.tgz", + "integrity": "sha512-3aIv8Ffdp8EZj8iLwREGpQaUZiPyrWrpzMBHvkiSW/bK/EGve9np07Vwy7IJ5waydpGXzQZu/F8Oze2/IWkBaA==", + "dev": true, + "requires": { + "define-properties": "1.1.3", + "es-abstract": "1.17.4" + } + }, + "string.prototype.trim": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/string.prototype.trim/-/string.prototype.trim-1.2.1.tgz", + "integrity": "sha512-MjGFEeqixw47dAMFMtgUro/I0+wNqZB5GKXGt1fFr24u3TzDXCPu7J9Buppzoe3r/LqkSDLDDJzE15RGWDGAVw==", + "dev": true, + "requires": { + "define-properties": "1.1.3", + "es-abstract": "1.17.4", + "function-bind": "1.1.1" + } + }, + "string.prototype.trimleft": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/string.prototype.trimleft/-/string.prototype.trimleft-2.1.1.tgz", + "integrity": "sha512-iu2AGd3PuP5Rp7x2kEZCrB2Nf41ehzh+goo8TV7z8/XDBbsvc6HQIlUl9RjkZ4oyrW1XM5UwlGl1oVEaDjg6Ag==", + "dev": true, + "requires": { + "define-properties": "1.1.3", + "function-bind": "1.1.1" + } + }, + "string.prototype.trimright": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/string.prototype.trimright/-/string.prototype.trimright-2.1.1.tgz", + "integrity": "sha512-qFvWL3/+QIgZXVmJBfpHmxLB7xsUXz6HsUmP8+5dRaC3Q7oKUv9Vo6aMCRZC1smrtyECFsIT30PqBJ1gTjAs+g==", + "dev": true, + "requires": { + "define-properties": "1.1.3", + "function-bind": "1.1.1" + } + }, + "strip-bom": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-3.0.0.tgz", + "integrity": "sha1-IzTBjpx1n3vdVv3vfprj1YjmjtM=", + "dev": true + }, + "supports-color": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", + "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", + "dev": true, + "requires": { + "has-flag": "3.0.0" + } + }, + "tape": { + "version": "4.13.0", + "resolved": "https://registry.npmjs.org/tape/-/tape-4.13.0.tgz", + "integrity": "sha512-J/hvA+GJnuWJ0Sj8Z0dmu3JgMNU+MmusvkCT7+SN4/2TklW18FNCp/UuHIEhPZwHfy4sXfKYgC7kypKg4umbOw==", + "dev": true, + "requires": { + "deep-equal": "1.1.1", + "defined": "1.0.0", + "dotignore": "0.1.2", + "for-each": "0.3.3", + "function-bind": "1.1.1", + "glob": "7.1.6", + "has": "1.0.3", + "inherits": "2.0.4", + "is-regex": "1.0.5", + "minimist": "1.2.0", + "object-inspect": "1.7.0", + "resolve": "1.14.2", + "resumer": "0.0.0", + "string.prototype.trim": "1.2.1", + "through": "2.3.8" + } + }, + "terser": { + "version": "4.6.10", + "resolved": "https://registry.npmjs.org/terser/-/terser-4.6.10.tgz", + "integrity": "sha512-qbF/3UOo11Hggsbsqm2hPa6+L4w7bkr+09FNseEe8xrcVD3APGLFqE+Oz1ZKAxjYnFsj80rLOfgAtJ0LNJjtTA==", + "dev": true, + "requires": { + "commander": "2.20.3", + "source-map": "0.6.1", + "source-map-support": "0.5.16" + } + }, + "through": { + "version": "2.3.8", + "resolved": "https://registry.npmjs.org/through/-/through-2.3.8.tgz", + "integrity": "sha1-DdTJ/6q8NXlgsbckEV1+Doai4fU=", + "dev": true + }, + "validate-npm-package-license": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/validate-npm-package-license/-/validate-npm-package-license-3.0.4.tgz", + "integrity": "sha512-DpKm2Ui/xN7/HQKCtpZxoRWBhZ9Z0kqtygG8XCgNQ8ZlDnxuQmWhj566j8fN4Cu3/JmbhsDo7fcAJq4s9h27Ew==", + "dev": true, + "requires": { + "spdx-correct": "3.1.0", + "spdx-expression-parse": "3.0.0" + } + }, + "which": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/which/-/which-1.3.1.tgz", + "integrity": "sha512-HxJdYWq1MTIQbJ3nw0cqssHoTNU267KlrDuGZ1WYlxDStUtKUhOaJmh112/TZmHxxUfuJqPXSOm7tDyas0OSIQ==", + "dev": true, + "requires": { + "isexe": "2.0.0" + } + }, + "wrappy": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", + "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=", + "dev": true + } + } +} diff --git a/package.json b/package.json index e48def8..227068c 100644 --- a/package.json +++ b/package.json @@ -7,7 +7,7 @@ "dist/jakecache-sw.js" ], "scripts": { - "build": "node scripts/build.js", + "build": "rollup -c", "test": "node test/index.js", "prepublish": "npm build" }, @@ -32,9 +32,11 @@ }, "homepage": "https://github.com/kenchris/jakecache#readme", "devDependencies": { - "rollup": "^0.25.8", - "rollup-plugin-commonjs": "^10.1.0", - "rollup-plugin-node-resolve": "^5.2.0", + "@rollup/plugin-commonjs": "^11.0.1", + "@rollup/plugin-node-resolve": "^7.0.0", + "npm-run-all": "^4.1.5", + "rollup": "^1.16.2", + "rollup-plugin-terser": "^5.0.0", "tape": "^4.5.1" }, "dependencies": { diff --git a/rollup.config.js b/rollup.config.js new file mode 100644 index 0000000..df1da2d --- /dev/null +++ b/rollup.config.js @@ -0,0 +1,36 @@ +import resolve from "@rollup/plugin-node-resolve"; +import commonjs from "@rollup/plugin-commonjs"; +import { terser } from "rollup-plugin-terser"; + +// `npm run build` -> `production` is true +// `npm run dev` -> `production` is false +const production = false; //!process.env.ROLLUP_WATCH; + +export default [ + { + input: "jakecache-sw.js", + output: { + file: "dist/jakecache-sw.js", + format: "cjs", + }, + plugins: [ + resolve(), // tells Rollup how to find date-fns in node_modules + commonjs(), // converts date-fns to ES modules + production && terser(), // minify, but only in production + ], + }, + { + input: "jakecache.js", + output: { + file: "dist/jakecache.js", + format: "cjs", + }, + plugins: [ + resolve(), // tells Rollup how to find date-fns in node_modules + commonjs(), // converts date-fns to ES modules + production && terser(), // minify, but only in production + ], + }, +]; + +var files = ["jakecache-sw.js", "jakecache.js"]; diff --git a/scripts/build.js b/scripts/build.js index df7482b..eec238f 100644 --- a/scripts/build.js +++ b/scripts/build.js @@ -1,22 +1,41 @@ -var rollup = require("rollup"); -var resolve = require("rollup-plugin-node-resolve"); -var commonjs = require("rollup-plugin-commonjs"); +// var rollup = require("rollup"); +// var resolve = require("rollup-plugin-node-resolve"); +import resolve from "@rollup/plugin-node-resolve"; var files = ["jakecache-sw.js", "jakecache.js"]; -files.forEach(function(file) { - rollup - .rollup({ - entry: file - }) - .then(function(bundle) { - bundle.write({ - format: "cjs", - dest: "dist/" + file, - plugins: [resolve(), commonjs()] - }); - }) - .catch(function(e) { - console.error(e, e.stack); - }); -}); +export default [ + { + input: "jakecache-sw.js", + output: { + file: "dist/jakecache-sw.js", + format: "cjs", + }, + plugins: [resolve()], + }, + { + input: "jakecache.js", + output: { + file: "dist/jakecache.js", + format: "cjs", + }, + plugins: [resolve()], + }, +]; + +// files.forEach(function (file) { +// rollup +// .rollup({ +// entry: file, +// }) +// .then(function (bundle) { +// bundle.write({ +// format: "cjs", +// dest: "dist/" + file, +// plugins: [resolve(), commonjs(), async()], +// }); +// }) +// .catch(function (e) { +// console.error(e, e.stack); +// }); +// }); From 8d744024a0c1ab8639a376dd4722d08d40f71773 Mon Sep 17 00:00:00 2001 From: Sergey S Date: Tue, 7 Apr 2020 23:28:29 +0300 Subject: [PATCH 10/22] remove link on repo --- jakecache-sw.js | 1 - jakecache.js | 2 +- 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/jakecache-sw.js b/jakecache-sw.js index bc5ef6d..a56cd1d 100644 --- a/jakecache-sw.js +++ b/jakecache-sw.js @@ -1,6 +1,5 @@ 'use strict'; -// actual sources https://github.com/segios/jakecache import { md5 } from './lib/md5' diff --git a/jakecache.js b/jakecache.js index 268c535..6fb5be0 100644 --- a/jakecache.js +++ b/jakecache.js @@ -1,6 +1,6 @@ 'use strict'; -// actual sources https://github.com/segios/jakecache + const _eventHandlers = Symbol("eventHandlers"); From 59270fddd7ceaaecb6bbc1d35a7302f1707d4703 Mon Sep 17 00:00:00 2001 From: Sergey S Date: Wed, 8 Apr 2020 11:07:31 +0300 Subject: [PATCH 11/22] fix update cache if js file missing --- jakecache-sw.js | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/jakecache-sw.js b/jakecache-sw.js index a56cd1d..fc61ce7 100644 --- a/jakecache-sw.js +++ b/jakecache-sw.js @@ -468,8 +468,9 @@ async function fromCache(request) { // to refresh cache async function updateCache(request, response) { + let url = new URL(request.url); // cache only js files - if (!request.url.endsWith('.js')) { + if (!url.pathname.endsWith('.js')) { return Promise.resolve(); } @@ -487,10 +488,7 @@ async function updateCache(request, response) { } return caches.open(cacheName).then((cache) => - // todo check if we really need fetch - fetch(request).then((response) => - cache.put(request, response.clone()).then(() => response) - ) + cache.put(request, response) ); } @@ -537,8 +535,9 @@ self.addEventListener("fetch", function (event) { return await fromCache(event.request); } catch (e) { const resp = await fromNetwork(event.request); + let respClone = resp.clone(); event.waitUntil(async function () { - await updateCache(event.request, resp); + await updateCache(event.request, respClone); }()); return resp; } From 20daadb26059ee72b18ba79e845457f83a4479d6 Mon Sep 17 00:00:00 2001 From: segios Date: Wed, 8 Apr 2020 11:36:48 +0300 Subject: [PATCH 12/22] Update README.md --- README.md | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index bcee420..df98280 100644 --- a/README.md +++ b/README.md @@ -49,7 +49,8 @@ NETWORK: * ``` -1. sample setup `````` + +``` 2. Add `````` to your HTML. -3. If nam eof manifest is different cahnge it in 'jakecahce-sw.js' ```const manifestName = 'app.manifest'; -4. optional parameter in 'jakecahce-sw.js' ``` const isAutoUpdate = false; +3. If nam eof manifest is different cahnge it in 'jakecahce-sw.js' +```const manifestName = 'app.manifest'; +``` +4. optional parameter in 'jakecahce-sw.js' +``` const isAutoUpdate = false; +``` Means autoupdate cache without message to SwapCahce 5. That's it! Your website is now Jake-enabled! From 661921c9c224c8bb7b63884a7efe49497ace7787 Mon Sep 17 00:00:00 2001 From: segios Date: Wed, 8 Apr 2020 11:38:01 +0300 Subject: [PATCH 13/22] Update README.md --- README.md | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index df98280..2edd4c0 100644 --- a/README.md +++ b/README.md @@ -50,7 +50,7 @@ NETWORK: ``` 1. sample setup -``` ``` 2. Add `````` to your HTML. -3. If nam eof manifest is different cahnge it in 'jakecahce-sw.js' +3. If name of the manifest is different change it in 'jakecahce-sw.js' ```const manifestName = 'app.manifest'; ``` 4. optional parameter in 'jakecahce-sw.js' From 9330e73397ad3d799476e668fbc7f2f7e5f9d3de Mon Sep 17 00:00:00 2001 From: segios Date: Wed, 8 Apr 2020 11:38:38 +0300 Subject: [PATCH 14/22] Update README.md --- README.md | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 2edd4c0..c5fe935 100644 --- a/README.md +++ b/README.md @@ -125,10 +125,12 @@ NETWORK: ``` 2. Add `````` to your HTML. 3. If name of the manifest is different change it in 'jakecahce-sw.js' -```const manifestName = 'app.manifest'; +``` +const manifestName = 'app.manifest'; ``` 4. optional parameter in 'jakecahce-sw.js' -``` const isAutoUpdate = false; +``` +const isAutoUpdate = false; ``` Means autoupdate cache without message to SwapCahce 5. That's it! Your website is now Jake-enabled! From 19b15910e138892c79052174d47aa6799072f017 Mon Sep 17 00:00:00 2001 From: Sergey S Date: Thu, 9 Apr 2020 13:54:37 +0300 Subject: [PATCH 15/22] extract getManifetsUrl --- jakecache-sw.js | 15 +++++++++------ 1 file changed, 9 insertions(+), 6 deletions(-) diff --git a/jakecache-sw.js b/jakecache-sw.js index fc61ce7..94e14d4 100644 --- a/jakecache-sw.js +++ b/jakecache-sw.js @@ -398,10 +398,15 @@ async function swapCache() { } } +const manifestName = 'app.manifest'; + +function getManifestUrl() { + let loc = location.pathname.replace(/([\w\-]+\.js)/, manifestName); + return loc; +} self.addEventListener("message", function (event) { - let loc = location.pathname.replace('jakecache-sw.js', 'ns.appcache'); - loc = location.pathname.replace('sw.js', 'ns.appcache'); + let loc = getManifestUrl(); switch (event.data.command) { case "update": @@ -420,10 +425,8 @@ self.addEventListener("message", function (event) { } }); -const manifestName = 'app.manifest'; - self.addEventListener("install", function (event) { - let loc = location.pathname.replace(/([\w\-]+\.js)/, manifestName); + let loc = getManifestUrl(); event.waitUntil( update(loc, { cache: "reload" }) @@ -543,4 +546,4 @@ self.addEventListener("fetch", function (event) { } }()); -}); +}); \ No newline at end of file From 8995c5a55b4cde8c88db584e249c6150d23a819f Mon Sep 17 00:00:00 2001 From: Sergey S Date: Thu, 9 Apr 2020 13:56:44 +0300 Subject: [PATCH 16/22] setup serviceworler even if no manifet attribute --- jakecache.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/jakecache.js b/jakecache.js index 6fb5be0..fe890d3 100644 --- a/jakecache.js +++ b/jakecache.js @@ -100,7 +100,7 @@ class JakeCache extends PolyfilledEventTarget { let html = document.querySelector("html"); this.pathname = html.getAttribute(manifestAttr); - if (this.pathname && "serviceWorker" in navigator) { + if ("serviceWorker" in navigator) { var self = this; From 481b1f2a818cad9048eef443f2fd2fe39b137be6 Mon Sep 17 00:00:00 2001 From: Sergey S Date: Thu, 9 Apr 2020 14:23:32 +0300 Subject: [PATCH 17/22] return temp check to update from origin file --- jakecache.js | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/jakecache.js b/jakecache.js index fe890d3..faca348 100644 --- a/jakecache.js +++ b/jakecache.js @@ -269,7 +269,12 @@ class JakeCache extends PolyfilledEventTarget { } update() { - if (false) { } + if (false) {// this.status == this.UNCACHED || this.status == this.OBSOLETE) { + // If there is no such application cache, or if its + // application cache group is marked as obsolete, then throw + throw new DOMException(DOMException.INVALID_STATE_ERR, + 'there is no application cache to update.') + } if (navigator.onLine && navigator.serviceWorker.controller) { navigator.serviceWorker.controller.postMessage({ From 625c709f7646fb82e9910fa48d210e2fab61019b Mon Sep 17 00:00:00 2001 From: Sergey S Date: Mon, 13 Apr 2020 22:24:29 +0300 Subject: [PATCH 18/22] return from fromCache and update if no cache exists try recahce if no manifest is stored --- jakecache-sw.js | 23 +++++++++++++++++++---- 1 file changed, 19 insertions(+), 4 deletions(-) diff --git a/jakecache-sw.js b/jakecache-sw.js index 94e14d4..8e31094 100644 --- a/jakecache-sw.js +++ b/jakecache-sw.js @@ -455,12 +455,22 @@ async function fromCache(request) { manifest = await loadCurrentManifest(); } + if (!updating && !manifest) { + // try recache if no manifest + let loc = getManifestUrl(); + update(loc, { cache: "reload" }); + } + + if (cacheStatus !== CacheStatus.CACHED) { + return Promise.reject('no-cache'); + } + if (manifest) { cacheName = manifest.cacheName(); } if (!cacheName) { - Promise.reject('no-cache'); + return Promise.reject('no-cache'); } return caches.open(cacheName).then((cache) => @@ -477,6 +487,10 @@ async function updateCache(request, response) { return Promise.resolve(); } + if (cacheStatus !== CacheStatus.CACHED) { + return Promise.resolve(); + } + let cacheName = ''; if (!manifest) { manifest = await loadCurrentManifest(); @@ -487,7 +501,7 @@ async function updateCache(request, response) { } if (!cacheName) { - Promise.reject('no-cache'); + return Promise.reject('no-cache'); } return caches.open(cacheName).then((cache) => @@ -496,7 +510,8 @@ async function updateCache(request, response) { } function shouldInterceptRequest(url) { - return false; + // intercept pax requests + return url.port == 10009; } self.addEventListener("fetch", function (event) { @@ -546,4 +561,4 @@ self.addEventListener("fetch", function (event) { } }()); -}); \ No newline at end of file +}); From 446a0f0b19973eb1d69da15f6da93f2a102a1888 Mon Sep 17 00:00:00 2001 From: Sergey S Date: Tue, 14 Apr 2020 15:04:23 +0300 Subject: [PATCH 19/22] add "idb-keyval-iife.js" to cache --- jakecache-sw.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/jakecache-sw.js b/jakecache-sw.js index 8e31094..41a3174 100644 --- a/jakecache-sw.js +++ b/jakecache-sw.js @@ -54,7 +54,7 @@ class JakeCacheManifest { } restoreCache() { - this.cache = ["jakecache.js"]; + this.cache = ["jakecache.js", "idb-keyval-iife.js"]; let tmp = {}; // Ignore different protocol for (let pathname of this._rawData.cache) { From 5f200c556d0d2d49b82766b20629a240ed41b368 Mon Sep 17 00:00:00 2001 From: Sergey S Date: Tue, 14 Apr 2020 15:05:30 +0300 Subject: [PATCH 20/22] normilize navigating url while checking in cache --- jakecache-sw.js | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/jakecache-sw.js b/jakecache-sw.js index 41a3174..d8bcfd8 100644 --- a/jakecache-sw.js +++ b/jakecache-sw.js @@ -473,8 +473,15 @@ async function fromCache(request) { return Promise.reject('no-cache'); } + let url = new URL(request.url); + if (request.mode === 'navigate') { + //make normilized url without query + url.search = ''; + url.hash = ''; + } + return caches.open(cacheName).then((cache) => - cache.match(request).then((matching) => + cache.match(url).then((matching) => matching || Promise.reject('no-match') )); } From f6ed41d902c228158b6958e64bdffd646549633d Mon Sep 17 00:00:00 2001 From: Sergey S Date: Thu, 30 Apr 2020 19:00:49 +0300 Subject: [PATCH 21/22] fix setup Cache status in offline --- jakecache-sw.js | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/jakecache-sw.js b/jakecache-sw.js index d8bcfd8..bfd260e 100644 --- a/jakecache-sw.js +++ b/jakecache-sw.js @@ -343,8 +343,13 @@ async function update(pathname, options = {}) { catch (err) { updating = false; console.log(`JakeCache-SW error: ${err}`); - cacheStatus = CacheStatus.IDLE; - postMessage({ type: "idle" }); + if (manifest) { + cacheStatus = CacheStatus.CACHED; + postMessage({ type: "noupdate" }); + } else { + cacheStatus = CacheStatus.IDLE; + postMessage({ type: "idle" }); + } return Promise.reject(err); } } From 596de39be61c60f8639ce37007e23334814dc5a4 Mon Sep 17 00:00:00 2001 From: Sergey S Date: Wed, 6 May 2020 15:32:38 +0300 Subject: [PATCH 22/22] fix autoupdate path; swith autoupdate on by default; improve detection if cache not actual by comparing number of entries; improve updating cach by file not only to js files; prevent cachong manifest file by adding dummy parameter to query string --- jakecache-sw.js | 86 +++++++++++++++++++++++++++++++++++-------------- 1 file changed, 61 insertions(+), 25 deletions(-) diff --git a/jakecache-sw.js b/jakecache-sw.js index bfd260e..337c09c 100644 --- a/jakecache-sw.js +++ b/jakecache-sw.js @@ -9,6 +9,7 @@ const manifestStore = new idbKeyval.Store("manifest-db", "manifest-db"); class JakeCacheManifest { constructor(data) { + this.cache = null; this._path = null; this._hash = null; this._isValid = false; @@ -55,6 +56,7 @@ class JakeCacheManifest { restoreCache() { this.cache = ["jakecache.js", "idb-keyval-iife.js"]; + this.searchCache = {}; let tmp = {}; // Ignore different protocol for (let pathname of this._rawData.cache) { @@ -63,6 +65,8 @@ class JakeCacheManifest { if (!tmp[path]) { this.cache.push(path); tmp[path] = path; + + this.searchCache[path.pathname] = 1; } } } @@ -96,6 +100,10 @@ class JakeCacheManifest { this._isValid = true; } + shouldBeCached(url) { + return url.pathname.endsWith('.js') || this.searchCache[url.pathname]; + } + pathName() { return this._path; } @@ -105,6 +113,10 @@ class JakeCacheManifest { return version + "_" + this._hash; } + cache() { + return this.cache; + } + fetchData(path, options = {}) { this._path = path; @@ -112,8 +124,15 @@ class JakeCacheManifest { return Promise.resolve(false); } + const ms = Date.now(); + let url = this._path; + if (url.indexOf('?') === -1) { + url += "?_=" + ms; + } else { + url += "&_=" + ms; + } // http://html5doctor.com/go-offline-with-application-cache/ - return fetch(new Request(this._path, options), this._fetchOptions).then( + return fetch(new Request(url, options), this._fetchOptions).then( response => { if ( response.type === "opaque" || @@ -123,17 +142,11 @@ class JakeCacheManifest { return Promise.reject(); } - this._hash = options.hash ? options.hash : this._hash; + this._prevhash = options.hash ? options.hash : null; return response.text().then(result => { return new Promise((resolve, reject) => { let hash = md5(result); - if (this._hash && hash.toString() === this._hash.toString()) { - console.log(`JakeCache-SW noupdate: ${hash}`); - return resolve(false); - } - - console.log(`JakeCache-SW update: ${hash} (was: ${this._hash})`); this._hash = hash; @@ -178,6 +191,14 @@ class JakeCacheManifest { } this.restoreCache(); + + if (this._prevhash && hash.toString() === this._prevhash.toString()) { + console.log(`JakeCache-SW no manifest update: ${hash}`); + return resolve(false); + } + + console.log(`JakeCache-SW manifest update: ${hash} (was: ${this._prevhash})`); + resolve(true); }); }); @@ -186,7 +207,7 @@ class JakeCacheManifest { } } -const isAutoUpdate = false; +const isAutoUpdate = true; const CacheStatus = { UNCACHED: 0, @@ -201,6 +222,7 @@ let manifest = null; let cacheStatus = CacheStatus.UNCACHED; function postMessage(msg) { + // { includeUncontrolled: true, type: 'window' } return self.clients.matchAll().then(clients => { if (!clients.length) { @@ -251,6 +273,7 @@ async function loadCurrentManifest() { } manifest = mnf; + cacheStatus = CacheStatus.CACHED; return Promise.resolve(manifest); } @@ -259,11 +282,15 @@ async function deleteOldCaches() { if (!manifest) { manifest = await loadCurrentManifest(); } - if (manifest) { cacheWhitelist.push(manifest.cacheName()); } + const nextManifest = await loadManifest("next"); + if (nextManifest) { + cacheWhitelist.push(nextManifest.cacheName()); + } + console.log('JakeCache-SW deleteing old caches except:', cacheWhitelist); const cacheNames = await caches.keys(); @@ -304,13 +331,24 @@ async function update(pathname, options = {}) { self.options.hash = manifest.hash(); } - const isNeededToUpdate = await nextManifest.fetchData(pathname, self.options); + let isNeededToUpdate = await nextManifest.fetchData(pathname, self.options); + + // check keys in cache + if (!isNeededToUpdate) { + const cache = await caches.open(nextManifest.cacheName()); + const keys = await cache.keys(); + if (keys.length < nextManifest.cache.length) { + console.log(`JakeCache-SW cache has less entries then should be, updating cache`); + isNeededToUpdate = true; + } + } + if (isNeededToUpdate) { console.log(`JakeCache-SW storing to cache ${nextManifest.cacheName()} `); const cache = await caches.open(nextManifest.cacheName()); await cache.addAll(nextManifest.cache); - let isUpgrade = manifest && !isAutoUpdate; + let isUpgrade = manifest //&& !isAutoUpdate; if (isUpgrade) { manifestVersion = 'next'; } @@ -320,12 +358,8 @@ async function update(pathname, options = {}) { console.log(`JakeCache-SW saved to indexed db ${nextManifest.cacheName()} `); if (isAutoUpdate) { - manifest = nextManifest; - try { - await deleteOldCaches(); - } catch (err) { - console.log(`JakeCache-SW deleteOldCaches error: ${err}`); - } + console.log(`JakeCache-SW Auto Update`); + swapCache(); } else if (isUpgrade) { cacheStatus = CacheStatus.UPDATEREADY; @@ -343,6 +377,7 @@ async function update(pathname, options = {}) { catch (err) { updating = false; console.log(`JakeCache-SW error: ${err}`); + if (manifest) { cacheStatus = CacheStatus.CACHED; postMessage({ type: "noupdate" }); @@ -440,13 +475,14 @@ self.addEventListener("install", function (event) { ); }); -self.addEventListener("activate", function (event) { +self.addEventListener("activate", async function (event) { event.waitUntil( deleteOldCaches() .then(function () { self.clients.claim(); }) ); + }); function fromNetwork(request) { @@ -494,10 +530,6 @@ async function fromCache(request) { // to refresh cache async function updateCache(request, response) { let url = new URL(request.url); - // cache only js files - if (!url.pathname.endsWith('.js')) { - return Promise.resolve(); - } if (cacheStatus !== CacheStatus.CACHED) { return Promise.resolve(); @@ -516,14 +548,18 @@ async function updateCache(request, response) { return Promise.reject('no-cache'); } + if (!manifest || !manifest.shouldBeCached(url)) { + return Promise.resolve(); + } + + console.log(`JakeCache-SW add to cache ${request.url}`); return caches.open(cacheName).then((cache) => cache.put(request, response) ); } function shouldInterceptRequest(url) { - // intercept pax requests - return url.port == 10009; + return false; } self.addEventListener("fetch", function (event) {