@angular/service-worker 12.2.8 → 13.0.0-next.11

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (62) hide show
  1. package/config/config.d.ts +1 -1
  2. package/config/package.json +5 -5
  3. package/{esm2015/config/config.js → esm2020/config/config.mjs} +0 -0
  4. package/{esm2015/config/index.js → esm2020/config/index.mjs} +0 -0
  5. package/{esm2015/config/public_api.js → esm2020/config/public_api.mjs} +0 -0
  6. package/{esm2015/config/src/duration.js → esm2020/config/src/duration.mjs} +0 -0
  7. package/{esm2015/config/src/filesystem.js → esm2020/config/src/filesystem.mjs} +0 -0
  8. package/esm2020/config/src/generator.mjs +147 -0
  9. package/{esm2015/config/src/glob.js → esm2020/config/src/glob.mjs} +0 -0
  10. package/{esm2015/config/src/in.js → esm2020/config/src/in.mjs} +0 -0
  11. package/{esm2015/index.js → esm2020/index.mjs} +0 -0
  12. package/{esm2015/public_api.js → esm2020/public_api.mjs} +0 -0
  13. package/esm2020/service-worker.mjs +5 -0
  14. package/{esm2015/src/index.js → esm2020/src/index.mjs} +0 -0
  15. package/esm2020/src/low_level.mjs +78 -0
  16. package/esm2020/src/module.mjs +128 -0
  17. package/esm2020/src/push.mjs +174 -0
  18. package/esm2020/src/update.mjs +61 -0
  19. package/fesm2015/{config.js → config.mjs} +2 -9
  20. package/fesm2015/config.mjs.map +1 -0
  21. package/fesm2015/{service-worker.js → service-worker.mjs} +23 -20
  22. package/fesm2015/service-worker.mjs.map +1 -0
  23. package/fesm2020/config.mjs +267 -0
  24. package/fesm2020/config.mjs.map +1 -0
  25. package/fesm2020/service-worker.mjs +463 -0
  26. package/fesm2020/service-worker.mjs.map +1 -0
  27. package/ngsw-config.js +169 -218
  28. package/ngsw-worker.js +1421 -1587
  29. package/package.json +36 -9
  30. package/service-worker.d.ts +29 -29
  31. package/bundles/service-worker-config.umd.js +0 -626
  32. package/bundles/service-worker-config.umd.js.map +0 -1
  33. package/bundles/service-worker.umd.js +0 -804
  34. package/bundles/service-worker.umd.js.map +0 -1
  35. package/config/config.metadata.json +0 -1
  36. package/config/index.ngfactory.d.ts +0 -2
  37. package/config/index.ngsummary.d.ts +0 -2
  38. package/config/public_api.ngfactory.d.ts +0 -2
  39. package/config/public_api.ngsummary.d.ts +0 -2
  40. package/config/src/duration.ngfactory.d.ts +0 -2
  41. package/config/src/duration.ngsummary.d.ts +0 -2
  42. package/config/src/filesystem.ngfactory.d.ts +0 -2
  43. package/config/src/filesystem.ngsummary.d.ts +0 -2
  44. package/config/src/generator.ngfactory.d.ts +0 -2
  45. package/config/src/generator.ngsummary.d.ts +0 -2
  46. package/config/src/glob.ngfactory.d.ts +0 -2
  47. package/config/src/glob.ngsummary.d.ts +0 -2
  48. package/config/src/in.ngfactory.d.ts +0 -2
  49. package/config/src/in.ngsummary.d.ts +0 -2
  50. package/config.d.ts +0 -7
  51. package/config.metadata.json +0 -1
  52. package/esm2015/config/config.externs.js +0 -6
  53. package/esm2015/config/src/generator.js +0 -150
  54. package/esm2015/service-worker.externs.js +0 -6
  55. package/esm2015/service-worker.js +0 -7
  56. package/esm2015/src/low_level.js +0 -75
  57. package/esm2015/src/module.js +0 -123
  58. package/esm2015/src/push.js +0 -173
  59. package/esm2015/src/update.js +0 -60
  60. package/fesm2015/config.js.map +0 -1
  61. package/fesm2015/service-worker.js.map +0 -1
  62. package/service-worker.metadata.json +0 -1
