UNPKG

@waiting/fetch

Version:

HTTP fetch API for browser and Node.js. Handle 302/303 redirect correctly on Node.js

320 lines 9.97 kB
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