modules/sockets/web3Base.js
const _ = require('../utils/underscore.js');
const Q = require('bluebird');
const oboe = require('oboe');
const SocketBase = require('./base');
const Socket = SocketBase.Socket;
const STATE = SocketBase.STATE;
module.exports = class Web3Socket extends Socket {
constructor(socketMgr, id) {
super(socketMgr, id);
this._sendRequests = {};
this._handleSocketResponse();
}
/**
* Send an RPC call.
* @param {Array|Object} single or array of payloads.
* @param {Object} options Additional options.
* @param {Boolean} [options.fullResult] If set then will return full result
* JSON, not just result value.
* @return {Promise}
*/
send(payload, options) {
return Q.try(() => {
if (!this.isConnected) {
throw new Error('Not connected');
}
const isBatch = _.isArray(payload);
const finalPayload = isBatch
? _.map(payload, p => this._finalizeSinglePayload(p))
: this._finalizeSinglePayload(payload);
/*
* For batch requests we use the id of the first request as the
* id to refer to the batch as one. We can do this because the
* response will also come back as a batch, in the same order as the
* the requests within the batch were sent.
*/
const id = isBatch ? finalPayload[0].id : finalPayload.id;
this._log.trace(isBatch ? 'Batch request' : 'Request', id, finalPayload);
this._sendRequests[id] = {
options,
/* Preserve the original id of the request so that we can
update the response with it */
origId: isBatch ? _.map(payload, p => p.id) : payload.id
};
this.write(JSON.stringify(finalPayload));
return new Q((resolve, reject) => {
_.extend(this._sendRequests[id], {
resolve,
reject
});
});
});
}
/**
* Construct a payload object.
* @param {Object} payload Payload to send.
* @param {String} payload.method Method name.
* @param {Object} [payload.params] Method arguments.
* @return {Object} final payload object
*/
_finalizeSinglePayload(payload) {
if (!payload.method) {
throw new Error('Method required');
}
return {
jsonrpc: '2.0',
id: _.uuid(),
method: payload.method,
params: payload.params || []
};
}
/**
* Handle responses from Geth.
* Responses are false, a single object, or an array of objects
*/
_handleSocketResponse() {
oboe(this)
.done(result => {
this._log.trace('JSON response', result);
try {
const isBatch = _.isArray(result);
const firstItem = isBatch ? result[0] : result;
const req = firstItem.id ? this._sendRequests[firstItem.id] : null;
if (req) {
this._log.trace(
isBatch ? 'Batch response' : 'Response',
firstItem.id,
result
);
// if we don't want full JSON result, send just the result
if (!_.get(req, 'options.fullResult')) {
if (isBatch) {
result = _.map(result, r => r.result);
} else {
result = result.result;
}
} else {
// restore original ids
if (isBatch) {
req.origId.forEach((id, idx) => {
if (result[idx]) {
result[idx].id = id;
}
});
} else {
result.id = req.origId;
}
}
req.resolve({
isBatch,
result
});
} else {
// not a response to a request so pass it on as a notification
this.emit('data-notification', result);
}
} catch (err) {
this._log.error('Error handling socket response', err);
}
})
.fail(err => {
this._log.error('Socket response error', err);
_.each(this._sendRequests, req => {
if (req.reject) {
req.reject(err);
}
});
this._sendRequests = {};
});
}
};