@angular/service-worker 13.2.0-next.1 → 13.2.0-next.2
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.
- package/config/config.d.ts +2 -2
- package/esm2020/src/module.mjs +4 -4
- package/esm2020/src/push.mjs +3 -3
- package/esm2020/src/update.mjs +3 -3
- package/fesm2015/config.mjs +2 -2
- package/fesm2015/service-worker.mjs +12 -12
- package/fesm2020/config.mjs +2 -2
- package/fesm2020/service-worker.mjs +12 -12
- package/ngsw-config.js +19 -31
- package/ngsw-worker.js +1771 -2763
- package/package.json +3 -3
- package/service-worker.d.ts +2 -2
package/ngsw-worker.js
CHANGED
|
@@ -1,2825 +1,1833 @@
|
|
|
1
|
-
(
|
|
2
|
-
|
|
1
|
+
(() => {
|
|
2
|
+
var __defProp = Object.defineProperty;
|
|
3
|
+
var __defProps = Object.defineProperties;
|
|
4
|
+
var __getOwnPropDescs = Object.getOwnPropertyDescriptors;
|
|
5
|
+
var __getOwnPropSymbols = Object.getOwnPropertySymbols;
|
|
6
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
7
|
+
var __propIsEnum = Object.prototype.propertyIsEnumerable;
|
|
8
|
+
var __defNormalProp = (obj, key, value) => key in obj ? __defProp(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value;
|
|
9
|
+
var __spreadValues = (a, b) => {
|
|
10
|
+
for (var prop in b || (b = {}))
|
|
11
|
+
if (__hasOwnProp.call(b, prop))
|
|
12
|
+
__defNormalProp(a, prop, b[prop]);
|
|
13
|
+
if (__getOwnPropSymbols)
|
|
14
|
+
for (var prop of __getOwnPropSymbols(b)) {
|
|
15
|
+
if (__propIsEnum.call(b, prop))
|
|
16
|
+
__defNormalProp(a, prop, b[prop]);
|
|
17
|
+
}
|
|
18
|
+
return a;
|
|
19
|
+
};
|
|
20
|
+
var __spreadProps = (a, b) => __defProps(a, __getOwnPropDescs(b));
|
|
3
21
|
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
* A wrapper around `CacheStorage` to allow interacting with caches more easily and consistently by:
|
|
13
|
-
* - Adding a `name` property to all opened caches, which can be used to easily perform other
|
|
14
|
-
* operations that require the cache name.
|
|
15
|
-
* - Name-spacing cache names to avoid conflicts with other caches on the same domain.
|
|
16
|
-
*/
|
|
17
|
-
class NamedCacheStorage {
|
|
18
|
-
constructor(original, cacheNamePrefix) {
|
|
19
|
-
this.original = original;
|
|
20
|
-
this.cacheNamePrefix = cacheNamePrefix;
|
|
21
|
-
}
|
|
22
|
-
delete(cacheName) {
|
|
23
|
-
return this.original.delete(`${this.cacheNamePrefix}:${cacheName}`);
|
|
24
|
-
}
|
|
25
|
-
has(cacheName) {
|
|
26
|
-
return this.original.has(`${this.cacheNamePrefix}:${cacheName}`);
|
|
27
|
-
}
|
|
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));
|
|
33
|
-
}
|
|
34
|
-
match(request, options) {
|
|
35
|
-
return this.original.match(request, options);
|
|
36
|
-
}
|
|
37
|
-
async open(cacheName) {
|
|
38
|
-
const cache = await this.original.open(`${this.cacheNamePrefix}:${cacheName}`);
|
|
39
|
-
return Object.assign(cache, { name: cacheName });
|
|
40
|
-
}
|
|
22
|
+
// bazel-out/k8-fastbuild-ST-2e5f3376adb5/bin/packages/service-worker/worker/src/named-cache-storage.mjs
|
|
23
|
+
var NamedCacheStorage = class {
|
|
24
|
+
constructor(original, cacheNamePrefix) {
|
|
25
|
+
this.original = original;
|
|
26
|
+
this.cacheNamePrefix = cacheNamePrefix;
|
|
27
|
+
}
|
|
28
|
+
delete(cacheName) {
|
|
29
|
+
return this.original.delete(`${this.cacheNamePrefix}:${cacheName}`);
|
|
41
30
|
}
|
|
31
|
+
has(cacheName) {
|
|
32
|
+
return this.original.has(`${this.cacheNamePrefix}:${cacheName}`);
|
|
33
|
+
}
|
|
34
|
+
async keys() {
|
|
35
|
+
const prefix = `${this.cacheNamePrefix}:`;
|
|
36
|
+
const allCacheNames = await this.original.keys();
|
|
37
|
+
const ownCacheNames = allCacheNames.filter((name) => name.startsWith(prefix));
|
|
38
|
+
return ownCacheNames.map((name) => name.slice(prefix.length));
|
|
39
|
+
}
|
|
40
|
+
match(request, options) {
|
|
41
|
+
return this.original.match(request, options);
|
|
42
|
+
}
|
|
43
|
+
async open(cacheName) {
|
|
44
|
+
const cache = await this.original.open(`${this.cacheNamePrefix}:${cacheName}`);
|
|
45
|
+
return Object.assign(cache, { name: cacheName });
|
|
46
|
+
}
|
|
47
|
+
};
|
|
42
48
|
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
this.caches = new NamedCacheStorage(caches, `ngsw:${parsedScopeUrl.path}`);
|
|
66
|
-
}
|
|
67
|
-
/**
|
|
68
|
-
* Wrapper around the `Request` constructor.
|
|
69
|
-
*/
|
|
70
|
-
newRequest(input, init) {
|
|
71
|
-
return new Request(input, init);
|
|
72
|
-
}
|
|
73
|
-
/**
|
|
74
|
-
* Wrapper around the `Response` constructor.
|
|
75
|
-
*/
|
|
76
|
-
newResponse(body, init) {
|
|
77
|
-
return new Response(body, init);
|
|
78
|
-
}
|
|
79
|
-
/**
|
|
80
|
-
* Wrapper around the `Headers` constructor.
|
|
81
|
-
*/
|
|
82
|
-
newHeaders(headers) {
|
|
83
|
-
return new Headers(headers);
|
|
84
|
-
}
|
|
85
|
-
/**
|
|
86
|
-
* Test if a given object is an instance of `Client`.
|
|
87
|
-
*/
|
|
88
|
-
isClient(source) {
|
|
89
|
-
return (source instanceof Client);
|
|
90
|
-
}
|
|
91
|
-
/**
|
|
92
|
-
* Read the current UNIX time in milliseconds.
|
|
93
|
-
*/
|
|
94
|
-
get time() {
|
|
95
|
-
return Date.now();
|
|
96
|
-
}
|
|
97
|
-
/**
|
|
98
|
-
* Get a normalized representation of a URL such as those found in the ServiceWorker's `ngsw.json`
|
|
99
|
-
* configuration.
|
|
100
|
-
*
|
|
101
|
-
* More specifically:
|
|
102
|
-
* 1. Resolve the URL relative to the ServiceWorker's scope.
|
|
103
|
-
* 2. If the URL is relative to the ServiceWorker's own origin, then only return the path part.
|
|
104
|
-
* Otherwise, return the full URL.
|
|
105
|
-
*
|
|
106
|
-
* @param url The raw request URL.
|
|
107
|
-
* @return A normalized representation of the URL.
|
|
108
|
-
*/
|
|
109
|
-
normalizeUrl(url) {
|
|
110
|
-
// Check the URL's origin against the ServiceWorker's.
|
|
111
|
-
const parsed = this.parseUrl(url, this.scopeUrl);
|
|
112
|
-
return (parsed.origin === this.origin ? parsed.path : url);
|
|
113
|
-
}
|
|
114
|
-
/**
|
|
115
|
-
* Parse a URL into its different parts, such as `origin`, `path` and `search`.
|
|
116
|
-
*/
|
|
117
|
-
parseUrl(url, relativeTo) {
|
|
118
|
-
// Workaround a Safari bug, see
|
|
119
|
-
// https://github.com/angular/angular/issues/31061#issuecomment-503637978
|
|
120
|
-
const parsed = !relativeTo ? new URL(url) : new URL(url, relativeTo);
|
|
121
|
-
return { origin: parsed.origin, path: parsed.pathname, search: parsed.search };
|
|
122
|
-
}
|
|
123
|
-
/**
|
|
124
|
-
* Wait for a given amount of time before completing a Promise.
|
|
125
|
-
*/
|
|
126
|
-
timeout(ms) {
|
|
127
|
-
return new Promise(resolve => {
|
|
128
|
-
setTimeout(() => resolve(), ms);
|
|
129
|
-
});
|
|
130
|
-
}
|
|
49
|
+
// bazel-out/k8-fastbuild-ST-2e5f3376adb5/bin/packages/service-worker/worker/src/adapter.mjs
|
|
50
|
+
var Adapter = class {
|
|
51
|
+
constructor(scopeUrl, caches) {
|
|
52
|
+
this.scopeUrl = scopeUrl;
|
|
53
|
+
const parsedScopeUrl = this.parseUrl(this.scopeUrl);
|
|
54
|
+
this.origin = parsedScopeUrl.origin;
|
|
55
|
+
this.caches = new NamedCacheStorage(caches, `ngsw:${parsedScopeUrl.path}`);
|
|
56
|
+
}
|
|
57
|
+
newRequest(input, init) {
|
|
58
|
+
return new Request(input, init);
|
|
59
|
+
}
|
|
60
|
+
newResponse(body, init) {
|
|
61
|
+
return new Response(body, init);
|
|
62
|
+
}
|
|
63
|
+
newHeaders(headers) {
|
|
64
|
+
return new Headers(headers);
|
|
65
|
+
}
|
|
66
|
+
isClient(source) {
|
|
67
|
+
return source instanceof Client;
|
|
68
|
+
}
|
|
69
|
+
get time() {
|
|
70
|
+
return Date.now();
|
|
131
71
|
}
|
|
72
|
+
normalizeUrl(url) {
|
|
73
|
+
const parsed = this.parseUrl(url, this.scopeUrl);
|
|
74
|
+
return parsed.origin === this.origin ? parsed.path : url;
|
|
75
|
+
}
|
|
76
|
+
parseUrl(url, relativeTo) {
|
|
77
|
+
const parsed = !relativeTo ? new URL(url) : new URL(url, relativeTo);
|
|
78
|
+
return { origin: parsed.origin, path: parsed.pathname, search: parsed.search };
|
|
79
|
+
}
|
|
80
|
+
timeout(ms) {
|
|
81
|
+
return new Promise((resolve) => {
|
|
82
|
+
setTimeout(() => resolve(), ms);
|
|
83
|
+
});
|
|
84
|
+
}
|
|
85
|
+
};
|
|
132
86
|
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
* found in the LICENSE file at https://angular.io/license
|
|
139
|
-
*/
|
|
140
|
-
/**
|
|
141
|
-
* An error returned in rejected promises if the given key is not found in the table.
|
|
142
|
-
*/
|
|
143
|
-
class NotFound {
|
|
144
|
-
constructor(table, key) {
|
|
145
|
-
this.table = table;
|
|
146
|
-
this.key = key;
|
|
147
|
-
}
|
|
87
|
+
// bazel-out/k8-fastbuild-ST-2e5f3376adb5/bin/packages/service-worker/worker/src/database.mjs
|
|
88
|
+
var NotFound = class {
|
|
89
|
+
constructor(table, key) {
|
|
90
|
+
this.table = table;
|
|
91
|
+
this.key = key;
|
|
148
92
|
}
|
|
93
|
+
};
|
|
149
94
|
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
*/
|
|
157
|
-
/**
|
|
158
|
-
* An implementation of a `Database` that uses the `CacheStorage` API to serialize
|
|
159
|
-
* state within mock `Response` objects.
|
|
160
|
-
*/
|
|
161
|
-
class CacheDatabase {
|
|
162
|
-
constructor(adapter) {
|
|
163
|
-
this.adapter = adapter;
|
|
164
|
-
this.cacheNamePrefix = 'db';
|
|
165
|
-
this.tables = new Map();
|
|
166
|
-
}
|
|
167
|
-
'delete'(name) {
|
|
168
|
-
if (this.tables.has(name)) {
|
|
169
|
-
this.tables.delete(name);
|
|
170
|
-
}
|
|
171
|
-
return this.adapter.caches.delete(`${this.cacheNamePrefix}:${name}`);
|
|
172
|
-
}
|
|
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);
|
|
188
|
-
}
|
|
95
|
+
// bazel-out/k8-fastbuild-ST-2e5f3376adb5/bin/packages/service-worker/worker/src/db-cache.mjs
|
|
96
|
+
var CacheDatabase = class {
|
|
97
|
+
constructor(adapter2) {
|
|
98
|
+
this.adapter = adapter2;
|
|
99
|
+
this.cacheNamePrefix = "db";
|
|
100
|
+
this.tables = /* @__PURE__ */ new Map();
|
|
189
101
|
}
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
102
|
+
"delete"(name) {
|
|
103
|
+
if (this.tables.has(name)) {
|
|
104
|
+
this.tables.delete(name);
|
|
105
|
+
}
|
|
106
|
+
return this.adapter.caches.delete(`${this.cacheNamePrefix}:${name}`);
|
|
107
|
+
}
|
|
108
|
+
async list() {
|
|
109
|
+
const prefix = `${this.cacheNamePrefix}:`;
|
|
110
|
+
const allCacheNames = await this.adapter.caches.keys();
|
|
111
|
+
const dbCacheNames = allCacheNames.filter((name) => name.startsWith(prefix));
|
|
112
|
+
return dbCacheNames.map((name) => name.slice(prefix.length));
|
|
113
|
+
}
|
|
114
|
+
async open(name, cacheQueryOptions) {
|
|
115
|
+
if (!this.tables.has(name)) {
|
|
116
|
+
const cache = await this.adapter.caches.open(`${this.cacheNamePrefix}:${name}`);
|
|
117
|
+
const table = new CacheTable(name, cache, this.adapter, cacheQueryOptions);
|
|
118
|
+
this.tables.set(name, table);
|
|
119
|
+
}
|
|
120
|
+
return this.tables.get(name);
|
|
121
|
+
}
|
|
122
|
+
};
|
|
123
|
+
var CacheTable = class {
|
|
124
|
+
constructor(name, cache, adapter2, cacheQueryOptions) {
|
|
125
|
+
this.name = name;
|
|
126
|
+
this.cache = cache;
|
|
127
|
+
this.adapter = adapter2;
|
|
128
|
+
this.cacheQueryOptions = cacheQueryOptions;
|
|
129
|
+
this.cacheName = this.cache.name;
|
|
130
|
+
}
|
|
131
|
+
request(key) {
|
|
132
|
+
return this.adapter.newRequest("/" + key);
|
|
133
|
+
}
|
|
134
|
+
"delete"(key) {
|
|
135
|
+
return this.cache.delete(this.request(key), this.cacheQueryOptions);
|
|
136
|
+
}
|
|
137
|
+
keys() {
|
|
138
|
+
return this.cache.keys().then((requests) => requests.map((req) => req.url.substr(1)));
|
|
139
|
+
}
|
|
140
|
+
read(key) {
|
|
141
|
+
return this.cache.match(this.request(key), this.cacheQueryOptions).then((res) => {
|
|
142
|
+
if (res === void 0) {
|
|
143
|
+
return Promise.reject(new NotFound(this.name, key));
|
|
220
144
|
}
|
|
145
|
+
return res.json();
|
|
146
|
+
});
|
|
221
147
|
}
|
|
148
|
+
write(key, value) {
|
|
149
|
+
return this.cache.put(this.request(key), this.adapter.newResponse(JSON.stringify(value)));
|
|
150
|
+
}
|
|
151
|
+
};
|
|
222
152
|
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
var UpdateCacheStatus = /*@__PURE__*/ (function (UpdateCacheStatus) {
|
|
231
|
-
UpdateCacheStatus[UpdateCacheStatus["NOT_CACHED"] = 0] = "NOT_CACHED";
|
|
232
|
-
UpdateCacheStatus[UpdateCacheStatus["CACHED_BUT_UNUSED"] = 1] = "CACHED_BUT_UNUSED";
|
|
233
|
-
UpdateCacheStatus[UpdateCacheStatus["CACHED"] = 2] = "CACHED";
|
|
234
|
-
return UpdateCacheStatus;
|
|
235
|
-
})({});
|
|
153
|
+
// bazel-out/k8-fastbuild-ST-2e5f3376adb5/bin/packages/service-worker/worker/src/api.mjs
|
|
154
|
+
var UpdateCacheStatus;
|
|
155
|
+
(function(UpdateCacheStatus2) {
|
|
156
|
+
UpdateCacheStatus2[UpdateCacheStatus2["NOT_CACHED"] = 0] = "NOT_CACHED";
|
|
157
|
+
UpdateCacheStatus2[UpdateCacheStatus2["CACHED_BUT_UNUSED"] = 1] = "CACHED_BUT_UNUSED";
|
|
158
|
+
UpdateCacheStatus2[UpdateCacheStatus2["CACHED"] = 2] = "CACHED";
|
|
159
|
+
})(UpdateCacheStatus || (UpdateCacheStatus = {}));
|
|
236
160
|
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
* found in the LICENSE file at https://angular.io/license
|
|
243
|
-
*/
|
|
244
|
-
class SwCriticalError extends Error {
|
|
245
|
-
constructor() {
|
|
246
|
-
super(...arguments);
|
|
247
|
-
this.isCritical = true;
|
|
248
|
-
}
|
|
161
|
+
// bazel-out/k8-fastbuild-ST-2e5f3376adb5/bin/packages/service-worker/worker/src/error.mjs
|
|
162
|
+
var SwCriticalError = class extends Error {
|
|
163
|
+
constructor() {
|
|
164
|
+
super(...arguments);
|
|
165
|
+
this.isCritical = true;
|
|
249
166
|
}
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
167
|
+
};
|
|
168
|
+
function errorToString(error) {
|
|
169
|
+
if (error instanceof Error) {
|
|
170
|
+
return `${error.message}
|
|
171
|
+
${error.stack}`;
|
|
172
|
+
} else {
|
|
173
|
+
return `${error}`;
|
|
257
174
|
}
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
175
|
+
}
|
|
176
|
+
var SwUnrecoverableStateError = class extends SwCriticalError {
|
|
177
|
+
constructor() {
|
|
178
|
+
super(...arguments);
|
|
179
|
+
this.isUnrecoverableState = true;
|
|
263
180
|
}
|
|
181
|
+
};
|
|
264
182
|
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
const
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
let [a, b, c, d, e] = [0x67452301, 0xefcdab89, 0x98badcfe, 0x10325476, 0xc3d2e1f0];
|
|
294
|
-
words32[len >> 5] |= 0x80 << (24 - len % 32);
|
|
295
|
-
words32[((len + 64 >> 9) << 4) + 15] = len;
|
|
296
|
-
for (let i = 0; i < words32.length; i += 16) {
|
|
297
|
-
const [h0, h1, h2, h3, h4] = [a, b, c, d, e];
|
|
298
|
-
for (let j = 0; j < 80; j++) {
|
|
299
|
-
if (j < 16) {
|
|
300
|
-
w[j] = words32[i + j];
|
|
301
|
-
}
|
|
302
|
-
else {
|
|
303
|
-
w[j] = rol32(w[j - 3] ^ w[j - 8] ^ w[j - 14] ^ w[j - 16], 1);
|
|
304
|
-
}
|
|
305
|
-
const [f, k] = fk(j, b, c, d);
|
|
306
|
-
const temp = [rol32(a, 5), f, e, k, w[j]].reduce(add32);
|
|
307
|
-
[e, d, c, b, a] = [d, c, rol32(b, 30), a, temp];
|
|
308
|
-
}
|
|
309
|
-
[a, b, c, d, e] = [add32(a, h0), add32(b, h1), add32(c, h2), add32(d, h3), add32(e, h4)];
|
|
310
|
-
}
|
|
311
|
-
return byteStringToHexString(words32ToByteString([a, b, c, d, e]));
|
|
312
|
-
}
|
|
313
|
-
function add32(a, b) {
|
|
314
|
-
return add32to64(a, b)[1];
|
|
315
|
-
}
|
|
316
|
-
function add32to64(a, b) {
|
|
317
|
-
const low = (a & 0xffff) + (b & 0xffff);
|
|
318
|
-
const high = (a >>> 16) + (b >>> 16) + (low >>> 16);
|
|
319
|
-
return [high >>> 16, (high << 16) | (low & 0xffff)];
|
|
320
|
-
}
|
|
321
|
-
// Rotate a 32b number left `count` position
|
|
322
|
-
function rol32(a, count) {
|
|
323
|
-
return (a << count) | (a >>> (32 - count));
|
|
324
|
-
}
|
|
325
|
-
var Endian = /*@__PURE__*/ (function (Endian) {
|
|
326
|
-
Endian[Endian["Little"] = 0] = "Little";
|
|
327
|
-
Endian[Endian["Big"] = 1] = "Big";
|
|
328
|
-
return Endian;
|
|
329
|
-
})({});
|
|
330
|
-
function fk(index, b, c, d) {
|
|
331
|
-
if (index < 20) {
|
|
332
|
-
return [(b & c) | (~b & d), 0x5a827999];
|
|
333
|
-
}
|
|
334
|
-
if (index < 40) {
|
|
335
|
-
return [b ^ c ^ d, 0x6ed9eba1];
|
|
336
|
-
}
|
|
337
|
-
if (index < 60) {
|
|
338
|
-
return [(b & c) | (b & d) | (c & d), 0x8f1bbcdc];
|
|
339
|
-
}
|
|
340
|
-
return [b ^ c ^ d, 0xca62c1d6];
|
|
183
|
+
// bazel-out/k8-fastbuild-ST-2e5f3376adb5/bin/packages/service-worker/worker/src/sha1.mjs
|
|
184
|
+
function sha1(str) {
|
|
185
|
+
const utf8 = str;
|
|
186
|
+
const words32 = stringToWords32(utf8, Endian.Big);
|
|
187
|
+
return _sha1(words32, utf8.length * 8);
|
|
188
|
+
}
|
|
189
|
+
function sha1Binary(buffer) {
|
|
190
|
+
const words32 = arrayBufferToWords32(buffer, Endian.Big);
|
|
191
|
+
return _sha1(words32, buffer.byteLength * 8);
|
|
192
|
+
}
|
|
193
|
+
function _sha1(words32, len) {
|
|
194
|
+
const w = [];
|
|
195
|
+
let [a, b, c, d, e] = [1732584193, 4023233417, 2562383102, 271733878, 3285377520];
|
|
196
|
+
words32[len >> 5] |= 128 << 24 - len % 32;
|
|
197
|
+
words32[(len + 64 >> 9 << 4) + 15] = len;
|
|
198
|
+
for (let i = 0; i < words32.length; i += 16) {
|
|
199
|
+
const [h0, h1, h2, h3, h4] = [a, b, c, d, e];
|
|
200
|
+
for (let j = 0; j < 80; j++) {
|
|
201
|
+
if (j < 16) {
|
|
202
|
+
w[j] = words32[i + j];
|
|
203
|
+
} else {
|
|
204
|
+
w[j] = rol32(w[j - 3] ^ w[j - 8] ^ w[j - 14] ^ w[j - 16], 1);
|
|
205
|
+
}
|
|
206
|
+
const [f, k] = fk(j, b, c, d);
|
|
207
|
+
const temp = [rol32(a, 5), f, e, k, w[j]].reduce(add32);
|
|
208
|
+
[e, d, c, b, a] = [d, c, rol32(b, 30), a, temp];
|
|
209
|
+
}
|
|
210
|
+
[a, b, c, d, e] = [add32(a, h0), add32(b, h1), add32(c, h2), add32(d, h3), add32(e, h4)];
|
|
341
211
|
}
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
212
|
+
return byteStringToHexString(words32ToByteString([a, b, c, d, e]));
|
|
213
|
+
}
|
|
214
|
+
function add32(a, b) {
|
|
215
|
+
return add32to64(a, b)[1];
|
|
216
|
+
}
|
|
217
|
+
function add32to64(a, b) {
|
|
218
|
+
const low = (a & 65535) + (b & 65535);
|
|
219
|
+
const high = (a >>> 16) + (b >>> 16) + (low >>> 16);
|
|
220
|
+
return [high >>> 16, high << 16 | low & 65535];
|
|
221
|
+
}
|
|
222
|
+
function rol32(a, count) {
|
|
223
|
+
return a << count | a >>> 32 - count;
|
|
224
|
+
}
|
|
225
|
+
var Endian;
|
|
226
|
+
(function(Endian2) {
|
|
227
|
+
Endian2[Endian2["Little"] = 0] = "Little";
|
|
228
|
+
Endian2[Endian2["Big"] = 1] = "Big";
|
|
229
|
+
})(Endian || (Endian = {}));
|
|
230
|
+
function fk(index, b, c, d) {
|
|
231
|
+
if (index < 20) {
|
|
232
|
+
return [b & c | ~b & d, 1518500249];
|
|
358
233
|
}
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
return index >= str.length ? 0 : str.charCodeAt(index) & 0xff;
|
|
362
|
-
}
|
|
363
|
-
else {
|
|
364
|
-
return index >= str.byteLength ? 0 : str[index] & 0xff;
|
|
365
|
-
}
|
|
234
|
+
if (index < 40) {
|
|
235
|
+
return [b ^ c ^ d, 1859775393];
|
|
366
236
|
}
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
if (endian === Endian.Big) {
|
|
370
|
-
for (let i = 0; i < 4; i++) {
|
|
371
|
-
word += byteAt(str, index + i) << (24 - 8 * i);
|
|
372
|
-
}
|
|
373
|
-
}
|
|
374
|
-
else {
|
|
375
|
-
for (let i = 0; i < 4; i++) {
|
|
376
|
-
word += byteAt(str, index + i) << 8 * i;
|
|
377
|
-
}
|
|
378
|
-
}
|
|
379
|
-
return word;
|
|
237
|
+
if (index < 60) {
|
|
238
|
+
return [b & c | b & d | c & d, 2400959708];
|
|
380
239
|
}
|
|
381
|
-
|
|
382
|
-
|
|
240
|
+
return [b ^ c ^ d, 3395469782];
|
|
241
|
+
}
|
|
242
|
+
function stringToWords32(str, endian) {
|
|
243
|
+
const size = str.length + 3 >>> 2;
|
|
244
|
+
const words32 = [];
|
|
245
|
+
for (let i = 0; i < size; i++) {
|
|
246
|
+
words32[i] = wordAt(str, i * 4, endian);
|
|
383
247
|
}
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
248
|
+
return words32;
|
|
249
|
+
}
|
|
250
|
+
function arrayBufferToWords32(buffer, endian) {
|
|
251
|
+
const size = buffer.byteLength + 3 >>> 2;
|
|
252
|
+
const words32 = [];
|
|
253
|
+
const view = new Uint8Array(buffer);
|
|
254
|
+
for (let i = 0; i < size; i++) {
|
|
255
|
+
words32[i] = wordAt(view, i * 4, endian);
|
|
390
256
|
}
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
257
|
+
return words32;
|
|
258
|
+
}
|
|
259
|
+
function byteAt(str, index) {
|
|
260
|
+
if (typeof str === "string") {
|
|
261
|
+
return index >= str.length ? 0 : str.charCodeAt(index) & 255;
|
|
262
|
+
} else {
|
|
263
|
+
return index >= str.byteLength ? 0 : str[index] & 255;
|
|
264
|
+
}
|
|
265
|
+
}
|
|
266
|
+
function wordAt(str, index, endian) {
|
|
267
|
+
let word = 0;
|
|
268
|
+
if (endian === Endian.Big) {
|
|
269
|
+
for (let i = 0; i < 4; i++) {
|
|
270
|
+
word += byteAt(str, index + i) << 24 - 8 * i;
|
|
271
|
+
}
|
|
272
|
+
} else {
|
|
273
|
+
for (let i = 0; i < 4; i++) {
|
|
274
|
+
word += byteAt(str, index + i) << 8 * i;
|
|
275
|
+
}
|
|
276
|
+
}
|
|
277
|
+
return word;
|
|
278
|
+
}
|
|
279
|
+
function words32ToByteString(words32) {
|
|
280
|
+
return words32.reduce((str, word) => str + word32ToByteString(word), "");
|
|
281
|
+
}
|
|
282
|
+
function word32ToByteString(word) {
|
|
283
|
+
let str = "";
|
|
284
|
+
for (let i = 0; i < 4; i++) {
|
|
285
|
+
str += String.fromCharCode(word >>> 8 * (3 - i) & 255);
|
|
286
|
+
}
|
|
287
|
+
return str;
|
|
288
|
+
}
|
|
289
|
+
function byteStringToHexString(str) {
|
|
290
|
+
let hex = "";
|
|
291
|
+
for (let i = 0; i < str.length; i++) {
|
|
292
|
+
const b = byteAt(str, i);
|
|
293
|
+
hex += (b >>> 4).toString(16) + (b & 15).toString(16);
|
|
398
294
|
}
|
|
295
|
+
return hex.toLowerCase();
|
|
296
|
+
}
|
|
399
297
|
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
if (
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
}
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
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
|
-
});
|
|
506
|
-
}
|
|
507
|
-
// In either case (revalidation or not), the cached response must be good.
|
|
508
|
-
return cachedResponse;
|
|
509
|
-
}
|
|
510
|
-
}
|
|
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
|
-
}
|
|
522
|
-
}
|
|
523
|
-
/**
|
|
524
|
-
* Some resources are cached without a hash, meaning that their expiration is controlled
|
|
525
|
-
* by HTTP caching headers. Check whether the given request/response pair is still valid
|
|
526
|
-
* per the caching headers.
|
|
527
|
-
*/
|
|
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;
|
|
551
|
-
}
|
|
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;
|
|
559
|
-
try {
|
|
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;
|
|
563
|
-
}
|
|
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);
|
|
573
|
-
}
|
|
574
|
-
const age = this.adapter.time - ts;
|
|
575
|
-
return age < 0 || age > maxAge;
|
|
576
|
-
}
|
|
577
|
-
catch {
|
|
578
|
-
// Assume stale.
|
|
579
|
-
return true;
|
|
580
|
-
}
|
|
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
|
-
}
|
|
599
|
-
}
|
|
600
|
-
/**
|
|
601
|
-
* Fetch the complete state of a cached resource, or return null if it's not found.
|
|
602
|
-
*/
|
|
603
|
-
async fetchFromCacheOnly(url) {
|
|
604
|
-
const cache = await this.cache;
|
|
298
|
+
// bazel-out/k8-fastbuild-ST-2e5f3376adb5/bin/packages/service-worker/worker/src/assets.mjs
|
|
299
|
+
var AssetGroup = class {
|
|
300
|
+
constructor(scope2, adapter2, idle, config, hashes, db, cacheNamePrefix) {
|
|
301
|
+
this.scope = scope2;
|
|
302
|
+
this.adapter = adapter2;
|
|
303
|
+
this.idle = idle;
|
|
304
|
+
this.config = config;
|
|
305
|
+
this.hashes = hashes;
|
|
306
|
+
this.db = db;
|
|
307
|
+
this.inFlightRequests = /* @__PURE__ */ new Map();
|
|
308
|
+
this.urls = [];
|
|
309
|
+
this.patterns = [];
|
|
310
|
+
this.name = config.name;
|
|
311
|
+
this.urls = config.urls.map((url) => adapter2.normalizeUrl(url));
|
|
312
|
+
this.patterns = config.patterns.map((pattern) => new RegExp(pattern));
|
|
313
|
+
this.cache = adapter2.caches.open(`${cacheNamePrefix}:${config.name}:cache`);
|
|
314
|
+
this.metadata = this.db.open(`${cacheNamePrefix}:${config.name}:meta`, config.cacheQueryOptions);
|
|
315
|
+
}
|
|
316
|
+
async cacheStatus(url) {
|
|
317
|
+
const cache = await this.cache;
|
|
318
|
+
const meta = await this.metadata;
|
|
319
|
+
const req = this.adapter.newRequest(url);
|
|
320
|
+
const res = await cache.match(req, this.config.cacheQueryOptions);
|
|
321
|
+
if (res === void 0) {
|
|
322
|
+
return UpdateCacheStatus.NOT_CACHED;
|
|
323
|
+
}
|
|
324
|
+
try {
|
|
325
|
+
const data = await meta.read(req.url);
|
|
326
|
+
if (!data.used) {
|
|
327
|
+
return UpdateCacheStatus.CACHED_BUT_UNUSED;
|
|
328
|
+
}
|
|
329
|
+
} catch (_) {
|
|
330
|
+
}
|
|
331
|
+
return UpdateCacheStatus.CACHED;
|
|
332
|
+
}
|
|
333
|
+
async getCacheNames() {
|
|
334
|
+
const [cache, metadata] = await Promise.all([
|
|
335
|
+
this.cache,
|
|
336
|
+
this.metadata
|
|
337
|
+
]);
|
|
338
|
+
return [cache.name, metadata.cacheName];
|
|
339
|
+
}
|
|
340
|
+
async handleFetch(req, _event) {
|
|
341
|
+
const url = this.adapter.normalizeUrl(req.url);
|
|
342
|
+
if (this.urls.indexOf(url) !== -1 || this.patterns.some((pattern) => pattern.test(url))) {
|
|
343
|
+
const cache = await this.cache;
|
|
344
|
+
const cachedResponse = await cache.match(req, this.config.cacheQueryOptions);
|
|
345
|
+
if (cachedResponse !== void 0) {
|
|
346
|
+
if (this.hashes.has(url)) {
|
|
347
|
+
return cachedResponse;
|
|
348
|
+
} else {
|
|
349
|
+
if (await this.needToRevalidate(req, cachedResponse)) {
|
|
350
|
+
this.idle.schedule(`revalidate(${cache.name}): ${req.url}`, async () => {
|
|
351
|
+
await this.fetchAndCacheOnce(req);
|
|
352
|
+
});
|
|
353
|
+
}
|
|
354
|
+
return cachedResponse;
|
|
355
|
+
}
|
|
356
|
+
}
|
|
357
|
+
const res = await this.fetchAndCacheOnce(this.adapter.newRequest(req.url));
|
|
358
|
+
return res.clone();
|
|
359
|
+
} else {
|
|
360
|
+
return null;
|
|
361
|
+
}
|
|
362
|
+
}
|
|
363
|
+
async needToRevalidate(req, res) {
|
|
364
|
+
if (res.headers.has("Cache-Control")) {
|
|
365
|
+
const cacheControl = res.headers.get("Cache-Control");
|
|
366
|
+
const cacheDirectives = cacheControl.split(",").map((v) => v.trim()).map((v) => v.split("="));
|
|
367
|
+
cacheDirectives.forEach((v) => v[0] = v[0].toLowerCase());
|
|
368
|
+
const maxAgeDirective = cacheDirectives.find((v) => v[0] === "max-age");
|
|
369
|
+
const cacheAge = maxAgeDirective ? maxAgeDirective[1] : void 0;
|
|
370
|
+
if (!cacheAge) {
|
|
371
|
+
return true;
|
|
372
|
+
}
|
|
373
|
+
try {
|
|
374
|
+
const maxAge = 1e3 * parseInt(cacheAge);
|
|
375
|
+
let ts;
|
|
376
|
+
try {
|
|
605
377
|
const metaTable = await this.metadata;
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
const
|
|
609
|
-
if (
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
}
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
|
|
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));
|
|
635
|
-
}
|
|
636
|
-
/**
|
|
637
|
-
* Fetch the given resource from the network, and cache it if able.
|
|
638
|
-
*/
|
|
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
|
-
}
|
|
664
|
-
try {
|
|
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);
|
|
676
|
-
}
|
|
677
|
-
return res;
|
|
678
|
-
}
|
|
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)}`);
|
|
685
|
-
}
|
|
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
|
-
}
|
|
692
|
-
}
|
|
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;
|
|
706
|
-
}
|
|
707
|
-
/**
|
|
708
|
-
* Load a particular asset from the network, accounting for hash validation.
|
|
709
|
-
*/
|
|
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.
|
|
754
|
-
// (Make sure to clone the response so it can be used later if it proves to be valid.)
|
|
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)`);
|
|
760
|
-
}
|
|
761
|
-
}
|
|
762
|
-
}
|
|
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})`);
|
|
768
|
-
}
|
|
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
|
-
}
|
|
776
|
-
}
|
|
777
|
-
/**
|
|
778
|
-
* Possibly update a resource, if it's expired and needs to be updated. A no-op otherwise.
|
|
779
|
-
*/
|
|
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;
|
|
794
|
-
}
|
|
795
|
-
}
|
|
796
|
-
// No up-to-date version of this resource could be found.
|
|
797
|
-
return false;
|
|
798
|
-
}
|
|
799
|
-
/**
|
|
800
|
-
* Construct a cache-busting URL for a given URL.
|
|
801
|
-
*/
|
|
802
|
-
cacheBust(url) {
|
|
803
|
-
return url + (url.indexOf('?') === -1 ? '?' : '&') + 'ngsw-cache-bust=' + Math.random();
|
|
804
|
-
}
|
|
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
|
-
}
|
|
815
|
-
}
|
|
378
|
+
ts = (await metaTable.read(req.url)).ts;
|
|
379
|
+
} catch (e) {
|
|
380
|
+
const date = res.headers.get("Date");
|
|
381
|
+
if (date === null) {
|
|
382
|
+
return true;
|
|
383
|
+
}
|
|
384
|
+
ts = Date.parse(date);
|
|
385
|
+
}
|
|
386
|
+
const age = this.adapter.time - ts;
|
|
387
|
+
return age < 0 || age > maxAge;
|
|
388
|
+
} catch (e) {
|
|
389
|
+
return true;
|
|
390
|
+
}
|
|
391
|
+
} else if (res.headers.has("Expires")) {
|
|
392
|
+
const expiresStr = res.headers.get("Expires");
|
|
393
|
+
try {
|
|
394
|
+
return this.adapter.time > Date.parse(expiresStr);
|
|
395
|
+
} catch (e) {
|
|
396
|
+
return true;
|
|
397
|
+
}
|
|
398
|
+
} else {
|
|
399
|
+
return true;
|
|
400
|
+
}
|
|
816
401
|
}
|
|
817
|
-
|
|
818
|
-
|
|
819
|
-
|
|
820
|
-
|
|
821
|
-
|
|
822
|
-
|
|
823
|
-
|
|
824
|
-
|
|
825
|
-
|
|
826
|
-
|
|
827
|
-
|
|
828
|
-
|
|
829
|
-
|
|
830
|
-
|
|
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;
|
|
858
|
-
const req = this.adapter.newRequest(url);
|
|
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);
|
|
862
|
-
if (alreadyCached) {
|
|
863
|
-
return;
|
|
864
|
-
}
|
|
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.
|
|
869
|
-
return;
|
|
870
|
-
}
|
|
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
|
-
}
|
|
877
|
-
}
|
|
402
|
+
async fetchFromCacheOnly(url) {
|
|
403
|
+
const cache = await this.cache;
|
|
404
|
+
const metaTable = await this.metadata;
|
|
405
|
+
const request = this.adapter.newRequest(url);
|
|
406
|
+
const response = await cache.match(request, this.config.cacheQueryOptions);
|
|
407
|
+
if (response === void 0) {
|
|
408
|
+
return null;
|
|
409
|
+
}
|
|
410
|
+
let metadata = void 0;
|
|
411
|
+
try {
|
|
412
|
+
metadata = await metaTable.read(request.url);
|
|
413
|
+
} catch (e) {
|
|
414
|
+
}
|
|
415
|
+
return { response, metadata };
|
|
878
416
|
}
|
|
879
|
-
|
|
880
|
-
|
|
881
|
-
|
|
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) {
|
|
898
|
-
return;
|
|
899
|
-
}
|
|
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) {
|
|
911
|
-
return;
|
|
912
|
-
}
|
|
913
|
-
// Update from the network.
|
|
914
|
-
await this.fetchAndCacheOnce(req, false);
|
|
915
|
-
}
|
|
916
|
-
}, Promise.resolve());
|
|
917
|
-
}
|
|
417
|
+
async unhashedResources() {
|
|
418
|
+
const cache = await this.cache;
|
|
419
|
+
return (await cache.keys()).map((request) => this.adapter.normalizeUrl(request.url)).filter((url) => !this.hashes.has(url));
|
|
918
420
|
}
|
|
421
|
+
async fetchAndCacheOnce(req, used = true) {
|
|
422
|
+
if (this.inFlightRequests.has(req.url)) {
|
|
423
|
+
return this.inFlightRequests.get(req.url);
|
|
424
|
+
}
|
|
425
|
+
const fetchOp = this.fetchFromNetwork(req);
|
|
426
|
+
this.inFlightRequests.set(req.url, fetchOp);
|
|
427
|
+
try {
|
|
428
|
+
const res = await fetchOp;
|
|
429
|
+
if (!res.ok) {
|
|
430
|
+
throw new Error(`Response not Ok (fetchAndCacheOnce): request for ${req.url} returned response ${res.status} ${res.statusText}`);
|
|
431
|
+
}
|
|
432
|
+
try {
|
|
433
|
+
const cache = await this.cache;
|
|
434
|
+
await cache.put(req, res.clone());
|
|
435
|
+
if (!this.hashes.has(this.adapter.normalizeUrl(req.url))) {
|
|
436
|
+
const meta = { ts: this.adapter.time, used };
|
|
437
|
+
const metaTable = await this.metadata;
|
|
438
|
+
await metaTable.write(req.url, meta);
|
|
439
|
+
}
|
|
440
|
+
return res;
|
|
441
|
+
} catch (err) {
|
|
442
|
+
throw new SwCriticalError(`Failed to update the caches for request to '${req.url}' (fetchAndCacheOnce): ${errorToString(err)}`);
|
|
443
|
+
}
|
|
444
|
+
} finally {
|
|
445
|
+
this.inFlightRequests.delete(req.url);
|
|
446
|
+
}
|
|
447
|
+
}
|
|
448
|
+
async fetchFromNetwork(req, redirectLimit = 3) {
|
|
449
|
+
const res = await this.cacheBustedFetchFromNetwork(req);
|
|
450
|
+
if (res["redirected"] && !!res.url) {
|
|
451
|
+
if (redirectLimit === 0) {
|
|
452
|
+
throw new SwCriticalError(`Response hit redirect limit (fetchFromNetwork): request redirected too many times, next is ${res.url}`);
|
|
453
|
+
}
|
|
454
|
+
return this.fetchFromNetwork(this.adapter.newRequest(res.url), redirectLimit - 1);
|
|
455
|
+
}
|
|
456
|
+
return res;
|
|
457
|
+
}
|
|
458
|
+
async cacheBustedFetchFromNetwork(req) {
|
|
459
|
+
const url = this.adapter.normalizeUrl(req.url);
|
|
460
|
+
if (this.hashes.has(url)) {
|
|
461
|
+
const canonicalHash = this.hashes.get(url);
|
|
462
|
+
let response = await this.safeFetch(req);
|
|
463
|
+
let makeCacheBustedRequest = response.ok;
|
|
464
|
+
if (makeCacheBustedRequest) {
|
|
465
|
+
const fetchedHash = sha1Binary(await response.clone().arrayBuffer());
|
|
466
|
+
makeCacheBustedRequest = fetchedHash !== canonicalHash;
|
|
467
|
+
}
|
|
468
|
+
if (makeCacheBustedRequest) {
|
|
469
|
+
const cacheBustReq = this.adapter.newRequest(this.cacheBust(req.url));
|
|
470
|
+
response = await this.safeFetch(cacheBustReq);
|
|
471
|
+
if (response.ok) {
|
|
472
|
+
const cacheBustedHash = sha1Binary(await response.clone().arrayBuffer());
|
|
473
|
+
if (canonicalHash !== cacheBustedHash) {
|
|
474
|
+
throw new SwCriticalError(`Hash mismatch (cacheBustedFetchFromNetwork): ${req.url}: expected ${canonicalHash}, got ${cacheBustedHash} (after cache busting)`);
|
|
475
|
+
}
|
|
476
|
+
}
|
|
477
|
+
}
|
|
478
|
+
if (!response.ok && response.status === 404) {
|
|
479
|
+
throw new SwUnrecoverableStateError(`Failed to retrieve hashed resource from the server. (AssetGroup: ${this.config.name} | URL: ${url})`);
|
|
480
|
+
}
|
|
481
|
+
return response;
|
|
482
|
+
} else {
|
|
483
|
+
return this.safeFetch(req);
|
|
484
|
+
}
|
|
485
|
+
}
|
|
486
|
+
async maybeUpdate(updateFrom, req, cache) {
|
|
487
|
+
const url = this.adapter.normalizeUrl(req.url);
|
|
488
|
+
if (this.hashes.has(url)) {
|
|
489
|
+
const hash = this.hashes.get(url);
|
|
490
|
+
const res = await updateFrom.lookupResourceWithHash(url, hash);
|
|
491
|
+
if (res !== null) {
|
|
492
|
+
await cache.put(req, res);
|
|
493
|
+
return true;
|
|
494
|
+
}
|
|
495
|
+
}
|
|
496
|
+
return false;
|
|
497
|
+
}
|
|
498
|
+
cacheBust(url) {
|
|
499
|
+
return url + (url.indexOf("?") === -1 ? "?" : "&") + "ngsw-cache-bust=" + Math.random();
|
|
500
|
+
}
|
|
501
|
+
async safeFetch(req) {
|
|
502
|
+
try {
|
|
503
|
+
return await this.scope.fetch(req);
|
|
504
|
+
} catch (e) {
|
|
505
|
+
return this.adapter.newResponse("", {
|
|
506
|
+
status: 504,
|
|
507
|
+
statusText: "Gateway Timeout"
|
|
508
|
+
});
|
|
509
|
+
}
|
|
510
|
+
}
|
|
511
|
+
};
|
|
512
|
+
var PrefetchAssetGroup = class extends AssetGroup {
|
|
513
|
+
async initializeFully(updateFrom) {
|
|
514
|
+
const cache = await this.cache;
|
|
515
|
+
await this.urls.reduce(async (previous, url) => {
|
|
516
|
+
await previous;
|
|
517
|
+
const req = this.adapter.newRequest(url);
|
|
518
|
+
const alreadyCached = await cache.match(req, this.config.cacheQueryOptions) !== void 0;
|
|
519
|
+
if (alreadyCached) {
|
|
520
|
+
return;
|
|
521
|
+
}
|
|
522
|
+
if (updateFrom !== void 0 && await this.maybeUpdate(updateFrom, req, cache)) {
|
|
523
|
+
return;
|
|
524
|
+
}
|
|
525
|
+
await this.fetchAndCacheOnce(req, false);
|
|
526
|
+
}, Promise.resolve());
|
|
527
|
+
if (updateFrom !== void 0) {
|
|
528
|
+
const metaTable = await this.metadata;
|
|
529
|
+
await (await updateFrom.previouslyCachedResources()).filter((url) => this.urls.indexOf(url) !== -1 || this.patterns.some((pattern) => pattern.test(url))).reduce(async (previous, url) => {
|
|
530
|
+
await previous;
|
|
531
|
+
const req = this.adapter.newRequest(url);
|
|
532
|
+
const alreadyCached = await cache.match(req, this.config.cacheQueryOptions) !== void 0;
|
|
533
|
+
if (alreadyCached) {
|
|
534
|
+
return;
|
|
535
|
+
}
|
|
536
|
+
const res = await updateFrom.lookupResourceWithoutHash(url);
|
|
537
|
+
if (res === null || res.metadata === void 0) {
|
|
538
|
+
return;
|
|
539
|
+
}
|
|
540
|
+
await cache.put(req, res.response);
|
|
541
|
+
await metaTable.write(req.url, __spreadProps(__spreadValues({}, res.metadata), { used: false }));
|
|
542
|
+
}, Promise.resolve());
|
|
543
|
+
}
|
|
544
|
+
}
|
|
545
|
+
};
|
|
546
|
+
var LazyAssetGroup = class extends AssetGroup {
|
|
547
|
+
async initializeFully(updateFrom) {
|
|
548
|
+
if (updateFrom === void 0) {
|
|
549
|
+
return;
|
|
550
|
+
}
|
|
551
|
+
const cache = await this.cache;
|
|
552
|
+
await this.urls.reduce(async (previous, url) => {
|
|
553
|
+
await previous;
|
|
554
|
+
const req = this.adapter.newRequest(url);
|
|
555
|
+
const alreadyCached = await cache.match(req, this.config.cacheQueryOptions) !== void 0;
|
|
556
|
+
if (alreadyCached) {
|
|
557
|
+
return;
|
|
558
|
+
}
|
|
559
|
+
const updated = await this.maybeUpdate(updateFrom, req, cache);
|
|
560
|
+
if (this.config.updateMode === "prefetch" && !updated) {
|
|
561
|
+
const cacheStatus = await updateFrom.recentCacheStatus(url);
|
|
562
|
+
if (cacheStatus !== UpdateCacheStatus.CACHED) {
|
|
563
|
+
return;
|
|
564
|
+
}
|
|
565
|
+
await this.fetchAndCacheOnce(req, false);
|
|
566
|
+
}
|
|
567
|
+
}, Promise.resolve());
|
|
568
|
+
}
|
|
569
|
+
};
|
|
919
570
|
|
|
920
|
-
|
|
921
|
-
|
|
922
|
-
|
|
923
|
-
|
|
924
|
-
|
|
925
|
-
|
|
926
|
-
|
|
927
|
-
|
|
928
|
-
|
|
929
|
-
|
|
930
|
-
|
|
931
|
-
|
|
932
|
-
constructor(state) {
|
|
933
|
-
if (state === undefined) {
|
|
934
|
-
state = {
|
|
935
|
-
head: null,
|
|
936
|
-
tail: null,
|
|
937
|
-
map: {},
|
|
938
|
-
count: 0,
|
|
939
|
-
};
|
|
940
|
-
}
|
|
941
|
-
this.state = state;
|
|
942
|
-
}
|
|
943
|
-
/**
|
|
944
|
-
* The current count of URLs in the list.
|
|
945
|
-
*/
|
|
946
|
-
get size() {
|
|
947
|
-
return this.state.count;
|
|
948
|
-
}
|
|
949
|
-
/**
|
|
950
|
-
* Remove the tail.
|
|
951
|
-
*/
|
|
952
|
-
pop() {
|
|
953
|
-
// If there is no tail, return null.
|
|
954
|
-
if (this.state.tail === null) {
|
|
955
|
-
return null;
|
|
956
|
-
}
|
|
957
|
-
const url = this.state.tail;
|
|
958
|
-
this.remove(url);
|
|
959
|
-
// This URL has been successfully evicted.
|
|
960
|
-
return url;
|
|
961
|
-
}
|
|
962
|
-
remove(url) {
|
|
963
|
-
const node = this.state.map[url];
|
|
964
|
-
if (node === undefined) {
|
|
965
|
-
return false;
|
|
966
|
-
}
|
|
967
|
-
// Special case if removing the current head.
|
|
968
|
-
if (this.state.head === url) {
|
|
969
|
-
// The node is the current head. Special case the removal.
|
|
970
|
-
if (node.next === null) {
|
|
971
|
-
// This is the only node. Reset the cache to be empty.
|
|
972
|
-
this.state.head = null;
|
|
973
|
-
this.state.tail = null;
|
|
974
|
-
this.state.map = {};
|
|
975
|
-
this.state.count = 0;
|
|
976
|
-
return true;
|
|
977
|
-
}
|
|
978
|
-
// There is at least one other node. Make the next node the new head.
|
|
979
|
-
const next = this.state.map[node.next];
|
|
980
|
-
next.previous = null;
|
|
981
|
-
this.state.head = next.url;
|
|
982
|
-
node.next = null;
|
|
983
|
-
delete this.state.map[url];
|
|
984
|
-
this.state.count--;
|
|
985
|
-
return true;
|
|
986
|
-
}
|
|
987
|
-
// The node is not the head, so it has a previous. It may or may not be the tail.
|
|
988
|
-
// If it is not, then it has a next. First, grab the previous node.
|
|
989
|
-
const previous = this.state.map[node.previous];
|
|
990
|
-
// Fix the forward pointer to skip over node and go directly to node.next.
|
|
991
|
-
previous.next = node.next;
|
|
992
|
-
// node.next may or may not be set. If it is, fix the back pointer to skip over node.
|
|
993
|
-
// If it's not set, then this node happened to be the tail, and the tail needs to be
|
|
994
|
-
// updated to point to the previous node (removing the tail).
|
|
995
|
-
if (node.next !== null) {
|
|
996
|
-
// There is a next node, fix its back pointer to skip this node.
|
|
997
|
-
this.state.map[node.next].previous = node.previous;
|
|
998
|
-
}
|
|
999
|
-
else {
|
|
1000
|
-
// There is no next node - the accessed node must be the tail. Move the tail pointer.
|
|
1001
|
-
this.state.tail = node.previous;
|
|
1002
|
-
}
|
|
1003
|
-
node.next = null;
|
|
1004
|
-
node.previous = null;
|
|
1005
|
-
delete this.state.map[url];
|
|
1006
|
-
// Count the removal.
|
|
1007
|
-
this.state.count--;
|
|
1008
|
-
return true;
|
|
1009
|
-
}
|
|
1010
|
-
accessed(url) {
|
|
1011
|
-
// When a URL is accessed, its node needs to be moved to the head of the chain.
|
|
1012
|
-
// This is accomplished in two steps:
|
|
1013
|
-
//
|
|
1014
|
-
// 1) remove the node from its position within the chain.
|
|
1015
|
-
// 2) insert the node as the new head.
|
|
1016
|
-
//
|
|
1017
|
-
// Sometimes, a URL is accessed which has not been seen before. In this case, step 1 can
|
|
1018
|
-
// be skipped completely (which will grow the chain by one). Of course, if the node is
|
|
1019
|
-
// already the head, this whole operation can be skipped.
|
|
1020
|
-
if (this.state.head === url) {
|
|
1021
|
-
// The URL is already in the head position, accessing it is a no-op.
|
|
1022
|
-
return;
|
|
1023
|
-
}
|
|
1024
|
-
// Look up the node in the map, and construct a new entry if it's
|
|
1025
|
-
const node = this.state.map[url] || { url, next: null, previous: null };
|
|
1026
|
-
// Step 1: remove the node from its position within the chain, if it is in the chain.
|
|
1027
|
-
if (this.state.map[url] !== undefined) {
|
|
1028
|
-
this.remove(url);
|
|
1029
|
-
}
|
|
1030
|
-
// Step 2: insert the node at the head of the chain.
|
|
1031
|
-
// First, check if there's an existing head node. If there is, it has previous: null.
|
|
1032
|
-
// Its previous pointer should be set to the node we're inserting.
|
|
1033
|
-
if (this.state.head !== null) {
|
|
1034
|
-
this.state.map[this.state.head].previous = url;
|
|
1035
|
-
}
|
|
1036
|
-
// The next pointer of the node being inserted gets set to the old head, before the head
|
|
1037
|
-
// pointer is updated to this node.
|
|
1038
|
-
node.next = this.state.head;
|
|
1039
|
-
// The new head is the new node.
|
|
1040
|
-
this.state.head = url;
|
|
1041
|
-
// If there is no tail, then this is the first node, and is both the head and the tail.
|
|
1042
|
-
if (this.state.tail === null) {
|
|
1043
|
-
this.state.tail = url;
|
|
1044
|
-
}
|
|
1045
|
-
// Set the node in the map of nodes (if the URL has been seen before, this is a no-op)
|
|
1046
|
-
// and count the insertion.
|
|
1047
|
-
this.state.map[url] = node;
|
|
1048
|
-
this.state.count++;
|
|
1049
|
-
}
|
|
571
|
+
// bazel-out/k8-fastbuild-ST-2e5f3376adb5/bin/packages/service-worker/worker/src/data.mjs
|
|
572
|
+
var LruList = class {
|
|
573
|
+
constructor(state) {
|
|
574
|
+
if (state === void 0) {
|
|
575
|
+
state = {
|
|
576
|
+
head: null,
|
|
577
|
+
tail: null,
|
|
578
|
+
map: {},
|
|
579
|
+
count: 0
|
|
580
|
+
};
|
|
581
|
+
}
|
|
582
|
+
this.state = state;
|
|
1050
583
|
}
|
|
1051
|
-
|
|
1052
|
-
|
|
1053
|
-
|
|
1054
|
-
|
|
1055
|
-
|
|
1056
|
-
|
|
1057
|
-
|
|
1058
|
-
|
|
1059
|
-
|
|
1060
|
-
|
|
1061
|
-
|
|
1062
|
-
|
|
1063
|
-
|
|
1064
|
-
|
|
1065
|
-
|
|
1066
|
-
|
|
1067
|
-
|
|
1068
|
-
|
|
1069
|
-
|
|
1070
|
-
|
|
1071
|
-
|
|
1072
|
-
|
|
1073
|
-
|
|
1074
|
-
|
|
1075
|
-
|
|
1076
|
-
|
|
1077
|
-
|
|
1078
|
-
|
|
1079
|
-
|
|
1080
|
-
|
|
1081
|
-
|
|
1082
|
-
|
|
1083
|
-
|
|
1084
|
-
|
|
1085
|
-
|
|
1086
|
-
|
|
1087
|
-
|
|
1088
|
-
|
|
1089
|
-
|
|
1090
|
-
|
|
1091
|
-
|
|
1092
|
-
|
|
1093
|
-
|
|
1094
|
-
|
|
1095
|
-
|
|
1096
|
-
|
|
1097
|
-
|
|
1098
|
-
|
|
1099
|
-
|
|
1100
|
-
|
|
1101
|
-
|
|
1102
|
-
|
|
1103
|
-
|
|
1104
|
-
|
|
1105
|
-
|
|
1106
|
-
|
|
1107
|
-
|
|
1108
|
-
|
|
1109
|
-
|
|
1110
|
-
|
|
1111
|
-
|
|
1112
|
-
|
|
1113
|
-
|
|
1114
|
-
|
|
1115
|
-
|
|
1116
|
-
|
|
1117
|
-
|
|
1118
|
-
|
|
1119
|
-
|
|
1120
|
-
|
|
1121
|
-
|
|
1122
|
-
|
|
1123
|
-
|
|
1124
|
-
|
|
1125
|
-
|
|
1126
|
-
|
|
1127
|
-
|
|
1128
|
-
|
|
1129
|
-
|
|
1130
|
-
|
|
1131
|
-
|
|
1132
|
-
|
|
1133
|
-
|
|
1134
|
-
|
|
1135
|
-
|
|
1136
|
-
|
|
1137
|
-
|
|
1138
|
-
|
|
1139
|
-
|
|
1140
|
-
|
|
1141
|
-
|
|
1142
|
-
|
|
1143
|
-
|
|
1144
|
-
|
|
1145
|
-
|
|
1146
|
-
|
|
1147
|
-
|
|
1148
|
-
|
|
1149
|
-
|
|
1150
|
-
|
|
1151
|
-
|
|
1152
|
-
|
|
1153
|
-
|
|
1154
|
-
|
|
1155
|
-
|
|
1156
|
-
|
|
1157
|
-
|
|
1158
|
-
|
|
1159
|
-
|
|
1160
|
-
|
|
1161
|
-
|
|
1162
|
-
|
|
1163
|
-
|
|
1164
|
-
|
|
1165
|
-
|
|
1166
|
-
|
|
1167
|
-
|
|
1168
|
-
|
|
1169
|
-
|
|
1170
|
-
|
|
1171
|
-
|
|
1172
|
-
|
|
1173
|
-
|
|
1174
|
-
|
|
1175
|
-
|
|
1176
|
-
|
|
1177
|
-
|
|
1178
|
-
|
|
1179
|
-
|
|
1180
|
-
|
|
1181
|
-
|
|
1182
|
-
|
|
1183
|
-
|
|
1184
|
-
|
|
1185
|
-
|
|
1186
|
-
|
|
1187
|
-
|
|
1188
|
-
|
|
1189
|
-
|
|
1190
|
-
|
|
1191
|
-
|
|
1192
|
-
|
|
1193
|
-
|
|
1194
|
-
|
|
1195
|
-
|
|
1196
|
-
|
|
1197
|
-
|
|
1198
|
-
|
|
1199
|
-
|
|
1200
|
-
|
|
1201
|
-
|
|
1202
|
-
|
|
1203
|
-
|
|
1204
|
-
|
|
1205
|
-
|
|
1206
|
-
|
|
1207
|
-
|
|
1208
|
-
|
|
1209
|
-
|
|
1210
|
-
|
|
1211
|
-
|
|
1212
|
-
|
|
1213
|
-
|
|
1214
|
-
|
|
1215
|
-
|
|
1216
|
-
|
|
1217
|
-
|
|
1218
|
-
|
|
1219
|
-
|
|
1220
|
-
|
|
1221
|
-
|
|
1222
|
-
|
|
1223
|
-
|
|
1224
|
-
|
|
1225
|
-
|
|
1226
|
-
|
|
1227
|
-
|
|
1228
|
-
|
|
1229
|
-
|
|
1230
|
-
|
|
1231
|
-
|
|
1232
|
-
|
|
1233
|
-
|
|
1234
|
-
|
|
1235
|
-
|
|
1236
|
-
|
|
1237
|
-
|
|
1238
|
-
|
|
1239
|
-
|
|
1240
|
-
|
|
1241
|
-
|
|
1242
|
-
|
|
1243
|
-
|
|
1244
|
-
|
|
1245
|
-
|
|
1246
|
-
|
|
1247
|
-
|
|
1248
|
-
|
|
1249
|
-
|
|
1250
|
-
|
|
1251
|
-
|
|
1252
|
-
|
|
1253
|
-
|
|
1254
|
-
|
|
1255
|
-
|
|
1256
|
-
|
|
1257
|
-
|
|
1258
|
-
|
|
1259
|
-
|
|
1260
|
-
|
|
1261
|
-
|
|
1262
|
-
|
|
1263
|
-
|
|
1264
|
-
|
|
1265
|
-
|
|
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 };
|
|
1278
|
-
}
|
|
1279
|
-
// Otherwise, or if there was an error, assume the response is expired, and evict it.
|
|
1280
|
-
}
|
|
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;
|
|
1290
|
-
}
|
|
1291
|
-
/**
|
|
1292
|
-
* Operation for caching the response from the server. This has to happen all
|
|
1293
|
-
* at once, so that the cache and LRU tracking remain in sync. If the network request
|
|
1294
|
-
* completes before the timeout, this logic will be run inline with the response flow.
|
|
1295
|
-
* If the request times out on the server, an error will be returned but the real network
|
|
1296
|
-
* request will still be running in the background, to be cached when it completes.
|
|
1297
|
-
*/
|
|
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);
|
|
1310
|
-
}
|
|
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.
|
|
584
|
+
get size() {
|
|
585
|
+
return this.state.count;
|
|
586
|
+
}
|
|
587
|
+
pop() {
|
|
588
|
+
if (this.state.tail === null) {
|
|
589
|
+
return null;
|
|
590
|
+
}
|
|
591
|
+
const url = this.state.tail;
|
|
592
|
+
this.remove(url);
|
|
593
|
+
return url;
|
|
594
|
+
}
|
|
595
|
+
remove(url) {
|
|
596
|
+
const node = this.state.map[url];
|
|
597
|
+
if (node === void 0) {
|
|
598
|
+
return false;
|
|
599
|
+
}
|
|
600
|
+
if (this.state.head === url) {
|
|
601
|
+
if (node.next === null) {
|
|
602
|
+
this.state.head = null;
|
|
603
|
+
this.state.tail = null;
|
|
604
|
+
this.state.map = {};
|
|
605
|
+
this.state.count = 0;
|
|
606
|
+
return true;
|
|
607
|
+
}
|
|
608
|
+
const next = this.state.map[node.next];
|
|
609
|
+
next.previous = null;
|
|
610
|
+
this.state.head = next.url;
|
|
611
|
+
node.next = null;
|
|
612
|
+
delete this.state.map[url];
|
|
613
|
+
this.state.count--;
|
|
614
|
+
return true;
|
|
615
|
+
}
|
|
616
|
+
const previous = this.state.map[node.previous];
|
|
617
|
+
previous.next = node.next;
|
|
618
|
+
if (node.next !== null) {
|
|
619
|
+
this.state.map[node.next].previous = node.previous;
|
|
620
|
+
} else {
|
|
621
|
+
this.state.tail = node.previous;
|
|
622
|
+
}
|
|
623
|
+
node.next = null;
|
|
624
|
+
node.previous = null;
|
|
625
|
+
delete this.state.map[url];
|
|
626
|
+
this.state.count--;
|
|
627
|
+
return true;
|
|
628
|
+
}
|
|
629
|
+
accessed(url) {
|
|
630
|
+
if (this.state.head === url) {
|
|
631
|
+
return;
|
|
632
|
+
}
|
|
633
|
+
const node = this.state.map[url] || { url, next: null, previous: null };
|
|
634
|
+
if (this.state.map[url] !== void 0) {
|
|
635
|
+
this.remove(url);
|
|
636
|
+
}
|
|
637
|
+
if (this.state.head !== null) {
|
|
638
|
+
this.state.map[this.state.head].previous = url;
|
|
639
|
+
}
|
|
640
|
+
node.next = this.state.head;
|
|
641
|
+
this.state.head = url;
|
|
642
|
+
if (this.state.tail === null) {
|
|
643
|
+
this.state.tail = url;
|
|
644
|
+
}
|
|
645
|
+
this.state.map[url] = node;
|
|
646
|
+
this.state.count++;
|
|
647
|
+
}
|
|
648
|
+
};
|
|
649
|
+
var DataGroup = class {
|
|
650
|
+
constructor(scope2, adapter2, config, db, debugHandler, cacheNamePrefix) {
|
|
651
|
+
this.scope = scope2;
|
|
652
|
+
this.adapter = adapter2;
|
|
653
|
+
this.config = config;
|
|
654
|
+
this.db = db;
|
|
655
|
+
this.debugHandler = debugHandler;
|
|
656
|
+
this._lru = null;
|
|
657
|
+
this.patterns = config.patterns.map((pattern) => new RegExp(pattern));
|
|
658
|
+
this.cache = adapter2.caches.open(`${cacheNamePrefix}:${config.name}:cache`);
|
|
659
|
+
this.lruTable = this.db.open(`${cacheNamePrefix}:${config.name}:lru`, config.cacheQueryOptions);
|
|
660
|
+
this.ageTable = this.db.open(`${cacheNamePrefix}:${config.name}:age`, config.cacheQueryOptions);
|
|
661
|
+
}
|
|
662
|
+
async lru() {
|
|
663
|
+
if (this._lru === null) {
|
|
664
|
+
const table = await this.lruTable;
|
|
665
|
+
try {
|
|
666
|
+
this._lru = new LruList(await table.read("lru"));
|
|
667
|
+
} catch (e) {
|
|
668
|
+
this._lru = new LruList();
|
|
669
|
+
}
|
|
670
|
+
}
|
|
671
|
+
return this._lru;
|
|
672
|
+
}
|
|
673
|
+
async syncLru() {
|
|
674
|
+
if (this._lru === null) {
|
|
675
|
+
return;
|
|
676
|
+
}
|
|
677
|
+
const table = await this.lruTable;
|
|
678
|
+
try {
|
|
679
|
+
return table.write("lru", this._lru.state);
|
|
680
|
+
} catch (err) {
|
|
681
|
+
this.debugHandler.log(err, `DataGroup(${this.config.name}@${this.config.version}).syncLru()`);
|
|
682
|
+
}
|
|
683
|
+
}
|
|
684
|
+
async handleFetch(req, event) {
|
|
685
|
+
if (!this.patterns.some((pattern) => pattern.test(req.url))) {
|
|
686
|
+
return null;
|
|
687
|
+
}
|
|
688
|
+
const lru = await this.lru();
|
|
689
|
+
switch (req.method) {
|
|
690
|
+
case "OPTIONS":
|
|
691
|
+
return null;
|
|
692
|
+
case "GET":
|
|
693
|
+
case "HEAD":
|
|
694
|
+
switch (this.config.strategy) {
|
|
695
|
+
case "freshness":
|
|
696
|
+
return this.handleFetchWithFreshness(req, event, lru);
|
|
697
|
+
case "performance":
|
|
698
|
+
return this.handleFetchWithPerformance(req, event, lru);
|
|
699
|
+
default:
|
|
700
|
+
throw new Error(`Unknown strategy: ${this.config.strategy}`);
|
|
701
|
+
}
|
|
702
|
+
default:
|
|
703
|
+
const wasCached = lru.remove(req.url);
|
|
704
|
+
if (wasCached) {
|
|
705
|
+
await this.clearCacheForUrl(req.url);
|
|
706
|
+
}
|
|
707
|
+
await this.syncLru();
|
|
708
|
+
return this.safeFetch(req);
|
|
709
|
+
}
|
|
710
|
+
}
|
|
711
|
+
async handleFetchWithPerformance(req, event, lru) {
|
|
712
|
+
let res = null;
|
|
713
|
+
const fromCache = await this.loadFromCache(req, lru);
|
|
714
|
+
if (fromCache !== null) {
|
|
715
|
+
res = fromCache.res;
|
|
716
|
+
if (this.config.refreshAheadMs !== void 0 && fromCache.age >= this.config.refreshAheadMs) {
|
|
717
|
+
event.waitUntil(this.safeCacheResponse(req, this.safeFetch(req), lru));
|
|
718
|
+
}
|
|
719
|
+
}
|
|
720
|
+
if (res !== null) {
|
|
721
|
+
return res;
|
|
722
|
+
}
|
|
723
|
+
const [timeoutFetch, networkFetch] = this.networkFetchWithTimeout(req);
|
|
724
|
+
res = await timeoutFetch;
|
|
725
|
+
if (res === void 0) {
|
|
726
|
+
res = this.adapter.newResponse(null, { status: 504, statusText: "Gateway Timeout" });
|
|
727
|
+
event.waitUntil(this.safeCacheResponse(req, networkFetch, lru));
|
|
728
|
+
} else {
|
|
729
|
+
await this.safeCacheResponse(req, res, lru);
|
|
730
|
+
}
|
|
731
|
+
return res;
|
|
732
|
+
}
|
|
733
|
+
async handleFetchWithFreshness(req, event, lru) {
|
|
734
|
+
const [timeoutFetch, networkFetch] = this.networkFetchWithTimeout(req);
|
|
735
|
+
let res;
|
|
736
|
+
try {
|
|
737
|
+
res = await timeoutFetch;
|
|
738
|
+
} catch (e) {
|
|
739
|
+
res = void 0;
|
|
740
|
+
}
|
|
741
|
+
if (res === void 0) {
|
|
742
|
+
event.waitUntil(this.safeCacheResponse(req, networkFetch, lru, true));
|
|
743
|
+
const fromCache = await this.loadFromCache(req, lru);
|
|
744
|
+
res = fromCache !== null ? fromCache.res : null;
|
|
745
|
+
} else {
|
|
746
|
+
await this.safeCacheResponse(req, res, lru, true);
|
|
747
|
+
}
|
|
748
|
+
if (res !== null) {
|
|
749
|
+
return res;
|
|
750
|
+
}
|
|
751
|
+
return networkFetch;
|
|
752
|
+
}
|
|
753
|
+
networkFetchWithTimeout(req) {
|
|
754
|
+
if (this.config.timeoutMs !== void 0) {
|
|
755
|
+
const networkFetch = this.scope.fetch(req);
|
|
756
|
+
const safeNetworkFetch = (async () => {
|
|
757
|
+
try {
|
|
758
|
+
return await networkFetch;
|
|
759
|
+
} catch (e) {
|
|
760
|
+
return this.adapter.newResponse(null, {
|
|
761
|
+
status: 504,
|
|
762
|
+
statusText: "Gateway Timeout"
|
|
763
|
+
});
|
|
764
|
+
}
|
|
765
|
+
})();
|
|
766
|
+
const networkFetchUndefinedError = (async () => {
|
|
767
|
+
try {
|
|
768
|
+
return await networkFetch;
|
|
769
|
+
} catch (e) {
|
|
770
|
+
return void 0;
|
|
771
|
+
}
|
|
772
|
+
})();
|
|
773
|
+
const timeout = this.adapter.timeout(this.config.timeoutMs);
|
|
774
|
+
return [Promise.race([networkFetchUndefinedError, timeout]), safeNetworkFetch];
|
|
775
|
+
} else {
|
|
776
|
+
const networkFetch = this.safeFetch(req);
|
|
777
|
+
return [networkFetch, networkFetch];
|
|
778
|
+
}
|
|
779
|
+
}
|
|
780
|
+
async safeCacheResponse(req, resOrPromise, lru, okToCacheOpaque) {
|
|
781
|
+
try {
|
|
782
|
+
const res = await resOrPromise;
|
|
783
|
+
try {
|
|
784
|
+
await this.cacheResponse(req, res, lru, okToCacheOpaque);
|
|
785
|
+
} catch (err) {
|
|
786
|
+
this.debugHandler.log(err, `DataGroup(${this.config.name}@${this.config.version}).safeCacheResponse(${req.url}, status: ${res.status})`);
|
|
787
|
+
}
|
|
788
|
+
} catch (e) {
|
|
789
|
+
}
|
|
790
|
+
}
|
|
791
|
+
async loadFromCache(req, lru) {
|
|
792
|
+
const cache = await this.cache;
|
|
793
|
+
let res = await cache.match(req, this.config.cacheQueryOptions);
|
|
794
|
+
if (res !== void 0) {
|
|
795
|
+
try {
|
|
796
|
+
const ageTable = await this.ageTable;
|
|
797
|
+
const age = this.adapter.time - (await ageTable.read(req.url)).age;
|
|
798
|
+
if (age <= this.config.maxAge) {
|
|
1315
799
|
lru.accessed(req.url);
|
|
1316
|
-
|
|
1317
|
-
|
|
1318
|
-
|
|
1319
|
-
|
|
1320
|
-
|
|
1321
|
-
|
|
1322
|
-
|
|
1323
|
-
|
|
1324
|
-
|
|
1325
|
-
|
|
1326
|
-
|
|
1327
|
-
|
|
1328
|
-
|
|
1329
|
-
|
|
1330
|
-
|
|
1331
|
-
|
|
1332
|
-
|
|
1333
|
-
|
|
1334
|
-
|
|
1335
|
-
|
|
1336
|
-
|
|
1337
|
-
|
|
1338
|
-
|
|
1339
|
-
|
|
1340
|
-
|
|
1341
|
-
|
|
1342
|
-
|
|
1343
|
-
|
|
1344
|
-
|
|
1345
|
-
|
|
1346
|
-
|
|
1347
|
-
|
|
1348
|
-
|
|
1349
|
-
|
|
1350
|
-
|
|
1351
|
-
|
|
1352
|
-
|
|
1353
|
-
|
|
1354
|
-
|
|
1355
|
-
|
|
1356
|
-
|
|
1357
|
-
|
|
1358
|
-
|
|
1359
|
-
|
|
1360
|
-
|
|
1361
|
-
}
|
|
1362
|
-
|
|
1363
|
-
|
|
1364
|
-
|
|
1365
|
-
|
|
1366
|
-
|
|
1367
|
-
|
|
1368
|
-
|
|
1369
|
-
|
|
1370
|
-
|
|
1371
|
-
|
|
1372
|
-
}
|
|
800
|
+
return { res, age };
|
|
801
|
+
}
|
|
802
|
+
} catch (e) {
|
|
803
|
+
}
|
|
804
|
+
lru.remove(req.url);
|
|
805
|
+
await this.clearCacheForUrl(req.url);
|
|
806
|
+
await this.syncLru();
|
|
807
|
+
}
|
|
808
|
+
return null;
|
|
809
|
+
}
|
|
810
|
+
async cacheResponse(req, res, lru, okToCacheOpaque = false) {
|
|
811
|
+
if (!(res.ok || okToCacheOpaque && res.type === "opaque")) {
|
|
812
|
+
return;
|
|
813
|
+
}
|
|
814
|
+
if (lru.size >= this.config.maxSize) {
|
|
815
|
+
const evictedUrl = lru.pop();
|
|
816
|
+
if (evictedUrl !== null) {
|
|
817
|
+
await this.clearCacheForUrl(evictedUrl);
|
|
818
|
+
}
|
|
819
|
+
}
|
|
820
|
+
lru.accessed(req.url);
|
|
821
|
+
await (await this.cache).put(req, res.clone());
|
|
822
|
+
const ageTable = await this.ageTable;
|
|
823
|
+
await ageTable.write(req.url, { age: this.adapter.time });
|
|
824
|
+
await this.syncLru();
|
|
825
|
+
}
|
|
826
|
+
async cleanup() {
|
|
827
|
+
await Promise.all([
|
|
828
|
+
this.cache.then((cache) => this.adapter.caches.delete(cache.name)),
|
|
829
|
+
this.ageTable.then((table) => this.db.delete(table.name)),
|
|
830
|
+
this.lruTable.then((table) => this.db.delete(table.name))
|
|
831
|
+
]);
|
|
832
|
+
}
|
|
833
|
+
async getCacheNames() {
|
|
834
|
+
const [cache, ageTable, lruTable] = await Promise.all([
|
|
835
|
+
this.cache,
|
|
836
|
+
this.ageTable,
|
|
837
|
+
this.lruTable
|
|
838
|
+
]);
|
|
839
|
+
return [cache.name, ageTable.cacheName, lruTable.cacheName];
|
|
840
|
+
}
|
|
841
|
+
async clearCacheForUrl(url) {
|
|
842
|
+
const [cache, ageTable] = await Promise.all([this.cache, this.ageTable]);
|
|
843
|
+
await Promise.all([
|
|
844
|
+
cache.delete(this.adapter.newRequest(url, { method: "GET" }), this.config.cacheQueryOptions),
|
|
845
|
+
cache.delete(this.adapter.newRequest(url, { method: "HEAD" }), this.config.cacheQueryOptions),
|
|
846
|
+
ageTable.delete(url)
|
|
847
|
+
]);
|
|
848
|
+
}
|
|
849
|
+
async safeFetch(req) {
|
|
850
|
+
try {
|
|
851
|
+
return this.scope.fetch(req);
|
|
852
|
+
} catch (e) {
|
|
853
|
+
return this.adapter.newResponse(null, {
|
|
854
|
+
status: 504,
|
|
855
|
+
statusText: "Gateway Timeout"
|
|
856
|
+
});
|
|
857
|
+
}
|
|
1373
858
|
}
|
|
859
|
+
};
|
|
1374
860
|
|
|
1375
|
-
|
|
1376
|
-
|
|
1377
|
-
|
|
1378
|
-
|
|
1379
|
-
|
|
1380
|
-
|
|
1381
|
-
|
|
1382
|
-
|
|
1383
|
-
|
|
1384
|
-
|
|
1385
|
-
|
|
1386
|
-
|
|
1387
|
-
|
|
1388
|
-
|
|
1389
|
-
|
|
1390
|
-
|
|
1391
|
-
|
|
1392
|
-
|
|
1393
|
-
|
|
1394
|
-
|
|
1395
|
-
|
|
1396
|
-
|
|
1397
|
-
|
|
1398
|
-
|
|
1399
|
-
this.
|
|
1400
|
-
|
|
1401
|
-
this.
|
|
1402
|
-
|
|
1403
|
-
|
|
1404
|
-
|
|
1405
|
-
|
|
1406
|
-
|
|
1407
|
-
|
|
1408
|
-
|
|
1409
|
-
|
|
1410
|
-
|
|
1411
|
-
|
|
1412
|
-
|
|
1413
|
-
|
|
1414
|
-
|
|
1415
|
-
|
|
1416
|
-
|
|
1417
|
-
|
|
1418
|
-
|
|
1419
|
-
|
|
1420
|
-
|
|
1421
|
-
|
|
1422
|
-
|
|
1423
|
-
|
|
1424
|
-
|
|
1425
|
-
|
|
1426
|
-
|
|
1427
|
-
|
|
1428
|
-
|
|
1429
|
-
|
|
1430
|
-
|
|
1431
|
-
|
|
1432
|
-
|
|
1433
|
-
|
|
1434
|
-
|
|
1435
|
-
|
|
1436
|
-
|
|
1437
|
-
|
|
1438
|
-
|
|
1439
|
-
|
|
1440
|
-
|
|
1441
|
-
|
|
1442
|
-
|
|
1443
|
-
|
|
1444
|
-
|
|
1445
|
-
|
|
1446
|
-
|
|
1447
|
-
|
|
1448
|
-
|
|
1449
|
-
|
|
1450
|
-
|
|
1451
|
-
|
|
1452
|
-
|
|
1453
|
-
|
|
1454
|
-
|
|
1455
|
-
|
|
1456
|
-
|
|
1457
|
-
|
|
1458
|
-
|
|
1459
|
-
|
|
1460
|
-
|
|
1461
|
-
|
|
1462
|
-
|
|
1463
|
-
|
|
1464
|
-
|
|
1465
|
-
|
|
1466
|
-
|
|
1467
|
-
|
|
1468
|
-
|
|
1469
|
-
|
|
1470
|
-
|
|
1471
|
-
|
|
1472
|
-
|
|
1473
|
-
|
|
1474
|
-
|
|
1475
|
-
|
|
1476
|
-
|
|
1477
|
-
|
|
1478
|
-
|
|
1479
|
-
|
|
1480
|
-
|
|
1481
|
-
|
|
1482
|
-
|
|
1483
|
-
|
|
1484
|
-
|
|
1485
|
-
|
|
1486
|
-
|
|
1487
|
-
|
|
1488
|
-
|
|
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;
|
|
1503
|
-
}
|
|
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);
|
|
1518
|
-
}
|
|
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.
|
|
1523
|
-
}
|
|
1524
|
-
}
|
|
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;
|
|
1530
|
-
}
|
|
1531
|
-
/**
|
|
1532
|
-
* Determine whether the request is a navigation request.
|
|
1533
|
-
* Takes into account: Request mode, `Accept` header, `navigationUrls` patterns.
|
|
1534
|
-
*/
|
|
1535
|
-
isNavigationRequest(req) {
|
|
1536
|
-
if (req.mode !== 'navigate') {
|
|
1537
|
-
return false;
|
|
1538
|
-
}
|
|
1539
|
-
if (!this.acceptsTextHtml(req)) {
|
|
1540
|
-
return false;
|
|
1541
|
-
}
|
|
1542
|
-
const urlPrefix = this.scope.registration.scope.replace(/\/$/, '');
|
|
1543
|
-
const url = req.url.startsWith(urlPrefix) ? req.url.substr(urlPrefix.length) : req.url;
|
|
1544
|
-
const urlWithoutQueryOrHash = url.replace(/[?#].*$/, '');
|
|
1545
|
-
return this.navigationUrls.include.some(regex => regex.test(urlWithoutQueryOrHash)) &&
|
|
1546
|
-
!this.navigationUrls.exclude.some(regex => regex.test(urlWithoutQueryOrHash));
|
|
1547
|
-
}
|
|
1548
|
-
/**
|
|
1549
|
-
* Check this version for a given resource with a particular hash.
|
|
1550
|
-
*/
|
|
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;
|
|
1564
|
-
}
|
|
1565
|
-
/**
|
|
1566
|
-
* Check this version for a given resource regardless of its hash.
|
|
1567
|
-
*/
|
|
1568
|
-
lookupResourceWithoutHash(url) {
|
|
1569
|
-
// Limit the search to asset groups, and only scan the cache, don't
|
|
1570
|
-
// load resources from the network.
|
|
1571
|
-
return this.assetGroups.reduce(async (potentialResponse, group) => {
|
|
1572
|
-
const resp = await potentialResponse;
|
|
1573
|
-
if (resp !== null) {
|
|
1574
|
-
return resp;
|
|
1575
|
-
}
|
|
1576
|
-
// fetchFromCacheOnly() avoids any network fetches, and returns the
|
|
1577
|
-
// full set of cache data, not just the Response.
|
|
1578
|
-
return group.fetchFromCacheOnly(url);
|
|
1579
|
-
}, Promise.resolve(null));
|
|
1580
|
-
}
|
|
1581
|
-
/**
|
|
1582
|
-
* List all unhashed resources from all asset groups.
|
|
1583
|
-
*/
|
|
1584
|
-
previouslyCachedResources() {
|
|
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));
|
|
1599
|
-
}
|
|
1600
|
-
/**
|
|
1601
|
-
* Return a list of the names of all caches used by this version.
|
|
1602
|
-
*/
|
|
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);
|
|
1609
|
-
}
|
|
1610
|
-
/**
|
|
1611
|
-
* Get the opaque application data which was provided with the manifest.
|
|
1612
|
-
*/
|
|
1613
|
-
get appData() {
|
|
1614
|
-
return this.manifest.appData || null;
|
|
1615
|
-
}
|
|
1616
|
-
/**
|
|
1617
|
-
* Check whether a request accepts `text/html` (based on the `Accept` header).
|
|
1618
|
-
*/
|
|
1619
|
-
acceptsTextHtml(req) {
|
|
1620
|
-
const accept = req.headers.get('Accept');
|
|
1621
|
-
if (accept === null) {
|
|
1622
|
-
return false;
|
|
1623
|
-
}
|
|
1624
|
-
const values = accept.split(',');
|
|
1625
|
-
return values.some(value => value.trim().toLowerCase() === 'text/html');
|
|
1626
|
-
}
|
|
861
|
+
// bazel-out/k8-fastbuild-ST-2e5f3376adb5/bin/packages/service-worker/worker/src/app-version.mjs
|
|
862
|
+
var BACKWARDS_COMPATIBILITY_NAVIGATION_URLS = [
|
|
863
|
+
{ positive: true, regex: "^/.*$" },
|
|
864
|
+
{ positive: false, regex: "^/.*\\.[^/]*$" },
|
|
865
|
+
{ positive: false, regex: "^/.*__" }
|
|
866
|
+
];
|
|
867
|
+
var AppVersion = class {
|
|
868
|
+
constructor(scope2, adapter2, database, idle, debugHandler, manifest, manifestHash) {
|
|
869
|
+
this.scope = scope2;
|
|
870
|
+
this.adapter = adapter2;
|
|
871
|
+
this.database = database;
|
|
872
|
+
this.debugHandler = debugHandler;
|
|
873
|
+
this.manifest = manifest;
|
|
874
|
+
this.manifestHash = manifestHash;
|
|
875
|
+
this.hashTable = /* @__PURE__ */ new Map();
|
|
876
|
+
this.indexUrl = this.adapter.normalizeUrl(this.manifest.index);
|
|
877
|
+
this._okay = true;
|
|
878
|
+
Object.keys(manifest.hashTable).forEach((url) => {
|
|
879
|
+
this.hashTable.set(adapter2.normalizeUrl(url), manifest.hashTable[url]);
|
|
880
|
+
});
|
|
881
|
+
const assetCacheNamePrefix = `${manifestHash}:assets`;
|
|
882
|
+
this.assetGroups = (manifest.assetGroups || []).map((config) => {
|
|
883
|
+
switch (config.installMode) {
|
|
884
|
+
case "prefetch":
|
|
885
|
+
return new PrefetchAssetGroup(scope2, adapter2, idle, config, this.hashTable, database, assetCacheNamePrefix);
|
|
886
|
+
case "lazy":
|
|
887
|
+
return new LazyAssetGroup(scope2, adapter2, idle, config, this.hashTable, database, assetCacheNamePrefix);
|
|
888
|
+
}
|
|
889
|
+
});
|
|
890
|
+
this.dataGroups = (manifest.dataGroups || []).map((config) => new DataGroup(scope2, adapter2, config, database, debugHandler, `${config.version}:data`));
|
|
891
|
+
manifest.navigationUrls = manifest.navigationUrls || BACKWARDS_COMPATIBILITY_NAVIGATION_URLS;
|
|
892
|
+
const includeUrls = manifest.navigationUrls.filter((spec) => spec.positive);
|
|
893
|
+
const excludeUrls = manifest.navigationUrls.filter((spec) => !spec.positive);
|
|
894
|
+
this.navigationUrls = {
|
|
895
|
+
include: includeUrls.map((spec) => new RegExp(spec.regex)),
|
|
896
|
+
exclude: excludeUrls.map((spec) => new RegExp(spec.regex))
|
|
897
|
+
};
|
|
898
|
+
}
|
|
899
|
+
get okay() {
|
|
900
|
+
return this._okay;
|
|
901
|
+
}
|
|
902
|
+
async initializeFully(updateFrom) {
|
|
903
|
+
try {
|
|
904
|
+
await this.assetGroups.reduce(async (previous, group) => {
|
|
905
|
+
await previous;
|
|
906
|
+
return group.initializeFully(updateFrom);
|
|
907
|
+
}, Promise.resolve());
|
|
908
|
+
} catch (err) {
|
|
909
|
+
this._okay = false;
|
|
910
|
+
throw err;
|
|
911
|
+
}
|
|
912
|
+
}
|
|
913
|
+
async handleFetch(req, event) {
|
|
914
|
+
const asset = await this.assetGroups.reduce(async (potentialResponse, group) => {
|
|
915
|
+
const resp = await potentialResponse;
|
|
916
|
+
if (resp !== null) {
|
|
917
|
+
return resp;
|
|
918
|
+
}
|
|
919
|
+
return group.handleFetch(req, event);
|
|
920
|
+
}, Promise.resolve(null));
|
|
921
|
+
if (asset !== null) {
|
|
922
|
+
return asset;
|
|
923
|
+
}
|
|
924
|
+
const data = await this.dataGroups.reduce(async (potentialResponse, group) => {
|
|
925
|
+
const resp = await potentialResponse;
|
|
926
|
+
if (resp !== null) {
|
|
927
|
+
return resp;
|
|
928
|
+
}
|
|
929
|
+
return group.handleFetch(req, event);
|
|
930
|
+
}, Promise.resolve(null));
|
|
931
|
+
if (data !== null) {
|
|
932
|
+
return data;
|
|
933
|
+
}
|
|
934
|
+
if (this.adapter.normalizeUrl(req.url) !== this.indexUrl && this.isNavigationRequest(req)) {
|
|
935
|
+
if (this.manifest.navigationRequestStrategy === "freshness") {
|
|
936
|
+
try {
|
|
937
|
+
return await this.scope.fetch(req);
|
|
938
|
+
} catch (e) {
|
|
939
|
+
}
|
|
940
|
+
}
|
|
941
|
+
return this.handleFetch(this.adapter.newRequest(this.indexUrl), event);
|
|
942
|
+
}
|
|
943
|
+
return null;
|
|
944
|
+
}
|
|
945
|
+
isNavigationRequest(req) {
|
|
946
|
+
if (req.mode !== "navigate") {
|
|
947
|
+
return false;
|
|
948
|
+
}
|
|
949
|
+
if (!this.acceptsTextHtml(req)) {
|
|
950
|
+
return false;
|
|
951
|
+
}
|
|
952
|
+
const urlPrefix = this.scope.registration.scope.replace(/\/$/, "");
|
|
953
|
+
const url = req.url.startsWith(urlPrefix) ? req.url.substr(urlPrefix.length) : req.url;
|
|
954
|
+
const urlWithoutQueryOrHash = url.replace(/[?#].*$/, "");
|
|
955
|
+
return this.navigationUrls.include.some((regex) => regex.test(urlWithoutQueryOrHash)) && !this.navigationUrls.exclude.some((regex) => regex.test(urlWithoutQueryOrHash));
|
|
956
|
+
}
|
|
957
|
+
async lookupResourceWithHash(url, hash) {
|
|
958
|
+
if (!this.hashTable.has(url)) {
|
|
959
|
+
return null;
|
|
960
|
+
}
|
|
961
|
+
if (this.hashTable.get(url) !== hash) {
|
|
962
|
+
return null;
|
|
963
|
+
}
|
|
964
|
+
const cacheState = await this.lookupResourceWithoutHash(url);
|
|
965
|
+
return cacheState && cacheState.response;
|
|
966
|
+
}
|
|
967
|
+
lookupResourceWithoutHash(url) {
|
|
968
|
+
return this.assetGroups.reduce(async (potentialResponse, group) => {
|
|
969
|
+
const resp = await potentialResponse;
|
|
970
|
+
if (resp !== null) {
|
|
971
|
+
return resp;
|
|
972
|
+
}
|
|
973
|
+
return group.fetchFromCacheOnly(url);
|
|
974
|
+
}, Promise.resolve(null));
|
|
1627
975
|
}
|
|
976
|
+
previouslyCachedResources() {
|
|
977
|
+
return this.assetGroups.reduce(async (resources, group) => (await resources).concat(await group.unhashedResources()), Promise.resolve([]));
|
|
978
|
+
}
|
|
979
|
+
async recentCacheStatus(url) {
|
|
980
|
+
return this.assetGroups.reduce(async (current, group) => {
|
|
981
|
+
const status = await current;
|
|
982
|
+
if (status === UpdateCacheStatus.CACHED) {
|
|
983
|
+
return status;
|
|
984
|
+
}
|
|
985
|
+
const groupStatus = await group.cacheStatus(url);
|
|
986
|
+
if (groupStatus === UpdateCacheStatus.NOT_CACHED) {
|
|
987
|
+
return status;
|
|
988
|
+
}
|
|
989
|
+
return groupStatus;
|
|
990
|
+
}, Promise.resolve(UpdateCacheStatus.NOT_CACHED));
|
|
991
|
+
}
|
|
992
|
+
async getCacheNames() {
|
|
993
|
+
const allGroupCacheNames = await Promise.all([
|
|
994
|
+
...this.assetGroups.map((group) => group.getCacheNames()),
|
|
995
|
+
...this.dataGroups.map((group) => group.getCacheNames())
|
|
996
|
+
]);
|
|
997
|
+
return [].concat(...allGroupCacheNames);
|
|
998
|
+
}
|
|
999
|
+
get appData() {
|
|
1000
|
+
return this.manifest.appData || null;
|
|
1001
|
+
}
|
|
1002
|
+
acceptsTextHtml(req) {
|
|
1003
|
+
const accept = req.headers.get("Accept");
|
|
1004
|
+
if (accept === null) {
|
|
1005
|
+
return false;
|
|
1006
|
+
}
|
|
1007
|
+
const values = accept.split(",");
|
|
1008
|
+
return values.some((value) => value.trim().toLowerCase() === "text/html");
|
|
1009
|
+
}
|
|
1010
|
+
};
|
|
1628
1011
|
|
|
1629
|
-
|
|
1630
|
-
|
|
1631
|
-
|
|
1632
|
-
|
|
1633
|
-
|
|
1634
|
-
|
|
1635
|
-
|
|
1636
|
-
|
|
1637
|
-
|
|
1638
|
-
|
|
1639
|
-
|
|
1640
|
-
|
|
1641
|
-
|
|
1642
|
-
|
|
1643
|
-
|
|
1644
|
-
|
|
1645
|
-
|
|
1646
|
-
// of messages in the log never exceeds 2 * DEBUG_LOG_BUFFER_SIZE.
|
|
1647
|
-
this.debugLogA = [];
|
|
1648
|
-
this.debugLogB = [];
|
|
1649
|
-
}
|
|
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:
|
|
1012
|
+
// bazel-out/k8-fastbuild-ST-2e5f3376adb5/bin/packages/service-worker/worker/src/debug.mjs
|
|
1013
|
+
var SW_VERSION = "13.2.0-next.2";
|
|
1014
|
+
var DEBUG_LOG_BUFFER_SIZE = 100;
|
|
1015
|
+
var DebugHandler = class {
|
|
1016
|
+
constructor(driver, adapter2) {
|
|
1017
|
+
this.driver = driver;
|
|
1018
|
+
this.adapter = adapter2;
|
|
1019
|
+
this.debugLogA = [];
|
|
1020
|
+
this.debugLogB = [];
|
|
1021
|
+
}
|
|
1022
|
+
async handleFetch(req) {
|
|
1023
|
+
const [state, versions, idle] = await Promise.all([
|
|
1024
|
+
this.driver.debugState(),
|
|
1025
|
+
this.driver.debugVersions(),
|
|
1026
|
+
this.driver.debugIdleState()
|
|
1027
|
+
]);
|
|
1028
|
+
const msgState = `NGSW Debug Info:
|
|
1657
1029
|
|
|
1658
1030
|
Driver version: ${SW_VERSION}
|
|
1659
1031
|
Driver state: ${state.state} (${state.why})
|
|
1660
|
-
Latest manifest hash: ${state.latestHash ||
|
|
1032
|
+
Latest manifest hash: ${state.latestHash || "none"}
|
|
1661
1033
|
Last update check: ${this.since(state.lastUpdateCheck)}`;
|
|
1662
|
-
|
|
1663
|
-
.map(version => `=== Version ${version.hash} ===
|
|
1034
|
+
const msgVersions = versions.map((version) => `=== Version ${version.hash} ===
|
|
1664
1035
|
|
|
1665
|
-
Clients: ${version.clients.join(
|
|
1666
|
-
|
|
1667
|
-
const msgIdle = `=== Idle Task Queue ===
|
|
1036
|
+
Clients: ${version.clients.join(", ")}`).join("\n\n");
|
|
1037
|
+
const msgIdle = `=== Idle Task Queue ===
|
|
1668
1038
|
Last update tick: ${this.since(idle.lastTrigger)}
|
|
1669
1039
|
Last update run: ${this.since(idle.lastRun)}
|
|
1670
1040
|
Task queue:
|
|
1671
|
-
${idle.queue.map(v =>
|
|
1041
|
+
${idle.queue.map((v) => " * " + v).join("\n")}
|
|
1672
1042
|
|
|
1673
1043
|
Debug log:
|
|
1674
1044
|
${this.formatDebugLog(this.debugLogB)}
|
|
1675
1045
|
${this.formatDebugLog(this.debugLogA)}
|
|
1676
1046
|
`;
|
|
1677
|
-
|
|
1047
|
+
return this.adapter.newResponse(`${msgState}
|
|
1678
1048
|
|
|
1679
1049
|
${msgVersions}
|
|
1680
1050
|
|
|
1681
|
-
${msgIdle}`, { headers: this.adapter.newHeaders({
|
|
1682
|
-
}
|
|
1683
|
-
since(time) {
|
|
1684
|
-
if (time === null) {
|
|
1685
|
-
return 'never';
|
|
1686
|
-
}
|
|
1687
|
-
let age = this.adapter.time - time;
|
|
1688
|
-
const days = Math.floor(age / 86400000);
|
|
1689
|
-
age = age % 86400000;
|
|
1690
|
-
const hours = Math.floor(age / 3600000);
|
|
1691
|
-
age = age % 3600000;
|
|
1692
|
-
const minutes = Math.floor(age / 60000);
|
|
1693
|
-
age = age % 60000;
|
|
1694
|
-
const seconds = Math.floor(age / 1000);
|
|
1695
|
-
const millis = age % 1000;
|
|
1696
|
-
return '' + (days > 0 ? `${days}d` : '') + (hours > 0 ? `${hours}h` : '') +
|
|
1697
|
-
(minutes > 0 ? `${minutes}m` : '') + (seconds > 0 ? `${seconds}s` : '') +
|
|
1698
|
-
(millis > 0 ? `${millis}u` : '');
|
|
1699
|
-
}
|
|
1700
|
-
log(value, context = '') {
|
|
1701
|
-
// Rotate the buffers if debugLogA has grown too large.
|
|
1702
|
-
if (this.debugLogA.length === DEBUG_LOG_BUFFER_SIZE) {
|
|
1703
|
-
this.debugLogB = this.debugLogA;
|
|
1704
|
-
this.debugLogA = [];
|
|
1705
|
-
}
|
|
1706
|
-
// Convert errors to string for logging.
|
|
1707
|
-
if (typeof value !== 'string') {
|
|
1708
|
-
value = this.errorToString(value);
|
|
1709
|
-
}
|
|
1710
|
-
// Log the message.
|
|
1711
|
-
this.debugLogA.push({ value, time: this.adapter.time, context });
|
|
1712
|
-
}
|
|
1713
|
-
errorToString(err) {
|
|
1714
|
-
return `${err.name}(${err.message}, ${err.stack})`;
|
|
1715
|
-
}
|
|
1716
|
-
formatDebugLog(log) {
|
|
1717
|
-
return log.map(entry => `[${this.since(entry.time)}] ${entry.value} ${entry.context}`)
|
|
1718
|
-
.join('\n');
|
|
1719
|
-
}
|
|
1051
|
+
${msgIdle}`, { headers: this.adapter.newHeaders({ "Content-Type": "text/plain" }) });
|
|
1720
1052
|
}
|
|
1721
|
-
|
|
1722
|
-
|
|
1723
|
-
|
|
1724
|
-
|
|
1725
|
-
|
|
1726
|
-
|
|
1727
|
-
|
|
1728
|
-
|
|
1729
|
-
|
|
1730
|
-
|
|
1731
|
-
|
|
1732
|
-
|
|
1733
|
-
|
|
1734
|
-
|
|
1735
|
-
this.queue = [];
|
|
1736
|
-
this.scheduled = null;
|
|
1737
|
-
this.empty = Promise.resolve();
|
|
1738
|
-
this.emptyResolve = null;
|
|
1739
|
-
this.lastTrigger = null;
|
|
1740
|
-
this.lastRun = null;
|
|
1741
|
-
this.oldestScheduledAt = null;
|
|
1742
|
-
}
|
|
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;
|
|
1787
|
-
}
|
|
1788
|
-
schedule(desc, run) {
|
|
1789
|
-
this.queue.push({ desc, run });
|
|
1790
|
-
if (this.emptyResolve === null) {
|
|
1791
|
-
this.empty = new Promise(resolve => {
|
|
1792
|
-
this.emptyResolve = resolve;
|
|
1793
|
-
});
|
|
1794
|
-
}
|
|
1795
|
-
if (this.oldestScheduledAt === null) {
|
|
1796
|
-
this.oldestScheduledAt = this.adapter.time;
|
|
1797
|
-
}
|
|
1798
|
-
}
|
|
1799
|
-
get size() {
|
|
1800
|
-
return this.queue.length;
|
|
1801
|
-
}
|
|
1802
|
-
get taskDescriptions() {
|
|
1803
|
-
return this.queue.map(task => task.desc);
|
|
1804
|
-
}
|
|
1053
|
+
since(time) {
|
|
1054
|
+
if (time === null) {
|
|
1055
|
+
return "never";
|
|
1056
|
+
}
|
|
1057
|
+
let age = this.adapter.time - time;
|
|
1058
|
+
const days = Math.floor(age / 864e5);
|
|
1059
|
+
age = age % 864e5;
|
|
1060
|
+
const hours = Math.floor(age / 36e5);
|
|
1061
|
+
age = age % 36e5;
|
|
1062
|
+
const minutes = Math.floor(age / 6e4);
|
|
1063
|
+
age = age % 6e4;
|
|
1064
|
+
const seconds = Math.floor(age / 1e3);
|
|
1065
|
+
const millis = age % 1e3;
|
|
1066
|
+
return (days > 0 ? `${days}d` : "") + (hours > 0 ? `${hours}h` : "") + (minutes > 0 ? `${minutes}m` : "") + (seconds > 0 ? `${seconds}s` : "") + (millis > 0 ? `${millis}u` : "");
|
|
1805
1067
|
}
|
|
1806
|
-
|
|
1807
|
-
|
|
1808
|
-
|
|
1809
|
-
|
|
1810
|
-
|
|
1811
|
-
|
|
1812
|
-
|
|
1813
|
-
|
|
1814
|
-
|
|
1815
|
-
return sha1(JSON.stringify(manifest));
|
|
1068
|
+
log(value, context = "") {
|
|
1069
|
+
if (this.debugLogA.length === DEBUG_LOG_BUFFER_SIZE) {
|
|
1070
|
+
this.debugLogB = this.debugLogA;
|
|
1071
|
+
this.debugLogA = [];
|
|
1072
|
+
}
|
|
1073
|
+
if (typeof value !== "string") {
|
|
1074
|
+
value = this.errorToString(value);
|
|
1075
|
+
}
|
|
1076
|
+
this.debugLogA.push({ value, time: this.adapter.time, context });
|
|
1816
1077
|
}
|
|
1078
|
+
errorToString(err) {
|
|
1079
|
+
return `${err.name}(${err.message}, ${err.stack})`;
|
|
1080
|
+
}
|
|
1081
|
+
formatDebugLog(log) {
|
|
1082
|
+
return log.map((entry) => `[${this.since(entry.time)}] ${entry.value} ${entry.context}`).join("\n");
|
|
1083
|
+
}
|
|
1084
|
+
};
|
|
1817
1085
|
|
|
1818
|
-
|
|
1819
|
-
|
|
1820
|
-
|
|
1821
|
-
|
|
1822
|
-
|
|
1823
|
-
|
|
1824
|
-
|
|
1825
|
-
|
|
1826
|
-
|
|
1827
|
-
|
|
1828
|
-
|
|
1829
|
-
|
|
1086
|
+
// bazel-out/k8-fastbuild-ST-2e5f3376adb5/bin/packages/service-worker/worker/src/idle.mjs
|
|
1087
|
+
var IdleScheduler = class {
|
|
1088
|
+
constructor(adapter2, delay, maxDelay, debug) {
|
|
1089
|
+
this.adapter = adapter2;
|
|
1090
|
+
this.delay = delay;
|
|
1091
|
+
this.maxDelay = maxDelay;
|
|
1092
|
+
this.debug = debug;
|
|
1093
|
+
this.queue = [];
|
|
1094
|
+
this.scheduled = null;
|
|
1095
|
+
this.empty = Promise.resolve();
|
|
1096
|
+
this.emptyResolve = null;
|
|
1097
|
+
this.lastTrigger = null;
|
|
1098
|
+
this.lastRun = null;
|
|
1099
|
+
this.oldestScheduledAt = null;
|
|
1100
|
+
}
|
|
1101
|
+
async trigger() {
|
|
1102
|
+
var _a;
|
|
1103
|
+
this.lastTrigger = this.adapter.time;
|
|
1104
|
+
if (this.queue.length === 0) {
|
|
1105
|
+
return;
|
|
1106
|
+
}
|
|
1107
|
+
if (this.scheduled !== null) {
|
|
1108
|
+
this.scheduled.cancel = true;
|
|
1109
|
+
}
|
|
1110
|
+
const scheduled = {
|
|
1111
|
+
cancel: false
|
|
1112
|
+
};
|
|
1113
|
+
this.scheduled = scheduled;
|
|
1114
|
+
const now = this.adapter.time;
|
|
1115
|
+
const maxDelay = Math.max(0, ((_a = this.oldestScheduledAt) != null ? _a : now) + this.maxDelay - now);
|
|
1116
|
+
const delay = Math.min(maxDelay, this.delay);
|
|
1117
|
+
await this.adapter.timeout(delay);
|
|
1118
|
+
if (scheduled.cancel) {
|
|
1119
|
+
return;
|
|
1120
|
+
}
|
|
1121
|
+
this.scheduled = null;
|
|
1122
|
+
await this.execute();
|
|
1830
1123
|
}
|
|
1124
|
+
async execute() {
|
|
1125
|
+
this.lastRun = this.adapter.time;
|
|
1126
|
+
while (this.queue.length > 0) {
|
|
1127
|
+
const queue = this.queue;
|
|
1128
|
+
this.queue = [];
|
|
1129
|
+
await queue.reduce(async (previous, task) => {
|
|
1130
|
+
await previous;
|
|
1131
|
+
try {
|
|
1132
|
+
await task.run();
|
|
1133
|
+
} catch (err) {
|
|
1134
|
+
this.debug.log(err, `while running idle task ${task.desc}`);
|
|
1135
|
+
}
|
|
1136
|
+
}, Promise.resolve());
|
|
1137
|
+
}
|
|
1138
|
+
if (this.emptyResolve !== null) {
|
|
1139
|
+
this.emptyResolve();
|
|
1140
|
+
this.emptyResolve = null;
|
|
1141
|
+
}
|
|
1142
|
+
this.empty = Promise.resolve();
|
|
1143
|
+
this.oldestScheduledAt = null;
|
|
1144
|
+
}
|
|
1145
|
+
schedule(desc, run) {
|
|
1146
|
+
this.queue.push({ desc, run });
|
|
1147
|
+
if (this.emptyResolve === null) {
|
|
1148
|
+
this.empty = new Promise((resolve) => {
|
|
1149
|
+
this.emptyResolve = resolve;
|
|
1150
|
+
});
|
|
1151
|
+
}
|
|
1152
|
+
if (this.oldestScheduledAt === null) {
|
|
1153
|
+
this.oldestScheduledAt = this.adapter.time;
|
|
1154
|
+
}
|
|
1155
|
+
}
|
|
1156
|
+
get size() {
|
|
1157
|
+
return this.queue.length;
|
|
1158
|
+
}
|
|
1159
|
+
get taskDescriptions() {
|
|
1160
|
+
return this.queue.map((task) => task.desc);
|
|
1161
|
+
}
|
|
1162
|
+
};
|
|
1831
1163
|
|
|
1832
|
-
|
|
1833
|
-
|
|
1834
|
-
|
|
1835
|
-
|
|
1836
|
-
|
|
1837
|
-
|
|
1838
|
-
|
|
1839
|
-
|
|
1840
|
-
|
|
1841
|
-
|
|
1842
|
-
|
|
1843
|
-
|
|
1844
|
-
|
|
1845
|
-
|
|
1846
|
-
|
|
1847
|
-
|
|
1848
|
-
|
|
1849
|
-
|
|
1850
|
-
|
|
1851
|
-
|
|
1852
|
-
|
|
1853
|
-
|
|
1854
|
-
|
|
1855
|
-
|
|
1856
|
-
|
|
1857
|
-
|
|
1858
|
-
|
|
1859
|
-
|
|
1860
|
-
|
|
1861
|
-
|
|
1862
|
-
|
|
1863
|
-
|
|
1864
|
-
|
|
1865
|
-
|
|
1866
|
-
|
|
1867
|
-
|
|
1868
|
-
|
|
1869
|
-
|
|
1870
|
-
|
|
1871
|
-
|
|
1872
|
-
|
|
1873
|
-
|
|
1874
|
-
|
|
1875
|
-
|
|
1876
|
-
|
|
1877
|
-
|
|
1878
|
-
|
|
1879
|
-
|
|
1880
|
-
|
|
1881
|
-
|
|
1882
|
-
|
|
1883
|
-
|
|
1884
|
-
|
|
1885
|
-
|
|
1886
|
-
|
|
1887
|
-
|
|
1888
|
-
|
|
1889
|
-
|
|
1890
|
-
|
|
1891
|
-
|
|
1892
|
-
|
|
1893
|
-
|
|
1894
|
-
|
|
1895
|
-
*/
|
|
1896
|
-
this.scheduledNavUpdateCheck = false;
|
|
1897
|
-
/**
|
|
1898
|
-
* Keep track of whether we have logged an invalid `only-if-cached` request.
|
|
1899
|
-
* (See `.onFetch()` for details.)
|
|
1900
|
-
*/
|
|
1901
|
-
this.loggedInvalidOnlyIfCachedRequest = false;
|
|
1902
|
-
this.ngswStatePath = this.adapter.parseUrl('ngsw/state', this.scope.registration.scope).path;
|
|
1903
|
-
// A promise resolving to the control DB table.
|
|
1904
|
-
this.controlTable = this.db.open('control');
|
|
1905
|
-
// The install event is triggered when the service worker is first installed.
|
|
1906
|
-
this.scope.addEventListener('install', (event) => {
|
|
1907
|
-
// SW code updates are separate from application updates, so code updates are
|
|
1908
|
-
// almost as straightforward as restarting the SW. Because of this, it's always
|
|
1909
|
-
// safe to skip waiting until application tabs are closed, and activate the new
|
|
1910
|
-
// SW version immediately.
|
|
1911
|
-
event.waitUntil(this.scope.skipWaiting());
|
|
1912
|
-
});
|
|
1913
|
-
// The activate event is triggered when this version of the service worker is
|
|
1914
|
-
// first activated.
|
|
1915
|
-
this.scope.addEventListener('activate', (event) => {
|
|
1916
|
-
event.waitUntil((async () => {
|
|
1917
|
-
// As above, it's safe to take over from existing clients immediately, since the new SW
|
|
1918
|
-
// version will continue to serve the old application.
|
|
1919
|
-
await this.scope.clients.claim();
|
|
1920
|
-
// Once all clients have been taken over, we can delete caches used by old versions of
|
|
1921
|
-
// `@angular/service-worker`, which are no longer needed. This can happen in the background.
|
|
1922
|
-
this.idle.schedule('activate: cleanup-old-sw-caches', async () => {
|
|
1923
|
-
try {
|
|
1924
|
-
await this.cleanupOldSwCaches();
|
|
1925
|
-
}
|
|
1926
|
-
catch (err) {
|
|
1927
|
-
// Nothing to do - cleanup failed. Just log it.
|
|
1928
|
-
this.debugger.log(err, 'cleanupOldSwCaches @ activate: cleanup-old-sw-caches');
|
|
1929
|
-
}
|
|
1930
|
-
});
|
|
1931
|
-
})());
|
|
1932
|
-
// Rather than wait for the first fetch event, which may not arrive until
|
|
1933
|
-
// the next time the application is loaded, the SW takes advantage of the
|
|
1934
|
-
// activation event to schedule initialization. However, if this were run
|
|
1935
|
-
// in the context of the 'activate' event, waitUntil() here would cause fetch
|
|
1936
|
-
// events to block until initialization completed. Thus, the SW does a
|
|
1937
|
-
// postMessage() to itself, to schedule a new event loop iteration with an
|
|
1938
|
-
// entirely separate event context. The SW will be kept alive by waitUntil()
|
|
1939
|
-
// within that separate context while initialization proceeds, while at the
|
|
1940
|
-
// same time the activation event is allowed to resolve and traffic starts
|
|
1941
|
-
// being served.
|
|
1942
|
-
if (this.scope.registration.active !== null) {
|
|
1943
|
-
this.scope.registration.active.postMessage({ action: 'INITIALIZE' });
|
|
1944
|
-
}
|
|
1945
|
-
});
|
|
1946
|
-
// Handle the fetch, message, and push events.
|
|
1947
|
-
this.scope.addEventListener('fetch', (event) => this.onFetch(event));
|
|
1948
|
-
this.scope.addEventListener('message', (event) => this.onMessage(event));
|
|
1949
|
-
this.scope.addEventListener('push', (event) => this.onPush(event));
|
|
1950
|
-
this.scope.addEventListener('notificationclick', (event) => this.onClick(event));
|
|
1951
|
-
// The debugger generates debug pages in response to debugging requests.
|
|
1952
|
-
this.debugger = new DebugHandler(this, this.adapter);
|
|
1953
|
-
// The IdleScheduler will execute idle tasks after a given delay.
|
|
1954
|
-
this.idle = new IdleScheduler(this.adapter, IDLE_DELAY, MAX_IDLE_DELAY, this.debugger);
|
|
1955
|
-
}
|
|
1956
|
-
/**
|
|
1957
|
-
* The handler for fetch events.
|
|
1958
|
-
*
|
|
1959
|
-
* This is the transition point between the synchronous event handler and the
|
|
1960
|
-
* asynchronous execution that eventually resolves for respondWith() and waitUntil().
|
|
1961
|
-
*/
|
|
1962
|
-
onFetch(event) {
|
|
1963
|
-
const req = event.request;
|
|
1964
|
-
const scopeUrl = this.scope.registration.scope;
|
|
1965
|
-
const requestUrlObj = this.adapter.parseUrl(req.url, scopeUrl);
|
|
1966
|
-
if (req.headers.has('ngsw-bypass') || /[?&]ngsw-bypass(?:[=&]|$)/i.test(requestUrlObj.search)) {
|
|
1967
|
-
return;
|
|
1968
|
-
}
|
|
1969
|
-
// The only thing that is served unconditionally is the debug page.
|
|
1970
|
-
if (requestUrlObj.path === this.ngswStatePath) {
|
|
1971
|
-
// Allow the debugger to handle the request, but don't affect SW state in any other way.
|
|
1972
|
-
event.respondWith(this.debugger.handleFetch(req));
|
|
1973
|
-
return;
|
|
1974
|
-
}
|
|
1975
|
-
// If the SW is in a broken state where it's not safe to handle requests at all,
|
|
1976
|
-
// returning causes the request to fall back on the network. This is preferred over
|
|
1977
|
-
// `respondWith(fetch(req))` because the latter still shows in DevTools that the
|
|
1978
|
-
// request was handled by the SW.
|
|
1979
|
-
if (this.state === DriverReadyState.SAFE_MODE) {
|
|
1980
|
-
// Even though the worker is in safe mode, idle tasks still need to happen so
|
|
1981
|
-
// things like update checks, etc. can take place.
|
|
1982
|
-
event.waitUntil(this.idle.trigger());
|
|
1983
|
-
return;
|
|
1984
|
-
}
|
|
1985
|
-
// Although "passive mixed content" (like images) only produces a warning without a
|
|
1986
|
-
// ServiceWorker, fetching it via a ServiceWorker results in an error. Let such requests be
|
|
1987
|
-
// handled by the browser, since handling with the ServiceWorker would fail anyway.
|
|
1988
|
-
// See https://github.com/angular/angular/issues/23012#issuecomment-376430187 for more details.
|
|
1989
|
-
if (requestUrlObj.origin.startsWith('http:') && scopeUrl.startsWith('https:')) {
|
|
1990
|
-
// Still, log the incident for debugging purposes.
|
|
1991
|
-
this.debugger.log(`Ignoring passive mixed content request: Driver.fetch(${req.url})`);
|
|
1992
|
-
return;
|
|
1993
|
-
}
|
|
1994
|
-
// When opening DevTools in Chrome, a request is made for the current URL (and possibly related
|
|
1995
|
-
// resources, e.g. scripts) with `cache: 'only-if-cached'` and `mode: 'no-cors'`. These request
|
|
1996
|
-
// will eventually fail, because `only-if-cached` is only allowed to be used with
|
|
1997
|
-
// `mode: 'same-origin'`.
|
|
1998
|
-
// This is likely a bug in Chrome DevTools. Avoid handling such requests.
|
|
1999
|
-
// (See also https://github.com/angular/angular/issues/22362.)
|
|
2000
|
-
// TODO(gkalpak): Remove once no longer necessary (i.e. fixed in Chrome DevTools).
|
|
2001
|
-
if (req.cache === 'only-if-cached' && req.mode !== 'same-origin') {
|
|
2002
|
-
// Log the incident only the first time it happens, to avoid spamming the logs.
|
|
2003
|
-
if (!this.loggedInvalidOnlyIfCachedRequest) {
|
|
2004
|
-
this.loggedInvalidOnlyIfCachedRequest = true;
|
|
2005
|
-
this.debugger.log(`Ignoring invalid request: 'only-if-cached' can be set only with 'same-origin' mode`, `Driver.fetch(${req.url}, cache: ${req.cache}, mode: ${req.mode})`);
|
|
2006
|
-
}
|
|
2007
|
-
return;
|
|
2008
|
-
}
|
|
2009
|
-
// Past this point, the SW commits to handling the request itself. This could still
|
|
2010
|
-
// fail (and result in `state` being set to `SAFE_MODE`), but even in that case the
|
|
2011
|
-
// SW will still deliver a response.
|
|
2012
|
-
event.respondWith(this.handleFetch(event));
|
|
2013
|
-
}
|
|
2014
|
-
/**
|
|
2015
|
-
* The handler for message events.
|
|
2016
|
-
*/
|
|
2017
|
-
onMessage(event) {
|
|
2018
|
-
// Ignore message events when the SW is in safe mode, for now.
|
|
2019
|
-
if (this.state === DriverReadyState.SAFE_MODE) {
|
|
2020
|
-
return;
|
|
2021
|
-
}
|
|
2022
|
-
// If the message doesn't have the expected signature, ignore it.
|
|
2023
|
-
const data = event.data;
|
|
2024
|
-
if (!data || !data.action) {
|
|
2025
|
-
return;
|
|
2026
|
-
}
|
|
2027
|
-
event.waitUntil((async () => {
|
|
2028
|
-
// Initialization is the only event which is sent directly from the SW to itself, and thus
|
|
2029
|
-
// `event.source` is not a `Client`. Handle it here, before the check for `Client` sources.
|
|
2030
|
-
if (data.action === 'INITIALIZE') {
|
|
2031
|
-
return this.ensureInitialized(event);
|
|
2032
|
-
}
|
|
2033
|
-
// Only messages from true clients are accepted past this point.
|
|
2034
|
-
// This is essentially a typecast.
|
|
2035
|
-
if (!this.adapter.isClient(event.source)) {
|
|
2036
|
-
return;
|
|
2037
|
-
}
|
|
2038
|
-
// Handle the message and keep the SW alive until it's handled.
|
|
2039
|
-
await this.ensureInitialized(event);
|
|
2040
|
-
await this.handleMessage(data, event.source);
|
|
2041
|
-
})());
|
|
2042
|
-
}
|
|
2043
|
-
onPush(msg) {
|
|
2044
|
-
// Push notifications without data have no effect.
|
|
2045
|
-
if (!msg.data) {
|
|
2046
|
-
return;
|
|
2047
|
-
}
|
|
2048
|
-
// Handle the push and keep the SW alive until it's handled.
|
|
2049
|
-
msg.waitUntil(this.handlePush(msg.data.json()));
|
|
2050
|
-
}
|
|
2051
|
-
onClick(event) {
|
|
2052
|
-
// Handle the click event and keep the SW alive until it's handled.
|
|
2053
|
-
event.waitUntil(this.handleClick(event.notification, event.action));
|
|
2054
|
-
}
|
|
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
|
-
}
|
|
2079
|
-
}
|
|
2080
|
-
async handleMessage(msg, from) {
|
|
2081
|
-
if (isMsgCheckForUpdates(msg)) {
|
|
2082
|
-
const action = this.checkForUpdate();
|
|
2083
|
-
await this.completeOperation(from, action, msg.nonce);
|
|
2084
|
-
}
|
|
2085
|
-
else if (isMsgActivateUpdate(msg)) {
|
|
2086
|
-
const action = this.updateClient(from);
|
|
2087
|
-
await this.completeOperation(from, action, msg.nonce);
|
|
2088
|
-
}
|
|
2089
|
-
}
|
|
2090
|
-
async handlePush(data) {
|
|
2091
|
-
await this.broadcast({
|
|
2092
|
-
type: 'PUSH',
|
|
2093
|
-
data,
|
|
2094
|
-
});
|
|
2095
|
-
if (!data.notification || !data.notification.title) {
|
|
2096
|
-
return;
|
|
2097
|
-
}
|
|
2098
|
-
const desc = data.notification;
|
|
2099
|
-
let options = {};
|
|
2100
|
-
NOTIFICATION_OPTION_NAMES.filter(name => desc.hasOwnProperty(name))
|
|
2101
|
-
.forEach(name => options[name] = desc[name]);
|
|
2102
|
-
await this.scope.registration.showNotification(desc['title'], options);
|
|
2103
|
-
}
|
|
2104
|
-
async handleClick(notification, action) {
|
|
2105
|
-
notification.close();
|
|
2106
|
-
const options = {};
|
|
2107
|
-
// The filter uses `name in notification` because the properties are on the prototype so
|
|
2108
|
-
// hasOwnProperty does not work here
|
|
2109
|
-
NOTIFICATION_OPTION_NAMES.filter(name => name in notification)
|
|
2110
|
-
.forEach(name => options[name] = notification[name]);
|
|
2111
|
-
const notificationAction = action === '' || action === undefined ? 'default' : action;
|
|
2112
|
-
const onActionClick = notification?.data?.onActionClick?.[notificationAction];
|
|
2113
|
-
const urlToOpen = new URL(onActionClick?.url ?? '', this.scope.registration.scope).href;
|
|
2114
|
-
switch (onActionClick?.operation) {
|
|
2115
|
-
case 'openWindow':
|
|
2116
|
-
await this.scope.clients.openWindow(urlToOpen);
|
|
2117
|
-
break;
|
|
2118
|
-
case 'focusLastFocusedOrOpen': {
|
|
2119
|
-
let matchingClient = await this.getLastFocusedMatchingClient(this.scope);
|
|
2120
|
-
if (matchingClient) {
|
|
2121
|
-
await matchingClient?.focus();
|
|
2122
|
-
}
|
|
2123
|
-
else {
|
|
2124
|
-
await this.scope.clients.openWindow(urlToOpen);
|
|
2125
|
-
}
|
|
2126
|
-
break;
|
|
2127
|
-
}
|
|
2128
|
-
case 'navigateLastFocusedOrOpen': {
|
|
2129
|
-
let matchingClient = await this.getLastFocusedMatchingClient(this.scope);
|
|
2130
|
-
if (matchingClient) {
|
|
2131
|
-
matchingClient = await matchingClient.navigate(urlToOpen);
|
|
2132
|
-
await matchingClient?.focus();
|
|
2133
|
-
}
|
|
2134
|
-
else {
|
|
2135
|
-
await this.scope.clients.openWindow(urlToOpen);
|
|
2136
|
-
}
|
|
2137
|
-
break;
|
|
2138
|
-
}
|
|
2139
|
-
}
|
|
2140
|
-
await this.broadcast({
|
|
2141
|
-
type: 'NOTIFICATION_CLICK',
|
|
2142
|
-
data: { action, notification: options },
|
|
2143
|
-
});
|
|
2144
|
-
}
|
|
2145
|
-
async getLastFocusedMatchingClient(scope) {
|
|
2146
|
-
const windowClients = await scope.clients.matchAll({ type: 'window' });
|
|
2147
|
-
// As per the spec windowClients are `sorted in the most recently focused order`
|
|
2148
|
-
return windowClients[0];
|
|
2149
|
-
}
|
|
2150
|
-
async completeOperation(client, promise, nonce) {
|
|
2151
|
-
const response = { type: 'OPERATION_COMPLETED', nonce };
|
|
2152
|
-
try {
|
|
2153
|
-
client.postMessage({
|
|
2154
|
-
...response,
|
|
2155
|
-
result: await promise,
|
|
2156
|
-
});
|
|
2157
|
-
}
|
|
2158
|
-
catch (e) {
|
|
2159
|
-
client.postMessage({
|
|
2160
|
-
...response,
|
|
2161
|
-
error: e.toString(),
|
|
2162
|
-
});
|
|
2163
|
-
}
|
|
2164
|
-
}
|
|
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 false;
|
|
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);
|
|
2192
|
-
return true;
|
|
2193
|
-
}
|
|
2194
|
-
async handleFetch(event) {
|
|
2195
|
-
try {
|
|
2196
|
-
// Ensure the SW instance has been initialized.
|
|
2197
|
-
await this.ensureInitialized(event);
|
|
2198
|
-
}
|
|
2199
|
-
catch {
|
|
2200
|
-
// Since the SW is already committed to responding to the currently active request,
|
|
2201
|
-
// respond with a network fetch.
|
|
2202
|
-
return this.safeFetch(event.request);
|
|
2203
|
-
}
|
|
2204
|
-
// On navigation requests, check for new updates.
|
|
2205
|
-
if (event.request.mode === 'navigate' && !this.scheduledNavUpdateCheck) {
|
|
2206
|
-
this.scheduledNavUpdateCheck = true;
|
|
2207
|
-
this.idle.schedule('check-updates-on-navigation', async () => {
|
|
2208
|
-
this.scheduledNavUpdateCheck = false;
|
|
2209
|
-
await this.checkForUpdate();
|
|
2210
|
-
});
|
|
2211
|
-
}
|
|
2212
|
-
// Decide which version of the app to use to serve this request. This is asynchronous as in
|
|
2213
|
-
// some cases, a record will need to be written to disk about the assignment that is made.
|
|
2214
|
-
const appVersion = await this.assignVersion(event);
|
|
2215
|
-
let res = null;
|
|
2216
|
-
try {
|
|
2217
|
-
if (appVersion !== null) {
|
|
2218
|
-
try {
|
|
2219
|
-
// Handle the request. First try the AppVersion. If that doesn't work, fall back on the
|
|
2220
|
-
// network.
|
|
2221
|
-
res = await appVersion.handleFetch(event.request, event);
|
|
2222
|
-
}
|
|
2223
|
-
catch (err) {
|
|
2224
|
-
if (err.isUnrecoverableState) {
|
|
2225
|
-
await this.notifyClientsAboutUnrecoverableState(appVersion, err.message);
|
|
2226
|
-
}
|
|
2227
|
-
if (err.isCritical) {
|
|
2228
|
-
// Something went wrong with handling the request from this version.
|
|
2229
|
-
this.debugger.log(err, `Driver.handleFetch(version: ${appVersion.manifestHash})`);
|
|
2230
|
-
await this.versionFailed(appVersion, err);
|
|
2231
|
-
return this.safeFetch(event.request);
|
|
2232
|
-
}
|
|
2233
|
-
throw err;
|
|
2234
|
-
}
|
|
2235
|
-
}
|
|
2236
|
-
// The response will be `null` only if no `AppVersion` can be assigned to the request or if
|
|
2237
|
-
// the assigned `AppVersion`'s manifest doesn't specify what to do about the request.
|
|
2238
|
-
// In that case, just fall back on the network.
|
|
2239
|
-
if (res === null) {
|
|
2240
|
-
return this.safeFetch(event.request);
|
|
2241
|
-
}
|
|
2242
|
-
// The `AppVersion` returned a usable response, so return it.
|
|
2243
|
-
return res;
|
|
2244
|
-
}
|
|
2245
|
-
finally {
|
|
2246
|
-
// Trigger the idle scheduling system. The Promise returned by `trigger()` will resolve after
|
|
2247
|
-
// a specific amount of time has passed. If `trigger()` hasn't been called again by then (e.g.
|
|
2248
|
-
// on a subsequent request), the idle task queue will be drained and the `Promise` won't
|
|
2249
|
-
// be resolved until that operation is complete as well.
|
|
2250
|
-
event.waitUntil(this.idle.trigger());
|
|
2251
|
-
}
|
|
2252
|
-
}
|
|
2253
|
-
/**
|
|
2254
|
-
* Attempt to quickly reach a state where it's safe to serve responses.
|
|
2255
|
-
*/
|
|
2256
|
-
async initialize() {
|
|
2257
|
-
// On initialization, all of the serialized state is read out of the 'control'
|
|
2258
|
-
// table. This includes:
|
|
2259
|
-
// - map of hashes to manifests of currently loaded application versions
|
|
2260
|
-
// - map of client IDs to their pinned versions
|
|
2261
|
-
// - record of the most recently fetched manifest hash
|
|
2262
|
-
//
|
|
2263
|
-
// If these values don't exist in the DB, then this is the either the first time
|
|
2264
|
-
// the SW has run or the DB state has been wiped or is inconsistent. In that case,
|
|
2265
|
-
// load a fresh copy of the manifest and reset the state from scratch.
|
|
2266
|
-
const table = await this.controlTable;
|
|
2267
|
-
// Attempt to load the needed state from the DB. If this fails, the catch {} block
|
|
2268
|
-
// will populate these variables with freshly constructed values.
|
|
2269
|
-
let manifests, assignments, latest;
|
|
2270
|
-
try {
|
|
2271
|
-
// Read them from the DB simultaneously.
|
|
2272
|
-
[manifests, assignments, latest] = await Promise.all([
|
|
2273
|
-
table.read('manifests'),
|
|
2274
|
-
table.read('assignments'),
|
|
2275
|
-
table.read('latest'),
|
|
2276
|
-
]);
|
|
2277
|
-
// Make sure latest manifest is correctly installed. If not (e.g. corrupted data),
|
|
2278
|
-
// it could stay locked in EXISTING_CLIENTS_ONLY or SAFE_MODE state.
|
|
2279
|
-
if (!this.versions.has(latest.latest) && !manifests.hasOwnProperty(latest.latest)) {
|
|
2280
|
-
this.debugger.log(`Missing manifest for latest version hash ${latest.latest}`, 'initialize: read from DB');
|
|
2281
|
-
throw new Error(`Missing manifest for latest hash ${latest.latest}`);
|
|
2282
|
-
}
|
|
2283
|
-
// Successfully loaded from saved state. This implies a manifest exists, so
|
|
2284
|
-
// the update check needs to happen in the background.
|
|
2285
|
-
this.idle.schedule('init post-load (update)', async () => {
|
|
2286
|
-
await this.checkForUpdate();
|
|
2287
|
-
});
|
|
2288
|
-
}
|
|
2289
|
-
catch (_) {
|
|
2290
|
-
// Something went wrong. Try to start over by fetching a new manifest from the
|
|
2291
|
-
// server and building up an empty initial state.
|
|
2292
|
-
const manifest = await this.fetchLatestManifest();
|
|
2293
|
-
const hash = hashManifest(manifest);
|
|
2294
|
-
manifests = { [hash]: manifest };
|
|
2295
|
-
assignments = {};
|
|
2296
|
-
latest = { latest: hash };
|
|
2297
|
-
// Save the initial state to the DB.
|
|
2298
|
-
await Promise.all([
|
|
2299
|
-
table.write('manifests', manifests),
|
|
2300
|
-
table.write('assignments', assignments),
|
|
2301
|
-
table.write('latest', latest),
|
|
2302
|
-
]);
|
|
2303
|
-
}
|
|
2304
|
-
// At this point, either the state has been loaded successfully, or fresh state
|
|
2305
|
-
// with a new copy of the manifest has been produced. At this point, the `Driver`
|
|
2306
|
-
// can have its internals hydrated from the state.
|
|
2307
|
-
// Schedule cleaning up obsolete caches in the background.
|
|
2308
|
-
this.idle.schedule('init post-load (cleanup)', async () => {
|
|
2309
|
-
await this.cleanupCaches();
|
|
2310
|
-
});
|
|
2311
|
-
// Initialize the `versions` map by setting each hash to a new `AppVersion` instance
|
|
2312
|
-
// for that manifest.
|
|
2313
|
-
Object.keys(manifests).forEach((hash) => {
|
|
2314
|
-
const manifest = manifests[hash];
|
|
2315
|
-
// If the manifest is newly initialized, an AppVersion may have already been
|
|
2316
|
-
// created for it.
|
|
2317
|
-
if (!this.versions.has(hash)) {
|
|
2318
|
-
this.versions.set(hash, new AppVersion(this.scope, this.adapter, this.db, this.idle, this.debugger, manifest, hash));
|
|
2319
|
-
}
|
|
2320
|
-
});
|
|
2321
|
-
// Map each client ID to its associated hash. Along the way, verify that the hash
|
|
2322
|
-
// is still valid for that client ID. It should not be possible for a client to
|
|
2323
|
-
// still be associated with a hash that was since removed from the state.
|
|
2324
|
-
Object.keys(assignments).forEach((clientId) => {
|
|
2325
|
-
const hash = assignments[clientId];
|
|
2326
|
-
if (this.versions.has(hash)) {
|
|
2327
|
-
this.clientVersionMap.set(clientId, hash);
|
|
2328
|
-
}
|
|
2329
|
-
else {
|
|
2330
|
-
this.clientVersionMap.set(clientId, latest.latest);
|
|
2331
|
-
this.debugger.log(`Unknown version ${hash} mapped for client ${clientId}, using latest instead`, `initialize: map assignments`);
|
|
2332
|
-
}
|
|
2333
|
-
});
|
|
2334
|
-
// Set the latest version.
|
|
2335
|
-
this.latestHash = latest.latest;
|
|
2336
|
-
// Finally, assert that the latest version is in fact loaded.
|
|
2337
|
-
if (!this.versions.has(latest.latest)) {
|
|
2338
|
-
throw new Error(`Invariant violated (initialize): latest hash ${latest.latest} has no known manifest`);
|
|
2339
|
-
}
|
|
2340
|
-
// Finally, wait for the scheduling of initialization of all versions in the
|
|
2341
|
-
// manifest. Ordinarily this just schedules the initializations to happen during
|
|
2342
|
-
// the next idle period, but in development mode this might actually wait for the
|
|
2343
|
-
// full initialization.
|
|
2344
|
-
// If any of these initializations fail, versionFailed() will be called either
|
|
2345
|
-
// synchronously or asynchronously to handle the failure and re-map clients.
|
|
2346
|
-
await Promise.all(Object.keys(manifests).map(async (hash) => {
|
|
2347
|
-
try {
|
|
2348
|
-
// Attempt to schedule or initialize this version. If this operation is
|
|
2349
|
-
// successful, then initialization either succeeded or was scheduled. If
|
|
2350
|
-
// it fails, then full initialization was attempted and failed.
|
|
2351
|
-
await this.scheduleInitialization(this.versions.get(hash));
|
|
2352
|
-
}
|
|
2353
|
-
catch (err) {
|
|
2354
|
-
this.debugger.log(err, `initialize: schedule init of ${hash}`);
|
|
2355
|
-
return false;
|
|
2356
|
-
}
|
|
2357
|
-
}));
|
|
2358
|
-
}
|
|
2359
|
-
lookupVersionByHash(hash, debugName = 'lookupVersionByHash') {
|
|
2360
|
-
// The version should exist, but check just in case.
|
|
2361
|
-
if (!this.versions.has(hash)) {
|
|
2362
|
-
throw new Error(`Invariant violated (${debugName}): want AppVersion for ${hash} but not loaded`);
|
|
2363
|
-
}
|
|
2364
|
-
return this.versions.get(hash);
|
|
2365
|
-
}
|
|
2366
|
-
/**
|
|
2367
|
-
* Decide which version of the manifest to use for the event.
|
|
2368
|
-
*/
|
|
2369
|
-
async assignVersion(event) {
|
|
2370
|
-
// First, check whether the event has a (non empty) client ID. If it does, the version may
|
|
2371
|
-
// already be associated.
|
|
2372
|
-
//
|
|
2373
|
-
// NOTE: For navigation requests, we care about the `resultingClientId`. If it is undefined or
|
|
2374
|
-
// the empty string (which is the case for sub-resource requests), we look at `clientId`.
|
|
2375
|
-
const clientId = event.resultingClientId || event.clientId;
|
|
2376
|
-
if (clientId) {
|
|
2377
|
-
// Check if there is an assigned client id.
|
|
2378
|
-
if (this.clientVersionMap.has(clientId)) {
|
|
2379
|
-
// There is an assignment for this client already.
|
|
2380
|
-
const hash = this.clientVersionMap.get(clientId);
|
|
2381
|
-
let appVersion = this.lookupVersionByHash(hash, 'assignVersion');
|
|
2382
|
-
// Ordinarily, this client would be served from its assigned version. But, if this
|
|
2383
|
-
// request is a navigation request, this client can be updated to the latest
|
|
2384
|
-
// version immediately.
|
|
2385
|
-
if (this.state === DriverReadyState.NORMAL && hash !== this.latestHash &&
|
|
2386
|
-
appVersion.isNavigationRequest(event.request)) {
|
|
2387
|
-
// Update this client to the latest version immediately.
|
|
2388
|
-
if (this.latestHash === null) {
|
|
2389
|
-
throw new Error(`Invariant violated (assignVersion): latestHash was null`);
|
|
2390
|
-
}
|
|
2391
|
-
const client = await this.scope.clients.get(clientId);
|
|
2392
|
-
if (client) {
|
|
2393
|
-
await this.updateClient(client);
|
|
2394
|
-
}
|
|
2395
|
-
appVersion = this.lookupVersionByHash(this.latestHash, 'assignVersion');
|
|
2396
|
-
}
|
|
2397
|
-
// TODO: make sure the version is valid.
|
|
2398
|
-
return appVersion;
|
|
2399
|
-
}
|
|
2400
|
-
else {
|
|
2401
|
-
// This is the first time this client ID has been seen. Whether the SW is in a
|
|
2402
|
-
// state to handle new clients depends on the current readiness state, so check
|
|
2403
|
-
// that first.
|
|
2404
|
-
if (this.state !== DriverReadyState.NORMAL) {
|
|
2405
|
-
// It's not safe to serve new clients in the current state. It's possible that
|
|
2406
|
-
// this is an existing client which has not been mapped yet (see below) but
|
|
2407
|
-
// even if that is the case, it's invalid to make an assignment to a known
|
|
2408
|
-
// invalid version, even if that assignment was previously implicit. Return
|
|
2409
|
-
// undefined here to let the caller know that no assignment is possible at
|
|
2410
|
-
// this time.
|
|
2411
|
-
return null;
|
|
2412
|
-
}
|
|
2413
|
-
// It's safe to handle this request. Two cases apply. Either:
|
|
2414
|
-
// 1) the browser assigned a client ID at the time of the navigation request, and
|
|
2415
|
-
// this is truly the first time seeing this client, or
|
|
2416
|
-
// 2) a navigation request came previously from the same client, but with no client
|
|
2417
|
-
// ID attached. Browsers do this to avoid creating a client under the origin in
|
|
2418
|
-
// the event the navigation request is just redirected.
|
|
2419
|
-
//
|
|
2420
|
-
// In case 1, the latest version can safely be used.
|
|
2421
|
-
// In case 2, the latest version can be used, with the assumption that the previous
|
|
2422
|
-
// navigation request was answered under the same version. This assumption relies
|
|
2423
|
-
// on the fact that it's unlikely an update will come in between the navigation
|
|
2424
|
-
// request and requests for subsequent resources on that page.
|
|
2425
|
-
// First validate the current state.
|
|
2426
|
-
if (this.latestHash === null) {
|
|
2427
|
-
throw new Error(`Invariant violated (assignVersion): latestHash was null`);
|
|
2428
|
-
}
|
|
2429
|
-
// Pin this client ID to the current latest version, indefinitely.
|
|
2430
|
-
this.clientVersionMap.set(clientId, this.latestHash);
|
|
2431
|
-
await this.sync();
|
|
2432
|
-
// Return the latest `AppVersion`.
|
|
2433
|
-
return this.lookupVersionByHash(this.latestHash, 'assignVersion');
|
|
2434
|
-
}
|
|
2435
|
-
}
|
|
2436
|
-
else {
|
|
2437
|
-
// No client ID was associated with the request. This must be a navigation request
|
|
2438
|
-
// for a new client. First check that the SW is accepting new clients.
|
|
2439
|
-
if (this.state !== DriverReadyState.NORMAL) {
|
|
2440
|
-
return null;
|
|
2441
|
-
}
|
|
2442
|
-
// Serve it with the latest version, and assume that the client will actually get
|
|
2443
|
-
// associated with that version on the next request.
|
|
2444
|
-
// First validate the current state.
|
|
2445
|
-
if (this.latestHash === null) {
|
|
2446
|
-
throw new Error(`Invariant violated (assignVersion): latestHash was null`);
|
|
2447
|
-
}
|
|
2448
|
-
// Return the latest `AppVersion`.
|
|
2449
|
-
return this.lookupVersionByHash(this.latestHash, 'assignVersion');
|
|
2450
|
-
}
|
|
2451
|
-
}
|
|
2452
|
-
async fetchLatestManifest(ignoreOfflineError = false) {
|
|
2453
|
-
const res = await this.safeFetch(this.adapter.newRequest('ngsw.json?ngsw-cache-bust=' + Math.random()));
|
|
2454
|
-
if (!res.ok) {
|
|
2455
|
-
if (res.status === 404) {
|
|
2456
|
-
await this.deleteAllCaches();
|
|
2457
|
-
await this.scope.registration.unregister();
|
|
2458
|
-
}
|
|
2459
|
-
else if ((res.status === 503 || res.status === 504) && ignoreOfflineError) {
|
|
2460
|
-
return null;
|
|
2461
|
-
}
|
|
2462
|
-
throw new Error(`Manifest fetch failed! (status: ${res.status})`);
|
|
2463
|
-
}
|
|
2464
|
-
this.lastUpdateCheck = this.adapter.time;
|
|
2465
|
-
return res.json();
|
|
2466
|
-
}
|
|
2467
|
-
async deleteAllCaches() {
|
|
2468
|
-
const cacheNames = await this.adapter.caches.keys();
|
|
2469
|
-
await Promise.all(cacheNames.map(name => this.adapter.caches.delete(name)));
|
|
2470
|
-
}
|
|
2471
|
-
/**
|
|
2472
|
-
* Schedule the SW's attempt to reach a fully prefetched state for the given AppVersion
|
|
2473
|
-
* when the SW is not busy and has connectivity. This returns a Promise which must be
|
|
2474
|
-
* awaited, as under some conditions the AppVersion might be initialized immediately.
|
|
2475
|
-
*/
|
|
2476
|
-
async scheduleInitialization(appVersion) {
|
|
2477
|
-
const initialize = async () => {
|
|
2478
|
-
try {
|
|
2479
|
-
await appVersion.initializeFully();
|
|
2480
|
-
}
|
|
2481
|
-
catch (err) {
|
|
2482
|
-
this.debugger.log(err, `initializeFully for ${appVersion.manifestHash}`);
|
|
2483
|
-
await this.versionFailed(appVersion, err);
|
|
2484
|
-
}
|
|
2485
|
-
};
|
|
2486
|
-
// TODO: better logic for detecting localhost.
|
|
2487
|
-
if (this.scope.registration.scope.indexOf('://localhost') > -1) {
|
|
2488
|
-
return initialize();
|
|
2489
|
-
}
|
|
2490
|
-
this.idle.schedule(`initialization(${appVersion.manifestHash})`, initialize);
|
|
2491
|
-
}
|
|
2492
|
-
async versionFailed(appVersion, err) {
|
|
2493
|
-
// This particular AppVersion is broken. First, find the manifest hash.
|
|
2494
|
-
const broken = Array.from(this.versions.entries()).find(([hash, version]) => version === appVersion);
|
|
2495
|
-
if (broken === undefined) {
|
|
2496
|
-
// This version is no longer in use anyway, so nobody cares.
|
|
2497
|
-
return;
|
|
2498
|
-
}
|
|
2499
|
-
const brokenHash = broken[0];
|
|
2500
|
-
// The specified version is broken and new clients should not be served from it. However, it is
|
|
2501
|
-
// deemed even riskier to switch the existing clients to a different version or to the network.
|
|
2502
|
-
// Therefore, we keep clients on their current version (even if broken) and ensure that no new
|
|
2503
|
-
// clients will be assigned to it.
|
|
2504
|
-
// TODO: notify affected apps.
|
|
2505
|
-
// The action taken depends on whether the broken manifest is the active (latest) or not.
|
|
2506
|
-
// - If the broken version is not the latest, no further action is necessary, since new clients
|
|
2507
|
-
// will be assigned to the latest version anyway.
|
|
2508
|
-
// - If the broken version is the latest, the SW cannot accept new clients (but can continue to
|
|
2509
|
-
// service old ones).
|
|
2510
|
-
if (this.latestHash === brokenHash) {
|
|
2511
|
-
// The latest manifest is broken. This means that new clients are at the mercy of the network,
|
|
2512
|
-
// but caches continue to be valid for previous versions. This is unfortunate but unavoidable.
|
|
2513
|
-
this.state = DriverReadyState.EXISTING_CLIENTS_ONLY;
|
|
2514
|
-
this.stateMessage = `Degraded due to: ${errorToString(err)}`;
|
|
2515
|
-
}
|
|
2516
|
-
}
|
|
2517
|
-
async setupUpdate(manifest, hash) {
|
|
2518
|
-
try {
|
|
2519
|
-
const newVersion = new AppVersion(this.scope, this.adapter, this.db, this.idle, this.debugger, manifest, hash);
|
|
2520
|
-
// Firstly, check if the manifest version is correct.
|
|
2521
|
-
if (manifest.configVersion !== SUPPORTED_CONFIG_VERSION) {
|
|
2522
|
-
await this.deleteAllCaches();
|
|
2523
|
-
await this.scope.registration.unregister();
|
|
2524
|
-
throw new Error(`Invalid config version: expected ${SUPPORTED_CONFIG_VERSION}, got ${manifest.configVersion}.`);
|
|
2525
|
-
}
|
|
2526
|
-
// Cause the new version to become fully initialized. If this fails, then the
|
|
2527
|
-
// version will not be available for use.
|
|
2528
|
-
await newVersion.initializeFully(this);
|
|
2529
|
-
// Install this as an active version of the app.
|
|
2530
|
-
this.versions.set(hash, newVersion);
|
|
2531
|
-
// Future new clients will use this hash as the latest version.
|
|
2532
|
-
this.latestHash = hash;
|
|
2533
|
-
// If we are in `EXISTING_CLIENTS_ONLY` mode (meaning we didn't have a clean copy of the last
|
|
2534
|
-
// latest version), we can now recover to `NORMAL` mode and start accepting new clients.
|
|
2535
|
-
if (this.state === DriverReadyState.EXISTING_CLIENTS_ONLY) {
|
|
2536
|
-
this.state = DriverReadyState.NORMAL;
|
|
2537
|
-
this.stateMessage = '(nominal)';
|
|
2538
|
-
}
|
|
2539
|
-
await this.sync();
|
|
2540
|
-
await this.notifyClientsAboutVersionReady(manifest, hash);
|
|
2541
|
-
}
|
|
2542
|
-
catch (e) {
|
|
2543
|
-
await this.notifyClientsAboutVersionInstallationFailed(manifest, hash, e);
|
|
2544
|
-
throw e;
|
|
2545
|
-
}
|
|
2546
|
-
}
|
|
2547
|
-
async checkForUpdate() {
|
|
2548
|
-
let hash = '(unknown)';
|
|
2549
|
-
try {
|
|
2550
|
-
const manifest = await this.fetchLatestManifest(true);
|
|
2551
|
-
if (manifest === null) {
|
|
2552
|
-
// Client or server offline. Unable to check for updates at this time.
|
|
2553
|
-
// Continue to service clients (existing and new).
|
|
2554
|
-
this.debugger.log('Check for update aborted. (Client or server offline.)');
|
|
2555
|
-
return false;
|
|
2556
|
-
}
|
|
2557
|
-
hash = hashManifest(manifest);
|
|
2558
|
-
// Check whether this is really an update.
|
|
2559
|
-
if (this.versions.has(hash)) {
|
|
2560
|
-
return false;
|
|
2561
|
-
}
|
|
2562
|
-
await this.notifyClientsAboutVersionDetected(manifest, hash);
|
|
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
|
-
}
|
|
2572
|
-
}
|
|
2573
|
-
/**
|
|
2574
|
-
* Synchronize the existing state to the underlying database.
|
|
2575
|
-
*/
|
|
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
|
-
}
|
|
2632
|
-
}
|
|
2633
|
-
/**
|
|
2634
|
-
* Delete caches that were used by older versions of `@angular/service-worker` to avoid running
|
|
2635
|
-
* into storage quota limitations imposed by browsers.
|
|
2636
|
-
* (Since at this point the SW has claimed all clients, it is safe to remove those caches.)
|
|
2637
|
-
*/
|
|
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)));
|
|
2646
|
-
}
|
|
2647
|
-
/**
|
|
2648
|
-
* Determine if a specific version of the given resource is cached anywhere within the SW,
|
|
2649
|
-
* and fetch it if so.
|
|
2650
|
-
*/
|
|
2651
|
-
lookupResourceWithHash(url, hash) {
|
|
2652
|
-
return Array
|
|
2653
|
-
// Scan through the set of all cached versions, valid or otherwise. It's safe to do such
|
|
2654
|
-
// lookups even for invalid versions as the cached version of a resource will have the
|
|
2655
|
-
// same hash regardless.
|
|
2656
|
-
.from(this.versions.values())
|
|
2657
|
-
// Reduce the set of versions to a single potential result. At any point along the
|
|
2658
|
-
// reduction, if a response has already been identified, then pass it through, as no
|
|
2659
|
-
// future operation could change the response. If no response has been found yet, keep
|
|
2660
|
-
// checking versions until one is or until all versions have been exhausted.
|
|
2661
|
-
.reduce(async (prev, version) => {
|
|
2662
|
-
// First, check the previous result. If a non-null result has been found already, just
|
|
2663
|
-
// return it.
|
|
2664
|
-
if (await prev !== null) {
|
|
2665
|
-
return prev;
|
|
2666
|
-
}
|
|
2667
|
-
// No result has been found yet. Try the next `AppVersion`.
|
|
2668
|
-
return version.lookupResourceWithHash(url, hash);
|
|
2669
|
-
}, Promise.resolve(null));
|
|
2670
|
-
}
|
|
2671
|
-
async lookupResourceWithoutHash(url) {
|
|
2672
|
-
await this.initialized;
|
|
2673
|
-
const version = this.versions.get(this.latestHash);
|
|
2674
|
-
return version ? version.lookupResourceWithoutHash(url) : null;
|
|
2675
|
-
}
|
|
2676
|
-
async previouslyCachedResources() {
|
|
2677
|
-
await this.initialized;
|
|
2678
|
-
const version = this.versions.get(this.latestHash);
|
|
2679
|
-
return version ? version.previouslyCachedResources() : [];
|
|
2680
|
-
}
|
|
2681
|
-
async recentCacheStatus(url) {
|
|
2682
|
-
const version = this.versions.get(this.latestHash);
|
|
2683
|
-
return version ? version.recentCacheStatus(url) : UpdateCacheStatus.NOT_CACHED;
|
|
2684
|
-
}
|
|
2685
|
-
mergeHashWithAppData(manifest, hash) {
|
|
2686
|
-
return {
|
|
2687
|
-
hash,
|
|
2688
|
-
appData: manifest.appData,
|
|
2689
|
-
};
|
|
2690
|
-
}
|
|
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 notifyClientsAboutVersionInstallationFailed(manifest, hash, error) {
|
|
2709
|
-
await this.initialized;
|
|
2710
|
-
const clients = await this.scope.clients.matchAll();
|
|
2711
|
-
await Promise.all(clients.map(async (client) => {
|
|
2712
|
-
// Send a notice.
|
|
2713
|
-
client.postMessage({
|
|
2714
|
-
type: 'VERSION_INSTALLATION_FAILED',
|
|
2715
|
-
version: this.mergeHashWithAppData(manifest, hash),
|
|
2716
|
-
error: errorToString(error),
|
|
2717
|
-
});
|
|
2718
|
-
}));
|
|
2719
|
-
}
|
|
2720
|
-
async notifyClientsAboutVersionDetected(manifest, hash) {
|
|
2721
|
-
await this.initialized;
|
|
2722
|
-
const clients = await this.scope.clients.matchAll();
|
|
2723
|
-
await Promise.all(clients.map(async (client) => {
|
|
2724
|
-
// Firstly, determine which version this client is on.
|
|
2725
|
-
const version = this.clientVersionMap.get(client.id);
|
|
2726
|
-
if (version === undefined) {
|
|
2727
|
-
// Unmapped client - assume it's the latest.
|
|
2728
|
-
return;
|
|
2729
|
-
}
|
|
2730
|
-
// Send a notice.
|
|
2731
|
-
client.postMessage({ type: 'VERSION_DETECTED', version: this.mergeHashWithAppData(manifest, hash) });
|
|
2732
|
-
}));
|
|
2733
|
-
}
|
|
2734
|
-
async notifyClientsAboutVersionReady(manifest, hash) {
|
|
2735
|
-
await this.initialized;
|
|
2736
|
-
const clients = await this.scope.clients.matchAll();
|
|
2737
|
-
await Promise.all(clients.map(async (client) => {
|
|
2738
|
-
// Firstly, determine which version this client is on.
|
|
2739
|
-
const version = this.clientVersionMap.get(client.id);
|
|
2740
|
-
if (version === undefined) {
|
|
2741
|
-
// Unmapped client - assume it's the latest.
|
|
2742
|
-
return;
|
|
2743
|
-
}
|
|
2744
|
-
if (version === this.latestHash) {
|
|
2745
|
-
// Client is already on the latest version, no need for a notification.
|
|
2746
|
-
return;
|
|
2747
|
-
}
|
|
2748
|
-
const current = this.versions.get(version);
|
|
2749
|
-
// Send a notice.
|
|
2750
|
-
const notice = {
|
|
2751
|
-
type: 'VERSION_READY',
|
|
2752
|
-
currentVersion: this.mergeHashWithAppData(current.manifest, version),
|
|
2753
|
-
latestVersion: this.mergeHashWithAppData(manifest, hash),
|
|
2754
|
-
};
|
|
2755
|
-
client.postMessage(notice);
|
|
2756
|
-
}));
|
|
2757
|
-
}
|
|
2758
|
-
async broadcast(msg) {
|
|
2759
|
-
const clients = await this.scope.clients.matchAll();
|
|
2760
|
-
clients.forEach(client => {
|
|
2761
|
-
client.postMessage(msg);
|
|
2762
|
-
});
|
|
2763
|
-
}
|
|
2764
|
-
async debugState() {
|
|
2765
|
-
return {
|
|
2766
|
-
state: DriverReadyState[this.state],
|
|
2767
|
-
why: this.stateMessage,
|
|
2768
|
-
latestHash: this.latestHash,
|
|
2769
|
-
lastUpdateCheck: this.lastUpdateCheck,
|
|
2770
|
-
};
|
|
2771
|
-
}
|
|
2772
|
-
async debugVersions() {
|
|
2773
|
-
// Build list of versions.
|
|
2774
|
-
return Array.from(this.versions.keys()).map(hash => {
|
|
2775
|
-
const version = this.versions.get(hash);
|
|
2776
|
-
const clients = Array.from(this.clientVersionMap.entries())
|
|
2777
|
-
.filter(([clientId, version]) => version === hash)
|
|
2778
|
-
.map(([clientId, version]) => clientId);
|
|
2779
|
-
return {
|
|
2780
|
-
hash,
|
|
2781
|
-
manifest: version.manifest,
|
|
2782
|
-
clients,
|
|
2783
|
-
status: '',
|
|
2784
|
-
};
|
|
2785
|
-
});
|
|
2786
|
-
}
|
|
2787
|
-
async debugIdleState() {
|
|
2788
|
-
return {
|
|
2789
|
-
queue: this.idle.taskDescriptions,
|
|
2790
|
-
lastTrigger: this.idle.lastTrigger,
|
|
2791
|
-
lastRun: this.idle.lastRun,
|
|
2792
|
-
};
|
|
2793
|
-
}
|
|
2794
|
-
async safeFetch(req) {
|
|
1164
|
+
// bazel-out/k8-fastbuild-ST-2e5f3376adb5/bin/packages/service-worker/worker/src/manifest.mjs
|
|
1165
|
+
function hashManifest(manifest) {
|
|
1166
|
+
return sha1(JSON.stringify(manifest));
|
|
1167
|
+
}
|
|
1168
|
+
|
|
1169
|
+
// bazel-out/k8-fastbuild-ST-2e5f3376adb5/bin/packages/service-worker/worker/src/msg.mjs
|
|
1170
|
+
function isMsgCheckForUpdates(msg) {
|
|
1171
|
+
return msg.action === "CHECK_FOR_UPDATES";
|
|
1172
|
+
}
|
|
1173
|
+
function isMsgActivateUpdate(msg) {
|
|
1174
|
+
return msg.action === "ACTIVATE_UPDATE";
|
|
1175
|
+
}
|
|
1176
|
+
|
|
1177
|
+
// bazel-out/k8-fastbuild-ST-2e5f3376adb5/bin/packages/service-worker/worker/src/driver.mjs
|
|
1178
|
+
var IDLE_DELAY = 5e3;
|
|
1179
|
+
var MAX_IDLE_DELAY = 3e4;
|
|
1180
|
+
var SUPPORTED_CONFIG_VERSION = 1;
|
|
1181
|
+
var NOTIFICATION_OPTION_NAMES = [
|
|
1182
|
+
"actions",
|
|
1183
|
+
"badge",
|
|
1184
|
+
"body",
|
|
1185
|
+
"data",
|
|
1186
|
+
"dir",
|
|
1187
|
+
"icon",
|
|
1188
|
+
"image",
|
|
1189
|
+
"lang",
|
|
1190
|
+
"renotify",
|
|
1191
|
+
"requireInteraction",
|
|
1192
|
+
"silent",
|
|
1193
|
+
"tag",
|
|
1194
|
+
"timestamp",
|
|
1195
|
+
"title",
|
|
1196
|
+
"vibrate"
|
|
1197
|
+
];
|
|
1198
|
+
var DriverReadyState;
|
|
1199
|
+
(function(DriverReadyState2) {
|
|
1200
|
+
DriverReadyState2[DriverReadyState2["NORMAL"] = 0] = "NORMAL";
|
|
1201
|
+
DriverReadyState2[DriverReadyState2["EXISTING_CLIENTS_ONLY"] = 1] = "EXISTING_CLIENTS_ONLY";
|
|
1202
|
+
DriverReadyState2[DriverReadyState2["SAFE_MODE"] = 2] = "SAFE_MODE";
|
|
1203
|
+
})(DriverReadyState || (DriverReadyState = {}));
|
|
1204
|
+
var Driver = class {
|
|
1205
|
+
constructor(scope2, adapter2, db) {
|
|
1206
|
+
this.scope = scope2;
|
|
1207
|
+
this.adapter = adapter2;
|
|
1208
|
+
this.db = db;
|
|
1209
|
+
this.state = DriverReadyState.NORMAL;
|
|
1210
|
+
this.stateMessage = "(nominal)";
|
|
1211
|
+
this.initialized = null;
|
|
1212
|
+
this.clientVersionMap = /* @__PURE__ */ new Map();
|
|
1213
|
+
this.versions = /* @__PURE__ */ new Map();
|
|
1214
|
+
this.latestHash = null;
|
|
1215
|
+
this.lastUpdateCheck = null;
|
|
1216
|
+
this.scheduledNavUpdateCheck = false;
|
|
1217
|
+
this.loggedInvalidOnlyIfCachedRequest = false;
|
|
1218
|
+
this.ngswStatePath = this.adapter.parseUrl("ngsw/state", this.scope.registration.scope).path;
|
|
1219
|
+
this.controlTable = this.db.open("control");
|
|
1220
|
+
this.scope.addEventListener("install", (event) => {
|
|
1221
|
+
event.waitUntil(this.scope.skipWaiting());
|
|
1222
|
+
});
|
|
1223
|
+
this.scope.addEventListener("activate", (event) => {
|
|
1224
|
+
event.waitUntil((async () => {
|
|
1225
|
+
await this.scope.clients.claim();
|
|
1226
|
+
this.idle.schedule("activate: cleanup-old-sw-caches", async () => {
|
|
2795
1227
|
try {
|
|
2796
|
-
|
|
2797
|
-
}
|
|
2798
|
-
|
|
2799
|
-
|
|
2800
|
-
|
|
2801
|
-
|
|
2802
|
-
|
|
2803
|
-
|
|
2804
|
-
|
|
2805
|
-
|
|
2806
|
-
|
|
2807
|
-
|
|
2808
|
-
|
|
2809
|
-
|
|
2810
|
-
|
|
1228
|
+
await this.cleanupOldSwCaches();
|
|
1229
|
+
} catch (err) {
|
|
1230
|
+
this.debugger.log(err, "cleanupOldSwCaches @ activate: cleanup-old-sw-caches");
|
|
1231
|
+
}
|
|
1232
|
+
});
|
|
1233
|
+
})());
|
|
1234
|
+
if (this.scope.registration.active !== null) {
|
|
1235
|
+
this.scope.registration.active.postMessage({ action: "INITIALIZE" });
|
|
1236
|
+
}
|
|
1237
|
+
});
|
|
1238
|
+
this.scope.addEventListener("fetch", (event) => this.onFetch(event));
|
|
1239
|
+
this.scope.addEventListener("message", (event) => this.onMessage(event));
|
|
1240
|
+
this.scope.addEventListener("push", (event) => this.onPush(event));
|
|
1241
|
+
this.scope.addEventListener("notificationclick", (event) => this.onClick(event));
|
|
1242
|
+
this.debugger = new DebugHandler(this, this.adapter);
|
|
1243
|
+
this.idle = new IdleScheduler(this.adapter, IDLE_DELAY, MAX_IDLE_DELAY, this.debugger);
|
|
1244
|
+
}
|
|
1245
|
+
onFetch(event) {
|
|
1246
|
+
const req = event.request;
|
|
1247
|
+
const scopeUrl = this.scope.registration.scope;
|
|
1248
|
+
const requestUrlObj = this.adapter.parseUrl(req.url, scopeUrl);
|
|
1249
|
+
if (req.headers.has("ngsw-bypass") || /[?&]ngsw-bypass(?:[=&]|$)/i.test(requestUrlObj.search)) {
|
|
1250
|
+
return;
|
|
1251
|
+
}
|
|
1252
|
+
if (requestUrlObj.path === this.ngswStatePath) {
|
|
1253
|
+
event.respondWith(this.debugger.handleFetch(req));
|
|
1254
|
+
return;
|
|
1255
|
+
}
|
|
1256
|
+
if (this.state === DriverReadyState.SAFE_MODE) {
|
|
1257
|
+
event.waitUntil(this.idle.trigger());
|
|
1258
|
+
return;
|
|
1259
|
+
}
|
|
1260
|
+
if (requestUrlObj.origin.startsWith("http:") && scopeUrl.startsWith("https:")) {
|
|
1261
|
+
this.debugger.log(`Ignoring passive mixed content request: Driver.fetch(${req.url})`);
|
|
1262
|
+
return;
|
|
1263
|
+
}
|
|
1264
|
+
if (req.cache === "only-if-cached" && req.mode !== "same-origin") {
|
|
1265
|
+
if (!this.loggedInvalidOnlyIfCachedRequest) {
|
|
1266
|
+
this.loggedInvalidOnlyIfCachedRequest = true;
|
|
1267
|
+
this.debugger.log(`Ignoring invalid request: 'only-if-cached' can be set only with 'same-origin' mode`, `Driver.fetch(${req.url}, cache: ${req.cache}, mode: ${req.mode})`);
|
|
1268
|
+
}
|
|
1269
|
+
return;
|
|
1270
|
+
}
|
|
1271
|
+
event.respondWith(this.handleFetch(event));
|
|
1272
|
+
}
|
|
1273
|
+
onMessage(event) {
|
|
1274
|
+
if (this.state === DriverReadyState.SAFE_MODE) {
|
|
1275
|
+
return;
|
|
1276
|
+
}
|
|
1277
|
+
const data = event.data;
|
|
1278
|
+
if (!data || !data.action) {
|
|
1279
|
+
return;
|
|
1280
|
+
}
|
|
1281
|
+
event.waitUntil((async () => {
|
|
1282
|
+
if (data.action === "INITIALIZE") {
|
|
1283
|
+
return this.ensureInitialized(event);
|
|
1284
|
+
}
|
|
1285
|
+
if (!this.adapter.isClient(event.source)) {
|
|
1286
|
+
return;
|
|
1287
|
+
}
|
|
1288
|
+
await this.ensureInitialized(event);
|
|
1289
|
+
await this.handleMessage(data, event.source);
|
|
1290
|
+
})());
|
|
1291
|
+
}
|
|
1292
|
+
onPush(msg) {
|
|
1293
|
+
if (!msg.data) {
|
|
1294
|
+
return;
|
|
1295
|
+
}
|
|
1296
|
+
msg.waitUntil(this.handlePush(msg.data.json()));
|
|
1297
|
+
}
|
|
1298
|
+
onClick(event) {
|
|
1299
|
+
event.waitUntil(this.handleClick(event.notification, event.action));
|
|
1300
|
+
}
|
|
1301
|
+
async ensureInitialized(event) {
|
|
1302
|
+
if (this.initialized !== null) {
|
|
1303
|
+
return this.initialized;
|
|
1304
|
+
}
|
|
1305
|
+
try {
|
|
1306
|
+
this.initialized = this.initialize();
|
|
1307
|
+
await this.initialized;
|
|
1308
|
+
} catch (error) {
|
|
1309
|
+
this.state = DriverReadyState.SAFE_MODE;
|
|
1310
|
+
this.stateMessage = `Initialization failed due to error: ${errorToString(error)}`;
|
|
1311
|
+
throw error;
|
|
1312
|
+
} finally {
|
|
1313
|
+
event.waitUntil(this.idle.trigger());
|
|
1314
|
+
}
|
|
1315
|
+
}
|
|
1316
|
+
async handleMessage(msg, from) {
|
|
1317
|
+
if (isMsgCheckForUpdates(msg)) {
|
|
1318
|
+
const action = this.checkForUpdate();
|
|
1319
|
+
await this.completeOperation(from, action, msg.nonce);
|
|
1320
|
+
} else if (isMsgActivateUpdate(msg)) {
|
|
1321
|
+
const action = this.updateClient(from);
|
|
1322
|
+
await this.completeOperation(from, action, msg.nonce);
|
|
1323
|
+
}
|
|
1324
|
+
}
|
|
1325
|
+
async handlePush(data) {
|
|
1326
|
+
await this.broadcast({
|
|
1327
|
+
type: "PUSH",
|
|
1328
|
+
data
|
|
1329
|
+
});
|
|
1330
|
+
if (!data.notification || !data.notification.title) {
|
|
1331
|
+
return;
|
|
1332
|
+
}
|
|
1333
|
+
const desc = data.notification;
|
|
1334
|
+
let options = {};
|
|
1335
|
+
NOTIFICATION_OPTION_NAMES.filter((name) => desc.hasOwnProperty(name)).forEach((name) => options[name] = desc[name]);
|
|
1336
|
+
await this.scope.registration.showNotification(desc["title"], options);
|
|
1337
|
+
}
|
|
1338
|
+
async handleClick(notification, action) {
|
|
1339
|
+
var _a, _b, _c;
|
|
1340
|
+
notification.close();
|
|
1341
|
+
const options = {};
|
|
1342
|
+
NOTIFICATION_OPTION_NAMES.filter((name) => name in notification).forEach((name) => options[name] = notification[name]);
|
|
1343
|
+
const notificationAction = action === "" || action === void 0 ? "default" : action;
|
|
1344
|
+
const onActionClick = (_b = (_a = notification == null ? void 0 : notification.data) == null ? void 0 : _a.onActionClick) == null ? void 0 : _b[notificationAction];
|
|
1345
|
+
const urlToOpen = new URL((_c = onActionClick == null ? void 0 : onActionClick.url) != null ? _c : "", this.scope.registration.scope).href;
|
|
1346
|
+
switch (onActionClick == null ? void 0 : onActionClick.operation) {
|
|
1347
|
+
case "openWindow":
|
|
1348
|
+
await this.scope.clients.openWindow(urlToOpen);
|
|
1349
|
+
break;
|
|
1350
|
+
case "focusLastFocusedOrOpen": {
|
|
1351
|
+
let matchingClient = await this.getLastFocusedMatchingClient(this.scope);
|
|
1352
|
+
if (matchingClient) {
|
|
1353
|
+
await (matchingClient == null ? void 0 : matchingClient.focus());
|
|
1354
|
+
} else {
|
|
1355
|
+
await this.scope.clients.openWindow(urlToOpen);
|
|
1356
|
+
}
|
|
1357
|
+
break;
|
|
1358
|
+
}
|
|
1359
|
+
case "navigateLastFocusedOrOpen": {
|
|
1360
|
+
let matchingClient = await this.getLastFocusedMatchingClient(this.scope);
|
|
1361
|
+
if (matchingClient) {
|
|
1362
|
+
matchingClient = await matchingClient.navigate(urlToOpen);
|
|
1363
|
+
await (matchingClient == null ? void 0 : matchingClient.focus());
|
|
1364
|
+
} else {
|
|
1365
|
+
await this.scope.clients.openWindow(urlToOpen);
|
|
1366
|
+
}
|
|
1367
|
+
break;
|
|
1368
|
+
}
|
|
1369
|
+
default:
|
|
1370
|
+
break;
|
|
1371
|
+
}
|
|
1372
|
+
await this.broadcast({
|
|
1373
|
+
type: "NOTIFICATION_CLICK",
|
|
1374
|
+
data: { action, notification: options }
|
|
1375
|
+
});
|
|
1376
|
+
}
|
|
1377
|
+
async getLastFocusedMatchingClient(scope2) {
|
|
1378
|
+
const windowClients = await scope2.clients.matchAll({ type: "window" });
|
|
1379
|
+
return windowClients[0];
|
|
1380
|
+
}
|
|
1381
|
+
async completeOperation(client, promise, nonce) {
|
|
1382
|
+
const response = { type: "OPERATION_COMPLETED", nonce };
|
|
1383
|
+
try {
|
|
1384
|
+
client.postMessage(__spreadProps(__spreadValues({}, response), {
|
|
1385
|
+
result: await promise
|
|
1386
|
+
}));
|
|
1387
|
+
} catch (e) {
|
|
1388
|
+
client.postMessage(__spreadProps(__spreadValues({}, response), {
|
|
1389
|
+
error: e.toString()
|
|
1390
|
+
}));
|
|
1391
|
+
}
|
|
1392
|
+
}
|
|
1393
|
+
async updateClient(client) {
|
|
1394
|
+
const existing = this.clientVersionMap.get(client.id);
|
|
1395
|
+
if (existing === this.latestHash) {
|
|
1396
|
+
return false;
|
|
1397
|
+
}
|
|
1398
|
+
let previous = void 0;
|
|
1399
|
+
if (existing !== void 0) {
|
|
1400
|
+
const existingVersion = this.versions.get(existing);
|
|
1401
|
+
previous = this.mergeHashWithAppData(existingVersion.manifest, existing);
|
|
1402
|
+
}
|
|
1403
|
+
this.clientVersionMap.set(client.id, this.latestHash);
|
|
1404
|
+
await this.sync();
|
|
1405
|
+
const current = this.versions.get(this.latestHash);
|
|
1406
|
+
const notice = {
|
|
1407
|
+
type: "UPDATE_ACTIVATED",
|
|
1408
|
+
previous,
|
|
1409
|
+
current: this.mergeHashWithAppData(current.manifest, this.latestHash)
|
|
1410
|
+
};
|
|
1411
|
+
client.postMessage(notice);
|
|
1412
|
+
return true;
|
|
1413
|
+
}
|
|
1414
|
+
async handleFetch(event) {
|
|
1415
|
+
try {
|
|
1416
|
+
await this.ensureInitialized(event);
|
|
1417
|
+
} catch (e) {
|
|
1418
|
+
return this.safeFetch(event.request);
|
|
1419
|
+
}
|
|
1420
|
+
if (event.request.mode === "navigate" && !this.scheduledNavUpdateCheck) {
|
|
1421
|
+
this.scheduledNavUpdateCheck = true;
|
|
1422
|
+
this.idle.schedule("check-updates-on-navigation", async () => {
|
|
1423
|
+
this.scheduledNavUpdateCheck = false;
|
|
1424
|
+
await this.checkForUpdate();
|
|
1425
|
+
});
|
|
1426
|
+
}
|
|
1427
|
+
const appVersion = await this.assignVersion(event);
|
|
1428
|
+
let res = null;
|
|
1429
|
+
try {
|
|
1430
|
+
if (appVersion !== null) {
|
|
1431
|
+
try {
|
|
1432
|
+
res = await appVersion.handleFetch(event.request, event);
|
|
1433
|
+
} catch (err) {
|
|
1434
|
+
if (err.isUnrecoverableState) {
|
|
1435
|
+
await this.notifyClientsAboutUnrecoverableState(appVersion, err.message);
|
|
1436
|
+
}
|
|
1437
|
+
if (err.isCritical) {
|
|
1438
|
+
this.debugger.log(err, `Driver.handleFetch(version: ${appVersion.manifestHash})`);
|
|
1439
|
+
await this.versionFailed(appVersion, err);
|
|
1440
|
+
return this.safeFetch(event.request);
|
|
1441
|
+
}
|
|
1442
|
+
throw err;
|
|
1443
|
+
}
|
|
1444
|
+
}
|
|
1445
|
+
if (res === null) {
|
|
1446
|
+
return this.safeFetch(event.request);
|
|
1447
|
+
}
|
|
1448
|
+
return res;
|
|
1449
|
+
} finally {
|
|
1450
|
+
event.waitUntil(this.idle.trigger());
|
|
1451
|
+
}
|
|
1452
|
+
}
|
|
1453
|
+
async initialize() {
|
|
1454
|
+
const table = await this.controlTable;
|
|
1455
|
+
let manifests, assignments, latest;
|
|
1456
|
+
try {
|
|
1457
|
+
[manifests, assignments, latest] = await Promise.all([
|
|
1458
|
+
table.read("manifests"),
|
|
1459
|
+
table.read("assignments"),
|
|
1460
|
+
table.read("latest")
|
|
1461
|
+
]);
|
|
1462
|
+
if (!this.versions.has(latest.latest) && !manifests.hasOwnProperty(latest.latest)) {
|
|
1463
|
+
this.debugger.log(`Missing manifest for latest version hash ${latest.latest}`, "initialize: read from DB");
|
|
1464
|
+
throw new Error(`Missing manifest for latest hash ${latest.latest}`);
|
|
1465
|
+
}
|
|
1466
|
+
this.idle.schedule("init post-load (update)", async () => {
|
|
1467
|
+
await this.checkForUpdate();
|
|
1468
|
+
});
|
|
1469
|
+
} catch (_) {
|
|
1470
|
+
const manifest = await this.fetchLatestManifest();
|
|
1471
|
+
const hash = hashManifest(manifest);
|
|
1472
|
+
manifests = { [hash]: manifest };
|
|
1473
|
+
assignments = {};
|
|
1474
|
+
latest = { latest: hash };
|
|
1475
|
+
await Promise.all([
|
|
1476
|
+
table.write("manifests", manifests),
|
|
1477
|
+
table.write("assignments", assignments),
|
|
1478
|
+
table.write("latest", latest)
|
|
1479
|
+
]);
|
|
1480
|
+
}
|
|
1481
|
+
this.idle.schedule("init post-load (cleanup)", async () => {
|
|
1482
|
+
await this.cleanupCaches();
|
|
1483
|
+
});
|
|
1484
|
+
Object.keys(manifests).forEach((hash) => {
|
|
1485
|
+
const manifest = manifests[hash];
|
|
1486
|
+
if (!this.versions.has(hash)) {
|
|
1487
|
+
this.versions.set(hash, new AppVersion(this.scope, this.adapter, this.db, this.idle, this.debugger, manifest, hash));
|
|
1488
|
+
}
|
|
1489
|
+
});
|
|
1490
|
+
Object.keys(assignments).forEach((clientId) => {
|
|
1491
|
+
const hash = assignments[clientId];
|
|
1492
|
+
if (this.versions.has(hash)) {
|
|
1493
|
+
this.clientVersionMap.set(clientId, hash);
|
|
1494
|
+
} else {
|
|
1495
|
+
this.clientVersionMap.set(clientId, latest.latest);
|
|
1496
|
+
this.debugger.log(`Unknown version ${hash} mapped for client ${clientId}, using latest instead`, `initialize: map assignments`);
|
|
1497
|
+
}
|
|
1498
|
+
});
|
|
1499
|
+
this.latestHash = latest.latest;
|
|
1500
|
+
if (!this.versions.has(latest.latest)) {
|
|
1501
|
+
throw new Error(`Invariant violated (initialize): latest hash ${latest.latest} has no known manifest`);
|
|
1502
|
+
}
|
|
1503
|
+
await Promise.all(Object.keys(manifests).map(async (hash) => {
|
|
1504
|
+
try {
|
|
1505
|
+
await this.scheduleInitialization(this.versions.get(hash));
|
|
1506
|
+
} catch (err) {
|
|
1507
|
+
this.debugger.log(err, `initialize: schedule init of ${hash}`);
|
|
1508
|
+
return false;
|
|
1509
|
+
}
|
|
1510
|
+
}));
|
|
1511
|
+
}
|
|
1512
|
+
lookupVersionByHash(hash, debugName = "lookupVersionByHash") {
|
|
1513
|
+
if (!this.versions.has(hash)) {
|
|
1514
|
+
throw new Error(`Invariant violated (${debugName}): want AppVersion for ${hash} but not loaded`);
|
|
1515
|
+
}
|
|
1516
|
+
return this.versions.get(hash);
|
|
1517
|
+
}
|
|
1518
|
+
async assignVersion(event) {
|
|
1519
|
+
const clientId = event.resultingClientId || event.clientId;
|
|
1520
|
+
if (clientId) {
|
|
1521
|
+
if (this.clientVersionMap.has(clientId)) {
|
|
1522
|
+
const hash = this.clientVersionMap.get(clientId);
|
|
1523
|
+
let appVersion = this.lookupVersionByHash(hash, "assignVersion");
|
|
1524
|
+
if (this.state === DriverReadyState.NORMAL && hash !== this.latestHash && appVersion.isNavigationRequest(event.request)) {
|
|
1525
|
+
if (this.latestHash === null) {
|
|
1526
|
+
throw new Error(`Invariant violated (assignVersion): latestHash was null`);
|
|
1527
|
+
}
|
|
1528
|
+
const client = await this.scope.clients.get(clientId);
|
|
1529
|
+
if (client) {
|
|
1530
|
+
await this.updateClient(client);
|
|
1531
|
+
}
|
|
1532
|
+
appVersion = this.lookupVersionByHash(this.latestHash, "assignVersion");
|
|
1533
|
+
}
|
|
1534
|
+
return appVersion;
|
|
1535
|
+
} else {
|
|
1536
|
+
if (this.state !== DriverReadyState.NORMAL) {
|
|
1537
|
+
return null;
|
|
1538
|
+
}
|
|
1539
|
+
if (this.latestHash === null) {
|
|
1540
|
+
throw new Error(`Invariant violated (assignVersion): latestHash was null`);
|
|
1541
|
+
}
|
|
1542
|
+
this.clientVersionMap.set(clientId, this.latestHash);
|
|
1543
|
+
await this.sync();
|
|
1544
|
+
return this.lookupVersionByHash(this.latestHash, "assignVersion");
|
|
1545
|
+
}
|
|
1546
|
+
} else {
|
|
1547
|
+
if (this.state !== DriverReadyState.NORMAL) {
|
|
1548
|
+
return null;
|
|
1549
|
+
}
|
|
1550
|
+
if (this.latestHash === null) {
|
|
1551
|
+
throw new Error(`Invariant violated (assignVersion): latestHash was null`);
|
|
1552
|
+
}
|
|
1553
|
+
return this.lookupVersionByHash(this.latestHash, "assignVersion");
|
|
1554
|
+
}
|
|
1555
|
+
}
|
|
1556
|
+
async fetchLatestManifest(ignoreOfflineError = false) {
|
|
1557
|
+
const res = await this.safeFetch(this.adapter.newRequest("ngsw.json?ngsw-cache-bust=" + Math.random()));
|
|
1558
|
+
if (!res.ok) {
|
|
1559
|
+
if (res.status === 404) {
|
|
1560
|
+
await this.deleteAllCaches();
|
|
1561
|
+
await this.scope.registration.unregister();
|
|
1562
|
+
} else if ((res.status === 503 || res.status === 504) && ignoreOfflineError) {
|
|
1563
|
+
return null;
|
|
1564
|
+
}
|
|
1565
|
+
throw new Error(`Manifest fetch failed! (status: ${res.status})`);
|
|
1566
|
+
}
|
|
1567
|
+
this.lastUpdateCheck = this.adapter.time;
|
|
1568
|
+
return res.json();
|
|
1569
|
+
}
|
|
1570
|
+
async deleteAllCaches() {
|
|
1571
|
+
const cacheNames = await this.adapter.caches.keys();
|
|
1572
|
+
await Promise.all(cacheNames.map((name) => this.adapter.caches.delete(name)));
|
|
1573
|
+
}
|
|
1574
|
+
async scheduleInitialization(appVersion) {
|
|
1575
|
+
const initialize = async () => {
|
|
1576
|
+
try {
|
|
1577
|
+
await appVersion.initializeFully();
|
|
1578
|
+
} catch (err) {
|
|
1579
|
+
this.debugger.log(err, `initializeFully for ${appVersion.manifestHash}`);
|
|
1580
|
+
await this.versionFailed(appVersion, err);
|
|
1581
|
+
}
|
|
1582
|
+
};
|
|
1583
|
+
if (this.scope.registration.scope.indexOf("://localhost") > -1) {
|
|
1584
|
+
return initialize();
|
|
1585
|
+
}
|
|
1586
|
+
this.idle.schedule(`initialization(${appVersion.manifestHash})`, initialize);
|
|
1587
|
+
}
|
|
1588
|
+
async versionFailed(appVersion, err) {
|
|
1589
|
+
const broken = Array.from(this.versions.entries()).find(([hash, version]) => version === appVersion);
|
|
1590
|
+
if (broken === void 0) {
|
|
1591
|
+
return;
|
|
1592
|
+
}
|
|
1593
|
+
const brokenHash = broken[0];
|
|
1594
|
+
if (this.latestHash === brokenHash) {
|
|
1595
|
+
this.state = DriverReadyState.EXISTING_CLIENTS_ONLY;
|
|
1596
|
+
this.stateMessage = `Degraded due to: ${errorToString(err)}`;
|
|
1597
|
+
}
|
|
1598
|
+
}
|
|
1599
|
+
async setupUpdate(manifest, hash) {
|
|
1600
|
+
try {
|
|
1601
|
+
const newVersion = new AppVersion(this.scope, this.adapter, this.db, this.idle, this.debugger, manifest, hash);
|
|
1602
|
+
if (manifest.configVersion !== SUPPORTED_CONFIG_VERSION) {
|
|
1603
|
+
await this.deleteAllCaches();
|
|
1604
|
+
await this.scope.registration.unregister();
|
|
1605
|
+
throw new Error(`Invalid config version: expected ${SUPPORTED_CONFIG_VERSION}, got ${manifest.configVersion}.`);
|
|
1606
|
+
}
|
|
1607
|
+
await newVersion.initializeFully(this);
|
|
1608
|
+
this.versions.set(hash, newVersion);
|
|
1609
|
+
this.latestHash = hash;
|
|
1610
|
+
if (this.state === DriverReadyState.EXISTING_CLIENTS_ONLY) {
|
|
1611
|
+
this.state = DriverReadyState.NORMAL;
|
|
1612
|
+
this.stateMessage = "(nominal)";
|
|
1613
|
+
}
|
|
1614
|
+
await this.sync();
|
|
1615
|
+
await this.notifyClientsAboutVersionReady(manifest, hash);
|
|
1616
|
+
} catch (e) {
|
|
1617
|
+
await this.notifyClientsAboutVersionInstallationFailed(manifest, hash, e);
|
|
1618
|
+
throw e;
|
|
1619
|
+
}
|
|
1620
|
+
}
|
|
1621
|
+
async checkForUpdate() {
|
|
1622
|
+
let hash = "(unknown)";
|
|
1623
|
+
try {
|
|
1624
|
+
const manifest = await this.fetchLatestManifest(true);
|
|
1625
|
+
if (manifest === null) {
|
|
1626
|
+
this.debugger.log("Check for update aborted. (Client or server offline.)");
|
|
1627
|
+
return false;
|
|
1628
|
+
}
|
|
1629
|
+
hash = hashManifest(manifest);
|
|
1630
|
+
if (this.versions.has(hash)) {
|
|
1631
|
+
return false;
|
|
1632
|
+
}
|
|
1633
|
+
await this.notifyClientsAboutVersionDetected(manifest, hash);
|
|
1634
|
+
await this.setupUpdate(manifest, hash);
|
|
1635
|
+
return true;
|
|
1636
|
+
} catch (err) {
|
|
1637
|
+
this.debugger.log(err, `Error occurred while updating to manifest ${hash}`);
|
|
1638
|
+
this.state = DriverReadyState.EXISTING_CLIENTS_ONLY;
|
|
1639
|
+
this.stateMessage = `Degraded due to failed initialization: ${errorToString(err)}`;
|
|
1640
|
+
return false;
|
|
1641
|
+
}
|
|
1642
|
+
}
|
|
1643
|
+
async sync() {
|
|
1644
|
+
const table = await this.controlTable;
|
|
1645
|
+
const manifests = {};
|
|
1646
|
+
this.versions.forEach((version, hash) => {
|
|
1647
|
+
manifests[hash] = version.manifest;
|
|
1648
|
+
});
|
|
1649
|
+
const assignments = {};
|
|
1650
|
+
this.clientVersionMap.forEach((hash, clientId) => {
|
|
1651
|
+
assignments[clientId] = hash;
|
|
1652
|
+
});
|
|
1653
|
+
const latest = {
|
|
1654
|
+
latest: this.latestHash
|
|
1655
|
+
};
|
|
1656
|
+
await Promise.all([
|
|
1657
|
+
table.write("manifests", manifests),
|
|
1658
|
+
table.write("assignments", assignments),
|
|
1659
|
+
table.write("latest", latest)
|
|
1660
|
+
]);
|
|
1661
|
+
}
|
|
1662
|
+
async cleanupCaches() {
|
|
1663
|
+
try {
|
|
1664
|
+
const activeClients = new Set((await this.scope.clients.matchAll()).map((client) => client.id));
|
|
1665
|
+
const knownClients = Array.from(this.clientVersionMap.keys());
|
|
1666
|
+
const obsoleteClients = knownClients.filter((id) => !activeClients.has(id));
|
|
1667
|
+
obsoleteClients.forEach((id) => this.clientVersionMap.delete(id));
|
|
1668
|
+
const usedVersions = new Set(this.clientVersionMap.values());
|
|
1669
|
+
const obsoleteVersions = Array.from(this.versions.keys()).filter((version) => !usedVersions.has(version) && version !== this.latestHash);
|
|
1670
|
+
obsoleteVersions.forEach((version) => this.versions.delete(version));
|
|
1671
|
+
await this.sync();
|
|
1672
|
+
const allCaches = await this.adapter.caches.keys();
|
|
1673
|
+
const usedCaches = new Set(await this.getCacheNames());
|
|
1674
|
+
const cachesToDelete = allCaches.filter((name) => !usedCaches.has(name));
|
|
1675
|
+
await Promise.all(cachesToDelete.map((name) => this.adapter.caches.delete(name)));
|
|
1676
|
+
} catch (err) {
|
|
1677
|
+
this.debugger.log(err, "cleanupCaches");
|
|
1678
|
+
}
|
|
1679
|
+
}
|
|
1680
|
+
async cleanupOldSwCaches() {
|
|
1681
|
+
const caches = this.adapter.caches.original;
|
|
1682
|
+
const cacheNames = await caches.keys();
|
|
1683
|
+
const oldSwCacheNames = cacheNames.filter((name) => /^ngsw:(?!\/)/.test(name));
|
|
1684
|
+
await Promise.all(oldSwCacheNames.map((name) => caches.delete(name)));
|
|
1685
|
+
}
|
|
1686
|
+
lookupResourceWithHash(url, hash) {
|
|
1687
|
+
return Array.from(this.versions.values()).reduce(async (prev, version) => {
|
|
1688
|
+
if (await prev !== null) {
|
|
1689
|
+
return prev;
|
|
2811
1690
|
}
|
|
1691
|
+
return version.lookupResourceWithHash(url, hash);
|
|
1692
|
+
}, Promise.resolve(null));
|
|
2812
1693
|
}
|
|
1694
|
+
async lookupResourceWithoutHash(url) {
|
|
1695
|
+
await this.initialized;
|
|
1696
|
+
const version = this.versions.get(this.latestHash);
|
|
1697
|
+
return version ? version.lookupResourceWithoutHash(url) : null;
|
|
1698
|
+
}
|
|
1699
|
+
async previouslyCachedResources() {
|
|
1700
|
+
await this.initialized;
|
|
1701
|
+
const version = this.versions.get(this.latestHash);
|
|
1702
|
+
return version ? version.previouslyCachedResources() : [];
|
|
1703
|
+
}
|
|
1704
|
+
async recentCacheStatus(url) {
|
|
1705
|
+
const version = this.versions.get(this.latestHash);
|
|
1706
|
+
return version ? version.recentCacheStatus(url) : UpdateCacheStatus.NOT_CACHED;
|
|
1707
|
+
}
|
|
1708
|
+
mergeHashWithAppData(manifest, hash) {
|
|
1709
|
+
return {
|
|
1710
|
+
hash,
|
|
1711
|
+
appData: manifest.appData
|
|
1712
|
+
};
|
|
1713
|
+
}
|
|
1714
|
+
async notifyClientsAboutUnrecoverableState(appVersion, reason) {
|
|
1715
|
+
const broken = Array.from(this.versions.entries()).find(([hash, version]) => version === appVersion);
|
|
1716
|
+
if (broken === void 0) {
|
|
1717
|
+
return;
|
|
1718
|
+
}
|
|
1719
|
+
const brokenHash = broken[0];
|
|
1720
|
+
const affectedClients = Array.from(this.clientVersionMap.entries()).filter(([clientId, hash]) => hash === brokenHash).map(([clientId]) => clientId);
|
|
1721
|
+
await Promise.all(affectedClients.map(async (clientId) => {
|
|
1722
|
+
const client = await this.scope.clients.get(clientId);
|
|
1723
|
+
if (client) {
|
|
1724
|
+
client.postMessage({ type: "UNRECOVERABLE_STATE", reason });
|
|
1725
|
+
}
|
|
1726
|
+
}));
|
|
1727
|
+
}
|
|
1728
|
+
async notifyClientsAboutVersionInstallationFailed(manifest, hash, error) {
|
|
1729
|
+
await this.initialized;
|
|
1730
|
+
const clients = await this.scope.clients.matchAll();
|
|
1731
|
+
await Promise.all(clients.map(async (client) => {
|
|
1732
|
+
client.postMessage({
|
|
1733
|
+
type: "VERSION_INSTALLATION_FAILED",
|
|
1734
|
+
version: this.mergeHashWithAppData(manifest, hash),
|
|
1735
|
+
error: errorToString(error)
|
|
1736
|
+
});
|
|
1737
|
+
}));
|
|
1738
|
+
}
|
|
1739
|
+
async notifyClientsAboutVersionDetected(manifest, hash) {
|
|
1740
|
+
await this.initialized;
|
|
1741
|
+
const clients = await this.scope.clients.matchAll();
|
|
1742
|
+
await Promise.all(clients.map(async (client) => {
|
|
1743
|
+
const version = this.clientVersionMap.get(client.id);
|
|
1744
|
+
if (version === void 0) {
|
|
1745
|
+
return;
|
|
1746
|
+
}
|
|
1747
|
+
client.postMessage({ type: "VERSION_DETECTED", version: this.mergeHashWithAppData(manifest, hash) });
|
|
1748
|
+
}));
|
|
1749
|
+
}
|
|
1750
|
+
async notifyClientsAboutVersionReady(manifest, hash) {
|
|
1751
|
+
await this.initialized;
|
|
1752
|
+
const clients = await this.scope.clients.matchAll();
|
|
1753
|
+
await Promise.all(clients.map(async (client) => {
|
|
1754
|
+
const version = this.clientVersionMap.get(client.id);
|
|
1755
|
+
if (version === void 0) {
|
|
1756
|
+
return;
|
|
1757
|
+
}
|
|
1758
|
+
if (version === this.latestHash) {
|
|
1759
|
+
return;
|
|
1760
|
+
}
|
|
1761
|
+
const current = this.versions.get(version);
|
|
1762
|
+
const notice = {
|
|
1763
|
+
type: "VERSION_READY",
|
|
1764
|
+
currentVersion: this.mergeHashWithAppData(current.manifest, version),
|
|
1765
|
+
latestVersion: this.mergeHashWithAppData(manifest, hash)
|
|
1766
|
+
};
|
|
1767
|
+
client.postMessage(notice);
|
|
1768
|
+
}));
|
|
1769
|
+
}
|
|
1770
|
+
async broadcast(msg) {
|
|
1771
|
+
const clients = await this.scope.clients.matchAll();
|
|
1772
|
+
clients.forEach((client) => {
|
|
1773
|
+
client.postMessage(msg);
|
|
1774
|
+
});
|
|
1775
|
+
}
|
|
1776
|
+
async debugState() {
|
|
1777
|
+
return {
|
|
1778
|
+
state: DriverReadyState[this.state],
|
|
1779
|
+
why: this.stateMessage,
|
|
1780
|
+
latestHash: this.latestHash,
|
|
1781
|
+
lastUpdateCheck: this.lastUpdateCheck
|
|
1782
|
+
};
|
|
1783
|
+
}
|
|
1784
|
+
async debugVersions() {
|
|
1785
|
+
return Array.from(this.versions.keys()).map((hash) => {
|
|
1786
|
+
const version = this.versions.get(hash);
|
|
1787
|
+
const clients = Array.from(this.clientVersionMap.entries()).filter(([clientId, version2]) => version2 === hash).map(([clientId, version2]) => clientId);
|
|
1788
|
+
return {
|
|
1789
|
+
hash,
|
|
1790
|
+
manifest: version.manifest,
|
|
1791
|
+
clients,
|
|
1792
|
+
status: ""
|
|
1793
|
+
};
|
|
1794
|
+
});
|
|
1795
|
+
}
|
|
1796
|
+
async debugIdleState() {
|
|
1797
|
+
return {
|
|
1798
|
+
queue: this.idle.taskDescriptions,
|
|
1799
|
+
lastTrigger: this.idle.lastTrigger,
|
|
1800
|
+
lastRun: this.idle.lastRun
|
|
1801
|
+
};
|
|
1802
|
+
}
|
|
1803
|
+
async safeFetch(req) {
|
|
1804
|
+
try {
|
|
1805
|
+
return await this.scope.fetch(req);
|
|
1806
|
+
} catch (err) {
|
|
1807
|
+
this.debugger.log(err, `Driver.fetch(${req.url})`);
|
|
1808
|
+
return this.adapter.newResponse(null, {
|
|
1809
|
+
status: 504,
|
|
1810
|
+
statusText: "Gateway Timeout"
|
|
1811
|
+
});
|
|
1812
|
+
}
|
|
1813
|
+
}
|
|
1814
|
+
async getCacheNames() {
|
|
1815
|
+
const controlTable = await this.controlTable;
|
|
1816
|
+
const appVersions = Array.from(this.versions.values());
|
|
1817
|
+
const appVersionCacheNames = await Promise.all(appVersions.map((version) => version.getCacheNames()));
|
|
1818
|
+
return [controlTable.cacheName].concat(...appVersionCacheNames);
|
|
1819
|
+
}
|
|
1820
|
+
};
|
|
2813
1821
|
|
|
2814
|
-
|
|
2815
|
-
|
|
2816
|
-
|
|
2817
|
-
|
|
2818
|
-
* Use of this source code is governed by an MIT-style license that can be
|
|
2819
|
-
* found in the LICENSE file at https://angular.io/license
|
|
2820
|
-
*/
|
|
2821
|
-
const scope = self;
|
|
2822
|
-
const adapter = new Adapter(scope.registration.scope, self.caches);
|
|
2823
|
-
new Driver(scope, adapter, new CacheDatabase(adapter));
|
|
2824
|
-
|
|
1822
|
+
// bazel-out/k8-fastbuild-ST-2e5f3376adb5/bin/packages/service-worker/worker/main.mjs
|
|
1823
|
+
var scope = self;
|
|
1824
|
+
var adapter = new Adapter(scope.registration.scope, self.caches);
|
|
1825
|
+
new Driver(scope, adapter, new CacheDatabase(adapter));
|
|
2825
1826
|
})();
|
|
1827
|
+
/**
|
|
1828
|
+
* @license
|
|
1829
|
+
* Copyright Google LLC All Rights Reserved.
|
|
1830
|
+
*
|
|
1831
|
+
* Use of this source code is governed by an MIT-style license that can be
|
|
1832
|
+
* found in the LICENSE file at https://angular.io/license
|
|
1833
|
+
*/
|