UNPKG

node-libcurl-ja3

Version:

Node.js native bindings for libcurl-impersonate. Impersonate Chrome, Edge, Firefox and Safari TLS fingerprints.

140 lines 5.33 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.create = void 0; const tslib_1 = require("tslib"); const curly_1 = require("../curly"); const child_process_1 = require("child_process"); const path_1 = tslib_1.__importDefault(require("path")); const curlDir = process.env['NODE_LIBCURL_JA3_BIN_PATH'] || path_1.default.resolve(__dirname, '../../deps/curl-impersonate/build/curl-impersonate/bin'); function parseCurlOutput(stderr, stdout) { const response = { StatusCode: 200, Headers: {}, Body: stdout.trim(), }; // Extract status line (e.g., "HTTP/2 200" or "HTTP/1.1 404 Not Found") const statusLineMatch = stderr.match(/< HTTP\/\d(\.\d)? (\d{3})/); if (statusLineMatch) { response.StatusCode = parseInt(statusLineMatch[2], 10); } // Extract headers const headerLines = stderr .split('\n') .filter((line) => line.startsWith('<') && !line.includes('HTTP/')); headerLines.forEach((line) => { const match = line.match(/< ([^:]+):\s*(.*)/); if (match) { response.Headers[match[1].trim()] = match[2].trim(); } }); return response; } function create(browser) { function runCommand(method, url, headers, body) { const args = ['-v', '-X', method]; if (headers) { for (const [key, value] of Object.entries(headers)) { args.push('-H', `"${key}: ${value}"`); } } if (method !== 'GET') { args.push('-d', body ? JSON.stringify(body) : ''); } args.push(url); const binName = 'curl_' + browser.replace(/_/g, ''); const curlCommand = path_1.default.resolve(`${curlDir}/${binName}`); const command = `${curlCommand} ${args.join(' ')}`; return new Promise((resolve, reject) => { var _a, _b; const process = (0, child_process_1.exec)(command, { encoding: 'utf8', }, (error, stdout, stderr) => { if (error) { reject(error); return; } const response = parseCurlOutput(stderr, stdout); if (response.StatusCode >= 400) { reject(new Error(`request error, status code ${response.StatusCode}`)); } resolve(response); }); // Optionally, you can also handle the streams directly (_a = process.stdout) === null || _a === void 0 ? void 0 : _a.setEncoding('utf8'); (_b = process.stderr) === null || _b === void 0 ? void 0 : _b.setEncoding('utf8'); }); } function fn(url, options = {}) { return new Promise((resolve, reject) => { const method = (options.customRequest || 'GET'); // construct headers const headers = {}; if (options.httpHeader) { options.httpHeader.forEach((header) => { const parts = header.split(':'); if (parts.length == 2) { headers[parts[0].trim()] = parts[1].trim(); } }); } runCommand(method, url, headers) .then((response) => { // get content type const contentTypeEntry = Object.entries(response.Headers).find(([k]) => k.toLowerCase() === 'content-type'); const contentType = contentTypeEntry ? contentTypeEntry[1] : ''; let parser = curly_1.defaultResponseBodyParsers[contentType]; if (!parser) { parser = (data, _headers) => data.toString('utf8'); } const headers = [{ ...response.Headers }]; // Convert string to Buffer before parsing const bodyBuffer = Buffer.from(response.Body); const body = parser(bodyBuffer, headers); const result = { statusCode: response.StatusCode, headers: headers, data: body, info: {}, }; resolve(result); }) .catch((e) => { reject(e); }); }); } fn.create = create; fn.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 fn[httpMethod] = httpMethodOptions === null ? fn : (url, options = {}) => fn(url, { ...httpMethodOptions(httpMethod, options), }); } return fn; } exports.create = create; //# sourceMappingURL=binaryBrowser.js.map