flumm-fetch/dist/index.js

86 lines
3.4 KiB
JavaScript

import http from "http";
import https from "https";
import { URL } from "url";
import querystring from "querystring";
import zlib from "zlib";
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) => {
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)
: 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"
}
};
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({
body: res,
headers: res.headers,
text: () => readData(res, "text"),
json: () => readData(res, "json"),
buffer: () => readData(res, "buffer"),
arrayBuffer: () => 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"));
});
}
if (body)
req.write(body);
req.end();
});
};
export default fetch;