-
Notifications
You must be signed in to change notification settings - Fork 5.1k
Fixes 'Connection not open on send' #3171
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -20,7 +20,7 @@ | |
* @date 2017 | ||
*/ | ||
|
||
"use strict"; | ||
'use strict'; | ||
|
||
var _ = require('underscore'); | ||
var errors = require('web3-core-helpers').errors; | ||
|
@@ -41,8 +41,7 @@ if (isNode) { | |
parseURL = function(url) { | ||
return new newURL(url); | ||
}; | ||
} | ||
else { | ||
} else { | ||
// Web3 supports Node.js 5, so fall back to the legacy URL API if necessary | ||
parseURL = require('url').parse; | ||
} | ||
|
@@ -55,9 +54,7 @@ if (isNode) { | |
// Default connection ws://localhost:8546 | ||
|
||
|
||
|
||
|
||
var WebsocketProvider = function WebsocketProvider(url, options) { | ||
var WebsocketProvider = function WebsocketProvider(url, options) { | ||
if (!Ws) { | ||
throw new Error('websocket is not available'); | ||
} | ||
|
@@ -81,7 +78,7 @@ var WebsocketProvider = function WebsocketProvider(url, options) { | |
|
||
// Allow a custom client configuration | ||
var clientConfig = options.clientConfig || undefined; | ||
|
||
// Allow a custom request options | ||
// https://github.com/theturtle32/WebSocket-Node/blob/master/docs/WebSocketClient.md#connectrequesturl-requestedprotocols-origin-headers-requestoptions | ||
var requestOptions = options.requestOptions || undefined; | ||
|
@@ -101,29 +98,29 @@ var WebsocketProvider = function WebsocketProvider(url, options) { | |
/*jshint maxcomplexity: 6 */ | ||
var data = (typeof e.data === 'string') ? e.data : ''; | ||
|
||
_this._parseResponse(data).forEach(function(result){ | ||
_this._parseResponse(data).forEach(function(result) { | ||
|
||
var id = null; | ||
|
||
// get the id which matches the returned id | ||
if(_.isArray(result)) { | ||
result.forEach(function(load){ | ||
if(_this.responseCallbacks[load.id]) | ||
if (_.isArray(result)) { | ||
result.forEach(function(load) { | ||
if (_this.responseCallbacks[load.id]) | ||
id = load.id; | ||
}); | ||
} else { | ||
id = result.id; | ||
} | ||
|
||
// notification | ||
if(!id && result && result.method && result.method.indexOf('_subscription') !== -1) { | ||
_this.notificationCallbacks.forEach(function(callback){ | ||
if(_.isFunction(callback)) | ||
if (!id && result && result.method && result.method.indexOf('_subscription') !== -1) { | ||
_this.notificationCallbacks.forEach(function(callback) { | ||
if (_.isFunction(callback)) | ||
callback(result); | ||
}); | ||
|
||
// fire the callback | ||
} else if(_this.responseCallbacks[id]) { | ||
} else if (_this.responseCallbacks[id]) { | ||
_this.responseCallbacks[id](null, result); | ||
delete _this.responseCallbacks[id]; | ||
} | ||
|
@@ -132,26 +129,26 @@ var WebsocketProvider = function WebsocketProvider(url, options) { | |
|
||
// make property `connected` which will return the current connection status | ||
Object.defineProperty(this, 'connected', { | ||
get: function () { | ||
return this.connection && this.connection.readyState === this.connection.OPEN; | ||
}, | ||
enumerable: true, | ||
}); | ||
get: function() { | ||
return this.connection && this.connection.readyState === this.connection.OPEN; | ||
}, | ||
enumerable: true | ||
}); | ||
}; | ||
|
||
/** | ||
Will add the error and end event to timeout existing calls | ||
|
||
@method addDefaultEvents | ||
*/ | ||
WebsocketProvider.prototype.addDefaultEvents = function(){ | ||
WebsocketProvider.prototype.addDefaultEvents = function() { | ||
var _this = this; | ||
|
||
this.connection.onerror = function(){ | ||
this.connection.onerror = function() { | ||
_this._timeout(); | ||
}; | ||
|
||
this.connection.onclose = function(){ | ||
this.connection.onclose = function() { | ||
_this._timeout(); | ||
|
||
// reset all requests and callbacks | ||
|
@@ -175,30 +172,30 @@ WebsocketProvider.prototype._parseResponse = function(data) { | |
|
||
// DE-CHUNKER | ||
var dechunkedData = data | ||
.replace(/\}[\n\r]?\{/g,'}|--|{') // }{ | ||
.replace(/\}\][\n\r]?\[\{/g,'}]|--|[{') // }][{ | ||
.replace(/\}[\n\r]?\[\{/g,'}|--|[{') // }[{ | ||
.replace(/\}\][\n\r]?\{/g,'}]|--|{') // }]{ | ||
.replace(/\}[\n\r]?\{/g, '}|--|{') // }{ | ||
.replace(/\}\][\n\r]?\[\{/g, '}]|--|[{') // }][{ | ||
.replace(/\}[\n\r]?\[\{/g, '}|--|[{') // }[{ | ||
.replace(/\}\][\n\r]?\{/g, '}]|--|{') // }]{ | ||
.split('|--|'); | ||
|
||
dechunkedData.forEach(function(data){ | ||
dechunkedData.forEach(function(data) { | ||
|
||
// prepend the last chunk | ||
if(_this.lastChunk) | ||
if (_this.lastChunk) | ||
data = _this.lastChunk + data; | ||
|
||
var result = null; | ||
|
||
try { | ||
result = JSON.parse(data); | ||
|
||
} catch(e) { | ||
} catch (e) { | ||
|
||
_this.lastChunk = data; | ||
|
||
// start timeout to cancel all requests | ||
clearTimeout(_this.lastChunkTimeout); | ||
_this.lastChunkTimeout = setTimeout(function(){ | ||
_this.lastChunkTimeout = setTimeout(function() { | ||
_this._timeout(); | ||
throw errors.InvalidResponse(data); | ||
}, 1000 * 15); | ||
|
@@ -210,7 +207,7 @@ WebsocketProvider.prototype._parseResponse = function(data) { | |
clearTimeout(_this.lastChunkTimeout); | ||
_this.lastChunk = null; | ||
|
||
if(result) | ||
if (result) | ||
returnValues.push(result); | ||
}); | ||
|
||
|
@@ -235,7 +232,7 @@ WebsocketProvider.prototype._addResponseCallback = function(payload, callback) { | |
|
||
// schedule triggering the error response if a custom timeout is set | ||
if (this._customTimeout) { | ||
setTimeout(function () { | ||
setTimeout(function() { | ||
if (_this.responseCallbacks[id]) { | ||
_this.responseCallbacks[id](errors.ConnectionTimeout(_this._customTimeout)); | ||
delete _this.responseCallbacks[id]; | ||
|
@@ -250,36 +247,42 @@ WebsocketProvider.prototype._addResponseCallback = function(payload, callback) { | |
@method _timeout | ||
*/ | ||
WebsocketProvider.prototype._timeout = function() { | ||
for(var key in this.responseCallbacks) { | ||
if(this.responseCallbacks.hasOwnProperty(key)){ | ||
for (var key in this.responseCallbacks) { | ||
if (this.responseCallbacks.hasOwnProperty(key)) { | ||
this.responseCallbacks[key](errors.InvalidConnection('on WS')); | ||
delete this.responseCallbacks[key]; | ||
} | ||
} | ||
}; | ||
|
||
|
||
WebsocketProvider.prototype.send = function (payload, callback) { | ||
WebsocketProvider.prototype.send = function(payload, callback) { | ||
var _this = this; | ||
|
||
if (this.connection.readyState === this.connection.CONNECTING) { | ||
setTimeout(function () { | ||
_this.send(payload, callback); | ||
}, 10); | ||
this.connection.addEventListener( | ||
'open', | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. What happens if this is never fired because the client is not available, for example when testing against a local node the user forgot to turn on? In the current flow it looks like it will send and error, but here maybe hang? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This is true it will with the changed lines of code just hang until the connection got established and in the case of a non-existing host will it never execute the JSON-RPC methods we have in the pipeline. The emitted error There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Great analysis! To me it seems like there are two execution contexts for Web3 with opposite expectations:
At the moment, web3 behaves mostly the way NodeJS contexts would prefer, and there are lots of complaints on the browser / dapp side. Maybe we can just acknowledge the difference in the docs/release notes and address it by toggling the different behaviors via the WDYT? This change could be paired with #3167... |
||
function() { | ||
_this.send(payload, callback); | ||
}, | ||
{once: true} | ||
); | ||
|
||
return; | ||
} | ||
|
||
// try reconnect, when connection is gone | ||
// if(!this.connection.writable) | ||
// this.connection.connect({url: this.url}); | ||
if (this.connection.readyState !== this.connection.OPEN) { | ||
console.error('connection not open on send()'); | ||
nivida marked this conversation as resolved.
Show resolved
Hide resolved
|
||
if (typeof this.connection.onerror === 'function') { | ||
this.connection.onerror(new Error('connection not open')); | ||
this.connection.onerror(new Error('connection not open on send()')); | ||
} else { | ||
console.error('no error callback'); | ||
} | ||
callback(new Error('connection not open')); | ||
|
||
callback(new Error('connection not open on send()')); | ||
nivida marked this conversation as resolved.
Show resolved
Hide resolved
|
||
|
||
return; | ||
} | ||
|
||
|
@@ -294,12 +297,12 @@ WebsocketProvider.prototype.send = function (payload, callback) { | |
@param {String} type 'notifcation', 'connect', 'error', 'end' or 'data' | ||
@param {Function} callback the callback to call | ||
*/ | ||
WebsocketProvider.prototype.on = function (type, callback) { | ||
WebsocketProvider.prototype.on = function(type, callback) { | ||
|
||
if(typeof callback !== 'function') | ||
if (typeof callback !== 'function') | ||
throw new Error('The second parameter callback must be a function.'); | ||
|
||
switch(type){ | ||
switch (type) { | ||
case 'data': | ||
this.notificationCallbacks.push(callback); | ||
break; | ||
|
@@ -331,13 +334,13 @@ WebsocketProvider.prototype.on = function (type, callback) { | |
@param {String} type 'notifcation', 'connect', 'error', 'end' or 'data' | ||
@param {Function} callback the callback to call | ||
*/ | ||
WebsocketProvider.prototype.removeListener = function (type, callback) { | ||
WebsocketProvider.prototype.removeListener = function(type, callback) { | ||
var _this = this; | ||
|
||
switch(type){ | ||
switch (type) { | ||
case 'data': | ||
this.notificationCallbacks.forEach(function(cb, index){ | ||
if(cb === callback) | ||
this.notificationCallbacks.forEach(function(cb, index) { | ||
if (cb === callback) | ||
_this.notificationCallbacks.splice(index, 1); | ||
}); | ||
break; | ||
|
@@ -356,8 +359,8 @@ WebsocketProvider.prototype.removeListener = function (type, callback) { | |
@method removeAllListeners | ||
@param {String} type 'notifcation', 'connect', 'error', 'end' or 'data' | ||
*/ | ||
WebsocketProvider.prototype.removeAllListeners = function (type) { | ||
switch(type){ | ||
WebsocketProvider.prototype.removeAllListeners = function(type) { | ||
switch (type) { | ||
case 'data': | ||
this.notificationCallbacks = []; | ||
break; | ||
|
@@ -387,7 +390,7 @@ WebsocketProvider.prototype.removeAllListeners = function (type) { | |
|
||
@method reset | ||
*/ | ||
WebsocketProvider.prototype.reset = function () { | ||
WebsocketProvider.prototype.reset = function() { | ||
this._timeout(); | ||
this.notificationCallbacks = []; | ||
|
||
|
@@ -398,7 +401,7 @@ WebsocketProvider.prototype.reset = function () { | |
this.addDefaultEvents(); | ||
}; | ||
|
||
WebsocketProvider.prototype.disconnect = function () { | ||
WebsocketProvider.prototype.disconnect = function() { | ||
if (this.connection) { | ||
this.connection.close(); | ||
} | ||
|
@@ -410,7 +413,7 @@ WebsocketProvider.prototype.disconnect = function () { | |
* @method supportsSubscriptions | ||
* @returns {boolean} | ||
*/ | ||
WebsocketProvider.prototype.supportsSubscriptions = function () { | ||
WebsocketProvider.prototype.supportsSubscriptions = function() { | ||
return true; | ||
}; | ||
|
||
|
Uh oh!
There was an error while loading. Please reload this page.