2014-01-30 19:25:25 +01:00
|
|
|
var server_events = (function () {
|
|
|
|
|
|
|
|
var exports = {};
|
|
|
|
|
|
|
|
var waiting_on_homeview_load = true;
|
|
|
|
|
|
|
|
var events_stored_during_tutorial = [];
|
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
|
|
|
|
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;
|
|
|
|
|
2014-01-31 06:24:15 +01:00
|
|
|
var clean_event = function clean_event (event) {
|
|
|
|
// 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
|
|
|
});
|
|
|
|
|
|
|
|
if (tutorial.is_running()) {
|
|
|
|
events_stored_during_tutorial = events_stored_during_tutorial.concat(events);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (events_stored_during_tutorial.length > 0) {
|
|
|
|
events = events_stored_during_tutorial.concat(events);
|
|
|
|
events_stored_during_tutorial = [];
|
|
|
|
}
|
|
|
|
|
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 = [];
|
|
|
|
}
|
|
|
|
|
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;
|
|
|
|
if (event.local_message_id !== undefined) {
|
|
|
|
msg.local_id = event.local_message_id;
|
|
|
|
}
|
|
|
|
messages.push(msg);
|
|
|
|
break;
|
|
|
|
case 'pointer':
|
|
|
|
new_pointer = event.pointer;
|
|
|
|
break;
|
|
|
|
case 'restart':
|
|
|
|
reload.initiate({message: "The application has been updated; reloading!"});
|
|
|
|
break;
|
|
|
|
case 'update_message':
|
|
|
|
messages_to_update.push(event);
|
|
|
|
break;
|
2014-02-03 20:14:18 +01:00
|
|
|
case 'realm':
|
|
|
|
if (event.op === 'update' && event.property === 'name') {
|
|
|
|
page_params.realm_name = event.value;
|
|
|
|
notifications.redraw_title();
|
|
|
|
}
|
|
|
|
break;
|
2014-01-30 19:25:25 +01:00
|
|
|
case 'realm_user':
|
|
|
|
if (event.op === 'add') {
|
2014-01-30 22:42:19 +01:00
|
|
|
people.add_in_realm(event.person);
|
2014-01-30 19:25:25 +01:00
|
|
|
} else if (event.op === 'remove') {
|
2014-01-30 22:42:19 +01:00
|
|
|
people.remove(event.person);
|
2014-01-30 19:25:25 +01:00
|
|
|
} else if (event.op === 'update') {
|
2014-01-30 22:42:19 +01:00
|
|
|
people.update(event.person);
|
2014-01-30 19:25:25 +01:00
|
|
|
}
|
|
|
|
break;
|
|
|
|
case 'stream':
|
|
|
|
if (event.op === 'update') {
|
|
|
|
// Legacy: Stream properties are still managed by subs.js on the client side.
|
|
|
|
subs.update_subscription_properties(event.name, event.property, event.value);
|
|
|
|
}
|
|
|
|
break;
|
2014-02-06 21:21:21 +01:00
|
|
|
case 'subscription':
|
2014-01-30 19:25:25 +01:00
|
|
|
if (event.op === 'add') {
|
2014-02-05 22:53:53 +01:00
|
|
|
_.each(event.subscriptions, function (sub) {
|
|
|
|
subs.mark_subscribed(sub.name, sub);
|
2014-01-30 19:25:25 +01:00
|
|
|
});
|
|
|
|
} else if (event.op === 'remove') {
|
2014-02-07 19:06:02 +01:00
|
|
|
_.each(event.subscriptions, function (rec) {
|
|
|
|
var sub = stream_data.get_sub_by_id(rec.stream_id);
|
|
|
|
subs.mark_sub_unsubscribed(sub);
|
2014-01-30 19:25:25 +01:00
|
|
|
});
|
|
|
|
} else if (event.op === 'update') {
|
|
|
|
subs.update_subscription_properties(event.name, event.property, event.value);
|
|
|
|
} else if (event.op === 'peer_add' || event.op === 'peer_remove') {
|
|
|
|
_.each(event.subscriptions, function (sub) {
|
|
|
|
var js_event_type;
|
|
|
|
if (event.op === 'peer_add') {
|
|
|
|
js_event_type = 'peer_subscribe.zulip';
|
|
|
|
|
|
|
|
stream_data.add_subscriber(sub, event.user_email);
|
|
|
|
} else if (event.op === 'peer_remove') {
|
|
|
|
js_event_type = 'peer_unsubscribe.zulip';
|
|
|
|
|
|
|
|
stream_data.remove_subscriber(sub, event.user_email);
|
|
|
|
}
|
|
|
|
|
|
|
|
$(document).trigger(js_event_type, {stream_name: sub,
|
|
|
|
user_email: event.user_email});
|
|
|
|
});
|
|
|
|
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
case 'presence':
|
|
|
|
var users = {};
|
|
|
|
users[event.email] = event.presence;
|
|
|
|
activity.set_user_statuses(users, event.server_timestamp);
|
|
|
|
break;
|
|
|
|
case 'update_message_flags':
|
|
|
|
var new_value = event.operation === "add";
|
|
|
|
switch(event.flag) {
|
|
|
|
case 'starred':
|
|
|
|
_.each(event.messages, function (message_id) {
|
|
|
|
ui.update_starred(message_id, new_value);
|
|
|
|
});
|
|
|
|
break;
|
|
|
|
case 'read':
|
|
|
|
var msgs_to_update = _.map(event.messages, function (message_id) {
|
2014-01-31 22:02:57 +01:00
|
|
|
return message_store.get(message_id);
|
2014-01-30 19:25:25 +01:00
|
|
|
});
|
2014-01-31 17:44:21 +01:00
|
|
|
unread.mark_messages_as_read(msgs_to_update, {from: "server"});
|
2014-01-30 19:25:25 +01:00
|
|
|
break;
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
case 'referral':
|
|
|
|
referral.update_state(event.referrals.granted, event.referrals.used);
|
|
|
|
break;
|
|
|
|
case 'realm_emoji':
|
|
|
|
emoji.update_emojis(event.realm_emoji);
|
|
|
|
break;
|
|
|
|
case 'alert_words':
|
|
|
|
alert_words.words = event.alert_words;
|
|
|
|
break;
|
|
|
|
case 'muted_topics':
|
|
|
|
muting_ui.handle_updates(event.muted_topics);
|
|
|
|
break;
|
|
|
|
case 'realm_filters':
|
|
|
|
page_params.realm_filters = event.realm_filters;
|
|
|
|
echo.set_realm_filters(page_params.realm_filters);
|
|
|
|
break;
|
|
|
|
}
|
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) {
|
2014-01-31 06:24:15 +01:00
|
|
|
try {
|
|
|
|
messages = echo.process_from_server(messages);
|
|
|
|
message_store.insert_new_messages(messages);
|
|
|
|
} 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
|
|
|
|
&& new_pointer > furthest_read)
|
|
|
|
{
|
|
|
|
furthest_read = new_pointer;
|
|
|
|
server_furthest_read = new_pointer;
|
|
|
|
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 {
|
|
|
|
message_store.update_messages(messages_to_update);
|
|
|
|
} 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);
|
|
|
|
|
2014-01-30 21:01:52 +01:00
|
|
|
get_events_params.dont_block = options.dont_block || get_events_failures > 0;
|
|
|
|
if (get_events_params.queue_id === undefined) {
|
|
|
|
get_events_params.queue_id = page_params.event_queue_id;
|
|
|
|
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
|
|
|
}
|
2014-01-30 20:29:00 +01:00
|
|
|
get_events_timeout = undefined;
|
|
|
|
get_events_xhr = channel.post({
|
2014-01-30 19:25:25 +01:00
|
|
|
url: '/json/get_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) {
|
2014-01-31 06:24:15 +01:00
|
|
|
try {
|
|
|
|
get_events_xhr = undefined;
|
|
|
|
get_events_failures = 0;
|
|
|
|
$('#connection-error').hide();
|
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
|
|
|
},
|
|
|
|
error: function (xhr, error_type, exn) {
|
2014-01-31 06:24:15 +01:00
|
|
|
try {
|
|
|
|
get_events_xhr = undefined;
|
|
|
|
// If we are old enough to have messages outside of the
|
|
|
|
// Tornado cache or if we're old enough that our message
|
|
|
|
// queue has been garbage collected, immediately reload.
|
|
|
|
if ((xhr.status === 400) &&
|
|
|
|
($.parseJSON(xhr.responseText).msg.indexOf("too old") !== -1 ||
|
|
|
|
$.parseJSON(xhr.responseText).msg.indexOf("Bad event queue id") !== -1)) {
|
|
|
|
page_params.event_queue_expired = true;
|
|
|
|
reload.initiate({immediate: true});
|
|
|
|
}
|
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;
|
|
|
|
$('#connection-error').hide();
|
|
|
|
} 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) {
|
|
|
|
$('#connection-error').show();
|
|
|
|
} else {
|
|
|
|
$('#connection-error').hide();
|
|
|
|
}
|
|
|
|
} 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);
|
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");
|
|
|
|
};
|
|
|
|
|
|
|
|
var watchdog_time = $.now();
|
|
|
|
setInterval(function () {
|
|
|
|
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;
|
|
|
|
}, 5000);
|
|
|
|
|
|
|
|
$(function () {
|
|
|
|
$(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();
|
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;
|
|
|
|
}
|
|
|
|
channel.del({
|
|
|
|
url: '/json/events',
|
|
|
|
data: {queue_id: page_params.event_queue_id}
|
|
|
|
});
|
2014-02-05 19:49:18 +01:00
|
|
|
};
|
2014-01-30 19:25:25 +01:00
|
|
|
|
|
|
|
window.addEventListener("beforeunload", function (event) {
|
2014-02-05 19:49:18 +01:00
|
|
|
exports.cleanup_event_queue();
|
2014-01-30 19:25:25 +01:00
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
return exports;
|
|
|
|
|
|
|
|
}());
|
|
|
|
if (typeof module !== 'undefined') {
|
|
|
|
module.exports = server_events;
|
|
|
|
}
|