mirror of
https://github.com/zulip/zulip.git
synced 2025-11-04 05:53:43 +00:00
Clients can only have one connection at a time, anyway, so we can just keep track of a client id, instead. This makes reconnections easier. It's a little funny to use queue ids for the client id, but we know they should exist by the time the client is connecting and they are guaranteed to already be unique and authenticatable. We will also eventually be integrating the event system and the socket code closer anyway. (imported from commit 1f60e06fb16d31d6c121deafd493fb304d19a6c2)
148 lines
5.2 KiB
JavaScript
148 lines
5.2 KiB
JavaScript
function Socket(url) {
|
|
this.url = url;
|
|
this._is_open = false;
|
|
this._is_authenticated = false;
|
|
this._send_queue = [];
|
|
this._next_req_id = 0;
|
|
this._requests = {};
|
|
this._connection_failures = 0;
|
|
this._timeout_id = null;
|
|
|
|
this._is_unloading = false;
|
|
$(window).on("unload", function () {
|
|
this._is_unloading = true;
|
|
});
|
|
|
|
this._supported_protocols = ['websocket', 'xdr-streaming', 'xhr-streaming',
|
|
'xdr-polling', 'xhr-polling', 'jsonp-polling'];
|
|
if (page_params.test_suite) {
|
|
this._supported_protocols = _.reject(this._supported_protocols,
|
|
function (x) { return x === 'xhr-streaming'; });
|
|
}
|
|
|
|
this._sockjs = new SockJS(url, null, {protocols_whitelist: this._supported_protocols});
|
|
this._setup_sockjs_callbacks(this._sockjs);
|
|
}
|
|
|
|
Socket.prototype = {
|
|
send: function Socket_send(msg, success, error) {
|
|
if (! this._can_send()) {
|
|
this._send_queue.push({msg: msg, success: success, error: error});
|
|
if (this._timeout_id !== null) {
|
|
clearTimeout(this._timeout_id);
|
|
}
|
|
this._do_reconnect();
|
|
return;
|
|
}
|
|
|
|
this._do_send('request', msg, success, error);
|
|
},
|
|
|
|
_do_send: function Socket__do_send(type, msg, success, error) {
|
|
var req_id = this._next_req_id;
|
|
this._next_req_id++;
|
|
this._requests[req_id] = {success: success, error: error};
|
|
// TODO: I think we might need to catch exceptions here for certain transports
|
|
this._sockjs.send(JSON.stringify({client_meta: {req_id: req_id},
|
|
type: type, request: msg}));
|
|
},
|
|
|
|
_can_send: function Socket__can_send() {
|
|
return this._is_open && this._is_authenticated;
|
|
},
|
|
|
|
_drain_queue_send: function Socket__drain_queue_send() {
|
|
var that = this;
|
|
var queue = this._send_queue;
|
|
this._send_queue = [];
|
|
_.each(queue, function (elem) {
|
|
that.send(elem.msg, elem.success, elem.error);
|
|
});
|
|
},
|
|
|
|
_drain_queue_error: function Socket__drain_queue_error() {
|
|
var that = this;
|
|
var queue = this._send_queue;
|
|
this._send_queue = [];
|
|
_.each(queue, function (elem) {
|
|
elem.error('connection');
|
|
});
|
|
},
|
|
|
|
_setup_sockjs_callbacks: function Socket__setup_sockjs_callbacks(sockjs) {
|
|
var that = this;
|
|
sockjs.onopen = function Socket__sockjs_onopen() {
|
|
blueslip.info("Socket connected.");
|
|
that._is_open = true;
|
|
|
|
// We can only authenticate after the DOM has loaded because we need
|
|
// the CSRF token
|
|
$(function () {
|
|
that._do_send('auth', {csrf_token: csrf_token,
|
|
queue_id: page_params.event_queue_id},
|
|
function () {
|
|
that._is_authenticated = true;
|
|
that._connection_failures = 0;
|
|
that._drain_queue_send();
|
|
},
|
|
function (type, resp) {
|
|
blueslip.info("Could not authenticate with server: " + resp.msg);
|
|
that._try_to_reconnect();
|
|
});
|
|
});
|
|
};
|
|
|
|
sockjs.onmessage = function Socket__sockjs_onmessage(event) {
|
|
var req_id = event.data.client_meta.req_id;
|
|
var req_info = that._requests[req_id];
|
|
if (req_info === undefined) {
|
|
blueslip.error("Got a response for an unknown request");
|
|
return;
|
|
}
|
|
|
|
if (event.data.response.result === 'success') {
|
|
req_info.success(event.data.response);
|
|
} else {
|
|
req_info.error('response', event.data.response);
|
|
}
|
|
delete that._requests[req_id];
|
|
};
|
|
|
|
sockjs.onclose = function Socket__sockjs_onclose() {
|
|
if (that._is_unloading) {
|
|
return;
|
|
}
|
|
blueslip.info("SockJS connection lost. Attempting to reconnect soon.");
|
|
that._try_to_reconnect();
|
|
};
|
|
},
|
|
|
|
_do_reconnect: _.throttle(function Socket__do_reconnect() {
|
|
blueslip.info("Attempting socket reconnect.");
|
|
this._sockjs = new SockJS(this.url, null, {protocols_whitelist: this._supported_protocols});
|
|
this._setup_sockjs_callbacks(this._sockjs);
|
|
}, 1000),
|
|
|
|
_try_to_reconnect: function Socket__try_to_reconnect() {
|
|
var that = this;
|
|
this._is_open = false;
|
|
this._is_authenticated = false;
|
|
this._connection_failures++;
|
|
this._drain_queue_error();
|
|
|
|
var wait_time;
|
|
if (this._connection_failures === 1) {
|
|
// We specify a non-zero timeout here so that we don't try to
|
|
// immediately reconnect when the page is refreshing
|
|
wait_time = 30;
|
|
} else {
|
|
wait_time = Math.min(90, Math.exp(this._connection_failures/2)) * 1000;
|
|
}
|
|
|
|
this._timeout_id = setTimeout(function () {
|
|
that._timeout_id = null;
|
|
that._do_reconnect();
|
|
}, wait_time);
|
|
}
|
|
};
|