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 bf6b8488..8eea93b1 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 38b26d2e..c7414f99 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 4ce8f80e..77893689 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 cbe87ffa..8f43a257 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 a1c88b57..f6a50f87 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 5f9f20cb..9ca4e78e 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 713dbc99..ce5ef48b 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; }