Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
46 changes: 30 additions & 16 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,7 @@
"eslint": "^5.16.0",
"eslint-friendly-formatter": "^4.0.1",
"eslint-loader": "^2.2.1",
"lolex": "^5.1.1",
"mocha": "6.2.0",
"mock-require": "^3.0.3",
"nyc": "^14.1.1",
Expand Down
23 changes: 19 additions & 4 deletions src/protocols/abstract/realtime.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@ const
KuzzleAbstractProtocol = require('./common');

class RTWrapper extends KuzzleAbstractProtocol {

constructor (host, options = {}) {
super(host, options);

Expand Down Expand Up @@ -52,27 +51,43 @@ class RTWrapper extends KuzzleAbstractProtocol {
*
* @param {Error} error
*/
clientNetworkError(error) {
clientNetworkError (error) {
this.state = 'offline';
this.clear();

const connectionError = new Error(`Unable to connect to kuzzle server at ${this.host}:${this.port}`);
connectionError.internal = error;

this.emit('networkError', connectionError);

if (this.autoReconnect && !this.retrying && !this.stopRetryingToConnect) {
this.retrying = true;

if ( typeof window === 'object'
&& typeof window.navigator === 'object'
&& window.navigator.onLine === false
) {
window.addEventListener(
'online',
() => {
this.retrying = false;
this.connect().catch(err => this.clientNetworkError(err));
},
{ once: true });
return;
}

setTimeout(() => {
this.retrying = false;
this.connect(this.host).catch(err => this.clientNetworkError(err));
}, this.reconnectionDelay);
} else {
}
else {
this.emit('disconnect');
}
}

isReady() {
isReady () {
return this.state === 'connected';
}
}
Expand Down
36 changes: 36 additions & 0 deletions test/mocks/window.mock.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
const
sinon = require('sinon'),
KuzzleEventEmitter = require('../../src/eventEmitter');

// A class to mock the global window object
class WindowMock extends KuzzleEventEmitter {
constructor () {
super();

if (typeof window !== 'undefined') {
throw new Error('Cannot mock add a global "window" object: already defined');
}

this.navigator = {
onLine: true
};

this.addEventListener = this.addListener;
sinon.spy(this, 'addEventListener');
}

static restore () {
delete global.window;
}

static inject () {
Object.defineProperty(global, 'window', {
value: new this(),
enumerable: false,
writable: false,
configurable: true
});
}
}

module.exports = WindowMock;
14 changes: 6 additions & 8 deletions test/protocol/socketio.test.js
Original file line number Diff line number Diff line change
@@ -1,11 +1,8 @@
const
should = require('should'),
sinon = require('sinon'),
SocketIO = require('../../src/protocols/socketio');

/**
* @global window
*/
SocketIO = require('../../src/protocols/socketio'),
windowMock = require('../mocks/window.mock');

describe('SocketIO networking module', () => {
let
Expand All @@ -15,6 +12,7 @@ describe('SocketIO networking module', () => {

beforeEach(() => {
clock = sinon.useFakeTimers();

socketStub = {
events: {},
eventOnce: {},
Expand Down Expand Up @@ -74,11 +72,13 @@ describe('SocketIO networking module', () => {
});
socketIO.socket = socketStub;

window = {io: sinon.stub().returns(socketStub)}; // eslint-disable-line
windowMock.inject();
window.io = sinon.stub().returns(socketStub);
});

afterEach(() => {
clock.restore();
windowMock.restore();
});

it('should expose an unique identifier', () => {
Expand Down Expand Up @@ -225,8 +225,6 @@ describe('SocketIO exposed methods', () => {

socketIO = new SocketIO('address');
socketIO.socket = socketStub;

window = {io: sinon.stub().returns(socketStub)}; // eslint-disable-line
});

it('should be able to listen to an event just once', () => {
Expand Down
71 changes: 63 additions & 8 deletions test/protocol/websocket.test.js
Original file line number Diff line number Diff line change
@@ -1,8 +1,10 @@
const
should = require('should'),
sinon = require('sinon'),
lolex = require('lolex'),
NodeWS = require('ws'),
WS = require('../../src/protocols/websocket');
WS = require('../../src/protocols/websocket'),
windowMock = require('../mocks/window.mock');

describe('WebSocket networking module', () => {
let
Expand All @@ -12,13 +14,13 @@ describe('WebSocket networking module', () => {
clientStub;

beforeEach(() => {
clock = sinon.useFakeTimers();
clock = lolex.install();
clientStub = {
send: sinon.stub(),
close: sinon.stub()
};

window = 'foobar'; // eslint-disable-line
windowMock.inject();
WebSocket = function (...args) { // eslint-disable-line
wsargs = args;
return clientStub;
Expand All @@ -32,9 +34,9 @@ describe('WebSocket networking module', () => {
});

afterEach(() => {
clock.restore();
clock.uninstall();
WebSocket = undefined; // eslint-disable-line
window = undefined; // eslint-disable-line
windowMock.restore();
});

it('should expose an unique identifier', () => {
Expand Down Expand Up @@ -156,6 +158,59 @@ describe('WebSocket networking module', () => {
return should(promise).be.rejectedWith('foobar');
});

it('should stop reconnecting if the browser goes offline', () => {
const cb = sinon.stub();

websocket.retrying = false;
websocket.addListener('networkError', cb);
should(websocket.listeners('networkError').length).be.eql(1);

websocket.connect();
websocket.connect = sinon.stub().rejects();
clientStub.onopen();
clientStub.onerror();

should(websocket.retrying).be.true();
should(cb).be.calledOnce();
should(websocket.connect).not.be.called();

window.navigator.onLine = false;

return clock.tickAsync(100)
.then(() => {
should(websocket.retrying).be.true();
should(cb).be.calledTwice();
should(websocket.connect).be.calledOnce();

should(window.addEventListener).calledWith(
'online',
sinon.match.func,
{ once: true });

return clock.tickAsync(100);
})
.then(() => {
// the important bit is there: cb hasn't been called since the last
// tick because the SDK does not try to connect if the browser is
// marked offline
should(cb).be.calledTwice();

should(websocket.retrying).be.true();
should(websocket.connect).be.calledOnce();

window.emit('online');
return clock.tickAsync(100);
})
.then(() => {
// And it started retrying to connect again now that the browser is
// "online"
should(cb).be.calledThrice();

should(websocket.retrying).be.true();
should(websocket.connect).be.calledTwice();
});
});

it('should call listeners on a "disconnect" event', () => {
const cb = sinon.stub();

Expand Down Expand Up @@ -371,7 +426,7 @@ describe('WebSocket networking module', () => {

it('should fallback to the ws module if there is no global WebSocket API', () => {
WebSocket = undefined; // eslint-disable-line
window = undefined; // eslint-disable-line
windowMock.restore();

const client = new WS('foobar');

Expand All @@ -391,7 +446,7 @@ describe('WebSocket networking module', () => {

it('should initialize pass allowed options to the ws ctor when using it', () => {
WebSocket = undefined; // eslint-disable-line
window = undefined; // eslint-disable-line
windowMock.restore();

let client = new WS('foobar');

Expand All @@ -413,7 +468,7 @@ describe('WebSocket networking module', () => {

it('should throw if invalid options are provided', () => {
WebSocket = undefined; // eslint-disable-line
window = undefined; // eslint-disable-line
windowMock.restore();

const invalidHeaders = ['foo', 'false', 'true', 123, []];

Expand Down