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.
This commit is contained in:
parent
1321a90667
commit
fd6e2954f7
10 changed files with 170 additions and 45 deletions
BIN
images/error_red.png
Normal file
BIN
images/error_red.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 433 B |
BIN
images/refresh.png
Normal file
BIN
images/refresh.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 299 B |
|
@ -18,6 +18,7 @@
|
|||
<button class='back'></button>
|
||||
<span class='title-text'>New Message</span>
|
||||
</div>
|
||||
<div class='socket-status'></div>
|
||||
</div>
|
||||
<div class='notifications'>
|
||||
<div class='notification info'>
|
||||
|
|
|
@ -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 <encrypted IncomingPushMessageSignal>
|
||||
|
@ -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) {
|
||||
|
|
|
@ -74,6 +74,10 @@
|
|||
|
||||
getBackground: function() {
|
||||
return chrome.extension.getBackgroundPage();
|
||||
},
|
||||
|
||||
getViews: function() {
|
||||
return chrome.extension.getViews();
|
||||
}
|
||||
};
|
||||
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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));
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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; }
|
||||
|
||||
|
|
Loading…
Reference in a new issue