diff --git a/CHANGELOG.md b/CHANGELOG.md index f0774ce8c05..1a39a5d6b25 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -185,3 +185,4 @@ Released with 1.0.0-beta.37 code base. ### Fixed - Fix intermittent CI build issues with `dtslint`. (#3479) +- Fix provider "error" / "end" events not fired when Websocket provider disconnects (#3485) diff --git a/packages/web3-core-requestmanager/src/index.js b/packages/web3-core-requestmanager/src/index.js index b91bdb83338..97b07c9fded 100644 --- a/packages/web3-core-requestmanager/src/index.js +++ b/packages/web3-core-requestmanager/src/index.js @@ -130,6 +130,13 @@ RequestManager.prototype.setProvider = function (provider, net) { subscription.callback(errors.ConnectionCloseError(event)); _this.subscriptions.delete(subscription.subscription.id); }); + + if(_this.provider.emit){ + _this.provider.emit('error', errors.ConnectionCloseError(event)); + } + } + if(_this.provider.emit){ + _this.provider.emit('end', event); } }); diff --git a/test/eth.subscribe.ganache.js b/test/eth.subscribe.ganache.js index d3d638843cc..bd2579d3ac2 100644 --- a/test/eth.subscribe.ganache.js +++ b/test/eth.subscribe.ganache.js @@ -224,18 +224,27 @@ describe('subscription connect/reconnect', function () { }); it('errors when the subscription got established (is running) and the connection does get closed', function () { + this.timeout(5000); + let counter = 0; + return new Promise(async function (resolve) { web3.eth .subscribe('newBlockHeaders') .once('data', async function () { await pify(server.close)(); }) - .once('error', function (err) { + .on('error', function (err) { + counter++; assert(err.message.includes('CONNECTION ERROR')); assert(err.message.includes('close code `1006`')); assert(err.message.includes('Connection dropped by remote peer.')); - resolve(); }); + + // Make sure error handler doesn't fire twice + await waitSeconds(2); + assert.equal(counter, 1); + web3.eth.currentProvider.removeAllListeners(); + resolve(); }); }); diff --git a/test/websocket.ganache.js b/test/websocket.ganache.js index b385f5abdba..f5525ba94ff 100644 --- a/test/websocket.ganache.js +++ b/test/websocket.ganache.js @@ -32,6 +32,111 @@ describe('WebsocketProvider (ganache)', function () { } }); + it('"error" handler fires if the client closes unilaterally', async function(){ + this.timeout(5000); + + server = ganache.server({port: port}); + await pify(server.listen)(port); + + // Open and verify connection + web3 = new Web3(new Web3.providers.WebsocketProvider(host + port)); + await web3.eth.getBlockNumber(); + + await new Promise(async function(resolve){ + web3.currentProvider.once('error', function(err){ + assert(err.message.includes('Connection dropped by remote peer.')) + assert(err.message.includes('1006')); + resolve(); + }); + + await pify(server.close)(); + }) + }) + + it('"error" handler fires if Web3 disconnects with error code', async function(){ + this.timeout(5000); + + server = ganache.server({port: port}); + await pify(server.listen)(port); + + // Open and verify connection + web3 = new Web3(new Web3.providers.WebsocketProvider(host + port)); + await web3.eth.getBlockNumber(); + + await new Promise(async function(resolve){ + web3.currentProvider.once('error', function(err){ + assert(err.message.includes('1012')); + assert(err.message.includes('restart')); + resolve(); + }); + + web3.currentProvider.disconnect(1012, 'restart'); + }) + }) + + it('"error" handler *DOES NOT* fire if disconnection is clean', async function(){ + this.timeout(5000); + + server = ganache.server({port: port}); + await pify(server.listen)(port); + + // Open and verify connection + web3 = new Web3(new Web3.providers.WebsocketProvider(host + port)); + await web3.eth.getBlockNumber(); + + await new Promise(async function(resolve, reject){ + web3.currentProvider.once('error', function(err){ + reject('Should not fire error handler') + }); + + web3.currentProvider.disconnect(1000); + await utils.waitSeconds(2) + resolve(); + }) + }) + + it('"end" handler fires with close event object if client disconnect', async function(){ + this.timeout(5000); + + server = ganache.server({port: port}); + await pify(server.listen)(port); + + // Open and verify connection + web3 = new Web3(new Web3.providers.WebsocketProvider(host + port)); + await web3.eth.getBlockNumber(); + + await new Promise(async function(resolve){ + web3.currentProvider.once('end', function(event){ + assert.equal(event.type, 'close'); + assert.equal(event.wasClean, false); + resolve(); + }); + + await pify(server.close)(); + }) + }) + + it('"end" handler fires with close event object if Web3 disconnects', async function(){ + this.timeout(5000); + + server = ganache.server({port: port}); + await pify(server.listen)(port); + + // Open and verify connection + web3 = new Web3(new Web3.providers.WebsocketProvider(host + port)); + await web3.eth.getBlockNumber(); + + await new Promise(async function(resolve){ + web3.currentProvider.once('end', function(event){ + assert.equal(event.type, 'close'); + assert.equal(event.wasClean, true); + resolve(); + }); + + web3.currentProvider.disconnect(1000); + }) + }); + // Here, the first error (try/caught) is fired by the request queue checker in // the onClose handler. The second error is fired by the readyState check in .send it('errors when requests continue after socket closed', async function () {