@luminati-io/cycletls
Version:
Spoof TLS/JA3 fingerprint in JS with help from Go
311 lines (310 loc) • 12.2 kB
JavaScript
;
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;