workbox-strategies.dev.js 51 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320132113221323132413251326132713281329133013311332133313341335133613371338133913401341134213431344134513461347134813491350135113521353135413551356135713581359136013611362136313641365136613671368136913701371137213731374137513761377137813791380138113821383138413851386138713881389139013911392139313941395139613971398139914001401140214031404140514061407140814091410141114121413141414151416141714181419142014211422142314241425142614271428142914301431143214331434143514361437143814391440144114421443144414451446144714481449145014511452145314541455145614571458145914601461146214631464146514661467146814691470147114721473147414751476147714781479148014811482148314841485148614871488148914901491149214931494149514961497149814991500150115021503150415051506150715081509
  1. this.workbox = this.workbox || {};
  2. this.workbox.strategies = (function (exports, assert_js, logger_js, WorkboxError_js, cacheNames_js, getFriendlyURL_js, cacheMatchIgnoreParams_js, Deferred_js, executeQuotaErrorCallbacks_js, timeout_js) {
  3. 'use strict';
  4. try {
  5. self['workbox:strategies:6.6.0'] && _();
  6. } catch (e) {}
  7. /*
  8. Copyright 2020 Google LLC
  9. Use of this source code is governed by an MIT-style
  10. license that can be found in the LICENSE file or at
  11. https://opensource.org/licenses/MIT.
  12. */
  13. function toRequest(input) {
  14. return typeof input === 'string' ? new Request(input) : input;
  15. }
  16. /**
  17. * A class created every time a Strategy instance instance calls
  18. * {@link workbox-strategies.Strategy~handle} or
  19. * {@link workbox-strategies.Strategy~handleAll} that wraps all fetch and
  20. * cache actions around plugin callbacks and keeps track of when the strategy
  21. * is "done" (i.e. all added `event.waitUntil()` promises have resolved).
  22. *
  23. * @memberof workbox-strategies
  24. */
  25. class StrategyHandler {
  26. /**
  27. * Creates a new instance associated with the passed strategy and event
  28. * that's handling the request.
  29. *
  30. * The constructor also initializes the state that will be passed to each of
  31. * the plugins handling this request.
  32. *
  33. * @param {workbox-strategies.Strategy} strategy
  34. * @param {Object} options
  35. * @param {Request|string} options.request A request to run this strategy for.
  36. * @param {ExtendableEvent} options.event The event associated with the
  37. * request.
  38. * @param {URL} [options.url]
  39. * @param {*} [options.params] The return value from the
  40. * {@link workbox-routing~matchCallback} (if applicable).
  41. */
  42. constructor(strategy, options) {
  43. this._cacheKeys = {};
  44. /**
  45. * The request the strategy is performing (passed to the strategy's
  46. * `handle()` or `handleAll()` method).
  47. * @name request
  48. * @instance
  49. * @type {Request}
  50. * @memberof workbox-strategies.StrategyHandler
  51. */
  52. /**
  53. * The event associated with this request.
  54. * @name event
  55. * @instance
  56. * @type {ExtendableEvent}
  57. * @memberof workbox-strategies.StrategyHandler
  58. */
  59. /**
  60. * A `URL` instance of `request.url` (if passed to the strategy's
  61. * `handle()` or `handleAll()` method).
  62. * Note: the `url` param will be present if the strategy was invoked
  63. * from a workbox `Route` object.
  64. * @name url
  65. * @instance
  66. * @type {URL|undefined}
  67. * @memberof workbox-strategies.StrategyHandler
  68. */
  69. /**
  70. * A `param` value (if passed to the strategy's
  71. * `handle()` or `handleAll()` method).
  72. * Note: the `param` param will be present if the strategy was invoked
  73. * from a workbox `Route` object and the
  74. * {@link workbox-routing~matchCallback} returned
  75. * a truthy value (it will be that value).
  76. * @name params
  77. * @instance
  78. * @type {*|undefined}
  79. * @memberof workbox-strategies.StrategyHandler
  80. */
  81. {
  82. assert_js.assert.isInstance(options.event, ExtendableEvent, {
  83. moduleName: 'workbox-strategies',
  84. className: 'StrategyHandler',
  85. funcName: 'constructor',
  86. paramName: 'options.event'
  87. });
  88. }
  89. Object.assign(this, options);
  90. this.event = options.event;
  91. this._strategy = strategy;
  92. this._handlerDeferred = new Deferred_js.Deferred();
  93. this._extendLifetimePromises = []; // Copy the plugins list (since it's mutable on the strategy),
  94. // so any mutations don't affect this handler instance.
  95. this._plugins = [...strategy.plugins];
  96. this._pluginStateMap = new Map();
  97. for (const plugin of this._plugins) {
  98. this._pluginStateMap.set(plugin, {});
  99. }
  100. this.event.waitUntil(this._handlerDeferred.promise);
  101. }
  102. /**
  103. * Fetches a given request (and invokes any applicable plugin callback
  104. * methods) using the `fetchOptions` (for non-navigation requests) and
  105. * `plugins` defined on the `Strategy` object.
  106. *
  107. * The following plugin lifecycle methods are invoked when using this method:
  108. * - `requestWillFetch()`
  109. * - `fetchDidSucceed()`
  110. * - `fetchDidFail()`
  111. *
  112. * @param {Request|string} input The URL or request to fetch.
  113. * @return {Promise<Response>}
  114. */
  115. async fetch(input) {
  116. const {
  117. event
  118. } = this;
  119. let request = toRequest(input);
  120. if (request.mode === 'navigate' && event instanceof FetchEvent && event.preloadResponse) {
  121. const possiblePreloadResponse = await event.preloadResponse;
  122. if (possiblePreloadResponse) {
  123. {
  124. logger_js.logger.log(`Using a preloaded navigation response for ` + `'${getFriendlyURL_js.getFriendlyURL(request.url)}'`);
  125. }
  126. return possiblePreloadResponse;
  127. }
  128. } // If there is a fetchDidFail plugin, we need to save a clone of the
  129. // original request before it's either modified by a requestWillFetch
  130. // plugin or before the original request's body is consumed via fetch().
  131. const originalRequest = this.hasCallback('fetchDidFail') ? request.clone() : null;
  132. try {
  133. for (const cb of this.iterateCallbacks('requestWillFetch')) {
  134. request = await cb({
  135. request: request.clone(),
  136. event
  137. });
  138. }
  139. } catch (err) {
  140. if (err instanceof Error) {
  141. throw new WorkboxError_js.WorkboxError('plugin-error-request-will-fetch', {
  142. thrownErrorMessage: err.message
  143. });
  144. }
  145. } // The request can be altered by plugins with `requestWillFetch` making
  146. // the original request (most likely from a `fetch` event) different
  147. // from the Request we make. Pass both to `fetchDidFail` to aid debugging.
  148. const pluginFilteredRequest = request.clone();
  149. try {
  150. let fetchResponse; // See https://github.com/GoogleChrome/workbox/issues/1796
  151. fetchResponse = await fetch(request, request.mode === 'navigate' ? undefined : this._strategy.fetchOptions);
  152. if ("dev" !== 'production') {
  153. logger_js.logger.debug(`Network request for ` + `'${getFriendlyURL_js.getFriendlyURL(request.url)}' returned a response with ` + `status '${fetchResponse.status}'.`);
  154. }
  155. for (const callback of this.iterateCallbacks('fetchDidSucceed')) {
  156. fetchResponse = await callback({
  157. event,
  158. request: pluginFilteredRequest,
  159. response: fetchResponse
  160. });
  161. }
  162. return fetchResponse;
  163. } catch (error) {
  164. {
  165. logger_js.logger.log(`Network request for ` + `'${getFriendlyURL_js.getFriendlyURL(request.url)}' threw an error.`, error);
  166. } // `originalRequest` will only exist if a `fetchDidFail` callback
  167. // is being used (see above).
  168. if (originalRequest) {
  169. await this.runCallbacks('fetchDidFail', {
  170. error: error,
  171. event,
  172. originalRequest: originalRequest.clone(),
  173. request: pluginFilteredRequest.clone()
  174. });
  175. }
  176. throw error;
  177. }
  178. }
  179. /**
  180. * Calls `this.fetch()` and (in the background) runs `this.cachePut()` on
  181. * the response generated by `this.fetch()`.
  182. *
  183. * The call to `this.cachePut()` automatically invokes `this.waitUntil()`,
  184. * so you do not have to manually call `waitUntil()` on the event.
  185. *
  186. * @param {Request|string} input The request or URL to fetch and cache.
  187. * @return {Promise<Response>}
  188. */
  189. async fetchAndCachePut(input) {
  190. const response = await this.fetch(input);
  191. const responseClone = response.clone();
  192. void this.waitUntil(this.cachePut(input, responseClone));
  193. return response;
  194. }
  195. /**
  196. * Matches a request from the cache (and invokes any applicable plugin
  197. * callback methods) using the `cacheName`, `matchOptions`, and `plugins`
  198. * defined on the strategy object.
  199. *
  200. * The following plugin lifecycle methods are invoked when using this method:
  201. * - cacheKeyWillByUsed()
  202. * - cachedResponseWillByUsed()
  203. *
  204. * @param {Request|string} key The Request or URL to use as the cache key.
  205. * @return {Promise<Response|undefined>} A matching response, if found.
  206. */
  207. async cacheMatch(key) {
  208. const request = toRequest(key);
  209. let cachedResponse;
  210. const {
  211. cacheName,
  212. matchOptions
  213. } = this._strategy;
  214. const effectiveRequest = await this.getCacheKey(request, 'read');
  215. const multiMatchOptions = Object.assign(Object.assign({}, matchOptions), {
  216. cacheName
  217. });
  218. cachedResponse = await caches.match(effectiveRequest, multiMatchOptions);
  219. {
  220. if (cachedResponse) {
  221. logger_js.logger.debug(`Found a cached response in '${cacheName}'.`);
  222. } else {
  223. logger_js.logger.debug(`No cached response found in '${cacheName}'.`);
  224. }
  225. }
  226. for (const callback of this.iterateCallbacks('cachedResponseWillBeUsed')) {
  227. cachedResponse = (await callback({
  228. cacheName,
  229. matchOptions,
  230. cachedResponse,
  231. request: effectiveRequest,
  232. event: this.event
  233. })) || undefined;
  234. }
  235. return cachedResponse;
  236. }
  237. /**
  238. * Puts a request/response pair in the cache (and invokes any applicable
  239. * plugin callback methods) using the `cacheName` and `plugins` defined on
  240. * the strategy object.
  241. *
  242. * The following plugin lifecycle methods are invoked when using this method:
  243. * - cacheKeyWillByUsed()
  244. * - cacheWillUpdate()
  245. * - cacheDidUpdate()
  246. *
  247. * @param {Request|string} key The request or URL to use as the cache key.
  248. * @param {Response} response The response to cache.
  249. * @return {Promise<boolean>} `false` if a cacheWillUpdate caused the response
  250. * not be cached, and `true` otherwise.
  251. */
  252. async cachePut(key, response) {
  253. const request = toRequest(key); // Run in the next task to avoid blocking other cache reads.
  254. // https://github.com/w3c/ServiceWorker/issues/1397
  255. await timeout_js.timeout(0);
  256. const effectiveRequest = await this.getCacheKey(request, 'write');
  257. {
  258. if (effectiveRequest.method && effectiveRequest.method !== 'GET') {
  259. throw new WorkboxError_js.WorkboxError('attempt-to-cache-non-get-request', {
  260. url: getFriendlyURL_js.getFriendlyURL(effectiveRequest.url),
  261. method: effectiveRequest.method
  262. });
  263. } // See https://github.com/GoogleChrome/workbox/issues/2818
  264. const vary = response.headers.get('Vary');
  265. if (vary) {
  266. logger_js.logger.debug(`The response for ${getFriendlyURL_js.getFriendlyURL(effectiveRequest.url)} ` + `has a 'Vary: ${vary}' header. ` + `Consider setting the {ignoreVary: true} option on your strategy ` + `to ensure cache matching and deletion works as expected.`);
  267. }
  268. }
  269. if (!response) {
  270. {
  271. logger_js.logger.error(`Cannot cache non-existent response for ` + `'${getFriendlyURL_js.getFriendlyURL(effectiveRequest.url)}'.`);
  272. }
  273. throw new WorkboxError_js.WorkboxError('cache-put-with-no-response', {
  274. url: getFriendlyURL_js.getFriendlyURL(effectiveRequest.url)
  275. });
  276. }
  277. const responseToCache = await this._ensureResponseSafeToCache(response);
  278. if (!responseToCache) {
  279. {
  280. logger_js.logger.debug(`Response '${getFriendlyURL_js.getFriendlyURL(effectiveRequest.url)}' ` + `will not be cached.`, responseToCache);
  281. }
  282. return false;
  283. }
  284. const {
  285. cacheName,
  286. matchOptions
  287. } = this._strategy;
  288. const cache = await self.caches.open(cacheName);
  289. const hasCacheUpdateCallback = this.hasCallback('cacheDidUpdate');
  290. const oldResponse = hasCacheUpdateCallback ? await cacheMatchIgnoreParams_js.cacheMatchIgnoreParams( // TODO(philipwalton): the `__WB_REVISION__` param is a precaching
  291. // feature. Consider into ways to only add this behavior if using
  292. // precaching.
  293. cache, effectiveRequest.clone(), ['__WB_REVISION__'], matchOptions) : null;
  294. {
  295. logger_js.logger.debug(`Updating the '${cacheName}' cache with a new Response ` + `for ${getFriendlyURL_js.getFriendlyURL(effectiveRequest.url)}.`);
  296. }
  297. try {
  298. await cache.put(effectiveRequest, hasCacheUpdateCallback ? responseToCache.clone() : responseToCache);
  299. } catch (error) {
  300. if (error instanceof Error) {
  301. // See https://developer.mozilla.org/en-US/docs/Web/API/DOMException#exception-QuotaExceededError
  302. if (error.name === 'QuotaExceededError') {
  303. await executeQuotaErrorCallbacks_js.executeQuotaErrorCallbacks();
  304. }
  305. throw error;
  306. }
  307. }
  308. for (const callback of this.iterateCallbacks('cacheDidUpdate')) {
  309. await callback({
  310. cacheName,
  311. oldResponse,
  312. newResponse: responseToCache.clone(),
  313. request: effectiveRequest,
  314. event: this.event
  315. });
  316. }
  317. return true;
  318. }
  319. /**
  320. * Checks the list of plugins for the `cacheKeyWillBeUsed` callback, and
  321. * executes any of those callbacks found in sequence. The final `Request`
  322. * object returned by the last plugin is treated as the cache key for cache
  323. * reads and/or writes. If no `cacheKeyWillBeUsed` plugin callbacks have
  324. * been registered, the passed request is returned unmodified
  325. *
  326. * @param {Request} request
  327. * @param {string} mode
  328. * @return {Promise<Request>}
  329. */
  330. async getCacheKey(request, mode) {
  331. const key = `${request.url} | ${mode}`;
  332. if (!this._cacheKeys[key]) {
  333. let effectiveRequest = request;
  334. for (const callback of this.iterateCallbacks('cacheKeyWillBeUsed')) {
  335. effectiveRequest = toRequest(await callback({
  336. mode,
  337. request: effectiveRequest,
  338. event: this.event,
  339. // params has a type any can't change right now.
  340. params: this.params // eslint-disable-line
  341. }));
  342. }
  343. this._cacheKeys[key] = effectiveRequest;
  344. }
  345. return this._cacheKeys[key];
  346. }
  347. /**
  348. * Returns true if the strategy has at least one plugin with the given
  349. * callback.
  350. *
  351. * @param {string} name The name of the callback to check for.
  352. * @return {boolean}
  353. */
  354. hasCallback(name) {
  355. for (const plugin of this._strategy.plugins) {
  356. if (name in plugin) {
  357. return true;
  358. }
  359. }
  360. return false;
  361. }
  362. /**
  363. * Runs all plugin callbacks matching the given name, in order, passing the
  364. * given param object (merged ith the current plugin state) as the only
  365. * argument.
  366. *
  367. * Note: since this method runs all plugins, it's not suitable for cases
  368. * where the return value of a callback needs to be applied prior to calling
  369. * the next callback. See
  370. * {@link workbox-strategies.StrategyHandler#iterateCallbacks}
  371. * below for how to handle that case.
  372. *
  373. * @param {string} name The name of the callback to run within each plugin.
  374. * @param {Object} param The object to pass as the first (and only) param
  375. * when executing each callback. This object will be merged with the
  376. * current plugin state prior to callback execution.
  377. */
  378. async runCallbacks(name, param) {
  379. for (const callback of this.iterateCallbacks(name)) {
  380. // TODO(philipwalton): not sure why `any` is needed. It seems like
  381. // this should work with `as WorkboxPluginCallbackParam[C]`.
  382. await callback(param);
  383. }
  384. }
  385. /**
  386. * Accepts a callback and returns an iterable of matching plugin callbacks,
  387. * where each callback is wrapped with the current handler state (i.e. when
  388. * you call each callback, whatever object parameter you pass it will
  389. * be merged with the plugin's current state).
  390. *
  391. * @param {string} name The name fo the callback to run
  392. * @return {Array<Function>}
  393. */
  394. *iterateCallbacks(name) {
  395. for (const plugin of this._strategy.plugins) {
  396. if (typeof plugin[name] === 'function') {
  397. const state = this._pluginStateMap.get(plugin);
  398. const statefulCallback = param => {
  399. const statefulParam = Object.assign(Object.assign({}, param), {
  400. state
  401. }); // TODO(philipwalton): not sure why `any` is needed. It seems like
  402. // this should work with `as WorkboxPluginCallbackParam[C]`.
  403. return plugin[name](statefulParam);
  404. };
  405. yield statefulCallback;
  406. }
  407. }
  408. }
  409. /**
  410. * Adds a promise to the
  411. * [extend lifetime promises]{@link https://w3c.github.io/ServiceWorker/#extendableevent-extend-lifetime-promises}
  412. * of the event event associated with the request being handled (usually a
  413. * `FetchEvent`).
  414. *
  415. * Note: you can await
  416. * {@link workbox-strategies.StrategyHandler~doneWaiting}
  417. * to know when all added promises have settled.
  418. *
  419. * @param {Promise} promise A promise to add to the extend lifetime promises
  420. * of the event that triggered the request.
  421. */
  422. waitUntil(promise) {
  423. this._extendLifetimePromises.push(promise);
  424. return promise;
  425. }
  426. /**
  427. * Returns a promise that resolves once all promises passed to
  428. * {@link workbox-strategies.StrategyHandler~waitUntil}
  429. * have settled.
  430. *
  431. * Note: any work done after `doneWaiting()` settles should be manually
  432. * passed to an event's `waitUntil()` method (not this handler's
  433. * `waitUntil()` method), otherwise the service worker thread my be killed
  434. * prior to your work completing.
  435. */
  436. async doneWaiting() {
  437. let promise;
  438. while (promise = this._extendLifetimePromises.shift()) {
  439. await promise;
  440. }
  441. }
  442. /**
  443. * Stops running the strategy and immediately resolves any pending
  444. * `waitUntil()` promises.
  445. */
  446. destroy() {
  447. this._handlerDeferred.resolve(null);
  448. }
  449. /**
  450. * This method will call cacheWillUpdate on the available plugins (or use
  451. * status === 200) to determine if the Response is safe and valid to cache.
  452. *
  453. * @param {Request} options.request
  454. * @param {Response} options.response
  455. * @return {Promise<Response|undefined>}
  456. *
  457. * @private
  458. */
  459. async _ensureResponseSafeToCache(response) {
  460. let responseToCache = response;
  461. let pluginsUsed = false;
  462. for (const callback of this.iterateCallbacks('cacheWillUpdate')) {
  463. responseToCache = (await callback({
  464. request: this.request,
  465. response: responseToCache,
  466. event: this.event
  467. })) || undefined;
  468. pluginsUsed = true;
  469. if (!responseToCache) {
  470. break;
  471. }
  472. }
  473. if (!pluginsUsed) {
  474. if (responseToCache && responseToCache.status !== 200) {
  475. responseToCache = undefined;
  476. }
  477. {
  478. if (responseToCache) {
  479. if (responseToCache.status !== 200) {
  480. if (responseToCache.status === 0) {
  481. logger_js.logger.warn(`The response for '${this.request.url}' ` + `is an opaque response. The caching strategy that you're ` + `using will not cache opaque responses by default.`);
  482. } else {
  483. logger_js.logger.debug(`The response for '${this.request.url}' ` + `returned a status code of '${response.status}' and won't ` + `be cached as a result.`);
  484. }
  485. }
  486. }
  487. }
  488. }
  489. return responseToCache;
  490. }
  491. }
  492. /*
  493. Copyright 2020 Google LLC
  494. Use of this source code is governed by an MIT-style
  495. license that can be found in the LICENSE file or at
  496. https://opensource.org/licenses/MIT.
  497. */
  498. /**
  499. * An abstract base class that all other strategy classes must extend from:
  500. *
  501. * @memberof workbox-strategies
  502. */
  503. class Strategy {
  504. /**
  505. * Creates a new instance of the strategy and sets all documented option
  506. * properties as public instance properties.
  507. *
  508. * Note: if a custom strategy class extends the base Strategy class and does
  509. * not need more than these properties, it does not need to define its own
  510. * constructor.
  511. *
  512. * @param {Object} [options]
  513. * @param {string} [options.cacheName] Cache name to store and retrieve
  514. * requests. Defaults to the cache names provided by
  515. * {@link workbox-core.cacheNames}.
  516. * @param {Array<Object>} [options.plugins] [Plugins]{@link https://developers.google.com/web/tools/workbox/guides/using-plugins}
  517. * to use in conjunction with this caching strategy.
  518. * @param {Object} [options.fetchOptions] Values passed along to the
  519. * [`init`](https://developer.mozilla.org/en-US/docs/Web/API/WindowOrWorkerGlobalScope/fetch#Parameters)
  520. * of [non-navigation](https://github.com/GoogleChrome/workbox/issues/1796)
  521. * `fetch()` requests made by this strategy.
  522. * @param {Object} [options.matchOptions] The
  523. * [`CacheQueryOptions`]{@link https://w3c.github.io/ServiceWorker/#dictdef-cachequeryoptions}
  524. * for any `cache.match()` or `cache.put()` calls made by this strategy.
  525. */
  526. constructor(options = {}) {
  527. /**
  528. * Cache name to store and retrieve
  529. * requests. Defaults to the cache names provided by
  530. * {@link workbox-core.cacheNames}.
  531. *
  532. * @type {string}
  533. */
  534. this.cacheName = cacheNames_js.cacheNames.getRuntimeName(options.cacheName);
  535. /**
  536. * The list
  537. * [Plugins]{@link https://developers.google.com/web/tools/workbox/guides/using-plugins}
  538. * used by this strategy.
  539. *
  540. * @type {Array<Object>}
  541. */
  542. this.plugins = options.plugins || [];
  543. /**
  544. * Values passed along to the
  545. * [`init`]{@link https://developer.mozilla.org/en-US/docs/Web/API/WindowOrWorkerGlobalScope/fetch#Parameters}
  546. * of all fetch() requests made by this strategy.
  547. *
  548. * @type {Object}
  549. */
  550. this.fetchOptions = options.fetchOptions;
  551. /**
  552. * The
  553. * [`CacheQueryOptions`]{@link https://w3c.github.io/ServiceWorker/#dictdef-cachequeryoptions}
  554. * for any `cache.match()` or `cache.put()` calls made by this strategy.
  555. *
  556. * @type {Object}
  557. */
  558. this.matchOptions = options.matchOptions;
  559. }
  560. /**
  561. * Perform a request strategy and returns a `Promise` that will resolve with
  562. * a `Response`, invoking all relevant plugin callbacks.
  563. *
  564. * When a strategy instance is registered with a Workbox
  565. * {@link workbox-routing.Route}, this method is automatically
  566. * called when the route matches.
  567. *
  568. * Alternatively, this method can be used in a standalone `FetchEvent`
  569. * listener by passing it to `event.respondWith()`.
  570. *
  571. * @param {FetchEvent|Object} options A `FetchEvent` or an object with the
  572. * properties listed below.
  573. * @param {Request|string} options.request A request to run this strategy for.
  574. * @param {ExtendableEvent} options.event The event associated with the
  575. * request.
  576. * @param {URL} [options.url]
  577. * @param {*} [options.params]
  578. */
  579. handle(options) {
  580. const [responseDone] = this.handleAll(options);
  581. return responseDone;
  582. }
  583. /**
  584. * Similar to {@link workbox-strategies.Strategy~handle}, but
  585. * instead of just returning a `Promise` that resolves to a `Response` it
  586. * it will return an tuple of `[response, done]` promises, where the former
  587. * (`response`) is equivalent to what `handle()` returns, and the latter is a
  588. * Promise that will resolve once any promises that were added to
  589. * `event.waitUntil()` as part of performing the strategy have completed.
  590. *
  591. * You can await the `done` promise to ensure any extra work performed by
  592. * the strategy (usually caching responses) completes successfully.
  593. *
  594. * @param {FetchEvent|Object} options A `FetchEvent` or an object with the
  595. * properties listed below.
  596. * @param {Request|string} options.request A request to run this strategy for.
  597. * @param {ExtendableEvent} options.event The event associated with the
  598. * request.
  599. * @param {URL} [options.url]
  600. * @param {*} [options.params]
  601. * @return {Array<Promise>} A tuple of [response, done]
  602. * promises that can be used to determine when the response resolves as
  603. * well as when the handler has completed all its work.
  604. */
  605. handleAll(options) {
  606. // Allow for flexible options to be passed.
  607. if (options instanceof FetchEvent) {
  608. options = {
  609. event: options,
  610. request: options.request
  611. };
  612. }
  613. const event = options.event;
  614. const request = typeof options.request === 'string' ? new Request(options.request) : options.request;
  615. const params = 'params' in options ? options.params : undefined;
  616. const handler = new StrategyHandler(this, {
  617. event,
  618. request,
  619. params
  620. });
  621. const responseDone = this._getResponse(handler, request, event);
  622. const handlerDone = this._awaitComplete(responseDone, handler, request, event); // Return an array of promises, suitable for use with Promise.all().
  623. return [responseDone, handlerDone];
  624. }
  625. async _getResponse(handler, request, event) {
  626. await handler.runCallbacks('handlerWillStart', {
  627. event,
  628. request
  629. });
  630. let response = undefined;
  631. try {
  632. response = await this._handle(request, handler); // The "official" Strategy subclasses all throw this error automatically,
  633. // but in case a third-party Strategy doesn't, ensure that we have a
  634. // consistent failure when there's no response or an error response.
  635. if (!response || response.type === 'error') {
  636. throw new WorkboxError_js.WorkboxError('no-response', {
  637. url: request.url
  638. });
  639. }
  640. } catch (error) {
  641. if (error instanceof Error) {
  642. for (const callback of handler.iterateCallbacks('handlerDidError')) {
  643. response = await callback({
  644. error,
  645. event,
  646. request
  647. });
  648. if (response) {
  649. break;
  650. }
  651. }
  652. }
  653. if (!response) {
  654. throw error;
  655. } else {
  656. logger_js.logger.log(`While responding to '${getFriendlyURL_js.getFriendlyURL(request.url)}', ` + `an ${error instanceof Error ? error.toString() : ''} error occurred. Using a fallback response provided by ` + `a handlerDidError plugin.`);
  657. }
  658. }
  659. for (const callback of handler.iterateCallbacks('handlerWillRespond')) {
  660. response = await callback({
  661. event,
  662. request,
  663. response
  664. });
  665. }
  666. return response;
  667. }
  668. async _awaitComplete(responseDone, handler, request, event) {
  669. let response;
  670. let error;
  671. try {
  672. response = await responseDone;
  673. } catch (error) {// Ignore errors, as response errors should be caught via the `response`
  674. // promise above. The `done` promise will only throw for errors in
  675. // promises passed to `handler.waitUntil()`.
  676. }
  677. try {
  678. await handler.runCallbacks('handlerDidRespond', {
  679. event,
  680. request,
  681. response
  682. });
  683. await handler.doneWaiting();
  684. } catch (waitUntilError) {
  685. if (waitUntilError instanceof Error) {
  686. error = waitUntilError;
  687. }
  688. }
  689. await handler.runCallbacks('handlerDidComplete', {
  690. event,
  691. request,
  692. response,
  693. error: error
  694. });
  695. handler.destroy();
  696. if (error) {
  697. throw error;
  698. }
  699. }
  700. }
  701. /**
  702. * Classes extending the `Strategy` based class should implement this method,
  703. * and leverage the {@link workbox-strategies.StrategyHandler}
  704. * arg to perform all fetching and cache logic, which will ensure all relevant
  705. * cache, cache options, fetch options and plugins are used (per the current
  706. * strategy instance).
  707. *
  708. * @name _handle
  709. * @instance
  710. * @abstract
  711. * @function
  712. * @param {Request} request
  713. * @param {workbox-strategies.StrategyHandler} handler
  714. * @return {Promise<Response>}
  715. *
  716. * @memberof workbox-strategies.Strategy
  717. */
  718. /*
  719. Copyright 2018 Google LLC
  720. Use of this source code is governed by an MIT-style
  721. license that can be found in the LICENSE file or at
  722. https://opensource.org/licenses/MIT.
  723. */
  724. const messages = {
  725. strategyStart: (strategyName, request) => `Using ${strategyName} to respond to '${getFriendlyURL_js.getFriendlyURL(request.url)}'`,
  726. printFinalResponse: response => {
  727. if (response) {
  728. logger_js.logger.groupCollapsed(`View the final response here.`);
  729. logger_js.logger.log(response || '[No response returned]');
  730. logger_js.logger.groupEnd();
  731. }
  732. }
  733. };
  734. /*
  735. Copyright 2018 Google LLC
  736. Use of this source code is governed by an MIT-style
  737. license that can be found in the LICENSE file or at
  738. https://opensource.org/licenses/MIT.
  739. */
  740. /**
  741. * An implementation of a [cache-first](https://developer.chrome.com/docs/workbox/caching-strategies-overview/#cache-first-falling-back-to-network)
  742. * request strategy.
  743. *
  744. * A cache first strategy is useful for assets that have been revisioned,
  745. * such as URLs like `/styles/example.a8f5f1.css`, since they
  746. * can be cached for long periods of time.
  747. *
  748. * If the network request fails, and there is no cache match, this will throw
  749. * a `WorkboxError` exception.
  750. *
  751. * @extends workbox-strategies.Strategy
  752. * @memberof workbox-strategies
  753. */
  754. class CacheFirst extends Strategy {
  755. /**
  756. * @private
  757. * @param {Request|string} request A request to run this strategy for.
  758. * @param {workbox-strategies.StrategyHandler} handler The event that
  759. * triggered the request.
  760. * @return {Promise<Response>}
  761. */
  762. async _handle(request, handler) {
  763. const logs = [];
  764. {
  765. assert_js.assert.isInstance(request, Request, {
  766. moduleName: 'workbox-strategies',
  767. className: this.constructor.name,
  768. funcName: 'makeRequest',
  769. paramName: 'request'
  770. });
  771. }
  772. let response = await handler.cacheMatch(request);
  773. let error = undefined;
  774. if (!response) {
  775. {
  776. logs.push(`No response found in the '${this.cacheName}' cache. ` + `Will respond with a network request.`);
  777. }
  778. try {
  779. response = await handler.fetchAndCachePut(request);
  780. } catch (err) {
  781. if (err instanceof Error) {
  782. error = err;
  783. }
  784. }
  785. {
  786. if (response) {
  787. logs.push(`Got response from network.`);
  788. } else {
  789. logs.push(`Unable to get a response from the network.`);
  790. }
  791. }
  792. } else {
  793. {
  794. logs.push(`Found a cached response in the '${this.cacheName}' cache.`);
  795. }
  796. }
  797. {
  798. logger_js.logger.groupCollapsed(messages.strategyStart(this.constructor.name, request));
  799. for (const log of logs) {
  800. logger_js.logger.log(log);
  801. }
  802. messages.printFinalResponse(response);
  803. logger_js.logger.groupEnd();
  804. }
  805. if (!response) {
  806. throw new WorkboxError_js.WorkboxError('no-response', {
  807. url: request.url,
  808. error
  809. });
  810. }
  811. return response;
  812. }
  813. }
  814. /*
  815. Copyright 2018 Google LLC
  816. Use of this source code is governed by an MIT-style
  817. license that can be found in the LICENSE file or at
  818. https://opensource.org/licenses/MIT.
  819. */
  820. /**
  821. * An implementation of a [cache-only](https://developer.chrome.com/docs/workbox/caching-strategies-overview/#cache-only)
  822. * request strategy.
  823. *
  824. * This class is useful if you want to take advantage of any
  825. * [Workbox plugins](https://developer.chrome.com/docs/workbox/using-plugins/).
  826. *
  827. * If there is no cache match, this will throw a `WorkboxError` exception.
  828. *
  829. * @extends workbox-strategies.Strategy
  830. * @memberof workbox-strategies
  831. */
  832. class CacheOnly extends Strategy {
  833. /**
  834. * @private
  835. * @param {Request|string} request A request to run this strategy for.
  836. * @param {workbox-strategies.StrategyHandler} handler The event that
  837. * triggered the request.
  838. * @return {Promise<Response>}
  839. */
  840. async _handle(request, handler) {
  841. {
  842. assert_js.assert.isInstance(request, Request, {
  843. moduleName: 'workbox-strategies',
  844. className: this.constructor.name,
  845. funcName: 'makeRequest',
  846. paramName: 'request'
  847. });
  848. }
  849. const response = await handler.cacheMatch(request);
  850. {
  851. logger_js.logger.groupCollapsed(messages.strategyStart(this.constructor.name, request));
  852. if (response) {
  853. logger_js.logger.log(`Found a cached response in the '${this.cacheName}' ` + `cache.`);
  854. messages.printFinalResponse(response);
  855. } else {
  856. logger_js.logger.log(`No response found in the '${this.cacheName}' cache.`);
  857. }
  858. logger_js.logger.groupEnd();
  859. }
  860. if (!response) {
  861. throw new WorkboxError_js.WorkboxError('no-response', {
  862. url: request.url
  863. });
  864. }
  865. return response;
  866. }
  867. }
  868. /*
  869. Copyright 2018 Google LLC
  870. Use of this source code is governed by an MIT-style
  871. license that can be found in the LICENSE file or at
  872. https://opensource.org/licenses/MIT.
  873. */
  874. const cacheOkAndOpaquePlugin = {
  875. /**
  876. * Returns a valid response (to allow caching) if the status is 200 (OK) or
  877. * 0 (opaque).
  878. *
  879. * @param {Object} options
  880. * @param {Response} options.response
  881. * @return {Response|null}
  882. *
  883. * @private
  884. */
  885. cacheWillUpdate: async ({
  886. response
  887. }) => {
  888. if (response.status === 200 || response.status === 0) {
  889. return response;
  890. }
  891. return null;
  892. }
  893. };
  894. /*
  895. Copyright 2018 Google LLC
  896. Use of this source code is governed by an MIT-style
  897. license that can be found in the LICENSE file or at
  898. https://opensource.org/licenses/MIT.
  899. */
  900. /**
  901. * An implementation of a
  902. * [network first](https://developer.chrome.com/docs/workbox/caching-strategies-overview/#network-first-falling-back-to-cache)
  903. * request strategy.
  904. *
  905. * By default, this strategy will cache responses with a 200 status code as
  906. * well as [opaque responses](https://developer.chrome.com/docs/workbox/caching-resources-during-runtime/#opaque-responses).
  907. * Opaque responses are are cross-origin requests where the response doesn't
  908. * support [CORS](https://enable-cors.org/).
  909. *
  910. * If the network request fails, and there is no cache match, this will throw
  911. * a `WorkboxError` exception.
  912. *
  913. * @extends workbox-strategies.Strategy
  914. * @memberof workbox-strategies
  915. */
  916. class NetworkFirst extends Strategy {
  917. /**
  918. * @param {Object} [options]
  919. * @param {string} [options.cacheName] Cache name to store and retrieve
  920. * requests. Defaults to cache names provided by
  921. * {@link workbox-core.cacheNames}.
  922. * @param {Array<Object>} [options.plugins] [Plugins]{@link https://developers.google.com/web/tools/workbox/guides/using-plugins}
  923. * to use in conjunction with this caching strategy.
  924. * @param {Object} [options.fetchOptions] Values passed along to the
  925. * [`init`](https://developer.mozilla.org/en-US/docs/Web/API/WindowOrWorkerGlobalScope/fetch#Parameters)
  926. * of [non-navigation](https://github.com/GoogleChrome/workbox/issues/1796)
  927. * `fetch()` requests made by this strategy.
  928. * @param {Object} [options.matchOptions] [`CacheQueryOptions`](https://w3c.github.io/ServiceWorker/#dictdef-cachequeryoptions)
  929. * @param {number} [options.networkTimeoutSeconds] If set, any network requests
  930. * that fail to respond within the timeout will fallback to the cache.
  931. *
  932. * This option can be used to combat
  933. * "[lie-fi]{@link https://developers.google.com/web/fundamentals/performance/poor-connectivity/#lie-fi}"
  934. * scenarios.
  935. */
  936. constructor(options = {}) {
  937. super(options); // If this instance contains no plugins with a 'cacheWillUpdate' callback,
  938. // prepend the `cacheOkAndOpaquePlugin` plugin to the plugins list.
  939. if (!this.plugins.some(p => 'cacheWillUpdate' in p)) {
  940. this.plugins.unshift(cacheOkAndOpaquePlugin);
  941. }
  942. this._networkTimeoutSeconds = options.networkTimeoutSeconds || 0;
  943. {
  944. if (this._networkTimeoutSeconds) {
  945. assert_js.assert.isType(this._networkTimeoutSeconds, 'number', {
  946. moduleName: 'workbox-strategies',
  947. className: this.constructor.name,
  948. funcName: 'constructor',
  949. paramName: 'networkTimeoutSeconds'
  950. });
  951. }
  952. }
  953. }
  954. /**
  955. * @private
  956. * @param {Request|string} request A request to run this strategy for.
  957. * @param {workbox-strategies.StrategyHandler} handler The event that
  958. * triggered the request.
  959. * @return {Promise<Response>}
  960. */
  961. async _handle(request, handler) {
  962. const logs = [];
  963. {
  964. assert_js.assert.isInstance(request, Request, {
  965. moduleName: 'workbox-strategies',
  966. className: this.constructor.name,
  967. funcName: 'handle',
  968. paramName: 'makeRequest'
  969. });
  970. }
  971. const promises = [];
  972. let timeoutId;
  973. if (this._networkTimeoutSeconds) {
  974. const {
  975. id,
  976. promise
  977. } = this._getTimeoutPromise({
  978. request,
  979. logs,
  980. handler
  981. });
  982. timeoutId = id;
  983. promises.push(promise);
  984. }
  985. const networkPromise = this._getNetworkPromise({
  986. timeoutId,
  987. request,
  988. logs,
  989. handler
  990. });
  991. promises.push(networkPromise);
  992. const response = await handler.waitUntil((async () => {
  993. // Promise.race() will resolve as soon as the first promise resolves.
  994. return (await handler.waitUntil(Promise.race(promises))) || ( // If Promise.race() resolved with null, it might be due to a network
  995. // timeout + a cache miss. If that were to happen, we'd rather wait until
  996. // the networkPromise resolves instead of returning null.
  997. // Note that it's fine to await an already-resolved promise, so we don't
  998. // have to check to see if it's still "in flight".
  999. await networkPromise);
  1000. })());
  1001. {
  1002. logger_js.logger.groupCollapsed(messages.strategyStart(this.constructor.name, request));
  1003. for (const log of logs) {
  1004. logger_js.logger.log(log);
  1005. }
  1006. messages.printFinalResponse(response);
  1007. logger_js.logger.groupEnd();
  1008. }
  1009. if (!response) {
  1010. throw new WorkboxError_js.WorkboxError('no-response', {
  1011. url: request.url
  1012. });
  1013. }
  1014. return response;
  1015. }
  1016. /**
  1017. * @param {Object} options
  1018. * @param {Request} options.request
  1019. * @param {Array} options.logs A reference to the logs array
  1020. * @param {Event} options.event
  1021. * @return {Promise<Response>}
  1022. *
  1023. * @private
  1024. */
  1025. _getTimeoutPromise({
  1026. request,
  1027. logs,
  1028. handler
  1029. }) {
  1030. let timeoutId;
  1031. const timeoutPromise = new Promise(resolve => {
  1032. const onNetworkTimeout = async () => {
  1033. {
  1034. logs.push(`Timing out the network response at ` + `${this._networkTimeoutSeconds} seconds.`);
  1035. }
  1036. resolve(await handler.cacheMatch(request));
  1037. };
  1038. timeoutId = setTimeout(onNetworkTimeout, this._networkTimeoutSeconds * 1000);
  1039. });
  1040. return {
  1041. promise: timeoutPromise,
  1042. id: timeoutId
  1043. };
  1044. }
  1045. /**
  1046. * @param {Object} options
  1047. * @param {number|undefined} options.timeoutId
  1048. * @param {Request} options.request
  1049. * @param {Array} options.logs A reference to the logs Array.
  1050. * @param {Event} options.event
  1051. * @return {Promise<Response>}
  1052. *
  1053. * @private
  1054. */
  1055. async _getNetworkPromise({
  1056. timeoutId,
  1057. request,
  1058. logs,
  1059. handler
  1060. }) {
  1061. let error;
  1062. let response;
  1063. try {
  1064. response = await handler.fetchAndCachePut(request);
  1065. } catch (fetchError) {
  1066. if (fetchError instanceof Error) {
  1067. error = fetchError;
  1068. }
  1069. }
  1070. if (timeoutId) {
  1071. clearTimeout(timeoutId);
  1072. }
  1073. {
  1074. if (response) {
  1075. logs.push(`Got response from network.`);
  1076. } else {
  1077. logs.push(`Unable to get a response from the network. Will respond ` + `with a cached response.`);
  1078. }
  1079. }
  1080. if (error || !response) {
  1081. response = await handler.cacheMatch(request);
  1082. {
  1083. if (response) {
  1084. logs.push(`Found a cached response in the '${this.cacheName}'` + ` cache.`);
  1085. } else {
  1086. logs.push(`No response found in the '${this.cacheName}' cache.`);
  1087. }
  1088. }
  1089. }
  1090. return response;
  1091. }
  1092. }
  1093. /*
  1094. Copyright 2018 Google LLC
  1095. Use of this source code is governed by an MIT-style
  1096. license that can be found in the LICENSE file or at
  1097. https://opensource.org/licenses/MIT.
  1098. */
  1099. /**
  1100. * An implementation of a
  1101. * [network-only](https://developer.chrome.com/docs/workbox/caching-strategies-overview/#network-only)
  1102. * request strategy.
  1103. *
  1104. * This class is useful if you want to take advantage of any
  1105. * [Workbox plugins](https://developer.chrome.com/docs/workbox/using-plugins/).
  1106. *
  1107. * If the network request fails, this will throw a `WorkboxError` exception.
  1108. *
  1109. * @extends workbox-strategies.Strategy
  1110. * @memberof workbox-strategies
  1111. */
  1112. class NetworkOnly extends Strategy {
  1113. /**
  1114. * @param {Object} [options]
  1115. * @param {Array<Object>} [options.plugins] [Plugins]{@link https://developers.google.com/web/tools/workbox/guides/using-plugins}
  1116. * to use in conjunction with this caching strategy.
  1117. * @param {Object} [options.fetchOptions] Values passed along to the
  1118. * [`init`](https://developer.mozilla.org/en-US/docs/Web/API/WindowOrWorkerGlobalScope/fetch#Parameters)
  1119. * of [non-navigation](https://github.com/GoogleChrome/workbox/issues/1796)
  1120. * `fetch()` requests made by this strategy.
  1121. * @param {number} [options.networkTimeoutSeconds] If set, any network requests
  1122. * that fail to respond within the timeout will result in a network error.
  1123. */
  1124. constructor(options = {}) {
  1125. super(options);
  1126. this._networkTimeoutSeconds = options.networkTimeoutSeconds || 0;
  1127. }
  1128. /**
  1129. * @private
  1130. * @param {Request|string} request A request to run this strategy for.
  1131. * @param {workbox-strategies.StrategyHandler} handler The event that
  1132. * triggered the request.
  1133. * @return {Promise<Response>}
  1134. */
  1135. async _handle(request, handler) {
  1136. {
  1137. assert_js.assert.isInstance(request, Request, {
  1138. moduleName: 'workbox-strategies',
  1139. className: this.constructor.name,
  1140. funcName: '_handle',
  1141. paramName: 'request'
  1142. });
  1143. }
  1144. let error = undefined;
  1145. let response;
  1146. try {
  1147. const promises = [handler.fetch(request)];
  1148. if (this._networkTimeoutSeconds) {
  1149. const timeoutPromise = timeout_js.timeout(this._networkTimeoutSeconds * 1000);
  1150. promises.push(timeoutPromise);
  1151. }
  1152. response = await Promise.race(promises);
  1153. if (!response) {
  1154. throw new Error(`Timed out the network response after ` + `${this._networkTimeoutSeconds} seconds.`);
  1155. }
  1156. } catch (err) {
  1157. if (err instanceof Error) {
  1158. error = err;
  1159. }
  1160. }
  1161. {
  1162. logger_js.logger.groupCollapsed(messages.strategyStart(this.constructor.name, request));
  1163. if (response) {
  1164. logger_js.logger.log(`Got response from network.`);
  1165. } else {
  1166. logger_js.logger.log(`Unable to get a response from the network.`);
  1167. }
  1168. messages.printFinalResponse(response);
  1169. logger_js.logger.groupEnd();
  1170. }
  1171. if (!response) {
  1172. throw new WorkboxError_js.WorkboxError('no-response', {
  1173. url: request.url,
  1174. error
  1175. });
  1176. }
  1177. return response;
  1178. }
  1179. }
  1180. /*
  1181. Copyright 2018 Google LLC
  1182. Use of this source code is governed by an MIT-style
  1183. license that can be found in the LICENSE file or at
  1184. https://opensource.org/licenses/MIT.
  1185. */
  1186. /**
  1187. * An implementation of a
  1188. * [stale-while-revalidate](https://developer.chrome.com/docs/workbox/caching-strategies-overview/#stale-while-revalidate)
  1189. * request strategy.
  1190. *
  1191. * Resources are requested from both the cache and the network in parallel.
  1192. * The strategy will respond with the cached version if available, otherwise
  1193. * wait for the network response. The cache is updated with the network response
  1194. * with each successful request.
  1195. *
  1196. * By default, this strategy will cache responses with a 200 status code as
  1197. * well as [opaque responses](https://developer.chrome.com/docs/workbox/caching-resources-during-runtime/#opaque-responses).
  1198. * Opaque responses are cross-origin requests where the response doesn't
  1199. * support [CORS](https://enable-cors.org/).
  1200. *
  1201. * If the network request fails, and there is no cache match, this will throw
  1202. * a `WorkboxError` exception.
  1203. *
  1204. * @extends workbox-strategies.Strategy
  1205. * @memberof workbox-strategies
  1206. */
  1207. class StaleWhileRevalidate extends Strategy {
  1208. /**
  1209. * @param {Object} [options]
  1210. * @param {string} [options.cacheName] Cache name to store and retrieve
  1211. * requests. Defaults to cache names provided by
  1212. * {@link workbox-core.cacheNames}.
  1213. * @param {Array<Object>} [options.plugins] [Plugins]{@link https://developers.google.com/web/tools/workbox/guides/using-plugins}
  1214. * to use in conjunction with this caching strategy.
  1215. * @param {Object} [options.fetchOptions] Values passed along to the
  1216. * [`init`](https://developer.mozilla.org/en-US/docs/Web/API/WindowOrWorkerGlobalScope/fetch#Parameters)
  1217. * of [non-navigation](https://github.com/GoogleChrome/workbox/issues/1796)
  1218. * `fetch()` requests made by this strategy.
  1219. * @param {Object} [options.matchOptions] [`CacheQueryOptions`](https://w3c.github.io/ServiceWorker/#dictdef-cachequeryoptions)
  1220. */
  1221. constructor(options = {}) {
  1222. super(options); // If this instance contains no plugins with a 'cacheWillUpdate' callback,
  1223. // prepend the `cacheOkAndOpaquePlugin` plugin to the plugins list.
  1224. if (!this.plugins.some(p => 'cacheWillUpdate' in p)) {
  1225. this.plugins.unshift(cacheOkAndOpaquePlugin);
  1226. }
  1227. }
  1228. /**
  1229. * @private
  1230. * @param {Request|string} request A request to run this strategy for.
  1231. * @param {workbox-strategies.StrategyHandler} handler The event that
  1232. * triggered the request.
  1233. * @return {Promise<Response>}
  1234. */
  1235. async _handle(request, handler) {
  1236. const logs = [];
  1237. {
  1238. assert_js.assert.isInstance(request, Request, {
  1239. moduleName: 'workbox-strategies',
  1240. className: this.constructor.name,
  1241. funcName: 'handle',
  1242. paramName: 'request'
  1243. });
  1244. }
  1245. const fetchAndCachePromise = handler.fetchAndCachePut(request).catch(() => {// Swallow this error because a 'no-response' error will be thrown in
  1246. // main handler return flow. This will be in the `waitUntil()` flow.
  1247. });
  1248. void handler.waitUntil(fetchAndCachePromise);
  1249. let response = await handler.cacheMatch(request);
  1250. let error;
  1251. if (response) {
  1252. {
  1253. logs.push(`Found a cached response in the '${this.cacheName}'` + ` cache. Will update with the network response in the background.`);
  1254. }
  1255. } else {
  1256. {
  1257. logs.push(`No response found in the '${this.cacheName}' cache. ` + `Will wait for the network response.`);
  1258. }
  1259. try {
  1260. // NOTE(philipwalton): Really annoying that we have to type cast here.
  1261. // https://github.com/microsoft/TypeScript/issues/20006
  1262. response = await fetchAndCachePromise;
  1263. } catch (err) {
  1264. if (err instanceof Error) {
  1265. error = err;
  1266. }
  1267. }
  1268. }
  1269. {
  1270. logger_js.logger.groupCollapsed(messages.strategyStart(this.constructor.name, request));
  1271. for (const log of logs) {
  1272. logger_js.logger.log(log);
  1273. }
  1274. messages.printFinalResponse(response);
  1275. logger_js.logger.groupEnd();
  1276. }
  1277. if (!response) {
  1278. throw new WorkboxError_js.WorkboxError('no-response', {
  1279. url: request.url,
  1280. error
  1281. });
  1282. }
  1283. return response;
  1284. }
  1285. }
  1286. exports.CacheFirst = CacheFirst;
  1287. exports.CacheOnly = CacheOnly;
  1288. exports.NetworkFirst = NetworkFirst;
  1289. exports.NetworkOnly = NetworkOnly;
  1290. exports.StaleWhileRevalidate = StaleWhileRevalidate;
  1291. exports.Strategy = Strategy;
  1292. exports.StrategyHandler = StrategyHandler;
  1293. return exports;
  1294. }({}, workbox.core._private, workbox.core._private, workbox.core._private, workbox.core._private, workbox.core._private, workbox.core._private, workbox.core._private, workbox.core._private, workbox.core._private));
  1295. //# sourceMappingURL=workbox-strategies.dev.js.map