applying transfer to react app

This commit is contained in:
Tyler Koenig
2021-09-20 16:54:47 -04:00
parent 8819f31dd0
commit c612b7d702
37373 changed files with 3775588 additions and 2871 deletions
+46
View File
@@ -0,0 +1,46 @@
/*
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.
*/
// We either expose defaults or we expose every named export.
import {assert} from './_private/assert.js';
import {cacheNames} from './_private/cacheNames.js';
import {cacheWrapper} from './_private/cacheWrapper.js';
import {canConstructReadableStream} from './_private/canConstructReadableStream.js';
import {canConstructResponseFromBodyStream} from './_private/canConstructResponseFromBodyStream.js';
import {dontWaitFor} from './_private/dontWaitFor.js';
import {DBWrapper} from './_private/DBWrapper.js';
import {Deferred} from './_private/Deferred.js';
import {deleteDatabase} from './_private/deleteDatabase.js';
import {executeQuotaErrorCallbacks} from './_private/executeQuotaErrorCallbacks.js';
import {fetchWrapper} from './_private/fetchWrapper.js';
import {getFriendlyURL} from './_private/getFriendlyURL.js';
import {logger} from './_private/logger.js';
import {resultingClientExists} from './_private/resultingClientExists.js';
import {timeout} from './_private/timeout.js';
import {WorkboxError} from './_private/WorkboxError.js';
import './_version.js';
export {
assert,
cacheNames,
cacheWrapper,
canConstructReadableStream,
canConstructResponseFromBodyStream,
dontWaitFor,
DBWrapper,
Deferred,
deleteDatabase,
executeQuotaErrorCallbacks,
fetchWrapper,
getFriendlyURL,
logger,
resultingClientExists,
timeout,
WorkboxError,
};
+325
View File
@@ -0,0 +1,325 @@
/*
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.
*/
import '../_version.js';
type IDBObjectStoreMethods = 'get' | 'count' | 'getKey' | 'getAll' |
'getAllKeys' | 'add' | 'put' | 'clear' | 'delete';
type Query = IDBValidKey | IDBKeyRange | null;
interface DBWrapperOptions {
onupgradeneeded?: (event: IDBVersionChangeEvent) => any;
onversionchange?: (event: IDBVersionChangeEvent) => any;
}
interface GetAllMatchingOptions {
index?: string;
query?: Query;
direction?: IDBCursorDirection;
count?: number;
includeKeys?: boolean;
}
/**
* 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
*/
export class DBWrapper {
private readonly _name: string;
private readonly _version: number;
private readonly _onupgradeneeded?: DBWrapperOptions['onupgradeneeded'];
private readonly _onversionchange: DBWrapperOptions['onversionchange'];
private _db: IDBDatabase | null = null;
// The following IDBObjectStore methods are shadowed on this class.
add?: Function;
clear?: Function;
count?: Function;
delete?: Function;
get?: Function;
put?: Function;
OPEN_TIMEOUT?: number;
/**
* @param {string} name
* @param {number} version
* @param {Object=} [callback]
* @param {!Function} [callbacks.onupgradeneeded]
* @param {!Function} [callbacks.onversionchange] Defaults to
* DBWrapper.prototype._onversionchange when not specified.
* @private
*/
constructor(name: string, version: number, {
onupgradeneeded,
onversionchange,
}: DBWrapperOptions = {}) {
this._name = name;
this._version = version;
this._onupgradeneeded = onupgradeneeded;
this._onversionchange = onversionchange || (() => this.close());
}
/**
* Returns the IDBDatabase instance (not normally needed).
* @return {IDBDatabase|undefined}
*
* @private
*/
get db(): IDBDatabase | null {
return this._db;
}
/**
* 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 = () => reject(openRequest.error);
openRequest.onupgradeneeded = (evt: IDBVersionChangeEvent) => {
if (openRequestTimedOut) {
openRequest.transaction!.abort();
openRequest.result.close();
} else if (typeof this._onupgradeneeded === 'function') {
this._onupgradeneeded(evt);
}
};
openRequest.onsuccess = () => {
const db = openRequest.result;
if (openRequestTimedOut) {
db.close();
} else {
db.onversionchange = this._onversionchange!.bind(this);
resolve(db);
}
};
});
return this;
}
/**
* Polyfills the native `getKey()` method. Note, this is overridden at
* runtime if the browser supports the native method.
*
* @param {string} storeName
* @param {*} query
* @return {Array}
* @private
*/
async getKey(storeName: string, query: Query) {
return (await this.getAllKeys(storeName, query, 1))[0];
}
/**
* Polyfills the native `getAll()` method. Note, this is overridden at
* runtime if the browser supports the native method.
*
* @param {string} storeName
* @param {*} query
* @param {number} count
* @return {Array}
* @private
*/
async getAll(storeName: string, query?: Query, count?: number) {
return await this.getAllMatching(storeName, {query, count});
}
/**
* Polyfills the native `getAllKeys()` method. Note, this is overridden at
* runtime if the browser supports the native method.
*
* @param {string} storeName
* @param {*} query
* @param {number} count
* @return {Array}
* @private
*/
async getAllKeys(storeName: string, query: Query, count: number) {
const entries = await this.getAllMatching(
storeName, {query, count, includeKeys: true})
return entries.map((entry: IDBCursor) => entry.key);
}
/**
* 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 {string} [opts.index] The index to use (if specified).
* @param {*} [opts.query]
* @param {IDBCursorDirection} [opts.direction]
* @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: string, {
index,
query = null, // IE/Edge errors if query === `undefined`.
direction = 'next',
count,
includeKeys = false,
}: GetAllMatchingOptions = {}): Promise<Array<IDBCursor | any>> {
return await this.transaction([storeName], 'readonly', (txn, done) => {
const store = txn.objectStore(storeName);
const target = index ? store.index(index) : store;
const results: any[] = [];
const request = target.openCursor(query, direction);
request.onsuccess = () => {
const cursor = request.result;
if (cursor) {
results.push(includeKeys ? cursor : cursor.value);
if (count && results.length >= 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 two arguments:
* 1. The IDBTransaction object
* 2. A `done` function, that's used to resolve the promise when
* when the transaction is done, if passed a value, the promise is
* resolved to that value.
*
* @param {Array<string>} storeNames An array of object store names
* involved in the transaction.
* @param {string} type Can be `readonly` or `readwrite`.
* @param {!Function} callback
* @return {*} The result of the transaction ran by the callback.
* @private
*/
async transaction(
storeNames: string | string[],
type: IDBTransactionMode,
callback: (txn: IDBTransaction, done: Function) => void,
): Promise<any> {
await this.open();
return await new Promise((resolve, reject) => {
const txn = this._db!.transaction(storeNames, type);
txn.onabort = () => reject(txn.error);
txn.oncomplete = () => resolve();
callback(txn, (value: any) => resolve(value));
});
}
/**
* 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: IDBObjectStoreMethods,
storeName: string,
type: IDBTransactionMode,
...args: any[]
) {
const callback = (txn: IDBTransaction, done: Function) => {
const objStore = txn.objectStore(storeName);
// TODO(philipwalton): Fix this underlying TS2684 error.
// @ts-ignore
const request = objStore[method].apply(objStore, args);
request.onsuccess = () => done(request.result);
};
return await this.transaction([storeName], type, callback);
}
/**
* 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();
this._db = null;
}
}
}
// Exposed on the prototype to let users modify the default timeout on a
// per-instance or global basis.
DBWrapper.prototype.OPEN_TIMEOUT = 2000;
// Wrap native IDBObjectStore methods according to their mode.
const methodsToWrap = {
readonly: ['get', 'count', 'getKey', 'getAll', 'getAllKeys'],
readwrite: ['add', 'put', 'clear', 'delete'],
};
for (const [mode, methods] of Object.entries(methodsToWrap)) {
for (const method of methods) {
if (method in IDBObjectStore.prototype) {
// Don't use arrow functions here since we're outside of the class.
DBWrapper.prototype[method as IDBObjectStoreMethods] =
async function(storeName: string, ...args: any[]) {
return await this._call(
method as IDBObjectStoreMethods,
storeName,
mode as IDBTransactionMode,
...args);
};
}
}
}
+37
View File
@@ -0,0 +1,37 @@
/*
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.
*/
import '../_version.js';
/**
* The Deferred class composes Promises in a way that allows for them to be
* resolved or rejected from outside the constructor. In most cases promises
* should be used directly, but Deferreds can be necessary when the logic to
* resolve a promise must be separate.
*
* @private
*/
class Deferred<T> {
promise: Promise<T>;
resolve!: (value?: T) => void;
reject!: (reason?: any) => void;
/**
* Creates a promise and exposes its resolve and reject functions as methods.
*/
constructor() {
this.promise = new Promise((resolve, reject) => {
this.resolve = resolve;
this.reject = reject;
});
}
}
export {Deferred};
+49
View File
@@ -0,0 +1,49 @@
/*
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.
*/
import {messageGenerator} from '../models/messages/messageGenerator.js';
import '../_version.js';
// TODO(philipwalton): remove once the switch to TypeScript is complete and
// we no longer need the `assert` module.
export interface WorkboxErrorDetails {
[propName: string]: any;
}
/**
* 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 {
name: string;
details?: WorkboxErrorDetails;
/**
*
* @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: string, details?: WorkboxErrorDetails) {
const message = messageGenerator(errorCode, details);
super(message);
this.name = errorCode;
this.details = details;
}
}
export {WorkboxError};
+98
View File
@@ -0,0 +1,98 @@
/*
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.
*/
import {WorkboxError, WorkboxErrorDetails} from '../_private/WorkboxError.js';
import '../_version.js';
/*
* 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: any[],
details: WorkboxErrorDetails,
) => {
if (!Array.isArray(value)) {
throw new WorkboxError('not-an-array', details);
}
};
const hasMethod = (
object: {[key: string]: any},
expectedMethod: string,
details: WorkboxErrorDetails,
) => {
const type = typeof object[expectedMethod];
if (type !== 'function') {
details['expectedMethod'] = expectedMethod;
throw new WorkboxError('missing-a-method', details);
}
};
const isType = (
object: {},
expectedType: string,
details: WorkboxErrorDetails,
) => {
if (typeof object !== expectedType) {
details['expectedType'] = expectedType;
throw new WorkboxError('incorrect-type', details);
}
};
const isInstance = (
object: {},
expectedClass: Function,
details: WorkboxErrorDetails,
) => {
if (!(object instanceof expectedClass)) {
details['expectedClass'] = expectedClass;
throw new WorkboxError('incorrect-class', details);
}
};
const isOneOf = (
value: any,
validValues: any[],
details: WorkboxErrorDetails) => {
if (!validValues.includes(value)) {
details['validValueDescription'] =
`Valid values are ${JSON.stringify(validValues)}.`
throw new WorkboxError('invalid-value', details);
}
};
const isArrayOfClass = (
value: any,
expectedClass: Function,
details: WorkboxErrorDetails,
) => {
const error = new WorkboxError('not-array-of-class', details);
if (!Array.isArray(value)) {
throw error;
}
for (const item of value) {
if (!(item instanceof expectedClass)) {
throw error;
}
}
};
const finalAssertExports = process.env.NODE_ENV === 'production' ? null : {
hasMethod,
isArray,
isInstance,
isOneOf,
isType,
isArrayOfClass,
};
export {finalAssertExports as assert};
+73
View File
@@ -0,0 +1,73 @@
/*
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.
*/
import '../_version.js';
declare let registration: ServiceWorkerRegistration|undefined;
export interface CacheNameDetails {
googleAnalytics: string;
precache: string;
prefix: string;
runtime: string;
suffix: string;
}
export interface PartialCacheNameDetails {
[propName: string]: string;
}
export type CacheNameDetailsProp =
'googleAnalytics' | 'precache' | 'prefix' | 'runtime' | 'suffix';
const _cacheNameDetails: CacheNameDetails = {
googleAnalytics: 'googleAnalytics',
precache: 'precache-v2',
prefix: 'workbox',
runtime: 'runtime',
suffix: typeof registration !== 'undefined' ? registration.scope : '',
};
const _createCacheName = (cacheName: string): string => {
return [_cacheNameDetails.prefix, cacheName, _cacheNameDetails.suffix]
.filter((value) => value && value.length > 0)
.join('-');
};
const eachCacheNameDetail = (fn: Function): void => {
for (const key of Object.keys(_cacheNameDetails)) {
fn(key as CacheNameDetailsProp);
}
}
export const cacheNames = {
updateDetails: (details: PartialCacheNameDetails) => {
eachCacheNameDetail((key: CacheNameDetailsProp) => {
if (typeof details[key] === 'string') {
_cacheNameDetails[key] = details[key];
}
})
},
getGoogleAnalyticsName: (userCacheName?: string) => {
return userCacheName || _createCacheName(_cacheNameDetails.googleAnalytics);
},
getPrecacheName: (userCacheName?: string) => {
return userCacheName || _createCacheName(_cacheNameDetails.precache);
},
getPrefix: () => {
return _cacheNameDetails.prefix;
},
getRuntimeName: (userCacheName?: string) => {
return userCacheName || _createCacheName(_cacheNameDetails.runtime);
},
getSuffix: () => {
return _cacheNameDetails.suffix;
},
};
+325
View File
@@ -0,0 +1,325 @@
/*
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.
*/
import {assert} from './assert.js';
import {executeQuotaErrorCallbacks} from './executeQuotaErrorCallbacks.js';
import {getFriendlyURL} from './getFriendlyURL.js';
import {logger} from './logger.js';
import {pluginEvents} from '../models/pluginEvents.js';
import {pluginUtils} from '../utils/pluginUtils.js';
import {WorkboxError} from './WorkboxError.js';
import {WorkboxPlugin} from '../types.js';
import '../_version.js';
interface MatchWrapperOptions {
cacheName: string;
request: Request;
event?: Event;
plugins?: WorkboxPlugin[];
matchOptions?: CacheQueryOptions;
}
interface PutWrapperOptions extends MatchWrapperOptions {
response: Response;
}
interface GetEffectiveRequestOptions {
request: Request;
mode: string;
plugins?: WorkboxPlugin[];
}
/**
* Checks the list of plugins for the cacheKeyWillBeUsed callback, and
* executes any of those callbacks found in sequence. The final `Request` object
* returned by the last plugin is treated as the cache key for cache reads
* and/or writes.
*
* @param {Object} options
* @param {Request} options.request
* @param {string} options.mode
* @param {Array<Object>} [options.plugins=[]]
* @return {Promise<Request>}
*
* @private
* @memberof module:workbox-core
*/
const _getEffectiveRequest = async ({
request,
mode,
plugins = [],
}: GetEffectiveRequestOptions) => {
const cacheKeyWillBeUsedPlugins = pluginUtils.filter(
plugins, pluginEvents.CACHE_KEY_WILL_BE_USED);
let effectiveRequest: Request | string = request;
for (const plugin of cacheKeyWillBeUsedPlugins) {
effectiveRequest = await plugin[pluginEvents.CACHE_KEY_WILL_BE_USED]!.call(
plugin, {mode, request: effectiveRequest});
if (typeof effectiveRequest === 'string') {
effectiveRequest = new Request(effectiveRequest);
}
if (process.env.NODE_ENV !== 'production') {
assert!.isInstance(effectiveRequest, Request, {
moduleName: 'Plugin',
funcName: pluginEvents.CACHE_KEY_WILL_BE_USED,
isReturnValueProblem: true,
});
}
}
return effectiveRequest;
};
/**
* This method will call cacheWillUpdate on the available plugins (or use
* status === 200) 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 = [],
}: {
request: Request;
response: Response;
event?: Event;
plugins?: WorkboxPlugin[];
}) => {
let responseToCache: Response|undefined = response;
let pluginsUsed = false;
for (const plugin of plugins) {
if (pluginEvents.CACHE_WILL_UPDATE in plugin) {
pluginsUsed = true;
const pluginMethod = plugin[pluginEvents.CACHE_WILL_UPDATE] as Function;
responseToCache = await pluginMethod.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) {
if (responseToCache.status !== 200) {
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 && responseToCache.status === 200 ?
responseToCache : undefined;
}
return responseToCache ? responseToCache : null;
};
/**
* 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 prompted 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 = [],
}: MatchWrapperOptions): Promise<Response|undefined> => {
const cache = await self.caches.open(cacheName);
const effectiveRequest = await _getEffectiveRequest({
plugins, request, mode: 'read'});
let cachedResponse = await cache.match(effectiveRequest, 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 (const plugin of plugins) {
if (pluginEvents.CACHED_RESPONSE_WILL_BE_USED in plugin) {
const pluginMethod =
plugin[pluginEvents.CACHED_RESPONSE_WILL_BE_USED] as Function;
cachedResponse = await pluginMethod.call(plugin, {
cacheName,
event,
matchOptions,
cachedResponse,
request: effectiveRequest,
});
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;
};
/**
* Wrapper around cache.put().
*
* Will call `cacheDidUpdate` on plugins if the cache was updated, using
* `matchOptions` when determining what the old entry is.
*
* @param {Object} options
* @param {string} options.cacheName
* @param {Request} options.request
* @param {Response} options.response
* @param {Event} [options.event]
* @param {Array<Object>} [options.plugins=[]]
* @param {Object} [options.matchOptions]
*
* @private
* @memberof module:workbox-core
*/
const putWrapper = async ({
cacheName,
request,
response,
event,
plugins = [],
matchOptions,
}: PutWrapperOptions): Promise<void> => {
if (process.env.NODE_ENV !== 'production') {
if (request.method && request.method !== 'GET') {
throw new WorkboxError('attempt-to-cache-non-get-request', {
url: getFriendlyURL(request.url),
method: request.method,
});
}
}
const effectiveRequest = await _getEffectiveRequest({
plugins, request, mode: 'write'});
if (!response) {
if (process.env.NODE_ENV !== 'production') {
logger.error(`Cannot cache non-existent response for ` +
`'${getFriendlyURL(effectiveRequest.url)}'.`);
}
throw new WorkboxError('cache-put-with-no-response', {
url: getFriendlyURL(effectiveRequest.url),
});
}
const responseToCache = await _isResponseSafeToCache({
event,
plugins,
response,
request: effectiveRequest,
});
if (!responseToCache) {
if (process.env.NODE_ENV !== 'production') {
logger.debug(`Response '${getFriendlyURL(effectiveRequest.url)}' will ` +
`not be cached.`, responseToCache);
}
return;
}
const cache = await self.caches.open(cacheName);
const updatePlugins = pluginUtils.filter(
plugins, pluginEvents.CACHE_DID_UPDATE);
const oldResponse = updatePlugins.length > 0 ?
await matchWrapper({cacheName, matchOptions, request: effectiveRequest}) :
null;
if (process.env.NODE_ENV !== 'production') {
logger.debug(`Updating the '${cacheName}' cache with a new Response for ` +
`${getFriendlyURL(effectiveRequest.url)}.`);
}
try {
await cache.put(effectiveRequest, 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 (const plugin of updatePlugins) {
await plugin[pluginEvents.CACHE_DID_UPDATE]!.call(plugin, {
cacheName,
event,
oldResponse,
newResponse: responseToCache,
request: effectiveRequest,
});
}
};
export const cacheWrapper = {
put: putWrapper,
match: matchWrapper,
};
+38
View File
@@ -0,0 +1,38 @@
/*
Copyright 2019 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.
*/
import '../_version.js';
let supportStatus: boolean | undefined;
/**
* A utility function that determines whether the current browser supports
* constructing a [`ReadableStream`](https://developer.mozilla.org/en-US/docs/Web/API/ReadableStream/ReadableStream)
* object.
*
* @return {boolean} `true`, if the current browser can successfully
* construct a `ReadableStream`, `false` otherwise.
*
* @private
*/
function canConstructReadableStream(): boolean {
if (supportStatus === undefined) {
// See https://github.com/GoogleChrome/workbox/issues/1473
try {
new ReadableStream({start() {}});
supportStatus = true;
} catch (error) {
supportStatus = false;
}
}
return supportStatus;
}
export {canConstructReadableStream};
@@ -0,0 +1,41 @@
/*
Copyright 2019 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.
*/
import '../_version.js';
let supportStatus: boolean | undefined;
/**
* A utility function that determines whether the current browser supports
* constructing a new `Response` from a `response.body` stream.
*
* @return {boolean} `true`, if the current browser can successfully
* construct a `Response` from a `response.body` stream, `false` otherwise.
*
* @private
*/
function canConstructResponseFromBodyStream(): boolean {
if (supportStatus === undefined) {
const testResponse = new Response('');
if ('body' in testResponse) {
try {
new Response(testResponse.body);
supportStatus = true;
} catch (error) {
supportStatus = false;
}
}
supportStatus = false;
}
return supportStatus;
}
export {canConstructResponseFromBodyStream};
+34
View File
@@ -0,0 +1,34 @@
/*
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.
*/
import '../_version.js';
/**
* Deletes the database.
* Note: this is exported separately from the DBWrapper module because most
* usages of IndexedDB in workbox dont need deleting, and this way it can be
* reused in tests to delete databases without creating DBWrapper instances.
*
* @param {string} name The database name.
* @private
*/
export const deleteDatabase = async (name: string) => {
await new Promise((resolve, reject) => {
const request = indexedDB.deleteDatabase(name);
request.onerror = () => {
reject(request.error);
};
request.onblocked = () => {
reject(new Error('Delete blocked'));
};
request.onsuccess = () => {
resolve();
};
});
};
+18
View File
@@ -0,0 +1,18 @@
/*
Copyright 2019 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.
*/
import '../_version.js';
/**
* A helper function that prevents a promise from being flagged as unused.
*
* @private
**/
export function dontWaitFor(promise: Promise<any>) {
// Effective no-op.
promise.then(() => {});
}
+39
View File
@@ -0,0 +1,39 @@
/*
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.
*/
import {logger} from '../_private/logger.js';
import {quotaErrorCallbacks} from '../models/quotaErrorCallbacks.js';
import '../_version.js';
/**
* Runs all of the callback functions, one at a time sequentially, in the order
* in which they were registered.
*
* @memberof module:workbox-core
* @private
*/
async function executeQuotaErrorCallbacks() {
if (process.env.NODE_ENV !== 'production') {
logger.log(`About to run ${quotaErrorCallbacks.size} ` +
`callbacks to clean up caches.`);
}
for (const callback of quotaErrorCallbacks) {
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};
+179
View File
@@ -0,0 +1,179 @@
/*
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.
*/
import {WorkboxError} from './WorkboxError.js';
import {logger} from './logger.js';
import {assert} from './assert.js';
import {getFriendlyURL} from '../_private/getFriendlyURL.js';
import {pluginEvents} from '../models/pluginEvents.js';
import {WorkboxPlugin} from '../types.js';
import {pluginUtils} from '../utils/pluginUtils.js';
import '../_version.js';
interface WrappedFetchOptions {
request: Request | string;
event?: ExtendableEvent;
plugins?: WorkboxPlugin[];
fetchOptions?: RequestInit;
}
/**
* Wrapper around the fetch API.
*
* Will call requestWillFetch on available plugins.
*
* @param {Object} options
* @param {Request|string} options.request
* @param {Object} [options.fetchOptions]
* @param {ExtendableEvent} [options.event]
* @param {Array<Object>} [options.plugins=[]]
* @return {Promise<Response>}
*
* @private
* @memberof module:workbox-core
*/
const wrappedFetch = async ({
request,
fetchOptions,
event,
plugins = [],
}: WrappedFetchOptions) => {
if (typeof request === 'string') {
request = new Request(request);
}
// 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 instanceof FetchEvent && 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 (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 (const plugin of plugins) {
if (pluginEvents.REQUEST_WILL_FETCH in plugin) {
const pluginMethod = plugin[pluginEvents.REQUEST_WILL_FETCH]!;
const requestClone = request.clone();
request = await pluginMethod.call(plugin, {
request: requestClone,
event,
}) as Request;
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 {
let fetchResponse;
// See https://github.com/GoogleChrome/workbox/issues/1796
if (request.mode === 'navigate') {
fetchResponse = await fetch(request);
} else {
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}'.`);
}
for (const plugin of plugins) {
if (pluginEvents.FETCH_DID_SUCCEED in plugin) {
fetchResponse = await plugin[pluginEvents.FETCH_DID_SUCCEED]!
.call(plugin, {
event,
request: pluginFilteredRequest,
response: fetchResponse,
});
if (process.env.NODE_ENV !== 'production') {
if (fetchResponse) {
assert!.isInstance(fetchResponse, Response, {
moduleName: 'Plugin',
funcName: pluginEvents.FETCH_DID_SUCCEED,
isReturnValueProblem: true,
});
}
}
}
}
return fetchResponse;
} catch (error) {
if (process.env.NODE_ENV !== 'production') {
logger.error(`Network request for `+
`'${getFriendlyURL(request.url)}' threw an error.`, error);
}
for (const 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};
+18
View File
@@ -0,0 +1,18 @@
/*
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.
*/
import '../_version.js';
const getFriendlyURL = (url: URL | string) => {
const urlObj = new URL(String(url), location.href);
// See https://github.com/GoogleChrome/workbox/issues/2323
// We want to include everything, except for the origin if it's same-origin.
return urlObj.href.replace(new RegExp(`^${location.origin}`), '');
};
export {getFriendlyURL};
+90
View File
@@ -0,0 +1,90 @@
/*
Copyright 2019 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.
*/
import '../_version.js';
// logger is used inside of both service workers and the window global scope.
declare global {
interface WorkerGlobalScope {
__WB_DISABLE_DEV_LOGS: boolean;
}
interface Window {
__WB_DISABLE_DEV_LOGS: boolean;
}
}
type LoggerMethods = 'debug'|'log'|'warn'|'error'|'groupCollapsed'|'groupEnd';
const logger = (process.env.NODE_ENV === 'production' ? null : (() => {
// Don't overwrite this value if it's already set.
// See https://github.com/GoogleChrome/workbox/pull/2284#issuecomment-560470923
if (!('__WB_DISABLE_DEV_LOGS' in self)) {
self.__WB_DISABLE_DEV_LOGS = false;
}
let inGroup = false;
const methodToColorMap: {[methodName: string]: string|null} = {
debug: `#7f8c8d`, // Gray
log: `#2ecc71`, // Green
warn: `#f39c12`, // Yellow
error: `#c0392b`, // Red
groupCollapsed: `#3498db`, // Blue
groupEnd: null, // No colored prefix on groupEnd
};
const print = function(method: LoggerMethods, args: any[]) {
if (self.__WB_DISABLE_DEV_LOGS) {
return;
}
if (method === 'groupCollapsed') {
// Safari doesn't print all console.groupCollapsed() arguments:
// https://bugs.webkit.org/show_bug.cgi?id=182754
if (/^((?!chrome|android).)*safari/i.test(navigator.userAgent)) {
console[method](...args);
return;
}
}
const styles = [
`background: ${methodToColorMap[method]}`,
`border-radius: 0.5em`,
`color: white`,
`font-weight: bold`,
`padding: 2px 0.5em`,
];
// When in a group, the workbox prefix is not displayed.
const logPrefix = inGroup ? [] : ['%cworkbox', styles.join(';')];
console[method](...logPrefix, ...args);
if (method === 'groupCollapsed') {
inGroup = true;
}
if (method === 'groupEnd') {
inGroup = false;
}
};
const api: {[methodName: string]: Function} = {};
const loggerMethods = Object.keys(methodToColorMap);
for (const key of loggerMethods) {
const method = key as LoggerMethods;
api[method] = (...args: any[]) => {
print(method, args);
};
}
return api as unknown;
})()) as Console;
export {logger};
+61
View File
@@ -0,0 +1,61 @@
/*
Copyright 2019 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.
*/
import {timeout} from './timeout.js';
import '../_version.js';
// Give TypeScript the correct global.
declare let self: ServiceWorkerGlobalScope;
const MAX_RETRY_TIME = 2000;
/**
* Returns a promise that resolves to a window client matching the passed
* `resultingClientId`. For browsers that don't support `resultingClientId`
* or if waiting for the resulting client to apper takes too long, resolve to
* `undefined`.
*
* @param {string} [resultingClientId]
* @return {Promise<Client|undefined>}
* @private
*/
export async function resultingClientExists(resultingClientId?: string): Promise<Client | undefined> {
if (!resultingClientId) {
return;
}
let existingWindows = await self.clients.matchAll({type: 'window'});
const existingWindowIds = new Set(existingWindows.map((w) => w.id));
let resultingWindow;
const startTime = performance.now();
// Only wait up to `MAX_RETRY_TIME` to find a matching client.
while (performance.now() - startTime < MAX_RETRY_TIME) {
existingWindows = await self.clients.matchAll({type: 'window'});
resultingWindow = existingWindows.find((w) => {
if (resultingClientId) {
// If we have a `resultingClientId`, we can match on that.
return w.id === resultingClientId
} else {
// Otherwise match on finding a window not in `existingWindowIds`.
return !existingWindowIds.has(w.id)
}
});
if (resultingWindow) {
break;
}
// Sleep for 100ms and retry.
await timeout(100);
}
return resultingWindow;
}
+22
View File
@@ -0,0 +1,22 @@
/*
Copyright 2019 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.
*/
import '../_version.js';
/**
* Returns a promise that resolves and the passed number of milliseconds.
* This utility is an async/await-friendly version of `setTimeout`.
*
* @param {number} ms
* @return {Promise}
* @private
*/
export function timeout(ms: number) {
return new Promise((resolve) => setTimeout(resolve, ms));
}
+2
View File
@@ -0,0 +1,2 @@
// @ts-ignore
try{self['workbox:core:5.1.4']&&_()}catch(e){}
+46
View File
@@ -0,0 +1,46 @@
/*
Copyright 2019 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.
*/
import {cacheNames as _cacheNames} from './_private/cacheNames.js';
import './_version.js';
/**
* Get the current cache names and prefix/suffix used by Workbox.
*
* `cacheNames.precache` is used for precached assets,
* `cacheNames.googleAnalytics` is used by `workbox-google-analytics` to
* store `analytics.js`, and `cacheNames.runtime` is used for everything else.
*
* `cacheNames.prefix` can be used to retrieve just the current prefix value.
* `cacheNames.suffix` can be used to retrieve just the current suffix value.
*
* @return {Object} An object with `precache`, `runtime`, `prefix`, and
* `googleAnalytics` properties.
*
* @memberof module:workbox-core
*/
const cacheNames = {
get googleAnalytics() {
return _cacheNames.getGoogleAnalyticsName();
},
get precache() {
return _cacheNames.getPrecacheName();
},
get prefix() {
return _cacheNames.getPrefix();
},
get runtime() {
return _cacheNames.getRuntimeName();
},
get suffix() {
return _cacheNames.getSuffix();
},
};
export {cacheNames}
+25
View File
@@ -0,0 +1,25 @@
/*
Copyright 2019 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.
*/
import './_version.js';
// Give TypeScript the correct global.
declare let self: ServiceWorkerGlobalScope;
/**
* Claim any currently available clients once the service worker
* becomes active. This is normally used in conjunction with `skipWaiting()`.
*
* @memberof module:workbox-core
*/
function clientsClaim() {
self.addEventListener('activate', () => self.clients.claim());
}
export {clientsClaim}
+54
View File
@@ -0,0 +1,54 @@
/*
Copyright 2019 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.
*/
import {canConstructResponseFromBodyStream} from './_private/canConstructResponseFromBodyStream.js';
import './_version.js';
/**
* Allows developers to copy a response and modify its `headers`, `status`,
* or `statusText` values (the values settable via a
* [`ResponseInit`]{@link https://developer.mozilla.org/en-US/docs/Web/API/Response/Response#Syntax}
* object in the constructor).
* To modify these values, pass a function as the second argument. That
* function will be invoked with a single object with the response properties
* `{headers, status, statusText}`. The return value of this function will
* be used as the `ResponseInit` for the new `Response`. To change the values
* either modify the passed parameter(s) and return it, or return a totally
* new object.
*
* @param {Response} response
* @param {Function} modifier
* @memberof module:workbox-core
*/
async function copyResponse(
response: Response,
modifier?: (responseInit: ResponseInit) => ResponseInit
) {
const clonedResponse = response.clone();
// Create a fresh `ResponseInit` object by cloning the headers.
const responseInit: ResponseInit = {
headers: new Headers(clonedResponse.headers),
status: clonedResponse.status,
statusText: clonedResponse.statusText,
}
// Apply any user modifications.
const modifiedResponseInit = modifier ? modifier(responseInit) : responseInit;
// Create the new response from the body stream and `ResponseInit`
// modifications. Note: not all browsers support the Response.body stream,
// so fall back to reading the entire body into memory as a blob.
const body = canConstructResponseFromBodyStream() ?
clonedResponse.body : await clonedResponse.blob();
return new Response(body, modifiedResponseInit);
}
export {copyResponse}
+36
View File
@@ -0,0 +1,36 @@
/*
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.
*/
import {registerQuotaErrorCallback} from './registerQuotaErrorCallback.js';
import * as _private from './_private.js';
import {cacheNames} from './cacheNames.js';
import {copyResponse} from './copyResponse.js';
import {clientsClaim} from './clientsClaim.js';
import {setCacheNameDetails} from './setCacheNameDetails.js';
import {skipWaiting} from './skipWaiting.js';
import './_version.js';
/**
* All of the Workbox service worker libraries use workbox-core for shared
* code as well as setting default values that need to be shared (like cache
* names).
*
* @module workbox-core
*/
export {
_private,
cacheNames,
clientsClaim,
copyResponse,
registerQuotaErrorCallback,
setCacheNameDetails,
skipWaiting,
};
export * from './types.js';
+30
View File
@@ -0,0 +1,30 @@
/*
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.
*/
import {messages} from './messages.js';
import '../../_version.js';
const fallback = (code: string, ...args: any[]) => {
let msg = code;
if (args.length > 0) {
msg += ` :: ${JSON.stringify(args)}`;
}
return msg;
};
const generatorFunction = (code: string, details = {}) => {
const message = messages[code];
if (!message) {
throw new Error(`Unable to find message for code '${code}'.`);
}
return message(details);
};
export const messageGenerator = (process.env.NODE_ENV === 'production') ?
fallback : generatorFunction;
+267
View File
@@ -0,0 +1,267 @@
/*
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.
*/
import '../../_version.js';
interface MessageParam {
[key: string]: any;
}
interface MessageMap {
[messageID: string]: (param: MessageParam) => string;
}
export const messages: MessageMap = {
'invalid-value': ({paramName, validValueDescription, value}) => {
if (!paramName || !validValueDescription) {
throw new Error(`Unexpected input to 'invalid-value' error.`);
}
return `The '${paramName}' parameter was given a value with an ` +
`unexpected value. ${validValueDescription} Received a value of ` +
`${JSON.stringify(value)}.`;
},
'not-an-array': ({moduleName, className, funcName, paramName}) => {
if (!moduleName || !className || !funcName || !paramName) {
throw new Error(`Unexpected input to 'not-an-array' error.`);
}
return `The parameter '${paramName}' passed into ` +
`'${moduleName}.${className}.${funcName}()' must be an array.`;
},
'incorrect-type': ({expectedType, paramName, moduleName, className,
funcName}) => {
if (!expectedType || !paramName || !moduleName || !funcName) {
throw new Error(`Unexpected input to 'incorrect-type' error.`);
}
return `The parameter '${paramName}' passed into ` +
`'${moduleName}.${className ? (className + '.') : ''}` +
`${funcName}()' must be of type ${expectedType}.`;
},
'incorrect-class': ({expectedClass, paramName, moduleName, className,
funcName, isReturnValueProblem}) => {
if (!expectedClass || !moduleName || !funcName) {
throw new Error(`Unexpected input to 'incorrect-class' error.`);
}
if (isReturnValueProblem) {
return `The return value from ` +
`'${moduleName}.${className ? (className + '.') : ''}${funcName}()' ` +
`must be an instance of class ${expectedClass.name}.`;
}
return `The parameter '${paramName}' passed into ` +
`'${moduleName}.${className ? (className + '.') : ''}${funcName}()' ` +
`must be an instance of class ${expectedClass.name}.`;
},
'missing-a-method': ({expectedMethod, paramName, moduleName, className,
funcName}) => {
if (!expectedMethod || !paramName || !moduleName || !className
|| !funcName) {
throw new Error(`Unexpected input to 'missing-a-method' error.`);
}
return `${moduleName}.${className}.${funcName}() expected the ` +
`'${paramName}' parameter to expose a '${expectedMethod}' method.`;
},
'add-to-cache-list-unexpected-type': ({entry}) => {
return `An unexpected entry was passed to ` +
`'workbox-precaching.PrecacheController.addToCacheList()' The entry ` +
`'${JSON.stringify(entry)}' isn't supported. You must supply an array of ` +
`strings with one or more characters, objects with a url property or ` +
`Request objects.`;
},
'add-to-cache-list-conflicting-entries': ({firstEntry, secondEntry}) => {
if (!firstEntry || !secondEntry) {
throw new Error(`Unexpected input to ` +
`'add-to-cache-list-duplicate-entries' error.`);
}
return `Two of the entries passed to ` +
`'workbox-precaching.PrecacheController.addToCacheList()' had the URL ` +
`${firstEntry._entryId} but different revision details. Workbox is ` +
`unable to cache and version the asset correctly. Please remove one ` +
`of the entries.`;
},
'plugin-error-request-will-fetch': ({thrownError}) => {
if (!thrownError) {
throw new Error(`Unexpected input to ` +
`'plugin-error-request-will-fetch', error.`);
}
return `An error was thrown by a plugins 'requestWillFetch()' method. ` +
`The thrown error message was: '${thrownError.message}'.`;
},
'invalid-cache-name': ({cacheNameId, value}) => {
if (!cacheNameId) {
throw new Error(
`Expected a 'cacheNameId' for error 'invalid-cache-name'`);
}
return `You must provide a name containing at least one character for ` +
`setCacheDetails({${cacheNameId}: '...'}). Received a value of ` +
`'${JSON.stringify(value)}'`;
},
'unregister-route-but-not-found-with-method': ({method}) => {
if (!method) {
throw new Error(`Unexpected input to ` +
`'unregister-route-but-not-found-with-method' error.`);
}
return `The route you're trying to unregister was not previously ` +
`registered for the method type '${method}'.`;
},
'unregister-route-route-not-registered': () => {
return `The route you're trying to unregister was not previously ` +
`registered.`;
},
'queue-replay-failed': ({name}) => {
return `Replaying the background sync queue '${name}' failed.`;
},
'duplicate-queue-name': ({name}) => {
return `The Queue name '${name}' is already being used. ` +
`All instances of backgroundSync.Queue must be given unique names.`;
},
'expired-test-without-max-age': ({methodName, paramName}) => {
return `The '${methodName}()' method can only be used when the ` +
`'${paramName}' is used in the constructor.`;
},
'unsupported-route-type': ({moduleName, className, funcName, paramName}) => {
return `The supplied '${paramName}' parameter was an unsupported type. ` +
`Please check the docs for ${moduleName}.${className}.${funcName} for ` +
`valid input types.`;
},
'not-array-of-class': ({value, expectedClass,
moduleName, className, funcName, paramName}) => {
return `The supplied '${paramName}' parameter must be an array of ` +
`'${expectedClass}' objects. Received '${JSON.stringify(value)},'. ` +
`Please check the call to ${moduleName}.${className}.${funcName}() ` +
`to fix the issue.`;
},
'max-entries-or-age-required': ({moduleName, className, funcName}) => {
return `You must define either config.maxEntries or config.maxAgeSeconds` +
`in ${moduleName}.${className}.${funcName}`;
},
'statuses-or-headers-required': ({moduleName, className, funcName}) => {
return `You must define either config.statuses or config.headers` +
`in ${moduleName}.${className}.${funcName}`;
},
'invalid-string': ({moduleName, funcName, paramName}) => {
if (!paramName || !moduleName || !funcName) {
throw new Error(`Unexpected input to 'invalid-string' error.`);
}
return `When using strings, the '${paramName}' parameter must start with ` +
`'http' (for cross-origin matches) or '/' (for same-origin matches). ` +
`Please see the docs for ${moduleName}.${funcName}() for ` +
`more info.`;
},
'channel-name-required': () => {
return `You must provide a channelName to construct a ` +
`BroadcastCacheUpdate instance.`;
},
'invalid-responses-are-same-args': () => {
return `The arguments passed into responsesAreSame() appear to be ` +
`invalid. Please ensure valid Responses are used.`;
},
'expire-custom-caches-only': () => {
return `You must provide a 'cacheName' property when using the ` +
`expiration plugin with a runtime caching strategy.`;
},
'unit-must-be-bytes': ({normalizedRangeHeader}) => {
if (!normalizedRangeHeader) {
throw new Error(`Unexpected input to 'unit-must-be-bytes' error.`);
}
return `The 'unit' portion of the Range header must be set to 'bytes'. ` +
`The Range header provided was "${normalizedRangeHeader}"`;
},
'single-range-only': ({normalizedRangeHeader}) => {
if (!normalizedRangeHeader) {
throw new Error(`Unexpected input to 'single-range-only' error.`);
}
return `Multiple ranges are not supported. Please use a single start ` +
`value, and optional end value. The Range header provided was ` +
`"${normalizedRangeHeader}"`;
},
'invalid-range-values': ({normalizedRangeHeader}) => {
if (!normalizedRangeHeader) {
throw new Error(`Unexpected input to 'invalid-range-values' error.`);
}
return `The Range header is missing both start and end values. At least ` +
`one of those values is needed. The Range header provided was ` +
`"${normalizedRangeHeader}"`;
},
'no-range-header': () => {
return `No Range header was found in the Request provided.`;
},
'range-not-satisfiable': ({size, start, end}) => {
return `The start (${start}) and end (${end}) values in the Range are ` +
`not satisfiable by the cached response, which is ${size} bytes.`;
},
'attempt-to-cache-non-get-request': ({url, method}) => {
return `Unable to cache '${url}' because it is a '${method}' request and ` +
`only 'GET' requests can be cached.`;
},
'cache-put-with-no-response': ({url}) => {
return `There was an attempt to cache '${url}' but the response was not ` +
`defined.`;
},
'no-response': ({url, error}) => {
let message = `The strategy could not generate a response for '${url}'.`;
if (error) {
message += ` The underlying error is ${error}.`;
}
return message;
},
'bad-precaching-response': ({url, status}) => {
return `The precaching request for '${url}' failed with an HTTP ` +
`status of ${status}.`;
},
'non-precached-url': ({url}) => {
return `createHandlerBoundToURL('${url}') was called, but that URL is ` +
`not precached. Please pass in a URL that is precached instead.`;
},
'add-to-cache-list-conflicting-integrities': ({url}) => {
return `Two of the entries passed to ` +
`'workbox-precaching.PrecacheController.addToCacheList()' had the URL ` +
`${url} with different integrity values. Please remove one of them.`;
},
'missing-precache-entry': ({cacheName, url}) => {
return `Unable to find a precached response in ${cacheName} for ${url}.`;
},
};
+20
View File
@@ -0,0 +1,20 @@
/*
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.
*/
import '../_version.js';
export const enum pluginEvents {
CACHE_DID_UPDATE = 'cacheDidUpdate',
CACHE_KEY_WILL_BE_USED = 'cacheKeyWillBeUsed',
CACHE_WILL_UPDATE = 'cacheWillUpdate',
CACHED_RESPONSE_WILL_BE_USED = 'cachedResponseWillBeUsed',
FETCH_DID_FAIL = 'fetchDidFail',
FETCH_DID_SUCCEED = 'fetchDidSucceed',
REQUEST_WILL_FETCH = 'requestWillFetch',
}
+15
View File
@@ -0,0 +1,15 @@
/*
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.
*/
import '../_version.js';
// Callbacks to be executed whenever there's a quota error.
const quotaErrorCallbacks: Set<Function> = new Set();
export {quotaErrorCallbacks};
+38
View File
@@ -0,0 +1,38 @@
/*
Copyright 2019 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.
*/
import {logger} from './_private/logger.js';
import {assert} from './_private/assert.js';
import {quotaErrorCallbacks} from './models/quotaErrorCallbacks.js';
import './_version.js';
/**
* Adds a function to the set of quotaErrorCallbacks that will be executed if
* there's a quota error.
*
* @param {Function} callback
* @memberof module:workbox-core
*/
function registerQuotaErrorCallback(callback: Function) {
if (process.env.NODE_ENV !== 'production') {
assert!.isType(callback, 'function', {
moduleName: 'workbox-core',
funcName: 'register',
paramName: 'callback',
});
}
quotaErrorCallbacks.add(callback);
if (process.env.NODE_ENV !== 'production') {
logger.log('Registered a callback to respond to quota errors.', callback);
}
}
export {registerQuotaErrorCallback};
+67
View File
@@ -0,0 +1,67 @@
/*
Copyright 2019 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.
*/
import {assert} from './_private/assert.js';
import {cacheNames, PartialCacheNameDetails} from './_private/cacheNames.js';
import {WorkboxError} from './_private/WorkboxError.js';
import './_version.js';
/**
* Modifies the default cache names used by the Workbox packages.
* Cache names are generated as `<prefix>-<Cache Name>-<suffix>`.
*
* @param {Object} details
* @param {Object} [details.prefix] The string to add to the beginning of
* the precache and runtime cache names.
* @param {Object} [details.suffix] The string to add to the end of
* the precache and runtime cache names.
* @param {Object} [details.precache] The cache name to use for precache
* caching.
* @param {Object} [details.runtime] The cache name to use for runtime caching.
* @param {Object} [details.googleAnalytics] The cache name to use for
* `workbox-google-analytics` caching.
*
* @memberof module:workbox-core
*/
function setCacheNameDetails(details: PartialCacheNameDetails) {
if (process.env.NODE_ENV !== 'production') {
Object.keys(details).forEach((key) => {
assert!.isType(details[key], 'string', {
moduleName: 'workbox-core',
funcName: 'setCacheNameDetails',
paramName: `details.${key}`,
});
});
if ('precache' in details && details['precache']!.length === 0) {
throw new WorkboxError('invalid-cache-name', {
cacheNameId: 'precache',
value: details['precache'],
});
}
if ('runtime' in details && details['runtime']!.length === 0) {
throw new WorkboxError('invalid-cache-name', {
cacheNameId: 'runtime',
value: details['runtime'],
});
}
if ('googleAnalytics' in details && details['googleAnalytics'].length === 0) {
throw new WorkboxError('invalid-cache-name', {
cacheNameId: 'googleAnalytics',
value: details['googleAnalytics'],
});
}
}
cacheNames.updateDetails(details);
}
export {setCacheNameDetails}
+28
View File
@@ -0,0 +1,28 @@
/*
Copyright 2019 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.
*/
import './_version.js';
// Give TypeScript the correct global.
declare let self: ServiceWorkerGlobalScope;
/**
* Force a service worker to activate immediately, instead of
* [waiting](https://developers.google.com/web/fundamentals/primers/service-workers/lifecycle#waiting)
* for existing clients to close.
*
* @memberof module:workbox-core
*/
function skipWaiting() {
// We need to explicitly call `self.skipWaiting()` here because we're
// shadowing `skipWaiting` with this local function.
self.addEventListener('install', () => self.skipWaiting());
}
export {skipWaiting}
+158
View File
@@ -0,0 +1,158 @@
/*
Copyright 2019 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.
*/
import './_version.js';
/**
* Options passed to a `RouteMatchCallback` function.
*/
export interface RouteMatchCallbackOptions {
url: URL;
request: Request;
event?: ExtendableEvent;
}
/**
* The "match" callback is used to determine if a `Route` should apply for a
* particular URL and request. When matching occurs in response to a fetch
* event from the client, the `event` object is also supplied. However, since
* the match callback can be invoked outside of a fetch event, matching logic
* should not assume the `event` object will always be available.
* If the match callback returns a truthy value, the matching route's
* `RouteHandlerCallback` will be invoked immediately. If the value returned
* is a non-empty array or object, that value will be set on the handler's
* `options.params` argument.
*/
export interface RouteMatchCallback {
(options: RouteMatchCallbackOptions): any;
}
/**
* Options passed to a `RouteHandlerCallback` function.
*/
export interface RouteHandlerCallbackOptions {
request: Request | string;
url?: URL;
event?: ExtendableEvent;
params?: string[] | {[paramName: string]: string};
}
/**
* The "handler" callback is invoked whenever a `Router` matches a URL/Request
* to a `Route` via its `RouteMatchCallback`. This handler callback should
* return a `Promise` that resolves with a `Response`.
*
* If a non-empty array or object is returned by the `RouteMatchCallback` it
* will be passed in as this handler's `options.params` argument.
*/
export interface RouteHandlerCallback {
(options: RouteHandlerCallbackOptions): Promise<Response>;
}
/**
* An object with a `handle` method of type `RouteHandlerCallback`.
*
* A `Route` object can be created with either an `RouteHandlerCallback`
* function or this `RouteHandler` object. The benefit of the `RouteHandler`
* is it can be extended (as is done by the `workbox-strategies` package).
*/
export interface RouteHandlerObject {
handle: RouteHandlerCallback;
}
/**
* Either a `RouteHandlerCallback` or a `RouteHandlerObject`.
* Most APIs in `workbox-routing` that accept route handlers take either.
*/
export type RouteHandler = RouteHandlerCallback | RouteHandlerObject;
export interface CacheDidUpdateCallbackParam {
cacheName: string;
oldResponse?: Response | null;
newResponse: Response;
request: Request;
event?: Event;
}
export interface CacheDidUpdateCallback {
(param: CacheDidUpdateCallbackParam): Promise<void | null | undefined>;
}
export interface CacheKeyWillBeUsedCallbackParam {
request: Request;
mode: string;
}
export interface CacheKeyWillBeUsedCallback {
(param: CacheKeyWillBeUsedCallbackParam): Promise<Request | string>;
}
export interface CacheWillUpdateCallbackParamParam {
response: Response;
request: Request;
event?: ExtendableEvent;
}
export interface CacheWillUpdateCallback {
(param: CacheWillUpdateCallbackParamParam): Promise<Response | void | null | undefined>;
}
export interface CachedResponseWillBeUsedCallbackParam {
cacheName: string;
request: Request;
matchOptions?: CacheQueryOptions;
cachedResponse?: Response;
event?: ExtendableEvent;
}
export interface CachedResponseWillBeUsedCallback {
(param: CachedResponseWillBeUsedCallbackParam): Promise<Response | void | null | undefined>;
}
export interface FetchDidFailCallbackParam {
originalRequest: Request;
error: Error;
request: Request;
event?: ExtendableEvent;
}
export interface FetchDidFailCallback {
(param: FetchDidFailCallbackParam): Promise<void | null | undefined>;
}
export interface FetchDidSucceedCallbackParam {
request: Request;
response: Response;
}
export interface FetchDidSucceedCallback {
(param: FetchDidSucceedCallbackParam): Promise<Response>;
}
export interface RequestWillFetchCallbackParam {
request: Request;
}
export interface RequestWillFetchCallback {
(param: RequestWillFetchCallbackParam): Promise<Request | void | null | undefined>;
}
/**
* An object with optional lifecycle callback properties for the fetch and
* cache operations.
*/
export interface WorkboxPlugin {
cacheDidUpdate?: CacheDidUpdateCallback;
cacheKeyWillBeUsed?: CacheKeyWillBeUsedCallback;
cacheWillUpdate?: CacheWillUpdateCallback;
cachedResponseWillBeUsed?: CachedResponseWillBeUsedCallback;
fetchDidFail?: FetchDidFailCallback;
fetchDidSucceed?: FetchDidSucceedCallback;
requestWillFetch?: RequestWillFetchCallback;
}
+17
View File
@@ -0,0 +1,17 @@
/*
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.
*/
import {WorkboxPlugin} from '../types.js';
import '../_version.js';
export const pluginUtils = {
filter: (plugins: WorkboxPlugin[], callbackName: string) => {
return plugins.filter((plugin) => callbackName in plugin);
},
};
+34
View File
@@ -0,0 +1,34 @@
/*
Copyright 2019 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.
*/
import {logger} from '../_private/logger.js';
import '../_version.js';
// A WorkboxCore instance must be exported before we can use the logger.
// This is so it can get the current log level.
if (process.env.NODE_ENV !== 'production') {
const padding = ' ';
logger.groupCollapsed('Welcome to Workbox!');
logger.log(`You are currently using a development build. ` +
`By default this will switch to prod builds when not on localhost. ` +
`You can force this with workbox.setConfig({debug: true|false}).`);
logger.log(
`📖 Read the guides and documentation\n` +
`${padding}https://developers.google.com/web/tools/workbox/`
);
logger.log(
`❓ Use the [workbox] tag on Stack Overflow to ask questions\n` +
`${padding}https://stackoverflow.com/questions/ask?tags=workbox`
);
logger.log(
`🐛 Found a bug? Report it on GitHub\n` +
`${padding}https://github.com/GoogleChrome/workbox/issues/new`
);
logger.groupEnd();
}