nssr
Version:
Node.js client for ShadowsocksR
392 lines (391 loc) • 17.7 kB
JavaScript
"use strict";
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
return new (P || (P = Promise))(function (resolve, reject) {
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
function step(result) { result.done ? resolve(result.value) : new P(function (resolve) { resolve(result.value); }).then(fulfilled, rejected); }
step((generator = generator.apply(thisArg, _arguments || [])).next());
});
};
var __generator = (this && this.__generator) || function (thisArg, body) {
var _ = { label: 0, sent: function() { if (t[0] & 1) throw t[1]; return t[1]; }, trys: [], ops: [] }, f, y, t, g;
return g = { next: verb(0), "throw": verb(1), "return": verb(2) }, typeof Symbol === "function" && (g[Symbol.iterator] = function() { return this; }), g;
function verb(n) { return function (v) { return step([n, v]); }; }
function step(op) {
if (f) throw new TypeError("Generator is already executing.");
while (_) try {
if (f = 1, y && (t = y[op[0] & 2 ? "return" : op[0] ? "throw" : "next"]) && !(t = t.call(y, op[1])).done) return t;
if (y = 0, t) op = [0, t.value];
switch (op[0]) {
case 0: case 1: t = op; break;
case 4: _.label++; return { value: op[1], done: false };
case 5: _.label++; y = op[1]; op = [0]; continue;
case 7: op = _.ops.pop(); _.trys.pop(); continue;
default:
if (!(t = _.trys, t = t.length > 0 && t[t.length - 1]) && (op[0] === 6 || op[0] === 2)) { _ = 0; continue; }
if (op[0] === 3 && (!t || (op[1] > t[0] && op[1] < t[3]))) { _.label = op[1]; break; }
if (op[0] === 6 && _.label < t[1]) { _.label = t[1]; t = op; break; }
if (t && _.label < t[2]) { _.label = t[2]; _.ops.push(op); break; }
if (t[2]) _.ops.pop();
_.trys.pop(); continue;
}
op = body.call(thisArg, _);
} catch (e) { op = [6, e]; y = 0; } finally { f = t = 0; }
if (op[0] & 5) throw op[1]; return { value: op[0] ? op[1] : void 0, done: true };
}
};
var program = require("commander");
var pkg = require("pjson");
var path = require("path");
var cp = require("child_process");
var fs = require("fs-extra");
var inquirer = require("inquirer");
var ora = require("ora");
var glob = require("glob");
var download = require("download-git-repo");
var semver = require("semver");
var honoka_1 = require("honoka");
var Config_1 = require("./Config");
var Validator_1 = require("./Validator");
var Logger_1 = require("./Logger");
module.exports = (function () {
function Cli() {
this.logger = new Logger_1.default();
this.config = new Config_1.default();
}
Cli.prototype.register = function () {
program
.version(pkg.version)
.command('init', 'initialize nssr')
.command('add', 'add a server')
.command('delete [server]', 'delete a server')
.command('list', 'list servers')
.command('start [server]', 'start server')
.command('stop', 'stop server')
.command('status', 'server status')
.parse(process.argv);
};
Cli.prototype.init = function () {
return __awaiter(this, void 0, void 0, function () {
var _this = this;
var answers, spinner_1;
return __generator(this, function (_a) {
switch (_a.label) {
case 0:
if (!this.checkInit(false)) {
return [2];
}
return [4, inquirer.prompt({
type: 'confirm',
name: 'initialize',
message: "Do you want to initialize" + (this.hasInit() ? ' again:' : ':'),
default: true
})];
case 1:
answers = _a.sent();
if (answers.initialize) {
fs.removeSync(Config_1.default.baseDir);
fs.mkdirpSync(Config_1.default.baseDir);
fs.mkdirpSync(Config_1.default.serverDir);
spinner_1 = ora("downloading library to " + Config_1.default.libDir);
spinner_1.start();
download('shadowsocksr-backup/shadowsocksr#manyuser', Config_1.default.libDir, { clone: false }, function (err) {
spinner_1.stop();
if (err) {
throw err;
}
else {
_this.logger.info("initialized to " + Config_1.default.baseDir);
}
});
}
return [2];
}
});
});
};
Cli.prototype.add = function () {
return __awaiter(this, void 0, void 0, function () {
var answers, serverConfigPath;
return __generator(this, function (_a) {
switch (_a.label) {
case 0:
if (!this.checkInit()) {
return [2];
}
return [4, inquirer.prompt([
{
type: 'input',
name: 'server_name',
message: 'Friendly server name:',
validate: function (input) {
return input.trim() !== '' && input === path.basename(input);
}
},
{
type: 'input',
name: 'server',
message: 'Server IP or domain:',
validate: function (input) {
return Validator_1.default.isIpAddress(input) || Validator_1.default.isDomain(input);
}
},
{
type: 'input',
name: 'server_port',
message: 'Server port:',
validate: function (input) { return Validator_1.default.isPort(input); }
},
{
type: 'input',
name: 'local_address',
message: 'Local address:',
default: '127.0.0.1',
validate: function (input) { return Validator_1.default.isIpAddress(input); }
},
{
type: 'input',
name: 'local_port',
message: 'Local port:',
default: 1080,
validate: function (input) { return Validator_1.default.isPort(input); }
},
{
type: 'password',
name: 'password',
message: 'Password:',
validate: function (input) { return input.length > 0; }
},
{
type: 'list',
name: 'method',
message: 'Method:',
choices: [
'none',
'aes-256-cfb',
'aes-192-cfb',
'aes-128-cfb',
'aes-256-cfb8',
'aes-192-cfb8',
'aes-128-cfb8',
'aes-256-ctr',
'aes-192-ctr',
'aes-128-ctr',
'chacha20-ietf',
'chacha20',
'rc4-md5',
'rc4-md5-6'
],
default: 'none'
},
{
type: 'list',
name: 'protocol',
message: 'Protocol:',
choices: [
'origin',
'verify_deflate',
'auth_sha1_v4',
'auth_sha1_v4_compatible',
'auth_aes128_md5',
'auth_aes128_sha1',
'auth_chain_a',
'auth_chain_b'
],
default: 'origin'
},
{
type: 'input',
name: 'protocol_param',
message: 'Protocol param:',
default: ''
},
{
type: 'list',
name: 'obfs',
message: 'Obfs:',
choices: [
'plain',
'http_simple',
'http_simple_compatible',
'http_post',
'http_post_compatible',
'tls1.2_ticket_auth',
'tls1.2_ticket_auth_compatible',
'tls1.2_ticket_fastauth',
'tls1.2_ticket_fastauth_compatible'
],
default: 'plain'
},
{
type: 'input',
name: 'obfs_param',
message: 'Obfs param:',
default: ''
}
])];
case 1:
answers = _a.sent();
answers.local_port = Number(answers.local_port);
answers.server_port = Number(answers.server_port);
serverConfigPath = path.join(Config_1.default.serverDir, answers.server_name + ".json");
delete answers.server_name;
fs.writeFileSync(serverConfigPath, JSON.stringify(answers, null, 4));
this.logger.info("create server to " + serverConfigPath);
return [2];
}
});
});
};
Cli.prototype.delete = function () {
if (!this.checkInit()) {
return;
}
if (program.args.length === 0) {
this.logger.warning('server name muse be provided');
}
else {
var configFile = path.join(Config_1.default.serverDir, program.args[0] + ".json");
if (!fs.existsSync(configFile)) {
this.logger.warning("server \"" + program.args[0] + "\" not exist");
}
else {
fs.unlinkSync(configFile);
this.logger.info("\"" + program.args[0] + "\" has been deleted");
}
}
};
Cli.prototype.start = function () {
if (!this.checkInit()) {
return;
}
this.stop();
if (program.args.length === 0) {
this.logger.warning('server name muse be provided');
}
else {
var configFile = path.join(Config_1.default.serverDir, program.args[0] + ".json");
if (!fs.existsSync(configFile)) {
this.logger.warning("server \"" + program.args[0] + "\" not exist");
}
else {
cp.execSync(path.join(Config_1.default.libDir, 'shadowsocks', 'local.py') + " -c " + configFile + " --pid-file " + Config_1.default.pidFile + " --log-file " + Config_1.default.logFile + " -d start");
var pid = this.getPid();
if (pid > 0) {
this.logger.info("start nssr at process: " + pid);
}
}
}
};
Cli.prototype.stop = function () {
if (!this.checkInit()) {
return;
}
var pid = this.getPid();
if (pid > 0) {
try {
process.kill(pid);
this.logger.info("kill nssr at process: " + pid);
}
catch (e) { }
fs.unlinkSync(Config_1.default.pidFile);
}
};
Cli.prototype.status = function () {
if (!this.checkInit()) {
return;
}
var isRunning;
var pid = this.getPid();
if (pid > 0) {
try {
isRunning = !!process.kill(pid, 0);
}
catch (e) {
isRunning = e.code === 'EPERM';
}
}
else {
isRunning = false;
}
if (isRunning && pid > 0) {
this.logger.info("nssr found running with process: " + pid);
}
else {
this.logger.info('nssr is not running');
}
};
Cli.prototype.list = function () {
var _this = this;
if (!this.checkInit()) {
return;
}
glob(path.join(Config_1.default.serverDir, '*.json'), function (err, files) {
if (err) {
throw err;
}
files.forEach(function (file) {
_this.logger.info(path.basename(file, '.json'));
});
});
};
Cli.prototype.hasInit = function () {
if (!fs.existsSync(Config_1.default.baseDir)) {
return false;
}
if (!fs.existsSync(Config_1.default.libDir)) {
return false;
}
return true;
};
Cli.prototype.checkInit = function (check) {
if (check === void 0) { check = true; }
program.version(pkg.version).parse(process.argv);
this.checkUpdate().catch(function () { });
if (!check) {
return true;
}
if (!this.hasInit()) {
this.logger.warning('please initialize before use');
return false;
}
return true;
};
Cli.prototype.getPid = function () {
if (fs.existsSync(Config_1.default.pidFile)) {
return Number(fs.readFileSync(Config_1.default.pidFile));
}
return 0;
};
Cli.prototype.checkUpdate = function () {
return __awaiter(this, void 0, void 0, function () {
var lastUpdateCheck, latestPkg, latestVersion, currentVersion;
return __generator(this, function (_a) {
switch (_a.label) {
case 0:
lastUpdateCheck = Number(this.config.get('lastUpdateCheck')) || 0;
if (lastUpdateCheck && Date.now() - lastUpdateCheck < 1000 * 60 * 60 * 24) {
return [2];
}
return [4, honoka_1.default.get(Config_1.default.packageURL)];
case 1:
latestPkg = _a.sent();
latestPkg = JSON.parse(latestPkg);
latestVersion = latestPkg.version;
currentVersion = pkg.version;
if (!semver.valid(latestVersion)) {
return [2];
}
this.config.set('lastUpdateCheck', Date.now());
if (semver.gt(latestVersion, currentVersion)) {
this.logger.info();
this.logger.warning("Your current version of nssr is out of date. The latest version is " + latestVersion + " while you're on " + currentVersion + ".");
this.logger.info();
}
return [2];
}
});
});
};
return Cli;
}());