@ayonli/jsext
Version:
A JavaScript extension package for building strong and modern applications.
908 lines (904 loc) • 27.8 kB
JavaScript
;
var bytes = require('../bytes.js');
var string = require('../string.js');
var http_userAgent = require('./user-agent.js');
/**
* Utility functions for handling HTTP related tasks, such as parsing headers.
* @module
* @experimental
*/
/**
* Parses the `Accept`, `Accept-Encoding` and `Accept-Language` headers.
*
* NOTE: This function automatically sorts the results by the q-factor value in
* descending order.
*
* @example
* ```ts
* import { parseAccepts } from "@ayonli/jsext/http";
*
* const accepts = parseAccepts("text/html,application/xhtml+xml;q=0.9");
* console.log(accepts);
* // [
* // { value: "text/html", weight: 1 },
* // { value: "application/xhtml+xml", weight: 0.9 }
* // ]
*
* const acceptEncodings = parseAccepts("gzip, deflate, br;q=0.8");
* console.log(acceptEncodings);
* // [
* // { value: "gzip", weight: 1 },
* // { value: "deflate", weight: 1 },
* // { value: "br", weight: 0.8 }
* // ]
*
* const acceptLanguages = parseAccepts("en-US,en;q=0.9");
* console.log(acceptLanguages);
* // [
* // { value: "en-US", weight: 1 },
* // { value: "en", weight: 0.9 }
* // ]
* ```
*/
function parseAccepts(str) {
return str.split(",").map((type) => {
const [value, weight] = type.split(";q=");
return {
type: value.trim(),
weight: weight ? parseFloat(weight) : 1,
};
}).sort((a, b) => b.weight - a.weight);
}
/**
* Parses the `Content-Type` header.
*
* @example
* ```ts
* import { parseContentType } from "@ayonli/jsext/http";
*
* const type = parseContentType("text/html; charset=utf-8");
* console.log(type);
* // { type: "text/html", charset: "utf-8" }
*
* const type2 = parseContentType("multipart/form-data; boundary=----WebKitFormBoundaryzjK4sVZ2QeZvz5zB");
* console.log(type2);
* // { type: "multipart/form-data", boundary: "----WebKitFormBoundaryzjK4sVZ2QeZvz5zB" }
* ```
*/
function parseContentType(str) {
const [type, ...params] = str.split(";").map((part) => part.trim());
if (!(type === null || type === void 0 ? void 0 : type.includes("/"))) {
throw new TypeError("Invalid Content-Type header");
}
const parsed = { type: type };
for (const param of params) {
if (param) {
const [key, value] = param.split("=");
if (key === "charset") {
parsed.charset = value !== null && value !== void 0 ? value : "";
}
else if (key === "boundary") {
parsed.boundary = value !== null && value !== void 0 ? value : "";
}
}
}
return parsed;
}
/**
* Parses the `Set-Cookie` header.
*
* @example
* ```ts
* import { parseCookie } from "@ayonli/jsext/http";
*
* const cookie = parseCookie("foo=bar; Domain=example.com; Path=/; Expires=Wed, 09 Jun 2021 10:18:14 GMT; HttpOnly; Secure; SameSite=Strict");
* console.log(cookie);
* // {
* // name: "foo",
* // value: "bar",
* // domain: "example.com",
* // path: "/",
* // expires: 1623233894000,
* // httpOnly: true,
* // secure: true,
* // sameSite: "strict"
* // }
* ```
*/
function parseCookie(str) {
const [nameValue, ...params] = str.split(";").map((part) => part.trim());
if (!nameValue || !nameValue.includes("=")) {
throw new TypeError("Invalid Set-Cookie header");
}
const [name, value] = nameValue.split("=");
const cookie = { name: name, value: value };
for (const param of params) {
if (param) {
const [key, value = ""] = param.split("=");
if (key === "Domain") {
cookie.domain = value;
}
else if (key === "Expires") {
cookie.expires = new Date(value).valueOf();
}
else if (key === "Max-Age") {
cookie.maxAge = parseInt(value);
}
else if (key === "HttpOnly") {
cookie.httpOnly = true;
}
else if (key === "Secure") {
cookie.secure = true;
}
else if (key === "Path") {
cookie.path = value || "/";
}
else if (key === "SameSite" && value) {
cookie.sameSite = value.toLowerCase();
}
else if (key === "Partitioned") {
cookie.partitioned = true;
}
}
}
return cookie;
}
/**
* Converts a {@link Cookie} object to a string.
*
* @example
* ```ts
* import { stringifyCookie } from "@ayonli/jsext/http";
*
* const cookie = stringifyCookie({
* name: "foo",
* value: "bar",
* domain: "example.com",
* path: "/",
* expires: new Date("2021-06-09T10:18:14Z"),
* httpOnly: true,
* secure: true,
* sameSite: "Strict"
* });
* console.log(cookie);
* // foo=bar; Domain=example.com; Path=/; Expires=Wed, 09 Jun 2021 10:18:14 GMT; HttpOnly; Secure; SameSite=Strict
*/
function stringifyCookie(cookie) {
let str = `${cookie.name}=${cookie.value}`;
if (cookie.domain)
str += `; Domain=${cookie.domain}`;
if (cookie.path)
str += `; Path=${cookie.path}`;
if (cookie.expires)
str += `; Expires=${new Date(cookie.expires).toUTCString()}`;
if (cookie.maxAge)
str += `; Max-Age=${cookie.maxAge}`;
if (cookie.httpOnly)
str += "; HttpOnly";
if (cookie.secure)
str += "; Secure";
if (cookie.sameSite)
str += `; SameSite=${string.capitalize(cookie.sameSite)}`;
if (cookie.partitioned)
str += "; Partitioned";
return str;
}
/**
* Parses the `Cookie` header or the `document.cookie` property.
*/
function parseCookies(str) {
return !str ? [] : str.split(/;\s*/g).reduce((cookies, part) => {
const [name, value] = part.split("=");
if (name && value !== undefined) {
cookies.push({ name, value });
}
return cookies;
}, []);
}
/**
* Converts a list of cookies to a string that can be used in the `Cookie`
* header.
*/
function stringifyCookies(cookies) {
return cookies.map(({ name, value }) => `${name}=${value}`).join("; ");
}
/**
* Gets the cookies from the `Cookie` header of the request or the `Set-Cookie`
* header of the response.
*
* @example
* ```ts
* import { getCookies } from "@ayonli/jsext/http";
*
* export default {
* fetch(req: Request) {
* const cookies = getCookies(req);
* console.log(cookies);
*
* return new Response("Hello, World!");
* }
* }
* ```
*/
function getCookies(obj) {
var _a;
if ("ok" in obj && "status" in obj) {
return obj.headers.getSetCookie().map(str => parseCookie(str));
}
else {
return parseCookies((_a = obj.headers.get("Cookie")) !== null && _a !== void 0 ? _a : "");
}
}
/**
* Gets the cookie by the given `name` from the `Cookie` header of the request
* or the `Set-Cookie` header of the response.
*
* @example
* ```ts
* import { getCookie } from "@ayonli/jsext/http";
*
* export default {
* fetch(req: Request) {
* const cookie = getCookie(req, "foo");
* console.log(cookie);
*
* return new Response("Hello, World!");
* }
* }
* ```
*/
function getCookie(obj, name) {
var _a;
return (_a = getCookies(obj).find(cookie => cookie.name === name)) !== null && _a !== void 0 ? _a : null;
}
/**
* Sets a cookie in the `Set-Cookie` header of the response.
*
* NOTE: This function can be used with both {@link Response} and {@link Headers}
* objects. However, when using with a `Headers` instance, make sure to set the
* cookie before the headers instance is used by the response object.
*
* @example
* ```ts
* import { setCookie } from "@ayonli/jsext/http";
*
* export default {
* fetch(req: Request) {
* const res = new Response("Hello, World!");
* setCookie(res, { name: "hello", value: "world" });
*
* return res;
* }
* }
* ```
*/
function setCookie(res, cookie) {
if (res instanceof Headers) {
res.append("Set-Cookie", stringifyCookie(cookie));
}
else {
res.headers.append("Set-Cookie", stringifyCookie(cookie));
}
}
/**
* Sets the `Content-Disposition` header with the given filename when the
* response is intended to be downloaded.
*
* This function encodes the filename with {@link encodeURIComponent} and sets
* both the `filename` and the `filename*` parameters in the header for maximum
* compatibility.
*
* NOTE: This function can be used with both {@link Response} and {@link Headers}
* objects. However, when using with a `Headers` instance, make sure to set the
* filename before the headers instance is used by the response object.
*
* @example
* ```ts
* import { setFilename } from "@ayonli/jsext/http";
*
* export default {
* fetch(req: Request) {
* const res = new Response("Hello, World!");
* setFilename(res, "hello.txt");
*
* return res;
* }
* }
* ```
*/
function setFilename(res, filename) {
filename = encodeURIComponent(filename);
const disposition = `attachment; filename="${filename}"; filename*=UTF-8''${filename}`;
if (res instanceof Headers) {
res.set("Content-Disposition", disposition);
}
else {
res.headers.set("Content-Disposition", disposition);
}
}
/**
* Parses the `Range` header.
*
* @example
* ```ts
* import { parseRange } from "@ayonli/jsext/http";
*
* const range = parseRange("bytes=0-499");
* console.log(range);
* // { unit: "bytes", ranges: [{ start: 0, end: 499 }] }
*
* const range2 = parseRange("bytes=0-499,1000-1499");
* console.log(range2);
* // { unit: "bytes", ranges: [{ start: 0, end: 499 }, { start: 1000, end: 1499 }] }
*
* const range3 = parseRange("bytes=2000-");
* console.log(range3);
* // { unit: "bytes", ranges: [{ start: 2000 }] }
*
* const range4 = parseRange("bytes=-500");
* console.log(range4);
* // { unit: "bytes", ranges: [], suffix: 500 }
* ```
*/
function parseRange(str) {
if (!str.includes("=")) {
throw new TypeError("Invalid Range header");
}
const [unit, ranges] = str.split("=").map((part) => part.trim());
const parsed = { unit: unit, ranges: [] };
for (const range of ranges.split(",")) {
if (!range || !range.includes("-"))
continue;
const [start, end] = range.split("-").map((part) => part.trim());
if (!start && !end) {
continue;
}
else if (!start) {
parsed.suffix = parseInt(end);
}
else if (!end) {
parsed.ranges.push({ start: parseInt(start) });
}
else {
parsed.ranges.push({ start: parseInt(start), end: parseInt(end) });
}
}
if ((!parsed.ranges.length && !parsed.suffix) ||
parsed.ranges.some((range) => range.start < 0 || (range.end && range.end <= range.start))) {
throw new TypeError("Invalid Range header");
}
return parsed;
}
/**
* Checks if the value from the `If-Match` header matches the given ETag.
*
* NOTE: Weak tags cannot be matched and will return `false`.
*
* @example
* ```ts
* import { etag, ifMatch } from "@ayonli/jsext/http";
*
* const _etag = await etag("Hello, World!");
* const match = ifMatch("d-3/1gIbsr1bCvZ2KQgJ7DpTGR3YH", _etag);
* console.log(match); // true
* ```
*/
function ifMatch(value, etag) {
// Weak tags cannot be matched and return false.
if (!value || etag.startsWith("W/")) {
return false;
}
if (value.trim() === "*") {
return true;
}
const tags = value.split(/\s*,\s*/);
return tags.includes(etag);
}
/**
* Checks if the value from the `If-None-Match` header matches the given ETag.
*
* @example
* ```ts
* import { etag, ifNoneMatch } from "@ayonli/jsext/http";
*
* const _etag = await etag("Hello, World!");
* const match = ifNoneMatch("d-3/1gIbsr1bCvZ2KQgJ7DpTGR3YH", _etag);
* console.log(match); // false
* ```
*/
function ifNoneMatch(value, etag) {
if (!value) {
return true;
}
if (value.trim() === "*") {
return false;
}
const tags = value.split(/\s*,\s*/).map((tag) => tag.startsWith("W/") ? tag.slice(2) : tag);
return !tags.includes(etag);
}
/**
* Parses the `Authorization` header with the `Basic` scheme.
*
* @example
* ```ts
* import { parseBasicAuth } from "@ayonli/jsext/http";
*
* const auth = parseBasicAuth("Basic cm9vdDpwYSQkdzByZA==");
* console.log(auth);
* // { username: "root", password: "pa$$w0rd" }
* ```
*/
function parseBasicAuth(str) {
const parts = str.split(" ");
const scheme = parts[0].toLowerCase();
const credentials = parts.slice(1).join(" ");
if (!scheme || !credentials) {
throw new TypeError("Invalid Authorization header");
}
else if (scheme !== "basic") {
throw new TypeError("Authorization scheme is not 'Basic'");
}
else {
const [username, password] = bytes.text(bytes.default(credentials, "base64")).split(":");
return { username: username, password: password };
}
}
/**
* Performs basic authentication verification for the request. When passed, this
* function returns nothing (`undefined`), otherwise it returns a `Response`
* with status `401 Unauthorized`, which should be responded to the client.
*
* @example
* ```ts
* import { verifyBasicAuth, type BasicAuthorization } from "@ayonli/jsext/http";
*
* const users = new Map([
* ["root", "pa$$w0rd"]
* ]);
*
* async function verify(auth: BasicAuthorization) {
* const password = users.get(auth.username);
* return !!password && password === auth.password;
* }
*
* export default {
* async fetch(req) {
* const res = await verifyBasicAuth(req, verify);
*
* if (res) {
* return res;
* }
*
* // continue with the request
* },
* };
* ```
*/
async function verifyBasicAuth(req, verify) {
const auth = req.headers.get("Authorization");
if (auth === null || auth === void 0 ? void 0 : auth.startsWith("Basic ")) {
try {
const credentials = parseBasicAuth(auth);
const ok = await verify(credentials);
if (ok) {
return;
}
}
catch (_a) { }
}
const { host } = new URL(req.url);
return new Response("Unauthorized", {
status: 401,
statusText: "Unauthorized",
headers: {
"WWW-Authenticate": `Basic realm="${host}"`,
},
});
}
const HTTP_METHODS = [
"GET",
"HEAD",
"POST",
"PUT",
"DELETE",
"CONNECT",
"OPTIONS",
"TRACE",
"PATCH",
];
const HTTP_STATUS = {
200: "OK",
201: "Created",
202: "Accepted",
204: "No Content",
206: "Partial Content",
301: "Moved Permanently",
302: "Found",
304: "Not Modified",
400: "Bad Request",
401: "Unauthorized",
403: "Forbidden",
404: "Not Found",
405: "Method Not Allowed",
406: "Not Acceptable",
408: "Request Timeout",
409: "Conflict",
410: "Gone",
413: "Payload Too Large",
414: "URI Too Long",
415: "Unsupported Media Type",
416: "Range Not Satisfiable",
417: "Expectation Failed",
426: "Upgrade Required",
500: "Internal Server Error",
501: "Not Implemented",
502: "Bad Gateway",
503: "Service Unavailable",
504: "Gateway Timeout",
505: "HTTP Version Not Supported",
};
function parseMessage(message) {
const headerEnd = message.indexOf("\r\n\r\n");
if (headerEnd === -1) {
throw new TypeError("Invalid message");
}
const headers = new Headers();
const headerLines = message.slice(0, headerEnd).split("\r\n");
for (let i = 0; i < headerLines.length; i++) {
const line = headerLines[i];
const lineNum = i + 1;
const index = line.indexOf(":");
if (index === -1) {
throw new SyntaxError("Invalid token in line " + lineNum);
}
const name = line.slice(0, index);
const value = line.slice(index + 1).trim();
try {
headers.append(name, value);
}
catch (_a) {
throw new TypeError(`Invalid header name '${name}' in line ${lineNum}`);
}
}
const body = message.slice(headerEnd + 4);
return { headers, body };
}
/**
* Parses the text message as an HTTP request.
*
* **NOTE:** This function only supports HTTP/1.1 protocol.
*
* @example
* ```ts
* // GET example
* import { parseRequest } from "@ayonli/jsext/http";
*
* const message = "GET /foo HTTP/1.1\r\nHost: example.com\r\n\r\n";
* const req = parseRequest(message);
*
* console.log(req.method); // "GET"
* console.log(req.url); // "http://example.com/foo"
* console.log(req.headers.get("Host")); // "example.com"
* ```
*
* @example
* ```ts
* // POST example
* import { parseRequest } from "@ayonli/jsext/http";
*
* const message = "POST /foo HTTP/1.1\r\n"
* + "Host: example.com\r\n"
* + "Content-Type: application/x-www-form-urlencoded\r\n"
* + "Content-Length: 19\r\n"
* + "\r\n"
* + "foo=hello&bar=world";
* const req = parseRequest(message);
*
* console.log(req.method); // "POST"
* console.log(req.url); // "http://example.com/foo"
* console.log(req.headers.get("Host")); // "example.com"
*
* const form = new URLSearchParams(await req.text());
*
* console.log(form.get("foo")); // "hello"
* console.log(form.get("bar")); // "world"
* ```
*/
function parseRequest(message) {
var _a;
let lineEnd = message.indexOf("\r\n");
if (lineEnd === -1) {
throw new TypeError("Invalid message");
}
const [method, url, version] = message.slice(0, lineEnd).split(/\s+/);
if (!method || !HTTP_METHODS.includes(method) ||
!url || !url.startsWith("/") ||
!(version === null || version === void 0 ? void 0 : version.startsWith("HTTP/"))) {
throw new TypeError("Invalid message");
}
else if (version !== "HTTP/1.1") {
throw new TypeError("Unsupported HTTP version");
}
const { headers, body: _body } = parseMessage(message.slice(lineEnd + 2));
let body = _body;
if (method === "GET" || method === "HEAD") {
if (body) {
throw new TypeError("Request with GET/HEAD method cannot have body.");
}
else {
body = null;
}
}
const host = (_a = headers.get("Host")) !== null && _a !== void 0 ? _a : "";
return new Request("http://" + host + url, {
method,
headers,
body,
});
}
/**
* Parses the text message as an HTTP response.
*
* @example
* ```ts
* import { parseResponse } from "@ayonli/jsext/http";
*
* const message = "HTTP/1.1 200 OK\r\n"
* + "Content-Type: text/plain\r\n"
* + "Content-Length: 12\r\n"
* + "\r\n"
* + "Hello, World!";
*
* const res = parseResponse(message);
*
* console.log(res.status); // 200
* console.log(res.statusText); // "OK"
* console.log(res.headers.get("Content-Type")); // "text/plain"
*
* const text = await res.text();
* console.log(text); // "Hello, World!"
* ```
*/
function parseResponse(message) {
let lineEnd = message.indexOf("\r\n");
if (lineEnd === -1) {
throw new TypeError("Invalid message");
}
const [version, _status, ...statusTexts] = message.slice(0, lineEnd).split(/\s+/);
const statusText = statusTexts.join(" ");
let status;
if (!(version === null || version === void 0 ? void 0 : version.startsWith("HTTP/")) ||
!_status || !Number.isInteger((status = Number(_status))) ||
!statusText) {
throw new TypeError("Invalid message");
}
const { headers, body: _body } = parseMessage(message.slice(lineEnd + 2));
let body = _body;
if (status === 204 || status === 304) {
if (body) {
throw new SyntaxError("Response with 204 or 304 status cannot have body.");
}
else {
body = null;
}
}
return new Response(body, {
status,
statusText,
headers,
});
}
/**
* Converts the request object to text format.
*
* @example
* ```ts
* // GET example
* import { stringifyRequest } from "@ayonli/jsext/http";
*
* const req = new Request("http://example.com/foo");
* const message = await stringifyRequest(req);
*
* console.log(message);
* // "GET /foo HTTP/1.1\r\nHost: example.com\r\n\r\n"
* ```
*
* @example
* ```ts
* // POST example
* import { stringifyRequest } from "@ayonli/jsext/http";
*
* const req = new Request("http://example.com/foo", {
* method: "POST",
* headers: {
* "Content-Type": "application/x-www-form-urlencoded",
* },
* body: "foo=hello&bar=world",
* });
* const message = await stringifyRequest(req);
*
* console.log(message);
* // "POST /foo HTTP/1.1\r\n" +
* // "Host: example.com\r\n" +
* // "Content-Type: application/x-www-form-urlencoded\r\n" +
* // "Content-Length: 19\r\n" +
* // "\r\n" +
* // "foo=hello&bar=world"
* ```
*/
async function stringifyRequest(req) {
const { host, pathname } = new URL(req.url);
let message = `${req.method} ${pathname} HTTP/1.1\r\n`;
let body = req.body ? await req.text() : "";
if (host && !req.headers.has("Host")) {
message += `Host: ${host}\r\n`;
}
for (const [name, value] of req.headers) {
message += `${string.capitalize(name, true)}: ${value}\r\n`;
}
if (body && !req.headers.has("Content-Length")) {
message += `Content-Length: ${bytes.default(body).length}\r\n`;
}
message += "\r\n" + body;
return message;
}
/**
* Converts the response object to text format.
*
* @example
* ```ts
* import { stringifyResponse } from "@ayonli/jsext/http";
*
* const res = new Response("Hello, World!", {
* headers: {
* "Content-Type": "text/plain",
* },
* });
* const message = await stringifyResponse(res);
*
* console.log(message);
* // "HTTP/1.1 200 OK\r\n" +
* // "Content-Type: text/plain\r\n" +
* // "Content-Length: 12\r\n" +
* // "\r\n" +
* // "Hello, World!"
* ```
*/
async function stringifyResponse(res) {
const statusText = res.statusText || HTTP_STATUS[res.status] || "";
let message = `HTTP/1.1 ${res.status} ${statusText}\r\n`;
let body = res.body ? await res.text() : "";
for (const [name, value] of res.headers) {
message += `${string.capitalize(name, true)}: ${value}\r\n`;
}
if (body && !res.headers.has("Content-Length")) {
message += `Content-Length: ${bytes.default(body).length}\r\n`;
}
message += "\r\n" + body;
return message;
}
/**
* Gets the suggested response type for the request.
*
* This function checks the `Accept` or the `Content-Type` header of the request,
* or the request method, or other factors to determine the most suitable
* response type for the client.
*
* For example, when requesting an article which is stored in markdown, the
* server can respond an HTML page for the browser, a plain text for the
* terminal, or a JSON object for the API client.
*
* This function returns the following response types:
*
* - `text`: plain text content (default)
* - `html`: an HTML page
* - `xml`: an XML document
* - `json`: a JSON object
* - `stream`: text stream or binary stream, depending on the use case
* - `none`: no content should be sent, such as for a `HEAD` request
*
* @example
* ```ts
* import { suggestResponseType } from "@ayonli/jsext/http";
*
* export default {
* async fetch(req: Request) {
* const type = suggestResponseType(req);
*
* if (type === "text") {
* return new Response("Hello, World!");
* } else if (type === "html") {
* return new Response("<h1>Hello, World!</h1>", {
* headers: { "Content-Type": "text/html" },
* });
* } else if (type === "xml") {
* return new Response("<xml><message>Hello, World!</message></xml>", {
* headers: { "Content-Type": "application/xml" },
* });
* } else if (type === "json") {
* return new Response(JSON.stringify({ message: "Hello, World!" }), {
* headers: { "Content-Type": "application/json" },
* });
* } else {
* return new Response(null, { status: 204 });
* }
* }
* }
* ```
*/
function suggestResponseType(req) {
var _a;
const accepts = req.headers.get("Accept");
const accept = accepts
? (_a = parseAccepts(accepts).sort((a, b) => b.weight - a.weight)[0]) === null || _a === void 0 ? void 0 : _a.type
: null;
const acceptAll = !accept || accept === "*/*";
const contentType = req.headers.get("Content-Type");
const fetchDest = req.headers.get("Sec-Fetch-Dest") || req.destination;
const xhr = req.headers.get("X-Requested-With") === "XMLHttpRequest";
if (req.method === "HEAD" || req.method === "OPTIONS") {
return "none";
}
else if ((accept === null || accept === void 0 ? void 0 : accept.includes("text/event-stream"))
|| (accept === null || accept === void 0 ? void 0 : accept.includes("application/octet-stream"))
|| (accept === null || accept === void 0 ? void 0 : accept.includes("multipart/form-data"))
|| /(image|audio|video)\//.test(accept !== null && accept !== void 0 ? accept : "")
|| ["font", "image", "audio", "video", "object", "embed"].includes(fetchDest)) {
return "stream";
}
else if ((accept === null || accept === void 0 ? void 0 : accept.includes("/json"))
|| (acceptAll && ((contentType === null || contentType === void 0 ? void 0 : contentType.includes("json")) || xhr))
|| fetchDest === "manifest") {
return "json";
}
else if ((accept === null || accept === void 0 ? void 0 : accept.includes("/xml"))
|| (acceptAll && (contentType === null || contentType === void 0 ? void 0 : contentType.includes("xml")))
|| fetchDest === "xslt") {
return "xml";
}
else if ((accept === null || accept === void 0 ? void 0 : accept.includes("/html"))
|| fetchDest === "document") {
return "html";
}
else {
const { pathname } = new URL(req.url);
if (pathname === "/api" ||
pathname.startsWith("/api/") ||
/\.json?$/i.test(pathname)) {
return "json";
}
else if (/\.xml$/i.test(pathname)) {
return "xml";
}
else if (/\.html?$/i.test(pathname)) {
return "html";
}
else {
return "text";
}
}
}
exports.parseUserAgent = http_userAgent.parseUserAgent;
exports.HTTP_METHODS = HTTP_METHODS;
exports.HTTP_STATUS = HTTP_STATUS;
exports.getCookie = getCookie;
exports.getCookies = getCookies;
exports.ifMatch = ifMatch;
exports.ifNoneMatch = ifNoneMatch;
exports.parseAccepts = parseAccepts;
exports.parseBasicAuth = parseBasicAuth;
exports.parseContentType = parseContentType;
exports.parseCookie = parseCookie;
exports.parseCookies = parseCookies;
exports.parseRange = parseRange;
exports.parseRequest = parseRequest;
exports.parseResponse = parseResponse;
exports.setCookie = setCookie;
exports.setFilename = setFilename;
exports.stringifyCookie = stringifyCookie;
exports.stringifyCookies = stringifyCookies;
exports.stringifyRequest = stringifyRequest;
exports.stringifyResponse = stringifyResponse;
exports.suggestResponseType = suggestResponseType;
exports.verifyBasicAuth = verifyBasicAuth;
//# sourceMappingURL=util.js.map