source: frontend/node_modules/workbox-expiration/ExpirationPlugin.js

Last change on this file was 9af201e, checked in by MBK <marija.karapandzova@…>, 11 days ago

Fix frontend appearance

  • Property mode set to 100644
File size: 11.0 KB
Line 
1/*
2 Copyright 2018 Google LLC
3
4 Use of this source code is governed by an MIT-style
5 license that can be found in the LICENSE file or at
6 https://opensource.org/licenses/MIT.
7*/
8import { assert } from 'workbox-core/_private/assert.js';
9import { cacheNames } from 'workbox-core/_private/cacheNames.js';
10import { dontWaitFor } from 'workbox-core/_private/dontWaitFor.js';
11import { getFriendlyURL } from 'workbox-core/_private/getFriendlyURL.js';
12import { logger } from 'workbox-core/_private/logger.js';
13import { registerQuotaErrorCallback } from 'workbox-core/registerQuotaErrorCallback.js';
14import { WorkboxError } from 'workbox-core/_private/WorkboxError.js';
15import { CacheExpiration } from './CacheExpiration.js';
16import './_version.js';
17/**
18 * This plugin can be used in a `workbox-strategy` to regularly enforce a
19 * limit on the age and / or the number of cached requests.
20 *
21 * It can only be used with `workbox-strategy` instances that have a
22 * [custom `cacheName` property set](/web/tools/workbox/guides/configure-workbox#custom_cache_names_in_strategies).
23 * In other words, it can't be used to expire entries in strategy that uses the
24 * default runtime cache name.
25 *
26 * Whenever a cached response is used or updated, this plugin will look
27 * at the associated cache and remove any old or extra responses.
28 *
29 * When using `maxAgeSeconds`, responses may be used *once* after expiring
30 * because the expiration clean up will not have occurred until *after* the
31 * cached response has been used. If the response has a "Date" header, then
32 * a light weight expiration check is performed and the response will not be
33 * used immediately.
34 *
35 * When using `maxEntries`, the entry least-recently requested will be removed
36 * from the cache first.
37 *
38 * @memberof workbox-expiration
39 */
40class ExpirationPlugin {
41 /**
42 * @param {ExpirationPluginOptions} config
43 * @param {number} [config.maxEntries] The maximum number of entries to cache.
44 * Entries used the least will be removed as the maximum is reached.
45 * @param {number} [config.maxAgeSeconds] The maximum age of an entry before
46 * it's treated as stale and removed.
47 * @param {Object} [config.matchOptions] The [`CacheQueryOptions`](https://developer.mozilla.org/en-US/docs/Web/API/Cache/delete#Parameters)
48 * that will be used when calling `delete()` on the cache.
49 * @param {boolean} [config.purgeOnQuotaError] Whether to opt this cache in to
50 * automatic deletion if the available storage quota has been exceeded.
51 */
52 constructor(config = {}) {
53 /**
54 * A "lifecycle" callback that will be triggered automatically by the
55 * `workbox-strategies` handlers when a `Response` is about to be returned
56 * from a [Cache](https://developer.mozilla.org/en-US/docs/Web/API/Cache) to
57 * the handler. It allows the `Response` to be inspected for freshness and
58 * prevents it from being used if the `Response`'s `Date` header value is
59 * older than the configured `maxAgeSeconds`.
60 *
61 * @param {Object} options
62 * @param {string} options.cacheName Name of the cache the response is in.
63 * @param {Response} options.cachedResponse The `Response` object that's been
64 * read from a cache and whose freshness should be checked.
65 * @return {Response} Either the `cachedResponse`, if it's
66 * fresh, or `null` if the `Response` is older than `maxAgeSeconds`.
67 *
68 * @private
69 */
70 this.cachedResponseWillBeUsed = async ({ event, request, cacheName, cachedResponse, }) => {
71 if (!cachedResponse) {
72 return null;
73 }
74 const isFresh = this._isResponseDateFresh(cachedResponse);
75 // Expire entries to ensure that even if the expiration date has
76 // expired, it'll only be used once.
77 const cacheExpiration = this._getCacheExpiration(cacheName);
78 dontWaitFor(cacheExpiration.expireEntries());
79 // Update the metadata for the request URL to the current timestamp,
80 // but don't `await` it as we don't want to block the response.
81 const updateTimestampDone = cacheExpiration.updateTimestamp(request.url);
82 if (event) {
83 try {
84 event.waitUntil(updateTimestampDone);
85 }
86 catch (error) {
87 if (process.env.NODE_ENV !== 'production') {
88 // The event may not be a fetch event; only log the URL if it is.
89 if ('request' in event) {
90 logger.warn(`Unable to ensure service worker stays alive when ` +
91 `updating cache entry for ` +
92 `'${getFriendlyURL(event.request.url)}'.`);
93 }
94 }
95 }
96 }
97 return isFresh ? cachedResponse : null;
98 };
99 /**
100 * A "lifecycle" callback that will be triggered automatically by the
101 * `workbox-strategies` handlers when an entry is added to a cache.
102 *
103 * @param {Object} options
104 * @param {string} options.cacheName Name of the cache that was updated.
105 * @param {string} options.request The Request for the cached entry.
106 *
107 * @private
108 */
109 this.cacheDidUpdate = async ({ cacheName, request, }) => {
110 if (process.env.NODE_ENV !== 'production') {
111 assert.isType(cacheName, 'string', {
112 moduleName: 'workbox-expiration',
113 className: 'Plugin',
114 funcName: 'cacheDidUpdate',
115 paramName: 'cacheName',
116 });
117 assert.isInstance(request, Request, {
118 moduleName: 'workbox-expiration',
119 className: 'Plugin',
120 funcName: 'cacheDidUpdate',
121 paramName: 'request',
122 });
123 }
124 const cacheExpiration = this._getCacheExpiration(cacheName);
125 await cacheExpiration.updateTimestamp(request.url);
126 await cacheExpiration.expireEntries();
127 };
128 if (process.env.NODE_ENV !== 'production') {
129 if (!(config.maxEntries || config.maxAgeSeconds)) {
130 throw new WorkboxError('max-entries-or-age-required', {
131 moduleName: 'workbox-expiration',
132 className: 'Plugin',
133 funcName: 'constructor',
134 });
135 }
136 if (config.maxEntries) {
137 assert.isType(config.maxEntries, 'number', {
138 moduleName: 'workbox-expiration',
139 className: 'Plugin',
140 funcName: 'constructor',
141 paramName: 'config.maxEntries',
142 });
143 }
144 if (config.maxAgeSeconds) {
145 assert.isType(config.maxAgeSeconds, 'number', {
146 moduleName: 'workbox-expiration',
147 className: 'Plugin',
148 funcName: 'constructor',
149 paramName: 'config.maxAgeSeconds',
150 });
151 }
152 }
153 this._config = config;
154 this._maxAgeSeconds = config.maxAgeSeconds;
155 this._cacheExpirations = new Map();
156 if (config.purgeOnQuotaError) {
157 registerQuotaErrorCallback(() => this.deleteCacheAndMetadata());
158 }
159 }
160 /**
161 * A simple helper method to return a CacheExpiration instance for a given
162 * cache name.
163 *
164 * @param {string} cacheName
165 * @return {CacheExpiration}
166 *
167 * @private
168 */
169 _getCacheExpiration(cacheName) {
170 if (cacheName === cacheNames.getRuntimeName()) {
171 throw new WorkboxError('expire-custom-caches-only');
172 }
173 let cacheExpiration = this._cacheExpirations.get(cacheName);
174 if (!cacheExpiration) {
175 cacheExpiration = new CacheExpiration(cacheName, this._config);
176 this._cacheExpirations.set(cacheName, cacheExpiration);
177 }
178 return cacheExpiration;
179 }
180 /**
181 * @param {Response} cachedResponse
182 * @return {boolean}
183 *
184 * @private
185 */
186 _isResponseDateFresh(cachedResponse) {
187 if (!this._maxAgeSeconds) {
188 // We aren't expiring by age, so return true, it's fresh
189 return true;
190 }
191 // Check if the 'date' header will suffice a quick expiration check.
192 // See https://github.com/GoogleChromeLabs/sw-toolbox/issues/164 for
193 // discussion.
194 const dateHeaderTimestamp = this._getDateHeaderTimestamp(cachedResponse);
195 if (dateHeaderTimestamp === null) {
196 // Unable to parse date, so assume it's fresh.
197 return true;
198 }
199 // If we have a valid headerTime, then our response is fresh iff the
200 // headerTime plus maxAgeSeconds is greater than the current time.
201 const now = Date.now();
202 return dateHeaderTimestamp >= now - this._maxAgeSeconds * 1000;
203 }
204 /**
205 * This method will extract the data header and parse it into a useful
206 * value.
207 *
208 * @param {Response} cachedResponse
209 * @return {number|null}
210 *
211 * @private
212 */
213 _getDateHeaderTimestamp(cachedResponse) {
214 if (!cachedResponse.headers.has('date')) {
215 return null;
216 }
217 const dateHeader = cachedResponse.headers.get('date');
218 const parsedDate = new Date(dateHeader);
219 const headerTime = parsedDate.getTime();
220 // If the Date header was invalid for some reason, parsedDate.getTime()
221 // will return NaN.
222 if (isNaN(headerTime)) {
223 return null;
224 }
225 return headerTime;
226 }
227 /**
228 * This is a helper method that performs two operations:
229 *
230 * - Deletes *all* the underlying Cache instances associated with this plugin
231 * instance, by calling caches.delete() on your behalf.
232 * - Deletes the metadata from IndexedDB used to keep track of expiration
233 * details for each Cache instance.
234 *
235 * When using cache expiration, calling this method is preferable to calling
236 * `caches.delete()` directly, since this will ensure that the IndexedDB
237 * metadata is also cleanly removed and open IndexedDB instances are deleted.
238 *
239 * Note that if you're *not* using cache expiration for a given cache, calling
240 * `caches.delete()` and passing in the cache's name should be sufficient.
241 * There is no Workbox-specific method needed for cleanup in that case.
242 */
243 async deleteCacheAndMetadata() {
244 // Do this one at a time instead of all at once via `Promise.all()` to
245 // reduce the chance of inconsistency if a promise rejects.
246 for (const [cacheName, cacheExpiration] of this._cacheExpirations) {
247 await self.caches.delete(cacheName);
248 await cacheExpiration.delete();
249 }
250 // Reset this._cacheExpirations to its initial state.
251 this._cacheExpirations = new Map();
252 }
253}
254export { ExpirationPlugin };
Note: See TracBrowser for help on using the repository browser.