123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289 |
- /*
- Copyright 2020 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} from 'workbox-core/_private/cacheNames.js';
- import {WorkboxError} from 'workbox-core/_private/WorkboxError.js';
- import {logger} from 'workbox-core/_private/logger.js';
- import {getFriendlyURL} from 'workbox-core/_private/getFriendlyURL.js';
- import {
- HandlerCallbackOptions,
- RouteHandlerObject,
- WorkboxPlugin,
- } from 'workbox-core/types.js';
- import {StrategyHandler} from './StrategyHandler.js';
- import './_version.js';
- export interface StrategyOptions {
- cacheName?: string;
- plugins?: WorkboxPlugin[];
- fetchOptions?: RequestInit;
- matchOptions?: CacheQueryOptions;
- }
- /**
- * An abstract base class that all other strategy classes must extend from:
- *
- * @memberof workbox-strategies
- */
- abstract class Strategy implements RouteHandlerObject {
- cacheName: string;
- plugins: WorkboxPlugin[];
- fetchOptions?: RequestInit;
- matchOptions?: CacheQueryOptions;
- protected abstract _handle(
- request: Request,
- handler: StrategyHandler,
- ): Promise<Response | undefined>;
- /**
- * Creates a new instance of the strategy and sets all documented option
- * properties as public instance properties.
- *
- * Note: if a custom strategy class extends the base Strategy class and does
- * not need more than these properties, it does not need to define its own
- * constructor.
- *
- * @param {Object} [options]
- * @param {string} [options.cacheName] Cache name to store and retrieve
- * requests. Defaults to the cache names provided by
- * {@link workbox-core.cacheNames}.
- * @param {Array<Object>} [options.plugins] [Plugins]{@link https://developers.google.com/web/tools/workbox/guides/using-plugins}
- * to use in conjunction with this caching strategy.
- * @param {Object} [options.fetchOptions] Values passed along to the
- * [`init`](https://developer.mozilla.org/en-US/docs/Web/API/WindowOrWorkerGlobalScope/fetch#Parameters)
- * of [non-navigation](https://github.com/GoogleChrome/workbox/issues/1796)
- * `fetch()` requests made by this strategy.
- * @param {Object} [options.matchOptions] The
- * [`CacheQueryOptions`]{@link https://w3c.github.io/ServiceWorker/#dictdef-cachequeryoptions}
- * for any `cache.match()` or `cache.put()` calls made by this strategy.
- */
- constructor(options: StrategyOptions = {}) {
- /**
- * Cache name to store and retrieve
- * requests. Defaults to the cache names provided by
- * {@link workbox-core.cacheNames}.
- *
- * @type {string}
- */
- this.cacheName = cacheNames.getRuntimeName(options.cacheName);
- /**
- * The list
- * [Plugins]{@link https://developers.google.com/web/tools/workbox/guides/using-plugins}
- * used by this strategy.
- *
- * @type {Array<Object>}
- */
- this.plugins = options.plugins || [];
- /**
- * Values passed along to the
- * [`init`]{@link https://developer.mozilla.org/en-US/docs/Web/API/WindowOrWorkerGlobalScope/fetch#Parameters}
- * of all fetch() requests made by this strategy.
- *
- * @type {Object}
- */
- this.fetchOptions = options.fetchOptions;
- /**
- * The
- * [`CacheQueryOptions`]{@link https://w3c.github.io/ServiceWorker/#dictdef-cachequeryoptions}
- * for any `cache.match()` or `cache.put()` calls made by this strategy.
- *
- * @type {Object}
- */
- this.matchOptions = options.matchOptions;
- }
- /**
- * Perform a request strategy and returns a `Promise` that will resolve with
- * a `Response`, invoking all relevant plugin callbacks.
- *
- * When a strategy instance is registered with a Workbox
- * {@link workbox-routing.Route}, this method is automatically
- * called when the route matches.
- *
- * Alternatively, this method can be used in a standalone `FetchEvent`
- * listener by passing it to `event.respondWith()`.
- *
- * @param {FetchEvent|Object} options A `FetchEvent` or an object with the
- * properties listed below.
- * @param {Request|string} options.request A request to run this strategy for.
- * @param {ExtendableEvent} options.event The event associated with the
- * request.
- * @param {URL} [options.url]
- * @param {*} [options.params]
- */
- handle(options: FetchEvent | HandlerCallbackOptions): Promise<Response> {
- const [responseDone] = this.handleAll(options);
- return responseDone;
- }
- /**
- * Similar to {@link workbox-strategies.Strategy~handle}, but
- * instead of just returning a `Promise` that resolves to a `Response` it
- * it will return an tuple of `[response, done]` promises, where the former
- * (`response`) is equivalent to what `handle()` returns, and the latter is a
- * Promise that will resolve once any promises that were added to
- * `event.waitUntil()` as part of performing the strategy have completed.
- *
- * You can await the `done` promise to ensure any extra work performed by
- * the strategy (usually caching responses) completes successfully.
- *
- * @param {FetchEvent|Object} options A `FetchEvent` or an object with the
- * properties listed below.
- * @param {Request|string} options.request A request to run this strategy for.
- * @param {ExtendableEvent} options.event The event associated with the
- * request.
- * @param {URL} [options.url]
- * @param {*} [options.params]
- * @return {Array<Promise>} A tuple of [response, done]
- * promises that can be used to determine when the response resolves as
- * well as when the handler has completed all its work.
- */
- handleAll(
- options: FetchEvent | HandlerCallbackOptions,
- ): [Promise<Response>, Promise<void>] {
- // Allow for flexible options to be passed.
- if (options instanceof FetchEvent) {
- options = {
- event: options,
- request: options.request,
- };
- }
- const event = options.event;
- const request =
- typeof options.request === 'string'
- ? new Request(options.request)
- : options.request;
- const params = 'params' in options ? options.params : undefined;
- const handler = new StrategyHandler(this, {event, request, params});
- const responseDone = this._getResponse(handler, request, event);
- const handlerDone = this._awaitComplete(
- responseDone,
- handler,
- request,
- event,
- );
- // Return an array of promises, suitable for use with Promise.all().
- return [responseDone, handlerDone];
- }
- async _getResponse(
- handler: StrategyHandler,
- request: Request,
- event: ExtendableEvent,
- ): Promise<Response> {
- await handler.runCallbacks('handlerWillStart', {event, request});
- let response: Response | undefined = undefined;
- try {
- response = await this._handle(request, handler);
- // The "official" Strategy subclasses all throw this error automatically,
- // but in case a third-party Strategy doesn't, ensure that we have a
- // consistent failure when there's no response or an error response.
- if (!response || response.type === 'error') {
- throw new WorkboxError('no-response', {url: request.url});
- }
- } catch (error) {
- if (error instanceof Error) {
- for (const callback of handler.iterateCallbacks('handlerDidError')) {
- response = await callback({error, event, request});
- if (response) {
- break;
- }
- }
- }
- if (!response) {
- throw error;
- } else if (process.env.NODE_ENV !== 'production') {
- logger.log(
- `While responding to '${getFriendlyURL(request.url)}', ` +
- `an ${
- error instanceof Error ? error.toString() : ''
- } error occurred. Using a fallback response provided by ` +
- `a handlerDidError plugin.`,
- );
- }
- }
- for (const callback of handler.iterateCallbacks('handlerWillRespond')) {
- response = await callback({event, request, response});
- }
- return response;
- }
- async _awaitComplete(
- responseDone: Promise<Response>,
- handler: StrategyHandler,
- request: Request,
- event: ExtendableEvent,
- ): Promise<void> {
- let response;
- let error;
- try {
- response = await responseDone;
- } catch (error) {
- // Ignore errors, as response errors should be caught via the `response`
- // promise above. The `done` promise will only throw for errors in
- // promises passed to `handler.waitUntil()`.
- }
- try {
- await handler.runCallbacks('handlerDidRespond', {
- event,
- request,
- response,
- });
- await handler.doneWaiting();
- } catch (waitUntilError) {
- if (waitUntilError instanceof Error) {
- error = waitUntilError;
- }
- }
- await handler.runCallbacks('handlerDidComplete', {
- event,
- request,
- response,
- error: error as Error,
- });
- handler.destroy();
- if (error) {
- throw error;
- }
- }
- }
- export {Strategy};
- /**
- * Classes extending the `Strategy` based class should implement this method,
- * and leverage the {@link workbox-strategies.StrategyHandler}
- * arg to perform all fetching and cache logic, which will ensure all relevant
- * cache, cache options, fetch options and plugins are used (per the current
- * strategy instance).
- *
- * @name _handle
- * @instance
- * @abstract
- * @function
- * @param {Request} request
- * @param {workbox-strategies.StrategyHandler} handler
- * @return {Promise<Response>}
- *
- * @memberof workbox-strategies.Strategy
- */
|