diff --git a/API.js b/API.js new file mode 100644 index 000000000..45f280848 --- /dev/null +++ b/API.js @@ -0,0 +1,200 @@ +var imports = require('soop').imports(); + +var API = function(opts) { + this._init(opts); +}; + +API.prototype._init = function(opts) { + var self = this; + + opts = opts || {}; + self.opts = opts; + + var Wallet = require('soop').load('./js/models/core/Wallet', { + Storage: opts.Storage || require('./test/FakeStorage'), + Network: opts.Network || require('./js/models/Network/WebRTC'), + Blockchain: opts.Blockchain || require('./js/models/Blockchain/Insight') + }); + + var config = { + wallet: { + requiredCopayers: opts.requiredCopayers || 3, + totalCopayers: opts.totalCopayers || 5, + } + }; + + var walletConfig = opts.walletConfig || config; + var walletOpts = opts.walletOpts || {}; + + self.wallet = self.opts.wallet || Wallet.factory.create(walletConfig, walletOpts); +}; + +API._coerceArgTypes = function(args, argTypes) { + for (var i in args) { + var arg = args[i]; + var argType = argTypes[i][1]; + if (typeof arg == 'string') { + switch (argType) { + case 'object': + args[i] = JSON.parse(arg); + break; + case 'number': + args[i] = Number(arg); + break; + } + } + } + + return args; +}; + +API.prototype._command = function(command, args, callback) { + var self = this; + + if (!command || command[0] == "_") + return callback(new Error('invalid command')); + + if (!API._checkArgTypes(command, args)) { + var argTypes = API.prototype[command].argTypes; + API._coerceArgTypes(args, argTypes) + if (!API._checkArgTypes(command, args)) + throw new Error('invalid arguments'); + } + + if (typeof self["_cmd_" + command] == 'function') { + var f = API.prototype[command]; + if (f.argTypes[f.argTypes.length-1][1] == 'function') + return self["_cmd_" + command].apply(self, args.concat([callback])); + else + return callback(null, self["_cmd_" + command].apply(self, args)); + }; + + return callback(new Error('invalid command')); +}; + +API._checkArgTypes = function(command, args) { + var f = API.prototype[command]; + + if (f.argTypes.length != args.length) { + + //if the function doesn't have a callback + if (!(f.argTypes.length == args.length + 1 && f.argTypes[f.argTypes.length-1][1] == 'function')) + return false; + } + + for (var i in args) { + if (typeof args[i] != f.argTypes[i][1]) + return false; + } + return true; +}; + +function decorate(command, argTypes) { + var d = function() { + API.prototype._command.call(this, command, Array.prototype.slice.call(arguments, 0)); + }; + + d.argTypes = argTypes; + + return d; +}; + +API.prototype._cmd_echo = function(str, callback) { + var self = this; + + return callback(null, str); +}; + +API.prototype.echo = decorate('echo', [ + ['str', 'string'], + ['callback', 'function'] + ]); + +API.prototype._cmd_echoNumber = function(num, callback) { + var self = this; + + return callback(null, num); +}; + +API.prototype.echoNumber = decorate('echoNumber', [ + ['num', 'number'], + ['callback', 'function'] + ]); + +API.prototype._cmd_echoObject = function(obj, callback) { + var self = this; + + return callback(null, obj); +}; + +API.prototype.echoObject = decorate('echoObject', [ + ['obj', 'object'], + ['callback', 'function'] + ]); + +/* +API.prototype.getBalance = function(callback) { + var self = this; + + return callback(null, self.wallet.getBalance([])); +}; + +API.prototype.getBalance.argTypes = + [ + ['callback', 'function'] + ]; +*/ + +API.prototype._cmd_getArgTypes = function(command, callback) { + var self = this; + + if (command[0] == '_' || typeof API.prototype[command] != 'function') + return callback(new Error('Invalid command')); + + var argTypes = API.prototype[command].argTypes; + + return callback(null, argTypes); +}; + +API.prototype.getArgTypes = decorate('getArgTypes', [ + ['command', 'string'], + ['callback', 'function'] + ]); + +API.prototype._cmd_getCommands = function(callback) { + var self = this; + + var fs = []; + + for (var i in API.prototype) { + var f = API.prototype[i]; + if (typeof f == 'function' && i[0] != "_") + fs.push(i); + }; + + return callback(null, fs); +}; + +API.prototype.getCommands = decorate('getCommands', [ + ['callback', 'function'] + ]); + +API.prototype._cmd_getPublicKeyRingId = function(callback) { + var self = this; + + return callback(null, self.wallet.publicKeyRing.walletId); +}; + +API.prototype.getPublicKeyRingId = decorate('getPublicKeyRingId', [ + ['callback', 'function'] + ]); + +API.prototype._cmd_help = function(callback) { + this._cmd_getCommands.apply(this, arguments); +}; + +API.prototype.help = decorate('help', [ + ['callback', 'function'] + ]); + +module.exports = require('soop')(API); diff --git a/bin/Copay.js b/bin/Copay.js deleted file mode 100644 index b8239e4bf..000000000 --- a/bin/Copay.js +++ /dev/null @@ -1,118 +0,0 @@ -var imports = require('soop').imports(); -var PublicKeyRing = imports.PublicKeyRing || require('../js/models/core/PublicKeyRing'); -var Wallet = imports.Wallet || require('../js/models/core/Wallet'); - -var Copay = function(opts) { - this._init(opts); -}; - -Copay.prototype._init = function(opts) { - var self = this; - - opts = opts ? opts : {}; - - self.optsList = [ - 'publicKeyRing', - 'wallet' - ]; - - self.opts = {}; - for (var a in self.optsList) { - if (opts.hasOwnProperty(a)) - self.opts[a] = opts[a]; - } - - self.publicKeyRing = self.opts.publicKeyRing ? self.opts.publicKeyRing : new PublicKeyRing(); - self.wallet = self.opts.wallet ? self.opts.wallet : new Wallet(); -}; - -Copay.prototype._command = function(command, args, callback) { - var self = this; - - if (command[0] == "_") - return callback(new Error('invalid command')); - - if (typeof self[command] == 'function') { - var f = Copay.prototype[command]; - if (f.argTypes[f.argTypes.length-1][1] == 'function') - return self[command].apply(self, args.concat([callback])); - else - return callback(null, self[command].apply(self, args)); - }; - - return callback(new Error('invalid command')); -}; - -Copay._checkArgTypes = function(command, args) { - var f = Copay.prototype[command]; - for (var i in args) { - if (typeof args[i] != f.argTypes[i][1]) - return false; - } - return true; -}; - -function checkArgs(name, args) { - if (!Copay._checkArgTypes(name, args)) - throw new Error('Invalid arguments'); -} - -Copay.prototype.echo = function(str, callback) { - var self = this; - checkArgs('echo', arguments); - - return callback(null, str); -}; - -Copay.prototype.echo.argTypes = - [ - ['str', 'string'], - ['callback', 'function'] - ]; - -Copay.prototype.getBalance = function(callback) { - var self = this; - checkArgs('getBalance', arguments); - - return callback(null, self.wallet.getBalance([])); -}; - -Copay.prototype.getBalance.argTypes = - [ - ['callback', 'function'] - ]; - -Copay.prototype.getCommands = function(callback) { - var self = this; - checkArgs('getCommands', arguments); - - var fs = []; - - for (var i in Copay.prototype) { - var f = Copay.prototype[i]; - if (typeof f == 'function' && i[0] != "_") - fs.push(i); - }; - - return callback(null, fs); -}; - -Copay.prototype.getCommands.argTypes = - [ - ['callback', 'function'] - ]; - -Copay.prototype.getPublicKeyRingId = function(callback) { - var self = this; - checkArgs('getPublicKeyRingId', arguments); - - return callback(null, self.publicKeyRing.id); -}; - -Copay.prototype.getPublicKeyRingId.argTypes = - [ - ['callback', 'function'] - ]; - - -module.exports = require('soop')(Copay); diff --git a/bin/copay b/bin/copay index 57ca91cbb..2721da0e2 100755 --- a/bin/copay +++ b/bin/copay @@ -1,30 +1,51 @@ #!/usr/bin/env node -var Copay = require('./Copay.js'); +var API = require('../API.js'); var commander = require('commander'); var main = function() { commander .version("0.0.1") - .option('-f, --file [file]', 'AES encrypted data file', 'copay.json.aes') + .option('-f, --file [file]', 'AES encrypted data file', 'api.json.aes') .option('-p, --pass [passphrase]', 'AES wallet passphrase') - .option('-c, --client', 'Issue command over RPC to copay daemon') + .option('-c, --client', 'Issue command over RPC to api daemon') .option('-d, --daemon', 'Run as daemon accepting RPC commands') .option('--rpcport [port]', 'RPC port [18332]', Number, 18332) .option('--rpcuser [user]', 'RPC user [user]', String, 'user') .option('--rpcpass [password]', 'RPC password [pass]', String, 'pass') .parse(process.argv); - var copay = new Copay(commander); + var api = new API(commander); var args = commander.args; - copay._command(args[0], args.slice(1), function(err, result) { - if (err) - return console.log("" + err); - - console.log(JSON.stringify(result, null, 2)); - }); + try { + var command = args[0]; + var commandArgs = args.slice(1); + + if (command[0] == '_' || typeof api[command] != 'function') + throw new Error('invalid command'); + + api[command].apply(api, commandArgs.concat([function(err, result) { + if (err) + return console.log("" + err); + + console.log(JSON.stringify(result, null, 2)); + }])); + } catch(err) { + if (err.toString() == 'Error: invalid command') { + console.log("" + err); + } + else if (err.toString() == 'Error: invalid arguments') { + console.log("" + err); + console.log('Arguments for ' + command + ':') + api.getArgTypes(command, function(err, result) { + console.log(JSON.stringify(result, null, 2)); + }); + } + else + throw (err); + } }; if (require.main === module) { diff --git a/test/test.API.js b/test/test.API.js new file mode 100644 index 000000000..5bf1edf28 --- /dev/null +++ b/test/test.API.js @@ -0,0 +1,133 @@ +'use strict'; + +var chai = chai || require('chai'); +var should = chai.should(); +var API = API || require('../API'); + +describe('API', function() { + + it('should have a command called "echo"', function() { + var api = new API(); + should.exist(api.echo); + }); + + it('should have argTypes for every command', function() { + for (var i in API.prototype) { + var f = API.prototype[i]; + if (i[0] != '_' && typeof f == 'function') { + f.argTypes.length.should.be.greaterThan(0); + } + }; + }) + + it('should throw an error for all commands when called with wrong number of arguments', function() { + var api = new API(); + for (var i in API.prototype) { + var f = API.prototype[i]; + if (i[0] != '_' && typeof f == 'function') { + var a = new Array(); + for (var j = 0; j <= f.argTypes.length + 1; j++) { + a.push(0); + } + (function() { + api[i].apply(api, a); + }).should.throw(); + } + }; + }); + + it('should have a callback in the arguments on every command', function() { + for (var i in API.prototype) { + var f = API.prototype[i]; + if (i[0] != '_' && typeof f == 'function') { + f.argTypes[f.argTypes.length-1][0].should.equal('callback'); + f.argTypes[f.argTypes.length-1][1].should.equal('function'); + } + } + }); + + describe('#echo', function() { + it('should echo a string', function(done) { + var api = new API(); + var str = 'mystr'; + api.echo(str, function(err, result) { + result.should.equal(str); + done(); + }); + }); + }); + + describe('#echoNumber', function() { + it('should echo a number', function(done) { + var api = new API(); + var num = 500; + api.echoNumber(num, function(err, result) { + result.should.equal(num); + (typeof result).should.equal('number'); + done(); + }); + }); + }); + + describe('#echoObject', function() { + it('should echo an object', function(done) { + var api = new API(); + var obj = {test:'test'}; + api.echoObject(obj, function(err, result) { + result.test.should.equal(obj.test); + (typeof result).should.equal('object'); + done(); + }); + }); + }); + + describe('#getArgTypes', function() { + it('should get the argTypes of echo', function(done) { + var api = new API(); + api.getArgTypes('echo', function(err, result) { + result[0][1].should.equal('string'); + done(); + }); + }); + }); + + describe('#getCommands', function() { + it('should get all the commands', function(done) { + var api = new API(); + var n = 0; + + for (var i in api) + if (i[0] != '_' && typeof api[i] == 'function') + n++; + + api.getCommands(function(err, result) { + result.length.should.equal(n); + done(); + }); + }); + }); + + describe('#getPublicKeyRingId', function() { + it('should get a public key ring ID', function(done) { + var api = new API(); + api.getPublicKeyRingId(function(err, result) { + result.length.should.be.greaterThan(5); + done(); + }); + }); + }); + + describe('#help', function() { + it('should call _cmd_getCommands', function(done) { + var api = new API(); + api._cmd_getCommands = function(callback) { + (typeof arguments[0]).should.equal('function'); + callback(null, ['item']); + } + api.help(function(err, result) { + result[0].should.equal('item'); + done(); + }); + }); + }); +});