diff --git a/src/common/connection.ts b/src/common/connection.ts index 009ced86..f5a3d5f9 100644 --- a/src/common/connection.ts +++ b/src/common/connection.ts @@ -36,12 +36,50 @@ export interface ConnectionOptions { */ export type ConnectionUserOptions = Partial +/** + * Ledger is used to store and reference ledger information that has been + * captured by the Connection class. + */ +class Ledger { + private availableVersions = new RangeSet() + latestVersion: null | number = null + feeBase: null | number = null + feeRef: null | number = null + + hasVersions(lowVersion: number, highVersion: number): boolean { + return this.availableVersions.containsRange(lowVersion, highVersion) + } + + hasVersion(version: number): boolean { + return this.availableVersions.containsValue(version) + } + + update(data: { + ledger_index?: string + validated_ledgers?: string + fee_base?: string + fee_ref?: string + }) { + this.latestVersion = Number(data.ledger_index) + if (data.validated_ledgers) { + this.availableVersions.reset() + this.availableVersions.parseAndAddRanges(data.validated_ledgers) + } else { + this.availableVersions.addValue(this.latestVersion) + } + if (data.fee_base) { + this.feeBase = Number(data.fee_base) + } + if (data.fee_ref) { + this.feeRef = Number(data.fee_ref) + } + } +} + class Connection extends EventEmitter { private _url: string private _isReady: boolean = false private _ws: null | WebSocket = null - protected _ledgerVersion: null | number = null - private _availableLedgerVersions = new RangeSet() private _nextRequestID: number = 1 private _retry: number = 0 private _connectTimer: null | NodeJS.Timeout = null @@ -49,16 +87,16 @@ class Connection extends EventEmitter { private _heartbeatInterval: null | NodeJS.Timeout = null private _onOpenErrorBound: null | null | ((...args: any[]) => void) = null private _onUnexpectedCloseBound: null | ((...args: any[]) => void) = null - private _fee_base: null | number = null - private _fee_ref: null | number = null private _trace: (id: string, message: string) => void = () => {} private _config: ConnectionOptions + private _ledger: Ledger constructor(url?: string, options: ConnectionUserOptions = {}) { super() this.setMaxListeners(Infinity) this._url = url + this._ledger = new Ledger() this._config = { timeout: 20 * 1000, connectionTimeout: 2 * 1000, @@ -71,21 +109,6 @@ class Connection extends EventEmitter { } } - _updateLedgerVersions(data) { - this._ledgerVersion = Number(data.ledger_index) - if (data.validated_ledgers) { - this._availableLedgerVersions.reset() - this._availableLedgerVersions.parseAndAddRanges(data.validated_ledgers) - } else { - this._availableLedgerVersions.addValue(this._ledgerVersion) - } - } - - _updateFees(data) { - this._fee_base = Number(data.fee_base) - this._fee_ref = Number(data.fee_ref) - } - // return value is array of arguments to Connection.emit _parseMessage(message): [string, Object] | ['error', string, string, Object] { const data = JSON.parse(message) @@ -101,8 +124,7 @@ class Connection extends EventEmitter { // Possible `data.type` values include 'ledgerClosed', // 'transaction', 'path_find', and many others. if (data.type === 'ledgerClosed') { - this._updateLedgerVersions(data) - this._updateFees(data) + this._ledger.update(data) } return [data.type, data] } @@ -212,8 +234,7 @@ class Connection extends EventEmitter { }) } - this._updateLedgerVersions(data) - this._updateFees(data) + this._ledger.update(data) this._rebindOnUnexpectedClose() this._retry = 0 @@ -432,44 +453,53 @@ class Connection extends EventEmitter { return this.request({command: 'ping'}).catch(() => this.reconnect()) } - _whenReady(promise: Promise): Promise { + /** + * Wait for a valid connection before resolving. Useful for deferring methods + * until a connection has been established. + */ + private _waitForReady(): Promise { return new Promise((resolve, reject) => { - promise.catch(reject) if (!this._shouldBeConnected) { reject(new NotConnectedError()) } else if (this._state === WebSocket.OPEN && this._isReady) { - promise.then(resolve, reject) + resolve() } else { - this.once('connected', () => promise.then(resolve, reject)) + this.once('connected', () => resolve()) } }) } - getLedgerVersion(): Promise { - return this._whenReady(Promise.resolve(this._ledgerVersion!)) + async getLedgerVersion(): Promise { + await this._waitForReady() + return this._ledger.latestVersion! } - hasLedgerVersions(lowLedgerVersion, highLedgerVersion): Promise { - return this._whenReady( - Promise.resolve( - this._availableLedgerVersions.containsRange( - lowLedgerVersion, - highLedgerVersion || this._ledgerVersion - ) - ) - ) + async getFeeBase(): Promise { + await this._waitForReady() + return this._ledger.feeBase! } - hasLedgerVersion(ledgerVersion): Promise { - return this.hasLedgerVersions(ledgerVersion, ledgerVersion) + async getFeeRef(): Promise { + await this._waitForReady() + return this._ledger.feeRef! } - getFeeBase(): Promise { - return this._whenReady(Promise.resolve(Number(this._fee_base))) + async hasLedgerVersions( + lowLedgerVersion: number, + highLedgerVersion: number | undefined + ): Promise { + // You can call hasVersions with a potentially unknown upper limit, which + // will just act as a check on the lower limit. + if (!highLedgerVersion) { + return this.hasLedgerVersion(lowLedgerVersion) + } + await this._waitForReady() + return this._ledger.hasVersions(lowLedgerVersion, highLedgerVersion) } - getFeeRef(): Promise { - return this._whenReady(Promise.resolve(Number(this._fee_ref))) + async hasLedgerVersion(ledgerVersion: number): Promise { + await this._waitForReady() + return this._ledger.hasVersion(ledgerVersion) } _send(message: string): Promise { @@ -543,7 +573,7 @@ class Connection extends EventEmitter { // JSON.stringify automatically removes keys with value of 'undefined' const message = JSON.stringify(Object.assign({}, request, {id})) - this._whenReady(this._send(message)) + this._send(message) .then(() => { const delay = timeout || this._config.timeout timer = setTimeout(() => _reject(new TimeoutError()), delay) diff --git a/test/connection-test.ts b/test/connection-test.ts index 88428f56..4c766e8c 100644 --- a/test/connection-test.ts +++ b/test/connection-test.ts @@ -79,6 +79,25 @@ describe('Connection', function() { }) }) + it('ledger methods work as expected', async function() { + assert.strictEqual(await this.api.connection.getLedgerVersion(), 8819951) + assert.strictEqual( + await this.api.connection.hasLedgerVersion(8819951), + true + ) + assert.strictEqual( + await this.api.connection.hasLedgerVersions(8819951, undefined), + true + ) + // It would be nice to test a better range, but the mocked ledger only supports this single number + assert.strictEqual( + await this.api.connection.hasLedgerVersions(8819951, 8819951), + true + ) + assert.strictEqual(await this.api.connection.getFeeBase(), 10) + assert.strictEqual(await this.api.connection.getFeeRef(), 10) + }) + it('with proxy', function(done) { if (isBrowser) { done()