UNPKG

@galihrivanto/node-libcurli

Version:

Node.js bindings for curl-impersonate library

208 lines 8.94 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.curly = exports.create = void 0; const CurlOption_1 = require("../generated/CurlOption"); const Curl_1 = require("./Curl"); const CurlFeature_1 = require("../enum/CurlFeature"); const curly_1 = require("../shared/curly"); const create = (defaultOptions = {}) => { function curly(url, options = {}) { const curlHandle = new Curl_1.Curl(); curlHandle.enable(CurlFeature_1.CurlFeature.NoDataParsing); curlHandle.setOpt('URL', `${options.curlyBaseUrl || ''}${url}`); // headers should be merged not overwritten const header = [ ...(defaultOptions.httpHeader || []), ...(defaultOptions.HTTPHEADER || []), ...(options.httpHeader || []), ]; const finalOptions = { ...defaultOptions, // remove httpHeader from options ...Object.fromEntries(Object.entries(options).filter(([key]) => key !== 'httpHeader')), HTTPHEADER: header, }; for (const key of Object.keys(finalOptions)) { const keyTyped = key; const optionName = keyTyped in CurlOption_1.CurlOptionCamelCaseMap ? CurlOption_1.CurlOptionCamelCaseMap[keyTyped] : keyTyped; // if it begins with curly we do not set it on the curlHandle // as it's an specific option for curly if (optionName.startsWith('curly') || !finalOptions[keyTyped]) continue; // @ts-ignore @TODO Try to type this curlHandle.setOpt(optionName, finalOptions[keyTyped]); } // streams! const { curlyStreamResponse, curlyStreamResponseHighWaterMark, curlyStreamUpload, } = finalOptions; const isUsingStream = !!(curlyStreamResponse || curlyStreamUpload); if (finalOptions.curlyProgressCallback) { if (typeof finalOptions.curlyProgressCallback !== 'function') { throw new TypeError('curlyProgressCallback must be a function with signature (number, number, number, number) => number'); } const fnToCall = isUsingStream ? 'setStreamProgressCallback' : 'setProgressCallback'; curlHandle[fnToCall](finalOptions.curlyProgressCallback); } if (curlyStreamResponse) { curlHandle.enable(CurlFeature_1.CurlFeature.StreamResponse); if (curlyStreamResponseHighWaterMark) { curlHandle.setStreamResponseHighWaterMark(curlyStreamResponseHighWaterMark); } } if (curlyStreamUpload) { curlHandle.setUploadStream(curlyStreamUpload); } const lowerCaseHeadersIfNecessary = (headers) => { // in-place modification // yeah, I know mutability is bad and all that if (finalOptions.curlyLowerCaseHeaders) { for (const headersReq of headers) { const entries = Object.entries(headersReq); for (const [headerKey, headerValue] of entries) { delete headersReq[headerKey]; // @ts-expect-error ignoring this for now headersReq[headerKey.toLowerCase()] = headerValue; } } } }; return new Promise((resolve, reject) => { let stream; if (curlyStreamResponse) { curlHandle.on('stream', (_stream, statusCode, headers) => { lowerCaseHeadersIfNecessary(headers); stream = _stream; resolve({ // @ts-ignore cannot be subtype yada yada data: stream, statusCode, headers, }); }); } curlHandle.on('end', (statusCode, data, headers) => { curlHandle.close(); // only need to the remaining here if we did not enabled // the stream response if (curlyStreamResponse) { return; } const contentTypeEntry = Object.entries(headers[headers.length - 1]).find(([k]) => k.toLowerCase() === 'content-type'); let contentType = (contentTypeEntry ? contentTypeEntry[1] : ''); // remove the metadata of the content-type, like charset // See https://tools.ietf.org/html/rfc7231#section-3.1.1.5 contentType = contentType.split(';')[0]; const responseBodyParsers = { ...curly.defaultResponseBodyParsers, ...finalOptions.curlyResponseBodyParsers, }; let foundParser = finalOptions.curlyResponseBodyParser; if (typeof foundParser === 'undefined') { for (const [contentTypeFormat, parser] of Object.entries(responseBodyParsers)) { if (typeof parser !== 'function') { return reject(new TypeError(`Response body parser for ${contentTypeFormat} must be a function`)); } if (contentType === contentTypeFormat) { foundParser = parser; break; } else if (contentTypeFormat === '*') { foundParser = parser; break; } else { const partsFormat = contentTypeFormat.split('/'); const partsContentType = contentType.split('/'); if (partsContentType.length === partsFormat.length && partsContentType.every((val, index) => partsFormat[index] === '*' || partsFormat[index] === val)) { foundParser = parser; break; } } } } if (foundParser && typeof foundParser !== 'function') { return reject(new TypeError('`curlyResponseBodyParser` passed to curly must be false or a function.')); } lowerCaseHeadersIfNecessary(headers); try { resolve({ statusCode: statusCode, data: foundParser ? foundParser(data, headers) : data, headers: headers, }); } catch (error) { reject(error); } }); curlHandle.on('error', (error, errorCode) => { curlHandle.close(); // @ts-ignore error.code = errorCode; // @ts-ignore error.isCurlError = true; // oops, if have a stream it means the promise // has been resolved with it // so instead of rejecting the original promise // we are emitting the error event on the stream if (stream) { stream.emit('error', error); } else { reject(error); } }); try { curlHandle.perform(); } catch (error) /* istanbul ignore next: this should never happen 🤷‍♂️ */ { curlHandle.close(); reject(error); } }); } curly.create = exports.create; curly.defaultResponseBodyParsers = curly_1.defaultResponseBodyParsers; const httpMethodOptionsMap = { get: null, post: (_m, o) => ({ post: true, ...o, }), head: (_m, o) => ({ nobody: true, ...o, }), _: (m, o) => ({ customRequest: m.toUpperCase(), ...o, }), }; for (const httpMethod of curly_1.methods) { const httpMethodOptionsKey = Object.prototype.hasOwnProperty.call(httpMethodOptionsMap, httpMethod) ? httpMethod : '_'; const httpMethodOptions = httpMethodOptionsMap[httpMethodOptionsKey]; // @ts-ignore curly[httpMethod] = httpMethodOptions === null ? curly : (url, options = {}) => curly(url, { ...httpMethodOptions(httpMethod, options), }); } // @ts-ignore return curly; }; exports.create = create; /** * Curly function * * @public */ exports.curly = (0, exports.create)(); //# sourceMappingURL=curly.js.map