Move/refactor keepalive logic and add disconnect timer
We now disconnect ourselves if we don't get the server's response to a
keepalive request within 30s. This way we will eventually disconnect if
the network goes away but the socket is not closed.*
* See code.google.com/p/chromium/issues/detail?id=197841 and
https://stackoverflow.com/questions/11755605/chrome-websocket-connection-not-closed-when-browser-closed
We will then try to reconnect once a minute (See 8a10c96
);
Keepalives belong at this level anyway, since the format is defined by
both the websocket resource protocol and our specific server url
structure.
// FREEBIE
This commit is contained in:
parent
8a10c96ab4
commit
7d3d634a2d
6 changed files with 62 additions and 65 deletions
|
@ -38398,7 +38398,6 @@ axolotlInternal.RecipientRecord = function() {
|
||||||
|
|
||||||
TextSecureWebSocket = function (url) {
|
TextSecureWebSocket = function (url) {
|
||||||
'use strict';
|
'use strict';
|
||||||
var keepAliveTimer;
|
|
||||||
var reconnectSemaphore = 0;
|
var reconnectSemaphore = 0;
|
||||||
var reconnectTimeout = 1000;
|
var reconnectTimeout = 1000;
|
||||||
var socket;
|
var socket;
|
||||||
|
@ -38408,27 +38407,10 @@ TextSecureWebSocket = function (url) {
|
||||||
onclose : function() {},
|
onclose : function() {},
|
||||||
onerror : function() {},
|
onerror : function() {},
|
||||||
getStatus : function() { return socket.readyState; },
|
getStatus : function() { return socket.readyState; },
|
||||||
close : function() { calledClose = true; }
|
close : function() { calledClose = true; socket.close(); }
|
||||||
};
|
};
|
||||||
var error;
|
var error;
|
||||||
|
|
||||||
function resetKeepAliveTimer() {
|
|
||||||
clearTimeout(keepAliveTimer);
|
|
||||||
if (calledClose) { return; }
|
|
||||||
keepAliveTimer = setTimeout(function() {
|
|
||||||
if (socket.readyState === WebSocket.OPEN) {
|
|
||||||
socket.send(
|
|
||||||
new textsecure.protobuf.WebSocketMessage({
|
|
||||||
type: textsecure.protobuf.WebSocketMessage.Type.REQUEST,
|
|
||||||
request: { verb: 'GET', path: '/v1/keepalive' }
|
|
||||||
}).encode().toArrayBuffer()
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
resetKeepAliveTimer();
|
|
||||||
}, 55000);
|
|
||||||
};
|
|
||||||
|
|
||||||
function onclose(e) {
|
function onclose(e) {
|
||||||
if (!error && !calledClose) {
|
if (!error && !calledClose) {
|
||||||
reconnectSemaphore--;
|
reconnectSemaphore--;
|
||||||
|
@ -38448,22 +38430,18 @@ TextSecureWebSocket = function (url) {
|
||||||
|
|
||||||
function onmessage(response) {
|
function onmessage(response) {
|
||||||
socketWrapper.onmessage(response);
|
socketWrapper.onmessage(response);
|
||||||
resetKeepAliveTimer();
|
|
||||||
};
|
};
|
||||||
|
|
||||||
function send(msg) {
|
function send(msg) {
|
||||||
resetKeepAliveTimer();
|
|
||||||
socket.send(msg);
|
socket.send(msg);
|
||||||
};
|
};
|
||||||
|
|
||||||
function connect() {
|
function connect() {
|
||||||
clearTimeout(keepAliveTimer);
|
|
||||||
if (++reconnectSemaphore <= 0) { return; }
|
if (++reconnectSemaphore <= 0) { return; }
|
||||||
|
|
||||||
if (socket) { socket.close(); }
|
if (socket) { socket.close(); }
|
||||||
socket = new WebSocket(url);
|
socket = new WebSocket(url);
|
||||||
|
|
||||||
socket.onopen = resetKeepAliveTimer;
|
|
||||||
socket.onerror = onerror
|
socket.onerror = onerror
|
||||||
socket.onclose = onclose;
|
socket.onclose = onclose;
|
||||||
socket.onmessage = onmessage;
|
socket.onmessage = onmessage;
|
||||||
|
@ -38527,6 +38505,10 @@ TextSecureWebSocket = function (url) {
|
||||||
window.crypto.getRandomValues(bits);
|
window.crypto.getRandomValues(bits);
|
||||||
this.id = dcodeIO.Long.fromBits(bits[0], bits[1], true);
|
this.id = dcodeIO.Long.fromBits(bits[0], bits[1], true);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (this.body === undefined) {
|
||||||
|
this.body = null;
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
var IncomingWebSocketRequest = function(options) {
|
var IncomingWebSocketRequest = function(options) {
|
||||||
|
@ -39507,9 +39489,26 @@ function generateKeys(count, progressCallback) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
new WebSocketResource(this.socket, this.handleRequest.bind(this));
|
this.wsr = new WebSocketResource(this.socket, this.handleRequest.bind(this));
|
||||||
|
this.resetKeepAliveTimer();
|
||||||
|
|
||||||
|
},
|
||||||
|
resetKeepAliveTimer: function() {
|
||||||
|
clearTimeout(this.keepAliveTimer);
|
||||||
|
clearTimeout(this.disconnectTimer);
|
||||||
|
this.keepAliveTimer = setTimeout(function() {
|
||||||
|
if (this.getStatus() === WebSocket.OPEN) {
|
||||||
|
this.wsr.sendRequest({
|
||||||
|
verb: 'GET',
|
||||||
|
path: '/v1/keepalive',
|
||||||
|
success: this.resetKeepAliveTimer.bind(this)
|
||||||
|
});
|
||||||
|
}
|
||||||
|
this.disconnectTimer = setTimeout(this.socket.close, 30000);
|
||||||
|
}.bind(this), 55000);
|
||||||
},
|
},
|
||||||
handleRequest: function(request) {
|
handleRequest: function(request) {
|
||||||
|
this.resetKeepAliveTimer();
|
||||||
// TODO: handle different types of requests. for now we only expect
|
// TODO: handle different types of requests. for now we only expect
|
||||||
// PUT /messages <encrypted IncomingPushMessageSignal>
|
// PUT /messages <encrypted IncomingPushMessageSignal>
|
||||||
textsecure.crypto.decryptWebsocketMessage(request.body).then(function(plaintext) {
|
textsecure.crypto.decryptWebsocketMessage(request.body).then(function(plaintext) {
|
||||||
|
|
|
@ -44,9 +44,26 @@
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
new WebSocketResource(this.socket, this.handleRequest.bind(this));
|
this.wsr = new WebSocketResource(this.socket, this.handleRequest.bind(this));
|
||||||
|
this.resetKeepAliveTimer();
|
||||||
|
|
||||||
|
},
|
||||||
|
resetKeepAliveTimer: function() {
|
||||||
|
clearTimeout(this.keepAliveTimer);
|
||||||
|
clearTimeout(this.disconnectTimer);
|
||||||
|
this.keepAliveTimer = setTimeout(function() {
|
||||||
|
if (this.getStatus() === WebSocket.OPEN) {
|
||||||
|
this.wsr.sendRequest({
|
||||||
|
verb: 'GET',
|
||||||
|
path: '/v1/keepalive',
|
||||||
|
success: this.resetKeepAliveTimer.bind(this)
|
||||||
|
});
|
||||||
|
}
|
||||||
|
this.disconnectTimer = setTimeout(this.socket.close, 30000);
|
||||||
|
}.bind(this), 55000);
|
||||||
},
|
},
|
||||||
handleRequest: function(request) {
|
handleRequest: function(request) {
|
||||||
|
this.resetKeepAliveTimer();
|
||||||
// TODO: handle different types of requests. for now we only expect
|
// TODO: handle different types of requests. for now we only expect
|
||||||
// PUT /messages <encrypted IncomingPushMessageSignal>
|
// PUT /messages <encrypted IncomingPushMessageSignal>
|
||||||
textsecure.crypto.decryptWebsocketMessage(request.body).then(function(plaintext) {
|
textsecure.crypto.decryptWebsocketMessage(request.body).then(function(plaintext) {
|
||||||
|
|
|
@ -45,6 +45,21 @@ describe('MessageReceiver', function() {
|
||||||
assert.strictEqual(signal.message.body, 'hello');
|
assert.strictEqual(signal.message.body, 'hello');
|
||||||
});
|
});
|
||||||
var messageReceiver = new textsecure.MessageReceiver(window);
|
var messageReceiver = new textsecure.MessageReceiver(window);
|
||||||
messageReceiver.connect();
|
});
|
||||||
|
|
||||||
|
it('sends a keepalive once a minute', function(done) {
|
||||||
|
this.timeout(60000);
|
||||||
|
var mockServer = new MockServer('ws://localhost:8081');
|
||||||
|
mockServer.on('connection', function(server) {
|
||||||
|
server.on('message', function(data) {
|
||||||
|
var message = textsecure.protobuf.WebSocketMessage.decode(data);
|
||||||
|
assert.strictEqual(message.type, textsecure.protobuf.WebSocketMessage.Type.REQUEST);
|
||||||
|
assert.strictEqual(message.request.verb, 'GET');
|
||||||
|
assert.strictEqual(message.request.path, '/v1/keepalive');
|
||||||
|
server.close();
|
||||||
|
done();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
var messageReceiver = new textsecure.MessageReceiver(window);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
|
@ -75,20 +75,4 @@ describe('TextSecureWebSocket', function() {
|
||||||
mockServer.close();
|
mockServer.close();
|
||||||
});
|
});
|
||||||
|
|
||||||
it('sends a keepalive once a minute', function(done) {
|
|
||||||
this.timeout(60000);
|
|
||||||
var mockServer = new MockServer('ws://localhost:8081');
|
|
||||||
mockServer.on('connection', function(server) {
|
|
||||||
server.on('message', function(data) {
|
|
||||||
var message = textsecure.protobuf.WebSocketMessage.decode(data);
|
|
||||||
assert.strictEqual(message.type, textsecure.protobuf.WebSocketMessage.Type.REQUEST);
|
|
||||||
assert.strictEqual(message.request.verb, 'GET');
|
|
||||||
assert.strictEqual(message.request.path, '/v1/keepalive');
|
|
||||||
socket.close();
|
|
||||||
server.close();
|
|
||||||
done();
|
|
||||||
});
|
|
||||||
});
|
|
||||||
var socket = new TextSecureWebSocket('ws://localhost:8081');
|
|
||||||
});
|
|
||||||
});
|
});
|
||||||
|
|
|
@ -51,6 +51,10 @@
|
||||||
window.crypto.getRandomValues(bits);
|
window.crypto.getRandomValues(bits);
|
||||||
this.id = dcodeIO.Long.fromBits(bits[0], bits[1], true);
|
this.id = dcodeIO.Long.fromBits(bits[0], bits[1], true);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (this.body === undefined) {
|
||||||
|
this.body = null;
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
var IncomingWebSocketRequest = function(options) {
|
var IncomingWebSocketRequest = function(options) {
|
||||||
|
|
|
@ -24,7 +24,6 @@
|
||||||
|
|
||||||
TextSecureWebSocket = function (url) {
|
TextSecureWebSocket = function (url) {
|
||||||
'use strict';
|
'use strict';
|
||||||
var keepAliveTimer;
|
|
||||||
var reconnectSemaphore = 0;
|
var reconnectSemaphore = 0;
|
||||||
var reconnectTimeout = 1000;
|
var reconnectTimeout = 1000;
|
||||||
var socket;
|
var socket;
|
||||||
|
@ -34,27 +33,10 @@ TextSecureWebSocket = function (url) {
|
||||||
onclose : function() {},
|
onclose : function() {},
|
||||||
onerror : function() {},
|
onerror : function() {},
|
||||||
getStatus : function() { return socket.readyState; },
|
getStatus : function() { return socket.readyState; },
|
||||||
close : function() { calledClose = true; }
|
close : function() { calledClose = true; socket.close(); }
|
||||||
};
|
};
|
||||||
var error;
|
var error;
|
||||||
|
|
||||||
function resetKeepAliveTimer() {
|
|
||||||
clearTimeout(keepAliveTimer);
|
|
||||||
if (calledClose) { return; }
|
|
||||||
keepAliveTimer = setTimeout(function() {
|
|
||||||
if (socket.readyState === WebSocket.OPEN) {
|
|
||||||
socket.send(
|
|
||||||
new textsecure.protobuf.WebSocketMessage({
|
|
||||||
type: textsecure.protobuf.WebSocketMessage.Type.REQUEST,
|
|
||||||
request: { verb: 'GET', path: '/v1/keepalive' }
|
|
||||||
}).encode().toArrayBuffer()
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
resetKeepAliveTimer();
|
|
||||||
}, 55000);
|
|
||||||
};
|
|
||||||
|
|
||||||
function onclose(e) {
|
function onclose(e) {
|
||||||
if (!error && !calledClose) {
|
if (!error && !calledClose) {
|
||||||
reconnectSemaphore--;
|
reconnectSemaphore--;
|
||||||
|
@ -74,22 +56,18 @@ TextSecureWebSocket = function (url) {
|
||||||
|
|
||||||
function onmessage(response) {
|
function onmessage(response) {
|
||||||
socketWrapper.onmessage(response);
|
socketWrapper.onmessage(response);
|
||||||
resetKeepAliveTimer();
|
|
||||||
};
|
};
|
||||||
|
|
||||||
function send(msg) {
|
function send(msg) {
|
||||||
resetKeepAliveTimer();
|
|
||||||
socket.send(msg);
|
socket.send(msg);
|
||||||
};
|
};
|
||||||
|
|
||||||
function connect() {
|
function connect() {
|
||||||
clearTimeout(keepAliveTimer);
|
|
||||||
if (++reconnectSemaphore <= 0) { return; }
|
if (++reconnectSemaphore <= 0) { return; }
|
||||||
|
|
||||||
if (socket) { socket.close(); }
|
if (socket) { socket.close(); }
|
||||||
socket = new WebSocket(url);
|
socket = new WebSocket(url);
|
||||||
|
|
||||||
socket.onopen = resetKeepAliveTimer;
|
|
||||||
socket.onerror = onerror
|
socket.onerror = onerror
|
||||||
socket.onclose = onclose;
|
socket.onclose = onclose;
|
||||||
socket.onmessage = onmessage;
|
socket.onmessage = onmessage;
|
||||||
|
|
Loading…
Reference in a new issue