2014-01-30 19:25:25 +01:00
|
|
|
var server_events = (function () {
|
|
|
|
|
|
|
|
var exports = {};
|
|
|
|
|
|
|
|
var waiting_on_homeview_load = true;
|
|
|
|
|
2014-01-30 20:50:18 +01:00
|
|
|
var events_stored_while_loading = [];
|
2014-01-30 19:25:25 +01:00
|
|
|
|
2014-01-30 20:29:00 +01:00
|
|
|
var get_events_xhr;
|
|
|
|
var get_events_timeout;
|
|
|
|
var get_events_failures = 0;
|
2014-01-30 21:01:52 +01:00
|
|
|
var get_events_params = {};
|
2014-01-30 19:25:25 +01:00
|
|
|
|
2017-05-04 02:39:54 +02:00
|
|
|
// This field keeps track of whether we are attempting to
|
|
|
|
// force-reconnect to the events server due to suspecting we are
|
|
|
|
// offline. It is important for avoiding races with the presence
|
|
|
|
// system when coming back from unsuspend.
|
|
|
|
exports.suspect_offline = false;
|
|
|
|
|
2014-01-30 20:29:00 +01:00
|
|
|
function get_events_success(events) {
|
2014-01-30 19:25:25 +01:00
|
|
|
var messages = [];
|
|
|
|
var messages_to_update = [];
|
|
|
|
var new_pointer;
|
|
|
|
|
2016-12-05 07:02:18 +01:00
|
|
|
var clean_event = function clean_event(event) {
|
2014-01-31 06:24:15 +01:00
|
|
|
// Only log a whitelist of the event to remove private data
|
|
|
|
return _.pick(event, 'id', 'type', 'op');
|
|
|
|
};
|
|
|
|
|
2014-01-30 19:25:25 +01:00
|
|
|
_.each(events, function (event) {
|
2014-01-31 06:24:15 +01:00
|
|
|
try {
|
|
|
|
get_events_params.last_event_id = Math.max(get_events_params.last_event_id,
|
|
|
|
event.id);
|
|
|
|
} catch (ex) {
|
|
|
|
blueslip.error('Failed to update last_event_id',
|
|
|
|
{event: clean_event(event)},
|
|
|
|
ex.stack);
|
|
|
|
}
|
2014-01-30 19:25:25 +01:00
|
|
|
});
|
|
|
|
|
2014-01-30 20:50:18 +01:00
|
|
|
if (waiting_on_homeview_load) {
|
|
|
|
events_stored_while_loading = events_stored_while_loading.concat(events);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (events_stored_while_loading.length > 0) {
|
|
|
|
events = events_stored_while_loading.concat(events);
|
|
|
|
events_stored_while_loading = [];
|
|
|
|
}
|
|
|
|
|
2017-05-31 22:04:19 +02:00
|
|
|
// Most events are dispatched via the code server_events_dispatch,
|
|
|
|
// called in the default case. The goal of this split is to avoid
|
|
|
|
// contributors needing to read or understand the complex and
|
|
|
|
// rarely modified logic for non-normal events.
|
2014-01-31 06:24:15 +01:00
|
|
|
var dispatch_event = function dispatch_event(event) {
|
2014-01-30 19:25:25 +01:00
|
|
|
switch (event.type) {
|
|
|
|
case 'message':
|
|
|
|
var msg = event.message;
|
|
|
|
msg.flags = event.flags;
|
sending messages: Extract sent_messages.js.
This commit extract send_messages.js to clean up code related
to the following things:
* sending data to /json/report_send_time
* restarting the event loop if events don't arrive on time
The code related to /json/report changes the following ways:
* We track the state almost completely in the new
send_messages.js module, with other modules just
making one-line calls.
* We no longer send "displayed" times to the servers, since
we were kind of lying about them anyway.
* We now explicitly track the state of each single sent
message in its own object.
* We now look up data related to the messages by local_id,
instead of message_id. The problem with message_id was
that is was mutable. Now we use local_id, and we extend
the local_id concept to messages that don't get rendered
client side. We no longer need to react to the
'message_id_changed' event to change our hash key.
* The code used to live in many places:
* various big chunks were scattered among compose.js,
and those were all moved or reduced to one-line
calls into the new module
* echo.js continues to make basically one-line calls,
but it no longer calls compose.report_as_received(),
nor does it set the "start" time.
* message_util.js used to report received events, but
only when they finally got drawn in the home view;
this code is gone now
The code related to restarting the event loop if events don't arrive
changes as follows:
* The timer now gets set up from within
send_messages.message_state.report_server_ack,
where we can easily inspect the current state of the
possibly-still-in-flight message.
* The code to confirm that an event was received happens now
in server_events.js, rather than later, so that we don't
falsely blame the event loop for a downstream bug. (Plus
it's easier to just do it one place.)
This change removes a fair amount of code from our node tests. Some
of the removal is good stuff related to us completing killing off
unnecessary code. Other removals are more expediency-driven, and
we should make another sweep at ramping up our coverage on compose.js,
with possibly a little more mocking of the new `send_messages` code
layer, since it's now abstracted better.
There is also some minor cleanup to echo.resend_message() in this
commit.
See #5968 for a detailed breakdown of the changes.
2017-07-30 12:56:46 +02:00
|
|
|
if (event.local_message_id) {
|
2017-07-14 19:30:23 +02:00
|
|
|
msg.local_id = event.local_message_id;
|
sending messages: Extract sent_messages.js.
This commit extract send_messages.js to clean up code related
to the following things:
* sending data to /json/report_send_time
* restarting the event loop if events don't arrive on time
The code related to /json/report changes the following ways:
* We track the state almost completely in the new
send_messages.js module, with other modules just
making one-line calls.
* We no longer send "displayed" times to the servers, since
we were kind of lying about them anyway.
* We now explicitly track the state of each single sent
message in its own object.
* We now look up data related to the messages by local_id,
instead of message_id. The problem with message_id was
that is was mutable. Now we use local_id, and we extend
the local_id concept to messages that don't get rendered
client side. We no longer need to react to the
'message_id_changed' event to change our hash key.
* The code used to live in many places:
* various big chunks were scattered among compose.js,
and those were all moved or reduced to one-line
calls into the new module
* echo.js continues to make basically one-line calls,
but it no longer calls compose.report_as_received(),
nor does it set the "start" time.
* message_util.js used to report received events, but
only when they finally got drawn in the home view;
this code is gone now
The code related to restarting the event loop if events don't arrive
changes as follows:
* The timer now gets set up from within
send_messages.message_state.report_server_ack,
where we can easily inspect the current state of the
possibly-still-in-flight message.
* The code to confirm that an event was received happens now
in server_events.js, rather than later, so that we don't
falsely blame the event loop for a downstream bug. (Plus
it's easier to just do it one place.)
This change removes a fair amount of code from our node tests. Some
of the removal is good stuff related to us completing killing off
unnecessary code. Other removals are more expediency-driven, and
we should make another sweep at ramping up our coverage on compose.js,
with possibly a little more mocking of the new `send_messages` code
layer, since it's now abstracted better.
There is also some minor cleanup to echo.resend_message() in this
commit.
See #5968 for a detailed breakdown of the changes.
2017-07-30 12:56:46 +02:00
|
|
|
sent_messages.report_event_received(event.local_message_id);
|
2017-07-14 19:30:23 +02:00
|
|
|
}
|
2014-01-30 19:25:25 +01:00
|
|
|
messages.push(msg);
|
|
|
|
break;
|
2016-08-23 03:53:05 +02:00
|
|
|
|
2014-01-30 19:25:25 +01:00
|
|
|
case 'pointer':
|
|
|
|
new_pointer = event.pointer;
|
|
|
|
break;
|
2016-08-23 03:53:05 +02:00
|
|
|
|
2014-01-30 19:25:25 +01:00
|
|
|
case 'update_message':
|
|
|
|
messages_to_update.push(event);
|
|
|
|
break;
|
|
|
|
|
2016-08-23 03:53:05 +02:00
|
|
|
default:
|
2017-05-31 08:53:09 +02:00
|
|
|
return server_events_dispatch.dispatch_normal_event(event);
|
2014-01-30 19:25:25 +01:00
|
|
|
}
|
2014-01-31 06:24:15 +01:00
|
|
|
};
|
|
|
|
|
|
|
|
_.each(events, function (event) {
|
|
|
|
try {
|
|
|
|
dispatch_event(event);
|
|
|
|
} catch (ex1) {
|
2014-02-10 22:48:49 +01:00
|
|
|
blueslip.error('Failed to process an event\n' +
|
|
|
|
blueslip.exception_msg(ex1),
|
2014-01-31 06:24:15 +01:00
|
|
|
{event: clean_event(event)},
|
|
|
|
ex1.stack);
|
|
|
|
}
|
2014-01-30 19:25:25 +01:00
|
|
|
});
|
|
|
|
|
|
|
|
if (messages.length !== 0) {
|
2017-10-12 00:49:25 +02:00
|
|
|
// Sort by ID, so that if we get multiple messages back from
|
|
|
|
// the server out-of-order, we'll still end up with our
|
|
|
|
// message lists in order.
|
|
|
|
messages = _.sortBy(messages, 'id');
|
2014-01-31 06:24:15 +01:00
|
|
|
try {
|
|
|
|
messages = echo.process_from_server(messages);
|
2017-03-19 20:23:48 +01:00
|
|
|
message_events.insert_new_messages(messages);
|
2014-01-31 06:24:15 +01:00
|
|
|
} catch (ex2) {
|
2014-02-10 22:48:49 +01:00
|
|
|
blueslip.error('Failed to insert new messages\n' +
|
|
|
|
blueslip.exception_msg(ex2),
|
2014-01-31 06:24:15 +01:00
|
|
|
undefined,
|
|
|
|
ex2.stack);
|
|
|
|
}
|
2014-01-30 19:25:25 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
if (new_pointer !== undefined
|
2016-12-01 13:30:30 +01:00
|
|
|
&& new_pointer > pointer.furthest_read) {
|
2016-04-12 17:38:47 +02:00
|
|
|
pointer.furthest_read = new_pointer;
|
|
|
|
pointer.server_furthest_read = new_pointer;
|
2014-01-30 19:25:25 +01:00
|
|
|
home_msg_list.select_id(new_pointer, {then_scroll: true, use_closest: true});
|
|
|
|
}
|
|
|
|
|
|
|
|
if ((home_msg_list.selected_id() === -1) && !home_msg_list.empty()) {
|
|
|
|
home_msg_list.select_id(home_msg_list.first().id, {then_scroll: false});
|
|
|
|
}
|
|
|
|
|
|
|
|
if (messages_to_update.length !== 0) {
|
2014-01-31 06:24:15 +01:00
|
|
|
try {
|
2017-03-19 20:23:48 +01:00
|
|
|
message_events.update_messages(messages_to_update);
|
2014-01-31 06:24:15 +01:00
|
|
|
} catch (ex3) {
|
2014-02-10 22:48:49 +01:00
|
|
|
blueslip.error('Failed to update messages\n' +
|
|
|
|
blueslip.exception_msg(ex3),
|
2014-01-31 06:24:15 +01:00
|
|
|
undefined,
|
|
|
|
ex3.stack);
|
|
|
|
}
|
2014-01-30 19:25:25 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2014-01-30 20:50:18 +01:00
|
|
|
function get_events(options) {
|
2014-01-30 19:25:25 +01:00
|
|
|
options = _.extend({dont_block: false}, options);
|
|
|
|
|
2016-03-31 07:26:14 +02:00
|
|
|
if (reload.is_in_progress()) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2014-01-30 21:01:52 +01:00
|
|
|
get_events_params.dont_block = options.dont_block || get_events_failures > 0;
|
2017-05-04 02:39:54 +02:00
|
|
|
|
|
|
|
if (get_events_params.dont_block) {
|
|
|
|
// If we're requesting an immediate re-connect to the server,
|
|
|
|
// that means it's fairly likely that this client has been off
|
|
|
|
// the Internet and thus may have stale state (which is
|
|
|
|
// important for potential presence issues).
|
|
|
|
exports.suspect_offline = true;
|
|
|
|
}
|
2014-01-30 21:01:52 +01:00
|
|
|
if (get_events_params.queue_id === undefined) {
|
2017-04-24 21:40:16 +02:00
|
|
|
get_events_params.queue_id = page_params.queue_id;
|
2014-01-30 21:01:52 +01:00
|
|
|
get_events_params.last_event_id = page_params.last_event_id;
|
2014-01-30 19:25:25 +01:00
|
|
|
}
|
|
|
|
|
2014-01-30 20:29:00 +01:00
|
|
|
if (get_events_xhr !== undefined) {
|
|
|
|
get_events_xhr.abort();
|
2014-01-30 19:25:25 +01:00
|
|
|
}
|
2014-01-30 20:29:00 +01:00
|
|
|
if (get_events_timeout !== undefined) {
|
|
|
|
clearTimeout(get_events_timeout);
|
2014-01-30 19:25:25 +01:00
|
|
|
}
|
2017-11-03 16:53:21 +01:00
|
|
|
|
|
|
|
get_events_params.client_gravatar = true;
|
|
|
|
|
2014-01-30 20:29:00 +01:00
|
|
|
get_events_timeout = undefined;
|
2016-10-19 02:46:21 +02:00
|
|
|
get_events_xhr = channel.get({
|
|
|
|
url: '/json/events',
|
2014-01-30 21:01:52 +01:00
|
|
|
data: get_events_params,
|
2014-01-30 19:25:25 +01:00
|
|
|
idempotent: true,
|
|
|
|
timeout: page_params.poll_timeout,
|
|
|
|
success: function (data) {
|
2017-05-04 02:39:54 +02:00
|
|
|
exports.suspect_offline = false;
|
2014-01-31 06:24:15 +01:00
|
|
|
try {
|
|
|
|
get_events_xhr = undefined;
|
|
|
|
get_events_failures = 0;
|
2017-03-23 20:37:08 +01:00
|
|
|
ui_report.hide_error($("#connection-error"));
|
2014-01-30 19:25:25 +01:00
|
|
|
|
2014-01-31 06:24:15 +01:00
|
|
|
get_events_success(data.events);
|
|
|
|
} catch (ex) {
|
2014-02-10 22:48:49 +01:00
|
|
|
blueslip.error('Failed to handle get_events success\n' +
|
|
|
|
blueslip.exception_msg(ex),
|
2014-01-31 06:24:15 +01:00
|
|
|
undefined,
|
|
|
|
ex.stack);
|
|
|
|
}
|
2014-01-30 20:29:00 +01:00
|
|
|
get_events_timeout = setTimeout(get_events, 0);
|
2014-01-30 19:25:25 +01:00
|
|
|
},
|
2016-12-02 14:06:06 +01:00
|
|
|
error: function (xhr, error_type) {
|
2014-01-31 06:24:15 +01:00
|
|
|
try {
|
|
|
|
get_events_xhr = undefined;
|
2017-03-23 05:13:23 +01:00
|
|
|
// If we're old enough that our message queue has been
|
|
|
|
// garbage collected, immediately reload.
|
2014-01-31 06:24:15 +01:00
|
|
|
if ((xhr.status === 400) &&
|
2017-07-21 02:20:31 +02:00
|
|
|
(JSON.parse(xhr.responseText).code === 'BAD_EVENT_QUEUE_ID')) {
|
2014-01-31 06:24:15 +01:00
|
|
|
page_params.event_queue_expired = true;
|
2015-11-29 03:00:00 +01:00
|
|
|
reload.initiate({immediate: true,
|
|
|
|
save_pointer: false,
|
2017-03-23 06:27:29 +01:00
|
|
|
save_narrow: true,
|
2015-11-29 03:47:36 +01:00
|
|
|
save_compose: true});
|
2014-01-31 06:24:15 +01:00
|
|
|
}
|
2014-01-30 19:25:25 +01:00
|
|
|
|
2014-01-31 06:24:15 +01:00
|
|
|
if (error_type === 'abort') {
|
|
|
|
// Don't restart if we explicitly aborted
|
|
|
|
return;
|
|
|
|
} else if (error_type === 'timeout') {
|
|
|
|
// Retry indefinitely on timeout.
|
|
|
|
get_events_failures = 0;
|
2017-03-23 20:37:08 +01:00
|
|
|
ui_report.hide_error($("#connection-error"));
|
2014-01-31 06:24:15 +01:00
|
|
|
} else {
|
|
|
|
get_events_failures += 1;
|
|
|
|
}
|
2014-01-30 19:25:25 +01:00
|
|
|
|
2014-01-31 06:24:15 +01:00
|
|
|
if (get_events_failures >= 5) {
|
2017-03-23 20:37:08 +01:00
|
|
|
ui_report.show_error($("#connection-error"));
|
2014-01-31 06:24:15 +01:00
|
|
|
} else {
|
2017-03-23 20:37:08 +01:00
|
|
|
ui_report.hide_error($("#connection-error"));
|
2014-01-31 06:24:15 +01:00
|
|
|
}
|
|
|
|
} catch (ex) {
|
2014-02-10 22:48:49 +01:00
|
|
|
blueslip.error('Failed to handle get_events error\n' +
|
|
|
|
blueslip.exception_msg(ex),
|
2014-01-31 06:24:15 +01:00
|
|
|
undefined,
|
|
|
|
ex.stack);
|
2014-01-30 19:25:25 +01:00
|
|
|
}
|
2014-01-30 20:29:00 +01:00
|
|
|
var retry_sec = Math.min(90, Math.exp(get_events_failures/2));
|
|
|
|
get_events_timeout = setTimeout(get_events, retry_sec*1000);
|
2017-01-12 00:17:43 +01:00
|
|
|
},
|
2014-01-30 19:25:25 +01:00
|
|
|
});
|
2014-01-30 20:50:18 +01:00
|
|
|
}
|
2014-01-30 19:25:25 +01:00
|
|
|
|
2014-01-30 20:29:00 +01:00
|
|
|
exports.assert_get_events_running = function assert_get_events_running(error_message) {
|
|
|
|
if (get_events_xhr === undefined && get_events_timeout === undefined) {
|
|
|
|
exports.restart_get_events({dont_block: true});
|
2014-01-30 19:25:25 +01:00
|
|
|
blueslip.error(error_message);
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
2014-01-30 20:29:00 +01:00
|
|
|
exports.restart_get_events = function restart_get_events(options) {
|
2014-01-30 20:50:18 +01:00
|
|
|
get_events(options);
|
2014-01-30 19:25:25 +01:00
|
|
|
};
|
|
|
|
|
2014-01-30 20:29:00 +01:00
|
|
|
exports.force_get_events = function force_get_events() {
|
2014-01-30 20:50:18 +01:00
|
|
|
get_events_timeout = setTimeout(get_events, 0);
|
2014-01-30 19:25:25 +01:00
|
|
|
};
|
|
|
|
|
|
|
|
exports.home_view_loaded = function home_view_loaded() {
|
|
|
|
waiting_on_homeview_load = false;
|
2014-01-30 20:50:18 +01:00
|
|
|
get_events_success([]);
|
2014-01-30 19:25:25 +01:00
|
|
|
$(document).trigger("home_view_loaded.zulip");
|
|
|
|
};
|
|
|
|
|
2017-05-04 02:20:25 +02:00
|
|
|
|
2014-01-30 19:25:25 +01:00
|
|
|
var watchdog_time = $.now();
|
2017-05-04 02:20:25 +02:00
|
|
|
exports.check_for_unsuspend = function () {
|
2014-01-30 19:25:25 +01:00
|
|
|
var new_time = $.now();
|
|
|
|
if ((new_time - watchdog_time) > 20000) { // 20 seconds.
|
|
|
|
// Defensively reset watchdog_time here in case there's an
|
|
|
|
// exception in one of the event handlers
|
|
|
|
watchdog_time = new_time;
|
|
|
|
// Our app's JS wasn't running, which probably means the machine was
|
|
|
|
// asleep.
|
|
|
|
$(document).trigger($.Event('unsuspend'));
|
|
|
|
}
|
|
|
|
watchdog_time = new_time;
|
2017-05-04 02:20:25 +02:00
|
|
|
};
|
|
|
|
setInterval(exports.check_for_unsuspend, 5000);
|
2014-01-30 19:25:25 +01:00
|
|
|
|
2017-07-04 03:14:24 +02:00
|
|
|
exports.initialize = function () {
|
2014-01-30 19:25:25 +01:00
|
|
|
$(document).on('unsuspend', function () {
|
2014-01-30 20:29:00 +01:00
|
|
|
// Immediately poll for new events on unsuspend
|
|
|
|
blueslip.log("Restarting get_events due to unsuspend");
|
|
|
|
get_events_failures = 0;
|
|
|
|
exports.restart_get_events({dont_block: true});
|
2014-01-30 19:25:25 +01:00
|
|
|
});
|
2014-01-30 20:50:18 +01:00
|
|
|
get_events();
|
2017-07-04 03:14:24 +02:00
|
|
|
};
|
2014-01-30 19:25:25 +01:00
|
|
|
|
2014-02-05 19:49:18 +01:00
|
|
|
exports.cleanup_event_queue = function cleanup_event_queue() {
|
2014-01-30 19:25:25 +01:00
|
|
|
// Submit a request to the server to cleanup our event queue
|
|
|
|
if (page_params.event_queue_expired === true) {
|
|
|
|
return;
|
|
|
|
}
|
2017-10-12 05:31:32 +02:00
|
|
|
blueslip.log("Cleaning up our event queue");
|
2016-03-31 08:01:52 +02:00
|
|
|
// Set expired because in a reload we may be called twice.
|
|
|
|
page_params.event_queue_expired = true;
|
2014-01-30 19:25:25 +01:00
|
|
|
channel.del({
|
|
|
|
url: '/json/events',
|
2017-04-24 21:40:16 +02:00
|
|
|
data: {queue_id: page_params.queue_id},
|
2014-01-30 19:25:25 +01:00
|
|
|
});
|
2014-02-05 19:49:18 +01:00
|
|
|
};
|
2014-01-30 19:25:25 +01:00
|
|
|
|
2016-12-02 14:06:06 +01:00
|
|
|
window.addEventListener("beforeunload", function () {
|
2014-02-05 19:49:18 +01:00
|
|
|
exports.cleanup_event_queue();
|
2014-01-30 19:25:25 +01:00
|
|
|
});
|
|
|
|
|
2016-08-04 17:44:35 +02:00
|
|
|
// For unit testing
|
|
|
|
exports._get_events_success = get_events_success;
|
2014-01-30 19:25:25 +01:00
|
|
|
|
|
|
|
return exports;
|
|
|
|
|
|
|
|
}());
|
|
|
|
if (typeof module !== 'undefined') {
|
|
|
|
module.exports = server_events;
|
|
|
|
}
|