Source: index.js

/**
 * @module granax
 * @license AGPL-3.0
 * @author Gordon Hall <gordonh@member.fsf.org>
 */

'use strict';

const path = require('path');
const { spawn, execFileSync } = require('child_process');
const { platform } = require('os');
const { Socket } = require('net');
const { readFileSync } = require('fs');

const BIN_PATH = path.join(__dirname, 'bin');
const LD_LIBRARY_PATH = path.join(
  BIN_PATH, 'tor-browser_en-US', 'Browser', 'TorBrowser', 'Tor'
);


/**
 * Returns a {@link TorController} with automatically constructed socket
 * to the local Tor bundle executable
 * @param {object} options
 * @param {object} torrcOptions
 * @returns {TorController}
 */
module.exports = function(options, torrcOptions) {
  let socket = new Socket();
  let controller = new module.exports.TorController(socket, options);
  let [torrc, datadir] = module.exports.torrc(torrcOptions);

  let exe = path.basename(module.exports.tor(platform()));
  let tor = path.join(BIN_PATH, 'Tor', exe);

  let args = process.env.GRANAX_TOR_ARGS
    ? process.env.GRANAX_TOR_ARGS.split(' ')
    : [];
  let child = spawn(tor, ['-f', torrc].concat(args), {
    cwd: BIN_PATH,
    env: { LD_LIBRARY_PATH: path.join(BIN_PATH, 'Tor') }
  });
  let portFileReads = 0;

  controller.process = child; // NB: Expose the tor process to userland

  function connect() {
    let port = null;

    try {
      port = parseInt(readFileSync(path.join(
        datadir,
        'control-port'
      )).toString().split(':')[1]);
    } catch (err) {
      /* istanbul ignore next */
      portFileReads++;

      /* istanbul ignore next */
      if (portFileReads <= 20) {
        return setTimeout(() => connect(), 1000);
      } else {
        return controller.emit('error',
          new Error('Failed to read control port'));
      }
    }

    socket.connect(port, '127.0.0.1');
  }

  /* istanbul ignore next */
  process.on('exit', () => child.kill());
  child.stdout.once('data', () => setTimeout(() => connect(), 1000));
  child.on('error', (err) => controller.emit('error', err));
  child.on('exit', (code) => {
    controller.emit('error', new Error('Tor exited with code ' + code));
  });

  return controller;
};

/**
 * Returns the local path to the tor bundle
 * @returns {string}
 */
module.exports.tor = function(platform) {
  /* eslint complexity: ["error", 7] */
  let torpath = null;

  switch (platform) {
    case 'win32':
      torpath = path.join(BIN_PATH, 'Browser', 'TorBrowser', 'Tor', 'tor.exe');
      break;
    case 'darwin':
      torpath = path.join(BIN_PATH, '.tbb.app', 'Contents', 'Resources',
        'TorBrowser', 'Tor', 'tor');
      break;
    case 'android':
    case 'linux':
      /* istanbul ignore else */
      if (process.env.GRANAX_USE_SYSTEM_TOR) {
        // NB: Use the system Tor installation on android and linux
        try {
          torpath = execFileSync('which', ['tor']).toString().trim();
        } catch (err) {
          /* istanbul ignore next */
          throw new Error('Tor is not installed');
        }
      } else {
        torpath = path.join(LD_LIBRARY_PATH, 'tor');
      }
      break;
    default:
      throw new Error(`Unsupported platform "${platform}"`);
  }

  return torpath;
};

/**
 * {@link TorController}
 */
module.exports.TorController = require('./lib/controller');

/**
 * {@link module:granax/commands}
 */
module.exports.commands = require('./lib/commands');

/**
 * {@link module:granax/replies}
 */
module.exports.replies = require('./lib/replies');

/**
 * {@link module:granax/torrc}
 */
module.exports.torrc = require('./lib/torrc');