@@ -539,9 +539,13 @@ impl RoomEventCacheInner {
539539mod private {
540540 use std:: sync:: Arc ;
541541
542- use matrix_sdk_base:: event_cache:: store:: EventCacheStoreLock ;
542+ use matrix_sdk_base:: {
543+ deserialized_responses:: { SyncTimelineEvent , TimelineEventKind } ,
544+ event_cache:: store:: EventCacheStoreLock ,
545+ linked_chunk:: Update ,
546+ } ;
543547 use once_cell:: sync:: OnceCell ;
544- use ruma:: OwnedRoomId ;
548+ use ruma:: { serde :: Raw , OwnedRoomId } ;
545549
546550 use super :: events:: RoomEvents ;
547551 use crate :: event_cache:: EventCacheError ;
@@ -587,13 +591,72 @@ mod private {
587591 Ok ( Self { room, store, events, waited_for_initial_prev_token : false } )
588592 }
589593
594+ /// Removes the bundled relations from an event, if they were present.
595+ ///
596+ /// Only replaces the present if it contained bundled relations.
597+ fn strip_relations_if_present < T > ( event : & mut Raw < T > ) {
598+ // We're going to get rid of the `unsigned`/`m.relations` field, if it's
599+ // present.
600+ // Use a closure that returns an option so we can quickly short-circuit.
601+ let mut closure = || -> Option < ( ) > {
602+ let mut val: serde_json:: Value = event. deserialize_as ( ) . ok ( ) ?;
603+ let unsigned = val. get_mut ( "unsigned" ) ?;
604+ let unsigned_obj = unsigned. as_object_mut ( ) ?;
605+ if unsigned_obj. remove ( "m.relations" ) . is_some ( ) {
606+ * event = Raw :: new ( & val) . ok ( ) ?. cast ( ) ;
607+ }
608+ None
609+ } ;
610+ let _ = closure ( ) ;
611+ }
612+
613+ /// Strips the bundled relations from a collection of events.
614+ fn strip_relations_from_events ( items : & mut [ SyncTimelineEvent ] ) {
615+ for ev in items. iter_mut ( ) {
616+ match & mut ev. kind {
617+ TimelineEventKind :: Decrypted ( decrypted) => {
618+ // Remove all information about encryption info for
619+ // the bundled events.
620+ decrypted. unsigned_encryption_info = None ;
621+
622+ // Remove the `unsigned`/`m.relations` field, if needs be.
623+ Self :: strip_relations_if_present ( & mut decrypted. event ) ;
624+ }
625+
626+ TimelineEventKind :: UnableToDecrypt { event, .. }
627+ | TimelineEventKind :: PlainText { event } => {
628+ Self :: strip_relations_if_present ( event) ;
629+ }
630+ }
631+ }
632+ }
633+
590634 /// Propagate changes to the underlying storage.
591635 async fn propagate_changes ( & mut self ) -> Result < ( ) , EventCacheError > {
592- let updates = self . events . updates ( ) . take ( ) ;
636+ let mut updates = self . events . updates ( ) . take ( ) ;
593637
594638 if !updates. is_empty ( ) {
595639 if let Some ( store) = self . store . get ( ) {
596640 let locked = store. lock ( ) . await ?;
641+
642+ // Strip relations from the `PushItems` updates.
643+ for up in updates. iter_mut ( ) {
644+ match up {
645+ Update :: PushItems { items, .. } => {
646+ Self :: strip_relations_from_events ( items)
647+ }
648+ // Other update kinds don't involve adding new events.
649+ Update :: NewItemsChunk { .. }
650+ | Update :: NewGapChunk { .. }
651+ | Update :: RemoveChunk ( _)
652+ | Update :: RemoveItem { .. }
653+ | Update :: DetachLastItems { .. }
654+ | Update :: StartReattachItems
655+ | Update :: EndReattachItems
656+ | Update :: Clear => { }
657+ }
658+ }
659+
597660 locked. handle_linked_chunk_updates ( & self . room , updates) . await ?;
598661 }
599662 }
@@ -959,6 +1022,85 @@ mod tests {
9591022 assert ! ( chunks. next( ) . is_none( ) ) ;
9601023 }
9611024
1025+ #[ cfg( not( target_arch = "wasm32" ) ) ] // This uses the cross-process lock, so needs time support.
1026+ #[ async_test]
1027+ async fn test_write_to_storage_strips_bundled_relations ( ) {
1028+ use ruma:: events:: BundledMessageLikeRelations ;
1029+
1030+ let room_id = room_id ! ( "!galette:saucisse.bzh" ) ;
1031+ let f = EventFactory :: new ( ) . room ( room_id) . sender ( user_id ! ( "@ben:saucisse.bzh" ) ) ;
1032+
1033+ let event_cache_store = Arc :: new ( MemoryStore :: new ( ) ) ;
1034+
1035+ let client = MockClientBuilder :: new ( "http://localhost" . to_owned ( ) )
1036+ . store_config (
1037+ StoreConfig :: new ( "hodlor" . to_owned ( ) ) . event_cache_store ( event_cache_store. clone ( ) ) ,
1038+ )
1039+ . build ( )
1040+ . await ;
1041+
1042+ let event_cache = client. event_cache ( ) ;
1043+
1044+ // Don't forget to subscribe and like^W enable storage!
1045+ event_cache. subscribe ( ) . unwrap ( ) ;
1046+ event_cache. enable_storage ( ) . unwrap ( ) ;
1047+
1048+ client. base_client ( ) . get_or_create_room ( room_id, matrix_sdk_base:: RoomState :: Joined ) ;
1049+ let room = client. get_room ( room_id) . unwrap ( ) ;
1050+
1051+ let ( room_event_cache, _drop_handles) = room. event_cache ( ) . await . unwrap ( ) ;
1052+
1053+ // Propagate an update for a message with bundled relations.
1054+ let mut relations = BundledMessageLikeRelations :: new ( ) ;
1055+ relations. replace =
1056+ Some ( Box :: new ( f. text_msg ( "Hello, Kind Sir" ) . sender ( * ALICE ) . into_raw_sync ( ) ) ) ;
1057+ let ev = f. text_msg ( "hey yo" ) . sender ( * ALICE ) . bundled_relations ( relations) . into_sync ( ) ;
1058+
1059+ let timeline = Timeline { limited : false , prev_batch : None , events : vec ! [ ev] } ;
1060+
1061+ room_event_cache
1062+ . inner
1063+ . handle_joined_room_update ( JoinedRoomUpdate { timeline, ..Default :: default ( ) } )
1064+ . await
1065+ . unwrap ( ) ;
1066+
1067+ // The in-memory linked chunk keeps the bundled relation.
1068+ {
1069+ let ( events, _) = room_event_cache. subscribe ( ) . await . unwrap ( ) ;
1070+
1071+ assert_eq ! ( events. len( ) , 1 ) ;
1072+
1073+ let ev = events[ 0 ] . raw ( ) . deserialize ( ) . unwrap ( ) ;
1074+ assert_let ! (
1075+ AnySyncTimelineEvent :: MessageLike ( AnySyncMessageLikeEvent :: RoomMessage ( msg) ) = ev
1076+ ) ;
1077+
1078+ let original = msg. as_original ( ) . unwrap ( ) ;
1079+ assert_eq ! ( original. content. body( ) , "hey yo" ) ;
1080+ assert ! ( original. unsigned. relations. replace. is_some( ) ) ;
1081+ }
1082+
1083+ // The one in storage does not.
1084+ let linked_chunk = event_cache_store. reload_linked_chunk ( room_id) . await . unwrap ( ) . unwrap ( ) ;
1085+
1086+ assert_eq ! ( linked_chunk. chunks( ) . count( ) , 1 ) ;
1087+
1088+ let mut chunks = linked_chunk. chunks ( ) ;
1089+ assert_matches ! ( chunks. next( ) . unwrap( ) . content( ) , ChunkContent :: Items ( events) => {
1090+ assert_eq!( events. len( ) , 1 ) ;
1091+
1092+ let ev = events[ 0 ] . raw( ) . deserialize( ) . unwrap( ) ;
1093+ assert_let!( AnySyncTimelineEvent :: MessageLike ( AnySyncMessageLikeEvent :: RoomMessage ( msg) ) = ev) ;
1094+
1095+ let original = msg. as_original( ) . unwrap( ) ;
1096+ assert_eq!( original. content. body( ) , "hey yo" ) ;
1097+ assert!( original. unsigned. relations. replace. is_none( ) ) ;
1098+ } ) ;
1099+
1100+ // That's all, folks!
1101+ assert ! ( chunks. next( ) . is_none( ) ) ;
1102+ }
1103+
9621104 #[ cfg( not( target_arch = "wasm32" ) ) ] // This uses the cross-process lock, so needs time support.
9631105 #[ async_test]
9641106 async fn test_load_from_storage ( ) {
0 commit comments