diff --git a/app/scripts/lib/metaRPCClientFactory.js b/app/scripts/lib/metaRPCClientFactory.js index 108da0e4c..689774f91 100644 --- a/app/scripts/lib/metaRPCClientFactory.js +++ b/app/scripts/lib/metaRPCClientFactory.js @@ -6,6 +6,7 @@ class MetaRPCClient { constructor(connectionStream) { this.connectionStream = connectionStream; this.notificationChannel = new SafeEventEmitter(); + this.uncaughtErrorChannel = new SafeEventEmitter(); this.requests = new Map(); this.connectionStream.on('data', this.handleResponse.bind(this)); this.connectionStream.on('end', this.close.bind(this)); @@ -17,34 +18,47 @@ class MetaRPCClient { }); } + onUncaughtError(handler) { + this.uncaughtErrorChannel.addListener('error', (error) => { + handler(error); + }); + } + close() { this.notificationChannel.removeAllListeners(); + this.uncaughtErrorChannel.removeAllListeners(); } handleResponse(data) { const { id, result, error, method, params } = data; + const isNotification = id === undefined && error === undefined; const cb = this.requests.get(id); - if (method && params && id) { + if (method && params && !isNotification) { // dont handle server-side to client-side requests return; } - if (method && params && !id) { + if (method && params && isNotification) { // handle servier-side to client-side notification this.notificationChannel.emit('notification', data); return; } - if (!cb) { - // not found in request list - return; - } if (error) { const e = new EthereumRpcError(error.code, error.message, error.data); // preserve the stack from serializeError e.stack = error.stack; - this.requests.delete(id); - cb(e); + if (cb) { + this.requests.delete(id); + cb(e); + return; + } + this.uncaughtErrorChannel.emit('error', e); + return; + } + + if (!cb) { + // not found in request list return; } diff --git a/app/scripts/lib/metaRPCClientFactory.test.js b/app/scripts/lib/metaRPCClientFactory.test.js index 624113c1a..d0553da73 100644 --- a/app/scripts/lib/metaRPCClientFactory.test.js +++ b/app/scripts/lib/metaRPCClientFactory.test.js @@ -85,4 +85,58 @@ describe('metaRPCClientFactory', function () { }); }); }); + + it('should be able to handle notifications', function (done) { + const streamTest = createThoughStream(); + const metaRPCClient = metaRPCClientFactory(streamTest); + + metaRPCClient.onNotification((notification) => { + assert(notification.method, 'foobarbaz'); + done(); + }); + + // send a notification + streamTest.write({ + jsonrpc: '2.0', + method: 'foobarbaz', + params: ['bar'], + }); + }); + + it('should be able to handle errors with no id', function (done) { + const streamTest = createThoughStream(); + const metaRPCClient = metaRPCClientFactory(streamTest); + + metaRPCClient.onUncaughtError((error) => { + assert(error.code, 1); + done(); + }); + + streamTest.write({ + jsonrpc: '2.0', + error: { + code: 1, + message: 'error msg', + }, + }); + }); + + it('should be able to handle errors with null id', function (done) { + const streamTest = createThoughStream(); + const metaRPCClient = metaRPCClientFactory(streamTest); + + metaRPCClient.onUncaughtError((error) => { + assert(error.code, 1); + done(); + }); + + streamTest.write({ + jsonrpc: '2.0', + id: null, + error: { + code: 1, + message: 'error msg', + }, + }); + }); });