diff --git a/package.json b/package.json index f8e89ce..22970e5 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "janus-videoroom-client", - "version": "4.1.4", + "version": "4.1.5", "main": "src/janus.js", "scripts": { "test": "mocha -b --exit -R spec --full-trace test/*-spec.js" diff --git a/src/client/index.js b/src/client/index.js index 4d05bf6..83415d2 100644 --- a/src/client/index.js +++ b/src/client/index.js @@ -71,6 +71,7 @@ class Client { this.reconnect = _.isBoolean(options.reconnect)? options.reconnect : true; this.token = _.get(options, 'token', null); this.apiSecret = _.get(options, 'apiSecret', null); + this.handshakeTimeout = _.get(options, 'handshakeTimeout', undefined); } getVersion() { @@ -81,9 +82,20 @@ class Client { return _.isObject(this.webSocket) && this.webSocket.readyState === 1; } + isConnecting() { + return _.isObject(this.webSocket) && this.webSocket.readyState === 0; + } + + isClosing() { + return _.isObject(this.webSocket) && this.webSocket.readyState === 2; + } + connect() { if(this.webSocket === null) { - this.webSocket = new this.WebSocket(this.url, this.protocol); + var opts = this.handshakeTimeout ? + { handshakeTimeout: this.handshakeTimeout } : + undefined; + this.webSocket = new this.WebSocket(this.url, this.protocol, opts); this.webSocket.on(WebSocketEvent.open, ()=>{ this.open(); }); this.webSocket.on(WebSocketEvent.close, ()=>{ this.close(); }); this.webSocket.on(WebSocketEvent.message, (message)=>{ this.message(message); }); @@ -109,7 +121,12 @@ class Client { } close(options) { + // When the connection finishes closing the onClose event handler + // will re-trigger this clean up. + if (this.isClosing()) { return; } + let connect = _.get(options, 'connect', false); + let closeHandler = ()=>{ this.stopConnectionTimeout(); if(this.webSocket !== null) { @@ -127,11 +144,10 @@ class Client { this.connect(); } }; - if(_.isObject(this.webSocket) && this.isConnected()) { + + if(this.isConnected() || this.isConnecting()) { this.webSocket.removeAllListeners('close'); - this.webSocket.on('close', ()=>{ - closeHandler(); - }); + this.webSocket.on('close', () => closeHandler()); this.webSocket.close(); } else { closeHandler(); diff --git a/src/mock/janus-server.js b/src/mock/janus-server.js index e69f660..52e4226 100644 --- a/src/mock/janus-server.js +++ b/src/mock/janus-server.js @@ -12,8 +12,13 @@ const logger = require('debug-logger')('mock:janus-server'); class JanusServer { constructor(options) { + var triggerHandshakeTimeout = options.triggerHandshakeTimeout; + this.port = options.port || 9002; this.http = http.createServer(); + + if (triggerHandshakeTimeout) { return; } + this.ws = new WebSocketServer({ server: this.http }); diff --git a/test/client-spec.js b/test/client-spec.js index 0565760..5d469c2 100644 --- a/test/client-spec.js +++ b/test/client-spec.js @@ -15,22 +15,34 @@ var request = { var mockServerPort = config.janus.server.port; var mockServerUrl = config.janus.server.url; +var brokenMockServerPort = config.janus.server.port + 1; +var brokenMockServerUrl = 'http://localhost:' + brokenMockServerPort; + +var handshakeTimeoutError = 'Opening handshake has timed out'; +var earlyCloseError = 'WebSocket was closed before the connection was established'; + + describe('Client', function() { var janusServerMock; + var brokenJanusServerMock; before(function(done){ janusServerMock = new JanusServerMock({ port: mockServerPort }); - janusServerMock.init().then(()=>{ - done(); - }).catch(()=>{ - done(err); + brokenJanusServerMock = new JanusServerMock({ + port: brokenMockServerPort, + triggerHandshakeTimeout: true }); + Promise.all([ + janusServerMock.init(), + brokenJanusServerMock.init() + ]).then(() => done()).catch((e) => done(e)); }); after(function(){ janusServerMock.close(); + brokenJanusServerMock.close(); }); it('should connect to websocket endpoint', function(done) { @@ -103,6 +115,47 @@ describe('Client', function() { client.connect(); }); + [ + {when: 'after', connectionTimeout: 200, handshakeTimeout: 300, expectedError: earlyCloseError}, + {when: 'before', connectionTimeout: 300, handshakeTimeout: 200, expectedError: handshakeTimeoutError} + ].forEach((test) => { + it(`does not throw uncaught exception if handshake timeout occurs ${test.when} connection timeout`, function(done) { + + var client = new Client({ + url: brokenMockServerUrl, + reconnect: true, + connectionTimeout: test.connectionTimeout, + handshakeTimeout: test.handshakeTimeout, + }); + var caughtError = false + var isDone = false + + function onUncaughtException(err) { + if (isDone) { return; } + isDone = true; + process.off('uncaughtException', onUncaughtException); + var msg = err.message; + done(new Error('Client should not cause uncaught exception: ' + msg)); + } + + client.onError((err) => { + if (isDone) { return; } + var msg = err.message; + caughtError = msg === test.expectedError; + if (caughtError) { + isDone = true; + process.off('uncaughtException', onUncaughtException); + done(); + } + + }); + + process.on('uncaughtException', onUncaughtException) + + client.connect(); + }); + }); + it('should fail sending an object, because client is disconnected', function(done){ var client = new Client({ url: mockServerUrl