UNPKG

@jeembo/cycletls

Version:

Spoof TLS/JA3 fingerprint in JS with help from Go

222 lines (221 loc) 8.72 kB
"use strict"; var __importDefault = (this && this.__importDefault) || function (mod) { return (mod && mod.__esModule) ? mod : { "default": mod }; }; Object.defineProperty(exports, "__esModule", { value: true }); const child_process_1 = require("child_process"); const path_1 = __importDefault(require("path")); const events_1 = require("events"); const ws_1 = __importDefault(require("ws")); const os_1 = __importDefault(require("os")); const util_1 = __importDefault(require("util")); let child; const cleanExit = async (message, exit = true) => { if (message) console.log(message); if (process.platform == 'win32') { if (child) { return new Promise((resolve, reject) => { (0, child_process_1.exec)('taskkill /pid ' + child.pid + ' /T /F', (error, stdout, stderr) => { if (error) { reject(error); console.warn(error); } if (exit) process.exit(); resolve(); }); }); } } else { if (child) { process.kill(-child.pid); if (exit) { process.exit(); } } } }; process.on('SIGINT', () => cleanExit()); process.on('SIGTERM', () => cleanExit()); const handleSpawn = (debug, fileName, port, workers) => { child = (0, child_process_1.spawn)(path_1.default.join(`'${__dirname}'`, fileName), { env: { WS_PORT: port.toString(), WORKERS: workers.toString(), }, shell: true, windowsHide: true, detached: process.platform !== 'win32' }); child.stderr.on('data', (stderr) => { if (stderr.toString().includes('Request_Id_On_The_Left')) { const splitRequestIdAndError = stderr.toString().split('Request_Id_On_The_Left'); const [requestId, error] = splitRequestIdAndError; //TODO Correctly parse logging messages // this.emit(requestId, { error: new Error(error) }); } else { debug ? cleanExit(new Error(stderr)) //TODO add Correct error logging url request/ response/ : cleanExit(`Error Processing Request (please open an issue https://github.com/Danny-Dasilva/CycleTLS/issues/new/choose) -> ${stderr}`, false) .then(() => handleSpawn(debug, fileName, port, workers)); } }); }; class Golang extends events_1.EventEmitter { server; queue; host; queueId; constructor(port, workers, debug) { super(); this.spawnServer(port, workers, debug); this.createClient(port, debug); } spawnServer(port, workers, debug) { let executableFilename; if (process.platform == 'win32') { executableFilename = 'index.exe'; } else if (process.platform == 'linux') { //build arm if (os_1.default.arch() == 'arm') { executableFilename = 'index-arm'; } else if (os_1.default.arch() == 'arm64') { executableFilename = 'index-arm64'; } else { //default executableFilename = 'index'; } } else if (process.platform == 'darwin') { executableFilename = 'index-mac'; } else { cleanExit(new Error('Operating system not supported')); } handleSpawn(debug, executableFilename, port, workers); } createClient(port, debug) { const server = new ws_1.default(`ws://localhost:${port}`); server.on('open', () => { // WebSocket connected - set server and emit ready this.server = server; this.server.on('message', (data) => { const message = JSON.parse(data); this.emit(message.RequestID, message); }); this.emit('ready'); }); const reconect = (status) => { if (debug) { console.warn(status); } server.removeAllListeners(); setTimeout(() => { this.createClient(port, debug); }, 100); }; server.on('close', reconect); server.on('error', reconect); } request(requestId, options) { const data = JSON.stringify({ requestId, options }); if (this.server) { this.server.send(data); } else { this.queue = this.queue || []; this.queue.push(data); if (this.queueId == null) { this.queueId = setInterval(() => { if (this.server) { this.queue.forEach((request) => { this.server.send(request); }); this.queue = []; clearInterval(this.queueId); this.queueId = null; } }, 100); } } } exit() { this.server.close(); cleanExit('Exit'); } } const initCycleTLS = async (initOptions) => { const { debug, workers = 100, port = 9119, } = initOptions; return new Promise((resolveReady) => { const instance = new Golang(port, workers, debug); instance.on('ready', () => { const CycleTLS = async (url, options = {}, method = 'get') => { return new Promise((resolveRequest, rejectRequest) => { const requestId = `${url}${Math.floor(Date.now() * Math.random())}`; //set default ja3, user agent, body and proxy if (!options?.ja3) options.ja3 = '771,4865-4867-4866-49195-49199-52393-52392-49196-49200-49162-49161-49171-49172-51-57-47-53-10,0-23-65281-10-11-35-16-5-51-43-13-45-28-21,29-23-24-25-256-257,0'; if (!options?.userAgent) options.userAgent = 'Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/101.0.4951.54 Safari/537.36'; if (!options?.body) options.body = ''; if (!options?.proxy) options.proxy = ''; //convert simple cookies const cookies = options?.cookies; if (typeof cookies === 'object' && !Array.isArray(cookies) && cookies !== null) { const tempArr = []; for (const [key, value] of Object.entries(options.cookies)) { tempArr.push({ name: key, value: value }); } options.cookies = tempArr; } instance.request(requestId, { url, ...options, method, }); let timer; // Don't use any const onResponse = (response) => { clearInterval(timer); if (response.error) return rejectRequest(response.error); try { //parse json responses response.Body = JSON.parse(response.Body); //override console.log full repl to display full body response.Body[util_1.default.inspect.custom] = function () { return JSON.stringify(this, undefined, 2); }; } catch (e) { } const { Status: status, Body: body, Headers: headers } = response; if (headers['Set-Cookie']) headers['Set-Cookie'] = headers['Set-Cookie'].split('/,/'); resolveRequest({ status, body, headers, }); }; instance.once(requestId, onResponse); timer = setTimeout(() => { rejectRequest('Timeout'); instance.removeListener(requestId, onResponse); }, options.timeout * 1000); }); }; CycleTLS.exit = instance.exit.bind(instance); resolveReady(CycleTLS); }); }); }; exports.default = initCycleTLS;