From fd6e2954f7a6cc66ff46a7eda8acbd7aa8173ae0 Mon Sep 17 00:00:00 2001 From: lilia Date: Mon, 9 Mar 2015 13:20:01 -0700 Subject: [PATCH] Curtail over-zealous websocket reconnects Closes #173 Previously, in the event of a failed websocket auth, we would attempt to reconnect once a second ad infinitum. This changeset ensures that we only reconnect automatically if the socket closed 'normally' as indicated by the code on the socket's CloseEvent. Otherwise, show a 'Websocket closed' error on the inbox view. Ideally we would show a more contextual error (ie, 'Unauthorized'), but unfortunately the actual server response code is not available to our code. It can be observed in the console output from the background page, but programmatically, we only receive the WebSocket CloseEvent codes listed here: https://developer.mozilla.org/en-US/docs/Web/API/CloseEvent#Status_codes The websocket error message is displayed by a normally-hidden but ever present socket status element. Clicking this element will immediately refresh the background page, which will try again to open the websocket connection. --- images/error_red.png | Bin 0 -> 433 bytes images/refresh.png | Bin 0 -> 299 bytes index.html | 1 + js/background.js | 20 ++++++++++++- js/chromium.js | 4 +++ js/libtextsecure.js | 56 ++++++++++++++++++++++--------------- js/views/inbox_view.js | 36 ++++++++++++++++++++++++ libtextsecure/websocket.js | 56 ++++++++++++++++++++++--------------- stylesheets/_index.scss | 25 +++++++++++++++++ stylesheets/manifest.css | 17 +++++++++++ 10 files changed, 170 insertions(+), 45 deletions(-) create mode 100644 images/error_red.png create mode 100644 images/refresh.png diff --git a/images/error_red.png b/images/error_red.png new file mode 100644 index 0000000000000000000000000000000000000000..069e12c282063964706527211fd536649d6b3c81 GIT binary patch literal 433 zcmeAS@N?(olHy`uVBq!ia0vp^LLkh+3?vf;>QaG}Lb6AYF9SoB8UsT^3j@P1pisjL z28L1t28LG&3=CE?7#PG0=Ijcz0ZJ4F_=LDVl~JZA`v3nw^Xv#dpzSIpL4Lsux>K%w zy*gQ&W7@yJli2s|nW3+94rbD_RR&VXH4>TcTqjec#&C z#Z}+GRR3P{Or2?w^Va(aE?Bch#q;&qZ_3uKZ}wj8N@C!vW1XZ@&7yTwh1oX|8F;qOza#(e#KYU0*YWIsakp4z_jQx+d(64^N?QD{ STf+%-2ZN`ppUXO@geCw5dY=RU literal 0 HcmV?d00001 diff --git a/images/refresh.png b/images/refresh.png new file mode 100644 index 0000000000000000000000000000000000000000..94ca8a1b4dda92911188b4383ee704999d888652 GIT binary patch literal 299 zcmeAS@N?(olHy`uVBq!ia0vp^LLkh+0wn(&ce?|m6p}rHd>I(3)EF2VS{N990fib~ zFff!FFfhDIU|_JC!N4G1FlSew4N!t9$=lt9;eUJonf*W>XMsm#F#`j)FbFd;%$g$s z6kP1-;uxZFe(NP$ze5EQ><|7ky%ptoXvV_+Re^`?lC+5r@8tAV8BJF{Tr75Yv>>(W z21l;ph6(q7*uDQy$MUf1P;7us;0l=&rYR@AqpT9oo~UCUNZqw_B;m%^TG=6VvUv<~}p3zV^ew_;csf pcJK7gp4y*Z(;tR@zZd@@zwc#m#nr9CDxlzC@O1TaS?83{1OTneaYX New Message +
diff --git a/js/background.js b/js/background.js index bf6b84885418..8eea93b11b0f 100644 --- a/js/background.js +++ b/js/background.js @@ -16,6 +16,7 @@ ;(function() { 'use strict'; + var socket; var conversations = new Whisper.ConversationCollection(); var messages = new Whisper.MessageCollection(); @@ -29,11 +30,20 @@ } extension.on('registration_done', init); + window.getSocketStatus = function() { + if (socket) { + return socket.getStatus(); + } else { + return WebSocket.CONNECTING; + } + }; + function init() { if (!textsecure.registration.isDone()) { return; } // initialize the socket and start listening for messages - var socket = textsecure.api.getMessageWebsocket(); + socket = textsecure.api.getMessageWebsocket(); + new WebSocketResource(socket, function(request) { // TODO: handle different types of requests. for now we only expect // PUT /messages @@ -58,6 +68,14 @@ }); extension.browserAction(window.openInbox); + + // refresh views + var views = extension.windows.getViews(); + for (var i = 0; i < views.length; ++i) { + if (views[i] !== window) { + views[i].location.reload(); + } + } } function onMessageReceived(pushMessage) { diff --git a/js/chromium.js b/js/chromium.js index 38b26d2efafd..c7414f99e779 100644 --- a/js/chromium.js +++ b/js/chromium.js @@ -74,6 +74,10 @@ getBackground: function() { return chrome.extension.getBackgroundPage(); + }, + + getViews: function() { + return chrome.extension.getViews(); } }; diff --git a/js/libtextsecure.js b/js/libtextsecure.js index 4ce8f80eed4a..77893689aa22 100644 --- a/js/libtextsecure.js +++ b/js/libtextsecure.js @@ -15905,14 +15905,16 @@ window.axolotl.sessions = { */ window.textsecure.websocket = function (url) { - var socketWrapper = { - onmessage : function() {}, - ondisconnect : function() {}, - }; - var socket; var keepAliveTimer; var reconnectSemaphore = 0; var reconnectTimeout = 1000; + var socket; + var socketWrapper = { + onmessage : function() {}, + onclose : function() {}, + onerror : function() {}, + getStatus : function() { return socket.readyState; } + }; function resetKeepAliveTimer() { clearTimeout(keepAliveTimer); @@ -15928,10 +15930,27 @@ window.axolotl.sessions = { }, 15000); }; - function reconnect(e) { - reconnectSemaphore--; - setTimeout(connect, reconnectTimeout); - socketWrapper.ondisconnect(e); + function onclose(e) { + if (e.code === 1000) { // CLOSE_NORMAL + reconnectSemaphore--; + setTimeout(connect, reconnectTimeout); + } else { + console.log('websocket closed', e.code); + } + socketWrapper.onclose(e); + }; + + function onerror(e) { + socketWrapper.onerror(e); + }; + + function onmessage(response) { + socketWrapper.onmessage(response); + resetKeepAliveTimer(); + }; + + function send(msg) { + socket.send(msg); }; function connect() { @@ -15941,19 +15960,12 @@ window.axolotl.sessions = { if (socket) { socket.close(); } socket = new WebSocket(url); - socket.onerror = reconnect; - socket.onclose = reconnect; - socket.onopen = resetKeepAliveTimer; - - socket.onmessage = function(response) { - socketWrapper.onmessage(response); - resetKeepAliveTimer(); - }; - - socketWrapper.send = function(msg) { - socket.send(msg); - } - } + socket.onopen = resetKeepAliveTimer; + socket.onerror = onerror + socket.onclose = onclose; + socket.onmessage = onmessage; + socketWrapper.send = send; + }; connect(); return socketWrapper; diff --git a/js/views/inbox_view.js b/js/views/inbox_view.js index cbe87ffa6b9b..8f43a257d0e2 100644 --- a/js/views/inbox_view.js +++ b/js/views/inbox_view.js @@ -19,6 +19,40 @@ window.Whisper = window.Whisper || {}; var bg = extension.windows.getBackground(); + var SocketView = Whisper.View.extend({ + className: 'status', + initialize: function() { + setInterval(function() { + var className, message = ''; + switch(bg.getSocketStatus && bg.getSocketStatus()) { + case WebSocket.CONNECTING: + className = 'connecting'; + break; + case WebSocket.OPEN: + className = 'open'; + break; + case WebSocket.CLOSING: + className = 'closing'; + break; + case WebSocket.CLOSED: + className = 'closed'; + message = 'Websocket closed'; + break; + } + if (!this.$el.hasClass(className)) { + this.$el.attr('class', className); + this.$el.text(message); + } + }.bind(this), 1000); + }, + events: { + 'click': 'reloadBackgroundPage' + }, + reloadBackgroundPage: function() { + bg.location.reload(); + } + }); + Whisper.InboxView = Backbone.View.extend({ initialize: function () { this.$gutter = $('#gutter'); @@ -34,6 +68,8 @@ collection : bg.inbox }).render(); + new SocketView().render().$el.appendTo(this.$el.find('.socket-status')); + window.addEventListener('beforeunload', function () { this.inbox.stopListening(); }.bind(this)); diff --git a/libtextsecure/websocket.js b/libtextsecure/websocket.js index a1c88b578d8b..f6a50f87304b 100644 --- a/libtextsecure/websocket.js +++ b/libtextsecure/websocket.js @@ -25,14 +25,16 @@ */ window.textsecure.websocket = function (url) { - var socketWrapper = { - onmessage : function() {}, - ondisconnect : function() {}, - }; - var socket; var keepAliveTimer; var reconnectSemaphore = 0; var reconnectTimeout = 1000; + var socket; + var socketWrapper = { + onmessage : function() {}, + onclose : function() {}, + onerror : function() {}, + getStatus : function() { return socket.readyState; } + }; function resetKeepAliveTimer() { clearTimeout(keepAliveTimer); @@ -48,10 +50,27 @@ }, 15000); }; - function reconnect(e) { - reconnectSemaphore--; - setTimeout(connect, reconnectTimeout); - socketWrapper.ondisconnect(e); + function onclose(e) { + if (e.code === 1000) { // CLOSE_NORMAL + reconnectSemaphore--; + setTimeout(connect, reconnectTimeout); + } else { + console.log('websocket closed', e.code); + } + socketWrapper.onclose(e); + }; + + function onerror(e) { + socketWrapper.onerror(e); + }; + + function onmessage(response) { + socketWrapper.onmessage(response); + resetKeepAliveTimer(); + }; + + function send(msg) { + socket.send(msg); }; function connect() { @@ -61,19 +80,12 @@ if (socket) { socket.close(); } socket = new WebSocket(url); - socket.onerror = reconnect; - socket.onclose = reconnect; - socket.onopen = resetKeepAliveTimer; - - socket.onmessage = function(response) { - socketWrapper.onmessage(response); - resetKeepAliveTimer(); - }; - - socketWrapper.send = function(msg) { - socket.send(msg); - } - } + socket.onopen = resetKeepAliveTimer; + socket.onerror = onerror + socket.onclose = onclose; + socket.onmessage = onmessage; + socketWrapper.send = send; + }; connect(); return socketWrapper; diff --git a/stylesheets/_index.scss b/stylesheets/_index.scss index 5f9f20cb3e78..9ca4e78ed2cc 100644 --- a/stylesheets/_index.scss +++ b/stylesheets/_index.scss @@ -13,6 +13,31 @@ // TODO: spinner } +.socket-status { + float: left; + padding: 6px; + + * { + cursor: pointer; + padding-left: 20px; + border-radius: $header-height; + min-height: 20px; + + &:hover { + background: $blue url('/images/refresh.png') center; + } + } + .connecting .icon { + background-color: $blue; + } + .closing { + background-color: $blue_l; + } + .closed { + background: url('/images/error_red.png') no-repeat left center; + } +} + .contact { .number, .checkbox { display: none; diff --git a/stylesheets/manifest.css b/stylesheets/manifest.css index 713dbc995a8c..ce5ef48bda19 100644 --- a/stylesheets/manifest.css +++ b/stylesheets/manifest.css @@ -143,6 +143,23 @@ button.back { #contacts { overflow: auto; } +.socket-status { + float: left; + padding: 6px; } + .socket-status * { + cursor: pointer; + padding-left: 20px; + border-radius: 36px; + min-height: 20px; } + .socket-status *:hover { + background: #2a92e7 url("/images/refresh.png") center; } + .socket-status .connecting .icon { + background-color: #2a92e7; } + .socket-status .closing { + background-color: #a2d2f4; } + .socket-status .closed { + background: url("/images/error_red.png") no-repeat left center; } + .contact .number, .contact .checkbox { display: none; }