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));
|
21 |
|
22 |
|
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}`);
|
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 | };
|
48 |
|
49 |
|
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();
|
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 | };
|
86 |
|
87 |
|
88 | var NotFound = class {
|
89 | constructor(table, key) {
|
90 | this.table = table;
|
91 | this.key = key;
|
92 | }
|
93 | };
|
94 |
|
95 |
|
96 | var CacheDatabase = class {
|
97 | constructor(adapter2) {
|
98 | this.adapter = adapter2;
|
99 | this.cacheNamePrefix = "db";
|
100 | this.tables = new Map();
|
101 | }
|
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.slice(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));
|
144 | }
|
145 | return res.json();
|
146 | });
|
147 | }
|
148 | write(key, value) {
|
149 | return this.cache.put(this.request(key), this.adapter.newResponse(JSON.stringify(value)));
|
150 | }
|
151 | };
|
152 |
|
153 |
|
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 = {}));
|
160 |
|
161 |
|
162 | var SwCriticalError = class extends Error {
|
163 | constructor() {
|
164 | super(...arguments);
|
165 | this.isCritical = true;
|
166 | }
|
167 | };
|
168 | function errorToString(error) {
|
169 | if (error instanceof Error) {
|
170 | return `${error.message}
|
171 | ${error.stack}`;
|
172 | } else {
|
173 | return `${error}`;
|
174 | }
|
175 | }
|
176 | var SwUnrecoverableStateError = class extends SwCriticalError {
|
177 | constructor() {
|
178 | super(...arguments);
|
179 | this.isUnrecoverableState = true;
|
180 | }
|
181 | };
|
182 |
|
183 |
|
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)];
|
211 | }
|
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];
|
233 | }
|
234 | if (index < 40) {
|
235 | return [b ^ c ^ d, 1859775393];
|
236 | }
|
237 | if (index < 60) {
|
238 | return [b & c | b & d | c & d, 2400959708];
|
239 | }
|
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);
|
247 | }
|
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);
|
256 | }
|
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);
|
294 | }
|
295 | return hex.toLowerCase();
|
296 | }
|
297 |
|
298 |
|
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 = 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 {
|
377 | const metaTable = await this.metadata;
|
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 | }
|
401 | }
|
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 };
|
416 | }
|
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));
|
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 | };
|
570 |
|
571 |
|
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;
|
583 | }
|
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 | var _a;
|
713 | const okToCacheOpaque = (_a = this.config.cacheOpaqueResponses) != null ? _a : false;
|
714 | let res = null;
|
715 | const fromCache = await this.loadFromCache(req, lru);
|
716 | if (fromCache !== null) {
|
717 | res = fromCache.res;
|
718 | if (this.config.refreshAheadMs !== void 0 && fromCache.age >= this.config.refreshAheadMs) {
|
719 | event.waitUntil(this.safeCacheResponse(req, this.safeFetch(req), lru, okToCacheOpaque));
|
720 | }
|
721 | }
|
722 | if (res !== null) {
|
723 | return res;
|
724 | }
|
725 | const [timeoutFetch, networkFetch] = this.networkFetchWithTimeout(req);
|
726 | res = await timeoutFetch;
|
727 | if (res === void 0) {
|
728 | res = this.adapter.newResponse(null, { status: 504, statusText: "Gateway Timeout" });
|
729 | event.waitUntil(this.safeCacheResponse(req, networkFetch, lru, okToCacheOpaque));
|
730 | } else {
|
731 | await this.safeCacheResponse(req, res, lru, okToCacheOpaque);
|
732 | }
|
733 | return res;
|
734 | }
|
735 | async handleFetchWithFreshness(req, event, lru) {
|
736 | var _a;
|
737 | const okToCacheOpaque = (_a = this.config.cacheOpaqueResponses) != null ? _a : true;
|
738 | const [timeoutFetch, networkFetch] = this.networkFetchWithTimeout(req);
|
739 | let res;
|
740 | try {
|
741 | res = await timeoutFetch;
|
742 | } catch (e) {
|
743 | res = void 0;
|
744 | }
|
745 | if (res === void 0) {
|
746 | event.waitUntil(this.safeCacheResponse(req, networkFetch, lru, okToCacheOpaque));
|
747 | const fromCache = await this.loadFromCache(req, lru);
|
748 | res = fromCache !== null ? fromCache.res : null;
|
749 | } else {
|
750 | await this.safeCacheResponse(req, res, lru, okToCacheOpaque);
|
751 | }
|
752 | if (res !== null) {
|
753 | return res;
|
754 | }
|
755 | return networkFetch;
|
756 | }
|
757 | networkFetchWithTimeout(req) {
|
758 | if (this.config.timeoutMs !== void 0) {
|
759 | const networkFetch = this.scope.fetch(req);
|
760 | const safeNetworkFetch = (async () => {
|
761 | try {
|
762 | return await networkFetch;
|
763 | } catch (e) {
|
764 | return this.adapter.newResponse(null, {
|
765 | status: 504,
|
766 | statusText: "Gateway Timeout"
|
767 | });
|
768 | }
|
769 | })();
|
770 | const networkFetchUndefinedError = (async () => {
|
771 | try {
|
772 | return await networkFetch;
|
773 | } catch (e) {
|
774 | return void 0;
|
775 | }
|
776 | })();
|
777 | const timeout = this.adapter.timeout(this.config.timeoutMs);
|
778 | return [Promise.race([networkFetchUndefinedError, timeout]), safeNetworkFetch];
|
779 | } else {
|
780 | const networkFetch = this.safeFetch(req);
|
781 | return [networkFetch, networkFetch];
|
782 | }
|
783 | }
|
784 | async safeCacheResponse(req, resOrPromise, lru, okToCacheOpaque) {
|
785 | try {
|
786 | const res = await resOrPromise;
|
787 | try {
|
788 | await this.cacheResponse(req, res, lru, okToCacheOpaque);
|
789 | } catch (err) {
|
790 | this.debugHandler.log(err, `DataGroup(${this.config.name}@${this.config.version}).safeCacheResponse(${req.url}, status: ${res.status})`);
|
791 | }
|
792 | } catch (e) {
|
793 | }
|
794 | }
|
795 | async loadFromCache(req, lru) {
|
796 | const cache = await this.cache;
|
797 | let res = await cache.match(req, this.config.cacheQueryOptions);
|
798 | if (res !== void 0) {
|
799 | try {
|
800 | const ageTable = await this.ageTable;
|
801 | const age = this.adapter.time - (await ageTable.read(req.url)).age;
|
802 | if (age <= this.config.maxAge) {
|
803 | lru.accessed(req.url);
|
804 | return { res, age };
|
805 | }
|
806 | } catch (e) {
|
807 | }
|
808 | lru.remove(req.url);
|
809 | await this.clearCacheForUrl(req.url);
|
810 | await this.syncLru();
|
811 | }
|
812 | return null;
|
813 | }
|
814 | async cacheResponse(req, res, lru, okToCacheOpaque = false) {
|
815 | if (!(res.ok || okToCacheOpaque && res.type === "opaque")) {
|
816 | return;
|
817 | }
|
818 | if (lru.size >= this.config.maxSize) {
|
819 | const evictedUrl = lru.pop();
|
820 | if (evictedUrl !== null) {
|
821 | await this.clearCacheForUrl(evictedUrl);
|
822 | }
|
823 | }
|
824 | lru.accessed(req.url);
|
825 | await (await this.cache).put(req, res.clone());
|
826 | const ageTable = await this.ageTable;
|
827 | await ageTable.write(req.url, { age: this.adapter.time });
|
828 | await this.syncLru();
|
829 | }
|
830 | async cleanup() {
|
831 | await Promise.all([
|
832 | this.cache.then((cache) => this.adapter.caches.delete(cache.name)),
|
833 | this.ageTable.then((table) => this.db.delete(table.name)),
|
834 | this.lruTable.then((table) => this.db.delete(table.name))
|
835 | ]);
|
836 | }
|
837 | async getCacheNames() {
|
838 | const [cache, ageTable, lruTable] = await Promise.all([
|
839 | this.cache,
|
840 | this.ageTable,
|
841 | this.lruTable
|
842 | ]);
|
843 | return [cache.name, ageTable.cacheName, lruTable.cacheName];
|
844 | }
|
845 | async clearCacheForUrl(url) {
|
846 | const [cache, ageTable] = await Promise.all([this.cache, this.ageTable]);
|
847 | await Promise.all([
|
848 | cache.delete(this.adapter.newRequest(url, { method: "GET" }), this.config.cacheQueryOptions),
|
849 | cache.delete(this.adapter.newRequest(url, { method: "HEAD" }), this.config.cacheQueryOptions),
|
850 | ageTable.delete(url)
|
851 | ]);
|
852 | }
|
853 | async safeFetch(req) {
|
854 | try {
|
855 | return this.scope.fetch(req);
|
856 | } catch (e) {
|
857 | return this.adapter.newResponse(null, {
|
858 | status: 504,
|
859 | statusText: "Gateway Timeout"
|
860 | });
|
861 | }
|
862 | }
|
863 | };
|
864 |
|
865 |
|
866 | var BACKWARDS_COMPATIBILITY_NAVIGATION_URLS = [
|
867 | { positive: true, regex: "^/.*$" },
|
868 | { positive: false, regex: "^/.*\\.[^/]*$" },
|
869 | { positive: false, regex: "^/.*__" }
|
870 | ];
|
871 | var AppVersion = class {
|
872 | constructor(scope2, adapter2, database, idle, debugHandler, manifest, manifestHash) {
|
873 | this.scope = scope2;
|
874 | this.adapter = adapter2;
|
875 | this.database = database;
|
876 | this.debugHandler = debugHandler;
|
877 | this.manifest = manifest;
|
878 | this.manifestHash = manifestHash;
|
879 | this.hashTable = new Map();
|
880 | this.indexUrl = this.adapter.normalizeUrl(this.manifest.index);
|
881 | this._okay = true;
|
882 | Object.keys(manifest.hashTable).forEach((url) => {
|
883 | this.hashTable.set(adapter2.normalizeUrl(url), manifest.hashTable[url]);
|
884 | });
|
885 | const assetCacheNamePrefix = `${manifestHash}:assets`;
|
886 | this.assetGroups = (manifest.assetGroups || []).map((config) => {
|
887 | switch (config.installMode) {
|
888 | case "prefetch":
|
889 | return new PrefetchAssetGroup(scope2, adapter2, idle, config, this.hashTable, database, assetCacheNamePrefix);
|
890 | case "lazy":
|
891 | return new LazyAssetGroup(scope2, adapter2, idle, config, this.hashTable, database, assetCacheNamePrefix);
|
892 | }
|
893 | });
|
894 | this.dataGroups = (manifest.dataGroups || []).map((config) => new DataGroup(scope2, adapter2, config, database, debugHandler, `${config.version}:data`));
|
895 | manifest.navigationUrls = manifest.navigationUrls || BACKWARDS_COMPATIBILITY_NAVIGATION_URLS;
|
896 | const includeUrls = manifest.navigationUrls.filter((spec) => spec.positive);
|
897 | const excludeUrls = manifest.navigationUrls.filter((spec) => !spec.positive);
|
898 | this.navigationUrls = {
|
899 | include: includeUrls.map((spec) => new RegExp(spec.regex)),
|
900 | exclude: excludeUrls.map((spec) => new RegExp(spec.regex))
|
901 | };
|
902 | }
|
903 | get okay() {
|
904 | return this._okay;
|
905 | }
|
906 | async initializeFully(updateFrom) {
|
907 | try {
|
908 | await this.assetGroups.reduce(async (previous, group) => {
|
909 | await previous;
|
910 | return group.initializeFully(updateFrom);
|
911 | }, Promise.resolve());
|
912 | } catch (err) {
|
913 | this._okay = false;
|
914 | throw err;
|
915 | }
|
916 | }
|
917 | async handleFetch(req, event) {
|
918 | const asset = await this.assetGroups.reduce(async (potentialResponse, group) => {
|
919 | const resp = await potentialResponse;
|
920 | if (resp !== null) {
|
921 | return resp;
|
922 | }
|
923 | return group.handleFetch(req, event);
|
924 | }, Promise.resolve(null));
|
925 | if (asset !== null) {
|
926 | return asset;
|
927 | }
|
928 | const data = await this.dataGroups.reduce(async (potentialResponse, group) => {
|
929 | const resp = await potentialResponse;
|
930 | if (resp !== null) {
|
931 | return resp;
|
932 | }
|
933 | return group.handleFetch(req, event);
|
934 | }, Promise.resolve(null));
|
935 | if (data !== null) {
|
936 | return data;
|
937 | }
|
938 | if (this.adapter.normalizeUrl(req.url) !== this.indexUrl && this.isNavigationRequest(req)) {
|
939 | if (this.manifest.navigationRequestStrategy === "freshness") {
|
940 | try {
|
941 | return await this.scope.fetch(req);
|
942 | } catch (e) {
|
943 | }
|
944 | }
|
945 | return this.handleFetch(this.adapter.newRequest(this.indexUrl), event);
|
946 | }
|
947 | return null;
|
948 | }
|
949 | isNavigationRequest(req) {
|
950 | if (req.mode !== "navigate") {
|
951 | return false;
|
952 | }
|
953 | if (!this.acceptsTextHtml(req)) {
|
954 | return false;
|
955 | }
|
956 | const urlPrefix = this.scope.registration.scope.replace(/\/$/, "");
|
957 | const url = req.url.startsWith(urlPrefix) ? req.url.slice(urlPrefix.length) : req.url;
|
958 | const urlWithoutQueryOrHash = url.replace(/[?#].*$/, "");
|
959 | return this.navigationUrls.include.some((regex) => regex.test(urlWithoutQueryOrHash)) && !this.navigationUrls.exclude.some((regex) => regex.test(urlWithoutQueryOrHash));
|
960 | }
|
961 | async lookupResourceWithHash(url, hash) {
|
962 | if (!this.hashTable.has(url)) {
|
963 | return null;
|
964 | }
|
965 | if (this.hashTable.get(url) !== hash) {
|
966 | return null;
|
967 | }
|
968 | const cacheState = await this.lookupResourceWithoutHash(url);
|
969 | return cacheState && cacheState.response;
|
970 | }
|
971 | lookupResourceWithoutHash(url) {
|
972 | return this.assetGroups.reduce(async (potentialResponse, group) => {
|
973 | const resp = await potentialResponse;
|
974 | if (resp !== null) {
|
975 | return resp;
|
976 | }
|
977 | return group.fetchFromCacheOnly(url);
|
978 | }, Promise.resolve(null));
|
979 | }
|
980 | previouslyCachedResources() {
|
981 | return this.assetGroups.reduce(async (resources, group) => (await resources).concat(await group.unhashedResources()), Promise.resolve([]));
|
982 | }
|
983 | async recentCacheStatus(url) {
|
984 | return this.assetGroups.reduce(async (current, group) => {
|
985 | const status = await current;
|
986 | if (status === UpdateCacheStatus.CACHED) {
|
987 | return status;
|
988 | }
|
989 | const groupStatus = await group.cacheStatus(url);
|
990 | if (groupStatus === UpdateCacheStatus.NOT_CACHED) {
|
991 | return status;
|
992 | }
|
993 | return groupStatus;
|
994 | }, Promise.resolve(UpdateCacheStatus.NOT_CACHED));
|
995 | }
|
996 | async getCacheNames() {
|
997 | const allGroupCacheNames = await Promise.all([
|
998 | ...this.assetGroups.map((group) => group.getCacheNames()),
|
999 | ...this.dataGroups.map((group) => group.getCacheNames())
|
1000 | ]);
|
1001 | return [].concat(...allGroupCacheNames);
|
1002 | }
|
1003 | get appData() {
|
1004 | return this.manifest.appData || null;
|
1005 | }
|
1006 | acceptsTextHtml(req) {
|
1007 | const accept = req.headers.get("Accept");
|
1008 | if (accept === null) {
|
1009 | return false;
|
1010 | }
|
1011 | const values = accept.split(",");
|
1012 | return values.some((value) => value.trim().toLowerCase() === "text/html");
|
1013 | }
|
1014 | };
|
1015 |
|
1016 |
|
1017 | var SW_VERSION = "14.1.2";
|
1018 | var DEBUG_LOG_BUFFER_SIZE = 100;
|
1019 | var DebugHandler = class {
|
1020 | constructor(driver, adapter2) {
|
1021 | this.driver = driver;
|
1022 | this.adapter = adapter2;
|
1023 | this.debugLogA = [];
|
1024 | this.debugLogB = [];
|
1025 | }
|
1026 | async handleFetch(req) {
|
1027 | const [state, versions, idle] = await Promise.all([
|
1028 | this.driver.debugState(),
|
1029 | this.driver.debugVersions(),
|
1030 | this.driver.debugIdleState()
|
1031 | ]);
|
1032 | const msgState = `NGSW Debug Info:
|
1033 |
|
1034 | Driver version: ${SW_VERSION}
|
1035 | Driver state: ${state.state} (${state.why})
|
1036 | Latest manifest hash: ${state.latestHash || "none"}
|
1037 | Last update check: ${this.since(state.lastUpdateCheck)}`;
|
1038 | const msgVersions = versions.map((version) => `=== Version ${version.hash} ===
|
1039 |
|
1040 | Clients: ${version.clients.join(", ")}`).join("\n\n");
|
1041 | const msgIdle = `=== Idle Task Queue ===
|
1042 | Last update tick: ${this.since(idle.lastTrigger)}
|
1043 | Last update run: ${this.since(idle.lastRun)}
|
1044 | Task queue:
|
1045 | ${idle.queue.map((v) => " * " + v).join("\n")}
|
1046 |
|
1047 | Debug log:
|
1048 | ${this.formatDebugLog(this.debugLogB)}
|
1049 | ${this.formatDebugLog(this.debugLogA)}
|
1050 | `;
|
1051 | return this.adapter.newResponse(`${msgState}
|
1052 |
|
1053 | ${msgVersions}
|
1054 |
|
1055 | ${msgIdle}`, { headers: this.adapter.newHeaders({ "Content-Type": "text/plain" }) });
|
1056 | }
|
1057 | since(time) {
|
1058 | if (time === null) {
|
1059 | return "never";
|
1060 | }
|
1061 | let age = this.adapter.time - time;
|
1062 | const days = Math.floor(age / 864e5);
|
1063 | age = age % 864e5;
|
1064 | const hours = Math.floor(age / 36e5);
|
1065 | age = age % 36e5;
|
1066 | const minutes = Math.floor(age / 6e4);
|
1067 | age = age % 6e4;
|
1068 | const seconds = Math.floor(age / 1e3);
|
1069 | const millis = age % 1e3;
|
1070 | return (days > 0 ? `${days}d` : "") + (hours > 0 ? `${hours}h` : "") + (minutes > 0 ? `${minutes}m` : "") + (seconds > 0 ? `${seconds}s` : "") + (millis > 0 ? `${millis}u` : "");
|
1071 | }
|
1072 | log(value, context = "") {
|
1073 | if (this.debugLogA.length === DEBUG_LOG_BUFFER_SIZE) {
|
1074 | this.debugLogB = this.debugLogA;
|
1075 | this.debugLogA = [];
|
1076 | }
|
1077 | if (typeof value !== "string") {
|
1078 | value = this.errorToString(value);
|
1079 | }
|
1080 | this.debugLogA.push({ value, time: this.adapter.time, context });
|
1081 | }
|
1082 | errorToString(err) {
|
1083 | return `${err.name}(${err.message}, ${err.stack})`;
|
1084 | }
|
1085 | formatDebugLog(log) {
|
1086 | return log.map((entry) => `[${this.since(entry.time)}] ${entry.value} ${entry.context}`).join("\n");
|
1087 | }
|
1088 | };
|
1089 |
|
1090 |
|
1091 | var IdleScheduler = class {
|
1092 | constructor(adapter2, delay, maxDelay, debug) {
|
1093 | this.adapter = adapter2;
|
1094 | this.delay = delay;
|
1095 | this.maxDelay = maxDelay;
|
1096 | this.debug = debug;
|
1097 | this.queue = [];
|
1098 | this.scheduled = null;
|
1099 | this.empty = Promise.resolve();
|
1100 | this.emptyResolve = null;
|
1101 | this.lastTrigger = null;
|
1102 | this.lastRun = null;
|
1103 | this.oldestScheduledAt = null;
|
1104 | }
|
1105 | async trigger() {
|
1106 | var _a;
|
1107 | this.lastTrigger = this.adapter.time;
|
1108 | if (this.queue.length === 0) {
|
1109 | return;
|
1110 | }
|
1111 | if (this.scheduled !== null) {
|
1112 | this.scheduled.cancel = true;
|
1113 | }
|
1114 | const scheduled = {
|
1115 | cancel: false
|
1116 | };
|
1117 | this.scheduled = scheduled;
|
1118 | const now = this.adapter.time;
|
1119 | const maxDelay = Math.max(0, ((_a = this.oldestScheduledAt) != null ? _a : now) + this.maxDelay - now);
|
1120 | const delay = Math.min(maxDelay, this.delay);
|
1121 | await this.adapter.timeout(delay);
|
1122 | if (scheduled.cancel) {
|
1123 | return;
|
1124 | }
|
1125 | this.scheduled = null;
|
1126 | await this.execute();
|
1127 | }
|
1128 | async execute() {
|
1129 | this.lastRun = this.adapter.time;
|
1130 | while (this.queue.length > 0) {
|
1131 | const queue = this.queue;
|
1132 | this.queue = [];
|
1133 | await queue.reduce(async (previous, task) => {
|
1134 | await previous;
|
1135 | try {
|
1136 | await task.run();
|
1137 | } catch (err) {
|
1138 | this.debug.log(err, `while running idle task ${task.desc}`);
|
1139 | }
|
1140 | }, Promise.resolve());
|
1141 | }
|
1142 | if (this.emptyResolve !== null) {
|
1143 | this.emptyResolve();
|
1144 | this.emptyResolve = null;
|
1145 | }
|
1146 | this.empty = Promise.resolve();
|
1147 | this.oldestScheduledAt = null;
|
1148 | }
|
1149 | schedule(desc, run) {
|
1150 | this.queue.push({ desc, run });
|
1151 | if (this.emptyResolve === null) {
|
1152 | this.empty = new Promise((resolve) => {
|
1153 | this.emptyResolve = resolve;
|
1154 | });
|
1155 | }
|
1156 | if (this.oldestScheduledAt === null) {
|
1157 | this.oldestScheduledAt = this.adapter.time;
|
1158 | }
|
1159 | }
|
1160 | get size() {
|
1161 | return this.queue.length;
|
1162 | }
|
1163 | get taskDescriptions() {
|
1164 | return this.queue.map((task) => task.desc);
|
1165 | }
|
1166 | };
|
1167 |
|
1168 |
|
1169 | function hashManifest(manifest) {
|
1170 | return sha1(JSON.stringify(manifest));
|
1171 | }
|
1172 |
|
1173 |
|
1174 | function isMsgCheckForUpdates(msg) {
|
1175 | return msg.action === "CHECK_FOR_UPDATES";
|
1176 | }
|
1177 | function isMsgActivateUpdate(msg) {
|
1178 | return msg.action === "ACTIVATE_UPDATE";
|
1179 | }
|
1180 |
|
1181 |
|
1182 | var IDLE_DELAY = 5e3;
|
1183 | var MAX_IDLE_DELAY = 3e4;
|
1184 | var SUPPORTED_CONFIG_VERSION = 1;
|
1185 | var NOTIFICATION_OPTION_NAMES = [
|
1186 | "actions",
|
1187 | "badge",
|
1188 | "body",
|
1189 | "data",
|
1190 | "dir",
|
1191 | "icon",
|
1192 | "image",
|
1193 | "lang",
|
1194 | "renotify",
|
1195 | "requireInteraction",
|
1196 | "silent",
|
1197 | "tag",
|
1198 | "timestamp",
|
1199 | "title",
|
1200 | "vibrate"
|
1201 | ];
|
1202 | var DriverReadyState;
|
1203 | (function(DriverReadyState2) {
|
1204 | DriverReadyState2[DriverReadyState2["NORMAL"] = 0] = "NORMAL";
|
1205 | DriverReadyState2[DriverReadyState2["EXISTING_CLIENTS_ONLY"] = 1] = "EXISTING_CLIENTS_ONLY";
|
1206 | DriverReadyState2[DriverReadyState2["SAFE_MODE"] = 2] = "SAFE_MODE";
|
1207 | })(DriverReadyState || (DriverReadyState = {}));
|
1208 | var Driver = class {
|
1209 | constructor(scope2, adapter2, db) {
|
1210 | this.scope = scope2;
|
1211 | this.adapter = adapter2;
|
1212 | this.db = db;
|
1213 | this.state = DriverReadyState.NORMAL;
|
1214 | this.stateMessage = "(nominal)";
|
1215 | this.initialized = null;
|
1216 | this.clientVersionMap = new Map();
|
1217 | this.versions = new Map();
|
1218 | this.latestHash = null;
|
1219 | this.lastUpdateCheck = null;
|
1220 | this.scheduledNavUpdateCheck = false;
|
1221 | this.loggedInvalidOnlyIfCachedRequest = false;
|
1222 | this.ngswStatePath = this.adapter.parseUrl("ngsw/state", this.scope.registration.scope).path;
|
1223 | this.controlTable = this.db.open("control");
|
1224 | this.scope.addEventListener("install", (event) => {
|
1225 | event.waitUntil(this.scope.skipWaiting());
|
1226 | });
|
1227 | this.scope.addEventListener("activate", (event) => {
|
1228 | event.waitUntil((async () => {
|
1229 | await this.scope.clients.claim();
|
1230 | this.idle.schedule("activate: cleanup-old-sw-caches", async () => {
|
1231 | try {
|
1232 | await this.cleanupOldSwCaches();
|
1233 | } catch (err) {
|
1234 | this.debugger.log(err, "cleanupOldSwCaches @ activate: cleanup-old-sw-caches");
|
1235 | }
|
1236 | });
|
1237 | })());
|
1238 | if (this.scope.registration.active !== null) {
|
1239 | this.scope.registration.active.postMessage({ action: "INITIALIZE" });
|
1240 | }
|
1241 | });
|
1242 | this.scope.addEventListener("fetch", (event) => this.onFetch(event));
|
1243 | this.scope.addEventListener("message", (event) => this.onMessage(event));
|
1244 | this.scope.addEventListener("push", (event) => this.onPush(event));
|
1245 | this.scope.addEventListener("notificationclick", (event) => this.onClick(event));
|
1246 | this.debugger = new DebugHandler(this, this.adapter);
|
1247 | this.idle = new IdleScheduler(this.adapter, IDLE_DELAY, MAX_IDLE_DELAY, this.debugger);
|
1248 | }
|
1249 | onFetch(event) {
|
1250 | const req = event.request;
|
1251 | const scopeUrl = this.scope.registration.scope;
|
1252 | const requestUrlObj = this.adapter.parseUrl(req.url, scopeUrl);
|
1253 | if (req.headers.has("ngsw-bypass") || /[?&]ngsw-bypass(?:[=&]|$)/i.test(requestUrlObj.search)) {
|
1254 | return;
|
1255 | }
|
1256 | if (requestUrlObj.path === this.ngswStatePath) {
|
1257 | event.respondWith(this.debugger.handleFetch(req));
|
1258 | return;
|
1259 | }
|
1260 | if (this.state === DriverReadyState.SAFE_MODE) {
|
1261 | event.waitUntil(this.idle.trigger());
|
1262 | return;
|
1263 | }
|
1264 | if (requestUrlObj.origin.startsWith("http:") && scopeUrl.startsWith("https:")) {
|
1265 | this.debugger.log(`Ignoring passive mixed content request: Driver.fetch(${req.url})`);
|
1266 | return;
|
1267 | }
|
1268 | if (req.cache === "only-if-cached" && req.mode !== "same-origin") {
|
1269 | if (!this.loggedInvalidOnlyIfCachedRequest) {
|
1270 | this.loggedInvalidOnlyIfCachedRequest = true;
|
1271 | 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})`);
|
1272 | }
|
1273 | return;
|
1274 | }
|
1275 | event.respondWith(this.handleFetch(event));
|
1276 | }
|
1277 | onMessage(event) {
|
1278 | if (this.state === DriverReadyState.SAFE_MODE) {
|
1279 | return;
|
1280 | }
|
1281 | const data = event.data;
|
1282 | if (!data || !data.action) {
|
1283 | return;
|
1284 | }
|
1285 | event.waitUntil((async () => {
|
1286 | if (data.action === "INITIALIZE") {
|
1287 | return this.ensureInitialized(event);
|
1288 | }
|
1289 | if (!this.adapter.isClient(event.source)) {
|
1290 | return;
|
1291 | }
|
1292 | await this.ensureInitialized(event);
|
1293 | await this.handleMessage(data, event.source);
|
1294 | })());
|
1295 | }
|
1296 | onPush(msg) {
|
1297 | if (!msg.data) {
|
1298 | return;
|
1299 | }
|
1300 | msg.waitUntil(this.handlePush(msg.data.json()));
|
1301 | }
|
1302 | onClick(event) {
|
1303 | event.waitUntil(this.handleClick(event.notification, event.action));
|
1304 | }
|
1305 | async ensureInitialized(event) {
|
1306 | if (this.initialized !== null) {
|
1307 | return this.initialized;
|
1308 | }
|
1309 | try {
|
1310 | this.initialized = this.initialize();
|
1311 | await this.initialized;
|
1312 | } catch (error) {
|
1313 | this.state = DriverReadyState.SAFE_MODE;
|
1314 | this.stateMessage = `Initialization failed due to error: ${errorToString(error)}`;
|
1315 | throw error;
|
1316 | } finally {
|
1317 | event.waitUntil(this.idle.trigger());
|
1318 | }
|
1319 | }
|
1320 | async handleMessage(msg, from) {
|
1321 | if (isMsgCheckForUpdates(msg)) {
|
1322 | const action = this.checkForUpdate();
|
1323 | await this.completeOperation(from, action, msg.nonce);
|
1324 | } else if (isMsgActivateUpdate(msg)) {
|
1325 | const action = this.updateClient(from);
|
1326 | await this.completeOperation(from, action, msg.nonce);
|
1327 | }
|
1328 | }
|
1329 | async handlePush(data) {
|
1330 | await this.broadcast({
|
1331 | type: "PUSH",
|
1332 | data
|
1333 | });
|
1334 | if (!data.notification || !data.notification.title) {
|
1335 | return;
|
1336 | }
|
1337 | const desc = data.notification;
|
1338 | let options = {};
|
1339 | NOTIFICATION_OPTION_NAMES.filter((name) => desc.hasOwnProperty(name)).forEach((name) => options[name] = desc[name]);
|
1340 | await this.scope.registration.showNotification(desc["title"], options);
|
1341 | }
|
1342 | async handleClick(notification, action) {
|
1343 | var _a, _b, _c;
|
1344 | notification.close();
|
1345 | const options = {};
|
1346 | NOTIFICATION_OPTION_NAMES.filter((name) => name in notification).forEach((name) => options[name] = notification[name]);
|
1347 | const notificationAction = action === "" || action === void 0 ? "default" : action;
|
1348 | const onActionClick = (_b = (_a = notification == null ? void 0 : notification.data) == null ? void 0 : _a.onActionClick) == null ? void 0 : _b[notificationAction];
|
1349 | const urlToOpen = new URL((_c = onActionClick == null ? void 0 : onActionClick.url) != null ? _c : "", this.scope.registration.scope).href;
|
1350 | switch (onActionClick == null ? void 0 : onActionClick.operation) {
|
1351 | case "openWindow":
|
1352 | await this.scope.clients.openWindow(urlToOpen);
|
1353 | break;
|
1354 | case "focusLastFocusedOrOpen": {
|
1355 | let matchingClient = await this.getLastFocusedMatchingClient(this.scope);
|
1356 | if (matchingClient) {
|
1357 | await (matchingClient == null ? void 0 : matchingClient.focus());
|
1358 | } else {
|
1359 | await this.scope.clients.openWindow(urlToOpen);
|
1360 | }
|
1361 | break;
|
1362 | }
|
1363 | case "navigateLastFocusedOrOpen": {
|
1364 | let matchingClient = await this.getLastFocusedMatchingClient(this.scope);
|
1365 | if (matchingClient) {
|
1366 | matchingClient = await matchingClient.navigate(urlToOpen);
|
1367 | await (matchingClient == null ? void 0 : matchingClient.focus());
|
1368 | } else {
|
1369 | await this.scope.clients.openWindow(urlToOpen);
|
1370 | }
|
1371 | break;
|
1372 | }
|
1373 | default:
|
1374 | break;
|
1375 | }
|
1376 | await this.broadcast({
|
1377 | type: "NOTIFICATION_CLICK",
|
1378 | data: { action, notification: options }
|
1379 | });
|
1380 | }
|
1381 | async getLastFocusedMatchingClient(scope2) {
|
1382 | const windowClients = await scope2.clients.matchAll({ type: "window" });
|
1383 | return windowClients[0];
|
1384 | }
|
1385 | async completeOperation(client, promise, nonce) {
|
1386 | const response = { type: "OPERATION_COMPLETED", nonce };
|
1387 | try {
|
1388 | client.postMessage(__spreadProps(__spreadValues({}, response), {
|
1389 | result: await promise
|
1390 | }));
|
1391 | } catch (e) {
|
1392 | client.postMessage(__spreadProps(__spreadValues({}, response), {
|
1393 | error: e.toString()
|
1394 | }));
|
1395 | }
|
1396 | }
|
1397 | async updateClient(client) {
|
1398 | const existing = this.clientVersionMap.get(client.id);
|
1399 | if (existing === this.latestHash) {
|
1400 | return false;
|
1401 | }
|
1402 | let previous = void 0;
|
1403 | if (existing !== void 0) {
|
1404 | const existingVersion = this.versions.get(existing);
|
1405 | previous = this.mergeHashWithAppData(existingVersion.manifest, existing);
|
1406 | }
|
1407 | this.clientVersionMap.set(client.id, this.latestHash);
|
1408 | await this.sync();
|
1409 | const current = this.versions.get(this.latestHash);
|
1410 | const notice = {
|
1411 | type: "UPDATE_ACTIVATED",
|
1412 | previous,
|
1413 | current: this.mergeHashWithAppData(current.manifest, this.latestHash)
|
1414 | };
|
1415 | client.postMessage(notice);
|
1416 | return true;
|
1417 | }
|
1418 | async handleFetch(event) {
|
1419 | try {
|
1420 | await this.ensureInitialized(event);
|
1421 | } catch (e) {
|
1422 | return this.safeFetch(event.request);
|
1423 | }
|
1424 | if (event.request.mode === "navigate" && !this.scheduledNavUpdateCheck) {
|
1425 | this.scheduledNavUpdateCheck = true;
|
1426 | this.idle.schedule("check-updates-on-navigation", async () => {
|
1427 | this.scheduledNavUpdateCheck = false;
|
1428 | await this.checkForUpdate();
|
1429 | });
|
1430 | }
|
1431 | const appVersion = await this.assignVersion(event);
|
1432 | let res = null;
|
1433 | try {
|
1434 | if (appVersion !== null) {
|
1435 | try {
|
1436 | res = await appVersion.handleFetch(event.request, event);
|
1437 | } catch (err) {
|
1438 | if (err.isUnrecoverableState) {
|
1439 | await this.notifyClientsAboutUnrecoverableState(appVersion, err.message);
|
1440 | }
|
1441 | if (err.isCritical) {
|
1442 | this.debugger.log(err, `Driver.handleFetch(version: ${appVersion.manifestHash})`);
|
1443 | await this.versionFailed(appVersion, err);
|
1444 | return this.safeFetch(event.request);
|
1445 | }
|
1446 | throw err;
|
1447 | }
|
1448 | }
|
1449 | if (res === null) {
|
1450 | return this.safeFetch(event.request);
|
1451 | }
|
1452 | return res;
|
1453 | } finally {
|
1454 | event.waitUntil(this.idle.trigger());
|
1455 | }
|
1456 | }
|
1457 | async initialize() {
|
1458 | const table = await this.controlTable;
|
1459 | let manifests, assignments, latest;
|
1460 | try {
|
1461 | [manifests, assignments, latest] = await Promise.all([
|
1462 | table.read("manifests"),
|
1463 | table.read("assignments"),
|
1464 | table.read("latest")
|
1465 | ]);
|
1466 | if (!this.versions.has(latest.latest) && !manifests.hasOwnProperty(latest.latest)) {
|
1467 | this.debugger.log(`Missing manifest for latest version hash ${latest.latest}`, "initialize: read from DB");
|
1468 | throw new Error(`Missing manifest for latest hash ${latest.latest}`);
|
1469 | }
|
1470 | this.idle.schedule("init post-load (update)", async () => {
|
1471 | await this.checkForUpdate();
|
1472 | });
|
1473 | } catch (_) {
|
1474 | const manifest = await this.fetchLatestManifest();
|
1475 | const hash = hashManifest(manifest);
|
1476 | manifests = { [hash]: manifest };
|
1477 | assignments = {};
|
1478 | latest = { latest: hash };
|
1479 | await Promise.all([
|
1480 | table.write("manifests", manifests),
|
1481 | table.write("assignments", assignments),
|
1482 | table.write("latest", latest)
|
1483 | ]);
|
1484 | }
|
1485 | this.idle.schedule("init post-load (cleanup)", async () => {
|
1486 | await this.cleanupCaches();
|
1487 | });
|
1488 | Object.keys(manifests).forEach((hash) => {
|
1489 | const manifest = manifests[hash];
|
1490 | if (!this.versions.has(hash)) {
|
1491 | this.versions.set(hash, new AppVersion(this.scope, this.adapter, this.db, this.idle, this.debugger, manifest, hash));
|
1492 | }
|
1493 | });
|
1494 | Object.keys(assignments).forEach((clientId) => {
|
1495 | const hash = assignments[clientId];
|
1496 | if (this.versions.has(hash)) {
|
1497 | this.clientVersionMap.set(clientId, hash);
|
1498 | } else {
|
1499 | this.clientVersionMap.set(clientId, latest.latest);
|
1500 | this.debugger.log(`Unknown version ${hash} mapped for client ${clientId}, using latest instead`, `initialize: map assignments`);
|
1501 | }
|
1502 | });
|
1503 | this.latestHash = latest.latest;
|
1504 | if (!this.versions.has(latest.latest)) {
|
1505 | throw new Error(`Invariant violated (initialize): latest hash ${latest.latest} has no known manifest`);
|
1506 | }
|
1507 | await Promise.all(Object.keys(manifests).map(async (hash) => {
|
1508 | try {
|
1509 | await this.scheduleInitialization(this.versions.get(hash));
|
1510 | } catch (err) {
|
1511 | this.debugger.log(err, `initialize: schedule init of ${hash}`);
|
1512 | return false;
|
1513 | }
|
1514 | }));
|
1515 | }
|
1516 | lookupVersionByHash(hash, debugName = "lookupVersionByHash") {
|
1517 | if (!this.versions.has(hash)) {
|
1518 | throw new Error(`Invariant violated (${debugName}): want AppVersion for ${hash} but not loaded`);
|
1519 | }
|
1520 | return this.versions.get(hash);
|
1521 | }
|
1522 | async assignVersion(event) {
|
1523 | const clientId = event.resultingClientId || event.clientId;
|
1524 | if (clientId) {
|
1525 | if (this.clientVersionMap.has(clientId)) {
|
1526 | const hash = this.clientVersionMap.get(clientId);
|
1527 | let appVersion = this.lookupVersionByHash(hash, "assignVersion");
|
1528 | if (this.state === DriverReadyState.NORMAL && hash !== this.latestHash && appVersion.isNavigationRequest(event.request)) {
|
1529 | if (this.latestHash === null) {
|
1530 | throw new Error(`Invariant violated (assignVersion): latestHash was null`);
|
1531 | }
|
1532 | const client = await this.scope.clients.get(clientId);
|
1533 | if (client) {
|
1534 | await this.updateClient(client);
|
1535 | }
|
1536 | appVersion = this.lookupVersionByHash(this.latestHash, "assignVersion");
|
1537 | }
|
1538 | return appVersion;
|
1539 | } else {
|
1540 | if (this.state !== DriverReadyState.NORMAL) {
|
1541 | return null;
|
1542 | }
|
1543 | if (this.latestHash === null) {
|
1544 | throw new Error(`Invariant violated (assignVersion): latestHash was null`);
|
1545 | }
|
1546 | this.clientVersionMap.set(clientId, this.latestHash);
|
1547 | await this.sync();
|
1548 | return this.lookupVersionByHash(this.latestHash, "assignVersion");
|
1549 | }
|
1550 | } else {
|
1551 | if (this.state !== DriverReadyState.NORMAL) {
|
1552 | return null;
|
1553 | }
|
1554 | if (this.latestHash === null) {
|
1555 | throw new Error(`Invariant violated (assignVersion): latestHash was null`);
|
1556 | }
|
1557 | return this.lookupVersionByHash(this.latestHash, "assignVersion");
|
1558 | }
|
1559 | }
|
1560 | async fetchLatestManifest(ignoreOfflineError = false) {
|
1561 | const res = await this.safeFetch(this.adapter.newRequest("ngsw.json?ngsw-cache-bust=" + Math.random()));
|
1562 | if (!res.ok) {
|
1563 | if (res.status === 404) {
|
1564 | await this.deleteAllCaches();
|
1565 | await this.scope.registration.unregister();
|
1566 | } else if ((res.status === 503 || res.status === 504) && ignoreOfflineError) {
|
1567 | return null;
|
1568 | }
|
1569 | throw new Error(`Manifest fetch failed! (status: ${res.status})`);
|
1570 | }
|
1571 | this.lastUpdateCheck = this.adapter.time;
|
1572 | return res.json();
|
1573 | }
|
1574 | async deleteAllCaches() {
|
1575 | const cacheNames = await this.adapter.caches.keys();
|
1576 | await Promise.all(cacheNames.map((name) => this.adapter.caches.delete(name)));
|
1577 | }
|
1578 | async scheduleInitialization(appVersion) {
|
1579 | const initialize = async () => {
|
1580 | try {
|
1581 | await appVersion.initializeFully();
|
1582 | } catch (err) {
|
1583 | this.debugger.log(err, `initializeFully for ${appVersion.manifestHash}`);
|
1584 | await this.versionFailed(appVersion, err);
|
1585 | }
|
1586 | };
|
1587 | if (this.scope.registration.scope.indexOf("://localhost") > -1) {
|
1588 | return initialize();
|
1589 | }
|
1590 | this.idle.schedule(`initialization(${appVersion.manifestHash})`, initialize);
|
1591 | }
|
1592 | async versionFailed(appVersion, err) {
|
1593 | const broken = Array.from(this.versions.entries()).find(([hash, version]) => version === appVersion);
|
1594 | if (broken === void 0) {
|
1595 | return;
|
1596 | }
|
1597 | const brokenHash = broken[0];
|
1598 | if (this.latestHash === brokenHash) {
|
1599 | this.state = DriverReadyState.EXISTING_CLIENTS_ONLY;
|
1600 | this.stateMessage = `Degraded due to: ${errorToString(err)}`;
|
1601 | }
|
1602 | }
|
1603 | async setupUpdate(manifest, hash) {
|
1604 | try {
|
1605 | const newVersion = new AppVersion(this.scope, this.adapter, this.db, this.idle, this.debugger, manifest, hash);
|
1606 | if (manifest.configVersion !== SUPPORTED_CONFIG_VERSION) {
|
1607 | await this.deleteAllCaches();
|
1608 | await this.scope.registration.unregister();
|
1609 | throw new Error(`Invalid config version: expected ${SUPPORTED_CONFIG_VERSION}, got ${manifest.configVersion}.`);
|
1610 | }
|
1611 | await newVersion.initializeFully(this);
|
1612 | this.versions.set(hash, newVersion);
|
1613 | this.latestHash = hash;
|
1614 | if (this.state === DriverReadyState.EXISTING_CLIENTS_ONLY) {
|
1615 | this.state = DriverReadyState.NORMAL;
|
1616 | this.stateMessage = "(nominal)";
|
1617 | }
|
1618 | await this.sync();
|
1619 | await this.notifyClientsAboutVersionReady(manifest, hash);
|
1620 | } catch (e) {
|
1621 | await this.notifyClientsAboutVersionInstallationFailed(manifest, hash, e);
|
1622 | throw e;
|
1623 | }
|
1624 | }
|
1625 | async checkForUpdate() {
|
1626 | let hash = "(unknown)";
|
1627 | try {
|
1628 | const manifest = await this.fetchLatestManifest(true);
|
1629 | if (manifest === null) {
|
1630 | this.debugger.log("Check for update aborted. (Client or server offline.)");
|
1631 | return false;
|
1632 | }
|
1633 | hash = hashManifest(manifest);
|
1634 | if (this.versions.has(hash)) {
|
1635 | await this.notifyClientsAboutNoNewVersionDetected(manifest, hash);
|
1636 | return false;
|
1637 | }
|
1638 | await this.notifyClientsAboutVersionDetected(manifest, hash);
|
1639 | await this.setupUpdate(manifest, hash);
|
1640 | return true;
|
1641 | } catch (err) {
|
1642 | this.debugger.log(err, `Error occurred while updating to manifest ${hash}`);
|
1643 | this.state = DriverReadyState.EXISTING_CLIENTS_ONLY;
|
1644 | this.stateMessage = `Degraded due to failed initialization: ${errorToString(err)}`;
|
1645 | return false;
|
1646 | }
|
1647 | }
|
1648 | async sync() {
|
1649 | const table = await this.controlTable;
|
1650 | const manifests = {};
|
1651 | this.versions.forEach((version, hash) => {
|
1652 | manifests[hash] = version.manifest;
|
1653 | });
|
1654 | const assignments = {};
|
1655 | this.clientVersionMap.forEach((hash, clientId) => {
|
1656 | assignments[clientId] = hash;
|
1657 | });
|
1658 | const latest = {
|
1659 | latest: this.latestHash
|
1660 | };
|
1661 | await Promise.all([
|
1662 | table.write("manifests", manifests),
|
1663 | table.write("assignments", assignments),
|
1664 | table.write("latest", latest)
|
1665 | ]);
|
1666 | }
|
1667 | async cleanupCaches() {
|
1668 | try {
|
1669 | const activeClients = new Set((await this.scope.clients.matchAll()).map((client) => client.id));
|
1670 | const knownClients = Array.from(this.clientVersionMap.keys());
|
1671 | const obsoleteClients = knownClients.filter((id) => !activeClients.has(id));
|
1672 | obsoleteClients.forEach((id) => this.clientVersionMap.delete(id));
|
1673 | const usedVersions = new Set(this.clientVersionMap.values());
|
1674 | const obsoleteVersions = Array.from(this.versions.keys()).filter((version) => !usedVersions.has(version) && version !== this.latestHash);
|
1675 | obsoleteVersions.forEach((version) => this.versions.delete(version));
|
1676 | await this.sync();
|
1677 | const allCaches = await this.adapter.caches.keys();
|
1678 | const usedCaches = new Set(await this.getCacheNames());
|
1679 | const cachesToDelete = allCaches.filter((name) => !usedCaches.has(name));
|
1680 | await Promise.all(cachesToDelete.map((name) => this.adapter.caches.delete(name)));
|
1681 | } catch (err) {
|
1682 | this.debugger.log(err, "cleanupCaches");
|
1683 | }
|
1684 | }
|
1685 | async cleanupOldSwCaches() {
|
1686 | const caches = this.adapter.caches.original;
|
1687 | const cacheNames = await caches.keys();
|
1688 | const oldSwCacheNames = cacheNames.filter((name) => /^ngsw:(?!\/)/.test(name));
|
1689 | await Promise.all(oldSwCacheNames.map((name) => caches.delete(name)));
|
1690 | }
|
1691 | lookupResourceWithHash(url, hash) {
|
1692 | return Array.from(this.versions.values()).reduce(async (prev, version) => {
|
1693 | if (await prev !== null) {
|
1694 | return prev;
|
1695 | }
|
1696 | return version.lookupResourceWithHash(url, hash);
|
1697 | }, Promise.resolve(null));
|
1698 | }
|
1699 | async lookupResourceWithoutHash(url) {
|
1700 | await this.initialized;
|
1701 | const version = this.versions.get(this.latestHash);
|
1702 | return version ? version.lookupResourceWithoutHash(url) : null;
|
1703 | }
|
1704 | async previouslyCachedResources() {
|
1705 | await this.initialized;
|
1706 | const version = this.versions.get(this.latestHash);
|
1707 | return version ? version.previouslyCachedResources() : [];
|
1708 | }
|
1709 | async recentCacheStatus(url) {
|
1710 | const version = this.versions.get(this.latestHash);
|
1711 | return version ? version.recentCacheStatus(url) : UpdateCacheStatus.NOT_CACHED;
|
1712 | }
|
1713 | mergeHashWithAppData(manifest, hash) {
|
1714 | return {
|
1715 | hash,
|
1716 | appData: manifest.appData
|
1717 | };
|
1718 | }
|
1719 | async notifyClientsAboutUnrecoverableState(appVersion, reason) {
|
1720 | const broken = Array.from(this.versions.entries()).find(([hash, version]) => version === appVersion);
|
1721 | if (broken === void 0) {
|
1722 | return;
|
1723 | }
|
1724 | const brokenHash = broken[0];
|
1725 | const affectedClients = Array.from(this.clientVersionMap.entries()).filter(([clientId, hash]) => hash === brokenHash).map(([clientId]) => clientId);
|
1726 | await Promise.all(affectedClients.map(async (clientId) => {
|
1727 | const client = await this.scope.clients.get(clientId);
|
1728 | if (client) {
|
1729 | client.postMessage({ type: "UNRECOVERABLE_STATE", reason });
|
1730 | }
|
1731 | }));
|
1732 | }
|
1733 | async notifyClientsAboutVersionInstallationFailed(manifest, hash, error) {
|
1734 | await this.initialized;
|
1735 | const clients = await this.scope.clients.matchAll();
|
1736 | await Promise.all(clients.map(async (client) => {
|
1737 | client.postMessage({
|
1738 | type: "VERSION_INSTALLATION_FAILED",
|
1739 | version: this.mergeHashWithAppData(manifest, hash),
|
1740 | error: errorToString(error)
|
1741 | });
|
1742 | }));
|
1743 | }
|
1744 | async notifyClientsAboutNoNewVersionDetected(manifest, hash) {
|
1745 | await this.initialized;
|
1746 | const clients = await this.scope.clients.matchAll();
|
1747 | await Promise.all(clients.map(async (client) => {
|
1748 | client.postMessage({ type: "NO_NEW_VERSION_DETECTED", version: this.mergeHashWithAppData(manifest, hash) });
|
1749 | }));
|
1750 | }
|
1751 | async notifyClientsAboutVersionDetected(manifest, hash) {
|
1752 | await this.initialized;
|
1753 | const clients = await this.scope.clients.matchAll();
|
1754 | await Promise.all(clients.map(async (client) => {
|
1755 | const version = this.clientVersionMap.get(client.id);
|
1756 | if (version === void 0) {
|
1757 | return;
|
1758 | }
|
1759 | client.postMessage({ type: "VERSION_DETECTED", version: this.mergeHashWithAppData(manifest, hash) });
|
1760 | }));
|
1761 | }
|
1762 | async notifyClientsAboutVersionReady(manifest, hash) {
|
1763 | await this.initialized;
|
1764 | const clients = await this.scope.clients.matchAll();
|
1765 | await Promise.all(clients.map(async (client) => {
|
1766 | const version = this.clientVersionMap.get(client.id);
|
1767 | if (version === void 0) {
|
1768 | return;
|
1769 | }
|
1770 | if (version === this.latestHash) {
|
1771 | return;
|
1772 | }
|
1773 | const current = this.versions.get(version);
|
1774 | const notice = {
|
1775 | type: "VERSION_READY",
|
1776 | currentVersion: this.mergeHashWithAppData(current.manifest, version),
|
1777 | latestVersion: this.mergeHashWithAppData(manifest, hash)
|
1778 | };
|
1779 | client.postMessage(notice);
|
1780 | }));
|
1781 | }
|
1782 | async broadcast(msg) {
|
1783 | const clients = await this.scope.clients.matchAll();
|
1784 | clients.forEach((client) => {
|
1785 | client.postMessage(msg);
|
1786 | });
|
1787 | }
|
1788 | async debugState() {
|
1789 | return {
|
1790 | state: DriverReadyState[this.state],
|
1791 | why: this.stateMessage,
|
1792 | latestHash: this.latestHash,
|
1793 | lastUpdateCheck: this.lastUpdateCheck
|
1794 | };
|
1795 | }
|
1796 | async debugVersions() {
|
1797 | return Array.from(this.versions.keys()).map((hash) => {
|
1798 | const version = this.versions.get(hash);
|
1799 | const clients = Array.from(this.clientVersionMap.entries()).filter(([clientId, version2]) => version2 === hash).map(([clientId, version2]) => clientId);
|
1800 | return {
|
1801 | hash,
|
1802 | manifest: version.manifest,
|
1803 | clients,
|
1804 | status: ""
|
1805 | };
|
1806 | });
|
1807 | }
|
1808 | async debugIdleState() {
|
1809 | return {
|
1810 | queue: this.idle.taskDescriptions,
|
1811 | lastTrigger: this.idle.lastTrigger,
|
1812 | lastRun: this.idle.lastRun
|
1813 | };
|
1814 | }
|
1815 | async safeFetch(req) {
|
1816 | try {
|
1817 | return await this.scope.fetch(req);
|
1818 | } catch (err) {
|
1819 | this.debugger.log(err, `Driver.fetch(${req.url})`);
|
1820 | return this.adapter.newResponse(null, {
|
1821 | status: 504,
|
1822 | statusText: "Gateway Timeout"
|
1823 | });
|
1824 | }
|
1825 | }
|
1826 | async getCacheNames() {
|
1827 | const controlTable = await this.controlTable;
|
1828 | const appVersions = Array.from(this.versions.values());
|
1829 | const appVersionCacheNames = await Promise.all(appVersions.map((version) => version.getCacheNames()));
|
1830 | return [controlTable.cacheName].concat(...appVersionCacheNames);
|
1831 | }
|
1832 | };
|
1833 |
|
1834 |
|
1835 | var scope = self;
|
1836 | var adapter = new Adapter(scope.registration.scope, self.caches);
|
1837 | new Driver(scope, adapter, new CacheDatabase(adapter));
|
1838 | })();
|
1839 |
|
1840 |
|
1841 |
|
1842 |
|
1843 |
|
1844 |
|
1845 |
|