nano-wallet-js-test-1
Version:
SDK for developers to create and interact with Nanocurrency wallet easily
197 lines (174 loc) • 4.87 kB
text/typescript
import { BlockRepresentation } from 'nanocurrency';
import { fetchWithTimeout } from '@/utils';
import {
AccountBalanceResponse,
AccountInfoResponse,
ProcessResponse,
ReceivableWithThresholdResponse,
WorkGenerateResponse,
} from './RpControllerc.types';
import { DEFAULT_TIMEOUT } from '@/Constants';
import Logger from '../logger';
const LOG_REQUEST_RESPONSE = false;
export interface NanoRpcConfig {
rpcUrls: string | string[];
workerUrls: string | string[];
timeout?: number;
debug?: boolean;
}
export default class NanoRPC {
rpcUrls: string[];
workerUrls: string[];
timeout: number;
private logger: Logger;
constructor({
rpcUrls,
workerUrls,
timeout = DEFAULT_TIMEOUT,
debug = false,
}: NanoRpcConfig) {
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<TRPCResponse = unknown>(
data: any,
urls = this.rpcUrls,
retry = 0,
): Promise<TRPCResponse> {
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 as TRPCResponse;
} catch (error: any) {
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: BlockRepresentation) {
const data = {
action: 'process',
json_block: 'true',
block,
};
return this.postRPC<ProcessResponse>(data);
}
async workGenerate(hash: string, difficulty: string) {
const data = {
action: 'work_generate',
hash,
difficulty,
};
return this.postRPC<WorkGenerateResponse>(data, this.workerUrls);
}
async accountInfo(account: string) {
const data = {
action: 'account_info',
account,
representative: true,
weight: true,
receivable: true,
};
return this.postRPC<AccountInfoResponse>(data);
}
async accountBalance(account: string) {
const data = {
action: 'account_balance',
account,
receivable: true,
};
return this.postRPC<AccountBalanceResponse>(data);
}
async receivable(account: string, { count = 100, threshold = '1' }) {
const data = {
action: 'receivable',
account,
count,
threshold,
};
return this.postRPC<ReceivableWithThresholdResponse>(data);
}
}