tor-ctrl
Version:
Node.js library for accessing the Tor control port
331 lines (329 loc) • 9.64 kB
JavaScript
import "./chunk-OLNOUFRH.mjs";
// src/index.ts
import { EventEmitter } from "node:events";
import { connect } from "node:net";
var TorControl = class extends EventEmitter {
_connection = null;
_config;
_state = "disconnected";
_debugger = null;
/**
* Get the current connection state of the TorControl
*/
get state() {
return this._state;
}
constructor(config = {}) {
super();
if (!Object.prototype.hasOwnProperty.call(config, "host")) {
config.host = "localhost";
}
if (!Object.prototype.hasOwnProperty.call(config, "port")) {
config.port = 9051;
}
this._config = config;
import("./src-R43MIFC6.mjs").then((pkg) => {
if (pkg) {
this._debugger = pkg.default("tor-ctrl");
}
}).catch(() => {
});
}
_debug(...args) {
if (this._debugger) {
this._debugger(...args);
}
}
/**
* Establish a connection to the TorControl
*/
connect() {
const conn = connect(this._config.port, this._config.host);
this._connection = conn;
return new Promise((resolve) => {
conn.on("error", (err) => {
const error = err instanceof Error ? err : new Error(`TorControl connection error: ${err}`);
this.emit("error", error);
return resolve({ error });
});
conn.once("data", (data) => {
const str = data.toString();
this._debug("data:", str);
if (str.substring(0, 3) === "250") {
this.emit("connect", conn);
return resolve({ data: true });
}
const error = new Error(`TorControl connection error: ${data}`);
this.emit("error", error);
return resolve({ error });
});
conn.on("close", () => {
this._state = "disconnected";
this.emit("close");
});
if (this._config.password) {
this.authenticate(this._config.password).then(({ error }) => {
if (error) {
this.emit("error", error);
return resolve({ error });
}
});
}
});
}
/**
* Close the connection to the TorControl
*/
async disconnect() {
if (this._connection) {
await this.quit();
this._connection.end();
this._connection = null;
}
}
get disconnected() {
return this._connection === null;
}
/**
* Send a command to the TorControl
*
* Example:
* ```typescript
* const { data, error } = await torControl.sendCommand(['GETINFO', 'version']);
* if (error) {
* console.error('Error:', error);
* return;
* }
* console.log('GETINFO:', data); // { code: 250, message: 'version=...' }
* ```
*
* @link https://spec.torproject.org/control-spec/commands.html
* @param command
*/
async sendCommand(command) {
if (!this._connection) {
const error = new Error("TorControl not connected");
this.emit("error", error);
return { error };
}
const conn = this._connection;
if (Array.isArray(command)) {
command = command.join(" ");
}
return new Promise((resolve) => {
conn.once("data", (data) => {
const str = data.toString();
this._debug("sendCommand:data", str, str.split(""));
const lines = str.split(/\r?\n/);
this._debug("sendCommand:lines", lines);
const result = [];
for (const line of lines) {
if (line !== "") {
const message = line.substring(4);
const code = Number(line.substring(0, 3));
this._debug("sendCommand:message", message);
this.emit("data", message);
result.push({ code, message });
}
}
return resolve({ data: result });
});
this._debug("sendCommand:command", command);
conn.write(`${command}\r
`);
});
}
async _solveAndPick(promise, pick = 0) {
const { data, error } = await promise;
if (error)
return { error };
return { data: data[pick] };
}
// ===============================
// Config
// ===============================
/**
* Authenticate
*
* @link https://spec.torproject.org/control-spec/commands.html?highlight=AUTHENTICATE#authenticate
* @param password
*/
async authenticate(password) {
return this._solveAndPick(this.sendCommand(`AUTHENTICATE "${password}"`));
}
/**
* Quit
*
* @link https://spec.torproject.org/control-spec/commands.html?highlight=QUIT#quit
*/
async quit() {
return this._solveAndPick(this.sendCommand("QUIT"));
}
/**
* Example:
*
* ```typescript
* const { data, error } = await torControl.getConfig('SocksPort');
* if (data) {
* console.log('SocksPort:', data); // SocksPort: 9050
* }
* ```
*
* @link https://spec.torproject.org/control-spec/commands.html?highlight=GETCONF#getconf
* @param key
*/
async getConfig(key) {
const { data, error } = await this._solveAndPick(this.sendCommand(["GETCONF", key]));
if (error)
return { error };
return { data: data.message.split("=")[1] };
}
/**
* @link https://spec.torproject.org/control-spec/commands.html?highlight=SETCONF#setconf
* @param key
* @param value
*/
async setConfig(key, value) {
return this._solveAndPick(this.sendCommand(["SETCONF", `${key}=${value}`]));
}
/**
* @link https://spec.torproject.org/control-spec/commands.html?highlight=RESETCONF#resetconf
* @param key
*/
async resetConfig(key) {
return this._solveAndPick(this.sendCommand(["RESETCONF", key]));
}
// ===============================
// Signals
// ===============================
async signal(signal) {
return this.sendCommand(["SIGNAL", signal]);
}
async signalReload() {
return this._solveAndPick(this.signal("RELOAD"));
}
async signalShutdown() {
return this._solveAndPick(this.signal("SHUTDOWN"));
}
async signalDump() {
return this._solveAndPick(this.signal("DUMP"));
}
async signalDebug() {
return this._solveAndPick(this.signal("DEBUG"));
}
async signalHalt() {
return this._solveAndPick(this.signal("HALT"));
}
async signalTerm() {
return this._solveAndPick(this.signal("TERM"));
}
async signalNewNym() {
return this._solveAndPick(this.signal("NEWNYM"));
}
async signalClearDnsCache() {
return this._solveAndPick(this.signal("CLEARDNSCACHE"));
}
async signalUsr1() {
return this._solveAndPick(this.signal("USR1"));
}
async signalUsr2() {
return this._solveAndPick(this.signal("USR2"));
}
// ===============================
// Misc
// ===============================
/**
* Example:
*
* ```typescript
* const { data, error } = await torControl.getInfo('version');
* if (data) {
* console.log('Version:', data); // Version: Tor
* }
* ```
*
* @link https://spec.torproject.org/control-spec/commands.html?highlight=GETINFO#getinfo
* @param key
*/
async getInfo(key) {
return this._solveAndPick(this.sendCommand(["GETINFO", key]));
}
/**
* Example:
*
* ```typescript
* const mar = await torControl.mapAddress('1.2.3.4', 'torproject.org');
* console.log('MAPADDRESS:', mar); // { code: 250, message: '1.2.3.4=torproject.org' }
*
* const gir = await torControl.getInfo('address-mappings/control');
* console.log('GETINFO:', gir); // { code: 250, message: 'address-mappings/control=1.2.3.4 torproject.org NEVER' }
* ```
*
* @link https://spec.torproject.org/control-spec/commands.html?highlight=MAPADDRESS#mapaddress
* @param address
* @param target
*/
async mapAddress(address, target) {
return this._solveAndPick(this.sendCommand(["MAPADDRESS", `${address}=${target}`]));
}
// ===============================
// Circuit
// ===============================
async extendCircuit(circuitId) {
return this._solveAndPick(this.sendCommand(["EXTENDCIRCUIT", circuitId]));
}
/**
* @link https://spec.torproject.org/control-spec/commands.html?highlight=SETCIRCUITPURPOSE#setcircuitpurpose
* @param circuitId
* @param purpose
*/
async setCircuitPurpose(circuitId, purpose) {
return this._solveAndPick(this.sendCommand(["SETCIRCUITPURPOSE", circuitId, purpose]));
}
/**
* @link https://spec.torproject.org/control-spec/commands.html?highlight=SETROUTERPURPOSE#setrouterpurpose
* @param nicknameOrKey
* @param purpose
*/
async setRouterPurpose(nicknameOrKey, purpose) {
return this._solveAndPick(this.sendCommand(["SETROUTERPURPOSE", nicknameOrKey, purpose]));
}
/**
* @link https://spec.torproject.org/control-spec/commands.html?highlight=CLOSESTREAM#closestream
* @param streamId
* @param reason
*/
async setStream(streamId, reason) {
return this._solveAndPick(this.sendCommand(["CLOSESTREAM", streamId, reason]));
}
/**
* @link https://spec.torproject.org/control-spec/commands.html?highlight=CLOSECIRCUIT#closecircuit
* @param circuitId
*/
async closeCircuit(circuitId) {
return this._solveAndPick(this.sendCommand(["CLOSECIRCUIT", circuitId]));
}
/**
* @link https://spec.torproject.org/control-spec/commands.html?highlight=ATTACHSTREAM#attachstream
* @param streamId
* @param circuitId
* @param hop
*/
async attachStream(streamId, circuitId, hop) {
return this._solveAndPick(
this.sendCommand(["ATTACHSTREAM", streamId, circuitId, hop ? String(hop) : ""])
);
}
// ===============================
// Alias
// ===============================
/**
* Alias for `signalNewNym`
*/
async getNewIdentity() {
return this.signalNewNym();
}
};
export {
TorControl
};