UNPKG

nano-wallet-js-test-1

Version:

SDK for developers to create and interact with Nanocurrency wallet easily

137 lines (136 loc) 5.12 kB
import { fetchWithTimeout } from '../utils'; import { DEFAULT_TIMEOUT } from '../Constants'; import Logger from '../logger'; const LOG_REQUEST_RESPONSE = false; export default class NanoRPC { rpcUrls; workerUrls; timeout; logger; constructor({ rpcUrls, workerUrls, timeout = DEFAULT_TIMEOUT, debug = false, }) { this.rpcUrls = rpcUrls instanceof Array ? rpcUrls : [rpcUrls]; if (this.rpcUrls.length < 0) { throw new Error('No RPC addresses provided'); } this.rpcUrls.forEach(addr => { try { new URL(addr); } catch (err) { throw new Error(`Invalid RPC address: ${addr}`); } }); this.workerUrls = workerUrls instanceof Array ? workerUrls : [workerUrls]; if (this.workerUrls.length < 0) { throw new Error('No workers addresses provided'); } this.workerUrls.forEach(addr => { try { new URL(addr); } catch (err) { throw new Error(`Invalid workers address: ${addr}`); } }); this.timeout = timeout; this.logger = new Logger('NANO_RPC', debug); } async postRPC(data, urls = this.rpcUrls, retry = 0) { const url = urls[retry]; const startedAt = Date.now(); try { const response = await fetchWithTimeout(url, { method: 'POST', body: JSON.stringify(data), headers: { 'Content-Type': 'application/json', }, timeout: this.timeout, }); const took = Date.now() - startedAt; if (!response?.ok) { this.logger.error(`url: ${url} | action: ${data.action} | status: ${response.status} (${response.statusText}) | took: ${took}ms`, LOG_REQUEST_RESPONSE ? `\n\t- Request: ${JSON.stringify(data)}\n\t- Response: ${JSON.stringify(await response.text())}` : ''); throw new Error('bad status in response'); } let body; // clone, so we can consume the body if response.json() fails const responseClone = response.clone(); try { body = await response.json(); } catch (err) { this.logger.error(`url: ${url} | action: ${data.action} | status: bad json in response | took: ${took}ms`, LOG_REQUEST_RESPONSE ? `\n\t- Request: ${JSON.stringify(data)}\n\t- Response: ${await responseClone.clone().text()}` : ''); throw new Error('bad json in response'); } if (typeof body === 'object' && 'error' in body) { throw new Error(body.error); } this.logger.info(`url: ${url} | action: ${data.action} | status: ${response.status} | took: ${took}ms`, LOG_REQUEST_RESPONSE ? `\n\t- Request: ${JSON.stringify(data)}\n\t- Response: ${JSON.stringify(body)}` : ''); return body; } catch (error) { const isRetryableError = error instanceof DOMException && (error.name === 'AbortError' || error.message === 'bad status in response' || error.message === 'bad json in response'); const canRetry = isRetryableError && retry < urls.length - 1; if (error instanceof DOMException && error.name === 'AbortError') { const took = Date.now() - startedAt; this.logger.error(`url: ${url} | action: ${data.action} | status: ${error.message} | took: ${took}ms | will retry: ${canRetry}`, LOG_REQUEST_RESPONSE ? `\n\t- Request: ${JSON.stringify(data)}` : ''); } if (canRetry) { return await this.postRPC(data, urls, retry + 1); } throw error; } } async process(block) { const data = { action: 'process', json_block: 'true', block, }; return this.postRPC(data); } async workGenerate(hash, difficulty) { const data = { action: 'work_generate', hash, difficulty, }; return this.postRPC(data, this.workerUrls); } async accountInfo(account) { const data = { action: 'account_info', account, representative: true, weight: true, receivable: true, }; return this.postRPC(data); } async accountBalance(account) { const data = { action: 'account_balance', account, receivable: true, }; return this.postRPC(data); } async receivable(account, { count = 100, threshold = '1' }) { const data = { action: 'receivable', account, count, threshold, }; return this.postRPC(data); } }