From cb8594373e9f97edb8237f48071ab0620c21f72b Mon Sep 17 00:00:00 2001 From: pabloFuente Date: Mon, 14 Jan 2019 10:18:04 +0100 Subject: [PATCH] openvidu-browser: reconnection fix for unexpected ws close (HttpSession expiring) --- openvidu-browser/src/OpenVidu/Connection.ts | 5 ++ openvidu-browser/src/OpenVidu/OpenVidu.ts | 21 ++++-- openvidu-browser/src/OpenVidu/Session.ts | 21 ++---- .../kurento-jsonrpc/clients/jsonrpcclient.js | 66 ++++++++++++------- .../transports/webSocketWithReconnection.js | 34 +++++----- .../KurentoUtils/kurento-jsonrpc/index.js | 2 +- 6 files changed, 83 insertions(+), 66 deletions(-) diff --git a/openvidu-browser/src/OpenVidu/Connection.ts b/openvidu-browser/src/OpenVidu/Connection.ts index 456eabdb..83dc9f2b 100644 --- a/openvidu-browser/src/OpenVidu/Connection.ts +++ b/openvidu-browser/src/OpenVidu/Connection.ts @@ -59,6 +59,11 @@ export class Connection { */ disposed = false; + /** + * @hidden + */ + rpcSessionId: string; + /** * @hidden */ diff --git a/openvidu-browser/src/OpenVidu/OpenVidu.ts b/openvidu-browser/src/OpenVidu/OpenVidu.ts index 06dc353d..73678f1f 100644 --- a/openvidu-browser/src/OpenVidu/OpenVidu.ts +++ b/openvidu-browser/src/OpenVidu/OpenVidu.ts @@ -91,7 +91,7 @@ export class OpenVidu { const getNewVideoDimensions = (): Promise<{ newWidth: number, newHeight: number }> => { return new Promise((resolve, reject) => { if (platform['isIonicIos']) { - // iOS Ionic. Limitation: must get new dimensions from an existing video element already inserted into DOM + // iOS Ionic. Limitation: must get new dimensions from an existing video element already inserted into DOM resolve({ newWidth: publisher.stream.streamManager.videos[0].video.videoWidth, newHeight: publisher.stream.streamManager.videos[0].video.videoHeight @@ -636,7 +636,7 @@ export class OpenVidu { * @hidden */ closeWs(): void { - this.jsonRpcClient.close(); + this.jsonRpcClient.close(4102, "Connection closed by client"); } /** @@ -678,7 +678,7 @@ export class OpenVidu { private disconnectCallback(): void { console.warn('Websocket connection lost'); if (this.isRoomAvailable()) { - this.session.onLostConnection(); + this.session.onLostConnection('networkDisconnect'); } else { alert('Connection error. Please reload page.'); } @@ -686,9 +686,7 @@ export class OpenVidu { private reconnectingCallback(): void { console.warn('Websocket connection lost (reconnecting)'); - if (this.isRoomAvailable()) { - this.session.onLostConnection(); - } else { + if (!this.isRoomAvailable()) { alert('Connection error. Please reload page.'); } } @@ -696,7 +694,16 @@ export class OpenVidu { private reconnectedCallback(): void { console.warn('Websocket reconnected'); if (this.isRoomAvailable()) { - this.session.onRecoveredConnection(); + this.sendRequest('connect', { sessionId: this.session.connection.rpcSessionId }, (error, response) => { + if (!!error) { + console.error(error); + this.session.onLostConnection("networkDisconnect"); + this.jsonRpcClient.close(4101, "Reconnection fault"); + } else { + this.jsonRpcClient.resetPing(); + this.session.onRecoveredConnection(); + } + }); } else { alert('Connection error. Please reload page.'); } diff --git a/openvidu-browser/src/OpenVidu/Session.ts b/openvidu-browser/src/OpenVidu/Session.ts index 7c83bd67..92e1c8cb 100644 --- a/openvidu-browser/src/OpenVidu/Session.ts +++ b/openvidu-browser/src/OpenVidu/Session.ts @@ -899,22 +899,10 @@ export class Session implements EventDispatcher { /** * @hidden */ - onLostConnection(): void { - - /*if (!this.connection) { - - console.warn('Not connected to session: if you are not debugging, this is probably a certificate error'); - - const url = 'https://' + this.openvidu.getWsUri().split('wss://')[1].split('/openvidu')[0]; - if (window.confirm('If you are not debugging, this is probably a certificate error at \"' + url + '\"\n\nClick OK to navigate and accept it')) { - location.assign(url + '/accept-certificate'); - } - return; - }*/ - - console.warn('Lost connection in Session ' + this.sessionId); + onLostConnection(reason: string): void { + console.warn('Lost connection in session ' + this.sessionId + ' waiting for reconnect'); if (!!this.sessionId && !this.connection.disposed) { - this.leave(true, 'networkDisconnect'); + this.leave(true, reason); } } @@ -923,7 +911,7 @@ export class Session implements EventDispatcher { */ onRecoveredConnection(): void { console.warn('Recovered connection in Session ' + this.sessionId); - this.ee.emitEvent('connectionRecovered', []); + // this.ee.emitEvent('connectionRecovered', []); } /** @@ -1048,6 +1036,7 @@ export class Session implements EventDispatcher { this.connection.connectionId = response.id; this.connection.creationTime = response.createdAt; this.connection.data = response.metadata; + this.connection.rpcSessionId = response.sessionId; // Initialize remote Connections with value returned by openvidu-server const events = { diff --git a/openvidu-browser/src/OpenViduInternal/KurentoUtils/kurento-jsonrpc/clients/jsonrpcclient.js b/openvidu-browser/src/OpenViduInternal/KurentoUtils/kurento-jsonrpc/clients/jsonrpcclient.js index e1ea6457..40f89275 100644 --- a/openvidu-browser/src/OpenViduInternal/KurentoUtils/kurento-jsonrpc/clients/jsonrpcclient.js +++ b/openvidu-browser/src/OpenViduInternal/KurentoUtils/kurento-jsonrpc/clients/jsonrpcclient.js @@ -18,7 +18,7 @@ var RpcBuilder = require('../'); var WebSocketWithReconnection = require('./transports/webSocketWithReconnection'); -Date.now = Date.now || function() { +Date.now = Date.now || function () { return +new Date; }; @@ -39,7 +39,7 @@ var Logger = console; * uri : URI to conntect to, * useSockJS : true (use SockJS) / false (use WebSocket) by default, * onconnected : callback method to invoke when connection is successful, - * ondisconnect : callback method to invoke when the connection is lost, + * ondisconnect : callback method to invoke when the connection is lost (max retries for reconnecting reached), * onreconnecting : callback method to invoke when the client is reconnecting, * onreconnected : callback method to invoke when the client successfully reconnects, * onerror : callback method to invoke when there is an error @@ -71,24 +71,26 @@ function JsonRpcClient(configuration) { var onconnected = wsConfig.onconnected; var onerror = wsConfig.onerror; - configuration.rpc.pull = function(params, request) { + configuration.rpc.pull = function (params, request) { request.reply(null, "push"); } - wsConfig.onreconnecting = function() { + wsConfig.onreconnecting = function () { Logger.debug("--------- ONRECONNECTING -----------"); if (status === RECONNECTING) { Logger.error("Websocket already in RECONNECTING state when receiving a new ONRECONNECTING message. Ignoring it"); return; } + stopPing(); + status = RECONNECTING; if (onreconnecting) { onreconnecting(); } } - wsConfig.onreconnected = function() { + wsConfig.onreconnected = function () { Logger.debug("--------- ONRECONNECTED -----------"); if (status === CONNECTED) { Logger.error("Websocket already in CONNECTED state when receiving a new ONRECONNECTED message. Ignoring it"); @@ -96,16 +98,14 @@ function JsonRpcClient(configuration) { } status = CONNECTED; - enabledPings = true; updateNotReconnectIfLessThan(); - usePing(); if (onreconnected) { onreconnected(); } } - wsConfig.onconnected = function() { + wsConfig.onconnected = function () { Logger.debug("--------- ONCONNECTED -----------"); if (status === CONNECTED) { Logger.error("Websocket already in CONNECTED state when receiving a new ONCONNECTED message. Ignoring it"); @@ -121,11 +121,13 @@ function JsonRpcClient(configuration) { } } - wsConfig.onerror = function(error) { + wsConfig.onerror = function (error) { Logger.debug("--------- ONERROR -----------"); status = DISCONNECTED; + stopPing(); + if (onerror) { onerror(error); } @@ -141,7 +143,7 @@ function JsonRpcClient(configuration) { }; var rpc = new RpcBuilder(RpcBuilder.packers.JsonRPC, rpcBuilderOptions, ws, - function(request) { + function (request) { Logger.debug('Received request: ' + JSON.stringify(request)); @@ -159,14 +161,14 @@ function JsonRpcClient(configuration) { } }); - this.send = function(method, params, callback) { + this.send = function (method, params, callback) { if (method !== 'ping') { Logger.debug('Request: method:' + method + " params:" + JSON.stringify(params)); } var requestTime = Date.now(); - rpc.encode(method, params, function(error, result) { + rpc.encode(method, params, function (error, result) { if (error) { try { Logger.error("ERROR:" + error.message + " in Request: method:" + @@ -203,8 +205,8 @@ function JsonRpcClient(configuration) { } pingNextNum++; - self.send('ping', params, (function(pingNum) { - return function(error, result) { + self.send('ping', params, (function (pingNum) { + return function (error, result) { if (error) { Logger.debug("Error in ping request #" + pingNum + " (" + error.message + ")"); @@ -224,9 +226,9 @@ function JsonRpcClient(configuration) { } /* - * If configuration.hearbeat has any value, the ping-pong will work with the interval - * of configuration.hearbeat - */ + * If configuration.hearbeat has any value, the ping-pong will work with the interval + * of configuration.hearbeat + */ function usePing() { if (!pingPongStarted) { Logger.debug("Starting ping (if configured)") @@ -239,8 +241,16 @@ function JsonRpcClient(configuration) { } } - this.close = function() { - Logger.debug("Closing jsonRpcClient explicitly by client"); + function stopPing() { + clearInterval(pingInterval); + pingPongStarted = false; + enabledPings = false; + pingNextNum = -1; + rpc.cancel(); + } + + this.close = function (code, reason) { + Logger.debug("Closing with code: " + code + " because: " + reason); if (pingInterval != undefined) { Logger.debug("Clearing ping interval"); @@ -251,26 +261,32 @@ function JsonRpcClient(configuration) { if (configuration.sendCloseMessage) { Logger.debug("Sending close message") - this.send('closeSession', null, function(error, result) { + this.send('closeSession', null, function (error, result) { if (error) { Logger.error("Error sending close message: " + JSON.stringify(error)); } - ws.close(); + ws.close(code, reason); }); } else { - ws.close(); + ws.close(code, reason); } } // This method is only for testing - this.forceClose = function(millis) { + this.forceClose = function (millis) { ws.forceClose(millis); } - this.reconnect = function() { + this.reconnect = function () { ws.reconnectWs(); } + + this.resetPing = function () { + enabledPings = true; + pingNextNum = 0; + usePing(); + } } -module.exports = JsonRpcClient; +module.exports = JsonRpcClient; \ No newline at end of file diff --git a/openvidu-browser/src/OpenViduInternal/KurentoUtils/kurento-jsonrpc/clients/transports/webSocketWithReconnection.js b/openvidu-browser/src/OpenViduInternal/KurentoUtils/kurento-jsonrpc/clients/transports/webSocketWithReconnection.js index 7dc87096..c0765504 100644 --- a/openvidu-browser/src/OpenViduInternal/KurentoUtils/kurento-jsonrpc/clients/transports/webSocketWithReconnection.js +++ b/openvidu-browser/src/OpenViduInternal/KurentoUtils/kurento-jsonrpc/clients/transports/webSocketWithReconnection.js @@ -48,7 +48,7 @@ config = { uri : wsUri, useSockJS : true (use SockJS) / false (use WebSocket) by default, onconnected : callback method to invoke when connection is successful, - ondisconnect : callback method to invoke when the connection is lost, + ondisconnect : callback method to invoke when the connection is lost (max retries for reconnecting reached), onreconnecting : callback method to invoke when the client is reconnecting, onreconnected : callback method to invoke when the client successfully reconnects, }; @@ -71,14 +71,14 @@ function WebSocketWithReconnection(config) { ws = new WebSocket(wsUri); } - ws.onopen = function() { + ws.onopen = function () { logConnected(ws, wsUri); if (config.onconnected) { config.onconnected(); } }; - ws.onerror = function(error) { + ws.onerror = function (error) { Logger.error("Could not connect to " + wsUri + " (invoking onerror if defined)", error); if (config.onerror) { config.onerror(error); @@ -93,7 +93,7 @@ function WebSocketWithReconnection(config) { } } - var reconnectionOnClose = function() { + var reconnectionOnClose = function () { if (ws.readyState === CLOSED) { if (closing) { Logger.debug("Connection closed by user"); @@ -129,11 +129,11 @@ function WebSocketWithReconnection(config) { } else { if (config.newWsUriOnReconnection) { - config.newWsUriOnReconnection(function(error, newWsUri) { + config.newWsUriOnReconnection(function (error, newWsUri) { if (error) { Logger.debug(error); - setTimeout(function() { + setTimeout(function () { reconnectToSameUri(maxRetries, numRetries + 1); }, RETRY_TIME_MS); } else { @@ -161,7 +161,7 @@ function WebSocketWithReconnection(config) { newWs = new WebSocket(wsUri); } - newWs.onopen = function() { + newWs.onopen = function () { Logger.debug("Reconnected after " + numRetries + " attempts..."); logConnected(newWs, wsUri); reconnecting = false; @@ -173,7 +173,7 @@ function WebSocketWithReconnection(config) { newWs.onclose = reconnectionOnClose; }; - var onErrorOrClose = function(error) { + var onErrorOrClose = function (error) { Logger.warn("Reconnection error: ", error); if (numRetries === maxRetries) { @@ -181,7 +181,7 @@ function WebSocketWithReconnection(config) { config.ondisconnect(); } } else { - setTimeout(function() { + setTimeout(function () { reconnectToSameUri(maxRetries, numRetries + 1); }, RETRY_TIME_MS); } @@ -192,14 +192,14 @@ function WebSocketWithReconnection(config) { ws = newWs; } - this.close = function() { + this.close = function () { closing = true; ws.close(); }; // This method is only for testing - this.forceClose = function(millis) { + this.forceClose = function (millis) { Logger.debug("Testing: Force WebSocket close"); if (millis) { @@ -209,7 +209,7 @@ function WebSocketWithReconnection(config) { forcingDisconnection = true; - setTimeout(function() { + setTimeout(function () { Logger.debug("Testing: Recover good wsUri " + goodWsUri); wsUri = goodWsUri; @@ -221,17 +221,17 @@ function WebSocketWithReconnection(config) { ws.close(); }; - this.reconnectWs = function() { + this.reconnectWs = function () { Logger.debug("reconnectWs"); reconnectToSameUri(MAX_RETRIES, 1); }; - this.send = function(message) { + this.send = function (message) { ws.send(message); }; - this.addEventListener = function(type, callback) { - registerMessageHandler = function() { + this.addEventListener = function (type, callback) { + registerMessageHandler = function () { ws.addEventListener(type, callback); }; @@ -239,4 +239,4 @@ function WebSocketWithReconnection(config) { }; } -module.exports = WebSocketWithReconnection; +module.exports = WebSocketWithReconnection; \ No newline at end of file diff --git a/openvidu-browser/src/OpenViduInternal/KurentoUtils/kurento-jsonrpc/index.js b/openvidu-browser/src/OpenViduInternal/KurentoUtils/kurento-jsonrpc/index.js index 343aa39f..408dcbc4 100644 --- a/openvidu-browser/src/OpenViduInternal/KurentoUtils/kurento-jsonrpc/index.js +++ b/openvidu-browser/src/OpenViduInternal/KurentoUtils/kurento-jsonrpc/index.js @@ -519,7 +519,7 @@ function RpcBuilder(packer, options, transport, onRequest) // Prevent to receive new messages var transport = this.getTransport(); if(transport && transport.close) - transport.close(); + transport.close(4003, "Cancel request"); // Request & processed responses this.cancel();