UNPKG

@mr-zwets/bchn-api-wrapper

Version:

a Typescript wrapper for interacting with the Bitcoin Cash Node (BCHN) API

93 lines (92 loc) 5.13 kB
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()); }); }; import { getRandomId, validateAndConstructUrl } from "./utils/utils.js"; import { RetryLimitExceededError } from "./utils/errors.js"; /** RPC client for full BCHN node interaction via JSON-RPC. */ export class BchnRpcClient { constructor(config) { var _a, _b, _c, _d; this.url = validateAndConstructUrl(config); if (!config.rpcUser) throw new Error('Need to provide rpcUser in config'); if (!config.rpcPassword) throw new Error('Need to provide rpcPassword in config'); this.rpcUser = config.rpcUser; this.rpcPassword = config.rpcPassword; // optional config this.maxRetries = (_a = config.maxRetries) !== null && _a !== void 0 ? _a : 0; this.retryDelayMs = (_b = config.retryDelayMs) !== null && _b !== void 0 ? _b : 100; this.logger = (_c = config.logger) !== null && _c !== void 0 ? _c : console; this.timeoutMs = (_d = config.timeoutMs) !== null && _d !== void 0 ? _d : 5000; } /** * Sends a typed RPC request to the BCHN node. * @example * const result = await client.request<GetBlockCount>("getblockcount"); * const block = await client.request<GetBlockVerbosity1>("getblock", hash, 1); */ request(endpoint, ...params) { return __awaiter(this, void 0, void 0, function* () { var _a; const auth = Buffer.from(`${this.rpcUser}:${this.rpcPassword}`).toString('base64'); // Retry logic for (let attempt = 0; attempt <= this.maxRetries; attempt++) { try { // Send the request with a timeout and retries const response = yield fetch(this.url, { method: 'POST', headers: { 'Content-Type': 'application/json', 'Authorization': `Basic ${auth}` }, body: JSON.stringify({ jsonrpc: '2.0', method: endpoint, params, id: getRandomId() }), signal: AbortSignal.timeout(this.timeoutMs), }); const result = yield response.json(); // Handle response errors if (!response.ok || result.error) { throw new Error(`Error: ${((_a = result.error) === null || _a === void 0 ? void 0 : _a.message) || response.statusText}`); } return result.result; // Return the result if successful } catch (error) { let errorMessage; // Check if the error is due to timeout or other fetch-related issues if (typeof error == 'string') { errorMessage = error; this.logger.error(error); } else if (error instanceof DOMException && error.name === 'TimeoutError') { // If error is an instance DOMException TimeoutError errorMessage = error.message; this.logger.error(`Request timed out after ${this.timeoutMs} ms`); } else if (error instanceof Error) { // If error is an instance of Error, you can safely access its properties errorMessage = error.message; this.logger.error(`Request failed with error: ${error.message}`); } // Retry if allowed if (attempt < this.maxRetries) { this.logger.warn(`Retrying request... (${attempt + 1}/${this.maxRetries})`); yield new Promise(res => setTimeout(res, this.retryDelayMs)); // Wait before retrying } else { // If no retries are left, throw the final error throw new RetryLimitExceededError(`Request failed after ${this.maxRetries + 1} attempts: ${errorMessage}`); } } } // This line ensures TypeScript is satisfied that a value will always be returned, but // it should never be reached if the retries fail, as the last attempt should throw an error. throw new Error('Request failed unexpectedly'); }); } }