Resolves TODOs in ./src/client (#1617)

* fix: fixes TODO's in ./src/client
This commit is contained in:
Nathan Nichols
2021-09-21 12:10:50 -07:00
committed by Mayukha Vadari
parent 8e1ab6c32b
commit d734d886c4
9 changed files with 95 additions and 103 deletions

View File

@@ -19,13 +19,13 @@ export default class BroadcastClient extends Client {
(server) => new Client(server, options),
)
// exposed for testing
this.clients = clients
this.getMethodNames().forEach((name: string) => {
this[name] = async (...args): Promise<unknown> =>
// eslint-disable-next-line max-len -- Need a long comment, TODO: figure out how to avoid this weirdness
/* eslint-disable-next-line @typescript-eslint/no-unsafe-return, @typescript-eslint/no-unsafe-call -- Types are outlined in Client class */
/* eslint-disable @typescript-eslint/no-unsafe-return, @typescript-eslint/no-unsafe-call -- Generates types
from the Client */
Promise.race(clients.map(async (client) => client[name](...args)))
/* eslint-enable @typescript-eslint/no-unsafe-return, @typescript-eslint/no-unsafe-call */
})
// connection methods must be overridden to apply to all client instances

View File

@@ -1,8 +1,6 @@
/* eslint-disable max-lines -- Connection is a big class */
import { EventEmitter } from 'events'
import { Agent } from 'http'
// eslint-disable-next-line node/no-deprecated-api -- TODO: resolve this
import { parse as parseURL } from 'url'
import _ from 'lodash'
import WebSocket from 'ws'
@@ -57,35 +55,49 @@ export const INTENTIONAL_DISCONNECT_CODE = 4000
type WebsocketState = 0 | 1 | 2 | 3
function getAgent(url: string, config: ConnectionOptions): Agent | undefined {
// TODO: replace deprecated method
if (config.proxy != null) {
const parsedURL = parseURL(url)
const parsedProxyURL = parseURL(config.proxy)
const proxyOverrides = _.omitBy(
{
secureEndpoint: parsedURL.protocol === 'wss:',
secureProxy: parsedProxyURL.protocol === 'https:',
auth: config.proxyAuthorization,
ca: config.trustedCertificates,
key: config.key,
passphrase: config.passphrase,
cert: config.certificate,
},
(value) => value == null,
)
const proxyOptions = { ...parsedProxyURL, ...proxyOverrides }
let HttpsProxyAgent
try {
// eslint-disable-next-line max-len -- Long eslint-disable-next-line TODO: figure out how to make this nicer
// eslint-disable-next-line import/max-dependencies, @typescript-eslint/no-unsafe-assignment, @typescript-eslint/no-require-imports, node/global-require, global-require, -- Necessary for the `require`
HttpsProxyAgent = require('https-proxy-agent')
} catch (_error) {
throw new Error('"proxy" option is not supported in the browser')
}
// eslint-disable-next-line @typescript-eslint/consistent-type-assertions, @typescript-eslint/no-unsafe-call -- Necessary
return new HttpsProxyAgent(proxyOptions) as unknown as Agent
if (config.proxy == null) {
return undefined
}
return undefined
const parsedURL = new URL(url)
const parsedProxyURL = new URL(config.proxy)
const proxyOptions = _.omitBy(
{
secureEndpoint: parsedURL.protocol === 'wss:',
secureProxy: parsedProxyURL.protocol === 'https:',
auth: config.proxyAuthorization,
ca: config.trustedCertificates,
key: config.key,
passphrase: config.passphrase,
cert: config.certificate,
href: parsedProxyURL.href,
origin: parsedProxyURL.origin,
protocol: parsedProxyURL.protocol,
username: parsedProxyURL.username,
password: parsedProxyURL.password,
host: parsedProxyURL.host,
hostname: parsedProxyURL.hostname,
port: parsedProxyURL.port,
pathname: parsedProxyURL.pathname,
search: parsedProxyURL.search,
hash: parsedProxyURL.hash,
},
(value) => value == null,
)
let HttpsProxyAgent: new (opt: typeof proxyOptions) => Agent
try {
/* eslint-disable @typescript-eslint/no-unsafe-assignment, @typescript-eslint/no-require-imports,
node/global-require, global-require, -- Necessary for the `require` */
HttpsProxyAgent = require('https-proxy-agent')
/* eslint-enable @typescript-eslint/no-unsafe-assignment, @typescript-eslint/no-require-imports,
node/global-require, global-require */
} catch (_error) {
throw new Error('"proxy" option is not supported in the browser')
}
return new HttpsProxyAgent(proxyOptions)
}
/**
@@ -244,8 +256,9 @@ export class Connection extends EventEmitter {
this.ws.on('error', () => clearTimeout(connectionTimeoutID))
this.ws.on('close', (reason) => this.onConnectionFailed(reason))
this.ws.on('close', () => clearTimeout(connectionTimeoutID))
// eslint-disable-next-line @typescript-eslint/no-misused-promises -- TODO: resolve this
this.ws.once('open', async () => this.onceOpen(connectionTimeoutID))
this.ws.once('open', () => {
void this.onceOpen(connectionTimeoutID)
})
return this.connectionManager.awaitConnection()
}
@@ -473,11 +486,9 @@ export class Connection extends EventEmitter {
*/
private startHeartbeatInterval(): void {
this.clearHeartbeatInterval()
this.heartbeatIntervalID = setInterval(
// eslint-disable-next-line @typescript-eslint/no-misused-promises -- TODO: resolve this
async () => this.heartbeat(),
this.config.timeout,
)
this.heartbeatIntervalID = setInterval(() => {
void this.heartbeat()
}, this.config.timeout)
}
/**

View File

@@ -1,4 +1,3 @@
/* eslint-disable @typescript-eslint/member-ordering -- TODO: remove when instance methods aren't members */
/* eslint-disable max-lines -- This might not be necessary later, but this file needs to be big right now */
import * as assert from 'assert'
import { EventEmitter } from 'events'
@@ -138,21 +137,6 @@ function clamp(value: number, min: number, max: number): number {
return Math.min(Math.max(value, min), max)
}
/**
* It returns a function that prepends params to the given func.
* A sugar function for JavaScript .bind() without the "this" (keyword) binding.
*
* @param func - A function to prepend params.
* @param params - Parameters to prepend to a function.
* @returns A function bound with params.
*/
// TODO Need to refactor prepend so TS can infer the correct function signature type
// eslint-disable-next-line @typescript-eslint/ban-types -- expected param types
function prepend(func: Function, ...params: unknown[]): Function {
// eslint-disable-next-line @typescript-eslint/no-unsafe-return -- safe to return
return func.bind(null, ...params)
}
interface MarkerRequest extends BaseRequest {
limit?: number
marker?: unknown
@@ -517,23 +501,23 @@ class Client extends EventEmitter {
return this.connection.isConnected()
}
// TODO: Use prepend for other instance methods as well.
public autofill = prepend(autofill, this)
public autofill = autofill
public getFee = getFee
// @deprecated Use autofill instead
public prepareTransaction = prepend(autofill, this)
public prepareTransaction = autofill
public getFee = prepend(getFee, this)
public submitTransaction = prepend(submitTransaction, this)
public submitTransaction = submitTransaction
public submitSignedTransaction = prepend(submitSignedTransaction, this)
public submitSignedTransaction = submitSignedTransaction
public getBalances = prepend(getBalances, this)
public getOrderbook = prepend(getOrderbook, this)
public getBalances = getBalances
public getOrderbook = getOrderbook
public combine = combine
public generateFaucetWallet = prepend(generateFaucetWallet, this)
public generateFaucetWallet = generateFaucetWallet
public errors = errors
}

View File

@@ -1,4 +1,3 @@
/* eslint-disable import/no-unused-modules -- This is used by webpack */
/* eslint-disable max-classes-per-file -- Needs to be a wrapper for ws */
import { EventEmitter } from 'events'
@@ -28,7 +27,7 @@ interface WSWrapperOptions {
* Provides `EventEmitter` interface for native browser `WebSocket`,
* same, as `ws` package provides.
*/
export default class WSWrapper extends EventEmitter {
class WSWrapper extends EventEmitter {
public static CONNECTING = 0
public static OPEN = 1
public static CLOSING = 2
@@ -97,3 +96,5 @@ export default class WSWrapper extends EventEmitter {
return this.ws.readyState
}
}
export default WSWrapper

View File

@@ -20,13 +20,13 @@ interface ClassicAccountAndTag {
/**
* Autofills fields in a transaction.
*
* @param client - A client.
* @param this - A client.
* @param transaction - A transaction to autofill fields.
* @param signersCount - The expected number of signers for this transaction. Used for multisign.
* @returns An autofilled transaction.
*/
async function autofill<T extends Transaction>(
client: Client,
this: Client,
transaction: T,
signersCount?: number,
): Promise<T> {
@@ -38,13 +38,13 @@ async function autofill<T extends Transaction>(
const promises: Array<Promise<void>> = []
if (tx.Sequence == null) {
promises.push(setNextValidSequenceNumber(client, tx))
promises.push(setNextValidSequenceNumber(this, tx))
}
if (tx.Fee == null) {
promises.push(calculateFeePerTransactionType(client, tx, signersCount))
promises.push(calculateFeePerTransactionType(this, tx, signersCount))
}
if (tx.LastLedgerSequence == null) {
promises.push(setLatestValidatedLedgerSequence(client, tx))
promises.push(setLatestValidatedLedgerSequence(this, tx))
}
return Promise.all(promises).then(() => tx)

View File

@@ -36,7 +36,7 @@ interface GetBalancesOptions {
* @returns An array of XRP/non-XRP balances.
*/
async function getBalances(
client: Client,
this: Client,
account: string,
options: GetBalancesOptions = {},
): Promise<Balance[]> {
@@ -47,9 +47,9 @@ async function getBalances(
ledger_index: options.ledger_index ?? 'validated',
ledger_hash: options.ledger_hash,
}
const balance = await client
.request(xrpRequest)
.then((response) => response.result.account_data.Balance)
const balance = await this.request(xrpRequest).then(
(response) => response.result.account_data.Balance,
)
const xrpBalance = { currency: 'XRP', value: dropsToXrp(balance) }
// 2. Get Non-XRP Balance
const linesRequest: AccountLinesRequest = {
@@ -60,7 +60,7 @@ async function getBalances(
peer: options.peer,
limit: options.limit,
}
const responses = await client.requestAll(linesRequest)
const responses = await this.requestAll(linesRequest)
const accountLinesBalance = _.flatMap(responses, (response) =>
formatBalances(response.result.lines),
)

View File

@@ -15,12 +15,12 @@ const BASE_10 = 10
* @returns The transaction fee.
*/
export default async function getFee(
client: Client,
this: Client,
cushion?: number,
): Promise<string> {
const feeCushion = cushion ?? client.feeCushion
const feeCushion = cushion ?? this.feeCushion
const serverInfo = (await client.request({ command: 'server_info' })).result
const serverInfo = (await this.request({ command: 'server_info' })).result
.info
const baseFee = serverInfo.validated_ledger?.base_fee_xrp
@@ -37,7 +37,7 @@ export default async function getFee(
let fee = baseFeeXrp.times(serverInfo.load_factor).times(feeCushion)
// Cap fee to `client.maxFeeXRP`
fee = BigNumber.min(fee, client.maxFeeXRP)
fee = BigNumber.min(fee, this.maxFeeXRP)
// Round fee to 6 decimal places
return new BigNumber(fee.toFixed(NUM_DECIMAL_PLACES)).toString(BASE_10)
}

View File

@@ -10,6 +10,8 @@ import {
TakerAmount,
} from '../models/methods/bookOffers'
const DEFAULT_LIMIT = 20
function sortOffers(offers: BookOffer[]): BookOffer[] {
return offers.sort((offerA, offerB) => {
const qualityA = offerA.quality ?? 0
@@ -34,36 +36,33 @@ interface OrderbookOptions {
/**
* Fetch orderbook (buy/sell orders) between two accounts.
*
* @param client - Client.
* @param taker_pays - Specs of the currency account taking the offer pays.
* @param taker_gets - Specs of the currency account taking the offer receives.
* @param takerPays
* @param takerGets
* @param this - Client.
* @param takerPays - Specs of the currency account taking the offer pays.
* @param takerGets - Specs of the currency account taking the offer receives.
* @param options - Options to include for getting orderbook between payer and receiver.
* @returns An object containing buy and sell objects.
*/
// eslint-disable-next-line max-params -- Function needs 4 params.
async function getOrderbook(
client: Client,
this: Client,
takerPays: TakerAmount,
takerGets: TakerAmount,
options: OrderbookOptions,
options: OrderbookOptions = {},
): Promise<Orderbook> {
const request: BookOffersRequest = {
command: 'book_offers',
taker_pays: takerPays,
taker_gets: takerGets,
ledger_index: options.ledger_index,
ledger_index: options.ledger_index ?? 'validated',
ledger_hash: options.ledger_hash,
limit: options.limit,
limit: options.limit ?? DEFAULT_LIMIT,
taker: options.taker,
}
// 2. Make Request
const directOfferResults = await client.requestAll(request)
const directOfferResults = await this.requestAll(request)
request.taker_gets = takerPays
request.taker_pays = takerGets
const reverseOfferResults = await client.requestAll(request)
const reverseOfferResults = await this.requestAll(request)
// 3. Return Formatted Response
const directOffers = _.flatMap(
directOfferResults,

View File

@@ -5,8 +5,6 @@ import { ValidationError } from '../errors'
import { Transaction } from '../models/transactions'
import { sign } from '../wallet/signer'
import autofill from './autofill'
/**
* Submits an unsigned transaction.
* Steps performed on a transaction:
@@ -14,33 +12,32 @@ import autofill from './autofill'
* 2. Sign & Encode.
* 3. Submit.
*
* @param client - A Client.
* @param this - A Client.
* @param wallet - A Wallet to sign a transaction.
* @param transaction - A transaction to autofill, sign & encode, and submit.
* @returns A promise that contains SubmitResponse.
* @throws RippledError if submit request fails.
*/
async function submitTransaction(
client: Client,
this: Client,
wallet: Wallet,
transaction: Transaction,
): Promise<SubmitResponse> {
// TODO: replace with client.autofill(transaction) once prepend refactor is fixed.
const tx = await autofill(client, transaction)
const tx = await this.autofill(transaction)
const signedTxEncoded = sign(wallet, tx)
return submitSignedTransaction(client, signedTxEncoded)
return this.submitSignedTransaction(signedTxEncoded)
}
/**
* Encodes and submits a signed transaction.
*
* @param client - A Client.
* @param this - A Client.
* @param signedTransaction - A signed transaction to encode (if not already) and submit.
* @returns A promise that contains SubmitResponse.
* @throws RippledError if submit request fails.
*/
async function submitSignedTransaction(
client: Client,
this: Client,
signedTransaction: Transaction | string,
): Promise<SubmitResponse> {
if (!isSigned(signedTransaction)) {
@@ -55,7 +52,7 @@ async function submitSignedTransaction(
command: 'submit',
tx_blob: signedTxEncoded,
}
return client.request(request)
return this.request(request)
}
function isSigned(transaction: Transaction | string): boolean {