UNPKG

24.7 kBSource Map (JSON)View Raw
1{"version":3,"names":[],"mappings":"","sources":["packages/workbox-cache-expiration/browser.mjs"],"sourcesContent":["this.workbox = this.workbox || {};\nthis.workbox.expiration = (function (exports,DBWrapper_mjs,WorkboxError_mjs,assert_mjs,logger_mjs,cacheNames_mjs,quota_mjs) {\n 'use strict';\n\n try {\n self.workbox.v['workbox:cache-expiration:3.5.0'] = 1;\n } catch (e) {} // eslint-disable-line\n\n /*\n Copyright 2017 Google Inc.\n\n Licensed under the Apache License, Version 2.0 (the \"License\");\n you may not use this file except in compliance with the License.\n You may obtain a copy of the License at\n\n https://www.apache.org/licenses/LICENSE-2.0\n\n Unless required by applicable law or agreed to in writing, software\n distributed under the License is distributed on an \"AS IS\" BASIS,\n WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n See the License for the specific language governing permissions and\n limitations under the License.\n */\n\n const URL_KEY = 'url';\n const TIMESTAMP_KEY = 'timestamp';\n\n /**\n * Returns the timestamp model.\n *\n * @private\n */\n class CacheTimestampsModel {\n /**\n *\n * @param {string} cacheName\n *\n * @private\n */\n constructor(cacheName) {\n // TODO Check cacheName\n\n this._cacheName = cacheName;\n this._storeName = cacheName;\n\n this._db = new DBWrapper_mjs.DBWrapper(this._cacheName, 2, {\n onupgradeneeded: evt => this._handleUpgrade(evt)\n });\n }\n\n /**\n * Should perform an upgrade of indexedDB.\n *\n * @param {Event} evt\n *\n * @private\n */\n _handleUpgrade(evt) {\n const db = evt.target.result;\n if (evt.oldVersion < 2) {\n // Remove old databases.\n if (db.objectStoreNames.contains('workbox-cache-expiration')) {\n db.deleteObjectStore('workbox-cache-expiration');\n }\n }\n\n db.createObjectStore(this._storeName, { keyPath: URL_KEY }).createIndex(TIMESTAMP_KEY, TIMESTAMP_KEY, { unique: false });\n }\n\n /**\n * @param {string} url\n * @param {number} timestamp\n *\n * @private\n */\n setTimestamp(url, timestamp) {\n var _this = this;\n\n return babelHelpers.asyncToGenerator(function* () {\n yield _this._db.put(_this._storeName, {\n [URL_KEY]: new URL(url, location).href,\n [TIMESTAMP_KEY]: timestamp\n });\n })();\n }\n\n /**\n * Get all of the timestamps in the indexedDB.\n *\n * @return {Array<Objects>}\n *\n * @private\n */\n getAllTimestamps() {\n var _this2 = this;\n\n return babelHelpers.asyncToGenerator(function* () {\n return yield _this2._db.getAllMatching(_this2._storeName, {\n index: TIMESTAMP_KEY\n });\n })();\n }\n\n /**\n * Returns the timestamp stored for a given URL.\n *\n * @param {string} url\n * @return {number}\n *\n * @private\n */\n getTimestamp(url) {\n var _this3 = this;\n\n return babelHelpers.asyncToGenerator(function* () {\n const timestampObject = yield _this3._db.get(_this3._storeName, url);\n return timestampObject.timestamp;\n })();\n }\n\n /**\n * @param {string} url\n *\n * @private\n */\n deleteUrl(url) {\n var _this4 = this;\n\n return babelHelpers.asyncToGenerator(function* () {\n yield _this4._db.delete(_this4._storeName, new URL(url, location).href);\n })();\n }\n\n /**\n * Removes the underlying IndexedDB object store entirely.\n */\n delete() {\n var _this5 = this;\n\n return babelHelpers.asyncToGenerator(function* () {\n yield _this5._db.deleteDatabase();\n _this5._db = null;\n })();\n }\n }\n\n /*\n Copyright 2017 Google Inc.\n\n Licensed under the Apache License, Version 2.0 (the \"License\");\n you may not use this file except in compliance with the License.\n You may obtain a copy of the License at\n\n https://www.apache.org/licenses/LICENSE-2.0\n\n Unless required by applicable law or agreed to in writing, software\n distributed under the License is distributed on an \"AS IS\" BASIS,\n WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n See the License for the specific language governing permissions and\n limitations under the License.\n */\n\n /**\n * The `CacheExpiration` class allows you define an expiration and / or\n * limit on the number of responses stored in a\n * [`Cache`](https://developer.mozilla.org/en-US/docs/Web/API/Cache).\n *\n * @memberof workbox.expiration\n */\n class CacheExpiration {\n /**\n * To construct a new CacheExpiration instance you must provide at least\n * one of the `config` properties.\n *\n * @param {string} cacheName Name of the cache to apply restrictions to.\n * @param {Object} config\n * @param {number} [config.maxEntries] The maximum number of entries to cache.\n * Entries used the least will be removed as the maximum is reached.\n * @param {number} [config.maxAgeSeconds] The maximum age of an entry before\n * it's treated as stale and removed.\n */\n constructor(cacheName, config = {}) {\n {\n assert_mjs.assert.isType(cacheName, 'string', {\n moduleName: 'workbox-cache-expiration',\n className: 'CacheExpiration',\n funcName: 'constructor',\n paramName: 'cacheName'\n });\n\n if (!(config.maxEntries || config.maxAgeSeconds)) {\n throw new WorkboxError_mjs.WorkboxError('max-entries-or-age-required', {\n moduleName: 'workbox-cache-expiration',\n className: 'CacheExpiration',\n funcName: 'constructor'\n });\n }\n\n if (config.maxEntries) {\n assert_mjs.assert.isType(config.maxEntries, 'number', {\n moduleName: 'workbox-cache-expiration',\n className: 'CacheExpiration',\n funcName: 'constructor',\n paramName: 'config.maxEntries'\n });\n\n // TODO: Assert is positive\n }\n\n if (config.maxAgeSeconds) {\n assert_mjs.assert.isType(config.maxAgeSeconds, 'number', {\n moduleName: 'workbox-cache-expiration',\n className: 'CacheExpiration',\n funcName: 'constructor',\n paramName: 'config.maxAgeSeconds'\n });\n\n // TODO: Assert is positive\n }\n }\n\n this._isRunning = false;\n this._rerunRequested = false;\n this._maxEntries = config.maxEntries;\n this._maxAgeSeconds = config.maxAgeSeconds;\n this._cacheName = cacheName;\n this._timestampModel = new CacheTimestampsModel(cacheName);\n }\n\n /**\n * Expires entries for the given cache and given criteria.\n */\n expireEntries() {\n var _this = this;\n\n return babelHelpers.asyncToGenerator(function* () {\n if (_this._isRunning) {\n _this._rerunRequested = true;\n return;\n }\n _this._isRunning = true;\n\n const now = Date.now();\n\n // First, expire old entries, if maxAgeSeconds is set.\n const oldEntries = yield _this._findOldEntries(now);\n\n // Once that's done, check for the maximum size.\n const extraEntries = yield _this._findExtraEntries();\n\n // Use a Set to remove any duplicates following the concatenation, then\n // convert back into an array.\n const allUrls = [...new Set(oldEntries.concat(extraEntries))];\n\n yield Promise.all([_this._deleteFromCache(allUrls), _this._deleteFromIDB(allUrls)]);\n\n {\n // TODO: break apart entries deleted due to expiration vs size restraints\n if (allUrls.length > 0) {\n logger_mjs.logger.groupCollapsed(`Expired ${allUrls.length} ` + `${allUrls.length === 1 ? 'entry' : 'entries'} and removed ` + `${allUrls.length === 1 ? 'it' : 'them'} from the ` + `'${_this._cacheName}' cache.`);\n logger_mjs.logger.log(`Expired the following ${allUrls.length === 1 ? 'URL' : 'URLs'}:`);\n allUrls.forEach(function (url) {\n return logger_mjs.logger.log(` ${url}`);\n });\n logger_mjs.logger.groupEnd();\n } else {\n logger_mjs.logger.debug(`Cache expiration ran and found no entries to remove.`);\n }\n }\n\n _this._isRunning = false;\n if (_this._rerunRequested) {\n _this._rerunRequested = false;\n _this.expireEntries();\n }\n })();\n }\n\n /**\n * Expires entries based on the maximum age.\n *\n * @param {number} expireFromTimestamp A timestamp.\n * @return {Promise<Array<string>>} A list of the URLs that were expired.\n *\n * @private\n */\n _findOldEntries(expireFromTimestamp) {\n var _this2 = this;\n\n return babelHelpers.asyncToGenerator(function* () {\n {\n assert_mjs.assert.isType(expireFromTimestamp, 'number', {\n moduleName: 'workbox-cache-expiration',\n className: 'CacheExpiration',\n funcName: '_findOldEntries',\n paramName: 'expireFromTimestamp'\n });\n }\n\n if (!_this2._maxAgeSeconds) {\n return [];\n }\n\n const expireOlderThan = expireFromTimestamp - _this2._maxAgeSeconds * 1000;\n const timestamps = yield _this2._timestampModel.getAllTimestamps();\n const expiredUrls = [];\n timestamps.forEach(function (timestampDetails) {\n if (timestampDetails.timestamp < expireOlderThan) {\n expiredUrls.push(timestampDetails.url);\n }\n });\n\n return expiredUrls;\n })();\n }\n\n /**\n * @return {Promise<Array>}\n *\n * @private\n */\n _findExtraEntries() {\n var _this3 = this;\n\n return babelHelpers.asyncToGenerator(function* () {\n const extraUrls = [];\n\n if (!_this3._maxEntries) {\n return [];\n }\n\n const timestamps = yield _this3._timestampModel.getAllTimestamps();\n while (timestamps.length > _this3._maxEntries) {\n const lastUsed = timestamps.shift();\n extraUrls.push(lastUsed.url);\n }\n\n return extraUrls;\n })();\n }\n\n /**\n * @param {Array<string>} urls Array of URLs to delete from cache.\n *\n * @private\n */\n _deleteFromCache(urls) {\n var _this4 = this;\n\n return babelHelpers.asyncToGenerator(function* () {\n const cache = yield caches.open(_this4._cacheName);\n for (const url of urls) {\n yield cache.delete(url);\n }\n })();\n }\n\n /**\n * @param {Array<string>} urls Array of URLs to delete from IDB\n *\n * @private\n */\n _deleteFromIDB(urls) {\n var _this5 = this;\n\n return babelHelpers.asyncToGenerator(function* () {\n for (const url of urls) {\n yield _this5._timestampModel.deleteUrl(url);\n }\n })();\n }\n\n /**\n * Update the timestamp for the given URL. This ensures the when\n * removing entries based on maximum entries, most recently used\n * is accurate or when expiring, the timestamp is up-to-date.\n *\n * @param {string} url\n */\n updateTimestamp(url) {\n var _this6 = this;\n\n return babelHelpers.asyncToGenerator(function* () {\n {\n assert_mjs.assert.isType(url, 'string', {\n moduleName: 'workbox-cache-expiration',\n className: 'CacheExpiration',\n funcName: 'updateTimestamp',\n paramName: 'url'\n });\n }\n\n const urlObject = new URL(url, location);\n urlObject.hash = '';\n\n yield _this6._timestampModel.setTimestamp(urlObject.href, Date.now());\n })();\n }\n\n /**\n * Can be used to check if a URL has expired or not before it's used.\n *\n * This requires a look up from IndexedDB, so can be slow.\n *\n * Note: This method will not remove the cached entry, call\n * `expireEntries()` to remove indexedDB and Cache entries.\n *\n * @param {string} url\n * @return {boolean}\n */\n isURLExpired(url) {\n var _this7 = this;\n\n return babelHelpers.asyncToGenerator(function* () {\n if (!_this7._maxAgeSeconds) {\n throw new WorkboxError_mjs.WorkboxError(`expired-test-without-max-age`, {\n methodName: 'isURLExpired',\n paramName: 'maxAgeSeconds'\n });\n }\n const urlObject = new URL(url, location);\n urlObject.hash = '';\n\n const timestamp = yield _this7._timestampModel.getTimestamp(urlObject.href);\n const expireOlderThan = Date.now() - _this7._maxAgeSeconds * 1000;\n return timestamp < expireOlderThan;\n })();\n }\n\n /**\n * Removes the IndexedDB object store used to keep track of cache expiration\n * metadata.\n */\n delete() {\n var _this8 = this;\n\n return babelHelpers.asyncToGenerator(function* () {\n // Make sure we don't attempt another rerun if we're called in the middle of\n // a cache expiration.\n _this8._rerunRequested = false;\n yield _this8._timestampModel.delete();\n })();\n }\n }\n\n /*\n Copyright 2016 Google Inc. All Rights Reserved.\n Licensed under the Apache License, Version 2.0 (the \"License\");\n you may not use this file except in compliance with the License.\n You may obtain a copy of the License at\n http://www.apache.org/licenses/LICENSE-2.0\n Unless required by applicable law or agreed to in writing, software\n distributed under the License is distributed on an \"AS IS\" BASIS,\n WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n See the License for the specific language governing permissions and\n limitations under the License.\n */\n\n /**\n * This plugin can be used in the Workbox APIs to regularly enforce a\n * limit on the age and / or the number of cached requests.\n *\n * Whenever a cached request is used or updated, this plugin will look\n * at the used Cache and remove any old or extra requests.\n *\n * When using `maxAgeSeconds`, requests may be used *once* after expiring\n * because the expiration clean up will not have occurred until *after* the\n * cached request has been used. If the request has a \"Date\" header, then\n * a light weight expiration check is performed and the request will not be\n * used immediately.\n *\n * When using `maxEntries`, the last request to be used will be the request\n * that is removed from the Cache.\n *\n * @memberof workbox.expiration\n */\n class Plugin {\n /**\n * @param {Object} config\n * @param {number} [config.maxEntries] The maximum number of entries to cache.\n * Entries used the least will be removed as the maximum is reached.\n * @param {number} [config.maxAgeSeconds] The maximum age of an entry before\n * it's treated as stale and removed.\n * @param {boolean} [config.purgeOnQuotaError] Whether to opt this cache in to\n * automatic deletion if the available storage quota has been exceeded.\n */\n constructor(config = {}) {\n {\n if (!(config.maxEntries || config.maxAgeSeconds)) {\n throw new WorkboxError_mjs.WorkboxError('max-entries-or-age-required', {\n moduleName: 'workbox-cache-expiration',\n className: 'Plugin',\n funcName: 'constructor'\n });\n }\n\n if (config.maxEntries) {\n assert_mjs.assert.isType(config.maxEntries, 'number', {\n moduleName: 'workbox-cache-expiration',\n className: 'Plugin',\n funcName: 'constructor',\n paramName: 'config.maxEntries'\n });\n }\n\n if (config.maxAgeSeconds) {\n assert_mjs.assert.isType(config.maxAgeSeconds, 'number', {\n moduleName: 'workbox-cache-expiration',\n className: 'Plugin',\n funcName: 'constructor',\n paramName: 'config.maxAgeSeconds'\n });\n }\n }\n\n this._config = config;\n this._maxAgeSeconds = config.maxAgeSeconds;\n this._cacheExpirations = new Map();\n\n if (config.purgeOnQuotaError) {\n quota_mjs.registerQuotaErrorCallback(() => this.deleteCacheAndMetadata());\n }\n }\n\n /**\n * A simple helper method to return a CacheExpiration instance for a given\n * cache name.\n *\n * @param {string} cacheName\n * @return {CacheExpiration}\n *\n * @private\n */\n _getCacheExpiration(cacheName) {\n if (cacheName === cacheNames_mjs.cacheNames.getRuntimeName()) {\n throw new WorkboxError_mjs.WorkboxError('expire-custom-caches-only');\n }\n\n let cacheExpiration = this._cacheExpirations.get(cacheName);\n if (!cacheExpiration) {\n cacheExpiration = new CacheExpiration(cacheName, this._config);\n this._cacheExpirations.set(cacheName, cacheExpiration);\n }\n return cacheExpiration;\n }\n\n /**\n * A \"lifecycle\" callback that will be triggered automatically by the\n * `workbox.runtimeCaching` handlers when a `Response` is about to be returned\n * from a [Cache](https://developer.mozilla.org/en-US/docs/Web/API/Cache) to\n * the handler. It allows the `Response` to be inspected for freshness and\n * prevents it from being used if the `Response`'s `Date` header value is\n * older than the configured `maxAgeSeconds`.\n *\n * @param {Object} input\n * @param {string} input.cacheName Name of the cache the responses belong to.\n * @param {Response} input.cachedResponse The `Response` object that's been\n * read from a cache and whose freshness should be checked.\n * @return {Response} Either the `cachedResponse`, if it's\n * fresh, or `null` if the `Response` is older than `maxAgeSeconds`.\n *\n * @private\n */\n cachedResponseWillBeUsed({ cacheName, cachedResponse }) {\n if (!cachedResponse) {\n return null;\n }\n\n let isFresh = this._isResponseDateFresh(cachedResponse);\n\n // Expire entries to ensure that even if the expiration date has\n // expired, it'll only be used once.\n const cacheExpiration = this._getCacheExpiration(cacheName);\n cacheExpiration.expireEntries();\n\n return isFresh ? cachedResponse : null;\n }\n\n /**\n * @param {Response} cachedResponse\n * @return {boolean}\n *\n * @private\n */\n _isResponseDateFresh(cachedResponse) {\n if (!this._maxAgeSeconds) {\n // We aren't expiring by age, so return true, it's fresh\n return true;\n }\n\n // Check if the 'date' header will suffice a quick expiration check.\n // See https://github.com/GoogleChromeLabs/sw-toolbox/issues/164 for\n // discussion.\n const dateHeaderTimestamp = this._getDateHeaderTimestamp(cachedResponse);\n if (dateHeaderTimestamp === null) {\n // Unable to parse date, so assume it's fresh.\n return true;\n }\n\n // If we have a valid headerTime, then our response is fresh iff the\n // headerTime plus maxAgeSeconds is greater than the current time.\n const now = Date.now();\n return dateHeaderTimestamp >= now - this._maxAgeSeconds * 1000;\n }\n\n /**\n * This method will extract the data header and parse it into a useful\n * value.\n *\n * @param {Response} cachedResponse\n * @return {number}\n *\n * @private\n */\n _getDateHeaderTimestamp(cachedResponse) {\n if (!cachedResponse.headers.has('date')) {\n return null;\n }\n\n const dateHeader = cachedResponse.headers.get('date');\n const parsedDate = new Date(dateHeader);\n const headerTime = parsedDate.getTime();\n\n // If the Date header was invalid for some reason, parsedDate.getTime()\n // will return NaN.\n if (isNaN(headerTime)) {\n return null;\n }\n\n return headerTime;\n }\n\n /**\n * A \"lifecycle\" callback that will be triggered automatically by the\n * `workbox.runtimeCaching` handlers when an entry is added to a cache.\n *\n * @param {Object} input\n * @param {string} input.cacheName Name of the cache the responses belong to.\n * @param {string} input.request The Request for the cached entry.\n *\n * @private\n */\n cacheDidUpdate({ cacheName, request }) {\n var _this = this;\n\n return babelHelpers.asyncToGenerator(function* () {\n {\n assert_mjs.assert.isType(cacheName, 'string', {\n moduleName: 'workbox-cache-expiration',\n className: 'Plugin',\n funcName: 'cacheDidUpdate',\n paramName: 'cacheName'\n });\n assert_mjs.assert.isInstance(request, Request, {\n moduleName: 'workbox-cache-expiration',\n className: 'Plugin',\n funcName: 'cacheDidUpdate',\n paramName: 'request'\n });\n }\n\n const cacheExpiration = _this._getCacheExpiration(cacheName);\n yield cacheExpiration.updateTimestamp(request.url);\n yield cacheExpiration.expireEntries();\n })();\n }\n\n /**\n * This is a helper method that performs two operations:\n *\n * - Deletes *all* the underlying Cache instances associated with this plugin\n * instance, by calling caches.delete() on you behalf.\n * - Deletes the metadata from IndexedDB used to keep track of expiration\n * details for each Cache instance.\n *\n * When using cache expiration, calling this method is preferable to calling\n * `caches.delete()` directly, since this will ensure that the IndexedDB\n * metadata is also cleanly removed and open IndexedDB instances are deleted.\n *\n * Note that if you're *not* using cache expiration for a given cache, calling\n * `caches.delete()` and passing in the cache's name should be sufficient.\n * There is no Workbox-specific method needed for cleanup in that case.\n */\n deleteCacheAndMetadata() {\n var _this2 = this;\n\n return babelHelpers.asyncToGenerator(function* () {\n // Do this one at a time instead of all at once via `Promise.all()` to\n // reduce the chance of inconsistency if a promise rejects.\n for (const [cacheName, cacheExpiration] of _this2._cacheExpirations) {\n yield caches.delete(cacheName);\n yield cacheExpiration.delete();\n }\n\n // Reset this._cacheExpirations to its initial state.\n _this2._cacheExpirations = new Map();\n })();\n }\n }\n\n /*\n Copyright 2017 Google Inc.\n\n Licensed under the Apache License, Version 2.0 (the \"License\");\n you may not use this file except in compliance with the License.\n You may obtain a copy of the License at\n\n https://www.apache.org/licenses/LICENSE-2.0\n\n Unless required by applicable law or agreed to in writing, software\n distributed under the License is distributed on an \"AS IS\" BASIS,\n WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n See the License for the specific language governing permissions and\n limitations under the License.\n */\n\n /*\n Copyright 2017 Google Inc.\n\n Licensed under the Apache License, Version 2.0 (the \"License\");\n you may not use this file except in compliance with the License.\n You may obtain a copy of the License at\n\n https://www.apache.org/licenses/LICENSE-2.0\n\n Unless required by applicable law or agreed to in writing, software\n distributed under the License is distributed on an \"AS IS\" BASIS,\n WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n See the License for the specific language governing permissions and\n limitations under the License.\n */\n\n exports.CacheExpiration = CacheExpiration;\n exports.Plugin = Plugin;\n\n return exports;\n\n}({},workbox.core._private,workbox.core._private,workbox.core._private,workbox.core._private,workbox.core._private,workbox.core._private));\n"],"file":"workbox-cache-expiration.dev.js"}
\No newline at end of file