From 406fd011ffaabed2e28c96a0fc42620b7550bb62 Mon Sep 17 00:00:00 2001 From: Benjamin Bouvier Date: Fri, 5 Apr 2024 16:36:36 +0200 Subject: [PATCH] room: extend `event_with_context` so it returns a fully decrypted /context response --- .../matrix-sdk-ui/src/notification_client.rs | 6 +- crates/matrix-sdk/src/room/messages.rs | 37 ++++++++++- crates/matrix-sdk/src/room/mod.rs | 66 +++++++++++++------ 3 files changed, 87 insertions(+), 22 deletions(-) diff --git a/crates/matrix-sdk-ui/src/notification_client.rs b/crates/matrix-sdk-ui/src/notification_client.rs index 86308c9ad..3ece08dee 100644 --- a/crates/matrix-sdk-ui/src/notification_client.rs +++ b/crates/matrix-sdk-ui/src/notification_client.rs @@ -480,8 +480,10 @@ impl NotificationClient { return Err(Error::UnknownRoom); }; - let (mut timeline_event, state_events) = - room.event_with_context(event_id, true).await?.ok_or(Error::ContextMissingEvent)?; + let response = room.event_with_context(event_id, true, uint!(0)).await?; + + let mut timeline_event = response.event.ok_or(Error::ContextMissingEvent)?; + let state_events = response.state; if let Some(decrypted_event) = self.retry_decryption(&room, timeline_event.event.cast_ref()).await? diff --git a/crates/matrix-sdk/src/room/messages.rs b/crates/matrix-sdk/src/room/messages.rs index 50c449228..f1b2200c4 100644 --- a/crates/matrix-sdk/src/room/messages.rs +++ b/crates/matrix-sdk/src/room/messages.rs @@ -121,7 +121,7 @@ impl fmt::Debug for MessagesOptions { } } -/// The result of a `Room::messages` call. +/// The result of a [`super::Room::messages`] call. /// /// In short, this is a possibly decrypted version of the response of a /// `room/messages` api call. @@ -139,3 +139,38 @@ pub struct Messages { /// A list of state events relevant to showing the `chunk`. pub state: Vec>, } + +/// The result of a [`super::Room::event_with_context`] query. +/// +/// This is a wrapper around +/// [`ruma::api::client::context::get_context::v3::Response`], with events +/// decrypted if needs be. +#[derive(Debug)] +pub struct EventWithContextResponse { + /// The event targeted by the /context query. + pub event: Option, + + /// Events before the target event, if a non-zero context size was + /// requested. + /// + /// Like the corresponding Ruma response, these are in reverse chronological + /// order. + pub events_before: Vec, + + /// Events after the target event, if a non-zero context size was requested. + /// + /// Like the corresponding Ruma response, these are in chronological order. + pub events_after: Vec, + + /// Token to paginate backwards, aka "start" token. + pub prev_batch_token: Option, + + /// Token to paginate forwards, aka "end" token. + pub next_batch_token: Option, + + /// State events related to the request. + /// + /// If lazy-loading of members was requested, this may contain room + /// membership events. + pub state: Vec>, +} diff --git a/crates/matrix-sdk/src/room/mod.rs b/crates/matrix-sdk/src/room/mod.rs index b660c6281..18bf2cc61 100644 --- a/crates/matrix-sdk/src/room/mod.rs +++ b/crates/matrix-sdk/src/room/mod.rs @@ -10,7 +10,10 @@ use std::{ use eyeball::SharedObservable; use futures_core::Stream; -use futures_util::stream::FuturesUnordered; +use futures_util::{ + future::{try_join, try_join_all}, + stream::FuturesUnordered, +}; use matrix_sdk_base::{ deserialized_responses::{ RawAnySyncOrStrippedState, RawSyncOrStrippedState, SyncOrStrippedState, TimelineEvent, @@ -65,14 +68,14 @@ use ruma::{ space::{child::SpaceChildEventContent, parent::SpaceParentEventContent}, tag::{TagInfo, TagName}, typing::SyncTypingEvent, - AnyRoomAccountDataEvent, AnyStateEvent, EmptyStateKey, MessageLikeEventContent, + AnyRoomAccountDataEvent, AnyTimelineEvent, EmptyStateKey, MessageLikeEventContent, MessageLikeEventType, RedactContent, RedactedStateEventContent, RoomAccountDataEvent, RoomAccountDataEventContent, RoomAccountDataEventType, StateEventContent, StateEventType, StaticEventContent, StaticStateEventContent, SyncStateEvent, }, push::{Action, PushConditionRoomCtx}, serde::Raw, - uint, EventId, Int, MatrixToUri, MatrixUri, MxcUri, OwnedEventId, OwnedRoomId, OwnedServerName, + EventId, Int, MatrixToUri, MatrixUri, MxcUri, OwnedEventId, OwnedRoomId, OwnedServerName, OwnedTransactionId, OwnedUserId, TransactionId, UInt, UserId, }; use serde::de::DeserializeOwned; @@ -80,7 +83,10 @@ use thiserror::Error; use tokio::sync::broadcast; use tracing::{debug, info, instrument, warn}; -use self::futures::{SendAttachment, SendMessageLikeEvent, SendRawMessageLikeEvent}; +use self::{ + futures::{SendAttachment, SendMessageLikeEvent, SendRawMessageLikeEvent}, + messages::EventWithContextResponse, +}; pub use self::{ member::{RoomMember, RoomMemberRole}, messages::{Messages, MessagesOptions}, @@ -382,11 +388,12 @@ impl Room { &self, event_id: &EventId, lazy_load_members: bool, - ) -> Result>)>> { + context_size: UInt, + ) -> Result { let mut request = context::get_context::v3::Request::new(self.room_id().to_owned(), event_id.to_owned()); - request.limit = uint!(0); + request.limit = context_size; if lazy_load_members { request.filter.lazy_load_options = @@ -395,23 +402,44 @@ impl Room { let response = self.client.send(request, None).await?; - let Some(event) = response.event else { - return Ok(None); + // Decrypts one event, if needs be. + let try_decrypt = |event: Raw| async move { + #[cfg(feature = "e2e-encryption")] + if let Ok(AnySyncTimelineEvent::MessageLike(AnySyncMessageLikeEvent::RoomEncrypted( + SyncMessageLikeEvent::Original(_), + ))) = event.deserialize_as::() + { + if let Ok(event) = self.decrypt_event(event.cast_ref()).await { + return Result::<_, Error>::Ok(event); + } + } + + let push_actions = self.event_push_actions(&event).await?; + + Result::<_, Error>::Ok(TimelineEvent { + event: event.clone(), + encryption_info: None, + push_actions, + }) }; - #[cfg(feature = "e2e-encryption")] - if let Ok(AnySyncTimelineEvent::MessageLike(AnySyncMessageLikeEvent::RoomEncrypted( - SyncMessageLikeEvent::Original(_), - ))) = event.deserialize_as::() - { - if let Ok(event) = self.decrypt_event(event.cast_ref()).await { - return Ok(Some((event, response.state))); - } - } + let target_event = + if let Some(event) = response.event { Some(try_decrypt(event).await?) } else { None }; - let push_actions = self.event_push_actions(&event).await?; + let (events_before, events_after) = try_join( + try_join_all(response.events_before.into_iter().map(try_decrypt)), + try_join_all(response.events_after.into_iter().map(try_decrypt)), + ) + .await?; - Ok(Some((TimelineEvent { event, encryption_info: None, push_actions }, response.state))) + Ok(EventWithContextResponse { + event: target_event, + events_before, + events_after, + state: response.state, + prev_batch_token: response.start, + next_batch_token: response.end, + }) } pub(crate) async fn request_members(&self) -> Result<()> {