diff --git a/crates/matrix-sdk-base/src/client.rs b/crates/matrix-sdk-base/src/client.rs index 66af1ad16..c36bf1746 100644 --- a/crates/matrix-sdk-base/src/client.rs +++ b/crates/matrix-sdk-base/src/client.rs @@ -336,7 +336,7 @@ impl BaseClient { SyncRoomRedactionEvent::Original(r), ), ) => { - room_info.handle_redaction(r); + room_info.handle_redaction(r, event.event.cast_ref()); let raw_event = event.event.clone().cast(); changes.add_redaction(room.room_id(), &r.redacts, raw_event); } diff --git a/crates/matrix-sdk-base/src/rooms/normal.rs b/crates/matrix-sdk-base/src/rooms/normal.rs index ef228db89..290785a66 100644 --- a/crates/matrix-sdk-base/src/rooms/normal.rs +++ b/crates/matrix-sdk-base/src/rooms/normal.rs @@ -23,6 +23,8 @@ use futures_util::stream::{self, StreamExt}; use matrix_sdk_common::deserialized_responses::SyncTimelineEvent; #[cfg(all(feature = "e2e-encryption", feature = "experimental-sliding-sync"))] use matrix_sdk_common::ring_buffer::RingBuffer; +#[cfg(feature = "experimental-sliding-sync")] +use ruma::events::AnySyncTimelineEvent; use ruma::{ api::client::sync::sync_events::v3::RoomSummary as RumaSummary, events::{ @@ -44,11 +46,10 @@ use ruma::{ RoomAccountDataEventType, }, room::RoomType, + serde::Raw, EventId, OwnedEventId, OwnedMxcUri, OwnedRoomAliasId, OwnedRoomId, OwnedUserId, RoomAliasId, RoomId, RoomVersionId, UserId, }; -#[cfg(all(feature = "e2e-encryption", feature = "experimental-sliding-sync"))] -use ruma::{events::AnySyncTimelineEvent, serde::Raw}; use serde::{Deserialize, Serialize}; use tracing::{debug, info, instrument, warn}; @@ -851,7 +852,22 @@ impl RoomInfo { } /// Handle the given redaction. - pub fn handle_redaction(&mut self, event: &OriginalSyncRoomRedactionEvent) { + pub fn handle_redaction( + &mut self, + event: &OriginalSyncRoomRedactionEvent, + _raw: &Raw, + ) { + #[cfg(feature = "experimental-sliding-sync")] + if let Some(latest_event) = &mut self.latest_event { + if latest_event.event_id().as_deref() == Some(&*event.redacts) { + let room_version = self.base_info.room_version().unwrap_or(&RoomVersionId::V1); + match apply_redaction(&latest_event.event, _raw, room_version) { + Some(redacted) => latest_event.event = redacted, + None => self.latest_event = None, + } + } + } + self.base_info.handle_redaction(event); } @@ -982,6 +998,41 @@ impl RoomInfo { } } +#[cfg(feature = "experimental-sliding-sync")] +fn apply_redaction( + event: &Raw, + raw_redaction: &Raw, + room_version: &RoomVersionId, +) -> Option> { + use ruma::canonical_json::redact_in_place; + + let mut event_json = match event.deserialize_as() { + Ok(json) => json, + Err(e) => { + warn!("Failed to deserialize latest event: {e}"); + return None; + } + }; + + let redacted_because = match raw_redaction.try_into() { + Ok(rb) => rb, + Err(e) => { + warn!("Redaction event is not valid canonical JSON: {e}"); + return None; + } + }; + + let redact_result = redact_in_place(&mut event_json, room_version, Some(redacted_because)); + + if let Err(e) = redact_result { + warn!("Failed to redact latest event: {e}"); + return None; + } + + let raw = Raw::new(&event_json).expect("CanonicalJsonObject must be serializable"); + Some(raw.cast()) +} + bitflags! { /// Room state filter as a bitset. /// diff --git a/crates/matrix-sdk-base/src/sliding_sync.rs b/crates/matrix-sdk-base/src/sliding_sync.rs index d3f3c208e..0b30763c0 100644 --- a/crates/matrix-sdk-base/src/sliding_sync.rs +++ b/crates/matrix-sdk-base/src/sliding_sync.rs @@ -452,6 +452,7 @@ mod test { sync::{Arc, RwLock as SyncRwLock}, }; + use assert_matches::assert_matches; use matrix_sdk_common::ring_buffer::RingBuffer; use matrix_sdk_test::async_test; use ruma::{ @@ -462,9 +463,10 @@ mod test { avatar::RoomAvatarEventContent, canonical_alias::RoomCanonicalAliasEventContent, member::{MembershipState, RoomMemberEventContent}, + message::SyncRoomMessageEvent, }, - AnySyncStateEvent, AnySyncTimelineEvent, GlobalAccountDataEventContent, - StateEventContent, + AnySyncMessageLikeEvent, AnySyncStateEvent, AnySyncTimelineEvent, + GlobalAccountDataEventContent, StateEventContent, }, mxc_uri, room_alias_id, room_id, serde::Raw, @@ -765,6 +767,56 @@ mod test { assert_eq!(ev_id(client_room.latest_event()), "$idb"); } + #[async_test] + async fn cached_latest_event_can_be_redacted() { + // Given a logged-in client + let client = logged_in_client().await; + let room_id = room_id!("!r:e.uk"); + let event_a = json!({ + "sender": "@alice:example.com", + "type": "m.room.message", + "event_id": "$ida", + "origin_server_ts": 12344446, + "content": { "body":"A", "msgtype": "m.text" }, + }); + + // When the sliding sync response contains a timeline + let room = room_with_timeline(&[event_a]); + let response = response_with_room(room_id, room).await; + client.process_sliding_sync(&response).await.expect("Failed to process sync"); + + // Then the room holds the latest event + let client_room = client.get_room(room_id).expect("No room found"); + assert_eq!(ev_id(client_room.latest_event()), "$ida"); + + let redaction = json!({ + "sender": "@alice:example.com", + "type": "m.room.redaction", + "event_id": "$idb", + "redacts": "$ida", + "origin_server_ts": 12344448, + "content": {}, + }); + + // When a redaction for that event is received + let room = room_with_timeline(&[redaction]); + let response = response_with_room(room_id, room).await; + client.process_sliding_sync(&response).await.expect("Failed to process sync"); + + // Then the room still holds the latest event + let client_room = client.get_room(room_id).expect("No room found"); + let latest_event = client_room.latest_event().unwrap(); + assert_eq!(latest_event.event_id().unwrap(), "$ida"); + + // But it's now redacted + assert_matches!( + latest_event.event.deserialize().unwrap(), + AnySyncTimelineEvent::MessageLike(AnySyncMessageLikeEvent::RoomMessage( + SyncRoomMessageEvent::Redacted(_) + )) + ); + } + #[test] fn when_no_events_we_dont_cache_any() { let events = &[];