aboutsummaryrefslogtreecommitdiff
path: root/node_modules/workbox-precaching/controllers
diff options
context:
space:
mode:
Diffstat (limited to 'node_modules/workbox-precaching/controllers')
-rw-r--r--node_modules/workbox-precaching/controllers/PrecacheController.mjs393
1 files changed, 393 insertions, 0 deletions
diff --git a/node_modules/workbox-precaching/controllers/PrecacheController.mjs b/node_modules/workbox-precaching/controllers/PrecacheController.mjs
new file mode 100644
index 00000000..d4b704b3
--- /dev/null
+++ b/node_modules/workbox-precaching/controllers/PrecacheController.mjs
@@ -0,0 +1,393 @@
+/*
+ Copyright 2017 Google Inc.
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ https://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+*/
+
+import {cacheNames} from 'workbox-core/_private/cacheNames.mjs';
+import {WorkboxError} from 'workbox-core/_private/WorkboxError.mjs';
+import {fetchWrapper} from 'workbox-core/_private/fetchWrapper.mjs';
+import {cacheWrapper} from 'workbox-core/_private/cacheWrapper.mjs';
+import {assert} from 'workbox-core/_private/assert.mjs';
+import PrecacheEntry from '../models/PrecacheEntry.mjs';
+import PrecachedDetailsModel from '../models/PrecachedDetailsModel.mjs';
+import showWarningsIfNeeded from '../utils/showWarningsIfNeeded.mjs';
+import printInstallDetails from '../utils/printInstallDetails.mjs';
+import printCleanupDetails from '../utils/printCleanupDetails.mjs';
+import cleanRedirect from '../utils/cleanRedirect.mjs';
+import '../_version.mjs';
+
+/**
+ * Performs efficient precaching of assets.
+ *
+ * @memberof workbox.precaching
+ */
+class PrecacheController {
+ /**
+ * Create a new PrecacheController.
+ *
+ * @param {string} cacheName
+ */
+ constructor(cacheName) {
+ this._cacheName = cacheNames.getPrecacheName(cacheName);
+ this._entriesToCacheMap = new Map();
+ this._precacheDetailsModel = new PrecachedDetailsModel(this._cacheName);
+ }
+
+ /**
+ * This method will add items to the precache list, removing duplicates
+ * and ensuring the information is valid.
+ *
+ * @param {
+ * Array<module:workbox-precaching.PrecacheController.PrecacheEntry|string>
+ * } entries Array of entries to
+ * precache.
+ */
+ addToCacheList(entries) {
+ if (process.env.NODE_ENV !== 'production') {
+ assert.isArray(entries, {
+ moduleName: 'workbox-precaching',
+ className: 'PrecacheController',
+ funcName: 'addToCacheList',
+ paramName: 'entries',
+ });
+ }
+
+ entries.map((userEntry) => {
+ this._addEntryToCacheList(
+ this._parseEntry(userEntry)
+ );
+ });
+ }
+
+ /**
+ * This method returns a precache entry.
+ *
+ * @private
+ * @param {string|Object} input
+ * @return {PrecacheEntry}
+ */
+ _parseEntry(input) {
+ switch (typeof input) {
+ case 'string': {
+ if (process.env.NODE_ENV !== 'production') {
+ if (input.length === 0) {
+ throw new WorkboxError(
+ 'add-to-cache-list-unexpected-type', {
+ entry: input,
+ }
+ );
+ }
+ }
+
+ return new PrecacheEntry(input, input, input);
+ }
+ case 'object': {
+ if (process.env.NODE_ENV !== 'production') {
+ if (!input || !input.url) {
+ throw new WorkboxError(
+ 'add-to-cache-list-unexpected-type', {
+ entry: input,
+ }
+ );
+ }
+ }
+
+ return new PrecacheEntry(
+ input, input.url, input.revision || input.url, !!input.revision);
+ }
+ default:
+ throw new WorkboxError('add-to-cache-list-unexpected-type', {
+ entry: input,
+ });
+ }
+ }
+
+ /**
+ * Adds an entry to the precache list, accounting for possible duplicates.
+ *
+ * @private
+ * @param {PrecacheEntry} entryToAdd
+ */
+ _addEntryToCacheList(entryToAdd) {
+ // Check if the entry is already part of the map
+ const existingEntry = this._entriesToCacheMap.get(entryToAdd._entryId);
+ if (!existingEntry) {
+ this._entriesToCacheMap.set(entryToAdd._entryId, entryToAdd);
+ return;
+ }
+
+ // Duplicates are fine, but make sure the revision information
+ // is the same.
+ if (existingEntry._revision !== entryToAdd._revision) {
+ throw new WorkboxError('add-to-cache-list-conflicting-entries', {
+ firstEntry: existingEntry._originalInput,
+ secondEntry: entryToAdd._originalInput,
+ });
+ }
+ }
+
+ /**
+ * Call this method from a service work install event to start
+ * precaching assets.
+ *
+ * @param {Object} options
+ * @param {boolean} [options.suppressWarnings] Suppress warning messages.
+ * @param {Event} [options.event] The install event (if needed).
+ * @param {Array<Object>} [options.plugins] Plugins to be used for fetching
+ * and caching during install.
+ * @return {Promise<workbox.precaching.InstallResult>}
+ */
+ async install({suppressWarnings = false, event, plugins} = {}) {
+ if (process.env.NODE_ENV !== 'production') {
+ if (suppressWarnings !== true) {
+ showWarningsIfNeeded(this._entriesToCacheMap);
+ }
+
+ if (plugins) {
+ assert.isArray(plugins, {
+ moduleName: 'workbox-precaching',
+ className: 'PrecacheController',
+ funcName: 'install',
+ paramName: 'plugins',
+ });
+ }
+ }
+
+ // Empty the temporary cache.
+ // NOTE: We remove all entries instead of deleting the cache as the cache
+ // may be marked for deletion but still exist until a later stage
+ // resulting in unexpected behavior of being deletect when all references
+ // are dropped.
+ // https://github.com/GoogleChrome/workbox/issues/1368
+ const tempCache = await caches.open(this._getTempCacheName());
+ const requests = await tempCache.keys();
+ await Promise.all(requests.map((request) => {
+ return tempCache.delete(request);
+ }));
+
+ const entriesToPrecache = [];
+ const entriesAlreadyPrecached = [];
+
+ for (const precacheEntry of this._entriesToCacheMap.values()) {
+ if (await this._precacheDetailsModel._isEntryCached(
+ this._cacheName, precacheEntry)) {
+ entriesAlreadyPrecached.push(precacheEntry);
+ } else {
+ entriesToPrecache.push(precacheEntry);
+ }
+ }
+
+ // Wait for all requests to be cached.
+ await Promise.all(entriesToPrecache.map((precacheEntry) => {
+ return this._cacheEntryInTemp({event, plugins, precacheEntry});
+ }));
+
+ if (process.env.NODE_ENV !== 'production') {
+ printInstallDetails(entriesToPrecache, entriesAlreadyPrecached);
+ }
+
+ return {
+ updatedEntries: entriesToPrecache,
+ notUpdatedEntries: entriesAlreadyPrecached,
+ };
+ }
+
+ /**
+ * Takes the current set of temporary files and moves them to the final
+ * cache, deleting the temporary cache once copying is complete.
+ *
+ * @param {Object} options
+ * @param {Array<Object>} options.plugins Plugins to be used for fetching
+ * and caching during install.
+ * @return {
+ * Promise<workbox.precaching.CleanupResult>}
+ * Resolves with an object containing details of the deleted cache requests
+ * and precache revision details.
+ */
+ async activate(options = {}) {
+ const tempCache = await caches.open(this._getTempCacheName());
+
+ const requests = await tempCache.keys();
+ // Process each request/response one at a time, deleting the temporary entry
+ // when done, to help avoid triggering quota errors.
+ for (const request of requests) {
+ const response = await tempCache.match(request);
+ await cacheWrapper.put({
+ cacheName: this._cacheName,
+ request,
+ response,
+ plugins: options.plugins,
+ });
+ await tempCache.delete(request);
+ }
+
+ return this._cleanup();
+ }
+
+ /**
+ * Returns the name of the temporary cache.
+ *
+ * @return {string}
+ *
+ * @private
+ */
+ _getTempCacheName() {
+ return `${this._cacheName}-temp`;
+ }
+
+ /**
+ * Requests the entry and saves it to the cache if the response
+ * is valid.
+ *
+ * @private
+ * @param {Object} options
+ * @param {BaseCacheEntry} options.precacheEntry The entry to fetch and cache.
+ * @param {Event} [options.event] The install event (if passed).
+ * @param {Array<Object>} [options.plugins] An array of plugins to apply to
+ * fetch and caching.
+ * @return {Promise<boolean>} Returns a promise that resolves once the entry
+ * has been fetched and cached or skipped if no update is needed. The
+ * promise resolves with true if the entry was cached / updated and
+ * false if the entry is already cached and up-to-date.
+ */
+ async _cacheEntryInTemp({precacheEntry, event, plugins}) {
+ let response = await fetchWrapper.fetch({
+ request: precacheEntry._networkRequest,
+ event,
+ fetchOptions: null,
+ plugins,
+ });
+
+ if (response.redirected) {
+ response = await cleanRedirect(response);
+ }
+
+ await cacheWrapper.put({
+ cacheName: this._getTempCacheName(),
+ request: precacheEntry._cacheRequest,
+ response,
+ event,
+ plugins,
+ });
+
+ await this._precacheDetailsModel._addEntry(precacheEntry);
+
+ return true;
+ }
+
+ /**
+ * Compare the URLs and determines which assets are no longer required
+ * in the cache.
+ *
+ * This should be called in the service worker activate event.
+ *
+ * @return {
+ * Promise<workbox.precaching.CleanupResult>}
+ * Resolves with an object containing details of the deleted cache requests
+ * and precache revision details.
+ *
+ * @private
+ */
+ async _cleanup() {
+ const expectedCacheUrls = [];
+ this._entriesToCacheMap.forEach((entry) => {
+ const fullUrl = new URL(entry._cacheRequest.url, location).toString();
+ expectedCacheUrls.push(fullUrl);
+ });
+
+ const [deletedCacheRequests, deletedRevisionDetails] = await Promise.all([
+ this._cleanupCache(expectedCacheUrls),
+ this._cleanupDetailsModel(expectedCacheUrls),
+ ]);
+
+ if (process.env.NODE_ENV !== 'production') {
+ printCleanupDetails(deletedCacheRequests, deletedRevisionDetails);
+ }
+
+ return {
+ deletedCacheRequests,
+ deletedRevisionDetails,
+ };
+ }
+
+ /**
+ * Goes through all the cache entries and removes any that are
+ * outdated.
+ *
+ * @private
+ * @param {Array<string>} expectedCacheUrls Array of URLs that are
+ * expected to be cached.
+ * @return {Promise<Array<string>>} Resolves to an array of URLs
+ * of cached requests that were deleted.
+ */
+ async _cleanupCache(expectedCacheUrls) {
+ if (!await caches.has(this._cacheName)) {
+ // Cache doesn't exist, so nothing to delete
+ return [];
+ }
+
+ const cache = await caches.open(this._cacheName);
+ const cachedRequests = await cache.keys();
+ const cachedRequestsToDelete = cachedRequests.filter((cachedRequest) => {
+ return !expectedCacheUrls.includes(
+ new URL(cachedRequest.url, location).toString()
+ );
+ });
+
+ await Promise.all(
+ cachedRequestsToDelete.map((cacheUrl) => cache.delete(cacheUrl))
+ );
+
+ return cachedRequestsToDelete.map((request) => request.url);
+ }
+
+ /**
+ * Goes through all entries in indexedDB and removes any that are outdated.
+ *
+ * @private
+ * @param {Array<string>} expectedCacheUrls Array of URLs that are
+ * expected to be cached.
+ * @return {Promise<Array<string>>} Resolves to an array of URLs removed
+ * from indexedDB.
+ */
+ async _cleanupDetailsModel(expectedCacheUrls) {
+ const revisionedEntries = await this._precacheDetailsModel._getAllEntries();
+ const detailsToDelete = revisionedEntries
+ .filter((entry) => {
+ const fullUrl = new URL(entry.value.url, location).toString();
+ return !expectedCacheUrls.includes(fullUrl);
+ });
+
+ await Promise.all(
+ detailsToDelete.map(
+ (entry) => this._precacheDetailsModel._deleteEntry(entry.primaryKey)
+ )
+ );
+ return detailsToDelete.map((entry) => {
+ return entry.value.url;
+ });
+ }
+
+ /**
+ * Returns an array of fully qualified URL's that will be precached.
+ *
+ * @return {Array<string>} An array of URLs.
+ */
+ getCachedUrls() {
+ return Array.from(this._entriesToCacheMap.keys())
+ .map((url) => new URL(url, location).href);
+ }
+}
+
+export default PrecacheController;