2014-01-30 20:47:56 +01:00
|
|
|
var CLOSE_REASONS = {
|
2018-12-18 19:34:45 +01:00
|
|
|
none_given: {code: 4000, msg: "No reason provided"},
|
2016-12-03 03:08:47 +01:00
|
|
|
no_heartbeat: {code: 4001, msg: "Missed too many heartbeats"},
|
2018-12-18 19:34:45 +01:00
|
|
|
auth_fail: {code: 4002, msg: "Authentication failed"},
|
|
|
|
ack_timeout: {code: 4003, msg: "ACK timeout"},
|
|
|
|
cant_send: {code: 4004, msg: "User attempted to send while Socket was not ready"},
|
|
|
|
unsuspend: {code: 4005, msg: "Got unsuspend event"},
|
2014-01-30 20:47:56 +01:00
|
|
|
};
|
|
|
|
|
2013-09-10 20:39:46 +02:00
|
|
|
function Socket(url) {
|
|
|
|
this.url = url;
|
|
|
|
this._is_open = false;
|
|
|
|
this._is_authenticated = false;
|
2013-11-18 21:43:09 +01:00
|
|
|
this._is_reconnecting = false;
|
|
|
|
this._reconnect_initiation_time = null;
|
2014-01-17 21:35:25 +01:00
|
|
|
this._next_req_id_counter = 0;
|
2013-09-10 20:39:46 +02:00
|
|
|
this._connection_failures = 0;
|
2013-11-15 21:52:33 +01:00
|
|
|
this._reconnect_timeout_id = null;
|
2013-11-16 00:28:07 +01:00
|
|
|
this._heartbeat_timeout_id = null;
|
2014-01-22 22:35:04 +01:00
|
|
|
this._localstorage_requests_key = 'zulip_socket_requests';
|
|
|
|
this._requests = this._localstorage_requests();
|
2013-09-10 20:39:46 +02:00
|
|
|
|
2013-11-19 23:52:03 +01:00
|
|
|
var that = this;
|
2013-09-10 20:39:46 +02:00
|
|
|
this._is_unloading = false;
|
|
|
|
$(window).on("unload", function () {
|
2013-11-19 23:52:03 +01:00
|
|
|
that._is_unloading = true;
|
2013-09-10 20:39:46 +02:00
|
|
|
});
|
|
|
|
|
2013-11-18 22:05:56 +01:00
|
|
|
$(document).on("unsuspend", function () {
|
2014-01-30 20:47:56 +01:00
|
|
|
that._try_to_reconnect({reason: 'unsuspend'});
|
2013-11-18 22:05:56 +01:00
|
|
|
});
|
|
|
|
|
2018-05-06 21:43:17 +02:00
|
|
|
this._supported_protocols = [
|
|
|
|
'websocket',
|
|
|
|
'xdr-streaming',
|
|
|
|
'xhr-streaming',
|
|
|
|
'xdr-polling',
|
|
|
|
'xhr-polling',
|
|
|
|
'jsonp-polling',
|
|
|
|
];
|
2013-10-11 21:36:49 +02:00
|
|
|
if (page_params.test_suite) {
|
|
|
|
this._supported_protocols = _.reject(this._supported_protocols,
|
|
|
|
function (x) { return x === 'xhr-streaming'; });
|
2014-03-06 20:42:09 +01:00
|
|
|
// Don't create the SockJS on startup when running under the test suite.
|
|
|
|
// The first XHR request gets considered part of the page load and
|
|
|
|
// therefore the PhantomJS onLoadFinished handler doesn't get called
|
|
|
|
// until the SockJS XHR finishes, which happens at the heartbeat, 25
|
|
|
|
// seconds later. The SockJS objects will be created on demand anyway.
|
|
|
|
} else {
|
|
|
|
this._create_sockjs_object();
|
2013-10-11 21:36:49 +02:00
|
|
|
}
|
2013-09-10 20:39:46 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
Socket.prototype = {
|
2014-03-06 20:35:17 +01:00
|
|
|
_create_sockjs_object: function Socket__create_sockjs_object() {
|
2017-01-10 20:07:16 +01:00
|
|
|
this._sockjs = new SockJS(this.url, null, {protocols_whitelist: this._supported_protocols});
|
2014-03-06 20:35:17 +01:00
|
|
|
this._setup_sockjs_callbacks(this._sockjs);
|
|
|
|
},
|
|
|
|
|
2014-01-22 22:31:41 +01:00
|
|
|
_make_request: function Socket__make_request(type) {
|
|
|
|
return {req_id: this._get_next_req_id(),
|
|
|
|
type: type,
|
|
|
|
state: 'pending'};
|
|
|
|
},
|
|
|
|
|
2014-01-22 22:35:04 +01:00
|
|
|
// Note that by default messages are queued and retried across
|
|
|
|
// browser restarts if a restart takes place before a message
|
|
|
|
// is successfully transmitted.
|
|
|
|
// If that is the case, the success/error callbacks will not
|
2017-07-17 21:55:37 +02:00
|
|
|
// be automatically called.
|
2014-01-22 22:31:41 +01:00
|
|
|
send: function Socket__send(msg, success, error) {
|
|
|
|
var request = this._make_request('request');
|
|
|
|
request.msg = msg;
|
|
|
|
request.success = success;
|
|
|
|
request.error = error;
|
|
|
|
this._save_request(request);
|
|
|
|
|
2018-06-04 21:09:11 +02:00
|
|
|
if (!this._can_send()) {
|
2014-01-30 20:47:56 +01:00
|
|
|
this._try_to_reconnect({reason: 'cant_send'});
|
2013-09-10 20:39:46 +02:00
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2014-01-22 22:31:41 +01:00
|
|
|
this._do_send(request);
|
2013-09-10 20:39:46 +02:00
|
|
|
},
|
|
|
|
|
2014-01-17 21:35:25 +01:00
|
|
|
_get_next_req_id: function Socket__get_next_req_id() {
|
2017-04-24 21:40:16 +02:00
|
|
|
var req_id = page_params.queue_id + ':' + this._next_req_id_counter;
|
2016-11-30 19:05:04 +01:00
|
|
|
this._next_req_id_counter += 1;
|
2014-01-17 21:35:25 +01:00
|
|
|
return req_id;
|
|
|
|
},
|
|
|
|
|
|
|
|
_req_id_too_new: function Socket__req_id_too_new(req_id) {
|
|
|
|
var counter = req_id.split(':')[2];
|
|
|
|
|
|
|
|
return parseInt(counter, 10) >= this._next_req_id_counter;
|
|
|
|
},
|
|
|
|
|
2014-01-22 22:31:41 +01:00
|
|
|
_req_id_sorter: function Socket__req_id_sorter(req_id_a, req_id_b) {
|
|
|
|
// Sort in ascending order
|
|
|
|
var a_count = parseInt(req_id_a.split(':')[2], 10);
|
|
|
|
var b_count = parseInt(req_id_b.split(':')[2], 10);
|
|
|
|
|
|
|
|
return a_count - b_count;
|
|
|
|
},
|
|
|
|
|
|
|
|
_do_send: function Socket__do_send(request) {
|
2013-11-18 23:15:35 +01:00
|
|
|
var that = this;
|
2014-01-22 22:31:41 +01:00
|
|
|
this._requests[request.req_id].ack_timeout_id = setTimeout(function () {
|
|
|
|
blueslip.info("Timeout on ACK for request " + request.req_id);
|
2014-01-30 20:47:56 +01:00
|
|
|
that._try_to_reconnect({reason: 'ack_timeout'});
|
2013-11-18 23:15:35 +01:00
|
|
|
}, 2000);
|
|
|
|
|
2013-12-05 20:11:19 +01:00
|
|
|
try {
|
2014-01-22 22:31:41 +01:00
|
|
|
this._update_request_state(request.req_id, 'sent');
|
|
|
|
this._sockjs.send(JSON.stringify({req_id: request.req_id,
|
|
|
|
type: request.type, request: request.msg}));
|
2013-12-05 20:11:19 +01:00
|
|
|
} catch (e) {
|
2014-01-22 22:31:41 +01:00
|
|
|
this._update_request_state(request.req_id, 'pending');
|
2013-12-05 20:11:19 +01:00
|
|
|
if (e instanceof Error && e.message === 'INVALID_STATE_ERR') {
|
|
|
|
// The connection was somehow closed. Our on-close handler will
|
|
|
|
// be called imminently and we'll retry this request upon reconnect.
|
|
|
|
return;
|
2014-01-23 18:34:27 +01:00
|
|
|
} else if (e instanceof Error && e.message.indexOf("NS_ERROR_NOT_CONNECTED") !== -1) {
|
|
|
|
// This is a rarely-occurring Firefox error. I'm not sure
|
|
|
|
// whether our on-close handler will be called, so let's just
|
|
|
|
// call close() explicitly.
|
|
|
|
this._sockjs.close();
|
|
|
|
return;
|
2013-12-05 20:11:19 +01:00
|
|
|
}
|
2016-12-02 21:34:35 +01:00
|
|
|
throw e;
|
2013-12-05 20:11:19 +01:00
|
|
|
}
|
2013-09-10 20:39:46 +02:00
|
|
|
},
|
|
|
|
|
|
|
|
_can_send: function Socket__can_send() {
|
|
|
|
return this._is_open && this._is_authenticated;
|
|
|
|
},
|
|
|
|
|
2013-11-19 00:07:06 +01:00
|
|
|
_resend: function Socket__resend(req_id) {
|
|
|
|
var req_info = this._requests[req_id];
|
|
|
|
if (req_info.ack_timeout_id !== null) {
|
|
|
|
clearTimeout(req_info.ack_timeout_id);
|
|
|
|
req_info.ack_timeout_id = null;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (req_info.type !== 'request') {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2014-01-22 22:31:41 +01:00
|
|
|
this._do_send(req_info);
|
2013-09-10 20:39:46 +02:00
|
|
|
},
|
|
|
|
|
2013-11-06 18:51:59 +01:00
|
|
|
_process_response: function Socket__process_response(req_id, response) {
|
|
|
|
var req_info = this._requests[req_id];
|
|
|
|
if (req_info === undefined) {
|
2014-01-17 21:35:25 +01:00
|
|
|
if (this._req_id_too_new(req_id)) {
|
2013-11-08 20:16:31 +01:00
|
|
|
blueslip.error("Got a response for an unknown request",
|
2014-01-22 22:31:41 +01:00
|
|
|
{request_id: req_id, next_id: this._next_req_id_counter,
|
2013-11-08 20:16:31 +01:00
|
|
|
outstanding_ids: _.keys(this._requests)});
|
|
|
|
}
|
|
|
|
// There is a small race where we might start reauthenticating
|
|
|
|
// before one of our requests has finished but then have the request
|
|
|
|
// finish and thus receive the finish notification both from the
|
|
|
|
// status inquiry and from the normal response. Therefore, we might
|
|
|
|
// be processing the response for a request where we already got the
|
|
|
|
// response from a status inquiry. In that case, don't process the
|
|
|
|
// response twice.
|
2013-11-06 18:51:59 +01:00
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2014-01-22 22:31:41 +01:00
|
|
|
if (response.result === 'success' && req_info.success !== undefined) {
|
2013-11-06 18:51:59 +01:00
|
|
|
req_info.success(response);
|
2014-01-22 22:31:41 +01:00
|
|
|
} else if (req_info.error !== undefined) {
|
2013-11-06 18:51:59 +01:00
|
|
|
req_info.error('response', response);
|
|
|
|
}
|
2014-01-22 22:31:41 +01:00
|
|
|
this._remove_request(req_id);
|
2013-09-10 20:39:46 +02:00
|
|
|
},
|
|
|
|
|
2013-11-18 23:15:35 +01:00
|
|
|
_process_ack: function Socket__process_ack(req_id) {
|
|
|
|
var req_info = this._requests[req_id];
|
|
|
|
if (req_info === undefined) {
|
|
|
|
blueslip.error("Got an ACK for an unknown request",
|
2014-01-17 21:35:25 +01:00
|
|
|
{request_id: req_id, next_id: this._next_req_id_counter,
|
2013-11-18 23:15:35 +01:00
|
|
|
outstanding_ids: _.keys(this._requests)});
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (req_info.ack_timeout_id !== null) {
|
|
|
|
clearTimeout(req_info.ack_timeout_id);
|
|
|
|
req_info.ack_timeout_id = null;
|
|
|
|
}
|
|
|
|
},
|
|
|
|
|
2013-09-10 20:39:46 +02:00
|
|
|
_setup_sockjs_callbacks: function Socket__setup_sockjs_callbacks(sockjs) {
|
|
|
|
var that = this;
|
|
|
|
sockjs.onopen = function Socket__sockjs_onopen() {
|
2014-01-10 18:00:09 +01:00
|
|
|
blueslip.info("Socket connected [transport=" + sockjs.protocol + "]");
|
2014-01-14 19:36:13 +01:00
|
|
|
if (that._reconnect_initiation_time !== null) {
|
|
|
|
// If this is a reconnect, network was probably
|
|
|
|
// recently interrupted, so we optimistically restart
|
2014-01-30 20:29:00 +01:00
|
|
|
// get_events
|
|
|
|
server_events.restart_get_events();
|
2014-01-14 19:36:13 +01:00
|
|
|
}
|
2013-09-10 20:39:46 +02:00
|
|
|
that._is_open = true;
|
|
|
|
|
2013-11-20 00:05:16 +01:00
|
|
|
// Notify listeners that we've finished the websocket handshake
|
|
|
|
$(document).trigger($.Event('websocket_postopen.zulip', {}));
|
|
|
|
|
2018-06-14 18:15:55 +02:00
|
|
|
var request = that._make_request('auth');
|
|
|
|
request.msg = {csrf_token: csrf_token,
|
|
|
|
queue_id: page_params.queue_id,
|
|
|
|
status_inquiries: _.keys(that._requests)};
|
|
|
|
request.success = function (resp) {
|
|
|
|
that._is_authenticated = true;
|
|
|
|
that._is_reconnecting = false;
|
|
|
|
that._reconnect_initiation_time = null;
|
|
|
|
that._connection_failures = 0;
|
|
|
|
var resend_queue = [];
|
|
|
|
_.each(resp.status_inquiries, function (status, id) {
|
|
|
|
if (status.status === 'complete') {
|
|
|
|
that._process_response(id, status.response);
|
|
|
|
} else if (status.status === 'received') {
|
|
|
|
that._update_request_state(id, 'sent');
|
|
|
|
} else if (status.status === 'not_received') {
|
|
|
|
resend_queue.push(id);
|
|
|
|
}
|
|
|
|
});
|
|
|
|
resend_queue.sort(that._req_id_sorter);
|
|
|
|
_.each(resend_queue, function (id) {
|
|
|
|
that._resend(id);
|
|
|
|
});
|
|
|
|
};
|
|
|
|
request.error = function (type, resp) {
|
|
|
|
blueslip.info("Could not authenticate with server: " + resp.msg);
|
|
|
|
that._connection_failures += 1;
|
|
|
|
that._try_to_reconnect({reason: 'auth_fail',
|
|
|
|
wait_time: that._reconnect_wait_time()});
|
|
|
|
};
|
|
|
|
that._save_request(request);
|
|
|
|
that._do_send(request);
|
2013-09-10 20:39:46 +02:00
|
|
|
};
|
|
|
|
|
|
|
|
sockjs.onmessage = function Socket__sockjs_onmessage(event) {
|
2013-11-18 23:15:35 +01:00
|
|
|
if (event.data.type === 'ack') {
|
|
|
|
that._process_ack(event.data.req_id);
|
|
|
|
} else {
|
2013-11-18 23:12:56 +01:00
|
|
|
that._process_response(event.data.req_id, event.data.response);
|
|
|
|
}
|
2013-09-10 20:39:46 +02:00
|
|
|
};
|
|
|
|
|
2014-01-30 20:48:14 +01:00
|
|
|
sockjs.onheartbeat = function Socket__sockjs_onheartbeat() {
|
2013-11-16 00:28:07 +01:00
|
|
|
if (that._heartbeat_timeout_id !== null) {
|
|
|
|
clearTimeout(that._heartbeat_timeout_id);
|
|
|
|
that._heartbeat_timeout_id = null;
|
|
|
|
}
|
|
|
|
that._heartbeat_timeout_id = setTimeout(function () {
|
|
|
|
that._heartbeat_timeout_id = null;
|
|
|
|
blueslip.info("Missed too many hearbeats");
|
2014-01-30 20:47:56 +01:00
|
|
|
that._try_to_reconnect({reason: 'no_heartbeat'});
|
2013-11-16 00:28:07 +01:00
|
|
|
}, 60000);
|
|
|
|
};
|
|
|
|
|
2014-01-30 05:17:52 +01:00
|
|
|
sockjs.onclose = function Socket__sockjs_onclose(event) {
|
2013-09-10 20:39:46 +02:00
|
|
|
if (that._is_unloading) {
|
|
|
|
return;
|
|
|
|
}
|
2013-11-20 00:05:16 +01:00
|
|
|
// We've failed to handshake, but notify that the attempt finished
|
|
|
|
$(document).trigger($.Event('websocket_postopen.zulip', {}));
|
|
|
|
|
2014-01-30 05:17:52 +01:00
|
|
|
blueslip.info("SockJS connection lost. Attempting to reconnect soon."
|
|
|
|
+ " (" + event.code.toString() + ", " + event.reason + ")");
|
2016-11-30 19:05:04 +01:00
|
|
|
that._connection_failures += 1;
|
2013-11-18 21:43:09 +01:00
|
|
|
that._is_reconnecting = false;
|
2014-01-30 20:47:56 +01:00
|
|
|
// We don't need to specify a reason because the Socket is already closed
|
2014-01-30 20:28:33 +01:00
|
|
|
that._try_to_reconnect({wait_time: that._reconnect_wait_time()});
|
2013-09-10 20:39:46 +02:00
|
|
|
};
|
|
|
|
},
|
|
|
|
|
2013-11-18 21:43:09 +01:00
|
|
|
_reconnect_wait_time: function Socket__reconnect_wait_time() {
|
2013-09-10 20:39:46 +02:00
|
|
|
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
|
2013-11-18 21:43:09 +01:00
|
|
|
return 30;
|
|
|
|
}
|
2018-06-04 21:13:07 +02:00
|
|
|
return Math.min(90, Math.exp(this._connection_failures / 2)) * 1000;
|
2013-11-18 21:43:09 +01:00
|
|
|
},
|
|
|
|
|
2014-01-30 20:28:33 +01:00
|
|
|
_try_to_reconnect: function Socket__try_to_reconnect(opts) {
|
2014-01-30 20:47:56 +01:00
|
|
|
opts = _.extend({wait_time: 0, reason: 'none_given'}, opts);
|
2013-11-18 21:43:09 +01:00
|
|
|
var that = this;
|
|
|
|
|
|
|
|
var now = (new Date()).getTime();
|
|
|
|
if (this._is_reconnecting && now - this._reconnect_initiation_time < 1000) {
|
|
|
|
// Only try to reconnect once a second
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (this._reconnect_timeout_id !== null) {
|
|
|
|
clearTimeout(this._reconnect_timeout_id);
|
|
|
|
this._reconnect_timeout_id = null;
|
|
|
|
}
|
|
|
|
|
2013-11-16 00:28:07 +01:00
|
|
|
if (this._heartbeat_timeout_id !== null) {
|
|
|
|
clearTimeout(that._heartbeat_timeout_id);
|
|
|
|
this._heartbeat_timeout_id = null;
|
|
|
|
}
|
|
|
|
|
2013-12-05 20:11:50 +01:00
|
|
|
// Cancel any pending auth requests and any timeouts for ACKs
|
2013-12-05 17:31:20 +01:00
|
|
|
_.each(this._requests, function (val, key) {
|
2013-12-05 20:11:50 +01:00
|
|
|
if (val.ack_timeout_id !== null) {
|
|
|
|
clearTimeout(val.ack_timeout_id);
|
|
|
|
val.ack_timeout_id = null;
|
|
|
|
}
|
|
|
|
|
2013-12-05 17:31:20 +01:00
|
|
|
if (val.type === 'auth') {
|
2014-01-22 22:31:41 +01:00
|
|
|
that._remove_request(key);
|
2013-12-05 17:31:20 +01:00
|
|
|
}
|
|
|
|
});
|
|
|
|
|
2013-11-18 21:43:09 +01:00
|
|
|
this._is_open = false;
|
|
|
|
this._is_authenticated = false;
|
|
|
|
this._is_reconnecting = true;
|
|
|
|
this._reconnect_initiation_time = now;
|
2014-01-30 20:30:49 +01:00
|
|
|
// This is a little weird because we're also called from the SockJS
|
|
|
|
// onclose handler. Fortunately, close() does nothing on an
|
2014-03-06 20:42:09 +01:00
|
|
|
// already-closed SockJS object. However, we do have to check that
|
|
|
|
// this._sockjs isn't undefined since it's not created immediately
|
|
|
|
// when running under the test suite.
|
|
|
|
if (this._sockjs !== undefined) {
|
|
|
|
var close_reason = CLOSE_REASONS[opts.reason];
|
|
|
|
this._sockjs.close(close_reason.code, close_reason.msg);
|
|
|
|
}
|
2013-09-10 20:39:46 +02:00
|
|
|
|
2013-11-15 21:52:33 +01:00
|
|
|
this._reconnect_timeout_id = setTimeout(function () {
|
|
|
|
that._reconnect_timeout_id = null;
|
2013-11-18 21:43:09 +01:00
|
|
|
blueslip.info("Attempting socket reconnect.");
|
2014-03-06 20:35:17 +01:00
|
|
|
that._create_sockjs_object();
|
2014-01-30 20:28:33 +01:00
|
|
|
}, opts.wait_time);
|
2014-01-22 22:31:41 +01:00
|
|
|
},
|
|
|
|
|
2014-01-22 22:35:04 +01:00
|
|
|
_localstorage_requests: function Socket__localstorage_requests() {
|
|
|
|
if (!localstorage.supported()) {
|
|
|
|
return {};
|
|
|
|
}
|
|
|
|
return JSON.parse(window.localStorage[this._localstorage_requests_key] || "{}");
|
|
|
|
},
|
|
|
|
|
|
|
|
_save_localstorage_requests: function Socket__save_localstorage_requests() {
|
|
|
|
if (!localstorage.supported()) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
// Auth requests are always session-specific, so don't store them for later
|
|
|
|
var non_auth_reqs = {};
|
|
|
|
_.each(this._requests, function (val, key) {
|
|
|
|
if (val.type !== 'auth') {
|
|
|
|
non_auth_reqs[key] = val;
|
|
|
|
}
|
|
|
|
});
|
|
|
|
|
2014-01-31 00:04:15 +01:00
|
|
|
try {
|
|
|
|
window.localStorage[this._localstorage_requests_key] = JSON.stringify(non_auth_reqs);
|
|
|
|
} catch (e) {
|
|
|
|
// We can't catch a specific exception type, because browsers return different types
|
|
|
|
// for out of space errors. See http://chrisberkhout.com/blog/localstorage-errors/ for
|
|
|
|
// more details.
|
|
|
|
blueslip.warn("Failed to save to local storage, caught exception when saving " + e);
|
|
|
|
}
|
2014-01-22 22:35:04 +01:00
|
|
|
},
|
|
|
|
|
2014-01-22 22:31:41 +01:00
|
|
|
_save_request: function Socket__save_request(request) {
|
|
|
|
this._requests[request.req_id] = request;
|
2014-01-22 22:35:04 +01:00
|
|
|
|
|
|
|
if (!localstorage.supported()) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
this._save_localstorage_requests();
|
2014-01-22 22:31:41 +01:00
|
|
|
},
|
|
|
|
|
|
|
|
_remove_request: function Socket__remove_request(req_id) {
|
|
|
|
delete this._requests[req_id];
|
2014-01-22 22:35:04 +01:00
|
|
|
|
|
|
|
if (!localstorage.supported()) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
this._save_localstorage_requests();
|
|
|
|
|
2014-01-22 22:31:41 +01:00
|
|
|
},
|
|
|
|
|
|
|
|
_update_request_state: function Socket__update_request_state(req_id, state) {
|
|
|
|
this._requests[req_id].state = state;
|
2014-01-22 22:35:04 +01:00
|
|
|
|
|
|
|
if (!localstorage.supported()) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
this._save_localstorage_requests();
|
2017-01-12 00:17:43 +01:00
|
|
|
},
|
2013-09-10 20:39:46 +02:00
|
|
|
};
|
2014-01-30 20:33:22 +01:00
|
|
|
|
2019-10-25 09:45:13 +02:00
|
|
|
module.exports = Socket;
|
2018-05-28 08:04:36 +02:00
|
|
|
window.Socket = Socket;
|