package/ngsw-worker.js CHANGED
@@ -1,40 +1,6 @@
1
1
  (function () {
2
2
  'use strict';
3
3
 
4
- /*! *****************************************************************************
5
- Copyright (c) Microsoft Corporation.
6
-
7
- Permission to use, copy, modify, and/or distribute this software for any
8
- purpose with or without fee is hereby granted.
9
-
10
- THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH
11
- REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
12
- AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,
13
- INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
14
- LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR
15
- OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
16
- PERFORMANCE OF THIS SOFTWARE.
17
- ***************************************************************************** */
18
- function __awaiter(thisArg, _arguments, P, generator) {
19
- function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
20
- return new (P || (P = Promise))(function (resolve, reject) {
21
- function fulfilled(value) { try {
22
- step(generator.next(value));
23
- }
24
- catch (e) {
25
- reject(e);
26
- } }
27
- function rejected(value) { try {
28
- step(generator["throw"](value));
29
- }
30
- catch (e) {
31
- reject(e);
32
- } }
33
- function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
34
- step((generator = generator.apply(thisArg, _arguments || [])).next());
35
- });
36
- }
37
-
38
4
  /**
39
5
  * @license
40
6
  * Copyright Google LLC All Rights Reserved.
@@ -59,22 +25,18 @@
59
25
  has(cacheName) {
60
26
  return this.original.has(`${this.cacheNamePrefix}:${cacheName}`);
61
27
  }
62
- keys() {
63
- return __awaiter(this, void 0, void 0, function* () {
64
- const prefix = `${this.cacheNamePrefix}:`;
65
- const allCacheNames = yield this.original.keys();
66
- const ownCacheNames = allCacheNames.filter(name => name.startsWith(prefix));
67
- return ownCacheNames.map(name => name.slice(prefix.length));
68
- });
28
+ async keys() {
29
+ const prefix = `${this.cacheNamePrefix}:`;
30
+ const allCacheNames = await this.original.keys();
31
+ const ownCacheNames = allCacheNames.filter(name => name.startsWith(prefix));
32
+ return ownCacheNames.map(name => name.slice(prefix.length));
69
33
  }
70
34
  match(request, options) {
71
35
  return this.original.match(request, options);
72
36
  }
73
- open(cacheName) {
74
- return __awaiter(this, void 0, void 0, function* () {
75
- const cache = yield this.original.open(`${this.cacheNamePrefix}:${cacheName}`);
76
- return Object.assign(cache, { name: cacheName });
77
- });
37
+ async open(cacheName) {
38
+ const cache = await this.original.open(`${this.cacheNamePrefix}:${cacheName}`);
39
+ return Object.assign(cache, { name: cacheName });
78
40
  }
79
41
  }
80
42
 
@@ -208,25 +170,21 @@
208
170
  }
209
171
  return this.adapter.caches.delete(`${this.cacheNamePrefix}:${name}`);
210
172
  }
211
- list() {
212
- return __awaiter(this, void 0, void 0, function* () {
213
- const prefix = `${this.cacheNamePrefix}:`;
214
- const allCacheNames = yield this.adapter.caches.keys();
215
- const dbCacheNames = allCacheNames.filter(name => name.startsWith(prefix));
216
- // Return the un-prefixed table names, so they can be used with other `CacheDatabase` methods
217
- // (for example, for opening/deleting a table).
218
- return dbCacheNames.map(name => name.slice(prefix.length));
219
- });
220
- }
221
- open(name, cacheQueryOptions) {
222
- return __awaiter(this, void 0, void 0, function* () {
223
- if (!this.tables.has(name)) {
224
- const cache = yield this.adapter.caches.open(`${this.cacheNamePrefix}:${name}`);
225
- const table = new CacheTable(name, cache, this.adapter, cacheQueryOptions);
226
- this.tables.set(name, table);
227
- }
228
- return this.tables.get(name);
229
- });
173
+ async list() {
174
+ const prefix = `${this.cacheNamePrefix}:`;
175
+ const allCacheNames = await this.adapter.caches.keys();
176
+ const dbCacheNames = allCacheNames.filter(name => name.startsWith(prefix));
177
+ // Return the un-prefixed table names, so they can be used with other `CacheDatabase` methods
178
+ // (for example, for opening/deleting a table).
179
+ return dbCacheNames.map(name => name.slice(prefix.length));
180
+ }
181
+ async open(name, cacheQueryOptions) {
182
+ if (!this.tables.has(name)) {
183
+ const cache = await this.adapter.caches.open(`${this.cacheNamePrefix}:${name}`);
184
+ const table = new CacheTable(name, cache, this.adapter, cacheQueryOptions);
185
+ this.tables.set(name, table);
186
+ }
187
+ return this.tables.get(name);
230
188
  }
231
189
  }
232
190
  /**
@@ -485,378 +443,358 @@
485
443
  this.metadata =
486
444
  this.db.open(`${cacheNamePrefix}:${config.name}:meta`, config.cacheQueryOptions);
487
445
  }
488
- cacheStatus(url) {
489
- return __awaiter(this, void 0, void 0, function* () {
490
- const cache = yield this.cache;
491
- const meta = yield this.metadata;
492
- const req = this.adapter.newRequest(url);
493
- const res = yield cache.match(req, this.config.cacheQueryOptions);
494
- if (res === undefined) {
495
- return UpdateCacheStatus.NOT_CACHED;
496
- }
497
- try {
498
- const data = yield meta.read(req.url);
499
- if (!data.used) {
500
- return UpdateCacheStatus.CACHED_BUT_UNUSED;
501
- }
502
- }
503
- catch (_) {
504
- // Error on the side of safety and assume cached.
446
+ async cacheStatus(url) {
447
+ const cache = await this.cache;
448
+ const meta = await this.metadata;
449
+ const req = this.adapter.newRequest(url);
450
+ const res = await cache.match(req, this.config.cacheQueryOptions);
451
+ if (res === undefined) {
452
+ return UpdateCacheStatus.NOT_CACHED;
453
+ }
454
+ try {
455
+ const data = await meta.read(req.url);
456
+ if (!data.used) {
457
+ return UpdateCacheStatus.CACHED_BUT_UNUSED;
505
458
  }
506
- return UpdateCacheStatus.CACHED;
507
- });
459
+ }
460
+ catch (_) {
461
+ // Error on the side of safety and assume cached.
462
+ }
463
+ return UpdateCacheStatus.CACHED;
508
464
  }
509
465
  /**
510
466
  * Return a list of the names of all caches used by this group.
511
467
  */
512
- getCacheNames() {
513
- return __awaiter(this, void 0, void 0, function* () {
514
- const [cache, metadata] = yield Promise.all([
515
- this.cache,
516
- this.metadata,
517
- ]);
518
- return [cache.name, metadata.cacheName];
519
- });
468
+ async getCacheNames() {
469
+ const [cache, metadata] = await Promise.all([
470
+ this.cache,
471
+ this.metadata,
472
+ ]);
473
+ return [cache.name, metadata.cacheName];
520
474
  }
521
475
  /**
522
476
  * Process a request for a given resource and return it, or return null if it's not available.
523
477
  */
524
- handleFetch(req, _event) {
525
- return __awaiter(this, void 0, void 0, function* () {
526
- const url = this.adapter.normalizeUrl(req.url);
527
- // Either the request matches one of the known resource URLs, one of the patterns for
528
- // dynamically matched URLs, or neither. Determine which is the case for this request in
529
- // order to decide how to handle it.
530
- if (this.urls.indexOf(url) !== -1 || this.patterns.some(pattern => pattern.test(url))) {
531
- // This URL matches a known resource. Either it's been cached already or it's missing, in
532
- // which case it needs to be loaded from the network.
533
- // Open the cache to check whether this resource is present.
534
- const cache = yield this.cache;
535
- // Look for a cached response. If one exists, it can be used to resolve the fetch
536
- // operation.
537
- const cachedResponse = yield cache.match(req, this.config.cacheQueryOptions);
538
- if (cachedResponse !== undefined) {
539
- // A response has already been cached (which presumably matches the hash for this
540
- // resource). Check whether it's safe to serve this resource from cache.
541
- if (this.hashes.has(url)) {
542
- // This resource has a hash, and thus is versioned by the manifest. It's safe to return
543
- // the response.
544
- return cachedResponse;
545
- }
546
- else {
547
- // This resource has no hash, and yet exists in the cache. Check how old this request is
548
- // to make sure it's still usable.
549
- if (yield this.needToRevalidate(req, cachedResponse)) {
550
- this.idle.schedule(`revalidate(${cache.name}): ${req.url}`, () => __awaiter(this, void 0, void 0, function* () {
551
- yield this.fetchAndCacheOnce(req);
552
- }));
553
- }
554
- // In either case (revalidation or not), the cached response must be good.
555
- return cachedResponse;
478
+ async handleFetch(req, _event) {
479
+ const url = this.adapter.normalizeUrl(req.url);
480
+ // Either the request matches one of the known resource URLs, one of the patterns for
481
+ // dynamically matched URLs, or neither. Determine which is the case for this request in
482
+ // order to decide how to handle it.
483
+ if (this.urls.indexOf(url) !== -1 || this.patterns.some(pattern => pattern.test(url))) {
484
+ // This URL matches a known resource. Either it's been cached already or it's missing, in
485
+ // which case it needs to be loaded from the network.
486
+ // Open the cache to check whether this resource is present.
487
+ const cache = await this.cache;
488
+ // Look for a cached response. If one exists, it can be used to resolve the fetch
489
+ // operation.
490
+ const cachedResponse = await cache.match(req, this.config.cacheQueryOptions);
491
+ if (cachedResponse !== undefined) {
492
+ // A response has already been cached (which presumably matches the hash for this
493
+ // resource). Check whether it's safe to serve this resource from cache.
494
+ if (this.hashes.has(url)) {
495
+ // This resource has a hash, and thus is versioned by the manifest. It's safe to return
496
+ // the response.
497
+ return cachedResponse;
498
+ }
499
+ else {
500
+ // This resource has no hash, and yet exists in the cache. Check how old this request is
501
+ // to make sure it's still usable.
502
+ if (await this.needToRevalidate(req, cachedResponse)) {
503
+ this.idle.schedule(`revalidate(${cache.name}): ${req.url}`, async () => {
504
+ await this.fetchAndCacheOnce(req);
505
+ });
556
506
  }
507
+ // In either case (revalidation or not), the cached response must be good.
508
+ return cachedResponse;
557
509
  }
558
- // No already-cached response exists, so attempt a fetch/cache operation. The original request
559
- // may specify things like credential inclusion, but for assets these are not honored in order
560
- // to avoid issues with opaque responses. The SW requests the data itself.
561
- const res = yield this.fetchAndCacheOnce(this.adapter.newRequest(req.url));
562
- // If this is successful, the response needs to be cloned as it might be used to respond to
563
- // multiple fetch operations at the same time.
564
- return res.clone();
565
- }
566
- else {
567
- return null;
568
510
  }
569
- });
511
+ // No already-cached response exists, so attempt a fetch/cache operation. The original request
512
+ // may specify things like credential inclusion, but for assets these are not honored in order
513
+ // to avoid issues with opaque responses. The SW requests the data itself.
514
+ const res = await this.fetchAndCacheOnce(this.adapter.newRequest(req.url));
515
+ // If this is successful, the response needs to be cloned as it might be used to respond to
516
+ // multiple fetch operations at the same time.
517
+ return res.clone();
518
+ }
519
+ else {
520
+ return null;
521
+ }
570
522
  }
571
523
  /**
572
524
  * Some resources are cached without a hash, meaning that their expiration is controlled
573
525
  * by HTTP caching headers. Check whether the given request/response pair is still valid
574
526
  * per the caching headers.
575
527
  */
576
- needToRevalidate(req, res) {
577
- return __awaiter(this, void 0, void 0, function* () {
578
- // Three different strategies apply here:
579
- // 1) The request has a Cache-Control header, and thus expiration needs to be based on its age.
580
- // 2) The request has an Expires header, and expiration is based on the current timestamp.
581
- // 3) The request has no applicable caching headers, and must be revalidated.
582
- if (res.headers.has('Cache-Control')) {
583
- // Figure out if there is a max-age directive in the Cache-Control header.
584
- const cacheControl = res.headers.get('Cache-Control');
585
- const cacheDirectives = cacheControl
586
- // Directives are comma-separated within the Cache-Control header value.
587
- .split(',')
588
- // Make sure each directive doesn't have extraneous whitespace.
589
- .map(v => v.trim())
590
- // Some directives have values (like maxage and s-maxage)
591
- .map(v => v.split('='));
592
- // Lowercase all the directive names.
593
- cacheDirectives.forEach(v => v[0] = v[0].toLowerCase());
594
- // Find the max-age directive, if one exists.
595
- const maxAgeDirective = cacheDirectives.find(v => v[0] === 'max-age');
596
- const cacheAge = maxAgeDirective ? maxAgeDirective[1] : undefined;
597
- if (!cacheAge) {
598
- // No usable TTL defined. Must assume that the response is stale.
599
- return true;
600
- }
601
- try {
602
- const maxAge = 1000 * parseInt(cacheAge);
603
- // Determine the origin time of this request. If the SW has metadata on the request (which
604
- // it
605
- // should), it will have the time the request was added to the cache. If it doesn't for some
606
- // reason, the request may have a Date header which will serve the same purpose.
607
- let ts;
608
- try {
609
- // Check the metadata table. If a timestamp is there, use it.
610
- const metaTable = yield this.metadata;
611
- ts = (yield metaTable.read(req.url)).ts;
612
- }
613
- catch (_a) {
614
- // Otherwise, look for a Date header.
615
- const date = res.headers.get('Date');
616
- if (date === null) {
617
- // Unable to determine when this response was created. Assume that it's stale, and
618
- // revalidate it.
619
- return true;
620
- }
621
- ts = Date.parse(date);
622
- }
623
- const age = this.adapter.time - ts;
624
- return age < 0 || age > maxAge;
625
- }
626
- catch (_b) {
627
- // Assume stale.
628
- return true;
629
- }
528
+ async needToRevalidate(req, res) {
529
+ // Three different strategies apply here:
530
+ // 1) The request has a Cache-Control header, and thus expiration needs to be based on its age.
531
+ // 2) The request has an Expires header, and expiration is based on the current timestamp.
532
+ // 3) The request has no applicable caching headers, and must be revalidated.
533
+ if (res.headers.has('Cache-Control')) {
534
+ // Figure out if there is a max-age directive in the Cache-Control header.
535
+ const cacheControl = res.headers.get('Cache-Control');
536
+ const cacheDirectives = cacheControl
537
+ // Directives are comma-separated within the Cache-Control header value.
538
+ .split(',')
539
+ // Make sure each directive doesn't have extraneous whitespace.
540
+ .map(v => v.trim())
541
+ // Some directives have values (like maxage and s-maxage)
542
+ .map(v => v.split('='));
543
+ // Lowercase all the directive names.
544
+ cacheDirectives.forEach(v => v[0] = v[0].toLowerCase());
545
+ // Find the max-age directive, if one exists.
546
+ const maxAgeDirective = cacheDirectives.find(v => v[0] === 'max-age');
547
+ const cacheAge = maxAgeDirective ? maxAgeDirective[1] : undefined;
548
+ if (!cacheAge) {
549
+ // No usable TTL defined. Must assume that the response is stale.
550
+ return true;
630
551
  }
631
- else if (res.headers.has('Expires')) {
632
- // Determine if the expiration time has passed.
633
- const expiresStr = res.headers.get('Expires');
552
+ try {
553
+ const maxAge = 1000 * parseInt(cacheAge);
554
+ // Determine the origin time of this request. If the SW has metadata on the request (which
555
+ // it
556
+ // should), it will have the time the request was added to the cache. If it doesn't for some
557
+ // reason, the request may have a Date header which will serve the same purpose.
558
+ let ts;
634
559
  try {
635
- // The request needs to be revalidated if the current time is later than the expiration
636
- // time, if it parses correctly.
637
- return this.adapter.time > Date.parse(expiresStr);
560
+ // Check the metadata table. If a timestamp is there, use it.
561
+ const metaTable = await this.metadata;
562
+ ts = (await metaTable.read(req.url)).ts;
638
563
  }
639
- catch (_c) {
640
- // The expiration date failed to parse, so revalidate as a precaution.
641
- return true;
564
+ catch {
565
+ // Otherwise, look for a Date header.
566
+ const date = res.headers.get('Date');
567
+ if (date === null) {
568
+ // Unable to determine when this response was created. Assume that it's stale, and
569
+ // revalidate it.
570
+ return true;
571
+ }
572
+ ts = Date.parse(date);
642
573
  }
574
+ const age = this.adapter.time - ts;
575
+ return age < 0 || age > maxAge;
643
576
  }
644
- else {
645
- // No way to evaluate staleness, so assume the response is already stale.
577
+ catch {
578
+ // Assume stale.
646
579
  return true;
647
580
  }
648
- });
581
+ }
582
+ else if (res.headers.has('Expires')) {
583
+ // Determine if the expiration time has passed.
584
+ const expiresStr = res.headers.get('Expires');
585
+ try {
586
+ // The request needs to be revalidated if the current time is later than the expiration
587
+ // time, if it parses correctly.
588
+ return this.adapter.time > Date.parse(expiresStr);
589
+ }
590
+ catch {
591
+ // The expiration date failed to parse, so revalidate as a precaution.
592
+ return true;
593
+ }
594
+ }
595
+ else {
596
+ // No way to evaluate staleness, so assume the response is already stale.
597
+ return true;
598
+ }
649
599
  }
650
600
  /**
651
601
  * Fetch the complete state of a cached resource, or return null if it's not found.
652
602
  */
653
- fetchFromCacheOnly(url) {
654
- return __awaiter(this, void 0, void 0, function* () {
655
- const cache = yield this.cache;
656
- const metaTable = yield this.metadata;
657
- // Lookup the response in the cache.
658
- const request = this.adapter.newRequest(url);
659
- const response = yield cache.match(request, this.config.cacheQueryOptions);
660
- if (response === undefined) {
661
- // It's not found, return null.
662
- return null;
663
- }
664
- // Next, lookup the cached metadata.
665
- let metadata = undefined;
666
- try {
667
- metadata = yield metaTable.read(request.url);
668
- }
669
- catch (_a) {
670
- // Do nothing, not found. This shouldn't happen, but it can be handled.
671
- }
672
- // Return both the response and any available metadata.
673
- return { response, metadata };
674
- });
603
+ async fetchFromCacheOnly(url) {
604
+ const cache = await this.cache;
605
+ const metaTable = await this.metadata;
606
+ // Lookup the response in the cache.
607
+ const request = this.adapter.newRequest(url);
608
+ const response = await cache.match(request, this.config.cacheQueryOptions);
609
+ if (response === undefined) {
610
+ // It's not found, return null.
611
+ return null;
612
+ }
613
+ // Next, lookup the cached metadata.
614
+ let metadata = undefined;
615
+ try {
616
+ metadata = await metaTable.read(request.url);
617
+ }
618
+ catch {
619
+ // Do nothing, not found. This shouldn't happen, but it can be handled.
620
+ }
621
+ // Return both the response and any available metadata.
622
+ return { response, metadata };
675
623
  }
676
624
  /**
677
625
  * Lookup all resources currently stored in the cache which have no associated hash.
678
626
  */
679
- unhashedResources() {
680
- return __awaiter(this, void 0, void 0, function* () {
681
- const cache = yield this.cache;
682
- // Start with the set of all cached requests.
683
- return (yield cache.keys())
684
- // Normalize their URLs.
685
- .map(request => this.adapter.normalizeUrl(request.url))
686
- // Exclude the URLs which have hashes.
687
- .filter(url => !this.hashes.has(url));
688
- });
627
+ async unhashedResources() {
628
+ const cache = await this.cache;
629
+ // Start with the set of all cached requests.
630
+ return (await cache.keys())
631
+ // Normalize their URLs.
632
+ .map(request => this.adapter.normalizeUrl(request.url))
633
+ // Exclude the URLs which have hashes.
634
+ .filter(url => !this.hashes.has(url));
689
635
  }
690
636
  /**
691
637
  * Fetch the given resource from the network, and cache it if able.
692
638
  */
693
- fetchAndCacheOnce(req, used = true) {
694
- return __awaiter(this, void 0, void 0, function* () {
695
- // The `inFlightRequests` map holds information about which caching operations are currently
696
- // underway for known resources. If this request appears there, another "thread" is already
697
- // in the process of caching it, and this work should not be duplicated.
698
- if (this.inFlightRequests.has(req.url)) {
699
- // There is a caching operation already in progress for this request. Wait for it to
700
- // complete, and hopefully it will have yielded a useful response.
701
- return this.inFlightRequests.get(req.url);
702
- }
703
- // No other caching operation is being attempted for this resource, so it will be owned here.
704
- // Go to the network and get the correct version.
705
- const fetchOp = this.fetchFromNetwork(req);
706
- // Save this operation in `inFlightRequests` so any other "thread" attempting to cache it
707
- // will block on this chain instead of duplicating effort.
708
- this.inFlightRequests.set(req.url, fetchOp);
709
- // Make sure this attempt is cleaned up properly on failure.
639
+ async fetchAndCacheOnce(req, used = true) {
640
+ // The `inFlightRequests` map holds information about which caching operations are currently
641
+ // underway for known resources. If this request appears there, another "thread" is already
642
+ // in the process of caching it, and this work should not be duplicated.
643
+ if (this.inFlightRequests.has(req.url)) {
644
+ // There is a caching operation already in progress for this request. Wait for it to
645
+ // complete, and hopefully it will have yielded a useful response.
646
+ return this.inFlightRequests.get(req.url);
647
+ }
648
+ // No other caching operation is being attempted for this resource, so it will be owned here.
649
+ // Go to the network and get the correct version.
650
+ const fetchOp = this.fetchFromNetwork(req);
651
+ // Save this operation in `inFlightRequests` so any other "thread" attempting to cache it
652
+ // will block on this chain instead of duplicating effort.
653
+ this.inFlightRequests.set(req.url, fetchOp);
654
+ // Make sure this attempt is cleaned up properly on failure.
655
+ try {
656
+ // Wait for a response. If this fails, the request will remain in `inFlightRequests`
657
+ // indefinitely.
658
+ const res = await fetchOp;
659
+ // It's very important that only successful responses are cached. Unsuccessful responses
660
+ // should never be cached as this can completely break applications.
661
+ if (!res.ok) {
662
+ throw new Error(`Response not Ok (fetchAndCacheOnce): request for ${req.url} returned response ${res.status} ${res.statusText}`);
663
+ }
710
664
  try {
711
- // Wait for a response. If this fails, the request will remain in `inFlightRequests`
712
- // indefinitely.
713
- const res = yield fetchOp;
714
- // It's very important that only successful responses are cached. Unsuccessful responses
715
- // should never be cached as this can completely break applications.
716
- if (!res.ok) {
717
- throw new Error(`Response not Ok (fetchAndCacheOnce): request for ${req.url} returned response ${res.status} ${res.statusText}`);
718
- }
719
- try {
720
- // This response is safe to cache (as long as it's cloned). Wait until the cache operation
721
- // is complete.
722
- const cache = yield this.cache;
723
- yield cache.put(req, res.clone());
724
- // If the request is not hashed, update its metadata, especially the timestamp. This is
725
- // needed for future determination of whether this cached response is stale or not.
726
- if (!this.hashes.has(this.adapter.normalizeUrl(req.url))) {
727
- // Metadata is tracked for requests that are unhashed.
728
- const meta = { ts: this.adapter.time, used };
729
- const metaTable = yield this.metadata;
730
- yield metaTable.write(req.url, meta);
731
- }
732
- return res;
733
- }
734
- catch (err) {
735
- // Among other cases, this can happen when the user clears all data through the DevTools,
736
- // but the SW is still running and serving another tab. In that case, trying to write to the
737
- // caches throws an `Entry was not found` error.
738
- // If this happens the SW can no longer work correctly. This situation is unrecoverable.
739
- throw new SwCriticalError(`Failed to update the caches for request to '${req.url}' (fetchAndCacheOnce): ${errorToString(err)}`);
665
+ // This response is safe to cache (as long as it's cloned). Wait until the cache operation
666
+ // is complete.
667
+ const cache = await this.cache;
668
+ await cache.put(req, res.clone());
669
+ // If the request is not hashed, update its metadata, especially the timestamp. This is
670
+ // needed for future determination of whether this cached response is stale or not.
671
+ if (!this.hashes.has(this.adapter.normalizeUrl(req.url))) {
672
+ // Metadata is tracked for requests that are unhashed.
673
+ const meta = { ts: this.adapter.time, used };
674
+ const metaTable = await this.metadata;
675
+ await metaTable.write(req.url, meta);
740
676
  }
677
+ return res;
741
678
  }
742
- finally {
743
- // Finally, it can be removed from `inFlightRequests`. This might result in a double-remove
744
- // if some other chain was already making this request too, but that won't hurt anything.
745
- this.inFlightRequests.delete(req.url);
679
+ catch (err) {
680
+ // Among other cases, this can happen when the user clears all data through the DevTools,
681
+ // but the SW is still running and serving another tab. In that case, trying to write to the
682
+ // caches throws an `Entry was not found` error.
683
+ // If this happens the SW can no longer work correctly. This situation is unrecoverable.
684
+ throw new SwCriticalError(`Failed to update the caches for request to '${req.url}' (fetchAndCacheOnce): ${errorToString(err)}`);
746
685
  }
747
- });
686
+ }
687
+ finally {
688
+ // Finally, it can be removed from `inFlightRequests`. This might result in a double-remove
689
+ // if some other chain was already making this request too, but that won't hurt anything.
690
+ this.inFlightRequests.delete(req.url);
691
+ }
748
692
  }
749
- fetchFromNetwork(req, redirectLimit = 3) {
750
- return __awaiter(this, void 0, void 0, function* () {
751
- // Make a cache-busted request for the resource.
752
- const res = yield this.cacheBustedFetchFromNetwork(req);
753
- // Check for redirected responses, and follow the redirects.
754
- if (res['redirected'] && !!res.url) {
755
- // If the redirect limit is exhausted, fail with an error.
756
- if (redirectLimit === 0) {
757
- throw new SwCriticalError(`Response hit redirect limit (fetchFromNetwork): request redirected too many times, next is ${res.url}`);
758
- }
759
- // Unwrap the redirect directly.
760
- return this.fetchFromNetwork(this.adapter.newRequest(res.url), redirectLimit - 1);
761
- }
762
- return res;
763
- });
693
+ async fetchFromNetwork(req, redirectLimit = 3) {
694
+ // Make a cache-busted request for the resource.
695
+ const res = await this.cacheBustedFetchFromNetwork(req);
696
+ // Check for redirected responses, and follow the redirects.
697
+ if (res['redirected'] && !!res.url) {
698
+ // If the redirect limit is exhausted, fail with an error.
699
+ if (redirectLimit === 0) {
700
+ throw new SwCriticalError(`Response hit redirect limit (fetchFromNetwork): request redirected too many times, next is ${res.url}`);
701
+ }
702
+ // Unwrap the redirect directly.
703
+ return this.fetchFromNetwork(this.adapter.newRequest(res.url), redirectLimit - 1);
704
+ }
705
+ return res;
764
706
  }
765
707
  /**
766
708
  * Load a particular asset from the network, accounting for hash validation.
767
709
  */
768
- cacheBustedFetchFromNetwork(req) {
769
- return __awaiter(this, void 0, void 0, function* () {
770
- const url = this.adapter.normalizeUrl(req.url);
771
- // If a hash is available for this resource, then compare the fetched version with the
772
- // canonical hash. Otherwise, the network version will have to be trusted.
773
- if (this.hashes.has(url)) {
774
- // It turns out this resource does have a hash. Look it up. Unless the fetched version
775
- // matches this hash, it's invalid and the whole manifest may need to be thrown out.
776
- const canonicalHash = this.hashes.get(url);
777
- // Ideally, the resource would be requested with cache-busting to guarantee the SW gets
778
- // the freshest version. However, doing this would eliminate any chance of the response
779
- // being in the HTTP cache. Given that the browser has recently actively loaded the page,
780
- // it's likely that many of the responses the SW needs to cache are in the HTTP cache and
781
- // are fresh enough to use. In the future, this could be done by setting cacheMode to
782
- // *only* check the browser cache for a cached version of the resource, when cacheMode is
783
- // fully supported. For now, the resource is fetched directly, without cache-busting, and
784
- // if the hash test fails a cache-busted request is tried before concluding that the
785
- // resource isn't correct. This gives the benefit of acceleration via the HTTP cache
786
- // without the risk of stale data, at the expense of a duplicate request in the event of
787
- // a stale response.
788
- // Fetch the resource from the network (possibly hitting the HTTP cache).
789
- let response = yield this.safeFetch(req);
790
- // Decide whether a cache-busted request is necessary. A cache-busted request is necessary
791
- // only if the request was successful but the hash of the retrieved contents does not match
792
- // the canonical hash from the manifest.
793
- let makeCacheBustedRequest = response.ok;
794
- if (makeCacheBustedRequest) {
795
- // The request was successful. A cache-busted request is only necessary if the hashes
796
- // don't match.
710
+ async cacheBustedFetchFromNetwork(req) {
711
+ const url = this.adapter.normalizeUrl(req.url);
712
+ // If a hash is available for this resource, then compare the fetched version with the
713
+ // canonical hash. Otherwise, the network version will have to be trusted.
714
+ if (this.hashes.has(url)) {
715
+ // It turns out this resource does have a hash. Look it up. Unless the fetched version
716
+ // matches this hash, it's invalid and the whole manifest may need to be thrown out.
717
+ const canonicalHash = this.hashes.get(url);
718
+ // Ideally, the resource would be requested with cache-busting to guarantee the SW gets
719
+ // the freshest version. However, doing this would eliminate any chance of the response
720
+ // being in the HTTP cache. Given that the browser has recently actively loaded the page,
721
+ // it's likely that many of the responses the SW needs to cache are in the HTTP cache and
722
+ // are fresh enough to use. In the future, this could be done by setting cacheMode to
723
+ // *only* check the browser cache for a cached version of the resource, when cacheMode is
724
+ // fully supported. For now, the resource is fetched directly, without cache-busting, and
725
+ // if the hash test fails a cache-busted request is tried before concluding that the
726
+ // resource isn't correct. This gives the benefit of acceleration via the HTTP cache
727
+ // without the risk of stale data, at the expense of a duplicate request in the event of
728
+ // a stale response.
729
+ // Fetch the resource from the network (possibly hitting the HTTP cache).
730
+ let response = await this.safeFetch(req);
731
+ // Decide whether a cache-busted request is necessary. A cache-busted request is necessary
732
+ // only if the request was successful but the hash of the retrieved contents does not match
733
+ // the canonical hash from the manifest.
734
+ let makeCacheBustedRequest = response.ok;
735
+ if (makeCacheBustedRequest) {
736
+ // The request was successful. A cache-busted request is only necessary if the hashes
737
+ // don't match.
738
+ // (Make sure to clone the response so it can be used later if it proves to be valid.)
739
+ const fetchedHash = sha1Binary(await response.clone().arrayBuffer());
740
+ makeCacheBustedRequest = (fetchedHash !== canonicalHash);
741
+ }
742
+ // Make a cache busted request to the network, if necessary.
743
+ if (makeCacheBustedRequest) {
744
+ // Hash failure, the version that was retrieved under the default URL did not have the
745
+ // hash expected. This could be because the HTTP cache got in the way and returned stale
746
+ // data, or because the version on the server really doesn't match. A cache-busting
747
+ // request will differentiate these two situations.
748
+ // TODO: handle case where the URL has parameters already (unlikely for assets).
749
+ const cacheBustReq = this.adapter.newRequest(this.cacheBust(req.url));
750
+ response = await this.safeFetch(cacheBustReq);
751
+ // If the response was successful, check the contents against the canonical hash.
752
+ if (response.ok) {
753
+ // Hash the contents.
797
754
  // (Make sure to clone the response so it can be used later if it proves to be valid.)
798
- const fetchedHash = sha1Binary(yield response.clone().arrayBuffer());
799
- makeCacheBustedRequest = (fetchedHash !== canonicalHash);
800
- }
801
- // Make a cache busted request to the network, if necessary.
802
- if (makeCacheBustedRequest) {
803
- // Hash failure, the version that was retrieved under the default URL did not have the
804
- // hash expected. This could be because the HTTP cache got in the way and returned stale
805
- // data, or because the version on the server really doesn't match. A cache-busting
806
- // request will differentiate these two situations.
807
- // TODO: handle case where the URL has parameters already (unlikely for assets).
808
- const cacheBustReq = this.adapter.newRequest(this.cacheBust(req.url));
809
- response = yield this.safeFetch(cacheBustReq);
810
- // If the response was successful, check the contents against the canonical hash.
811
- if (response.ok) {
812
- // Hash the contents.
813
- // (Make sure to clone the response so it can be used later if it proves to be valid.)
814
- const cacheBustedHash = sha1Binary(yield response.clone().arrayBuffer());
815
- // If the cache-busted version doesn't match, then the manifest is not an accurate
816
- // representation of the server's current set of files, and the SW should give up.
817
- if (canonicalHash !== cacheBustedHash) {
818
- throw new SwCriticalError(`Hash mismatch (cacheBustedFetchFromNetwork): ${req.url}: expected ${canonicalHash}, got ${cacheBustedHash} (after cache busting)`);
819
- }
755
+ const cacheBustedHash = sha1Binary(await response.clone().arrayBuffer());
756
+ // If the cache-busted version doesn't match, then the manifest is not an accurate
757
+ // representation of the server's current set of files, and the SW should give up.
758
+ if (canonicalHash !== cacheBustedHash) {
759
+ throw new SwCriticalError(`Hash mismatch (cacheBustedFetchFromNetwork): ${req.url}: expected ${canonicalHash}, got ${cacheBustedHash} (after cache busting)`);
820
760
  }
821
761
  }
822
- // At this point, `response` is either successful with a matching hash or is unsuccessful.
823
- // Before returning it, check whether it failed with a 404 status. This would signify an
824
- // unrecoverable state.
825
- if (!response.ok && (response.status === 404)) {
826
- throw new SwUnrecoverableStateError(`Failed to retrieve hashed resource from the server. (AssetGroup: ${this.config.name} | URL: ${url})`);
827
- }
828
- // Return the response (successful or unsuccessful).
829
- return response;
830
762
  }
831
- else {
832
- // This URL doesn't exist in our hash database, so it must be requested directly.
833
- return this.safeFetch(req);
763
+ // At this point, `response` is either successful with a matching hash or is unsuccessful.
764
+ // Before returning it, check whether it failed with a 404 status. This would signify an
765
+ // unrecoverable state.
766
+ if (!response.ok && (response.status === 404)) {
767
+ throw new SwUnrecoverableStateError(`Failed to retrieve hashed resource from the server. (AssetGroup: ${this.config.name} | URL: ${url})`);
834
768
  }
835
- });
769
+ // Return the response (successful or unsuccessful).
770
+ return response;
771
+ }
772
+ else {
773
+ // This URL doesn't exist in our hash database, so it must be requested directly.
774
+ return this.safeFetch(req);
775
+ }
836
776
  }
837
777
  /**
838
778
  * Possibly update a resource, if it's expired and needs to be updated. A no-op otherwise.
839
779
  */
840
- maybeUpdate(updateFrom, req, cache) {
841
- return __awaiter(this, void 0, void 0, function* () {
842
- const url = this.adapter.normalizeUrl(req.url);
843
- // Check if this resource is hashed and already exists in the cache of a prior version.
844
- if (this.hashes.has(url)) {
845
- const hash = this.hashes.get(url);
846
- // Check the caches of prior versions, using the hash to ensure the correct version of
847
- // the resource is loaded.
848
- const res = yield updateFrom.lookupResourceWithHash(url, hash);
849
- // If a previously cached version was available, copy it over to this cache.
850
- if (res !== null) {
851
- // Copy to this cache.
852
- yield cache.put(req, res);
853
- // No need to do anything further with this resource, it's now cached properly.
854
- return true;
855
- }
780
+ async maybeUpdate(updateFrom, req, cache) {
781
+ const url = this.adapter.normalizeUrl(req.url);
782
+ // Check if this resource is hashed and already exists in the cache of a prior version.
783
+ if (this.hashes.has(url)) {
784
+ const hash = this.hashes.get(url);
785
+ // Check the caches of prior versions, using the hash to ensure the correct version of
786
+ // the resource is loaded.
787
+ const res = await updateFrom.lookupResourceWithHash(url, hash);
788
+ // If a previously cached version was available, copy it over to this cache.
789
+ if (res !== null) {
790
+ // Copy to this cache.
791
+ await cache.put(req, res);
792
+ // No need to do anything further with this resource, it's now cached properly.
793
+ return true;
856
794
  }
857
- // No up-to-date version of this resource could be found.
858
- return false;
859
- });
795
+ }
796
+ // No up-to-date version of this resource could be found.
797
+ return false;
860
798
  }
861
799
  /**
862
800
  * Construct a cache-busting URL for a given URL.
@@ -864,124 +802,118 @@
864
802
  cacheBust(url) {
865
803
  return url + (url.indexOf('?') === -1 ? '?' : '&') + 'ngsw-cache-bust=' + Math.random();
866
804
  }
867
- safeFetch(req) {
868
- return __awaiter(this, void 0, void 0, function* () {
869
- try {
870
- return yield this.scope.fetch(req);
871
- }
872
- catch (_a) {
873
- return this.adapter.newResponse('', {
874
- status: 504,
875
- statusText: 'Gateway Timeout',
876
- });
877
- }
878
- });
805
+ async safeFetch(req) {
806
+ try {
807
+ return await this.scope.fetch(req);
808
+ }
809
+ catch {
810
+ return this.adapter.newResponse('', {
811
+ status: 504,
812
+ statusText: 'Gateway Timeout',
813
+ });
814
+ }
879
815
  }
880
816
  }
881
817
  /**
882
818
  * An `AssetGroup` that prefetches all of its resources during initialization.
883
819
  */
884
820
  class PrefetchAssetGroup extends AssetGroup {
885
- initializeFully(updateFrom) {
886
- return __awaiter(this, void 0, void 0, function* () {
887
- // Open the cache which actually holds requests.
888
- const cache = yield this.cache;
889
- // Cache all known resources serially. As this reduce proceeds, each Promise waits
890
- // on the last before starting the fetch/cache operation for the next request. Any
891
- // errors cause fall-through to the final Promise which rejects.
892
- yield this.urls.reduce((previous, url) => __awaiter(this, void 0, void 0, function* () {
893
- // Wait on all previous operations to complete.
894
- yield previous;
895
- // Construct the Request for this url.
821
+ async initializeFully(updateFrom) {
822
+ // Open the cache which actually holds requests.
823
+ const cache = await this.cache;
824
+ // Cache all known resources serially. As this reduce proceeds, each Promise waits
825
+ // on the last before starting the fetch/cache operation for the next request. Any
826
+ // errors cause fall-through to the final Promise which rejects.
827
+ await this.urls.reduce(async (previous, url) => {
828
+ // Wait on all previous operations to complete.
829
+ await previous;
830
+ // Construct the Request for this url.
831
+ const req = this.adapter.newRequest(url);
832
+ // First, check the cache to see if there is already a copy of this resource.
833
+ const alreadyCached = (await cache.match(req, this.config.cacheQueryOptions)) !== undefined;
834
+ // If the resource is in the cache already, it can be skipped.
835
+ if (alreadyCached) {
836
+ return;
837
+ }
838
+ // If an update source is available.
839
+ if (updateFrom !== undefined && await this.maybeUpdate(updateFrom, req, cache)) {
840
+ return;
841
+ }
842
+ // Otherwise, go to the network and hopefully cache the response (if successful).
843
+ await this.fetchAndCacheOnce(req, false);
844
+ }, Promise.resolve());
845
+ // Handle updating of unknown (unhashed) resources. This is only possible if there's
846
+ // a source to update from.
847
+ if (updateFrom !== undefined) {
848
+ const metaTable = await this.metadata;
849
+ // Select all of the previously cached resources. These are cached unhashed resources
850
+ // from previous versions of the app, in any asset group.
851
+ await (await updateFrom.previouslyCachedResources())
852
+ // First, narrow down the set of resources to those which are handled by this group.
853
+ // Either it's a known URL, or it matches a given pattern.
854
+ .filter(url => this.urls.indexOf(url) !== -1 || this.patterns.some(pattern => pattern.test(url)))
855
+ // Finally, process each resource in turn.
856
+ .reduce(async (previous, url) => {
857
+ await previous;
896
858
  const req = this.adapter.newRequest(url);
897
- // First, check the cache to see if there is already a copy of this resource.
898
- const alreadyCached = (yield cache.match(req, this.config.cacheQueryOptions)) !== undefined;
899
- // If the resource is in the cache already, it can be skipped.
859
+ // It's possible that the resource in question is already cached. If so,
860
+ // continue to the next one.
861
+ const alreadyCached = (await cache.match(req, this.config.cacheQueryOptions) !== undefined);
900
862
  if (alreadyCached) {
901
863
  return;
902
864
  }
903
- // If an update source is available.
904
- if (updateFrom !== undefined && (yield this.maybeUpdate(updateFrom, req, cache))) {
865
+ // Get the most recent old version of the resource.
866
+ const res = await updateFrom.lookupResourceWithoutHash(url);
867
+ if (res === null || res.metadata === undefined) {
868
+ // Unexpected, but not harmful.
905
869
  return;
906
870
  }
907
- // Otherwise, go to the network and hopefully cache the response (if successful).
908
- yield this.fetchAndCacheOnce(req, false);
909
- }), Promise.resolve());
910
- // Handle updating of unknown (unhashed) resources. This is only possible if there's
911
- // a source to update from.
912
- if (updateFrom !== undefined) {
913
- const metaTable = yield this.metadata;
914
- // Select all of the previously cached resources. These are cached unhashed resources
915
- // from previous versions of the app, in any asset group.
916
- yield (yield updateFrom.previouslyCachedResources())
917
- // First, narrow down the set of resources to those which are handled by this group.
918
- // Either it's a known URL, or it matches a given pattern.
919
- .filter(url => this.urls.indexOf(url) !== -1 || this.patterns.some(pattern => pattern.test(url)))
920
- // Finally, process each resource in turn.
921
- .reduce((previous, url) => __awaiter(this, void 0, void 0, function* () {
922
- yield previous;
923
- const req = this.adapter.newRequest(url);
924
- // It's possible that the resource in question is already cached. If so,
925
- // continue to the next one.
926
- const alreadyCached = ((yield cache.match(req, this.config.cacheQueryOptions)) !== undefined);
927
- if (alreadyCached) {
928
- return;
929
- }
930
- // Get the most recent old version of the resource.
931
- const res = yield updateFrom.lookupResourceWithoutHash(url);
932
- if (res === null || res.metadata === undefined) {
933
- // Unexpected, but not harmful.
934
- return;
935
- }
936
- // Write it into the cache. It may already be expired, but it can still serve
937
- // traffic until it's updated (stale-while-revalidate approach).
938
- yield cache.put(req, res.response);
939
- yield metaTable.write(req.url, Object.assign(Object.assign({}, res.metadata), { used: false }));
940
- }), Promise.resolve());
941
- }
942
- });
871
+ // Write it into the cache. It may already be expired, but it can still serve
872
+ // traffic until it's updated (stale-while-revalidate approach).
873
+ await cache.put(req, res.response);
874
+ await metaTable.write(req.url, { ...res.metadata, used: false });
875
+ }, Promise.resolve());
876
+ }
943
877
  }
944
878
  }
945
879
  class LazyAssetGroup extends AssetGroup {
946
- initializeFully(updateFrom) {
947
- return __awaiter(this, void 0, void 0, function* () {
948
- // No action necessary if no update source is available - resources managed in this group
949
- // are all lazily loaded, so there's nothing to initialize.
950
- if (updateFrom === undefined) {
880
+ async initializeFully(updateFrom) {
881
+ // No action necessary if no update source is available - resources managed in this group
882
+ // are all lazily loaded, so there's nothing to initialize.
883
+ if (updateFrom === undefined) {
884
+ return;
885
+ }
886
+ // Open the cache which actually holds requests.
887
+ const cache = await this.cache;
888
+ // Loop through the listed resources, caching any which are available.
889
+ await this.urls.reduce(async (previous, url) => {
890
+ // Wait on all previous operations to complete.
891
+ await previous;
892
+ // Construct the Request for this url.
893
+ const req = this.adapter.newRequest(url);
894
+ // First, check the cache to see if there is already a copy of this resource.
895
+ const alreadyCached = (await cache.match(req, this.config.cacheQueryOptions)) !== undefined;
896
+ // If the resource is in the cache already, it can be skipped.
897
+ if (alreadyCached) {
951
898
  return;
952
899
  }
953
- // Open the cache which actually holds requests.
954
- const cache = yield this.cache;
955
- // Loop through the listed resources, caching any which are available.
956
- yield this.urls.reduce((previous, url) => __awaiter(this, void 0, void 0, function* () {
957
- // Wait on all previous operations to complete.
958
- yield previous;
959
- // Construct the Request for this url.
960
- const req = this.adapter.newRequest(url);
961
- // First, check the cache to see if there is already a copy of this resource.
962
- const alreadyCached = (yield cache.match(req, this.config.cacheQueryOptions)) !== undefined;
963
- // If the resource is in the cache already, it can be skipped.
964
- if (alreadyCached) {
900
+ const updated = await this.maybeUpdate(updateFrom, req, cache);
901
+ if (this.config.updateMode === 'prefetch' && !updated) {
902
+ // If the resource was not updated, either it was not cached before or
903
+ // the previously cached version didn't match the updated hash. In that
904
+ // case, prefetch update mode dictates that the resource will be updated,
905
+ // except if it was not previously utilized. Check the status of the
906
+ // cached resource to see.
907
+ const cacheStatus = await updateFrom.recentCacheStatus(url);
908
+ // If the resource is not cached, or was cached but unused, then it will be
909
+ // loaded lazily.
910
+ if (cacheStatus !== UpdateCacheStatus.CACHED) {
965
911
  return;
966
912
  }
967
- const updated = yield this.maybeUpdate(updateFrom, req, cache);
968
- if (this.config.updateMode === 'prefetch' && !updated) {
969
- // If the resource was not updated, either it was not cached before or
970
- // the previously cached version didn't match the updated hash. In that
971
- // case, prefetch update mode dictates that the resource will be updated,
972
- // except if it was not previously utilized. Check the status of the
973
- // cached resource to see.
974
- const cacheStatus = yield updateFrom.recentCacheStatus(url);
975
- // If the resource is not cached, or was cached but unused, then it will be
976
- // loaded lazily.
977
- if (cacheStatus !== UpdateCacheStatus.CACHED) {
978
- return;
979
- }
980
- // Update from the network.
981
- yield this.fetchAndCacheOnce(req, false);
982
- }
983
- }), Promise.resolve());
984
- });
913
+ // Update from the network.
914
+ await this.fetchAndCacheOnce(req, false);
915
+ }
916
+ }, Promise.resolve());
985
917
  }
986
918
  }
987
919
 
@@ -1139,174 +1071,164 @@
1139
1071
  /**
1140
1072
  * Lazily initialize/load the LRU chain.
1141
1073
  */
1142
- lru() {
1143
- return __awaiter(this, void 0, void 0, function* () {
1144
- if (this._lru === null) {
1145
- const table = yield this.lruTable;
1146
- try {
1147
- this._lru = new LruList(yield table.read('lru'));
1148
- }
1149
- catch (_a) {
1150
- this._lru = new LruList();
1151
- }
1074
+ async lru() {
1075
+ if (this._lru === null) {
1076
+ const table = await this.lruTable;
1077
+ try {
1078
+ this._lru = new LruList(await table.read('lru'));
1152
1079
  }
1153
- return this._lru;
1154
- });
1080
+ catch {
1081
+ this._lru = new LruList();
1082
+ }
1083
+ }
1084
+ return this._lru;
1155
1085
  }
1156
1086
  /**
1157
1087
  * Sync the LRU chain to non-volatile storage.
1158
1088
  */
1159
- syncLru() {
1160
- return __awaiter(this, void 0, void 0, function* () {
1161
- if (this._lru === null) {
1162
- return;
1163
- }
1164
- const table = yield this.lruTable;
1165
- try {
1166
- return table.write('lru', this._lru.state);
1167
- }
1168
- catch (err) {
1169
- // Writing lru cache table failed. This could be a result of a full storage.
1170
- // Continue serving clients as usual.
1171
- this.debugHandler.log(err, `DataGroup(${this.config.name}@${this.config.version}).syncLru()`);
1172
- // TODO: Better detect/handle full storage; e.g. using
1173
- // [navigator.storage](https://developer.mozilla.org/en-US/docs/Web/API/NavigatorStorage/storage).
1174
- }
1175
- });
1089
+ async syncLru() {
1090
+ if (this._lru === null) {
1091
+ return;
1092
+ }
1093
+ const table = await this.lruTable;
1094
+ try {
1095
+ return table.write('lru', this._lru.state);
1096
+ }
1097
+ catch (err) {
1098
+ // Writing lru cache table failed. This could be a result of a full storage.
1099
+ // Continue serving clients as usual.
1100
+ this.debugHandler.log(err, `DataGroup(${this.config.name}@${this.config.version}).syncLru()`);
1101
+ // TODO: Better detect/handle full storage; e.g. using
1102
+ // [navigator.storage](https://developer.mozilla.org/en-US/docs/Web/API/NavigatorStorage/storage).
1103
+ }
1176
1104
  }
1177
1105
  /**
1178
1106
  * Process a fetch event and return a `Response` if the resource is covered by this group,
1179
1107
  * or `null` otherwise.
1180
1108
  */
1181
- handleFetch(req, event) {
1182
- return __awaiter(this, void 0, void 0, function* () {
1183
- // Do nothing
1184
- if (!this.patterns.some(pattern => pattern.test(req.url))) {
1109
+ async handleFetch(req, event) {
1110
+ // Do nothing
1111
+ if (!this.patterns.some(pattern => pattern.test(req.url))) {
1112
+ return null;
1113
+ }
1114
+ // Lazily initialize the LRU cache.
1115
+ const lru = await this.lru();
1116
+ // The URL matches this cache. First, check whether this is a mutating request or not.
1117
+ switch (req.method) {
1118
+ case 'OPTIONS':
1119
+ // Don't try to cache this - it's non-mutating, but is part of a mutating request.
1120
+ // Most likely SWs don't even see this, but this guard is here just in case.
1185
1121
  return null;
1186
- }
1187
- // Lazily initialize the LRU cache.
1188
- const lru = yield this.lru();
1189
- // The URL matches this cache. First, check whether this is a mutating request or not.
1190
- switch (req.method) {
1191
- case 'OPTIONS':
1192
- // Don't try to cache this - it's non-mutating, but is part of a mutating request.
1193
- // Most likely SWs don't even see this, but this guard is here just in case.
1194
- return null;
1195
- case 'GET':
1196
- case 'HEAD':
1197
- // Handle the request with whatever strategy was selected.
1198
- switch (this.config.strategy) {
1199
- case 'freshness':
1200
- return this.handleFetchWithFreshness(req, event, lru);
1201
- case 'performance':
1202
- return this.handleFetchWithPerformance(req, event, lru);
1203
- default:
1204
- throw new Error(`Unknown strategy: ${this.config.strategy}`);
1205
- }
1206
- default:
1207
- // This was a mutating request. Assume the cache for this URL is no longer valid.
1208
- const wasCached = lru.remove(req.url);
1209
- // If there was a cached entry, remove it.
1210
- if (wasCached) {
1211
- yield this.clearCacheForUrl(req.url);
1212
- }
1213
- // Sync the LRU chain to non-volatile storage.
1214
- yield this.syncLru();
1215
- // Finally, fall back on the network.
1216
- return this.safeFetch(req);
1217
- }
1218
- });
1219
- }
1220
- handleFetchWithPerformance(req, event, lru) {
1221
- return __awaiter(this, void 0, void 0, function* () {
1222
- let res = null;
1223
- // Check the cache first. If the resource exists there (and is not expired), the cached
1224
- // version can be used.
1225
- const fromCache = yield this.loadFromCache(req, lru);
1226
- if (fromCache !== null) {
1227
- res = fromCache.res;
1228
- // Check the age of the resource.
1229
- if (this.config.refreshAheadMs !== undefined && fromCache.age >= this.config.refreshAheadMs) {
1230
- event.waitUntil(this.safeCacheResponse(req, this.safeFetch(req), lru));
1122
+ case 'GET':
1123
+ case 'HEAD':
1124
+ // Handle the request with whatever strategy was selected.
1125
+ switch (this.config.strategy) {
1126
+ case 'freshness':
1127
+ return this.handleFetchWithFreshness(req, event, lru);
1128
+ case 'performance':
1129
+ return this.handleFetchWithPerformance(req, event, lru);
1130
+ default:
1131
+ throw new Error(`Unknown strategy: ${this.config.strategy}`);
1231
1132
  }
1232
- }
1233
- if (res !== null) {
1234
- return res;
1235
- }
1236
- // No match from the cache. Go to the network. Note that this is not an 'await'
1237
- // call, networkFetch is the actual Promise. This is due to timeout handling.
1238
- const [timeoutFetch, networkFetch] = this.networkFetchWithTimeout(req);
1239
- res = yield timeoutFetch;
1240
- // Since fetch() will always return a response, undefined indicates a timeout.
1241
- if (res === undefined) {
1242
- // The request timed out. Return a Gateway Timeout error.
1243
- res = this.adapter.newResponse(null, { status: 504, statusText: 'Gateway Timeout' });
1244
- // Cache the network response eventually.
1245
- event.waitUntil(this.safeCacheResponse(req, networkFetch, lru));
1246
- }
1247
- else {
1248
- // The request completed in time, so cache it inline with the response flow.
1249
- yield this.safeCacheResponse(req, res, lru);
1250
- }
1251
- return res;
1252
- });
1133
+ default:
1134
+ // This was a mutating request. Assume the cache for this URL is no longer valid.
1135
+ const wasCached = lru.remove(req.url);
1136
+ // If there was a cached entry, remove it.
1137
+ if (wasCached) {
1138
+ await this.clearCacheForUrl(req.url);
1139
+ }
1140
+ // Sync the LRU chain to non-volatile storage.
1141
+ await this.syncLru();
1142
+ // Finally, fall back on the network.
1143
+ return this.safeFetch(req);
1144
+ }
1253
1145
  }
1254
- handleFetchWithFreshness(req, event, lru) {
1255
- return __awaiter(this, void 0, void 0, function* () {
1256
- // Start with a network fetch.
1257
- const [timeoutFetch, networkFetch] = this.networkFetchWithTimeout(req);
1258
- let res;
1259
- // If that fetch errors, treat it as a timed out request.
1260
- try {
1261
- res = yield timeoutFetch;
1262
- }
1263
- catch (_a) {
1264
- res = undefined;
1265
- }
1266
- // If the network fetch times out or errors, fall back on the cache.
1267
- if (res === undefined) {
1268
- event.waitUntil(this.safeCacheResponse(req, networkFetch, lru, true));
1269
- // Ignore the age, the network response will be cached anyway due to the
1270
- // behavior of freshness.
1271
- const fromCache = yield this.loadFromCache(req, lru);
1272
- res = (fromCache !== null) ? fromCache.res : null;
1146
+ async handleFetchWithPerformance(req, event, lru) {
1147
+ let res = null;
1148
+ // Check the cache first. If the resource exists there (and is not expired), the cached
1149
+ // version can be used.
1150
+ const fromCache = await this.loadFromCache(req, lru);
1151
+ if (fromCache !== null) {
1152
+ res = fromCache.res;
1153
+ // Check the age of the resource.
1154
+ if (this.config.refreshAheadMs !== undefined && fromCache.age >= this.config.refreshAheadMs) {
1155
+ event.waitUntil(this.safeCacheResponse(req, this.safeFetch(req), lru));
1273
1156
  }
1274
- else {
1275
- yield this.safeCacheResponse(req, res, lru, true);
1276
- }
1277
- // Either the network fetch didn't time out, or the cache yielded a usable response.
1278
- // In either case, use it.
1279
- if (res !== null) {
1280
- return res;
1281
- }
1282
- // No response in the cache. No choice but to fall back on the full network fetch.
1283
- return networkFetch;
1284
- });
1157
+ }
1158
+ if (res !== null) {
1159
+ return res;
1160
+ }
1161
+ // No match from the cache. Go to the network. Note that this is not an 'await'
1162
+ // call, networkFetch is the actual Promise. This is due to timeout handling.
1163
+ const [timeoutFetch, networkFetch] = this.networkFetchWithTimeout(req);
1164
+ res = await timeoutFetch;
1165
+ // Since fetch() will always return a response, undefined indicates a timeout.
1166
+ if (res === undefined) {
1167
+ // The request timed out. Return a Gateway Timeout error.
1168
+ res = this.adapter.newResponse(null, { status: 504, statusText: 'Gateway Timeout' });
1169
+ // Cache the network response eventually.
1170
+ event.waitUntil(this.safeCacheResponse(req, networkFetch, lru));
1171
+ }
1172
+ else {
1173
+ // The request completed in time, so cache it inline with the response flow.
1174
+ await this.safeCacheResponse(req, res, lru);
1175
+ }
1176
+ return res;
1177
+ }
1178
+ async handleFetchWithFreshness(req, event, lru) {
1179
+ // Start with a network fetch.
1180
+ const [timeoutFetch, networkFetch] = this.networkFetchWithTimeout(req);
1181
+ let res;
1182
+ // If that fetch errors, treat it as a timed out request.
1183
+ try {
1184
+ res = await timeoutFetch;
1185
+ }
1186
+ catch {
1187
+ res = undefined;
1188
+ }
1189
+ // If the network fetch times out or errors, fall back on the cache.
1190
+ if (res === undefined) {
1191
+ event.waitUntil(this.safeCacheResponse(req, networkFetch, lru, true));
1192
+ // Ignore the age, the network response will be cached anyway due to the
1193
+ // behavior of freshness.
1194
+ const fromCache = await this.loadFromCache(req, lru);
1195
+ res = (fromCache !== null) ? fromCache.res : null;
1196
+ }
1197
+ else {
1198
+ await this.safeCacheResponse(req, res, lru, true);
1199
+ }
1200
+ // Either the network fetch didn't time out, or the cache yielded a usable response.
1201
+ // In either case, use it.
1202
+ if (res !== null) {
1203
+ return res;
1204
+ }
1205
+ // No response in the cache. No choice but to fall back on the full network fetch.
1206
+ return networkFetch;
1285
1207
  }
1286
1208
  networkFetchWithTimeout(req) {
1287
1209
  // If there is a timeout configured, race a timeout Promise with the network fetch.
1288
1210
  // Otherwise, just fetch from the network directly.
1289
1211
  if (this.config.timeoutMs !== undefined) {
1290
1212
  const networkFetch = this.scope.fetch(req);
1291
- const safeNetworkFetch = (() => __awaiter(this, void 0, void 0, function* () {
1213
+ const safeNetworkFetch = (async () => {
1292
1214
  try {
1293
- return yield networkFetch;
1215
+ return await networkFetch;
1294
1216
  }
1295
- catch (_a) {
1217
+ catch {
1296
1218
  return this.adapter.newResponse(null, {
1297
1219
  status: 504,
1298
1220
  statusText: 'Gateway Timeout',
1299
1221
  });
1300
1222
  }
1301
- }))();
1302
- const networkFetchUndefinedError = (() => __awaiter(this, void 0, void 0, function* () {
1223
+ })();
1224
+ const networkFetchUndefinedError = (async () => {
1303
1225
  try {
1304
- return yield networkFetch;
1226
+ return await networkFetch;
1305
1227
  }
1306
- catch (_b) {
1228
+ catch {
1307
1229
  return undefined;
1308
1230
  }
1309
- }))();
1231
+ })();
1310
1232
  // Construct a Promise<undefined> for the timeout.
1311
1233
  const timeout = this.adapter.timeout(this.config.timeoutMs);
1312
1234
  // Race that with the network fetch. This will either be a Response, or `undefined`
@@ -1319,56 +1241,52 @@
1319
1241
  return [networkFetch, networkFetch];
1320
1242
  }
1321
1243
  }
1322
- safeCacheResponse(req, resOrPromise, lru, okToCacheOpaque) {
1323
- return __awaiter(this, void 0, void 0, function* () {
1244
+ async safeCacheResponse(req, resOrPromise, lru, okToCacheOpaque) {
1245
+ try {
1246
+ const res = await resOrPromise;
1324
1247
  try {
1325
- const res = yield resOrPromise;
1326
- try {
1327
- yield this.cacheResponse(req, res, lru, okToCacheOpaque);
1328
- }
1329
- catch (err) {
1330
- // Saving the API response failed. This could be a result of a full storage.
1331
- // Since this data is cached lazily and temporarily, continue serving clients as usual.
1332
- this.debugHandler.log(err, `DataGroup(${this.config.name}@${this.config.version}).safeCacheResponse(${req.url}, status: ${res.status})`);
1333
- // TODO: Better detect/handle full storage; e.g. using
1334
- // [navigator.storage](https://developer.mozilla.org/en-US/docs/Web/API/NavigatorStorage/storage).
1335
- }
1248
+ await this.cacheResponse(req, res, lru, okToCacheOpaque);
1336
1249
  }
1337
- catch (_a) {
1338
- // Request failed
1339
- // TODO: Handle this error somehow?
1250
+ catch (err) {
1251
+ // Saving the API response failed. This could be a result of a full storage.
1252
+ // Since this data is cached lazily and temporarily, continue serving clients as usual.
1253
+ this.debugHandler.log(err, `DataGroup(${this.config.name}@${this.config.version}).safeCacheResponse(${req.url}, status: ${res.status})`);
1254
+ // TODO: Better detect/handle full storage; e.g. using
1255
+ // [navigator.storage](https://developer.mozilla.org/en-US/docs/Web/API/NavigatorStorage/storage).
1340
1256
  }
1341
- });
1257
+ }
1258
+ catch {
1259
+ // Request failed
1260
+ // TODO: Handle this error somehow?
1261
+ }
1342
1262
  }
1343
- loadFromCache(req, lru) {
1344
- return __awaiter(this, void 0, void 0, function* () {
1345
- // Look for a response in the cache. If one exists, return it.
1346
- const cache = yield this.cache;
1347
- let res = yield cache.match(req, this.config.cacheQueryOptions);
1348
- if (res !== undefined) {
1349
- // A response was found in the cache, but its age is not yet known. Look it up.
1350
- try {
1351
- const ageTable = yield this.ageTable;
1352
- const age = this.adapter.time - (yield ageTable.read(req.url)).age;
1353
- // If the response is young enough, use it.
1354
- if (age <= this.config.maxAge) {
1355
- // Successful match from the cache. Use the response, after marking it as having
1356
- // been accessed.
1357
- lru.accessed(req.url);
1358
- return { res, age };
1359
- }
1360
- // Otherwise, or if there was an error, assume the response is expired, and evict it.
1361
- }
1362
- catch (_a) {
1363
- // Some error getting the age for the response. Assume it's expired.
1263
+ async loadFromCache(req, lru) {
1264
+ // Look for a response in the cache. If one exists, return it.
1265
+ const cache = await this.cache;
1266
+ let res = await cache.match(req, this.config.cacheQueryOptions);
1267
+ if (res !== undefined) {
1268
+ // A response was found in the cache, but its age is not yet known. Look it up.
1269
+ try {
1270
+ const ageTable = await this.ageTable;
1271
+ const age = this.adapter.time - (await ageTable.read(req.url)).age;
1272
+ // If the response is young enough, use it.
1273
+ if (age <= this.config.maxAge) {
1274
+ // Successful match from the cache. Use the response, after marking it as having
1275
+ // been accessed.
1276
+ lru.accessed(req.url);
1277
+ return { res, age };
1364
1278
  }
1365
- lru.remove(req.url);
1366
- yield this.clearCacheForUrl(req.url);
1367
- // TODO: avoid duplicate in event of network timeout, maybe.
1368
- yield this.syncLru();
1279
+ // Otherwise, or if there was an error, assume the response is expired, and evict it.
1369
1280
  }
1370
- return null;
1371
- });
1281
+ catch {
1282
+ // Some error getting the age for the response. Assume it's expired.
1283
+ }
1284
+ lru.remove(req.url);
1285
+ await this.clearCacheForUrl(req.url);
1286
+ // TODO: avoid duplicate in event of network timeout, maybe.
1287
+ await this.syncLru();
1288
+ }
1289
+ return null;
1372
1290
  }
1373
1291
  /**
1374
1292
  * Operation for caching the response from the server. This has to happen all
@@ -1377,60 +1295,54 @@
1377
1295
  * If the request times out on the server, an error will be returned but the real network
1378
1296
  * request will still be running in the background, to be cached when it completes.
1379
1297
  */
1380
- cacheResponse(req, res, lru, okToCacheOpaque = false) {
1381
- return __awaiter(this, void 0, void 0, function* () {
1382
- // Only cache successful responses.
1383
- if (!(res.ok || (okToCacheOpaque && res.type === 'opaque'))) {
1384
- return;
1385
- }
1386
- // If caching this response would make the cache exceed its maximum size, evict something
1387
- // first.
1388
- if (lru.size >= this.config.maxSize) {
1389
- // The cache is too big, evict something.
1390
- const evictedUrl = lru.pop();
1391
- if (evictedUrl !== null) {
1392
- yield this.clearCacheForUrl(evictedUrl);
1393
- }
1298
+ async cacheResponse(req, res, lru, okToCacheOpaque = false) {
1299
+ // Only cache successful responses.
1300
+ if (!(res.ok || (okToCacheOpaque && res.type === 'opaque'))) {
1301
+ return;
1302
+ }
1303
+ // If caching this response would make the cache exceed its maximum size, evict something
1304
+ // first.
1305
+ if (lru.size >= this.config.maxSize) {
1306
+ // The cache is too big, evict something.
1307
+ const evictedUrl = lru.pop();
1308
+ if (evictedUrl !== null) {
1309
+ await this.clearCacheForUrl(evictedUrl);
1394
1310
  }
1395
- // TODO: evaluate for possible race conditions during flaky network periods.
1396
- // Mark this resource as having been accessed recently. This ensures it won't be evicted
1397
- // until enough other resources are requested that it falls off the end of the LRU chain.
1398
- lru.accessed(req.url);
1399
- // Store the response in the cache (cloning because the browser will consume
1400
- // the body during the caching operation).
1401
- yield (yield this.cache).put(req, res.clone());
1402
- // Store the age of the cache.
1403
- const ageTable = yield this.ageTable;
1404
- yield ageTable.write(req.url, { age: this.adapter.time });
1405
- // Sync the LRU chain to non-volatile storage.
1406
- yield this.syncLru();
1407
- });
1311
+ }
1312
+ // TODO: evaluate for possible race conditions during flaky network periods.
1313
+ // Mark this resource as having been accessed recently. This ensures it won't be evicted
1314
+ // until enough other resources are requested that it falls off the end of the LRU chain.
1315
+ lru.accessed(req.url);
1316
+ // Store the response in the cache (cloning because the browser will consume
1317
+ // the body during the caching operation).
1318
+ await (await this.cache).put(req, res.clone());
1319
+ // Store the age of the cache.
1320
+ const ageTable = await this.ageTable;
1321
+ await ageTable.write(req.url, { age: this.adapter.time });
1322
+ // Sync the LRU chain to non-volatile storage.
1323
+ await this.syncLru();
1408
1324
  }
1409
1325
  /**
1410
1326
  * Delete all of the saved state which this group uses to track resources.
1411
1327
  */
1412
- cleanup() {
1413
- return __awaiter(this, void 0, void 0, function* () {
1414
- // Remove both the cache and the database entries which track LRU stats.
1415
- yield Promise.all([
1416
- this.cache.then(cache => this.adapter.caches.delete(cache.name)),
1417
- this.ageTable.then(table => this.db.delete(table.name)),
1418
- this.lruTable.then(table => this.db.delete(table.name)),
1419
- ]);
1420
- });
1328
+ async cleanup() {
1329
+ // Remove both the cache and the database entries which track LRU stats.
1330
+ await Promise.all([
1331
+ this.cache.then(cache => this.adapter.caches.delete(cache.name)),
1332
+ this.ageTable.then(table => this.db.delete(table.name)),
1333
+ this.lruTable.then(table => this.db.delete(table.name)),
1334
+ ]);
1421
1335
  }
1422
1336
  /**
1423
1337
  * Return a list of the names of all caches used by this group.
1424
1338
  */
1425
- getCacheNames() {
1426
- return __awaiter(this, void 0, void 0, function* () {
1427
- const [cache, ageTable, lruTable] = yield Promise.all([
1428
- this.cache,
1429
- this.ageTable,
1430
- this.lruTable,
1431
- ]);
1432
- return [cache.name, ageTable.cacheName, lruTable.cacheName];
1433
- });
1339
+ async getCacheNames() {
1340
+ const [cache, ageTable, lruTable] = await Promise.all([
1341
+ this.cache,
1342
+ this.ageTable,
1343
+ this.lruTable,
1344
+ ]);
1345
+ return [cache.name, ageTable.cacheName, lruTable.cacheName];
1434
1346
  }
1435
1347
  /**
1436
1348
  * Clear the state of the cache for a particular resource.
@@ -1439,28 +1351,24 @@
1439
1351
  * been done already. This clears the GET and HEAD versions of the request from
1440
1352
  * the cache itself, as well as the metadata stored in the age table.
1441
1353
  */
1442
- clearCacheForUrl(url) {
1443
- return __awaiter(this, void 0, void 0, function* () {
1444
- const [cache, ageTable] = yield Promise.all([this.cache, this.ageTable]);
1445
- yield Promise.all([
1446
- cache.delete(this.adapter.newRequest(url, { method: 'GET' }), this.config.cacheQueryOptions),
1447
- cache.delete(this.adapter.newRequest(url, { method: 'HEAD' }), this.config.cacheQueryOptions),
1448
- ageTable.delete(url),
1449
- ]);
1450
- });
1451
- }
1452
- safeFetch(req) {
1453
- return __awaiter(this, void 0, void 0, function* () {
1454
- try {
1455
- return this.scope.fetch(req);
1456
- }
1457
- catch (_a) {
1458
- return this.adapter.newResponse(null, {
1459
- status: 504,
1460
- statusText: 'Gateway Timeout',
1461
- });
1462
- }
1463
- });
1354
+ async clearCacheForUrl(url) {
1355
+ const [cache, ageTable] = await Promise.all([this.cache, this.ageTable]);
1356
+ await Promise.all([
1357
+ cache.delete(this.adapter.newRequest(url, { method: 'GET' }), this.config.cacheQueryOptions),
1358
+ cache.delete(this.adapter.newRequest(url, { method: 'HEAD' }), this.config.cacheQueryOptions),
1359
+ ageTable.delete(url),
1360
+ ]);
1361
+ }
1362
+ async safeFetch(req) {
1363
+ try {
1364
+ return this.scope.fetch(req);
1365
+ }
1366
+ catch {
1367
+ return this.adapter.newResponse(null, {
1368
+ status: 504,
1369
+ statusText: 'Gateway Timeout',
1370
+ });
1371
+ }
1464
1372
  }
1465
1373
  }
1466
1374
 
@@ -1543,86 +1451,82 @@
1543
1451
  * required
1544
1452
  * data has been safely downloaded.
1545
1453
  */
1546
- initializeFully(updateFrom) {
1547
- return __awaiter(this, void 0, void 0, function* () {
1548
- try {
1549
- // Fully initialize each asset group, in series. Starts with an empty Promise,
1550
- // and waits for the previous groups to have been initialized before initializing
1551
- // the next one in turn.
1552
- yield this.assetGroups.reduce((previous, group) => __awaiter(this, void 0, void 0, function* () {
1553
- // Wait for the previous groups to complete initialization. If there is a
1554
- // failure, this will throw, and each subsequent group will throw, until the
1555
- // whole sequence fails.
1556
- yield previous;
1557
- // Initialize this group.
1558
- return group.initializeFully(updateFrom);
1559
- }), Promise.resolve());
1454
+ async initializeFully(updateFrom) {
1455
+ try {
1456
+ // Fully initialize each asset group, in series. Starts with an empty Promise,
1457
+ // and waits for the previous groups to have been initialized before initializing
1458
+ // the next one in turn.
1459
+ await this.assetGroups.reduce(async (previous, group) => {
1460
+ // Wait for the previous groups to complete initialization. If there is a
1461
+ // failure, this will throw, and each subsequent group will throw, until the
1462
+ // whole sequence fails.
1463
+ await previous;
1464
+ // Initialize this group.
1465
+ return group.initializeFully(updateFrom);
1466
+ }, Promise.resolve());
1467
+ }
1468
+ catch (err) {
1469
+ this._okay = false;
1470
+ throw err;
1471
+ }
1472
+ }
1473
+ async handleFetch(req, event) {
1474
+ // Check the request against each `AssetGroup` in sequence. If an `AssetGroup` can't handle the
1475
+ // request,
1476
+ // it will return `null`. Thus, the first non-null response is the SW's answer to the request.
1477
+ // So reduce
1478
+ // the group list, keeping track of a possible response. If there is one, it gets passed
1479
+ // through, and if
1480
+ // not the next group is consulted to produce a candidate response.
1481
+ const asset = await this.assetGroups.reduce(async (potentialResponse, group) => {
1482
+ // Wait on the previous potential response. If it's not null, it should just be passed
1483
+ // through.
1484
+ const resp = await potentialResponse;
1485
+ if (resp !== null) {
1486
+ return resp;
1560
1487
  }
1561
- catch (err) {
1562
- this._okay = false;
1563
- throw err;
1488
+ // No response has been found yet. Maybe this group will have one.
1489
+ return group.handleFetch(req, event);
1490
+ }, Promise.resolve(null));
1491
+ // The result of the above is the asset response, if there is any, or null otherwise. Return the
1492
+ // asset
1493
+ // response if there was one. If not, check with the data caching groups.
1494
+ if (asset !== null) {
1495
+ return asset;
1496
+ }
1497
+ // Perform the same reduction operation as above, but this time processing
1498
+ // the data caching groups.
1499
+ const data = await this.dataGroups.reduce(async (potentialResponse, group) => {
1500
+ const resp = await potentialResponse;
1501
+ if (resp !== null) {
1502
+ return resp;
1564
1503
  }
1565
- });
1566
- }
1567
- handleFetch(req, event) {
1568
- return __awaiter(this, void 0, void 0, function* () {
1569
- // Check the request against each `AssetGroup` in sequence. If an `AssetGroup` can't handle the
1570
- // request,
1571
- // it will return `null`. Thus, the first non-null response is the SW's answer to the request.
1572
- // So reduce
1573
- // the group list, keeping track of a possible response. If there is one, it gets passed
1574
- // through, and if
1575
- // not the next group is consulted to produce a candidate response.
1576
- const asset = yield this.assetGroups.reduce((potentialResponse, group) => __awaiter(this, void 0, void 0, function* () {
1577
- // Wait on the previous potential response. If it's not null, it should just be passed
1578
- // through.
1579
- const resp = yield potentialResponse;
1580
- if (resp !== null) {
1581
- return resp;
1582
- }
1583
- // No response has been found yet. Maybe this group will have one.
1584
- return group.handleFetch(req, event);
1585
- }), Promise.resolve(null));
1586
- // The result of the above is the asset response, if there is any, or null otherwise. Return the
1587
- // asset
1588
- // response if there was one. If not, check with the data caching groups.
1589
- if (asset !== null) {
1590
- return asset;
1591
- }
1592
- // Perform the same reduction operation as above, but this time processing
1593
- // the data caching groups.
1594
- const data = yield this.dataGroups.reduce((potentialResponse, group) => __awaiter(this, void 0, void 0, function* () {
1595
- const resp = yield potentialResponse;
1596
- if (resp !== null) {
1597
- return resp;
1504
+ return group.handleFetch(req, event);
1505
+ }, Promise.resolve(null));
1506
+ // If the data caching group returned a response, go with it.
1507
+ if (data !== null) {
1508
+ return data;
1509
+ }
1510
+ // Next, check if this is a navigation request for a route. Detect circular
1511
+ // navigations by checking if the request URL is the same as the index URL.
1512
+ if (this.adapter.normalizeUrl(req.url) !== this.indexUrl && this.isNavigationRequest(req)) {
1513
+ if (this.manifest.navigationRequestStrategy === 'freshness') {
1514
+ // For navigation requests the freshness was configured. The request will always go trough
1515
+ // the network and fallback to default `handleFetch` behavior in case of failure.
1516
+ try {
1517
+ return await this.scope.fetch(req);
1598
1518
  }
1599
- return group.handleFetch(req, event);
1600
- }), Promise.resolve(null));
1601
- // If the data caching group returned a response, go with it.
1602
- if (data !== null) {
1603
- return data;
1604
- }
1605
- // Next, check if this is a navigation request for a route. Detect circular
1606
- // navigations by checking if the request URL is the same as the index URL.
1607
- if (this.adapter.normalizeUrl(req.url) !== this.indexUrl && this.isNavigationRequest(req)) {
1608
- if (this.manifest.navigationRequestStrategy === 'freshness') {
1609
- // For navigation requests the freshness was configured. The request will always go trough
1610
- // the network and fallback to default `handleFetch` behavior in case of failure.
1611
- try {
1612
- return yield this.scope.fetch(req);
1613
- }
1614
- catch (_a) {
1615
- // Navigation request failed - application is likely offline.
1616
- // Proceed forward to the default `handleFetch` behavior, where
1617
- // `indexUrl` will be requested and it should be available in the cache.
1618
- }
1519
+ catch {
1520
+ // Navigation request failed - application is likely offline.
1521
+ // Proceed forward to the default `handleFetch` behavior, where
1522
+ // `indexUrl` will be requested and it should be available in the cache.
1619
1523
  }
1620
- // This was a navigation request. Re-enter `handleFetch` with a request for
1621
- // the URL.
1622
- return this.handleFetch(this.adapter.newRequest(this.indexUrl), event);
1623
1524
  }
1624
- return null;
1625
- });
1525
+ // This was a navigation request. Re-enter `handleFetch` with a request for
1526
+ // the URL.
1527
+ return this.handleFetch(this.adapter.newRequest(this.indexUrl), event);
1528
+ }
1529
+ return null;
1626
1530
  }
1627
1531
  /**
1628
1532
  * Determine whether the request is a navigation request.
@@ -1644,21 +1548,19 @@
1644
1548
  /**
1645
1549
  * Check this version for a given resource with a particular hash.
1646
1550
  */
1647
- lookupResourceWithHash(url, hash) {
1648
- return __awaiter(this, void 0, void 0, function* () {
1649
- // Verify that this version has the requested resource cached. If not,
1650
- // there's no point in trying.
1651
- if (!this.hashTable.has(url)) {
1652
- return null;
1653
- }
1654
- // Next, check whether the resource has the correct hash. If not, any cached
1655
- // response isn't usable.
1656
- if (this.hashTable.get(url) !== hash) {
1657
- return null;
1658
- }
1659
- const cacheState = yield this.lookupResourceWithoutHash(url);
1660
- return cacheState && cacheState.response;
1661
- });
1551
+ async lookupResourceWithHash(url, hash) {
1552
+ // Verify that this version has the requested resource cached. If not,
1553
+ // there's no point in trying.
1554
+ if (!this.hashTable.has(url)) {
1555
+ return null;
1556
+ }
1557
+ // Next, check whether the resource has the correct hash. If not, any cached
1558
+ // response isn't usable.
1559
+ if (this.hashTable.get(url) !== hash) {
1560
+ return null;
1561
+ }
1562
+ const cacheState = await this.lookupResourceWithoutHash(url);
1563
+ return cacheState && cacheState.response;
1662
1564
  }
1663
1565
  /**
1664
1566
  * Check this version for a given resource regardless of its hash.
@@ -1666,48 +1568,44 @@
1666
1568
  lookupResourceWithoutHash(url) {
1667
1569
  // Limit the search to asset groups, and only scan the cache, don't
1668
1570
  // load resources from the network.
1669
- return this.assetGroups.reduce((potentialResponse, group) => __awaiter(this, void 0, void 0, function* () {
1670
- const resp = yield potentialResponse;
1571
+ return this.assetGroups.reduce(async (potentialResponse, group) => {
1572
+ const resp = await potentialResponse;
1671
1573
  if (resp !== null) {
1672
1574
  return resp;
1673
1575
  }
1674
1576
  // fetchFromCacheOnly() avoids any network fetches, and returns the
1675
1577
  // full set of cache data, not just the Response.
1676
1578
  return group.fetchFromCacheOnly(url);
1677
- }), Promise.resolve(null));
1579
+ }, Promise.resolve(null));
1678
1580
  }
1679
1581
  /**
1680
1582
  * List all unhashed resources from all asset groups.
1681
1583
  */
1682
1584
  previouslyCachedResources() {
1683
- return this.assetGroups.reduce((resources, group) => __awaiter(this, void 0, void 0, function* () { return (yield resources).concat(yield group.unhashedResources()); }), Promise.resolve([]));
1684
- }
1685
- recentCacheStatus(url) {
1686
- return __awaiter(this, void 0, void 0, function* () {
1687
- return this.assetGroups.reduce((current, group) => __awaiter(this, void 0, void 0, function* () {
1688
- const status = yield current;
1689
- if (status === UpdateCacheStatus.CACHED) {
1690
- return status;
1691
- }
1692
- const groupStatus = yield group.cacheStatus(url);
1693
- if (groupStatus === UpdateCacheStatus.NOT_CACHED) {
1694
- return status;
1695
- }
1696
- return groupStatus;
1697
- }), Promise.resolve(UpdateCacheStatus.NOT_CACHED));
1698
- });
1585
+ return this.assetGroups.reduce(async (resources, group) => (await resources).concat(await group.unhashedResources()), Promise.resolve([]));
1586
+ }
1587
+ async recentCacheStatus(url) {
1588
+ return this.assetGroups.reduce(async (current, group) => {
1589
+ const status = await current;
1590
+ if (status === UpdateCacheStatus.CACHED) {
1591
+ return status;
1592
+ }
1593
+ const groupStatus = await group.cacheStatus(url);
1594
+ if (groupStatus === UpdateCacheStatus.NOT_CACHED) {
1595
+ return status;
1596
+ }
1597
+ return groupStatus;
1598
+ }, Promise.resolve(UpdateCacheStatus.NOT_CACHED));
1699
1599
  }
1700
1600
  /**
1701
1601
  * Return a list of the names of all caches used by this version.
1702
1602
  */
1703
- getCacheNames() {
1704
- return __awaiter(this, void 0, void 0, function* () {
1705
- const allGroupCacheNames = yield Promise.all([
1706
- ...this.assetGroups.map(group => group.getCacheNames()),
1707
- ...this.dataGroups.map(group => group.getCacheNames()),
1708
- ]);
1709
- return [].concat(...allGroupCacheNames);
1710
- });
1603
+ async getCacheNames() {
1604
+ const allGroupCacheNames = await Promise.all([
1605
+ ...this.assetGroups.map(group => group.getCacheNames()),
1606
+ ...this.dataGroups.map(group => group.getCacheNames()),
1607
+ ]);
1608
+ return [].concat(...allGroupCacheNames);
1711
1609
  }
1712
1610
  /**
1713
1611
  * Get the opaque application data which was provided with the manifest.
@@ -1735,7 +1633,7 @@
1735
1633
  * Use of this source code is governed by an MIT-style license that can be
1736
1634
  * found in the LICENSE file at https://angular.io/license
1737
1635
  */
1738
- const SW_VERSION = '12.2.8';
1636
+ const SW_VERSION = '13.0.0-next.11';
1739
1637
  const DEBUG_LOG_BUFFER_SIZE = 100;
1740
1638
  class DebugHandler {
1741
1639
  constructor(driver, adapter) {
@@ -1749,25 +1647,24 @@
1749
1647
  this.debugLogA = [];
1750
1648
  this.debugLogB = [];
1751
1649
  }
1752
- handleFetch(req) {
1753
- return __awaiter(this, void 0, void 0, function* () {
1754
- const [state, versions, idle] = yield Promise.all([
1755
- this.driver.debugState(),
1756
- this.driver.debugVersions(),
1757
- this.driver.debugIdleState(),
1758
- ]);
1759
- const msgState = `NGSW Debug Info:
1650
+ async handleFetch(req) {
1651
+ const [state, versions, idle] = await Promise.all([
1652
+ this.driver.debugState(),
1653
+ this.driver.debugVersions(),
1654
+ this.driver.debugIdleState(),
1655
+ ]);
1656
+ const msgState = `NGSW Debug Info:
1760
1657
 
1761
1658
  Driver version: ${SW_VERSION}
1762
1659
  Driver state: ${state.state} (${state.why})
1763
1660
  Latest manifest hash: ${state.latestHash || 'none'}
1764
1661
  Last update check: ${this.since(state.lastUpdateCheck)}`;
1765
- const msgVersions = versions
1766
- .map(version => `=== Version ${version.hash} ===
1662
+ const msgVersions = versions
1663
+ .map(version => `=== Version ${version.hash} ===
1767
1664
 
1768
1665
  Clients: ${version.clients.join(', ')}`)
1769
- .join('\n\n');
1770
- const msgIdle = `=== Idle Task Queue ===
1666
+ .join('\n\n');
1667
+ const msgIdle = `=== Idle Task Queue ===
1771
1668
  Last update tick: ${this.since(idle.lastTrigger)}
1772
1669
  Last update run: ${this.since(idle.lastRun)}
1773
1670
  Task queue:
@@ -1777,12 +1674,11 @@ Debug log:
1777
1674
  ${this.formatDebugLog(this.debugLogB)}
1778
1675
  ${this.formatDebugLog(this.debugLogA)}
1779
1676
  `;
1780
- return this.adapter.newResponse(`${msgState}
1677
+ return this.adapter.newResponse(`${msgState}
1781
1678
 
1782
1679
  ${msgVersions}
1783
1680
 
1784
1681
  ${msgIdle}`, { headers: this.adapter.newHeaders({ 'Content-Type': 'text/plain' }) });
1785
- });
1786
1682
  }
1787
1683
  since(time) {
1788
1684
  if (time === null) {
@@ -1844,55 +1740,50 @@ ${msgIdle}`, { headers: this.adapter.newHeaders({ 'Content-Type': 'text/plain' }
1844
1740
  this.lastRun = null;
1845
1741
  this.oldestScheduledAt = null;
1846
1742
  }
1847
- trigger() {
1848
- var _a;
1849
- return __awaiter(this, void 0, void 0, function* () {
1850
- this.lastTrigger = this.adapter.time;
1851
- if (this.queue.length === 0) {
1852
- return;
1853
- }
1854
- if (this.scheduled !== null) {
1855
- this.scheduled.cancel = true;
1856
- }
1857
- const scheduled = {
1858
- cancel: false,
1859
- };
1860
- this.scheduled = scheduled;
1861
- // Ensure that no task remains pending for longer than `this.maxDelay` ms.
1862
- const now = this.adapter.time;
1863
- const maxDelay = Math.max(0, ((_a = this.oldestScheduledAt) !== null && _a !== void 0 ? _a : now) + this.maxDelay - now);
1864
- const delay = Math.min(maxDelay, this.delay);
1865
- yield this.adapter.timeout(delay);
1866
- if (scheduled.cancel) {
1867
- return;
1868
- }
1869
- this.scheduled = null;
1870
- yield this.execute();
1871
- });
1872
- }
1873
- execute() {
1874
- return __awaiter(this, void 0, void 0, function* () {
1875
- this.lastRun = this.adapter.time;
1876
- while (this.queue.length > 0) {
1877
- const queue = this.queue;
1878
- this.queue = [];
1879
- yield queue.reduce((previous, task) => __awaiter(this, void 0, void 0, function* () {
1880
- yield previous;
1881
- try {
1882
- yield task.run();
1883
- }
1884
- catch (err) {
1885
- this.debug.log(err, `while running idle task ${task.desc}`);
1886
- }
1887
- }), Promise.resolve());
1888
- }
1889
- if (this.emptyResolve !== null) {
1890
- this.emptyResolve();
1891
- this.emptyResolve = null;
1892
- }
1893
- this.empty = Promise.resolve();
1894
- this.oldestScheduledAt = null;
1895
- });
1743
+ async trigger() {
1744
+ this.lastTrigger = this.adapter.time;
1745
+ if (this.queue.length === 0) {
1746
+ return;
1747
+ }
1748
+ if (this.scheduled !== null) {
1749
+ this.scheduled.cancel = true;
1750
+ }
1751
+ const scheduled = {
1752
+ cancel: false,
1753
+ };
1754
+ this.scheduled = scheduled;
1755
+ // Ensure that no task remains pending for longer than `this.maxDelay` ms.
1756
+ const now = this.adapter.time;
1757
+ const maxDelay = Math.max(0, (this.oldestScheduledAt ?? now) + this.maxDelay - now);
1758
+ const delay = Math.min(maxDelay, this.delay);
1759
+ await this.adapter.timeout(delay);
1760
+ if (scheduled.cancel) {
1761
+ return;
1762
+ }
1763
+ this.scheduled = null;
1764
+ await this.execute();
1765
+ }
1766
+ async execute() {
1767
+ this.lastRun = this.adapter.time;
1768
+ while (this.queue.length > 0) {
1769
+ const queue = this.queue;
1770
+ this.queue = [];
1771
+ await queue.reduce(async (previous, task) => {
1772
+ await previous;
1773
+ try {
1774
+ await task.run();
1775
+ }
1776
+ catch (err) {
1777
+ this.debug.log(err, `while running idle task ${task.desc}`);
1778
+ }
1779
+ }, Promise.resolve());
1780
+ }
1781
+ if (this.emptyResolve !== null) {
1782
+ this.emptyResolve();
1783
+ this.emptyResolve = null;
1784
+ }
1785
+ this.empty = Promise.resolve();
1786
+ this.oldestScheduledAt = null;
1896
1787
  }
1897
1788
  schedule(desc, run) {
1898
1789
  this.queue.push({ desc, run });
@@ -2022,22 +1913,22 @@ ${msgIdle}`, { headers: this.adapter.newHeaders({ 'Content-Type': 'text/plain' }
2022
1913
  // The activate event is triggered when this version of the service worker is
2023
1914
  // first activated.
2024
1915
  this.scope.addEventListener('activate', (event) => {
2025
- event.waitUntil((() => __awaiter(this, void 0, void 0, function* () {
1916
+ event.waitUntil((async () => {
2026
1917
  // As above, it's safe to take over from existing clients immediately, since the new SW
2027
1918
  // version will continue to serve the old application.
2028
- yield this.scope.clients.claim();
1919
+ await this.scope.clients.claim();
2029
1920
  // Once all clients have been taken over, we can delete caches used by old versions of
2030
1921
  // `@angular/service-worker`, which are no longer needed. This can happen in the background.
2031
- this.idle.schedule('activate: cleanup-old-sw-caches', () => __awaiter(this, void 0, void 0, function* () {
1922
+ this.idle.schedule('activate: cleanup-old-sw-caches', async () => {
2032
1923
  try {
2033
- yield this.cleanupOldSwCaches();
1924
+ await this.cleanupOldSwCaches();
2034
1925
  }
2035
1926
  catch (err) {
2036
1927
  // Nothing to do - cleanup failed. Just log it.
2037
1928
  this.debugger.log(err, 'cleanupOldSwCaches @ activate: cleanup-old-sw-caches');
2038
1929
  }
2039
- }));
2040
- }))());
1930
+ });
1931
+ })());
2041
1932
  // Rather than wait for the first fetch event, which may not arrive until
2042
1933
  // the next time the application is loaded, the SW takes advantage of the
2043
1934
  // activation event to schedule initialization. However, if this were run
@@ -2133,7 +2024,7 @@ ${msgIdle}`, { headers: this.adapter.newHeaders({ 'Content-Type': 'text/plain' }
2133
2024
  if (!data || !data.action) {
2134
2025
  return;
2135
2026
  }
2136
- event.waitUntil((() => __awaiter(this, void 0, void 0, function* () {
2027
+ event.waitUntil((async () => {
2137
2028
  // Initialization is the only event which is sent directly from the SW to itself, and thus
2138
2029
  // `event.source` is not a `Client`. Handle it here, before the check for `Client` sources.
2139
2030
  if (data.action === 'INITIALIZE') {
@@ -2145,9 +2036,9 @@ ${msgIdle}`, { headers: this.adapter.newHeaders({ 'Content-Type': 'text/plain' }
2145
2036
  return;
2146
2037
  }
2147
2038
  // Handle the message and keep the SW alive until it's handled.
2148
- yield this.ensureInitialized(event);
2149
- yield this.handleMessage(data, event.source);
2150
- }))());
2039
+ await this.ensureInitialized(event);
2040
+ await this.handleMessage(data, event.source);
2041
+ })());
2151
2042
  }
2152
2043
  onPush(msg) {
2153
2044
  // Push notifications without data have no effect.
@@ -2161,323 +2052,308 @@ ${msgIdle}`, { headers: this.adapter.newHeaders({ 'Content-Type': 'text/plain' }
2161
2052
  // Handle the click event and keep the SW alive until it's handled.
2162
2053
  event.waitUntil(this.handleClick(event.notification, event.action));
2163
2054
  }
2164
- ensureInitialized(event) {
2165
- return __awaiter(this, void 0, void 0, function* () {
2166
- // Since the SW may have just been started, it may or may not have been initialized already.
2167
- // `this.initialized` will be `null` if initialization has not yet been attempted, or will be a
2168
- // `Promise` which will resolve (successfully or unsuccessfully) if it has.
2169
- if (this.initialized !== null) {
2170
- return this.initialized;
2171
- }
2172
- // Initialization has not yet been attempted, so attempt it. This should only ever happen once
2173
- // per SW instantiation.
2174
- try {
2175
- this.initialized = this.initialize();
2176
- yield this.initialized;
2177
- }
2178
- catch (error) {
2179
- // If initialization fails, the SW needs to enter a safe state, where it declines to respond
2180
- // to network requests.
2181
- this.state = DriverReadyState.SAFE_MODE;
2182
- this.stateMessage = `Initialization failed due to error: ${errorToString(error)}`;
2183
- throw error;
2184
- }
2185
- finally {
2186
- // Regardless if initialization succeeded, background tasks still need to happen.
2187
- event.waitUntil(this.idle.trigger());
2188
- }
2189
- });
2055
+ async ensureInitialized(event) {
2056
+ // Since the SW may have just been started, it may or may not have been initialized already.
2057
+ // `this.initialized` will be `null` if initialization has not yet been attempted, or will be a
2058
+ // `Promise` which will resolve (successfully or unsuccessfully) if it has.
2059
+ if (this.initialized !== null) {
2060
+ return this.initialized;
2061
+ }
2062
+ // Initialization has not yet been attempted, so attempt it. This should only ever happen once
2063
+ // per SW instantiation.
2064
+ try {
2065
+ this.initialized = this.initialize();
2066
+ await this.initialized;
2067
+ }
2068
+ catch (error) {
2069
+ // If initialization fails, the SW needs to enter a safe state, where it declines to respond
2070
+ // to network requests.
2071
+ this.state = DriverReadyState.SAFE_MODE;
2072
+ this.stateMessage = `Initialization failed due to error: ${errorToString(error)}`;
2073
+ throw error;
2074
+ }
2075
+ finally {
2076
+ // Regardless if initialization succeeded, background tasks still need to happen.
2077
+ event.waitUntil(this.idle.trigger());
2078
+ }
2190
2079
  }
2191
- handleMessage(msg, from) {
2192
- return __awaiter(this, void 0, void 0, function* () {
2193
- if (isMsgCheckForUpdates(msg)) {
2194
- const action = (() => __awaiter(this, void 0, void 0, function* () {
2195
- yield this.checkForUpdate();
2196
- }))();
2197
- yield this.reportStatus(from, action, msg.statusNonce);
2198
- }
2199
- else if (isMsgActivateUpdate(msg)) {
2200
- yield this.reportStatus(from, this.updateClient(from), msg.statusNonce);
2201
- }
2202
- });
2080
+ async handleMessage(msg, from) {
2081
+ if (isMsgCheckForUpdates(msg)) {
2082
+ const action = (async () => {
2083
+ await this.checkForUpdate();
2084
+ })();
2085
+ await this.reportStatus(from, action, msg.statusNonce);
2086
+ }
2087
+ else if (isMsgActivateUpdate(msg)) {
2088
+ await this.reportStatus(from, this.updateClient(from), msg.statusNonce);
2089
+ }
2203
2090
  }
2204
- handlePush(data) {
2205
- return __awaiter(this, void 0, void 0, function* () {
2206
- yield this.broadcast({
2207
- type: 'PUSH',
2208
- data,
2209
- });
2210
- if (!data.notification || !data.notification.title) {
2211
- return;
2212
- }
2213
- const desc = data.notification;
2214
- let options = {};
2215
- NOTIFICATION_OPTION_NAMES.filter(name => desc.hasOwnProperty(name))
2216
- .forEach(name => options[name] = desc[name]);
2217
- yield this.scope.registration.showNotification(desc['title'], options);
2091
+ async handlePush(data) {
2092
+ await this.broadcast({
2093
+ type: 'PUSH',
2094
+ data,
2218
2095
  });
2219
- }
2220
- handleClick(notification, action) {
2221
- var _a, _b, _c;
2222
- return __awaiter(this, void 0, void 0, function* () {
2223
- notification.close();
2224
- const options = {};
2225
- // The filter uses `name in notification` because the properties are on the prototype so
2226
- // hasOwnProperty does not work here
2227
- NOTIFICATION_OPTION_NAMES.filter(name => name in notification)
2228
- .forEach(name => options[name] = notification[name]);
2229
- const notificationAction = action === '' || action === undefined ? 'default' : action;
2230
- const onActionClick = (_b = (_a = notification === null || notification === void 0 ? void 0 : notification.data) === null || _a === void 0 ? void 0 : _a.onActionClick) === null || _b === void 0 ? void 0 : _b[notificationAction];
2231
- const urlToOpen = new URL((_c = onActionClick === null || onActionClick === void 0 ? void 0 : onActionClick.url) !== null && _c !== void 0 ? _c : '', this.scope.registration.scope).href;
2232
- switch (onActionClick === null || onActionClick === void 0 ? void 0 : onActionClick.operation) {
2233
- case 'openWindow':
2234
- yield this.scope.clients.openWindow(urlToOpen);
2235
- break;
2236
- case 'focusLastFocusedOrOpen': {
2237
- let matchingClient = yield this.getLastFocusedMatchingClient(this.scope);
2238
- if (matchingClient) {
2239
- yield (matchingClient === null || matchingClient === void 0 ? void 0 : matchingClient.focus());
2240
- }
2241
- else {
2242
- yield this.scope.clients.openWindow(urlToOpen);
2243
- }
2244
- break;
2096
+ if (!data.notification || !data.notification.title) {
2097
+ return;
2098
+ }
2099
+ const desc = data.notification;
2100
+ let options = {};
2101
+ NOTIFICATION_OPTION_NAMES.filter(name => desc.hasOwnProperty(name))
2102
+ .forEach(name => options[name] = desc[name]);
2103
+ await this.scope.registration.showNotification(desc['title'], options);
2104
+ }
2105
+ async handleClick(notification, action) {
2106
+ notification.close();
2107
+ const options = {};
2108
+ // The filter uses `name in notification` because the properties are on the prototype so
2109
+ // hasOwnProperty does not work here
2110
+ NOTIFICATION_OPTION_NAMES.filter(name => name in notification)
2111
+ .forEach(name => options[name] = notification[name]);
2112
+ const notificationAction = action === '' || action === undefined ? 'default' : action;
2113
+ const onActionClick = notification?.data?.onActionClick?.[notificationAction];
2114
+ const urlToOpen = new URL(onActionClick?.url ?? '', this.scope.registration.scope).href;
2115
+ switch (onActionClick?.operation) {
2116
+ case 'openWindow':
2117
+ await this.scope.clients.openWindow(urlToOpen);
2118
+ break;
2119
+ case 'focusLastFocusedOrOpen': {
2120
+ let matchingClient = await this.getLastFocusedMatchingClient(this.scope);
2121
+ if (matchingClient) {
2122
+ await matchingClient?.focus();
2245
2123
  }
2246
- case 'navigateLastFocusedOrOpen': {
2247
- let matchingClient = yield this.getLastFocusedMatchingClient(this.scope);
2248
- if (matchingClient) {
2249
- matchingClient = yield matchingClient.navigate(urlToOpen);
2250
- yield (matchingClient === null || matchingClient === void 0 ? void 0 : matchingClient.focus());
2251
- }
2252
- else {
2253
- yield this.scope.clients.openWindow(urlToOpen);
2254
- }
2255
- break;
2124
+ else {
2125
+ await this.scope.clients.openWindow(urlToOpen);
2256
2126
  }
2127
+ break;
2257
2128
  }
2258
- yield this.broadcast({
2259
- type: 'NOTIFICATION_CLICK',
2260
- data: { action, notification: options },
2261
- });
2129
+ case 'navigateLastFocusedOrOpen': {
2130
+ let matchingClient = await this.getLastFocusedMatchingClient(this.scope);
2131
+ if (matchingClient) {
2132
+ matchingClient = await matchingClient.navigate(urlToOpen);
2133
+ await matchingClient?.focus();
2134
+ }
2135
+ else {
2136
+ await this.scope.clients.openWindow(urlToOpen);
2137
+ }
2138
+ break;
2139
+ }
2140
+ }
2141
+ await this.broadcast({
2142
+ type: 'NOTIFICATION_CLICK',
2143
+ data: { action, notification: options },
2262
2144
  });
2263
2145
  }
2264
- getLastFocusedMatchingClient(scope) {
2265
- return __awaiter(this, void 0, void 0, function* () {
2266
- const windowClients = yield scope.clients.matchAll({ type: 'window' });
2267
- // As per the spec windowClients are `sorted in the most recently focused order`
2268
- return windowClients[0];
2269
- });
2146
+ async getLastFocusedMatchingClient(scope) {
2147
+ const windowClients = await scope.clients.matchAll({ type: 'window' });
2148
+ // As per the spec windowClients are `sorted in the most recently focused order`
2149
+ return windowClients[0];
2270
2150
  }
2271
- reportStatus(client, promise, nonce) {
2272
- return __awaiter(this, void 0, void 0, function* () {
2273
- const response = { type: 'STATUS', nonce, status: true };
2274
- try {
2275
- yield promise;
2276
- client.postMessage(response);
2277
- }
2278
- catch (e) {
2279
- client.postMessage(Object.assign(Object.assign({}, response), { status: false, error: e.toString() }));
2280
- }
2281
- });
2151
+ async reportStatus(client, promise, nonce) {
2152
+ const response = { type: 'STATUS', nonce, status: true };
2153
+ try {
2154
+ await promise;
2155
+ client.postMessage(response);
2156
+ }
2157
+ catch (e) {
2158
+ client.postMessage({
2159
+ ...response,
2160
+ status: false,
2161
+ error: e.toString(),
2162
+ });
2163
+ }
2282
2164
  }
2283
- updateClient(client) {
2284
- return __awaiter(this, void 0, void 0, function* () {
2285
- // Figure out which version the client is on. If it's not on the latest,
2286
- // it needs to be moved.
2287
- const existing = this.clientVersionMap.get(client.id);
2288
- if (existing === this.latestHash) {
2289
- // Nothing to do, this client is already on the latest version.
2290
- return;
2291
- }
2292
- // Switch the client over.
2293
- let previous = undefined;
2294
- // Look up the application data associated with the existing version. If there
2295
- // isn't any, fall back on using the hash.
2296
- if (existing !== undefined) {
2297
- const existingVersion = this.versions.get(existing);
2298
- previous = this.mergeHashWithAppData(existingVersion.manifest, existing);
2299
- }
2300
- // Set the current version used by the client, and sync the mapping to disk.
2301
- this.clientVersionMap.set(client.id, this.latestHash);
2302
- yield this.sync();
2303
- // Notify the client about this activation.
2304
- const current = this.versions.get(this.latestHash);
2305
- const notice = {
2306
- type: 'UPDATE_ACTIVATED',
2307
- previous,
2308
- current: this.mergeHashWithAppData(current.manifest, this.latestHash),
2309
- };
2310
- client.postMessage(notice);
2311
- });
2165
+ async updateClient(client) {
2166
+ // Figure out which version the client is on. If it's not on the latest,
2167
+ // it needs to be moved.
2168
+ const existing = this.clientVersionMap.get(client.id);
2169
+ if (existing === this.latestHash) {
2170
+ // Nothing to do, this client is already on the latest version.
2171
+ return;
2172
+ }
2173
+ // Switch the client over.
2174
+ let previous = undefined;
2175
+ // Look up the application data associated with the existing version. If there
2176
+ // isn't any, fall back on using the hash.
2177
+ if (existing !== undefined) {
2178
+ const existingVersion = this.versions.get(existing);
2179
+ previous = this.mergeHashWithAppData(existingVersion.manifest, existing);
2180
+ }
2181
+ // Set the current version used by the client, and sync the mapping to disk.
2182
+ this.clientVersionMap.set(client.id, this.latestHash);
2183
+ await this.sync();
2184
+ // Notify the client about this activation.
2185
+ const current = this.versions.get(this.latestHash);
2186
+ const notice = {
2187
+ type: 'UPDATE_ACTIVATED',
2188
+ previous,
2189
+ current: this.mergeHashWithAppData(current.manifest, this.latestHash),
2190
+ };
2191
+ client.postMessage(notice);
2312
2192
  }
2313
- handleFetch(event) {
2314
- return __awaiter(this, void 0, void 0, function* () {
2315
- try {
2316
- // Ensure the SW instance has been initialized.
2317
- yield this.ensureInitialized(event);
2318
- }
2319
- catch (_a) {
2320
- // Since the SW is already committed to responding to the currently active request,
2321
- // respond with a network fetch.
2322
- return this.safeFetch(event.request);
2323
- }
2324
- // On navigation requests, check for new updates.
2325
- if (event.request.mode === 'navigate' && !this.scheduledNavUpdateCheck) {
2326
- this.scheduledNavUpdateCheck = true;
2327
- this.idle.schedule('check-updates-on-navigation', () => __awaiter(this, void 0, void 0, function* () {
2328
- this.scheduledNavUpdateCheck = false;
2329
- yield this.checkForUpdate();
2330
- }));
2331
- }
2332
- // Decide which version of the app to use to serve this request. This is asynchronous as in
2333
- // some cases, a record will need to be written to disk about the assignment that is made.
2334
- const appVersion = yield this.assignVersion(event);
2335
- let res = null;
2336
- try {
2337
- if (appVersion !== null) {
2338
- try {
2339
- // Handle the request. First try the AppVersion. If that doesn't work, fall back on the
2340
- // network.
2341
- res = yield appVersion.handleFetch(event.request, event);
2193
+ async handleFetch(event) {
2194
+ try {
2195
+ // Ensure the SW instance has been initialized.
2196
+ await this.ensureInitialized(event);
2197
+ }
2198
+ catch {
2199
+ // Since the SW is already committed to responding to the currently active request,
2200
+ // respond with a network fetch.
2201
+ return this.safeFetch(event.request);
2202
+ }
2203
+ // On navigation requests, check for new updates.
2204
+ if (event.request.mode === 'navigate' && !this.scheduledNavUpdateCheck) {
2205
+ this.scheduledNavUpdateCheck = true;
2206
+ this.idle.schedule('check-updates-on-navigation', async () => {
2207
+ this.scheduledNavUpdateCheck = false;
2208
+ await this.checkForUpdate();
2209
+ });
2210
+ }
2211
+ // Decide which version of the app to use to serve this request. This is asynchronous as in
2212
+ // some cases, a record will need to be written to disk about the assignment that is made.
2213
+ const appVersion = await this.assignVersion(event);
2214
+ let res = null;
2215
+ try {
2216
+ if (appVersion !== null) {
2217
+ try {
2218
+ // Handle the request. First try the AppVersion. If that doesn't work, fall back on the
2219
+ // network.
2220
+ res = await appVersion.handleFetch(event.request, event);
2221
+ }
2222
+ catch (err) {
2223
+ if (err.isUnrecoverableState) {
2224
+ await this.notifyClientsAboutUnrecoverableState(appVersion, err.message);
2342
2225
  }
2343
- catch (err) {
2344
- if (err.isUnrecoverableState) {
2345
- yield this.notifyClientsAboutUnrecoverableState(appVersion, err.message);
2346
- }
2347
- if (err.isCritical) {
2348
- // Something went wrong with handling the request from this version.
2349
- this.debugger.log(err, `Driver.handleFetch(version: ${appVersion.manifestHash})`);
2350
- yield this.versionFailed(appVersion, err);
2351
- return this.safeFetch(event.request);
2352
- }
2353
- throw err;
2226
+ if (err.isCritical) {
2227
+ // Something went wrong with handling the request from this version.
2228
+ this.debugger.log(err, `Driver.handleFetch(version: ${appVersion.manifestHash})`);
2229
+ await this.versionFailed(appVersion, err);
2230
+ return this.safeFetch(event.request);
2354
2231
  }
2232
+ throw err;
2355
2233
  }
2356
- // The response will be `null` only if no `AppVersion` can be assigned to the request or if
2357
- // the assigned `AppVersion`'s manifest doesn't specify what to do about the request.
2358
- // In that case, just fall back on the network.
2359
- if (res === null) {
2360
- return this.safeFetch(event.request);
2361
- }
2362
- // The `AppVersion` returned a usable response, so return it.
2363
- return res;
2364
2234
  }
2365
- finally {
2366
- // Trigger the idle scheduling system. The Promise returned by `trigger()` will resolve after
2367
- // a specific amount of time has passed. If `trigger()` hasn't been called again by then (e.g.
2368
- // on a subsequent request), the idle task queue will be drained and the `Promise` won't
2369
- // be resolved until that operation is complete as well.
2370
- event.waitUntil(this.idle.trigger());
2235
+ // The response will be `null` only if no `AppVersion` can be assigned to the request or if
2236
+ // the assigned `AppVersion`'s manifest doesn't specify what to do about the request.
2237
+ // In that case, just fall back on the network.
2238
+ if (res === null) {
2239
+ return this.safeFetch(event.request);
2371
2240
  }
2372
- });
2241
+ // The `AppVersion` returned a usable response, so return it.
2242
+ return res;
2243
+ }
2244
+ finally {
2245
+ // Trigger the idle scheduling system. The Promise returned by `trigger()` will resolve after
2246
+ // a specific amount of time has passed. If `trigger()` hasn't been called again by then (e.g.
2247
+ // on a subsequent request), the idle task queue will be drained and the `Promise` won't
2248
+ // be resolved until that operation is complete as well.
2249
+ event.waitUntil(this.idle.trigger());
2250
+ }
2373
2251
  }
2374
2252
  /**
2375
2253
  * Attempt to quickly reach a state where it's safe to serve responses.
2376
2254
  */
2377
- initialize() {
2378
- return __awaiter(this, void 0, void 0, function* () {
2379
- // On initialization, all of the serialized state is read out of the 'control'
2380
- // table. This includes:
2381
- // - map of hashes to manifests of currently loaded application versions
2382
- // - map of client IDs to their pinned versions
2383
- // - record of the most recently fetched manifest hash
2384
- //
2385
- // If these values don't exist in the DB, then this is the either the first time
2386
- // the SW has run or the DB state has been wiped or is inconsistent. In that case,
2387
- // load a fresh copy of the manifest and reset the state from scratch.
2388
- const table = yield this.controlTable;
2389
- // Attempt to load the needed state from the DB. If this fails, the catch {} block
2390
- // will populate these variables with freshly constructed values.
2391
- let manifests, assignments, latest;
2392
- try {
2393
- // Read them from the DB simultaneously.
2394
- [manifests, assignments, latest] = yield Promise.all([
2395
- table.read('manifests'),
2396
- table.read('assignments'),
2397
- table.read('latest'),
2398
- ]);
2399
- // Make sure latest manifest is correctly installed. If not (e.g. corrupted data),
2400
- // it could stay locked in EXISTING_CLIENTS_ONLY or SAFE_MODE state.
2401
- if (!this.versions.has(latest.latest) && !manifests.hasOwnProperty(latest.latest)) {
2402
- this.debugger.log(`Missing manifest for latest version hash ${latest.latest}`, 'initialize: read from DB');
2403
- throw new Error(`Missing manifest for latest hash ${latest.latest}`);
2404
- }
2405
- // Successfully loaded from saved state. This implies a manifest exists, so
2406
- // the update check needs to happen in the background.
2407
- this.idle.schedule('init post-load (update)', () => __awaiter(this, void 0, void 0, function* () {
2408
- yield this.checkForUpdate();
2409
- }));
2410
- }
2411
- catch (_) {
2412
- // Something went wrong. Try to start over by fetching a new manifest from the
2413
- // server and building up an empty initial state.
2414
- const manifest = yield this.fetchLatestManifest();
2415
- const hash = hashManifest(manifest);
2416
- manifests = { [hash]: manifest };
2417
- assignments = {};
2418
- latest = { latest: hash };
2419
- // Save the initial state to the DB.
2420
- yield Promise.all([
2421
- table.write('manifests', manifests),
2422
- table.write('assignments', assignments),
2423
- table.write('latest', latest),
2424
- ]);
2425
- }
2426
- // At this point, either the state has been loaded successfully, or fresh state
2427
- // with a new copy of the manifest has been produced. At this point, the `Driver`
2428
- // can have its internals hydrated from the state.
2429
- // Schedule cleaning up obsolete caches in the background.
2430
- this.idle.schedule('init post-load (cleanup)', () => __awaiter(this, void 0, void 0, function* () {
2431
- yield this.cleanupCaches();
2432
- }));
2433
- // Initialize the `versions` map by setting each hash to a new `AppVersion` instance
2434
- // for that manifest.
2435
- Object.keys(manifests).forEach((hash) => {
2436
- const manifest = manifests[hash];
2437
- // If the manifest is newly initialized, an AppVersion may have already been
2438
- // created for it.
2439
- if (!this.versions.has(hash)) {
2440
- this.versions.set(hash, new AppVersion(this.scope, this.adapter, this.db, this.idle, this.debugger, manifest, hash));
2441
- }
2442
- });
2443
- // Map each client ID to its associated hash. Along the way, verify that the hash
2444
- // is still valid for that client ID. It should not be possible for a client to
2445
- // still be associated with a hash that was since removed from the state.
2446
- Object.keys(assignments).forEach((clientId) => {
2447
- const hash = assignments[clientId];
2448
- if (this.versions.has(hash)) {
2449
- this.clientVersionMap.set(clientId, hash);
2450
- }
2451
- else {
2452
- this.clientVersionMap.set(clientId, latest.latest);
2453
- this.debugger.log(`Unknown version ${hash} mapped for client ${clientId}, using latest instead`, `initialize: map assignments`);
2454
- }
2255
+ async initialize() {
2256
+ // On initialization, all of the serialized state is read out of the 'control'
2257
+ // table. This includes:
2258
+ // - map of hashes to manifests of currently loaded application versions
2259
+ // - map of client IDs to their pinned versions
2260
+ // - record of the most recently fetched manifest hash
2261
+ //
2262
+ // If these values don't exist in the DB, then this is the either the first time
2263
+ // the SW has run or the DB state has been wiped or is inconsistent. In that case,
2264
+ // load a fresh copy of the manifest and reset the state from scratch.
2265
+ const table = await this.controlTable;
2266
+ // Attempt to load the needed state from the DB. If this fails, the catch {} block
2267
+ // will populate these variables with freshly constructed values.
2268
+ let manifests, assignments, latest;
2269
+ try {
2270
+ // Read them from the DB simultaneously.
2271
+ [manifests, assignments, latest] = await Promise.all([
2272
+ table.read('manifests'),
2273
+ table.read('assignments'),
2274
+ table.read('latest'),
2275
+ ]);
2276
+ // Make sure latest manifest is correctly installed. If not (e.g. corrupted data),
2277
+ // it could stay locked in EXISTING_CLIENTS_ONLY or SAFE_MODE state.
2278
+ if (!this.versions.has(latest.latest) && !manifests.hasOwnProperty(latest.latest)) {
2279
+ this.debugger.log(`Missing manifest for latest version hash ${latest.latest}`, 'initialize: read from DB');
2280
+ throw new Error(`Missing manifest for latest hash ${latest.latest}`);
2281
+ }
2282
+ // Successfully loaded from saved state. This implies a manifest exists, so
2283
+ // the update check needs to happen in the background.
2284
+ this.idle.schedule('init post-load (update)', async () => {
2285
+ await this.checkForUpdate();
2455
2286
  });
2456
- // Set the latest version.
2457
- this.latestHash = latest.latest;
2458
- // Finally, assert that the latest version is in fact loaded.
2459
- if (!this.versions.has(latest.latest)) {
2460
- throw new Error(`Invariant violated (initialize): latest hash ${latest.latest} has no known manifest`);
2461
- }
2462
- // Finally, wait for the scheduling of initialization of all versions in the
2463
- // manifest. Ordinarily this just schedules the initializations to happen during
2464
- // the next idle period, but in development mode this might actually wait for the
2465
- // full initialization.
2466
- // If any of these initializations fail, versionFailed() will be called either
2467
- // synchronously or asynchronously to handle the failure and re-map clients.
2468
- yield Promise.all(Object.keys(manifests).map((hash) => __awaiter(this, void 0, void 0, function* () {
2469
- try {
2470
- // Attempt to schedule or initialize this version. If this operation is
2471
- // successful, then initialization either succeeded or was scheduled. If
2472
- // it fails, then full initialization was attempted and failed.
2473
- yield this.scheduleInitialization(this.versions.get(hash));
2474
- }
2475
- catch (err) {
2476
- this.debugger.log(err, `initialize: schedule init of ${hash}`);
2477
- return false;
2478
- }
2479
- })));
2287
+ }
2288
+ catch (_) {
2289
+ // Something went wrong. Try to start over by fetching a new manifest from the
2290
+ // server and building up an empty initial state.
2291
+ const manifest = await this.fetchLatestManifest();
2292
+ const hash = hashManifest(manifest);
2293
+ manifests = { [hash]: manifest };
2294
+ assignments = {};
2295
+ latest = { latest: hash };
2296
+ // Save the initial state to the DB.
2297
+ await Promise.all([
2298
+ table.write('manifests', manifests),
2299
+ table.write('assignments', assignments),
2300
+ table.write('latest', latest),
2301
+ ]);
2302
+ }
2303
+ // At this point, either the state has been loaded successfully, or fresh state
2304
+ // with a new copy of the manifest has been produced. At this point, the `Driver`
2305
+ // can have its internals hydrated from the state.
2306
+ // Schedule cleaning up obsolete caches in the background.
2307
+ this.idle.schedule('init post-load (cleanup)', async () => {
2308
+ await this.cleanupCaches();
2309
+ });
2310
+ // Initialize the `versions` map by setting each hash to a new `AppVersion` instance
2311
+ // for that manifest.
2312
+ Object.keys(manifests).forEach((hash) => {
2313
+ const manifest = manifests[hash];
2314
+ // If the manifest is newly initialized, an AppVersion may have already been
2315
+ // created for it.
2316
+ if (!this.versions.has(hash)) {
2317
+ this.versions.set(hash, new AppVersion(this.scope, this.adapter, this.db, this.idle, this.debugger, manifest, hash));
2318
+ }
2319
+ });
2320
+ // Map each client ID to its associated hash. Along the way, verify that the hash
2321
+ // is still valid for that client ID. It should not be possible for a client to
2322
+ // still be associated with a hash that was since removed from the state.
2323
+ Object.keys(assignments).forEach((clientId) => {
2324
+ const hash = assignments[clientId];
2325
+ if (this.versions.has(hash)) {
2326
+ this.clientVersionMap.set(clientId, hash);
2327
+ }
2328
+ else {
2329
+ this.clientVersionMap.set(clientId, latest.latest);
2330
+ this.debugger.log(`Unknown version ${hash} mapped for client ${clientId}, using latest instead`, `initialize: map assignments`);
2331
+ }
2480
2332
  });
2333
+ // Set the latest version.
2334
+ this.latestHash = latest.latest;
2335
+ // Finally, assert that the latest version is in fact loaded.
2336
+ if (!this.versions.has(latest.latest)) {
2337
+ throw new Error(`Invariant violated (initialize): latest hash ${latest.latest} has no known manifest`);
2338
+ }
2339
+ // Finally, wait for the scheduling of initialization of all versions in the
2340
+ // manifest. Ordinarily this just schedules the initializations to happen during
2341
+ // the next idle period, but in development mode this might actually wait for the
2342
+ // full initialization.
2343
+ // If any of these initializations fail, versionFailed() will be called either
2344
+ // synchronously or asynchronously to handle the failure and re-map clients.
2345
+ await Promise.all(Object.keys(manifests).map(async (hash) => {
2346
+ try {
2347
+ // Attempt to schedule or initialize this version. If this operation is
2348
+ // successful, then initialization either succeeded or was scheduled. If
2349
+ // it fails, then full initialization was attempted and failed.
2350
+ await this.scheduleInitialization(this.versions.get(hash));
2351
+ }
2352
+ catch (err) {
2353
+ this.debugger.log(err, `initialize: schedule init of ${hash}`);
2354
+ return false;
2355
+ }
2356
+ }));
2481
2357
  }
2482
2358
  lookupVersionByHash(hash, debugName = 'lookupVersionByHash') {
2483
2359
  // The version should exist, but check just in case.
@@ -2489,304 +2365,284 @@ ${msgIdle}`, { headers: this.adapter.newHeaders({ 'Content-Type': 'text/plain' }
2489
2365
  /**
2490
2366
  * Decide which version of the manifest to use for the event.
2491
2367
  */
2492
- assignVersion(event) {
2493
- return __awaiter(this, void 0, void 0, function* () {
2494
- // First, check whether the event has a (non empty) client ID. If it does, the version may
2495
- // already be associated.
2496
- //
2497
- // NOTE: For navigation requests, we care about the `resultingClientId`. If it is undefined or
2498
- // the empty string (which is the case for sub-resource requests), we look at `clientId`.
2499
- const clientId = event.resultingClientId || event.clientId;
2500
- if (clientId) {
2501
- // Check if there is an assigned client id.
2502
- if (this.clientVersionMap.has(clientId)) {
2503
- // There is an assignment for this client already.
2504
- const hash = this.clientVersionMap.get(clientId);
2505
- let appVersion = this.lookupVersionByHash(hash, 'assignVersion');
2506
- // Ordinarily, this client would be served from its assigned version. But, if this
2507
- // request is a navigation request, this client can be updated to the latest
2508
- // version immediately.
2509
- if (this.state === DriverReadyState.NORMAL && hash !== this.latestHash &&
2510
- appVersion.isNavigationRequest(event.request)) {
2511
- // Update this client to the latest version immediately.
2512
- if (this.latestHash === null) {
2513
- throw new Error(`Invariant violated (assignVersion): latestHash was null`);
2514
- }
2515
- const client = yield this.scope.clients.get(clientId);
2516
- if (client) {
2517
- yield this.updateClient(client);
2518
- }
2519
- appVersion = this.lookupVersionByHash(this.latestHash, 'assignVersion');
2520
- }
2521
- // TODO: make sure the version is valid.
2522
- return appVersion;
2523
- }
2524
- else {
2525
- // This is the first time this client ID has been seen. Whether the SW is in a
2526
- // state to handle new clients depends on the current readiness state, so check
2527
- // that first.
2528
- if (this.state !== DriverReadyState.NORMAL) {
2529
- // It's not safe to serve new clients in the current state. It's possible that
2530
- // this is an existing client which has not been mapped yet (see below) but
2531
- // even if that is the case, it's invalid to make an assignment to a known
2532
- // invalid version, even if that assignment was previously implicit. Return
2533
- // undefined here to let the caller know that no assignment is possible at
2534
- // this time.
2535
- return null;
2536
- }
2537
- // It's safe to handle this request. Two cases apply. Either:
2538
- // 1) the browser assigned a client ID at the time of the navigation request, and
2539
- // this is truly the first time seeing this client, or
2540
- // 2) a navigation request came previously from the same client, but with no client
2541
- // ID attached. Browsers do this to avoid creating a client under the origin in
2542
- // the event the navigation request is just redirected.
2543
- //
2544
- // In case 1, the latest version can safely be used.
2545
- // In case 2, the latest version can be used, with the assumption that the previous
2546
- // navigation request was answered under the same version. This assumption relies
2547
- // on the fact that it's unlikely an update will come in between the navigation
2548
- // request and requests for subsequent resources on that page.
2549
- // First validate the current state.
2368
+ async assignVersion(event) {
2369
+ // First, check whether the event has a (non empty) client ID. If it does, the version may
2370
+ // already be associated.
2371
+ //
2372
+ // NOTE: For navigation requests, we care about the `resultingClientId`. If it is undefined or
2373
+ // the empty string (which is the case for sub-resource requests), we look at `clientId`.
2374
+ const clientId = event.resultingClientId || event.clientId;
2375
+ if (clientId) {
2376
+ // Check if there is an assigned client id.
2377
+ if (this.clientVersionMap.has(clientId)) {
2378
+ // There is an assignment for this client already.
2379
+ const hash = this.clientVersionMap.get(clientId);
2380
+ let appVersion = this.lookupVersionByHash(hash, 'assignVersion');
2381
+ // Ordinarily, this client would be served from its assigned version. But, if this
2382
+ // request is a navigation request, this client can be updated to the latest
2383
+ // version immediately.
2384
+ if (this.state === DriverReadyState.NORMAL && hash !== this.latestHash &&
2385
+ appVersion.isNavigationRequest(event.request)) {
2386
+ // Update this client to the latest version immediately.
2550
2387
  if (this.latestHash === null) {
2551
2388
  throw new Error(`Invariant violated (assignVersion): latestHash was null`);
2552
2389
  }
2553
- // Pin this client ID to the current latest version, indefinitely.
2554
- this.clientVersionMap.set(clientId, this.latestHash);
2555
- yield this.sync();
2556
- // Return the latest `AppVersion`.
2557
- return this.lookupVersionByHash(this.latestHash, 'assignVersion');
2390
+ const client = await this.scope.clients.get(clientId);
2391
+ if (client) {
2392
+ await this.updateClient(client);
2393
+ }
2394
+ appVersion = this.lookupVersionByHash(this.latestHash, 'assignVersion');
2558
2395
  }
2396
+ // TODO: make sure the version is valid.
2397
+ return appVersion;
2559
2398
  }
2560
2399
  else {
2561
- // No client ID was associated with the request. This must be a navigation request
2562
- // for a new client. First check that the SW is accepting new clients.
2400
+ // This is the first time this client ID has been seen. Whether the SW is in a
2401
+ // state to handle new clients depends on the current readiness state, so check
2402
+ // that first.
2563
2403
  if (this.state !== DriverReadyState.NORMAL) {
2404
+ // It's not safe to serve new clients in the current state. It's possible that
2405
+ // this is an existing client which has not been mapped yet (see below) but
2406
+ // even if that is the case, it's invalid to make an assignment to a known
2407
+ // invalid version, even if that assignment was previously implicit. Return
2408
+ // undefined here to let the caller know that no assignment is possible at
2409
+ // this time.
2564
2410
  return null;
2565
2411
  }
2566
- // Serve it with the latest version, and assume that the client will actually get
2567
- // associated with that version on the next request.
2412
+ // It's safe to handle this request. Two cases apply. Either:
2413
+ // 1) the browser assigned a client ID at the time of the navigation request, and
2414
+ // this is truly the first time seeing this client, or
2415
+ // 2) a navigation request came previously from the same client, but with no client
2416
+ // ID attached. Browsers do this to avoid creating a client under the origin in
2417
+ // the event the navigation request is just redirected.
2418
+ //
2419
+ // In case 1, the latest version can safely be used.
2420
+ // In case 2, the latest version can be used, with the assumption that the previous
2421
+ // navigation request was answered under the same version. This assumption relies
2422
+ // on the fact that it's unlikely an update will come in between the navigation
2423
+ // request and requests for subsequent resources on that page.
2568
2424
  // First validate the current state.
2569
2425
  if (this.latestHash === null) {
2570
2426
  throw new Error(`Invariant violated (assignVersion): latestHash was null`);
2571
2427
  }
2428
+ // Pin this client ID to the current latest version, indefinitely.
2429
+ this.clientVersionMap.set(clientId, this.latestHash);
2430
+ await this.sync();
2572
2431
  // Return the latest `AppVersion`.
2573
2432
  return this.lookupVersionByHash(this.latestHash, 'assignVersion');
2574
2433
  }
2575
- });
2434
+ }
2435
+ else {
2436
+ // No client ID was associated with the request. This must be a navigation request
2437
+ // for a new client. First check that the SW is accepting new clients.
2438
+ if (this.state !== DriverReadyState.NORMAL) {
2439
+ return null;
2440
+ }
2441
+ // Serve it with the latest version, and assume that the client will actually get
2442
+ // associated with that version on the next request.
2443
+ // First validate the current state.
2444
+ if (this.latestHash === null) {
2445
+ throw new Error(`Invariant violated (assignVersion): latestHash was null`);
2446
+ }
2447
+ // Return the latest `AppVersion`.
2448
+ return this.lookupVersionByHash(this.latestHash, 'assignVersion');
2449
+ }
2576
2450
  }
2577
- fetchLatestManifest(ignoreOfflineError = false) {
2578
- return __awaiter(this, void 0, void 0, function* () {
2579
- const res = yield this.safeFetch(this.adapter.newRequest('ngsw.json?ngsw-cache-bust=' + Math.random()));
2580
- if (!res.ok) {
2581
- if (res.status === 404) {
2582
- yield this.deleteAllCaches();
2583
- yield this.scope.registration.unregister();
2584
- }
2585
- else if ((res.status === 503 || res.status === 504) && ignoreOfflineError) {
2586
- return null;
2587
- }
2588
- throw new Error(`Manifest fetch failed! (status: ${res.status})`);
2451
+ async fetchLatestManifest(ignoreOfflineError = false) {
2452
+ const res = await this.safeFetch(this.adapter.newRequest('ngsw.json?ngsw-cache-bust=' + Math.random()));
2453
+ if (!res.ok) {
2454
+ if (res.status === 404) {
2455
+ await this.deleteAllCaches();
2456
+ await this.scope.registration.unregister();
2589
2457
  }
2590
- this.lastUpdateCheck = this.adapter.time;
2591
- return res.json();
2592
- });
2458
+ else if ((res.status === 503 || res.status === 504) && ignoreOfflineError) {
2459
+ return null;
2460
+ }
2461
+ throw new Error(`Manifest fetch failed! (status: ${res.status})`);
2462
+ }
2463
+ this.lastUpdateCheck = this.adapter.time;
2464
+ return res.json();
2593
2465
  }
2594
- deleteAllCaches() {
2595
- return __awaiter(this, void 0, void 0, function* () {
2596
- const cacheNames = yield this.adapter.caches.keys();
2597
- yield Promise.all(cacheNames.map(name => this.adapter.caches.delete(name)));
2598
- });
2466
+ async deleteAllCaches() {
2467
+ const cacheNames = await this.adapter.caches.keys();
2468
+ await Promise.all(cacheNames.map(name => this.adapter.caches.delete(name)));
2599
2469
  }
2600
2470
  /**
2601
2471
  * Schedule the SW's attempt to reach a fully prefetched state for the given AppVersion
2602
2472
  * when the SW is not busy and has connectivity. This returns a Promise which must be
2603
2473
  * awaited, as under some conditions the AppVersion might be initialized immediately.
2604
2474
  */
2605
- scheduleInitialization(appVersion) {
2606
- return __awaiter(this, void 0, void 0, function* () {
2607
- const initialize = () => __awaiter(this, void 0, void 0, function* () {
2608
- try {
2609
- yield appVersion.initializeFully();
2610
- }
2611
- catch (err) {
2612
- this.debugger.log(err, `initializeFully for ${appVersion.manifestHash}`);
2613
- yield this.versionFailed(appVersion, err);
2614
- }
2615
- });
2616
- // TODO: better logic for detecting localhost.
2617
- if (this.scope.registration.scope.indexOf('://localhost') > -1) {
2618
- return initialize();
2475
+ async scheduleInitialization(appVersion) {
2476
+ const initialize = async () => {
2477
+ try {
2478
+ await appVersion.initializeFully();
2619
2479
  }
2620
- this.idle.schedule(`initialization(${appVersion.manifestHash})`, initialize);
2621
- });
2480
+ catch (err) {
2481
+ this.debugger.log(err, `initializeFully for ${appVersion.manifestHash}`);
2482
+ await this.versionFailed(appVersion, err);
2483
+ }
2484
+ };
2485
+ // TODO: better logic for detecting localhost.
2486
+ if (this.scope.registration.scope.indexOf('://localhost') > -1) {
2487
+ return initialize();
2488
+ }
2489
+ this.idle.schedule(`initialization(${appVersion.manifestHash})`, initialize);
2622
2490
  }
2623
- versionFailed(appVersion, err) {
2624
- return __awaiter(this, void 0, void 0, function* () {
2625
- // This particular AppVersion is broken. First, find the manifest hash.
2626
- const broken = Array.from(this.versions.entries()).find(([hash, version]) => version === appVersion);
2627
- if (broken === undefined) {
2628
- // This version is no longer in use anyway, so nobody cares.
2629
- return;
2491
+ async versionFailed(appVersion, err) {
2492
+ // This particular AppVersion is broken. First, find the manifest hash.
2493
+ const broken = Array.from(this.versions.entries()).find(([hash, version]) => version === appVersion);
2494
+ if (broken === undefined) {
2495
+ // This version is no longer in use anyway, so nobody cares.
2496
+ return;
2497
+ }
2498
+ const brokenHash = broken[0];
2499
+ // The specified version is broken and new clients should not be served from it. However, it is
2500
+ // deemed even riskier to switch the existing clients to a different version or to the network.
2501
+ // Therefore, we keep clients on their current version (even if broken) and ensure that no new
2502
+ // clients will be assigned to it.
2503
+ // TODO: notify affected apps.
2504
+ // The action taken depends on whether the broken manifest is the active (latest) or not.
2505
+ // - If the broken version is not the latest, no further action is necessary, since new clients
2506
+ // will be assigned to the latest version anyway.
2507
+ // - If the broken version is the latest, the SW cannot accept new clients (but can continue to
2508
+ // service old ones).
2509
+ if (this.latestHash === brokenHash) {
2510
+ // The latest manifest is broken. This means that new clients are at the mercy of the network,
2511
+ // but caches continue to be valid for previous versions. This is unfortunate but unavoidable.
2512
+ this.state = DriverReadyState.EXISTING_CLIENTS_ONLY;
2513
+ this.stateMessage = `Degraded due to: ${errorToString(err)}`;
2514
+ try {
2515
+ await this.sync();
2630
2516
  }
2631
- const brokenHash = broken[0];
2632
- // The specified version is broken and new clients should not be served from it. However, it is
2633
- // deemed even riskier to switch the existing clients to a different version or to the network.
2634
- // Therefore, we keep clients on their current version (even if broken) and ensure that no new
2635
- // clients will be assigned to it.
2636
- // TODO: notify affected apps.
2637
- // The action taken depends on whether the broken manifest is the active (latest) or not.
2638
- // - If the broken version is not the latest, no further action is necessary, since new clients
2639
- // will be assigned to the latest version anyway.
2640
- // - If the broken version is the latest, the SW cannot accept new clients (but can continue to
2641
- // service old ones).
2642
- if (this.latestHash === brokenHash) {
2643
- // The latest manifest is broken. This means that new clients are at the mercy of the network,
2644
- // but caches continue to be valid for previous versions. This is unfortunate but unavoidable.
2645
- this.state = DriverReadyState.EXISTING_CLIENTS_ONLY;
2646
- this.stateMessage = `Degraded due to: ${errorToString(err)}`;
2647
- try {
2648
- yield this.sync();
2649
- }
2650
- catch (err2) {
2651
- // We are already in a bad state. No need to make things worse.
2652
- // Just log the error and move on.
2653
- this.debugger.log(err2, `Driver.versionFailed(${err.message || err})`);
2654
- }
2517
+ catch (err2) {
2518
+ // We are already in a bad state. No need to make things worse.
2519
+ // Just log the error and move on.
2520
+ this.debugger.log(err2, `Driver.versionFailed(${err.message || err})`);
2655
2521
  }
2656
- });
2657
- }
2658
- setupUpdate(manifest, hash) {
2659
- return __awaiter(this, void 0, void 0, function* () {
2660
- const newVersion = new AppVersion(this.scope, this.adapter, this.db, this.idle, this.debugger, manifest, hash);
2661
- // Firstly, check if the manifest version is correct.
2662
- if (manifest.configVersion !== SUPPORTED_CONFIG_VERSION) {
2663
- yield this.deleteAllCaches();
2664
- yield this.scope.registration.unregister();
2665
- throw new Error(`Invalid config version: expected ${SUPPORTED_CONFIG_VERSION}, got ${manifest.configVersion}.`);
2666
- }
2667
- // Cause the new version to become fully initialized. If this fails, then the
2668
- // version will not be available for use.
2669
- yield newVersion.initializeFully(this);
2670
- // Install this as an active version of the app.
2671
- this.versions.set(hash, newVersion);
2672
- // Future new clients will use this hash as the latest version.
2673
- this.latestHash = hash;
2674
- // If we are in `EXISTING_CLIENTS_ONLY` mode (meaning we didn't have a clean copy of the last
2675
- // latest version), we can now recover to `NORMAL` mode and start accepting new clients.
2676
- if (this.state === DriverReadyState.EXISTING_CLIENTS_ONLY) {
2677
- this.state = DriverReadyState.NORMAL;
2678
- this.stateMessage = '(nominal)';
2679
- }
2680
- yield this.sync();
2681
- yield this.notifyClientsAboutUpdate(newVersion);
2682
- });
2522
+ }
2683
2523
  }
2684
- checkForUpdate() {
2685
- return __awaiter(this, void 0, void 0, function* () {
2686
- let hash = '(unknown)';
2687
- try {
2688
- const manifest = yield this.fetchLatestManifest(true);
2689
- if (manifest === null) {
2690
- // Client or server offline. Unable to check for updates at this time.
2691
- // Continue to service clients (existing and new).
2692
- this.debugger.log('Check for update aborted. (Client or server offline.)');
2693
- return false;
2694
- }
2695
- hash = hashManifest(manifest);
2696
- // Check whether this is really an update.
2697
- if (this.versions.has(hash)) {
2698
- return false;
2699
- }
2700
- yield this.setupUpdate(manifest, hash);
2701
- return true;
2524
+ async setupUpdate(manifest, hash) {
2525
+ const newVersion = new AppVersion(this.scope, this.adapter, this.db, this.idle, this.debugger, manifest, hash);
2526
+ // Firstly, check if the manifest version is correct.
2527
+ if (manifest.configVersion !== SUPPORTED_CONFIG_VERSION) {
2528
+ await this.deleteAllCaches();
2529
+ await this.scope.registration.unregister();
2530
+ throw new Error(`Invalid config version: expected ${SUPPORTED_CONFIG_VERSION}, got ${manifest.configVersion}.`);
2531
+ }
2532
+ // Cause the new version to become fully initialized. If this fails, then the
2533
+ // version will not be available for use.
2534
+ await newVersion.initializeFully(this);
2535
+ // Install this as an active version of the app.
2536
+ this.versions.set(hash, newVersion);
2537
+ // Future new clients will use this hash as the latest version.
2538
+ this.latestHash = hash;
2539
+ // If we are in `EXISTING_CLIENTS_ONLY` mode (meaning we didn't have a clean copy of the last
2540
+ // latest version), we can now recover to `NORMAL` mode and start accepting new clients.
2541
+ if (this.state === DriverReadyState.EXISTING_CLIENTS_ONLY) {
2542
+ this.state = DriverReadyState.NORMAL;
2543
+ this.stateMessage = '(nominal)';
2544
+ }
2545
+ await this.sync();
2546
+ await this.notifyClientsAboutUpdate(newVersion);
2547
+ }
2548
+ async checkForUpdate() {
2549
+ let hash = '(unknown)';
2550
+ try {
2551
+ const manifest = await this.fetchLatestManifest(true);
2552
+ if (manifest === null) {
2553
+ // Client or server offline. Unable to check for updates at this time.
2554
+ // Continue to service clients (existing and new).
2555
+ this.debugger.log('Check for update aborted. (Client or server offline.)');
2556
+ return false;
2702
2557
  }
2703
- catch (err) {
2704
- this.debugger.log(err, `Error occurred while updating to manifest ${hash}`);
2705
- this.state = DriverReadyState.EXISTING_CLIENTS_ONLY;
2706
- this.stateMessage = `Degraded due to failed initialization: ${errorToString(err)}`;
2558
+ hash = hashManifest(manifest);
2559
+ // Check whether this is really an update.
2560
+ if (this.versions.has(hash)) {
2707
2561
  return false;
2708
2562
  }
2709
- });
2563
+ await this.setupUpdate(manifest, hash);
2564
+ return true;
2565
+ }
2566
+ catch (err) {
2567
+ this.debugger.log(err, `Error occurred while updating to manifest ${hash}`);
2568
+ this.state = DriverReadyState.EXISTING_CLIENTS_ONLY;
2569
+ this.stateMessage = `Degraded due to failed initialization: ${errorToString(err)}`;
2570
+ return false;
2571
+ }
2710
2572
  }
2711
2573
  /**
2712
2574
  * Synchronize the existing state to the underlying database.
2713
2575
  */
2714
- sync() {
2715
- return __awaiter(this, void 0, void 0, function* () {
2716
- const table = yield this.controlTable;
2717
- // Construct a serializable map of hashes to manifests.
2718
- const manifests = {};
2719
- this.versions.forEach((version, hash) => {
2720
- manifests[hash] = version.manifest;
2721
- });
2722
- // Construct a serializable map of client ids to version hashes.
2723
- const assignments = {};
2724
- this.clientVersionMap.forEach((hash, clientId) => {
2725
- assignments[clientId] = hash;
2726
- });
2727
- // Record the latest entry. Since this is a sync which is necessarily happening after
2728
- // initialization, latestHash should always be valid.
2729
- const latest = {
2730
- latest: this.latestHash,
2731
- };
2732
- // Synchronize all of these.
2733
- yield Promise.all([
2734
- table.write('manifests', manifests),
2735
- table.write('assignments', assignments),
2736
- table.write('latest', latest),
2737
- ]);
2738
- });
2739
- }
2740
- cleanupCaches() {
2741
- return __awaiter(this, void 0, void 0, function* () {
2742
- try {
2743
- // Query for all currently active clients, and list the client IDs. This may skip some clients
2744
- // in the browser back-forward cache, but not much can be done about that.
2745
- const activeClients = new Set((yield this.scope.clients.matchAll()).map(client => client.id));
2746
- // A simple list of client IDs that the SW has kept track of. Subtracting `activeClients` from
2747
- // this list will result in the set of client IDs which are being tracked but are no longer
2748
- // used in the browser, and thus can be cleaned up.
2749
- const knownClients = Array.from(this.clientVersionMap.keys());
2750
- // Remove clients in the `clientVersionMap` that are no longer active.
2751
- const obsoleteClients = knownClients.filter(id => !activeClients.has(id));
2752
- obsoleteClients.forEach(id => this.clientVersionMap.delete(id));
2753
- // Next, determine the set of versions which are still used. All others can be removed.
2754
- const usedVersions = new Set(this.clientVersionMap.values());
2755
- // Collect all obsolete versions by filtering out used versions from the set of all versions.
2756
- const obsoleteVersions = Array.from(this.versions.keys())
2757
- .filter(version => !usedVersions.has(version) && version !== this.latestHash);
2758
- // Remove all the versions which are no longer used.
2759
- obsoleteVersions.forEach(version => this.versions.delete(version));
2760
- // Commit all the changes to the saved state.
2761
- yield this.sync();
2762
- // Delete all caches that are no longer needed.
2763
- const allCaches = yield this.adapter.caches.keys();
2764
- const usedCaches = new Set(yield this.getCacheNames());
2765
- const cachesToDelete = allCaches.filter(name => !usedCaches.has(name));
2766
- yield Promise.all(cachesToDelete.map(name => this.adapter.caches.delete(name)));
2767
- }
2768
- catch (err) {
2769
- // Oh well? Not much that can be done here. These caches will be removed on the next attempt
2770
- // or when the SW revs its format version, which happens from time to time.
2771
- this.debugger.log(err, 'cleanupCaches');
2772
- }
2773
- });
2576
+ async sync() {
2577
+ const table = await this.controlTable;
2578
+ // Construct a serializable map of hashes to manifests.
2579
+ const manifests = {};
2580
+ this.versions.forEach((version, hash) => {
2581
+ manifests[hash] = version.manifest;
2582
+ });
2583
+ // Construct a serializable map of client ids to version hashes.
2584
+ const assignments = {};
2585
+ this.clientVersionMap.forEach((hash, clientId) => {
2586
+ assignments[clientId] = hash;
2587
+ });
2588
+ // Record the latest entry. Since this is a sync which is necessarily happening after
2589
+ // initialization, latestHash should always be valid.
2590
+ const latest = {
2591
+ latest: this.latestHash,
2592
+ };
2593
+ // Synchronize all of these.
2594
+ await Promise.all([
2595
+ table.write('manifests', manifests),
2596
+ table.write('assignments', assignments),
2597
+ table.write('latest', latest),
2598
+ ]);
2599
+ }
2600
+ async cleanupCaches() {
2601
+ try {
2602
+ // Query for all currently active clients, and list the client IDs. This may skip some clients
2603
+ // in the browser back-forward cache, but not much can be done about that.
2604
+ const activeClients = new Set((await this.scope.clients.matchAll()).map(client => client.id));
2605
+ // A simple list of client IDs that the SW has kept track of. Subtracting `activeClients` from
2606
+ // this list will result in the set of client IDs which are being tracked but are no longer
2607
+ // used in the browser, and thus can be cleaned up.
2608
+ const knownClients = Array.from(this.clientVersionMap.keys());
2609
+ // Remove clients in the `clientVersionMap` that are no longer active.
2610
+ const obsoleteClients = knownClients.filter(id => !activeClients.has(id));
2611
+ obsoleteClients.forEach(id => this.clientVersionMap.delete(id));
2612
+ // Next, determine the set of versions which are still used. All others can be removed.
2613
+ const usedVersions = new Set(this.clientVersionMap.values());
2614
+ // Collect all obsolete versions by filtering out used versions from the set of all versions.
2615
+ const obsoleteVersions = Array.from(this.versions.keys())
2616
+ .filter(version => !usedVersions.has(version) && version !== this.latestHash);
2617
+ // Remove all the versions which are no longer used.
2618
+ obsoleteVersions.forEach(version => this.versions.delete(version));
2619
+ // Commit all the changes to the saved state.
2620
+ await this.sync();
2621
+ // Delete all caches that are no longer needed.
2622
+ const allCaches = await this.adapter.caches.keys();
2623
+ const usedCaches = new Set(await this.getCacheNames());
2624
+ const cachesToDelete = allCaches.filter(name => !usedCaches.has(name));
2625
+ await Promise.all(cachesToDelete.map(name => this.adapter.caches.delete(name)));
2626
+ }
2627
+ catch (err) {
2628
+ // Oh well? Not much that can be done here. These caches will be removed on the next attempt
2629
+ // or when the SW revs its format version, which happens from time to time.
2630
+ this.debugger.log(err, 'cleanupCaches');
2631
+ }
2774
2632
  }
2775
2633
  /**
2776
2634
  * Delete caches that were used by older versions of `@angular/service-worker` to avoid running
2777
2635
  * into storage quota limitations imposed by browsers.
2778
2636
  * (Since at this point the SW has claimed all clients, it is safe to remove those caches.)
2779
2637
  */
2780
- cleanupOldSwCaches() {
2781
- return __awaiter(this, void 0, void 0, function* () {
2782
- // This is an exceptional case, where we need to interact with caches that would not be
2783
- // generated by this ServiceWorker (but by old versions of it). Use the native `CacheStorage`
2784
- // directly.
2785
- const caches = this.adapter.caches.original;
2786
- const cacheNames = yield caches.keys();
2787
- const oldSwCacheNames = cacheNames.filter(name => /^ngsw:(?!\/)/.test(name));
2788
- yield Promise.all(oldSwCacheNames.map(name => caches.delete(name)));
2789
- });
2638
+ async cleanupOldSwCaches() {
2639
+ // This is an exceptional case, where we need to interact with caches that would not be
2640
+ // generated by this ServiceWorker (but by old versions of it). Use the native `CacheStorage`
2641
+ // directly.
2642
+ const caches = this.adapter.caches.original;
2643
+ const cacheNames = await caches.keys();
2644
+ const oldSwCacheNames = cacheNames.filter(name => /^ngsw:(?!\/)/.test(name));
2645
+ await Promise.all(oldSwCacheNames.map(name => caches.delete(name)));
2790
2646
  }
2791
2647
  /**
2792
2648
  * Determine if a specific version of the given resource is cached anywhere within the SW,
@@ -2802,35 +2658,29 @@ ${msgIdle}`, { headers: this.adapter.newHeaders({ 'Content-Type': 'text/plain' }
2802
2658
  // reduction, if a response has already been identified, then pass it through, as no
2803
2659
  // future operation could change the response. If no response has been found yet, keep
2804
2660
  // checking versions until one is or until all versions have been exhausted.
2805
- .reduce((prev, version) => __awaiter(this, void 0, void 0, function* () {
2661
+ .reduce(async (prev, version) => {
2806
2662
  // First, check the previous result. If a non-null result has been found already, just
2807
2663
  // return it.
2808
- if ((yield prev) !== null) {
2664
+ if (await prev !== null) {
2809
2665
  return prev;
2810
2666
  }
2811
2667
  // No result has been found yet. Try the next `AppVersion`.
2812
2668
  return version.lookupResourceWithHash(url, hash);
2813
- }), Promise.resolve(null));
2669
+ }, Promise.resolve(null));
2814
2670
  }
2815
- lookupResourceWithoutHash(url) {
2816
- return __awaiter(this, void 0, void 0, function* () {
2817
- yield this.initialized;
2818
- const version = this.versions.get(this.latestHash);
2819
- return version ? version.lookupResourceWithoutHash(url) : null;
2820
- });
2671
+ async lookupResourceWithoutHash(url) {
2672
+ await this.initialized;
2673
+ const version = this.versions.get(this.latestHash);
2674
+ return version ? version.lookupResourceWithoutHash(url) : null;
2821
2675
  }
2822
- previouslyCachedResources() {
2823
- return __awaiter(this, void 0, void 0, function* () {
2824
- yield this.initialized;
2825
- const version = this.versions.get(this.latestHash);
2826
- return version ? version.previouslyCachedResources() : [];
2827
- });
2676
+ async previouslyCachedResources() {
2677
+ await this.initialized;
2678
+ const version = this.versions.get(this.latestHash);
2679
+ return version ? version.previouslyCachedResources() : [];
2828
2680
  }
2829
- recentCacheStatus(url) {
2830
- return __awaiter(this, void 0, void 0, function* () {
2831
- const version = this.versions.get(this.latestHash);
2832
- return version ? version.recentCacheStatus(url) : UpdateCacheStatus.NOT_CACHED;
2833
- });
2681
+ async recentCacheStatus(url) {
2682
+ const version = this.versions.get(this.latestHash);
2683
+ return version ? version.recentCacheStatus(url) : UpdateCacheStatus.NOT_CACHED;
2834
2684
  }
2835
2685
  mergeHashWithAppData(manifest, hash) {
2836
2686
  return {
@@ -2838,116 +2688,100 @@ ${msgIdle}`, { headers: this.adapter.newHeaders({ 'Content-Type': 'text/plain' }
2838
2688
  appData: manifest.appData,
2839
2689
  };
2840
2690
  }
2841
- notifyClientsAboutUnrecoverableState(appVersion, reason) {
2842
- return __awaiter(this, void 0, void 0, function* () {
2843
- const broken = Array.from(this.versions.entries()).find(([hash, version]) => version === appVersion);
2844
- if (broken === undefined) {
2845
- // This version is no longer in use anyway, so nobody cares.
2691
+ async notifyClientsAboutUnrecoverableState(appVersion, reason) {
2692
+ const broken = Array.from(this.versions.entries()).find(([hash, version]) => version === appVersion);
2693
+ if (broken === undefined) {
2694
+ // This version is no longer in use anyway, so nobody cares.
2695
+ return;
2696
+ }
2697
+ const brokenHash = broken[0];
2698
+ const affectedClients = Array.from(this.clientVersionMap.entries())
2699
+ .filter(([clientId, hash]) => hash === brokenHash)
2700
+ .map(([clientId]) => clientId);
2701
+ await Promise.all(affectedClients.map(async (clientId) => {
2702
+ const client = await this.scope.clients.get(clientId);
2703
+ if (client) {
2704
+ client.postMessage({ type: 'UNRECOVERABLE_STATE', reason });
2705
+ }
2706
+ }));
2707
+ }
2708
+ async notifyClientsAboutUpdate(next) {
2709
+ await this.initialized;
2710
+ const clients = await this.scope.clients.matchAll();
2711
+ await Promise.all(clients.map(async (client) => {
2712
+ // Firstly, determine which version this client is on.
2713
+ const version = this.clientVersionMap.get(client.id);
2714
+ if (version === undefined) {
2715
+ // Unmapped client - assume it's the latest.
2846
2716
  return;
2847
2717
  }
2848
- const brokenHash = broken[0];
2849
- const affectedClients = Array.from(this.clientVersionMap.entries())
2850
- .filter(([clientId, hash]) => hash === brokenHash)
2851
- .map(([clientId]) => clientId);
2852
- yield Promise.all(affectedClients.map((clientId) => __awaiter(this, void 0, void 0, function* () {
2853
- const client = yield this.scope.clients.get(clientId);
2854
- if (client) {
2855
- client.postMessage({ type: 'UNRECOVERABLE_STATE', reason });
2856
- }
2857
- })));
2858
- });
2718
+ if (version === this.latestHash) {
2719
+ // Client is already on the latest version, no need for a notification.
2720
+ return;
2721
+ }
2722
+ const current = this.versions.get(version);
2723
+ // Send a notice.
2724
+ const notice = {
2725
+ type: 'UPDATE_AVAILABLE',
2726
+ current: this.mergeHashWithAppData(current.manifest, version),
2727
+ available: this.mergeHashWithAppData(next.manifest, this.latestHash),
2728
+ };
2729
+ client.postMessage(notice);
2730
+ }));
2859
2731
  }
2860
- notifyClientsAboutUpdate(next) {
2861
- return __awaiter(this, void 0, void 0, function* () {
2862
- yield this.initialized;
2863
- const clients = yield this.scope.clients.matchAll();
2864
- yield Promise.all(clients.map((client) => __awaiter(this, void 0, void 0, function* () {
2865
- // Firstly, determine which version this client is on.
2866
- const version = this.clientVersionMap.get(client.id);
2867
- if (version === undefined) {
2868
- // Unmapped client - assume it's the latest.
2869
- return;
2870
- }
2871
- if (version === this.latestHash) {
2872
- // Client is already on the latest version, no need for a notification.
2873
- return;
2874
- }
2875
- const current = this.versions.get(version);
2876
- // Send a notice.
2877
- const notice = {
2878
- type: 'UPDATE_AVAILABLE',
2879
- current: this.mergeHashWithAppData(current.manifest, version),
2880
- available: this.mergeHashWithAppData(next.manifest, this.latestHash),
2881
- };
2882
- client.postMessage(notice);
2883
- })));
2732
+ async broadcast(msg) {
2733
+ const clients = await this.scope.clients.matchAll();
2734
+ clients.forEach(client => {
2735
+ client.postMessage(msg);
2884
2736
  });
2885
2737
  }
2886
- broadcast(msg) {
2887
- return __awaiter(this, void 0, void 0, function* () {
2888
- const clients = yield this.scope.clients.matchAll();
2889
- clients.forEach(client => {
2890
- client.postMessage(msg);
2891
- });
2892
- });
2738
+ async debugState() {
2739
+ return {
2740
+ state: DriverReadyState[this.state],
2741
+ why: this.stateMessage,
2742
+ latestHash: this.latestHash,
2743
+ lastUpdateCheck: this.lastUpdateCheck,
2744
+ };
2893
2745
  }
2894
- debugState() {
2895
- return __awaiter(this, void 0, void 0, function* () {
2746
+ async debugVersions() {
2747
+ // Build list of versions.
2748
+ return Array.from(this.versions.keys()).map(hash => {
2749
+ const version = this.versions.get(hash);
2750
+ const clients = Array.from(this.clientVersionMap.entries())
2751
+ .filter(([clientId, version]) => version === hash)
2752
+ .map(([clientId, version]) => clientId);
2896
2753
  return {
2897
- state: DriverReadyState[this.state],
2898
- why: this.stateMessage,
2899
- latestHash: this.latestHash,
2900
- lastUpdateCheck: this.lastUpdateCheck,
2754
+ hash,
2755
+ manifest: version.manifest,
2756
+ clients,
2757
+ status: '',
2901
2758
  };
2902
2759
  });
2903
2760
  }
2904
- debugVersions() {
2905
- return __awaiter(this, void 0, void 0, function* () {
2906
- // Build list of versions.
2907
- return Array.from(this.versions.keys()).map(hash => {
2908
- const version = this.versions.get(hash);
2909
- const clients = Array.from(this.clientVersionMap.entries())
2910
- .filter(([clientId, version]) => version === hash)
2911
- .map(([clientId, version]) => clientId);
2912
- return {
2913
- hash,
2914
- manifest: version.manifest,
2915
- clients,
2916
- status: '',
2917
- };
2918
- });
2919
- });
2920
- }
2921
- debugIdleState() {
2922
- return __awaiter(this, void 0, void 0, function* () {
2923
- return {
2924
- queue: this.idle.taskDescriptions,
2925
- lastTrigger: this.idle.lastTrigger,
2926
- lastRun: this.idle.lastRun,
2927
- };
2928
- });
2761
+ async debugIdleState() {
2762
+ return {
2763
+ queue: this.idle.taskDescriptions,
2764
+ lastTrigger: this.idle.lastTrigger,
2765
+ lastRun: this.idle.lastRun,
2766
+ };
2929
2767
  }
2930
- safeFetch(req) {
2931
- return __awaiter(this, void 0, void 0, function* () {
2932
- try {
2933
- return yield this.scope.fetch(req);
2934
- }
2935
- catch (err) {
2936
- this.debugger.log(err, `Driver.fetch(${req.url})`);
2937
- return this.adapter.newResponse(null, {
2938
- status: 504,
2939
- statusText: 'Gateway Timeout',
2940
- });
2941
- }
2942
- });
2768
+ async safeFetch(req) {
2769
+ try {
2770
+ return await this.scope.fetch(req);
2771
+ }
2772
+ catch (err) {
2773
+ this.debugger.log(err, `Driver.fetch(${req.url})`);
2774
+ return this.adapter.newResponse(null, {
2775
+ status: 504,
2776
+ statusText: 'Gateway Timeout',
2777
+ });
2778
+ }
2943
2779
  }
2944
- getCacheNames() {
2945
- return __awaiter(this, void 0, void 0, function* () {
2946
- const controlTable = yield this.controlTable;
2947
- const appVersions = Array.from(this.versions.values());
2948
- const appVersionCacheNames = yield Promise.all(appVersions.map(version => version.getCacheNames()));
2949
- return [controlTable.cacheName].concat(...appVersionCacheNames);
2950
- });
2780
+ async getCacheNames() {
2781
+ const controlTable = await this.controlTable;
2782
+ const appVersions = Array.from(this.versions.values());
2783
+ const appVersionCacheNames = await Promise.all(appVersions.map(version => version.getCacheNames()));
2784
+ return [controlTable.cacheName].concat(...appVersionCacheNames);
2951
2785
  }
2952
2786
  }
2953
2787