[TASK] emit connected and disconnected events from api

This commit is contained in:
Ivan Tivonenko
2016-03-22 06:07:56 +02:00
parent 499b8c8d8b
commit 69c1ccbb6b
7 changed files with 149 additions and 14 deletions

View File

@@ -61,6 +61,8 @@
- [API Events](#api-events) - [API Events](#api-events)
- [ledger](#ledger) - [ledger](#ledger)
- [error](#error) - [error](#error)
- [connected](#connected)
- [disconnected](#disconnected)
<!-- END doctoc generated TOC please keep comment here to allow auto update --> <!-- 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) => { api.on('error', (errorCode, errorMessage) => {
console.log(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(() => { api.connect().then(() => {
/* insert code here */ /* insert code here */
}).then(() => { }).then(() => {
@@ -3593,3 +3603,35 @@ api.on('error', (errorCode, errorMessage, data) => {
tooBusy: The server is too busy to help you now. 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) => { api.on('error', (errorCode, errorMessage) => {
console.log(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(() => { api.connect().then(() => {
/* insert code here */ /* insert code here */
}).then(() => { }).then(() => {

View File

@@ -47,3 +47,35 @@ api.on('error', (errorCode, errorMessage, data) => {
``` ```
tooBusy: The server is too busy to help you now. 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

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

View File

@@ -1,3 +1,5 @@
'use strict'; // eslint-disable-line
const _ = require('lodash'); const _ = require('lodash');
const {EventEmitter} = require('events'); const {EventEmitter} = require('events');
const WebSocket = require('ws'); const WebSocket = require('ws');
@@ -97,36 +99,42 @@ class Connection extends EventEmitter {
return this._state === WebSocket.OPEN && this._isReady; return this._state === WebSocket.OPEN && this._isReady;
} }
_onUnexpectedClose(resolve, reject) { _onUnexpectedClose(beforeOpen, resolve, reject, code) {
if (this._onOpenErrorBound) { if (this._onOpenErrorBound) {
this._ws.removeListener('error', this._onOpenErrorBound); this._ws.removeListener('error', this._onOpenErrorBound);
this._onOpenErrorBound = null; this._onOpenErrorBound = null;
} }
this._ws = null; this._ws = null;
this._isReady = false; this._isReady = false;
if (_.isFunction(resolve)) { if (beforeOpen) {
// connection was closed before it was properly opened, so we must return // connection was closed before it was properly opened, so we must return
// error to connect's caller // error to connect's caller
this.connect().then(resolve, reject); this.connect().then(resolve, reject);
} else { } else {
this.emit('disconnected', true); // 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(); this._retryConnect();
} }
} }
_retryConnect() { _calculateTimeout(retriesCount) {
this._retry += 1; return (retriesCount < 40)
const retryTimeout = (this._retry < 40)
// First, for 2 seconds: 20 times per second // First, for 2 seconds: 20 times per second
? (1000 / 20) ? (1000 / 20)
: (this._retry < 40 + 60) : (retriesCount < 40 + 60)
// Then, for 1 minute: once per second // Then, for 1 minute: once per second
? (1000) ? (1000)
: (this._retry < 40 + 60 + 60) : (retriesCount < 40 + 60 + 60)
// Then, for 10 minutes: once every 10 seconds // Then, for 10 minutes: once every 10 seconds
? (10 * 1000) ? (10 * 1000)
// Then: once every 30 seconds // Then: once every 30 seconds
: (30 * 1000); : (30 * 1000);
}
_retryConnect() {
this._retry += 1;
const retryTimeout = this._calculateTimeout(this._retry);
this._retryTimer = setTimeout(() => { this._retryTimer = setTimeout(() => {
this.connect().catch(this._retryConnect.bind(this)); this.connect().catch(this._retryConnect.bind(this));
}, retryTimeout); }, retryTimeout);
@@ -139,7 +147,8 @@ class Connection extends EventEmitter {
_onOpen() { _onOpen() {
this._ws.removeListener('close', this._onUnexpectedCloseBound); 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.once('close', this._onUnexpectedCloseBound);
this._ws.removeListener('error', this._onOpenErrorBound); this._ws.removeListener('error', this._onOpenErrorBound);
@@ -234,7 +243,7 @@ class Connection extends EventEmitter {
// resolve connect's promise after reconnect in that case. // resolve connect's promise after reconnect in that case.
// after open event we will rebound _onUnexpectedCloseBound // after open event we will rebound _onUnexpectedCloseBound
// without resolve and reject functions // without resolve and reject functions
this._onUnexpectedCloseBound = this._onUnexpectedClose.bind(this, this._onUnexpectedCloseBound = this._onUnexpectedClose.bind(this, true,
resolve, reject); resolve, reject);
this._ws.once('close', this._onUnexpectedCloseBound); this._ws.once('close', this._onUnexpectedCloseBound);
this._ws.once('open', () => this._onOpen().then(resolve, reject)); this._ws.once('open', () => this._onOpen().then(resolve, reject));
@@ -252,10 +261,10 @@ class Connection extends EventEmitter {
this._ws.once('close', resolve); this._ws.once('close', resolve);
} else { } else {
this._ws.removeListener('close', this._onUnexpectedCloseBound); this._ws.removeListener('close', this._onUnexpectedCloseBound);
this._ws.once('close', () => { this._ws.once('close', code => {
this._ws = null; this._ws = null;
this._isReady = false; this._isReady = false;
this.emit('disconnected', false); this.emit('disconnected', code || 1000); // 1000 - CLOSE_NORMAL
resolve(); resolve();
}); });
this._ws.close(); this._ws.close();

View File

@@ -1,3 +1,4 @@
'use strict'; // eslint-disable-line
/* eslint-disable max-nested-callbacks */ /* eslint-disable max-nested-callbacks */
const _ = require('lodash'); const _ = require('lodash');
@@ -212,7 +213,9 @@ describe('Connection', function() {
let connectsCount = 0; let connectsCount = 0;
let disconnectsCount = 0; let disconnectsCount = 0;
this.api.connection.on('disconnected', () => { let code = 0;
this.api.connection.on('disconnected', _code => {
code = _code;
disconnectsCount += 1; disconnectsCount += 1;
}); });
this.api.connection.on('connected', () => { this.api.connection.on('connected', () => {
@@ -224,6 +227,9 @@ describe('Connection', function() {
if (disconnectsCount !== 3) { if (disconnectsCount !== 3) {
done(new Error('disconnectsCount must be equal to 3 (got ' + done(new Error('disconnectsCount must be equal to 3 (got ' +
disconnectsCount + ' instead)')); disconnectsCount + ' instead)'));
} else if (code !== 1006) {
done(new Error('disconnect must send code 1006 (got ' + code +
' instead)'));
} else { } else {
done(); done();
} }
@@ -233,6 +239,36 @@ describe('Connection', function() {
breakConnection(); 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() { it('Multiply connect calls', function() {
return this.api.connect().then(() => { return this.api.connect().then(() => {
return this.api.connect(); return this.api.connect();

View File

@@ -1,3 +1,5 @@
'use strict'; // eslint-disable-line
const net = require('net'); const net = require('net');
const RippleAPI = require('ripple-api').RippleAPI; const RippleAPI = require('ripple-api').RippleAPI;
const RippleAPIBroadcast = require('ripple-api').RippleAPIBroadcast; const RippleAPIBroadcast = require('ripple-api').RippleAPIBroadcast;