golos-lib-js
Version:
Golos-js the JavaScript library with API for GOLOS blockchain
335 lines (332 loc) • 10.8 kB
JavaScript
;
var _events = _interopRequireDefault(require("events"));
var _cloneDeep = _interopRequireDefault(require("lodash/cloneDeep"));
var _defaults = _interopRequireDefault(require("lodash/defaults"));
var _detectNode = _interopRequireDefault(require("detect-node"));
var _debug = _interopRequireDefault(require("debug"));
var _config = _interopRequireDefault(require("../config"));
var _methods = _interopRequireDefault(require("./methods"));
var _utils = require("../utils");
var _promisify = require("../promisify");
var _transports = _interopRequireDefault(require("./transports"));
function _interopRequireDefault(e) { return e && e.__esModule ? e : { default: e }; }
const debugEmitters = (0, _debug.default)('golos:emitters');
const debugSetup = (0, _debug.default)('golos:setup');
const DEFAULTS = {
id: 0
};
class Golos extends _events.default {
constructor() {
let options = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {};
super(options);
(0, _defaults.default)(options, DEFAULTS);
this.options = (0, _cloneDeep.default)(options);
this._streamDefaults = {
mode: 'head',
startBlock: 0
};
}
_setTransport(url) {
if (url && url.match('^((http|https)?:\/\/)')) {
this.transport = new _transports.default.http({
url
});
this.url = url;
} else if (url && url.match('^((ws|wss)?:\/\/)')) {
this.transport = new _transports.default.ws({
url
});
this.url = url;
} else {
throw Error("unknown transport! [" + url + "]");
}
}
setWebSocket(url) {
this.customUrl = !!url;
if (this.transport) this.stop();
this.url = url || _config.default.get('websocket');
debugSetup('Setting WS', this.url);
}
start() {
if (!this.transport) {
const url = this.url || _config.default.get('websocket');
this._setTransport(url);
}
return this.transport.start();
}
stop() {
debugSetup('Stopping...');
const ret = this.transport.stop();
this.transport = null;
this.url = null;
this.customUrl = false;
return ret;
}
send(api, data, callback) {
debugSetup('Golos::send', api, data);
if (!this.transport) {
this.start();
} else {
if (!this.customUrl && this.url !== _config.default.get('websocket')) {
debugSetup('websocket URL changed, restarting transport...');
this.stop();
this.start();
}
}
return this.transport.send(api, data, callback);
}
callReliable(method) {
for (var _len = arguments.length, args = new Array(_len > 1 ? _len - 1 : 0), _key = 1; _key < _len; _key++) {
args[_key - 1] = arguments[_key];
}
const callback = args[args.length - 1];
const argsWithoutCb = args.slice(0, args.length - 1);
this[method](...argsWithoutCb, (err, result) => {
if (err || !result) {
console.error(method + ' reliable call - fail, retrying... Cause:', err ? err.message : 'API returned null');
setTimeout(() => {
this.callReliable(method, ...args);
}, 1000);
return;
}
callback(null, result);
});
}
streamBlockNumber(options, callback) {
if (typeof options === 'function') {
callback = options;
options = {};
}
(0, _defaults.default)(options, this._streamDefaults);
let startBlock = options.startBlock;
let prevBlock = 0;
let running = true;
const update = () => {
if (!running) return;
this.callReliableAsync('getDynamicGlobalProperties').then(result => {
const currentBlock = options.mode === 'irreversible' ? result.last_irreversible_block_num : result.head_block_number;
if (!startBlock) startBlock = currentBlock;
for (let i = startBlock; i <= currentBlock; i++) {
const ret = callback(null, i);
if (ret === true || !running) return;
}
startBlock = currentBlock + 1;
setTimeout(() => {
update();
}, 1000);
}, err => {
callback(err);
});
};
update();
return function () {
running = false;
};
}
streamBlock(options, callback) {
if (typeof options === 'function') {
callback = options;
options = {};
}
(0, _defaults.default)(options, this._streamDefaults);
let current = '';
let last = '';
const release = this.streamBlockNumber(options, (err, id) => {
if (err) {
release();
callback(err);
return;
}
current = id;
if (current !== last) {
last = current;
this.callReliable('getBlock', current, (err, res) => {
res.block_num = current;
res.timestamp_prev = new Date(new Date(res.timestamp).getTime() - 3000).toISOString().split('.')[0];
const ret = callback(err, res);
if (ret === true) release();
});
}
});
return release;
}
streamTransactions(options, callback) {
if (typeof options === 'function') {
callback = options;
options = {};
}
(0, _defaults.default)(options, this._streamDefaults);
const release = this.streamBlock(options, (err, block) => {
if (err) {
release();
callback(err);
return;
}
if (block && block.transactions) {
block.transactions.forEach(transaction => {
const ret = callback(null, transaction, block);
if (ret === true) release();
});
}
});
return release;
}
streamOperations() {
let options = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {};
let callback = arguments.length > 1 ? arguments[1] : undefined;
if (typeof options === 'function') {
callback = options;
options = {};
}
(0, _defaults.default)(options, this._streamDefaults);
const release = this.streamTransactions(options, (err, transaction, block) => {
if (err) {
release();
callback(err);
return;
}
transaction.operations.forEach(operation => {
const ret = callback(null, operation, transaction, block);
if (ret === true) release();
});
});
return release;
}
streamEvents(options, callback) {
if (typeof options === 'function') {
callback = options;
options = {};
}
(0, _defaults.default)(options, this._streamDefaults);
let current = '';
let last = '';
const release = this.streamBlockNumber(options, (err, id) => {
if (err) {
release();
callback(err);
return;
}
current = id;
if (current !== last) {
last = current;
this.callReliable('getEventsInBlock', current, false, (err, res) => {
for (const eventMeta of res) {
const ret = callback(err, eventMeta.op, eventMeta);
if (ret === true) release();
}
});
}
});
return release;
}
}
// Generate Methods from methods.js
_methods.default.forEach(method => {
const methodName = method.method_name || (0, _utils.camelCase)(method.method);
const methodParams = method.params || [];
const defaultParms = {};
const hasDefaultValues = method.has_default_values;
if (hasDefaultValues) {
methodParams.forEach(param => {
const [p, value] = param.split('=');
defaultParms[p] = value ? JSON.parse(value) : '';
});
}
Golos.prototype[`${methodName}With`] = function Golos$$specializedSendWith(options, callback) {
const params = methodParams.map(param => options[hasDefaultValues ? param.split('=')[0] : param]);
return this.send(method.api, {
method: method.method,
params
}, callback);
};
Golos.prototype[methodName] = function Golos$specializedSend() {
for (var _len2 = arguments.length, args = new Array(_len2), _key2 = 0; _key2 < _len2; _key2++) {
args[_key2] = arguments[_key2];
}
let options = {};
let callback;
if (hasDefaultValues) {
callback = args.pop();
if (typeof callback !== 'function') {
args = [...args, callback];
}
methodParams.forEach((param, i) => {
const [p, value] = param.split('=');
if (args[i]) {
options[p] = args[i];
}
});
options = Object.assign({}, defaultParms, options);
} else {
const opt = methodParams.reduce((memo, param, i) => {
memo[param] = args[i];
return memo;
}, {});
options = Object.assign({}, opt);
callback = args[methodParams.length];
}
return this[`${methodName}With`](options, callback);
};
});
(0, _promisify.promisifyAll)(Golos.prototype);
Golos.prototype['setBlockAppliedCallback'] = function Golos$setCallback(type, callback) {
return this.send('database_api', {
method: 'set_block_applied_callback',
params: [type]
}, callback);
};
Golos.prototype['setPendingTransactionCallback'] = function Golos$setCallback(callback) {
return this.send('database_api', {
method: 'set_pending_transaction_callback',
params: []
}, callback);
};
Golos.prototype['setPrivateMessageCallback'] = function Golos$setCallback(query, callback) {
return this.send('private_message', {
method: 'set_callback',
params: [query]
}, callback);
};
Golos.prototype['subscribeToMarket'] = function Golos$setCallback(query, callback) {
return this.send('market_history', {
method: 'subscribe_to_market',
params: [query]
}, callback);
};
Golos.prototype['waitEventAsync'] = async function (getData) {
let startBlock = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : null;
let waitBlocks = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : 4;
if (!getData) throw new Error('waitEvent requires function/lambda as getData argument which will process event and return data');
const {
head_block_number,
time
} = await this.getDynamicGlobalPropertiesAsync();
let data = undefined;
let block = head_block_number;
while (block < head_block_number + waitBlocks) {
const res = await this.getEventsInBlockAsync(block, true);
for (const op of res) {
data = await getData(op.op);
if (data !== undefined) break;
}
if (data !== undefined) break;
let interval = 3000;
if (block === head_block_number) {
const now = Date.now();
const nowHB = +new Date(time);
interval -= now - nowHB;
interval += 10;
}
await new Promise(resolve => setTimeout(resolve, interval));
++block;
}
if (data === undefined) {
throw new Error('waitEvent timeouted - and still no event occured');
}
return data;
};
// Export singleton instance
const golos = new Golos();
exports = module.exports = golos;
exports.Golos = Golos;
exports.Golos.DEFAULTS = DEFAULTS;