/* 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} [options.plugins=[]] * @return {Promise} * * @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};