mirror of
https://github.com/Xahau/xahau.js.git
synced 2025-11-19 19:55:51 +00:00
Add request(), hasNextPage(), and requestNextPage() (#887)
* Add support for all rippled APIs, including subscriptions. * Add support for arbitrary stream message types. * Note that rippled APIs take amounts in drops. * request() will be available in ripple-lib version 1.0.0+
This commit is contained in:
188
docs/index.md
188
docs/index.md
@@ -33,6 +33,11 @@
|
||||
- [Payment Channel Create](#payment-channel-create)
|
||||
- [Payment Channel Fund](#payment-channel-fund)
|
||||
- [Payment Channel Claim](#payment-channel-claim)
|
||||
- [rippled APIs](#rippled-apis)
|
||||
- [Listening to streams](#listening-to-streams)
|
||||
- [request](#request)
|
||||
- [hasNextPage](#hasnextpage)
|
||||
- [requestNextPage](#requestnextpage)
|
||||
- [API Methods](#api-methods)
|
||||
- [connect](#connect)
|
||||
- [disconnect](#disconnect)
|
||||
@@ -84,7 +89,7 @@
|
||||
|
||||
# Introduction
|
||||
|
||||
RippleAPI is the official client library to the XRP Ledger. Currently, RippleAPI is only available in JavaScript.
|
||||
RippleAPI (ripple-lib) is the official client library to the XRP Ledger. Currently, RippleAPI is only available in JavaScript.
|
||||
Using RippleAPI, you can:
|
||||
|
||||
* [Query transactions from the XRP Ledger history](#gettransaction)
|
||||
@@ -93,8 +98,6 @@ Using RippleAPI, you can:
|
||||
* [Generate a new XRP Ledger Address](#generateaddress)
|
||||
* ... and [much more](#api-methods).
|
||||
|
||||
RippleAPI only provides access to *validated*, *immutable* transaction data.
|
||||
|
||||
## Boilerplate
|
||||
|
||||
Use the following [boilerplate code](https://en.wikipedia.org/wiki/Boilerplate_code) to wrap your custom code using RippleAPI.
|
||||
@@ -750,6 +753,185 @@ signature | string | *Optional* Signed claim authorizing withdrawal of XRP from
|
||||
```
|
||||
|
||||
|
||||
# rippled APIs
|
||||
|
||||
ripple-lib relies on [rippled APIs](https://ripple.com/build/rippled-apis/) for all online functionality. With ripple-lib version 1.0.0 and higher, you can easily access rippled APIs through ripple-lib. Use the `request()`, `hasNextPage()`, and `requestNextPage()` methods:
|
||||
* Use `request()` to issue any `rippled` command, including `account_currencies`, `subscribe`, and `unsubscribe`. [Full list of API Methods](https://ripple.com/build/rippled-apis/#api-methods).
|
||||
* Use `hasNextPage()` to determine whether a response has more pages. This is true when the response includes a [`marker` field](https://ripple.com/build/rippled-apis/#markers-and-pagination).
|
||||
* Use `requestNextPage()` to request the next page of data.
|
||||
|
||||
When using rippled APIs, [specify XRP amounts in drops](https://ripple.com/build/rippled-apis/#specifying-currency-amounts). 1 XRP = 1000000 drops.
|
||||
|
||||
## Listening to streams
|
||||
|
||||
The `rippled` server can push updates to your client when various events happen. Refer to [Subscriptions in the `rippled` API docs](https://ripple.com/build/rippled-apis/#subscriptions) for details.
|
||||
|
||||
Note that the `streams` parameter for generic streams takes an array. For example, to subscribe to the `validations` stream, use `{ streams: [ 'validations' ] }`.
|
||||
|
||||
The string names of some generic streams to subscribe to are in the table below. (Refer to `rippled` for an up-to-date list of streams.)
|
||||
|
||||
Type | Description
|
||||
---- | -----------
|
||||
`server` | Sends a message whenever the status of the `rippled` server (for example, network connectivity) changes.
|
||||
`ledger` | Sends a message whenever the consensus process declares a new validated ledger.
|
||||
`transactions` | Sends a message whenever a transaction is included in a closed ledger.
|
||||
`transactions_proposed` | Sends a message whenever a transaction is included in a closed ledger, as well as some transactions that have not yet been included in a validated ledger and may never be. Not all proposed transactions appear before validation. Even some transactions that don't succeed are included in validated ledgers because they take the anti-spam transaction fee.
|
||||
`validations` | Sends a message whenever the server receives a validation message, also called a validation vote, regardless of whether the server trusts the validator.
|
||||
`manifests` | Sends a message whenever the server receives a manifest.
|
||||
`peer_status` | (Admin-only) Information about connected peer `rippled` servers, especially with regards to the consensus process.
|
||||
|
||||
When you subscribe to a stream, you must also listen to the relevant message type(s). Some of the available message types are in the table below. (Refer to `rippled` for an up-to-date list of message types.)
|
||||
|
||||
Type | Description
|
||||
---- | -----------
|
||||
`ledgerClosed` | Sent by the `ledger` stream when the consensus process declares a new fully validated ledger. The message identifies the ledger and provides some information about its contents.
|
||||
`validationReceived` | Sent by the `validations` stream when the server receives a validation message, also called a validation vote, regardless of whether the server trusts the validator.
|
||||
`manifestReceived` | Sent by the `manifests` stream when the server receives a manifest.
|
||||
`transaction` | Sent by many subscriptions including `transactions`, `transactions_proposed`, `accounts`, `accounts_proposed`, and `book` (Order Book). See [Transaction Streams](https://ripple.com/build/rippled-apis/#transaction-streams) for details.
|
||||
`peerStatusChange` | (Admin-only) Reports a large amount of information on the activities of other `rippled` servers to which the server is connected.
|
||||
|
||||
To register your listener function, use `connection.on(type, handler)`.
|
||||
|
||||
Here is an example of listening for transactions on given account(s):
|
||||
```
|
||||
const account = 'rf1BiGeXwwQoi8Z2ueFYTEXSwuJYfV2Jpn' // Replace with the account you want notifications for
|
||||
api.connect().then(() => { // Omit this if you are already connected
|
||||
|
||||
// 'transaction' can be replaced with the relevant `type` from the table above
|
||||
api.connection.on('transaction', (event) => {
|
||||
|
||||
// Do something useful with `event`
|
||||
console.log(JSON.stringify(event, null, 2))
|
||||
})
|
||||
|
||||
api.request('subscribe', {
|
||||
accounts: [ account ]
|
||||
}).then(response => {
|
||||
if (response.status === 'success') {
|
||||
console.log('Successfully subscribed')
|
||||
}
|
||||
}).catch(error => {
|
||||
// Handle `error`
|
||||
})
|
||||
})
|
||||
```
|
||||
|
||||
The subscription ends when you unsubscribe or the WebSocket connection is closed.
|
||||
|
||||
For full details, see [rippled Subscriptions](https://ripple.com/build/rippled-apis/#subscriptions).
|
||||
|
||||
## request
|
||||
|
||||
`request(command: string, options: object): Promise<object>`
|
||||
|
||||
Returns the response from invoking the specified command, with the specified options, on the connected rippled server.
|
||||
|
||||
Refer to [rippled APIs](https://ripple.com/build/rippled-apis/) for commands and options. All XRP amounts must be specified in drops. One drop is equal to 0.000001 XRP. See [Specifying Currency Amounts](https://ripple.com/build/rippled-apis/#specifying-currency-amounts).
|
||||
|
||||
Most commands return data for the `current` (in-progress, open) ledger by default. Do not rely on this. Always specify a ledger version in your request. In the example below, the 'validated' ledger is requested, which is the most recent ledger that has been validated by the whole network. See [Specifying Ledgers](https://ripple.com/build/rippled-apis/#specifying-ledgers).
|
||||
|
||||
### Return Value
|
||||
|
||||
This method returns a promise that resolves with the response from rippled.
|
||||
|
||||
### Example
|
||||
|
||||
```javascript
|
||||
// Replace 'ledger' with your desired rippled command
|
||||
return api.request('ledger', {
|
||||
ledger_index: 'validated'
|
||||
}).then(response => {
|
||||
/* Do something useful with response */
|
||||
console.log(JSON.stringify(response, null, 2))
|
||||
}).catch(console.error);
|
||||
```
|
||||
|
||||
|
||||
```json
|
||||
{
|
||||
"ledger": {
|
||||
"accepted": true,
|
||||
"account_hash": "F9E9653EA76EA0AEA58AC98A8E19EDCEC8299C2940519A190674FFAED3639A1F",
|
||||
"close_flags": 0,
|
||||
"close_time": 577999430,
|
||||
"close_time_human": "2018-Apr-25 19:23:50",
|
||||
"close_time_resolution": 10,
|
||||
"closed": true,
|
||||
"hash": "450E5CB0A39495839DA9CD9A0FED74BD71CBB929423A907ADC00F14FC7E7F920",
|
||||
"ledger_hash": "450E5CB0A39495839DA9CD9A0FED74BD71CBB929423A907ADC00F14FC7E7F920",
|
||||
"ledger_index": "38217406",
|
||||
"parent_close_time": 577999422,
|
||||
"parent_hash": "B8B364C63EB9E13FDB89CB729FEF833089B8438CBEB8FC41744CB667209221B3",
|
||||
"seqNum": "38217406",
|
||||
"totalCoins": "99992286058637091",
|
||||
"total_coins": "99992286058637091",
|
||||
"transaction_hash": "5BDD3D2780C28FB2C91C3404BD8ED04786B764B1E18CF319888EDE2C09834726"
|
||||
},
|
||||
"ledger_hash": "450E5CB0A39495839DA9CD9A0FED74BD71CBB929423A907ADC00F14FC7E7F920",
|
||||
"ledger_index": 38217406,
|
||||
"validated": true
|
||||
}
|
||||
```
|
||||
|
||||
|
||||
## hasNextPage
|
||||
|
||||
`hasNextPage(currentResponse): boolean`
|
||||
|
||||
Returns `true` when there are more pages available.
|
||||
|
||||
When there are more results than contained in the response, the response includes a `marker` field. You can use this convenience method, or check for `marker` yourself.
|
||||
|
||||
See [Markers and Pagination](https://ripple.com/build/rippled-apis/#markers-and-pagination).
|
||||
|
||||
### Return Value
|
||||
|
||||
This method returns `true` if `currentResponse` includes a `marker`.
|
||||
|
||||
### Example
|
||||
|
||||
```javascript
|
||||
return api.request('ledger_data', {
|
||||
ledger_index: 'validated'
|
||||
}).then(response => {
|
||||
/* Do something useful with response */
|
||||
|
||||
if (api.hasNextPage(response)) {
|
||||
/* There are more pages available */
|
||||
}
|
||||
}).catch(console.error);
|
||||
```
|
||||
|
||||
## requestNextPage
|
||||
|
||||
`requestNextPage(command: string, params: object = {}, currentResponse: object): Promise<object>`
|
||||
|
||||
Requests the next page of data.
|
||||
|
||||
You can use this convenience method, or include `currentResponse.marker` in `params` yourself, when using `request`.
|
||||
|
||||
See [Markers and Pagination](https://ripple.com/build/rippled-apis/#markers-and-pagination).
|
||||
|
||||
### Return Value
|
||||
|
||||
This method returns a promise that resolves with the next page of data from rippled.
|
||||
|
||||
If the response does not have a next page, the promise will reject with `new errors.NotFoundError('response does not have a next page')`.
|
||||
|
||||
### Example
|
||||
|
||||
```javascript
|
||||
const command = 'ledger_data'
|
||||
const params = {
|
||||
ledger_index: 'validated'
|
||||
}
|
||||
return api.request(command, params).then(response => {
|
||||
return api.requestNextPage(command, params, response)
|
||||
}).then(response_page_2 => {
|
||||
/* Do something useful with second page of response */
|
||||
}).catch(console.error);
|
||||
```
|
||||
|
||||
# API Methods
|
||||
|
||||
## connect
|
||||
|
||||
27
docs/src/hasNextPage.md.ejs
Normal file
27
docs/src/hasNextPage.md.ejs
Normal file
@@ -0,0 +1,27 @@
|
||||
## hasNextPage
|
||||
|
||||
`hasNextPage(currentResponse): boolean`
|
||||
|
||||
Returns `true` when there are more pages available.
|
||||
|
||||
When there are more results than contained in the response, the response includes a `marker` field. You can use this convenience method, or check for `marker` yourself.
|
||||
|
||||
See [Markers and Pagination](https://ripple.com/build/rippled-apis/#markers-and-pagination).
|
||||
|
||||
### Return Value
|
||||
|
||||
This method returns `true` if `currentResponse` includes a `marker`.
|
||||
|
||||
### Example
|
||||
|
||||
```javascript
|
||||
return api.request('ledger_data', {
|
||||
ledger_index: 'validated'
|
||||
}).then(response => {
|
||||
/* Do something useful with response */
|
||||
|
||||
if (api.hasNextPage(response)) {
|
||||
/* There are more pages available */
|
||||
}
|
||||
}).catch(console.error);
|
||||
```
|
||||
@@ -4,6 +4,10 @@
|
||||
<% include basictypes.md.ejs %>
|
||||
<% include transactions.md.ejs %>
|
||||
<% include specifications.md.ejs %>
|
||||
<% include rippledAPIs.md.ejs %>
|
||||
<% include request.md.ejs %>
|
||||
<% include hasNextPage.md.ejs %>
|
||||
<% include requestNextPage.md.ejs %>
|
||||
<% include methods.md.ejs %>
|
||||
<% include connect.md.ejs %>
|
||||
<% include disconnect.md.ejs %>
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
# Introduction
|
||||
|
||||
RippleAPI is the official client library to the XRP Ledger. Currently, RippleAPI is only available in JavaScript.
|
||||
RippleAPI (ripple-lib) is the official client library to the XRP Ledger. Currently, RippleAPI is only available in JavaScript.
|
||||
Using RippleAPI, you can:
|
||||
|
||||
* [Query transactions from the XRP Ledger history](#gettransaction)
|
||||
@@ -8,5 +8,3 @@ Using RippleAPI, you can:
|
||||
* [Submit](#submit) transactions to the XRP Ledger, including [Payments](#payment), [Orders](#order), [Settings changes](#settings), and [other types](#transaction-types)
|
||||
* [Generate a new XRP Ledger Address](#generateaddress)
|
||||
* ... and [much more](#api-methods).
|
||||
|
||||
RippleAPI only provides access to *validated*, *immutable* transaction data.
|
||||
|
||||
27
docs/src/request.md.ejs
Normal file
27
docs/src/request.md.ejs
Normal file
@@ -0,0 +1,27 @@
|
||||
## request
|
||||
|
||||
`request(command: string, options: object): Promise<object>`
|
||||
|
||||
Returns the response from invoking the specified command, with the specified options, on the connected rippled server.
|
||||
|
||||
Refer to [rippled APIs](https://ripple.com/build/rippled-apis/) for commands and options. All XRP amounts must be specified in drops. One drop is equal to 0.000001 XRP. See [Specifying Currency Amounts](https://ripple.com/build/rippled-apis/#specifying-currency-amounts).
|
||||
|
||||
Most commands return data for the `current` (in-progress, open) ledger by default. Do not rely on this. Always specify a ledger version in your request. In the example below, the 'validated' ledger is requested, which is the most recent ledger that has been validated by the whole network. See [Specifying Ledgers](https://ripple.com/build/rippled-apis/#specifying-ledgers).
|
||||
|
||||
### Return Value
|
||||
|
||||
This method returns a promise that resolves with the response from rippled.
|
||||
|
||||
### Example
|
||||
|
||||
```javascript
|
||||
// Replace 'ledger' with your desired rippled command
|
||||
return api.request('ledger', {
|
||||
ledger_index: 'validated'
|
||||
}).then(response => {
|
||||
/* Do something useful with response */
|
||||
console.log(JSON.stringify(response, null, 2))
|
||||
}).catch(console.error);
|
||||
```
|
||||
|
||||
<%- renderFixture('responses/ledger.json') %>
|
||||
29
docs/src/requestNextPage.md.ejs
Normal file
29
docs/src/requestNextPage.md.ejs
Normal file
@@ -0,0 +1,29 @@
|
||||
## requestNextPage
|
||||
|
||||
`requestNextPage(command: string, params: object = {}, currentResponse: object): Promise<object>`
|
||||
|
||||
Requests the next page of data.
|
||||
|
||||
You can use this convenience method, or include `currentResponse.marker` in `params` yourself, when using `request`.
|
||||
|
||||
See [Markers and Pagination](https://ripple.com/build/rippled-apis/#markers-and-pagination).
|
||||
|
||||
### Return Value
|
||||
|
||||
This method returns a promise that resolves with the next page of data from rippled.
|
||||
|
||||
If the response does not have a next page, the promise will reject with `new errors.NotFoundError('response does not have a next page')`.
|
||||
|
||||
### Example
|
||||
|
||||
```javascript
|
||||
const command = 'ledger_data'
|
||||
const params = {
|
||||
ledger_index: 'validated'
|
||||
}
|
||||
return api.request(command, params).then(response => {
|
||||
return api.requestNextPage(command, params, response)
|
||||
}).then(response_page_2 => {
|
||||
/* Do something useful with second page of response */
|
||||
}).catch(console.error);
|
||||
```
|
||||
66
docs/src/rippledAPIs.md.ejs
Normal file
66
docs/src/rippledAPIs.md.ejs
Normal file
@@ -0,0 +1,66 @@
|
||||
# rippled APIs
|
||||
|
||||
ripple-lib relies on [rippled APIs](https://ripple.com/build/rippled-apis/) for all online functionality. With ripple-lib version 1.0.0 and higher, you can easily access rippled APIs through ripple-lib. Use the `request()`, `hasNextPage()`, and `requestNextPage()` methods:
|
||||
* Use `request()` to issue any `rippled` command, including `account_currencies`, `subscribe`, and `unsubscribe`. [Full list of API Methods](https://ripple.com/build/rippled-apis/#api-methods).
|
||||
* Use `hasNextPage()` to determine whether a response has more pages. This is true when the response includes a [`marker` field](https://ripple.com/build/rippled-apis/#markers-and-pagination).
|
||||
* Use `requestNextPage()` to request the next page of data.
|
||||
|
||||
When using rippled APIs, [specify XRP amounts in drops](https://ripple.com/build/rippled-apis/#specifying-currency-amounts). 1 XRP = 1000000 drops.
|
||||
|
||||
## Listening to streams
|
||||
|
||||
The `rippled` server can push updates to your client when various events happen. Refer to [Subscriptions in the `rippled` API docs](https://ripple.com/build/rippled-apis/#subscriptions) for details.
|
||||
|
||||
Note that the `streams` parameter for generic streams takes an array. For example, to subscribe to the `validations` stream, use `{ streams: [ 'validations' ] }`.
|
||||
|
||||
The string names of some generic streams to subscribe to are in the table below. (Refer to `rippled` for an up-to-date list of streams.)
|
||||
|
||||
Type | Description
|
||||
---- | -----------
|
||||
`server` | Sends a message whenever the status of the `rippled` server (for example, network connectivity) changes.
|
||||
`ledger` | Sends a message whenever the consensus process declares a new validated ledger.
|
||||
`transactions` | Sends a message whenever a transaction is included in a closed ledger.
|
||||
`transactions_proposed` | Sends a message whenever a transaction is included in a closed ledger, as well as some transactions that have not yet been included in a validated ledger and may never be. Not all proposed transactions appear before validation. Even some transactions that don't succeed are included in validated ledgers because they take the anti-spam transaction fee.
|
||||
`validations` | Sends a message whenever the server receives a validation message, also called a validation vote, regardless of whether the server trusts the validator.
|
||||
`manifests` | Sends a message whenever the server receives a manifest.
|
||||
`peer_status` | (Admin-only) Information about connected peer `rippled` servers, especially with regards to the consensus process.
|
||||
|
||||
When you subscribe to a stream, you must also listen to the relevant message type(s). Some of the available message types are in the table below. (Refer to `rippled` for an up-to-date list of message types.)
|
||||
|
||||
Type | Description
|
||||
---- | -----------
|
||||
`ledgerClosed` | Sent by the `ledger` stream when the consensus process declares a new fully validated ledger. The message identifies the ledger and provides some information about its contents.
|
||||
`validationReceived` | Sent by the `validations` stream when the server receives a validation message, also called a validation vote, regardless of whether the server trusts the validator.
|
||||
`manifestReceived` | Sent by the `manifests` stream when the server receives a manifest.
|
||||
`transaction` | Sent by many subscriptions including `transactions`, `transactions_proposed`, `accounts`, `accounts_proposed`, and `book` (Order Book). See [Transaction Streams](https://ripple.com/build/rippled-apis/#transaction-streams) for details.
|
||||
`peerStatusChange` | (Admin-only) Reports a large amount of information on the activities of other `rippled` servers to which the server is connected.
|
||||
|
||||
To register your listener function, use `connection.on(type, handler)`.
|
||||
|
||||
Here is an example of listening for transactions on given account(s):
|
||||
```
|
||||
const account = 'rf1BiGeXwwQoi8Z2ueFYTEXSwuJYfV2Jpn' // Replace with the account you want notifications for
|
||||
api.connect().then(() => { // Omit this if you are already connected
|
||||
|
||||
// 'transaction' can be replaced with the relevant `type` from the table above
|
||||
api.connection.on('transaction', (event) => {
|
||||
|
||||
// Do something useful with `event`
|
||||
console.log(JSON.stringify(event, null, 2))
|
||||
})
|
||||
|
||||
api.request('subscribe', {
|
||||
accounts: [ account ]
|
||||
}).then(response => {
|
||||
if (response.status === 'success') {
|
||||
console.log('Successfully subscribed')
|
||||
}
|
||||
}).catch(error => {
|
||||
// Handle `error`
|
||||
})
|
||||
})
|
||||
```
|
||||
|
||||
The subscription ends when you unsubscribe or the WebSocket connection is closed.
|
||||
|
||||
For full details, see [rippled Subscriptions](https://ripple.com/build/rippled-apis/#subscriptions).
|
||||
89
src/api.ts
89
src/api.ts
@@ -1,4 +1,3 @@
|
||||
import * as _ from 'lodash'
|
||||
import {EventEmitter} from 'events'
|
||||
import {Connection, errors, validate} from './common'
|
||||
import {
|
||||
@@ -87,25 +86,12 @@ function getCollectKeyFromCommand(command: string): string|undefined {
|
||||
}
|
||||
}
|
||||
|
||||
// prevent access to non-validated ledger versions
|
||||
export class RestrictedConnection extends Connection {
|
||||
request(request: any, timeout?: number) {
|
||||
const ledger_index = request.ledger_index
|
||||
if (ledger_index !== undefined && ledger_index !== 'validated') {
|
||||
if (!_.isNumber(ledger_index) || ledger_index > this._ledgerVersion) {
|
||||
return Promise.reject(new errors.LedgerVersionError(
|
||||
`ledgerVersion ${ledger_index} is greater than server\'s ` +
|
||||
`most recent validated ledger: ${this._ledgerVersion}`))
|
||||
}
|
||||
}
|
||||
return super.request(request, timeout)
|
||||
}
|
||||
}
|
||||
|
||||
class RippleAPI extends EventEmitter {
|
||||
|
||||
_feeCushion: number
|
||||
connection: RestrictedConnection
|
||||
|
||||
// New in > 0.21.0
|
||||
// non-validated ledger versions are allowed, and passed to rippled as-is.
|
||||
connection: Connection
|
||||
|
||||
// these are exposed only for use by unit tests; they are not part of the API.
|
||||
static _PRIVATE = {
|
||||
@@ -121,7 +107,7 @@ class RippleAPI extends EventEmitter {
|
||||
this._feeCushion = options.feeCushion || 1.2
|
||||
const serverURL = options.server
|
||||
if (serverURL !== undefined) {
|
||||
this.connection = new RestrictedConnection(serverURL, options)
|
||||
this.connection = new Connection(serverURL, options)
|
||||
this.connection.on('ledgerClosed', message => {
|
||||
this.emit('ledger', formatLedgerClose(message))
|
||||
})
|
||||
@@ -137,14 +123,14 @@ class RippleAPI extends EventEmitter {
|
||||
} else {
|
||||
// use null object pattern to provide better error message if user
|
||||
// tries to call a method that requires a connection
|
||||
this.connection = new RestrictedConnection(null, options)
|
||||
this.connection = new Connection(null, options)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
async _request(command: 'account_info', params: AccountInfoRequest):
|
||||
async request(command: 'account_info', params: AccountInfoRequest):
|
||||
Promise<AccountInfoResponse>
|
||||
async _request(command: 'account_lines', params: AccountLinesRequest):
|
||||
async request(command: 'account_lines', params: AccountLinesRequest):
|
||||
Promise<AccountLinesResponse>
|
||||
|
||||
/**
|
||||
@@ -152,33 +138,62 @@ class RippleAPI extends EventEmitter {
|
||||
* For an account's trust lines and balances,
|
||||
* see `getTrustlines` and `getBalances`.
|
||||
*/
|
||||
async _request(command: 'account_objects', params: AccountObjectsRequest):
|
||||
async request(command: 'account_objects', params: AccountObjectsRequest):
|
||||
Promise<AccountObjectsResponse>
|
||||
|
||||
async _request(command: 'account_offers', params: AccountOffersRequest):
|
||||
async request(command: 'account_offers', params: AccountOffersRequest):
|
||||
Promise<AccountOffersResponse>
|
||||
async _request(command: 'book_offers', params: BookOffersRequest):
|
||||
async request(command: 'book_offers', params: BookOffersRequest):
|
||||
Promise<BookOffersResponse>
|
||||
async _request(command: 'gateway_balances', params: GatewayBalancesRequest):
|
||||
async request(command: 'gateway_balances', params: GatewayBalancesRequest):
|
||||
Promise<GatewayBalancesResponse>
|
||||
async _request(command: 'ledger', params: LedgerRequest):
|
||||
async request(command: 'ledger', params: LedgerRequest):
|
||||
Promise<LedgerResponse>
|
||||
async _request(command: 'ledger_entry', params: LedgerEntryRequest):
|
||||
async request(command: 'ledger_entry', params: LedgerEntryRequest):
|
||||
Promise<LedgerEntryResponse>
|
||||
|
||||
async request(command: string, params: object):
|
||||
Promise<object>
|
||||
|
||||
/**
|
||||
* Makes a request to the API with the given command and
|
||||
* additional request body parameters.
|
||||
*
|
||||
* NOTE: This command is under development.
|
||||
*/
|
||||
async _request(command: string, params: object = {}) {
|
||||
async request(command: string, params: object = {}): Promise<object> {
|
||||
return this.connection.request({
|
||||
...params,
|
||||
command
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true if there are more pages of data.
|
||||
*
|
||||
* When there are more results than contained in the response, the response
|
||||
* includes a `marker` field.
|
||||
*
|
||||
* See https://ripple.com/build/rippled-apis/#markers-and-pagination
|
||||
*/
|
||||
hasNextPage<T extends {marker?: string}>(currentResponse: T): boolean {
|
||||
return !!currentResponse.marker
|
||||
}
|
||||
|
||||
async requestNextPage<T extends {marker?: string}>(
|
||||
command: string,
|
||||
params: object = {},
|
||||
currentResponse: T
|
||||
): Promise<object> {
|
||||
if (!currentResponse.marker) {
|
||||
return Promise.reject(
|
||||
new errors.NotFoundError('response does not have a next page')
|
||||
)
|
||||
}
|
||||
const nextPageParams = Object.assign({}, params, {
|
||||
marker: currentResponse.marker
|
||||
})
|
||||
return this.request(command, nextPageParams)
|
||||
}
|
||||
|
||||
/**
|
||||
* Makes multiple paged requests to the API to return a given number of
|
||||
* resources. _requestAll() will make multiple requests until the `limit`
|
||||
@@ -188,8 +203,9 @@ class RippleAPI extends EventEmitter {
|
||||
* If the command is unknown, an additional `collect` property is required to
|
||||
* know which response key contains the array of resources.
|
||||
*
|
||||
* NOTE: This command is under development and should not yet be relied
|
||||
* on by external consumers.
|
||||
* NOTE: This command is used by existing methods and is not recommended for
|
||||
* general use. Instead, use rippled's built-in pagination and make multiple
|
||||
* requests as needed.
|
||||
*/
|
||||
async _requestAll(command: 'account_offers', params: AccountOffersRequest):
|
||||
Promise<AccountOffersResponse[]>
|
||||
@@ -222,12 +238,9 @@ class RippleAPI extends EventEmitter {
|
||||
limit: countRemaining,
|
||||
marker
|
||||
}
|
||||
// NOTE: We have to generalize the `this._request()` function signature
|
||||
// here until we add support for unknown commands (since command is some
|
||||
// unknown string).
|
||||
const singleResult = await (<Function>this._request)(command, repeatProps)
|
||||
const singleResult = await this.request(command, repeatProps)
|
||||
const collectedData = singleResult[collectKey]
|
||||
marker = singleResult.marker
|
||||
marker = singleResult['marker']
|
||||
results.push(singleResult)
|
||||
// Make sure we handle when no data (not even an empty array) is returned.
|
||||
const isExpectedFormat = Array.isArray(collectedData)
|
||||
|
||||
@@ -7,12 +7,6 @@ import {RippledError, DisconnectedError, NotConnectedError,
|
||||
TimeoutError, ResponseFormatError, ConnectionError,
|
||||
RippledNotInitializedError} from './errors'
|
||||
|
||||
function isStreamMessageType(type) {
|
||||
return type === 'ledgerClosed' ||
|
||||
type === 'transaction' ||
|
||||
type === 'path_find'
|
||||
}
|
||||
|
||||
export interface ConnectionOptions {
|
||||
trace?: boolean,
|
||||
proxy?: string
|
||||
@@ -93,16 +87,17 @@ class Connection extends EventEmitter {
|
||||
throw new ResponseFormatError('valid id not found in response')
|
||||
}
|
||||
return [data.id.toString(), data]
|
||||
} else if (isStreamMessageType(data.type)) {
|
||||
if (data.type === 'ledgerClosed') {
|
||||
this._updateLedgerVersions(data)
|
||||
this._updateFees(data)
|
||||
}
|
||||
return [data.type, data]
|
||||
} else if (data.type === undefined && data.error) {
|
||||
return ['error', data.error, data.error_message, data] // e.g. slowDown
|
||||
}
|
||||
throw new ResponseFormatError('unrecognized message type: ' + data.type)
|
||||
|
||||
// Possible `data.type` values include 'ledgerClosed',
|
||||
// 'transaction', 'path_find', and many others.
|
||||
if (data.type === 'ledgerClosed') {
|
||||
this._updateLedgerVersions(data)
|
||||
this._updateFees(data)
|
||||
}
|
||||
return [data.type, data]
|
||||
}
|
||||
|
||||
_onMessage(message) {
|
||||
@@ -427,7 +422,7 @@ class Connection extends EventEmitter {
|
||||
function onDisconnect() {
|
||||
clearTimeout(timer)
|
||||
self.removeAllListeners(eventName)
|
||||
reject(new DisconnectedError())
|
||||
reject(new DisconnectedError('websocket was closed'))
|
||||
}
|
||||
|
||||
function cleanup() {
|
||||
|
||||
@@ -35,7 +35,7 @@ export default async function getAccountInfo(
|
||||
// 1. Validate
|
||||
validate.getAccountInfo({address, options})
|
||||
// 2. Make Request
|
||||
const response = await this._request('account_info', {
|
||||
const response = await this.request('account_info', {
|
||||
account: address,
|
||||
ledger_index: options.ledgerVersion || 'validated'
|
||||
})
|
||||
|
||||
@@ -14,7 +14,7 @@ export default async function getAccountObjects(
|
||||
// through to rippled. rippled validates requests.
|
||||
|
||||
// Make Request
|
||||
const response = await this._request('account_objects', removeUndefined({
|
||||
const response = await this.request('account_objects', removeUndefined({
|
||||
account: address,
|
||||
type: options.type,
|
||||
ledger_hash: options.ledgerHash,
|
||||
|
||||
@@ -54,7 +54,7 @@ async function getBalanceSheet(
|
||||
validate.getBalanceSheet({address, options})
|
||||
options = await ensureLedgerVersion.call(this, options)
|
||||
// 2. Make Request
|
||||
const response = await this._request('gateway_balances', {
|
||||
const response = await this.request('gateway_balances', {
|
||||
account: address,
|
||||
strict: true,
|
||||
hotwallet: options.excludeAddresses,
|
||||
|
||||
@@ -15,7 +15,7 @@ async function getLedger(
|
||||
// 1. Validate
|
||||
validate.getLedger({options})
|
||||
// 2. Make Request
|
||||
const response = await this._request('ledger', {
|
||||
const response = await this.request('ledger', {
|
||||
ledger_index: options.ledgerVersion || 'validated',
|
||||
expand: options.includeAllData,
|
||||
transactions: options.includeTransactions,
|
||||
|
||||
@@ -23,7 +23,7 @@ async function getPaymentChannel(
|
||||
// 1. Validate
|
||||
validate.getPaymentChannel({id})
|
||||
// 2. Make Request
|
||||
const response = await this._request('ledger_entry', {
|
||||
const response = await this.request('ledger_entry', {
|
||||
index: id,
|
||||
binary: false,
|
||||
ledger_index: 'validated'
|
||||
|
||||
@@ -33,7 +33,7 @@ async function getSettings(
|
||||
// 1. Validate
|
||||
validate.getSettings({address, options})
|
||||
// 2. Make Request
|
||||
const response = await this._request('account_info', {
|
||||
const response = await this.request('account_info', {
|
||||
account: address,
|
||||
ledger_index: options.ledgerVersion || 'validated',
|
||||
signer_lists: true
|
||||
|
||||
@@ -51,6 +51,55 @@ describe('RippleAPI', function () {
|
||||
assert.strictEqual(error.inspect(), '[RippleError(mess, { data: 1 })]');
|
||||
});
|
||||
|
||||
describe('pagination', function () {
|
||||
|
||||
describe('hasNextPage', function () {
|
||||
|
||||
it('returns true when there is another page', function () {
|
||||
return this.api.request('ledger_data').then(response => {
|
||||
assert(this.api.hasNextPage(response));
|
||||
}
|
||||
);
|
||||
});
|
||||
|
||||
it('returns false when there are no more pages', function () {
|
||||
return this.api.request('ledger_data').then(response => {
|
||||
return this.api.requestNextPage('ledger_data', {}, response);
|
||||
}).then(response => {
|
||||
assert(!this.api.hasNextPage(response));
|
||||
});
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
describe('requestNextPage', function () {
|
||||
|
||||
it('requests the next page', function () {
|
||||
return this.api.request('ledger_data').then(response => {
|
||||
return this.api.requestNextPage('ledger_data', {}, response);
|
||||
}).then(response => {
|
||||
assert.equal(response.state[0].index, '000B714B790C3C79FEE00D17C4DEB436B375466F29679447BA64F265FD63D731')
|
||||
});
|
||||
});
|
||||
|
||||
it('rejects when there are no more pages', function () {
|
||||
return this.api.request('ledger_data').then(response => {
|
||||
return this.api.requestNextPage('ledger_data', {}, response);
|
||||
}).then(response => {
|
||||
assert(!this.api.hasNextPage(response))
|
||||
return this.api.requestNextPage('ledger_data', {}, response);
|
||||
}).then(() => {
|
||||
assert(false, 'Should reject');
|
||||
}).catch(error => {
|
||||
assert(error instanceof Error);
|
||||
assert.equal(error.message, 'response does not have a next page')
|
||||
});
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
describe('preparePayment', function () {
|
||||
|
||||
it('normal', function () {
|
||||
@@ -1213,7 +1262,7 @@ describe('RippleAPI', function () {
|
||||
});
|
||||
|
||||
it('request account_objects', function () {
|
||||
return this.api._request('account_objects', {
|
||||
return this.api.request('account_objects', {
|
||||
account: address
|
||||
}).then(response =>
|
||||
checkResult(responses.getAccountObjects, 'AccountObjectsResponse', response));
|
||||
@@ -1221,7 +1270,7 @@ describe('RippleAPI', function () {
|
||||
|
||||
it('request account_objects - invalid options', function () {
|
||||
// Intentionally no local validation of these options
|
||||
return this.api._request('account_objects', {
|
||||
return this.api.request('account_objects', {
|
||||
account: address,
|
||||
invalid: 'options'
|
||||
}).then(response =>
|
||||
@@ -1528,12 +1577,12 @@ describe('RippleAPI', function () {
|
||||
_.partial(checkResult, responses.getLedger.header, 'getLedger'));
|
||||
});
|
||||
|
||||
// New in > 0.21.0
|
||||
// future ledger versions are allowed, and passed to rippled as-is.
|
||||
it('getLedger - future ledger version', function () {
|
||||
return this.api.getLedger({ ledgerVersion: 14661789 }).then(() => {
|
||||
assert(false, 'Should throw LedgerVersionError');
|
||||
}).catch(error => {
|
||||
assert(error instanceof this.api.errors.LedgerVersionError);
|
||||
});
|
||||
return this.api.getLedger({ ledgerVersion: 14661789 }).then(response => {
|
||||
assert(response)
|
||||
})
|
||||
});
|
||||
|
||||
it('getLedger - with state as hashes', function () {
|
||||
|
||||
@@ -394,10 +394,10 @@ describe('Connection', function() {
|
||||
});
|
||||
|
||||
it('unrecognized message type', function(done) {
|
||||
this.api.on('error', (errorCode, errorMessage, message) => {
|
||||
assert.strictEqual(errorCode, 'badMessage');
|
||||
assert.strictEqual(errorMessage, 'unrecognized message type: unknown');
|
||||
assert.strictEqual(message, '{"type":"unknown"}');
|
||||
// This enables us to automatically support any
|
||||
// new messages added by rippled in the future.
|
||||
this.api.connection.on('unknown', (event) => {
|
||||
assert.deepEqual(event, {type: 'unknown'})
|
||||
done();
|
||||
});
|
||||
|
||||
|
||||
23
test/fixtures/responses/ledger.json
vendored
Normal file
23
test/fixtures/responses/ledger.json
vendored
Normal file
@@ -0,0 +1,23 @@
|
||||
{
|
||||
"ledger": {
|
||||
"accepted": true,
|
||||
"account_hash": "F9E9653EA76EA0AEA58AC98A8E19EDCEC8299C2940519A190674FFAED3639A1F",
|
||||
"close_flags": 0,
|
||||
"close_time": 577999430,
|
||||
"close_time_human": "2018-Apr-25 19:23:50",
|
||||
"close_time_resolution": 10,
|
||||
"closed": true,
|
||||
"hash": "450E5CB0A39495839DA9CD9A0FED74BD71CBB929423A907ADC00F14FC7E7F920",
|
||||
"ledger_hash": "450E5CB0A39495839DA9CD9A0FED74BD71CBB929423A907ADC00F14FC7E7F920",
|
||||
"ledger_index": "38217406",
|
||||
"parent_close_time": 577999422,
|
||||
"parent_hash": "B8B364C63EB9E13FDB89CB729FEF833089B8438CBEB8FC41744CB667209221B3",
|
||||
"seqNum": "38217406",
|
||||
"totalCoins": "99992286058637091",
|
||||
"total_coins": "99992286058637091",
|
||||
"transaction_hash": "5BDD3D2780C28FB2C91C3404BD8ED04786B764B1E18CF319888EDE2C09834726"
|
||||
},
|
||||
"ledger_hash": "450E5CB0A39495839DA9CD9A0FED74BD71CBB929423A907ADC00F14FC7E7F920",
|
||||
"ledger_index": 38217406,
|
||||
"validated": true
|
||||
}
|
||||
4
test/fixtures/rippled/index.js
vendored
4
test/fixtures/rippled/index.js
vendored
@@ -37,6 +37,10 @@ module.exports = {
|
||||
usd_xrp: require('./book-offers-usd-xrp'),
|
||||
xrp_usd: require('./book-offers-xrp-usd')
|
||||
},
|
||||
ledger_data: {
|
||||
first_page: require('./ledger-data-first-page'),
|
||||
last_page: require('./ledger-data-last-page')
|
||||
},
|
||||
ledger_entry: {
|
||||
error: require('./ledger-entry-error')
|
||||
},
|
||||
|
||||
40
test/fixtures/rippled/ledger-data-first-page.json
vendored
Normal file
40
test/fixtures/rippled/ledger-data-first-page.json
vendored
Normal file
@@ -0,0 +1,40 @@
|
||||
{
|
||||
"id": 0,
|
||||
"status": "success",
|
||||
"type": "response",
|
||||
"result": {
|
||||
"ledger_hash":
|
||||
"102A6E70FFB18C18E97BB56E3047B0E45EA1BCC90BFCCB8CBB0D07BF0E2AB449",
|
||||
"ledger_index": 38202000,
|
||||
"marker":
|
||||
"000B714B790C3C79FEE00D17C4DEB436B375466F29679447BA64F265FD63D730",
|
||||
"state": [
|
||||
{
|
||||
"Flags": 0,
|
||||
"Indexes": [
|
||||
"B32769DB3BE790E959A96CF37A62414479E3EB20A5AEC7156B2BF8FD816DBFF8"
|
||||
],
|
||||
"LedgerEntryType": "DirectoryNode",
|
||||
"Owner": "rwt5iiE1mRbBgNhH6spU4nKgHcE7xK9joN",
|
||||
"RootIndex":
|
||||
"0005C961C890079D3C4CC8317F9735D388C3CE3D9BCDC152D3C9A7C08F508D1B",
|
||||
"index":
|
||||
"0005C961C890079D3C4CC8317F9735D388C3CE3D9BCDC152D3C9A7C08F508D1B"
|
||||
},
|
||||
{
|
||||
"Account": "rpzpyUjdWKmz7yyMvirk3abcaNvSPmDpJn",
|
||||
"Balance": "91508000",
|
||||
"Flags": 0,
|
||||
"LedgerEntryType": "AccountRoot",
|
||||
"OwnerCount": 0,
|
||||
"PreviousTxnID":
|
||||
"F62A5A5EC92DE4E52663B9C7B44A2B76DAB1371737C83A5A81127CBDA84DFE9E",
|
||||
"PreviousTxnLgrSeq": 35672898,
|
||||
"Sequence": 1,
|
||||
"index":
|
||||
"000B6A1287DB6174F61B1BF987E630CF41DA2A2131CFEB6C5C8143A8F539E9D1"
|
||||
}
|
||||
],
|
||||
"validated": true
|
||||
}
|
||||
}
|
||||
47
test/fixtures/rippled/ledger-data-last-page.json
vendored
Normal file
47
test/fixtures/rippled/ledger-data-last-page.json
vendored
Normal file
@@ -0,0 +1,47 @@
|
||||
{
|
||||
"id": 0,
|
||||
"status": "success",
|
||||
"type": "response",
|
||||
"result": {
|
||||
"ledger_hash":
|
||||
"102A6E70FFB18C18E97BB56E3047B0E45EA1BCC90BFCCB8CBB0D07BF0E2AB449",
|
||||
"ledger_index": 38202000,
|
||||
"state": [
|
||||
{
|
||||
"Account": "rN3rdDNhQidDuzTFU1ArXWr89B4JG9xZ99",
|
||||
"Balance": "249222644",
|
||||
"Flags": 0,
|
||||
"LedgerEntryType": "AccountRoot",
|
||||
"OwnerCount": 0,
|
||||
"PreviousTxnID":
|
||||
"9A6EEBB6055E2C768BCA3B89B458A5D14A931449443053D9A1A9256F79D590DC",
|
||||
"PreviousTxnLgrSeq": 35891744,
|
||||
"Sequence": 1,
|
||||
"index":
|
||||
"000B714B790C3C79FEE00D17C4DEB436B375466F29679447BA64F265FD63D731"
|
||||
},
|
||||
{
|
||||
"Account": "rLNNqGs2jJKQcg2CuoACuwkJ1ssga9LTYT",
|
||||
"BookDirectory":
|
||||
"6FA9AF02AF19345DC187747EF07CDABECA37CB6DCFFB045E5A08D0CF885B163B",
|
||||
"BookNode": "0000000000000000",
|
||||
"Flags": 0,
|
||||
"LedgerEntryType": "Offer",
|
||||
"OwnerNode": "0000000000000000",
|
||||
"PreviousTxnID":
|
||||
"5D3E557E7C08FA90EF9EE144165855B3823BD24319F28BDD81E23C3573398C44",
|
||||
"PreviousTxnLgrSeq": 38040457,
|
||||
"Sequence": 9,
|
||||
"TakerGets": {
|
||||
"currency": "CNY",
|
||||
"issuer": "rPT74sUcTBTQhkHVD54WGncoqXEAMYbmH7",
|
||||
"value": "322.4"
|
||||
},
|
||||
"TakerPays": "80000000",
|
||||
"index":
|
||||
"0011C33FA959278D478E7A3811D7DBB9E43E1768E12538CD54B028E5E7DA96E5"
|
||||
}
|
||||
],
|
||||
"validated": true
|
||||
}
|
||||
}
|
||||
@@ -237,6 +237,15 @@ module.exports = function createMockRippled(port) {
|
||||
}
|
||||
});
|
||||
|
||||
mock.on('request_ledger_data', function (request, conn) {
|
||||
assert.strictEqual(request.command, 'ledger_data');
|
||||
if (request.marker) {
|
||||
conn.send(createResponse(request, fixtures.ledger_data.last_page));
|
||||
} else {
|
||||
conn.send(createResponse(request, fixtures.ledger_data.first_page));
|
||||
}
|
||||
});
|
||||
|
||||
mock.on('request_ledger_entry', function (request, conn) {
|
||||
assert.strictEqual(request.command, 'ledger_entry');
|
||||
if (request.index ===
|
||||
|
||||
Reference in New Issue
Block a user