class schlass
This commit is contained in:
parent
fd0eef5cbe
commit
e197050aa2
192
dist/index.js
vendored
192
dist/index.js
vendored
@ -3,106 +3,108 @@ import https from "https";
|
||||
import { URL } from "url";
|
||||
import querystring from "querystring";
|
||||
import zlib from "zlib";
|
||||
const parseCookies = (cookieHeader) => {
|
||||
const cookies = {};
|
||||
if (cookieHeader) {
|
||||
class fetch {
|
||||
cookies = {};
|
||||
constructor() { }
|
||||
parseCookies(cookieHeader) {
|
||||
if (!cookieHeader)
|
||||
return;
|
||||
cookieHeader.split(";").forEach(cookie => {
|
||||
const [key, value] = cookie.split("=");
|
||||
if (key && value)
|
||||
cookies[key.trim()] = value.trim();
|
||||
this.cookies[key.trim()] = value.trim();
|
||||
});
|
||||
}
|
||||
return cookies;
|
||||
};
|
||||
const formatCookies = (cookies) => Object.entries(cookies).map(([key, value]) => `${key}=${value}`).join("; ");
|
||||
const addCookiesToRequest = (cookies, headers) => {
|
||||
headers["Cookie"] = formatCookies(cookies);
|
||||
};
|
||||
const decompress = (data, encoding) => {
|
||||
return encoding === "br" ? zlib.brotliDecompressSync(data) :
|
||||
encoding === "gzip" ? zlib.gunzipSync(data) :
|
||||
encoding === "deflate" ? zlib.inflateSync(data) :
|
||||
data;
|
||||
};
|
||||
const readData = (res, mode) => new Promise((resolve, reject) => {
|
||||
const chunks = [];
|
||||
res
|
||||
.on("data", chunk => chunks.push(chunk))
|
||||
.on("end", () => {
|
||||
try {
|
||||
const data = decompress(Buffer.concat(chunks), res.headers["content-encoding"]);
|
||||
resolve((mode === "json" ? JSON.parse(data.toString("utf8")) :
|
||||
mode === "buffer" ? data :
|
||||
mode === "arraybuffer" ? new Uint8Array(data).buffer :
|
||||
data.toString("utf8")));
|
||||
}
|
||||
catch (err) {
|
||||
reject(err);
|
||||
}
|
||||
})
|
||||
.on("error", reject);
|
||||
});
|
||||
const fetch = async (urlString, options = {}, redirectCount = 0, cookies = {}) => {
|
||||
const { protocol, hostname, pathname, search, port } = new URL(urlString);
|
||||
options.followRedirects = options.followRedirects ?? true;
|
||||
if (options.signal?.aborted)
|
||||
throw new Error("Request aborted");
|
||||
const body = options.method === "POST" && options.body
|
||||
? options.headers?.["Content-Type"] === "application/json"
|
||||
? JSON.stringify(options.body)
|
||||
: querystring.stringify(options.body)
|
||||
: null;
|
||||
const requestOptions = {
|
||||
hostname,
|
||||
port: port || (protocol === "https:" ? 443 : 80),
|
||||
path: pathname + search,
|
||||
method: options.method || "GET",
|
||||
headers: {
|
||||
...options.headers,
|
||||
...(body ? { "Content-Length": Buffer.byteLength(body) } : {}),
|
||||
"Accept-Encoding": "br, gzip, deflate"
|
||||
}
|
||||
};
|
||||
addCookiesToRequest(cookies, requestOptions.headers);
|
||||
return new Promise((resolve, reject) => {
|
||||
const requester = protocol === "https:" ? https : http;
|
||||
const req = requester.request(requestOptions, res => {
|
||||
if (res.headers["set-cookie"])
|
||||
cookies = parseCookies(res.headers["set-cookie"].join("; "));
|
||||
if (options.followRedirects && res.statusCode && res.statusCode >= 300 && res.statusCode < 400 && res.headers.location) {
|
||||
if (redirectCount >= (options.maxRedirects || 5))
|
||||
return reject(new Error("Max redirects exceeded"));
|
||||
if (!res.headers.location)
|
||||
return reject(new Error("Redirect location not provided"));
|
||||
const nextUrl = new URL(res.headers.location, urlString);
|
||||
return resolve(fetch(nextUrl.toString(), options, redirectCount + 1, cookies));
|
||||
formatCookies() {
|
||||
return Object.entries(this.cookies)
|
||||
.map(([key, value]) => `${key}=${value}`)
|
||||
.join("; ");
|
||||
}
|
||||
decompress(data, encoding) {
|
||||
return encoding === "br" ? zlib.brotliDecompressSync(data) :
|
||||
encoding === "gzip" ? zlib.gunzipSync(data) :
|
||||
encoding === "deflate" ? zlib.inflateSync(data) :
|
||||
data;
|
||||
}
|
||||
readData(res, mode) {
|
||||
return new Promise((resolve, reject) => {
|
||||
const chunks = [];
|
||||
res
|
||||
.on("data", chunk => chunks.push(chunk))
|
||||
.on("end", () => {
|
||||
try {
|
||||
const data = this.decompress(Buffer.concat(chunks), res.headers["content-encoding"]);
|
||||
resolve(mode === "json" ? JSON.parse(data.toString("utf8")) :
|
||||
mode === "buffer" ? data :
|
||||
mode === "arraybuffer" ? new Uint8Array(data).buffer :
|
||||
data.toString("utf8"));
|
||||
}
|
||||
catch (err) {
|
||||
reject(err);
|
||||
}
|
||||
})
|
||||
.on("error", reject);
|
||||
});
|
||||
}
|
||||
async fetch(urlString, options = {}, redirectCount = 0) {
|
||||
options.followRedirects = options.followRedirects ?? true;
|
||||
const { protocol, hostname, pathname, search, port } = new URL(urlString);
|
||||
const body = options.method === "POST" && options.body
|
||||
? options.headers?.["Content-Type"] === "application/json"
|
||||
? JSON.stringify(options.body)
|
||||
: querystring.stringify(options.body)
|
||||
: null;
|
||||
const requestOptions = {
|
||||
hostname,
|
||||
port: port || (protocol === "https:" ? 443 : 80),
|
||||
path: pathname + search,
|
||||
method: options.method || "GET",
|
||||
headers: {
|
||||
...options.headers,
|
||||
...(body ? { "Content-Length": Buffer.byteLength(body) } : {}),
|
||||
"Cookie": this.formatCookies(),
|
||||
"Accept-Encoding": "br, gzip, deflate",
|
||||
}
|
||||
if (res.statusCode && (res.statusCode < 200 || res.statusCode >= 300))
|
||||
return reject(new Error(`Request failed with status code ${res.statusCode}`));
|
||||
resolve({
|
||||
body: res,
|
||||
headers: res.headers,
|
||||
cookies: cookies,
|
||||
text: () => readData(res, "text"),
|
||||
json: () => readData(res, "json"),
|
||||
buffer: () => readData(res, "buffer"),
|
||||
arrayBuffer: () => readData(res, "arraybuffer")
|
||||
};
|
||||
return new Promise((resolve, reject) => {
|
||||
const requester = protocol === "https:" ? https : http;
|
||||
const req = requester.request(requestOptions, res => {
|
||||
this.parseCookies(res.headers["set-cookie"]?.join("; "));
|
||||
if (options.followRedirects && res.statusCode && res.statusCode >= 300 && res.statusCode < 400 && res.headers.location) {
|
||||
req.destroy();
|
||||
if (redirectCount >= (options.maxRedirects || 5))
|
||||
return reject(new Error("Max redirects exceeded"));
|
||||
if (!res.headers.location)
|
||||
return reject(new Error("Redirect location not provided"));
|
||||
const nextUrl = new URL(res.headers.location, urlString);
|
||||
return resolve(this.fetch(nextUrl.toString(), options, redirectCount + 1));
|
||||
}
|
||||
return resolve({
|
||||
body: res,
|
||||
headers: res.headers,
|
||||
cookies: this.cookies,
|
||||
text: () => this.readData(res, "text"),
|
||||
json: () => this.readData(res, "json"),
|
||||
buffer: () => this.readData(res, "buffer"),
|
||||
arrayBuffer: () => this.readData(res, "arraybuffer"),
|
||||
});
|
||||
});
|
||||
});
|
||||
req.setTimeout(options.timeout || 5e3, () => {
|
||||
req.destroy();
|
||||
reject(new Error("Request timed out"));
|
||||
});
|
||||
req.on("error", reject);
|
||||
if (options.signal) {
|
||||
options.signal.addEventListener("abort", () => {
|
||||
req.destroy(new Error("Request aborted"));
|
||||
reject(new Error("Request aborted"));
|
||||
req.setTimeout(options.timeout || 5e3, () => {
|
||||
req.destroy();
|
||||
reject(new Error("Request timed out"));
|
||||
});
|
||||
}
|
||||
if (body)
|
||||
req.write(body);
|
||||
req.end();
|
||||
});
|
||||
};
|
||||
export default fetch;
|
||||
req.on("error", reject);
|
||||
if (options.signal) {
|
||||
options.signal.addEventListener("abort", () => {
|
||||
req.destroy(new Error("Request aborted"));
|
||||
reject(new Error("Request aborted"));
|
||||
});
|
||||
}
|
||||
if (body)
|
||||
req.write(body);
|
||||
req.end();
|
||||
});
|
||||
}
|
||||
}
|
||||
const inst = new fetch();
|
||||
export default inst.fetch.bind(inst);
|
||||
|
266
src/index.ts
266
src/index.ts
@ -4,162 +4,150 @@ import { URL } from "url";
|
||||
import querystring from "querystring";
|
||||
import zlib from "zlib";
|
||||
|
||||
type CookieStorage = Record<string, string>;
|
||||
class fetch {
|
||||
private cookies: Record<string, string> = {};
|
||||
|
||||
type ResponseData = {
|
||||
body: http.IncomingMessage;
|
||||
headers: http.IncomingHttpHeaders;
|
||||
cookies: CookieStorage;
|
||||
text: () => Promise<string>;
|
||||
json: () => Promise<JSON>;
|
||||
buffer: () => Promise<Buffer>;
|
||||
arrayBuffer: () => Promise<ArrayBuffer>;
|
||||
};
|
||||
constructor() { }
|
||||
|
||||
interface ExtendedRequestOptions extends http.RequestOptions {
|
||||
body?: any;
|
||||
timeout?: number;
|
||||
followRedirects?: boolean;
|
||||
maxRedirects?: number;
|
||||
signal?: AbortSignal;
|
||||
}
|
||||
private parseCookies(cookieHeader: string | undefined): void {
|
||||
if(!cookieHeader)
|
||||
return;
|
||||
|
||||
const parseCookies = (cookieHeader: string | undefined): CookieStorage => {
|
||||
const cookies: CookieStorage = {};
|
||||
if(cookieHeader) {
|
||||
cookieHeader.split(";").forEach(cookie => {
|
||||
const [key, value] = cookie.split("=");
|
||||
if(key && value)
|
||||
cookies[key.trim()] = value.trim();
|
||||
this.cookies[key.trim()] = value.trim();
|
||||
});
|
||||
}
|
||||
return cookies;
|
||||
};
|
||||
|
||||
const formatCookies = (cookies: CookieStorage): string =>
|
||||
Object.entries(cookies).map(([key, value]) => `${key}=${value}`).join("; ");
|
||||
private formatCookies(): string {
|
||||
return Object.entries(this.cookies)
|
||||
.map(([key, value]) => `${key}=${value}`)
|
||||
.join("; ");
|
||||
}
|
||||
|
||||
const addCookiesToRequest = (cookies: CookieStorage, headers: http.OutgoingHttpHeaders): void => {
|
||||
headers["Cookie"] = formatCookies(cookies);
|
||||
};
|
||||
private decompress(
|
||||
data: Buffer,
|
||||
encoding: string | undefined
|
||||
): Buffer {
|
||||
return encoding === "br" ? zlib.brotliDecompressSync(data) :
|
||||
encoding === "gzip" ? zlib.gunzipSync(data) :
|
||||
encoding === "deflate" ? zlib.inflateSync(data) :
|
||||
data;
|
||||
}
|
||||
|
||||
const decompress = (
|
||||
data: Buffer,
|
||||
encoding: string | undefined
|
||||
): Buffer => {
|
||||
return encoding === "br" ? zlib.brotliDecompressSync(data) :
|
||||
encoding === "gzip" ? zlib.gunzipSync(data) :
|
||||
encoding === "deflate" ? zlib.inflateSync(data) :
|
||||
data;
|
||||
};
|
||||
private readData<T>(
|
||||
res: http.IncomingMessage,
|
||||
mode: "text" | "json" | "buffer" | "arraybuffer"
|
||||
): Promise<T> {
|
||||
return new Promise((resolve, reject) => {
|
||||
const chunks: Buffer[] = [];
|
||||
res
|
||||
.on("data", chunk => chunks.push(chunk))
|
||||
.on("end", () => {
|
||||
try {
|
||||
const data = this.decompress(Buffer.concat(chunks), res.headers["content-encoding"]);
|
||||
resolve(
|
||||
mode === "json" ? JSON.parse(data.toString("utf8")) :
|
||||
mode === "buffer" ? data :
|
||||
mode === "arraybuffer" ? new Uint8Array(data).buffer :
|
||||
data.toString("utf8")
|
||||
);
|
||||
}
|
||||
catch(err: any) {
|
||||
reject(err);
|
||||
}
|
||||
})
|
||||
.on("error", reject);
|
||||
});
|
||||
}
|
||||
|
||||
const readData = <T>(
|
||||
res: http.IncomingMessage,
|
||||
mode: "text" | "json" | "buffer" | "arraybuffer"
|
||||
): Promise<T> =>
|
||||
new Promise((resolve, reject) => {
|
||||
const chunks: Buffer[] = [];
|
||||
res
|
||||
.on("data", chunk => chunks.push(chunk))
|
||||
.on("end", () => {
|
||||
try {
|
||||
const data = decompress(Buffer.concat(chunks), res.headers["content-encoding"]);
|
||||
resolve((
|
||||
mode === "json" ? JSON.parse(data.toString("utf8")) :
|
||||
mode === "buffer" ? data :
|
||||
mode === "arraybuffer" ? new Uint8Array(data).buffer :
|
||||
data.toString("utf8")
|
||||
)) as T;
|
||||
async fetch(
|
||||
urlString: string,
|
||||
options: http.RequestOptions & {
|
||||
body?: any;
|
||||
timeout?: number;
|
||||
followRedirects?: boolean;
|
||||
maxRedirects?: number;
|
||||
signal?: AbortSignal;
|
||||
} = {},
|
||||
redirectCount = 0
|
||||
): Promise<{
|
||||
body: http.IncomingMessage;
|
||||
headers: http.IncomingHttpHeaders;
|
||||
cookies: Record<string, string>;
|
||||
text: () => Promise<string>;
|
||||
json: () => Promise<JSON>;
|
||||
buffer: () => Promise<Buffer>;
|
||||
arrayBuffer: () => Promise<ArrayBuffer>;
|
||||
}> {
|
||||
options.followRedirects = options.followRedirects ?? true;
|
||||
|
||||
const { protocol, hostname, pathname, search, port } = new URL(urlString);
|
||||
const body = options.method === "POST" && options.body
|
||||
? options.headers?.["Content-Type"] === "application/json"
|
||||
? JSON.stringify(options.body)
|
||||
: querystring.stringify(options.body as querystring.ParsedUrlQueryInput)
|
||||
: null;
|
||||
const requestOptions: http.RequestOptions = {
|
||||
hostname,
|
||||
port: port || (protocol === "https:" ? 443 : 80),
|
||||
path: pathname + search,
|
||||
method: options.method || "GET",
|
||||
headers: {
|
||||
...options.headers,
|
||||
...(body ? { "Content-Length": Buffer.byteLength(body) } : {}),
|
||||
"Cookie": this.formatCookies(),
|
||||
"Accept-Encoding": "br, gzip, deflate",
|
||||
}
|
||||
};
|
||||
|
||||
return new Promise((resolve, reject) => {
|
||||
const requester = protocol === "https:" ? https : http;
|
||||
const req = requester.request(requestOptions, res => {
|
||||
this.parseCookies(res.headers["set-cookie"]?.join("; "));
|
||||
if(options.followRedirects && res.statusCode && res.statusCode >= 300 && res.statusCode < 400 && res.headers.location) {
|
||||
req.destroy();
|
||||
if(redirectCount >= (options.maxRedirects || 5))
|
||||
return reject(new Error("Max redirects exceeded"));
|
||||
|
||||
if(!res.headers.location)
|
||||
return reject(new Error("Redirect location not provided"));
|
||||
|
||||
const nextUrl = new URL(res.headers.location, urlString);
|
||||
return resolve(this.fetch(nextUrl.toString(), options, redirectCount + 1));
|
||||
}
|
||||
catch(err: any) {
|
||||
reject(err);
|
||||
}
|
||||
})
|
||||
.on("error", reject);
|
||||
});
|
||||
return resolve({
|
||||
body: res,
|
||||
headers: res.headers,
|
||||
cookies: this.cookies,
|
||||
text: () => this.readData<string>(res, "text"),
|
||||
json: () => this.readData<JSON>(res, "json"),
|
||||
buffer: () => this.readData<Buffer>(res, "buffer"),
|
||||
arrayBuffer: () => this.readData<ArrayBuffer>(res, "arraybuffer"),
|
||||
});
|
||||
});
|
||||
|
||||
const fetch = async (
|
||||
urlString: string,
|
||||
options: ExtendedRequestOptions = {},
|
||||
redirectCount: number = 0,
|
||||
cookies: CookieStorage = {}
|
||||
): Promise<ResponseData> => {
|
||||
const { protocol, hostname, pathname, search, port } = new URL(urlString);
|
||||
options.followRedirects = options.followRedirects ?? true;
|
||||
req.setTimeout(options.timeout || 5e3, () => {
|
||||
req.destroy();
|
||||
reject(new Error("Request timed out"));
|
||||
});
|
||||
|
||||
if(options.signal?.aborted)
|
||||
throw new Error("Request aborted");
|
||||
req.on("error", reject);
|
||||
|
||||
const body =
|
||||
options.method === "POST" && options.body
|
||||
? options.headers?.["Content-Type"] === "application/json"
|
||||
? JSON.stringify(options.body)
|
||||
: querystring.stringify(options.body as querystring.ParsedUrlQueryInput)
|
||||
: null;
|
||||
|
||||
const requestOptions: http.RequestOptions = {
|
||||
hostname,
|
||||
port: port || (protocol === "https:" ? 443 : 80),
|
||||
path: pathname + search,
|
||||
method: options.method || "GET",
|
||||
headers: {
|
||||
...options.headers,
|
||||
...(body ? { "Content-Length": Buffer.byteLength(body) } : {}),
|
||||
"Accept-Encoding": "br, gzip, deflate"
|
||||
}
|
||||
};
|
||||
|
||||
addCookiesToRequest(cookies, requestOptions.headers!);
|
||||
|
||||
return new Promise((resolve, reject) => {
|
||||
const requester = protocol === "https:" ? https : http;
|
||||
const req = requester.request(requestOptions, res => {
|
||||
if(res.headers["set-cookie"])
|
||||
cookies = parseCookies(res.headers["set-cookie"].join("; "));
|
||||
|
||||
if(options.followRedirects && res.statusCode && res.statusCode >= 300 && res.statusCode < 400 && res.headers.location) {
|
||||
if(redirectCount >= (options.maxRedirects || 5))
|
||||
return reject(new Error("Max redirects exceeded"));
|
||||
|
||||
if(!res.headers.location)
|
||||
return reject(new Error("Redirect location not provided"));
|
||||
|
||||
const nextUrl = new URL(res.headers.location, urlString);
|
||||
return resolve(fetch(nextUrl.toString(), options, redirectCount + 1, cookies));
|
||||
if(options.signal) {
|
||||
options.signal.addEventListener("abort", () => {
|
||||
req.destroy(new Error("Request aborted"));
|
||||
reject(new Error("Request aborted"));
|
||||
});
|
||||
}
|
||||
|
||||
if(res.statusCode && (res.statusCode < 200 || res.statusCode >= 300))
|
||||
return reject(new Error(`Request failed with status code ${res.statusCode}`));
|
||||
|
||||
resolve({
|
||||
body: res,
|
||||
headers: res.headers,
|
||||
cookies: cookies,
|
||||
text: () => readData<string>(res, "text"),
|
||||
json: () => readData<JSON>(res, "json"),
|
||||
buffer: () => readData<Buffer>(res, "buffer"),
|
||||
arrayBuffer: () => readData<ArrayBuffer>(res, "arraybuffer")
|
||||
});
|
||||
if(body)
|
||||
req.write(body);
|
||||
req.end();
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
req.setTimeout(options.timeout || 5e3, () => {
|
||||
req.destroy();
|
||||
reject(new Error("Request timed out"));
|
||||
});
|
||||
|
||||
req.on("error", reject);
|
||||
|
||||
if(options.signal) {
|
||||
options.signal.addEventListener("abort", () => {
|
||||
req.destroy(new Error("Request aborted"));
|
||||
reject(new Error("Request aborted"));
|
||||
});
|
||||
}
|
||||
|
||||
if(body)
|
||||
req.write(body);
|
||||
req.end();
|
||||
});
|
||||
};
|
||||
|
||||
export default fetch;
|
||||
const inst = new fetch();
|
||||
export default inst.fetch.bind(inst);
|
||||
|
Loading…
x
Reference in New Issue
Block a user