Huge News!Announcing our $40M Series B led by Abstract Ventures.Learn More
Socket
Sign inDemoInstall
Socket

workbox-expiration

Package Overview
Dependencies
Maintainers
6
Versions
58
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

workbox-expiration - npm Package Compare versions

Comparing version 6.1.5 to 6.2.0-alpha.0

2

_version.js
"use strict";
// @ts-ignore
try {
self['workbox:expiration:6.1.5'] && _();
self['workbox:expiration:6.2.0-alpha.0'] && _();
}
catch (e) { }
this.workbox = this.workbox || {};
this.workbox.expiration = (function (exports, assert_js, dontWaitFor_js, logger_js, WorkboxError_js, DBWrapper_js, deleteDatabase_js, cacheNames_js, getFriendlyURL_js, registerQuotaErrorCallback_js) {
'use strict';
this.workbox.expiration = (function (exports, assert_js, dontWaitFor_js, logger_js, WorkboxError_js, cacheNames_js, getFriendlyURL_js, registerQuotaErrorCallback_js) {
'use strict';
try {
self['workbox:expiration:6.1.5'] && _();
} catch (e) {}
function _extends() {
_extends = Object.assign || function (target) {
for (var i = 1; i < arguments.length; i++) {
var source = arguments[i];
/*
Copyright 2018 Google LLC
for (var key in source) {
if (Object.prototype.hasOwnProperty.call(source, key)) {
target[key] = source[key];
}
}
}
Use of this source code is governed by an MIT-style
license that can be found in the LICENSE file or at
https://opensource.org/licenses/MIT.
*/
const DB_NAME = 'workbox-expiration';
const OBJECT_STORE_NAME = 'cache-entries';
const normalizeURL = unNormalizedUrl => {
const url = new URL(unNormalizedUrl, location.href);
url.hash = '';
return url.href;
return target;
};
/**
* Returns the timestamp model.
*
* @private
*/
return _extends.apply(this, arguments);
}
class CacheTimestampsModel {
/**
*
* @param {string} cacheName
*
* @private
*/
constructor(cacheName) {
this._cacheName = cacheName;
this._db = new DBWrapper_js.DBWrapper(DB_NAME, 1, {
onupgradeneeded: event => this._handleUpgrade(event)
});
}
/**
* Should perform an upgrade of indexedDB.
*
* @param {Event} event
*
* @private
*/
const instanceOfAny = (object, constructors) => constructors.some(c => object instanceof c);
let idbProxyableTypes;
let cursorAdvanceMethods; // This is a function to prevent it throwing up in node environments.
_handleUpgrade(event) {
const db = event.target.result; // TODO(philipwalton): EdgeHTML doesn't support arrays as a keyPath, so we
// have to use the `id` keyPath here and create our own values (a
// concatenation of `url + cacheName`) instead of simply using
// `keyPath: ['url', 'cacheName']`, which is supported in other browsers.
function getIdbProxyableTypes() {
return idbProxyableTypes || (idbProxyableTypes = [IDBDatabase, IDBObjectStore, IDBIndex, IDBCursor, IDBTransaction]);
} // This is a function to prevent it throwing up in node environments.
const objStore = db.createObjectStore(OBJECT_STORE_NAME, {
keyPath: 'id'
}); // TODO(philipwalton): once we don't have to support EdgeHTML, we can
// create a single index with the keyPath `['cacheName', 'timestamp']`
// instead of doing both these indexes.
objStore.createIndex('cacheName', 'cacheName', {
unique: false
});
objStore.createIndex('timestamp', 'timestamp', {
unique: false
}); // Previous versions of `workbox-expiration` used `this._cacheName`
// as the IDBDatabase name.
function getCursorAdvanceMethods() {
return cursorAdvanceMethods || (cursorAdvanceMethods = [IDBCursor.prototype.advance, IDBCursor.prototype.continue, IDBCursor.prototype.continuePrimaryKey]);
}
deleteDatabase_js.deleteDatabase(this._cacheName);
}
/**
* @param {string} url
* @param {number} timestamp
*
* @private
*/
const cursorRequestMap = new WeakMap();
const transactionDoneMap = new WeakMap();
const transactionStoreNamesMap = new WeakMap();
const transformCache = new WeakMap();
const reverseTransformCache = new WeakMap();
function promisifyRequest(request) {
const promise = new Promise((resolve, reject) => {
const unlisten = () => {
request.removeEventListener('success', success);
request.removeEventListener('error', error);
};
async setTimestamp(url, timestamp) {
url = normalizeURL(url);
const entry = {
url,
timestamp,
cacheName: this._cacheName,
// Creating an ID from the URL and cache name won't be necessary once
// Edge switches to Chromium and all browsers we support work with
// array keyPaths.
id: this._getId(url)
};
await this._db.put(OBJECT_STORE_NAME, entry);
}
/**
* Returns the timestamp stored for a given URL.
*
* @param {string} url
* @return {number}
*
* @private
*/
const success = () => {
resolve(wrap(request.result));
unlisten();
};
const error = () => {
reject(request.error);
unlisten();
};
async getTimestamp(url) {
const entry = await this._db.get(OBJECT_STORE_NAME, this._getId(url));
return entry.timestamp;
}
/**
* Iterates through all the entries in the object store (from newest to
* oldest) and removes entries once either `maxCount` is reached or the
* entry's timestamp is less than `minTimestamp`.
*
* @param {number} minTimestamp
* @param {number} maxCount
* @return {Array<string>}
*
* @private
*/
request.addEventListener('success', success);
request.addEventListener('error', error);
});
promise.then(value => {
// Since cursoring reuses the IDBRequest (*sigh*), we cache it for later retrieval
// (see wrapFunction).
if (value instanceof IDBCursor) {
cursorRequestMap.set(value, request);
} // Catching to avoid "Uncaught Promise exceptions"
}).catch(() => {}); // This mapping exists in reverseTransformCache but doesn't doesn't exist in transformCache. This
// is because we create many promises from a single IDBRequest.
async expireEntries(minTimestamp, maxCount) {
const entriesToDelete = await this._db.transaction(OBJECT_STORE_NAME, 'readwrite', (txn, done) => {
const store = txn.objectStore(OBJECT_STORE_NAME);
const request = store.index('timestamp').openCursor(null, 'prev');
const entriesToDelete = [];
let entriesNotDeletedCount = 0;
reverseTransformCache.set(promise, request);
return promise;
}
request.onsuccess = () => {
const cursor = request.result;
function cacheDonePromiseForTransaction(tx) {
// Early bail if we've already created a done promise for this transaction.
if (transactionDoneMap.has(tx)) return;
const done = new Promise((resolve, reject) => {
const unlisten = () => {
tx.removeEventListener('complete', complete);
tx.removeEventListener('error', error);
tx.removeEventListener('abort', error);
};
if (cursor) {
const result = cursor.value; // TODO(philipwalton): once we can use a multi-key index, we
// won't have to check `cacheName` here.
const complete = () => {
resolve();
unlisten();
};
if (result.cacheName === this._cacheName) {
// Delete an entry if it's older than the max age or
// if we already have the max number allowed.
if (minTimestamp && result.timestamp < minTimestamp || maxCount && entriesNotDeletedCount >= maxCount) {
// TODO(philipwalton): we should be able to delete the
// entry right here, but doing so causes an iteration
// bug in Safari stable (fixed in TP). Instead we can
// store the keys of the entries to delete, and then
// delete the separate transactions.
// https://github.com/GoogleChrome/workbox/issues/1978
// cursor.delete();
// We only need to return the URL, not the whole entry.
entriesToDelete.push(cursor.value);
} else {
entriesNotDeletedCount++;
}
}
const error = () => {
reject(tx.error || new DOMException('AbortError', 'AbortError'));
unlisten();
};
cursor.continue();
} else {
done(entriesToDelete);
}
};
}); // TODO(philipwalton): once the Safari bug in the following issue is fixed,
// we should be able to remove this loop and do the entry deletion in the
// cursor loop above:
// https://github.com/GoogleChrome/workbox/issues/1978
tx.addEventListener('complete', complete);
tx.addEventListener('error', error);
tx.addEventListener('abort', error);
}); // Cache it for later retrieval.
const urlsDeleted = [];
transactionDoneMap.set(tx, done);
}
for (const entry of entriesToDelete) {
await this._db.delete(OBJECT_STORE_NAME, entry.id);
urlsDeleted.push(entry.url);
let idbProxyTraps = {
get(target, prop, receiver) {
if (target instanceof IDBTransaction) {
// Special handling for transaction.done.
if (prop === 'done') return transactionDoneMap.get(target); // Polyfill for objectStoreNames because of Edge.
if (prop === 'objectStoreNames') {
return target.objectStoreNames || transactionStoreNamesMap.get(target);
} // Make tx.store return the only store in the transaction, or undefined if there are many.
if (prop === 'store') {
return receiver.objectStoreNames[1] ? undefined : receiver.objectStore(receiver.objectStoreNames[0]);
}
} // Else transform whatever we get back.
return urlsDeleted;
}
/**
* Takes a URL and returns an ID that will be unique in the object store.
*
* @param {string} url
* @return {string}
*
* @private
*/
return wrap(target[prop]);
},
_getId(url) {
// Creating an ID from the URL and cache name won't be necessary once
// Edge switches to Chromium and all browsers we support work with
// array keyPaths.
return this._cacheName + '|' + normalizeURL(url);
set(target, prop, value) {
target[prop] = value;
return true;
},
has(target, prop) {
if (target instanceof IDBTransaction && (prop === 'done' || prop === 'store')) {
return true;
}
return prop in target;
}
/*
Copyright 2018 Google LLC
};
Use of this source code is governed by an MIT-style
license that can be found in the LICENSE file or at
https://opensource.org/licenses/MIT.
*/
function replaceTraps(callback) {
idbProxyTraps = callback(idbProxyTraps);
}
function wrapFunction(func) {
// Due to expected object equality (which is enforced by the caching in `wrap`), we
// only create one new func per func.
// Edge doesn't support objectStoreNames (booo), so we polyfill it here.
if (func === IDBDatabase.prototype.transaction && !('objectStoreNames' in IDBTransaction.prototype)) {
return function (storeNames, ...args) {
const tx = func.call(unwrap(this), storeNames, ...args);
transactionStoreNamesMap.set(tx, storeNames.sort ? storeNames.sort() : [storeNames]);
return wrap(tx);
};
} // Cursor methods are special, as the behaviour is a little more different to standard IDB. In
// IDB, you advance the cursor and wait for a new 'success' on the IDBRequest that gave you the
// cursor. It's kinda like a promise that can resolve with many values. That doesn't make sense
// with real promises, so each advance methods returns a new promise for the cursor object, or
// undefined if the end of the cursor has been reached.
if (getCursorAdvanceMethods().includes(func)) {
return function (...args) {
// Calling the original function with the proxy as 'this' causes ILLEGAL INVOCATION, so we use
// the original object.
func.apply(unwrap(this), args);
return wrap(cursorRequestMap.get(this));
};
}
return function (...args) {
// Calling the original function with the proxy as 'this' causes ILLEGAL INVOCATION, so we use
// the original object.
return wrap(func.apply(unwrap(this), args));
};
}
function transformCachableValue(value) {
if (typeof value === 'function') return wrapFunction(value); // This doesn't return, it just creates a 'done' promise for the transaction,
// which is later returned for transaction.done (see idbObjectHandler).
if (value instanceof IDBTransaction) cacheDonePromiseForTransaction(value);
if (instanceOfAny(value, getIdbProxyableTypes())) return new Proxy(value, idbProxyTraps); // Return the same value back if we're not going to transform it.
return value;
}
function wrap(value) {
// We sometimes generate multiple promises from a single IDBRequest (eg when cursoring), because
// IDB is weird and a single IDBRequest can yield many responses, so these can't be cached.
if (value instanceof IDBRequest) return promisifyRequest(value); // If we've already transformed this value before, reuse the transformed value.
// This is faster, but it also provides object equality.
if (transformCache.has(value)) return transformCache.get(value);
const newValue = transformCachableValue(value); // Not all types are transformed.
// These may be primitive types, so they can't be WeakMap keys.
if (newValue !== value) {
transformCache.set(value, newValue);
reverseTransformCache.set(newValue, value);
}
return newValue;
}
const unwrap = value => reverseTransformCache.get(value);
/**
* Open a database.
*
* @param name Name of the database.
* @param version Schema version.
* @param callbacks Additional callbacks.
*/
function openDB(name, version, {
blocked,
upgrade,
blocking,
terminated
} = {}) {
const request = indexedDB.open(name, version);
const openPromise = wrap(request);
if (upgrade) {
request.addEventListener('upgradeneeded', event => {
upgrade(wrap(request.result), event.oldVersion, event.newVersion, wrap(request.transaction));
});
}
if (blocked) request.addEventListener('blocked', () => blocked());
openPromise.then(db => {
if (terminated) db.addEventListener('close', () => terminated());
if (blocking) db.addEventListener('versionchange', () => blocking());
}).catch(() => {});
return openPromise;
}
/**
* Delete a database.
*
* @param name Name of the database.
*/
function deleteDB(name, {
blocked
} = {}) {
const request = indexedDB.deleteDatabase(name);
if (blocked) request.addEventListener('blocked', () => blocked());
return wrap(request).then(() => undefined);
}
const readMethods = ['get', 'getKey', 'getAll', 'getAllKeys', 'count'];
const writeMethods = ['put', 'add', 'delete', 'clear'];
const cachedMethods = new Map();
function getMethod(target, prop) {
if (!(target instanceof IDBDatabase && !(prop in target) && typeof prop === 'string')) {
return;
}
if (cachedMethods.get(prop)) return cachedMethods.get(prop);
const targetFuncName = prop.replace(/FromIndex$/, '');
const useIndex = prop !== targetFuncName;
const isWrite = writeMethods.includes(targetFuncName);
if ( // Bail if the target doesn't exist on the target. Eg, getAll isn't in Edge.
!(targetFuncName in (useIndex ? IDBIndex : IDBObjectStore).prototype) || !(isWrite || readMethods.includes(targetFuncName))) {
return;
}
const method = async function (storeName, ...args) {
// isWrite ? 'readwrite' : undefined gzipps better, but fails in Edge :(
const tx = this.transaction(storeName, isWrite ? 'readwrite' : 'readonly');
let target = tx.store;
if (useIndex) target = target.index(args.shift()); // Must reject if op rejects.
// If it's a write operation, must reject if tx.done rejects.
// Must reject with op rejection first.
// Must resolve with op value.
// Must handle both promises (no unhandled rejections)
return (await Promise.all([target[targetFuncName](...args), isWrite && tx.done]))[0];
};
cachedMethods.set(prop, method);
return method;
}
replaceTraps(oldTraps => _extends({}, oldTraps, {
get: (target, prop, receiver) => getMethod(target, prop) || oldTraps.get(target, prop, receiver),
has: (target, prop) => !!getMethod(target, prop) || oldTraps.has(target, prop)
}));
try {
self['workbox:expiration:6.2.0-alpha.0'] && _();
} catch (e) {}
/*
Copyright 2018 Google LLC
Use of this source code is governed by an MIT-style
license that can be found in the LICENSE file or at
https://opensource.org/licenses/MIT.
*/
const DB_NAME = 'workbox-expiration';
const CACHE_OBJECT_STORE = 'cache-entries';
const normalizeURL = unNormalizedUrl => {
const url = new URL(unNormalizedUrl, location.href);
url.hash = '';
return url.href;
};
/**
* Returns the timestamp model.
*
* @private
*/
class CacheTimestampsModel {
/**
* The `CacheExpiration` class allows you define an expiration and / or
* limit on the number of responses stored in a
* [`Cache`](https://developer.mozilla.org/en-US/docs/Web/API/Cache).
*
* @memberof module:workbox-expiration
* @param {string} cacheName
*
* @private
*/
constructor(cacheName) {
this._db = null;
this._cacheName = cacheName;
}
/**
* Performs an upgrade of indexedDB.
*
* @param {IDBPDatabase<CacheDbSchema>} db
*
* @private
*/
class CacheExpiration {
/**
* To construct a new CacheExpiration instance you must provide at least
* one of the `config` properties.
*
* @param {string} cacheName Name of the cache to apply restrictions to.
* @param {Object} config
* @param {number} [config.maxEntries] The maximum number of entries to cache.
* Entries used the least will be removed as the maximum is reached.
* @param {number} [config.maxAgeSeconds] The maximum age of an entry before
* it's treated as stale and removed.
* @param {Object} [config.matchOptions] The [`CacheQueryOptions`](https://developer.mozilla.org/en-US/docs/Web/API/Cache/delete#Parameters)
* that will be used when calling `delete()` on the cache.
*/
constructor(cacheName, config = {}) {
this._isRunning = false;
this._rerunRequested = false;
{
assert_js.assert.isType(cacheName, 'string', {
moduleName: 'workbox-expiration',
className: 'CacheExpiration',
funcName: 'constructor',
paramName: 'cacheName'
});
_upgradeDb(db) {
// TODO(philipwalton): EdgeHTML doesn't support arrays as a keyPath, so we
// have to use the `id` keyPath here and create our own values (a
// concatenation of `url + cacheName`) instead of simply using
// `keyPath: ['url', 'cacheName']`, which is supported in other browsers.
const objStore = db.createObjectStore(CACHE_OBJECT_STORE, {
keyPath: 'id'
}); // TODO(philipwalton): once we don't have to support EdgeHTML, we can
// create a single index with the keyPath `['cacheName', 'timestamp']`
// instead of doing both these indexes.
if (!(config.maxEntries || config.maxAgeSeconds)) {
throw new WorkboxError_js.WorkboxError('max-entries-or-age-required', {
moduleName: 'workbox-expiration',
className: 'CacheExpiration',
funcName: 'constructor'
});
}
objStore.createIndex('cacheName', 'cacheName', {
unique: false
});
objStore.createIndex('timestamp', 'timestamp', {
unique: false
});
}
/**
* Performs an upgrade of indexedDB and deletes deprecated DBs.
*
* @param {IDBPDatabase<CacheDbSchema>} db
*
* @private
*/
if (config.maxEntries) {
assert_js.assert.isType(config.maxEntries, 'number', {
moduleName: 'workbox-expiration',
className: 'CacheExpiration',
funcName: 'constructor',
paramName: 'config.maxEntries'
});
}
if (config.maxAgeSeconds) {
assert_js.assert.isType(config.maxAgeSeconds, 'number', {
moduleName: 'workbox-expiration',
className: 'CacheExpiration',
funcName: 'constructor',
paramName: 'config.maxAgeSeconds'
});
}
}
_upgradeDbAndDeleteOldDbs(db) {
this._upgradeDb(db);
this._maxEntries = config.maxEntries;
this._maxAgeSeconds = config.maxAgeSeconds;
this._matchOptions = config.matchOptions;
this._cacheName = cacheName;
this._timestampModel = new CacheTimestampsModel(cacheName);
if (this._cacheName) {
void deleteDB(this._cacheName);
}
/**
* Expires entries for the given cache and given criteria.
*/
}
/**
* @param {string} url
* @param {number} timestamp
*
* @private
*/
async expireEntries() {
if (this._isRunning) {
this._rerunRequested = true;
return;
}
async setTimestamp(url, timestamp) {
url = normalizeURL(url);
const entry = {
url,
timestamp,
cacheName: this._cacheName,
// Creating an ID from the URL and cache name won't be necessary once
// Edge switches to Chromium and all browsers we support work with
// array keyPaths.
id: this._getId(url)
};
const db = await this.getDb();
await db.put(CACHE_OBJECT_STORE, entry);
}
/**
* Returns the timestamp stored for a given URL.
*
* @param {string} url
* @return {number | undefined}
*
* @private
*/
this._isRunning = true;
const minTimestamp = this._maxAgeSeconds ? Date.now() - this._maxAgeSeconds * 1000 : 0;
const urlsExpired = await this._timestampModel.expireEntries(minTimestamp, this._maxEntries); // Delete URLs from the cache
const cache = await self.caches.open(this._cacheName);
async getTimestamp(url) {
const db = await this.getDb();
const entry = await db.get(CACHE_OBJECT_STORE, this._getId(url));
return entry == null ? void 0 : entry.timestamp;
}
/**
* Iterates through all the entries in the object store (from newest to
* oldest) and removes entries once either `maxCount` is reached or the
* entry's timestamp is less than `minTimestamp`.
*
* @param {number} minTimestamp
* @param {number} maxCount
* @return {Array<string>}
*
* @private
*/
for (const url of urlsExpired) {
await cache.delete(url, this._matchOptions);
}
{
if (urlsExpired.length > 0) {
logger_js.logger.groupCollapsed(`Expired ${urlsExpired.length} ` + `${urlsExpired.length === 1 ? 'entry' : 'entries'} and removed ` + `${urlsExpired.length === 1 ? 'it' : 'them'} from the ` + `'${this._cacheName}' cache.`);
logger_js.logger.log(`Expired the following ${urlsExpired.length === 1 ? 'URL' : 'URLs'}:`);
urlsExpired.forEach(url => logger_js.logger.log(` ${url}`));
logger_js.logger.groupEnd();
async expireEntries(minTimestamp, maxCount) {
const db = await this.getDb();
let cursor = await db.transaction(CACHE_OBJECT_STORE).store.index('timestamp').openCursor(null, 'prev');
const entriesToDelete = [];
let entriesNotDeletedCount = 0;
while (cursor) {
const result = cursor.value; // TODO(philipwalton): once we can use a multi-key index, we
// won't have to check `cacheName` here.
if (result.cacheName === this._cacheName) {
// Delete an entry if it's older than the max age or
// if we already have the max number allowed.
if (minTimestamp && result.timestamp < minTimestamp || maxCount && entriesNotDeletedCount >= maxCount) {
// TODO(philipwalton): we should be able to delete the
// entry right here, but doing so causes an iteration
// bug in Safari stable (fixed in TP). Instead we can
// store the keys of the entries to delete, and then
// delete the separate transactions.
// https://github.com/GoogleChrome/workbox/issues/1978
// cursor.delete();
// We only need to return the URL, not the whole entry.
entriesToDelete.push(cursor.value);
} else {
logger_js.logger.debug(`Cache expiration ran and found no entries to remove.`);
entriesNotDeletedCount++;
}
}
this._isRunning = false;
cursor = await cursor.continue();
} // TODO(philipwalton): once the Safari bug in the following issue is fixed,
// we should be able to remove this loop and do the entry deletion in the
// cursor loop above:
// https://github.com/GoogleChrome/workbox/issues/1978
if (this._rerunRequested) {
this._rerunRequested = false;
dontWaitFor_js.dontWaitFor(this.expireEntries());
}
const urlsDeleted = [];
for (const entry of entriesToDelete) {
await db.delete(CACHE_OBJECT_STORE, entry.id);
urlsDeleted.push(entry.url);
}
/**
* Update the timestamp for the given URL. This ensures the when
* removing entries based on maximum entries, most recently used
* is accurate or when expiring, the timestamp is up-to-date.
*
* @param {string} url
*/
return urlsDeleted;
}
/**
* Takes a URL and returns an ID that will be unique in the object store.
*
* @param {string} url
* @return {string}
*
* @private
*/
async updateTimestamp(url) {
{
assert_js.assert.isType(url, 'string', {
_getId(url) {
// Creating an ID from the URL and cache name won't be necessary once
// Edge switches to Chromium and all browsers we support work with
// array keyPaths.
return this._cacheName + '|' + normalizeURL(url);
}
/**
* Returns an open connection to the database.
*
* @private
*/
async getDb() {
if (!this._db) {
this._db = await openDB(DB_NAME, 1, {
upgrade: this._upgradeDbAndDeleteOldDbs
});
}
return this._db;
}
}
/*
Copyright 2018 Google LLC
Use of this source code is governed by an MIT-style
license that can be found in the LICENSE file or at
https://opensource.org/licenses/MIT.
*/
/**
* The `CacheExpiration` class allows you define an expiration and / or
* limit on the number of responses stored in a
* [`Cache`](https://developer.mozilla.org/en-US/docs/Web/API/Cache).
*
* @memberof module:workbox-expiration
*/
class CacheExpiration {
/**
* To construct a new CacheExpiration instance you must provide at least
* one of the `config` properties.
*
* @param {string} cacheName Name of the cache to apply restrictions to.
* @param {Object} config
* @param {number} [config.maxEntries] The maximum number of entries to cache.
* Entries used the least will be removed as the maximum is reached.
* @param {number} [config.maxAgeSeconds] The maximum age of an entry before
* it's treated as stale and removed.
* @param {Object} [config.matchOptions] The [`CacheQueryOptions`](https://developer.mozilla.org/en-US/docs/Web/API/Cache/delete#Parameters)
* that will be used when calling `delete()` on the cache.
*/
constructor(cacheName, config = {}) {
this._isRunning = false;
this._rerunRequested = false;
{
assert_js.assert.isType(cacheName, 'string', {
moduleName: 'workbox-expiration',
className: 'CacheExpiration',
funcName: 'constructor',
paramName: 'cacheName'
});
if (!(config.maxEntries || config.maxAgeSeconds)) {
throw new WorkboxError_js.WorkboxError('max-entries-or-age-required', {
moduleName: 'workbox-expiration',
className: 'CacheExpiration',
funcName: 'updateTimestamp',
paramName: 'url'
funcName: 'constructor'
});
}
await this._timestampModel.setTimestamp(url, Date.now());
if (config.maxEntries) {
assert_js.assert.isType(config.maxEntries, 'number', {
moduleName: 'workbox-expiration',
className: 'CacheExpiration',
funcName: 'constructor',
paramName: 'config.maxEntries'
});
}
if (config.maxAgeSeconds) {
assert_js.assert.isType(config.maxAgeSeconds, 'number', {
moduleName: 'workbox-expiration',
className: 'CacheExpiration',
funcName: 'constructor',
paramName: 'config.maxAgeSeconds'
});
}
}
/**
* Can be used to check if a URL has expired or not before it's used.
*
* This requires a look up from IndexedDB, so can be slow.
*
* Note: This method will not remove the cached entry, call
* `expireEntries()` to remove indexedDB and Cache entries.
*
* @param {string} url
* @return {boolean}
*/
this._maxEntries = config.maxEntries;
this._maxAgeSeconds = config.maxAgeSeconds;
this._matchOptions = config.matchOptions;
this._cacheName = cacheName;
this._timestampModel = new CacheTimestampsModel(cacheName);
}
/**
* Expires entries for the given cache and given criteria.
*/
async isURLExpired(url) {
if (!this._maxAgeSeconds) {
{
throw new WorkboxError_js.WorkboxError(`expired-test-without-max-age`, {
methodName: 'isURLExpired',
paramName: 'maxAgeSeconds'
});
}
async expireEntries() {
if (this._isRunning) {
this._rerunRequested = true;
return;
}
this._isRunning = true;
const minTimestamp = this._maxAgeSeconds ? Date.now() - this._maxAgeSeconds * 1000 : 0;
const urlsExpired = await this._timestampModel.expireEntries(minTimestamp, this._maxEntries); // Delete URLs from the cache
const cache = await self.caches.open(this._cacheName);
for (const url of urlsExpired) {
await cache.delete(url, this._matchOptions);
}
{
if (urlsExpired.length > 0) {
logger_js.logger.groupCollapsed(`Expired ${urlsExpired.length} ` + `${urlsExpired.length === 1 ? 'entry' : 'entries'} and removed ` + `${urlsExpired.length === 1 ? 'it' : 'them'} from the ` + `'${this._cacheName}' cache.`);
logger_js.logger.log(`Expired the following ${urlsExpired.length === 1 ? 'URL' : 'URLs'}:`);
urlsExpired.forEach(url => logger_js.logger.log(` ${url}`));
logger_js.logger.groupEnd();
} else {
const timestamp = await this._timestampModel.getTimestamp(url);
const expireOlderThan = Date.now() - this._maxAgeSeconds * 1000;
return timestamp < expireOlderThan;
logger_js.logger.debug(`Cache expiration ran and found no entries to remove.`);
}
}
/**
* Removes the IndexedDB object store used to keep track of cache expiration
* metadata.
*/
this._isRunning = false;
async delete() {
// Make sure we don't attempt another rerun if we're called in the middle of
// a cache expiration.
if (this._rerunRequested) {
this._rerunRequested = false;
await this._timestampModel.expireEntries(Infinity); // Expires all.
dontWaitFor_js.dontWaitFor(this.expireEntries());
}
}
/**
* Update the timestamp for the given URL. This ensures the when
* removing entries based on maximum entries, most recently used
* is accurate or when expiring, the timestamp is up-to-date.
*
* @param {string} url
*/
/*
Copyright 2018 Google LLC
Use of this source code is governed by an MIT-style
license that can be found in the LICENSE file or at
https://opensource.org/licenses/MIT.
*/
async updateTimestamp(url) {
{
assert_js.assert.isType(url, 'string', {
moduleName: 'workbox-expiration',
className: 'CacheExpiration',
funcName: 'updateTimestamp',
paramName: 'url'
});
}
await this._timestampModel.setTimestamp(url, Date.now());
}
/**
* This plugin can be used in a `workbox-strategy` to regularly enforce a
* limit on the age and / or the number of cached requests.
* Can be used to check if a URL has expired or not before it's used.
*
* It can only be used with `workbox-strategy` instances that have a
* [custom `cacheName` property set](/web/tools/workbox/guides/configure-workbox#custom_cache_names_in_strategies).
* In other words, it can't be used to expire entries in strategy that uses the
* default runtime cache name.
* This requires a look up from IndexedDB, so can be slow.
*
* Whenever a cached request is used or updated, this plugin will look
* at the associated cache and remove any old or extra requests.
* Note: This method will not remove the cached entry, call
* `expireEntries()` to remove indexedDB and Cache entries.
*
* When using `maxAgeSeconds`, requests may be used *once* after expiring
* because the expiration clean up will not have occurred until *after* the
* cached request has been used. If the request has a "Date" header, then
* a light weight expiration check is performed and the request will not be
* used immediately.
*
* When using `maxEntries`, the entry least-recently requested will be removed
* from the cache first.
*
* @memberof module:workbox-expiration
* @param {string} url
* @return {boolean}
*/
class ExpirationPlugin {
/**
* @param {Object} config
* @param {number} [config.maxEntries] The maximum number of entries to cache.
* Entries used the least will be removed as the maximum is reached.
* @param {number} [config.maxAgeSeconds] The maximum age of an entry before
* it's treated as stale and removed.
* @param {Object} [config.matchOptions] The [`CacheQueryOptions`](https://developer.mozilla.org/en-US/docs/Web/API/Cache/delete#Parameters)
* that will be used when calling `delete()` on the cache.
* @param {boolean} [config.purgeOnQuotaError] Whether to opt this cache in to
* automatic deletion if the available storage quota has been exceeded.
*/
constructor(config = {}) {
/**
* A "lifecycle" callback that will be triggered automatically by the
* `workbox-strategies` handlers when a `Response` is about to be returned
* from a [Cache](https://developer.mozilla.org/en-US/docs/Web/API/Cache) to
* the handler. It allows the `Response` to be inspected for freshness and
* prevents it from being used if the `Response`'s `Date` header value is
* older than the configured `maxAgeSeconds`.
*
* @param {Object} options
* @param {string} options.cacheName Name of the cache the response is in.
* @param {Response} options.cachedResponse The `Response` object that's been
* read from a cache and whose freshness should be checked.
* @return {Response} Either the `cachedResponse`, if it's
* fresh, or `null` if the `Response` is older than `maxAgeSeconds`.
*
* @private
*/
this.cachedResponseWillBeUsed = async ({
event,
request,
cacheName,
cachedResponse
}) => {
if (!cachedResponse) {
return null;
}
const isFresh = this._isResponseDateFresh(cachedResponse); // Expire entries to ensure that even if the expiration date has
// expired, it'll only be used once.
async isURLExpired(url) {
if (!this._maxAgeSeconds) {
{
throw new WorkboxError_js.WorkboxError(`expired-test-without-max-age`, {
methodName: 'isURLExpired',
paramName: 'maxAgeSeconds'
});
}
} else {
const timestamp = await this._timestampModel.getTimestamp(url);
const expireOlderThan = Date.now() - this._maxAgeSeconds * 1000;
return timestamp !== undefined ? timestamp < expireOlderThan : true;
}
}
/**
* Removes the IndexedDB object store used to keep track of cache expiration
* metadata.
*/
const cacheExpiration = this._getCacheExpiration(cacheName);
async delete() {
// Make sure we don't attempt another rerun if we're called in the middle of
// a cache expiration.
this._rerunRequested = false;
await this._timestampModel.expireEntries(Infinity); // Expires all.
}
dontWaitFor_js.dontWaitFor(cacheExpiration.expireEntries()); // Update the metadata for the request URL to the current timestamp,
// but don't `await` it as we don't want to block the response.
}
const updateTimestampDone = cacheExpiration.updateTimestamp(request.url);
/*
Copyright 2018 Google LLC
if (event) {
try {
event.waitUntil(updateTimestampDone);
} catch (error) {
{
// The event may not be a fetch event; only log the URL if it is.
if ('request' in event) {
logger_js.logger.warn(`Unable to ensure service worker stays alive when ` + `updating cache entry for ` + `'${getFriendlyURL_js.getFriendlyURL(event.request.url)}'.`);
}
}
}
}
Use of this source code is governed by an MIT-style
license that can be found in the LICENSE file or at
https://opensource.org/licenses/MIT.
*/
/**
* This plugin can be used in a `workbox-strategy` to regularly enforce a
* limit on the age and / or the number of cached requests.
*
* It can only be used with `workbox-strategy` instances that have a
* [custom `cacheName` property set](/web/tools/workbox/guides/configure-workbox#custom_cache_names_in_strategies).
* In other words, it can't be used to expire entries in strategy that uses the
* default runtime cache name.
*
* Whenever a cached request is used or updated, this plugin will look
* at the associated cache and remove any old or extra requests.
*
* When using `maxAgeSeconds`, requests may be used *once* after expiring
* because the expiration clean up will not have occurred until *after* the
* cached request has been used. If the request has a "Date" header, then
* a light weight expiration check is performed and the request will not be
* used immediately.
*
* When using `maxEntries`, the entry least-recently requested will be removed
* from the cache first.
*
* @memberof module:workbox-expiration
*/
return isFresh ? cachedResponse : null;
};
/**
* A "lifecycle" callback that will be triggered automatically by the
* `workbox-strategies` handlers when an entry is added to a cache.
*
* @param {Object} options
* @param {string} options.cacheName Name of the cache that was updated.
* @param {string} options.request The Request for the cached entry.
*
* @private
*/
class ExpirationPlugin {
/**
* @param {ExpirationPluginOptions} config
* @param {number} [config.maxEntries] The maximum number of entries to cache.
* Entries used the least will be removed as the maximum is reached.
* @param {number} [config.maxAgeSeconds] The maximum age of an entry before
* it's treated as stale and removed.
* @param {Object} [config.matchOptions] The [`CacheQueryOptions`](https://developer.mozilla.org/en-US/docs/Web/API/Cache/delete#Parameters)
* that will be used when calling `delete()` on the cache.
* @param {boolean} [config.purgeOnQuotaError] Whether to opt this cache in to
* automatic deletion if the available storage quota has been exceeded.
*/
constructor(config = {}) {
/**
* A "lifecycle" callback that will be triggered automatically by the
* `workbox-strategies` handlers when a `Response` is about to be returned
* from a [Cache](https://developer.mozilla.org/en-US/docs/Web/API/Cache) to
* the handler. It allows the `Response` to be inspected for freshness and
* prevents it from being used if the `Response`'s `Date` header value is
* older than the configured `maxAgeSeconds`.
*
* @param {Object} options
* @param {string} options.cacheName Name of the cache the response is in.
* @param {Response} options.cachedResponse The `Response` object that's been
* read from a cache and whose freshness should be checked.
* @return {Response} Either the `cachedResponse`, if it's
* fresh, or `null` if the `Response` is older than `maxAgeSeconds`.
*
* @private
*/
this.cachedResponseWillBeUsed = async ({
event,
request,
cacheName,
cachedResponse
}) => {
if (!cachedResponse) {
return null;
}
const isFresh = this._isResponseDateFresh(cachedResponse); // Expire entries to ensure that even if the expiration date has
// expired, it'll only be used once.
this.cacheDidUpdate = async ({
cacheName,
request
}) => {
{
assert_js.assert.isType(cacheName, 'string', {
moduleName: 'workbox-expiration',
className: 'Plugin',
funcName: 'cacheDidUpdate',
paramName: 'cacheName'
});
assert_js.assert.isInstance(request, Request, {
moduleName: 'workbox-expiration',
className: 'Plugin',
funcName: 'cacheDidUpdate',
paramName: 'request'
});
}
const cacheExpiration = this._getCacheExpiration(cacheName);
const cacheExpiration = this._getCacheExpiration(cacheName);
await cacheExpiration.updateTimestamp(request.url);
await cacheExpiration.expireEntries();
};
dontWaitFor_js.dontWaitFor(cacheExpiration.expireEntries()); // Update the metadata for the request URL to the current timestamp,
// but don't `await` it as we don't want to block the response.
{
if (!(config.maxEntries || config.maxAgeSeconds)) {
throw new WorkboxError_js.WorkboxError('max-entries-or-age-required', {
moduleName: 'workbox-expiration',
className: 'Plugin',
funcName: 'constructor'
});
}
const updateTimestampDone = cacheExpiration.updateTimestamp(request.url);
if (config.maxEntries) {
assert_js.assert.isType(config.maxEntries, 'number', {
moduleName: 'workbox-expiration',
className: 'Plugin',
funcName: 'constructor',
paramName: 'config.maxEntries'
});
if (event) {
try {
event.waitUntil(updateTimestampDone);
} catch (error) {
{
// The event may not be a fetch event; only log the URL if it is.
if ('request' in event) {
logger_js.logger.warn(`Unable to ensure service worker stays alive when ` + `updating cache entry for ` + `'${getFriendlyURL_js.getFriendlyURL(event.request.url)}'.`);
}
}
}
if (config.maxAgeSeconds) {
assert_js.assert.isType(config.maxAgeSeconds, 'number', {
moduleName: 'workbox-expiration',
className: 'Plugin',
funcName: 'constructor',
paramName: 'config.maxAgeSeconds'
});
}
}
this._config = config;
this._maxAgeSeconds = config.maxAgeSeconds;
this._cacheExpirations = new Map();
if (config.purgeOnQuotaError) {
registerQuotaErrorCallback_js.registerQuotaErrorCallback(() => this.deleteCacheAndMetadata());
}
}
return isFresh ? cachedResponse : null;
};
/**
* A simple helper method to return a CacheExpiration instance for a given
* cache name.
* A "lifecycle" callback that will be triggered automatically by the
* `workbox-strategies` handlers when an entry is added to a cache.
*
* @param {string} cacheName
* @return {CacheExpiration}
* @param {Object} options
* @param {string} options.cacheName Name of the cache that was updated.
* @param {string} options.request The Request for the cached entry.
*

@@ -550,111 +787,183 @@ * @private

_getCacheExpiration(cacheName) {
if (cacheName === cacheNames_js.cacheNames.getRuntimeName()) {
throw new WorkboxError_js.WorkboxError('expire-custom-caches-only');
this.cacheDidUpdate = async ({
cacheName,
request
}) => {
{
assert_js.assert.isType(cacheName, 'string', {
moduleName: 'workbox-expiration',
className: 'Plugin',
funcName: 'cacheDidUpdate',
paramName: 'cacheName'
});
assert_js.assert.isInstance(request, Request, {
moduleName: 'workbox-expiration',
className: 'Plugin',
funcName: 'cacheDidUpdate',
paramName: 'request'
});
}
let cacheExpiration = this._cacheExpirations.get(cacheName);
const cacheExpiration = this._getCacheExpiration(cacheName);
if (!cacheExpiration) {
cacheExpiration = new CacheExpiration(cacheName, this._config);
await cacheExpiration.updateTimestamp(request.url);
await cacheExpiration.expireEntries();
};
this._cacheExpirations.set(cacheName, cacheExpiration);
{
if (!(config.maxEntries || config.maxAgeSeconds)) {
throw new WorkboxError_js.WorkboxError('max-entries-or-age-required', {
moduleName: 'workbox-expiration',
className: 'Plugin',
funcName: 'constructor'
});
}
return cacheExpiration;
if (config.maxEntries) {
assert_js.assert.isType(config.maxEntries, 'number', {
moduleName: 'workbox-expiration',
className: 'Plugin',
funcName: 'constructor',
paramName: 'config.maxEntries'
});
}
if (config.maxAgeSeconds) {
assert_js.assert.isType(config.maxAgeSeconds, 'number', {
moduleName: 'workbox-expiration',
className: 'Plugin',
funcName: 'constructor',
paramName: 'config.maxAgeSeconds'
});
}
}
/**
* @param {Response} cachedResponse
* @return {boolean}
*
* @private
*/
this._config = config;
this._maxAgeSeconds = config.maxAgeSeconds;
this._cacheExpirations = new Map();
_isResponseDateFresh(cachedResponse) {
if (!this._maxAgeSeconds) {
// We aren't expiring by age, so return true, it's fresh
return true;
} // Check if the 'date' header will suffice a quick expiration check.
// See https://github.com/GoogleChromeLabs/sw-toolbox/issues/164 for
// discussion.
if (config.purgeOnQuotaError) {
registerQuotaErrorCallback_js.registerQuotaErrorCallback(() => this.deleteCacheAndMetadata());
}
}
/**
* A simple helper method to return a CacheExpiration instance for a given
* cache name.
*
* @param {string} cacheName
* @return {CacheExpiration}
*
* @private
*/
const dateHeaderTimestamp = this._getDateHeaderTimestamp(cachedResponse);
_getCacheExpiration(cacheName) {
if (cacheName === cacheNames_js.cacheNames.getRuntimeName()) {
throw new WorkboxError_js.WorkboxError('expire-custom-caches-only');
}
if (dateHeaderTimestamp === null) {
// Unable to parse date, so assume it's fresh.
return true;
} // If we have a valid headerTime, then our response is fresh iff the
// headerTime plus maxAgeSeconds is greater than the current time.
let cacheExpiration = this._cacheExpirations.get(cacheName);
if (!cacheExpiration) {
cacheExpiration = new CacheExpiration(cacheName, this._config);
const now = Date.now();
return dateHeaderTimestamp >= now - this._maxAgeSeconds * 1000;
this._cacheExpirations.set(cacheName, cacheExpiration);
}
/**
* This method will extract the data header and parse it into a useful
* value.
*
* @param {Response} cachedResponse
* @return {number|null}
*
* @private
*/
return cacheExpiration;
}
/**
* @param {Response} cachedResponse
* @return {boolean}
*
* @private
*/
_getDateHeaderTimestamp(cachedResponse) {
if (!cachedResponse.headers.has('date')) {
return null;
}
const dateHeader = cachedResponse.headers.get('date');
const parsedDate = new Date(dateHeader);
const headerTime = parsedDate.getTime(); // If the Date header was invalid for some reason, parsedDate.getTime()
// will return NaN.
_isResponseDateFresh(cachedResponse) {
if (!this._maxAgeSeconds) {
// We aren't expiring by age, so return true, it's fresh
return true;
} // Check if the 'date' header will suffice a quick expiration check.
// See https://github.com/GoogleChromeLabs/sw-toolbox/issues/164 for
// discussion.
if (isNaN(headerTime)) {
return null;
}
return headerTime;
}
/**
* This is a helper method that performs two operations:
*
* - Deletes *all* the underlying Cache instances associated with this plugin
* instance, by calling caches.delete() on your behalf.
* - Deletes the metadata from IndexedDB used to keep track of expiration
* details for each Cache instance.
*
* When using cache expiration, calling this method is preferable to calling
* `caches.delete()` directly, since this will ensure that the IndexedDB
* metadata is also cleanly removed and open IndexedDB instances are deleted.
*
* Note that if you're *not* using cache expiration for a given cache, calling
* `caches.delete()` and passing in the cache's name should be sufficient.
* There is no Workbox-specific method needed for cleanup in that case.
*/
const dateHeaderTimestamp = this._getDateHeaderTimestamp(cachedResponse);
if (dateHeaderTimestamp === null) {
// Unable to parse date, so assume it's fresh.
return true;
} // If we have a valid headerTime, then our response is fresh iff the
// headerTime plus maxAgeSeconds is greater than the current time.
async deleteCacheAndMetadata() {
// Do this one at a time instead of all at once via `Promise.all()` to
// reduce the chance of inconsistency if a promise rejects.
for (const [cacheName, cacheExpiration] of this._cacheExpirations) {
await self.caches.delete(cacheName);
await cacheExpiration.delete();
} // Reset this._cacheExpirations to its initial state.
const now = Date.now();
return dateHeaderTimestamp >= now - this._maxAgeSeconds * 1000;
}
/**
* This method will extract the data header and parse it into a useful
* value.
*
* @param {Response} cachedResponse
* @return {number|null}
*
* @private
*/
this._cacheExpirations = new Map();
_getDateHeaderTimestamp(cachedResponse) {
if (!cachedResponse.headers.has('date')) {
return null;
}
const dateHeader = cachedResponse.headers.get('date');
const parsedDate = new Date(dateHeader);
const headerTime = parsedDate.getTime(); // If the Date header was invalid for some reason, parsedDate.getTime()
// will return NaN.
if (isNaN(headerTime)) {
return null;
}
return headerTime;
}
/**
* This is a helper method that performs two operations:
*
* - Deletes *all* the underlying Cache instances associated with this plugin
* instance, by calling caches.delete() on your behalf.
* - Deletes the metadata from IndexedDB used to keep track of expiration
* details for each Cache instance.
*
* When using cache expiration, calling this method is preferable to calling
* `caches.delete()` directly, since this will ensure that the IndexedDB
* metadata is also cleanly removed and open IndexedDB instances are deleted.
*
* Note that if you're *not* using cache expiration for a given cache, calling
* `caches.delete()` and passing in the cache's name should be sufficient.
* There is no Workbox-specific method needed for cleanup in that case.
*/
exports.CacheExpiration = CacheExpiration;
exports.ExpirationPlugin = ExpirationPlugin;
return exports;
async deleteCacheAndMetadata() {
// Do this one at a time instead of all at once via `Promise.all()` to
// reduce the chance of inconsistency if a promise rejects.
for (const [cacheName, cacheExpiration] of this._cacheExpirations) {
await self.caches.delete(cacheName);
await cacheExpiration.delete();
} // Reset this._cacheExpirations to its initial state.
}({}, workbox.core._private, workbox.core._private, workbox.core._private, workbox.core._private, workbox.core._private, workbox.core._private, workbox.core._private, workbox.core._private, workbox.core));
this._cacheExpirations = new Map();
}
}
exports.CacheExpiration = CacheExpiration;
exports.ExpirationPlugin = ExpirationPlugin;
return exports;
}({}, workbox.core._private, workbox.core._private, workbox.core._private, workbox.core._private, workbox.core._private, workbox.core._private, workbox.core));
//# sourceMappingURL=workbox-expiration.dev.js.map

@@ -1,2 +0,2 @@

this.workbox=this.workbox||{},this.workbox.expiration=function(t,s,e,i,a,n,h){"use strict";try{self["workbox:expiration:6.1.5"]&&_()}catch(t){}const r="cache-entries",c=t=>{const s=new URL(t,location.href);return s.hash="",s.href};class o{constructor(t){this.T=t,this.i=new e.DBWrapper("workbox-expiration",1,{onupgradeneeded:t=>this.M(t)})}M(t){const s=t.target.result.createObjectStore(r,{keyPath:"id"});s.createIndex("cacheName","cacheName",{unique:!1}),s.createIndex("timestamp","timestamp",{unique:!1}),i.deleteDatabase(this.T)}async setTimestamp(t,s){const e={url:t=c(t),timestamp:s,cacheName:this.T,id:this.F(t)};await this.i.put(r,e)}async getTimestamp(t){return(await this.i.get(r,this.F(t))).timestamp}async expireEntries(t,s){const e=await this.i.transaction(r,"readwrite",((e,i)=>{const a=e.objectStore(r).index("timestamp").openCursor(null,"prev"),n=[];let h=0;a.onsuccess=()=>{const e=a.result;if(e){const i=e.value;i.cacheName===this.T&&(t&&i.timestamp<t||s&&h>=s?n.push(e.value):h++),e.continue()}else i(n)}})),i=[];for(const t of e)await this.i.delete(r,t.id),i.push(t.url);return i}F(t){return this.T+"|"+c(t)}}class u{constructor(t,s={}){this.H=!1,this.I=!1,this.G=s.maxEntries,this.J=s.maxAgeSeconds,this.V=s.matchOptions,this.T=t,this.W=new o(t)}async expireEntries(){if(this.H)return void(this.I=!0);this.H=!0;const t=this.J?Date.now()-1e3*this.J:0,e=await this.W.expireEntries(t,this.G),i=await self.caches.open(this.T);for(const t of e)await i.delete(t,this.V);this.H=!1,this.I&&(this.I=!1,s.dontWaitFor(this.expireEntries()))}async updateTimestamp(t){await this.W.setTimestamp(t,Date.now())}async isURLExpired(t){if(this.J){return await this.W.getTimestamp(t)<Date.now()-1e3*this.J}return!1}async delete(){this.I=!1,await this.W.expireEntries(1/0)}}return t.CacheExpiration=u,t.ExpirationPlugin=class{constructor(t={}){this.cachedResponseWillBeUsed=async({event:t,request:e,cacheName:i,cachedResponse:a})=>{if(!a)return null;const n=this.X(a),h=this.Y(i);s.dontWaitFor(h.expireEntries());const r=h.updateTimestamp(e.url);if(t)try{t.waitUntil(r)}catch(t){}return n?a:null},this.cacheDidUpdate=async({cacheName:t,request:s})=>{const e=this.Y(t);await e.updateTimestamp(s.url),await e.expireEntries()},this.Z=t,this.J=t.maxAgeSeconds,this.$=new Map,t.purgeOnQuotaError&&n.registerQuotaErrorCallback((()=>this.deleteCacheAndMetadata()))}Y(t){if(t===a.cacheNames.getRuntimeName())throw new h.WorkboxError("expire-custom-caches-only");let s=this.$.get(t);return s||(s=new u(t,this.Z),this.$.set(t,s)),s}X(t){if(!this.J)return!0;const s=this.tt(t);if(null===s)return!0;return s>=Date.now()-1e3*this.J}tt(t){if(!t.headers.has("date"))return null;const s=t.headers.get("date"),e=new Date(s).getTime();return isNaN(e)?null:e}async deleteCacheAndMetadata(){for(const[t,s]of this.$)await self.caches.delete(t),await s.delete();this.$=new Map}},t}({},workbox.core._private,workbox.core._private,workbox.core._private,workbox.core._private,workbox.core,workbox.core._private);
this.workbox=this.workbox||{},this.workbox.expiration=function(t,e,n,s,i){"use strict";function r(){return(r=Object.assign||function(t){for(var e=1;e<arguments.length;e++){var n=arguments[e];for(var s in n)Object.prototype.hasOwnProperty.call(n,s)&&(t[s]=n[s])}return t}).apply(this,arguments)}let a,o;const c=new WeakMap,u=new WeakMap,h=new WeakMap,f=new WeakMap,l=new WeakMap;let d={get(t,e,n){if(t instanceof IDBTransaction){if("done"===e)return u.get(t);if("objectStoreNames"===e)return t.objectStoreNames||h.get(t);if("store"===e)return n.objectStoreNames[1]?void 0:n.objectStore(n.objectStoreNames[0])}return D(t[e])},set:(t,e,n)=>(t[e]=n,!0),has:(t,e)=>t instanceof IDBTransaction&&("done"===e||"store"===e)||e in t};function w(t){return t!==IDBDatabase.prototype.transaction||"objectStoreNames"in IDBTransaction.prototype?(o||(o=[IDBCursor.prototype.advance,IDBCursor.prototype.continue,IDBCursor.prototype.continuePrimaryKey])).includes(t)?function(...e){return t.apply(m(this),e),D(c.get(this))}:function(...e){return D(t.apply(m(this),e))}:function(e,...n){const s=t.call(m(this),e,...n);return h.set(s,e.sort?e.sort():[e]),D(s)}}function p(t){return"function"==typeof t?w(t):(t instanceof IDBTransaction&&function(t){if(u.has(t))return;const e=new Promise(((e,n)=>{const s=()=>{t.removeEventListener("complete",i),t.removeEventListener("error",r),t.removeEventListener("abort",r)},i=()=>{e(),s()},r=()=>{n(t.error||new DOMException("AbortError","AbortError")),s()};t.addEventListener("complete",i),t.addEventListener("error",r),t.addEventListener("abort",r)}));u.set(t,e)}(t),e=t,(a||(a=[IDBDatabase,IDBObjectStore,IDBIndex,IDBCursor,IDBTransaction])).some((t=>e instanceof t))?new Proxy(t,d):t);var e}function D(t){if(t instanceof IDBRequest)return function(t){const e=new Promise(((e,n)=>{const s=()=>{t.removeEventListener("success",i),t.removeEventListener("error",r)},i=()=>{e(D(t.result)),s()},r=()=>{n(t.error),s()};t.addEventListener("success",i),t.addEventListener("error",r)}));return e.then((e=>{e instanceof IDBCursor&&c.set(e,t)})).catch((()=>{})),l.set(e,t),e}(t);if(f.has(t))return f.get(t);const e=p(t);return e!==t&&(f.set(t,e),l.set(e,t)),e}const m=t=>l.get(t);const b=["get","getKey","getAll","getAllKeys","count"],y=["put","add","delete","clear"],I=new Map;function B(t,e){if(!(t instanceof IDBDatabase)||e in t||"string"!=typeof e)return;if(I.get(e))return I.get(e);const n=e.replace(/FromIndex$/,""),s=e!==n,i=y.includes(n);if(!(n in(s?IDBIndex:IDBObjectStore).prototype)||!i&&!b.includes(n))return;const r=async function(t,...e){const r=this.transaction(t,i?"readwrite":"readonly");let a=r.store;return s&&(a=a.index(e.shift())),(await Promise.all([a[n](...e),i&&r.done]))[0]};return I.set(e,r),r}d=(t=>r({},t,{get:(e,n,s)=>B(e,n)||t.get(e,n,s),has:(e,n)=>!!B(e,n)||t.has(e,n)}))(d);try{self["workbox:expiration:6.2.0-alpha.0"]&&_()}catch(t){}const g="cache-entries",x=t=>{const e=new URL(t,location.href);return e.hash="",e.href};class k{constructor(t){this.t=null,this.M=t}i(t){const e=t.createObjectStore(g,{keyPath:"id"});e.createIndex("cacheName","cacheName",{unique:!1}),e.createIndex("timestamp","timestamp",{unique:!1})}N(t){this.i(t),this.M&&function(t,{blocked:e}={}){const n=indexedDB.deleteDatabase(t);e&&n.addEventListener("blocked",(()=>e())),D(n).then((()=>{}))}(this.M)}async setTimestamp(t,e){const n={url:t=x(t),timestamp:e,cacheName:this.M,id:this.T(t)},s=await this.getDb();await s.put(g,n)}async getTimestamp(t){const e=await this.getDb(),n=await e.get(g,this.T(t));return null==n?void 0:n.timestamp}async expireEntries(t,e){const n=await this.getDb();let s=await n.transaction(g).store.index("timestamp").openCursor(null,"prev");const i=[];let r=0;for(;s;){const n=s.value;n.cacheName===this.M&&(t&&n.timestamp<t||e&&r>=e?i.push(s.value):r++),s=await s.continue()}const a=[];for(const t of i)await n.delete(g,t.id),a.push(t.url);return a}T(t){return this.M+"|"+x(t)}async getDb(){return this.t||(this.t=await function(t,e,{blocked:n,upgrade:s,blocking:i,terminated:r}={}){const a=indexedDB.open(t,e),o=D(a);return s&&a.addEventListener("upgradeneeded",(t=>{s(D(a.result),t.oldVersion,t.newVersion,D(a.transaction))})),n&&a.addEventListener("blocked",(()=>n())),o.then((t=>{r&&t.addEventListener("close",(()=>r())),i&&t.addEventListener("versionchange",(()=>i()))})).catch((()=>{})),o}("workbox-expiration",1,{upgrade:this.N})),this.t}}class v{constructor(t,e={}){this.P=!1,this.W=!1,this.S=e.maxEntries,this.K=e.maxAgeSeconds,this.L=e.matchOptions,this.M=t,this.H=new k(t)}async expireEntries(){if(this.P)return void(this.W=!0);this.P=!0;const t=this.K?Date.now()-1e3*this.K:0,n=await this.H.expireEntries(t,this.S),s=await self.caches.open(this.M);for(const t of n)await s.delete(t,this.L);this.P=!1,this.W&&(this.W=!1,e.dontWaitFor(this.expireEntries()))}async updateTimestamp(t){await this.H.setTimestamp(t,Date.now())}async isURLExpired(t){if(this.K){const e=await this.H.getTimestamp(t),n=Date.now()-1e3*this.K;return void 0===e||e<n}return!1}async delete(){this.W=!1,await this.H.expireEntries(1/0)}}return t.CacheExpiration=v,t.ExpirationPlugin=class{constructor(t={}){this.cachedResponseWillBeUsed=async({event:t,request:n,cacheName:s,cachedResponse:i})=>{if(!i)return null;const r=this.$(i),a=this.G(s);e.dontWaitFor(a.expireEntries());const o=a.updateTimestamp(n.url);if(t)try{t.waitUntil(o)}catch(t){}return r?i:null},this.cacheDidUpdate=async({cacheName:t,request:e})=>{const n=this.G(t);await n.updateTimestamp(e.url),await n.expireEntries()},this.J=t,this.K=t.maxAgeSeconds,this.V=new Map,t.purgeOnQuotaError&&s.registerQuotaErrorCallback((()=>this.deleteCacheAndMetadata()))}G(t){if(t===n.cacheNames.getRuntimeName())throw new i.WorkboxError("expire-custom-caches-only");let e=this.V.get(t);return e||(e=new v(t,this.J),this.V.set(t,e)),e}$(t){if(!this.K)return!0;const e=this.X(t);if(null===e)return!0;return e>=Date.now()-1e3*this.K}X(t){if(!t.headers.has("date"))return null;const e=t.headers.get("date"),n=new Date(e).getTime();return isNaN(n)?null:n}async deleteCacheAndMetadata(){for(const[t,e]of this.V)await self.caches.delete(t),await e.delete();this.V=new Map}},t}({},workbox.core._private,workbox.core._private,workbox.core,workbox.core._private);
//# sourceMappingURL=workbox-expiration.prod.js.map

@@ -155,3 +155,3 @@ /*

const expireOlderThan = Date.now() - (this._maxAgeSeconds * 1000);
return (timestamp < expireOlderThan);
return timestamp !== undefined ? (timestamp < expireOlderThan) : true;
}

@@ -158,0 +158,0 @@ }

import { WorkboxPlugin } from 'workbox-core/types.js';
import './_version.js';
export interface ExpirationPluginOptions {
maxEntries?: number;
maxAgeSeconds?: number;
matchOptions?: CacheQueryOptions;
purgeOnQuotaError?: boolean;
}
/**

@@ -31,3 +37,3 @@ * This plugin can be used in a `workbox-strategy` to regularly enforce a

/**
* @param {Object} config
* @param {ExpirationPluginOptions} config
* @param {number} [config.maxEntries] The maximum number of entries to cache.

@@ -42,8 +48,3 @@ * Entries used the least will be removed as the maximum is reached.

*/
constructor(config?: {
maxEntries?: number;
maxAgeSeconds?: number;
matchOptions?: CacheQueryOptions;
purgeOnQuotaError?: boolean;
});
constructor(config?: ExpirationPluginOptions);
/**

@@ -50,0 +51,0 @@ * A simple helper method to return a CacheExpiration instance for a given

@@ -42,3 +42,3 @@ /*

/**
* @param {Object} config
* @param {ExpirationPluginOptions} config
* @param {number} [config.maxEntries] The maximum number of entries to cache.

@@ -45,0 +45,0 @@ * Entries used the least will be removed as the maximum is reached.

@@ -9,3 +9,3 @@ import '../_version.js';

private readonly _cacheName;
private readonly _db;
private _db;
/**

@@ -19,10 +19,18 @@ *

/**
* Should perform an upgrade of indexedDB.
* Performs an upgrade of indexedDB.
*
* @param {Event} event
* @param {IDBPDatabase<CacheDbSchema>} db
*
* @private
*/
private _handleUpgrade;
private _upgradeDb;
/**
* Performs an upgrade of indexedDB and deletes deprecated DBs.
*
* @param {IDBPDatabase<CacheDbSchema>} db
*
* @private
*/
private _upgradeDbAndDeleteOldDbs;
/**
* @param {string} url

@@ -38,7 +46,7 @@ * @param {number} timestamp

* @param {string} url
* @return {number}
* @return {number | undefined}
*
* @private
*/
getTimestamp(url: string): Promise<number>;
getTimestamp(url: string): Promise<number | undefined>;
/**

@@ -65,3 +73,9 @@ * Iterates through all the entries in the object store (from newest to

private _getId;
/**
* Returns an open connection to the database.
*
* @private
*/
private getDb;
}
export { CacheTimestampsModel };

@@ -8,7 +8,6 @@ /*

*/
import { DBWrapper } from 'workbox-core/_private/DBWrapper.js';
import { deleteDatabase } from 'workbox-core/_private/deleteDatabase.js';
import { openDB, deleteDB } from 'idb';
import '../_version.js';
const DB_NAME = 'workbox-expiration';
const OBJECT_STORE_NAME = 'cache-entries';
const CACHE_OBJECT_STORE = 'cache-entries';
const normalizeURL = (unNormalizedUrl) => {

@@ -32,16 +31,13 @@ const url = new URL(unNormalizedUrl, location.href);

constructor(cacheName) {
this._db = null;
this._cacheName = cacheName;
this._db = new DBWrapper(DB_NAME, 1, {
onupgradeneeded: (event) => this._handleUpgrade(event),
});
}
/**
* Should perform an upgrade of indexedDB.
* Performs an upgrade of indexedDB.
*
* @param {Event} event
* @param {IDBPDatabase<CacheDbSchema>} db
*
* @private
*/
_handleUpgrade(event) {
const db = event.target.result;
_upgradeDb(db) {
// TODO(philipwalton): EdgeHTML doesn't support arrays as a keyPath, so we

@@ -51,3 +47,3 @@ // have to use the `id` keyPath here and create our own values (a

// `keyPath: ['url', 'cacheName']`, which is supported in other browsers.
const objStore = db.createObjectStore(OBJECT_STORE_NAME, { keyPath: 'id' });
const objStore = db.createObjectStore(CACHE_OBJECT_STORE, { keyPath: 'id' });
// TODO(philipwalton): once we don't have to support EdgeHTML, we can

@@ -58,7 +54,17 @@ // create a single index with the keyPath `['cacheName', 'timestamp']`

objStore.createIndex('timestamp', 'timestamp', { unique: false });
// Previous versions of `workbox-expiration` used `this._cacheName`
// as the IDBDatabase name.
deleteDatabase(this._cacheName);
}
/**
* Performs an upgrade of indexedDB and deletes deprecated DBs.
*
* @param {IDBPDatabase<CacheDbSchema>} db
*
* @private
*/
_upgradeDbAndDeleteOldDbs(db) {
this._upgradeDb(db);
if (this._cacheName) {
void deleteDB(this._cacheName);
}
}
/**
* @param {string} url

@@ -80,3 +86,4 @@ * @param {number} timestamp

};
await this._db.put(OBJECT_STORE_NAME, entry);
const db = await this.getDb();
await db.put(CACHE_OBJECT_STORE, entry);
}

@@ -87,3 +94,3 @@ /**

* @param {string} url
* @return {number}
* @return {number | undefined}
*

@@ -93,4 +100,5 @@ * @private

async getTimestamp(url) {
const entry = await this._db.get(OBJECT_STORE_NAME, this._getId(url));
return entry.timestamp;
const db = await this.getDb();
const entry = await db.get(CACHE_OBJECT_STORE, this._getId(url));
return entry?.timestamp;
}

@@ -109,39 +117,31 @@ /**

async expireEntries(minTimestamp, maxCount) {
const entriesToDelete = await this._db.transaction(OBJECT_STORE_NAME, 'readwrite', (txn, done) => {
const store = txn.objectStore(OBJECT_STORE_NAME);
const request = store.index('timestamp').openCursor(null, 'prev');
const entriesToDelete = [];
let entriesNotDeletedCount = 0;
request.onsuccess = () => {
const cursor = request.result;
if (cursor) {
const result = cursor.value;
// TODO(philipwalton): once we can use a multi-key index, we
// won't have to check `cacheName` here.
if (result.cacheName === this._cacheName) {
// Delete an entry if it's older than the max age or
// if we already have the max number allowed.
if ((minTimestamp && result.timestamp < minTimestamp) ||
(maxCount && entriesNotDeletedCount >= maxCount)) {
// TODO(philipwalton): we should be able to delete the
// entry right here, but doing so causes an iteration
// bug in Safari stable (fixed in TP). Instead we can
// store the keys of the entries to delete, and then
// delete the separate transactions.
// https://github.com/GoogleChrome/workbox/issues/1978
// cursor.delete();
// We only need to return the URL, not the whole entry.
entriesToDelete.push(cursor.value);
}
else {
entriesNotDeletedCount++;
}
}
cursor.continue();
const db = await this.getDb();
let cursor = await db.transaction(CACHE_OBJECT_STORE).store.index('timestamp').openCursor(null, 'prev');
const entriesToDelete = [];
let entriesNotDeletedCount = 0;
while (cursor) {
const result = cursor.value;
// TODO(philipwalton): once we can use a multi-key index, we
// won't have to check `cacheName` here.
if (result.cacheName === this._cacheName) {
// Delete an entry if it's older than the max age or
// if we already have the max number allowed.
if ((minTimestamp && result.timestamp < minTimestamp) ||
(maxCount && entriesNotDeletedCount >= maxCount)) {
// TODO(philipwalton): we should be able to delete the
// entry right here, but doing so causes an iteration
// bug in Safari stable (fixed in TP). Instead we can
// store the keys of the entries to delete, and then
// delete the separate transactions.
// https://github.com/GoogleChrome/workbox/issues/1978
// cursor.delete();
// We only need to return the URL, not the whole entry.
entriesToDelete.push(cursor.value);
}
else {
done(entriesToDelete);
entriesNotDeletedCount++;
}
};
});
}
cursor = await cursor.continue();
}
// TODO(philipwalton): once the Safari bug in the following issue is fixed,

@@ -153,3 +153,3 @@ // we should be able to remove this loop and do the entry deletion in the

for (const entry of entriesToDelete) {
await this._db.delete(OBJECT_STORE_NAME, entry.id);
await db.delete(CACHE_OBJECT_STORE, entry.id);
urlsDeleted.push(entry.url);

@@ -173,3 +173,16 @@ }

}
/**
* Returns an open connection to the database.
*
* @private
*/
async getDb() {
if (!this._db) {
this._db = await openDB(DB_NAME, 1, {
upgrade: this._upgradeDbAndDeleteOldDbs,
});
}
return this._db;
}
}
export { CacheTimestampsModel };
{
"name": "workbox-expiration",
"version": "6.1.5",
"version": "6.2.0-alpha.0",
"license": "MIT",

@@ -25,5 +25,6 @@ "author": "Google's Web DevRel Team",

"dependencies": {
"workbox-core": "^6.1.5"
"idb": "^6.0.0",
"workbox-core": "^6.2.0-alpha.0"
},
"gitHead": "d559fc8b3240f723fd9721f3976797dcedf7112b"
"gitHead": "46af63c1780955345c117c63c8c8dd54f3d40220"
}
// @ts-ignore
try{self['workbox:expiration:6.1.5']&&_()}catch(e){}
try{self['workbox:expiration:6.2.0-alpha.0']&&_()}catch(e){}

@@ -100,3 +100,3 @@ /*

*/
async expireEntries() {
async expireEntries(): Promise<void> {
if (this._isRunning) {

@@ -150,3 +150,3 @@ this._rerunRequested = true;

*/
async updateTimestamp(url: string) {
async updateTimestamp(url: string): Promise<void> {
if (process.env.NODE_ENV !== 'production') {

@@ -187,3 +187,3 @@ assert!.isType(url, 'string', {

const expireOlderThan = Date.now() - (this._maxAgeSeconds * 1000);
return (timestamp < expireOlderThan);
return timestamp !== undefined ? (timestamp < expireOlderThan) : true;
}

@@ -196,3 +196,3 @@ }

*/
async delete() {
async delete(): Promise<void> {
// Make sure we don't attempt another rerun if we're called in the middle of

@@ -199,0 +199,0 @@ // a cache expiration.

@@ -22,6 +22,13 @@ /*

export interface ExpirationPluginOptions {
maxEntries?: number;
maxAgeSeconds?: number;
matchOptions?: CacheQueryOptions;
purgeOnQuotaError?: boolean;
}
/**
* This plugin can be used in a `workbox-strategy` to regularly enforce a
* limit on the age and / or the number of cached requests.
*
*
* It can only be used with `workbox-strategy` instances that have a

@@ -47,3 +54,3 @@ * [custom `cacheName` property set](/web/tools/workbox/guides/configure-workbox#custom_cache_names_in_strategies).

class ExpirationPlugin implements WorkboxPlugin {
private readonly _config: object;
private readonly _config: ExpirationPluginOptions;
private readonly _maxAgeSeconds?: number;

@@ -53,3 +60,3 @@ private _cacheExpirations: Map<string, CacheExpiration>;

/**
* @param {Object} config
* @param {ExpirationPluginOptions} config
* @param {number} [config.maxEntries] The maximum number of entries to cache.

@@ -64,8 +71,3 @@ * Entries used the least will be removed as the maximum is reached.

*/
constructor(config: {
maxEntries?: number;
maxAgeSeconds?: number;
matchOptions?: CacheQueryOptions;
purgeOnQuotaError?: boolean;
} = {}) {
constructor(config: ExpirationPluginOptions = {}) {
if (process.env.NODE_ENV !== 'production') {

@@ -290,3 +292,3 @@ if (!(config.maxEntries || config.maxAgeSeconds)) {

*/
async deleteCacheAndMetadata() {
async deleteCacheAndMetadata(): Promise<void> {
// Do this one at a time instead of all at once via `Promise.all()` to

@@ -293,0 +295,0 @@ // reduce the chance of inconsistency if a promise rejects.

@@ -9,4 +9,3 @@ /*

import {DBWrapper} from 'workbox-core/_private/DBWrapper.js';
import {deleteDatabase} from 'workbox-core/_private/deleteDatabase.js';
import {openDB, DBSchema, IDBPDatabase, deleteDB} from 'idb';
import '../_version.js';

@@ -16,3 +15,3 @@

const DB_NAME = 'workbox-expiration';
const OBJECT_STORE_NAME = 'cache-entries';
const CACHE_OBJECT_STORE = 'cache-entries';

@@ -34,2 +33,10 @@ const normalizeURL = (unNormalizedUrl: string) => {

interface CacheDbSchema extends DBSchema {
'cache-entries': {
key: string;
value: CacheTimestampsModelEntry;
indexes: {cacheName: string; timestamp: number};
};
}
/**

@@ -42,3 +49,3 @@ * Returns the timestamp model.

private readonly _cacheName: string;
private readonly _db: DBWrapper;
private _db: IDBPDatabase<CacheDbSchema> | null = null;

@@ -54,17 +61,12 @@ /**

this._db = new DBWrapper(DB_NAME, 1, {
onupgradeneeded: (event) => this._handleUpgrade(event),
});
}
/**
* Should perform an upgrade of indexedDB.
* Performs an upgrade of indexedDB.
*
* @param {Event} event
* @param {IDBPDatabase<CacheDbSchema>} db
*
* @private
*/
private _handleUpgrade(event: IDBVersionChangeEvent) {
const db = (event.target as IDBOpenDBRequest).result;
private _upgradeDb(db: IDBPDatabase<CacheDbSchema>) {
// TODO(philipwalton): EdgeHTML doesn't support arrays as a keyPath, so we

@@ -74,3 +76,3 @@ // have to use the `id` keyPath here and create our own values (a

// `keyPath: ['url', 'cacheName']`, which is supported in other browsers.
const objStore = db.createObjectStore(OBJECT_STORE_NAME, {keyPath: 'id'});
const objStore = db.createObjectStore(CACHE_OBJECT_STORE, {keyPath: 'id'});

@@ -83,8 +85,19 @@ // TODO(philipwalton): once we don't have to support EdgeHTML, we can

// Previous versions of `workbox-expiration` used `this._cacheName`
// as the IDBDatabase name.
deleteDatabase(this._cacheName);
}
/**
* Performs an upgrade of indexedDB and deletes deprecated DBs.
*
* @param {IDBPDatabase<CacheDbSchema>} db
*
* @private
*/
private _upgradeDbAndDeleteOldDbs(db: IDBPDatabase<CacheDbSchema>) {
this._upgradeDb(db);
if (this._cacheName) {
void deleteDB(this._cacheName);
}
}
/**
* @param {string} url

@@ -95,3 +108,3 @@ * @param {number} timestamp

*/
async setTimestamp(url: string, timestamp: number) {
async setTimestamp(url: string, timestamp: number): Promise<void> {
url = normalizeURL(url);

@@ -108,4 +121,4 @@

};
await this._db.put!(OBJECT_STORE_NAME, entry);
const db = await this.getDb();
await db.put(CACHE_OBJECT_STORE, entry);
}

@@ -117,11 +130,10 @@

* @param {string} url
* @return {number}
* @return {number | undefined}
*
* @private
*/
async getTimestamp(url: string): Promise<number> {
const entry: CacheTimestampsModelEntry =
await this._db.get!(OBJECT_STORE_NAME, this._getId(url));
return entry.timestamp;
async getTimestamp(url: string): Promise<number | undefined> {
const db = await this.getDb();
const entry = await db.get(CACHE_OBJECT_STORE, this._getId(url));
return entry?.timestamp;
}

@@ -141,40 +153,32 @@

async expireEntries(minTimestamp: number, maxCount?: number): Promise<string[]> {
const entriesToDelete = await this._db.transaction(
OBJECT_STORE_NAME, 'readwrite', (txn, done) => {
const store = txn.objectStore(OBJECT_STORE_NAME);
const request = store.index('timestamp').openCursor(null, 'prev')
const db = await this.getDb();
let cursor = await db.transaction(CACHE_OBJECT_STORE).store.index('timestamp').openCursor(null, 'prev')
const entriesToDelete: CacheTimestampsModelEntry[] = [];
let entriesNotDeletedCount = 0;
while (cursor) {
const result = cursor.value;
// TODO(philipwalton): once we can use a multi-key index, we
// won't have to check `cacheName` here.
if (result.cacheName === this._cacheName) {
// Delete an entry if it's older than the max age or
// if we already have the max number allowed.
if ((minTimestamp && result.timestamp < minTimestamp) ||
(maxCount && entriesNotDeletedCount >= maxCount)) {
// TODO(philipwalton): we should be able to delete the
// entry right here, but doing so causes an iteration
// bug in Safari stable (fixed in TP). Instead we can
// store the keys of the entries to delete, and then
// delete the separate transactions.
// https://github.com/GoogleChrome/workbox/issues/1978
// cursor.delete();
const entriesToDelete: string[] = [];
let entriesNotDeletedCount = 0;
request.onsuccess = () => {
const cursor = request.result;
if (cursor) {
const result = cursor.value;
// TODO(philipwalton): once we can use a multi-key index, we
// won't have to check `cacheName` here.
if (result.cacheName === this._cacheName) {
// Delete an entry if it's older than the max age or
// if we already have the max number allowed.
if ((minTimestamp && result.timestamp < minTimestamp) ||
(maxCount && entriesNotDeletedCount >= maxCount)) {
// TODO(philipwalton): we should be able to delete the
// entry right here, but doing so causes an iteration
// bug in Safari stable (fixed in TP). Instead we can
// store the keys of the entries to delete, and then
// delete the separate transactions.
// https://github.com/GoogleChrome/workbox/issues/1978
// cursor.delete();
// We only need to return the URL, not the whole entry.
entriesToDelete.push(cursor.value);
} else {
entriesNotDeletedCount++;
}
}
cursor = await cursor.continue();
}
// We only need to return the URL, not the whole entry.
entriesToDelete.push(cursor.value);
} else {
entriesNotDeletedCount++;
}
}
cursor.continue();
} else {
done(entriesToDelete);
}
};
});

@@ -187,3 +191,3 @@ // TODO(philipwalton): once the Safari bug in the following issue is fixed,

for (const entry of entriesToDelete) {
await this._db.delete!(OBJECT_STORE_NAME, entry.id);
await db.delete(CACHE_OBJECT_STORE, entry.id);
urlsDeleted.push(entry.url);

@@ -209,4 +213,18 @@ }

}
/**
* Returns an open connection to the database.
*
* @private
*/
private async getDb() {
if (!this._db) {
this._db = await openDB(DB_NAME, 1, {
upgrade: this._upgradeDbAndDeleteOldDbs,
});
}
return this._db;
}
}
export {CacheTimestampsModel};

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

SocketSocket SOC 2 Logo

Product

  • Package Alerts
  • Integrations
  • Docs
  • Pricing
  • FAQ
  • Roadmap
  • Changelog

Packages

npm

Stay in touch

Get open source security insights delivered straight into your inbox.


  • Terms
  • Privacy
  • Security

Made with ⚡️ by Socket Inc