frontend/public/workbox-6b19f60b.js (view raw)
1define("./workbox-6b19f60b.js",['exports'], function (exports) { 'use strict';
2
3 try {
4 self['workbox:core:6.1.5'] && _();
5 } catch (e) {}
6
7 /*
8 Copyright 2019 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 const logger = (() => {
14 // Don't overwrite this value if it's already set.
15 // See https://github.com/GoogleChrome/workbox/pull/2284#issuecomment-560470923
16 if (!('__WB_DISABLE_DEV_LOGS' in self)) {
17 self.__WB_DISABLE_DEV_LOGS = false;
18 }
19
20 let inGroup = false;
21 const methodToColorMap = {
22 debug: `#7f8c8d`,
23 log: `#2ecc71`,
24 warn: `#f39c12`,
25 error: `#c0392b`,
26 groupCollapsed: `#3498db`,
27 groupEnd: null
28 };
29
30 const print = function (method, args) {
31 if (self.__WB_DISABLE_DEV_LOGS) {
32 return;
33 }
34
35 if (method === 'groupCollapsed') {
36 // Safari doesn't print all console.groupCollapsed() arguments:
37 // https://bugs.webkit.org/show_bug.cgi?id=182754
38 if (/^((?!chrome|android).)*safari/i.test(navigator.userAgent)) {
39 console[method](...args);
40 return;
41 }
42 }
43
44 const styles = [`background: ${methodToColorMap[method]}`, `border-radius: 0.5em`, `color: white`, `font-weight: bold`, `padding: 2px 0.5em`]; // When in a group, the workbox prefix is not displayed.
45
46 const logPrefix = inGroup ? [] : ['%cworkbox', styles.join(';')];
47 console[method](...logPrefix, ...args);
48
49 if (method === 'groupCollapsed') {
50 inGroup = true;
51 }
52
53 if (method === 'groupEnd') {
54 inGroup = false;
55 }
56 };
57
58 const api = {};
59 const loggerMethods = Object.keys(methodToColorMap);
60
61 for (const key of loggerMethods) {
62 const method = key;
63
64 api[method] = (...args) => {
65 print(method, args);
66 };
67 }
68
69 return api;
70 })();
71
72 /*
73 Copyright 2018 Google LLC
74
75 Use of this source code is governed by an MIT-style
76 license that can be found in the LICENSE file or at
77 https://opensource.org/licenses/MIT.
78 */
79 const messages$1 = {
80 'invalid-value': ({
81 paramName,
82 validValueDescription,
83 value
84 }) => {
85 if (!paramName || !validValueDescription) {
86 throw new Error(`Unexpected input to 'invalid-value' error.`);
87 }
88
89 return `The '${paramName}' parameter was given a value with an ` + `unexpected value. ${validValueDescription} Received a value of ` + `${JSON.stringify(value)}.`;
90 },
91 'not-an-array': ({
92 moduleName,
93 className,
94 funcName,
95 paramName
96 }) => {
97 if (!moduleName || !className || !funcName || !paramName) {
98 throw new Error(`Unexpected input to 'not-an-array' error.`);
99 }
100
101 return `The parameter '${paramName}' passed into ` + `'${moduleName}.${className}.${funcName}()' must be an array.`;
102 },
103 'incorrect-type': ({
104 expectedType,
105 paramName,
106 moduleName,
107 className,
108 funcName
109 }) => {
110 if (!expectedType || !paramName || !moduleName || !funcName) {
111 throw new Error(`Unexpected input to 'incorrect-type' error.`);
112 }
113
114 return `The parameter '${paramName}' passed into ` + `'${moduleName}.${className ? className + '.' : ''}` + `${funcName}()' must be of type ${expectedType}.`;
115 },
116 'incorrect-class': ({
117 expectedClass,
118 paramName,
119 moduleName,
120 className,
121 funcName,
122 isReturnValueProblem
123 }) => {
124 if (!expectedClass || !moduleName || !funcName) {
125 throw new Error(`Unexpected input to 'incorrect-class' error.`);
126 }
127
128 if (isReturnValueProblem) {
129 return `The return value from ` + `'${moduleName}.${className ? className + '.' : ''}${funcName}()' ` + `must be an instance of class ${expectedClass.name}.`;
130 }
131
132 return `The parameter '${paramName}' passed into ` + `'${moduleName}.${className ? className + '.' : ''}${funcName}()' ` + `must be an instance of class ${expectedClass.name}.`;
133 },
134 'missing-a-method': ({
135 expectedMethod,
136 paramName,
137 moduleName,
138 className,
139 funcName
140 }) => {
141 if (!expectedMethod || !paramName || !moduleName || !className || !funcName) {
142 throw new Error(`Unexpected input to 'missing-a-method' error.`);
143 }
144
145 return `${moduleName}.${className}.${funcName}() expected the ` + `'${paramName}' parameter to expose a '${expectedMethod}' method.`;
146 },
147 'add-to-cache-list-unexpected-type': ({
148 entry
149 }) => {
150 return `An unexpected entry was passed to ` + `'workbox-precaching.PrecacheController.addToCacheList()' The entry ` + `'${JSON.stringify(entry)}' isn't supported. You must supply an array of ` + `strings with one or more characters, objects with a url property or ` + `Request objects.`;
151 },
152 'add-to-cache-list-conflicting-entries': ({
153 firstEntry,
154 secondEntry
155 }) => {
156 if (!firstEntry || !secondEntry) {
157 throw new Error(`Unexpected input to ` + `'add-to-cache-list-duplicate-entries' error.`);
158 }
159
160 return `Two of the entries passed to ` + `'workbox-precaching.PrecacheController.addToCacheList()' had the URL ` + `${firstEntry._entryId} but different revision details. Workbox is ` + `unable to cache and version the asset correctly. Please remove one ` + `of the entries.`;
161 },
162 'plugin-error-request-will-fetch': ({
163 thrownError
164 }) => {
165 if (!thrownError) {
166 throw new Error(`Unexpected input to ` + `'plugin-error-request-will-fetch', error.`);
167 }
168
169 return `An error was thrown by a plugins 'requestWillFetch()' method. ` + `The thrown error message was: '${thrownError.message}'.`;
170 },
171 'invalid-cache-name': ({
172 cacheNameId,
173 value
174 }) => {
175 if (!cacheNameId) {
176 throw new Error(`Expected a 'cacheNameId' for error 'invalid-cache-name'`);
177 }
178
179 return `You must provide a name containing at least one character for ` + `setCacheDetails({${cacheNameId}: '...'}). Received a value of ` + `'${JSON.stringify(value)}'`;
180 },
181 'unregister-route-but-not-found-with-method': ({
182 method
183 }) => {
184 if (!method) {
185 throw new Error(`Unexpected input to ` + `'unregister-route-but-not-found-with-method' error.`);
186 }
187
188 return `The route you're trying to unregister was not previously ` + `registered for the method type '${method}'.`;
189 },
190 'unregister-route-route-not-registered': () => {
191 return `The route you're trying to unregister was not previously ` + `registered.`;
192 },
193 'queue-replay-failed': ({
194 name
195 }) => {
196 return `Replaying the background sync queue '${name}' failed.`;
197 },
198 'duplicate-queue-name': ({
199 name
200 }) => {
201 return `The Queue name '${name}' is already being used. ` + `All instances of backgroundSync.Queue must be given unique names.`;
202 },
203 'expired-test-without-max-age': ({
204 methodName,
205 paramName
206 }) => {
207 return `The '${methodName}()' method can only be used when the ` + `'${paramName}' is used in the constructor.`;
208 },
209 'unsupported-route-type': ({
210 moduleName,
211 className,
212 funcName,
213 paramName
214 }) => {
215 return `The supplied '${paramName}' parameter was an unsupported type. ` + `Please check the docs for ${moduleName}.${className}.${funcName} for ` + `valid input types.`;
216 },
217 'not-array-of-class': ({
218 value,
219 expectedClass,
220 moduleName,
221 className,
222 funcName,
223 paramName
224 }) => {
225 return `The supplied '${paramName}' parameter must be an array of ` + `'${expectedClass}' objects. Received '${JSON.stringify(value)},'. ` + `Please check the call to ${moduleName}.${className}.${funcName}() ` + `to fix the issue.`;
226 },
227 'max-entries-or-age-required': ({
228 moduleName,
229 className,
230 funcName
231 }) => {
232 return `You must define either config.maxEntries or config.maxAgeSeconds` + `in ${moduleName}.${className}.${funcName}`;
233 },
234 'statuses-or-headers-required': ({
235 moduleName,
236 className,
237 funcName
238 }) => {
239 return `You must define either config.statuses or config.headers` + `in ${moduleName}.${className}.${funcName}`;
240 },
241 'invalid-string': ({
242 moduleName,
243 funcName,
244 paramName
245 }) => {
246 if (!paramName || !moduleName || !funcName) {
247 throw new Error(`Unexpected input to 'invalid-string' error.`);
248 }
249
250 return `When using strings, the '${paramName}' parameter must start with ` + `'http' (for cross-origin matches) or '/' (for same-origin matches). ` + `Please see the docs for ${moduleName}.${funcName}() for ` + `more info.`;
251 },
252 'channel-name-required': () => {
253 return `You must provide a channelName to construct a ` + `BroadcastCacheUpdate instance.`;
254 },
255 'invalid-responses-are-same-args': () => {
256 return `The arguments passed into responsesAreSame() appear to be ` + `invalid. Please ensure valid Responses are used.`;
257 },
258 'expire-custom-caches-only': () => {
259 return `You must provide a 'cacheName' property when using the ` + `expiration plugin with a runtime caching strategy.`;
260 },
261 'unit-must-be-bytes': ({
262 normalizedRangeHeader
263 }) => {
264 if (!normalizedRangeHeader) {
265 throw new Error(`Unexpected input to 'unit-must-be-bytes' error.`);
266 }
267
268 return `The 'unit' portion of the Range header must be set to 'bytes'. ` + `The Range header provided was "${normalizedRangeHeader}"`;
269 },
270 'single-range-only': ({
271 normalizedRangeHeader
272 }) => {
273 if (!normalizedRangeHeader) {
274 throw new Error(`Unexpected input to 'single-range-only' error.`);
275 }
276
277 return `Multiple ranges are not supported. Please use a single start ` + `value, and optional end value. The Range header provided was ` + `"${normalizedRangeHeader}"`;
278 },
279 'invalid-range-values': ({
280 normalizedRangeHeader
281 }) => {
282 if (!normalizedRangeHeader) {
283 throw new Error(`Unexpected input to 'invalid-range-values' error.`);
284 }
285
286 return `The Range header is missing both start and end values. At least ` + `one of those values is needed. The Range header provided was ` + `"${normalizedRangeHeader}"`;
287 },
288 'no-range-header': () => {
289 return `No Range header was found in the Request provided.`;
290 },
291 'range-not-satisfiable': ({
292 size,
293 start,
294 end
295 }) => {
296 return `The start (${start}) and end (${end}) values in the Range are ` + `not satisfiable by the cached response, which is ${size} bytes.`;
297 },
298 'attempt-to-cache-non-get-request': ({
299 url,
300 method
301 }) => {
302 return `Unable to cache '${url}' because it is a '${method}' request and ` + `only 'GET' requests can be cached.`;
303 },
304 'cache-put-with-no-response': ({
305 url
306 }) => {
307 return `There was an attempt to cache '${url}' but the response was not ` + `defined.`;
308 },
309 'no-response': ({
310 url,
311 error
312 }) => {
313 let message = `The strategy could not generate a response for '${url}'.`;
314
315 if (error) {
316 message += ` The underlying error is ${error}.`;
317 }
318
319 return message;
320 },
321 'bad-precaching-response': ({
322 url,
323 status
324 }) => {
325 return `The precaching request for '${url}' failed` + (status ? ` with an HTTP status of ${status}.` : `.`);
326 },
327 'non-precached-url': ({
328 url
329 }) => {
330 return `createHandlerBoundToURL('${url}') was called, but that URL is ` + `not precached. Please pass in a URL that is precached instead.`;
331 },
332 'add-to-cache-list-conflicting-integrities': ({
333 url
334 }) => {
335 return `Two of the entries passed to ` + `'workbox-precaching.PrecacheController.addToCacheList()' had the URL ` + `${url} with different integrity values. Please remove one of them.`;
336 },
337 'missing-precache-entry': ({
338 cacheName,
339 url
340 }) => {
341 return `Unable to find a precached response in ${cacheName} for ${url}.`;
342 },
343 'cross-origin-copy-response': ({
344 origin
345 }) => {
346 return `workbox-core.copyResponse() can only be used with same-origin ` + `responses. It was passed a response with origin ${origin}.`;
347 }
348 };
349
350 /*
351 Copyright 2018 Google LLC
352
353 Use of this source code is governed by an MIT-style
354 license that can be found in the LICENSE file or at
355 https://opensource.org/licenses/MIT.
356 */
357
358 const generatorFunction = (code, details = {}) => {
359 const message = messages$1[code];
360
361 if (!message) {
362 throw new Error(`Unable to find message for code '${code}'.`);
363 }
364
365 return message(details);
366 };
367
368 const messageGenerator = generatorFunction;
369
370 /*
371 Copyright 2018 Google LLC
372
373 Use of this source code is governed by an MIT-style
374 license that can be found in the LICENSE file or at
375 https://opensource.org/licenses/MIT.
376 */
377 /**
378 * Workbox errors should be thrown with this class.
379 * This allows use to ensure the type easily in tests,
380 * helps developers identify errors from workbox
381 * easily and allows use to optimise error
382 * messages correctly.
383 *
384 * @private
385 */
386
387 class WorkboxError extends Error {
388 /**
389 *
390 * @param {string} errorCode The error code that
391 * identifies this particular error.
392 * @param {Object=} details Any relevant arguments
393 * that will help developers identify issues should
394 * be added as a key on the context object.
395 */
396 constructor(errorCode, details) {
397 const message = messageGenerator(errorCode, details);
398 super(message);
399 this.name = errorCode;
400 this.details = details;
401 }
402
403 }
404
405 /*
406 Copyright 2018 Google LLC
407
408 Use of this source code is governed by an MIT-style
409 license that can be found in the LICENSE file or at
410 https://opensource.org/licenses/MIT.
411 */
412 /*
413 * This method throws if the supplied value is not an array.
414 * The destructed values are required to produce a meaningful error for users.
415 * The destructed and restructured object is so it's clear what is
416 * needed.
417 */
418
419 const isArray = (value, details) => {
420 if (!Array.isArray(value)) {
421 throw new WorkboxError('not-an-array', details);
422 }
423 };
424
425 const hasMethod = (object, expectedMethod, details) => {
426 const type = typeof object[expectedMethod];
427
428 if (type !== 'function') {
429 details['expectedMethod'] = expectedMethod;
430 throw new WorkboxError('missing-a-method', details);
431 }
432 };
433
434 const isType = (object, expectedType, details) => {
435 if (typeof object !== expectedType) {
436 details['expectedType'] = expectedType;
437 throw new WorkboxError('incorrect-type', details);
438 }
439 };
440
441 const isInstance = (object, expectedClass, details) => {
442 if (!(object instanceof expectedClass)) {
443 details['expectedClass'] = expectedClass;
444 throw new WorkboxError('incorrect-class', details);
445 }
446 };
447
448 const isOneOf = (value, validValues, details) => {
449 if (!validValues.includes(value)) {
450 details['validValueDescription'] = `Valid values are ${JSON.stringify(validValues)}.`;
451 throw new WorkboxError('invalid-value', details);
452 }
453 };
454
455 const isArrayOfClass = (value, expectedClass, details) => {
456 const error = new WorkboxError('not-array-of-class', details);
457
458 if (!Array.isArray(value)) {
459 throw error;
460 }
461
462 for (const item of value) {
463 if (!(item instanceof expectedClass)) {
464 throw error;
465 }
466 }
467 };
468
469 const finalAssertExports = {
470 hasMethod,
471 isArray,
472 isInstance,
473 isOneOf,
474 isType,
475 isArrayOfClass
476 };
477
478 try {
479 self['workbox:routing:6.1.5'] && _();
480 } catch (e) {}
481
482 /*
483 Copyright 2018 Google LLC
484
485 Use of this source code is governed by an MIT-style
486 license that can be found in the LICENSE file or at
487 https://opensource.org/licenses/MIT.
488 */
489 /**
490 * The default HTTP method, 'GET', used when there's no specific method
491 * configured for a route.
492 *
493 * @type {string}
494 *
495 * @private
496 */
497
498 const defaultMethod = 'GET';
499 /**
500 * The list of valid HTTP methods associated with requests that could be routed.
501 *
502 * @type {Array<string>}
503 *
504 * @private
505 */
506
507 const validMethods = ['DELETE', 'GET', 'HEAD', 'PATCH', 'POST', 'PUT'];
508
509 /*
510 Copyright 2018 Google LLC
511
512 Use of this source code is governed by an MIT-style
513 license that can be found in the LICENSE file or at
514 https://opensource.org/licenses/MIT.
515 */
516 /**
517 * @param {function()|Object} handler Either a function, or an object with a
518 * 'handle' method.
519 * @return {Object} An object with a handle method.
520 *
521 * @private
522 */
523
524 const normalizeHandler = handler => {
525 if (handler && typeof handler === 'object') {
526 {
527 finalAssertExports.hasMethod(handler, 'handle', {
528 moduleName: 'workbox-routing',
529 className: 'Route',
530 funcName: 'constructor',
531 paramName: 'handler'
532 });
533 }
534
535 return handler;
536 } else {
537 {
538 finalAssertExports.isType(handler, 'function', {
539 moduleName: 'workbox-routing',
540 className: 'Route',
541 funcName: 'constructor',
542 paramName: 'handler'
543 });
544 }
545
546 return {
547 handle: handler
548 };
549 }
550 };
551
552 /*
553 Copyright 2018 Google LLC
554
555 Use of this source code is governed by an MIT-style
556 license that can be found in the LICENSE file or at
557 https://opensource.org/licenses/MIT.
558 */
559 /**
560 * A `Route` consists of a pair of callback functions, "match" and "handler".
561 * The "match" callback determine if a route should be used to "handle" a
562 * request by returning a non-falsy value if it can. The "handler" callback
563 * is called when there is a match and should return a Promise that resolves
564 * to a `Response`.
565 *
566 * @memberof module:workbox-routing
567 */
568
569 class Route {
570 /**
571 * Constructor for Route class.
572 *
573 * @param {module:workbox-routing~matchCallback} match
574 * A callback function that determines whether the route matches a given
575 * `fetch` event by returning a non-falsy value.
576 * @param {module:workbox-routing~handlerCallback} handler A callback
577 * function that returns a Promise resolving to a Response.
578 * @param {string} [method='GET'] The HTTP method to match the Route
579 * against.
580 */
581 constructor(match, handler, method = defaultMethod) {
582 {
583 finalAssertExports.isType(match, 'function', {
584 moduleName: 'workbox-routing',
585 className: 'Route',
586 funcName: 'constructor',
587 paramName: 'match'
588 });
589
590 if (method) {
591 finalAssertExports.isOneOf(method, validMethods, {
592 paramName: 'method'
593 });
594 }
595 } // These values are referenced directly by Router so cannot be
596 // altered by minificaton.
597
598
599 this.handler = normalizeHandler(handler);
600 this.match = match;
601 this.method = method;
602 }
603 /**
604 *
605 * @param {module:workbox-routing-handlerCallback} handler A callback
606 * function that returns a Promise resolving to a Response
607 */
608
609
610 setCatchHandler(handler) {
611 this.catchHandler = normalizeHandler(handler);
612 }
613
614 }
615
616 /*
617 Copyright 2018 Google LLC
618
619 Use of this source code is governed by an MIT-style
620 license that can be found in the LICENSE file or at
621 https://opensource.org/licenses/MIT.
622 */
623 /**
624 * RegExpRoute makes it easy to create a regular expression based
625 * [Route]{@link module:workbox-routing.Route}.
626 *
627 * For same-origin requests the RegExp only needs to match part of the URL. For
628 * requests against third-party servers, you must define a RegExp that matches
629 * the start of the URL.
630 *
631 * [See the module docs for info.]{@link https://developers.google.com/web/tools/workbox/modules/workbox-routing}
632 *
633 * @memberof module:workbox-routing
634 * @extends module:workbox-routing.Route
635 */
636
637 class RegExpRoute extends Route {
638 /**
639 * If the regular expression contains
640 * [capture groups]{@link https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/RegExp#grouping-back-references},
641 * the captured values will be passed to the
642 * [handler's]{@link module:workbox-routing~handlerCallback} `params`
643 * argument.
644 *
645 * @param {RegExp} regExp The regular expression to match against URLs.
646 * @param {module:workbox-routing~handlerCallback} handler A callback
647 * function that returns a Promise resulting in a Response.
648 * @param {string} [method='GET'] The HTTP method to match the Route
649 * against.
650 */
651 constructor(regExp, handler, method) {
652 {
653 finalAssertExports.isInstance(regExp, RegExp, {
654 moduleName: 'workbox-routing',
655 className: 'RegExpRoute',
656 funcName: 'constructor',
657 paramName: 'pattern'
658 });
659 }
660
661 const match = ({
662 url
663 }) => {
664 const result = regExp.exec(url.href); // Return immediately if there's no match.
665
666 if (!result) {
667 return;
668 } // Require that the match start at the first character in the URL string
669 // if it's a cross-origin request.
670 // See https://github.com/GoogleChrome/workbox/issues/281 for the context
671 // behind this behavior.
672
673
674 if (url.origin !== location.origin && result.index !== 0) {
675 {
676 logger.debug(`The regular expression '${regExp}' only partially matched ` + `against the cross-origin URL '${url}'. RegExpRoute's will only ` + `handle cross-origin requests if they match the entire URL.`);
677 }
678
679 return;
680 } // If the route matches, but there aren't any capture groups defined, then
681 // this will return [], which is truthy and therefore sufficient to
682 // indicate a match.
683 // If there are capture groups, then it will return their values.
684
685
686 return result.slice(1);
687 };
688
689 super(match, handler, method);
690 }
691
692 }
693
694 /*
695 Copyright 2018 Google LLC
696
697 Use of this source code is governed by an MIT-style
698 license that can be found in the LICENSE file or at
699 https://opensource.org/licenses/MIT.
700 */
701
702 const getFriendlyURL = url => {
703 const urlObj = new URL(String(url), location.href); // See https://github.com/GoogleChrome/workbox/issues/2323
704 // We want to include everything, except for the origin if it's same-origin.
705
706 return urlObj.href.replace(new RegExp(`^${location.origin}`), '');
707 };
708
709 /*
710 Copyright 2018 Google LLC
711
712 Use of this source code is governed by an MIT-style
713 license that can be found in the LICENSE file or at
714 https://opensource.org/licenses/MIT.
715 */
716 /**
717 * The Router can be used to process a FetchEvent through one or more
718 * [Routes]{@link module:workbox-routing.Route} responding with a Request if
719 * a matching route exists.
720 *
721 * If no route matches a given a request, the Router will use a "default"
722 * handler if one is defined.
723 *
724 * Should the matching Route throw an error, the Router will use a "catch"
725 * handler if one is defined to gracefully deal with issues and respond with a
726 * Request.
727 *
728 * If a request matches multiple routes, the **earliest** registered route will
729 * be used to respond to the request.
730 *
731 * @memberof module:workbox-routing
732 */
733
734 class Router {
735 /**
736 * Initializes a new Router.
737 */
738 constructor() {
739 this._routes = new Map();
740 this._defaultHandlerMap = new Map();
741 }
742 /**
743 * @return {Map<string, Array<module:workbox-routing.Route>>} routes A `Map` of HTTP
744 * method name ('GET', etc.) to an array of all the corresponding `Route`
745 * instances that are registered.
746 */
747
748
749 get routes() {
750 return this._routes;
751 }
752 /**
753 * Adds a fetch event listener to respond to events when a route matches
754 * the event's request.
755 */
756
757
758 addFetchListener() {
759 // See https://github.com/Microsoft/TypeScript/issues/28357#issuecomment-436484705
760 self.addEventListener('fetch', event => {
761 const {
762 request
763 } = event;
764 const responsePromise = this.handleRequest({
765 request,
766 event
767 });
768
769 if (responsePromise) {
770 event.respondWith(responsePromise);
771 }
772 });
773 }
774 /**
775 * Adds a message event listener for URLs to cache from the window.
776 * This is useful to cache resources loaded on the page prior to when the
777 * service worker started controlling it.
778 *
779 * The format of the message data sent from the window should be as follows.
780 * Where the `urlsToCache` array may consist of URL strings or an array of
781 * URL string + `requestInit` object (the same as you'd pass to `fetch()`).
782 *
783 * ```
784 * {
785 * type: 'CACHE_URLS',
786 * payload: {
787 * urlsToCache: [
788 * './script1.js',
789 * './script2.js',
790 * ['./script3.js', {mode: 'no-cors'}],
791 * ],
792 * },
793 * }
794 * ```
795 */
796
797
798 addCacheListener() {
799 // See https://github.com/Microsoft/TypeScript/issues/28357#issuecomment-436484705
800 self.addEventListener('message', event => {
801 if (event.data && event.data.type === 'CACHE_URLS') {
802 const {
803 payload
804 } = event.data;
805
806 {
807 logger.debug(`Caching URLs from the window`, payload.urlsToCache);
808 }
809
810 const requestPromises = Promise.all(payload.urlsToCache.map(entry => {
811 if (typeof entry === 'string') {
812 entry = [entry];
813 }
814
815 const request = new Request(...entry);
816 return this.handleRequest({
817 request,
818 event
819 }); // TODO(philipwalton): TypeScript errors without this typecast for
820 // some reason (probably a bug). The real type here should work but
821 // doesn't: `Array<Promise<Response> | undefined>`.
822 })); // TypeScript
823
824 event.waitUntil(requestPromises); // If a MessageChannel was used, reply to the message on success.
825
826 if (event.ports && event.ports[0]) {
827 requestPromises.then(() => event.ports[0].postMessage(true));
828 }
829 }
830 });
831 }
832 /**
833 * Apply the routing rules to a FetchEvent object to get a Response from an
834 * appropriate Route's handler.
835 *
836 * @param {Object} options
837 * @param {Request} options.request The request to handle.
838 * @param {ExtendableEvent} options.event The event that triggered the
839 * request.
840 * @return {Promise<Response>|undefined} A promise is returned if a
841 * registered route can handle the request. If there is no matching
842 * route and there's no `defaultHandler`, `undefined` is returned.
843 */
844
845
846 handleRequest({
847 request,
848 event
849 }) {
850 {
851 finalAssertExports.isInstance(request, Request, {
852 moduleName: 'workbox-routing',
853 className: 'Router',
854 funcName: 'handleRequest',
855 paramName: 'options.request'
856 });
857 }
858
859 const url = new URL(request.url, location.href);
860
861 if (!url.protocol.startsWith('http')) {
862 {
863 logger.debug(`Workbox Router only supports URLs that start with 'http'.`);
864 }
865
866 return;
867 }
868
869 const sameOrigin = url.origin === location.origin;
870 const {
871 params,
872 route
873 } = this.findMatchingRoute({
874 event,
875 request,
876 sameOrigin,
877 url
878 });
879 let handler = route && route.handler;
880 const debugMessages = [];
881
882 {
883 if (handler) {
884 debugMessages.push([`Found a route to handle this request:`, route]);
885
886 if (params) {
887 debugMessages.push([`Passing the following params to the route's handler:`, params]);
888 }
889 }
890 } // If we don't have a handler because there was no matching route, then
891 // fall back to defaultHandler if that's defined.
892
893
894 const method = request.method;
895
896 if (!handler && this._defaultHandlerMap.has(method)) {
897 {
898 debugMessages.push(`Failed to find a matching route. Falling ` + `back to the default handler for ${method}.`);
899 }
900
901 handler = this._defaultHandlerMap.get(method);
902 }
903
904 if (!handler) {
905 {
906 // No handler so Workbox will do nothing. If logs is set of debug
907 // i.e. verbose, we should print out this information.
908 logger.debug(`No route found for: ${getFriendlyURL(url)}`);
909 }
910
911 return;
912 }
913
914 {
915 // We have a handler, meaning Workbox is going to handle the route.
916 // print the routing details to the console.
917 logger.groupCollapsed(`Router is responding to: ${getFriendlyURL(url)}`);
918 debugMessages.forEach(msg => {
919 if (Array.isArray(msg)) {
920 logger.log(...msg);
921 } else {
922 logger.log(msg);
923 }
924 });
925 logger.groupEnd();
926 } // Wrap in try and catch in case the handle method throws a synchronous
927 // error. It should still callback to the catch handler.
928
929
930 let responsePromise;
931
932 try {
933 responsePromise = handler.handle({
934 url,
935 request,
936 event,
937 params
938 });
939 } catch (err) {
940 responsePromise = Promise.reject(err);
941 } // Get route's catch handler, if it exists
942
943
944 const catchHandler = route && route.catchHandler;
945
946 if (responsePromise instanceof Promise && (this._catchHandler || catchHandler)) {
947 responsePromise = responsePromise.catch(async err => {
948 // If there's a route catch handler, process that first
949 if (catchHandler) {
950 {
951 // Still include URL here as it will be async from the console group
952 // and may not make sense without the URL
953 logger.groupCollapsed(`Error thrown when responding to: ` + ` ${getFriendlyURL(url)}. Falling back to route's Catch Handler.`);
954 logger.error(`Error thrown by:`, route);
955 logger.error(err);
956 logger.groupEnd();
957 }
958
959 try {
960 return await catchHandler.handle({
961 url,
962 request,
963 event,
964 params
965 });
966 } catch (catchErr) {
967 err = catchErr;
968 }
969 }
970
971 if (this._catchHandler) {
972 {
973 // Still include URL here as it will be async from the console group
974 // and may not make sense without the URL
975 logger.groupCollapsed(`Error thrown when responding to: ` + ` ${getFriendlyURL(url)}. Falling back to global Catch Handler.`);
976 logger.error(`Error thrown by:`, route);
977 logger.error(err);
978 logger.groupEnd();
979 }
980
981 return this._catchHandler.handle({
982 url,
983 request,
984 event
985 });
986 }
987
988 throw err;
989 });
990 }
991
992 return responsePromise;
993 }
994 /**
995 * Checks a request and URL (and optionally an event) against the list of
996 * registered routes, and if there's a match, returns the corresponding
997 * route along with any params generated by the match.
998 *
999 * @param {Object} options
1000 * @param {URL} options.url
1001 * @param {boolean} options.sameOrigin The result of comparing `url.origin`
1002 * against the current origin.
1003 * @param {Request} options.request The request to match.
1004 * @param {Event} options.event The corresponding event.
1005 * @return {Object} An object with `route` and `params` properties.
1006 * They are populated if a matching route was found or `undefined`
1007 * otherwise.
1008 */
1009
1010
1011 findMatchingRoute({
1012 url,
1013 sameOrigin,
1014 request,
1015 event
1016 }) {
1017 const routes = this._routes.get(request.method) || [];
1018
1019 for (const route of routes) {
1020 let params;
1021 const matchResult = route.match({
1022 url,
1023 sameOrigin,
1024 request,
1025 event
1026 });
1027
1028 if (matchResult) {
1029 {
1030 // Warn developers that using an async matchCallback is almost always
1031 // not the right thing to do.
1032 if (matchResult instanceof Promise) {
1033 logger.warn(`While routing ${getFriendlyURL(url)}, an async ` + `matchCallback function was used. Please convert the ` + `following route to use a synchronous matchCallback function:`, route);
1034 }
1035 } // See https://github.com/GoogleChrome/workbox/issues/2079
1036
1037
1038 params = matchResult;
1039
1040 if (Array.isArray(matchResult) && matchResult.length === 0) {
1041 // Instead of passing an empty array in as params, use undefined.
1042 params = undefined;
1043 } else if (matchResult.constructor === Object && Object.keys(matchResult).length === 0) {
1044 // Instead of passing an empty object in as params, use undefined.
1045 params = undefined;
1046 } else if (typeof matchResult === 'boolean') {
1047 // For the boolean value true (rather than just something truth-y),
1048 // don't set params.
1049 // See https://github.com/GoogleChrome/workbox/pull/2134#issuecomment-513924353
1050 params = undefined;
1051 } // Return early if have a match.
1052
1053
1054 return {
1055 route,
1056 params
1057 };
1058 }
1059 } // If no match was found above, return and empty object.
1060
1061
1062 return {};
1063 }
1064 /**
1065 * Define a default `handler` that's called when no routes explicitly
1066 * match the incoming request.
1067 *
1068 * Each HTTP method ('GET', 'POST', etc.) gets its own default handler.
1069 *
1070 * Without a default handler, unmatched requests will go against the
1071 * network as if there were no service worker present.
1072 *
1073 * @param {module:workbox-routing~handlerCallback} handler A callback
1074 * function that returns a Promise resulting in a Response.
1075 * @param {string} [method='GET'] The HTTP method to associate with this
1076 * default handler. Each method has its own default.
1077 */
1078
1079
1080 setDefaultHandler(handler, method = defaultMethod) {
1081 this._defaultHandlerMap.set(method, normalizeHandler(handler));
1082 }
1083 /**
1084 * If a Route throws an error while handling a request, this `handler`
1085 * will be called and given a chance to provide a response.
1086 *
1087 * @param {module:workbox-routing~handlerCallback} handler A callback
1088 * function that returns a Promise resulting in a Response.
1089 */
1090
1091
1092 setCatchHandler(handler) {
1093 this._catchHandler = normalizeHandler(handler);
1094 }
1095 /**
1096 * Registers a route with the router.
1097 *
1098 * @param {module:workbox-routing.Route} route The route to register.
1099 */
1100
1101
1102 registerRoute(route) {
1103 {
1104 finalAssertExports.isType(route, 'object', {
1105 moduleName: 'workbox-routing',
1106 className: 'Router',
1107 funcName: 'registerRoute',
1108 paramName: 'route'
1109 });
1110 finalAssertExports.hasMethod(route, 'match', {
1111 moduleName: 'workbox-routing',
1112 className: 'Router',
1113 funcName: 'registerRoute',
1114 paramName: 'route'
1115 });
1116 finalAssertExports.isType(route.handler, 'object', {
1117 moduleName: 'workbox-routing',
1118 className: 'Router',
1119 funcName: 'registerRoute',
1120 paramName: 'route'
1121 });
1122 finalAssertExports.hasMethod(route.handler, 'handle', {
1123 moduleName: 'workbox-routing',
1124 className: 'Router',
1125 funcName: 'registerRoute',
1126 paramName: 'route.handler'
1127 });
1128 finalAssertExports.isType(route.method, 'string', {
1129 moduleName: 'workbox-routing',
1130 className: 'Router',
1131 funcName: 'registerRoute',
1132 paramName: 'route.method'
1133 });
1134 }
1135
1136 if (!this._routes.has(route.method)) {
1137 this._routes.set(route.method, []);
1138 } // Give precedence to all of the earlier routes by adding this additional
1139 // route to the end of the array.
1140
1141
1142 this._routes.get(route.method).push(route);
1143 }
1144 /**
1145 * Unregisters a route with the router.
1146 *
1147 * @param {module:workbox-routing.Route} route The route to unregister.
1148 */
1149
1150
1151 unregisterRoute(route) {
1152 if (!this._routes.has(route.method)) {
1153 throw new WorkboxError('unregister-route-but-not-found-with-method', {
1154 method: route.method
1155 });
1156 }
1157
1158 const routeIndex = this._routes.get(route.method).indexOf(route);
1159
1160 if (routeIndex > -1) {
1161 this._routes.get(route.method).splice(routeIndex, 1);
1162 } else {
1163 throw new WorkboxError('unregister-route-route-not-registered');
1164 }
1165 }
1166
1167 }
1168
1169 /*
1170 Copyright 2019 Google LLC
1171
1172 Use of this source code is governed by an MIT-style
1173 license that can be found in the LICENSE file or at
1174 https://opensource.org/licenses/MIT.
1175 */
1176 let defaultRouter;
1177 /**
1178 * Creates a new, singleton Router instance if one does not exist. If one
1179 * does already exist, that instance is returned.
1180 *
1181 * @private
1182 * @return {Router}
1183 */
1184
1185 const getOrCreateDefaultRouter = () => {
1186 if (!defaultRouter) {
1187 defaultRouter = new Router(); // The helpers that use the default Router assume these listeners exist.
1188
1189 defaultRouter.addFetchListener();
1190 defaultRouter.addCacheListener();
1191 }
1192
1193 return defaultRouter;
1194 };
1195
1196 /*
1197 Copyright 2019 Google LLC
1198
1199 Use of this source code is governed by an MIT-style
1200 license that can be found in the LICENSE file or at
1201 https://opensource.org/licenses/MIT.
1202 */
1203 /**
1204 * Easily register a RegExp, string, or function with a caching
1205 * strategy to a singleton Router instance.
1206 *
1207 * This method will generate a Route for you if needed and
1208 * call [registerRoute()]{@link module:workbox-routing.Router#registerRoute}.
1209 *
1210 * @param {RegExp|string|module:workbox-routing.Route~matchCallback|module:workbox-routing.Route} capture
1211 * If the capture param is a `Route`, all other arguments will be ignored.
1212 * @param {module:workbox-routing~handlerCallback} [handler] A callback
1213 * function that returns a Promise resulting in a Response. This parameter
1214 * is required if `capture` is not a `Route` object.
1215 * @param {string} [method='GET'] The HTTP method to match the Route
1216 * against.
1217 * @return {module:workbox-routing.Route} The generated `Route`(Useful for
1218 * unregistering).
1219 *
1220 * @memberof module:workbox-routing
1221 */
1222
1223 function registerRoute(capture, handler, method) {
1224 let route;
1225
1226 if (typeof capture === 'string') {
1227 const captureUrl = new URL(capture, location.href);
1228
1229 {
1230 if (!(capture.startsWith('/') || capture.startsWith('http'))) {
1231 throw new WorkboxError('invalid-string', {
1232 moduleName: 'workbox-routing',
1233 funcName: 'registerRoute',
1234 paramName: 'capture'
1235 });
1236 } // We want to check if Express-style wildcards are in the pathname only.
1237 // TODO: Remove this log message in v4.
1238
1239
1240 const valueToCheck = capture.startsWith('http') ? captureUrl.pathname : capture; // See https://github.com/pillarjs/path-to-regexp#parameters
1241
1242 const wildcards = '[*:?+]';
1243
1244 if (new RegExp(`${wildcards}`).exec(valueToCheck)) {
1245 logger.debug(`The '$capture' parameter contains an Express-style wildcard ` + `character (${wildcards}). Strings are now always interpreted as ` + `exact matches; use a RegExp for partial or wildcard matches.`);
1246 }
1247 }
1248
1249 const matchCallback = ({
1250 url
1251 }) => {
1252 {
1253 if (url.pathname === captureUrl.pathname && url.origin !== captureUrl.origin) {
1254 logger.debug(`${capture} only partially matches the cross-origin URL ` + `${url}. This route will only handle cross-origin requests ` + `if they match the entire URL.`);
1255 }
1256 }
1257
1258 return url.href === captureUrl.href;
1259 }; // If `capture` is a string then `handler` and `method` must be present.
1260
1261
1262 route = new Route(matchCallback, handler, method);
1263 } else if (capture instanceof RegExp) {
1264 // If `capture` is a `RegExp` then `handler` and `method` must be present.
1265 route = new RegExpRoute(capture, handler, method);
1266 } else if (typeof capture === 'function') {
1267 // If `capture` is a function then `handler` and `method` must be present.
1268 route = new Route(capture, handler, method);
1269 } else if (capture instanceof Route) {
1270 route = capture;
1271 } else {
1272 throw new WorkboxError('unsupported-route-type', {
1273 moduleName: 'workbox-routing',
1274 funcName: 'registerRoute',
1275 paramName: 'capture'
1276 });
1277 }
1278
1279 const defaultRouter = getOrCreateDefaultRouter();
1280 defaultRouter.registerRoute(route);
1281 return route;
1282 }
1283
1284 try {
1285 self['workbox:strategies:6.1.5'] && _();
1286 } catch (e) {}
1287
1288 /*
1289 Copyright 2018 Google LLC
1290
1291 Use of this source code is governed by an MIT-style
1292 license that can be found in the LICENSE file or at
1293 https://opensource.org/licenses/MIT.
1294 */
1295 const cacheOkAndOpaquePlugin = {
1296 /**
1297 * Returns a valid response (to allow caching) if the status is 200 (OK) or
1298 * 0 (opaque).
1299 *
1300 * @param {Object} options
1301 * @param {Response} options.response
1302 * @return {Response|null}
1303 *
1304 * @private
1305 */
1306 cacheWillUpdate: async ({
1307 response
1308 }) => {
1309 if (response.status === 200 || response.status === 0) {
1310 return response;
1311 }
1312
1313 return null;
1314 }
1315 };
1316
1317 /*
1318 Copyright 2018 Google LLC
1319
1320 Use of this source code is governed by an MIT-style
1321 license that can be found in the LICENSE file or at
1322 https://opensource.org/licenses/MIT.
1323 */
1324 const _cacheNameDetails = {
1325 googleAnalytics: 'googleAnalytics',
1326 precache: 'precache-v2',
1327 prefix: 'workbox',
1328 runtime: 'runtime',
1329 suffix: typeof registration !== 'undefined' ? registration.scope : ''
1330 };
1331
1332 const _createCacheName = cacheName => {
1333 return [_cacheNameDetails.prefix, cacheName, _cacheNameDetails.suffix].filter(value => value && value.length > 0).join('-');
1334 };
1335
1336 const eachCacheNameDetail = fn => {
1337 for (const key of Object.keys(_cacheNameDetails)) {
1338 fn(key);
1339 }
1340 };
1341
1342 const cacheNames = {
1343 updateDetails: details => {
1344 eachCacheNameDetail(key => {
1345 if (typeof details[key] === 'string') {
1346 _cacheNameDetails[key] = details[key];
1347 }
1348 });
1349 },
1350 getGoogleAnalyticsName: userCacheName => {
1351 return userCacheName || _createCacheName(_cacheNameDetails.googleAnalytics);
1352 },
1353 getPrecacheName: userCacheName => {
1354 return userCacheName || _createCacheName(_cacheNameDetails.precache);
1355 },
1356 getPrefix: () => {
1357 return _cacheNameDetails.prefix;
1358 },
1359 getRuntimeName: userCacheName => {
1360 return userCacheName || _createCacheName(_cacheNameDetails.runtime);
1361 },
1362 getSuffix: () => {
1363 return _cacheNameDetails.suffix;
1364 }
1365 };
1366
1367 function _extends() {
1368 _extends = Object.assign || function (target) {
1369 for (var i = 1; i < arguments.length; i++) {
1370 var source = arguments[i];
1371
1372 for (var key in source) {
1373 if (Object.prototype.hasOwnProperty.call(source, key)) {
1374 target[key] = source[key];
1375 }
1376 }
1377 }
1378
1379 return target;
1380 };
1381
1382 return _extends.apply(this, arguments);
1383 }
1384
1385 function stripParams(fullURL, ignoreParams) {
1386 const strippedURL = new URL(fullURL);
1387
1388 for (const param of ignoreParams) {
1389 strippedURL.searchParams.delete(param);
1390 }
1391
1392 return strippedURL.href;
1393 }
1394 /**
1395 * Matches an item in the cache, ignoring specific URL params. This is similar
1396 * to the `ignoreSearch` option, but it allows you to ignore just specific
1397 * params (while continuing to match on the others).
1398 *
1399 * @private
1400 * @param {Cache} cache
1401 * @param {Request} request
1402 * @param {Object} matchOptions
1403 * @param {Array<string>} ignoreParams
1404 * @return {Promise<Response|undefined>}
1405 */
1406
1407
1408 async function cacheMatchIgnoreParams(cache, request, ignoreParams, matchOptions) {
1409 const strippedRequestURL = stripParams(request.url, ignoreParams); // If the request doesn't include any ignored params, match as normal.
1410
1411 if (request.url === strippedRequestURL) {
1412 return cache.match(request, matchOptions);
1413 } // Otherwise, match by comparing keys
1414
1415
1416 const keysOptions = _extends({}, matchOptions, {
1417 ignoreSearch: true
1418 });
1419
1420 const cacheKeys = await cache.keys(request, keysOptions);
1421
1422 for (const cacheKey of cacheKeys) {
1423 const strippedCacheKeyURL = stripParams(cacheKey.url, ignoreParams);
1424
1425 if (strippedRequestURL === strippedCacheKeyURL) {
1426 return cache.match(cacheKey, matchOptions);
1427 }
1428 }
1429
1430 return;
1431 }
1432
1433 /*
1434 Copyright 2018 Google LLC
1435
1436 Use of this source code is governed by an MIT-style
1437 license that can be found in the LICENSE file or at
1438 https://opensource.org/licenses/MIT.
1439 */
1440 /**
1441 * The Deferred class composes Promises in a way that allows for them to be
1442 * resolved or rejected from outside the constructor. In most cases promises
1443 * should be used directly, but Deferreds can be necessary when the logic to
1444 * resolve a promise must be separate.
1445 *
1446 * @private
1447 */
1448
1449 class Deferred {
1450 /**
1451 * Creates a promise and exposes its resolve and reject functions as methods.
1452 */
1453 constructor() {
1454 this.promise = new Promise((resolve, reject) => {
1455 this.resolve = resolve;
1456 this.reject = reject;
1457 });
1458 }
1459
1460 }
1461
1462 /*
1463 Copyright 2018 Google LLC
1464
1465 Use of this source code is governed by an MIT-style
1466 license that can be found in the LICENSE file or at
1467 https://opensource.org/licenses/MIT.
1468 */
1469
1470 const quotaErrorCallbacks = new Set();
1471
1472 /*
1473 Copyright 2018 Google LLC
1474
1475 Use of this source code is governed by an MIT-style
1476 license that can be found in the LICENSE file or at
1477 https://opensource.org/licenses/MIT.
1478 */
1479 /**
1480 * Runs all of the callback functions, one at a time sequentially, in the order
1481 * in which they were registered.
1482 *
1483 * @memberof module:workbox-core
1484 * @private
1485 */
1486
1487 async function executeQuotaErrorCallbacks() {
1488 {
1489 logger.log(`About to run ${quotaErrorCallbacks.size} ` + `callbacks to clean up caches.`);
1490 }
1491
1492 for (const callback of quotaErrorCallbacks) {
1493 await callback();
1494
1495 {
1496 logger.log(callback, 'is complete.');
1497 }
1498 }
1499
1500 {
1501 logger.log('Finished running callbacks.');
1502 }
1503 }
1504
1505 /*
1506 Copyright 2019 Google LLC
1507 Use of this source code is governed by an MIT-style
1508 license that can be found in the LICENSE file or at
1509 https://opensource.org/licenses/MIT.
1510 */
1511 /**
1512 * Returns a promise that resolves and the passed number of milliseconds.
1513 * This utility is an async/await-friendly version of `setTimeout`.
1514 *
1515 * @param {number} ms
1516 * @return {Promise}
1517 * @private
1518 */
1519
1520 function timeout(ms) {
1521 return new Promise(resolve => setTimeout(resolve, ms));
1522 }
1523
1524 function toRequest(input) {
1525 return typeof input === 'string' ? new Request(input) : input;
1526 }
1527 /**
1528 * A class created every time a Strategy instance instance calls
1529 * [handle()]{@link module:workbox-strategies.Strategy~handle} or
1530 * [handleAll()]{@link module:workbox-strategies.Strategy~handleAll} that wraps all fetch and
1531 * cache actions around plugin callbacks and keeps track of when the strategy
1532 * is "done" (i.e. all added `event.waitUntil()` promises have resolved).
1533 *
1534 * @memberof module:workbox-strategies
1535 */
1536
1537
1538 class StrategyHandler {
1539 /**
1540 * Creates a new instance associated with the passed strategy and event
1541 * that's handling the request.
1542 *
1543 * The constructor also initializes the state that will be passed to each of
1544 * the plugins handling this request.
1545 *
1546 * @param {module:workbox-strategies.Strategy} strategy
1547 * @param {Object} options
1548 * @param {Request|string} options.request A request to run this strategy for.
1549 * @param {ExtendableEvent} options.event The event associated with the
1550 * request.
1551 * @param {URL} [options.url]
1552 * @param {*} [options.params]
1553 * [match callback]{@link module:workbox-routing~matchCallback},
1554 * (if applicable).
1555 */
1556 constructor(strategy, options) {
1557 this._cacheKeys = {};
1558 /**
1559 * The request the strategy is performing (passed to the strategy's
1560 * `handle()` or `handleAll()` method).
1561 * @name request
1562 * @instance
1563 * @type {Request}
1564 * @memberof module:workbox-strategies.StrategyHandler
1565 */
1566
1567 /**
1568 * The event associated with this request.
1569 * @name event
1570 * @instance
1571 * @type {ExtendableEvent}
1572 * @memberof module:workbox-strategies.StrategyHandler
1573 */
1574
1575 /**
1576 * A `URL` instance of `request.url` (if passed to the strategy's
1577 * `handle()` or `handleAll()` method).
1578 * Note: the `url` param will be present if the strategy was invoked
1579 * from a workbox `Route` object.
1580 * @name url
1581 * @instance
1582 * @type {URL|undefined}
1583 * @memberof module:workbox-strategies.StrategyHandler
1584 */
1585
1586 /**
1587 * A `param` value (if passed to the strategy's
1588 * `handle()` or `handleAll()` method).
1589 * Note: the `param` param will be present if the strategy was invoked
1590 * from a workbox `Route` object and the
1591 * [match callback]{@link module:workbox-routing~matchCallback} returned
1592 * a truthy value (it will be that value).
1593 * @name params
1594 * @instance
1595 * @type {*|undefined}
1596 * @memberof module:workbox-strategies.StrategyHandler
1597 */
1598
1599 {
1600 finalAssertExports.isInstance(options.event, ExtendableEvent, {
1601 moduleName: 'workbox-strategies',
1602 className: 'StrategyHandler',
1603 funcName: 'constructor',
1604 paramName: 'options.event'
1605 });
1606 }
1607
1608 Object.assign(this, options);
1609 this.event = options.event;
1610 this._strategy = strategy;
1611 this._handlerDeferred = new Deferred();
1612 this._extendLifetimePromises = []; // Copy the plugins list (since it's mutable on the strategy),
1613 // so any mutations don't affect this handler instance.
1614
1615 this._plugins = [...strategy.plugins];
1616 this._pluginStateMap = new Map();
1617
1618 for (const plugin of this._plugins) {
1619 this._pluginStateMap.set(plugin, {});
1620 }
1621
1622 this.event.waitUntil(this._handlerDeferred.promise);
1623 }
1624 /**
1625 * Fetches a given request (and invokes any applicable plugin callback
1626 * methods) using the `fetchOptions` (for non-navigation requests) and
1627 * `plugins` defined on the `Strategy` object.
1628 *
1629 * The following plugin lifecycle methods are invoked when using this method:
1630 * - `requestWillFetch()`
1631 * - `fetchDidSucceed()`
1632 * - `fetchDidFail()`
1633 *
1634 * @param {Request|string} input The URL or request to fetch.
1635 * @return {Promise<Response>}
1636 */
1637
1638
1639 async fetch(input) {
1640 const {
1641 event
1642 } = this;
1643 let request = toRequest(input);
1644
1645 if (request.mode === 'navigate' && event instanceof FetchEvent && event.preloadResponse) {
1646 const possiblePreloadResponse = await event.preloadResponse;
1647
1648 if (possiblePreloadResponse) {
1649 {
1650 logger.log(`Using a preloaded navigation response for ` + `'${getFriendlyURL(request.url)}'`);
1651 }
1652
1653 return possiblePreloadResponse;
1654 }
1655 } // If there is a fetchDidFail plugin, we need to save a clone of the
1656 // original request before it's either modified by a requestWillFetch
1657 // plugin or before the original request's body is consumed via fetch().
1658
1659
1660 const originalRequest = this.hasCallback('fetchDidFail') ? request.clone() : null;
1661
1662 try {
1663 for (const cb of this.iterateCallbacks('requestWillFetch')) {
1664 request = await cb({
1665 request: request.clone(),
1666 event
1667 });
1668 }
1669 } catch (err) {
1670 throw new WorkboxError('plugin-error-request-will-fetch', {
1671 thrownError: err
1672 });
1673 } // The request can be altered by plugins with `requestWillFetch` making
1674 // the original request (most likely from a `fetch` event) different
1675 // from the Request we make. Pass both to `fetchDidFail` to aid debugging.
1676
1677
1678 const pluginFilteredRequest = request.clone();
1679
1680 try {
1681 let fetchResponse; // See https://github.com/GoogleChrome/workbox/issues/1796
1682
1683 fetchResponse = await fetch(request, request.mode === 'navigate' ? undefined : this._strategy.fetchOptions);
1684
1685 if ("development" !== 'production') {
1686 logger.debug(`Network request for ` + `'${getFriendlyURL(request.url)}' returned a response with ` + `status '${fetchResponse.status}'.`);
1687 }
1688
1689 for (const callback of this.iterateCallbacks('fetchDidSucceed')) {
1690 fetchResponse = await callback({
1691 event,
1692 request: pluginFilteredRequest,
1693 response: fetchResponse
1694 });
1695 }
1696
1697 return fetchResponse;
1698 } catch (error) {
1699 {
1700 logger.log(`Network request for ` + `'${getFriendlyURL(request.url)}' threw an error.`, error);
1701 } // `originalRequest` will only exist if a `fetchDidFail` callback
1702 // is being used (see above).
1703
1704
1705 if (originalRequest) {
1706 await this.runCallbacks('fetchDidFail', {
1707 error,
1708 event,
1709 originalRequest: originalRequest.clone(),
1710 request: pluginFilteredRequest.clone()
1711 });
1712 }
1713
1714 throw error;
1715 }
1716 }
1717 /**
1718 * Calls `this.fetch()` and (in the background) runs `this.cachePut()` on
1719 * the response generated by `this.fetch()`.
1720 *
1721 * The call to `this.cachePut()` automatically invokes `this.waitUntil()`,
1722 * so you do not have to manually call `waitUntil()` on the event.
1723 *
1724 * @param {Request|string} input The request or URL to fetch and cache.
1725 * @return {Promise<Response>}
1726 */
1727
1728
1729 async fetchAndCachePut(input) {
1730 const response = await this.fetch(input);
1731 const responseClone = response.clone();
1732 this.waitUntil(this.cachePut(input, responseClone));
1733 return response;
1734 }
1735 /**
1736 * Matches a request from the cache (and invokes any applicable plugin
1737 * callback methods) using the `cacheName`, `matchOptions`, and `plugins`
1738 * defined on the strategy object.
1739 *
1740 * The following plugin lifecycle methods are invoked when using this method:
1741 * - cacheKeyWillByUsed()
1742 * - cachedResponseWillByUsed()
1743 *
1744 * @param {Request|string} key The Request or URL to use as the cache key.
1745 * @return {Promise<Response|undefined>} A matching response, if found.
1746 */
1747
1748
1749 async cacheMatch(key) {
1750 const request = toRequest(key);
1751 let cachedResponse;
1752 const {
1753 cacheName,
1754 matchOptions
1755 } = this._strategy;
1756 const effectiveRequest = await this.getCacheKey(request, 'read');
1757
1758 const multiMatchOptions = _extends({}, matchOptions, {
1759 cacheName
1760 });
1761
1762 cachedResponse = await caches.match(effectiveRequest, multiMatchOptions);
1763
1764 {
1765 if (cachedResponse) {
1766 logger.debug(`Found a cached response in '${cacheName}'.`);
1767 } else {
1768 logger.debug(`No cached response found in '${cacheName}'.`);
1769 }
1770 }
1771
1772 for (const callback of this.iterateCallbacks('cachedResponseWillBeUsed')) {
1773 cachedResponse = (await callback({
1774 cacheName,
1775 matchOptions,
1776 cachedResponse,
1777 request: effectiveRequest,
1778 event: this.event
1779 })) || undefined;
1780 }
1781
1782 return cachedResponse;
1783 }
1784 /**
1785 * Puts a request/response pair in the cache (and invokes any applicable
1786 * plugin callback methods) using the `cacheName` and `plugins` defined on
1787 * the strategy object.
1788 *
1789 * The following plugin lifecycle methods are invoked when using this method:
1790 * - cacheKeyWillByUsed()
1791 * - cacheWillUpdate()
1792 * - cacheDidUpdate()
1793 *
1794 * @param {Request|string} key The request or URL to use as the cache key.
1795 * @param {Response} response The response to cache.
1796 * @return {Promise<boolean>} `false` if a cacheWillUpdate caused the response
1797 * not be cached, and `true` otherwise.
1798 */
1799
1800
1801 async cachePut(key, response) {
1802 const request = toRequest(key); // Run in the next task to avoid blocking other cache reads.
1803 // https://github.com/w3c/ServiceWorker/issues/1397
1804
1805 await timeout(0);
1806 const effectiveRequest = await this.getCacheKey(request, 'write');
1807
1808 {
1809 if (effectiveRequest.method && effectiveRequest.method !== 'GET') {
1810 throw new WorkboxError('attempt-to-cache-non-get-request', {
1811 url: getFriendlyURL(effectiveRequest.url),
1812 method: effectiveRequest.method
1813 });
1814 }
1815 }
1816
1817 if (!response) {
1818 {
1819 logger.error(`Cannot cache non-existent response for ` + `'${getFriendlyURL(effectiveRequest.url)}'.`);
1820 }
1821
1822 throw new WorkboxError('cache-put-with-no-response', {
1823 url: getFriendlyURL(effectiveRequest.url)
1824 });
1825 }
1826
1827 const responseToCache = await this._ensureResponseSafeToCache(response);
1828
1829 if (!responseToCache) {
1830 {
1831 logger.debug(`Response '${getFriendlyURL(effectiveRequest.url)}' ` + `will not be cached.`, responseToCache);
1832 }
1833
1834 return false;
1835 }
1836
1837 const {
1838 cacheName,
1839 matchOptions
1840 } = this._strategy;
1841 const cache = await self.caches.open(cacheName);
1842 const hasCacheUpdateCallback = this.hasCallback('cacheDidUpdate');
1843 const oldResponse = hasCacheUpdateCallback ? await cacheMatchIgnoreParams( // TODO(philipwalton): the `__WB_REVISION__` param is a precaching
1844 // feature. Consider into ways to only add this behavior if using
1845 // precaching.
1846 cache, effectiveRequest.clone(), ['__WB_REVISION__'], matchOptions) : null;
1847
1848 {
1849 logger.debug(`Updating the '${cacheName}' cache with a new Response ` + `for ${getFriendlyURL(effectiveRequest.url)}.`);
1850 }
1851
1852 try {
1853 await cache.put(effectiveRequest, hasCacheUpdateCallback ? responseToCache.clone() : responseToCache);
1854 } catch (error) {
1855 // See https://developer.mozilla.org/en-US/docs/Web/API/DOMException#exception-QuotaExceededError
1856 if (error.name === 'QuotaExceededError') {
1857 await executeQuotaErrorCallbacks();
1858 }
1859
1860 throw error;
1861 }
1862
1863 for (const callback of this.iterateCallbacks('cacheDidUpdate')) {
1864 await callback({
1865 cacheName,
1866 oldResponse,
1867 newResponse: responseToCache.clone(),
1868 request: effectiveRequest,
1869 event: this.event
1870 });
1871 }
1872
1873 return true;
1874 }
1875 /**
1876 * Checks the list of plugins for the `cacheKeyWillBeUsed` callback, and
1877 * executes any of those callbacks found in sequence. The final `Request`
1878 * object returned by the last plugin is treated as the cache key for cache
1879 * reads and/or writes. If no `cacheKeyWillBeUsed` plugin callbacks have
1880 * been registered, the passed request is returned unmodified
1881 *
1882 * @param {Request} request
1883 * @param {string} mode
1884 * @return {Promise<Request>}
1885 */
1886
1887
1888 async getCacheKey(request, mode) {
1889 if (!this._cacheKeys[mode]) {
1890 let effectiveRequest = request;
1891
1892 for (const callback of this.iterateCallbacks('cacheKeyWillBeUsed')) {
1893 effectiveRequest = toRequest(await callback({
1894 mode,
1895 request: effectiveRequest,
1896 event: this.event,
1897 params: this.params
1898 }));
1899 }
1900
1901 this._cacheKeys[mode] = effectiveRequest;
1902 }
1903
1904 return this._cacheKeys[mode];
1905 }
1906 /**
1907 * Returns true if the strategy has at least one plugin with the given
1908 * callback.
1909 *
1910 * @param {string} name The name of the callback to check for.
1911 * @return {boolean}
1912 */
1913
1914
1915 hasCallback(name) {
1916 for (const plugin of this._strategy.plugins) {
1917 if (name in plugin) {
1918 return true;
1919 }
1920 }
1921
1922 return false;
1923 }
1924 /**
1925 * Runs all plugin callbacks matching the given name, in order, passing the
1926 * given param object (merged ith the current plugin state) as the only
1927 * argument.
1928 *
1929 * Note: since this method runs all plugins, it's not suitable for cases
1930 * where the return value of a callback needs to be applied prior to calling
1931 * the next callback. See
1932 * [`iterateCallbacks()`]{@link module:workbox-strategies.StrategyHandler#iterateCallbacks}
1933 * below for how to handle that case.
1934 *
1935 * @param {string} name The name of the callback to run within each plugin.
1936 * @param {Object} param The object to pass as the first (and only) param
1937 * when executing each callback. This object will be merged with the
1938 * current plugin state prior to callback execution.
1939 */
1940
1941
1942 async runCallbacks(name, param) {
1943 for (const callback of this.iterateCallbacks(name)) {
1944 // TODO(philipwalton): not sure why `any` is needed. It seems like
1945 // this should work with `as WorkboxPluginCallbackParam[C]`.
1946 await callback(param);
1947 }
1948 }
1949 /**
1950 * Accepts a callback and returns an iterable of matching plugin callbacks,
1951 * where each callback is wrapped with the current handler state (i.e. when
1952 * you call each callback, whatever object parameter you pass it will
1953 * be merged with the plugin's current state).
1954 *
1955 * @param {string} name The name fo the callback to run
1956 * @return {Array<Function>}
1957 */
1958
1959
1960 *iterateCallbacks(name) {
1961 for (const plugin of this._strategy.plugins) {
1962 if (typeof plugin[name] === 'function') {
1963 const state = this._pluginStateMap.get(plugin);
1964
1965 const statefulCallback = param => {
1966 const statefulParam = _extends({}, param, {
1967 state
1968 }); // TODO(philipwalton): not sure why `any` is needed. It seems like
1969 // this should work with `as WorkboxPluginCallbackParam[C]`.
1970
1971
1972 return plugin[name](statefulParam);
1973 };
1974
1975 yield statefulCallback;
1976 }
1977 }
1978 }
1979 /**
1980 * Adds a promise to the
1981 * [extend lifetime promises]{@link https://w3c.github.io/ServiceWorker/#extendableevent-extend-lifetime-promises}
1982 * of the event event associated with the request being handled (usually a
1983 * `FetchEvent`).
1984 *
1985 * Note: you can await
1986 * [`doneWaiting()`]{@link module:workbox-strategies.StrategyHandler~doneWaiting}
1987 * to know when all added promises have settled.
1988 *
1989 * @param {Promise} promise A promise to add to the extend lifetime promises
1990 * of the event that triggered the request.
1991 */
1992
1993
1994 waitUntil(promise) {
1995 this._extendLifetimePromises.push(promise);
1996
1997 return promise;
1998 }
1999 /**
2000 * Returns a promise that resolves once all promises passed to
2001 * [`waitUntil()`]{@link module:workbox-strategies.StrategyHandler~waitUntil}
2002 * have settled.
2003 *
2004 * Note: any work done after `doneWaiting()` settles should be manually
2005 * passed to an event's `waitUntil()` method (not this handler's
2006 * `waitUntil()` method), otherwise the service worker thread my be killed
2007 * prior to your work completing.
2008 */
2009
2010
2011 async doneWaiting() {
2012 let promise;
2013
2014 while (promise = this._extendLifetimePromises.shift()) {
2015 await promise;
2016 }
2017 }
2018 /**
2019 * Stops running the strategy and immediately resolves any pending
2020 * `waitUntil()` promises.
2021 */
2022
2023
2024 destroy() {
2025 this._handlerDeferred.resolve();
2026 }
2027 /**
2028 * This method will call cacheWillUpdate on the available plugins (or use
2029 * status === 200) to determine if the Response is safe and valid to cache.
2030 *
2031 * @param {Request} options.request
2032 * @param {Response} options.response
2033 * @return {Promise<Response|undefined>}
2034 *
2035 * @private
2036 */
2037
2038
2039 async _ensureResponseSafeToCache(response) {
2040 let responseToCache = response;
2041 let pluginsUsed = false;
2042
2043 for (const callback of this.iterateCallbacks('cacheWillUpdate')) {
2044 responseToCache = (await callback({
2045 request: this.request,
2046 response: responseToCache,
2047 event: this.event
2048 })) || undefined;
2049 pluginsUsed = true;
2050
2051 if (!responseToCache) {
2052 break;
2053 }
2054 }
2055
2056 if (!pluginsUsed) {
2057 if (responseToCache && responseToCache.status !== 200) {
2058 responseToCache = undefined;
2059 }
2060
2061 {
2062 if (responseToCache) {
2063 if (responseToCache.status !== 200) {
2064 if (responseToCache.status === 0) {
2065 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.`);
2066 } else {
2067 logger.debug(`The response for '${this.request.url}' ` + `returned a status code of '${response.status}' and won't ` + `be cached as a result.`);
2068 }
2069 }
2070 }
2071 }
2072 }
2073
2074 return responseToCache;
2075 }
2076
2077 }
2078
2079 /*
2080 Copyright 2020 Google LLC
2081
2082 Use of this source code is governed by an MIT-style
2083 license that can be found in the LICENSE file or at
2084 https://opensource.org/licenses/MIT.
2085 */
2086 /**
2087 * An abstract base class that all other strategy classes must extend from:
2088 *
2089 * @memberof module:workbox-strategies
2090 */
2091
2092 class Strategy {
2093 /**
2094 * Creates a new instance of the strategy and sets all documented option
2095 * properties as public instance properties.
2096 *
2097 * Note: if a custom strategy class extends the base Strategy class and does
2098 * not need more than these properties, it does not need to define its own
2099 * constructor.
2100 *
2101 * @param {Object} [options]
2102 * @param {string} [options.cacheName] Cache name to store and retrieve
2103 * requests. Defaults to the cache names provided by
2104 * [workbox-core]{@link module:workbox-core.cacheNames}.
2105 * @param {Array<Object>} [options.plugins] [Plugins]{@link https://developers.google.com/web/tools/workbox/guides/using-plugins}
2106 * to use in conjunction with this caching strategy.
2107 * @param {Object} [options.fetchOptions] Values passed along to the
2108 * [`init`](https://developer.mozilla.org/en-US/docs/Web/API/WindowOrWorkerGlobalScope/fetch#Parameters)
2109 * of [non-navigation](https://github.com/GoogleChrome/workbox/issues/1796)
2110 * `fetch()` requests made by this strategy.
2111 * @param {Object} [options.matchOptions] The
2112 * [`CacheQueryOptions`]{@link https://w3c.github.io/ServiceWorker/#dictdef-cachequeryoptions}
2113 * for any `cache.match()` or `cache.put()` calls made by this strategy.
2114 */
2115 constructor(options = {}) {
2116 /**
2117 * Cache name to store and retrieve
2118 * requests. Defaults to the cache names provided by
2119 * [workbox-core]{@link module:workbox-core.cacheNames}.
2120 *
2121 * @type {string}
2122 */
2123 this.cacheName = cacheNames.getRuntimeName(options.cacheName);
2124 /**
2125 * The list
2126 * [Plugins]{@link https://developers.google.com/web/tools/workbox/guides/using-plugins}
2127 * used by this strategy.
2128 *
2129 * @type {Array<Object>}
2130 */
2131
2132 this.plugins = options.plugins || [];
2133 /**
2134 * Values passed along to the
2135 * [`init`]{@link https://developer.mozilla.org/en-US/docs/Web/API/WindowOrWorkerGlobalScope/fetch#Parameters}
2136 * of all fetch() requests made by this strategy.
2137 *
2138 * @type {Object}
2139 */
2140
2141 this.fetchOptions = options.fetchOptions;
2142 /**
2143 * The
2144 * [`CacheQueryOptions`]{@link https://w3c.github.io/ServiceWorker/#dictdef-cachequeryoptions}
2145 * for any `cache.match()` or `cache.put()` calls made by this strategy.
2146 *
2147 * @type {Object}
2148 */
2149
2150 this.matchOptions = options.matchOptions;
2151 }
2152 /**
2153 * Perform a request strategy and returns a `Promise` that will resolve with
2154 * a `Response`, invoking all relevant plugin callbacks.
2155 *
2156 * When a strategy instance is registered with a Workbox
2157 * [route]{@link module:workbox-routing.Route}, this method is automatically
2158 * called when the route matches.
2159 *
2160 * Alternatively, this method can be used in a standalone `FetchEvent`
2161 * listener by passing it to `event.respondWith()`.
2162 *
2163 * @param {FetchEvent|Object} options A `FetchEvent` or an object with the
2164 * properties listed below.
2165 * @param {Request|string} options.request A request to run this strategy for.
2166 * @param {ExtendableEvent} options.event The event associated with the
2167 * request.
2168 * @param {URL} [options.url]
2169 * @param {*} [options.params]
2170 */
2171
2172
2173 handle(options) {
2174 const [responseDone] = this.handleAll(options);
2175 return responseDone;
2176 }
2177 /**
2178 * Similar to [`handle()`]{@link module:workbox-strategies.Strategy~handle}, but
2179 * instead of just returning a `Promise` that resolves to a `Response` it
2180 * it will return an tuple of [response, done] promises, where the former
2181 * (`response`) is equivalent to what `handle()` returns, and the latter is a
2182 * Promise that will resolve once any promises that were added to
2183 * `event.waitUntil()` as part of performing the strategy have completed.
2184 *
2185 * You can await the `done` promise to ensure any extra work performed by
2186 * the strategy (usually caching responses) completes successfully.
2187 *
2188 * @param {FetchEvent|Object} options A `FetchEvent` or an object with the
2189 * properties listed below.
2190 * @param {Request|string} options.request A request to run this strategy for.
2191 * @param {ExtendableEvent} options.event The event associated with the
2192 * request.
2193 * @param {URL} [options.url]
2194 * @param {*} [options.params]
2195 * @return {Array<Promise>} A tuple of [response, done]
2196 * promises that can be used to determine when the response resolves as
2197 * well as when the handler has completed all its work.
2198 */
2199
2200
2201 handleAll(options) {
2202 // Allow for flexible options to be passed.
2203 if (options instanceof FetchEvent) {
2204 options = {
2205 event: options,
2206 request: options.request
2207 };
2208 }
2209
2210 const event = options.event;
2211 const request = typeof options.request === 'string' ? new Request(options.request) : options.request;
2212 const params = 'params' in options ? options.params : undefined;
2213 const handler = new StrategyHandler(this, {
2214 event,
2215 request,
2216 params
2217 });
2218
2219 const responseDone = this._getResponse(handler, request, event);
2220
2221 const handlerDone = this._awaitComplete(responseDone, handler, request, event); // Return an array of promises, suitable for use with Promise.all().
2222
2223
2224 return [responseDone, handlerDone];
2225 }
2226
2227 async _getResponse(handler, request, event) {
2228 await handler.runCallbacks('handlerWillStart', {
2229 event,
2230 request
2231 });
2232 let response = undefined;
2233
2234 try {
2235 response = await this._handle(request, handler); // The "official" Strategy subclasses all throw this error automatically,
2236 // but in case a third-party Strategy doesn't, ensure that we have a
2237 // consistent failure when there's no response or an error response.
2238
2239 if (!response || response.type === 'error') {
2240 throw new WorkboxError('no-response', {
2241 url: request.url
2242 });
2243 }
2244 } catch (error) {
2245 for (const callback of handler.iterateCallbacks('handlerDidError')) {
2246 response = await callback({
2247 error,
2248 event,
2249 request
2250 });
2251
2252 if (response) {
2253 break;
2254 }
2255 }
2256
2257 if (!response) {
2258 throw error;
2259 } else {
2260 logger.log(`While responding to '${getFriendlyURL(request.url)}', ` + `an ${error} error occurred. Using a fallback response provided by ` + `a handlerDidError plugin.`);
2261 }
2262 }
2263
2264 for (const callback of handler.iterateCallbacks('handlerWillRespond')) {
2265 response = await callback({
2266 event,
2267 request,
2268 response
2269 });
2270 }
2271
2272 return response;
2273 }
2274
2275 async _awaitComplete(responseDone, handler, request, event) {
2276 let response;
2277 let error;
2278
2279 try {
2280 response = await responseDone;
2281 } catch (error) {// Ignore errors, as response errors should be caught via the `response`
2282 // promise above. The `done` promise will only throw for errors in
2283 // promises passed to `handler.waitUntil()`.
2284 }
2285
2286 try {
2287 await handler.runCallbacks('handlerDidRespond', {
2288 event,
2289 request,
2290 response
2291 });
2292 await handler.doneWaiting();
2293 } catch (waitUntilError) {
2294 error = waitUntilError;
2295 }
2296
2297 await handler.runCallbacks('handlerDidComplete', {
2298 event,
2299 request,
2300 response,
2301 error
2302 });
2303 handler.destroy();
2304
2305 if (error) {
2306 throw error;
2307 }
2308 }
2309
2310 }
2311 /**
2312 * Classes extending the `Strategy` based class should implement this method,
2313 * and leverage the [`handler`]{@link module:workbox-strategies.StrategyHandler}
2314 * arg to perform all fetching and cache logic, which will ensure all relevant
2315 * cache, cache options, fetch options and plugins are used (per the current
2316 * strategy instance).
2317 *
2318 * @name _handle
2319 * @instance
2320 * @abstract
2321 * @function
2322 * @param {Request} request
2323 * @param {module:workbox-strategies.StrategyHandler} handler
2324 * @return {Promise<Response>}
2325 *
2326 * @memberof module:workbox-strategies.Strategy
2327 */
2328
2329 /*
2330 Copyright 2018 Google LLC
2331
2332 Use of this source code is governed by an MIT-style
2333 license that can be found in the LICENSE file or at
2334 https://opensource.org/licenses/MIT.
2335 */
2336 const messages = {
2337 strategyStart: (strategyName, request) => `Using ${strategyName} to respond to '${getFriendlyURL(request.url)}'`,
2338 printFinalResponse: response => {
2339 if (response) {
2340 logger.groupCollapsed(`View the final response here.`);
2341 logger.log(response || '[No response returned]');
2342 logger.groupEnd();
2343 }
2344 }
2345 };
2346
2347 /*
2348 Copyright 2018 Google LLC
2349
2350 Use of this source code is governed by an MIT-style
2351 license that can be found in the LICENSE file or at
2352 https://opensource.org/licenses/MIT.
2353 */
2354 /**
2355 * An implementation of a
2356 * [network first]{@link https://developers.google.com/web/fundamentals/instant-and-offline/offline-cookbook/#network-falling-back-to-cache}
2357 * request strategy.
2358 *
2359 * By default, this strategy will cache responses with a 200 status code as
2360 * well as [opaque responses]{@link https://developers.google.com/web/tools/workbox/guides/handle-third-party-requests}.
2361 * Opaque responses are are cross-origin requests where the response doesn't
2362 * support [CORS]{@link https://enable-cors.org/}.
2363 *
2364 * If the network request fails, and there is no cache match, this will throw
2365 * a `WorkboxError` exception.
2366 *
2367 * @extends module:workbox-strategies.Strategy
2368 * @memberof module:workbox-strategies
2369 */
2370
2371 class NetworkFirst extends Strategy {
2372 /**
2373 * @param {Object} [options]
2374 * @param {string} [options.cacheName] Cache name to store and retrieve
2375 * requests. Defaults to cache names provided by
2376 * [workbox-core]{@link module:workbox-core.cacheNames}.
2377 * @param {Array<Object>} [options.plugins] [Plugins]{@link https://developers.google.com/web/tools/workbox/guides/using-plugins}
2378 * to use in conjunction with this caching strategy.
2379 * @param {Object} [options.fetchOptions] Values passed along to the
2380 * [`init`](https://developer.mozilla.org/en-US/docs/Web/API/WindowOrWorkerGlobalScope/fetch#Parameters)
2381 * of [non-navigation](https://github.com/GoogleChrome/workbox/issues/1796)
2382 * `fetch()` requests made by this strategy.
2383 * @param {Object} [options.matchOptions] [`CacheQueryOptions`](https://w3c.github.io/ServiceWorker/#dictdef-cachequeryoptions)
2384 * @param {number} [options.networkTimeoutSeconds] If set, any network requests
2385 * that fail to respond within the timeout will fallback to the cache.
2386 *
2387 * This option can be used to combat
2388 * "[lie-fi]{@link https://developers.google.com/web/fundamentals/performance/poor-connectivity/#lie-fi}"
2389 * scenarios.
2390 */
2391 constructor(options = {}) {
2392 super(options); // If this instance contains no plugins with a 'cacheWillUpdate' callback,
2393 // prepend the `cacheOkAndOpaquePlugin` plugin to the plugins list.
2394
2395 if (!this.plugins.some(p => 'cacheWillUpdate' in p)) {
2396 this.plugins.unshift(cacheOkAndOpaquePlugin);
2397 }
2398
2399 this._networkTimeoutSeconds = options.networkTimeoutSeconds || 0;
2400
2401 {
2402 if (this._networkTimeoutSeconds) {
2403 finalAssertExports.isType(this._networkTimeoutSeconds, 'number', {
2404 moduleName: 'workbox-strategies',
2405 className: this.constructor.name,
2406 funcName: 'constructor',
2407 paramName: 'networkTimeoutSeconds'
2408 });
2409 }
2410 }
2411 }
2412 /**
2413 * @private
2414 * @param {Request|string} request A request to run this strategy for.
2415 * @param {module:workbox-strategies.StrategyHandler} handler The event that
2416 * triggered the request.
2417 * @return {Promise<Response>}
2418 */
2419
2420
2421 async _handle(request, handler) {
2422 const logs = [];
2423
2424 {
2425 finalAssertExports.isInstance(request, Request, {
2426 moduleName: 'workbox-strategies',
2427 className: this.constructor.name,
2428 funcName: 'handle',
2429 paramName: 'makeRequest'
2430 });
2431 }
2432
2433 const promises = [];
2434 let timeoutId;
2435
2436 if (this._networkTimeoutSeconds) {
2437 const {
2438 id,
2439 promise
2440 } = this._getTimeoutPromise({
2441 request,
2442 logs,
2443 handler
2444 });
2445
2446 timeoutId = id;
2447 promises.push(promise);
2448 }
2449
2450 const networkPromise = this._getNetworkPromise({
2451 timeoutId,
2452 request,
2453 logs,
2454 handler
2455 });
2456
2457 promises.push(networkPromise);
2458 const response = await handler.waitUntil((async () => {
2459 // Promise.race() will resolve as soon as the first promise resolves.
2460 return (await handler.waitUntil(Promise.race(promises))) || ( // If Promise.race() resolved with null, it might be due to a network
2461 // timeout + a cache miss. If that were to happen, we'd rather wait until
2462 // the networkPromise resolves instead of returning null.
2463 // Note that it's fine to await an already-resolved promise, so we don't
2464 // have to check to see if it's still "in flight".
2465 await networkPromise);
2466 })());
2467
2468 {
2469 logger.groupCollapsed(messages.strategyStart(this.constructor.name, request));
2470
2471 for (const log of logs) {
2472 logger.log(log);
2473 }
2474
2475 messages.printFinalResponse(response);
2476 logger.groupEnd();
2477 }
2478
2479 if (!response) {
2480 throw new WorkboxError('no-response', {
2481 url: request.url
2482 });
2483 }
2484
2485 return response;
2486 }
2487 /**
2488 * @param {Object} options
2489 * @param {Request} options.request
2490 * @param {Array} options.logs A reference to the logs array
2491 * @param {Event} options.event
2492 * @return {Promise<Response>}
2493 *
2494 * @private
2495 */
2496
2497
2498 _getTimeoutPromise({
2499 request,
2500 logs,
2501 handler
2502 }) {
2503 let timeoutId;
2504 const timeoutPromise = new Promise(resolve => {
2505 const onNetworkTimeout = async () => {
2506 {
2507 logs.push(`Timing out the network response at ` + `${this._networkTimeoutSeconds} seconds.`);
2508 }
2509
2510 resolve(await handler.cacheMatch(request));
2511 };
2512
2513 timeoutId = setTimeout(onNetworkTimeout, this._networkTimeoutSeconds * 1000);
2514 });
2515 return {
2516 promise: timeoutPromise,
2517 id: timeoutId
2518 };
2519 }
2520 /**
2521 * @param {Object} options
2522 * @param {number|undefined} options.timeoutId
2523 * @param {Request} options.request
2524 * @param {Array} options.logs A reference to the logs Array.
2525 * @param {Event} options.event
2526 * @return {Promise<Response>}
2527 *
2528 * @private
2529 */
2530
2531
2532 async _getNetworkPromise({
2533 timeoutId,
2534 request,
2535 logs,
2536 handler
2537 }) {
2538 let error;
2539 let response;
2540
2541 try {
2542 response = await handler.fetchAndCachePut(request);
2543 } catch (fetchError) {
2544 error = fetchError;
2545 }
2546
2547 if (timeoutId) {
2548 clearTimeout(timeoutId);
2549 }
2550
2551 {
2552 if (response) {
2553 logs.push(`Got response from network.`);
2554 } else {
2555 logs.push(`Unable to get a response from the network. Will respond ` + `with a cached response.`);
2556 }
2557 }
2558
2559 if (error || !response) {
2560 response = await handler.cacheMatch(request);
2561
2562 {
2563 if (response) {
2564 logs.push(`Found a cached response in the '${this.cacheName}'` + ` cache.`);
2565 } else {
2566 logs.push(`No response found in the '${this.cacheName}' cache.`);
2567 }
2568 }
2569 }
2570
2571 return response;
2572 }
2573
2574 }
2575
2576 /*
2577 Copyright 2018 Google LLC
2578
2579 Use of this source code is governed by an MIT-style
2580 license that can be found in the LICENSE file or at
2581 https://opensource.org/licenses/MIT.
2582 */
2583 /**
2584 * An implementation of a
2585 * [network-only]{@link https://developers.google.com/web/fundamentals/instant-and-offline/offline-cookbook/#network-only}
2586 * request strategy.
2587 *
2588 * This class is useful if you want to take advantage of any
2589 * [Workbox plugins]{@link https://developers.google.com/web/tools/workbox/guides/using-plugins}.
2590 *
2591 * If the network request fails, this will throw a `WorkboxError` exception.
2592 *
2593 * @extends module:workbox-strategies.Strategy
2594 * @memberof module:workbox-strategies
2595 */
2596
2597 class NetworkOnly extends Strategy {
2598 /**
2599 * @param {Object} [options]
2600 * @param {Array<Object>} [options.plugins] [Plugins]{@link https://developers.google.com/web/tools/workbox/guides/using-plugins}
2601 * to use in conjunction with this caching strategy.
2602 * @param {Object} [options.fetchOptions] Values passed along to the
2603 * [`init`](https://developer.mozilla.org/en-US/docs/Web/API/WindowOrWorkerGlobalScope/fetch#Parameters)
2604 * of [non-navigation](https://github.com/GoogleChrome/workbox/issues/1796)
2605 * `fetch()` requests made by this strategy.
2606 * @param {number} [options.networkTimeoutSeconds] If set, any network requests
2607 * that fail to respond within the timeout will result in a network error.
2608 */
2609 constructor(options = {}) {
2610 super(options);
2611 this._networkTimeoutSeconds = options.networkTimeoutSeconds || 0;
2612 }
2613 /**
2614 * @private
2615 * @param {Request|string} request A request to run this strategy for.
2616 * @param {module:workbox-strategies.StrategyHandler} handler The event that
2617 * triggered the request.
2618 * @return {Promise<Response>}
2619 */
2620
2621
2622 async _handle(request, handler) {
2623 {
2624 finalAssertExports.isInstance(request, Request, {
2625 moduleName: 'workbox-strategies',
2626 className: this.constructor.name,
2627 funcName: '_handle',
2628 paramName: 'request'
2629 });
2630 }
2631
2632 let error = undefined;
2633 let response;
2634
2635 try {
2636 const promises = [handler.fetch(request)];
2637
2638 if (this._networkTimeoutSeconds) {
2639 const timeoutPromise = timeout(this._networkTimeoutSeconds * 1000);
2640 promises.push(timeoutPromise);
2641 }
2642
2643 response = await Promise.race(promises);
2644
2645 if (!response) {
2646 throw new Error(`Timed out the network response after ` + `${this._networkTimeoutSeconds} seconds.`);
2647 }
2648 } catch (err) {
2649 error = err;
2650 }
2651
2652 {
2653 logger.groupCollapsed(messages.strategyStart(this.constructor.name, request));
2654
2655 if (response) {
2656 logger.log(`Got response from network.`);
2657 } else {
2658 logger.log(`Unable to get a response from the network.`);
2659 }
2660
2661 messages.printFinalResponse(response);
2662 logger.groupEnd();
2663 }
2664
2665 if (!response) {
2666 throw new WorkboxError('no-response', {
2667 url: request.url,
2668 error
2669 });
2670 }
2671
2672 return response;
2673 }
2674
2675 }
2676
2677 /*
2678 Copyright 2019 Google LLC
2679
2680 Use of this source code is governed by an MIT-style
2681 license that can be found in the LICENSE file or at
2682 https://opensource.org/licenses/MIT.
2683 */
2684 /**
2685 * Claim any currently available clients once the service worker
2686 * becomes active. This is normally used in conjunction with `skipWaiting()`.
2687 *
2688 * @memberof module:workbox-core
2689 */
2690
2691 function clientsClaim() {
2692 self.addEventListener('activate', () => self.clients.claim());
2693 }
2694
2695 exports.NetworkFirst = NetworkFirst;
2696 exports.NetworkOnly = NetworkOnly;
2697 exports.clientsClaim = clientsClaim;
2698 exports.registerRoute = registerRoute;
2699
2700});
2701//# sourceMappingURL=workbox-6b19f60b.js.map