UNPKG

@luminati-io/cycletls

Version:

Spoof TLS/JA3 fingerprint in JS with help from Go

311 lines (310 loc) 12.2 kB
"use strict"; var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) { if (k2 === undefined) k2 = k; var desc = Object.getOwnPropertyDescriptor(m, k); if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) { desc = { enumerable: true, get: function() { return m[k]; } }; } Object.defineProperty(o, k2, desc); }) : (function(o, m, k, k2) { if (k2 === undefined) k2 = k; o[k2] = m[k]; })); var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) { Object.defineProperty(o, "default", { enumerable: true, value: v }); }) : function(o, v) { o["default"] = v; }); var __importStar = (this && this.__importStar) || function (mod) { if (mod && mod.__esModule) return mod; var result = {}; if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k); __setModuleDefault(result, mod); return result; }; 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 http = __importStar(require("http")); const os_1 = __importDefault(require("os")); const util_1 = __importDefault(require("util")); let child; let lastRequestID; const cleanExit = async (message, exit) => { if (message) console.log(message); exit = exit ?? true; if (process.platform == "win32") { if (child) { new Promise((resolve, reject) => { (0, child_process_1.exec)("taskkill /pid " + child.pid + " /T /F", (error, stdout, stderr) => { if (error) { console.warn(error); } if (exit) process.exit(); }); }); } } else { if (child) { //linux/darwin os new Promise((resolve, reject) => { process.kill(-child.pid); if (exit) process.exit(); }); } } }; const handleSpawn = (debug, fileName, port) => { child = (0, child_process_1.spawn)(path_1.default.join(`"${__dirname}"`, fileName), { env: { WS_PORT: port.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)); } }); }; class Golang extends events_1.EventEmitter { server; queue; host; queueId; constructor(port, debug) { super(); let server = http.createServer(); server.listen(port) .on('listening', () => { server.close(() => { this.spawnServer(port, debug); this.host = true; }); }) .on('error', () => { this.createClient(port, debug); this.host = false; }); } spawnServer(port, 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); this.createClient(port, debug); } 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"); }); server.on("error", (err) => { // Connection error - retry in .1s server.removeAllListeners(); setTimeout(() => { this.createClient(port, debug); }, 100); }); } request(requestId, options) { lastRequestID = requestId; if (this.server) { this.server.send(JSON.stringify({ requestId, options })); } else { if (this.queue == null) { this.queue = []; } this.queue.push(JSON.stringify({ requestId, options })); if (this.queueId == null) { this.queueId = setInterval(() => { if (this.server) { for (let request of this.queue) { this.server.send(request); } this.queue = []; clearInterval(this.queueId); this.queueId = null; } }, 100); } } } exit() { if (process.platform == "win32") { return new Promise((resolve, reject) => { this.server.close(); if (this.host) { (0, child_process_1.exec)("taskkill /pid " + child.pid + " /T /F", (error, stdout, stderr) => { if (error) { console.warn(error); } resolve(stdout ? stdout : stderr); }); } else { resolve(null); } }); } else { return new Promise((resolve, reject) => { this.server.close(); if (this.host) { process.kill(-child.pid); resolve(null); } else { resolve(null); } }); } } } const initCycleTLS = async (initOptions = {}) => { return new Promise((resolveReady) => { let { port, debug } = initOptions; if (!port) port = 9119; if (!debug) debug = false; const instance = new Golang(port, debug); instance.on("ready", () => { const CycleTLS = (() => { const CycleTLS = async (url, options, method = "get") => { return new Promise((resolveRequest, rejectRequest) => { const requestId = `${url}${Math.floor(Date.now() * Math.random())}`; //set default options options = options ?? {}; //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, }); instance.once(requestId, (response) => { 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, }); }); }); }; CycleTLS.head = (url, options) => { return CycleTLS(url, options, "head"); }; CycleTLS.get = (url, options) => { return CycleTLS(url, options, "get"); }; CycleTLS.post = (url, options) => { return CycleTLS(url, options, "post"); }; CycleTLS.put = (url, options) => { return CycleTLS(url, options, "put"); }; CycleTLS.delete = (url, options) => { return CycleTLS(url, options, "delete"); }; CycleTLS.trace = (url, options) => { return CycleTLS(url, options, "trace"); }; CycleTLS.options = (url, options) => { return CycleTLS(url, options, "options"); }; CycleTLS.connect = (url, options) => { return CycleTLS(url, options, "options"); }; CycleTLS.patch = (url, options) => { return CycleTLS(url, options, "patch"); }; CycleTLS.exit = async () => { return instance.exit(); }; return CycleTLS; })(); resolveReady(CycleTLS); }); }); }; exports.default = initCycleTLS; // CommonJS support for default export module.exports = initCycleTLS; module.exports.default = initCycleTLS; module.exports.__esModule = true;