/* 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 { pluginUtils } from '../utils/pluginUtils.js'; import '../_version.js'; /** * 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 = [], }) => { 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, "fetchDidFail" /* 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 ("requestWillFetch" /* REQUEST_WILL_FETCH */ in plugin) { const pluginMethod = plugin["requestWillFetch" /* REQUEST_WILL_FETCH */]; const requestClone = request.clone(); request = await pluginMethod.call(plugin, { request: requestClone, event, }); if (process.env.NODE_ENV !== 'production') { if (request) { assert.isInstance(request, Request, { moduleName: 'Plugin', funcName: "cachedResponseWillBeUsed" /* 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 ("fetchDidSucceed" /* FETCH_DID_SUCCEED */ in plugin) { fetchResponse = await plugin["fetchDidSucceed" /* 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: "fetchDidSucceed" /* 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["fetchDidFail" /* FETCH_DID_FAIL */].call(plugin, { error, event, originalRequest: originalRequest.clone(), request: pluginFilteredRequest.clone(), }); } throw error; } }; const fetchWrapper = { fetch: wrappedFetch, }; export { fetchWrapper };