mirror of
https://github.com/zulip/zulip.git
synced 2025-10-30 11:33:51 +00:00
ES and TypeScript modules are strict by default and don’t need this directive. ESLint will remind us to add it to new CommonJS files and remove it from ES and TypeScript modules. Signed-off-by: Anders Kaseorg <anders@zulip.com>
330 lines
11 KiB
JavaScript
330 lines
11 KiB
JavaScript
"use strict";
|
|
|
|
const _ = require("lodash");
|
|
// Docs: https://zulip.readthedocs.io/en/latest/subsystems/events-system.html
|
|
|
|
let waiting_on_homeview_load = true;
|
|
|
|
let events_stored_while_loading = [];
|
|
|
|
let get_events_xhr;
|
|
let get_events_timeout;
|
|
let get_events_failures = 0;
|
|
const get_events_params = {};
|
|
|
|
// 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;
|
|
|
|
function get_events_success(events) {
|
|
let messages = [];
|
|
const update_message_events = [];
|
|
const post_message_events = [];
|
|
|
|
const clean_event = function clean_event(event) {
|
|
// Only log a whitelist of the event to remove private data
|
|
return _.pick(event, "id", "type", "op");
|
|
};
|
|
|
|
for (const event of events) {
|
|
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);
|
|
}
|
|
}
|
|
|
|
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 = [];
|
|
}
|
|
|
|
// 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.
|
|
const dispatch_event = function dispatch_event(event) {
|
|
switch (event.type) {
|
|
case "message": {
|
|
const msg = event.message;
|
|
msg.flags = event.flags;
|
|
if (event.local_message_id) {
|
|
msg.local_id = event.local_message_id;
|
|
sent_messages.report_event_received(event.local_message_id);
|
|
}
|
|
messages.push(msg);
|
|
break;
|
|
}
|
|
|
|
case "update_message":
|
|
update_message_events.push(event);
|
|
break;
|
|
|
|
case "delete_message":
|
|
case "submessage":
|
|
case "update_message_flags":
|
|
post_message_events.push(event);
|
|
break;
|
|
|
|
default:
|
|
return server_events_dispatch.dispatch_normal_event(event);
|
|
}
|
|
};
|
|
|
|
for (const event of events) {
|
|
try {
|
|
dispatch_event(event);
|
|
} catch (ex1) {
|
|
blueslip.error(
|
|
"Failed to process an event\n" + blueslip.exception_msg(ex1),
|
|
{event: clean_event(event)},
|
|
ex1.stack,
|
|
);
|
|
}
|
|
}
|
|
|
|
if (messages.length !== 0) {
|
|
// 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");
|
|
try {
|
|
messages = echo.process_from_server(messages);
|
|
if (messages.length > 0) {
|
|
messages.forEach(message_store.set_message_booleans);
|
|
|
|
const sent_by_this_client = messages.some((msg) =>
|
|
sent_messages.messages.has(msg.local_id),
|
|
);
|
|
// If some message in this batch of events was sent by this
|
|
// client, almost every time, this message will be the only one
|
|
// in messages, because multiple messages being returned by
|
|
// get_events usually only happens when a client is offline.
|
|
// But in any case, insert_new_messages handles multiple
|
|
// messages, only one of which was sent by this client,
|
|
// correctly.
|
|
|
|
message_events.insert_new_messages(messages, sent_by_this_client);
|
|
}
|
|
} catch (ex2) {
|
|
blueslip.error(
|
|
"Failed to insert new messages\n" + blueslip.exception_msg(ex2),
|
|
undefined,
|
|
ex2.stack,
|
|
);
|
|
}
|
|
}
|
|
|
|
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 (update_message_events.length !== 0) {
|
|
try {
|
|
message_events.update_messages(update_message_events);
|
|
} catch (ex3) {
|
|
blueslip.error(
|
|
"Failed to update messages\n" + blueslip.exception_msg(ex3),
|
|
undefined,
|
|
ex3.stack,
|
|
);
|
|
}
|
|
}
|
|
|
|
// We do things like updating message flags and deleting messages last,
|
|
// to avoid ordering issues that are caused by batch handling of
|
|
// messages above.
|
|
for (const event of post_message_events) {
|
|
server_events_dispatch.dispatch_normal_event(event);
|
|
}
|
|
}
|
|
|
|
function show_ui_connection_error() {
|
|
ui_report.show_error($("#connection-error"));
|
|
$("#connection-error").addClass("get-events-error");
|
|
}
|
|
|
|
function hide_ui_connection_error() {
|
|
ui_report.hide_error($("#connection-error"));
|
|
$("#connection-error").removeClass("get-events-error");
|
|
}
|
|
|
|
function get_events(options) {
|
|
options = {dont_block: false, ...options};
|
|
|
|
if (reload_state.is_in_progress()) {
|
|
return;
|
|
}
|
|
|
|
get_events_params.dont_block = options.dont_block || get_events_failures > 0;
|
|
|
|
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;
|
|
}
|
|
if (get_events_params.queue_id === undefined) {
|
|
get_events_params.queue_id = page_params.queue_id;
|
|
get_events_params.last_event_id = page_params.last_event_id;
|
|
}
|
|
|
|
if (get_events_xhr !== undefined) {
|
|
get_events_xhr.abort();
|
|
}
|
|
if (get_events_timeout !== undefined) {
|
|
clearTimeout(get_events_timeout);
|
|
}
|
|
|
|
get_events_params.client_gravatar = true;
|
|
get_events_params.slim_presence = true;
|
|
|
|
get_events_timeout = undefined;
|
|
get_events_xhr = channel.get({
|
|
url: "/json/events",
|
|
data: get_events_params,
|
|
idempotent: true,
|
|
timeout: page_params.poll_timeout,
|
|
success(data) {
|
|
exports.suspect_offline = false;
|
|
try {
|
|
get_events_xhr = undefined;
|
|
get_events_failures = 0;
|
|
hide_ui_connection_error();
|
|
|
|
get_events_success(data.events);
|
|
} catch (ex) {
|
|
blueslip.error(
|
|
"Failed to handle get_events success\n" + blueslip.exception_msg(ex),
|
|
undefined,
|
|
ex.stack,
|
|
);
|
|
}
|
|
get_events_timeout = setTimeout(get_events, 0);
|
|
},
|
|
error(xhr, error_type) {
|
|
try {
|
|
get_events_xhr = undefined;
|
|
// If we're old enough that our message queue has been
|
|
// garbage collected, immediately reload.
|
|
if (
|
|
xhr.status === 400 &&
|
|
JSON.parse(xhr.responseText).code === "BAD_EVENT_QUEUE_ID"
|
|
) {
|
|
page_params.event_queue_expired = true;
|
|
reload.initiate({
|
|
immediate: true,
|
|
save_pointer: false,
|
|
save_narrow: true,
|
|
save_compose: true,
|
|
});
|
|
}
|
|
|
|
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;
|
|
hide_ui_connection_error();
|
|
} else {
|
|
get_events_failures += 1;
|
|
}
|
|
|
|
if (get_events_failures >= 5) {
|
|
show_ui_connection_error();
|
|
} else {
|
|
hide_ui_connection_error();
|
|
}
|
|
} catch (ex) {
|
|
blueslip.error(
|
|
"Failed to handle get_events error\n" + blueslip.exception_msg(ex),
|
|
undefined,
|
|
ex.stack,
|
|
);
|
|
}
|
|
const retry_sec = Math.min(90, Math.exp(get_events_failures / 2));
|
|
get_events_timeout = setTimeout(get_events, retry_sec * 1000);
|
|
},
|
|
});
|
|
}
|
|
|
|
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});
|
|
blueslip.error(error_message);
|
|
}
|
|
};
|
|
|
|
exports.restart_get_events = function restart_get_events(options) {
|
|
get_events(options);
|
|
};
|
|
|
|
exports.force_get_events = function force_get_events() {
|
|
get_events_timeout = setTimeout(get_events, 0);
|
|
};
|
|
|
|
exports.home_view_loaded = function home_view_loaded() {
|
|
waiting_on_homeview_load = false;
|
|
get_events_success([]);
|
|
$(document).trigger("home_view_loaded.zulip");
|
|
};
|
|
|
|
let watchdog_time = $.now();
|
|
exports.check_for_unsuspend = function () {
|
|
const 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;
|
|
};
|
|
setInterval(exports.check_for_unsuspend, 5000);
|
|
|
|
exports.initialize = function () {
|
|
$(document).on("unsuspend", () => {
|
|
// 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});
|
|
});
|
|
get_events();
|
|
};
|
|
|
|
exports.cleanup_event_queue = function cleanup_event_queue() {
|
|
// Submit a request to the server to cleanup our event queue
|
|
if (page_params.event_queue_expired === true) {
|
|
return;
|
|
}
|
|
blueslip.log("Cleaning up our event queue");
|
|
// Set expired because in a reload we may be called twice.
|
|
page_params.event_queue_expired = true;
|
|
channel.del({
|
|
url: "/json/events",
|
|
data: {queue_id: page_params.queue_id},
|
|
ignore_reload: true,
|
|
});
|
|
};
|
|
|
|
window.addEventListener("beforeunload", () => {
|
|
exports.cleanup_event_queue();
|
|
});
|
|
|
|
// For unit testing
|
|
exports._get_events_success = get_events_success;
|
|
|
|
window.server_events = exports;
|