UNPKG

rekwest

Version:

The robust request library that humanity deserves 🌐

137 lines (111 loc) 4.29 kB
import http2 from 'node:http2'; import { setTimeout as setTimeoutPromise } from 'node:timers/promises'; import { requestCredentials, requestRedirect, requestRedirectCodes, } from './constants.mjs'; import { Cookies } from './cookies.mjs'; import { RequestError } from './errors.mjs'; import rekwest from './index.mjs'; import { mixin } from './mixin.mjs'; import { admix, maxRetryAfterError, sameOrigin, } from './utils.mjs'; const { HTTP2_HEADER_LOCATION, HTTP2_HEADER_RETRY_AFTER, HTTP2_HEADER_SET_COOKIE, HTTP2_METHOD_GET, HTTP2_METHOD_HEAD, HTTP2_METHOD_POST, HTTP_STATUS_BAD_REQUEST, HTTP_STATUS_FOUND, HTTP_STATUS_MOVED_PERMANENTLY, HTTP_STATUS_SEE_OTHER, } = http2.constants; export const postflight = (req, res, options, { reject, resolve }) => { const { cookies, credentials, follow, h2, redirect, url } = options; let headers; if (h2) { headers = res; res = req; } else { res.once('error', reject); } admix(res, headers, options); if (cookies !== false && res.headers[HTTP2_HEADER_SET_COOKIE]) { if (Cookies.jar.has(url.origin)) { const cookie = new Cookies(res.headers[HTTP2_HEADER_SET_COOKIE], options); Cookies.jar.get(url.origin).forEach((val, key) => { if (!cookie.has(key)) { cookie.set(key, val); } }); Cookies.jar.set(url.origin, cookie); } else { Cookies.jar.set(url.origin, new Cookies(res.headers[HTTP2_HEADER_SET_COOKIE], options)); } } Reflect.defineProperty(res, 'cookies', { enumerable: true, value: cookies !== false && Cookies.jar.has(url.origin) ? Cookies.jar.get(url.origin) : void 0, }); const { statusCode } = res; if (follow && /3\d{2}/.test(statusCode) && res.headers[HTTP2_HEADER_LOCATION]) { if (!requestRedirectCodes.includes(statusCode)) { return res.emit('error', new RangeError(`Invalid status code: ${ statusCode }`)); } if (redirect === requestRedirect.error) { return res.emit('error', new RequestError(`Unexpected redirect, redirect mode is set to '${ redirect }'.`)); } if (redirect === requestRedirect.follow) { const location = new URL(res.headers[HTTP2_HEADER_LOCATION], url); if (!/^https?:/i.test(location.protocol)) { return res.emit('error', new RequestError('URL scheme must be "http" or "https".')); } if (!sameOrigin(location, url)) { if (credentials !== requestCredentials.include) { options.credentials = requestCredentials.omit; } options.h2 = false; } if (statusCode !== HTTP_STATUS_SEE_OTHER && options.body?.pipe?.constructor === Function) { return res.emit('error', new RequestError(`Unable to ${ redirect } redirect with streamable body.`)); } if (([ HTTP_STATUS_MOVED_PERMANENTLY, HTTP_STATUS_FOUND, ].includes(statusCode) && options.method === HTTP2_METHOD_POST) || (statusCode === HTTP_STATUS_SEE_OTHER && ![ HTTP2_METHOD_GET, HTTP2_METHOD_HEAD, ].includes(options.method))) { for (const it of Object.keys(options.headers).filter((val) => /^content-/i.test(val))) { Reflect.deleteProperty(options.headers, it); } options.body = null; options.method = HTTP2_METHOD_GET; } options.follow--; options.redirected = true; options.url = location; if (statusCode === HTTP_STATUS_MOVED_PERMANENTLY && res.headers[HTTP2_HEADER_RETRY_AFTER]) { let interval = res.headers[HTTP2_HEADER_RETRY_AFTER]; interval = Number(interval) * 1e3 || new Date(interval) - Date.now(); if (interval > options.maxRetryAfter) { return res.emit('error', maxRetryAfterError(interval, { cause: mixin(res, options) })); } return setTimeoutPromise(interval).then(() => rekwest(options.url, options).then(resolve, reject)); } return rekwest(options.url, options).then(resolve, reject); } } if (statusCode >= HTTP_STATUS_BAD_REQUEST) { return reject(mixin(res, options)); } resolve(mixin(res, options)); };