Compare commits

...

12 Commits

Author SHA1 Message Date
Alan Cohen
eb04e878ba 0.16.9 2016-03-22 18:39:33 -07:00
Chris Clark
c71febd116 Merge pull request #709 from darkdarkdragon/reconnect_fix
Reconnection fix
2016-03-22 18:12:28 -07:00
Ivan Tivonenko
69c1ccbb6b [TASK] emit connected and disconnected events from api 2016-03-23 02:05:07 +02:00
Ivan Tivonenko
499b8c8d8b [FIX] fix multiple reconnections issue 2016-03-23 01:18:08 +02:00
Chris Clark
ea009f9a84 Merge pull request #708 from clark800/fix-maker-exchange-rate
Fix makerExchangeRate for getOrders when rippled provides quality
2016-03-22 14:14:23 -07:00
Chris Clark
dc784d4567 Fix makerExchangeRate for getOrders when rippled provides quality 2016-03-22 13:21:26 -07:00
Alan Cohen
9ffc8a2c0b Merge pull request #707 from ripple/fix/filter-source-amount
FIX: Filtering source_amount pathfind correctly
2016-03-21 14:48:37 -07:00
Alan Cohen
5b20fe573e FIX: Filtering source_amount pathfind correctly
Bug:

```js
api.connect().then(() => {
  const pathfind = {
    source: {
      address: USDCold,
      amount: {
        currency: 'USD',
        value: '1.00' // <<<< Rippled response has "1" not "1.00"
      }
    },
    destination: {
      address: EURCold,
      amount: {currency: 'EUR'}
    }
  };
  return api.getPaths(pathfind).then(paths => {
    console.log('PATHS: \n', JSON.stringify(paths, null, 2));
  });
}).catch(console.log);

```
2016-03-21 11:44:55 -07:00
Chris Clark
7e466bb80f Merge pull request #705 from darkdarkdragon/code_comment
[FIX] add small code comment
2016-03-15 12:14:05 -07:00
Ivan Tivonenko
1f8418b447 [FIX] add small code comment 2016-03-15 20:58:22 +02:00
Alan Cohen
ccfc57fc62 Merge pull request #704 from darkdarkdragon/fix_edge_test
[FIX] fix test hang in microsoft edge
2016-03-15 10:27:01 -07:00
Ivan Tivonenko
1d31fccd72 [FIX] fix test hang in microsoft edge 2016-03-15 05:04:47 +02:00
14 changed files with 238 additions and 20 deletions

View File

