diff options
Diffstat (limited to 'node_modules/workbox-core/_private')
| -rw-r--r-- | node_modules/workbox-core/_private/DBWrapper.mjs | 325 | ||||
| -rw-r--r-- | node_modules/workbox-core/_private/WorkboxError.mjs | 48 | ||||
| -rw-r--r-- | node_modules/workbox-core/_private/assert.mjs | 109 | ||||
| -rw-r--r-- | node_modules/workbox-core/_private/cacheNames.mjs | 52 | ||||
| -rw-r--r-- | node_modules/workbox-core/_private/cacheWrapper.mjs | 236 | ||||
| -rw-r--r-- | node_modules/workbox-core/_private/checkSWFileCacheHeaders.mjs | 88 | ||||
| -rw-r--r-- | node_modules/workbox-core/_private/fetchWrapper.mjs | 143 | ||||
| -rw-r--r-- | node_modules/workbox-core/_private/getFriendlyURL.mjs | 27 | ||||
| -rw-r--r-- | node_modules/workbox-core/_private/logger.mjs | 95 | ||||
| -rw-r--r-- | node_modules/workbox-core/_private/quota.mjs | 73 |
10 files changed, 1196 insertions, 0 deletions
diff --git a/node_modules/workbox-core/_private/DBWrapper.mjs b/node_modules/workbox-core/_private/DBWrapper.mjs new file mode 100644 index 00000000..61def037 --- /dev/null +++ b/node_modules/workbox-core/_private/DBWrapper.mjs @@ -0,0 +1,325 @@ +/* + 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 '../_version.mjs'; + +/** + * A class that wraps common IndexedDB functionality in a promise-based API. + * It exposes all the underlying power and functionality of IndexedDB, but + * wraps the most commonly used features in a way that's much simpler to use. + * + * @private + */ +class DBWrapper { + /** + * @param {string} name + * @param {number} version + * @param {Object=} [callback] + * @param {function(this:DBWrapper, Event)} [callbacks.onupgradeneeded] + * @param {function(this:DBWrapper, Event)} [callbacks.onversionchange] + * Defaults to DBWrapper.prototype._onversionchange when not specified. + */ + constructor(name, version, { + onupgradeneeded, + onversionchange = this._onversionchange, + } = {}) { + this._name = name; + this._version = version; + this._onupgradeneeded = onupgradeneeded; + this._onversionchange = onversionchange; + + // If this is null, it means the database isn't open. + this._db = null; + } + + /** + * Opens a connected to an IDBDatabase, invokes any onupgradedneeded + * callback, and added an onversionchange callback to the database. + * + * @return {IDBDatabase} + * + * @private + */ + async open() { + if (this._db) return; + + this._db = await new Promise((resolve, reject) => { + // This flag is flipped to true if the timeout callback runs prior + // to the request failing or succeeding. Note: we use a timeout instead + // of an onblocked handler since there are cases where onblocked will + // never never run. A timeout better handles all possible scenarios: + // https://github.com/w3c/IndexedDB/issues/223 + let openRequestTimedOut = false; + setTimeout(() => { + openRequestTimedOut = true; + reject(new Error('The open request was blocked and timed out')); + }, this.OPEN_TIMEOUT); + + const openRequest = indexedDB.open(this._name, this._version); + openRequest.onerror = (evt) => reject(openRequest.error); + openRequest.onupgradeneeded = (evt) => { + if (openRequestTimedOut) { + openRequest.transaction.abort(); + evt.target.result.close(); + } else if (this._onupgradeneeded) { + this._onupgradeneeded(evt); + } + }; + openRequest.onsuccess = (evt) => { + const db = evt.target.result; + if (openRequestTimedOut) { + db.close(); + } else { + db.onversionchange = this._onversionchange; + resolve(db); + } + }; + }); + + return this; + } + + /** + * Delegates to the native `get()` method for the object store. + * + * @param {string} storeName The name of the object store to put the value. + * @param {...*} args The values passed to the delegated method. + * @return {*} The key of the entry. + * + * @private + */ + async get(storeName, ...args) { + return await this._call('get', storeName, 'readonly', ...args); + } + + /** + * Delegates to the native `add()` method for the object store. + * + * @param {string} storeName The name of the object store to put the value. + * @param {...*} args The values passed to the delegated method. + * @return {*} The key of the entry. + * + * @private + */ + async add(storeName, ...args) { + return await this._call('add', storeName, 'readwrite', ...args); + } + + /** + * Delegates to the native `put()` method for the object store. + * + * @param {string} storeName The name of the object store to put the value. + * @param {...*} args The values passed to the delegated method. + * @return {*} The key of the entry. + * + * @private + */ + async put(storeName, ...args) { + return await this._call('put', storeName, 'readwrite', ...args); + } + + /** + * Delegates to the native `delete()` method for the object store. + * + * @param {string} storeName + * @param {...*} args The values passed to the delegated method. + * + * @private + */ + async delete(storeName, ...args) { + await this._call('delete', storeName, 'readwrite', ...args); + } + + /** + * Deletes the underlying database, ensuring that any open connections are + * closed first. + * + * @private + */ + async deleteDatabase() { + this.close(); + this._db = null; + await new Promise((resolve, reject) => { + const request = indexedDB.deleteDatabase(this._name); + request.onerror = (evt) => reject(evt.target.error); + request.onblocked = () => reject(new Error('Deletion was blocked.')); + request.onsuccess = () => resolve(); + }); + } + + /** + * Delegates to the native `getAll()` or polyfills it via the `find()` + * method in older browsers. + * + * @param {string} storeName + * @param {*} query + * @param {number} count + * @return {Array} + * + * @private + */ + async getAll(storeName, query, count) { + if ('getAll' in IDBObjectStore.prototype) { + return await this._call('getAll', storeName, 'readonly', query, count); + } else { + return await this.getAllMatching(storeName, {query, count}); + } + } + + /** + * Supports flexible lookup in an object store by specifying an index, + * query, direction, and count. This method returns an array of objects + * with the signature . + * + * @param {string} storeName + * @param {Object} [opts] + * @param {IDBCursorDirection} [opts.direction] + * @param {*} [opts.query] + * @param {string} [opts.index] The index to use (if specified). + * @param {number} [opts.count] The max number of results to return. + * @param {boolean} [opts.includeKeys] When true, the structure of the + * returned objects is changed from an array of values to an array of + * objects in the form {key, primaryKey, value}. + * @return {Array} + * + * @private + */ + async getAllMatching(storeName, opts = {}) { + return await this.transaction([storeName], 'readonly', (stores, done) => { + const store = stores[storeName]; + const target = opts.index ? store.index(opts.index) : store; + const results = []; + + // Passing `undefined` arguments to Edge's `openCursor(...)` causes + // 'DOMException: DataError' + // Details in issue: https://github.com/GoogleChrome/workbox/issues/1509 + const query = opts.query || null; + const direction = opts.direction || 'next'; + target.openCursor(query, direction).onsuccess = (evt) => { + const cursor = evt.target.result; + if (cursor) { + const {primaryKey, key, value} = cursor; + results.push(opts.includeKeys ? {primaryKey, key, value} : value); + if (opts.count && results.length >= opts.count) { + done(results); + } else { + cursor.continue(); + } + } else { + done(results); + } + }; + }); + } + + /** + * Accepts a list of stores, a transaction type, and a callback and + * performs a transaction. A promise is returned that resolves to whatever + * value the callback chooses. The callback holds all the transaction logic + * and is invoked with three arguments: + * 1. An object mapping object store names to IDBObjectStore values. + * 2. A `done` function, that's used to resolve the promise when + * when the transaction is done. + * 3. An `abort` function that can be called to abort the transaction + * at any time. + * + * @param {Array<string>} storeNames An array of object store names + * involved in the transaction. + * @param {string} type Can be `readonly` or `readwrite`. + * @param {function(Object, function(), function(*)):?IDBRequest} callback + * @return {*} The result of the transaction ran by the callback. + * + * @private + */ + async transaction(storeNames, type, callback) { + await this.open(); + const result = await new Promise((resolve, reject) => { + const txn = this._db.transaction(storeNames, type); + const done = (value) => resolve(value); + const abort = () => { + reject(new Error('The transaction was manually aborted')); + txn.abort(); + }; + txn.onerror = (evt) => reject(evt.target.error); + txn.onabort = (evt) => reject(evt.target.error); + txn.oncomplete = () => resolve(); + + const stores = {}; + for (const storeName of storeNames) { + stores[storeName] = txn.objectStore(storeName); + } + callback(stores, done, abort); + }); + return result; + } + + /** + * Delegates async to a native IDBObjectStore method. + * + * @param {string} method The method name. + * @param {string} storeName The object store name. + * @param {string} type Can be `readonly` or `readwrite`. + * @param {...*} args The list of args to pass to the native method. + * @return {*} The result of the transaction. + * + * @private + */ + async _call(method, storeName, type, ...args) { + await this.open(); + const callback = (stores, done) => { + stores[storeName][method](...args).onsuccess = (evt) => { + done(evt.target.result); + }; + }; + + return await this.transaction([storeName], type, callback); + } + + /** + * The default onversionchange handler, which closes the database so other + * connections can open without being blocked. + * + * @param {Event} evt + * + * @private + */ + _onversionchange(evt) { + this.close(); + } + + /** + * Closes the connection opened by `DBWrapper.open()`. Generally this method + * doesn't need to be called since: + * 1. It's usually better to keep a connection open since opening + * a new connection is somewhat slow. + * 2. Connections are automatically closed when the reference is + * garbage collected. + * The primary use case for needing to close a connection is when another + * reference (typically in another tab) needs to upgrade it and would be + * blocked by the current, open connection. + * + * @private + */ + close() { + if (this._db) this._db.close(); + } +} + +// Exposed to let users modify the default timeout on a per-instance +// or global basis. +DBWrapper.prototype.OPEN_TIMEOUT = 2000; + +export {DBWrapper}; diff --git a/node_modules/workbox-core/_private/WorkboxError.mjs b/node_modules/workbox-core/_private/WorkboxError.mjs new file mode 100644 index 00000000..6a2d0c5a --- /dev/null +++ b/node_modules/workbox-core/_private/WorkboxError.mjs @@ -0,0 +1,48 @@ +/* + 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 messageGenerator from '../models/messages/messageGenerator.mjs'; +import '../_version.mjs'; + +/** + * Workbox errors should be thrown with this class. + * This allows use to ensure the type easily in tests, + * helps developers identify errors from workbox + * easily and allows use to optimise error + * messages correctly. + * + * @private + */ +class WorkboxError extends Error { + /** + * + * @param {string} errorCode The error code that + * identifies this particular error. + * @param {Object=} details Any relevant arguments + * that will help developers identify issues should + * be added as a key on the context object. + */ + constructor(errorCode, details) { + let message = messageGenerator(errorCode, details); + + super(message); + + this.name = errorCode; + this.details = details; + } +} + +export {WorkboxError}; diff --git a/node_modules/workbox-core/_private/assert.mjs b/node_modules/workbox-core/_private/assert.mjs new file mode 100644 index 00000000..f9832834 --- /dev/null +++ b/node_modules/workbox-core/_private/assert.mjs @@ -0,0 +1,109 @@ +/* + 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 {WorkboxError} from '../_private/WorkboxError.mjs'; +import '../_version.mjs'; + +/* + * This method returns true if the current context is a service worker. + */ +const isSwEnv = (moduleName) => { + if (!('ServiceWorkerGlobalScope' in self)) { + throw new WorkboxError('not-in-sw', {moduleName}); + } +}; + +/* + * This method throws if the supplied value is not an array. + * The destructed values are required to produce a meaningful error for users. + * The destructed and restructured object is so it's clear what is + * needed. + */ +const isArray = (value, {moduleName, className, funcName, paramName}) => { + if (!Array.isArray(value)) { + throw new WorkboxError('not-an-array', { + moduleName, + className, + funcName, + paramName, + }); + } +}; + +const hasMethod = (object, expectedMethod, + {moduleName, className, funcName, paramName}) => { + const type = typeof object[expectedMethod]; + if (type !== 'function') { + throw new WorkboxError('missing-a-method', {paramName, expectedMethod, + moduleName, className, funcName}); + } +}; + +const isType = (object, expectedType, + {moduleName, className, funcName, paramName}) => { + if (typeof object !== expectedType) { + throw new WorkboxError('incorrect-type', {paramName, expectedType, + moduleName, className, funcName}); + } +}; + +const isInstance = (object, expectedClass, + {moduleName, className, funcName, + paramName, isReturnValueProblem}) => { + if (!(object instanceof expectedClass)) { + throw new WorkboxError('incorrect-class', {paramName, expectedClass, + moduleName, className, funcName, isReturnValueProblem}); + } +}; + +const isOneOf = (value, validValues, {paramName}) => { + if (!validValues.includes(value)) { + throw new WorkboxError('invalid-value', { + paramName, + value, + validValueDescription: `Valid values are ${JSON.stringify(validValues)}.`, + }); + } +}; + +const isArrayOfClass = (value, expectedClass, + {moduleName, className, funcName, paramName}) => { + const error = new WorkboxError('not-array-of-class', { + value, expectedClass, + moduleName, className, funcName, paramName, + }); + if (!Array.isArray(value)) { + throw error; + } + + for (let item of value) { + if (!(item instanceof expectedClass)) { + throw error; + } + } +}; + +const finalAssertExports = process.env.NODE_ENV === 'production' ? null : { + hasMethod, + isArray, + isInstance, + isOneOf, + isSwEnv, + isType, + isArrayOfClass, +}; + +export {finalAssertExports as assert}; diff --git a/node_modules/workbox-core/_private/cacheNames.mjs b/node_modules/workbox-core/_private/cacheNames.mjs new file mode 100644 index 00000000..11698cdf --- /dev/null +++ b/node_modules/workbox-core/_private/cacheNames.mjs @@ -0,0 +1,52 @@ +/* + 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 '../_version.mjs'; + +const _cacheNameDetails = { + prefix: 'workbox', + suffix: self.registration.scope, + googleAnalytics: 'googleAnalytics', + precache: 'precache', + runtime: 'runtime', +}; + +const _createCacheName = (cacheName) => { + return [_cacheNameDetails.prefix, cacheName, _cacheNameDetails.suffix] + .filter((value) => value.length > 0) + .join('-'); +}; + +const cacheNames = { + updateDetails: (details) => { + Object.keys(_cacheNameDetails).forEach((key) => { + if (typeof details[key] !== 'undefined') { + _cacheNameDetails[key] = details[key]; + } + }); + }, + getGoogleAnalyticsName: (userCacheName) => { + return userCacheName || _createCacheName(_cacheNameDetails.googleAnalytics); + }, + getPrecacheName: (userCacheName) => { + return userCacheName || _createCacheName(_cacheNameDetails.precache); + }, + getRuntimeName: (userCacheName) => { + return userCacheName || _createCacheName(_cacheNameDetails.runtime); + }, +}; + +export {cacheNames}; diff --git a/node_modules/workbox-core/_private/cacheWrapper.mjs b/node_modules/workbox-core/_private/cacheWrapper.mjs new file mode 100644 index 00000000..7c71884c --- /dev/null +++ b/node_modules/workbox-core/_private/cacheWrapper.mjs @@ -0,0 +1,236 @@ +/* + 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 pluginEvents from '../models/pluginEvents.mjs'; +import pluginUtils from '../utils/pluginUtils.mjs'; +import {WorkboxError} from './WorkboxError.mjs'; +import {assert} from './assert.mjs'; +import {executeQuotaErrorCallbacks} from './quota.mjs'; +import {getFriendlyURL} from './getFriendlyURL.mjs'; +import {logger} from './logger.mjs'; + +import '../_version.mjs'; + +/** + * Wrapper around cache.put(). + * + * Will call `cacheDidUpdate` on plugins if the cache was updated. + * + * @param {Object} options + * @param {string} options.cacheName + * @param {Request} options.request + * @param {Response} options.response + * @param {Event} [options.event] + * @param {Array<Object>} [options.plugins=[]] + * + * @private + * @memberof module:workbox-core + */ +const putWrapper = async ({ + cacheName, + request, + response, + event, + plugins = [], + } = {}) => { + if (!response) { + if (process.env.NODE_ENV !== 'production') { + logger.error(`Cannot cache non-existent response for ` + + `'${getFriendlyURL(request.url)}'.`); + } + + throw new WorkboxError('cache-put-with-no-response', { + url: getFriendlyURL(request.url), + }); + } + + let responseToCache = + await _isResponseSafeToCache({request, response, event, plugins}); + + if (!responseToCache) { + if (process.env.NODE_ENV !== 'production') { + logger.debug(`Response '${getFriendlyURL(request.url)}' will not be ` + + `cached.`, responseToCache); + } + return; + } + + if (process.env.NODE_ENV !== 'production') { + if (responseToCache.method && responseToCache.method !== 'GET') { + throw new WorkboxError('attempt-to-cache-non-get-request', { + url: getFriendlyURL(request.url), + method: responseToCache.method, + }); + } + } + + const cache = await caches.open(cacheName); + + const updatePlugins = pluginUtils.filter( + plugins, pluginEvents.CACHE_DID_UPDATE); + + let oldResponse = updatePlugins.length > 0 ? + await matchWrapper({cacheName, request}) : null; + + if (process.env.NODE_ENV !== 'production') { + logger.debug(`Updating the '${cacheName}' cache with a new Response for ` + + `${getFriendlyURL(request.url)}.`); + } + + try { + await cache.put(request, responseToCache); + } catch (error) { + // See https://developer.mozilla.org/en-US/docs/Web/API/DOMException#exception-QuotaExceededError + if (error.name === 'QuotaExceededError') { + await executeQuotaErrorCallbacks(); + } + throw error; + } + + for (let plugin of updatePlugins) { + await plugin[pluginEvents.CACHE_DID_UPDATE].call(plugin, { + cacheName, + request, + event, + oldResponse, + newResponse: responseToCache, + }); + } +}; + +/** + * This is a wrapper around cache.match(). + * + * @param {Object} options + * @param {string} options.cacheName Name of the cache to match against. + * @param {Request} options.request The Request that will be used to look up + *. cache entries. + * @param {Event} [options.event] The event that propted the action. + * @param {Object} [options.matchOptions] Options passed to cache.match(). + * @param {Array<Object>} [options.plugins=[]] Array of plugins. + * @return {Response} A cached response if available. + * + * @private + * @memberof module:workbox-core + */ +const matchWrapper = async ({ + cacheName, + request, + event, + matchOptions, + plugins = []}) => { + const cache = await caches.open(cacheName); + let cachedResponse = await cache.match(request, matchOptions); + if (process.env.NODE_ENV !== 'production') { + if (cachedResponse) { + logger.debug(`Found a cached response in '${cacheName}'.`); + } else { + logger.debug(`No cached response found in '${cacheName}'.`); + } + } + for (let plugin of plugins) { + if (pluginEvents.CACHED_RESPONSE_WILL_BE_USED in plugin) { + cachedResponse = await plugin[pluginEvents.CACHED_RESPONSE_WILL_BE_USED] + .call(plugin, { + cacheName, + request, + event, + matchOptions, + cachedResponse, + }); + if (process.env.NODE_ENV !== 'production') { + if (cachedResponse) { + assert.isInstance(cachedResponse, Response, { + moduleName: 'Plugin', + funcName: pluginEvents.CACHED_RESPONSE_WILL_BE_USED, + isReturnValueProblem: true, + }); + } + } + } + } + return cachedResponse; +}; + +/** + * This method will call cacheWillUpdate on the available plugins (or use + * response.ok) to determine if the Response is safe and valid to cache. + * + * @param {Object} options + * @param {Request} options.request + * @param {Response} options.response + * @param {Event} [options.event] + * @param {Array<Object>} [options.plugins=[]] + * @return {Promise<Response>} + * + * @private + * @memberof module:workbox-core + */ +const _isResponseSafeToCache = async ({request, response, event, plugins}) => { + let responseToCache = response; + let pluginsUsed = false; + for (let plugin of plugins) { + if (pluginEvents.CACHE_WILL_UPDATE in plugin) { + pluginsUsed = true; + responseToCache = await plugin[pluginEvents.CACHE_WILL_UPDATE] + .call(plugin, { + request, + response: responseToCache, + event, + }); + + if (process.env.NODE_ENV !== 'production') { + if (responseToCache) { + assert.isInstance(responseToCache, Response, { + moduleName: 'Plugin', + funcName: pluginEvents.CACHE_WILL_UPDATE, + isReturnValueProblem: true, + }); + } + } + + if (!responseToCache) { + break; + } + } + } + + if (!pluginsUsed) { + if (process.env.NODE_ENV !== 'production') { + if (!responseToCache.ok) { + if (responseToCache.status === 0) { + logger.warn(`The response for '${request.url}' is an opaque ` + + `response. The caching strategy that you're using will not ` + + `cache opaque responses by default.`); + } else { + logger.debug(`The response for '${request.url}' returned ` + + `a status code of '${response.status}' and won't be cached as a ` + + `result.`); + } + } + } + responseToCache = responseToCache.ok ? responseToCache : null; + } + + return responseToCache ? responseToCache : null; +}; + +const cacheWrapper = { + put: putWrapper, + match: matchWrapper, +}; + +export {cacheWrapper}; diff --git a/node_modules/workbox-core/_private/checkSWFileCacheHeaders.mjs b/node_modules/workbox-core/_private/checkSWFileCacheHeaders.mjs new file mode 100644 index 00000000..7382ceed --- /dev/null +++ b/node_modules/workbox-core/_private/checkSWFileCacheHeaders.mjs @@ -0,0 +1,88 @@ +/* + 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 {logger} from './logger.mjs'; +import '../_version.mjs'; + +/** + * Logs a warning to the user recommending changing + * to max-age=0 or no-cache. + * + * @param {string} cacheControlHeader + * + * @private + */ +function showWarning(cacheControlHeader) { + const docsUrl = 'https://developers.google.com/web/tools/workbox/guides/service-worker-checklist#cache-control_of_your_service_worker_file'; + logger.warn(`You are setting a 'cache-control' header of ` + + `'${cacheControlHeader}' on your service worker file. This should be ` + + `set to 'max-age=0' or 'no-cache' to ensure the latest service worker ` + + `is served to your users. Learn more here: ${docsUrl}` + ); +} + +/** + * Checks for cache-control header on SW file and + * warns the developer if it exists with a value + * other than max-age=0 or no-cache. + * + * @return {Promise} + * @private + */ +function checkSWFileCacheHeaders() { + // This is wrapped as an iife to allow async/await while making + // rollup exclude it in builds. + return (async () => { + try { + const swFile = self.location.href; + const response = await fetch(swFile); + if (!response.ok) { + // Response failed so nothing we can check; + return; + } + + if (!response.headers.has('cache-control')) { + // No cache control header. + return; + } + + const cacheControlHeader = response.headers.get('cache-control'); + const maxAgeResult = /max-age\s*=\s*(\d*)/g.exec(cacheControlHeader); + if (maxAgeResult) { + if (parseInt(maxAgeResult[1], 10) === 0) { + return; + } + } + + if (cacheControlHeader.indexOf('no-cache') !== -1) { + return; + } + + if (cacheControlHeader.indexOf('no-store') !== -1) { + return; + } + + showWarning(cacheControlHeader); + } catch (err) { + // NOOP + } + })(); +} + +const finalCheckSWFileCacheHeaders = + process.env.NODE_ENV === 'production' ? null : checkSWFileCacheHeaders; + +export {finalCheckSWFileCacheHeaders as checkSWFileCacheHeaders}; diff --git a/node_modules/workbox-core/_private/fetchWrapper.mjs b/node_modules/workbox-core/_private/fetchWrapper.mjs new file mode 100644 index 00000000..fc9e7eeb --- /dev/null +++ b/node_modules/workbox-core/_private/fetchWrapper.mjs @@ -0,0 +1,143 @@ +/* + 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 {WorkboxError} from './WorkboxError.mjs'; +import {logger} from './logger.mjs'; +import {assert} from './assert.mjs'; +import {getFriendlyURL} from '../_private/getFriendlyURL.mjs'; +import pluginEvents from '../models/pluginEvents.mjs'; +import pluginUtils from '../utils/pluginUtils.mjs'; +import '../_version.mjs'; + +/** + * Wrapper around the fetch API. + * + * Will call requestWillFetch on available plugins. + * + * @param {Object} options + * @param {Request|string} options.request + * @param {Object} [options.fetchOptions] + * @param {Event} [options.event] + * @param {Array<Object>} [options.plugins=[]] + * @return {Promise<Response>} + * + * @private + * @memberof module:workbox-core + */ +const wrappedFetch = async ({ + request, + fetchOptions, + event, + plugins = []}) => { + // We *should* be able to call `await event.preloadResponse` even if it's + // undefined, but for some reason, doing so leads to errors in our Node unit + // tests. To work around that, explicitly check preloadResponse's value first. + if (event && event.preloadResponse) { + const possiblePreloadResponse = await event.preloadResponse; + if (possiblePreloadResponse) { + if (process.env.NODE_ENV !== 'production') { + logger.log(`Using a preloaded navigation response for ` + + `'${getFriendlyURL(request.url)}'`); + } + return possiblePreloadResponse; + } + } + + if (typeof request === 'string') { + request = new Request(request); + } + + if (process.env.NODE_ENV !== 'production') { + assert.isInstance(request, Request, { + paramName: request, + expectedClass: 'Request', + moduleName: 'workbox-core', + className: 'fetchWrapper', + funcName: 'wrappedFetch', + }); + } + + const failedFetchPlugins = pluginUtils.filter( + plugins, pluginEvents.FETCH_DID_FAIL); + + // If there is a fetchDidFail plugin, we need to save a clone of the + // original request before it's either modified by a requestWillFetch + // plugin or before the original request's body is consumed via fetch(). + const originalRequest = failedFetchPlugins.length > 0 ? + request.clone() : null; + + try { + for (let plugin of plugins) { + if (pluginEvents.REQUEST_WILL_FETCH in plugin) { + request = await plugin[pluginEvents.REQUEST_WILL_FETCH].call(plugin, { + request: request.clone(), + event, + }); + + if (process.env.NODE_ENV !== 'production') { + if (request) { + assert.isInstance(request, Request, { + moduleName: 'Plugin', + funcName: pluginEvents.CACHED_RESPONSE_WILL_BE_USED, + isReturnValueProblem: true, + }); + } + } + } + } + } catch (err) { + throw new WorkboxError('plugin-error-request-will-fetch', { + thrownError: err, + }); + } + + // The request can be altered by plugins with `requestWillFetch` making + // the original request (Most likely from a `fetch` event) to be different + // to the Request we make. Pass both to `fetchDidFail` to aid debugging. + const pluginFilteredRequest = request.clone(); + + try { + const fetchResponse = await fetch(request, fetchOptions); + if (process.env.NODE_ENV !== 'production') { + logger.debug(`Network request for `+ + `'${getFriendlyURL(request.url)}' returned a response with ` + + `status '${fetchResponse.status}'.`); + } + return fetchResponse; + } catch (error) { + if (process.env.NODE_ENV !== 'production') { + logger.error(`Network request for `+ + `'${getFriendlyURL(request.url)}' threw an error.`, error); + } + + for (let plugin of failedFetchPlugins) { + await plugin[pluginEvents.FETCH_DID_FAIL].call(plugin, { + error, + event, + originalRequest: originalRequest.clone(), + request: pluginFilteredRequest.clone(), + }); + } + + throw error; + } +}; + +const fetchWrapper = { + fetch: wrappedFetch, +}; + +export {fetchWrapper}; diff --git a/node_modules/workbox-core/_private/getFriendlyURL.mjs b/node_modules/workbox-core/_private/getFriendlyURL.mjs new file mode 100644 index 00000000..e7159feb --- /dev/null +++ b/node_modules/workbox-core/_private/getFriendlyURL.mjs @@ -0,0 +1,27 @@ +/* + 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 '../_version.mjs'; + +const getFriendlyURL = (url) => { + const urlObj = new URL(url, location); + if (urlObj.origin === location.origin) { + return urlObj.pathname; + } + return urlObj.href; +}; + +export {getFriendlyURL}; diff --git a/node_modules/workbox-core/_private/logger.mjs b/node_modules/workbox-core/_private/logger.mjs new file mode 100644 index 00000000..cfa11f8f --- /dev/null +++ b/node_modules/workbox-core/_private/logger.mjs @@ -0,0 +1,95 @@ +/* + 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 LOG_LEVELS from '../models/LogLevels.mjs'; +import '../_version.mjs'; + +// Safari doesn't print all console.groupCollapsed() arguments. +// Related bug: https://bugs.webkit.org/show_bug.cgi?id=182754 +const isSafari = /^((?!chrome|android).)*safari/i.test(navigator.userAgent); + +const GREY = `#7f8c8d`; +const GREEN = `#2ecc71`; +const YELLOW = `#f39c12`; +const RED = `#c0392b`; +const BLUE = `#3498db`; + +const getDefaultLogLevel = () => (process.env.NODE_ENV === 'production') ? + LOG_LEVELS.warn : LOG_LEVELS.log; + +let logLevel = getDefaultLogLevel(); +const shouldPrint = (minLevel) => (logLevel <= minLevel); +const setLoggerLevel = (newLogLevel) => logLevel = newLogLevel; +const getLoggerLevel = () => logLevel; + +// We always want groups to be logged unless logLevel is silent. +const groupLevel = LOG_LEVELS.error; + +const _print = function(keyName, logArgs, levelColor) { + const logLevel = keyName.indexOf('group') === 0 ? + groupLevel : LOG_LEVELS[keyName]; + if (!shouldPrint(logLevel)) { + return; + } + + if (!levelColor || (keyName === 'groupCollapsed' && isSafari)) { + console[keyName](...logArgs); + return; + } + + const logPrefix = [ + '%cworkbox', + `background: ${levelColor}; color: white; padding: 2px 0.5em; ` + + `border-radius: 0.5em;`, + ]; + console[keyName](...logPrefix, ...logArgs); +}; + +const groupEnd = () => { + if (shouldPrint(groupLevel)) { + console.groupEnd(); + } +}; + +const defaultExport = { + groupEnd, + unprefixed: { + groupEnd, + }, +}; + +const setupLogs = (keyName, color) => { + defaultExport[keyName] = + (...args) => _print(keyName, args, color); + defaultExport.unprefixed[keyName] = + (...args) => _print(keyName, args); +}; + +const levelToColor = { + debug: GREY, + log: GREEN, + warn: YELLOW, + error: RED, + groupCollapsed: BLUE, +}; +Object.keys(levelToColor).forEach( + (keyName) => setupLogs(keyName, levelToColor[keyName]) +); + +export {getDefaultLogLevel}; +export {setLoggerLevel}; +export {getLoggerLevel}; +export {defaultExport as logger}; diff --git a/node_modules/workbox-core/_private/quota.mjs b/node_modules/workbox-core/_private/quota.mjs new file mode 100644 index 00000000..98e5d962 --- /dev/null +++ b/node_modules/workbox-core/_private/quota.mjs @@ -0,0 +1,73 @@ +/* + Copyright 2018 Google Inc. All Rights Reserved. + 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 + + http://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 {logger} from './logger.mjs'; +import {assert} from './assert.mjs'; + +import '../_version.mjs'; + +const callbacks = new Set(); + +/** + * Adds a function to the set of callbacks that will be executed when there's + * a quota error. + * + * @param {Function} callback + * @memberof workbox.core + */ +function registerQuotaErrorCallback(callback) { + if (process.env.NODE_ENV !== 'production') { + assert.isType(callback, 'function', { + moduleName: 'workbox-core', + funcName: 'register', + paramName: 'callback', + }); + } + + callbacks.add(callback); + + if (process.env.NODE_ENV !== 'production') { + logger.log('Registered a callback to respond to quota errors.', callback); + } +} + +/** + * Runs all of the callback functions, one at a time sequentially, in the order + * in which they were registered. + * + * @memberof workbox.core + * @private + */ +async function executeQuotaErrorCallbacks() { + if (process.env.NODE_ENV !== 'production') { + logger.log(`About to run ${callbacks.size} callbacks to clean up caches.`); + } + + for (const callback of callbacks) { + await callback(); + if (process.env.NODE_ENV !== 'production') { + logger.log(callback, 'is complete.'); + } + } + + if (process.env.NODE_ENV !== 'production') { + logger.log('Finished running callbacks.'); + } +} + +export { + executeQuotaErrorCallbacks, + registerQuotaErrorCallback, +}; |
