CacheFirst.mjs 6.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194
  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 {assert} from 'workbox-core/_private/assert.mjs';
  8. import {cacheNames} from 'workbox-core/_private/cacheNames.mjs';
  9. import {cacheWrapper} from 'workbox-core/_private/cacheWrapper.mjs';
  10. import {fetchWrapper} from 'workbox-core/_private/fetchWrapper.mjs';
  11. import {getFriendlyURL} from 'workbox-core/_private/getFriendlyURL.mjs';
  12. import {logger} from 'workbox-core/_private/logger.mjs';
  13. import {WorkboxError} from 'workbox-core/_private/WorkboxError.mjs';
  14. import {messages} from './utils/messages.mjs';
  15. import './_version.mjs';
  16. /**
  17. * An implementation of a [cache-first]{@link https://developers.google.com/web/fundamentals/instant-and-offline/offline-cookbook/#cache-falling-back-to-network}
  18. * request strategy.
  19. *
  20. * A cache first strategy is useful for assets that have been revisioned,
  21. * such as URLs like `/styles/example.a8f5f1.css`, since they
  22. * can be cached for long periods of time.
  23. *
  24. * If the network request fails, and there is no cache match, this will throw
  25. * a `WorkboxError` exception.
  26. *
  27. * @memberof workbox.strategies
  28. */
  29. class CacheFirst {
  30. /**
  31. * @param {Object} options
  32. * @param {string} options.cacheName Cache name to store and retrieve
  33. * requests. Defaults to cache names provided by
  34. * [workbox-core]{@link workbox.core.cacheNames}.
  35. * @param {Array<Object>} options.plugins [Plugins]{@link https://developers.google.com/web/tools/workbox/guides/using-plugins}
  36. * to use in conjunction with this caching strategy.
  37. * @param {Object} options.fetchOptions Values passed along to the
  38. * [`init`](https://developer.mozilla.org/en-US/docs/Web/API/WindowOrWorkerGlobalScope/fetch#Parameters)
  39. * of all fetch() requests made by this strategy.
  40. * @param {Object} options.matchOptions [`CacheQueryOptions`](https://w3c.github.io/ServiceWorker/#dictdef-cachequeryoptions)
  41. */
  42. constructor(options = {}) {
  43. this._cacheName = cacheNames.getRuntimeName(options.cacheName);
  44. this._plugins = options.plugins || [];
  45. this._fetchOptions = options.fetchOptions || null;
  46. this._matchOptions = options.matchOptions || null;
  47. }
  48. /**
  49. * This method will perform a request strategy and follows an API that
  50. * will work with the
  51. * [Workbox Router]{@link workbox.routing.Router}.
  52. *
  53. * @param {Object} options
  54. * @param {Request} options.request The request to run this strategy for.
  55. * @param {Event} [options.event] The event that triggered the request.
  56. * @return {Promise<Response>}
  57. */
  58. async handle({event, request}) {
  59. return this.makeRequest({
  60. event,
  61. request: request || event.request,
  62. });
  63. }
  64. /**
  65. * This method can be used to perform a make a standalone request outside the
  66. * context of the [Workbox Router]{@link workbox.routing.Router}.
  67. *
  68. * See "[Advanced Recipes](https://developers.google.com/web/tools/workbox/guides/advanced-recipes#make-requests)"
  69. * for more usage information.
  70. *
  71. * @param {Object} options
  72. * @param {Request|string} options.request Either a
  73. * [`Request`]{@link https://developer.mozilla.org/en-US/docs/Web/API/Request}
  74. * object, or a string URL, corresponding to the request to be made.
  75. * @param {FetchEvent} [options.event] If provided, `event.waitUntil()` will
  76. be called automatically to extend the service worker's lifetime.
  77. * @return {Promise<Response>}
  78. */
  79. async makeRequest({event, request}) {
  80. const logs = [];
  81. if (typeof request === 'string') {
  82. request = new Request(request);
  83. }
  84. if (process.env.NODE_ENV !== 'production') {
  85. assert.isInstance(request, Request, {
  86. moduleName: 'workbox-strategies',
  87. className: 'CacheFirst',
  88. funcName: 'makeRequest',
  89. paramName: 'request',
  90. });
  91. }
  92. let response = await cacheWrapper.match({
  93. cacheName: this._cacheName,
  94. request,
  95. event,
  96. matchOptions: this._matchOptions,
  97. plugins: this._plugins,
  98. });
  99. let error;
  100. if (!response) {
  101. if (process.env.NODE_ENV !== 'production') {
  102. logs.push(
  103. `No response found in the '${this._cacheName}' cache. ` +
  104. `Will respond with a network request.`);
  105. }
  106. try {
  107. response = await this._getFromNetwork(request, event);
  108. } catch (err) {
  109. error = err;
  110. }
  111. if (process.env.NODE_ENV !== 'production') {
  112. if (response) {
  113. logs.push(`Got response from network.`);
  114. } else {
  115. logs.push(`Unable to get a response from the network.`);
  116. }
  117. }
  118. } else {
  119. if (process.env.NODE_ENV !== 'production') {
  120. logs.push(
  121. `Found a cached response in the '${this._cacheName}' cache.`);
  122. }
  123. }
  124. if (process.env.NODE_ENV !== 'production') {
  125. logger.groupCollapsed(
  126. messages.strategyStart('CacheFirst', request));
  127. for (let log of logs) {
  128. logger.log(log);
  129. }
  130. messages.printFinalResponse(response);
  131. logger.groupEnd();
  132. }
  133. if (!response) {
  134. throw new WorkboxError('no-response', {url: request.url, error});
  135. }
  136. return response;
  137. }
  138. /**
  139. * Handles the network and cache part of CacheFirst.
  140. *
  141. * @param {Request} request
  142. * @param {FetchEvent} [event]
  143. * @return {Promise<Response>}
  144. *
  145. * @private
  146. */
  147. async _getFromNetwork(request, event) {
  148. const response = await fetchWrapper.fetch({
  149. request,
  150. event,
  151. fetchOptions: this._fetchOptions,
  152. plugins: this._plugins,
  153. });
  154. // Keep the service worker while we put the request to the cache
  155. const responseClone = response.clone();
  156. const cachePutPromise = cacheWrapper.put({
  157. cacheName: this._cacheName,
  158. request,
  159. response: responseClone,
  160. event,
  161. plugins: this._plugins,
  162. });
  163. if (event) {
  164. try {
  165. event.waitUntil(cachePutPromise);
  166. } catch (error) {
  167. if (process.env.NODE_ENV !== 'production') {
  168. logger.warn(`Unable to ensure service worker stays alive when ` +
  169. `updating cache for '${getFriendlyURL(request.url)}'.`);
  170. }
  171. }
  172. }
  173. return response;
  174. }
  175. }
  176. export {CacheFirst};