UNPKG

serwist

Version:

A Swiss Army knife for service workers.

1 lines 84.6 kB
{"version":3,"file":"index.legacy.mjs","names":["privateCacheNames","cleanupOutdatedCaches","privateCacheNames","clientsClaim","fallbacks","fallbacksImpl","disableDevLogs"],"sources":["../src/legacy/utils/PrecacheCacheKeyPlugin.ts","../src/legacy/PrecacheController.ts","../src/legacy/singletonPrecacheController.ts","../src/legacy/addPlugins.ts","../src/legacy/PrecacheRoute.ts","../src/legacy/Router.ts","../src/legacy/singletonRouter.ts","../src/legacy/registerRoute.ts","../src/legacy/addRoute.ts","../src/legacy/createHandlerBoundToURL.ts","../src/legacy/PrecacheFallbackPlugin.ts","../src/legacy/fallbacks.ts","../src/legacy/getCacheKeyForURL.ts","../src/legacy/handlePrecaching.ts","../src/legacy/constants.ts","../src/legacy/initializeGoogleAnalytics.ts","../src/legacy/registerRuntimeCaching.ts","../src/legacy/installSerwist.ts","../src/legacy/matchPrecache.ts","../src/legacy/precache.ts","../src/legacy/precacheAndRoute.ts","../src/legacy/setCatchHandler.ts","../src/legacy/setDefaultHandler.ts","../src/legacy/unregisterRoute.ts"],"sourcesContent":["/*\n Copyright 2020 Google LLC\n\n Use of this source code is governed by an MIT-style\n license that can be found in the LICENSE file or at\n https://opensource.org/licenses/MIT.\n*/\n\nimport type { SerwistPlugin, SerwistPluginCallbackParam } from \"../../types.js\";\nimport type { PrecacheController } from \"../PrecacheController.js\";\n\n/**\n * A plugin, designed to be used with PrecacheController, to translate URLs into\n * the corresponding cache key, based on the current revision info.\n *\n * @private\n */\nexport class PrecacheCacheKeyPlugin implements SerwistPlugin {\n private readonly _precacheController: PrecacheController;\n\n constructor({ precacheController }: { precacheController: PrecacheController }) {\n this._precacheController = precacheController;\n }\n\n cacheKeyWillBeUsed: SerwistPlugin[\"cacheKeyWillBeUsed\"] = async ({ request, params }: SerwistPluginCallbackParam[\"cacheKeyWillBeUsed\"]) => {\n // Params is type any, can't change right now.\n const cacheKey = params?.cacheKey || this._precacheController.getCacheKeyForURL(request.url);\n\n return cacheKey ? new Request(cacheKey, { headers: request.headers }) : request;\n };\n}\n","/*\n Copyright 2019 Google LLC\n\n Use of this source code is governed by an MIT-style\n license that can be found in the LICENSE file or at\n https://opensource.org/licenses/MIT.\n*/\n\nimport { parallel } from \"@serwist/utils\";\nimport { PrecacheStrategy } from \"../lib/strategies/PrecacheStrategy.js\";\nimport type { Strategy } from \"../lib/strategies/Strategy.js\";\nimport type { CleanupResult, InstallResult, PrecacheEntry, RouteHandlerCallback, SerwistPlugin } from \"../types.js\";\nimport { assert } from \"../utils/assert.js\";\nimport { cacheNames as privateCacheNames } from \"../utils/cacheNames.js\";\nimport { createCacheKey } from \"../utils/createCacheKey.js\";\nimport { logger } from \"../utils/logger.js\";\nimport { PrecacheInstallReportPlugin } from \"../utils/PrecacheInstallReportPlugin.js\";\nimport { printCleanupDetails } from \"../utils/printCleanupDetails.js\";\nimport { printInstallDetails } from \"../utils/printInstallDetails.js\";\nimport { SerwistError } from \"../utils/SerwistError.js\";\nimport { waitUntil } from \"../utils/waitUntil.js\";\nimport { PrecacheCacheKeyPlugin } from \"./utils/PrecacheCacheKeyPlugin.js\";\n\n// Give TypeScript the correct global.\ndeclare const self: ServiceWorkerGlobalScope;\n\ninterface PrecacheControllerOptions {\n /**\n * The cache to use for precaching.\n */\n cacheName?: string;\n /**\n * Plugins to use when precaching as well as responding to fetch\n * events for precached assets.\n */\n plugins?: SerwistPlugin[];\n /**\n * Whether to attempt to get the response from the network if there's\n * a precache miss.\n */\n fallbackToNetwork?: boolean;\n /**\n * The number of precache requests that should be made concurrently.\n *\n * @default 1\n */\n concurrentPrecaching?: number;\n}\n\n/**\n * Performs efficient precaching of assets.\n * @deprecated\n */\nexport class PrecacheController {\n private _installAndActiveListenersAdded?: boolean;\n private _concurrentPrecaching: number;\n private readonly _strategy: Strategy;\n private readonly _urlsToCacheKeys: Map<string, string> = new Map();\n private readonly _urlsToCacheModes: Map<string, \"reload\" | \"default\" | \"no-store\" | \"no-cache\" | \"force-cache\" | \"only-if-cached\"> = new Map();\n private readonly _cacheKeysToIntegrities: Map<string, string> = new Map();\n\n /**\n * Create a new PrecacheController.\n *\n * @param options\n */\n constructor({ cacheName, plugins = [], fallbackToNetwork = true, concurrentPrecaching = 1 }: PrecacheControllerOptions = {}) {\n this._concurrentPrecaching = concurrentPrecaching;\n this._strategy = new PrecacheStrategy({\n cacheName: privateCacheNames.getPrecacheName(cacheName),\n plugins: [...plugins, new PrecacheCacheKeyPlugin({ precacheController: this })],\n fallbackToNetwork,\n });\n // Bind the install and activate methods to the instance.\n this.install = this.install.bind(this);\n this.activate = this.activate.bind(this);\n }\n\n /**\n * The strategy created by this controller and\n * used to cache assets and respond to `fetch` events.\n */\n get strategy(): Strategy {\n return this._strategy;\n }\n\n /**\n * Adds items to the precache list, removing any duplicates and\n * stores the files in the precache cache when the service\n * worker installs.\n *\n * This method can be called multiple times.\n *\n * @param entries Array of entries to precache.\n */\n precache(entries: (PrecacheEntry | string)[]): void {\n this.addToCacheList(entries);\n\n if (!this._installAndActiveListenersAdded) {\n self.addEventListener(\"install\", this.install);\n self.addEventListener(\"activate\", this.activate);\n this._installAndActiveListenersAdded = true;\n }\n }\n\n /**\n * This method will add items to the precache list, removing duplicates\n * and ensuring the information is valid.\n *\n * @param entries Array of entries to precache.\n */\n addToCacheList(entries: (PrecacheEntry | string)[]): void {\n if (process.env.NODE_ENV !== \"production\") {\n assert!.isArray(entries, {\n moduleName: \"serwist/legacy\",\n className: \"PrecacheController\",\n funcName: \"addToCacheList\",\n paramName: \"entries\",\n });\n }\n\n const urlsToWarnAbout: string[] = [];\n for (const entry of entries) {\n // See https://github.com/GoogleChrome/workbox/issues/2259\n if (typeof entry === \"string\") {\n urlsToWarnAbout.push(entry);\n } else if (entry && !entry.integrity && entry.revision === undefined) {\n urlsToWarnAbout.push(entry.url);\n }\n\n const { cacheKey, url } = createCacheKey(entry);\n const cacheMode = typeof entry !== \"string\" && entry.revision ? \"reload\" : \"default\";\n\n if (this._urlsToCacheKeys.has(url) && this._urlsToCacheKeys.get(url) !== cacheKey) {\n throw new SerwistError(\"add-to-cache-list-conflicting-entries\", {\n firstEntry: this._urlsToCacheKeys.get(url),\n secondEntry: cacheKey,\n });\n }\n\n if (typeof entry !== \"string\" && entry.integrity) {\n if (this._cacheKeysToIntegrities.has(cacheKey) && this._cacheKeysToIntegrities.get(cacheKey) !== entry.integrity) {\n throw new SerwistError(\"add-to-cache-list-conflicting-integrities\", {\n url,\n });\n }\n this._cacheKeysToIntegrities.set(cacheKey, entry.integrity);\n }\n\n this._urlsToCacheKeys.set(url, cacheKey);\n this._urlsToCacheModes.set(url, cacheMode);\n\n if (urlsToWarnAbout.length > 0) {\n const warningMessage = `Serwist is precaching URLs without revision info: ${urlsToWarnAbout.join(\n \", \",\n )}\\nThis is generally NOT safe. Learn more at https://bit.ly/wb-precache`;\n if (process.env.NODE_ENV === \"production\") {\n // Use console directly to display this warning without bloating\n // bundle sizes by pulling in all of the logger codebase in prod.\n console.warn(warningMessage);\n } else {\n logger.warn(warningMessage);\n }\n }\n }\n }\n\n /**\n * Precaches new and updated assets. Call this method from the service worker\n * install event.\n *\n * Note: this method calls `event.waitUntil()` for you, so you do not need\n * to call it yourself in your event handlers.\n *\n * @param event\n * @returns\n */\n install(event: ExtendableEvent): Promise<InstallResult> {\n return waitUntil<InstallResult>(event, async () => {\n const installReportPlugin = new PrecacheInstallReportPlugin();\n this.strategy.plugins.push(installReportPlugin);\n\n await parallel(this._concurrentPrecaching, Array.from(this._urlsToCacheKeys.entries()), async ([url, cacheKey]): Promise<void> => {\n const integrity = this._cacheKeysToIntegrities.get(cacheKey);\n const cacheMode = this._urlsToCacheModes.get(url);\n\n const request = new Request(url, {\n integrity,\n cache: cacheMode,\n credentials: \"same-origin\",\n });\n\n await Promise.all(\n this.strategy.handleAll({\n event,\n request,\n url: new URL(request.url),\n params: { cacheKey },\n }),\n );\n });\n\n const { updatedURLs, notUpdatedURLs } = installReportPlugin;\n\n if (process.env.NODE_ENV !== \"production\") {\n printInstallDetails(updatedURLs, notUpdatedURLs);\n }\n\n return { updatedURLs, notUpdatedURLs };\n });\n }\n\n /**\n * Deletes assets that are no longer present in the current precache manifest.\n * Call this method from the service worker activate event.\n *\n * Note: this method calls `event.waitUntil()` for you, so you do not need\n * to call it yourself in your event handlers.\n *\n * @param event\n * @returns\n */\n activate(event: ExtendableEvent): Promise<CleanupResult> {\n return waitUntil<CleanupResult>(event, async () => {\n const cache = await self.caches.open(this.strategy.cacheName);\n const currentlyCachedRequests = await cache.keys();\n const expectedCacheKeys = new Set(this._urlsToCacheKeys.values());\n\n const deletedCacheRequests: string[] = [];\n\n for (const request of currentlyCachedRequests) {\n if (!expectedCacheKeys.has(request.url)) {\n await cache.delete(request);\n deletedCacheRequests.push(request.url);\n }\n }\n\n if (process.env.NODE_ENV !== \"production\") {\n printCleanupDetails(deletedCacheRequests);\n }\n\n return { deletedCacheRequests };\n });\n }\n\n /**\n * Returns a mapping of a precached URL to the corresponding cache key, taking\n * into account the revision information for the URL.\n *\n * @returns A URL to cache key mapping.\n */\n getURLsToCacheKeys(): Map<string, string> {\n return this._urlsToCacheKeys;\n }\n\n /**\n * Returns a list of all the URLs that have been precached by the current\n * service worker.\n *\n * @returns The precached URLs.\n */\n getCachedURLs(): string[] {\n return [...this._urlsToCacheKeys.keys()];\n }\n\n /**\n * Returns the cache key used for storing a given URL. If that URL is\n * unversioned, like `/index.html', then the cache key will be the original\n * URL with a search parameter appended to it.\n *\n * @param url A URL whose cache key you want to look up.\n * @returns The versioned URL that corresponds to a cache key\n * for the original URL, or undefined if that URL isn't precached.\n */\n getCacheKeyForURL(url: string): string | undefined {\n const urlObject = new URL(url, location.href);\n return this._urlsToCacheKeys.get(urlObject.href);\n }\n\n /**\n * @param url A cache key whose SRI you want to look up.\n * @returns The subresource integrity associated with the cache key,\n * or undefined if it's not set.\n */\n getIntegrityForCacheKey(cacheKey: string): string | undefined {\n return this._cacheKeysToIntegrities.get(cacheKey);\n }\n\n /**\n * This acts as a drop-in replacement for\n * [`cache.match()`](https://developer.mozilla.org/en-US/docs/Web/API/Cache/match)\n * with the following differences:\n *\n * - It knows what the name of the precache is, and only checks in that cache.\n * - It allows you to pass in an \"original\" URL without versioning parameters,\n * and it will automatically look up the correct cache key for the currently\n * active revision of that URL.\n *\n * E.g., `matchPrecache('index.html')` will find the correct precached\n * response for the currently active service worker, even if the actual cache\n * key is `'/index.html?__WB_REVISION__=1234abcd'`.\n *\n * @param request The key (without revisioning parameters)\n * to look up in the precache.\n * @returns\n */\n async matchPrecache(request: string | Request): Promise<Response | undefined> {\n const url = request instanceof Request ? request.url : request;\n const cacheKey = this.getCacheKeyForURL(url);\n if (cacheKey) {\n const cache = await self.caches.open(this.strategy.cacheName);\n return cache.match(cacheKey);\n }\n return undefined;\n }\n\n /**\n * Returns a function that looks up `url` in the precache (taking into\n * account revision information), and returns the corresponding `Response`.\n *\n * @param url The precached URL which will be used to lookup the response.\n * @return\n */\n createHandlerBoundToURL(url: string): RouteHandlerCallback {\n const cacheKey = this.getCacheKeyForURL(url);\n if (!cacheKey) {\n throw new SerwistError(\"non-precached-url\", { url });\n }\n return (options) => {\n options.request = new Request(url);\n options.params = { cacheKey, ...options.params };\n\n return this.strategy.handle(options);\n };\n }\n}\n","/*\n Copyright 2019 Google LLC\n\n Use of this source code is governed by an MIT-style\n license that can be found in the LICENSE file or at\n https://opensource.org/licenses/MIT.\n*/\n\nimport { PrecacheController } from \"./PrecacheController.js\";\n\nlet defaultPrecacheController: PrecacheController | undefined;\n\n/**\n * Creates a new, singleton {@linkcode PrecacheController} if one does not exist. If one does\n * already exist, that instance is returned. This instance is used by Serwist's\n * {@linkcode PrecacheController}-dependent functions and classes unless you provide a different\n * {@linkcode PrecacheController} to them.\n *\n * @returns The singleton {@linkcode PrecacheController}.\n * @deprecated\n */\nexport const getSingletonPrecacheController = (): PrecacheController => {\n if (!defaultPrecacheController) {\n defaultPrecacheController = new PrecacheController();\n }\n return defaultPrecacheController;\n};\n\n/**\n * Changes the singleton {@linkcode PrecacheController} to a different instance. This is meant for when you do not\n * want to pass your own {@linkcode PrecacheController} to every one of Serwist's {@linkcode PrecacheController}-dependent\n * functions and classes.\n *\n * It is highly recommended that you call this before anything else, if you plan on doing so.\n *\n * @example\n * ```js\n * import { PrecacheController, setSingletonPrecacheController } from \"serwist/legacy\";\n *\n * const controller = new PrecacheController();\n *\n * setSingletonPrecacheController(controller);\n *\n * // Do something with your controller...\n * ```\n * @param router\n * @returns The new singleton {@linkcode PrecacheController}.\n * @deprecated\n */\nexport const setSingletonPrecacheController = (precacheController: PrecacheController): PrecacheController => {\n defaultPrecacheController = precacheController;\n return defaultPrecacheController;\n};\n","/*\n Copyright 2019 Google LLC\n\n Use of this source code is governed by an MIT-style\n license that can be found in the LICENSE file or at\n https://opensource.org/licenses/MIT.\n*/\n\nimport type { SerwistPlugin } from \"../types.js\";\nimport { getSingletonPrecacheController } from \"./singletonPrecacheController.js\";\n\n/**\n * Adds plugins to the precaching strategy.\n *\n * @param plugins\n * @deprecated\n */\nexport const addPlugins = (plugins: SerwistPlugin[]): void => {\n const precacheController = getSingletonPrecacheController();\n precacheController.strategy.plugins.push(...plugins);\n};\n","/*\n Copyright 2020 Google LLC\n\n Use of this source code is governed by an MIT-style\n license that can be found in the LICENSE file or at\n https://opensource.org/licenses/MIT.\n*/\n\nimport { Route } from \"../Route.js\";\nimport type { PrecacheRouteOptions, RouteMatchCallback, RouteMatchCallbackOptions } from \"../types.js\";\nimport { generateURLVariations } from \"../utils/generateURLVariations.js\";\nimport { getFriendlyURL } from \"../utils/getFriendlyURL.js\";\nimport { logger } from \"../utils/logger.js\";\nimport type { PrecacheController } from \"./PrecacheController.js\";\n\n/**\n * A subclass of {@linkcode Route} that takes a {@linkcode PrecacheController}\n * instance and uses it to match incoming requests and handle fetching\n * responses from the precache.\n * @deprecated\n */\nexport class PrecacheRoute extends Route {\n /**\n * @param precacheController A {@linkcode PrecacheController}\n * instance used to both match requests and respond to `fetch` events.\n * @param options Options to control how requests are matched\n * against the list of precached URLs.\n */\n constructor(precacheController: PrecacheController, options?: PrecacheRouteOptions) {\n const match: RouteMatchCallback = ({ request }: RouteMatchCallbackOptions) => {\n const urlsToCacheKeys = precacheController.getURLsToCacheKeys();\n for (const possibleURL of generateURLVariations(request.url, options)) {\n const cacheKey = urlsToCacheKeys.get(possibleURL);\n if (cacheKey) {\n const integrity = precacheController.getIntegrityForCacheKey(cacheKey);\n return { cacheKey, integrity };\n }\n }\n if (process.env.NODE_ENV !== \"production\") {\n logger.debug(`Precaching did not find a match for ${getFriendlyURL(request.url)}`);\n }\n return;\n };\n\n super(match, precacheController.strategy);\n }\n}\n","/*\n Copyright 2018 Google LLC\n\n Use of this source code is governed by an MIT-style\n license that can be found in the LICENSE file or at\n https://opensource.org/licenses/MIT.\n*/\n\nimport type { HTTPMethod } from \"../constants.js\";\nimport { defaultMethod } from \"../constants.js\";\nimport type { Route } from \"../Route.js\";\nimport type { RouteHandler, RouteHandlerCallbackOptions, RouteHandlerObject, RouteMatchCallback, RouteMatchCallbackOptions } from \"../types.js\";\nimport { assert } from \"../utils/assert.js\";\nimport { getFriendlyURL } from \"../utils/getFriendlyURL.js\";\nimport { logger } from \"../utils/logger.js\";\nimport { normalizeHandler } from \"../utils/normalizeHandler.js\";\nimport { parseRoute } from \"../utils/parseRoute.js\";\nimport { SerwistError } from \"../utils/SerwistError.js\";\n\ndeclare const self: ServiceWorkerGlobalScope;\n\ntype RequestArgs = string | [string, RequestInit?];\n\ninterface CacheURLsMessageData {\n type: string;\n payload: {\n urlsToCache: RequestArgs[];\n };\n}\n\n/**\n * A class that can be used to process a `fetch` event using one or more route(s), responding with a response\n * if a matching route exists.\n *\n * If no route matches given a request, the router will use the default handler if one is defined.\n *\n * Should the matching route throw an error, the router will use the catch handler if one is defined to\n * gracefully deal with issues and respond with a response.\n *\n * If a request matches multiple routes, the earliest registered route will be used to respond to the it.\n * @deprecated\n */\nexport class Router {\n private readonly _routes: Map<HTTPMethod, Route[]>;\n private readonly _defaultHandlerMap: Map<HTTPMethod, RouteHandlerObject>;\n private _fetchListenerHandler: ((ev: FetchEvent) => void) | null = null;\n private _cacheListenerHandler: ((ev: ExtendableMessageEvent) => void) | null = null;\n private _catchHandler?: RouteHandlerObject;\n\n /**\n * Initializes a new Router.\n */\n constructor() {\n this._routes = new Map();\n this._defaultHandlerMap = new Map();\n }\n\n /**\n * @returns routes A `Map` of HTTP method name (`'GET'`, etc.) to an array of all\n * the corresponding {@linkcode Route} instances that are registered.\n */\n get routes(): Map<HTTPMethod, Route[]> {\n return this._routes;\n }\n\n /**\n * Adds a `fetch` event listener to respond to events when a route matches\n * the event's request. Effectively no-op if `addFetchListener` has been\n * called, but `removeFetchListener` has not.\n */\n addFetchListener(): void {\n if (!this._fetchListenerHandler) {\n this._fetchListenerHandler = (event) => {\n const { request } = event;\n const responsePromise = this.handleRequest({ request, event });\n if (responsePromise) {\n event.respondWith(responsePromise);\n }\n };\n self.addEventListener(\"fetch\", this._fetchListenerHandler);\n }\n }\n\n /**\n * Removes `fetch` event listener added by `addFetchListener`.\n * Effectively no-op if either `addFetchListener` has not been called or,\n * if it has, so has `removeFetchListener`.\n */\n removeFetchListener(): void {\n if (this._fetchListenerHandler) {\n self.removeEventListener(\"fetch\", this._fetchListenerHandler);\n this._fetchListenerHandler = null;\n }\n }\n\n /**\n * Adds a `message` event listener for URLs to cache from the window.\n * This is useful to cache resources loaded on the page prior to when the\n * service worker started controlling it. Effectively no-op if `addCacheListener`\n * has been called, but `removeCacheListener` hasn't.\n *\n * The format of the message data sent from the window should be as follows.\n * Where the `urlsToCache` array may consist of URL strings or an array of\n * URL string + `requestInit` object (the same as you'd pass to `fetch()`).\n *\n * ```\n * {\n * type: 'CACHE_URLS',\n * payload: {\n * urlsToCache: [\n * './script1.js',\n * './script2.js',\n * ['./script3.js', {mode: 'no-cors'}],\n * ],\n * },\n * }\n * ```\n */\n addCacheListener(): void {\n if (!this._cacheListenerHandler) {\n this._cacheListenerHandler = (event) => {\n if (event.data && event.data.type === \"CACHE_URLS\") {\n const { payload }: CacheURLsMessageData = event.data;\n\n if (process.env.NODE_ENV !== \"production\") {\n logger.debug(\"Caching URLs from the window\", payload.urlsToCache);\n }\n\n const requestPromises = Promise.all(\n payload.urlsToCache.map((entry: string | [string, RequestInit?]) => {\n if (typeof entry === \"string\") {\n entry = [entry];\n }\n\n const request = new Request(...entry);\n return this.handleRequest({ request, event });\n }),\n );\n\n event.waitUntil(requestPromises);\n\n // If a MessageChannel was used, reply to the message on success.\n if (event.ports?.[0]) {\n void requestPromises.then(() => event.ports[0].postMessage(true));\n }\n }\n };\n self.addEventListener(\"message\", this._cacheListenerHandler);\n }\n }\n\n /**\n * Removes the `message` event listener added by `addCacheListener`.\n * Effectively no-op if either `addCacheListener` has not been called or,\n * if it has, so has `removeCacheListener`.\n */\n removeCacheListener(): void {\n if (this._cacheListenerHandler) {\n self.removeEventListener(\"message\", this._cacheListenerHandler);\n }\n }\n\n /**\n * Apply the routing rules to a `fetch` event to get a response from an\n * appropriate route.\n *\n * @param options\n * @returns A promise is returned if a registered route can handle the request.\n * If there is no matching route and there's no `defaultHandler`, `undefined`\n * is returned.\n */\n handleRequest({\n request,\n event,\n }: {\n /**\n * The request to handle.\n */\n request: Request;\n /**\n * The event that triggered the request.\n */\n event: ExtendableEvent;\n }): Promise<Response> | undefined {\n if (process.env.NODE_ENV !== \"production\") {\n assert!.isInstance(request, Request, {\n moduleName: \"serwist/legacy\",\n className: \"Router\",\n funcName: \"handleRequest\",\n paramName: \"options.request\",\n });\n }\n\n const url = new URL(request.url, location.href);\n if (!url.protocol.startsWith(\"http\")) {\n if (process.env.NODE_ENV !== \"production\") {\n logger.debug(\"Router only supports URLs that start with 'http'.\");\n }\n return;\n }\n\n const sameOrigin = url.origin === location.origin;\n const { params, route } = this.findMatchingRoute({\n event,\n request,\n sameOrigin,\n url,\n });\n let handler = route?.handler;\n\n const debugMessages = [];\n if (process.env.NODE_ENV !== \"production\") {\n if (handler) {\n debugMessages.push([\"Found a route to handle this request:\", route]);\n\n if (params) {\n debugMessages.push([`Passing the following params to the route's handler:`, params]);\n }\n }\n }\n\n // If we don't have a handler because there was no matching route, then\n // fall back to defaultHandler if that's defined.\n const method = request.method as HTTPMethod;\n if (!handler && this._defaultHandlerMap.has(method)) {\n if (process.env.NODE_ENV !== \"production\") {\n debugMessages.push(`Failed to find a matching route. Falling back to the default handler for ${method}.`);\n }\n handler = this._defaultHandlerMap.get(method);\n }\n\n if (!handler) {\n if (process.env.NODE_ENV !== \"production\") {\n // No handler so Serwist will do nothing. If logs is set of debug\n // i.e. verbose, we should print out this information.\n logger.debug(`No route found for: ${getFriendlyURL(url)}`);\n }\n return;\n }\n\n if (process.env.NODE_ENV !== \"production\") {\n // We have a handler, meaning Serwist is going to handle the route.\n // print the routing details to the console.\n logger.groupCollapsed(`Router is responding to: ${getFriendlyURL(url)}`);\n\n for (const msg of debugMessages) {\n if (Array.isArray(msg)) {\n logger.log(...msg);\n } else {\n logger.log(msg);\n }\n }\n\n logger.groupEnd();\n }\n\n // Wrap in try and catch in case the handle method throws a synchronous\n // error. It should still callback to the catch handler.\n let responsePromise: Promise<Response>;\n try {\n responsePromise = handler.handle({ url, request, event, params });\n } catch (err) {\n responsePromise = Promise.reject(err);\n }\n\n // Get route's catch handler, if it exists\n const catchHandler = route?.catchHandler;\n\n if (responsePromise instanceof Promise && (this._catchHandler || catchHandler)) {\n responsePromise = responsePromise.catch(async (err) => {\n // If there's a route catch handler, process that first\n if (catchHandler) {\n if (process.env.NODE_ENV !== \"production\") {\n // Still include URL here as it will be async from the console group\n // and may not make sense without the URL\n logger.groupCollapsed(`Error thrown when responding to: ${getFriendlyURL(url)}. Falling back to route's Catch Handler.`);\n logger.error(\"Error thrown by:\", route);\n logger.error(err);\n logger.groupEnd();\n }\n\n try {\n return await catchHandler.handle({ url, request, event, params });\n } catch (catchErr) {\n if (catchErr instanceof Error) {\n err = catchErr;\n }\n }\n }\n\n if (this._catchHandler) {\n if (process.env.NODE_ENV !== \"production\") {\n // Still include URL here as it will be async from the console group\n // and may not make sense without the URL\n logger.groupCollapsed(`Error thrown when responding to: ${getFriendlyURL(url)}. Falling back to global Catch Handler.`);\n logger.error(\"Error thrown by:\", route);\n logger.error(err);\n logger.groupEnd();\n }\n return this._catchHandler.handle({ url, request, event });\n }\n\n throw err;\n });\n }\n\n return responsePromise;\n }\n\n /**\n * Checks a request and URL (and optionally an event) against the list of\n * registered routes, and if there's a match, returns the corresponding\n * route along with any params generated by the match.\n *\n * @param options\n * @returns An object with `route` and `params` properties. They are populated\n * if a matching route was found or `undefined` otherwise.\n */\n findMatchingRoute({ url, sameOrigin, request, event }: RouteMatchCallbackOptions): {\n route?: Route;\n params?: RouteHandlerCallbackOptions[\"params\"];\n } {\n const routes = this._routes.get(request.method as HTTPMethod) || [];\n for (const route of routes) {\n let params: Promise<any> | undefined;\n // route.match returns type any, not possible to change right now.\n const matchResult = route.match({ url, sameOrigin, request, event });\n if (matchResult) {\n if (process.env.NODE_ENV !== \"production\") {\n // Warn developers that using an async matchCallback is almost always\n // not the right thing to do.\n if (matchResult instanceof Promise) {\n logger.warn(\n `While routing ${getFriendlyURL(\n url,\n )}, an async matchCallback function was used. Please convert the following route to use a synchronous matchCallback function:`,\n route,\n );\n }\n }\n\n // See https://github.com/GoogleChrome/workbox/issues/2079\n params = matchResult;\n if (Array.isArray(params) && params.length === 0) {\n // Instead of passing an empty array in as params, use undefined.\n params = undefined;\n } else if (matchResult.constructor === Object && Object.keys(matchResult).length === 0) {\n // Instead of passing an empty object in as params, use undefined.\n params = undefined;\n } else if (typeof matchResult === \"boolean\") {\n // For the boolean value true (rather than just something truth-y),\n // don't set params.\n // See https://github.com/GoogleChrome/workbox/pull/2134#issuecomment-513924353\n params = undefined;\n }\n\n // Return early if have a match.\n return { route, params };\n }\n }\n // If no match was found above, return and empty object.\n return {};\n }\n\n /**\n * Define a default handler that's called when no routes explicitly\n * match the incoming request.\n *\n * Each HTTP method (`'GET'`, `'POST'`, etc.) gets its own default handler.\n *\n * Without a default handler, unmatched requests will go against the\n * network as if there were no service worker present.\n *\n * @param handler A callback function that returns a promise resulting in a response.\n * @param method The HTTP method to associate with this default handler. Each method\n * has its own default. Defaults to `'GET'`.\n */\n setDefaultHandler(handler: RouteHandler, method: HTTPMethod = defaultMethod): void {\n this._defaultHandlerMap.set(method, normalizeHandler(handler));\n }\n\n /**\n * If a `Route` throws an error while handling a request, this `handler`\n * will be called and given a chance to provide a response.\n *\n * @param handler A callback function that returns a Promise resulting\n * in a Response.\n */\n setCatchHandler(handler: RouteHandler): void {\n this._catchHandler = normalizeHandler(handler);\n }\n\n /**\n * Registers a `RegExp`, string, or function with a caching\n * strategy to the router.\n *\n * @param capture If the capture param is a {@linkcode Route} object, all other arguments will be ignored.\n * @param handler A callback function that returns a promise resulting in a response.\n * This parameter is required if `capture` is not a {@linkcode Route} object.\n * @param method The HTTP method to match the route against. Defaults to `'GET'`.\n * @returns The generated {@linkcode Route} object.\n */\n registerCapture(capture: RegExp | string | RouteMatchCallback | Route, handler?: RouteHandler, method?: HTTPMethod): Route {\n const route = parseRoute(capture, handler, method);\n this.registerRoute(route);\n return route;\n }\n\n /**\n * Registers a route with the router.\n *\n * @param route The route to register.\n */\n registerRoute(route: Route): void {\n if (process.env.NODE_ENV !== \"production\") {\n assert!.isType(route, \"object\", {\n moduleName: \"serwist/legacy\",\n className: \"Router\",\n funcName: \"registerRoute\",\n paramName: \"route\",\n });\n\n assert!.hasMethod(route, \"match\", {\n moduleName: \"serwist/legacy\",\n className: \"Router\",\n funcName: \"registerRoute\",\n paramName: \"route\",\n });\n\n assert!.isType(route.handler, \"object\", {\n moduleName: \"serwist/legacy\",\n className: \"Router\",\n funcName: \"registerRoute\",\n paramName: \"route\",\n });\n\n assert!.hasMethod(route.handler, \"handle\", {\n moduleName: \"serwist/legacy\",\n className: \"Router\",\n funcName: \"registerRoute\",\n paramName: \"route.handler\",\n });\n\n assert!.isType(route.method, \"string\", {\n moduleName: \"serwist/legacy\",\n className: \"Router\",\n funcName: \"registerRoute\",\n paramName: \"route.method\",\n });\n }\n\n if (!this._routes.has(route.method)) {\n this._routes.set(route.method, []);\n }\n\n // Give precedence to all of the earlier routes by adding this additional\n // route to the end of the array.\n this._routes.get(route.method)!.push(route);\n }\n\n /**\n * Unregisters a route from the router.\n *\n * @param route The route to unregister.\n */\n unregisterRoute(route: Route): void {\n if (!this._routes.has(route.method)) {\n throw new SerwistError(\"unregister-route-but-not-found-with-method\", {\n method: route.method,\n });\n }\n\n const routeIndex = this._routes.get(route.method)!.indexOf(route);\n if (routeIndex > -1) {\n this._routes.get(route.method)!.splice(routeIndex, 1);\n } else {\n throw new SerwistError(\"unregister-route-route-not-registered\");\n }\n }\n}\n","/*\n Copyright 2019 Google LLC\n\n Use of this source code is governed by an MIT-style\n license that can be found in the LICENSE file or at\n https://opensource.org/licenses/MIT.\n*/\n\nimport { Router } from \"./Router.js\";\n\nlet defaultRouter: Router | undefined;\n\n/**\n * Creates a new, singleton {@linkcode Router} if one does not exist. If one does\n * already exist, that instance is returned. This instance is used by\n * Serwist's {@linkcode Router}-dependent functions and classes unless you provide\n * a different {@linkcode Router} to them.\n *\n * @returns The singleton {@linkcode Router}.\n * @deprecated\n */\nexport const getSingletonRouter = (): Router => {\n if (!defaultRouter) {\n defaultRouter = new Router();\n\n // The helpers that use the default Router assume these listeners exist.\n defaultRouter.addFetchListener();\n defaultRouter.addCacheListener();\n }\n return defaultRouter;\n};\n\n/**\n * Changes the singleton {@linkcode Router} to a different instance. This is meant for when you do not\n * want to pass your own {@linkcode Router} to every one of Serwist's {@linkcode Router}-dependent functions and classes.\n * If this or {@linkcode getSingletonRouter} has been called before, it removes the listeners of the\n * previous singleton {@linkcode Router}. It also adds those of the new one, so you need not do that yourself.\n *\n * It is highly recommended that you call this before anything else, if you plan on doing so.\n *\n * @example\n * ```js\n * import { Router, setSingletonRouter } from \"serwist/legacy\";\n *\n * const router = new Router();\n *\n * setSingletonRouter(router);\n *\n * router.registerRoute(\n * new Route(\n * /\\/api\\/.*\\/*.json/,\n * new NetworkOnly(),\n * \"POST\",\n * ),\n * );\n * ```\n * @param router\n * @returns The new singleton {@linkcode Router}.\n * @deprecated\n */\nexport const setSingletonRouter = (router: Router): Router => {\n if (defaultRouter) {\n defaultRouter.removeFetchListener();\n defaultRouter.removeCacheListener();\n }\n defaultRouter = router;\n defaultRouter.addFetchListener();\n defaultRouter.addCacheListener();\n return defaultRouter;\n};\n","/*\n Copyright 2019 Google LLC\n\n Use of this source code is governed by an MIT-style\n license that can be found in the LICENSE file or at\n https://opensource.org/licenses/MIT.\n*/\n\nimport type { HTTPMethod } from \"../constants.js\";\nimport type { Route } from \"../Route.js\";\nimport type { RouteHandler, RouteMatchCallback } from \"../types.js\";\nimport type { Router } from \"./Router.js\";\nimport { getSingletonRouter } from \"./singletonRouter.js\";\nimport type { unregisterRoute } from \"./unregisterRoute.js\";\n\n/**\n * Registers a `RegExp`, string, or function with a caching\n * strategy to a singleton {@linkcode Router} instance.\n *\n * @param capture If the capture param is a {@linkcode Route}, all other arguments will be ignored.\n * @param handler A callback function that returns a promise resulting in a response.\n * This parameter is required if `capture` is not a {@linkcode Route} object.\n * @param method The HTTP method to match the route against. Defaults to `'GET'`.\n * @returns The generated {@linkcode Route} object, which can then be provided to {@linkcode unregisterRoute} if needed.\n * @deprecated\n */\nexport const registerRoute = (capture: RegExp | string | RouteMatchCallback | Route, handler?: RouteHandler, method?: HTTPMethod): Route => {\n return getSingletonRouter().registerCapture(capture, handler, method);\n};\n","/*\n Copyright 2019 Google LLC\n Use of this source code is governed by an MIT-style\n license that can be found in the LICENSE file or at\n https://opensource.org/licenses/MIT.\n*/\n\nimport type { PrecacheRouteOptions } from \"../types.js\";\nimport { PrecacheRoute } from \"./PrecacheRoute.js\";\nimport { registerRoute } from \"./registerRoute.js\";\nimport { getSingletonPrecacheController } from \"./singletonPrecacheController.js\";\n\n/**\n * Add a `fetch` listener that will\n * respond to [network requests](https://developer.mozilla.org/en-US/docs/Web/API/Service_Worker_API/Using_Service_Workers#Custom_responses_to_requests)\n * with precached assets to the service worker.\n *\n * As for requests for assets that aren't precached, the `fetch` event will not be\n * responded to, allowing the event to fall through to other `fetch` event listeners.\n *\n * @param options See {@linkcode PrecacheRouteOptions}.\n * @deprecated\n */\nexport const addRoute = (options?: PrecacheRouteOptions): void => {\n const precacheRoute = new PrecacheRoute(getSingletonPrecacheController(), options);\n registerRoute(precacheRoute);\n};\n","/*\n Copyright 2019 Google LLC\n\n Use of this source code is governed by an MIT-style\n license that can be found in the LICENSE file or at\n https://opensource.org/licenses/MIT.\n*/\n\nimport type { RouteHandlerCallback } from \"../types.js\";\nimport type { PrecacheController } from \"./PrecacheController.js\";\nimport { getSingletonPrecacheController } from \"./singletonPrecacheController.js\";\n\n/**\n * Helper function that calls {@linkcode PrecacheController.createHandlerBoundToURL}\n * on the default {@linkcode PrecacheController} instance.\n *\n * If you are creating your own {@linkcode PrecacheController}, then call the\n * {@linkcode PrecacheController.createHandlerBoundToURL} function on that instance\n * instead of using this function.\n *\n * @param url The precached URL which will be used to look up the response.\n * @param fallbackToNetwork Whether to attempt to get the\n * response from the network if there's a precache miss.\n * @return\n * @deprecated\n */\nexport const createHandlerBoundToURL = (url: string): RouteHandlerCallback => {\n const precacheController = getSingletonPrecacheController();\n return precacheController.createHandlerBoundToURL(url);\n};\n","/*\n Copyright 2020 Google LLC\n\n Use of this source code is governed by an MIT-style\n license that can be found in the LICENSE file or at\n https://opensource.org/licenses/MIT.\n*/\n\nimport type { HandlerDidErrorCallbackParam, SerwistPlugin } from \"../types.js\";\nimport type { PrecacheController } from \"./PrecacheController.js\";\nimport { getSingletonPrecacheController } from \"./singletonPrecacheController.js\";\n\n/**\n * @deprecated\n */\nexport interface PrecacheFallbackEntry {\n /**\n * A function that checks whether the fallback entry can be used\n * for a request.\n */\n matcher: (param: HandlerDidErrorCallbackParam) => boolean;\n /**\n * A precached URL to be used as a fallback.\n */\n url: string;\n}\n\n/**\n * @deprecated\n */\nexport interface PrecacheFallbackPluginOptions {\n /**\n * Precached URLs to be used as the fallback\n * if the associated strategy can't generate a response.\n */\n fallbackUrls: (string | PrecacheFallbackEntry)[];\n /**\n * An optional {@linkcode PrecacheController} instance. If not provided,\n * the default {@linkcode PrecacheController} will be used.\n */\n precacheController?: PrecacheController;\n}\n\n/**\n * A class that allows you to specify offline fallbacks\n * to be used when a given strategy is unable to generate a response.\n *\n * It does this by intercepting the `handlerDidError` plugin callback\n * and returning a precached response, taking the expected revision parameter\n * into account automatically.\n *\n * Unless you explicitly pass in a {@linkcode PrecacheController} instance to the\n * constructor, the default instance will be used.\n *\n * @deprecated\n */\nexport class PrecacheFallbackPlugin implements SerwistPlugin {\n private readonly _fallbackUrls: (string | PrecacheFallbackEntry)[];\n private readonly _precacheController: PrecacheController;\n\n /**\n * Constructs a new instance with the associated `fallbackUrls`.\n *\n * @param config\n */\n constructor({ fallbackUrls, precacheController }: PrecacheFallbackPluginOptions) {\n this._fallbackUrls = fallbackUrls;\n this._precacheController = precacheController || getSingletonPrecacheController();\n }\n\n /**\n * @returns The precache response for one of the fallback URLs, or `undefined` if\n * nothing satisfies the conditions.\n * @private\n */\n async handlerDidError(param: HandlerDidErrorCallbackParam) {\n for (const fallback of this._fallbackUrls) {\n if (typeof fallback === \"string\") {\n const fallbackResponse = await this._precacheController.matchPrecache(fallback);\n if (fallbackResponse !== undefined) {\n return fallbackResponse;\n }\n } else if (fallback.matcher(param)) {\n const fallbackResponse = await this._precacheController.matchPrecache(fallback.url);\n if (fallbackResponse !== undefined) {\n return fallbackResponse;\n }\n }\n }\n return undefined;\n }\n}\n","import { Strategy } from \"../lib/strategies/Strategy.js\";\nimport type { PrecacheRouteOptions, RuntimeCaching } from \"../types.js\";\nimport type { PrecacheController } from \"./PrecacheController.js\";\nimport type { PrecacheFallbackEntry } from \"./PrecacheFallbackPlugin.js\";\nimport { PrecacheFallbackPlugin } from \"./PrecacheFallbackPlugin.js\";\nimport { PrecacheRoute } from \"./PrecacheRoute.js\";\nimport type { Router } from \"./Router.js\";\nimport { getSingletonPrecacheController } from \"./singletonPrecacheController.js\";\nimport { getSingletonRouter } from \"./singletonRouter.js\";\n\nexport interface FallbackEntry extends PrecacheFallbackEntry {\n /**\n * The revision used for precaching.\n */\n revision: string;\n}\n\nexport interface FallbacksOptions {\n /**\n * A list of fallback entries.\n */\n entries: FallbackEntry[];\n /**\n * Precache options that will be used for your\n * fallback entries.\n */\n precacheOptions?: PrecacheRouteOptions;\n}\n\nexport interface FallbacksOptions {\n /**\n * An optional {@linkcode PrecacheController} instance. If not provided, the singleton\n * {@linkcode PrecacheController} will be used.\n */\n precacheController?: PrecacheController;\n /**\n * An optional {@linkcode Router} instance. If not provided, the singleton {@linkcode Router}\n * will be used.\n */\n router?: Router;\n /**\n * Your previous list of caching strategies.\n */\n runtimeCaching: RuntimeCaching[];\n /**\n * A list of fallback entries.\n */\n entries: FallbackEntry[];\n /**\n * Precache options that will be used for your\n * fallback entries.\n */\n precacheOptions?: PrecacheRouteOptions;\n}\n\n/**\n * Precaches routes so that they can be used as a fallback when\n * a Strategy fails to generate a response.\n *\n * Note: This function mutates `runtimeCaching`. It also precaches the URLs\n * defined in `entries`, so you must NOT precache any of them beforehand.\n *\n * @param options\n * @returns The modified `runtimeCaching` array.\n * @deprecated\n */\nexport const fallbacks = ({\n precacheController = getSingletonPrecacheController(),\n router = getSingletonRouter(),\n runtimeCaching,\n entries,\n precacheOptions,\n}: FallbacksOptions): RuntimeCaching[] => {\n precacheController.precache(entries);\n router.registerRoute(new PrecacheRoute(precacheController, precacheOptions));\n\n const fallbackPlugin = new PrecacheFallbackPlugin({\n fallbackUrls: entries,\n });\n\n runtimeCaching.forEach((cacheEntry) => {\n if (\n cacheEntry.handler instanceof Strategy &&\n // PrecacheFallbackPlugin also has `handlerDidError`, so we don't need to check for its instances.\n !cacheEntry.handler.plugins.some((plugin) => \"handlerDidError\" in plugin)\n ) {\n cacheEntry.handler.plugins.push(fallbackPlugin);\n }\n });\n\n return runtimeCaching;\n};\n","/*\n Copyright 2019 Google LLC\n\n Use of this source code is governed by an MIT-style\n license that can be found in the LICENSE file or at\n https://opensource.org/licenses/MIT.\n*/\n\nimport { getSingletonPrecacheController } from \"./singletonPrecacheController.js\";\n\n/**\n * Takes in a URL, and returns the corresponding URL that could be used to\n * lookup the entry in the precache.\n *\n * If a relative URL is provided, the location of the service worker file will\n * be used as the base.\n *\n * For precached entries without revision information, the cache key will be the\n * same as the original URL.\n *\n * For precached entries with revision information, the cache key will be the\n * original URL with the addition of a query parameter used for keeping track of\n * the revision info.\n *\n * @param url The URL whose cache key to look up.\n * @returns The cache key that corresponds to that URL.\n * @deprecated\n */\nexport const getCacheKeyForURL = (url: string): string | undefined => {\n const precacheController = getSingletonPrecacheController();\n return precacheController.getCacheKeyForURL(url);\n};\n","import { NavigationRoute } from \"../NavigationRoute.js\";\nimport type { PrecacheEntry, PrecacheRouteOptions } from \"../types.js\";\nimport { cleanupOutdatedCaches as cleanupOutdatedCachesImpl } from \"../utils/cleanupOutdatedCaches.js\";\nimport { createHandlerBoundToURL } from \"./createHandlerBoundToURL.js\";\nimport type { PrecacheController } from \"./PrecacheController.js\";\nimport { PrecacheRoute } from \"./PrecacheRoute.js\";\nimport type { Router } from \"./Router.js\";\nimport { getSingletonPrecacheController } from \"./singletonPrecacheController.js\";\nimport { getSingletonRouter } from \"./singletonRouter.js\";\n\n/**\n * @deprecated\n */\nexport interface HandlePrecachingOptions {\n /**\n * An optional {@linkcode PrecacheController} instance. If not provided, the singleton\n * {@linkcode PrecacheController} will be used.\n */\n precacheController?: PrecacheController;\n /**\n * An optional {@linkcode Router} instance. If not provided, the singleton {@linkcode Router}\n * will be used.\n */\n router?: Router;\n /**\n * A list of URLs that should be cached.\n */\n precacheEntries?: (PrecacheEntry | string)[];\n /**\n * Options to customize how Serwist precaches the URLs.\n */\n pr