UNPKG

node-reddcoin

Version:
293 lines (227 loc) 9.32 kB
var http = require('http'), https = require('https'); var api = require('./commands'), errors = require('./errors'); function Client (options) { this.opts = { host: '127.0.0.1', port: 45443, method: 'POST', user: '', pass: '', headers: { 'Host': 'localhost', 'Authorization': '' }, passphrasecallback: null, https: false, ca: null }; if (options) { this.set(options) } } Client.prototype = { invalid: function (command) { var args = Array.prototype.slice.call(arguments, 1); var fn = args.pop(); if (typeof fn !== 'function') { fn = console.log } return fn(new Error('No such command "' + command + '"')) }, send: function (command) { var self = this; var commandOptions = {}; // Due to the horrible nature of this libraries architecture we need to botch it. // If the first element of the command array is an object then we need to extract // the actual command and then process the options. if (command instanceof Object) { commandOptions = command; command = commandOptions.command; } var args = Array.prototype.slice.call(arguments, 1); var callbackFunction; if (typeof args[args.length - 1] === 'function') { callbackFunction = args.pop().bind(this); } else { callbackFunction = console.log; } var rpcData = JSON.stringify({ id: new Date().getTime(), method: command.toLowerCase(), params: args }); var options = this.opts; options.headers['Content-Length'] = rpcData.length; var request; if (options.https === true) { request = https.request; } else { request = http.request; } var req = request(options, function (res) { var data = ''; res.setEncoding('utf8'); res.on('data', function (chunk) { data += chunk; }); res.on('end', function () { try { data = JSON.parse(data); } catch (exception) { var errMsg = res.statusCode !== 200 ? 'Invalid params ' + res.statusCode : 'Failed to parse JSON'; errMsg += ' : ' + JSON.stringify(data); return fn(new Error(errMsg)); } if (data.error) { if (data.error.code === errors.RPC_WALLET_UNLOCK_NEEDED && options.passphrasecallback) { return self.unlock(command, args, false, callbackFunction); } else if (data.error.code === errors.RPC_WALLET_ALREADY_UNLOCKED_STAKING_ONLY && options.passphrasecallback) { return self.unlock(command, args, true, callbackFunction); } else { var err = new Error(JSON.stringify(data)); err.code = data.error.code; return callbackFunction(err); } } var result = (data.result !== null) ? data.result : data; callbackFunction(null, result); // Now lets handle the unlockToStaking function if (commandOptions.unlockToStaking !== undefined && commandOptions.unlockToStaking) { // We need to relock incase the wallet hasn't already been locked back up from walletpassphrase command self.send('walletlock', function() { self.send('walletpassphrase', commandOptions.walletPassword, 99999999, true, function (err, result) { commandOptions.walletPassword = null; }); }); } }); }); req.on('error', callbackFunction); req.end(rpcData); return this; }, exec: function (command) { var func = api.isCommand(command) ? 'send' : 'invalid'; return this[func].apply(this, arguments); }, auth: function (user, pass) { if (user && pass) { this.opts.headers['Authorization'] = ('Basic ') + new Buffer(user + ':' + pass).toString('base64'); } return this; }, unlock: function (command, args, stakingUnlock, fn) { var self = this; var walletPassword = null; var retry = function (rpcError) { if (rpcError) { fn(rpcError); } else { var sendArgs = args.slice(); // If we need to unlock back to staking then we will modify // the command to be an object, which will be picked up by // send. Which will handle it from there. // We can use the wallet password as it gets set before this function is called // its a weird event of functions being called. if (stakingUnlock) { command = { command: command, unlockToStaking: stakingUnlock, walletPassword: walletPassword } } sendArgs.unshift(command); sendArgs.push(fn); // The apply() method calls a function with a given this value and arguments provided as an array. // https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Function/apply self.send.apply(self, sendArgs); // Clear it immediately, don't know enough about JS to know if this is useless. walletPassword = null; } }; // The line below calls the user specific callback with the 3 arguments: command, args, callback // Once the user has finished retrieving the passphrase, the callback will be called to complete // the actual command wanting to be performed in the first place. this.opts.passphrasecallback(command, args, function (error, passphrase, timeout, staking) { // Set the variable above as we need to use it in case of needing to re-unlock the wallet // back to staking mode. walletPassword = passphrase; // The application has called the actual "callback" parameter within their custom callback. if (staking == null || staking == undefined) { staking = false; } // If the wallet was currently staking we need to perform a few more steps. // 1. Lock the wallet // 2. Send the password to properly unlock it // 3. Actually send the command // 4. After the command, we need to re-unlock it to staking mode var handleRetry = function () { if (error) { fn(error); } else { // Send the unlock, and set the callback to the retry function which will // re-call the original RPC command we intended. self.send('walletpassphrase', passphrase, timeout, staking, retry); } }; if (stakingUnlock) { // Since the wallet is partially locked, we have to lock it back to fully unlock it (logic) self.send('walletlock', function (err, info) { handleRetry(); }); } else { // Since the wallet is locked fully, we will call it normally to unlock it handleRetry(); } }); }, set: function (k, v) { var type = typeof(k); if (typeof(k) === 'object') { for (var key in k) { if (!k.hasOwnProperty(key)) { continue; // Check } this.set(key, k[key]); } return; } var opts = this.opts; k = k.toLowerCase(); if (opts.hasOwnProperty(k)) { opts[k] = v; if (/^(user|pass)$/.test(k)) { this.auth(opts.user, opts.pass) } else if (k === 'host') { opts.headers['Host'] = v } else if (k === 'passphrasecallback' || k === 'https' || k === 'ca') { opts[k] = v } } return this; }, get: function (k) { //Special case for booleans if (this.opts[k] === false) { return false; } else { if (this.opts[k] !== false) { var opt = this.opts[k.toLowerCase()] } return opt; //new Error('No such option "'+k+'" exists'); } }, errors: errors }; api.commands.forEach(function (command) { var cp = Client.prototype; var tlc = [command.toLowerCase()]; cp[command] = cp[tlc] = function () { cp.send.apply(this, tlc.concat(([]).slice.call(arguments))); }; }); module.exports = function (options) { return new Client(options) };