UNPKG

@steempro/dsteem

Version:
219 lines (218 loc) 10 kB
"use strict"; /** * @file Steem RPC client implementation. * @author Johan Nordberg <code@johan-nordberg.com> * @license * Copyright (c) 2017 Johan Nordberg. All Rights Reserved. * * Redistribution and use in source and binary forms, with or without modification, * are permitted provided that the following conditions are met: * * 1. Redistribution of source code must retain the above copyright notice, this * list of conditions and the following disclaimer. * * 2. Redistribution in binary form must reproduce the above copyright notice, * this list of conditions and the following disclaimer in the documentation * and/or other materials provided with the distribution. * * 3. Neither the name of the copyright holder nor the names of its contributors * may be used to endorse or promote products derived from this software without * specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. * IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED * OF THE POSSIBILITY OF SUCH DAMAGE. * * You acknowledge that this software is not designed, licensed or intended for use * in the design, construction, operation or maintenance of any military facility. */ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) { function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); } return new (P || (P = Promise))(function (resolve, reject) { function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } } function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } } function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); } step((generator = generator.apply(thisArg, _arguments || [])).next()); }); }; Object.defineProperty(exports, "__esModule", { value: true }); exports.Client = exports.DEFAULT_ADDRESS_PREFIX = exports.DEFAULT_CHAIN_ID = exports.VERSION = void 0; const assert = require("assert"); const verror_1 = require("verror"); const version_1 = require("./version"); const blockchain_1 = require("./helpers/blockchain"); const broadcast_1 = require("./helpers/broadcast"); const database_1 = require("./helpers/database"); const hivemind_1 = require("./helpers/hivemind"); const key_1 = require("./helpers/key"); const rc_1 = require("./helpers/rc"); const transaction_1 = require("./helpers/transaction"); const utils_1 = require("./utils"); /** * Library version. */ exports.VERSION = version_1.default; /** * Main Steem network chain id. */ exports.DEFAULT_CHAIN_ID = Buffer.from("0000000000000000000000000000000000000000000000000000000000000000", "hex"); /** * Main Steem network address prefix. */ exports.DEFAULT_ADDRESS_PREFIX = "STM"; /** * RPC Client * ---------- * Can be used in both node.js and the browser. Also see {@link ClientOptions}. */ class Client { /** * @param address The address to the Steem RPC server, * e.g. `https://api.steemit.com`. or [`https://api.steemit.com`, `https://another.api.com`] * @param options Client options. */ constructor(address, options = {}) { if (options.rebrandedApi) { // tslint:disable-next-line: no-console console.log("Warning: rebrandedApi is deprecated and safely can be removed from client options"); } this.currentAddress = Array.isArray(address) ? address[0] : address; this.address = address; this.options = options; this.chainId = options.chainId ? Buffer.from(options.chainId, "hex") : exports.DEFAULT_CHAIN_ID; assert.equal(this.chainId.length, 32, "invalid chain id"); this.addressPrefix = options.addressPrefix || exports.DEFAULT_ADDRESS_PREFIX; this.timeout = options.timeout || 60 * 1000; this.backoff = options.backoff || defaultBackoff; this.failoverThreshold = options.failoverThreshold || 3; this.consoleOnFailover = options.consoleOnFailover || false; this.database = new database_1.DatabaseAPI(this); this.broadcast = new broadcast_1.BroadcastAPI(this); this.blockchain = new blockchain_1.Blockchain(this); this.rc = new rc_1.RCAPI(this); this.hivemind = new hivemind_1.HivemindAPI(this); this.keys = new key_1.AccountByKeyAPI(this); this.transaction = new transaction_1.TransactionStatusAPI(this); } /** * Create a new client instance configured for the testnet. */ static testnet(options) { let opts = {}; if (options) { opts = utils_1.copy(options); opts.agent = options.agent; } opts.addressPrefix = "TST"; opts.chainId = "18dcf0a285365fc58b71f18b3d3fec954aa0c141c44e4e5cb4cf777b9eab274e"; return new Client("https://testnet.steem.com", opts); } /** * Make a RPC call to the server. * * @param api The API to call, e.g. `database_api`. * @param method The API method, e.g. `get_dynamic_global_properties`. * @param params Array of parameters to pass to the method, optional. * */ call(api, method, params = []) { return __awaiter(this, void 0, void 0, function* () { const request = { id: "0", jsonrpc: "2.0", method: "call", params: [api, method, params], }; const body = JSON.stringify(request, (key, value) => { // encode Buffers as hex strings instead of an array of bytes if (value && typeof value === "object" && value.type === "Buffer") { return Buffer.from(value.data).toString("hex"); } return value; }); const opts = { body, cache: "no-cache", headers: { "User-Agent": `dsteem/${version_1.default}` }, method: "POST", mode: "cors", }; // Self is not defined within Node environments // This check is needed because the user agent cannot be set in a browser if (typeof self === undefined) { opts.headers = { "User-Agent": `dsteem/${version_1.default}`, }; } if (this.options.agent) { opts.agent = this.options.agent; } let fetchTimeout; if (api !== "network_broadcast_api" && !method.startsWith("broadcast_transaction")) { // bit of a hack to work around some nodes high error rates // only effective in node.js (until timeout spec lands in browsers) fetchTimeout = (tries) => (tries + 1) * 500; } const { response, currentAddress, } = yield utils_1.retryingFetch(this.currentAddress, this.address, opts, this.timeout, this.failoverThreshold, this.consoleOnFailover, this.backoff, fetchTimeout); // After failover, change the currently active address if (currentAddress !== this.currentAddress) { this.currentAddress = currentAddress; } // resolve FC error messages into something more readable if (response.error) { const formatValue = (value) => { switch (typeof value) { case "object": return JSON.stringify(value); default: return String(value); } }; const { data } = response.error; let { message } = response.error; if (data && data.stack && data.stack.length > 0) { const top = data.stack[0]; const topData = utils_1.copy(top.data); message = top.format.replace(/\$\{([a-z_]+)\}/gi, (match, key) => { let rv = match; if (topData[key]) { rv = formatValue(topData[key]); delete topData[key]; } return rv; }); const unformattedData = Object.keys(topData) .map((key) => ({ key, value: formatValue(topData[key]) })) .map((item) => `${item.key}=${item.value}`); if (unformattedData.length > 0) { message += " " + unformattedData.join(" "); } } throw new verror_1.VError({ info: data, name: "RPCError" }, message); } assert.equal(response.id, request.id, "got invalid response id"); return response.result; }); } updateOperations(rebrandedApi) { // tslint:disable-next-line: no-console console.log("Warning: call to updateOperations() is deprecated and can safely be removed"); } } exports.Client = Client; /** * Default backoff function. * ```min(tries*10^2, 10 seconds)``` */ const defaultBackoff = (tries) => Math.min(Math.pow(tries * 10, 2), 10 * 1000);