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 var __publicField = (obj, key, value) => { 22 __defNormalProp(obj, typeof key !== "symbol" ? key + "" : key, value); 23 return value; 24 }; 25 26
26// packages/service-worker/worker/src/named-cache-storage.js 27 var NamedCacheStorage = class { 28 constructor(original, cacheNamePrefix) { 29 __publicField(this, "original"); 30 __publicField(this, "cacheNamePrefix"); 31 this.original = original; 32 this.cacheNamePrefix = cacheNamePrefix; 33 } 34 delete(cacheName) { 35 return this.original.delete(`${this.cacheNamePrefix}:${cacheName}`); 36 } 37 has(cacheName) { 38 return this.original.has(`${this.cacheNamePrefix}:${cacheName}`); 39 } 40 async keys() { 41 const prefix = `${this.cacheNamePrefix}:`; 42 const allCacheNames = await this.original.keys(); 43 const ownCacheNames = allCacheNames.filter((name) => name.startsWith(prefix)); 44 return ownCacheNames.map((name) => name.slice(prefix.length)); 45 } 46 match(request, options) { 47 return this.original.match(request, options); 48 } 49 async open(cacheName) { 50 const cache = await this.original.open(`${this.cacheNamePrefix}:${cacheName}`); 51 return Object.assign(cache, { name: cacheName }); 52 } 53 }; 54 55
55// packages/service-worker/worker/src/adapter.js 56 var Adapter = class { 57 constructor(scopeUrl, caches) { 58 __publicField(this, "scopeUrl"); 59 __publicField(this, "caches"); 60 __publicField(this, "origin"); 61 this.scopeUrl = scopeUrl; 62 const parsedScopeUrl = this.parseUrl(this.scopeUrl); 63 this.origin = parsedScopeUrl.origin; 64 this.caches = new NamedCacheStorage(caches, `ngsw:${parsedScopeUrl.path}`); 65 } 66 /** 67 * Wrapper around the `Request` constructor. 68 */ 69 newRequest(input, init) { 70 return new Request(input, init); 71 } 72 /** 73 * Wrapper around the `Response` constructor. 74 */ 75 newResponse(body, init) { 76 return new Response(body, init); 77 } 78 /** 79 * Wrapper around the `Headers` constructor. 80 */ 81 newHeaders(headers) { 82 return new Headers(headers); 83 } 84 /** 85 * Test if a given object is an instance of `Client`. 86 */ 87 isClient(source) { 88 return source instanceof Client; 89 } 90 /** 91 * Read the current UNIX time in milliseconds. 92 */ 93 get time() { 94 return Date.now(); 95 } 96 /** 97 * Get a normalized representation of a URL such as those found in the ServiceWorker's `ngsw.json` 98 * configuration. 99 * 100 * More specifically: 101 * 1. Resolve the URL relative to the ServiceWorker's scope. 102 * 2. If the URL is relative to the ServiceWorker's own origin, then only return the path part. 103 * Otherwise, return the full URL. 104 * 105 * @param url The raw request URL. 106 * @return A normalized representation of the URL. 107 */ 108 normalizeUrl(url) { 109 const parsed = this.parseUrl(url, this.scopeUrl); 110 return parsed.origin === this.origin ? parsed.path : url; 111 } 112 /** 113 * Parse a URL into its different parts, such as `origin`, `path` and `search`. 114 */ 115 parseUrl(url, relativeTo) { 116 const parsed = !relativeTo ? new URL(url) : new URL(url, relativeTo); 117 return { origin: parsed.origin, path: parsed.pathname, search: parsed.search }; 118 } 119 /** 120 * Wait for a given amount of time before completing a Promise. 121 */ 122 timeout(ms) { 123 return new Promise((resolve) => { 124 setTimeout(() => resolve(), ms); 125 }); 126 } 127 }; 128 129
129// packages/service-worker/worker/src/database.js 130 var NotFound = class { 131 constructor(table, key) { 132 __publicField(this, "table"); 133 __publicField(this, "key"); 134 this.table = table; 135 this.key = key; 136 } 137 }; 138 139
139// packages/service-worker/worker/src/db-cache.js 140 var CacheDatabase = class { 141 constructor(adapter2) { 142 __publicField(this, "adapter"); 143 __publicField(this, "cacheNamePrefix", "db"); 144 __publicField(this, "tables", /* @__PURE__ */ new Map()); 145 this.adapter = adapter2; 146 } 147 "delete"(name) { 148 if (this.tables.has(name)) { 149 this.tables.delete(name); 150 } 151 return this.adapter.caches.delete(`${this.cacheNamePrefix}:${name}`); 152 } 153 async list() { 154 const prefix = `${this.cacheNamePrefix}:`; 155 const allCacheNames = await this.adapter.caches.keys(); 156 const dbCacheNames = allCacheNames.filter((name) => name.startsWith(prefix)); 157 return dbCacheNames.map((name) => name.slice(prefix.length)); 158 } 159 async open(name, cacheQueryOptions) { 160 if (!this.tables.has(name)) { 161 const cache = await this.adapter.caches.open(`${this.cacheNamePrefix}:${name}`); 162 const table = new CacheTable(name, cache, this.adapter, cacheQueryOptions); 163 this.tables.set(name, table); 164 } 165 return this.tables.get(name); 166 } 167 }; 168 var CacheTable = class { 169 constructor(name, cache, adapter2, cacheQueryOptions) { 170 __publicField(this, "name"); 171 __publicField(this, "cache"); 172 __publicField(this, "adapter"); 173 __publicField(this, "cacheQueryOptions"); 174 __publicField(this, "cacheName"); 175 this.name = name; 176 this.cache = cache; 177 this.adapter = adapter2; 178 this.cacheQueryOptions = cacheQueryOptions; 179 this.cacheName = this.cache.name; 180 } 181 request(key) { 182 return this.adapter.newRequest("/" + key); 183 } 184 "delete"(key) { 185 return this.cache.delete(this.request(key), this.cacheQueryOptions); 186 } 187 keys() { 188 return this.cache.keys().then((requests) => requests.map((req) => req.url.slice(1))); 189 } 190 read(key) { 191 return this.cache.match(this.request(key), this.cacheQueryOptions).then((res) => { 192 if (res === void 0) { 193 return Promise.reject(new NotFound(this.name, key)); 194 } 195 return res.json(); 196 }); 197 } 198 write(key, value) { 199 return this.cache.put(this.request(key), this.adapter.newResponse(JSON.stringify(value))); 200 } 201 }; 202 203
203// packages/service-worker/worker/src/api.js 204 var UpdateCacheStatus; 205 (function(UpdateCacheStatus2) { 206 UpdateCacheStatus2[UpdateCacheStatus2["NOT_CACHED"] = 0] = "NOT_CACHED"; 207 UpdateCacheStatus2[UpdateCacheStatus2["CACHED_BUT_UNUSED"] = 1] = "CACHED_BUT_UNUSED"; 208 UpdateCacheStatus2[UpdateCacheStatus2["CACHED"] = 2] = "CACHED"; 209 })(UpdateCacheStatus || (UpdateCacheStatus = {})); 210 211
211// packages/service-worker/worker/src/error.js 212 var SwCriticalError = class extends Error { 213 constructor() { 214 super(...arguments); 215 __publicField(this, "isCritical", true); 216 } 217 }; 218 function errorToString(error) { 219 if (error instanceof Error) { 220 return `${error.message} 221${error.stack}`; 222 } else { 223 return `${error}`; 224 } 225 } 226 var SwUnrecoverableStateError = class extends SwCriticalError { 227 constructor() { 228 super(...arguments); 229 __publicField(this, "isUnrecoverableState", true); 230 } 231 }; 232 233
233// packages/service-worker/worker/src/sha1.js 234 function sha1(str) { 235 const utf8 = str; 236 const words32 = stringToWords32(utf8, Endian.Big); 237 return _sha1(words32, utf8.length * 8); 238 } 239 function sha1Binary(buffer) { 240 const words32 = arrayBufferToWords32(buffer, Endian.Big); 241 return _sha1(words32, buffer.byteLength * 8); 242 } 243 function _sha1(words32, len) { 244 const w = []; 245 let [a, b, c, d, e] = [1732584193, 4023233417, 2562383102, 271733878, 3285377520]; 246 words32[len >> 5] |= 128 << 24 - len % 32; 247 words32[(len + 64 >> 9 << 4) + 15] = len; 248 for (let i = 0; i < words32.length; i += 16) { 249 const [h0, h1, h2, h3, h4] = [a, b, c, d, e]; 250 for (let j = 0; j < 80; j++) { 251 if (j < 16) { 252 w[j] = words32[i + j]; 253 } else { 254 w[j] = rol32(w[j - 3] ^ w[j - 8] ^ w[j - 14] ^ w[j - 16], 1); 255 } 256 const [f, k] = fk(j, b, c, d); 257 const temp = [rol32(a, 5), f, e, k, w[j]].reduce(add32); 258 [e, d, c, b, a] = [d, c, rol32(b, 30), a, temp]; 259 } 260 [a, b, c, d, e] = [add32(a, h0), add32(b, h1), add32(c, h2), add32(d, h3), add32(e, h4)]; 261 } 262 return byteStringToHexString(words32ToByteString([a, b, c, d, e])); 263 } 264 function add32(a, b) { 265 return add32to64(a, b)[1]; 266 } 267 function add32to64(a, b) { 268 const low = (a & 65535) + (b & 65535); 269 const high = (a >>> 16) + (b >>> 16) + (low >>> 16); 270 return [high >>> 16, high << 16 | low & 65535]; 271 } 272 function rol32(a, count) { 273 return a << count | a >>> 32 - count; 274 } 275 var Endian; 276 (function(Endian2) { 277 Endian2[Endian2["Little"] = 0] = "Little"; 278 Endian2[Endian2["Big"] = 1] = "Big"; 279 })(Endian || (Endian = {})); 280 function fk(index, b, c, d) { 281 if (index < 20) { 282 return [b & c | ~b & d, 1518500249]; 283 } 284 if (index < 40) { 285 return [b ^ c ^ d, 1859775393]; 286 } 287 if (index < 60) { 288 return [b & c | b & d | c & d, 2400959708]; 289 } 290 return [b ^ c ^ d, 3395469782]; 291 } 292 function stringToWords32(str, endian) { 293 const size = str.length + 3 >>> 2; 294 const words32 = []; 295 for (let i = 0; i < size; i++) { 296 words32[i] = wordAt(str, i * 4, endian); 297 } 298 return words32; 299 } 300 function arrayBufferToWords32(buffer, endian) { 301 const size = buffer.byteLength + 3 >>> 2; 302 const words32 = []; 303 const view = new Uint8Array(buffer); 304 for (let i = 0; i < size; i++) { 305 words32[i] = wordAt(view, i * 4, endian); 306 } 307 return words32; 308 } 309 function byteAt(str, index) { 310 if (typeof str === "string") { 311 return index >= str.length ? 0 : str.charCodeAt(index) & 255; 312 } else { 313 return index >= str.byteLength ? 0 : str[index] & 255; 314 } 315 } 316 function wordAt(str, index, endian) { 317 let word = 0; 318 if (endian === Endian.Big) { 319 for (let i = 0; i < 4; i++) { 320 word += byteAt(str, index + i) << 24 - 8 * i; 321 } 322 } else { 323 for (let i = 0; i < 4; i++) { 324 word += byteAt(str, index + i) << 8 * i; 325 } 326 } 327 return word; 328 } 329 function words32ToByteString(words32) { 330 return words32.reduce((str, word) => str + word32ToByteString(word), ""); 331 } 332 function word32ToByteString(word) { 333 let str = ""; 334 for (let i = 0; i < 4; i++) { 335 str += String.fromCharCode(word >>> 8 * (3 - i) & 255); 336 } 337 return str; 338 } 339 function byteStringToHexString(str) { 340 let hex = ""; 341 for (let i = 0; i < str.length; i++) { 342 const b = byteAt(str, i); 343 hex += (b >>> 4).toString(16) + (b & 15).toString(16); 344 } 345 return hex.toLowerCase(); 346 } 347 348
348// packages/service-worker/worker/src/assets.js 349 var AssetGroup = class { 350 constructor(scope2, adapter2, idle, config, hashes, db, cacheNamePrefix) { 351 __publicField(this, "scope"); 352 __publicField(this, "adapter"); 353 __publicField(this, "idle"); 354 __publicField(this, "config"); 355 __publicField(this, "hashes"); 356 __publicField(this, "db"); 357 /** 358 * A deduplication cache, to make sure the SW never makes two network requests 359 * for the same resource at once. Managed by `fetchAndCacheOnce`. 360 */ 361 __publicField(this, "inFlightRequests", /* @__PURE__ */ new Map()); 362 /** 363 * Normalized resource URLs. 364 */ 365 __publicField(this, "urls", []); 366 /** 367 * Regular expression patterns. 368 */ 369 __publicField(this, "patterns", []); 370 /** 371 * A Promise which resolves to the `Cache` used to back this asset group. This 372 * is opened from the constructor. 373 */ 374 __publicField(this, "cache"); 375 /** 376 * Group name from the configuration. 377 */ 378 __publicField(this, "name"); 379 /** 380 * Metadata associated with specific cache entries. 381 */ 382 __publicField(this, "metadata"); 383 this.scope = scope2; 384 this.adapter = adapter2; 385 this.idle = idle; 386 this.config = config; 387 this.hashes = hashes; 388 this.db = db; 389 this.name = config.name; 390 this.urls = config.urls.map((url) => adapter2.normalizeUrl(url)); 391 this.patterns = config.patterns.map((pattern) => new RegExp(pattern)); 392 this.cache = adapter2.caches.open(`${cacheNamePrefix}:${config.name}:cache`); 393 this.metadata = this.db.open(`${cacheNamePrefix}:${config.name}:meta`, config.cacheQueryOptions); 394 } 395 async cacheStatus(url) { 396 const cache = await this.cache; 397 const meta = await this.metadata; 398 const req = this.adapter.newRequest(url); 399 const res = await cache.match(req, this.config.cacheQueryOptions); 400 if (res === void 0) { 401 return UpdateCacheStatus.NOT_CACHED; 402 } 403 try { 404 const data = await meta.read(req.url); 405 if (!data.used) { 406 return UpdateCacheStatus.CACHED_BUT_UNUSED; 407 } 408 } catch (_) { 409 } 410 return UpdateCacheStatus.CACHED; 411 } 412 /** 413 * Return a list of the names of all caches used by this group. 414 */ 415 async getCacheNames() { 416 const [cache, metadata] = await Promise.all([this.cache, this.metadata]); 417 return [cache.name, metadata.cacheName]; 418 } 419 /** 420 * Process a request for a given resource and return it, or return null if it's not available. 421 */ 422 async handleFetch(req, _event) { 423 const url = this.adapter.normalizeUrl(req.url); 424 if (this.urls.indexOf(url) !== -1 || this.patterns.some((pattern) => pattern.test(url))) { 425 const cache = await this.cache; 426 let cachedResponse; 427 try { 428 cachedResponse = await cache.match(req, this.config.cacheQueryOptions); 429 } catch (error) { 430 throw new SwCriticalError(`Cache is throwing while looking for a match: ${error}`); 431 } 432 if (cachedResponse !== void 0) { 433 if (this.hashes.has(url)) { 434 return cachedResponse; 435 } else { 436 if (await this.needToRevalidate(req, cachedResponse)) { 437 this.idle.schedule(`revalidate(${cache.name}): ${req.url}`, async () => { 438 await this.fetchAndCacheOnce(req); 439 }); 440 } 441 return cachedResponse; 442 } 443 } 444 const res = await this.fetchAndCacheOnce(this.newRequestWithMetadata(req.url, req)); 445 return res.clone(); 446 } else { 447 return null; 448 } 449 } 450 /** 451 * Some resources are cached without a hash, meaning that their expiration is controlled 452 * by HTTP caching headers. Check whether the given request/response pair is still valid 453 * per the caching headers. 454 */ 455 async needToRevalidate(req, res) { 456 if (res.headers.has("Cache-Control")) { 457 const cacheControl = res.headers.get("Cache-Control"); 458 const cacheDirectives = cacheControl.split(",").map((v) => v.trim()).map((v) => v.split("="));
459 cacheDirectives.forEach((v) => v[0] = v[0].toLowerCase()); 460 const maxAgeDirective = cacheDirectives.find((v) => v[0] === "max-age"); 461 const cacheAge = maxAgeDirective ? maxAgeDirective[1] : void 0; 462 if (!cacheAge) { 463 return true; 464 } 465 try { 466 const maxAge = 1e3 * parseInt(cacheAge); 467 let ts; 468 try { 469 const metaTable = await this.metadata; 470 ts = (await metaTable.read(req.url)).ts; 471 } catch (e) { 472 const date = res.headers.get("Date"); 473 if (date === null) { 474 return true; 475 } 476 ts = Date.parse(date); 477 } 478 const age = this.adapter.time - ts; 479 return age < 0 || age > maxAge; 480 } catch (e) { 481 return true; 482 } 483 } else if (res.headers.has("Expires")) { 484 const expiresStr = res.headers.get("Expires"); 485 try { 486 return this.adapter.time > Date.parse(expiresStr); 487 } catch (e) { 488 return true; 489 } 490 } else { 491 return true; 492 } 493 } 494 /** 495 * Fetch the complete state of a cached resource, or return null if it's not found. 496 */ 497 async fetchFromCacheOnly(url) { 498 const cache = await this.cache; 499 const metaTable = await this.metadata; 500 const request = this.adapter.newRequest(url); 501 const response = await cache.match(request, this.config.cacheQueryOptions); 502 if (response === void 0) { 503 return null; 504 } 505 let metadata = void 0; 506 try { 507 metadata = await metaTable.read(request.url); 508 } catch (e) { 509 } 510 return { response, metadata }; 511 } 512 /** 513 * Lookup all resources currently stored in the cache which have no associated hash. 514 */ 515 async unhashedResources() { 516 const cache = await this.cache; 517 return (await cache.keys()).map((request) => this.adapter.normalizeUrl(request.url)).filter((url) => !this.hashes.has(url)); 518 } 519 /** 520 * Fetch the given resource from the network, and cache it if able. 521 */ 522 async fetchAndCacheOnce(req, used = true) { 523 if (this.inFlightRequests.has(req.url)) { 524 return this.inFlightRequests.get(req.url); 525 } 526 const fetchOp = this.fetchFromNetwork(req); 527 this.inFlightRequests.set(req.url, fetchOp); 528 try { 529 const res = await fetchOp; 530 if (!res.ok) { 531 throw new Error(`Response not Ok (fetchAndCacheOnce): request for ${req.url} returned response ${res.status} ${res.statusText}`); 532 } 533 try { 534 const cache = await this.cache; 535 await cache.put(req, res.clone()); 536 if (!this.hashes.has(this.adapter.normalizeUrl(req.url))) { 537 const meta = { ts: this.adapter.time, used }; 538 const metaTable = await this.metadata; 539 await metaTable.write(req.url, meta); 540 } 541 return res; 542 } catch (err) { 543 throw new SwCriticalError(`Failed to update the caches for request to '${req.url}' (fetchAndCacheOnce): ${errorToString(err)}`); 544 } 545 } finally { 546 this.inFlightRequests.delete(req.url); 547 } 548 } 549 async fetchFromNetwork(req, redirectLimit = 3) { 550 const res = await this.cacheBustedFetchFromNetwork(req); 551 if (res["redirected"] && !!res.url) { 552 if (redirectLimit === 0) { 553 throw new SwCriticalError(`Response hit redirect limit (fetchFromNetwork): request redirected too many times, next is ${res.url}`); 554 } 555 return this.fetchFromNetwork(this.newRequestWithMetadata(res.url, req), redirectLimit - 1); 556 } 557 return res; 558 } 559 /** 560 * Load a particular asset from the network, accounting for hash validation. 561 */ 562 async cacheBustedFetchFromNetwork(req) { 563 const url = this.adapter.normalizeUrl(req.url); 564 if (this.hashes.has(url)) { 565 const canonicalHash = this.hashes.get(url); 566 let response = await this.safeFetch(req); 567 let makeCacheBustedRequest = response.ok; 568 if (makeCacheBustedRequest) { 569 const fetchedHash = sha1Binary(await response.clone().arrayBuffer()); 570 makeCacheBustedRequest = fetchedHash !== canonicalHash; 571 } 572 if (makeCacheBustedRequest) { 573 const cacheBustReq = this.newRequestWithMetadata(this.cacheBust(req.url), req); 574 response = await this.safeFetch(cacheBustReq); 575 if (response.ok) { 576 const cacheBustedHash = sha1Binary(await response.clone().arrayBuffer()); 577 if (canonicalHash !== cacheBustedHash) { 578 throw new SwCriticalError(`Hash mismatch (cacheBustedFetchFromNetwork): ${req.url}: expected ${canonicalHash}, got ${cacheBustedHash} (after cache busting)`); 579 } 580 } 581 } 582 if (!response.ok && response.status === 404) { 583 throw new SwUnrecoverableStateError(`Failed to retrieve hashed resource from the server. (AssetGroup: ${this.config.name} | URL: ${url})`); 584 } 585 return response; 586 } else { 587 return this.safeFetch(req); 588 } 589 } 590 /** 591 * Possibly update a resource, if it's expired and needs to be updated. A no-op otherwise. 592 */ 593 async maybeUpdate(updateFrom, req, cache) { 594 const url = this.adapter.normalizeUrl(req.url); 595 if (this.hashes.has(url)) { 596 const hash = this.hashes.get(url);
597 const res = await updateFrom.lookupResourceWithHash(url, hash); 598 if (res !== null) { 599 await cache.put(req, res); 600 return true; 601 } 602 } 603 return false; 604 } 605 /** 606 * Create a new `Request` based on the specified URL and `RequestInit` options, preserving only 607 * metadata that are known to be safe. 608 * 609 * Currently, only headers are preserved. 610 * 611 * NOTE: 612 * Things like credential inclusion are intentionally omitted to avoid issues with opaque 613 * responses. 614 * 615 * TODO(gkalpak): 616 * Investigate preserving more metadata. See, also, discussion on preserving `mode`: 617 * https://github.com/angular/angular/issues/41931#issuecomment-1227601347 618 */ 619 newRequestWithMetadata(url, options) { 620 return this.adapter.newRequest(url, { headers: options.headers }); 621 } 622 /** 623 * Construct a cache-busting URL for a given URL. 624 */ 625 cacheBust(url) { 626 return url + (url.indexOf("?") === -1 ? "?" : "&") + "ngsw-cache-bust=" + Math.random(); 627 } 628 async safeFetch(req) { 629 try { 630 return await this.scope.fetch(req); 631 } catch (e) { 632 return this.adapter.newResponse("", { 633 status: 504, 634 statusText: "Gateway Timeout" 635 }); 636 } 637 } 638 }; 639 var PrefetchAssetGroup = class extends AssetGroup { 640 async initializeFully(updateFrom) { 641 const cache = await this.cache; 642 await this.urls.reduce(async (previous, url) => { 643 await previous; 644 const req = this.adapter.newRequest(url); 645 let alreadyCached = false; 646 try { 647 alreadyCached = await cache.match(req, this.config.cacheQueryOptions) !== void 0; 648 } catch (error) { 649 throw new SwCriticalError(`Cache is throwing while looking for a match in a PrefetchAssetGroup: ${error}`); 650 } 651 if (alreadyCached) { 652 return; 653 } 654 if (updateFrom !== void 0 && await this.maybeUpdate(updateFrom, req, cache)) { 655 return; 656 } 657 await this.fetchAndCacheOnce(req, false); 658 }, Promise.resolve()); 659 if (updateFrom !== void 0) { 660 const metaTable = await this.metadata; 661 await (await updateFrom.previouslyCachedResources()).filter((url) => this.urls.indexOf(url) !== -1 || this.patterns.some((pattern) => pattern.test(url))).reduce(async (previous, url) => { 662 await previous; 663 const req = this.adapter.newRequest(url); 664 const alreadyCached = await cache.match(req, this.config.cacheQueryOptions) !== void 0; 665 if (alreadyCached) { 666 return; 667 } 668 const res = await updateFrom.lookupResourceWithoutHash(url); 669 if (res === null || res.metadata === void 0) { 670 return; 671 } 672 await cache.put(req, res.response); 673 await metaTable.write(req.url, __spreadProps(__spreadValues({}, res.metadata), { used: false })); 674 }, Promise.resolve()); 675 } 676 } 677 }; 678 var LazyAssetGroup = class extends AssetGroup { 679 async initializeFully(updateFrom) { 680 if (updateFrom === void 0) { 681 return; 682 } 683 const cache = await this.cache; 684 await this.urls.reduce(async (previous, url) => { 685 await previous; 686 const req = this.adapter.newRequest(url); 687 let alreadyCached = false; 688 try { 689 alreadyCached = await cache.match(req, this.config.cacheQueryOptions) !== void 0; 690 } catch (error) { 691 throw new SwCriticalError(`Cache is throwing while looking for a match in a LazyAssetGroup: ${error}`); 692 } 693 if (alreadyCached) { 694 return; 695 } 696 const updated = await this.maybeUpdate(updateFrom, req, cache); 697 if (this.config.updateMode === "prefetch" && !updated) { 698 const cacheStatus = await updateFrom.recentCacheStatus(url); 699 if (cacheStatus !== UpdateCacheStatus.CACHED) { 700 return; 701 } 702 await this.fetchAndCacheOnce(req, false); 703 } 704 }, Promise.resolve()); 705 } 706 }; 707 708
708// packages/service-worker/worker/src/data.js 709 var LruList = class { 710 constructor(state) { 711 __publicField(this, "state"); 712 if (state === void 0) { 713 state = { 714 head: null, 715 tail: null, 716 map: {}, 717 count: 0 718 }; 719 } 720 this.state = state; 721 } 722 /** 723 * The current count of URLs in the list. 724 */ 725 get size() { 726 return this.state.count; 727 } 728 /** 729 * Remove the tail. 730 */ 731 pop() { 732 if (this.state.tail === null) { 733 return null; 734 } 735 const url = this.state.tail; 736 this.remove(url); 737 return url; 738 } 739 remove(url) { 740 const node = this.state.map[url]; 741 if (node === void 0) { 742 return false; 743 } 744 if (this.state.head === url) { 745 if (node.next === null) { 746 this.state.head = null; 747 this.state.tail = null; 748 this.state.map = {}; 749 this.state.count = 0; 750 return true; 751 } 752 const next = this.state.map[node.next]; 753 next.previous = null; 754 this.state.head = next.url; 755 node.next = null; 756 delete this.state.map[url]; 757 this.state.count--; 758 return true; 759 } 760 const previous = this.state.map[node.previous]; 761 previous.next = node.next; 762 if (node.next !== null) { 763 this.state.map[node.next].previous = node.previous; 764 } else { 765 this.state.tail = node.previous; 766 } 767 node.next = null; 768 node.previous = null; 769 delete this.state.map[url]; 770 this.state.count--; 771 return true; 772 } 773 accessed(url) { 774 if (this.state.head === url) { 775 return; 776 } 777 const node = this.state.map[url] || { url, next: null, previous: null }; 778 if (this.state.map[url] !== void 0) { 779 this.remove(url); 780 } 781 if (this.state.head !== null) { 782 this.state.map[this.state.head].previous = url; 783 } 784 node.next = this.state.head; 785 this.state.head = url; 786 if (this.state.tail === null) { 787 this.state.tail = url; 788 } 789 this.state.map[url] = node; 790 this.state.count++; 791 } 792 }; 793 var DataGroup = class { 794 constructor(scope2, adapter2, config, db, debugHandler, cacheNamePrefix) { 795 __publicField(this, "scope"); 796 __publicField(this, "adapter"); 797 __publicField(this, "config"); 798 __publicField(this, "db"); 799 __publicField(this, "debugHandler"); 800 /** 801 * Compiled regular expression set used to determine which resources fall under the purview 802 * of this group. 803 */ 804 __publicField(this, "patterns"); 805 /** 806 * The `Cache` instance in which resources belonging to this group are cached. 807 */ 808 __publicField(this, "cache"); 809 /** 810 * Tracks the LRU state of resources in this cache. 811 */ 812 __publicField(this, "_lru", null); 813 /** 814 * Database table used to store the state of the LRU cache. 815 */ 816 __publicField(this, "lruTable"); 817 /** 818 * Database table used to store metadata for resources in the cache. 819 */ 820 __publicField(this, "ageTable"); 821 this.scope = scope2; 822 this.adapter = adapter2; 823 this.config = config; 824 this.db = db; 825 this.debugHandler = debugHandler; 826 this.patterns = config.patterns.map((pattern) => new RegExp(pattern)); 827 this.cache = adapter2.caches.open(`${cacheNamePrefix}:${config.name}:cache`); 828 this.lruTable = this.db.open(`${cacheNamePrefix}:${config.name}:lru`, config.cacheQueryOptions); 829 this.ageTable = this.db.open(`${cacheNamePrefix}:${config.name}:age`, config.cacheQueryOptions); 830 } 831 /** 832 * Lazily initialize/load the LRU chain. 833 */ 834 async lru() { 835 if (this._lru === null) { 836 const table = await this.lruTable; 837 try { 838 this._lru = new LruList(await table.read("lru")); 839 } catch (e) { 840 this._lru = new LruList(); 841 } 842 } 843 return this._lru; 844 } 845 /** 846 * Sync the LRU chain to non-volatile storage. 847 */ 848 async syncLru() { 849 if (this._lru === null) { 850 return; 851 } 852 const table = await this.lruTable;
853 try { 854 return table.write("lru", this._lru.state); 855 } catch (err) { 856 this.debugHandler.log(err, `DataGroup(${this.config.name}@${this.config.version}).syncLru()`); 857 await this.detectStorageFull(); 858 } 859 } 860 /** 861 * Process a fetch event and return a `Response` if the resource is covered by this group, 862 * or `null` otherwise. 863 */ 864 async handleFetch(req, event) { 865 if (!this.patterns.some((pattern) => pattern.test(req.url))) { 866 return null; 867 } 868 const lru = await this.lru(); 869 switch (req.method) { 870 case "OPTIONS": 871 return null; 872 case "GET": 873 case "HEAD": 874 switch (this.config.strategy) { 875 case "freshness": 876 return this.handleFetchWithFreshness(req, event, lru); 877 case "performance": 878 return this.handleFetchWithPerformance(req, event, lru); 879 default: 880 throw new Error(`Unknown strategy: ${this.config.strategy}`); 881 } 882 default: 883 const wasCached = lru.remove(req.url); 884 if (wasCached) { 885 await this.clearCacheForUrl(req.url); 886 } 887 await this.syncLru(); 888 return this.safeFetch(req); 889 } 890 } 891 async handleFetchWithPerformance(req, event, lru) { 892 var _a; 893 const okToCacheOpaque = (_a = this.config.cacheOpaqueResponses) != null ? _a : false; 894 let res = null; 895 const fromCache = await this.loadFromCache(req, lru); 896 if (fromCache !== null) { 897 res = fromCache.res; 898 if (this.config.refreshAheadMs !== void 0 && fromCache.age >= this.config.refreshAheadMs) { 899 event.waitUntil(this.safeCacheResponse(req, this.safeFetch(req), lru, okToCacheOpaque)); 900 } 901 } 902 if (res !== null) { 903 return res; 904 } 905 const [timeoutFetch, networkFetch] = this.networkFetchWithTimeout(req); 906 res = await timeoutFetch; 907 if (res === void 0) { 908 res = this.adapter.newResponse(null, { status: 504, statusText: "Gateway Timeout" }); 909 event.waitUntil(this.safeCacheResponse(req, networkFetch, lru, okToCacheOpaque)); 910 } else { 911 await this.safeCacheResponse(req, res, lru, okToCacheOpaque); 912 } 913 return res; 914 } 915 async handleFetchWithFreshness(req, event, lru) { 916 var _a; 917 const okToCacheOpaque = (_a = this.config.cacheOpaqueResponses) != null ? _a : true; 918 const [timeoutFetch, networkFetch] = this.networkFetchWithTimeout(req); 919 let res; 920 try { 921 res = await timeoutFetch; 922 } catch (e) { 923 res = void 0; 924 } 925 if (res === void 0) { 926 event.waitUntil(this.safeCacheResponse(req, networkFetch, lru, okToCacheOpaque)); 927 const fromCache = await this.loadFromCache(req, lru); 928 res = fromCache !== null ? fromCache.res : null; 929 } else { 930 await this.safeCacheResponse(req, res, lru, okToCacheOpaque); 931 } 932 if (res !== null) { 933 return res; 934 } 935 return networkFetch; 936 } 937 networkFetchWithTimeout(req) { 938 if (this.config.timeoutMs !== void 0) { 939 const networkFetch = this.scope.fetch(req); 940 const safeNetworkFetch = (async () => { 941 try { 942 return await networkFetch; 943 } catch (e) { 944 return this.adapter.newResponse(null, { 945 status: 504, 946 statusText: "Gateway Timeout" 947 }); 948 } 949 })(); 950 const networkFetchUndefinedError = (async () => { 951 try { 952 return await networkFetch; 953 } catch (e) { 954 return void 0; 955 } 956 })(); 957 const timeout = this.adapter.timeout(this.config.timeoutMs); 958 return [Promise.race([networkFetchUndefinedError, timeout]), safeNetworkFetch]; 959 } else { 960 const networkFetch = this.safeFetch(req); 961 return [networkFetch, networkFetch]; 962 } 963 } 964 async safeCacheResponse(req, resOrPromise, lru, okToCacheOpaque) { 965 try { 966 const res = await resOrPromise; 967 try { 968 await this.cacheResponse(req, res, lru, okToCacheOpaque); 969 } catch (err) { 970 this.debugHandler.log(err, `DataGroup(${this.config.name}@${this.config.version}).safeCacheResponse(${req.url}, status: ${res.status})`); 971 await this.detectStorageFull(); 972 } 973 } catch (e) { 974 } 975 } 976 async loadFromCache(req, lru) { 977 const cache = await this.cache; 978 let res = await cache.match(req, this.config.cacheQueryOptions); 979 if (res !== void 0) { 980 try { 981 const ageTable = await this.ageTable; 982 const age = this.adapter.time - (await ageTable.read(req.url)).age; 983 if (age <= this.config.maxAge) { 984 lru.accessed(req.url); 985 return { res, age }; 986 } 987 } catch (e) { 988 } 989 lru.remove(req.url); 990 await this.clearCacheForUrl(req.url); 991 await this.syncLru(); 992 } 993 return null; 994 } 995 /** 996 * Operation for caching the response from the server. This has to happen all 997 * at once, so that the cache and LRU tracking remain in sync. If the network request 998 * completes before the timeout, this logic will be run inline with the response flow. 999 * If the request times out on the server, an error will be returned but the real network 1000 * request will still be running in the background, to be cached when it completes. 1001 */ 1002 async cacheResponse(req, res, lru, okToCacheOpaque = false) { 1003 if (!(res.ok || okToCacheOpaque && res.type === "opaque")) { 1004 return; 1005 } 1006 if (lru.size >= this.config.maxSize) { 1007 const evictedUrl = lru.pop(); 1008 if (evictedUrl !== null) { 1009 await this.clearCacheForUrl(evictedUrl); 1010 } 1011 } 1012 lru.accessed(req.url); 1013 await (await this.cache).put(req, res.clone()); 1014 const ageTable = await this.ageTable; 1015 await ageTable.write(req.url, { age: this.adapter.time }); 1016 await this.syncLru(); 1017 } 1018 /** 1019 * Delete all of the saved state which this group uses to track resources. 1020 */ 1021 async cleanup() { 1022 await Promise.all([ 1023 this.cache.then((cache) => this.adapter.caches.delete(cache.name)), 1024 this.ageTable.then((table) => this.db.delete(table.name)), 1025 this.lruTable.then((table) => this.db.delete(table.name)) 1026 ]); 1027 } 1028 /** 1029 * Return a list of the names of all caches used by this group. 1030 */ 1031 async getCacheNames() { 1032 const [cache, ageTable, lruTable] = await Promise.all([ 1033 this.cache, 1034 this.ageTable, 1035 this.lruTable 1036 ]); 1037 return [cache.name, ageTable.cacheName, lruTable.cacheName]; 1038 } 1039 /** 1040 * Clear the state of the cache for a particular resource. 1041 * 1042 * This doesn't remove the resource from the LRU table, that is assumed to have 1043 * been done already. This clears the GET and HEAD versions of the request from 1044 * the cache itself, as well as the metadata stored in the age table. 1045 */ 1046 async clearCacheForUrl(url) { 1047 const [cache, ageTable] = await Promise.all([this.cache, this.ageTable]); 1048 await Promise.all([ 1049 cache.delete(this.adapter.newRequest(url, { method: "GET" }), this.config.cacheQueryOptions), 1050 cache.delete(this.adapter.newRequest(url, { method: "HEAD" }), this.config.cacheQueryOptions), 1051 ageTable.delete(url) 1052 ]); 1053 } 1054 async safeFetch(req) {
1055 try { 1056 return this.scope.fetch(req); 1057 } catch (e) { 1058 return this.adapter.newResponse(null, { 1059 status: 504, 1060 statusText: "Gateway Timeout" 1061 }); 1062 } 1063 } 1064 /** 1065 * Detect if storage is full or approaching capacity. 1066 * Returns true if storage is at or near capacity. 1067 */ 1068 async detectStorageFull() { 1069 try { 1070 const estimate = await navigator.storage.estimate(); 1071 const { quota, usage } = estimate; 1072 if (typeof quota !== "number" || typeof usage !== "number") { 1073 return; 1074 } 1075 const usagePercentage = usage / quota * 100; 1076 const isStorageFull = usagePercentage >= 95; 1077 if (isStorageFull) { 1078 this.debugHandler.log("Storage is full or nearly full", `DataGroup(${this.config.name}@${this.config.version}).detectStorageFull()`); 1079 } 1080 } catch (e) { 1081 } 1082 } 1083 }; 1084 1085
1085// packages/service-worker/worker/src/app-version.js 1086 var AppVersion = class { 1087 constructor(scope2, adapter2, database, idle, debugHandler, manifest, manifestHash) { 1088 __publicField(this, "scope"); 1089 __publicField(this, "adapter"); 1090 __publicField(this, "database"); 1091 __publicField(this, "debugHandler"); 1092 __publicField(this, "manifest"); 1093 __publicField(this, "manifestHash"); 1094 /** 1095 * A Map of absolute URL paths (`/foo.txt`) to the known hash of their contents (if available). 1096 */ 1097 __publicField(this, "hashTable", /* @__PURE__ */ new Map()); 1098 /** 1099 * All of the asset groups active in this version of the app. 1100 */ 1101 __publicField(this, "assetGroups"); 1102 /** 1103 * All of the data groups active in this version of the app. 1104 */ 1105 __publicField(this, "dataGroups"); 1106 /** 1107 * Requests to URLs that match any of the `include` RegExps and none of the `exclude` RegExps 1108 * are considered navigation requests and handled accordingly. 1109 */ 1110 __publicField(this, "navigationUrls"); 1111 /** 1112 * The normalized URL to the file that serves as the index page to satisfy navigation requests. 1113 * Usually this is `/index.html`. 1114 */ 1115 __publicField(this, "indexUrl"); 1116 /** 1117 * Tracks whether the manifest has encountered any inconsistencies. 1118 */ 1119 __publicField(this, "_okay", true); 1120 this.scope = scope2; 1121 this.adapter = adapter2; 1122 this.database = database; 1123 this.debugHandler = debugHandler; 1124 this.manifest = manifest; 1125 this.manifestHash = manifestHash; 1126 this.indexUrl = this.adapter.normalizeUrl(this.manifest.index); 1127 Object.keys(manifest.hashTable).forEach((url) => { 1128 this.hashTable.set(adapter2.normalizeUrl(url), manifest.hashTable[url]); 1129 }); 1130 const assetCacheNamePrefix = `${manifestHash}:assets`; 1131 this.assetGroups = (manifest.assetGroups || []).map((config) => { 1132 switch (config.installMode) { 1133 case "prefetch": 1134 return new PrefetchAssetGroup(scope2, adapter2, idle, config, this.hashTable, database, assetCacheNamePrefix); 1135 case "lazy": 1136 return new LazyAssetGroup(scope2, adapter2, idle, config, this.hashTable, database, assetCacheNamePrefix); 1137 } 1138 }); 1139 this.dataGroups = (manifest.dataGroups || []).map((config) => new DataGroup(scope2, adapter2, config, database, debugHandler, `${config.version}:data`)); 1140 const includeUrls = manifest.navigationUrls.filter((spec) => spec.positive); 1141 const excludeUrls = manifest.navigationUrls.filter((spec) => !spec.positive); 1142 this.navigationUrls = { 1143 include: includeUrls.map((spec) => new RegExp(spec.regex)), 1144 exclude: excludeUrls.map((spec) => new RegExp(spec.regex)) 1145 }; 1146 } 1147 get okay() { 1148 return this._okay; 1149 } 1150 /** 1151 * Fully initialize this version of the application. If this Promise resolves successfully, all 1152 * required 1153 * data has been safely downloaded. 1154 */ 1155 async initializeFully(updateFrom) { 1156 try { 1157 await this.assetGroups.reduce(async (previous, group) => { 1158 await previous; 1159 return group.initializeFully(updateFrom); 1160 }, Promise.resolve()); 1161 } catch (err) { 1162 this._okay = false; 1163 throw err; 1164 } 1165 } 1166 async handleFetch(req, event) { 1167 const asset = await this.assetGroups.reduce(async (potentialResponse, group) => { 1168 const resp = await potentialResponse; 1169 if (resp !== null) { 1170 return resp; 1171 } 1172 return group.handleFetch(req, event); 1173 }, Promise.resolve(null)); 1174 if (asset !== null) { 1175 return asset; 1176 } 1177 const data = await this.dataGroups.reduce(async (potentialResponse, group) => { 1178 const resp = await potentialResponse; 1179 if (resp !== null) { 1180 return resp; 1181 } 1182 return group.handleFetch(req, event); 1183 }, Promise.resolve(null)); 1184 if (data !== null) { 1185 return data; 1186 } 1187 if (this.adapter.normalizeUrl(req.url) !== this.indexUrl && this.isNavigationRequest(req)) { 1188 if (this.manifest.navigationRequestStrategy === "freshness") { 1189 try { 1190 return await this.scope.fetch(req); 1191 } catch (e) { 1192 } 1193 } 1194 return this.handleFetch(this.adapter.newRequest(this.indexUrl), event); 1195 } 1196 return null; 1197 } 1198 /** 1199 * Determine whether the request is a navigation request. 1200 * Takes into account: Request method and mode, `Accept` header, `navigationUrls` patterns. 1201 */ 1202 isNavigationRequest(req) { 1203 if (req.method !== "GET" || req.mode !== "navigate") { 1204 return false;
1205 } 1206 if (!this.acceptsTextHtml(req)) { 1207 return false; 1208 } 1209 const urlPrefix = this.scope.registration.scope.replace(/\/$/, ""); 1210 const url = req.url.startsWith(urlPrefix) ? req.url.slice(urlPrefix.length) : req.url; 1211 const urlWithoutQueryOrHash = url.replace(/[?#].*$/, ""); 1212 return this.navigationUrls.include.some((regex) => regex.test(urlWithoutQueryOrHash)) && !this.navigationUrls.exclude.some((regex) => regex.test(urlWithoutQueryOrHash)); 1213 } 1214 /** 1215 * Check this version for a given resource with a particular hash. 1216 */ 1217 async lookupResourceWithHash(url, hash) { 1218 if (!this.hashTable.has(url)) { 1219 return null; 1220 } 1221 if (this.hashTable.get(url) !== hash) { 1222 return null; 1223 } 1224 const cacheState = await this.lookupResourceWithoutHash(url); 1225 return cacheState && cacheState.response; 1226 } 1227 /** 1228 * Check this version for a given resource regardless of its hash. 1229 */ 1230 lookupResourceWithoutHash(url) { 1231 return this.assetGroups.reduce(async (potentialResponse, group) => { 1232 const resp = await potentialResponse; 1233 if (resp !== null) { 1234 return resp; 1235 } 1236 return group.fetchFromCacheOnly(url); 1237 }, Promise.resolve(null)); 1238 } 1239 /** 1240 * List all unhashed resources from all asset groups. 1241 */ 1242 previouslyCachedResources() { 1243 return this.assetGroups.reduce(async (resources, group) => (await resources).concat(await group.unhashedResources()), Promise.resolve([])); 1244 } 1245 async recentCacheStatus(url) { 1246 return this.assetGroups.reduce(async (current, group) => { 1247 const status = await current; 1248 if (status === UpdateCacheStatus.CACHED) { 1249 return status; 1250 } 1251 const groupStatus = await group.cacheStatus(url); 1252 if (groupStatus === UpdateCacheStatus.NOT_CACHED) { 1253 return status; 1254 } 1255 return groupStatus; 1256 }, Promise.resolve(UpdateCacheStatus.NOT_CACHED)); 1257 } 1258 /** 1259 * Return a list of the names of all caches used by this version. 1260 */ 1261 async getCacheNames() { 1262 const allGroupCacheNames = await Promise.all([ 1263 ...this.assetGroups.map((group) => group.getCacheNames()), 1264 ...this.dataGroups.map((group) => group.getCacheNames()) 1265 ]); 1266 return [].concat(...allGroupCacheNames); 1267 } 1268 /** 1269 * Get the opaque application data which was provided with the manifest. 1270 */ 1271 get appData() { 1272 return this.manifest.appData || null; 1273 } 1274 /** 1275 * Check whether a request accepts `text/html` (based on the `Accept` header). 1276 */ 1277 acceptsTextHtml(req) { 1278 const accept = req.headers.get("Accept"); 1279 if (accept === null) { 1280 return false; 1281 } 1282 const values = accept.split(","); 1283 return values.some((value) => value.trim().toLowerCase() === "text/html"); 1284 } 1285 }; 1286 1287
1287// packages/service-worker/worker/src/debug.js 1288 var SW_VERSION = "20.3.16"; 1289 var DEBUG_LOG_BUFFER_SIZE = 100; 1290 var DebugHandler = class { 1291 constructor(driver, adapter2) { 1292 __publicField(this, "driver"); 1293 __publicField(this, "adapter"); 1294 // There are two debug log message arrays. debugLogA records new debugging messages. 1295 // Once it reaches DEBUG_LOG_BUFFER_SIZE, the array is moved to debugLogB and a new 1296 // array is assigned to debugLogA. This ensures that insertion to the debug log is 1297 // always O(1) no matter the number of logged messages, and that the total number 1298 // of messages in the log never exceeds 2 * DEBUG_LOG_BUFFER_SIZE. 1299 __publicField(this, "debugLogA", []); 1300 __publicField(this, "debugLogB", []); 1301 this.driver = driver; 1302 this.adapter = adapter2; 1303 } 1304 async handleFetch(req) { 1305 const [state, versions, idle] = await Promise.all([ 1306 this.driver.debugState(), 1307 this.driver.debugVersions(), 1308 this.driver.debugIdleState() 1309 ]); 1310 const msgState = `NGSW Debug Info: 1311 1312Driver version: ${SW_VERSION} 1313Driver state: ${state.state} (${state.why}) 1314Latest manifest hash: ${state.latestHash || "none"} 1315Last update check: ${this.since(state.lastUpdateCheck)}`; 1316 const msgVersions = versions.map((version) => `=== Version ${version.hash} === 1317 1318Clients: ${version.clients.join(", ")}`).join("\n\n"); 1319 const msgIdle = `=== Idle Task Queue === 1320Last update tick: ${this.since(idle.lastTrigger)} 1321Last update run: ${this.since(idle.lastRun)} 1322Task queue: 1323${idle.queue.map((v) => " * " + v).join("\n")} 1324 1325Debug log: 1326${this.formatDebugLog(this.debugLogB)} 1327${this.formatDebugLog(this.debugLogA)} 1328`; 1329 return this.adapter.newResponse(`${msgState} 1330 1331${msgVersions} 1332 1333${msgIdle}`, { headers: this.adapter.newHeaders({ "Content-Type": "text/plain" }) }); 1334 } 1335 since(time) { 1336 if (time === null) { 1337 return "never"; 1338 } 1339 let age = this.adapter.time - time; 1340 const days = Math.floor(age / 864e5); 1341 age = age % 864e5; 1342 const hours = Math.floor(age / 36e5); 1343 age = age % 36e5; 1344 const minutes = Math.floor(age / 6e4); 1345 age = age % 6e4; 1346 const seconds = Math.floor(age / 1e3); 1347 const millis = age % 1e3; 1348 return (days > 0 ? `${days}d` : "") + (hours > 0 ? `${hours}h` : "") + (minutes > 0 ? `${minutes}m` : "") + (seconds > 0 ? `${seconds}s` : "") + (millis > 0 ? `${millis}u` : ""); 1349 } 1350 log(value, context = "") { 1351 if (this.debugLogA.length === DEBUG_LOG_BUFFER_SIZE) { 1352 this.debugLogB = this.debugLogA; 1353 this.debugLogA = []; 1354 } 1355 if (typeof value !== "string") { 1356 value = this.errorToString(value); 1357 } 1358 this.debugLogA.push({ value, time: this.adapter.time, context }); 1359 } 1360 errorToString(err) { 1361 return `${err.name}(${err.message}, ${err.stack})`; 1362 } 1363 formatDebugLog(log) { 1364 return log.map((entry) => `[${this.since(entry.time)}] ${entry.value} ${entry.context}`).join("\n"); 1365 } 1366 }; 1367 1368
1368// packages/service-worker/worker/src/idle.js 1369 var IdleScheduler = class { 1370 constructor(adapter2, delay, maxDelay, debug) { 1371 __publicField(this, "adapter"); 1372 __publicField(this, "delay"); 1373 __publicField(this, "maxDelay"); 1374 __publicField(this, "debug"); 1375 __publicField(this, "queue", []); 1376 __publicField(this, "scheduled", null); 1377 __publicField(this, "empty", Promise.resolve()); 1378 __publicField(this, "emptyResolve", null); 1379 __publicField(this, "lastTrigger", null); 1380 __publicField(this, "lastRun", null); 1381 __publicField(this, "oldestScheduledAt", null); 1382 this.adapter = adapter2; 1383 this.delay = delay; 1384 this.maxDelay = maxDelay; 1385 this.debug = debug; 1386 } 1387 async trigger() { 1388 var _a; 1389 this.lastTrigger = this.adapter.time; 1390 if (this.queue.length === 0) { 1391 return; 1392 } 1393 if (this.scheduled !== null) { 1394 this.scheduled.cancel = true; 1395 } 1396 const scheduled = { 1397 cancel: false 1398 }; 1399 this.scheduled = scheduled; 1400 const now = this.adapter.time; 1401 const maxDelay = Math.max(0, ((_a = this.oldestScheduledAt) != null ? _a : now) + this.maxDelay - now); 1402 const delay = Math.min(maxDelay, this.delay); 1403 await this.adapter.timeout(delay); 1404 if (scheduled.cancel) { 1405 return; 1406 } 1407 this.scheduled = null; 1408 await this.execute(); 1409 } 1410 async execute() { 1411 this.lastRun = this.adapter.time; 1412 while (this.queue.length > 0) { 1413 const queue = this.queue; 1414 this.queue = []; 1415 await queue.reduce(async (previous, task) => { 1416 await previous; 1417 try { 1418 await task.run(); 1419 } catch (err) { 1420 this.debug.log(err, `while running idle task ${task.desc}`); 1421 } 1422 }, Promise.resolve()); 1423 } 1424 if (this.emptyResolve !== null) { 1425 this.emptyResolve(); 1426 this.emptyResolve = null; 1427 } 1428 this.empty = Promise.resolve(); 1429 this.oldestScheduledAt = null; 1430 } 1431 schedule(desc, run) { 1432 this.queue.push({ desc, run }); 1433 if (this.emptyResolve === null) { 1434 this.empty = new Promise((resolve) => { 1435 this.emptyResolve = resolve; 1436 }); 1437 } 1438 if (this.oldestScheduledAt === null) { 1439 this.oldestScheduledAt = this.adapter.time; 1440 } 1441 } 1442 get size() { 1443 return this.queue.length; 1444 } 1445 get taskDescriptions() { 1446 return this.queue.map((task) => task.desc); 1447 } 1448 }; 1449 1450
1450// packages/service-worker/worker/src/manifest.js 1451 function hashManifest(manifest) { 1452 return sha1(JSON.stringify(manifest)); 1453 } 1454 1455
1455// packages/service-worker/worker/src/msg.js 1456 function isMsgCheckForUpdates(msg) { 1457 return msg.action === "CHECK_FOR_UPDATES"; 1458 } 1459 function isMsgActivateUpdate(msg) { 1460 return msg.action === "ACTIVATE_UPDATE"; 1461 } 1462 1463
1463// packages/service-worker/worker/src/driver.js 1464 var IDLE_DELAY = 5e3; 1465 var MAX_IDLE_DELAY = 3e4; 1466 var SUPPORTED_CONFIG_VERSION = 1; 1467 var NOTIFICATION_OPTION_NAMES = [ 1468 "actions", 1469 "badge", 1470 "body", 1471 "data", 1472 "dir", 1473 "icon", 1474 "image", 1475 "lang", 1476 "renotify", 1477 "requireInteraction", 1478 "silent", 1479 "tag", 1480 "timestamp", 1481 "title", 1482 "vibrate" 1483 ]; 1484 function isLocalhost(scope2) { 1485 return /(?:^https?:\/\/)?(?:(?:^|[^\w.])localhost|\[::1\]|127(?:\.\d{1,3}){3})(?::\d+)?(?:\/.*)?$/.test(scope2); 1486 } 1487 var DriverReadyState; 1488 (function(DriverReadyState2) { 1489 DriverReadyState2[DriverReadyState2["NORMAL"] = 0] = "NORMAL"; 1490 DriverReadyState2[DriverReadyState2["EXISTING_CLIENTS_ONLY"] = 1] = "EXISTING_CLIENTS_ONLY"; 1491 DriverReadyState2[DriverReadyState2["SAFE_MODE"] = 2] = "SAFE_MODE"; 1492 })(DriverReadyState || (DriverReadyState = {})); 1493 var Driver = class { 1494 constructor(scope2, adapter2, db) { 1495 __publicField(this, "scope"); 1496 __publicField(this, "adapter"); 1497 __publicField(this, "db"); 1498 /** 1499 * Tracks the current readiness condition under which the SW is operating. This controls 1500 * whether the SW attempts to respond to some or all requests. 1501 */ 1502 __publicField(this, "state", DriverReadyState.NORMAL); 1503 __publicField(this, "stateMessage", "(nominal)"); 1504 /** 1505 * Tracks whether the SW is in an initialized state or not. Before initialization, 1506 * it's not legal to respond to requests. 1507 */ 1508 __publicField(this, "initialized", null); 1509 /** 1510 * Maps client IDs to the manifest hash of the application version being used to serve 1511 * them. If a client ID is not present here, it has not yet been assigned a version. 1512 * 1513 * If a ManifestHash appears here, it is also present in the `versions` map below. 1514 */ 1515 __publicField(this, "clientVersionMap", /* @__PURE__ */ new Map()); 1516 /** 1517 * Maps manifest hashes to instances of `AppVersion` for those manifests. 1518 */ 1519 __publicField(this, "versions", /* @__PURE__ */ new Map()); 1520 /** 1521 * The latest version fetched from the server. 1522 * 1523 * Valid after initialization has completed. 1524 */ 1525 __publicField(this, "latestHash", null); 1526 __publicField(this, "lastUpdateCheck", null); 1527 /** 1528 * Whether there is a check for updates currently scheduled due to navigation. 1529 */ 1530 __publicField(this, "scheduledNavUpdateCheck", false); 1531 /** 1532 * Keep track of whether we have logged an invalid `only-if-cached` request. 1533 * (See `.onFetch()` for details.) 1534 */ 1535 __publicField(this, "loggedInvalidOnlyIfCachedRequest", false); 1536 __publicField(this, "ngswStatePath"); 1537 /** 1538 * A scheduler which manages a queue of tasks that need to be executed when the SW is 1539 * not doing any other work (not processing any other requests). 1540 */ 1541 __publicField(this, "idle"); 1542 __publicField(this, "debugger"); 1543 // A promise resolving to the control DB table. 1544 __publicField(this, "controlTable"); 1545 this.scope = scope2; 1546 this.adapter = adapter2; 1547 this.db = db; 1548 this.controlTable = this.db.open("control"); 1549 this.ngswStatePath = this.adapter.parseUrl("ngsw/state", this.scope.registration.scope).path; 1550 this.scope.addEventListener("install", (event) => { 1551 event.waitUntil(this.scope.skipWaiting()); 1552 }); 1553 this.scope.addEventListener("activate", (event) => { 1554 event.waitUntil((async () => { 1555 await this.scope.clients.claim(); 1556 })()); 1557 if (this.scope.registration.active !== null) { 1558 this.scope.registration.active.postMessage({ action: "INITIALIZE" }); 1559 } 1560 }); 1561 this.scope.addEventListener("fetch", (event) => this.onFetch(event)); 1562 this.scope.addEventListener("message", (event) => this.onMessage(event)); 1563 this.scope.addEventListener("push", (event) => this.onPush(event)); 1564 this.scope.addEventListener("notificationclick", (event) => this.onClick(event)); 1565 this.scope.addEventListener("notificationclose", (event) => this.onClose(event)); 1566 this.scope.addEventListener("pushsubscriptionchange", (event) => ( 1567 // This is a bug in TypeScript, where they removed `PushSub
1567scriptionChangeEvent` 1568 // based on the incorrect assumption that browsers don't support it. 1569 this.onPushSubscriptionChange(event) 1570 )); 1571 this.scope.addEventListener("messageerror", (event) => this.onMessageError(event)); 1572 this.scope.addEventListener("unhandledrejection", (event) => this.onUnhandledRejection(event)); 1573 this.debugger = new DebugHandler(this, this.adapter); 1574 this.idle = new IdleScheduler(this.adapter, IDLE_DELAY, MAX_IDLE_DELAY, this.debugger); 1575 } 1576 /** 1577 * The handler for fetch events. 1578 * 1579 * This is the transition point between the synchronous event handler and the 1580 * asynchronous execution that eventually resolves for respondWith() and waitUntil(). 1581 */ 1582 onFetch(event) { 1583 const req = event.request; 1584 const scopeUrl = this.scope.registration.scope; 1585 const requestUrlObj = this.adapter.parseUrl(req.url, scopeUrl); 1586 if (req.headers.has("ngsw-bypass") || /[?&]ngsw-bypass(?:[=&]|$)/i.test(requestUrlObj.search)) { 1587 return; 1588 } 1589 if (requestUrlObj.path === this.ngswStatePath) { 1590 event.respondWith(this.debugger.handleFetch(req)); 1591 return; 1592 } 1593 if (this.state === DriverReadyState.SAFE_MODE) { 1594 event.waitUntil(this.idle.trigger()); 1595 return; 1596 } 1597 if (requestUrlObj.origin.startsWith("http:") && scopeUrl.startsWith("https:")) { 1598 this.debugger.log(`Ignoring passive mixed content request: Driver.fetch(${req.url})`); 1599 return; 1600 } 1601 if (req.cache === "only-if-cached" && req.mode !== "same-origin") { 1602 if (!this.loggedInvalidOnlyIfCachedRequest) { 1603 this.loggedInvalidOnlyIfCachedRequest = true; 1604 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})`); 1605 } 1606 return; 1607 } 1608 event.respondWith(this.handleFetch(event)); 1609 } 1610 /** 1611 * The handler for message events. 1612 */ 1613 onMessage(event) { 1614 if (this.state === DriverReadyState.SAFE_MODE) { 1615 return; 1616 } 1617 const data = event.data; 1618 if (!data || !data.action) { 1619 return; 1620 } 1621 event.waitUntil((async () => { 1622 if (data.action === "INITIALIZE") { 1623 return this.ensureInitialized(event); 1624 } 1625 if (!this.adapter.isClient(event.source)) { 1626 return; 1627 } 1628 await this.ensureInitialized(event); 1629 await this.handleMessage(data, event.source); 1630 })()); 1631 } 1632 onPush(msg) { 1633 if (!msg.data) { 1634 return; 1635 } 1636 msg.waitUntil(this.handlePush(msg.data.json())); 1637 } 1638 onClick(event) { 1639 event.waitUntil(this.handleClick(event.notification, event.action)); 1640 } 1641 onClose(event) { 1642 event.waitUntil(this.handleClose(event.notification, event.action)); 1643 } 1644 onPushSubscriptionChange(event) { 1645 event.waitUntil(this.handlePushSubscriptionChange(event)); 1646 } 1647 onMessageError(event) { 1648 this.debugger.log(`Message error occurred - data could not be deserialized`, `Driver.onMessageError(origin: ${event.origin})`); 1649 } 1650 onUnhandledRejection(event) { 1651 this.debugger.log(`Unhandled promise rejection occurred`, `Driver.onUnhandledRejection(reason: ${event.reason})`); 1652 } 1653 async ensureInitialized(event) { 1654 if (this.initialized !== null) { 1655 return this.initialized; 1656 } 1657 try { 1658 this.initialized = this.initialize(); 1659 await this.initialized; 1660 } catch (error) { 1661 this.state = DriverReadyState.SAFE_MODE; 1662 this.stateMessage = `Initialization failed due to error: ${errorToString(error)}`; 1663 throw error; 1664 } finally { 1665 event.waitUntil(this.idle.trigger()); 1666 } 1667 } 1668 async handleMessage(msg, from) { 1669 if (isMsgCheckForUpdates(msg)) { 1670 const action = this.checkForUpdate(); 1671 await this.completeOperation(from, action, msg.nonce); 1672 } else if (isMsgActivateUpdate(msg)) { 1673 const action = this.updateClient(from); 1674 await this.completeOperation(from, action, msg.nonce); 1675 } 1676 } 1677 async handlePush(data) { 1678 await this.broadcast({ 1679 type: "PUSH", 1680 data 1681 }); 1682 if (!data.notification || !data.notification.title) { 1683 return; 1684 } 1685 const desc = data.notification; 1686 let options = {}; 1687 NOTIFICATION_OPTION_NAMES.filter((name) =>
1687 desc.hasOwnProperty(name)).forEach((name) => options[name] = desc[name]); 1688 await this.scope.registration.showNotification(desc["title"], options); 1689 } 1690 async handleClick(notification, action) { 1691 var _a, _b, _c; 1692 notification.close(); 1693 const options = {}; 1694 NOTIFICATION_OPTION_NAMES.filter((name) => name in notification).forEach((name) => options[name] = notification[name]); 1695 const notificationAction = action === "" || action === void 0 ? "default" : action; 1696 const onActionClick = (_b = (_a = notification == null ? void 0 : notification.data) == null ? void 0 : _a.onActionClick) == null ? void 0 : _b[notificationAction]; 1697 const urlToOpen = new URL((_c = onActionClick == null ? void 0 : onActionClick.url) != null ? _c : "", this.scope.registration.scope).href; 1698 switch (onActionClick == null ? void 0 : onActionClick.operation) { 1699 case "openWindow": 1700 await this.scope.clients.openWindow(urlToOpen); 1701 break; 1702 case "focusLastFocusedOrOpen": { 1703 let matchingClient = await this.getLastFocusedMatchingClient(this.scope); 1704 if (matchingClient) { 1705 await (matchingClient == null ? void 0 : matchingClient.focus()); 1706 } else { 1707 await this.scope.clients.openWindow(urlToOpen); 1708 } 1709 break; 1710 } 1711 case "navigateLastFocusedOrOpen": { 1712 let matchingClient = await this.getLastFocusedMatchingClient(this.scope); 1713 if (matchingClient) { 1714 matchingClient = await matchingClient.navigate(urlToOpen); 1715 await (matchingClient == null ? void 0 : matchingClient.focus()); 1716 } else { 1717 await this.scope.clients.openWindow(urlToOpen); 1718 } 1719 break; 1720 } 1721 case "sendRequest": { 1722 await this.scope.fetch(urlToOpen); 1723 break; 1724 } 1725 default: 1726 break; 1727 } 1728 await this.broadcast({ 1729 type: "NOTIFICATION_CLICK", 1730 data: { action, notification: options } 1731 }); 1732 } 1733 /** 1734 * Handles the closing of a notification by extracting its options and 1735 * broadcasting a `NOTIFICATION_CLOSE` message. 1736 * 1737 * This is typically called when a notification is dismissed by the user 1738 * or closed programmatically, and it relays that information to clients 1739 * listening for service worker events. 1740 * 1741 * @param notification - The original `Notification` object that was closed. 1742 * @param action - The action string associated with the close event, if any (usually an empty string). 1743 */ 1744 async handleClose(notification, action) { 1745 const options = {}; 1746 NOTIFICATION_OPTION_NAMES.filter((name) => name in notification).forEach((name) => options[name] = notification[name]); 1747 await this.broadcast({ 1748 type: "NOTIFICATION_CLOSE", 1749 data: { action, notification: options } 1750 }); 1751 } 1752 /** 1753 * Handles changes to the push subscription by capturing the old and new 1754 * subscription details and broadcasting a `PUSH_SUBSCRIPTION_CHANGE` message. 1755 * 1756 * This method is triggered when the browser invalidates an existing push 1757 * subscription and creates a new one, which can happen without user interaction. 1758 * It ensures that clients listening for service worker events are informed 1759 * of the subscription update. 1760 * 1761 * @param event - The `PushSubscriptionChangeEvent` containing the old and new subscriptions. 1762 */ 1763 async handlePushSubscriptionChange(event) { 1764 const { oldSubscription, newSubscription } = event; 1765 await this.broadcast({ 1766 type: "PUSH_SUBSCRIPTION_CHANGE", 1767 data: { oldSubscription, newSubscription } 1768 }); 1769 } 1770 async getLastFocusedMatchingClient(scope2) { 1771 const windowClients = await scope2.clients.matchAll({ type: "window" }); 1772 return windowClients[0]; 1773 } 1774 async completeOperation(client, promise, nonce) { 1775 const response = { type: "OPERATION_COMPLETED", nonce }; 1776 try { 1777 client.postMessage(__spreadProps(__spreadValues({}, response), { 1778 result: await promise 1779 })); 1780 } catch (e) { 1781 client.postMessage(__spreadProps(__spreadValues({}, response), { 1782 error: e.toString() 1783 })); 1784 } 1785 } 1786 async updateClient(client) { 1787 const existing = this.clientVersionMap.get(client.id); 1788 if (existing === this.latestHash) { 1789 return false;
1790 } 1791 let previous = void 0; 1792 if (existing !== void 0) { 1793 const existingVersion = this.versions.get(existing); 1794 previous = this.mergeHashWithAppData(existingVersion.manifest, existing); 1795 } 1796 this.clientVersionMap.set(client.id, this.latestHash); 1797 await this.sync(); 1798 const current = this.versions.get(this.latestHash); 1799 return true; 1800 } 1801 async handleFetch(event) { 1802 try { 1803 await this.ensureInitialized(event); 1804 } catch (e) { 1805 return this.safeFetch(event.request); 1806 } 1807 if (event.request.mode === "navigate" && !this.scheduledNavUpdateCheck) { 1808 this.scheduledNavUpdateCheck = true; 1809 this.idle.schedule("check-updates-on-navigation", async () => { 1810 this.scheduledNavUpdateCheck = false; 1811 await this.checkForUpdate(); 1812 }); 1813 } 1814 const appVersion = await this.assignVersion(event); 1815 const isVersionWithinMaxAge = (appVersion == null ? void 0 : appVersion.manifest.applicationMaxAge) === void 0 || this.adapter.time - appVersion.manifest.timestamp < appVersion.manifest.applicationMaxAge; 1816 let res = null; 1817 try { 1818 if (appVersion !== null && isVersionWithinMaxAge) { 1819 try { 1820 res = await appVersion.handleFetch(event.request, event); 1821 } catch (err) { 1822 if (err.isUnrecoverableState) { 1823 await this.notifyClientsAboutUnrecoverableState(appVersion, err.message); 1824 } 1825 if (err.isCritical) { 1826 this.debugger.log(err, `Driver.handleFetch(version: ${appVersion.manifestHash})`); 1827 await this.versionFailed(appVersion, err); 1828 return this.safeFetch(event.request); 1829 } 1830 throw err; 1831 } 1832 } 1833 if (res === null) { 1834 return this.safeFetch(event.request); 1835 } 1836 return res; 1837 } finally { 1838 event.waitUntil(this.idle.trigger()); 1839 } 1840 } 1841 /** 1842 * Attempt to quickly reach a state where it's safe to serve responses. 1843 */ 1844 async initialize() { 1845 const table = await this.controlTable; 1846 let manifests, assignments, latest; 1847 try { 1848 [manifests, assignments, latest] = await Promise.all([ 1849 table.read("manifests"), 1850 table.read("assignments"), 1851 table.read("latest") 1852 ]); 1853 if (!this.versions.has(latest.latest) && !manifests.hasOwnProperty(latest.latest)) { 1854 this.debugger.log(`Missing manifest for latest version hash ${latest.latest}`, "initialize: read from DB"); 1855 throw new Error(`Missing manifest for latest hash ${latest.latest}`); 1856 } 1857 this.idle.schedule("init post-load (update)", async () => { 1858 await this.checkForUpdate(); 1859 }); 1860 } catch (_) { 1861 const manifest = await this.fetchLatestManifest(); 1862 const hash = hashManifest(manifest); 1863 manifests = { [hash]: manifest }; 1864 assignments = {}; 1865 latest = { latest: hash }; 1866 await Promise.all([ 1867 table.write("manifests", manifests), 1868 table.write("assignments", assignments), 1869 table.write("latest", latest) 1870 ]); 1871 } 1872 this.idle.schedule("init post-load (cleanup)", async () => { 1873 await this.cleanupCaches(); 1874 }); 1875 Object.keys(manifests).forEach((hash) => { 1876 const manifest = manifests[hash]; 1877 if (!this.versions.has(hash)) { 1878 this.versions.set(hash, new AppVersion(this.scope, this.adapter, this.db, this.idle, this.debugger, manifest, hash)); 1879 } 1880 }); 1881 Object.keys(assignments).forEach((clientId) => { 1882 const hash = assignments[clientId]; 1883 if (this.versions.has(hash)) { 1884 this.clientVersionMap.set(clientId, hash); 1885 } else { 1886 this.clientVersionMap.set(clientId, latest.latest); 1887 this.debugger.log(`Unknown version ${hash} mapped for client ${clientId}, using latest instead`, `initialize: map assignments`); 1888 } 1889 }); 1890 this.latestHash = latest.latest; 1891 if (!this.versions.has(latest.latest)) { 1892 throw new Error(`Invariant violated (initialize): latest hash ${latest.latest} has no known manifest`); 1893 }
1894 await Promise.all(Object.keys(manifests).map(async (hash) => { 1895 try { 1896 await this.scheduleInitialization(this.versions.get(hash)); 1897 } catch (err) { 1898 this.debugger.log(err, `initialize: schedule init of ${hash}`); 1899 } 1900 })); 1901 } 1902 lookupVersionByHash(hash, debugName = "lookupVersionByHash") { 1903 if (!this.versions.has(hash)) { 1904 throw new Error(`Invariant violated (${debugName}): want AppVersion for ${hash} but not loaded`); 1905 } 1906 return this.versions.get(hash); 1907 } 1908 /** 1909 * Decide which version of the manifest to use for the event. 1910 */ 1911 async assignVersion(event) { 1912 const isWorkerScriptRequest = event.request.destination === "worker" && event.resultingClientId && event.clientId; 1913 const clientId = isWorkerScriptRequest ? event.clientId : event.resultingClientId || event.clientId; 1914 if (clientId) { 1915 if (this.clientVersionMap.has(clientId)) { 1916 const hash = this.clientVersionMap.get(clientId); 1917 let appVersion = this.lookupVersionByHash(hash, "assignVersion"); 1918 if (this.state === DriverReadyState.NORMAL && hash !== this.latestHash && appVersion.isNavigationRequest(event.request)) { 1919 if (this.latestHash === null) { 1920 throw new Error(`Invariant violated (assignVersion): latestHash was null`); 1921 } 1922 const client = await this.scope.clients.get(clientId); 1923 if (client) { 1924 await this.updateClient(client); 1925 } 1926 appVersion = this.lookupVersionByHash(this.latestHash, "assignVersion"); 1927 } 1928 if (isWorkerScriptRequest) { 1929 if (!this.clientVersionMap.has(event.resultingClientId)) { 1930 this.clientVersionMap.set(event.resultingClientId, hash); 1931 await this.sync(); 1932 } else if (this.clientVersionMap.get(event.resultingClientId) !== hash) { 1933 throw new Error(`Version mismatch between worker client ${event.resultingClientId} and requesting client ${clientId}`); 1934 } 1935 } 1936 return appVersion; 1937 } else { 1938 if (this.state !== DriverReadyState.NORMAL) { 1939 return null; 1940 } 1941 if (this.latestHash === null) { 1942 throw new Error(`Invariant violated (assignVersion): latestHash was null`); 1943 } 1944 if (isWorkerScriptRequest) { 1945 if (!this.clientVersionMap.has(event.resultingClientId)) { 1946 this.clientVersionMap.set(event.resultingClientId, this.latestHash); 1947 } else if (this.clientVersionMap.get(event.resultingClientId) !== this.latestHash) { 1948 throw new Error(`Version mismatch between worker client ${event.resultingClientId} and requesting client ${clientId}`); 1949 } 1950 } 1951 this.clientVersionMap.set(clientId, this.latestHash); 1952 await this.sync(); 1953 return this.lookupVersionByHash(this.latestHash, "assignVersion"); 1954 } 1955 } else { 1956 if (this.state !== DriverReadyState.NORMAL) { 1957 return null; 1958 } 1959 if (this.latestHash === null) { 1960 throw new Error(`Invariant violated (assignVersion): latestHash was null`); 1961 } 1962 return this.lookupVersionByHash(this.latestHash, "assignVersion"); 1963 } 1964 } 1965 async fetchLatestManifest(ignoreOfflineError = false) { 1966 const res = await this.safeFetch(this.adapter.newRequest("ngsw.json?ngsw-cache-bust=" + Math.random())); 1967 if (!res.ok) { 1968 if (res.status === 404) { 1969 await this.deleteAllCaches(); 1970 await this.scope.registration.unregister(); 1971 } else if ((res.status === 503 || res.status === 504) && ignoreOfflineError) { 1972 return null; 1973 } 1974 throw new Error(`Manifest fetch failed! (status: ${res.status})`); 1975 } 1976 this.lastUpdateCheck = this.adapter.time; 1977 return res.json(); 1978 } 1979 async deleteAllCaches() { 1980 const cacheNames = await this.adapter.caches.keys(); 1981 await Promise.all(cacheNames.map((name) => this.adapter.caches.delete(name))); 1982 } 1983 /** 1984 * Schedule the SW's attempt to reach a fully prefetched state for the given AppVersion 1985 * when the SW is not busy and has connectivity. This returns a Promise which must be 1986 * awaited, as under some conditions the AppVersion might be initialized immediately. 1987 */ 1988 async scheduleInitialization(appVersion) { 1989 const initialize = async () => { 1990 try { 1991 await appVersion.initializeFully(); 1992 } catch (err) { 1993 this.debugger.log(err, `initializeFully for ${appVersion.manifestHash}`); 1994 await this.versionFailed(appVersion, err); 1995 } 1996 }; 1997 if (isLocalhost(this.scope.registration.scope)) { 1998 return initialize(); 1999 } 2000 this.idle.schedule(`initialization(${appVersion.manifestHash})`, initialize); 2001 } 2002 async versionFailed(appVersion, err) { 2003 const broken = Array.from(this.versions.entries()).find(([hash, version]
2003) => version === appVersion); 2004 if (broken === void 0) { 2005 return; 2006 } 2007 const brokenHash = broken[0]; 2008 if (this.latestHash === brokenHash) { 2009 this.state = DriverReadyState.EXISTING_CLIENTS_ONLY; 2010 this.stateMessage = `Degraded due to: ${errorToString(err)}`; 2011 } 2012 } 2013 async setupUpdate(manifest, hash) { 2014 try { 2015 const newVersion = new AppVersion(this.scope, this.adapter, this.db, this.idle, this.debugger, manifest, hash); 2016 if (manifest.configVersion !== SUPPORTED_CONFIG_VERSION) { 2017 await this.deleteAllCaches(); 2018 await this.scope.registration.unregister(); 2019 throw new Error(`Invalid config version: expected ${SUPPORTED_CONFIG_VERSION}, got ${manifest.configVersion}.`); 2020 } 2021 await newVersion.initializeFully(this); 2022 this.versions.set(hash, newVersion); 2023 this.latestHash = hash; 2024 if (this.state === DriverReadyState.EXISTING_CLIENTS_ONLY) { 2025 this.state = DriverReadyState.NORMAL; 2026 this.stateMessage = "(nominal)"; 2027 } 2028 await this.sync(); 2029 await this.notifyClientsAboutVersionReady(manifest, hash); 2030 } catch (e) { 2031 await this.notifyClientsAboutVersionInstallationFailed(manifest, hash, e); 2032 throw e; 2033 } 2034 } 2035 async checkForUpdate() { 2036 let hash = "(unknown)"; 2037 try { 2038 const manifest = await this.fetchLatestManifest(true); 2039 if (manifest === null) { 2040 this.debugger.log("Check for update aborted. (Client or server offline.)"); 2041 return false; 2042 } 2043 hash = hashManifest(manifest); 2044 if (this.versions.has(hash)) { 2045 await this.notifyClientsAboutNoNewVersionDetected(manifest, hash); 2046 return false; 2047 } 2048 await this.notifyClientsAboutVersionDetected(manifest, hash); 2049 await this.setupUpdate(manifest, hash); 2050 return true; 2051 } catch (err) { 2052 this.debugger.log(err, `Error occurred while updating to manifest ${hash}`); 2053 this.state = DriverReadyState.EXISTING_CLIENTS_ONLY; 2054 this.stateMessage = `Degraded due to failed initialization: ${errorToString(err)}`; 2055 return false; 2056 } 2057 } 2058 /** 2059 * Synchronize the existing state to the underlying database. 2060 */ 2061 async sync() { 2062 const table = await this.controlTable; 2063 const manifests = {}; 2064 this.versions.forEach((version, hash) => { 2065 manifests[hash] = version.manifest; 2066 }); 2067 const assignments = {}; 2068 this.clientVersionMap.forEach((hash, clientId) => { 2069 assignments[clientId] = hash; 2070 }); 2071 const latest = { 2072 latest: this.latestHash 2073 }; 2074 await Promise.all([ 2075 table.write("manifests", manifests), 2076 table.write("assignments", assignments), 2077 table.write("latest", latest) 2078 ]); 2079 } 2080 async cleanupCaches() { 2081 try { 2082 const activeClients = new Set((await this.scope.clients.matchAll()).map((client) => client.id)); 2083 const knownClients = Array.from(this.clientVersionMap.keys()); 2084 const obsoleteClients = knownClients.filter((id) => !activeClients.has(id)); 2085 obsoleteClients.forEach((id) => this.clientVersionMap.delete(id)); 2086 const usedVersions = new Set(this.clientVersionMap.values()); 2087 const obsoleteVersions = Array.from(this.versions.keys()).filter((version) => !usedVersions.has(version) && version !== this.latestHash); 2088 obsoleteVersions.forEach((version) => this.versions.delete(version)); 2089 await this.sync(); 2090 const allCaches = await this.adapter.caches.keys(); 2091 const usedCaches = new Set(await this.getCacheNames()); 2092 const cachesToDelete = allCaches.filter((name) => !usedCaches.has(name)); 2093 await Promise.all(cachesToDelete.map((name) => this.adapter.caches.delete(name))); 2094 } catch (err) { 2095 this.debugger.log(err, "cleanupCaches"); 2096 } 2097 } 2098 /** 2099 * Determine if a specific version of the given resource is cached anywhere within the SW, 2100 * and fetch it if so. 2101 */
2102 lookupResourceWithHash(url, hash) { 2103 return Array.from(this.versions.values()).reduce(async (prev, version) => { 2104 if (await prev !== null) { 2105 return prev; 2106 } 2107 return version.lookupResourceWithHash(url, hash); 2108 }, Promise.resolve(null)); 2109 } 2110 async lookupResourceWithoutHash(url) { 2111 await this.initialized; 2112 const version = this.versions.get(this.latestHash); 2113 return version ? version.lookupResourceWithoutHash(url) : null; 2114 } 2115 async previouslyCachedResources() { 2116 await this.initialized; 2117 const version = this.versions.get(this.latestHash); 2118 return version ? version.previouslyCachedResources() : []; 2119 } 2120 async recentCacheStatus(url) { 2121 const version = this.versions.get(this.latestHash); 2122 return version ? version.recentCacheStatus(url) : UpdateCacheStatus.NOT_CACHED; 2123 } 2124 mergeHashWithAppData(manifest, hash) { 2125 return { 2126 hash, 2127 appData: manifest.appData 2128 }; 2129 } 2130 async notifyClientsAboutUnrecoverableState(appVersion, reason) { 2131 const broken = Array.from(this.versions.entries()).find(([hash, version]) => version === appVersion); 2132 if (broken === void 0) { 2133 return; 2134 } 2135 const brokenHash = broken[0]; 2136 const affectedClients = Array.from(this.clientVersionMap.entries()).filter(([clientId, hash]) => hash === brokenHash).map(([clientId]) => clientId); 2137 await Promise.all(affectedClients.map(async (clientId) => { 2138 const client = await this.scope.clients.get(clientId); 2139 if (client) { 2140 client.postMessage({ type: "UNRECOVERABLE_STATE", reason }); 2141 } 2142 })); 2143 } 2144 async notifyClientsAboutVersionInstallationFailed(manifest, hash, error) { 2145 await this.initialized; 2146 const clients = await this.scope.clients.matchAll(); 2147 await Promise.all(clients.map(async (client) => { 2148 client.postMessage({ 2149 type: "VERSION_INSTALLATION_FAILED", 2150 version: this.mergeHashWithAppData(manifest, hash), 2151 error: errorToString(error) 2152 }); 2153 })); 2154 } 2155 async notifyClientsAboutNoNewVersionDetected(manifest, hash) { 2156 await this.initialized; 2157 const clients = await this.scope.clients.matchAll(); 2158 await Promise.all(clients.map(async (client) => { 2159 client.postMessage({ 2160 type: "NO_NEW_VERSION_DETECTED", 2161 version: this.mergeHashWithAppData(manifest, hash) 2162 }); 2163 })); 2164 } 2165 async notifyClientsAboutVersionDetected(manifest, hash) { 2166 await this.initialized; 2167 const clients = await this.scope.clients.matchAll(); 2168 await Promise.all(clients.map(async (client) => { 2169 const version = this.clientVersionMap.get(client.id); 2170 if (version === void 0) { 2171 return; 2172 } 2173 client.postMessage({ 2174 type: "VERSION_DETECTED", 2175 version: this.mergeHashWithAppData(manifest, hash) 2176 }); 2177 })); 2178 } 2179 async notifyClientsAboutVersionReady(manifest, hash) { 2180 await this.initialized; 2181 const clients = await this.scope.clients.matchAll(); 2182 await Promise.all(clients.map(async (client) => { 2183 const version = this.clientVersionMap.get(client.id); 2184 if (version === void 0) { 2185 return; 2186 } 2187 if (version === this.latestHash) { 2188 return; 2189 } 2190 const current = this.versions.get(version); 2191 const notice = { 2192 type: "VERSION_READY", 2193 currentVersion: this.mergeHashWithAppData(current.manifest, version), 2194 latestVersion: this.mergeHashWithAppData(manifest, hash) 2195 }; 2196 client.postMessage(notice); 2197 })); 2198 } 2199 async broadcast(msg) { 2200 const clients = await this.scope.clients.matchAll(); 2201 clients.forEach((client) => { 2202 client.postMessage(msg); 2203 }); 2204 } 2205 async debugState() { 2206 return { 2207 state: DriverReadyState[this.state], 2208 why: this.stateMessage, 2209 latestHash: this.latestHash, 2210 lastUpdateCheck: this.lastUpdateCheck 2211 }; 2212 } 2213 async debugVersions() { 2214 return Array.from(this.versions.keys()).map((hash) => { 2215 const version = this.versions.get(hash); 2216 const clients = Array.from(this.clientVersionMap.entries()).filter(([clientId, version2]) => version2 === hash).map(([clientId, version2]) => clientId); 2217 return { 2218 hash, 2219 manifest: version.manifest, 2220 clients, 2221 status: "" 2222 }; 2223 }); 2224 } 2225 async debugIdleState() { 2226 return { 2227 queue: this.idle.taskDescriptions, 2228 lastTrigger: this.idle.lastTrigger, 2229 lastRun: this.idle.lastRun 2230 }; 2231 } 2232 async safeFetch(req) {
2233 try { 2234 return await this.scope.fetch(req); 2235 } catch (err) { 2236 this.debugger.log(err, `Driver.fetch(${req.url})`); 2237 return this.adapter.newResponse(null, { 2238 status: 504, 2239 statusText: "Gateway Timeout" 2240 }); 2241 } 2242 } 2243 async getCacheNames() { 2244 const controlTable = await this.controlTable; 2245 const appVersions = Array.from(this.versions.values()); 2246 const appVersionCacheNames = await Promise.all(appVersions.map((version) => version.getCacheNames())); 2247 return [controlTable.cacheName].concat(...appVersionCacheNames); 2248 } 2249 }; 2250 2251
2251// packages/service-worker/worker/main.ts 2252 var scope = self; 2253 var adapter = new Adapter(scope.registration.scope, self.caches); 2254 new Driver(scope, adapter, new CacheDatabase(adapter)); 2255})(); 2256/** 2257 * @license 2258 * Copyright Google LLC All Rights Reserved. 2259 * 2260 * Use of this source code is governed by an MIT-style license that can be 2261 * found in the LICENSE file at https://angular.dev/license 2262 */ 2263importScripts("./ngsw-notifications.js");
Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.