-
Notifications
You must be signed in to change notification settings - Fork 270
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Process rooms' read status client-side #2953
Conversation
12b2391
to
afe3dbf
Compare
Codecov ReportAttention:
Additional details and impacted files@@ Coverage Diff @@
## main #2953 +/- ##
==========================================
- Coverage 83.50% 83.28% -0.22%
==========================================
Files 221 222 +1
Lines 23007 23123 +116
==========================================
+ Hits 19211 19258 +47
- Misses 3796 3865 +69 ☔ View full report in Codecov by Sentry. |
On Android this doesn't seem to work properly:
Then I tap on it, open the room, read everything (a few read receipts should have been sent). I go back to the room list, and I still see the unread notification indicator, a new log appears but the unread and highlighted count stays the same (7, 14). Restarting the app also has no effect. |
As soon as I sent the message above, I tried again sending a message with no mention, then opening the room in EXA and it's working fine now 🤷♂️ . I have another room with the unread indicator and the values stuck, maybe it needs a new message first to be unstuck? |
afe3dbf
to
68ee9d6
Compare
The new implementation with |
68ee9d6
to
fb763e3
Compare
…te fields of `RoomInfo`
fb763e3
to
5602e8c
Compare
…pt state Before this patch, we needed to clone the inner `timeline_queue` and turn it into a concrete `Vec<SyncTimelineEvent>`, just to iterate on the elements, and because returning an iterator from a trait method is impractical. This now changes it to return the actual concrete type of `timeline_queue`, so we don't need the extra allocations. Ideally, matrix-sdk and matrix-sdk-base would be merged, so we don't need to use a trait at all here.
This helps supporting cases where we want to show that a room has some activity (unread messages) but no notifications.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I've added several comments, and I reckon their need to be addressed. The fundamental logic behind this PR looks valid to me, good job. Also thank you for writing atomic commits, it's helped a lot!
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Looks good. I've mentioned some limitations in my comments, and here are a couple more:
-
In matrix-js-sdk, we generate a "synthetic" receipt when we send a message (or receive a message we sent from another client), meaning we consider everything before that message to be read. I don't see that here, but I might have missed it.
-
This will need more complex data structures if/when we support threads.
// Find a private or public read receipt for the current user. | ||
let mut receipt_event_id = None; | ||
if let Some((event_id, receipt)) = | ||
receipt_event.user_receipt(user_id, ReceiptType::ReadPrivate) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This code always picks the private receipt if it is present, but the spec says:
Between m.read and m.read.private, the receipt which is more “ahead” or “recent” is used when determining the highest read-up-to mark
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Pondered about this a bit, and what we get from the read receipts extension of sliding sync is a mapping of room id -> event id -> user id -> receipt. So to know which receipt is "ahead" in terms of sync order, we'd need to iterate over the events (with the same caveat that they could be from this sync, or a previous one, or a future one).
Would using the time of the receipt be good enough to compare them? (it's the read event timestamp, if I'm not mistaken, so that's forgeable since it's user-defined, but I don't see any big threat here)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
As I tried to describe here it's tricky to decide the order of messages, and it does matter. If you disagree with the server over which message is first, the server will discard the receipts you send (if it think it already has a later one) and you will end up in an inconsistent state between server and client, potentially causing stuck unreads.
The ts
in the receipt is the time at which it was created (I am fairly sure?), so it doesn't help us know which events are read. Even if it's the ts
of the read event, it still might disagree with the server's idea of what is first.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If you disagree with the server over which message is first, the server will discard the receipts you send (if it think it already has a later one) and you will end up in an inconsistent state between server and client, potentially causing stuck unreads.
Thanks, that's new information 🥲
// about. | ||
|
||
// First, save the event id as the latest one that has a read receipt. | ||
room_info.read_receipts.latest_read_receipt_event_id = Some(receipt_event_id.clone()); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This unconditionally saves a receipt we receive as the latest one. I don't know whether this can happen, but if we receive a receipt out of order, we may end up with incorrect unreadness.
Knowing which receipt is latest is particularly problematic, because we have to find the events they refer to, and then decide which of those is "latest".
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
(I assume this can happen, and, to be explicit, I think the correct behaviour is to figure out which is latest and keep only that one.)
In matrix-js-sdk, we hold on to "dangling" receipts for which we don't have an event, so that when/if we get the event, we can use that to decide whether the receipt is after the one we already have.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Very good point. Well it'll be for a later iteration of this PR I suppose 🥴
- copyright notice - doc comments and better doc in general - use static dispatch instead of &dyn T - other misc comments
For what it's worth, I consider this a different feature request. We do have the concept of an implicit read receipt when we send a message, but that's only client-side (no read marker/receipt is sent to the server in that case). We can tackle that later. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
All feedback have been addressed. There is room (ha. ha.) for improvements, but let's address that in another PR.
/// update the notification count in the room. | ||
/// | ||
/// Returns a boolean indicating if it's found the event and updated the count. | ||
fn find_and_count_events<'a>( |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
OK
Tested with element-hq/element-x-android#2080 and it's working fine. Sometimes the mention/badge does not vanish when I open the room and go back to the room list, but I guess it's not a blocker for today. |
d8bd5e4
to
da1c2da
Compare
The test fails only in the codecov build, not in a local build or in the other integration test. Needs further investigation.
da1c2da
to
85ddfb0
Compare
This adds support for processing the number of unread messages and mentions (highlights) client-side. This can be used to know whether a room has unread content or not (if any count > 0), or if there are mentions (highlights > 0) for the user in a given room. Or, it can also be used as a high-level approximation of the number of unread messages/mentions for recently visited rooms.
This implementation is not final, but attempts to be useful in the short-term. It's relatively simple. It is an improvement over the status quo, in that it can handle events from encrypted rooms, and it has a short-term memory to reconcile read receipts received at a different time than the events they relate to.
Some implementation notes:
UnreadNotificationCounts
which returns information as computed by the server. While it's reliable for non-encrypted rooms, it is not for encrypted rooms. A previous version of this PR overrode that, but it was indicated that this field was still useful to have per se, so it's untouched in this new version.RoomInfo
gets a new fieldReadReceipts
. This is used to contain both the event id to which attaches the latest read receipt we've observed, and to contain the number of unread messages and highlights. This is only computed for sliding sync, but with some effort we could port it over to regular sync v2 as well. Thenum_unread_messages
field reflects the number of unread messages, whilenum_unread_mentions
reflects the number of highlights, according to push rules. Note: this relies on push rules being available and the push context being properly computed, and mentions won't work properly if any of these is missing.timeline_queue
in-memory cache, that's partially persisted to disk), and reconciling a read receipt against those. It can also handle read-receipts for events that come in future syncs (which is possible because: federation 🥳), since it's possible to save the receipt as soon as it's seen, and reconcile it later.There's an integration test that was used to ensure that the approach is viable. There are units tests for determining interesting events that would mark a room as unread.