diff --git a/README.md b/README.md index 8623e6e..c5fe935 100644 --- a/README.md +++ b/README.md @@ -49,9 +49,91 @@ NETWORK: * ``` -1. Include ``jakecache.js`` on your page, maybe via `````` +1. sample setup +``` + function injectJakeCacheScript() { + var s = document.createElement('script'); + s.type = 'text/javascript'; + s.async = true; + s.src = '@Url.Content("~/jakecache.js")'; + var ss = document.getElementsByTagName('script')[0]; + ss.parentNode.insertBefore(s, ss); + } + + function setUpCachehandlers() { + if (!('serviceWorker' in navigator)) { + console.log('no serviceWorker'); + return; + } + + if (!window.jakeCache) { + setTimeout(setUpCachehandlers, 500); + return; + } + + window.jakeCache.addEventListener('downloading', function (ev) { + console.log('JakeCache downloading'); + + }); + + window.jakeCache.addEventListener('cached', function (ev) { + console.log('JakeCache cached'); + + }); + + window.jakeCache.addEventListener('sw-not-attached', function (ev) { + console.log('JakeCache Service worker not attached !!!'); + + }); + + window.jakeCache.addEventListener('checking', function (ev) { + console.log('JakeCache checking'); + + }); + + window.jakeCache.addEventListener('updateready', function (ev) { + console.log('JakeCache updateready'); + window.jakeCache.swapCache(); + }); + + window.jakeCache.addEventListener('updated', function (ev) { + console.log('JakeCache updated'); + var url = window.location.href; + + // reload to root of application + if (window.location.href.indexOf('#') > 0) { + url = window.location.href.substr(0, window.location.href.indexOf('#')) ; + } + if (url && !url.endsWith('/')) { + url += '/'; + } + window.location = url; + }); + + window.jakeCache.addEventListener('error', function (ev) { + console.log(ev.message); + }); + + console.log('jakeCache handlers were setup'); + } + + if ('serviceWorker' in navigator) { + injectJakeCacheScript(); + setUpCachehandlers(); + } + +``` 2. Add `````` to your HTML. -3. That's it! Your website is now Jake-enabled! +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' +``` +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/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..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 } @@ -264,360 +264,532 @@ function md5 (string, key, raw) { return raw_hmac_md5(key, string) } -class JakeCacheManifest { - - constructor () { - this._path = null - this._hash = null - this._isValid = false - this._fetchOptions = { credentials: "same-origin" } - } +self.importScripts("idb-keyval-iife.js"); - groupName () { - let filename = this._path.substring(this._path.lastIndexOf('/') + 1) - return filename - } +const manifestStore = new idbKeyval.Store("manifest-db", "manifest-db"); - fetchData (path, options = {}) { - this._path = path - - if (this._isValid && options.cache !== 'reload') { - return Promise.resolve(false) +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); + } } - // 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._rawData = { - cache: [], - fallback: [], - network: [] - } - - 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})`) - - let lines = result.split(/\r|\n/) - let header = 'cache' // default. - - let firstLine = lines.shift() - if (firstLine !== 'CACHE MANIFEST') { - return reject() - } - - for (let line of lines) { - line = line.replace(/#.*$/, '').trim() - - if (line === '') { - continue - } + 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 + }; + } - let res = line.match(/^([A-Z]*):/) - if (res) { - header = res[1].toLowerCase() - continue - } + restoreManifest(manifestData) { + if (!manifestData) { + this._isValid = false; + return; + } + this._path = manifestData.path; + this._hash = manifestData.hash; + this._rawData = manifestData.rawData; - if (!this._rawData[header]) { - this._rawData[header] = [] - } - this._rawData[header].push(line) - } + this.restoreCache(); + } - this.cache = ['jakecache.js'] - // Ignore different protocol - for (let pathname of this._rawData.cache) { - let path = new URL(pathname, location) + 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) { - this.cache.push(path) + if (!tmp[path]) { + this.cache.push(path); + tmp[path] = 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.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.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 + } + + this.allowNetworkFallback = false; + this.network = []; + for (let entry of this._rawData.network) { + if (entry === "*") { + this.allowNetworkFallback = true; + continue; } - let path = new URL(entry, location) + let path = new URL(entry, location); if (path.protocol === location.protocol) { - this.network.push(path) + this.network.push(path); } - } + } - this._isValid = true - resolve(true) - }) - }) - }) - } -} + this._isValid = true; + } -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 - } -}) + pathName() { + return this._path; + } + + cacheName() { + let version = this._rawData.version; + return version + "_" + this._hash; + } -let manifest = new JakeCacheManifest() + fetchData(path, options = {}) { + this._path = path; + + if (this._isValid && options.cache !== "reload") { + return Promise.resolve(false); + } + + // 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._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); + }); + }); + } + ); + } +} + +const isAutoUpdate = false; const CacheStatus = { - UNCACHED: 0, - IDLE: 1, - CHECKING: 2, - DOWNLOADING: 3, - UPDATEREADY: 4, - OBSOLETE: 5 + UNCACHED: 0, + IDLE: 1, + CHECKING: 2, + DOWNLOADING: 3, + UPDATEREADY: 4, + OBSOLETE: 5 +}; + +let manifest = null; +let cacheStatus = CacheStatus.UNCACHED; + +function postMessage(msg) { + return self.clients.matchAll().then(clients => { + + if (!clients.length) { + console.log(`JakeCache-SW no clients!! message:`, msg); + } + + return Promise.all( + clients.map(client => { + return client.postMessage(msg); + }) + ); + }); } -let cacheStatus = CacheStatus.UNCACHED +async function storeManifest(newManifest, manifestVersion) { + manifestVersion = manifestVersion || "current"; + + await idbKeyval.set(manifestVersion, newManifest.manifestData(), manifestStore); -function postMessage (msg) { - return self.clients.matchAll().then(clients => { - return Promise.all(clients.map(client => { - return client.postMessage(msg) - })) - }) + return Promise.resolve(newManifest); } -function swapCache () { - caches.keys().then(keyList => { - return Promise.all(keyList.map(key => { - return caches.delete(key) - })) - }).then(() => { - // FIXME: Add new keys. - }) +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); + } } -// 7.9.4 -function update (pathname, options = {}) { - if (!pathname) { - console.log('No pathname!') - return Promise.reject() - } - // *.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() +async function loadCurrentManifest() { + const mnf = await loadManifest("current"); + if (!mnf) { + manifest = null; + cacheStatus = CacheStatus.UNCACHED; + console.log("JakeCache-SW uncached "); + return Promise.resolve(null); } - // *.2.4 and *.2.6 - if (cacheStatus === CacheStatus.CHECKING) { - postMessage({ type: 'checking' }) - postMessage({ type: 'abort' }) - return Promise.reject() + manifest = mnf; + return Promise.resolve(manifest); +} + +async function deleteOldCaches() { + let cacheWhitelist = []; + if (!manifest) { + manifest = await loadCurrentManifest(); } - // *.2.4, *.2.5, *.2.6 - if (cacheStatus === CacheStatus.DOWNLOADING) { - postMessage({ type: 'checking' }) - postMessage({ type: 'downloading' }) - postMessage({ type: 'abort' }) - return Promise.reject() + + if (manifest) { + cacheWhitelist.push(manifest.cacheName()); } - 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) - }) - }).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() + + console.log('JakeCache-SW deleteing old caches except:', cacheWhitelist); + + const cacheNames = await caches.keys(); + return Promise.all( + cacheNames.map(function (cacheName) { + if (cacheWhitelist.indexOf(cacheName) === -1) { + return caches.delete(cacheName); + } + })); +} + +let updating = false; + +async function update(pathname, options = {}) { + if (!pathname) { + console.log("JakeCache-SW No pathname!"); + return Promise.reject('No pathname'); + } + + if (updating) { + console.log("JakeCache-SW already updating"); + return Promise.reject('already updating'); } - // Appcache is no-cors by default. - this.requests = manifest.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, 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 - - // Redirects are fatal. - if (response.url !== request.url) { - throw Error() + updating = true; + + let nextManifest = new JakeCacheManifest(); + self.options = options; + + let manifestVersion = 'current'; + + try { + if (!manifest) { + manifest = await loadCurrentManifest(); + } + + 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); + } +} + +async function swapCache() { + + try { + if (!manifest) { + manifest = await loadCurrentManifest(); + } + + const mnfstNextData = await idbKeyval.get("next", manifestStore); - // FIXME: should we update this.total below? + if (mnfstNextData) { + await idbKeyval.set("current", mnfstNextData, manifestStore); + manifest = new JakeCacheManifest(mnfstNextData); - 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 - } + await idbKeyval.del("next", manifestStore); - // HTTP caching rules, such as Cache-Control: no-store, are ignored. - if ((response.headers.get('cache-control') || '').match(/no-store/i)) { - return undefined - } + 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`); } - 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) + if (!manifest) { + cacheStatus = CacheStatus.UNCACHED; + } else { + cacheStatus = CacheStatus.CACHED; + } } - }).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' }) - }) - }).catch(err => { - if (err) { - postMessage({ type: 'error' }, err) - console.log(err) + 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('install', function (event) { - event.waitUntil(self.skipWaiting()) -}) -self.addEventListener('activate', function (event) { - event.waitUntil(self.clients.claim()) -}) +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; + } +}); + +const manifestName = 'manifest.appcache'; + +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); +} -self.addEventListener('fetch', function (event) { - if (cacheStatus === CacheStatus.UNCACHED) { - return fetch(event.request) - } +async function fromCache(request) { - let url = new URL(event.request.url) + let cacheName = ''; + if (!manifest) { + manifest = await loadCurrentManifest(); + } - // Ignore non-GET and different schemes. - if (event.request.method !== 'GET' || url.scheme !== location.scheme) { - return - } + if (manifest) { + cacheName = manifest.cacheName(); + } - // 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 (!cacheName) { + Promise.reject('no-cache'); } - 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() - } + return caches.open(cacheName).then((cache) => + cache.match(request).then((matching) => + matching || Promise.reject('no-match') + )); +} + +// to refresh cache +async function updateCache(request, response) { + // cach eonly js files + if (!request.url.endsWith('.js')) { + return Promise.resolve(); + } - if (response.type !== 'opaque') { - if (response.status < 200 || response.status >= 300) { - throw Error() - } + 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' } - }).catch(_ => { - return cache.match(fallback) - }) - } - } + })); + 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 () { - if (manifest.allowNetworkFallback) { - return fetch(event.request) - } + try { + return await fromCache(event.request); + } catch (e) { + const resp = await fromNetwork(event.request); + event.waitUntil(async function () { + await updateCache(event.request); + }()); + return resp; + } + }()); - return response // failure. - }) - })) -}) \ No newline at end of file +}); diff --git a/dist/jakecache.js b/dist/jakecache.js index b582cbd..5a0b4e0 100644 --- a/dist/jakecache.js +++ b/dist/jakecache.js @@ -1,199 +1,280 @@ '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", + "sw-not-attached" + ]); 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; } + const manifestAttr = "manifest"; + let onload = () => { - if (document.readyState !== 'complete') { - return + if (document.readyState !== "complete") { + return; } + + let html = document.querySelector("html"); + this.pathname = html.getAttribute(manifestAttr); + + if (this.pathname && "serviceWorker" in navigator) { - 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}`) + var self = this; - 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 + navigator.serviceWorker + .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(err => { - console.log(`JakeCache installation failed: ${err}`) - }) + .catch(function (error) { + console.error('JakeCache error when registering service worker', error, arguments); + }); } - } + }; - if (document.readyState === 'complete') { - onload() + if (document.readyState === "complete") { + onload(); } else { - document.onreadystatechange = onload + document.onreadystatechange = onload; } - this[_status] = this.UNCACHED + 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 !!!'); - navigator.serviceWorker.addEventListener('message', event => { + { + this.dispatchEvent(new CustomEvent("sw-not-attached")); + } + }); + }; + + this[_status] = this.UNCACHED; + + 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")); + this.checkIfServiceWorkerAttachedToPage(); + 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")); + console.log('JakeCache noupdate event'); + this.checkIfServiceWorkerAttachedToPage(); + break; + case "progress": + let ev = new ProgressEvent("progress", event.data); + ev.url = event.data.url; + 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)); + + // try to update + this.update(); + 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) {} + update() { - 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 () { + 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(); diff --git a/jakecache-sw.js b/jakecache-sw.js index 72d5c52..337c09c 100644 --- a/jakecache-sw.js +++ b/jakecache-sw.js @@ -1,359 +1,612 @@ +'use strict'; + + 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.cache = null; + 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 () { - this._path = null - this._hash = null - this._isValid = false - this._fetchOptions = { credentials: "same-origin" } - } - - groupName () { - let filename = this._path.substring(this._path.lastIndexOf('/') + 1) - return filename - } - - fetchData (path, options = {}) { - this._path = path - - if (this._isValid && options.cache !== 'reload') { - return Promise.resolve(false) - } - - // 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._rawData = { - cache: [], - fallback: [], - network: [] - } - - 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})`) - - let lines = result.split(/\r|\n/) - let header = 'cache' // default. - - let firstLine = lines.shift() - if (firstLine !== 'CACHE MANIFEST') { - return reject() - } - - for (let line of lines) { - line = line.replace(/#.*$/, '').trim() - - if (line === '') { - continue - } + 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 + }; + } - let res = line.match(/^([A-Z]*):/) - if (res) { - header = res[1].toLowerCase() - continue - } + restoreManifest(manifestData) { + if (!manifestData) { + this._isValid = false; + return; + } + this._path = manifestData.path; + this._hash = manifestData.hash; + this._rawData = manifestData.rawData; - if (!this._rawData[header]) { - this._rawData[header] = [] - } - this._rawData[header].push(line) - } + this.restoreCache(); + } - this.cache = ['jakecache.js'] - // Ignore different protocol - for (let pathname of this._rawData.cache) { - let path = new URL(pathname, location) + restoreCache() { + this.cache = ["jakecache.js", "idb-keyval-iife.js"]; + this.searchCache = {}; + let tmp = {}; + // 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) + if (!tmp[path]) { + this.cache.push(path); + tmp[path] = path; + + this.searchCache[path.pathname] = 1; + } } - } + } - 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.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.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 + } + + this.allowNetworkFallback = false; + this.network = []; + for (let entry of this._rawData.network) { + if (entry === "*") { + this.allowNetworkFallback = true; + continue; } - let path = new URL(entry, location) + let path = new URL(entry, location); if (path.protocol === location.protocol) { - this.network.push(path) + this.network.push(path); + } + } + + this._isValid = true; + } + + shouldBeCached(url) { + return url.pathname.endsWith('.js') || this.searchCache[url.pathname]; + } + + pathName() { + return this._path; + } + + cacheName() { + let version = this._rawData.version; + return version + "_" + this._hash; + } + + cache() { + return this.cache; + } + + fetchData(path, options = {}) { + this._path = path; + + if (this._isValid && options.cache !== "reload") { + 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(url, options), this._fetchOptions).then( + response => { + if ( + response.type === "opaque" || + response.status === 404 || + response.status === 410 + ) { + return Promise.reject(); + } + + this._prevhash = options.hash ? options.hash : null; + + return response.text().then(result => { + return new Promise((resolve, reject) => { + let hash = md5(result); + + 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(); + + 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); + }); + }); } - } - - this._isValid = true - resolve(true) - }) - }) - }) - } + ); + } } -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 = new JakeCacheManifest() +const isAutoUpdate = true; const CacheStatus = { - UNCACHED: 0, - IDLE: 1, - CHECKING: 2, - DOWNLOADING: 3, - UPDATEREADY: 4, - OBSOLETE: 5 + UNCACHED: 0, + IDLE: 1, + CHECKING: 2, + DOWNLOADING: 3, + UPDATEREADY: 4, + OBSOLETE: 5 +}; + +let manifest = null; +let cacheStatus = CacheStatus.UNCACHED; + +function postMessage(msg) { + // { includeUncontrolled: true, type: 'window' } + return self.clients.matchAll().then(clients => { + + if (!clients.length) { + console.log(`JakeCache-SW no clients!! message:`, msg); + } + + return Promise.all( + clients.map(client => { + return client.postMessage(msg); + }) + ); + }); } -let cacheStatus = CacheStatus.UNCACHED +async function storeManifest(newManifest, manifestVersion) { + manifestVersion = manifestVersion || "current"; -function postMessage (msg) { - return self.clients.matchAll().then(clients => { - return Promise.all(clients.map(client => { - return client.postMessage(msg) - })) - }) + await idbKeyval.set(manifestVersion, newManifest.manifestData(), manifestStore); + + return Promise.resolve(newManifest); } -function swapCache () { - caches.keys().then(keyList => { - return Promise.all(keyList.map(key => { - return caches.delete(key) - })) - }).then(() => { - // FIXME: Add new keys. - }) +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); + } +} + + +async function loadCurrentManifest() { + const mnf = await loadManifest("current"); + if (!mnf) { + manifest = null; + cacheStatus = CacheStatus.UNCACHED; + console.log("JakeCache-SW uncached "); + return Promise.resolve(null); + } + + manifest = mnf; + cacheStatus = CacheStatus.CACHED; + return Promise.resolve(manifest); +} + +async function deleteOldCaches() { + let cacheWhitelist = []; + 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(); + return Promise.all( + cacheNames.map(function (cacheName) { + if (cacheWhitelist.indexOf(cacheName) === -1) { + return caches.delete(cacheName); + } + })); } -// 7.9.4 -function update (pathname, options = {}) { - if (!pathname) { - console.log('No pathname!') - return Promise.reject() - } - - // *.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() - } - - // *.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) - }) - }).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() - } - - // Appcache is no-cors by default. - this.requests = manifest.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, 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 - - // Redirects are fatal. - if (response.url !== request.url) { - throw Error() +let updating = false; + +async function update(pathname, options = {}) { + if (!pathname) { + console.log("JakeCache-SW No pathname!"); + return Promise.reject('No pathname'); + } + + if (updating) { + console.log("JakeCache-SW already updating"); + return Promise.reject('already updating'); + } + + updating = true; + + let nextManifest = new JakeCacheManifest(); + self.options = options; + + let manifestVersion = 'current'; + + try { + if (!manifest) { + manifest = await loadCurrentManifest(); } - // FIXME: should we update this.total below? + if (manifest) { + self.options.hash = manifest.hash(); + } - 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 - } + let isNeededToUpdate = await nextManifest.fetchData(pathname, self.options); - // HTTP caching rules, such as Cache-Control: no-store, are ignored. - if ((response.headers.get('cache-control') || '').match(/no-store/i)) { - return undefined - } + // 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; + } } - 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' }) - }) - }).catch(err => { - if (err) { - postMessage({ type: 'error' }, err) - console.log(err) - } - }) -} + 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()} `); -self.addEventListener('install', function (event) { - event.waitUntil(self.skipWaiting()) -}) - -self.addEventListener('activate', function (event) { - event.waitUntil(self.clients.claim()) -}) - -self.addEventListener('fetch', function (event) { - if (cacheStatus === CacheStatus.UNCACHED) { - return fetch(event.request) - } - - let url = new URL(event.request.url) - - // Ignore non-GET and different schemes. - if (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) - } - - 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 (isAutoUpdate) { + console.log(`JakeCache-SW Auto Update`); + swapCache(); + } + else if (isUpgrade) { + cacheStatus = CacheStatus.UPDATEREADY; + postMessage({ type: "updateready" }); } - if (response.type !== 'opaque') { - if (response.status < 200 || response.status >= 300) { - throw Error() - } + 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}`); + + if (manifest) { + cacheStatus = CacheStatus.CACHED; + postMessage({ type: "noupdate" }); + } else { + cacheStatus = CacheStatus.IDLE; + postMessage({ type: "idle" }); + } + return Promise.reject(err); + } +} + +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}`); } - }).catch(_ => { - return cache.match(fallback) - }) + + console.log(`JakeCache-SW swapCache done`); + + postMessage({ type: "updated" }); + } else { + console.log(`JakeCache-SW no manifest to update to`); } - } - if (manifest.allowNetworkFallback) { - return fetch(event.request) - } + 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); + } +} + +const manifestName = 'app.manifest'; + +function getManifestUrl() { + let loc = location.pathname.replace(/([\w\-]+\.js)/, manifestName); + return loc; +} + +self.addEventListener("message", function (event) { + let loc = getManifestUrl(); + + 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("install", function (event) { + let loc = getManifestUrl(); + + event.waitUntil( + update(loc, { cache: "reload" }) + .catch((e) => Promise.resolve()) + .finally(() => self.skipWaiting()) + ); +}); + +self.addEventListener("activate", async 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(); + } + + 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) { + 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(url).then((matching) => + matching || Promise.reject('no-match') + )); +} + +// to refresh cache +async function updateCache(request, response) { + let url = new URL(request.url); + + if (cacheStatus !== CacheStatus.CACHED) { + return Promise.resolve(); + } + + let cacheName = ''; + if (!manifest) { + manifest = await loadCurrentManifest(); + } + + if (manifest) { + cacheName = manifest.cacheName(); + } + + if (!cacheName) { + 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) { + 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' + } + })); + return; + } + + 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 () { + + try { + 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, respClone); + }()); + return resp; + } + }()); - return response // failure. - }) - })) -}) +}); diff --git a/jakecache.js b/jakecache.js index 6807f83..faca348 100644 --- a/jakecache.js +++ b/jakecache.js @@ -1,169 +1,274 @@ -const _eventHandlers = Symbol('eventHandlers') +'use strict'; -let CustomEvent = window.CustomEvent -let DOMException = window.DOMException -let ErrorEvent = window.ErrorEvent -let ProgressEvent = window.ProgressEvent + + +const _eventHandlers = Symbol("eventHandlers"); + +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, { + this[_eventHandlers][name] = { handler: null, listeners: [] }; + Object.defineProperty(this, "on" + name, { get: function () { - return this[_eventHandlers][name]['handler'] + return this[_eventHandlers][name]["handler"]; }, 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", + "sw-not-attached" + ]); 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; } + const manifestAttr = "manifest"; + let onload = () => { - if (document.readyState !== 'complete') { - return + if (document.readyState !== "complete") { + return; } - let html = document.querySelector('html') - this.pathname = html.getAttribute('manifest') + let html = document.querySelector("html"); + this.pathname = html.getAttribute(manifestAttr); + + if ("serviceWorker" in navigator) { + + var self = this; + + navigator.serviceWorker + .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 (this.pathname && 'serviceWorker' in navigator) { - navigator.serviceWorker.register('jakecache-sw.js').then(registration => { - console.log(`JakeCache installed for ${registration.scope}`) + 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("JakeCache cache check for update"); - if (registration.active) { - // Check whether we have a cache, or cache it (no reload enforced). - console.log('cache check') + let html = document.querySelector("html"); + this.pathname = html.getAttribute(manifestAttr); + if (navigator.onLine && registration.active) { registration.active.postMessage({ - command: 'update', + 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; + } + + // 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 + 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")); + this.checkIfServiceWorkerAttachedToPage(); + 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")); + console.log('JakeCache noupdate event'); + this.checkIfServiceWorkerAttachedToPage(); + break; + case "progress": + let ev = new ProgressEvent("progress", event.data); + ev.url = event.data.url; + 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)); + + // try to update + this.update(); + 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) {// 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 @@ -171,32 +276,37 @@ class JakeCache extends PolyfilledEventTarget { 'there is no application cache to update.') } - 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 () { + 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(); \ No newline at end of file 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-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 b3dd8b1..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,7 +32,14 @@ }, "homepage": "https://github.com/kenchris/jakecache#readme", "devDependencies": { - "rollup": "^0.25.8", + "@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": { + "idb-keyval": "^3.2.0" } -} \ No newline at end of file +} 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 a874e93..eec238f 100644 --- a/scripts/build.js +++ b/scripts/build.js @@ -1,20 +1,41 @@ -var rollup = require('rollup') +// 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'] +var files = ["jakecache-sw.js", "jakecache.js"]; -files.forEach(function(file) { +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()], + }, +]; - rollup.rollup({ - entry: file - }).then( function ( bundle ) { - - bundle.write({ - format: 'cjs', - dest: 'dist/' + file - }); - - }).catch(function(e) { - console.error(e, e.stack) - }) - -}) +// 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); +// }); +// });