PrecacheController.mjs 7.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251
  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 {WorkboxError} from 'workbox-core/_private/WorkboxError.mjs';
  12. import {cleanRedirect} from './utils/cleanRedirect.mjs';
  13. import {createCacheKey} from './utils/createCacheKey.mjs';
  14. import {printCleanupDetails} from './utils/printCleanupDetails.mjs';
  15. import {printInstallDetails} from './utils/printInstallDetails.mjs';
  16. import './_version.mjs';
  17. /**
  18. * Performs efficient precaching of assets.
  19. *
  20. * @memberof module:workbox-precaching
  21. */
  22. class PrecacheController {
  23. /**
  24. * Create a new PrecacheController.
  25. *
  26. * @param {string} [cacheName] An optional name for the cache, to override
  27. * the default precache name.
  28. */
  29. constructor(cacheName) {
  30. this._cacheName = cacheNames.getPrecacheName(cacheName);
  31. this._urlsToCacheKeys = new Map();
  32. }
  33. /**
  34. * This method will add items to the precache list, removing duplicates
  35. * and ensuring the information is valid.
  36. *
  37. * @param {
  38. * Array<module:workbox-precaching.PrecacheController.PrecacheEntry|string>
  39. * } entries Array of entries to precache.
  40. */
  41. addToCacheList(entries) {
  42. if (process.env.NODE_ENV !== 'production') {
  43. assert.isArray(entries, {
  44. moduleName: 'workbox-precaching',
  45. className: 'PrecacheController',
  46. funcName: 'addToCacheList',
  47. paramName: 'entries',
  48. });
  49. }
  50. for (const entry of entries) {
  51. const {cacheKey, url} = createCacheKey(entry);
  52. if (this._urlsToCacheKeys.has(url) &&
  53. this._urlsToCacheKeys.get(url) !== cacheKey) {
  54. throw new WorkboxError('add-to-cache-list-conflicting-entries', {
  55. firstEntry: this._urlsToCacheKeys.get(url),
  56. secondEntry: cacheKey,
  57. });
  58. }
  59. this._urlsToCacheKeys.set(url, cacheKey);
  60. }
  61. }
  62. /**
  63. * Precaches new and updated assets. Call this method from the service worker
  64. * install event.
  65. *
  66. * @param {Object} options
  67. * @param {Event} [options.event] The install event (if needed).
  68. * @param {Array<Object>} [options.plugins] Plugins to be used for fetching
  69. * and caching during install.
  70. * @return {Promise<workbox.precaching.InstallResult>}
  71. */
  72. async install({event, plugins} = {}) {
  73. if (process.env.NODE_ENV !== 'production') {
  74. if (plugins) {
  75. assert.isArray(plugins, {
  76. moduleName: 'workbox-precaching',
  77. className: 'PrecacheController',
  78. funcName: 'install',
  79. paramName: 'plugins',
  80. });
  81. }
  82. }
  83. const urlsToPrecache = [];
  84. const urlsAlreadyPrecached = [];
  85. const cache = await caches.open(this._cacheName);
  86. const alreadyCachedRequests = await cache.keys();
  87. const alreadyCachedURLs = new Set(alreadyCachedRequests.map(
  88. (request) => request.url));
  89. for (const cacheKey of this._urlsToCacheKeys.values()) {
  90. if (alreadyCachedURLs.has(cacheKey)) {
  91. urlsAlreadyPrecached.push(cacheKey);
  92. } else {
  93. urlsToPrecache.push(cacheKey);
  94. }
  95. }
  96. const precacheRequests = urlsToPrecache.map((url) => {
  97. return this._addURLToCache({event, plugins, url});
  98. });
  99. await Promise.all(precacheRequests);
  100. if (process.env.NODE_ENV !== 'production') {
  101. printInstallDetails(urlsToPrecache, urlsAlreadyPrecached);
  102. }
  103. return {
  104. updatedURLs: urlsToPrecache,
  105. notUpdatedURLs: urlsAlreadyPrecached,
  106. };
  107. }
  108. /**
  109. * Deletes assets that are no longer present in the current precache manifest.
  110. * Call this method from the service worker activate event.
  111. *
  112. * @return {Promise<workbox.precaching.CleanupResult>}
  113. */
  114. async activate() {
  115. const cache = await caches.open(this._cacheName);
  116. const currentlyCachedRequests = await cache.keys();
  117. const expectedCacheKeys = new Set(this._urlsToCacheKeys.values());
  118. const deletedURLs = [];
  119. for (const request of currentlyCachedRequests) {
  120. if (!expectedCacheKeys.has(request.url)) {
  121. await cache.delete(request);
  122. deletedURLs.push(request.url);
  123. }
  124. }
  125. if (process.env.NODE_ENV !== 'production') {
  126. printCleanupDetails(deletedURLs);
  127. }
  128. return {deletedURLs};
  129. }
  130. /**
  131. * Requests the entry and saves it to the cache if the response is valid.
  132. * By default, any response with a status code of less than 400 (including
  133. * opaque responses) is considered valid.
  134. *
  135. * If you need to use custom criteria to determine what's valid and what
  136. * isn't, then pass in an item in `options.plugins` that implements the
  137. * `cacheWillUpdate()` lifecycle event.
  138. *
  139. * @private
  140. * @param {Object} options
  141. * @param {string} options.url The URL to fetch and cache.
  142. * @param {Event} [options.event] The install event (if passed).
  143. * @param {Array<Object>} [options.plugins] An array of plugins to apply to
  144. * fetch and caching.
  145. */
  146. async _addURLToCache({url, event, plugins}) {
  147. const request = new Request(url, {credentials: 'same-origin'});
  148. let response = await fetchWrapper.fetch({
  149. event,
  150. plugins,
  151. request,
  152. });
  153. // Allow developers to override the default logic about what is and isn't
  154. // valid by passing in a plugin implementing cacheWillUpdate(), e.g.
  155. // a workbox.cacheableResponse.Plugin instance.
  156. let cacheWillUpdateCallback;
  157. for (const plugin of (plugins || [])) {
  158. if ('cacheWillUpdate' in plugin) {
  159. cacheWillUpdateCallback = plugin.cacheWillUpdate.bind(plugin);
  160. }
  161. }
  162. const isValidResponse = cacheWillUpdateCallback ?
  163. // Use a callback if provided. It returns a truthy value if valid.
  164. cacheWillUpdateCallback({event, request, response}) :
  165. // Otherwise, default to considering any response status under 400 valid.
  166. // This includes, by default, considering opaque responses valid.
  167. response.status < 400;
  168. // Consider this a failure, leading to the `install` handler failing, if
  169. // we get back an invalid response.
  170. if (!isValidResponse) {
  171. throw new WorkboxError('bad-precaching-response', {
  172. url,
  173. status: response.status,
  174. });
  175. }
  176. if (response.redirected) {
  177. response = await cleanRedirect(response);
  178. }
  179. await cacheWrapper.put({
  180. event,
  181. plugins,
  182. request,
  183. response,
  184. cacheName: this._cacheName,
  185. matchOptions: {
  186. ignoreSearch: true,
  187. },
  188. });
  189. }
  190. /**
  191. * Returns a mapping of a precached URL to the corresponding cache key, taking
  192. * into account the revision information for the URL.
  193. *
  194. * @return {Map<string, string>} A URL to cache key mapping.
  195. */
  196. getURLsToCacheKeys() {
  197. return this._urlsToCacheKeys;
  198. }
  199. /**
  200. * Returns a list of all the URLs that have been precached by the current
  201. * service worker.
  202. *
  203. * @return {Array<string>} The precached URLs.
  204. */
  205. getCachedURLs() {
  206. return [...this._urlsToCacheKeys.keys()];
  207. }
  208. /**
  209. * Returns the cache key used for storing a given URL. If that URL is
  210. * unversioned, like `/index.html', then the cache key will be the original
  211. * URL with a search parameter appended to it.
  212. *
  213. * @param {string} url A URL whose cache key you want to look up.
  214. * @return {string} The versioned URL that corresponds to a cache key
  215. * for the original URL, or undefined if that URL isn't precached.
  216. */
  217. getCacheKeyForURL(url) {
  218. const urlObject = new URL(url, location);
  219. return this._urlsToCacheKeys.get(urlObject.href);
  220. }
  221. }
  222. export {PrecacheController};