fetchWrapper.js 5.6 KB

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