Files
zulip/static/js/presence.js
Steve Howell 14396c3e32 presence: Prep for upcoming changes to server data.
In the next commit we're going to change what the
server sends for the following:

    - page_params
    - server responses to /json/users/me/presence

We will **not** yet be changing the format of the data
that we get in events when users update their presence.

It's also just a bit in flux what our final formats
will be for various presence payloads, and different
optimizations may lead us to use different data
structures in different payloads.

So for now we decouple these two things:

    raw_info: this is intended to represent a
        snapshot of the latest data from the
        server, including some data like
        timestamps that are only used
        in downstream calculations and not
        user-facing

    exports.presence_info: this is calculated
        info for modules like buddy_data that
        just need to know active vs. idle and
        last_active_date

Another change that happens here is we rename
set_info_for_user to update_info_for_event,
which just makes it clear that the function
expects data in the "event" format (as opposed
to the format for page_params or server
responses).

As of now keeping the intermediate raw_info data
around feels slightly awkward, because we just
immediately calculate presence_info for any kind
of update.  This may be sorta surprising if you
just skim the code and see the various timeout
constants.  You would think we might be automatically
expiring "active" statuses in the client due to
the simple passage of time, but in fact the precise
places we do this are all triggered by new data
from the server and we re-calculate statuses
immediately.

(There are indirect ways that clients
have timing logic, since they ask the
server for new data at various intervals, but a
smarter client could simply expire users on its
own, or at least with a more efficient transfer
of info between it and the server. One of
the thing that complicates client-side logic
is that server and client clocks may be out
of sync.  Also, it's not inherently super expensive
to get updates from the server.)
2020-02-10 14:37:44 -08:00

175 lines
5.2 KiB
JavaScript

// This module just manages data. See activity.js for
// the UI of our buddy list.
// The following Maps have user_id as the key. Some of the
// user_ids may not yet be registered in people.js.
// See the long comment in `set_info` below for details.
// In future commits we'll use raw_info to facilitate
// handling server events and/or timeout events.
const raw_info = new Map();
exports.presence_info = new Map();
/* Mark users as offline after 140 seconds since their last checkin,
* Keep in sync with zerver/tornado/event_queue.py:receiver_is_idle
*/
const OFFLINE_THRESHOLD_SECS = 140;
const BIG_REALM_COUNT = 250;
exports.is_active = function (user_id) {
if (exports.presence_info.has(user_id)) {
const status = exports.presence_info.get(user_id).status;
if (status === "active") {
return true;
}
}
return false;
};
exports.get_status = function (user_id) {
if (people.is_my_user_id(user_id)) {
return "active";
}
if (exports.presence_info.has(user_id)) {
return exports.presence_info.get(user_id).status;
}
return "offline";
};
exports.get_user_ids = function () {
return Array.from(exports.presence_info.keys());
};
function status_from_timestamp(baseline_time, info) {
let status = 'offline';
let last_active = 0;
for (const [device, device_presence] of Object.entries(info)) {
const age = baseline_time - device_presence.timestamp;
if (last_active < device_presence.timestamp) {
last_active = device_presence.timestamp;
}
if (age < OFFLINE_THRESHOLD_SECS) {
switch (device_presence.status) {
case 'active':
status = device_presence.status;
break;
case 'idle':
if (status !== 'active') {
status = device_presence.status;
}
break;
case 'offline':
if (status !== 'active' && status !== 'idle') {
status = device_presence.status;
}
break;
default:
blueslip.error('Unexpected status', {presence_object: device_presence, device: device}, undefined);
}
}
}
return {status: status,
last_active: last_active };
}
// For testing
exports._status_from_timestamp = status_from_timestamp;
exports.update_info_from_event = function (user_id, info, server_time) {
raw_info.set(user_id, {
info: info,
server_time: server_time,
});
const status = status_from_timestamp(server_time, info);
exports.presence_info.set(user_id, status);
};
exports.set_info = function (presences, server_timestamp) {
raw_info.clear();
exports.presence_info.clear();
for (const [user_id_str, info] of Object.entries(presences)) {
const user_id = parseInt(user_id_str, 10);
// Note: In contrast with essentially every other piece of
// state updates we receive from the server, precense updates
// are pulled independently from server_events_dispatch.js.
//
// This means that if we're coming back from offline and new
// users were created in the meantime, we'll be populating
// exports.presence_info with user IDs not yet present in
// people.js. This is safe because we when we build the
// buddy list, we only process user_ids that are in people.js
// (because we need their name, etc.).
raw_info.set(user_id, {
info: info,
server_time: server_timestamp,
});
const status = status_from_timestamp(server_timestamp,
info);
exports.presence_info.set(user_id, status);
}
exports.update_info_for_small_realm();
};
exports.update_info_for_small_realm = function () {
if (people.get_realm_count() >= BIG_REALM_COUNT) {
// For big realms, we don't want to bloat our buddy
// lists with lots of long-time-inactive users.
return;
}
// For small realms, we create presence info for users
// that the server didn't include in its presence update.
const persons = people.get_realm_persons();
for (const person of persons) {
const user_id = person.user_id;
let status = "offline";
if (exports.presence_info.has(user_id)) {
// this is normal, we have data for active
// users that we don't want to clobber.
continue;
}
if (person.is_bot) {
// we don't show presence for bots
continue;
}
if (people.is_my_user_id(user_id)) {
status = "active";
}
exports.presence_info.set(user_id, {
status: status,
last_active: undefined,
});
}
};
exports.last_active_date = function (user_id) {
const info = exports.presence_info.get(user_id);
if (!info || !info.last_active) {
return;
}
const date = new XDate(info.last_active * 1000);
return date;
};
exports.initialize = function () {
presence.set_info(page_params.presences,
page_params.initial_servertime);
delete page_params.presences;
};
window.presence = exports;