feat: enhance fetch function with cookie handling
This commit is contained in:
parent
65f1707358
commit
fd0eef5cbe
27
dist/index.js
vendored
27
dist/index.js
vendored
@ -3,6 +3,21 @@ import https from "https";
|
|||||||
import { URL } from "url";
|
import { URL } from "url";
|
||||||
import querystring from "querystring";
|
import querystring from "querystring";
|
||||||
import zlib from "zlib";
|
import zlib from "zlib";
|
||||||
|
const parseCookies = (cookieHeader) => {
|
||||||
|
const cookies = {};
|
||||||
|
if (cookieHeader) {
|
||||||
|
cookieHeader.split(";").forEach(cookie => {
|
||||||
|
const [key, value] = cookie.split("=");
|
||||||
|
if (key && value)
|
||||||
|
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) => {
|
const decompress = (data, encoding) => {
|
||||||
return encoding === "br" ? zlib.brotliDecompressSync(data) :
|
return encoding === "br" ? zlib.brotliDecompressSync(data) :
|
||||||
encoding === "gzip" ? zlib.gunzipSync(data) :
|
encoding === "gzip" ? zlib.gunzipSync(data) :
|
||||||
@ -27,8 +42,9 @@ const readData = (res, mode) => new Promise((resolve, reject) => {
|
|||||||
})
|
})
|
||||||
.on("error", reject);
|
.on("error", reject);
|
||||||
});
|
});
|
||||||
const fetch = async (urlString, options = {}, redirectCount = 0) => {
|
const fetch = async (urlString, options = {}, redirectCount = 0, cookies = {}) => {
|
||||||
const { protocol, hostname, pathname, search, port } = new URL(urlString);
|
const { protocol, hostname, pathname, search, port } = new URL(urlString);
|
||||||
|
options.followRedirects = options.followRedirects ?? true;
|
||||||
if (options.signal?.aborted)
|
if (options.signal?.aborted)
|
||||||
throw new Error("Request aborted");
|
throw new Error("Request aborted");
|
||||||
const body = options.method === "POST" && options.body
|
const body = options.method === "POST" && options.body
|
||||||
@ -47,19 +63,26 @@ const fetch = async (urlString, options = {}, redirectCount = 0) => {
|
|||||||
"Accept-Encoding": "br, gzip, deflate"
|
"Accept-Encoding": "br, gzip, deflate"
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
addCookiesToRequest(cookies, requestOptions.headers);
|
||||||
return new Promise((resolve, reject) => {
|
return new Promise((resolve, reject) => {
|
||||||
const requester = protocol === "https:" ? https : http;
|
const requester = protocol === "https:" ? https : http;
|
||||||
const req = requester.request(requestOptions, res => {
|
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 (options.followRedirects && res.statusCode && res.statusCode >= 300 && res.statusCode < 400 && res.headers.location) {
|
||||||
if (redirectCount >= (options.maxRedirects || 5))
|
if (redirectCount >= (options.maxRedirects || 5))
|
||||||
return reject(new Error("Max redirects exceeded"));
|
return reject(new Error("Max redirects exceeded"));
|
||||||
return resolve(fetch(new URL(res.headers.location, urlString).toString(), options, redirectCount + 1));
|
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 (res.statusCode && (res.statusCode < 200 || res.statusCode >= 300))
|
if (res.statusCode && (res.statusCode < 200 || res.statusCode >= 300))
|
||||||
return reject(new Error(`Request failed with status code ${res.statusCode}`));
|
return reject(new Error(`Request failed with status code ${res.statusCode}`));
|
||||||
resolve({
|
resolve({
|
||||||
body: res,
|
body: res,
|
||||||
headers: res.headers,
|
headers: res.headers,
|
||||||
|
cookies: cookies,
|
||||||
text: () => readData(res, "text"),
|
text: () => readData(res, "text"),
|
||||||
json: () => readData(res, "json"),
|
json: () => readData(res, "json"),
|
||||||
buffer: () => readData(res, "buffer"),
|
buffer: () => readData(res, "buffer"),
|
||||||
|
40
src/index.ts
40
src/index.ts
@ -4,9 +4,12 @@ import { URL } from "url";
|
|||||||
import querystring from "querystring";
|
import querystring from "querystring";
|
||||||
import zlib from "zlib";
|
import zlib from "zlib";
|
||||||
|
|
||||||
|
type CookieStorage = Record<string, string>;
|
||||||
|
|
||||||
type ResponseData = {
|
type ResponseData = {
|
||||||
body: http.IncomingMessage;
|
body: http.IncomingMessage;
|
||||||
headers: http.IncomingHttpHeaders;
|
headers: http.IncomingHttpHeaders;
|
||||||
|
cookies: CookieStorage;
|
||||||
text: () => Promise<string>;
|
text: () => Promise<string>;
|
||||||
json: () => Promise<JSON>;
|
json: () => Promise<JSON>;
|
||||||
buffer: () => Promise<Buffer>;
|
buffer: () => Promise<Buffer>;
|
||||||
@ -21,6 +24,25 @@ interface ExtendedRequestOptions extends http.RequestOptions {
|
|||||||
signal?: AbortSignal;
|
signal?: AbortSignal;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
return cookies;
|
||||||
|
};
|
||||||
|
|
||||||
|
const formatCookies = (cookies: CookieStorage): string =>
|
||||||
|
Object.entries(cookies).map(([key, value]) => `${key}=${value}`).join("; ");
|
||||||
|
|
||||||
|
const addCookiesToRequest = (cookies: CookieStorage, headers: http.OutgoingHttpHeaders): void => {
|
||||||
|
headers["Cookie"] = formatCookies(cookies);
|
||||||
|
};
|
||||||
|
|
||||||
const decompress = (
|
const decompress = (
|
||||||
data: Buffer,
|
data: Buffer,
|
||||||
encoding: string | undefined
|
encoding: string | undefined
|
||||||
@ -59,9 +81,11 @@ const readData = <T>(
|
|||||||
const fetch = async (
|
const fetch = async (
|
||||||
urlString: string,
|
urlString: string,
|
||||||
options: ExtendedRequestOptions = {},
|
options: ExtendedRequestOptions = {},
|
||||||
redirectCount: number = 0
|
redirectCount: number = 0,
|
||||||
|
cookies: CookieStorage = {}
|
||||||
): Promise<ResponseData> => {
|
): Promise<ResponseData> => {
|
||||||
const { protocol, hostname, pathname, search, port } = new URL(urlString);
|
const { protocol, hostname, pathname, search, port } = new URL(urlString);
|
||||||
|
options.followRedirects = options.followRedirects ?? true;
|
||||||
|
|
||||||
if(options.signal?.aborted)
|
if(options.signal?.aborted)
|
||||||
throw new Error("Request aborted");
|
throw new Error("Request aborted");
|
||||||
@ -85,20 +109,32 @@ const fetch = async (
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
addCookiesToRequest(cookies, requestOptions.headers!);
|
||||||
|
|
||||||
return new Promise((resolve, reject) => {
|
return new Promise((resolve, reject) => {
|
||||||
const requester = protocol === "https:" ? https : http;
|
const requester = protocol === "https:" ? https : http;
|
||||||
const req = requester.request(requestOptions, res => {
|
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(options.followRedirects && res.statusCode && res.statusCode >= 300 && res.statusCode < 400 && res.headers.location) {
|
||||||
if(redirectCount >= (options.maxRedirects || 5))
|
if(redirectCount >= (options.maxRedirects || 5))
|
||||||
return reject(new Error("Max redirects exceeded"));
|
return reject(new Error("Max redirects exceeded"));
|
||||||
return resolve(fetch(new URL(res.headers.location, urlString).toString(), options, redirectCount + 1));
|
|
||||||
|
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(res.statusCode && (res.statusCode < 200 || res.statusCode >= 300))
|
if(res.statusCode && (res.statusCode < 200 || res.statusCode >= 300))
|
||||||
return reject(new Error(`Request failed with status code ${res.statusCode}`));
|
return reject(new Error(`Request failed with status code ${res.statusCode}`));
|
||||||
|
|
||||||
resolve({
|
resolve({
|
||||||
body: res,
|
body: res,
|
||||||
headers: res.headers,
|
headers: res.headers,
|
||||||
|
cookies: cookies,
|
||||||
text: () => readData<string>(res, "text"),
|
text: () => readData<string>(res, "text"),
|
||||||
json: () => readData<JSON>(res, "json"),
|
json: () => readData<JSON>(res, "json"),
|
||||||
buffer: () => readData<Buffer>(res, "buffer"),
|
buffer: () => readData<Buffer>(res, "buffer"),
|
||||||
|
Loading…
x
Reference in New Issue
Block a user