hive-js-dev
Version:
Steem.js the JavaScript API for Steem blockchain
478 lines (411 loc) • 18.7 kB
JavaScript
'use strict';
var _typeof = typeof Symbol === "function" && typeof Symbol.iterator === "symbol" ? function (obj) { return typeof obj; } : function (obj) { return obj && typeof Symbol === "function" && obj.constructor === Symbol && obj !== Symbol.prototype ? "symbol" : typeof obj; };
var _createClass = function () { function defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; }();
var _events = require('events');
var _events2 = _interopRequireDefault(_events);
var _bluebird = require('bluebird');
var _bluebird2 = _interopRequireDefault(_bluebird);
var _config = require('../config');
var _config2 = _interopRequireDefault(_config);
var _methods = require('./methods');
var _methods2 = _interopRequireDefault(_methods);
var _transports = require('./transports');
var _transports2 = _interopRequireDefault(_transports);
var _http = require('./transports/http');
var _utils = require('../utils');
var _ecc = require('../auth/ecc');
var _serializer = require('../auth/serializer');
var _rpcAuth = require('@steemit/rpc-auth');
function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } }
function _possibleConstructorReturn(self, call) { if (!self) { throw new ReferenceError("this hasn't been initialised - super() hasn't been called"); } return call && (typeof call === "object" || typeof call === "function") ? call : self; }
function _inherits(subClass, superClass) { if (typeof superClass !== "function" && superClass !== null) { throw new TypeError("Super expression must either be null or a function, not " + typeof superClass); } subClass.prototype = Object.create(superClass && superClass.prototype, { constructor: { value: subClass, enumerable: false, writable: true, configurable: true } }); if (superClass) Object.setPrototypeOf ? Object.setPrototypeOf(subClass, superClass) : subClass.__proto__ = superClass; }
var Steem = function (_EventEmitter) {
_inherits(Steem, _EventEmitter);
function Steem() {
var options = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {};
_classCallCheck(this, Steem);
var _this = _possibleConstructorReturn(this, (Steem.__proto__ || Object.getPrototypeOf(Steem)).call(this, options));
_this._setTransport(options);
_this._setLogger(options);
_this.options = options;
_this.seqNo = 0; // used for rpc calls
_this.error_count = 0;
_this.api_index = 0;
_this.error_threshold = 5;
_this.alternative_api_endpoints = ['https://api.hive.blog', 'https://anyx.io'];
_methods2.default.forEach(function (method) {
var methodName = method.method_name || (0, _utils.camelCase)(method.method);
var methodParams = method.params || [];
_this[methodName + 'With'] = function (options, callback) {
return _this.send(method.api, {
method: method.method,
params: methodParams.map(function (param) {
return options[param];
})
}, callback);
};
_this[methodName] = function () {
for (var _len = arguments.length, args = Array(_len), _key = 0; _key < _len; _key++) {
args[_key] = arguments[_key];
}
var options = methodParams.reduce(function (memo, param, i) {
memo[param] = args[i]; // eslint-disable-line no-param-reassign
return memo;
}, {});
var callback = args[methodParams.length];
return _this[methodName + 'With'](options, callback);
};
_this[methodName + 'WithAsync'] = _bluebird2.default.promisify(_this[methodName + 'With']);
_this[methodName + 'Async'] = _bluebird2.default.promisify(_this[methodName]);
});
_this.callAsync = _bluebird2.default.promisify(_this.call);
_this.signedCallAsync = _bluebird2.default.promisify(_this.signedCall);
console.log("Alternate endpoitns: ", _this.options.alternative_api_endpoints);
console.log("Error Failover Threshold: ", _this.options.failover_threshold);
_this.notifyError = _this.notifyError.bind(_this);
return _this;
}
_createClass(Steem, [{
key: '_setTransport',
value: function _setTransport(options) {
if (options.url && options.url.match('^((http|https)?:\/\/)')) {
options.uri = options.url;
options.transport = 'http';
this._transportType = options.transport;
this.options = options;
this.transport = new _transports2.default.http(options);
} else if (options.url && options.url.match('^((ws|wss)?:\/\/)')) {
options.websocket = options.url;
options.transport = 'ws';
this._transportType = options.transport;
this.options = options;
this.transport = new _transports2.default.ws(options);
} else if (options.transport) {
if (this.transport && this._transportType !== options.transport) {
this.transport.stop();
}
this._transportType = options.transport;
if (typeof options.transport === 'string') {
if (!_transports2.default[options.transport]) {
throw new TypeError('Invalid `transport`, valid values are `http`, `ws` or a class');
}
this.transport = new _transports2.default[options.transport](options);
} else {
this.transport = new options.transport(options);
}
} else {
this.transport = new _transports2.default.ws(options);
}
}
}, {
key: '_setLogger',
value: function _setLogger(options) {
if (options.hasOwnProperty('logger')) {
switch (_typeof(options.logger)) {
case 'function':
this.__logger = {
log: options.logger
};
break;
case 'object':
if (typeof options.logger.log !== 'function') {
throw new Error('setOptions({logger:{}}) must have a property .log of type function');
}
this.__logger = options.logger;
break;
case 'undefined':
if (this.__logger) break;
default:
this.__logger = false;
}
}
}
}, {
key: 'log',
value: function log(logLevel) {
if (this.__logger) {
if (arguments.length > 1 && typeof this.__logger[logLevel] === 'function') {
var args = Array.prototype.slice.call(arguments, 1);
this.__logger[logLevel].apply(this.__logger, args);
} else {
this.__logger.log.apply(this.__logger, arguments);
}
}
}
}, {
key: 'start',
value: function start() {
return this.transport.start();
}
}, {
key: 'stop',
value: function stop() {
return this.transport.stop();
}
}, {
key: 'send',
value: function send(api, data, callback) {
var cb = callback;
if (this.__logger) {
var id = Math.random();
var self = this;
this.log('xmit:' + id + ':', data);
cb = function cb(e, d) {
if (e) {
self.log('error', 'rsp:' + id + ':\n\n', e, d);
} else {
self.log('rsp:' + id + ':', d);
}
if (callback) {
callback.apply(self, arguments);
}
};
}
return this.transport.send(api, data, cb);
}
}, {
key: 'call',
value: function call(method, params, callback) {
var _this2 = this;
if (this._transportType !== 'http') {
callback(new Error('RPC methods can only be called when using http transport'));
return;
}
var id = ++this.seqNo;
(0, _http.jsonRpc)(this.options.uri, { method: method, params: params, id: id }).then(function (res) {
callback(null, res);
}, function (err) {
_this2.notifyError(err, err instanceof _http.RPCError);callback(err);
});
}
}, {
key: 'signedCall',
value: function signedCall(method, params, account, key, callback) {
var _this3 = this;
if (this._transportType !== 'http') {
callback(new Error('RPC methods can only be called when using http transport'));
return;
}
var id = ++this.seqNo;
var request = void 0;
try {
request = (0, _rpcAuth.sign)({ method: method, params: params, id: id }, account, [key]);
} catch (error) {
callback(error);
return;
}
(0, _http.jsonRpc)(this.options.uri, request).then(function (res) {
callback(null, res);
}, function (err) {
callback(err);_this3.notifyError(err);
});
}
}, {
key: 'setOptions',
value: function setOptions(options) {
Object.assign(this.options, options);
if (options.hasOwnProperty('failover_threshold')) this.failover_threshold = options.failover_threshold;
if (options.hasOwnProperty('alternative_api_endpoints')) this.alternative_api_endpoints = options.alternative_api_endpoints;
this._setLogger(options);
this._setTransport(options);
this.transport.setOptions(options);
if (options.hasOwnProperty('useTestNet')) {
_config2.default.set('address_prefix', options.useTestNet ? 'TST' : 'STM');
}
if (options.hasOwnProperty('url')) {
var new_index = 0;
for (var i = 0; i < this.alternative_api_endpoints.length; i++) {
var temp_endpoint = this.alternative_api_endpoints[i];
if (temp_endpoint === options.url) {
new_index = i;
break;
}
}
this.api_index = new_index;
var new_endpoint = this.alternative_api_endpoints[this.api_index];
}
}
}, {
key: 'setWebSocket',
value: function setWebSocket(url) {
this.setOptions({
websocket: url
});
}
}, {
key: 'setUri',
value: function setUri(url) {
this.setOptions({
uri: url
});
}
}, {
key: 'streamBlockNumber',
value: function streamBlockNumber() {
var mode = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : 'head';
var _this4 = this;
var callback = arguments[1];
var ts = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : 200;
if (typeof mode === 'function') {
callback = mode;
mode = 'head';
}
var current = '';
var running = true;
var update = function update() {
if (!running) return;
_this4.getDynamicGlobalPropertiesAsync().then(function (result) {
var blockId = mode === 'irreversible' ? result.last_irreversible_block_num : result.head_block_number;
if (blockId !== current) {
if (current) {
for (var i = current; i < blockId; i++) {
if (i !== current) {
callback(null, i);
}
current = i;
}
} else {
current = blockId;
callback(null, blockId);
}
}
_bluebird2.default.delay(ts).then(function () {
update();
});
}, function (err) {
callback(err);
});
};
update();
return function () {
running = false;
};
}
}, {
key: 'streamBlock',
value: function streamBlock() {
var _this5 = this;
var mode = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : 'head';
var callback = arguments[1];
if (typeof mode === 'function') {
callback = mode;
mode = 'head';
}
var current = '';
var last = '';
var release = this.streamBlockNumber(mode, function (err, id) {
if (err) {
release();
callback(err);
return;
}
current = id;
if (current !== last) {
last = current;
_this5.getBlock(current, callback);
}
});
return release;
}
}, {
key: 'streamTransactions',
value: function streamTransactions() {
var mode = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : 'head';
var callback = arguments[1];
if (typeof mode === 'function') {
callback = mode;
mode = 'head';
}
var release = this.streamBlock(mode, function (err, result) {
if (err) {
release();
callback(err);
return;
}
if (result && result.transactions) {
result.transactions.forEach(function (transaction) {
callback(null, transaction);
});
}
});
return release;
}
}, {
key: 'streamOperations',
value: function streamOperations() {
var mode = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : 'head';
var callback = arguments[1];
if (typeof mode === 'function') {
callback = mode;
mode = 'head';
}
var release = this.streamTransactions(mode, function (err, transaction) {
if (err) {
release();
callback(err);
return;
}
transaction.operations.forEach(function (operation) {
callback(null, operation);
});
});
return release;
}
}, {
key: 'broadcastTransactionSynchronousWith',
value: function broadcastTransactionSynchronousWith(options, callback) {
var trx = options.trx;
return this.send('network_broadcast_api', {
method: 'broadcast_transaction_synchronous',
params: [trx]
}, function (err, result) {
if (err) {
var signed_transaction = _serializer.ops.signed_transaction;
// console.log('-- broadcastTransactionSynchronous -->', JSON.stringify(signed_transaction.toObject(trx), null, 2));
// toObject converts objects into serializable types
var trObject = signed_transaction.toObject(trx);
var buf = signed_transaction.toBuffer(trx);
err.digest = _ecc.hash.sha256(buf).toString('hex');
err.transaction_id = buf.toString('hex');
err.transaction = JSON.stringify(trObject);
callback(err, '');
} else {
callback('', result);
}
});
}
}, {
key: 'notifyError',
value: function notifyError(err) {
var ignore = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : false;
if (ignore) {
return;
}
if (this.failover_threshold === undefined || this.alternative_api_endpoints === undefined) {
return;
}
if (err && err.toString().includes("overseer")) {
console.log("ignoring an overseer error, as this doesn't exist in hive");
return;
}
this.error_count++;
if (this.error_count >= this.failover_threshold) {
var current_url = this.options.url;
this.error_count = 0;
this.api_index++;
if (this.api_index >= this.alternative_api_endpoints.length) {
this.api_index = 0;
}
var nextEndpoint = this.alternative_api_endpoints[this.api_index];
console.log("failing over. old endpoint was: ", current_url, " new one is: ", nextEndpoint);
this.setOptions({ url: nextEndpoint });
}
}
}]);
return Steem;
}(_events2.default);
// Export singleton instance
var steem = new Steem(_config2.default);
exports = module.exports = steem;
exports.Steem = Steem;