@waiting/fetch
Version:
HTTP fetch API for browser and Node.js. Handle 302/303 redirect correctly on Node.js
320 lines • 9.97 kB
JavaScript
import { ReadStream } from 'node:fs';
import QueryString from 'qs';
import { FormData, Headers } from 'undici';
import { initialOptions } from './config.js';
import { ContentTypeList } from './types.js';
/** Update initialFetchOptions */
export function setGlobalRequestOptions(options) {
for (const [key, value] of Object.entries(options)) {
Object.defineProperty(initialOptions, key, {
configurable: true,
enumerable: true,
writable: true,
value,
});
}
}
/** Get copy of initialFetchOptions */
export function getGlobalRequestOptions() {
return { ...initialOptions };
}
export function buildQueryString(url, data) {
/* istanbul ignore else */
if (data && typeof data === 'object' && Object.keys(data).length) {
const ps = QueryString.stringify(data);
return url.includes('?') ? `${url}&${ps}` : `${url}?${ps}`;
}
return url;
}
/** Split FetchOptions object to RequestInit and Args */
export function splitInitArgs(options) {
const opts = { ...options };
const args = {};
/* istanbul ignore else */
if (typeof opts.cookies !== 'undefined') {
args.cookies = opts.cookies;
}
delete opts.cookies;
if (opts.abortController && typeof opts.abortController.abort === 'function') {
args.abortController = opts.abortController;
}
delete opts.abortController;
/* istanbul ignore else */
if (typeof opts.contentType !== 'undefined') {
args.contentType = opts.contentType;
}
delete opts.contentType;
if (typeof opts.data !== 'undefined') {
args.data = opts.data;
}
delete opts.data;
if (opts.dataType) {
args.dataType = opts.dataType;
}
delete opts.dataType;
/* c8 ignore next */
if (typeof opts.keepRedirectCookies !== 'undefined') {
args.keepRedirectCookies = !!opts.keepRedirectCookies;
}
delete opts.keepRedirectCookies;
/* c8 ignore next */
if (typeof opts.processData !== 'undefined') {
args.processData = opts.processData;
}
delete opts.processData;
/* c8 ignore next */
if (typeof opts.timeout !== 'undefined') {
args.timeout = opts.timeout;
}
delete opts.timeout;
const requestInit = { ...opts };
return {
args,
requestInit,
};
}
export function processParams(options) {
const initOpts = { ...initialOptions, ...options };
const opts = splitInitArgs(initOpts);
return processInitOpts(opts);
}
export function processInitOpts(options) {
let opts = { ...options };
opts = processHeaders(opts); // at first!
opts = processAbortController(opts);
opts = processCookies(opts);
opts = processMethod(opts);
opts.args.dataType = processDataType(opts.args.dataType);
opts.args.timeout = parseTimeout(opts.args.timeout);
const redirect = processRedirect(!!opts.args.keepRedirectCookies, opts.requestInit.redirect);
if (redirect) {
opts.requestInit.redirect = redirect;
}
return opts;
}
function processHeaders(options) {
const { args, requestInit } = options;
requestInit.headers = requestInit.headers
? new Headers(requestInit.headers)
: new Headers();
const { headers } = requestInit;
if (!headers.has('Accept')) {
headers.set('Accept', 'application/json, text/html, text/javascript, text/plain, */*');
}
return { args, requestInit };
}
function processAbortController(options) {
const { args, requestInit } = options;
if (!args.abortController || typeof args.abortController.abort !== 'function') {
if (typeof AbortController === 'function') {
args.abortController = new AbortController();
}
else {
throw new TypeError('AbortController not available');
}
}
requestInit.signal = args.abortController.signal;
return { args, requestInit };
}
function processCookies(options) {
const { args, requestInit } = options;
const data = args.cookies;
const arr = [];
if (data && typeof data === 'object') {
for (let [key, value] of Object.entries(data)) {
/* istanbul ignore else */
if (key && typeof key === 'string') {
key = key.trim();
/* istanbul ignore else */
if (!key) {
continue;
}
value = typeof value === 'string' || typeof value === 'number' ? value.toString().trim() : '';
arr.push(`${key}=${value}`);
}
}
}
if (arr.length) {
const headers = requestInit.headers;
let cookies = headers.get('Cookie');
if (cookies) {
cookies = cookies.trim();
let ret = arr.join('; ');
/* istanbul ignore if */
if (cookies.endsWith(';')) {
cookies = cookies.slice(0, -1);
ret = `${cookies}; ` + ret;
}
else {
ret = `${cookies}; ` + ret;
}
headers.set('Cookie', ret);
}
else {
headers.set('Cookie', arr.join('; '));
}
}
return { args, requestInit };
}
function processMethod(options) {
const { args, requestInit } = options;
if (requestInit.method && ['DELETE', 'POST', 'PUT'].includes(requestInit.method)) {
const headers = requestInit.headers;
if (args.contentType === false) {
void 0;
}
else if (args.contentType) {
headers.set('Content-Type', args.contentType);
}
/* istanbul ignore else */
else if (!headers.has('Content-Type')) {
headers.set('Content-Type', ContentTypeList.json);
}
}
return { args, requestInit };
}
/**
* Parse type of return data
* @returns default: json
**/
function processDataType(value) {
/* istanbul ignore else */
if (typeof value === 'string'
&& ['arrayBuffer', 'bare', 'blob', 'formData', 'json', 'text', 'raw'].includes(value)) {
return value;
}
return 'json';
}
function parseTimeout(ps) {
const value = typeof ps === 'number' && ps >= 0 ? Math.ceil(ps) : Infinity;
return value === Infinity || !Number.isSafeInteger(value) ? Infinity : value;
}
/**
* set redirect to 'manual' for retrieve cookies during 301/302 when keepRedirectCookies:TRUE
* and current value only when "follow"
*/
function processRedirect(keepRedirectCookies, curValue) {
// not change value if on Browser
/* istanbul ignore else */
// eslint-disable-next-line unicorn/prefer-global-this
if (keepRedirectCookies && typeof window === 'undefined') {
/* istanbul ignore else */
if (curValue === 'follow') {
return 'manual';
}
}
return curValue ? curValue : 'follow';
}
/**
* Return input url string
*/
export function processRequestGetLikeData(input, args) {
let url = '';
if (typeof args.data === 'undefined') {
url = input;
}
else if (args.processData) { // override the value of body by args.data
url = buildQueryString(input, args.data);
}
else {
throw new TypeError('Typeof args.data invalid for GET/DELETE when args.processData not true, type is :' + typeof args.data);
}
return url;
}
export function processRequestPostLikeData(args) {
let body;
const { data } = args;
if (typeof data === 'string') {
body = data;
}
else if (typeof data === 'undefined') {
body = null;
}
else if (data === null) {
body = null;
}
else if (data instanceof FormData) {
body = data;
}
// else if (data instanceof NodeFormData) {
// throw new TypeError('NodeFormData from pkg "form-data" not supported, use FormData from "undici" instead')
// }
else if (typeof Blob !== 'undefined' && data instanceof Blob) {
// @ts-ignore
body = data;
}
else if (typeof ArrayBuffer !== 'undefined' && data instanceof ArrayBuffer) {
body = data;
}
else if (typeof URLSearchParams !== 'undefined' && data instanceof URLSearchParams) {
body = data;
}
else if (typeof ReadableStream !== 'undefined' && data instanceof ReadableStream) {
body = data;
}
else if (typeof ReadStream !== 'undefined' && data instanceof ReadStream) {
body = data;
}
else if (args.processData) {
const { contentType } = args;
if (typeof contentType === 'string' && contentType.includes('json')) {
body = JSON.stringify(data);
}
else {
body = QueryString.stringify(data);
}
}
else {
body = data;
}
return body;
}
/** "foo=cookie_foo; Secure; Path=/" */
export function parseRespCookie(cookie) {
/* istanbul ignore else */
if (!cookie) {
return;
}
const arr = cookie.split(/;/);
const ret = {};
for (let row of arr) {
row = row.trim();
/* istanbul ignore else */
if (!row) {
continue;
}
if (!row.includes('=')) {
continue;
}
if (row.startsWith('Path=')) {
continue;
}
const [key, value] = row.split('=');
/* istanbul ignore else */
if (key && value) {
ret[key] = value;
}
}
/* istanbul ignore else */
// eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
if (ret && Object.keys(ret).length) {
return ret;
}
}
export function pickUrlStrFromRequestInfo(input) {
let url = '';
if (typeof input === 'string') {
url = input;
}
else if (input instanceof URL) {
url = input.toString();
}
else if (input instanceof Request) {
url = input.url;
}
else {
url = '';
}
return url;
}
//# sourceMappingURL=util.js.map