@jeembo/cycletls
Version:
Spoof TLS/JA3 fingerprint in JS with help from Go
222 lines (221 loc) • 8.72 kB
JavaScript
;
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;