Skip to content
This repository has been archived by the owner on Jun 14, 2018. It is now read-only.

Expand web3.js transport method wrapping to improve error reporting #6

Merged
merged 3 commits into from
Jul 18, 2017
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
23 changes: 23 additions & 0 deletions error.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
var inherits = require("util").inherits;
var TruffleError = require("truffle-error");

// HACK: string comparison seems to be only way to identify being unable to
// connect to RPC node.
var NOT_CONNECTED_MESSAGE = 'Invalid JSON RPC response: ""';

function ProviderError(message, error) {
if (message == NOT_CONNECTED_MESSAGE) {
message = "Could not connect to your Ethereum client. " +
"Please check that your Ethereum client:\n" +
" - is running\n" +
" - is accepting RPC connections (i.e., \"--rpc\" option is used in geth)\n" +
" - is accessible over the network\n" +
" - is properly configured in your Truffle configuration file (truffle.js)\n";
}
ProviderError.super_.call(this, message);
this.message = message;
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I found this pattern in truffle-compile but I don't know if any of that is extraneous here.

}

inherits(ProviderError, TruffleError);

module.exports = ProviderError;
30 changes: 3 additions & 27 deletions index.js
Original file line number Diff line number Diff line change
@@ -1,34 +1,10 @@
var Web3 = require("web3");

var wrapper = require('./wrapper');

module.exports = {
wrap: function(provider, options) {
options = options || {};

if (options.verbose || options.verboseRpc) {
this.makeVerbose(provider, options.logger);
}
return provider;
},

makeVerbose: function(provider, logger) {
logger = logger || console;

// // If you want to see what web3 is sending and receiving.
var oldAsync = provider.sendAsync;

if (oldAsync.is_verbose) return;

provider.sendAsync = function(options, callback) {
logger.log(" > " + JSON.stringify(options, null, 2).split("\n").join("\n > "));
oldAsync.call(provider, options, function(error, result) {
if (error == null) {
logger.log(" < " + JSON.stringify(result, null, 2).split("\n").join("\n < "));
}
callback(error, result)
});
};

provider.sendAsync.is_verbose = true;
return wrapper.wrap(provider, options);
},

create: function(options) {
Expand Down
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@
},
"homepage": "https://github.com/trufflesuite/truffle-provider#readme",
"dependencies": {
"truffle-error": "0.0.2",
"web3": "^0.18.0"
},
"devDependencies": {
Expand Down
138 changes: 138 additions & 0 deletions wrapper.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,138 @@
var ProviderError = require('./error');

module.exports = {
/*
* Web3.js Transport Wrapper
*
* Wraps an underlying web3 provider's RPC transport methods (send/sendAsync)
* for Truffle-specific purposes, mainly for logging / request verbosity.
*/
wrap: function(provider, options) {
/* wrapping should be idempotent */
if (provider._alreadyWrapped) return provider;

/* setup options defaults */
options = options || {};
// custom logger
options.logger = options.logger || console;
// to see what web3 is sending and receiving.
options.verbose = options.verbose || options.verboseRpc || false;

/* create wrapper functions for before/after send */
var preHook = this.preHook(options);
var postHook = this.postHook(options);

var originalSend = provider.send.bind(provider);
var originalSendAsync = provider.sendAsync.bind(provider);

/* overwrite methods */
provider.send = this.send(originalSend, preHook, postHook);
provider.sendAsync = this.sendAsync(originalSendAsync, preHook, postHook);

/* mark as wrapped */
provider._alreadyWrapped = true;

return provider;
},

/*
* Transport Hook Generators
*
* Used to wrap underlying web3.js behavior before/after sending request
* payloads to the RPC.
*
* Transport hooks may be used to perform additional operations before/after
* sending, and/or to modify request/response data.
*
* Each generator accepts an `options` argument and uses it to construct
* and return a function.
*
* Returned functions accept relevant arguments and return potentially new
* versions of those arguments (for payload/result/error overrides)
*/

// before send/sendAsync
preHook: function(options) {
return function(payload) {
if (options.verbose) {
// for request payload debugging
options.logger.log(" > " + JSON.stringify(payload, null, 2).split("\n").join("\n > "));
}

return payload;
};
},

// after send/sendAsync
postHook: function(options) {
return function(payload, error, result) {
if (error != null) {
// wrap errors in internal error class
error = new ProviderError(error.message, error);
return [payload, error, result];
}

if (options.verbose) {
options.logger.log(" < " + JSON.stringify(result, null, 2).split("\n").join("\n < "));
}

return [payload, error, result];
};
},

/*
* Transport Method Generators
*
* Generate wrapped versions of `send`/`sendAsync`, given original method and
* transport hooks.
*
* Pre-condition: originals are bound correctly (`send.bind(provider)`)
*
* Return the wrapped function matching the original function's signature.
*/

// wrap a `provider.send` function with behavior hooks
// returns a function(payload) to replace `provider.send`
send: function(originalSend, preHook, postHook) {
return function(payload) {
var result = null;
var error = null;

payload = preHook(payload);

try {
result = originalSend(payload);
} catch (e) {
error = e;
}

var modified = postHook(payload, error, result);
payload = modified[0];
error = modified[1];
result = modified[2];
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm taking this approach specifically to modify the error that gets thrown, but it doesn't work as nicely without being able to return multiple values.

Wondering if there's a less awkward way to do this same thing (if only we could use fancy new destructuring :)


if (error != null) {
throw error;
}

return result;
}
},

// wrap a `provider.sendAsync` function with behavior hooks
// returns a function(payload, callback) to replace `provider.sendAsync`
sendAsync: function(originalSendAsync, preHook, postHook) {
return function(payload, callback) {
payload = preHook(payload);

originalSendAsync(payload, function(error, result) {
var modified = postHook(payload, error, result);
payload = modified[0];
error = modified[1];
result = modified[2];

callback(error, result);
});
};
}
}