uniqhtt
Version:
A sophisticated, enterprise-grade HTTP client for Node.js, Web, and edge environments featuring intelligent cookie management, advanced web crawling capabilities, comprehensive automation tools, and a new TypeScript-first Pro API with HTTP/2, HTTP/3, stre
1,333 lines (1,330 loc) • 280 kB
JavaScript
// src/core/adapters/base.ts
import { Buffer as Buffer2 } from "node:buffer";
import path from "node:path";
import PQueue from "p-queue";
// src/core/util/cookies.ts
import { CookieJar as TouchCookieJar, Cookie as TouchCookie } from "tough-cookie";
var Cookie = class extends TouchCookie {
constructor(options3) {
super(options3);
}
getExpires() {
let expires = 0;
if (this.expires && typeof this.expires !== "string") {
expires = Math.round(this.expires.getTime() / 1e3);
} else if (this.maxAge) {
if (this.maxAge === "Infinity") {
expires = 2147483647;
} else if (this.maxAge === "-Infinity") {
expires = 0;
} else if (typeof this.maxAge === "number") {
expires = Math.round(Date.now() / 1e3 + this.maxAge);
}
}
return expires;
}
toNetscapeFormat() {
const domain = !this.hostOnly ? this.domain.startsWith(".") ? this.domain : "." + this.domain : this.domain;
const secure = this.secure ? "TRUE" : "FALSE";
const expires = this.getExpires();
return `${domain} TRUE ${this.path} ${secure} ${expires} ${this.key} ${this.value}`;
}
toSetCookieString() {
let str = this.cookieString();
if (this.expires instanceof Date) str += `; Expires=${this.expires.toUTCString()}`;
if (this.maxAge != null) str += `; Max-Age=${this.maxAge}`;
if (this.domain) str += `; Domain=${this.domain}`;
if (this.path) str += `; Path=${this.path}`;
if (this.secure) str += "; Secure";
if (this.httpOnly) str += "; HttpOnly";
if (this.sameSite) str += `; SameSite=${this.sameSite}`;
if (this.extensions) str += `; ${this.extensions.join("; ")}`;
return str;
}
/**
* Retrieves the complete URL from the cookie object
* @returns {string | undefined} The complete URL including protocol, domain and path. Returns undefined if domain is not set
* @example
* const cookie = new Cookie({
* domain: "example.com",
* path: "/path",
* secure: true
* });
* cookie.getURL(); // Returns: "https://example.com/path"
*/
getURL() {
if (!this.domain) return void 0;
const protocol = this.secure ? "https://" : "http://";
const domain = this.domain.startsWith(".") ? this.domain.substring(1) : this.domain;
const path5 = this.path || "/";
return `${protocol}${domain}${path5}`;
}
};
var CookieJar = class extends TouchCookieJar {
constructor(store, options3) {
super(store, options3);
}
generateCookies(data) {
const cookies = !data ? (this.toJSON()?.cookies || []).map((cookie) => new Cookie(cookie)) : data[0] instanceof Cookie ? data : data.map((cookie) => new Cookie(cookie instanceof TouchCookie ? cookie : Cookie.fromJSON(cookie)));
const netscape = cookies.map((cookie) => cookie.toNetscapeFormat());
const cookieString = cookies.map((cookie) => cookie.cookieString());
const setCookiesString = cookies.map((cookie) => cookie.toSetCookieString());
let netscapeString = "# Netscape HTTP Cookie File\n";
netscapeString += "# This file was generated by uniqhtt npm package\n";
netscapeString += "# https://www.npmjs.com/package/uniqhtt\n";
netscapeString += netscape.join("\n") || "";
return {
array: cookies,
serialized: this.toJSON()?.cookies || [],
netscape: netscapeString,
string: cookieString.join("; "),
setCookiesString
};
}
cookies() {
return this.generateCookies();
}
parseResponseCookies(cookies) {
return this.generateCookies(cookies);
}
static toNetscapeCookie(cookies) {
cookies = cookies.map((cookie) => {
return cookie instanceof Cookie ? cookie : new Cookie(Cookie.fromJSON(cookie));
});
let netscapeString = "# Netscape HTTP Cookie File\n";
netscapeString += "# This file was generated by uniqhtt npm package\n";
netscapeString += "# https://www.npmjs.com/package/uniqhtt\n";
netscapeString += cookies.map((cookie) => cookie.toNetscapeFormat()).join("\n") || "";
return netscapeString;
}
static toCookieString(cookies) {
cookies = cookies.map((cookie) => {
return cookie instanceof Cookie ? cookie : new Cookie(Cookie.fromJSON(cookie));
});
return cookies.map((cookie) => cookie.toNetscapeFormat()).join("; ") || "";
}
toCookieString() {
return this.cookies().string;
}
toNetscapeCookie() {
return this.cookies().netscape;
}
toArray() {
return this.cookies().array;
}
toSetCookies() {
return this.cookies().setCookiesString;
}
toSerializedCookies() {
return this.cookies().serialized;
}
setCookiesSync(cookiesData, url) {
const cookies = [];
if (Array.isArray(cookiesData)) {
cookiesData.forEach((c) => {
const cookie = c instanceof Cookie ? c : typeof c === "string" ? new Cookie(Cookie.parse(c)) : new Cookie(Cookie.fromJSON(c));
let isFailed = 0;
while (isFailed < 2) {
try {
if (cookie) {
const _url = isFailed > 0 ? cookie.getURL() || url || this.getUrlFromCookie(cookie) : url || this.getUrlFromCookie(cookie);
if (_url) {
const __cookie = this.setCookieSync(cookie, _url);
if (__cookie) {
cookies.push(__cookie);
}
}
isFailed = 4;
break;
} else {
isFailed++;
}
} catch (error) {
isFailed++;
if (isFailed > 1) {
break;
}
}
}
});
} else if (typeof cookiesData === "string") {
if (cookiesData.includes(",") && (cookiesData.includes("=") && (cookiesData.includes(";") || cookiesData.includes("expires=") || cookiesData.includes("path=")))) {
const setCookieStrings = this.splitSetCookiesString(cookiesData);
setCookieStrings.forEach((cookieStr) => {
const cookie = new Cookie(Cookie.parse(cookieStr));
let isFailed = 0;
while (isFailed < 2) {
try {
if (cookie) {
const _url = isFailed > 0 ? cookie.getURL() || url || this.getUrlFromCookie(cookie) : url || this.getUrlFromCookie(cookie);
if (_url) {
const __cookie = this.setCookieSync(cookie, _url);
if (__cookie) {
cookies.push(__cookie);
}
}
isFailed = 4;
break;
} else {
isFailed++;
}
} catch (error) {
isFailed++;
if (isFailed > 1) {
break;
}
}
}
});
} else if (cookiesData.includes("=") && cookiesData.includes(";")) {
const cookiePairs = cookiesData.split(";");
cookiePairs.forEach((pair) => {
const trimmedPair = pair.trim();
if (trimmedPair) {
const cookie = new Cookie({ key: trimmedPair.split("=")[0].trim(), value: trimmedPair.split("=")[1]?.trim() || "" });
let isFailed = 0;
while (isFailed < 2) {
try {
if (cookie) {
const _url = isFailed > 0 ? cookie.getURL() || url || this.getUrlFromCookie(cookie) : url || this.getUrlFromCookie(cookie);
if (_url) {
const __cookie = this.setCookieSync(cookie, _url);
if (__cookie) {
cookies.push(__cookie);
}
}
isFailed = 4;
break;
} else {
isFailed++;
}
} catch (error) {
isFailed++;
if (isFailed > 1) {
break;
}
}
}
}
});
} else if (cookiesData.includes(" ") && /^\S+\t/.test(cookiesData)) {
const netscapeCookies = this.parseNetscapeCookies(cookiesData);
netscapeCookies.forEach((c) => {
const cookie = new Cookie(c);
let isFailed = 0;
while (isFailed < 2) {
try {
if (cookie) {
const _url = isFailed > 0 ? cookie.getURL() || url || this.getUrlFromCookie(cookie) : url || this.getUrlFromCookie(cookie);
if (_url) {
const __cookie = this.setCookieSync(cookie, _url);
if (__cookie) {
cookies.push(__cookie);
}
}
isFailed = 4;
break;
} else {
isFailed++;
}
} catch (error) {
isFailed++;
if (isFailed > 1) {
break;
}
}
}
});
} else if (cookiesData.includes("=")) {
const cookie = new Cookie(Cookie.parse(cookiesData));
let isFailed = 0;
while (isFailed < 2) {
try {
if (cookie) {
const _url = isFailed > 0 ? cookie.getURL() || url || this.getUrlFromCookie(cookie) : url || this.getUrlFromCookie(cookie);
if (_url) {
const __cookie = this.setCookieSync(cookie, _url);
if (__cookie) {
cookies.push(__cookie);
}
}
isFailed = 4;
break;
} else {
isFailed++;
}
} catch (error) {
isFailed++;
if (isFailed > 1) {
break;
}
}
}
}
}
return this.generateCookies(cookies);
}
// Helper method to split Set-Cookie strings that may contain commas within their values
splitSetCookiesString(cookiesString) {
const result = [];
let currentCookie = "";
let withinValue = false;
for (let i = 0; i < cookiesString.length; i++) {
const char = cookiesString[i];
if (char === "," && !withinValue) {
result.push(currentCookie.trim());
currentCookie = "";
continue;
}
if (char === "=") {
withinValue = true;
} else if (char === ";") {
withinValue = false;
}
currentCookie += char;
}
if (currentCookie.trim()) {
result.push(currentCookie.trim());
}
return result;
}
getUrlFromCookie(cookie) {
if (!cookie.domain) return void 0;
const proto = cookie.secure ? "https://" : "http://";
const domain = cookie.domain.startsWith(".") ? cookie.domain.substring(1) : cookie.domain;
const pathname = cookie.path || "/";
return `${proto}${domain}${pathname}`;
}
parseNetscapeCookies = (cookieText) => {
const lines = cookieText.split("\n").filter((line) => line.trim() !== "" && !line.startsWith("#"));
return lines.map((line) => {
const parts = line.split(" ");
if (parts.length < 7) {
throw new Error(`Invalid Netscape cookie format: ${line}`);
}
const [domain, _, path5, secureStr, expiresStr, name, value] = parts;
let expires = null;
if (expiresStr !== "0" && expiresStr !== "") {
expires = new Date(parseInt(expiresStr, 10) * 1e3);
}
return {
domain,
path: path5,
secure: secureStr.toUpperCase() === "TRUE",
expires,
key: name,
value,
httpOnly: false,
// Not specified in Netscape format, default to false
sameSite: void 0
// Not specified in Netscape format
};
});
};
/**
* Converts Netscape cookie format to an array of Set-Cookie header strings
*
* @param netscapeCookieText - Netscape format cookie string
* @returns Array of Set-Cookie header strings
*/
static netscapeCookiesToSetCookieArray(netscapeCookieText) {
const parseNetscapeCookies = (cookieText) => {
const lines = cookieText.split("\n").filter((line) => line.trim() !== "" && !line.startsWith("#"));
return lines.map((line) => {
const parts = line.split(" ");
if (parts.length < 7) {
throw new Error(`Invalid Netscape cookie format: ${line}`);
}
const [domain, _, path5, secureStr, expiresStr, name, value] = parts;
let expires = null;
if (expiresStr !== "0" && expiresStr !== "") {
expires = new Date(parseInt(expiresStr, 10) * 1e3);
}
return {
domain,
path: path5,
secure: secureStr.toUpperCase() === "TRUE",
expires,
name,
value,
httpOnly: false,
// Not specified in Netscape format, default to false
sameSite: void 0
// Not specified in Netscape format
};
});
};
const cookieToSetCookieString = (cookie) => {
let setCookie = `${cookie.name}=${cookie.value}`;
if (cookie.domain) {
setCookie += `; Domain=${cookie.domain}`;
}
if (cookie.path) {
setCookie += `; Path=${cookie.path}`;
}
if (cookie.expires) {
setCookie += `; Expires=${cookie.expires.toUTCString()}`;
}
if (cookie.secure) {
setCookie += "; Secure";
}
if (cookie.httpOnly) {
setCookie += "; HttpOnly";
}
if (cookie.sameSite) {
setCookie += `; SameSite=${cookie.sameSite}`;
}
return setCookie;
};
try {
const cookies = parseNetscapeCookies(netscapeCookieText);
return cookies.map(cookieToSetCookieString);
} catch (error) {
return [];
}
}
};
// src/core/util/httpOptions.ts
import YuniqFormData from "form-data";
var ERROR_INFO = {
"ECONNREFUSED": {
"code": -111,
// Typical errno for Connection Refused (e.g., Linux)
"message": "Connection Refused: The target server actively refused the TCP connection attempt."
},
"ECONNRESET": {
"code": -104,
// Typical errno for Connection Reset by Peer
"message": "Connection Reset: An existing TCP connection was forcibly closed by the peer (server or intermediary)."
},
"ETIMEDOUT": {
"code": -110,
// Typical errno for Connection Timed Out
"message": "Connection Timeout: Attempt to establish a TCP connection timed out (no response received within the timeout period)."
},
"ENOTFOUND": {
"code": -3008,
// Node.js specific code for DNS lookup failed (UV_EAI_NODATA or similar)
"message": "DNS Lookup Failed: DNS lookup for this hostname failed (domain name does not exist or DNS server error)."
},
"EAI_AGAIN": {
"code": -3001,
// Node.js specific code for temporary DNS failure (UV_EAI_AGAIN)
"message": "Temporary DNS Failure: Temporary failure in DNS name resolution; retrying might succeed."
},
"EPROTO": {
"code": -71,
// Typical errno for Protocol Error
"message": "Protocol Error: A protocol error occurred, often during the TLS/SSL handshake phase."
},
"ERR_INVALID_PROTOCOL": {
"code": -1001,
// Custom code for Node.js specific error
"message": "Invalid URL Protocol: The provided URL uses an unsupported or invalid protocol."
},
"ERR_TLS_CERT_ALTNAME_INVALID": {
"code": -1002,
// Custom code for Node.js specific error
"message": "Certificate Invalid Alt Name: The server's SSL/TLS certificate hostname does not match the requested domain (Subject Alternative Name mismatch)."
},
"ERR_TLS_HANDSHAKE_TIMEOUT": {
"code": -1003,
// Custom code for Node.js specific error
"message": "TLS Handshake Timeout: The TLS/SSL handshake timed out before completing."
},
"ERR_TLS_INVALID_PROTOCOL_VERSION": {
"code": -1004,
// Custom code for Node.js specific error
"message": "Invalid TLS Protocol Version: The client and server could not agree on a mutually supported TLS/SSL protocol version."
},
"ERR_TLS_RENEGOTIATION_DISABLED": {
"code": -1005,
// Custom code for Node.js specific error
"message": "TLS Renegotiation Disabled: An attempt at TLS/SSL renegotiation was made, but it is disabled or disallowed by the server/configuration."
},
"ERR_TLS_CERT_SIGNATURE_ALGORITHM_UNSUPPORTED": {
"code": -1006,
// Custom code for Node.js specific error
"message": "Unsupported Cert Signature Algorithm: The signature algorithm used in the server's SSL/TLS certificate is not supported or deemed insecure by the client."
},
"ERR_HTTP_HEADERS_SENT": {
"code": -1007,
// Custom code for Node.js specific error
"message": "Headers Already Sent: An attempt was made to send HTTP headers after they had already been sent (application logic error)."
},
"ERR_INVALID_ARG_TYPE": {
"code": -1008,
// Custom code for Node.js specific error
"message": "Invalid Argument Type: An argument of an incorrect type was passed to the underlying HTTP(S) request function."
},
"ERR_INVALID_URL": {
"code": -1009,
// Custom code for Node.js specific error
"message": "Invalid URL: The provided URL is syntactically invalid or cannot be parsed."
},
"ERR_STREAM_DESTROYED": {
"code": -1010,
// Custom code for Node.js specific error
"message": "Stream Destroyed: The readable/writable stream associated with the request/response was destroyed prematurely."
},
"ERR_STREAM_PREMATURE_CLOSE": {
"code": -1011,
// Custom code for Node.js specific error
"message": "Premature Stream Close: The server closed the connection before sending the complete response body (e.g., incomplete chunked encoding)."
},
"UND_ERR_CONNECT_TIMEOUT": {
"code": -1020,
// Custom code for undici specific error
"message": "Connect Timeout (uniqhtt): Timeout occurred specifically while waiting for the TCP socket connection to be established."
},
"UND_ERR_HEADERS_TIMEOUT": {
"code": -1021,
// Custom code for undici specific error
"message": "Headers Timeout (uniqhtt): Timeout occurred while waiting to receive the complete HTTP response headers from the server."
},
"UND_ERR_SOCKET": {
"code": -1022,
// Custom code for undici specific error (often wraps lower-level errors)
"message": "Socket Error (uniqhtt): An error occurred at the underlying socket level (may wrap other errors like ECONNRESET)."
},
"UND_ERR_INFO": {
"code": -1023,
// Custom code for undici specific error
"message": "Invalid Request Info (uniqhtt): Internal error related to invalid or missing metadata needed to process the request."
},
"UND_ERR_ABORTED": {
"code": -1024,
// Custom code for undici specific error
"message": "Request Aborted (uniqhtt): The request was explicitly aborted, often due to a timeout signal or user action."
},
"ABORT_ERR": {
"code": -1025,
// Custom code representing the standard DOM AbortError
"message": "Request Aborted: The request was explicitly aborted, often due to a timeout signal or user action."
},
"UND_ERR_REQUEST_TIMEOUT": {
"code": -1026,
// Custom code for undici specific error
"message": "Request Timeout (uniqhtt): The request timed out (check specific connect/headers/body timeouts if applicable)."
},
"UNQ_UNKOWN_ERROR": {
"code": -9999,
// Generic code for unknown errors
"message": "Unknown Error: An unspecified or unrecognized error occurred during the request."
},
"UNQ_FILE_PERMISSION_ERROR": {
"code": -1027,
// Custom code for file permission related errors
"message": "File Permission Error: Insufficient permissions to read or write a required file."
},
"UNQ_MISSING_REDIRECT_LOCATION": {
"code": -1028,
// Custom code for missing Location header on redirect
"message": "Redirect Location Not Found: Redirect header (Location:) missing in the server's response for a redirect status code (3xx)."
},
"UNQ_DECOMPRESSION_ERROR": {
"code": -1029,
// Custom code for content decompression errors
"message": "Decompression Error: Failed to decompress response body. Data may be corrupt or encoding incorrect."
},
"UNQ_DOWNLOAD_FAILED": {
"code": -1030,
// Custom code for generic download failure
"message": "Download Failed: The resource could not be fully downloaded due to an unspecified error during data transfer."
},
"UNQ_HTTP_ERROR": {
"code": -1031,
// Custom code for non-2xx/3xx HTTP responses
"message": "HTTP Error: The server responded with a non-successful HTTP status code."
},
"UNQ_REDIRECT_DENIED": {
"code": -1032,
// Custom code for when redirect following is disallowed
"message": "Redirect Denied: A redirect response was received, but following redirects is disabled or disallowed by configuration/user."
},
"UNQ_PROXY_INVALID_PROTOCOL": {
"code": -1033,
// Custom code for bad proxy protocol
"message": "Invalid Proxy Protocol: The specified proxy URL has an invalid or unsupported protocol scheme."
},
"UNQ_PROXY_INVALID_HOSTPORT": {
"code": -1034,
// Custom code for bad proxy host/port
"message": "Invalid Proxy Host/Port: The hostname or port number provided for the proxy server is invalid or malformed."
},
"UNQ_SOCKS_CONNECTION_FAILED": {
"code": -1040,
// General SOCKS connection error
"message": "SOCKS Proxy Connection Failed: Failed to establish connection with the SOCKS proxy server (check host, port, network)."
},
"UNQ_SOCKS_AUTHENTICATION_FAILED": {
"code": -1041,
// SOCKS auth error
"message": "SOCKS Proxy Authentication Failed: Authentication with the SOCKS5 proxy failed (invalid credentials or unsupported method)."
},
"UNQ_SOCKS_TARGET_CONNECTION_FAILED": {
"code": -1042,
// Error reported by SOCKS proxy for target connect
"message": "SOCKS Proxy Target Connection Failed: The SOCKS proxy reported failure connecting to the final destination (e.g., Connection refused, Host unreachable, Network unreachable)."
},
"UNQ_SOCKS_PROTOCOL_ERROR": {
"code": -1043,
// Malformed SOCKS reply/request
"message": "SOCKS Proxy Protocol Error: Invalid or malformed response received from the SOCKS proxy during handshake or connection."
},
"UNQ_PROXY_ERROR": {
"code": -1047,
// Generic proxy error code
"message": "Proxy Error: An unspecified error occurred while communicating with or through the proxy server."
}
};
function prepareHTTPOptions(type, runtime, options3, url, method, adapter, isCurl, maxRedirection, queueOptions, isRedirected, redirectedUrl, mainUrl, isRetrying, redirectCode, lastDomain) {
const validMethods = ["post", "put", "patch"];
const forContentType = validMethods.includes(method.toLowerCase());
let fetchOptions = { others: {} };
let headers = options3.headers instanceof Headers ? options3.headers : new Headers(options3.headers || {});
let requestCookies = [];
let useCookies = options3.enableCookieJar || typeof options3.enableCookieJar === "undefined";
let contentType = options3.contentType || headers.get("Content-Type") || (forContentType ? "application/json" : void 0);
if (options3.customHeaders) {
fetchOptions.headers = new Headers(options3.customHeaders);
} else if (options3.headers) {
fetchOptions.headers = headers instanceof Headers ? headers : new Headers(headers);
} else {
fetchOptions.headers = new Headers();
}
if (headers.has("Cookie")) {
const cookieString = headers.get("Cookie");
if (useCookies && !redirectedUrl && !isRedirected) {
runtime.setCookies(cookieString, url);
}
headers.delete("Cookie");
fetchOptions.headers.delete("Cookie");
}
if (useCookies) {
if (options3.cookies && !redirectedUrl && !isRedirected) {
runtime.setCookies(options3.cookies, url);
}
}
let cookiesString = "";
if (useCookies) {
requestCookies = runtime.jar.getCookiesSync(url).map((c) => new Cookie(c));
cookiesString = runtime.jar.getCookieStringSync(url);
}
if (requestCookies.length > 0) {
fetchOptions.headers.set("Cookie", cookiesString);
}
if (options3.body) {
fetchOptions.body = options3.body;
}
const isFormData = fetchOptions.body && (fetchOptions.body instanceof FormData || fetchOptions.body instanceof YuniqFormData);
if (!isFormData) {
if (options3.isFormData || options3.isJson || options3.isMultipart) {
if (options3.isFormData) {
fetchOptions.body = options3.body;
contentType = "application/x-www-form-urlencoded";
fetchOptions.headers.set("Content-Type", contentType);
} else if (options3.isJson) {
fetchOptions.body = options3.body;
contentType = "application/json";
fetchOptions.headers.set("Content-Type", contentType);
} else if (options3.isMultipart) {
fetchOptions.body = options3.body;
}
} else {
if (options3.json) {
fetchOptions.body = JSON.stringify(options3.json);
contentType = "application/json";
fetchOptions.headers.set("Content-Type", contentType);
} else if (options3.form_params) {
fetchOptions.body = new URLSearchParams(options3.form_params).toString();
contentType = "application/x-www-form-urlencoded";
fetchOptions.headers.set("Content-Type", contentType);
} else if (options3.multipart && !(options3.multipart instanceof FormData || options3.multipart instanceof YuniqFormData)) {
const formData = typeof FormData !== "undefined" ? new FormData() : new YuniqFormData();
Object.entries(options3.multipart).forEach(([key, value]) => {
formData.append(key, value);
});
fetchOptions.body = formData;
} else if (options3.requestType) {
switch (options3.requestType) {
case "json":
contentType = "application/json";
if (typeof fetchOptions.body === "object") {
fetchOptions.body = JSON.stringify(fetchOptions.body);
}
fetchOptions.headers.set("Content-Type", contentType);
break;
case "form":
contentType = "application/x-www-form-urlencoded";
if (typeof fetchOptions.body === "object") {
fetchOptions.body = new URLSearchParams(fetchOptions.body).toString();
}
fetchOptions.headers.set("Content-Type", contentType);
break;
case "formData":
if (typeof fetchOptions.body === "object") {
const formData = typeof FormData !== "undefined" ? new FormData() : new YuniqFormData();
Object.entries(fetchOptions.body).forEach(([key, value]) => {
formData.append(key, value ? typeof value === "object" ? JSON.stringify(value) : value.toString() : "");
});
fetchOptions.body = formData;
}
break;
case "text":
contentType = "text/plain";
fetchOptions.headers.set("Content-Type", contentType);
break;
}
} else if (contentType) {
const type2 = contentType.toLowerCase();
if (type2.includes("json")) {
fetchOptions.headers.set("Content-Type", "application/json");
if (fetchOptions.body && typeof fetchOptions.body === "object") {
fetchOptions.body = JSON.stringify(fetchOptions.body);
}
} else if (type2.includes("x-www-form-urlencoded")) {
fetchOptions.headers.set("Content-Type", "application/x-www-form-urlencoded");
if (fetchOptions.body && typeof fetchOptions.body === "object") {
fetchOptions.body = new URLSearchParams(fetchOptions.body).toString();
}
} else if (type2.includes("multipart")) {
if (fetchOptions.body && typeof fetchOptions.body === "object") {
const formData = new FormData();
Object.entries(fetchOptions.body).forEach(([key, value]) => {
formData.append(key, value);
});
fetchOptions.body = formData;
}
} else if (type2.includes("text/") || type2.includes("/javascript")) {
fetchOptions.body = fetchOptions.body ? typeof fetchOptions.body === "object" ? JSON.stringify(fetchOptions.body) : fetchOptions.body : "";
fetchOptions.headers.set("Content-Type", contentType);
}
} else if (contentType && !options3.withoutContentType) {
fetchOptions.headers.set("Content-Type", contentType);
}
}
}
if (options3.withoutContentType || isFormData) {
fetchOptions.headers.delete("Content-Type");
}
if (options3.withoutBodyOnRedirect && isRedirected) {
fetchOptions.body = void 0;
}
if (options3.printHeaders) {
console.log("Fetch headers:", fetchOptions.headers);
}
if ((typeof options3.autoSetReferer !== "boolean" || options3.autoSetReferer) && redirectedUrl) {
if (!options3.customHeaders) fetchOptions.headers.set("Referer", redirectedUrl);
}
for (const option of options3.innerFetchOption) {
if (Object.keys(options3).includes(option) && typeof options3[option] !== "undefined" && options3[option] !== null) {
fetchOptions.others[option] = options3[option];
}
}
if (!useCookies) {
fetchOptions.headers.delete("Cookie");
}
if (fetchOptions.body && (fetchOptions.body instanceof FormData || fetchOptions.body instanceof YuniqFormData)) {
fetchOptions.headers.delete("Content-Type");
}
delete fetchOptions.credentials;
const config = {
requestBody: fetchOptions.body,
requestOptions: { useCookies, config: null, useHTTP2: options3.useHTTP2 || runtime?.useHTTP2 || false }
};
let uniqhttConfig;
let redirectOptions = null;
if (!options3.uniqhttConfig) {
uniqhttConfig = buildConfig(
{},
fetchOptions.body ?? null,
method.toUpperCase(),
null,
url,
maxRedirection || 0,
options3.mimicBrowser || false,
options3.proxy || options3.thisProxy || null,
options3.timeout || 0,
options3.retry || null,
queueOptions || null,
// queueOptions
options3.signal || null,
// signal
isCurl,
// iscurl
null,
// redirectedOptions
adapter,
// adapter
requestCookies,
useCookies
);
} else {
uniqhttConfig = options3.uniqhttConfig;
if (!uniqhttConfig.redirectOptions) uniqhttConfig.redirectOptions = [];
if (!isRetrying) {
redirectOptions = {
method: method.toUpperCase(),
url: new URL(url),
requestBody: fetchOptions.body || null,
requestHeader: {}
};
}
}
if (method.toLowerCase() === "get" && fetchOptions.headers.has("Content-Type")) {
fetchOptions.headers.delete("Content-Type");
}
if (options3.mimicBrowser) {
if (!fetchOptions.headers.has("user-agent")) {
fetchOptions.headers.set("user-agent", options3.defaultUserAgent);
}
if (!fetchOptions.headers.has("accept")) {
fetchOptions.headers.set(
"accept",
"text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8"
);
}
if (!fetchOptions.headers.has("accept-language")) {
fetchOptions.headers.set("accept-language", "en-US,en;q=0.9");
}
fetchOptions.headers.set("host", new URL(url).host);
if ([`POST`, `PUT`, `PATCH`, `DELETE`, `OPTIONS`].includes(method.toUpperCase()))
fetchOptions.headers.set("origin", new URL(mainUrl || url).origin);
if (mainUrl && fetchOptions.headers.has("origin")) {
const r = [`POST`, `PUT`, `PATCH`, `DELETE`, `OPTIONS`].includes(method.toUpperCase());
if (!r) fetchOptions.headers.delete("origin");
}
if (mainUrl && !fetchOptions.headers.has("referer")) {
fetchOptions.headers.set("referer", mainUrl);
}
} else if (mainUrl || redirectedUrl) {
fetchOptions.headers.set("host", new URL(url).host);
if ([`POST`, `PUT`, `PATCH`, `DELETE`, `OPTIONS`].includes(method.toUpperCase()))
fetchOptions.headers.set("origin", new URL(mainUrl || url).origin);
} else {
if (!fetchOptions.headers.has("origin") && options3.autoSetOrigin) {
const r = [`POST`, `PUT`, `PATCH`, `DELETE`, `OPTIONS`].includes(method.toUpperCase());
if (r) fetchOptions.headers.set("origin", new URL(mainUrl || url).origin);
}
if (mainUrl && !fetchOptions.headers.has("referer") && options3.autoSetReferer) {
fetchOptions.headers.set("referer", mainUrl);
}
}
if (redirectCode && lastDomain) {
fetchOptions.headers.set("referer", lastDomain);
}
const _headers = {};
for (const [key, value] of fetchOptions.headers.entries()) {
_headers[key.toLowerCase()] = value;
}
config.requestOptions.headers = _headers;
if (type === "node") {
config.requestOptions.proxy = options3.proxy ?? options3.thisProxy;
config.requestOptions.filename = options3.fileName;
}
config.requestOptions.method = method;
if (redirectOptions) {
redirectOptions.requestHeader = _headers;
uniqhttConfig.redirectOptions?.push(redirectOptions);
} else if (!isRetrying) {
uniqhttConfig.requestHeader = _headers;
}
if (options3.auth) {
config.auth = options3.auth;
}
fetchOptions = void 0;
config.requestOptions.useCookies = useCookies;
config.requestOptions.config = uniqhttConfig;
return config;
}
function buildConfig(requestHeader, requestBody, method, httpAgent, url, maxRedirection, mimicBrowser, proxy, timeout, retry, queueOptions, signal, isCurl, redirectOptions, adapter, requestCookies, cookiesEnabled) {
return {
requestHeader,
requestBody,
method,
httpAgent,
url: url instanceof URL ? url : new URL(url),
maxRedirection,
mimicBrowser,
proxy,
timeout,
retry,
queueOptions,
signal,
isCurl,
redirectOptions: redirectOptions ? Array.isArray(redirectOptions) ? redirectOptions : [redirectOptions] : null,
adapter,
requestCookies,
cookiesEnabled
};
}
function getCode(code) {
const error = ERROR_INFO[code];
if (error) {
return {
code,
errno: error.code,
message: error.message
};
} else {
const error2 = ERROR_INFO["UNQ_UNKOWN_ERROR"];
return {
code: "UNQ_UNKOWN_ERROR",
errno: error2.code,
message: error2.message
};
}
}
// src/core/adapters/base.ts
import * as process2 from "node:process";
var UniqhttError2 = class _UniqhttError extends Error {
response = {};
#method;
#url;
#finalUrl;
#statusText;
#urls;
#headers;
code;
#status;
config;
constructor(message, response, data, code, headers, config, urls) {
super(message);
this.name = "UniqhttError";
this.#headers = headers;
this.#method = config.method;
this.code = code;
this.#status = response.status;
if (response instanceof Response) {
const length = response.headers.get("Content-Length");
this.response = {
urls: urls || [response.url],
data,
status: response.status,
statusText: response.statusText,
finalUrl: response.url,
cookies: {
array: [],
string: "",
netscape: ""
},
headers,
contentType: response.headers.get("Content-Type"),
contentLength: length ? parseInt(length) : void 0,
config
};
} else {
this.response = {
data,
urls: urls || [response.url],
status: response?.status,
statusText: response?.statusText,
finalUrl: response?.url,
cookies: response?.cookies ?? {
array: [],
string: "",
netscape: ""
},
headers: Object.keys(typeof response.headers === "object" ? response.headers : {}).length > 1 ? response.headers : headers,
contentType: response.contentType,
config,
contentLength: response.contentLength ? response.contentLength : void 0
};
}
this.#url = this.response.finalUrl;
this.#finalUrl = this.response.finalUrl;
this.#statusText = this.response.statusText;
this.config = config;
this.#urls = urls && urls.length > 0 ? urls : [this.#url];
this.#clearStack();
Object.setPrototypeOf(this, _UniqhttError.prototype);
}
toJSON() {
return {
name: this.name,
message: this.message,
method: this.#method,
url: this.#url,
headers: this.#headers,
status: this.#status,
config: this.config,
code: this.code,
cause: this.cause ? typeof this.cause === "string" ? this.cause : this.cause?.message || null : null,
finalUrl: this.#finalUrl,
statusText: this.#statusText,
urls: this.#urls
};
}
#clearStack() {
this.stack = this.stack?.split("\n").filter((line) => !line.includes("node_modules") && !line.includes("toNetscapeFormat")).join("\n");
}
};
var Base = class {
queue = null;
isQueueEnabled = false;
jar = null;
// protected fetch: typeof fetch;
innerFetchOption = ["headers", "mode", "cache", "referrerPolicy", "integrity", "keepalive", "compress"];
environment = "node";
defaultUserAgent = "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/131.0.0.0 Safari/537.36";
baseURL = null;
defaultHeaders = null;
defaultDebug = void 0;
mimicBrowser;
debug;
timeout;
retry;
queueOptions;
isCurl = { status: false, message: "Initializing" };
tempPath;
useCurl;
RETRYABLE_STATUS_CODES = /* @__PURE__ */ new Set([408, 429, 500, 502, 503, 504]);
rejectUnauthorized;
useSecureContext;
httpAgent;
httpsAgent;
enableCookieJar = true;
constructor(init) {
if (init && init.queueOptions?.enable) {
this.queue = new PQueue(init.queueOptions.options);
this.isQueueEnabled = true;
}
this.queueOptions = init?.queueOptions || null;
}
shouldRetry(status, isForbidden, isUnauthorized) {
if (status === 401 && isUnauthorized || status === 403 && isForbidden) return true;
return this.RETRYABLE_STATUS_CODES.has(status);
}
/**
* queueEnabled = true to enable PQueue instance for further http request, otherwise pass false to turn it off
*/
set queueEnabled(value) {
if (value && !this.isQueueEnabled) {
this.queue = new PQueue();
this.isQueueEnabled = true;
this.queueOptions = { ...this.queueOptions || {}, enable: value };
} else if (!value && this.isQueueEnabled) {
this.isQueueEnabled = false;
this.queueOptions = { ...this.queueOptions || {}, enable: value };
}
}
setQueueOptions(queueOptions) {
if (queueOptions.enable && !this.isQueueEnabled) {
this.queue = new PQueue(queueOptions.options);
this.isQueueEnabled = true;
} else if (!queueOptions.enable && this.isQueueEnabled) {
this.isQueueEnabled = false;
}
}
/**
* get the state of pQueue if its running or not.
*/
get queueEnabled() {
return this.isQueueEnabled;
}
/**
* Checks if the provided error is an instance of UniqhttError.
*
* @param error - The error object to check.
* @returns A boolean indicating whether the error is an instance of UniqhttError.
*/
isUniqhttError(error) {
return error?.name === "UniqhttError";
}
setCookies(cookies, url, startNew) {
if (startNew) this.jar.removeAllCookiesSync();
this.jar.setCookiesSync(cookies, url);
}
getCookies() {
return this.jar.cookies();
}
async get(input, config) {
return this.request(input, "GET", void 0, config);
}
// Clear all cookies for the current agent
clearCookies() {
this.jar?.removeAllCookiesSync();
}
async post(input, data, config) {
return this.request(input, "POST", data, config);
}
async postForm(input, data, config) {
let tempData = "";
if (data instanceof URLSearchParams) tempData = data.toString();
else if (data instanceof FormData) {
const keys = [];
for (const [key, value] of data.entries()) {
keys.push(`${key}=${value}`);
}
tempData = keys.join("&");
} else if (typeof data === "string") tempData = data;
else if (typeof data === "object") {
const params = new URLSearchParams();
for (const [key, value] of Object.entries(data)) {
params.append(key, value ? typeof value === "object" ? JSON.stringify(value) : value.toString() : "");
}
tempData = params.toString();
}
const headers = config?.headers ? config.headers instanceof Headers ? config.headers : new Headers(config.headers) : new Headers();
headers.set("Content-Type", "application/x-www-form-urlencoded");
return this.request(input, "POST", tempData, { ...config, headers, isFormData: true });
}
async postJson(input, data, config) {
let tempData = {};
if (typeof data === "string") {
tempData = this.parseJson(data);
if (!tempData) {
throw new Error("Invalid JSON string");
}
} else tempData = data;
const headers = config?.headers ? config.headers instanceof Headers ? config.headers : new Headers(config.headers) : new Headers();
headers.set("Content-Type", "application/json");
return this.request(input, "POST", JSON.stringify(tempData), { ...config, headers, isJson: true });
}
async put(input, data, config) {
return this.request(input, "PUT", data, config);
}
async putForm(input, data, config) {
let tempData = "";
if (data instanceof URLSearchParams) tempData = data.toString();
else if (data instanceof FormData) {
const keys = [];
for (const [key, value] of data.entries()) {
keys.push(`${key}=${value}`);
}
tempData = keys.join("&");
} else if (typeof data === "string") tempData = data;
else if (typeof data === "object") {
const params = new URLSearchParams();
for (const [key, value] of Object.entries(data)) {
params.append(key, value ? typeof value === "object" ? JSON.stringify(value) : value.toString() : "");
}
tempData = params.toString();
}
const headers = config?.headers ? config.headers instanceof Headers ? config.headers : new Headers(config.headers) : new Headers();
headers.set("Content-Type", "application/x-www-form-urlencoded");
return this.request(input, "PUT", tempData, { ...config, headers, isFormData: true });
}
async putJson(input, data, config) {
let tempData = {};
if (typeof data === "string") {
tempData = this.parseJson(data);
if (!tempData) {
throw new Error("Invalid JSON string");
}
} else tempData = data;
const headers = config?.headers ? config.headers instanceof Headers ? config.headers : new Headers(config.headers) : new Headers();
headers.set("Content-Type", "application/json");
return this.request(input, "PUT", JSON.stringify(tempData), { ...config, headers, isJson: true });
}
async patch(input, data, config) {
return this.request(input, "PATCH", data, config);
}
async patchForm(input, data, config) {
let tempData = "";
if (data instanceof URLSearchParams) tempData = data.toString();
else if (data instanceof FormData) {
const keys = [];
for (const [key, value] of data.entries()) {
keys.push(`${key}=${value}`);
}
tempData = keys.join("&");
} else if (typeof data === "string") tempData = data;
else if (typeof data === "object") {
const params = new URLSearchParams();
for (const [key, value] of Object.entries(data)) {
params.append(key, value ? typeof value === "object" ? JSON.stringify(value) : value.toString() : "");
}
tempData = params.toString();
}
const headers = config?.headers ? config.headers instanceof Headers ? config.headers : new Headers(config.headers) : new Headers();
headers.set("Content-Type", "application/x-www-form-urlencoded");
return this.request(input, "PATCH", tempData, { ...config, headers, isFormData: true });
}
async patchJson(input, data, config) {
let tempData = {};
if (typeof data === "string") {
tempData = this.parseJson(data);
if (!tempData) {
throw new Error("Invalid JSON string");
}
} else tempData = data;
const headers = config?.headers ? config.headers instanceof Headers ? config.headers : new Headers(config.headers) : new Headers();
headers.set("Content-Type", "application/json");
return this.request(input, "PATCH", JSON.stringify(tempData), { ...config, headers, isJson: true });
}
async delete(input, config) {
return this.request(input, "DELETE", void 0, config);
}
async head(input, config) {
return this.request(input, "HEAD", void 0, config);
}
async options(input, config) {
return this.request(input, "OPTIONS", void 0, config);
}
parseJson(data) {
try {
return JSON.parse(data);
} catch (e) {
return null;
}
}
async Error(response, message, config, urls, code) {
if (response instanceof Response) {
const contentType = response.headers.get("Content-Type");
const body = await this.parseResponseBody(response, contentType);
let headers = {};
if (response.headers instanceof Headers) {
response.headers.forEach((value, name) => {
if (name.toLowerCase() !== "set-cookie" && name.toLowerCase() !== "set-cookies") headers[name] = value;
});
}
return new UniqhttError2(message, response, body, code, headers, config || {}, urls);
} else if (response.headers) {
const body = response.body instanceof Buffer2 ? response?.body : response.body;
const headers = response.headers ?? {};
const contentType = response.contentType ?? null;
return new UniqhttError2(
message,
response,
body && contentType ? await this.parseResponseBody(response, contentType) : body ? body.toString("utf-8") : null,
code,
headers,
config || {},
urls
);
} else {
const res = {
status: response.status,
statusText: response.statusText,
url: response.url,
headers: response.headers ?? {},
body: null,
contentLength: 0,
cookies: {},
contentType: void 0
};
const headers = response.headers ?? {};
return new UniqhttError2(message, res, null, code, headers, config || {}, urls);
}
}
async formatResponse(response, finalUrl, isBuffer, config, downloadConfig, urls = [], cookies) {
const contentType = response instanceof Response ? response.headers.get("Content-Type") : response.contentType;
const body = !downloadConfig ? isBuffer ? response instanceof Response ? Buffer2.from(await response.arrayBuffer()) : response.body : await this.parseResponseBody(response, contentType) : null;
let headers = {};
let length;
if (response instanceof Response) {
const _length = response.headers.get("content-length");
response.headers.forEach((value, name) => {
headers[name] = value;
});
if (_length) {
length = parseInt(_length);
}
} else {
const _length = response.headers["content-length"];
if (_length) {
length = parseInt(_length);
}
headers = response.headers;
}
return {
urls,
contentLength: length,
data: body,
status: response.status,
statusText: response.statusText,
headers,
finalUrl,
cookies: this.jar?.parseResponseCookies(cookies),
config,
contentType,
...downloadConfig
};
}
async parseResponseBody(response, contentType) {
if (contentType && contentType.includes("application/json") || contentType && contentType.includes("application/dns-json") || contentType && contentType.includes("application/jsonrequest")) {
return this.parseJsonData(response instanceof Response ? await response.text() : response.body ? response.body : "");
}
const textRelatedTypes = [
"text",
"xml",
"xhtml+xml",
"html",
"php",
"javascript",
"ecmascript",
"x-javascript",
"typescript",
"x-httpd-php",
"x-php",
"x-python",
"x-python",
"x-ruby",
"x-ruby",
"x-sh",
"x-bash",
"x-java",
"x-java-source",
"x-c",
"x-c++",
"x-csrc",
"x-chdr",
"x-csharp",
"x-csh",
"x-go",
"x-go",
"x-scala",
"x-scala",
"x-rust",
"rust",
"x-swift",
"x-swift",
"x-kotlin",
"x-kotlin",
"x-perl",
"x-perl",
"x-lua",
"x-lua",
"x-haskell",
"x-haskell",
"x-sql",
"sql",
"css",
"csv",
"plain",
"markdown",
"x-markdown",
"x-latex",
"x-tex"
];
if (contentType && textRelatedTypes.some((type) => contentType.includes(type))) {
return response instanceof Response ? await response.text() : response.body ? response.body.toString("utf-8") : "";
}
return response instanceof Response ? await response.arrayBuffer() : response.body ? response.body : Buffer2.alloc(0);
}
parseJsonData(body) {
try {
if (typeof body === "string" && body.length < 3) return body;
return JSON.parse(typeof body === "string" ? body : body.toString("utf-8"));
} catch {
try {
return JSON.parse(Buffer2.from(typeof body === "string" ? body : body.toString("base64"), "base64").toString("utf-8"));
} catch {
return body.toString("utf-8");
}
}
}
async getHeaders(url) {
const response = await fetch(url, { method: "HEAD" });
return Object.fromEntries(response.headers.entries());
}
parseInputHeaders = (headers) => {
headers = headers instanceof Headers ? Array.from(headers.entries()).reduce((acc, [key, value]) => {
acc[key.toLowerCase()] = value;
return acc;
}, {}) : headers || {};
const defaultHeaders = new Headers(headers);
const __headers = this.defaultHeaders || {};
for (const [key, value] of Obj