web3.js/lib/web3/requestmanager.js

264 lines
6.7 KiB
JavaScript

/*
This file is part of ethereum.js.
ethereum.js is free software: you can redistribute it and/or modify
it under the terms of the GNU Lesser General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
ethereum.js is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU Lesser General Public License for more details.
You should have received a copy of the GNU Lesser General Public License
along with ethereum.js. If not, see <http://www.gnu.org/licenses/>.
*/
/**
* @file requestmanager.js
* @author Jeffrey Wilcke <[email protected]>
* @author Marek Kotewicz <[email protected]>
* @author Marian Oancea <[email protected]>
* @author Fabian Vogelsteller <[email protected]>
* @author Gav Wood <[email protected]>
* @date 2014
*/
var Jsonrpc = require('./jsonrpc');
var utils = require('../utils/utils');
var c = require('../utils/config');
var errors = require('./errors');
/**
* It's responsible for passing messages to providers
* It's also responsible for polling the ethereum node for incoming messages
* Default poll timeout is 1 second
* Singleton
*/
var RequestManager = function (provider) {
// singleton pattern
if (arguments.callee._singletonInstance) {
return arguments.callee._singletonInstance;
}
arguments.callee._singletonInstance = this;
this.provider = provider;
this.polls = {};
this.timeout = null;
this.isPolling = false;
};
/**
* @return {RequestManager} singleton
*/
RequestManager.getInstance = function () {
var instance = new RequestManager();
return instance;
};
/**
* Should be used to synchronously send request
*
* @method send
* @param {Object} data
* @return {Object}
*/
RequestManager.prototype.send = function (data) {
if (!this.provider) {
console.error(errors.InvalidProvider());
return null;
}
var payload = Jsonrpc.getInstance().toPayload(data.method, data.params);
var result = this.provider.send(payload);
if (!Jsonrpc.getInstance().isValidResponse(result)) {
throw errors.InvalidResponse(result);
}
return result.result;
};
/**
* Should be used to asynchronously send request
*
* @method sendAsync
* @param {Object} data
* @param {Function} callback
*/
RequestManager.prototype.sendAsync = function (data, callback) {
if (!this.provider) {
return callback(errors.InvalidProvider());
}
var payload = Jsonrpc.getInstance().toPayload(data.method, data.params);
this.provider.sendAsync(payload, function (err, result) {
if (err) {
return callback(err);
}
if (!Jsonrpc.getInstance().isValidResponse(result)) {
return callback(errors.InvalidResponse(result));
}
callback(null, result.result);
});
};
/**
* Should be called to asynchronously send batch request
*
* @method sendBatch
* @param {Array} batch data
* @param {Function} callback
*/
RequestManager.prototype.sendBatch = function (data, callback) {
if (!this.provider) {
return callback(errors.InvalidProvider());
}
var payload = Jsonrpc.getInstance().toBatchPayload(data);
this.provider.sendAsync(payload, function (err, results) {
if (err) {
return callback(err);
}
if (!utils.isArray(results)) {
return callback(errors.InvalidResponse(results));
}
callback(err, results);
});
};
/**
* Should be used to set provider of request manager
*
* @method setProvider
* @param {Object}
*/
RequestManager.prototype.setProvider = function (p) {
this.provider = p;
if (this.provider && !this.isPolling) {
this.poll();
this.isPolling = true;
}
};
/*jshint maxparams:4 */
/**
* Should be used to start polling
*
* @method startPolling
* @param {Object} data
* @param {Number} pollId
* @param {Function} callback
* @param {Function} uninstall
*
* @todo cleanup number of params
*/
RequestManager.prototype.startPolling = function (data, pollId, callback, uninstall) {
this.polls['poll_'+ pollId] = {data: data, id: pollId, callback: callback, uninstall: uninstall};
};
/*jshint maxparams:3 */
/**
* Should be used to stop polling for filter with given id
*
* @method stopPolling
* @param {Number} pollId
*/
RequestManager.prototype.stopPolling = function (pollId) {
delete this.polls['poll_'+ pollId];
};
/**
* Should be called to reset the polling mechanism of the request manager
*
* @method reset
*/
RequestManager.prototype.reset = function () {
for (var key in this.polls) {
this.polls[key].uninstall();
}
this.polls = {};
if (this.timeout) {
clearTimeout(this.timeout);
this.timeout = null;
}
this.poll();
};
/**
* Should be called to poll for changes on filter with given id
*
* @method poll
*/
RequestManager.prototype.poll = function () {
/*jshint maxcomplexity: 6 */
this.timeout = setTimeout(this.poll.bind(this), c.ETH_POLLING_TIMEOUT);
if (Object.keys(this.polls).length === 0) {
return;
}
if (!this.provider) {
console.error(errors.InvalidProvider());
return;
}
var pollsData = [];
var pollsKeys = [];
for (var key in this.polls) {
pollsData.push(this.polls[key].data);
pollsKeys.push(key);
}
if (pollsData.length === 0) {
return;
}
var payload = Jsonrpc.getInstance().toBatchPayload(pollsData);
var self = this;
this.provider.sendAsync(payload, function (error, results) {
// TODO: console log?
if (error) {
return;
}
if (!utils.isArray(results)) {
throw errors.InvalidResponse(results);
}
results.map(function (result, index) {
var key = pollsKeys[index];
// make sure the filter is still installed after arrival of the request
if (self.polls[key]) {
result.callback = self.polls[key].callback;
return result;
} else
return false;
}).filter(function (result) {
return !!result;
}).filter(function (result) {
var valid = Jsonrpc.getInstance().isValidResponse(result);
if (!valid) {
result.callback(errors.InvalidResponse(result));
}
return valid;
}).filter(function (result) {
return utils.isArray(result.result) && result.result.length > 0;
}).forEach(function (result) {
result.callback(null, result.result);
});
});
};
module.exports = RequestManager;