Files
zulip/static/js/peer_data.js
Steve Howell e243af531b refactor: Extract get_user_set in peer_data.
We now use the same code in all places to
get the bucket of user_ids that correspond
to a stream, and we consistently treat
a stream as having zero subscribers, not
an undefined number of subscribers, in
the hypothetical case of us asking about
a stream that we're not tracking.

The behavior for untracked streams has
always been problematic, since if a
stream is untracked, all bets are off.

So now if we don't "track" the stream,
the subscriber count is zero.  None of
our callers distinguish between undefined
and zero.

And we just consider the stream to be subscribed
by a user when add_subscriber is called,
even if we haven't been told by stream_data
to track the stream.  (We also stop
returning true/false from add_subscriber,
since only test code was looking at it.)

We protect against the most likely source
of internal-to-the-frontend bugs by adding
the assert_number() call.

We generally have to assume that the server
is sending us sensible data at page load
time, or all bets are off.

And we have good protections in place
for unknown ids in our dispatch code
for peer_add/peer_remove events.
2021-01-29 15:21:07 -08:00

152 lines
4.6 KiB
JavaScript

const {LazySet} = require("./lazy_set");
const people = require("./people");
// This maps a stream_id to a LazySet of user_ids who are subscribed.
// We maintain the invariant that this has keys for all all stream_ids
// that we track in the other data structures. We intialize it during
// clear_subscriptions.
let stream_subscribers;
function assert_number(id) {
if (typeof id !== "number") {
blueslip.error(`You must pass ids as numbers to peer_data. id = ${id}`);
}
}
function get_user_set(stream_id) {
assert_number(stream_id);
// This is an internal function to get the LazySet of users.
// We create one on the fly as necessary, but we warn in that case.
let subscribers = stream_subscribers.get(stream_id);
if (subscribers === undefined) {
blueslip.warn("We called get_user_set for an untracked stream: " + stream_id);
subscribers = new LazySet([]);
stream_subscribers.set(stream_id, subscribers);
}
return subscribers;
}
export function clear() {
stream_subscribers = new Map();
}
export function maybe_clear_subscribers(stream_id) {
if (!stream_subscribers.has(stream_id)) {
set_subscribers(stream_id, []);
}
}
export function is_subscriber_subset(stream_id1, stream_id2) {
const sub1_set = get_user_set(stream_id1);
const sub2_set = get_user_set(stream_id2);
return Array.from(sub1_set.keys()).every((key) => sub2_set.has(key));
}
export function potential_subscribers(stream_id) {
/*
This is a list of unsubscribed users
for the current stream, who the current
user could potentially subscribe to the
stream. This may include some bots.
We currently use it for typeahead in
stream_edit.js.
This may be a superset of the actual
subscribers that you can change in some cases
(like if you're a guest?); we should refine this
going forward, especially if we use it for something
other than typeahead. (The guest use case
may be moot now for other reasons.)
*/
const subscribers = stream_subscribers.get(stream_id);
function is_potential_subscriber(person) {
// Use verbose style to force better test
// coverage, plus we may add more conditions over
// time.
if (subscribers.has(person.user_id)) {
return false;
}
return true;
}
return people.filter_all_users(is_potential_subscriber);
}
export function get_subscriber_count(stream_id) {
const subscribers = get_user_set(stream_id);
return subscribers.size;
}
export function get_subscribers(stream_id) {
// This is our external interface for callers who just
// want an array of user_ids who are subscribed to a stream.
const subscribers = get_user_set(stream_id);
return Array.from(subscribers.keys());
}
export function set_subscribers(stream_id, user_ids) {
const subscribers = new LazySet(user_ids || []);
stream_subscribers.set(stream_id, subscribers);
return subscribers;
}
export function add_subscriber(stream_id, user_id) {
// If stream_id/user_id are unknown to us, we will
// still track it, but we will warn.
const subscribers = get_user_set(stream_id);
const person = people.get_by_user_id(user_id);
if (person === undefined) {
blueslip.warn("We tried to add invalid subscriber: " + user_id);
}
subscribers.add(user_id);
}
export function remove_subscriber(stream_id, user_id) {
const subscribers = get_user_set(stream_id);
if (!subscribers.has(user_id)) {
blueslip.warn("We tried to remove invalid subscriber: " + user_id);
return false;
}
subscribers.delete(user_id);
return true;
}
export function bulk_add_subscribers({stream_ids, user_ids}) {
// We rely on our callers to validate stream_ids and user_ids.
for (const stream_id of stream_ids) {
const subscribers = get_user_set(stream_id);
for (const user_id of user_ids) {
subscribers.add(user_id);
}
}
}
export function bulk_remove_subscribers({stream_ids, user_ids}) {
// We rely on our callers to validate stream_ids and user_ids.
for (const stream_id of stream_ids) {
const subscribers = get_user_set(stream_id);
for (const user_id of user_ids) {
subscribers.delete(user_id);
}
}
}
export function is_user_subscribed(stream_id, user_id) {
// Most callers should call stream_data.is_user_subscribed,
// which does additional checks.
const subscribers = get_user_set(stream_id);
return subscribers.has(user_id);
}