@@ -61,6 +61,8 @@
- [API Events](#api-events)
- [ledger](#ledger)
- [error](#error)
- [connected](#connected)
- [disconnected](#disconnected)
<!-- END doctoc generated TOC please keep comment here to allow auto update -->
@@ -90,6 +92,14 @@ const api = new RippleAPI({
api.on('error', (errorCode, errorMessage) => {
console.log(errorCode + ': ' + errorMessage);
});
api.on('connected', () => {
console.log('connected');
});
api.on('disconnected', (code) => {
// code - [close code](https://developer.mozilla.org/en-US/docs/Web/API/CloseEvent) sent by the server
// will be 1000 if this was normal closure
console.log('disconnected, code:', code);
});
api.connect().then(() => {
/* insert code here */
}).then(() => {
@@ -3593,3 +3603,35 @@ api.on('error', (errorCode, errorMessage, data) => {
tooBusy: The server is too busy to help you now.
```
## connected
This event is emitted after connection successfully opened.
### Example
```javascript
api.on('connected', () => {
console.log('Connection is open now.');
});
```
## disconnected
This event is emitted when connection is closed.
### Return Value
The only parameter is a number containing the [close code](https://developer.mozilla.org/en-US/docs/Web/API/CloseEvent) send by the server.
### Example
```javascript
api.on('disconnected', (code) => {
if (code !== 1000) {
console.log('Connection is closed due to error.');
} else {
console.log('Connection is closed normally.');
}
});
```

View File

@@ -11,6 +11,14 @@ const api = new RippleAPI({
api.on('error', (errorCode, errorMessage) => {
console.log(errorCode + ': ' + errorMessage);
});
api.on('connected', () => {
console.log('connected');
});
api.on('disconnected', (code) => {
// code - [close code](https://developer.mozilla.org/en-US/docs/Web/API/CloseEvent) sent by the server
// will be 1000 if this was normal closure
console.log('disconnected, code:', code);
});
api.connect().then(() => {
/* insert code here */
}).then(() => {

View File

@@ -47,3 +47,35 @@ api.on('error', (errorCode, errorMessage, data) => {
```
tooBusy: The server is too busy to help you now.
```
## connected
This event is emitted after connection successfully opened.
### Example
```javascript
api.on('connected', () => {
console.log('Connection is open now.');
});
```
## disconnected
This event is emitted when connection is closed.
### Return Value
The only parameter is a number containing the [close code](https://developer.mozilla.org/en-US/docs/Web/API/CloseEvent) send by the server.
### Example
```javascript
api.on('disconnected', (code) => {
if (code !== 1000) {
console.log('Connection is closed due to error.');
} else {
console.log('Connection is closed normally.');
}
});
```

2
npm-shrinkwrap.json generated
View File

@@ -1,6 +1,6 @@
{
"name": "ripple-lib",
"version": "0.16.8",
"version": "0.16.9",
"dependencies": {
"ajv": {
"version": "1.4.10",

View File

@@ -1,6 +1,6 @@
{
"name": "ripple-lib",
"version": "0.16.8",
"version": "0.16.9",
"license": "ISC",
"description": "A JavaScript API for interacting with Ripple in Node.js and the browser",
"files": [
@@ -33,7 +33,7 @@
"assert-diff": "^1.0.1",
"babel-cli": "^6.4.0",
"babel-core": "^6.4.0",
"babel-eslint": "^4.1.6",
"babel-eslint": "^4.1.8",
"babel-loader": "^6.2.1",
"babel-plugin-syntax-flow": "^6.3.13",
"babel-plugin-transform-flow-strip-types": "^6.4.0",

View File

@@ -1,4 +1,3 @@
'use strict';
const _ = require('lodash');
const MochaSauce = require('mocha-in-sauce');
@@ -18,7 +17,6 @@ function main() {
maxDuration: 180000,
// the current build name (optional)
build: Date.now(),
seleniumVersion: '2.50.1',
url: testUrl,
runSauceConnect: true
};

View File

@@ -1,5 +1,5 @@
/* @flow */
'use strict';
'use strict'; // eslint-disable-line
/* eslint-disable max-len */
// Enable core-js polyfills. This allows use of ES6/7 extensions listed here:
@@ -89,6 +89,12 @@ class RippleAPI extends EventEmitter {
this.connection.on('error', (errorCode, errorMessage, data) => {
this.emit('error', errorCode, errorMessage, data);
});
this.connection.on('connected', () => {
this.emit('connected');
});
this.connection.on('disconnected', onError => {
this.emit('disconnected', onError);
});
} else {
// use null object pattern to provide better error message if user
// tries to call a method that requires a connection

View File

@@ -1,3 +1,5 @@
'use strict'; // eslint-disable-line
const _ = require('lodash');
const {EventEmitter} = require('events');
const WebSocket = require('ws');
@@ -35,6 +37,8 @@ class Connection extends EventEmitter {
this._ledgerVersion = null;
this._availableLedgerVersions = new RangeSet();
this._nextRequestID = 1;
this._retry = 0;
this._retryTimer = null;
}
_updateLedgerVersions(data) {
@@ -95,23 +99,61 @@ class Connection extends EventEmitter {
return this._state === WebSocket.OPEN && this._isReady;
}
_onUnexpectedClose(resolve = function() {}, reject = function() {}) {
_onUnexpectedClose(beforeOpen, resolve, reject, code) {
if (this._onOpenErrorBound) {
this._ws.removeListener('error', this._onOpenErrorBound);
this._onOpenErrorBound = null;
}
this._ws = null;
this._isReady = false;
this.connect().then(resolve, reject);
if (beforeOpen) {
// connection was closed before it was properly opened, so we must return
// error to connect's caller
this.connect().then(resolve, reject);
} else {
// if first parameter ws lib sends close code,
// but sometimes it forgots about it, so default to 1006 - CLOSE_ABNORMAL
this.emit('disconnected', code || 1006);
this._retryConnect();
}
}
_calculateTimeout(retriesCount) {
return (retriesCount < 40)
// First, for 2 seconds: 20 times per second
? (1000 / 20)
: (retriesCount < 40 + 60)
// Then, for 1 minute: once per second
? (1000)
: (retriesCount < 40 + 60 + 60)
// Then, for 10 minutes: once every 10 seconds
? (10 * 1000)
// Then: once every 30 seconds
: (30 * 1000);
}
_retryConnect() {
this._retry += 1;
const retryTimeout = this._calculateTimeout(this._retry);
this._retryTimer = setTimeout(() => {
this.connect().catch(this._retryConnect.bind(this));
}, retryTimeout);
}
_clearReconnectTimer() {
clearTimeout(this._retryTimer);
this._retryTimer = null;
}
_onOpen() {
this._ws.removeListener('close', this._onUnexpectedCloseBound);
this._onUnexpectedCloseBound = this._onUnexpectedClose.bind(this);
this._onUnexpectedCloseBound =
this._onUnexpectedClose.bind(this, false, null, null);
this._ws.once('close', this._onUnexpectedCloseBound);
this._ws.removeListener('error', this._onOpenErrorBound);
this._onOpenErrorBound = null;
this._retry = 0;
this._ws.on('error', error =>
this.emit('error', 'websocket', error.message, error));
@@ -127,6 +169,7 @@ class Connection extends EventEmitter {
}
_onOpenError(reject, error) {
this._onOpenErrorBound = null;
reject(new NotConnectedError(error && error.message));
}
@@ -174,6 +217,7 @@ class Connection extends EventEmitter {
}
connect() {
this._clearReconnectTimer();
return new Promise((resolve, reject) => {
if (!this._url) {
reject(new ConnectionError(
@@ -199,7 +243,7 @@ class Connection extends EventEmitter {
// resolve connect's promise after reconnect in that case.
// after open event we will rebound _onUnexpectedCloseBound
// without resolve and reject functions
this._onUnexpectedCloseBound = this._onUnexpectedClose.bind(this,
this._onUnexpectedCloseBound = this._onUnexpectedClose.bind(this, true,
resolve, reject);
this._ws.once('close', this._onUnexpectedCloseBound);
this._ws.once('open', () => this._onOpen().then(resolve, reject));
@@ -208,6 +252,8 @@ class Connection extends EventEmitter {
}
disconnect() {
this._clearReconnectTimer();
this._retry = 0;
return new Promise(resolve => {
if (this._state === WebSocket.CLOSED) {
resolve();
@@ -215,9 +261,10 @@ class Connection extends EventEmitter {
this._ws.once('close', resolve);
} else {
this._ws.removeListener('close', this._onUnexpectedCloseBound);
this._ws.once('close', () => {
this._ws.once('close', code => {
this._ws = null;
this._isReady = false;
this.emit('disconnected', code || 1000); // 1000 - CLOSE_NORMAL
resolve();
});
this._ws.close();

View File

@@ -1,5 +1,5 @@
/* @flow */
'use strict';
'use strict'; // eslint-disable-line strict
const utils = require('./utils');
const flags = require('./flags').orderFlags;
const parseAmount = require('./amount');
@@ -31,11 +31,14 @@ function parseAccountOrder(address: string, order: Object): Object {
expirationTime: utils.parseTimestamp(order.expiration)
});
const makerExchangeRate = order.quality ?
utils.adjustQualityForXRP(order.quality.toString(),
takerGetsAmount.currency, takerPaysAmount.currency) :
computeQuality(takerGetsAmount, takerPaysAmount);
const properties = {
maker: address,
sequence: order.seq,
makerExchangeRate: order.quality ? order.quality.toString()
: computeQuality(takerGetsAmount, takerPaysAmount)
makerExchangeRate: makerExchangeRate
};
return {specification, properties};

View File

@@ -93,7 +93,7 @@ function filterSourceFundsLowPaths(pathfind: PathFind,
paths.alternatives = _.filter(paths.alternatives, alt => {
return alt.source_amount &&
pathfind.source.amount &&
alt.source_amount.value === pathfind.source.amount.value;
new BigNumber(alt.source_amount.value).eq(pathfind.source.amount.value);
});
}
return paths;

View File

@@ -1,3 +1,4 @@
'use strict'; // eslint-disable-line
/* eslint-disable max-nested-callbacks */
const _ = require('lodash');
@@ -117,7 +118,9 @@ describe('Connection', function() {
}
}
const connection = new utils.common.Connection('ws://127.0.0.1:321');
// Address where no one listens
const connection =
new utils.common.Connection('ws://testripple.circleci.com:129');
connection.on('error', done);
connection.connect().catch(error => {
assert(error instanceof this.api.errors.NotConnectedError);
@@ -191,6 +194,81 @@ describe('Connection', function() {
}, 1);
});
it('reconnect on several unexpected close', function(done) {
if (process.browser) {
// can't be tested in browser this way, so skipping
done();
return;
}
this.timeout(7000);
const self = this;
function breakConnection() {
setTimeout(() => {
self.mockRippled.close();
setTimeout(() => {
self.mockRippled = setupAPI.createMockRippled(self._mockedServerPort);
}, 1500);
}, 21);
}
let connectsCount = 0;
let disconnectsCount = 0;
let code = 0;
this.api.connection.on('disconnected', _code => {
code = _code;
disconnectsCount += 1;
});
this.api.connection.on('connected', () => {
connectsCount += 1;
if (connectsCount < 3) {
breakConnection();
}
if (connectsCount === 3) {
if (disconnectsCount !== 3) {
done(new Error('disconnectsCount must be equal to 3 (got ' +
disconnectsCount + ' instead)'));
} else if (code !== 1006) {
done(new Error('disconnect must send code 1006 (got ' + code +
' instead)'));
} else {
done();
}
}
});
breakConnection();
});
it('should emit disconnected event with code 1000 (CLOSE_NORMAL)',
function(done
) {
this.api.once('disconnected', code => {
assert.strictEqual(code, 1000);
done();
});
this.api.disconnect();
});
it('should emit disconnected event with code 1006 (CLOSE_ABNORMAL)',
function(done
) {
if (process.browser) {
// can't be tested in browser this way, so skipping
done();
return;
}
this.api.once('disconnected', code => {
assert.strictEqual(code, 1006);
done();
});
this.mockRippled.close();
});
it('should emit connected event on after reconnect', function(done) {
this.api.once('connected', done);
this.api.connection._ws.close();
});
it('Multiply connect calls', function() {
return this.api.connect().then(() => {
return this.api.connect();

View File

@@ -3,7 +3,7 @@
"address": "r9cZA1mLK5R5Am25ArfXFmqgNwjZgnfk59",
"amount": {
"currency": "USD",
"value": "5"
"value": "5.00"
}
},
"destination": {

View File

@@ -1,4 +1,4 @@
'use strict';
'use strict'; // eslint-disable-line strict
const _ = require('lodash');
const addresses = require('../addresses');
@@ -160,6 +160,7 @@ module.exports = function(request, options = {}) {
{
'flags': 0,
'seq': 814018,
'quality': '16922629533.64839',
'taker_gets': {
'currency': 'NZD',
'issuer': 'rsP3mgGb2tcYUrxiLFiHJiQXhsziegtwBc',

View File

@@ -1,4 +1,5 @@
'use strict';
'use strict'; // eslint-disable-line
const net = require('net');
const RippleAPI = require('ripple-api').RippleAPI;
const RippleAPIBroadcast = require('ripple-api').RippleAPIBroadcast;
@@ -27,6 +28,7 @@ function getFreePort() {
function setupMockRippledConnection(testcase, port) {
return new Promise((resolve, reject) => {
testcase.mockRippled = createMockRippled(port);
testcase._mockedServerPort = port;
testcase.api = new RippleAPI({server: 'ws://localhost:' + port});
testcase.api.connect().then(() => {
testcase.api.once('ledger', () => resolve());
@@ -73,5 +75,6 @@ function teardown(done) {
module.exports = {
setup: setup,
teardown: teardown,
setupBroadcast: setupBroadcast
setupBroadcast: setupBroadcast,
createMockRippled: createMockRippled
};