diff --git a/app.js b/app.js index 1a0ee5f..c13a39e 100644 --- a/app.js +++ b/app.js @@ -1,6 +1,7 @@ #!/usr/bin/env node var ExpressApp = require('./lib/expressapp'); +var WsApp = require('./lib/wsapp'); var basePath = process.env.BWS_BASE_PATH || '/bws/api'; var port = process.env.BWS_PORT || 3001; @@ -8,6 +9,12 @@ var port = process.env.BWS_PORT || 3001; var app = ExpressApp.start({ basePath: basePath, }); -app.listen(port); +//app.listen(port); + +var server = require('http').Server(app); + +var ws = WsApp.start(server); + +server.listen(port); console.log('Bitcore Wallet Service running on port ' + port); diff --git a/lib/eventbroadcaster.js b/lib/eventbroadcaster.js new file mode 100644 index 0000000..e8ab506 --- /dev/null +++ b/lib/eventbroadcaster.js @@ -0,0 +1,25 @@ +'use strict'; + +var log = require('npmlog'); +log.debug = log.verbose; +var inherits = require('inherits'); +var events = require('events'); +var nodeutil = require('util'); + +function EventBroadcaster() {}; + +nodeutil.inherits(EventBroadcaster, events.EventEmitter); + +EventBroadcaster.prototype.broadcast = function(eventName, serviceInstance, args) { + this.emit(eventName, serviceInstance, args); +}; + +var _eventBroadcasterInstance; +EventBroadcaster.singleton = function() { + if (!_eventBroadcasterInstance) { + _eventBroadcasterInstance = new EventBroadcaster(); + } + return _eventBroadcasterInstance; +}; + +module.exports = EventBroadcaster.singleton(); diff --git a/lib/model/notification.js b/lib/model/notification.js index 18f94ec..2cffa09 100644 --- a/lib/model/notification.js +++ b/lib/model/notification.js @@ -37,6 +37,7 @@ Notification.create = function(opts) { x.id = _.padLeft(now, 14, '0') + _.padLeft(opts.ticker || 0, 4, '0'); x.type = opts.type || 'general'; x.data = opts.data; + x.creatorId = opts.creatorId; return x; }; @@ -48,6 +49,7 @@ Notification.fromObj = function(obj) { x.id = obj.id; x.type = obj.type, x.data = obj.data; + x.creatorId = obj.creatorId; return x; }; diff --git a/lib/server.js b/lib/server.js index 8864375..40d7a0b 100644 --- a/lib/server.js +++ b/lib/server.js @@ -4,9 +4,6 @@ var $ = require('preconditions').singleton(); var async = require('async'); var log = require('npmlog'); log.debug = log.verbose; -var inherits = require('inherits'); -var events = require('events'); -var nodeutil = require('util'); var WalletUtils = require('bitcore-wallet-utils'); var Bitcore = WalletUtils.Bitcore; @@ -18,6 +15,7 @@ var Explorers = require('bitcore-explorers'); var ClientError = require('./clienterror'); var Utils = require('./utils'); var Storage = require('./storage'); +var EventBroadcaster = require('./eventbroadcaster'); var Wallet = require('./model/wallet'); var Copayer = require('./model/copayer'); @@ -28,6 +26,7 @@ var Notification = require('./model/notification'); var initialized = false; var storage, blockExplorer; + /** * Creates an instance of the Bitcore Wallet Service. * @constructor @@ -41,8 +40,9 @@ function WalletService() { this.notifyTicker = 0; }; -nodeutil.inherits(WalletService, events.EventEmitter); - +WalletService.onNotification = function(func) { + EventBroadcaster.on('notification', func); +}; /** * Initializes global settings for all instances. @@ -161,6 +161,14 @@ WalletService.prototype._verifySignature = function(text, signature, pubKey) { return WalletUtils.verifyMessage(text, signature, pubKey); }; +/** + * _emit + * + * @param {Object} args + */ +WalletService.prototype._emit = function(eventName, args) { + EventBroadcaster.broadcast(eventName, this, args); +}; /** * _notify @@ -180,9 +188,10 @@ WalletService.prototype._notify = function(type, data) { type: type, data: data, ticker: this.notifyTicker++, + creatorId: self.copayerId, }); this.storage.storeNotification(walletId, n, function() { - self.emit(n); + self._emit('notification', n); }); }; diff --git a/lib/wsapp.js b/lib/wsapp.js new file mode 100644 index 0000000..af7698f --- /dev/null +++ b/lib/wsapp.js @@ -0,0 +1,52 @@ +'use strict'; + +var $ = require('preconditions').singleton(); +var _ = require('lodash'); +var async = require('async'); +var log = require('npmlog'); +var express = require('express'); +var querystring = require('querystring'); +var bodyParser = require('body-parser') +var Uuid = require('uuid'); + +var WalletService = require('./server'); + +log.debug = log.verbose; +log.level = 'debug'; + +var subscriptions = {}; + +var WsApp = function() {}; + +WsApp._unauthorized = function() { + socket.emit('unauthorized'); + socket.disconnect(); +}; + +WsApp.start = function(server) { + var self = this; + + var io = require('socket.io')(server); + + WalletService.onNotification(function(serviceInstance, args) { + io.to(serviceInstance.walletId).emit('notification', args); + }); + + io.on('connection', function(socket) { + socket.nonce = Uuid.v4(); + socket.emit('challenge', socket.nonce); + + socket.on('authorize', function(data) { + if (data.message != socket.nonce) return WsApp.unauthorized(); + + WalletService.getInstanceWithAuth(data, function(err, res) { + if (err) return WsApp.unauthorized(); + + socket.join(res.walletId); + socket.emit('authorized'); + }); + }); + }); +}; + +module.exports = WsApp; diff --git a/package.json b/package.json index 3581cf5..db3ff0a 100644 --- a/package.json +++ b/package.json @@ -2,7 +2,7 @@ "name": "bitcore-wallet-service", "description": "A service for Mutisig HD Bitcoin Wallets", "author": "BitPay Inc", - "version": "0.0.13", + "version": "0.0.14", "keywords": [ "bitcoin", "copay", @@ -35,6 +35,7 @@ "read": "^1.0.5", "request": "^2.53.0", "sjcl": "^1.0.2", + "socket.io": "^1.3.5", "uuid": "*" }, "devDependencies": { @@ -53,13 +54,10 @@ "coveralls": "./node_modules/.bin/istanbul cover ./node_modules/mocha/bin/_mocha --report lcovonly -- -R spec && cat ./coverage/lcov.info | ./node_modules/coveralls/bin/coveralls.js && rm -rf ./coverage" }, "contributors": [{ - "name": "Ivan Socolsky", - "email": "ivan@bitpay.com" - }, - - { - "name": "Matias Alejo Garcia", - "email": "ematiu@gmail.com" - } - ] + "name": "Ivan Socolsky", + "email": "ivan@bitpay.com" + }, { + "name": "Matias Alejo Garcia", + "email": "ematiu@gmail.com" + }] } diff --git a/test/integration/server.js b/test/integration/server.js index f726385..c57b926 100644 --- a/test/integration/server.js +++ b/test/integration/server.js @@ -1611,6 +1611,7 @@ describe('Copay server', function() { should.not.exist(err); var last = _.last(notifications); last.type.should.equal('TxProposalFinallyAccepted'); + last.creatorId.should.equal(wallet.copayers[1].id); last.data.txProposalId.should.equal(txp.id); done(); }); @@ -1868,6 +1869,9 @@ describe('Copay server', function() { should.not.exist(err); var types = _.pluck(notifications, 'type'); types.should.deep.equal(['NewTxProposal', 'NewTxProposal', 'NewTxProposal', 'NewAddress']); + var creators = _.uniq(_.pluck(notifications, 'creatorId')); + creators.length.should.equal(1); + creators[0].should.equal(wallet.copayers[0].id); done(); }); }); @@ -1945,7 +1949,7 @@ describe('Copay server', function() { server.getPendingTxs({}, function(err, txs) { var tx = txs[2]; var signatures = helpers.clientSign(tx, TestData.copayers[0].xPrivKey); - sinon.spy(server, 'emit'); + sinon.spy(server, '_emit'); server.signTx({ txProposalId: tx.id, signatures: signatures, @@ -1964,9 +1968,9 @@ describe('Copay server', function() { var types = _.pluck(notifications, 'type'); types.should.deep.equal(['NewOutgoingTx', 'TxProposalFinallyAccepted', 'TxProposalAcceptedBy']); // Check also events - server.emit.getCall(0).args[0].type.should.equal('TxProposalAcceptedBy'); - server.emit.getCall(1).args[0].type.should.equal('TxProposalFinallyAccepted');; - server.emit.getCall(2).args[0].type.should.equal('NewOutgoingTx'); + server._emit.getCall(0).args[1].type.should.equal('TxProposalAcceptedBy'); + server._emit.getCall(1).args[1].type.should.equal('TxProposalFinallyAccepted');; + server._emit.getCall(2).args[1].type.should.equal('NewOutgoingTx'); done(); });