serwist
Version:
A Swiss Army knife for service workers.
1,162 lines (1,161 loc) • 41.9 kB
JavaScript
import { d as finalAssertExports, f as SerwistError, l as logger, m as cacheNames, n as clientsClaim, r as cleanupOutdatedCaches, t as waitUntil, u as getFriendlyURL } from "./chunks/waitUntil-BHDx3Rgo.js";
import { E as disableDevLogs, S as BackgroundSyncPlugin, _ as NetworkFirst, a as createCacheKey, c as generateURLVariations, f as NavigationRoute, g as NetworkOnly, h as normalizeHandler, i as PrecacheInstallReportPlugin, m as Route, n as printCleanupDetails, o as setCacheNameDetails, p as PrecacheStrategy, r as parseRoute, t as printInstallDetails, u as enableNavigationPreload, y as Strategy } from "./chunks/printInstallDetails-c9A08ZVZ.js";
import { parallel } from "@serwist/utils";
//#region src/legacy/utils/PrecacheCacheKeyPlugin.ts
/**
* A plugin, designed to be used with PrecacheController, to translate URLs into
* the corresponding cache key, based on the current revision info.
*
* @private
*/
var PrecacheCacheKeyPlugin = class {
_precacheController;
constructor({ precacheController }) {
this._precacheController = precacheController;
}
cacheKeyWillBeUsed = async ({ request, params }) => {
const cacheKey = params?.cacheKey || this._precacheController.getCacheKeyForURL(request.url);
return cacheKey ? new Request(cacheKey, { headers: request.headers }) : request;
};
};
//#endregion
//#region src/legacy/PrecacheController.ts
/**
* Performs efficient precaching of assets.
* @deprecated
*/
var PrecacheController = class {
_installAndActiveListenersAdded;
_concurrentPrecaching;
_strategy;
_urlsToCacheKeys = /* @__PURE__ */ new Map();
_urlsToCacheModes = /* @__PURE__ */ new Map();
_cacheKeysToIntegrities = /* @__PURE__ */ new Map();
/**
* Create a new PrecacheController.
*
* @param options
*/
constructor({ cacheName, plugins = [], fallbackToNetwork = true, concurrentPrecaching = 1 } = {}) {
this._concurrentPrecaching = concurrentPrecaching;
this._strategy = new PrecacheStrategy({
cacheName: cacheNames.getPrecacheName(cacheName),
plugins: [...plugins, new PrecacheCacheKeyPlugin({ precacheController: this })],
fallbackToNetwork
});
this.install = this.install.bind(this);
this.activate = this.activate.bind(this);
}
/**
* The strategy created by this controller and
* used to cache assets and respond to `fetch` events.
*/
get strategy() {
return this._strategy;
}
/**
* Adds items to the precache list, removing any duplicates and
* stores the files in the precache cache when the service
* worker installs.
*
* This method can be called multiple times.
*
* @param entries Array of entries to precache.
*/
precache(entries) {
this.addToCacheList(entries);
if (!this._installAndActiveListenersAdded) {
self.addEventListener("install", this.install);
self.addEventListener("activate", this.activate);
this._installAndActiveListenersAdded = true;
}
}
/**
* This method will add items to the precache list, removing duplicates
* and ensuring the information is valid.
*
* @param entries Array of entries to precache.
*/
addToCacheList(entries) {
if (process.env.NODE_ENV !== "production") finalAssertExports.isArray(entries, {
moduleName: "serwist/legacy",
className: "PrecacheController",
funcName: "addToCacheList",
paramName: "entries"
});
const urlsToWarnAbout = [];
for (const entry of entries) {
if (typeof entry === "string") urlsToWarnAbout.push(entry);
else if (entry && !entry.integrity && entry.revision === void 0) urlsToWarnAbout.push(entry.url);
const { cacheKey, url } = createCacheKey(entry);
const cacheMode = typeof entry !== "string" && entry.revision ? "reload" : "default";
if (this._urlsToCacheKeys.has(url) && this._urlsToCacheKeys.get(url) !== cacheKey) throw new SerwistError("add-to-cache-list-conflicting-entries", {
firstEntry: this._urlsToCacheKeys.get(url),
secondEntry: cacheKey
});
if (typeof entry !== "string" && entry.integrity) {
if (this._cacheKeysToIntegrities.has(cacheKey) && this._cacheKeysToIntegrities.get(cacheKey) !== entry.integrity) throw new SerwistError("add-to-cache-list-conflicting-integrities", { url });
this._cacheKeysToIntegrities.set(cacheKey, entry.integrity);
}
this._urlsToCacheKeys.set(url, cacheKey);
this._urlsToCacheModes.set(url, cacheMode);
if (urlsToWarnAbout.length > 0) {
const warningMessage = `Serwist is precaching URLs without revision info: ${urlsToWarnAbout.join(", ")}\nThis is generally NOT safe. Learn more at https://bit.ly/wb-precache`;
if (process.env.NODE_ENV === "production") console.warn(warningMessage);
else logger.warn(warningMessage);
}
}
}
/**
* Precaches new and updated assets. Call this method from the service worker
* install event.
*
* Note: this method calls `event.waitUntil()` for you, so you do not need
* to call it yourself in your event handlers.
*
* @param event
* @returns
*/
install(event) {
return waitUntil(event, async () => {
const installReportPlugin = new PrecacheInstallReportPlugin();
this.strategy.plugins.push(installReportPlugin);
await parallel(this._concurrentPrecaching, Array.from(this._urlsToCacheKeys.entries()), async ([url, cacheKey]) => {
const integrity = this._cacheKeysToIntegrities.get(cacheKey);
const cacheMode = this._urlsToCacheModes.get(url);
const request = new Request(url, {
integrity,
cache: cacheMode,
credentials: "same-origin"
});
await Promise.all(this.strategy.handleAll({
event,
request,
url: new URL(request.url),
params: { cacheKey }
}));
});
const { updatedURLs, notUpdatedURLs } = installReportPlugin;
if (process.env.NODE_ENV !== "production") printInstallDetails(updatedURLs, notUpdatedURLs);
return {
updatedURLs,
notUpdatedURLs
};
});
}
/**
* Deletes assets that are no longer present in the current precache manifest.
* Call this method from the service worker activate event.
*
* Note: this method calls `event.waitUntil()` for you, so you do not need
* to call it yourself in your event handlers.
*
* @param event
* @returns
*/
activate(event) {
return waitUntil(event, async () => {
const cache = await self.caches.open(this.strategy.cacheName);
const currentlyCachedRequests = await cache.keys();
const expectedCacheKeys = new Set(this._urlsToCacheKeys.values());
const deletedCacheRequests = [];
for (const request of currentlyCachedRequests) if (!expectedCacheKeys.has(request.url)) {
await cache.delete(request);
deletedCacheRequests.push(request.url);
}
if (process.env.NODE_ENV !== "production") printCleanupDetails(deletedCacheRequests);
return { deletedCacheRequests };
});
}
/**
* Returns a mapping of a precached URL to the corresponding cache key, taking
* into account the revision information for the URL.
*
* @returns A URL to cache key mapping.
*/
getURLsToCacheKeys() {
return this._urlsToCacheKeys;
}
/**
* Returns a list of all the URLs that have been precached by the current
* service worker.
*
* @returns The precached URLs.
*/
getCachedURLs() {
return [...this._urlsToCacheKeys.keys()];
}
/**
* Returns the cache key used for storing a given URL. If that URL is
* unversioned, like `/index.html', then the cache key will be the original
* URL with a search parameter appended to it.
*
* @param url A URL whose cache key you want to look up.
* @returns The versioned URL that corresponds to a cache key
* for the original URL, or undefined if that URL isn't precached.
*/
getCacheKeyForURL(url) {
const urlObject = new URL(url, location.href);
return this._urlsToCacheKeys.get(urlObject.href);
}
/**
* @param url A cache key whose SRI you want to look up.
* @returns The subresource integrity associated with the cache key,
* or undefined if it's not set.
*/
getIntegrityForCacheKey(cacheKey) {
return this._cacheKeysToIntegrities.get(cacheKey);
}
/**
* This acts as a drop-in replacement for
* [`cache.match()`](https://developer.mozilla.org/en-US/docs/Web/API/Cache/match)
* with the following differences:
*
* - It knows what the name of the precache is, and only checks in that cache.
* - It allows you to pass in an "original" URL without versioning parameters,
* and it will automatically look up the correct cache key for the currently
* active revision of that URL.
*
* E.g., `matchPrecache('index.html')` will find the correct precached
* response for the currently active service worker, even if the actual cache
* key is `'/index.html?__WB_REVISION__=1234abcd'`.
*
* @param request The key (without revisioning parameters)
* to look up in the precache.
* @returns
*/
async matchPrecache(request) {
const url = request instanceof Request ? request.url : request;
const cacheKey = this.getCacheKeyForURL(url);
if (cacheKey) return (await self.caches.open(this.strategy.cacheName)).match(cacheKey);
}
/**
* Returns a function that looks up `url` in the precache (taking into
* account revision information), and returns the corresponding `Response`.
*
* @param url The precached URL which will be used to lookup the response.
* @return
*/
createHandlerBoundToURL(url) {
const cacheKey = this.getCacheKeyForURL(url);
if (!cacheKey) throw new SerwistError("non-precached-url", { url });
return (options) => {
options.request = new Request(url);
options.params = {
cacheKey,
...options.params
};
return this.strategy.handle(options);
};
}
};
//#endregion
//#region src/legacy/singletonPrecacheController.ts
let defaultPrecacheController;
/**
* Creates a new, singleton {@linkcode PrecacheController} if one does not exist. If one does
* already exist, that instance is returned. This instance is used by Serwist's
* {@linkcode PrecacheController}-dependent functions and classes unless you provide a different
* {@linkcode PrecacheController} to them.
*
* @returns The singleton {@linkcode PrecacheController}.
* @deprecated
*/
const getSingletonPrecacheController = () => {
if (!defaultPrecacheController) defaultPrecacheController = new PrecacheController();
return defaultPrecacheController;
};
/**
* Changes the singleton {@linkcode PrecacheController} to a different instance. This is meant for when you do not
* want to pass your own {@linkcode PrecacheController} to every one of Serwist's {@linkcode PrecacheController}-dependent
* functions and classes.
*
* It is highly recommended that you call this before anything else, if you plan on doing so.
*
* @example
* ```js
* import { PrecacheController, setSingletonPrecacheController } from "serwist/legacy";
*
* const controller = new PrecacheController();
*
* setSingletonPrecacheController(controller);
*
* // Do something with your controller...
* ```
* @param router
* @returns The new singleton {@linkcode PrecacheController}.
* @deprecated
*/
const setSingletonPrecacheController = (precacheController) => {
defaultPrecacheController = precacheController;
return defaultPrecacheController;
};
//#endregion
//#region src/legacy/addPlugins.ts
/**
* Adds plugins to the precaching strategy.
*
* @param plugins
* @deprecated
*/
const addPlugins = (plugins) => {
getSingletonPrecacheController().strategy.plugins.push(...plugins);
};
//#endregion
//#region src/legacy/PrecacheRoute.ts
/**
* A subclass of {@linkcode Route} that takes a {@linkcode PrecacheController}
* instance and uses it to match incoming requests and handle fetching
* responses from the precache.
* @deprecated
*/
var PrecacheRoute = class extends Route {
/**
* @param precacheController A {@linkcode PrecacheController}
* instance used to both match requests and respond to `fetch` events.
* @param options Options to control how requests are matched
* against the list of precached URLs.
*/
constructor(precacheController, options) {
const match = ({ request }) => {
const urlsToCacheKeys = precacheController.getURLsToCacheKeys();
for (const possibleURL of generateURLVariations(request.url, options)) {
const cacheKey = urlsToCacheKeys.get(possibleURL);
if (cacheKey) return {
cacheKey,
integrity: precacheController.getIntegrityForCacheKey(cacheKey)
};
}
if (process.env.NODE_ENV !== "production") logger.debug(`Precaching did not find a match for ${getFriendlyURL(request.url)}`);
};
super(match, precacheController.strategy);
}
};
//#endregion
//#region src/legacy/Router.ts
/**
* A class that can be used to process a `fetch` event using one or more route(s), responding with a response
* if a matching route exists.
*
* If no route matches given a request, the router will use the default handler if one is defined.
*
* Should the matching route throw an error, the router will use the catch handler if one is defined to
* gracefully deal with issues and respond with a response.
*
* If a request matches multiple routes, the earliest registered route will be used to respond to the it.
* @deprecated
*/
var Router = class {
_routes;
_defaultHandlerMap;
_fetchListenerHandler = null;
_cacheListenerHandler = null;
_catchHandler;
/**
* Initializes a new Router.
*/
constructor() {
this._routes = /* @__PURE__ */ new Map();
this._defaultHandlerMap = /* @__PURE__ */ new Map();
}
/**
* @returns routes A `Map` of HTTP method name (`'GET'`, etc.) to an array of all
* the corresponding {@linkcode Route} instances that are registered.
*/
get routes() {
return this._routes;
}
/**
* Adds a `fetch` event listener to respond to events when a route matches
* the event's request. Effectively no-op if `addFetchListener` has been
* called, but `removeFetchListener` has not.
*/
addFetchListener() {
if (!this._fetchListenerHandler) {
this._fetchListenerHandler = (event) => {
const { request } = event;
const responsePromise = this.handleRequest({
request,
event
});
if (responsePromise) event.respondWith(responsePromise);
};
self.addEventListener("fetch", this._fetchListenerHandler);
}
}
/**
* Removes `fetch` event listener added by `addFetchListener`.
* Effectively no-op if either `addFetchListener` has not been called or,
* if it has, so has `removeFetchListener`.
*/
removeFetchListener() {
if (this._fetchListenerHandler) {
self.removeEventListener("fetch", this._fetchListenerHandler);
this._fetchListenerHandler = null;
}
}
/**
* Adds a `message` event listener for URLs to cache from the window.
* This is useful to cache resources loaded on the page prior to when the
* service worker started controlling it. Effectively no-op if `addCacheListener`
* has been called, but `removeCacheListener` hasn't.
*
* The format of the message data sent from the window should be as follows.
* Where the `urlsToCache` array may consist of URL strings or an array of
* URL string + `requestInit` object (the same as you'd pass to `fetch()`).
*
* ```
* {
* type: 'CACHE_URLS',
* payload: {
* urlsToCache: [
* './script1.js',
* './script2.js',
* ['./script3.js', {mode: 'no-cors'}],
* ],
* },
* }
* ```
*/
addCacheListener() {
if (!this._cacheListenerHandler) {
this._cacheListenerHandler = (event) => {
if (event.data && event.data.type === "CACHE_URLS") {
const { payload } = event.data;
if (process.env.NODE_ENV !== "production") logger.debug("Caching URLs from the window", payload.urlsToCache);
const requestPromises = Promise.all(payload.urlsToCache.map((entry) => {
if (typeof entry === "string") entry = [entry];
const request = new Request(...entry);
return this.handleRequest({
request,
event
});
}));
event.waitUntil(requestPromises);
if (event.ports?.[0]) requestPromises.then(() => event.ports[0].postMessage(true));
}
};
self.addEventListener("message", this._cacheListenerHandler);
}
}
/**
* Removes the `message` event listener added by `addCacheListener`.
* Effectively no-op if either `addCacheListener` has not been called or,
* if it has, so has `removeCacheListener`.
*/
removeCacheListener() {
if (this._cacheListenerHandler) self.removeEventListener("message", this._cacheListenerHandler);
}
/**
* Apply the routing rules to a `fetch` event to get a response from an
* appropriate route.
*
* @param options
* @returns A promise is returned if a registered route can handle the request.
* If there is no matching route and there's no `defaultHandler`, `undefined`
* is returned.
*/
handleRequest({ request, event }) {
if (process.env.NODE_ENV !== "production") finalAssertExports.isInstance(request, Request, {
moduleName: "serwist/legacy",
className: "Router",
funcName: "handleRequest",
paramName: "options.request"
});
const url = new URL(request.url, location.href);
if (!url.protocol.startsWith("http")) {
if (process.env.NODE_ENV !== "production") logger.debug("Router only supports URLs that start with 'http'.");
return;
}
const sameOrigin = url.origin === location.origin;
const { params, route } = this.findMatchingRoute({
event,
request,
sameOrigin,
url
});
let handler = route?.handler;
const debugMessages = [];
if (process.env.NODE_ENV !== "production") {
if (handler) {
debugMessages.push(["Found a route to handle this request:", route]);
if (params) debugMessages.push([`Passing the following params to the route's handler:`, params]);
}
}
const method = request.method;
if (!handler && this._defaultHandlerMap.has(method)) {
if (process.env.NODE_ENV !== "production") debugMessages.push(`Failed to find a matching route. Falling back to the default handler for ${method}.`);
handler = this._defaultHandlerMap.get(method);
}
if (!handler) {
if (process.env.NODE_ENV !== "production") logger.debug(`No route found for: ${getFriendlyURL(url)}`);
return;
}
if (process.env.NODE_ENV !== "production") {
logger.groupCollapsed(`Router is responding to: ${getFriendlyURL(url)}`);
for (const msg of debugMessages) if (Array.isArray(msg)) logger.log(...msg);
else logger.log(msg);
logger.groupEnd();
}
let responsePromise;
try {
responsePromise = handler.handle({
url,
request,
event,
params
});
} catch (err) {
responsePromise = Promise.reject(err);
}
const catchHandler = route?.catchHandler;
if (responsePromise instanceof Promise && (this._catchHandler || catchHandler)) responsePromise = responsePromise.catch(async (err) => {
if (catchHandler) {
if (process.env.NODE_ENV !== "production") {
logger.groupCollapsed(`Error thrown when responding to: ${getFriendlyURL(url)}. Falling back to route's Catch Handler.`);
logger.error("Error thrown by:", route);
logger.error(err);
logger.groupEnd();
}
try {
return await catchHandler.handle({
url,
request,
event,
params
});
} catch (catchErr) {
if (catchErr instanceof Error) err = catchErr;
}
}
if (this._catchHandler) {
if (process.env.NODE_ENV !== "production") {
logger.groupCollapsed(`Error thrown when responding to: ${getFriendlyURL(url)}. Falling back to global Catch Handler.`);
logger.error("Error thrown by:", route);
logger.error(err);
logger.groupEnd();
}
return this._catchHandler.handle({
url,
request,
event
});
}
throw err;
});
return responsePromise;
}
/**
* Checks a request and URL (and optionally an event) against the list of
* registered routes, and if there's a match, returns the corresponding
* route along with any params generated by the match.
*
* @param options
* @returns An object with `route` and `params` properties. They are populated
* if a matching route was found or `undefined` otherwise.
*/
findMatchingRoute({ url, sameOrigin, request, event }) {
const routes = this._routes.get(request.method) || [];
for (const route of routes) {
let params;
const matchResult = route.match({
url,
sameOrigin,
request,
event
});
if (matchResult) {
if (process.env.NODE_ENV !== "production") {
if (matchResult instanceof Promise) logger.warn(`While routing ${getFriendlyURL(url)}, an async matchCallback function was used. Please convert the following route to use a synchronous matchCallback function:`, route);
}
params = matchResult;
if (Array.isArray(params) && params.length === 0) params = void 0;
else if (matchResult.constructor === Object && Object.keys(matchResult).length === 0) params = void 0;
else if (typeof matchResult === "boolean") params = void 0;
return {
route,
params
};
}
}
return {};
}
/**
* Define a default handler that's called when no routes explicitly
* match the incoming request.
*
* Each HTTP method (`'GET'`, `'POST'`, etc.) gets its own default handler.
*
* Without a default handler, unmatched requests will go against the
* network as if there were no service worker present.
*
* @param handler A callback function that returns a promise resulting in a response.
* @param method The HTTP method to associate with this default handler. Each method
* has its own default. Defaults to `'GET'`.
*/
setDefaultHandler(handler, method = "GET") {
this._defaultHandlerMap.set(method, normalizeHandler(handler));
}
/**
* If a `Route` throws an error while handling a request, this `handler`
* will be called and given a chance to provide a response.
*
* @param handler A callback function that returns a Promise resulting
* in a Response.
*/
setCatchHandler(handler) {
this._catchHandler = normalizeHandler(handler);
}
/**
* Registers a `RegExp`, string, or function with a caching
* strategy to the router.
*
* @param capture If the capture param is a {@linkcode Route} object, all other arguments will be ignored.
* @param handler A callback function that returns a promise resulting in a response.
* This parameter is required if `capture` is not a {@linkcode Route} object.
* @param method The HTTP method to match the route against. Defaults to `'GET'`.
* @returns The generated {@linkcode Route} object.
*/
registerCapture(capture, handler, method) {
const route = parseRoute(capture, handler, method);
this.registerRoute(route);
return route;
}
/**
* Registers a route with the router.
*
* @param route The route to register.
*/
registerRoute(route) {
if (process.env.NODE_ENV !== "production") {
finalAssertExports.isType(route, "object", {
moduleName: "serwist/legacy",
className: "Router",
funcName: "registerRoute",
paramName: "route"
});
finalAssertExports.hasMethod(route, "match", {
moduleName: "serwist/legacy",
className: "Router",
funcName: "registerRoute",
paramName: "route"
});
finalAssertExports.isType(route.handler, "object", {
moduleName: "serwist/legacy",
className: "Router",
funcName: "registerRoute",
paramName: "route"
});
finalAssertExports.hasMethod(route.handler, "handle", {
moduleName: "serwist/legacy",
className: "Router",
funcName: "registerRoute",
paramName: "route.handler"
});
finalAssertExports.isType(route.method, "string", {
moduleName: "serwist/legacy",
className: "Router",
funcName: "registerRoute",
paramName: "route.method"
});
}
if (!this._routes.has(route.method)) this._routes.set(route.method, []);
this._routes.get(route.method).push(route);
}
/**
* Unregisters a route from the router.
*
* @param route The route to unregister.
*/
unregisterRoute(route) {
if (!this._routes.has(route.method)) throw new SerwistError("unregister-route-but-not-found-with-method", { method: route.method });
const routeIndex = this._routes.get(route.method).indexOf(route);
if (routeIndex > -1) this._routes.get(route.method).splice(routeIndex, 1);
else throw new SerwistError("unregister-route-route-not-registered");
}
};
//#endregion
//#region src/legacy/singletonRouter.ts
let defaultRouter;
/**
* Creates a new, singleton {@linkcode Router} if one does not exist. If one does
* already exist, that instance is returned. This instance is used by
* Serwist's {@linkcode Router}-dependent functions and classes unless you provide
* a different {@linkcode Router} to them.
*
* @returns The singleton {@linkcode Router}.
* @deprecated
*/
const getSingletonRouter = () => {
if (!defaultRouter) {
defaultRouter = new Router();
defaultRouter.addFetchListener();
defaultRouter.addCacheListener();
}
return defaultRouter;
};
/**
* Changes the singleton {@linkcode Router} to a different instance. This is meant for when you do not
* want to pass your own {@linkcode Router} to every one of Serwist's {@linkcode Router}-dependent functions and classes.
* If this or {@linkcode getSingletonRouter} has been called before, it removes the listeners of the
* previous singleton {@linkcode Router}. It also adds those of the new one, so you need not do that yourself.
*
* It is highly recommended that you call this before anything else, if you plan on doing so.
*
* @example
* ```js
* import { Router, setSingletonRouter } from "serwist/legacy";
*
* const router = new Router();
*
* setSingletonRouter(router);
*
* router.registerRoute(
* new Route(
* /\/api\/.*\/*.json/,
* new NetworkOnly(),
* "POST",
* ),
* );
* ```
* @param router
* @returns The new singleton {@linkcode Router}.
* @deprecated
*/
const setSingletonRouter = (router) => {
if (defaultRouter) {
defaultRouter.removeFetchListener();
defaultRouter.removeCacheListener();
}
defaultRouter = router;
defaultRouter.addFetchListener();
defaultRouter.addCacheListener();
return defaultRouter;
};
//#endregion
//#region src/legacy/registerRoute.ts
/**
* Registers a `RegExp`, string, or function with a caching
* strategy to a singleton {@linkcode Router} instance.
*
* @param capture If the capture param is a {@linkcode Route}, all other arguments will be ignored.
* @param handler A callback function that returns a promise resulting in a response.
* This parameter is required if `capture` is not a {@linkcode Route} object.
* @param method The HTTP method to match the route against. Defaults to `'GET'`.
* @returns The generated {@linkcode Route} object, which can then be provided to {@linkcode unregisterRoute} if needed.
* @deprecated
*/
const registerRoute = (capture, handler, method) => {
return getSingletonRouter().registerCapture(capture, handler, method);
};
//#endregion
//#region src/legacy/addRoute.ts
/**
* Add a `fetch` listener that will
* respond to [network requests](https://developer.mozilla.org/en-US/docs/Web/API/Service_Worker_API/Using_Service_Workers#Custom_responses_to_requests)
* with precached assets to the service worker.
*
* As for requests for assets that aren't precached, the `fetch` event will not be
* responded to, allowing the event to fall through to other `fetch` event listeners.
*
* @param options See {@linkcode PrecacheRouteOptions}.
* @deprecated
*/
const addRoute = (options) => {
registerRoute(new PrecacheRoute(getSingletonPrecacheController(), options));
};
//#endregion
//#region src/legacy/createHandlerBoundToURL.ts
/**
* Helper function that calls {@linkcode PrecacheController.createHandlerBoundToURL}
* on the default {@linkcode PrecacheController} instance.
*
* If you are creating your own {@linkcode PrecacheController}, then call the
* {@linkcode PrecacheController.createHandlerBoundToURL} function on that instance
* instead of using this function.
*
* @param url The precached URL which will be used to look up the response.
* @param fallbackToNetwork Whether to attempt to get the
* response from the network if there's a precache miss.
* @return
* @deprecated
*/
const createHandlerBoundToURL = (url) => {
return getSingletonPrecacheController().createHandlerBoundToURL(url);
};
//#endregion
//#region src/legacy/PrecacheFallbackPlugin.ts
/**
* A class that allows you to specify offline fallbacks
* to be used when a given strategy is unable to generate a response.
*
* It does this by intercepting the `handlerDidError` plugin callback
* and returning a precached response, taking the expected revision parameter
* into account automatically.
*
* Unless you explicitly pass in a {@linkcode PrecacheController} instance to the
* constructor, the default instance will be used.
*
* @deprecated
*/
var PrecacheFallbackPlugin = class {
_fallbackUrls;
_precacheController;
/**
* Constructs a new instance with the associated `fallbackUrls`.
*
* @param config
*/
constructor({ fallbackUrls, precacheController }) {
this._fallbackUrls = fallbackUrls;
this._precacheController = precacheController || getSingletonPrecacheController();
}
/**
* @returns The precache response for one of the fallback URLs, or `undefined` if
* nothing satisfies the conditions.
* @private
*/
async handlerDidError(param) {
for (const fallback of this._fallbackUrls) if (typeof fallback === "string") {
const fallbackResponse = await this._precacheController.matchPrecache(fallback);
if (fallbackResponse !== void 0) return fallbackResponse;
} else if (fallback.matcher(param)) {
const fallbackResponse = await this._precacheController.matchPrecache(fallback.url);
if (fallbackResponse !== void 0) return fallbackResponse;
}
}
};
//#endregion
//#region src/legacy/fallbacks.ts
/**
* Precaches routes so that they can be used as a fallback when
* a Strategy fails to generate a response.
*
* Note: This function mutates `runtimeCaching`. It also precaches the URLs
* defined in `entries`, so you must NOT precache any of them beforehand.
*
* @param options
* @returns The modified `runtimeCaching` array.
* @deprecated
*/
const fallbacks = ({ precacheController = getSingletonPrecacheController(), router = getSingletonRouter(), runtimeCaching, entries, precacheOptions }) => {
precacheController.precache(entries);
router.registerRoute(new PrecacheRoute(precacheController, precacheOptions));
const fallbackPlugin = new PrecacheFallbackPlugin({ fallbackUrls: entries });
runtimeCaching.forEach((cacheEntry) => {
if (cacheEntry.handler instanceof Strategy && !cacheEntry.handler.plugins.some((plugin) => "handlerDidError" in plugin)) cacheEntry.handler.plugins.push(fallbackPlugin);
});
return runtimeCaching;
};
//#endregion
//#region src/legacy/getCacheKeyForURL.ts
/**
* Takes in a URL, and returns the corresponding URL that could be used to
* lookup the entry in the precache.
*
* If a relative URL is provided, the location of the service worker file will
* be used as the base.
*
* For precached entries without revision information, the cache key will be the
* same as the original URL.
*
* For precached entries with revision information, the cache key will be the
* original URL with the addition of a query parameter used for keeping track of
* the revision info.
*
* @param url The URL whose cache key to look up.
* @returns The cache key that corresponds to that URL.
* @deprecated
*/
const getCacheKeyForURL = (url) => {
return getSingletonPrecacheController().getCacheKeyForURL(url);
};
//#endregion
//#region src/legacy/handlePrecaching.ts
/**
* Handles a list of precache entries and cleans up outdated caches.
*
* @param options
* @deprecated
*/
const handlePrecaching = ({ precacheController = getSingletonPrecacheController(), router = getSingletonRouter(), precacheEntries, precacheOptions, cleanupOutdatedCaches: cleanupOutdatedCaches$1 = false, navigateFallback, navigateFallbackAllowlist, navigateFallbackDenylist }) => {
if (!!precacheEntries && precacheEntries.length > 0) {
precacheController.precache(precacheEntries);
router.registerRoute(new PrecacheRoute(precacheController, precacheOptions));
if (cleanupOutdatedCaches$1) cleanupOutdatedCaches();
if (navigateFallback) router.registerRoute(new NavigationRoute(createHandlerBoundToURL(navigateFallback), {
allowlist: navigateFallbackAllowlist,
denylist: navigateFallbackDenylist
}));
}
};
//#endregion
//#region src/legacy/constants.ts
const QUEUE_NAME = "serwist-google-analytics";
const MAX_RETENTION_TIME = 2880;
const COLLECT_PATHS_REGEX = /^\/(\w+\/)?collect/;
//#endregion
//#region src/legacy/initializeGoogleAnalytics.ts
/**
* Creates the requestWillDequeue callback to be used with the background
* sync plugin. The callback takes the failed request and adds the
* `qt` param based on the current time, as well as applies any other
* user-defined hit modifications.
*
* @param config
* @returns The requestWillDequeue callback function.
* @private
*/
const createOnSyncCallback = (config) => {
return async ({ queue }) => {
let entry;
while (entry = await queue.shiftRequest()) {
const { request, timestamp } = entry;
const url = new URL(request.url);
try {
const params = request.method === "POST" ? new URLSearchParams(await request.clone().text()) : url.searchParams;
const originalHitTime = timestamp - (Number(params.get("qt")) || 0);
const queueTime = Date.now() - originalHitTime;
params.set("qt", String(queueTime));
if (config.parameterOverrides) for (const param of Object.keys(config.parameterOverrides)) {
const value = config.parameterOverrides[param];
params.set(param, value);
}
if (typeof config.hitFilter === "function") config.hitFilter.call(null, params);
await fetch(new Request(url.origin + url.pathname, {
body: params.toString(),
method: "POST",
mode: "cors",
credentials: "omit",
headers: { "Content-Type": "text/plain" }
}));
if (process.env.NODE_ENV !== "production") logger.log(`Request for '${getFriendlyURL(url.href)}' has been replayed`);
} catch (err) {
await queue.unshiftRequest(entry);
if (process.env.NODE_ENV !== "production") logger.log(`Request for '${getFriendlyURL(url.href)}' failed to replay, putting it back in the queue.`);
throw err;
}
}
if (process.env.NODE_ENV !== "production") logger.log("All Google Analytics request successfully replayed; the queue is now empty!");
};
};
/**
* Creates GET and POST routes to catch failed Measurement Protocol hits.
*
* @param bgSyncPlugin
* @returns The created routes.
* @private
*/
const createCollectRoutes = (bgSyncPlugin) => {
const match = ({ url }) => url.hostname === "www.google-analytics.com" && COLLECT_PATHS_REGEX.test(url.pathname);
const handler = new NetworkOnly({ plugins: [bgSyncPlugin] });
return [new Route(match, handler, "GET"), new Route(match, handler, "POST")];
};
/**
* Creates a route with a network first strategy for the analytics.js script.
*
* @param cacheName
* @returns The created route.
* @private
*/
const createAnalyticsJsRoute = (cacheName) => {
const match = ({ url }) => url.hostname === "www.google-analytics.com" && url.pathname === "/analytics.js";
return new Route(match, new NetworkFirst({ cacheName }), "GET");
};
/**
* Creates a route with a network first strategy for the gtag.js script.
*
* @param cacheName
* @returns The created route.
* @private
*/
const createGtagJsRoute = (cacheName) => {
const match = ({ url }) => url.hostname === "www.googletagmanager.com" && url.pathname === "/gtag/js";
return new Route(match, new NetworkFirst({ cacheName }), "GET");
};
/**
* Creates a route with a network first strategy for the gtm.js script.
*
* @param cacheName
* @returns The created route.
* @private
*/
const createGtmJsRoute = (cacheName) => {
const match = ({ url }) => url.hostname === "www.googletagmanager.com" && url.pathname === "/gtm.js";
return new Route(match, new NetworkFirst({ cacheName }), "GET");
};
/**
* Initialize Serwist's offline Google Analytics v3 support.
*
* @param options
* @deprecated Use `serwist.initializeGoogleAnalytics` instead.
*/
const initializeGoogleAnalytics = ({ router = getSingletonRouter(), cacheName, ...options } = {}) => {
const resolvedCacheName = cacheNames.getGoogleAnalyticsName(cacheName);
const bgSyncPlugin = new BackgroundSyncPlugin(QUEUE_NAME, {
maxRetentionTime: MAX_RETENTION_TIME,
onSync: createOnSyncCallback(options)
});
const routes = [
createGtmJsRoute(resolvedCacheName),
createAnalyticsJsRoute(resolvedCacheName),
createGtagJsRoute(resolvedCacheName),
...createCollectRoutes(bgSyncPlugin)
];
for (const route of routes) router.registerRoute(route);
};
//#endregion
//#region src/legacy/registerRuntimeCaching.ts
/**
* Registers caching strategies to a singleton Router instance. It is a simple
* syntatic sugar for {@linkcode registerRoute}.
*
* @param runtimeCachingList
* @returns
* @deprecated
*/
const registerRuntimeCaching = (...runtimeCachingList) => {
for (const entry of runtimeCachingList) registerRoute(entry.matcher, entry.handler, entry.method);
};
//#endregion
//#region src/legacy/installSerwist.ts
/**
* Abstracts away the core APIs of Serwist.
*
* @param options - `installSerwist` options.
* @deprecated
*/
const installSerwist = ({ precacheController = getSingletonPrecacheController(), router = getSingletonRouter(), precacheEntries, precacheOptions, cleanupOutdatedCaches, navigateFallback, navigateFallbackAllowlist, navigateFallbackDenylist, skipWaiting, importScripts, navigationPreload = false, cacheId, clientsClaim: clientsClaim$1 = false, runtimeCaching, offlineAnalyticsConfig, disableDevLogs: disableDevLogs$1 = false, fallbacks: fallbacks$1 }) => {
if (!!importScripts && importScripts.length > 0) self.importScripts(...importScripts);
if (navigationPreload) enableNavigationPreload();
if (cacheId !== void 0) setCacheNameDetails({ prefix: cacheId });
if (skipWaiting) self.skipWaiting();
else self.addEventListener("message", (event) => {
if (event.data && event.data.type === "SKIP_WAITING") self.skipWaiting();
});
if (clientsClaim$1) clientsClaim();
handlePrecaching({
precacheController,
router,
precacheEntries,
precacheOptions,
cleanupOutdatedCaches,
navigateFallback,
navigateFallbackAllowlist,
navigateFallbackDenylist
});
if (runtimeCaching !== void 0) {
if (fallbacks$1 !== void 0) runtimeCaching = fallbacks({
precacheController,
router,
runtimeCaching,
entries: fallbacks$1.entries,
precacheOptions
});
registerRuntimeCaching(...runtimeCaching);
}
if (offlineAnalyticsConfig !== void 0) if (typeof offlineAnalyticsConfig === "boolean") offlineAnalyticsConfig && initializeGoogleAnalytics({ router });
else initializeGoogleAnalytics({
...offlineAnalyticsConfig,
router
});
if (disableDevLogs$1) disableDevLogs();
};
//#endregion
//#region src/legacy/matchPrecache.ts
/**
* Helper function that calls {@linkcode PrecacheController.matchPrecache}
* on the default {@linkcode PrecacheController} instance.
*
* If you are creating your own {@linkcode PrecacheController}, then call
* the {@linkcode PrecacheController.matchPrecache} function on that instance
* instead of using this function.
*
* @param request The key (without revisioning parameters)
* to look up in the precache.
* @returns
* @deprecated
*/
const matchPrecache = (request) => {
return getSingletonPrecacheController().matchPrecache(request);
};
//#endregion
//#region src/legacy/precache.ts
/**
* Adds items to the precache list, removing any duplicates and
* stores the files in the precache cache when the service
* worker installs.
*
* This method can be called multiple times.
*
* Please note: This method **will not** serve any of the cached files for you.
* It only precaches files. To respond to a network request you call
* {@linkcode addRoute}.
*
* If you have a single array of files to precache, you can just call
* {@linkcode precacheAndRoute}.
*
* @param entries Array of entries to precache.
* @deprecated
*/
const precache = (entries) => {
getSingletonPrecacheController().precache(entries);
};
//#endregion
//#region src/legacy/precacheAndRoute.ts
/**
* This method will add entries to the precache list and add a route to
* respond to `fetch` events.
*
* This is a convenience method that will call
* {@linkcode precache} and {@linkcode addRoute} in a single call.
*
* @param entries Array of entries to precache.
* @param options See the {@linkcode PrecacheRouteOptions} options.
* @deprecated
*/
const precacheAndRoute = (entries, options) => {
precache(entries);
addRoute(options);
};
//#endregion
//#region src/legacy/setCatchHandler.ts
/**
* If a route throws an error while handling a request, this handler
* will be called and given a chance to provide a response.
*
* @param handler A callback function that returns a promise resulting in a response.
* @deprecated
*/
const setCatchHandler = (handler) => {
getSingletonRouter().setCatchHandler(handler);
};
//#endregion
//#region src/legacy/setDefaultHandler.ts
/**
* Defines a default handler that's called when no routes explicitly
* match the incoming request.
*
* Without a default handler, unmatched requests will go against the
* network as if there were no service worker present.
*
* @param handler A callback function that returns a promise resulting in a response.
* @deprecated
*/
const setDefaultHandler = (handler) => {
getSingletonRouter().setDefaultHandler(handler);
};
//#endregion
//#region src/legacy/unregisterRoute.ts
/**
* Unregisters a route from the singleton {@linkcode Router} instance.
*
* @param route The route to unregister.
* @deprecated
*/
const unregisterRoute = (route) => {
getSingletonRouter().unregisterRoute(route);
};
//#endregion
export { PrecacheController, PrecacheFallbackPlugin, PrecacheRoute, Router, addPlugins, addRoute, createHandlerBoundToURL, fallbacks, getCacheKeyForURL, getSingletonPrecacheController, getSingletonRouter, handlePrecaching, initializeGoogleAnalytics, installSerwist, matchPrecache, precache, precacheAndRoute, registerRoute, registerRuntimeCaching, setCatchHandler, setDefaultHandler, setSingletonPrecacheController, setSingletonRouter, unregisterRoute };
//# sourceMappingURL=index.legacy.mjs.map