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, headers, referrer, referrer policy, redirect policy, an explicit 610 * `credentials: 'omit'`, and the HTTP cache mode are preserved. On cross-origin redirects, 611 * sensitive headers are removed. This includes `Authorization`, as required by the Fetch redirect 612 * algorithm, and forbidden request headers that could contain credentials. 613 * 614 * NOTE: 615 * `credentials: 'same-origin'` and `credentials: 'include'` are intentionally not preserved. 616 * Forwarding `'include'` could leak cookies to cross-origin asset hosts, and forwarding 617 * `'same-origin'` matches the default `fetch()` behavior so there is nothing to preserve. 618 * Requests with `cache: 'only-if-cached'` and `mode !== 'same-origin'` are short-circuited 619 * earlier in `Driver.onFetch()` (they are a known Chrome DevTools quirk), so no special 620 * handling for that combination is needed here. 621 * TODO(gkalpak): 622 * Investigate preserving more metadata. See, also, discussion on preserving `mode`: 623 * https://github.com/angular/angular/issues/41931#issuecomment-1227601347. 624 */ 625 newRequestWithMetadata(url, options) { 626 let headers = options.headers; 627 const parsedUrl = this.adapter.parseUrl(url, this.adapter.origin); 628 const hasHeaders = headers.keys().next().done !== true; 629 if (hasHeaders && parsedUrl.origin !== this.adapter.origin) { 630 headers = this.adapter.newHeaders(options.headers); 631 headers.delete("Authorization"); 632 headers.delete("Proxy-Authorization"); 633 headers.delete("Cookie"); 634 } 635 const init = { 636 headers, 637 referrer: options.referrer, 638 referrerPolicy: options.referrerPolicy, 639 redirect: options.redirect 640 }; 641 if (options.credentials === "omit") { 642 init.credentials = "omit"; 643 } 644 if (options.cache !== void 0) { 645 init.cache = options.cache; 646 } 647 return this.adapter.newRequest(url, init); 648 } 649 /** 650 * Construct a cache-busting URL for a given URL. 651 */ 652 cacheBust(url) { 653 return url + (url.indexOf("?") === -1 ? "?" : "&") + "ngsw-cache-bust=" + Math.random(); 654 } 655 async safeFetch(req) { 656 try { 657 return await this.scope.fetch(req); 658 } catch (e) { 659 return this.adapter.newResponse("", { 660 status: 504, 661 statusText: "Gateway Timeout" 662 }); 663 } 664 } 665 }; 666 var PrefetchAssetGroup = class extends AssetGroup { 667 async initializeFully(updateFrom) { 668 const cache = await this.cache; 669 await this.urls.reduce(async (previous, url) => { 670 await previous; 671 const req = this.adapter.newRequest(url); 672 let alreadyCached = false; 673 try { 674 alreadyCached = await cache.match(req, this.config.cacheQueryOptions) !== void 0; 675 } catch (error) { 676 throw new SwCriticalError(`Cache is throwing while looking for a match in a PrefetchAssetGroup: ${error}`); 677 } 678 if (alreadyCached) { 679 return; 680 } 681 if (updateFrom !== void 0 && await this.maybeUpdate(updateFrom, req, cache)) { 682 return; 683 } 684 await this.fetchAndCacheOnce(req, false); 685 }, Promise.resolve()); 686 if (updateFrom !== void 0) { 687 const metaTable = await this.metadata; 688 await (await updateFrom.previouslyCachedResources()).filter((url) => this.urls.indexOf(url) !== -1 || this.patterns.some((pattern) => pattern.test(url))).reduce(async (previous, url) => { 689 await previous; 690 const req = this.adapter.newRequest(url); 691 const alreadyCached = await cache.match(req, this.config.cacheQueryOptions) !== void 0; 692 if (alreadyCached) { 693 return; 694 }
695 const res = await updateFrom.lookupResourceWithoutHash(url); 696 if (res === null || res.metadata === void 0) { 697 return; 698 } 699 await cache.put(req, res.response); 700 await metaTable.write(req.url, __spreadProps(__spreadValues({}, res.metadata), { used: false })); 701 }, Promise.resolve()); 702 } 703 } 704 }; 705 var LazyAssetGroup = class extends AssetGroup { 706 async initializeFully(updateFrom) { 707 if (updateFrom === void 0) { 708 return; 709 } 710 const cache = await this.cache; 711 await this.urls.reduce(async (previous, url) => { 712 await previous; 713 const req = this.adapter.newRequest(url); 714 let alreadyCached = false; 715 try { 716 alreadyCached = await cache.match(req, this.config.cacheQueryOptions) !== void 0; 717 } catch (error) { 718 throw new SwCriticalError(`Cache is throwing while looking for a match in a LazyAssetGroup: ${error}`); 719 } 720 if (alreadyCached) { 721 return; 722 } 723 const updated = await this.maybeUpdate(updateFrom, req, cache); 724 if (this.config.updateMode === "prefetch" && !updated) { 725 const cacheStatus = await updateFrom.recentCacheStatus(url); 726 if (cacheStatus !== UpdateCacheStatus.CACHED) { 727 return; 728 } 729 await this.fetchAndCacheOnce(req, false); 730 } 731 }, Promise.resolve()); 732 } 733 }; 734 735
735// packages/service-worker/worker/src/data.js 736 var LruList = class { 737 constructor(state) { 738 __publicField(this, "state"); 739 if (state === void 0) { 740 state = { 741 head: null, 742 tail: null, 743 map: {}, 744 count: 0 745 }; 746 } 747 this.state = state; 748 } 749 /** 750 * The current count of URLs in the list. 751 */ 752 get size() { 753 return this.state.count; 754 } 755 /** 756 * Remove the tail. 757 */ 758 pop() { 759 if (this.state.tail === null) { 760 return null; 761 } 762 const url = this.state.tail; 763 this.remove(url); 764 return url; 765 } 766 remove(url) { 767 const node = this.state.map[url]; 768 if (node === void 0) { 769 return false; 770 } 771 if (this.state.head === url) { 772 if (node.next === null) { 773 this.state.head = null; 774 this.state.tail = null; 775 this.state.map = {}; 776 this.state.count = 0; 777 return true; 778 } 779 const next = this.state.map[node.next]; 780 next.previous = null; 781 this.state.head = next.url; 782 node.next = null; 783 delete this.state.map[url]; 784 this.state.count--; 785 return true; 786 } 787 const previous = this.state.map[node.previous]; 788 previous.next = node.next; 789 if (node.next !== null) { 790 this.state.map[node.next].previous = node.previous; 791 } else { 792 this.state.tail = node.previous; 793 } 794 node.next = null; 795 node.previous = null; 796 delete this.state.map[url]; 797 this.state.count--; 798 return true; 799 } 800 accessed(url) { 801 if (this.state.head === url) { 802 return; 803 } 804 const node = this.state.map[url] || { url, next: null, previous: null }; 805 if (this.state.map[url] !== void 0) { 806 this.remove(url); 807 } 808 if (this.state.head !== null) { 809 this.state.map[this.state.head].previous = url; 810 } 811 node.next = this.state.head; 812 this.state.head = url; 813 if (this.state.tail === null) { 814 this.state.tail = url; 815 } 816 this.state.map[url] = node; 817 this.state.count++; 818 } 819 }; 820 var DataGroup = class { 821 constructor(scope2, adapter2, config, db, debugHandler, cacheNamePrefix) { 822 __publicField(this, "scope"); 823 __publicField(this, "adapter"); 824 __publicField(this, "config"); 825 __publicField(this, "db"); 826 __publicField(this, "debugHandler"); 827 /** 828 * Compiled regular expression set used to determine which resources fall under the purview 829 * of this group. 830 */ 831 __publicField(this, "patterns"); 832 /** 833 * The `Cache` instance in which resources belonging to this group are cached. 834 */ 835 __publicField(this, "cache"); 836 /** 837 * Tracks the LRU state of resources in this cache. 838 */ 839 __publicField(this, "_lru", null); 840 /** 841 * Database table used to store the state of the LRU cache. 842 */ 843 __publicField(this, "lruTable"); 844 /** 845 * Database table used to store metadata for resources in the cache. 846 */ 847 __publicField(this, "ageTable"); 848 this.scope = scope2; 849 this.adapter = adapter2; 850 this.config = config; 851 this.db = db; 852 this.debugHandler = debugHandler; 853 this.patterns = config.patterns.map((pattern) => new RegExp(pattern)); 854 this.cache = adapter2.caches.open(`${cacheNamePrefix}:${config.name}:cache`); 855 this.lruTable = this.db.open(`${cacheNamePrefix}:${config.name}:lru`, config.cacheQueryOptions); 856 this.ageTable = this.db.open(`${cacheNamePrefix}:${config.name}:age`, config.cacheQueryOptions); 857 } 858 /** 859 * Lazily initialize/load the LRU chain. 860 */ 861 async lru() { 862 if (this._lru === null) { 863 const table = await this.lruTable; 864 try { 865 this._lru = new LruList(await table.read("lru")); 866 } catch (e) { 867 this._lru = new LruList(); 868 } 869 } 870 return this._lru; 871 } 872 /** 873 * Sync the LRU chain to non-volatile storage. 874 */ 875 async syncLru() { 876 if (this._lru === null) { 877 return; 878 } 879 const table = await this.lruTable;
880 try { 881 return table.write("lru", this._lru.state); 882 } catch (err) { 883 this.debugHandler.log(err, `DataGroup(${this.config.name}@${this.config.version}).syncLru()`); 884 await this.detectStorageFull(); 885 } 886 } 887 /** 888 * Process a fetch event and return a `Response` if the resource is covered by this group, 889 * or `null` otherwise. 890 */ 891 async handleFetch(req, event) { 892 if (!this.patterns.some((pattern) => pattern.test(req.url))) { 893 return null; 894 } 895 const lru = await this.lru(); 896 switch (req.method) { 897 case "OPTIONS": 898 return null; 899 case "GET": 900 case "HEAD": 901 switch (this.config.strategy) { 902 case "freshness": 903 return this.handleFetchWithFreshness(req, event, lru); 904 case "performance": 905 return this.handleFetchWithPerformance(req, event, lru); 906 default: 907 throw new Error(`Unknown strategy: ${this.config.strategy}`); 908 } 909 default: 910 const wasCached = lru.remove(req.url); 911 if (wasCached) { 912 await this.clearCacheForUrl(req.url); 913 } 914 await this.syncLru(); 915 return this.safeFetch(req); 916 } 917 } 918 async handleFetchWithPerformance(req, event, lru) { 919 var _a; 920 const okToCacheOpaque = (_a = this.config.cacheOpaqueResponses) != null ? _a : false; 921 let res = null; 922 const fromCache = await this.loadFromCache(req, lru); 923 if (fromCache !== null) { 924 res = fromCache.res; 925 if (this.config.refreshAheadMs !== void 0 && fromCache.age >= this.config.refreshAheadMs) { 926 event.waitUntil(this.safeCacheResponse(req, this.safeFetch(req), lru, okToCacheOpaque)); 927 } 928 } 929 if (res !== null) { 930 return res; 931 } 932 const [timeoutFetch, networkFetch] = this.networkFetchWithTimeout(req); 933 res = await timeoutFetch; 934 if (res === void 0) { 935 res = this.adapter.newResponse(null, { status: 504, statusText: "Gateway Timeout" }); 936 event.waitUntil(this.safeCacheResponse(req, networkFetch, lru, okToCacheOpaque)); 937 } else { 938 await this.safeCacheResponse(req, res, lru, okToCacheOpaque); 939 } 940 return res; 941 } 942 async handleFetchWithFreshness(req, event, lru) { 943 var _a; 944 const okToCacheOpaque = (_a = this.config.cacheOpaqueResponses) != null ? _a : true; 945 const [timeoutFetch, networkFetch] = this.networkFetchWithTimeout(req); 946 let res; 947 try { 948 res = await timeoutFetch; 949 } catch (e) { 950 res = void 0; 951 } 952 if (res === void 0) { 953 event.waitUntil(this.safeCacheResponse(req, networkFetch, lru, okToCacheOpaque)); 954 const fromCache = await this.loadFromCache(req, lru); 955 res = fromCache !== null ? fromCache.res : null; 956 } else { 957 await this.safeCacheResponse(req, res, lru, okToCacheOpaque); 958 } 959 if (res !== null) { 960 return res; 961 } 962 return networkFetch; 963 } 964 networkFetchWithTimeout(req) { 965 if (this.config.timeoutMs !== void 0) { 966 const networkFetch = this.scope.fetch(req); 967 const safeNetworkFetch = (async () => { 968 try { 969 return await networkFetch; 970 } catch (e) { 971 return this.adapter.newResponse(null, { 972 status: 504, 973 statusText: "Gateway Timeout" 974 }); 975 } 976 })(); 977 const networkFetchUndefinedError = (async () => { 978 try { 979 return await networkFetch; 980 } catch (e) { 981 return void 0; 982 } 983 })(); 984 const timeout = this.adapter.timeout(this.config.timeoutMs); 985 return [Promise.race([networkFetchUndefinedError, timeout]), safeNetworkFetch]; 986 } else { 987 const networkFetch = this.safeFetch(req); 988 return [networkFetch, networkFetch]; 989 } 990 } 991 async safeCacheResponse(req, resOrPromise, lru, okToCacheOpaque) { 992 try { 993 const res = await resOrPromise; 994 try { 995 await this.cacheResponse(req, res, lru, okToCacheOpaque); 996 } catch (err) { 997 this.debugHandler.log(err, `DataGroup(${this.config.name}@${this.config.version}).safeCacheResponse(${req.url}, status: ${res.status})`); 998 await this.detectStorageFull(); 999 } 1000 } catch (e) { 1001 } 1002 } 1003 async loadFromCache(req, lru) { 1004 const cache = await this.cache; 1005 let res = await cache.match(req, this.config.cacheQueryOptions); 1006 if (res !== void 0) { 1007 try { 1008 const ageTable = await this.ageTable; 1009 const age = this.adapter.time - (await ageTable.read(req.url)).age; 1010 if (age <= this.config.maxAge) { 1011 lru.accessed(req.url); 1012 return { res, age }; 1013 } 1014 } catch (e) { 1015 } 1016 lru.remove(req.url); 1017 await this.clearCacheForUrl(req.url); 1018 await this.syncLru(); 1019 } 1020 return null; 1021 } 1022 /** 1023 * Operation for caching the response from the server. This has to happen all 1024 * at once, so that the cache and LRU tracking remain in sync. If the network request 1025 * completes before the timeout, this logic will be run inline with the response flow. 1026 * If the request times out on the server, an error will be returned but the real network 1027 * request will still be running in the background, to be cached when it completes. 1028 */ 1029 async cacheResponse(req, res, lru, okToCacheOpaque = false) { 1030 if (!(res.ok || okToCacheOpaque && res.type === "opaque")) { 1031 return; 1032 } 1033 if (lru.size >= this.config.maxSize) { 1034 const evictedUrl = lru.pop(); 1035 if (evictedUrl !== null) { 1036 await this.clearCacheForUrl(evictedUrl); 1037 } 1038 } 1039 lru.accessed(req.url); 1040 await (await this.cache).put(req, res.clone()); 1041 const ageTable = await this.ageTable; 1042 await ageTable.write(req.url, { age: this.adapter.time }); 1043 await this.syncLru(); 1044 } 1045 /** 1046 * Delete all of the saved state which this group uses to track resources. 1047 */ 1048 async cleanup() { 1049 await Promise.all([ 1050 this.cache.then((cache) => this.adapter.caches.delete(cache.name)), 1051 this.ageTable.then((table) => this.db.delete(table.name)), 1052 this.lruTable.then((table) => this.db.delete(table.name)) 1053 ]); 1054 } 1055 /** 1056 * Return a list of the names of all caches used by this group. 1057 */ 1058 async getCacheNames() { 1059 const [cache, ageTable, lruTable] = await Promise.all([ 1060 this.cache, 1061 this.ageTable, 1062 this.lruTable 1063 ]); 1064 return [cache.name, ageTable.cacheName, lruTable.cacheName]; 1065 } 1066 /** 1067 * Clear the state of the cache for a particular resource. 1068 * 1069 * This doesn't remove the resource from the LRU table, that is assumed to have 1070 * been done already. This clears the GET and HEAD versions of the request from 1071 * the cache itself, as well as the metadata stored in the age table. 1072 */ 1073 async clearCacheForUrl(url) { 1074 const [cache, ageTable] = await Promise.all([this.cache, this.ageTable]); 1075 await Promise.all([ 1076 cache.delete(this.adapter.newRequest(url, { method: "GET" }), this.config.cacheQueryOptions), 1077 cache.delete(this.adapter.newRequest(url, { method: "HEAD" }), this.config.cacheQueryOptions), 1078 ageTable.delete(url) 1079 ]); 1080 } 1081 async safeFetch(req) {
1082 try { 1083 return this.scope.fetch(req); 1084 } catch (e) { 1085 return this.adapter.newResponse(null, { 1086 status: 504, 1087 statusText: "Gateway Timeout" 1088 }); 1089 } 1090 } 1091 /** 1092 * Detect if storage is full or approaching capacity. 1093 * Returns true if storage is at or near capacity. 1094 */ 1095 async detectStorageFull() { 1096 try { 1097 const estimate = await navigator.storage.estimate(); 1098 const { quota, usage } = estimate; 1099 if (typeof quota !== "number" || typeof usage !== "number") { 1100 return; 1101 } 1102 const usagePercentage = usage / quota * 100; 1103 const isStorageFull = usagePercentage >= 95; 1104 if (isStorageFull) { 1105 this.debugHandler.log("Storage is full or nearly full", `DataGroup(${this.config.name}@${this.config.version}).detectStorageFull()`); 1106 } 1107 } catch (e) { 1108 } 1109 } 1110 }; 1111 1112
1112// packages/service-worker/worker/src/app-version.js 1113 var AppVersion = class { 1114 constructor(scope2, adapter2, database, idle, debugHandler, manifest, manifestHash) { 1115 __publicField(this, "scope"); 1116 __publicField(this, "adapter"); 1117 __publicField(this, "database"); 1118 __publicField(this, "debugHandler"); 1119 __publicField(this, "manifest"); 1120 __publicField(this, "manifestHash"); 1121 /** 1122 * A Map of absolute URL paths (`/foo.txt`) to the known hash of their contents (if available). 1123 */ 1124 __publicField(this, "hashTable", /* @__PURE__ */ new Map()); 1125 /** 1126 * All of the asset groups active in this version of the app. 1127 */ 1128 __publicField(this, "assetGroups"); 1129 /** 1130 * All of the data groups active in this version of the app. 1131 */ 1132 __publicField(this, "dataGroups"); 1133 /** 1134 * Requests to URLs that match any of the `include` RegExps and none of the `exclude` RegExps 1135 * are considered navigation requests and handled accordingly. 1136 */ 1137 __publicField(this, "navigationUrls"); 1138 /** 1139 * The normalized URL to the file that serves as the index page to satisfy navigation requests. 1140 * Usually this is `/index.html`. 1141 */ 1142 __publicField(this, "indexUrl"); 1143 /** 1144 * Tracks whether the manifest has encountered any inconsistencies. 1145 */ 1146 __publicField(this, "_okay", true); 1147 this.scope = scope2; 1148 this.adapter = adapter2; 1149 this.database = database; 1150 this.debugHandler = debugHandler; 1151 this.manifest = manifest; 1152 this.manifestHash = manifestHash; 1153 this.indexUrl = this.adapter.normalizeUrl(this.manifest.index); 1154 Object.keys(manifest.hashTable).forEach((url) => { 1155 this.hashTable.set(adapter2.normalizeUrl(url), manifest.hashTable[url]); 1156 }); 1157 const assetCacheNamePrefix = `${manifestHash}:assets`; 1158 this.assetGroups = (manifest.assetGroups || []).map((config) => { 1159 switch (config.installMode) { 1160 case "prefetch": 1161 return new PrefetchAssetGroup(scope2, adapter2, idle, config, this.hashTable, database, assetCacheNamePrefix); 1162 case "lazy": 1163 return new LazyAssetGroup(scope2, adapter2, idle, config, this.hashTable, database, assetCacheNamePrefix); 1164 } 1165 }); 1166 this.dataGroups = (manifest.dataGroups || []).map((config) => new DataGroup(scope2, adapter2, config, database, debugHandler, `${config.version}:data`)); 1167 const includeUrls = manifest.navigationUrls.filter((spec) => spec.positive); 1168 const excludeUrls = manifest.navigationUrls.filter((spec) => !spec.positive); 1169 this.navigationUrls = { 1170 include: includeUrls.map((spec) => new RegExp(spec.regex)), 1171 exclude: excludeUrls.map((spec) => new RegExp(spec.regex)) 1172 }; 1173 } 1174 get okay() { 1175 return this._okay; 1176 } 1177 /** 1178 * Fully initialize this version of the application. If this Promise resolves successfully, all 1179 * required 1180 * data has been safely downloaded. 1181 */ 1182 async initializeFully(updateFrom) { 1183 try { 1184 await this.assetGroups.reduce(async (previous, group) => { 1185 await previous; 1186 return group.initializeFully(updateFrom); 1187 }, Promise.resolve()); 1188 } catch (err) { 1189 this._okay = false; 1190 throw err; 1191 } 1192 } 1193 async handleFetch(req, event) { 1194 const asset = await this.assetGroups.reduce(async (potentialResponse, group) => { 1195 const resp = await potentialResponse; 1196 if (resp !== null) { 1197 return resp; 1198 } 1199 return group.handleFetch(req, event); 1200 }, Promise.resolve(null)); 1201 if (asset !== null) { 1202 return asset; 1203 } 1204 const data = await this.dataGroups.reduce(async (potentialResponse, group) => { 1205 const resp = await potentialResponse; 1206 if (resp !== null) { 1207 return resp; 1208 } 1209 return group.handleFetch(req, event); 1210 }, Promise.resolve(null)); 1211 if (data !== null) { 1212 return data; 1213 } 1214 if (this.adapter.normalizeUrl(req.url) !== this.indexUrl && this.isNavigationRequest(req)) { 1215 if (this.manifest.navigationRequestStrategy === "freshness") { 1216 try { 1217 return await this.scope.fetch(req); 1218 } catch (e) { 1219 } 1220 } 1221 return this.handleFetch(this.adapter.newRequest(this.indexUrl), event); 1222 } 1223 return null; 1224 } 1225 /** 1226 * Determine whether the request is a navigation request. 1227 * Takes into account: Request method and mode, `Accept` header, `navigationUrls` patterns. 1228 */ 1229 isNavigationRequest(req) { 1230 if (req.method !== "GET" || req.mode !== "navigate") { 1231 return false;
1232 } 1233 if (!this.acceptsTextHtml(req)) { 1234 return false; 1235 } 1236 const urlPrefix = this.scope.registration.scope.replace(/\/$/, ""); 1237 const url = req.url.startsWith(urlPrefix) ? req.url.slice(urlPrefix.length) : req.url; 1238 const urlWithoutQueryOrHash = url.replace(/[?#].*$/, ""); 1239 return this.navigationUrls.include.some((regex) => regex.test(urlWithoutQueryOrHash)) && !this.navigationUrls.exclude.some((regex) => regex.test(urlWithoutQueryOrHash)); 1240 } 1241 /** 1242 * Check this version for a given resource with a particular hash. 1243 */ 1244 async lookupResourceWithHash(url, hash) { 1245 if (!this.hashTable.has(url)) { 1246 return null; 1247 } 1248 if (this.hashTable.get(url) !== hash) { 1249 return null; 1250 } 1251 const cacheState = await this.lookupResourceWithoutHash(url); 1252 return cacheState && cacheState.response; 1253 } 1254 /** 1255 * Check this version for a given resource regardless of its hash. 1256 */ 1257 lookupResourceWithoutHash(url) { 1258 return this.assetGroups.reduce(async (potentialResponse, group) => { 1259 const resp = await potentialResponse; 1260 if (resp !== null) { 1261 return resp; 1262 } 1263 return group.fetchFromCacheOnly(url); 1264 }, Promise.resolve(null)); 1265 } 1266 /** 1267 * List all unhashed resources from all asset groups. 1268 */ 1269 previouslyCachedResources() { 1270 return this.assetGroups.reduce(async (resources, group) => (await resources).concat(await group.unhashedResources()), Promise.resolve([])); 1271 } 1272 async recentCacheStatus(url) { 1273 return this.assetGroups.reduce(async (current, group) => { 1274 const status = await current; 1275 if (status === UpdateCacheStatus.CACHED) { 1276 return status; 1277 } 1278 const groupStatus = await group.cacheStatus(url); 1279 if (groupStatus === UpdateCacheStatus.NOT_CACHED) { 1280 return status; 1281 } 1282 return groupStatus; 1283 }, Promise.resolve(UpdateCacheStatus.NOT_CACHED)); 1284 } 1285 /** 1286 * Return a list of the names of all caches used by this version. 1287 */ 1288 async getCacheNames() { 1289 const allGroupCacheNames = await Promise.all([ 1290 ...this.assetGroups.map((group) => group.getCacheNames()), 1291 ...this.dataGroups.map((group) => group.getCacheNames()) 1292 ]); 1293 return [].concat(...allGroupCacheNames); 1294 } 1295 /** 1296 * Get the opaque application data which was provided with the manifest. 1297 */ 1298 get appData() { 1299 return this.manifest.appData || null; 1300 } 1301 /** 1302 * Check whether a request accepts `text/html` (based on the `Accept` header). 1303 */ 1304 acceptsTextHtml(req) { 1305 const accept = req.headers.get("Accept"); 1306 if (accept === null) { 1307 return false; 1308 } 1309 const values = accept.split(","); 1310 return values.some((value) => value.trim().toLowerCase() === "text/html"); 1311 } 1312 }; 1313 1314
1314// packages/service-worker/worker/src/debug.js 1315 var SW_VERSION = "21.2.21"; 1316 var DEBUG_LOG_BUFFER_SIZE = 100; 1317 var DebugHandler = class { 1318 constructor(driver, adapter2) { 1319 __publicField(this, "driver"); 1320 __publicField(this, "adapter"); 1321 // There are two debug log message arrays. debugLogA records new debugging messages. 1322 // Once it reaches DEBUG_LOG_BUFFER_SIZE, the array is moved to debugLogB and a new 1323 // array is assigned to debugLogA. This ensures that insertion to the debug log is 1324 // always O(1) no matter the number of logged messages, and that the total number 1325 // of messages in the log never exceeds 2 * DEBUG_LOG_BUFFER_SIZE. 1326 __publicField(this, "debugLogA", []); 1327 __publicField(this, "debugLogB", []); 1328 this.driver = driver; 1329 this.adapter = adapter2; 1330 } 1331 async handleFetch(req) { 1332 const [state, versions, idle] = await Promise.all([ 1333 this.driver.debugState(), 1334 this.driver.debugVersions(), 1335 this.driver.debugIdleState() 1336 ]); 1337 const msgState = `NGSW Debug Info: 1338 1339Driver version: ${SW_VERSION} 1340Driver state: ${state.state} (${state.why}) 1341Latest manifest hash: ${state.latestHash || "none"} 1342Last update check: ${this.since(state.lastUpdateCheck)}`; 1343 const msgVersions = versions.map((version) => `=== Version ${version.hash} === 1344 1345Clients: ${version.clients.join(", ")}`).join("\n\n"); 1346 const msgIdle = `=== Idle Task Queue === 1347Last update tick: ${this.since(idle.lastTrigger)} 1348Last update run: ${this.since(idle.lastRun)} 1349Task queue: 1350${idle.queue.map((v) => " * " + v).join("\n")} 1351 1352Debug log: 1353${this.formatDebugLog(this.debugLogB)} 1354${this.formatDebugLog(this.debugLogA)} 1355`; 1356 return this.adapter.newResponse(`${msgState} 1357 1358${msgVersions} 1359 1360${msgIdle}`, { headers: this.adapter.newHeaders({ "Content-Type": "text/plain" }) }); 1361 } 1362 since(time) { 1363 if (time === null) { 1364 return "never"; 1365 } 1366 let age = this.adapter.time - time; 1367 const days = Math.floor(age / 864e5); 1368 age = age % 864e5; 1369 const hours = Math.floor(age / 36e5); 1370 age = age % 36e5; 1371 const minutes = Math.floor(age / 6e4); 1372 age = age % 6e4; 1373 const seconds = Math.floor(age / 1e3); 1374 const millis = age % 1e3; 1375 return (days > 0 ? `${days}d` : "") + (hours > 0 ? `${hours}h` : "") + (minutes > 0 ? `${minutes}m` : "") + (seconds > 0 ? `${seconds}s` : "") + (millis > 0 ? `${millis}u` : ""); 1376 } 1377 log(value, context = "") { 1378 if (this.debugLogA.length === DEBUG_LOG_BUFFER_SIZE) { 1379 this.debugLogB = this.debugLogA; 1380 this.debugLogA = []; 1381 } 1382 if (typeof value !== "string") { 1383 value = this.errorToString(value); 1384 } 1385 this.debugLogA.push({ value, time: this.adapter.time, context }); 1386 } 1387 errorToString(err) { 1388 return `${err.name}(${err.message}, ${err.stack})`; 1389 } 1390 formatDebugLog(log) { 1391 return log.map((entry) => `[${this.since(entry.time)}] ${entry.value} ${entry.context}`).join("\n"); 1392 } 1393 }; 1394 1395
1395// packages/service-worker/worker/src/idle.js 1396 var IdleScheduler = class { 1397 constructor(adapter2, delay, maxDelay, debug) { 1398 __publicField(this, "adapter"); 1399 __publicField(this, "delay"); 1400 __publicField(this, "maxDelay"); 1401 __publicField(this, "debug"); 1402 __publicField(this, "queue", []); 1403 __publicField(this, "scheduled", null); 1404 __publicField(this, "empty", Promise.resolve()); 1405 __publicField(this, "emptyResolve", null); 1406 __publicField(this, "lastTrigger", null); 1407 __publicField(this, "lastRun", null); 1408 __publicField(this, "oldestScheduledAt", null); 1409 this.adapter = adapter2; 1410 this.delay = delay; 1411 this.maxDelay = maxDelay; 1412 this.debug = debug; 1413 } 1414 async trigger() { 1415 var _a; 1416 this.lastTrigger = this.adapter.time; 1417 if (this.queue.length === 0) { 1418 return; 1419 } 1420 if (this.scheduled !== null) { 1421 this.scheduled.cancel = true; 1422 } 1423 const scheduled = { 1424 cancel: false 1425 }; 1426 this.scheduled = scheduled; 1427 const now = this.adapter.time; 1428 const maxDelay = Math.max(0, ((_a = this.oldestScheduledAt) != null ? _a : now) + this.maxDelay - now); 1429 const delay = Math.min(maxDelay, this.delay); 1430 await this.adapter.timeout(delay); 1431 if (scheduled.cancel) { 1432 return; 1433 } 1434 this.scheduled = null; 1435 await this.execute(); 1436 } 1437 async execute() { 1438 this.lastRun = this.adapter.time; 1439 while (this.queue.length > 0) { 1440 const queue = this.queue; 1441 this.queue = []; 1442 await queue.reduce(async (previous, task) => { 1443 await previous; 1444 try { 1445 await task.run(); 1446 } catch (err) { 1447 this.debug.log(err, `while running idle task ${task.desc}`); 1448 } 1449 }, Promise.resolve()); 1450 } 1451 if (this.emptyResolve !== null) { 1452 this.emptyResolve(); 1453 this.emptyResolve = null; 1454 } 1455 this.empty = Promise.resolve(); 1456 this.oldestScheduledAt = null; 1457 } 1458 schedule(desc, run) { 1459 this.queue.push({ desc, run }); 1460 if (this.emptyResolve === null) { 1461 this.empty = new Promise((resolve) => { 1462 this.emptyResolve = resolve; 1463 }); 1464 } 1465 if (this.oldestScheduledAt === null) { 1466 this.oldestScheduledAt = this.adapter.time; 1467 } 1468 } 1469 get size() { 1470 return this.queue.length; 1471 } 1472 get taskDescriptions() { 1473 return this.queue.map((task) => task.desc); 1474 } 1475 }; 1476 1477
1477// packages/service-worker/worker/src/manifest.js 1478 function hashManifest(manifest) { 1479 return sha1(JSON.stringify(manifest)); 1480 } 1481 1482
1482// packages/service-worker/worker/src/msg.js 1483 function isMsgCheckForUpdates(msg) { 1484 return msg.action === "CHECK_FOR_UPDATES"; 1485 } 1486 function isMsgActivateUpdate(msg) { 1487 return msg.action === "ACTIVATE_UPDATE"; 1488 } 1489 1490
1490// packages/service-worker/worker/src/driver.js 1491 var IDLE_DELAY = 5e3; 1492 var MAX_IDLE_DELAY = 3e4; 1493 var SUPPORTED_CONFIG_VERSION = 1; 1494 var NOTIFICATION_OPTION_NAMES = [ 1495 "actions", 1496 "badge", 1497 "body", 1498 "data", 1499 "dir", 1500 "icon", 1501 "image", 1502 "lang", 1503 "renotify", 1504 "requireInteraction", 1505 "silent", 1506 "tag", 1507 "timestamp", 1508 "title", 1509 "vibrate" 1510 ]; 1511 function isLocalhost(scope2) { 1512 return /(?:^https?:\/\/)?(?:(?:^|[^\w.])localhost|\[::1\]|127(?:\.\d{1,3}){3})(?::\d+)?(?:\/.*)?$/.test(scope2); 1513 } 1514 var DriverReadyState; 1515 (function(DriverReadyState2) { 1516 DriverReadyState2[DriverReadyState2["NORMAL"] = 0] = "NORMAL"; 1517 DriverReadyState2[DriverReadyState2["EXISTING_CLIENTS_ONLY"] = 1] = "EXISTING_CLIENTS_ONLY"; 1518 DriverReadyState2[DriverReadyState2["SAFE_MODE"] = 2] = "SAFE_MODE"; 1519 })(DriverReadyState || (DriverReadyState = {})); 1520 var Driver = class { 1521 constructor(scope2, adapter2, db) { 1522 __publicField(this, "scope"); 1523 __publicField(this, "adapter"); 1524 __publicField(this, "db"); 1525 /** 1526 * Tracks the current readiness condition under which the SW is operating. This controls 1527 * whether the SW attempts to respond to some or all requests. 1528 */ 1529 __publicField(this, "state", DriverReadyState.NORMAL); 1530 __publicField(this, "stateMessage", "(nominal)"); 1531 /** 1532 * Tracks whether the SW is in an initialized state or not. Before initialization, 1533 * it's not legal to respond to requests. 1534 */ 1535 __publicField(this, "initialized", null); 1536 /** 1537 * Maps client IDs to the manifest hash of the application version being used to serve 1538 * them. If a client ID is not present here, it has not yet been assigned a version. 1539 * 1540 * If a ManifestHash appears here, it is also present in the `versions` map below. 1541 */ 1542 __publicField(this, "clientVersionMap", /* @__PURE__ */ new Map()); 1543 /** 1544 * Maps manifest hashes to instances of `AppVersion` for those manifests. 1545 */ 1546 __publicField(this, "versions", /* @__PURE__ */ new Map()); 1547 /** 1548 * The latest version fetched from the server. 1549 * 1550 * Valid after initialization has completed. 1551 */ 1552 __publicField(this, "latestHash", null); 1553 __publicField(this, "lastUpdateCheck", null); 1554 /** 1555 * Whether there is a check for updates currently scheduled due to navigation. 1556 */ 1557 __publicField(this, "scheduledNavUpdateCheck", false); 1558 /** 1559 * Keep track of whether we have logged an invalid `only-if-cached` request. 1560 * (See `.onFetch()` for details.) 1561 */ 1562 __publicField(this, "loggedInvalidOnlyIfCachedRequest", false); 1563 __publicField(this, "ngswStatePath"); 1564 /** 1565 * A scheduler which manages a queue of tasks that need to be executed when the SW is 1566 * not doing any other work (not processing any other requests). 1567 */ 1568 __publicField(this, "idle"); 1569 __publicField(this, "debugger"); 1570 // A promise resolving to the control DB table. 1571 __publicField(this, "controlTable"); 1572 this.scope = scope2; 1573 this.adapter = adapter2; 1574 this.db = db; 1575 this.controlTable = this.db.open("control"); 1576 this.ngswStatePath = this.adapter.parseUrl("ngsw/state", this.scope.registration.scope).path; 1577 this.scope.addEventListener("install", (event) => { 1578 event.waitUntil(this.scope.skipWaiting()); 1579 }); 1580 this.scope.addEventListener("activate", (event) => { 1581 event.waitUntil((async () => { 1582 await this.scope.clients.claim(); 1583 })()); 1584 if (this.scope.registration.active !== null) { 1585 this.scope.registration.active.postMessage({ action: "INITIALIZE" }); 1586 } 1587 }); 1588 this.scope.addEventListener("fetch", (event) => this.onFetch(event)); 1589 this.scope.addEventListener("message", (event) => this.onMessage(event)); 1590 this.scope.addEventListener("push", (event) => this.onPush(event)); 1591 this.scope.addEventListener("notificationclick", (event) => this.onClick(event)); 1592 this.scope.addEventListener("notificationclose", (event) => this.onClose(event)); 1593 this.scope.addEventListener("pushsubscriptionchange", (event) => ( 1594 // This is a bug in TypeScript, where they removed `PushSub
1594scriptionChangeEvent` 1595 // based on the incorrect assumption that browsers don't support it. 1596 this.onPushSubscriptionChange(event) 1597 )); 1598 this.scope.addEventListener("messageerror", (event) => this.onMessageError(event)); 1599 this.scope.addEventListener("unhandledrejection", (event) => this.onUnhandledRejection(event)); 1600 this.debugger = new DebugHandler(this, this.adapter); 1601 this.idle = new IdleScheduler(this.adapter, IDLE_DELAY, MAX_IDLE_DELAY, this.debugger); 1602 } 1603 /** 1604 * The handler for fetch events. 1605 * 1606 * This is the transition point between the synchronous event handler and the 1607 * asynchronous execution that eventually resolves for respondWith() and waitUntil(). 1608 */ 1609 onFetch(event) { 1610 const req = event.request; 1611 const scopeUrl = this.scope.registration.scope; 1612 const requestUrlObj = this.adapter.parseUrl(req.url, scopeUrl); 1613 if (req.headers.has("ngsw-bypass") || /[?&]ngsw-bypass(?:[=&]|$)/i.test(requestUrlObj.search)) { 1614 return; 1615 } 1616 if (requestUrlObj.path === this.ngswStatePath) { 1617 event.respondWith(this.debugger.handleFetch(req)); 1618 return; 1619 } 1620 if (this.state === DriverReadyState.SAFE_MODE) { 1621 event.waitUntil(this.idle.trigger()); 1622 return; 1623 } 1624 if (requestUrlObj.origin.startsWith("http:") && scopeUrl.startsWith("https:")) { 1625 this.debugger.log(`Ignoring passive mixed content request: Driver.fetch(${req.url})`); 1626 return; 1627 } 1628 if (req.cache === "only-if-cached" && req.mode !== "same-origin") { 1629 if (!this.loggedInvalidOnlyIfCachedRequest) { 1630 this.loggedInvalidOnlyIfCachedRequest = true; 1631 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})`); 1632 } 1633 return; 1634 } 1635 event.respondWith(this.handleFetch(event)); 1636 } 1637 /** 1638 * The handler for message events. 1639 */ 1640 onMessage(event) { 1641 if (this.state === DriverReadyState.SAFE_MODE) { 1642 return; 1643 } 1644 const data = event.data; 1645 if (!data || !data.action) { 1646 return; 1647 } 1648 event.waitUntil((async () => { 1649 if (data.action === "INITIALIZE") { 1650 return this.ensureInitialized(event); 1651 } 1652 if (!this.adapter.isClient(event.source)) { 1653 return; 1654 } 1655 await this.ensureInitialized(event); 1656 await this.handleMessage(data, event.source); 1657 })()); 1658 } 1659 onPush(msg) { 1660 if (!msg.data) { 1661 return; 1662 } 1663 msg.waitUntil(this.handlePush(msg.data.json())); 1664 } 1665 onClick(event) { 1666 event.waitUntil(this.handleClick(event.notification, event.action)); 1667 } 1668 onClose(event) { 1669 event.waitUntil(this.handleClose(event.notification, event.action)); 1670 } 1671 onPushSubscriptionChange(event) { 1672 event.waitUntil(this.handlePushSubscriptionChange(event)); 1673 } 1674 onMessageError(event) { 1675 this.debugger.log(`Message error occurred - data could not be deserialized`, `Driver.onMessageError(origin: ${event.origin})`); 1676 } 1677 onUnhandledRejection(event) { 1678 this.debugger.log(`Unhandled promise rejection occurred`, `Driver.onUnhandledRejection(reason: ${event.reason})`); 1679 } 1680 async ensureInitialized(event) { 1681 if (this.initialized !== null) { 1682 return this.initialized; 1683 } 1684 try { 1685 this.initialized = this.initialize(); 1686 await this.initialized; 1687 } catch (error) { 1688 this.state = DriverReadyState.SAFE_MODE; 1689 this.stateMessage = `Initialization failed due to error: ${errorToString(error)}`; 1690 throw error; 1691 } finally { 1692 event.waitUntil(this.idle.trigger()); 1693 } 1694 } 1695 async handleMessage(msg, from) { 1696 if (isMsgCheckForUpdates(msg)) { 1697 const action = this.checkForUpdate(); 1698 await this.completeOperation(from, action, msg.nonce); 1699 } else if (isMsgActivateUpdate(msg)) { 1700 const action = this.updateClient(from); 1701 await this.completeOperation(from, action, msg.nonce); 1702 } 1703 } 1704 async handlePush(data) { 1705 await this.broadcast({ 1706 type: "PUSH", 1707 data 1708 }); 1709 if (!data.notification || !data.notification.title) { 1710 return; 1711 } 1712 const desc = data.notification; 1713 let options = {}; 1714 NOTIFICATION_OPTION_NAMES.filter((name) =>
1714 desc.hasOwnProperty(name)).forEach((name) => options[name] = desc[name]); 1715 await this.scope.registration.showNotification(desc["title"], options); 1716 } 1717 async handleClick(notification, action) { 1718 var _a, _b, _c; 1719 notification.close(); 1720 const options = {}; 1721 NOTIFICATION_OPTION_NAMES.filter((name) => name in notification).forEach((name) => options[name] = notification[name]); 1722 const notificationAction = action === "" || action === void 0 ? "default" : action; 1723 const onActionClick = (_b = (_a = notification == null ? void 0 : notification.data) == null ? void 0 : _a.onActionClick) == null ? void 0 : _b[notificationAction]; 1724 const urlToOpen = new URL((_c = onActionClick == null ? void 0 : onActionClick.url) != null ? _c : "", this.scope.registration.scope).href; 1725 switch (onActionClick == null ? void 0 : onActionClick.operation) { 1726 case "openWindow": 1727 await this.scope.clients.openWindow(urlToOpen); 1728 break; 1729 case "focusLastFocusedOrOpen": { 1730 let matchingClient = await this.getLastFocusedMatchingClient(this.scope); 1731 if (matchingClient) { 1732 await (matchingClient == null ? void 0 : matchingClient.focus()); 1733 } else { 1734 await this.scope.clients.openWindow(urlToOpen); 1735 } 1736 break; 1737 } 1738 case "navigateLastFocusedOrOpen": { 1739 let matchingClient = await this.getLastFocusedMatchingClient(this.scope); 1740 if (matchingClient) { 1741 matchingClient = await matchingClient.navigate(urlToOpen); 1742 await (matchingClient == null ? void 0 : matchingClient.focus()); 1743 } else { 1744 await this.scope.clients.openWindow(urlToOpen); 1745 } 1746 break; 1747 } 1748 case "sendRequest": { 1749 await this.scope.fetch(urlToOpen); 1750 break; 1751 } 1752 default: 1753 break; 1754 } 1755 await this.broadcast({ 1756 type: "NOTIFICATION_CLICK", 1757 data: { action, notification: options } 1758 }); 1759 } 1760 /** 1761 * Handles the closing of a notification by extracting its options and 1762 * broadcasting a `NOTIFICATION_CLOSE` message. 1763 * 1764 * This is typically called when a notification is dismissed by the user 1765 * or closed programmatically, and it relays that information to clients 1766 * listening for service worker events. 1767 * 1768 * @param notification - The original `Notification` object that was closed. 1769 * @param action - The action string associated with the close event, if any (usually an empty string). 1770 */ 1771 async handleClose(notification, action) { 1772 const options = {}; 1773 NOTIFICATION_OPTION_NAMES.filter((name) => name in notification).forEach((name) => options[name] = notification[name]); 1774 await this.broadcast({ 1775 type: "NOTIFICATION_CLOSE", 1776 data: { action, notification: options } 1777 }); 1778 } 1779 /** 1780 * Handles changes to the push subscription by capturing the old and new 1781 * subscription details and broadcasting a `PUSH_SUBSCRIPTION_CHANGE` message. 1782 * 1783 * This method is triggered when the browser invalidates an existing push 1784 * subscription and creates a new one, which can happen without user interaction. 1785 * It ensures that clients listening for service worker events are informed 1786 * of the subscription update. 1787 * 1788 * @param event - The `PushSubscriptionChangeEvent` containing the old and new subscriptions. 1789 */ 1790 async handlePushSubscriptionChange(event) { 1791 const { oldSubscription, newSubscription } = event; 1792 await this.broadcast({ 1793 type: "PUSH_SUBSCRIPTION_CHANGE", 1794 data: { oldSubscription, newSubscription } 1795 }); 1796 } 1797 async getLastFocusedMatchingClient(scope2) { 1798 const windowClients = await scope2.clients.matchAll({ type: "window" }); 1799 return windowClients[0]; 1800 } 1801 async completeOperation(client, promise, nonce) { 1802 const response = { type: "OPERATION_COMPLETED", nonce }; 1803 try { 1804 client.postMessage(__spreadProps(__spreadValues({}, response), { 1805 result: await promise 1806 })); 1807 } catch (e) { 1808 client.postMessage(__spreadProps(__spreadValues({}, response), { 1809 error: e.toString() 1810 })); 1811 } 1812 } 1813 async updateClient(client) { 1814 const existing = this.clientVersionMap.get(client.id); 1815 if (existing === this.latestHash) { 1816 return false;
1817 } 1818 let previous = void 0; 1819 if (existing !== void 0) { 1820 const existingVersion = this.versions.get(existing); 1821 previous = this.mergeHashWithAppData(existingVersion.manifest, existing); 1822 } 1823 this.clientVersionMap.set(client.id, this.latestHash); 1824 await this.sync(); 1825 const current = this.versions.get(this.latestHash); 1826 return true; 1827 } 1828 async handleFetch(event) { 1829 try { 1830 await this.ensureInitialized(event); 1831 } catch (e) { 1832 return this.safeFetch(event.request); 1833 } 1834 if (event.request.mode === "navigate" && !this.scheduledNavUpdateCheck) { 1835 this.scheduledNavUpdateCheck = true; 1836 this.idle.schedule("check-updates-on-navigation", async () => { 1837 this.scheduledNavUpdateCheck = false; 1838 await this.checkForUpdate(); 1839 }); 1840 } 1841 const appVersion = await this.assignVersion(event); 1842 const isVersionWithinMaxAge = (appVersion == null ? void 0 : appVersion.manifest.applicationMaxAge) === void 0 || this.adapter.time - appVersion.manifest.timestamp < appVersion.manifest.applicationMaxAge; 1843 let res = null; 1844 try { 1845 if (appVersion !== null && isVersionWithinMaxAge) { 1846 try { 1847 res = await appVersion.handleFetch(event.request, event); 1848 } catch (err) { 1849 if (err.isUnrecoverableState) { 1850 await this.notifyClientsAboutUnrecoverableState(appVersion, err.message); 1851 } 1852 if (err.isCritical) { 1853 this.debugger.log(err, `Driver.handleFetch(version: ${appVersion.manifestHash})`); 1854 await this.versionFailed(appVersion, err); 1855 return this.safeFetch(event.request); 1856 } 1857 throw err; 1858 } 1859 } 1860 if (res === null) { 1861 return this.safeFetch(event.request); 1862 } 1863 return res; 1864 } finally { 1865 event.waitUntil(this.idle.trigger()); 1866 } 1867 } 1868 /** 1869 * Attempt to quickly reach a state where it's safe to serve responses. 1870 */ 1871 async initialize() { 1872 const table = await this.controlTable; 1873 let manifests, assignments, latest; 1874 try { 1875 [manifests, assignments, latest] = await Promise.all([ 1876 table.read("manifests"), 1877 table.read("assignments"), 1878 table.read("latest") 1879 ]); 1880 if (!this.versions.has(latest.latest) && !manifests.hasOwnProperty(latest.latest)) { 1881 this.debugger.log(`Missing manifest for latest version hash ${latest.latest}`, "initialize: read from DB"); 1882 throw new Error(`Missing manifest for latest hash ${latest.latest}`); 1883 } 1884 this.idle.schedule("init post-load (update)", async () => { 1885 await this.checkForUpdate(); 1886 }); 1887 } catch (_) { 1888 const manifest = await this.fetchLatestManifest(); 1889 const hash = hashManifest(manifest); 1890 manifests = { [hash]: manifest }; 1891 assignments = {}; 1892 latest = { latest: hash }; 1893 await Promise.all([ 1894 table.write("manifests", manifests), 1895 table.write("assignments", assignments), 1896 table.write("latest", latest) 1897 ]); 1898 } 1899 this.idle.schedule("init post-load (cleanup)", async () => { 1900 await this.cleanupCaches(); 1901 }); 1902 Object.keys(manifests).forEach((hash) => { 1903 const manifest = manifests[hash]; 1904 if (!this.versions.has(hash)) { 1905 this.versions.set(hash, new AppVersion(this.scope, this.adapter, this.db, this.idle, this.debugger, manifest, hash)); 1906 } 1907 }); 1908 Object.keys(assignments).forEach((clientId) => { 1909 const hash = assignments[clientId]; 1910 if (this.versions.has(hash)) { 1911 this.clientVersionMap.set(clientId, hash); 1912 } else { 1913 this.clientVersionMap.set(clientId, latest.latest); 1914 this.debugger.log(`Unknown version ${hash} mapped for client ${clientId}, using latest instead`, `initialize: map assignments`); 1915 } 1916 }); 1917 this.latestHash = latest.latest; 1918 if (!this.versions.has(latest.latest)) { 1919 throw new Error(`Invariant violated (initialize): latest hash ${latest.latest} has no known manifest`); 1920 }
1921 await Promise.all(Object.keys(manifests).map(async (hash) => { 1922 try { 1923 await this.scheduleInitialization(this.versions.get(hash)); 1924 } catch (err) { 1925 this.debugger.log(err, `initialize: schedule init of ${hash}`); 1926 } 1927 })); 1928 } 1929 lookupVersionByHash(hash, debugName = "lookupVersionByHash") { 1930 if (!this.versions.has(hash)) { 1931 throw new Error(`Invariant violated (${debugName}): want AppVersion for ${hash} but not loaded`); 1932 } 1933 return this.versions.get(hash); 1934 } 1935 /** 1936 * Decide which version of the manifest to use for the event. 1937 */ 1938 async assignVersion(event) { 1939 const isWorkerScriptRequest = event.request.destination === "worker" && event.resultingClientId && event.clientId; 1940 const clientId = isWorkerScriptRequest ? event.clientId : event.resultingClientId || event.clientId; 1941 if (clientId) { 1942 if (this.clientVersionMap.has(clientId)) { 1943 const hash = this.clientVersionMap.get(clientId); 1944 let appVersion = this.lookupVersionByHash(hash, "assignVersion"); 1945 if (this.state === DriverReadyState.NORMAL && hash !== this.latestHash && appVersion.isNavigationRequest(event.request)) { 1946 if (this.latestHash === null) { 1947 throw new Error(`Invariant violated (assignVersion): latestHash was null`); 1948 } 1949 const client = await this.scope.clients.get(clientId); 1950 if (client) { 1951 await this.updateClient(client); 1952 } 1953 appVersion = this.lookupVersionByHash(this.latestHash, "assignVersion"); 1954 } 1955 if (isWorkerScriptRequest) { 1956 if (!this.clientVersionMap.has(event.resultingClientId)) { 1957 this.clientVersionMap.set(event.resultingClientId, hash); 1958 await this.sync(); 1959 } else if (this.clientVersionMap.get(event.resultingClientId) !== hash) { 1960 throw new Error(`Version mismatch between worker client ${event.resultingClientId} and requesting client ${clientId}`); 1961 } 1962 } 1963 return appVersion; 1964 } else { 1965 if (this.state !== DriverReadyState.NORMAL) { 1966 return null; 1967 } 1968 if (this.latestHash === null) { 1969 throw new Error(`Invariant violated (assignVersion): latestHash was null`); 1970 } 1971 if (isWorkerScriptRequest) { 1972 if (!this.clientVersionMap.has(event.resultingClientId)) { 1973 this.clientVersionMap.set(event.resultingClientId, this.latestHash); 1974 } else if (this.clientVersionMap.get(event.resultingClientId) !== this.latestHash) { 1975 throw new Error(`Version mismatch between worker client ${event.resultingClientId} and requesting client ${clientId}`); 1976 } 1977 } 1978 this.clientVersionMap.set(clientId, this.latestHash); 1979 await this.sync(); 1980 return this.lookupVersionByHash(this.latestHash, "assignVersion"); 1981 } 1982 } else { 1983 if (this.state !== DriverReadyState.NORMAL) { 1984 return null; 1985 } 1986 if (this.latestHash === null) { 1987 throw new Error(`Invariant violated (assignVersion): latestHash was null`); 1988 } 1989 return this.lookupVersionByHash(this.latestHash, "assignVersion"); 1990 } 1991 } 1992 async fetchLatestManifest(ignoreOfflineError = false) { 1993 const res = await this.safeFetch(this.adapter.newRequest("ngsw.json?ngsw-cache-bust=" + Math.random())); 1994 if (!res.ok) { 1995 if (res.status === 404) { 1996 await this.deleteAllCaches(); 1997 await this.scope.registration.unregister(); 1998 } else if ((res.status === 503 || res.status === 504) && ignoreOfflineError) { 1999 return null; 2000 } 2001 throw new Error(`Manifest fetch failed! (status: ${res.status})`); 2002 } 2003 this.lastUpdateCheck = this.adapter.time; 2004 return res.json(); 2005 } 2006 async deleteAllCaches() { 2007 const cacheNames = await this.adapter.caches.keys(); 2008 await Promise.all(cacheNames.map((name) => this.adapter.caches.delete(name))); 2009 } 2010 /** 2011 * Schedule the SW's attempt to reach a fully prefetched state for the given AppVersion 2012 * when the SW is not busy and has connectivity. This returns a Promise which must be 2013 * awaited, as under some conditions the AppVersion might be initialized immediately. 2014 */ 2015 async scheduleInitialization(appVersion) { 2016 const initialize = async () => { 2017 try { 2018 await appVersion.initializeFully(); 2019 } catch (err) { 2020 this.debugger.log(err, `initializeFully for ${appVersion.manifestHash}`); 2021 await this.versionFailed(appVersion, err); 2022 } 2023 }; 2024 if (isLocalhost(this.scope.registration.scope)) { 2025 return initialize(); 2026 } 2027 this.idle.schedule(`initialization(${appVersion.manifestHash})`, initialize); 2028 } 2029 async versionFailed(appVersion, err) { 2030 const broken = Array.from(this.versions.entries()).find(([hash, version]
2030) => version === appVersion); 2031 if (broken === void 0) { 2032 return; 2033 } 2034 const brokenHash = broken[0]; 2035 if (this.latestHash === brokenHash) { 2036 this.state = DriverReadyState.EXISTING_CLIENTS_ONLY; 2037 this.stateMessage = `Degraded due to: ${errorToString(err)}`; 2038 } 2039 } 2040 async setupUpdate(manifest, hash) { 2041 try { 2042 const newVersion = new AppVersion(this.scope, this.adapter, this.db, this.idle, this.debugger, manifest, hash); 2043 if (manifest.configVersion !== SUPPORTED_CONFIG_VERSION) { 2044 await this.deleteAllCaches(); 2045 await this.scope.registration.unregister(); 2046 throw new Error(`Invalid config version: expected ${SUPPORTED_CONFIG_VERSION}, got ${manifest.configVersion}.`); 2047 } 2048 await newVersion.initializeFully(this); 2049 this.versions.set(hash, newVersion); 2050 this.latestHash = hash; 2051 if (this.state === DriverReadyState.EXISTING_CLIENTS_ONLY) { 2052 this.state = DriverReadyState.NORMAL; 2053 this.stateMessage = "(nominal)"; 2054 } 2055 await this.sync(); 2056 await this.notifyClientsAboutVersionReady(manifest, hash); 2057 } catch (e) { 2058 await this.notifyClientsAboutVersionInstallationFailed(manifest, hash, e); 2059 throw e; 2060 } 2061 } 2062 async checkForUpdate() { 2063 let hash = "(unknown)"; 2064 try { 2065 const manifest = await this.fetchLatestManifest(true); 2066 if (manifest === null) { 2067 this.debugger.log("Check for update aborted. (Client or server offline.)"); 2068 return false; 2069 } 2070 hash = hashManifest(manifest); 2071 if (this.versions.has(hash)) { 2072 await this.notifyClientsAboutNoNewVersionDetected(manifest, hash); 2073 return false; 2074 } 2075 await this.notifyClientsAboutVersionDetected(manifest, hash); 2076 await this.setupUpdate(manifest, hash); 2077 return true; 2078 } catch (err) { 2079 this.debugger.log(err, `Error occurred while updating to manifest ${hash}`); 2080 this.state = DriverReadyState.EXISTING_CLIENTS_ONLY; 2081 this.stateMessage = `Degraded due to failed initialization: ${errorToString(err)}`; 2082 return false; 2083 } 2084 } 2085 /** 2086 * Synchronize the existing state to the underlying database. 2087 */ 2088 async sync() { 2089 const table = await this.controlTable; 2090 const manifests = {}; 2091 this.versions.forEach((version, hash) => { 2092 manifests[hash] = version.manifest; 2093 }); 2094 const assignments = {}; 2095 this.clientVersionMap.forEach((hash, clientId) => { 2096 assignments[clientId] = hash; 2097 }); 2098 const latest = { 2099 latest: this.latestHash 2100 }; 2101 await Promise.all([ 2102 table.write("manifests", manifests), 2103 table.write("assignments", assignments), 2104 table.write("latest", latest) 2105 ]); 2106 } 2107 async cleanupCaches() { 2108 try { 2109 const activeClients = new Set((await this.scope.clients.matchAll()).map((client) => client.id)); 2110 const knownClients = Array.from(this.clientVersionMap.keys()); 2111 const obsoleteClients = knownClients.filter((id) => !activeClients.has(id)); 2112 obsoleteClients.forEach((id) => this.clientVersionMap.delete(id)); 2113 const usedVersions = new Set(this.clientVersionMap.values()); 2114 const obsoleteVersions = Array.from(this.versions.keys()).filter((version) => !usedVersions.has(version) && version !== this.latestHash); 2115 obsoleteVersions.forEach((version) => this.versions.delete(version)); 2116 await this.sync(); 2117 const allCaches = await this.adapter.caches.keys(); 2118 const usedCaches = new Set(await this.getCacheNames()); 2119 const cachesToDelete = allCaches.filter((name) => !usedCaches.has(name)); 2120 await Promise.all(cachesToDelete.map((name) => this.adapter.caches.delete(name))); 2121 } catch (err) { 2122 this.debugger.log(err, "cleanupCaches"); 2123 } 2124 } 2125 /** 2126 * Determine if a specific version of the given resource is cached anywhere within the SW, 2127 * and fetch it if so. 2128 */
2129 lookupResourceWithHash(url, hash) { 2130 return Array.from(this.versions.values()).reduce(async (prev, version) => { 2131 if (await prev !== null) { 2132 return prev; 2133 } 2134 return version.lookupResourceWithHash(url, hash); 2135 }, Promise.resolve(null)); 2136 } 2137 async lookupResourceWithoutHash(url) { 2138 await this.initialized; 2139 const version = this.versions.get(this.latestHash); 2140 return version ? version.lookupResourceWithoutHash(url) : null; 2141 } 2142 async previouslyCachedResources() { 2143 await this.initialized; 2144 const version = this.versions.get(this.latestHash); 2145 return version ? version.previouslyCachedResources() : []; 2146 } 2147 async recentCacheStatus(url) { 2148 const version = this.versions.get(this.latestHash); 2149 return version ? version.recentCacheStatus(url) : UpdateCacheStatus.NOT_CACHED; 2150 } 2151 mergeHashWithAppData(manifest, hash) { 2152 return { 2153 hash, 2154 appData: manifest.appData 2155 }; 2156 } 2157 async notifyClientsAboutUnrecoverableState(appVersion, reason) { 2158 const broken = Array.from(this.versions.entries()).find(([hash, version]) => version === appVersion); 2159 if (broken === void 0) { 2160 return; 2161 } 2162 const brokenHash = broken[0]; 2163 const affectedClients = Array.from(this.clientVersionMap.entries()).filter(([clientId, hash]) => hash === brokenHash).map(([clientId]) => clientId); 2164 await Promise.all(affectedClients.map(async (clientId) => { 2165 const client = await this.scope.clients.get(clientId); 2166 if (client) { 2167 client.postMessage({ type: "UNRECOVERABLE_STATE", reason }); 2168 } 2169 })); 2170 } 2171 async notifyClientsAboutVersionInstallationFailed(manifest, hash, error) { 2172 await this.initialized; 2173 const clients = await this.scope.clients.matchAll(); 2174 await Promise.all(clients.map(async (client) => { 2175 client.postMessage({ 2176 type: "VERSION_INSTALLATION_FAILED", 2177 version: this.mergeHashWithAppData(manifest, hash), 2178 error: errorToString(error) 2179 }); 2180 })); 2181 } 2182 async notifyClientsAboutNoNewVersionDetected(manifest, hash) { 2183 await this.initialized; 2184 const clients = await this.scope.clients.matchAll(); 2185 await Promise.all(clients.map(async (client) => { 2186 client.postMessage({ 2187 type: "NO_NEW_VERSION_DETECTED", 2188 version: this.mergeHashWithAppData(manifest, hash) 2189 }); 2190 })); 2191 } 2192 async notifyClientsAboutVersionDetected(manifest, hash) { 2193 await this.initialized; 2194 const clients = await this.scope.clients.matchAll(); 2195 await Promise.all(clients.map(async (client) => { 2196 const version = this.clientVersionMap.get(client.id); 2197 if (version === void 0) { 2198 return; 2199 } 2200 client.postMessage({ 2201 type: "VERSION_DETECTED", 2202 version: this.mergeHashWithAppData(manifest, hash) 2203 }); 2204 })); 2205 } 2206 async notifyClientsAboutVersionReady(manifest, hash) { 2207 await this.initialized; 2208 const clients = await this.scope.clients.matchAll(); 2209 await Promise.all(clients.map(async (client) => { 2210 const version = this.clientVersionMap.get(client.id); 2211 if (version === void 0) { 2212 return; 2213 } 2214 if (version === this.latestHash) { 2215 return; 2216 } 2217 const current = this.versions.get(version); 2218 const notice = { 2219 type: "VERSION_READY", 2220 currentVersion: this.mergeHashWithAppData(current.manifest, version), 2221 latestVersion: this.mergeHashWithAppData(manifest, hash) 2222 }; 2223 client.postMessage(notice); 2224 })); 2225 } 2226 async broadcast(msg) { 2227 const clients = await this.scope.clients.matchAll(); 2228 clients.forEach((client) => { 2229 client.postMessage(msg); 2230 }); 2231 } 2232 async debugState() { 2233 return { 2234 state: DriverReadyState[this.state], 2235 why: this.stateMessage, 2236 latestHash: this.latestHash, 2237 lastUpdateCheck: this.lastUpdateCheck 2238 }; 2239 } 2240 async debugVersions() { 2241 return Array.from(this.versions.keys()).map((hash) => { 2242 const version = this.versions.get(hash); 2243 const clients = Array.from(this.clientVersionMap.entries()).filter(([clientId, version2]) => version2 === hash).map(([clientId, version2]) => clientId); 2244 return { 2245 hash, 2246 manifest: version.manifest, 2247 clients, 2248 status: "" 2249 }; 2250 }); 2251 } 2252 async debugIdleState() { 2253 return { 2254 queue: this.idle.taskDescriptions, 2255 lastTrigger: this.idle.lastTrigger, 2256 lastRun: this.idle.lastRun 2257 }; 2258 } 2259 async safeFetch(req) {
2260 try { 2261 return await this.scope.fetch(req); 2262 } catch (err) { 2263 this.debugger.log(err, `Driver.fetch(${req.url})`); 2264 return this.adapter.newResponse(null, { 2265 status: 504, 2266 statusText: "Gateway Timeout" 2267 }); 2268 } 2269 } 2270 async getCacheNames() { 2271 const controlTable = await this.controlTable; 2272 const appVersions = Array.from(this.versions.values()); 2273 const appVersionCacheNames = await Promise.all(appVersions.map((version) => version.getCacheNames())); 2274 return [controlTable.cacheName].concat(...appVersionCacheNames); 2275 } 2276 }; 2277 2278
2278// packages/service-worker/worker/main.ts 2279 var scope = self; 2280 var adapter = new Adapter(scope.registration.scope, self.caches); 2281 new Driver(scope, adapter, new CacheDatabase(adapter)); 2282})(); 2283/** 2284 * @license 2285 * Copyright Google LLC All Rights Reserved. 2286 * 2287 * Use of this source code is governed by an MIT-style license that can be 2288 * found in the LICENSE file at https://angular.dev/license 2289 */
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.