workbox-expiration.prod.js.map 27.9 KB
{"version":3,"file":"workbox-expiration.prod.js","sources":["../_version.js","../models/CacheTimestampsModel.js","../CacheExpiration.js","../ExpirationPlugin.js"],"sourcesContent":["\"use strict\";\n// @ts-ignore\ntry {\n    self['workbox:expiration:5.1.4'] && _();\n}\ncatch (e) { }\n","/*\n  Copyright 2018 Google LLC\n\n  Use of this source code is governed by an MIT-style\n  license that can be found in the LICENSE file or at\n  https://opensource.org/licenses/MIT.\n*/\nimport { DBWrapper } from 'workbox-core/_private/DBWrapper.js';\nimport { deleteDatabase } from 'workbox-core/_private/deleteDatabase.js';\nimport '../_version.js';\nconst DB_NAME = 'workbox-expiration';\nconst OBJECT_STORE_NAME = 'cache-entries';\nconst normalizeURL = (unNormalizedUrl) => {\n    const url = new URL(unNormalizedUrl, location.href);\n    url.hash = '';\n    return url.href;\n};\n/**\n * Returns the timestamp model.\n *\n * @private\n */\nclass CacheTimestampsModel {\n    /**\n     *\n     * @param {string} cacheName\n     *\n     * @private\n     */\n    constructor(cacheName) {\n        this._cacheName = cacheName;\n        this._db = new DBWrapper(DB_NAME, 1, {\n            onupgradeneeded: (event) => this._handleUpgrade(event),\n        });\n    }\n    /**\n     * Should perform an upgrade of indexedDB.\n     *\n     * @param {Event} event\n     *\n     * @private\n     */\n    _handleUpgrade(event) {\n        const db = event.target.result;\n        // TODO(philipwalton): EdgeHTML doesn't support arrays as a keyPath, so we\n        // have to use the `id` keyPath here and create our own values (a\n        // concatenation of `url + cacheName`) instead of simply using\n        // `keyPath: ['url', 'cacheName']`, which is supported in other browsers.\n        const objStore = db.createObjectStore(OBJECT_STORE_NAME, { keyPath: 'id' });\n        // TODO(philipwalton): once we don't have to support EdgeHTML, we can\n        // create a single index with the keyPath `['cacheName', 'timestamp']`\n        // instead of doing both these indexes.\n        objStore.createIndex('cacheName', 'cacheName', { unique: false });\n        objStore.createIndex('timestamp', 'timestamp', { unique: false });\n        // Previous versions of `workbox-expiration` used `this._cacheName`\n        // as the IDBDatabase name.\n        deleteDatabase(this._cacheName);\n    }\n    /**\n     * @param {string} url\n     * @param {number} timestamp\n     *\n     * @private\n     */\n    async setTimestamp(url, timestamp) {\n        url = normalizeURL(url);\n        const entry = {\n            url,\n            timestamp,\n            cacheName: this._cacheName,\n            // Creating an ID from the URL and cache name won't be necessary once\n            // Edge switches to Chromium and all browsers we support work with\n            // array keyPaths.\n            id: this._getId(url),\n        };\n        await this._db.put(OBJECT_STORE_NAME, entry);\n    }\n    /**\n     * Returns the timestamp stored for a given URL.\n     *\n     * @param {string} url\n     * @return {number}\n     *\n     * @private\n     */\n    async getTimestamp(url) {\n        const entry = await this._db.get(OBJECT_STORE_NAME, this._getId(url));\n        return entry.timestamp;\n    }\n    /**\n     * Iterates through all the entries in the object store (from newest to\n     * oldest) and removes entries once either `maxCount` is reached or the\n     * entry's timestamp is less than `minTimestamp`.\n     *\n     * @param {number} minTimestamp\n     * @param {number} maxCount\n     * @return {Array<string>}\n     *\n     * @private\n     */\n    async expireEntries(minTimestamp, maxCount) {\n        const entriesToDelete = await this._db.transaction(OBJECT_STORE_NAME, 'readwrite', (txn, done) => {\n            const store = txn.objectStore(OBJECT_STORE_NAME);\n            const request = store.index('timestamp').openCursor(null, 'prev');\n            const entriesToDelete = [];\n            let entriesNotDeletedCount = 0;\n            request.onsuccess = () => {\n                const cursor = request.result;\n                if (cursor) {\n                    const result = cursor.value;\n                    // TODO(philipwalton): once we can use a multi-key index, we\n                    // won't have to check `cacheName` here.\n                    if (result.cacheName === this._cacheName) {\n                        // Delete an entry if it's older than the max age or\n                        // if we already have the max number allowed.\n                        if ((minTimestamp && result.timestamp < minTimestamp) ||\n                            (maxCount && entriesNotDeletedCount >= maxCount)) {\n                            // TODO(philipwalton): we should be able to delete the\n                            // entry right here, but doing so causes an iteration\n                            // bug in Safari stable (fixed in TP). Instead we can\n                            // store the keys of the entries to delete, and then\n                            // delete the separate transactions.\n                            // https://github.com/GoogleChrome/workbox/issues/1978\n                            // cursor.delete();\n                            // We only need to return the URL, not the whole entry.\n                            entriesToDelete.push(cursor.value);\n                        }\n                        else {\n                            entriesNotDeletedCount++;\n                        }\n                    }\n                    cursor.continue();\n                }\n                else {\n                    done(entriesToDelete);\n                }\n            };\n        });\n        // TODO(philipwalton): once the Safari bug in the following issue is fixed,\n        // we should be able to remove this loop and do the entry deletion in the\n        // cursor loop above:\n        // https://github.com/GoogleChrome/workbox/issues/1978\n        const urlsDeleted = [];\n        for (const entry of entriesToDelete) {\n            await this._db.delete(OBJECT_STORE_NAME, entry.id);\n            urlsDeleted.push(entry.url);\n        }\n        return urlsDeleted;\n    }\n    /**\n     * Takes a URL and returns an ID that will be unique in the object store.\n     *\n     * @param {string} url\n     * @return {string}\n     *\n     * @private\n     */\n    _getId(url) {\n        // Creating an ID from the URL and cache name won't be necessary once\n        // Edge switches to Chromium and all browsers we support work with\n        // array keyPaths.\n        return this._cacheName + '|' + normalizeURL(url);\n    }\n}\nexport { CacheTimestampsModel };\n","/*\n  Copyright 2018 Google LLC\n\n  Use of this source code is governed by an MIT-style\n  license that can be found in the LICENSE file or at\n  https://opensource.org/licenses/MIT.\n*/\nimport { assert } from 'workbox-core/_private/assert.js';\nimport { dontWaitFor } from 'workbox-core/_private/dontWaitFor.js';\nimport { logger } from 'workbox-core/_private/logger.js';\nimport { WorkboxError } from 'workbox-core/_private/WorkboxError.js';\nimport { CacheTimestampsModel } from './models/CacheTimestampsModel.js';\nimport './_version.js';\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 module:workbox-expiration\n */\nclass 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        this._isRunning = false;\n        this._rerunRequested = false;\n        if (process.env.NODE_ENV !== 'production') {\n            assert.isType(cacheName, 'string', {\n                moduleName: 'workbox-expiration',\n                className: 'CacheExpiration',\n                funcName: 'constructor',\n                paramName: 'cacheName',\n            });\n            if (!(config.maxEntries || config.maxAgeSeconds)) {\n                throw new WorkboxError('max-entries-or-age-required', {\n                    moduleName: 'workbox-expiration',\n                    className: 'CacheExpiration',\n                    funcName: 'constructor',\n                });\n            }\n            if (config.maxEntries) {\n                assert.isType(config.maxEntries, 'number', {\n                    moduleName: 'workbox-expiration',\n                    className: 'CacheExpiration',\n                    funcName: 'constructor',\n                    paramName: 'config.maxEntries',\n                });\n                // TODO: Assert is positive\n            }\n            if (config.maxAgeSeconds) {\n                assert.isType(config.maxAgeSeconds, 'number', {\n                    moduleName: 'workbox-expiration',\n                    className: 'CacheExpiration',\n                    funcName: 'constructor',\n                    paramName: 'config.maxAgeSeconds',\n                });\n                // TODO: Assert is positive\n            }\n        }\n        this._maxEntries = config.maxEntries;\n        this._maxAgeSeconds = config.maxAgeSeconds;\n        this._cacheName = cacheName;\n        this._timestampModel = new CacheTimestampsModel(cacheName);\n    }\n    /**\n     * Expires entries for the given cache and given criteria.\n     */\n    async expireEntries() {\n        if (this._isRunning) {\n            this._rerunRequested = true;\n            return;\n        }\n        this._isRunning = true;\n        const minTimestamp = this._maxAgeSeconds ?\n            Date.now() - (this._maxAgeSeconds * 1000) : 0;\n        const urlsExpired = await this._timestampModel.expireEntries(minTimestamp, this._maxEntries);\n        // Delete URLs from the cache\n        const cache = await self.caches.open(this._cacheName);\n        for (const url of urlsExpired) {\n            await cache.delete(url);\n        }\n        if (process.env.NODE_ENV !== 'production') {\n            if (urlsExpired.length > 0) {\n                logger.groupCollapsed(`Expired ${urlsExpired.length} ` +\n                    `${urlsExpired.length === 1 ? 'entry' : 'entries'} and removed ` +\n                    `${urlsExpired.length === 1 ? 'it' : 'them'} from the ` +\n                    `'${this._cacheName}' cache.`);\n                logger.log(`Expired the following ${urlsExpired.length === 1 ?\n                    'URL' : 'URLs'}:`);\n                urlsExpired.forEach((url) => logger.log(`    ${url}`));\n                logger.groupEnd();\n            }\n            else {\n                logger.debug(`Cache expiration ran and found no entries to remove.`);\n            }\n        }\n        this._isRunning = false;\n        if (this._rerunRequested) {\n            this._rerunRequested = false;\n            dontWaitFor(this.expireEntries());\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    async updateTimestamp(url) {\n        if (process.env.NODE_ENV !== 'production') {\n            assert.isType(url, 'string', {\n                moduleName: 'workbox-expiration',\n                className: 'CacheExpiration',\n                funcName: 'updateTimestamp',\n                paramName: 'url',\n            });\n        }\n        await this._timestampModel.setTimestamp(url, Date.now());\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    async isURLExpired(url) {\n        if (!this._maxAgeSeconds) {\n            if (process.env.NODE_ENV !== 'production') {\n                throw new WorkboxError(`expired-test-without-max-age`, {\n                    methodName: 'isURLExpired',\n                    paramName: 'maxAgeSeconds',\n                });\n            }\n            return false;\n        }\n        else {\n            const timestamp = await this._timestampModel.getTimestamp(url);\n            const expireOlderThan = Date.now() - (this._maxAgeSeconds * 1000);\n            return (timestamp < expireOlderThan);\n        }\n    }\n    /**\n     * Removes the IndexedDB object store used to keep track of cache expiration\n     * metadata.\n     */\n    async delete() {\n        // Make sure we don't attempt another rerun if we're called in the middle of\n        // a cache expiration.\n        this._rerunRequested = false;\n        await this._timestampModel.expireEntries(Infinity); // Expires all.\n    }\n}\nexport { CacheExpiration };\n","/*\n  Copyright 2018 Google LLC\n\n  Use of this source code is governed by an MIT-style\n  license that can be found in the LICENSE file or at\n  https://opensource.org/licenses/MIT.\n*/\nimport { assert } from 'workbox-core/_private/assert.js';\nimport { cacheNames } from 'workbox-core/_private/cacheNames.js';\nimport { dontWaitFor } from 'workbox-core/_private/dontWaitFor.js';\nimport { getFriendlyURL } from 'workbox-core/_private/getFriendlyURL.js';\nimport { logger } from 'workbox-core/_private/logger.js';\nimport { registerQuotaErrorCallback } from 'workbox-core/registerQuotaErrorCallback.js';\nimport { WorkboxError } from 'workbox-core/_private/WorkboxError.js';\nimport { CacheExpiration } from './CacheExpiration.js';\nimport './_version.js';\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 entry least-recently requested will be removed\n * from the cache first.\n *\n * @memberof module:workbox-expiration\n */\nclass ExpirationPlugin {\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         * A \"lifecycle\" callback that will be triggered automatically by the\n         * `workbox-strategies` 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} options\n         * @param {string} options.cacheName Name of the cache the response is in.\n         * @param {Response} options.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        this.cachedResponseWillBeUsed = async ({ event, request, cacheName, cachedResponse }) => {\n            if (!cachedResponse) {\n                return null;\n            }\n            const isFresh = this._isResponseDateFresh(cachedResponse);\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            dontWaitFor(cacheExpiration.expireEntries());\n            // Update the metadata for the request URL to the current timestamp,\n            // but don't `await` it as we don't want to block the response.\n            const updateTimestampDone = cacheExpiration.updateTimestamp(request.url);\n            if (event) {\n                try {\n                    event.waitUntil(updateTimestampDone);\n                }\n                catch (error) {\n                    if (process.env.NODE_ENV !== 'production') {\n                        // The event may not be a fetch event; only log the URL if it is.\n                        if ('request' in event) {\n                            logger.warn(`Unable to ensure service worker stays alive when ` +\n                                `updating cache entry for ` +\n                                `'${getFriendlyURL(event.request.url)}'.`);\n                        }\n                    }\n                }\n            }\n            return isFresh ? cachedResponse : null;\n        };\n        /**\n         * A \"lifecycle\" callback that will be triggered automatically by the\n         * `workbox-strategies` handlers when an entry is added to a cache.\n         *\n         * @param {Object} options\n         * @param {string} options.cacheName Name of the cache that was updated.\n         * @param {string} options.request The Request for the cached entry.\n         *\n         * @private\n         */\n        this.cacheDidUpdate = async ({ cacheName, request }) => {\n            if (process.env.NODE_ENV !== 'production') {\n                assert.isType(cacheName, 'string', {\n                    moduleName: 'workbox-expiration',\n                    className: 'Plugin',\n                    funcName: 'cacheDidUpdate',\n                    paramName: 'cacheName',\n                });\n                assert.isInstance(request, Request, {\n                    moduleName: 'workbox-expiration',\n                    className: 'Plugin',\n                    funcName: 'cacheDidUpdate',\n                    paramName: 'request',\n                });\n            }\n            const cacheExpiration = this._getCacheExpiration(cacheName);\n            await cacheExpiration.updateTimestamp(request.url);\n            await cacheExpiration.expireEntries();\n        };\n        if (process.env.NODE_ENV !== 'production') {\n            if (!(config.maxEntries || config.maxAgeSeconds)) {\n                throw new WorkboxError('max-entries-or-age-required', {\n                    moduleName: 'workbox-expiration',\n                    className: 'Plugin',\n                    funcName: 'constructor',\n                });\n            }\n            if (config.maxEntries) {\n                assert.isType(config.maxEntries, 'number', {\n                    moduleName: 'workbox-expiration',\n                    className: 'Plugin',\n                    funcName: 'constructor',\n                    paramName: 'config.maxEntries',\n                });\n            }\n            if (config.maxAgeSeconds) {\n                assert.isType(config.maxAgeSeconds, 'number', {\n                    moduleName: 'workbox-expiration',\n                    className: 'Plugin',\n                    funcName: 'constructor',\n                    paramName: 'config.maxAgeSeconds',\n                });\n            }\n        }\n        this._config = config;\n        this._maxAgeSeconds = config.maxAgeSeconds;\n        this._cacheExpirations = new Map();\n        if (config.purgeOnQuotaError) {\n            registerQuotaErrorCallback(() => this.deleteCacheAndMetadata());\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.getRuntimeName()) {\n            throw new WorkboxError('expire-custom-caches-only');\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     * @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        // 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        // 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     * This method will extract the data header and parse it into a useful\n     * value.\n     *\n     * @param {Response} cachedResponse\n     * @return {number|null}\n     *\n     * @private\n     */\n    _getDateHeaderTimestamp(cachedResponse) {\n        if (!cachedResponse.headers.has('date')) {\n            return null;\n        }\n        const dateHeader = cachedResponse.headers.get('date');\n        const parsedDate = new Date(dateHeader);\n        const headerTime = parsedDate.getTime();\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        return headerTime;\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 your 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    async deleteCacheAndMetadata() {\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 this._cacheExpirations) {\n            await self.caches.delete(cacheName);\n            await cacheExpiration.delete();\n        }\n        // Reset this._cacheExpirations to its initial state.\n        this._cacheExpirations = new Map();\n    }\n}\nexport { ExpirationPlugin };\n"],"names":["self","_","e","normalizeURL","unNormalizedUrl","url","URL","location","href","hash","CacheTimestampsModel","constructor","cacheName","_cacheName","_db","DBWrapper","onupgradeneeded","event","this","_handleUpgrade","objStore","target","result","createObjectStore","keyPath","createIndex","unique","deleteDatabase","timestamp","entry","id","_getId","put","get","minTimestamp","maxCount","entriesToDelete","transaction","txn","done","request","objectStore","index","openCursor","entriesNotDeletedCount","onsuccess","cursor","value","push","continue","urlsDeleted","delete","CacheExpiration","config","_isRunning","_rerunRequested","_maxEntries","maxEntries","_maxAgeSeconds","maxAgeSeconds","_timestampModel","Date","now","urlsExpired","expireEntries","cache","caches","open","dontWaitFor","setTimestamp","getTimestamp","Infinity","cachedResponseWillBeUsed","async","cachedResponse","isFresh","_isResponseDateFresh","cacheExpiration","_getCacheExpiration","updateTimestampDone","updateTimestamp","waitUntil","error","cacheDidUpdate","_config","_cacheExpirations","Map","purgeOnQuotaError","registerQuotaErrorCallback","deleteCacheAndMetadata","cacheNames","getRuntimeName","WorkboxError","set","dateHeaderTimestamp","_getDateHeaderTimestamp","headers","has","dateHeader","headerTime","getTime","isNaN"],"mappings":"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"}