From 65f1707358fb7ecb07966879e75159db442bd810 Mon Sep 17 00:00:00 2001 From: Flummi Date: Tue, 18 Mar 2025 13:58:57 +0100 Subject: [PATCH] feat: add support for request redirection and abort signal handling --- dist/index.js | 16 +++++++++++++++- src/index.ts | 27 +++++++++++++++++++++++++-- 2 files changed, 40 insertions(+), 3 deletions(-) diff --git a/dist/index.js b/dist/index.js index 57f0af0..fa911b5 100644 --- a/dist/index.js +++ b/dist/index.js @@ -27,8 +27,10 @@ const readData = (res, mode) => new Promise((resolve, 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); + 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) @@ -48,6 +50,11 @@ export default async (urlString, options = {}) => { return new Promise((resolve, reject) => { const requester = protocol === "https:" ? https : http; 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)) return reject(new Error(`Request failed with status code ${res.statusCode}`)); resolve({ @@ -64,8 +71,15 @@ export default async (urlString, options = {}) => { 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; diff --git a/src/index.ts b/src/index.ts index 9f736e4..07aabb3 100644 --- a/src/index.ts +++ b/src/index.ts @@ -16,6 +16,9 @@ type ResponseData = { interface ExtendedRequestOptions extends http.RequestOptions { body?: any; timeout?: number; + followRedirects?: boolean; + maxRedirects?: number; + signal?: AbortSignal; } const decompress = ( @@ -53,12 +56,16 @@ const readData = ( .on("error", reject); }); -export default async ( +const fetch = async ( urlString: string, - options: ExtendedRequestOptions = {} + options: ExtendedRequestOptions = {}, + redirectCount: number = 0 ): Promise => { 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 ? options.headers?.["Content-Type"] === "application/json" @@ -81,6 +88,12 @@ export default async ( return new Promise((resolve, reject) => { const requester = protocol === "https:" ? https : http; 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)) return reject(new Error(`Request failed with status code ${res.statusCode}`)); resolve({ @@ -99,8 +112,18 @@ export default async ( }); 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;