feat: add support for request redirection and abort signal handling

This commit is contained in:
Flummi 2025-03-18 13:58:57 +01:00
parent 3f1effbb4a
commit 65f1707358
2 changed files with 40 additions and 3 deletions

16
dist/index.js vendored
View File

@ -27,8 +27,10 @@ const readData = (res, mode) => new Promise((resolve, reject) => {
}) })
.on("error", reject); .on("error", reject);
}); });
export default async (urlString, options = {}) => { const fetch = async (urlString, options = {}, redirectCount = 0) => {
const { protocol, hostname, pathname, search, port } = new URL(urlString); const { protocol, hostname, pathname, search, port } = new URL(urlString);
if (options.signal?.aborted)
throw new Error("Request aborted");
const body = options.method === "POST" && options.body const body = options.method === "POST" && options.body
? options.headers?.["Content-Type"] === "application/json" ? options.headers?.["Content-Type"] === "application/json"
? JSON.stringify(options.body) ? JSON.stringify(options.body)
@ -48,6 +50,11 @@ export default async (urlString, options = {}) => {
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 (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"));
return resolve(fetch(new URL(res.headers.location, urlString).toString(), options, redirectCount + 1));
}
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({
@ -64,8 +71,15 @@ export default async (urlString, options = {}) => {
reject(new Error("Request timed out")); reject(new Error("Request timed out"));
}); });
req.on("error", reject); req.on("error", reject);
if (options.signal) {
options.signal.addEventListener("abort", () => {
req.destroy(new Error("Request aborted"));
reject(new Error("Request aborted"));
});
}
if (body) if (body)
req.write(body); req.write(body);
req.end(); req.end();
}); });
}; };
export default fetch;

View File

@ -16,6 +16,9 @@ type ResponseData = {
interface ExtendedRequestOptions extends http.RequestOptions { interface ExtendedRequestOptions extends http.RequestOptions {
body?: any; body?: any;
timeout?: number; timeout?: number;
followRedirects?: boolean;
maxRedirects?: number;
signal?: AbortSignal;
} }
const decompress = ( const decompress = (
@ -53,12 +56,16 @@ const readData = <T>(
.on("error", reject); .on("error", reject);
}); });
export default async ( const fetch = async (
urlString: string, urlString: string,
options: ExtendedRequestOptions = {} options: ExtendedRequestOptions = {},
redirectCount: number = 0
): Promise<ResponseData> => { ): Promise<ResponseData> => {
const { protocol, hostname, pathname, search, port } = new URL(urlString); const { protocol, hostname, pathname, search, port } = new URL(urlString);
if(options.signal?.aborted)
throw new Error("Request aborted");
const body = const body =
options.method === "POST" && options.body options.method === "POST" && options.body
? options.headers?.["Content-Type"] === "application/json" ? options.headers?.["Content-Type"] === "application/json"
@ -81,6 +88,12 @@ export default async (
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(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"));
return resolve(fetch(new URL(res.headers.location, urlString).toString(), options, redirectCount + 1));
}
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({
@ -99,8 +112,18 @@ export default async (
}); });
req.on("error", reject); req.on("error", reject);
if(options.signal) {
options.signal.addEventListener("abort", () => {
req.destroy(new Error("Request aborted"));
reject(new Error("Request aborted"));
});
}
if(body) if(body)
req.write(body); req.write(body);
req.end(); req.end();
}); });
}; };
export default fetch;