serwist
Version:
A Swiss Army knife for service workers.
145 lines (125 loc) • 4.48 kB
text/typescript
/*
Copyright 2018 Google LLC
Use of this source code is governed by an MIT-style
license that can be found in the LICENSE file or at
https://opensource.org/licenses/MIT.
*/
import { SerwistError } from "../../utils/SerwistError.js";
import { assert } from "../../utils/assert.js";
import { getFriendlyURL } from "../../utils/getFriendlyURL.js";
import { logger } from "../../utils/logger.js";
export interface CacheableResponseOptions {
/**
* One or more HTTP status codes that a response can have to be considered cacheable.
*/
statuses?: number[];
/**
* A mapping of header names and expected values that a response can have and be
* considered cacheable. If multiple headers are provided, only one needs to be present.
*/
headers?: HeadersInit;
}
/**
* Allows you to set up rules determining what status codes and/or headers need
* to be present in order for a [response](https://developer.mozilla.org/en-US/docs/Web/API/Response)
* to be considered cacheable.
*/
export class CacheableResponse {
private readonly _statuses?: CacheableResponseOptions["statuses"];
private readonly _headers?: Headers;
/**
* To construct a new `CacheableResponse` instance you must provide at least
* one of the `config` properties.
*
* If both `statuses` and `headers` are specified, then both conditions must
* be met for the response to be considered cacheable.
*
* @param config
*/
constructor(config: CacheableResponseOptions = {}) {
if (process.env.NODE_ENV !== "production") {
if (!(config.statuses || config.headers)) {
throw new SerwistError("statuses-or-headers-required", {
moduleName: "serwist",
className: "CacheableResponse",
funcName: "constructor",
});
}
if (config.statuses) {
assert!.isArray(config.statuses, {
moduleName: "serwist",
className: "CacheableResponse",
funcName: "constructor",
paramName: "config.statuses",
});
}
if (config.headers) {
assert!.isType(config.headers, "object", {
moduleName: "serwist",
className: "CacheableResponse",
funcName: "constructor",
paramName: "config.headers",
});
}
}
this._statuses = config.statuses;
if (config.headers) {
this._headers = new Headers(config.headers);
}
}
/**
* Checks a response to see whether it's cacheable or not.
*
* @param response The response whose cacheability is being
* checked.
* @returns `true` if the response is cacheable, and `false`
* otherwise.
*/
isResponseCacheable(response: Response): boolean {
if (process.env.NODE_ENV !== "production") {
assert!.isInstance(response, Response, {
moduleName: "serwist",
className: "CacheableResponse",
funcName: "isResponseCacheable",
paramName: "response",
});
}
let cacheable = true;
if (this._statuses) {
cacheable = this._statuses.includes(response.status);
}
if (this._headers && cacheable) {
for (const [headerName, headerValue] of this._headers.entries()) {
if (response.headers.get(headerName) !== headerValue) {
cacheable = false;
break;
}
}
}
if (process.env.NODE_ENV !== "production") {
if (!cacheable) {
logger.groupCollapsed(
`The request for '${getFriendlyURL(response.url)}' returned a response that does not meet the criteria for being cached.`,
);
logger.groupCollapsed("View cacheability criteria here.");
logger.log(`Cacheable statuses: ${JSON.stringify(this._statuses)}`);
logger.log(`Cacheable headers: ${JSON.stringify(this._headers, null, 2)}`);
logger.groupEnd();
const logFriendlyHeaders: { [key: string]: string } = {};
response.headers.forEach((value, key) => {
logFriendlyHeaders[key] = value;
});
logger.groupCollapsed("View response status and headers here.");
logger.log(`Response status: ${response.status}`);
logger.log(`Response headers: ${JSON.stringify(logFriendlyHeaders, null, 2)}`);
logger.groupEnd();
logger.groupCollapsed("View full response details here.");
logger.log(response.headers);
logger.log(response);
logger.groupEnd();
logger.groupEnd();
}
}
return cacheable;
}
}