UNPKG

@steemit/steem-js

Version:

JavaScript library for the Steem blockchain

157 lines (150 loc) 4.76 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.default = void 0; exports.jsonRpc = jsonRpc; var _crossFetch = _interopRequireDefault(require("cross-fetch")); var _debug = _interopRequireDefault(require("debug")); var _retry = _interopRequireDefault(require("retry")); var _base = _interopRequireDefault(require("./base")); function _interopRequireDefault(e) { return e && e.__esModule ? e : { default: e }; } const debug = (0, _debug.default)('steem:http'); class RPCError extends Error { constructor(rpcError) { super(rpcError.message); this.name = 'RPCError'; this.code = rpcError.code; this.data = rpcError.data; } } /** * Makes a JSON-RPC request using `fetch` or a user-provided `fetchMethod`. * * @param {string} uri - The URI to the JSON-RPC endpoint. * @param {string} options.method - The remote JSON-RPC method to call. * @param {string} options.id - ID for the request, for matching to a response. * @param {*} options.params - The params for the remote method. * @param {function} [options.fetchMethod=fetch] - A function with the same * signature as `fetch`, which can be used to make the network request, or for * stubbing in tests. * @param {number} [options.timeoutMs=30000] - Request timeout in milliseconds. */ function jsonRpc(uri, { method, id, params, fetchMethod = _crossFetch.default, timeoutMs = 30000 }) { const payload = { id, jsonrpc: '2.0', method, params }; let timeoutId = null; // Create a promise that will reject after the timeout const timeoutPromise = new Promise((_, reject) => { timeoutId = setTimeout(() => { reject(new Error(`Request timeout after ${timeoutMs}ms`)); }, timeoutMs); }); // Create the fetch promise const fetchPromise = fetchMethod(uri, { body: JSON.stringify(payload), method: 'post', mode: 'cors', headers: { Accept: 'application/json, text/plain, */*', 'Content-Type': 'application/json' } }).then(res => { if (!res.ok) { throw new Error(`HTTP ${res.status}: ${res.statusText}`); } return res.json(); }).then(rpcRes => { if (rpcRes.id !== id) { throw new Error(`Invalid response id: ${rpcRes.id}`); } if (rpcRes.error) { throw new RPCError(rpcRes.error); } return rpcRes.result; }); // Race the fetch against the timeout return Promise.race([fetchPromise, timeoutPromise]).finally(() => { // Clear the timeout to avoid memory leaks if (timeoutId) { clearTimeout(timeoutId); } }); } class HttpTransport extends _base.default { send(api, data, callback) { if (this.options.useAppbaseApi) { api = 'condenser_api'; } debug('Steem::send', api, data); const id = data.id || this.id++; const params = [api, data.method, data.params]; const retriable = this.retriable(api, data); const fetchMethod = this.options.fetchMethod; // Use a longer timeout for broadcast operations (60s) and standard operations (30s) const timeoutMs = this.isBroadcastOperation(data.method) ? 60000 : 30000; if (retriable) { retriable.attempt(currentAttempt => { jsonRpc(this.options.uri, { method: 'call', id, params, fetchMethod, timeoutMs }).then(res => { callback(null, res); }, err => { if (retriable.retry(err)) { return; } callback(retriable.mainError()); }); }); } else { jsonRpc(this.options.uri, { method: 'call', id, params, fetchMethod, timeoutMs }).then(res => { callback(null, res); }, err => { callback(err); }); } } isBroadcastOperation(method) { return this.nonRetriableOperations.some(op => op === method); } get nonRetriableOperations() { return this.options.nonRetriableOperations || ['broadcast_transaction', 'broadcast_transaction_with_callback', 'broadcast_transaction_synchronous', 'broadcast_block']; } // An object which can be used to track retries. retriable(api, data) { if (this.isBroadcastOperation(data.method)) { // Do not retry if the operation is non-retriable. return null; } else if (Object(this.options.retry) === this.options.retry) { // If `this.options.retry` is a map of options, pass those to operation. return _retry.default.operation(this.options.retry); } else if (this.options.retry) { // If `this.options.retry` is `true`, use default options. return _retry.default.operation(); } else { // Otherwise, don't retry. return null; } } } exports.default = HttpTransport;