fetchWrapper.mjs 4.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165
  1. /*
  2. Copyright 2018 Google LLC
  3. Use of this source code is governed by an MIT-style
  4. license that can be found in the LICENSE file or at
  5. https://opensource.org/licenses/MIT.
  6. */
  7. import {WorkboxError} from './WorkboxError.mjs';
  8. import {logger} from './logger.mjs';
  9. import {assert} from './assert.mjs';
  10. import {getFriendlyURL} from '../_private/getFriendlyURL.mjs';
  11. import {pluginEvents} from '../models/pluginEvents.mjs';
  12. import {pluginUtils} from '../utils/pluginUtils.mjs';
  13. import '../_version.mjs';
  14. /**
  15. * Wrapper around the fetch API.
  16. *
  17. * Will call requestWillFetch on available plugins.
  18. *
  19. * @param {Object} options
  20. * @param {Request|string} options.request
  21. * @param {Object} [options.fetchOptions]
  22. * @param {Event} [options.event]
  23. * @param {Array<Object>} [options.plugins=[]]
  24. * @return {Promise<Response>}
  25. *
  26. * @private
  27. * @memberof module:workbox-core
  28. */
  29. const wrappedFetch = async ({
  30. request,
  31. fetchOptions,
  32. event,
  33. plugins = []}) => {
  34. // We *should* be able to call `await event.preloadResponse` even if it's
  35. // undefined, but for some reason, doing so leads to errors in our Node unit
  36. // tests. To work around that, explicitly check preloadResponse's value first.
  37. if (event && event.preloadResponse) {
  38. const possiblePreloadResponse = await event.preloadResponse;
  39. if (possiblePreloadResponse) {
  40. if (process.env.NODE_ENV !== 'production') {
  41. logger.log(`Using a preloaded navigation response for ` +
  42. `'${getFriendlyURL(request.url)}'`);
  43. }
  44. return possiblePreloadResponse;
  45. }
  46. }
  47. if (typeof request === 'string') {
  48. request = new Request(request);
  49. }
  50. if (process.env.NODE_ENV !== 'production') {
  51. assert.isInstance(request, Request, {
  52. paramName: request,
  53. expectedClass: 'Request',
  54. moduleName: 'workbox-core',
  55. className: 'fetchWrapper',
  56. funcName: 'wrappedFetch',
  57. });
  58. }
  59. const failedFetchPlugins = pluginUtils.filter(
  60. plugins, pluginEvents.FETCH_DID_FAIL);
  61. // If there is a fetchDidFail plugin, we need to save a clone of the
  62. // original request before it's either modified by a requestWillFetch
  63. // plugin or before the original request's body is consumed via fetch().
  64. const originalRequest = failedFetchPlugins.length > 0 ?
  65. request.clone() : null;
  66. try {
  67. for (let plugin of plugins) {
  68. if (pluginEvents.REQUEST_WILL_FETCH in plugin) {
  69. request = await plugin[pluginEvents.REQUEST_WILL_FETCH].call(plugin, {
  70. request: request.clone(),
  71. event,
  72. });
  73. if (process.env.NODE_ENV !== 'production') {
  74. if (request) {
  75. assert.isInstance(request, Request, {
  76. moduleName: 'Plugin',
  77. funcName: pluginEvents.CACHED_RESPONSE_WILL_BE_USED,
  78. isReturnValueProblem: true,
  79. });
  80. }
  81. }
  82. }
  83. }
  84. } catch (err) {
  85. throw new WorkboxError('plugin-error-request-will-fetch', {
  86. thrownError: err,
  87. });
  88. }
  89. // The request can be altered by plugins with `requestWillFetch` making
  90. // the original request (Most likely from a `fetch` event) to be different
  91. // to the Request we make. Pass both to `fetchDidFail` to aid debugging.
  92. let pluginFilteredRequest = request.clone();
  93. try {
  94. let fetchResponse;
  95. // See https://github.com/GoogleChrome/workbox/issues/1796
  96. if (request.mode === 'navigate') {
  97. fetchResponse = await fetch(request);
  98. } else {
  99. fetchResponse = await fetch(request, fetchOptions);
  100. }
  101. if (process.env.NODE_ENV !== 'production') {
  102. logger.debug(`Network request for `+
  103. `'${getFriendlyURL(request.url)}' returned a response with ` +
  104. `status '${fetchResponse.status}'.`);
  105. }
  106. for (const plugin of plugins) {
  107. if (pluginEvents.FETCH_DID_SUCCEED in plugin) {
  108. fetchResponse = await plugin[pluginEvents.FETCH_DID_SUCCEED]
  109. .call(plugin, {
  110. event,
  111. request: pluginFilteredRequest,
  112. response: fetchResponse,
  113. });
  114. if (process.env.NODE_ENV !== 'production') {
  115. if (fetchResponse) {
  116. assert.isInstance(fetchResponse, Response, {
  117. moduleName: 'Plugin',
  118. funcName: pluginEvents.FETCH_DID_SUCCEED,
  119. isReturnValueProblem: true,
  120. });
  121. }
  122. }
  123. }
  124. }
  125. return fetchResponse;
  126. } catch (error) {
  127. if (process.env.NODE_ENV !== 'production') {
  128. logger.error(`Network request for `+
  129. `'${getFriendlyURL(request.url)}' threw an error.`, error);
  130. }
  131. for (const plugin of failedFetchPlugins) {
  132. await plugin[pluginEvents.FETCH_DID_FAIL].call(plugin, {
  133. error,
  134. event,
  135. originalRequest: originalRequest.clone(),
  136. request: pluginFilteredRequest.clone(),
  137. });
  138. }
  139. throw error;
  140. }
  141. };
  142. const fetchWrapper = {
  143. fetch: wrappedFetch,
  144. };
  145. export {fetchWrapper};