1#[cfg(feature = "e2e-encryption")]
17use std::sync::Arc;
18use std::{
19 collections::{BTreeMap, BTreeSet, HashMap},
20 fmt,
21 ops::Deref,
22};
23
24use eyeball::{SharedObservable, Subscriber};
25use eyeball_im::{Vector, VectorDiff};
26use futures_util::Stream;
27use matrix_sdk_common::{cross_process_lock::CrossProcessLockConfig, timer};
28#[cfg(feature = "experimental-x509-identity-verification")]
29use matrix_sdk_crypto::x509::{RawX509Signer, RawX509Verifier};
30#[cfg(feature = "e2e-encryption")]
31use matrix_sdk_crypto::{
32 CollectStrategy, DecryptionSettings, EncryptionSettings, OlmError, OlmMachine,
33 OlmMachineBuilder, TrustRequirement, store::DynCryptoStore,
34 store::types::RoomPendingKeyBundleDetails, types::requests::ToDeviceRequest,
35};
36#[cfg(doc)]
37use ruma::DeviceId;
38#[cfg(feature = "e2e-encryption")]
39use ruma::events::room::{history_visibility::HistoryVisibility, member::MembershipState};
40use ruma::{
41 OwnedRoomId, OwnedUserId, RoomId, UserId,
42 api::client::{self as api, sync::sync_events::v5},
43 events::{
44 StateEvent, StateEventType,
45 ignored_user_list::IgnoredUserListEventContent,
46 push_rules::{PushRulesEvent, PushRulesEventContent},
47 room::member::SyncRoomMemberEvent,
48 },
49 push::Ruleset,
50 time::Instant,
51};
52use tokio::sync::{Mutex, broadcast};
53#[cfg(feature = "e2e-encryption")]
54use tokio::sync::{RwLock, RwLockReadGuard};
55use tracing::{Level, debug, enabled, info, instrument, warn};
56
57#[cfg(feature = "e2e-encryption")]
58use crate::RoomMemberships;
59use crate::{
60 RoomStateFilter, SessionMeta, StateStore,
61 deserialized_responses::DisplayName,
62 error::{Error, Result},
63 event_cache::store::EventCacheStoreLock,
64 media::store::MediaStoreLock,
65 response_processors::{self as processors, Context},
66 room::{
67 Room, RoomInfoNotableUpdate, RoomInfoNotableUpdateReasons, RoomMembersUpdate, RoomState,
68 },
69 store::{
70 AvatarCache, BaseStateStore, DynStateStore, MemoryStore, Result as StoreResult,
71 RoomLoadSettings, StateChanges, StateStoreDataKey, StateStoreDataValue, StateStoreExt,
72 StoreConfig,
73 ambiguity_map::{AmbiguityCache, is_member_active},
74 },
75 sync::{RoomUpdates, SyncResponse},
76};
77
78#[derive(Clone)]
99pub struct BaseClient {
100 pub(crate) state_store: BaseStateStore,
102
103 event_cache_store: EventCacheStoreLock,
105
106 media_store: MediaStoreLock,
108
109 #[cfg(feature = "e2e-encryption")]
114 crypto_store: Arc<DynCryptoStore>,
115
116 #[cfg(feature = "e2e-encryption")]
120 olm_machine: Arc<RwLock<Option<OlmMachine>>>,
121
122 pub(crate) ignore_user_list_changes: SharedObservable<Vec<String>>,
124
125 pub(crate) global_profile_updates_sender: broadcast::Sender<BTreeSet<OwnedUserId>>,
128
129 #[cfg(feature = "e2e-encryption")]
132 pub room_key_recipient_strategy: CollectStrategy,
133
134 #[cfg(feature = "e2e-encryption")]
136 pub decryption_settings: DecryptionSettings,
137
138 #[cfg(feature = "e2e-encryption")]
140 pub handle_verification_events: bool,
141
142 pub threading_support: ThreadingSupport,
144
145 #[cfg(feature = "experimental-x509-identity-verification")]
148 x509_signer: Option<Arc<dyn RawX509Signer>>,
149
150 #[cfg(feature = "experimental-x509-identity-verification")]
153 x509_verifier: Option<Arc<dyn RawX509Verifier>>,
154
155 pub dm_room_definition: DmRoomDefinition,
157}
158
159#[cfg(not(tarpaulin_include))]
160impl fmt::Debug for BaseClient {
161 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
162 f.debug_struct("BaseClient")
163 .field("session_meta", &self.state_store.session_meta())
164 .field("sync_token", &self.state_store.sync_token)
165 .finish_non_exhaustive()
166 }
167}
168
169#[derive(Clone, Copy, Debug)]
181pub enum ThreadingSupport {
182 Enabled {
184 with_subscriptions: bool,
190 },
191 Disabled,
193}
194
195impl BaseClient {
196 pub fn new(
203 config: StoreConfig,
204 threading_support: ThreadingSupport,
205 dm_room_definition: DmRoomDefinition,
206 ) -> Self {
207 let store = BaseStateStore::new(config.state_store);
208
209 BaseClient {
210 state_store: store,
211 event_cache_store: config.event_cache_store,
212 media_store: config.media_store,
213 #[cfg(feature = "e2e-encryption")]
214 crypto_store: config.crypto_store,
215 #[cfg(feature = "e2e-encryption")]
216 olm_machine: Default::default(),
217 ignore_user_list_changes: Default::default(),
218 global_profile_updates_sender: broadcast::Sender::new(16),
219 #[cfg(feature = "e2e-encryption")]
220 room_key_recipient_strategy: Default::default(),
221 #[cfg(feature = "e2e-encryption")]
222 decryption_settings: DecryptionSettings {
223 sender_device_trust_requirement: TrustRequirement::Untrusted,
224 },
225 #[cfg(feature = "e2e-encryption")]
226 handle_verification_events: true,
227 threading_support,
228 #[cfg(feature = "experimental-x509-identity-verification")]
229 x509_signer: None,
230 #[cfg(feature = "experimental-x509-identity-verification")]
231 x509_verifier: None,
232 dm_room_definition,
233 }
234 }
235
236 #[cfg(feature = "e2e-encryption")]
239 pub async fn clone_with_in_memory_state_store(
240 &self,
241 cross_process_mode: CrossProcessLockConfig,
242 handle_verification_events: bool,
243 ) -> Result<Self> {
244 let config = StoreConfig::new(cross_process_mode).state_store(MemoryStore::new());
245 let config = config.crypto_store(self.crypto_store.clone());
246
247 let copy = Self {
248 state_store: BaseStateStore::new(config.state_store),
249 event_cache_store: config.event_cache_store,
250 media_store: config.media_store,
251 crypto_store: self.crypto_store.clone(),
258 olm_machine: self.olm_machine.clone(),
259 ignore_user_list_changes: Default::default(),
260 global_profile_updates_sender: broadcast::Sender::new(16),
261 room_key_recipient_strategy: self.room_key_recipient_strategy.clone(),
262 decryption_settings: self.decryption_settings.clone(),
263 handle_verification_events,
264 threading_support: self.threading_support,
265 #[cfg(feature = "experimental-x509-identity-verification")]
266 x509_signer: self.x509_signer.clone(),
267 #[cfg(feature = "experimental-x509-identity-verification")]
268 x509_verifier: self.x509_verifier.clone(),
269 dm_room_definition: self.dm_room_definition.clone(),
270 };
271
272 copy.state_store.derive_from_other(&self.state_store).await?;
273
274 Ok(copy)
275 }
276
277 #[cfg(feature = "experimental-x509-identity-verification")]
280 pub fn set_x509_signer(&mut self, x509_signer: Option<Arc<dyn RawX509Signer>>) {
281 self.x509_signer = x509_signer;
282 }
283
284 #[cfg(feature = "experimental-x509-identity-verification")]
287 pub fn set_x509_verifier(&mut self, x509_verifier: Option<Arc<dyn RawX509Verifier>>) {
288 self.x509_verifier = x509_verifier
289 }
290
291 #[cfg(not(feature = "e2e-encryption"))]
294 #[allow(clippy::unused_async)]
295 pub async fn clone_with_in_memory_state_store(
296 &self,
297 cross_process_store_config: CrossProcessLockConfig,
298 _handle_verification_events: bool,
299 ) -> Result<Self> {
300 let config = StoreConfig::new(cross_process_store_config).state_store(MemoryStore::new());
301 Ok(Self::new(config, ThreadingSupport::Disabled, DmRoomDefinition::default()))
302 }
303
304 pub fn session_meta(&self) -> Option<&SessionMeta> {
310 self.state_store.session_meta()
311 }
312
313 pub fn rooms(&self) -> Vec<Room> {
315 self.state_store.rooms()
316 }
317
318 pub fn rooms_filtered(&self, filter: RoomStateFilter) -> Vec<Room> {
320 self.state_store.rooms_filtered(filter)
321 }
322
323 pub fn rooms_stream(
326 &self,
327 ) -> (Vector<Room>, impl Stream<Item = Vec<VectorDiff<Room>>> + use<>) {
328 self.state_store.rooms_stream()
329 }
330
331 pub fn get_or_create_room(&self, room_id: &RoomId, room_state: RoomState) -> Room {
334 self.state_store.get_or_create_room(room_id, room_state)
335 }
336
337 pub fn state_store(&self) -> &DynStateStore {
339 self.state_store.deref()
340 }
341
342 pub fn event_cache_store(&self) -> &EventCacheStoreLock {
344 &self.event_cache_store
345 }
346
347 pub fn media_store(&self) -> &MediaStoreLock {
349 &self.media_store
350 }
351
352 pub fn is_active(&self) -> bool {
356 self.state_store.session_meta().is_some()
357 }
358
359 pub async fn activate(
391 &self,
392 session_meta: SessionMeta,
393 room_load_settings: RoomLoadSettings,
394 #[cfg(feature = "e2e-encryption")] custom_account: Option<
395 crate::crypto::vodozemac::olm::Account,
396 >,
397 ) -> Result<()> {
398 debug!(user_id = ?session_meta.user_id, device_id = ?session_meta.device_id, "Activating the client");
399
400 self.state_store.load_rooms(&session_meta.user_id, room_load_settings).await?;
401 self.state_store.load_sync_token().await?;
402 self.state_store.set_session_meta(session_meta);
403
404 #[cfg(feature = "e2e-encryption")]
405 self.regenerate_olm(custom_account).await?;
406
407 Ok(())
408 }
409
410 #[cfg(feature = "e2e-encryption")]
414 pub async fn regenerate_olm(
415 &self,
416 custom_account: Option<crate::crypto::vodozemac::olm::Account>,
417 ) -> Result<()> {
418 tracing::debug!("regenerating OlmMachine");
419 let session_meta = self.session_meta().ok_or(Error::OlmError(OlmError::MissingSession))?;
420
421 let builder = OlmMachineBuilder::new(&session_meta.user_id, &session_meta.device_id)
424 .with_crypto_store(self.crypto_store.clone())
425 .with_custom_account(custom_account);
426
427 #[cfg(feature = "experimental-x509-identity-verification")]
428 let builder = builder
429 .with_x509_verifier(self.x509_verifier.clone())
430 .with_x509_signer(self.x509_signer.clone());
431
432 let olm_machine = builder.build().await.map_err(OlmError::from)?;
433
434 *self.olm_machine.write().await = Some(olm_machine);
435 Ok(())
436 }
437
438 pub async fn sync_token(&self) -> Option<String> {
441 self.state_store.sync_token.read().await.clone()
442 }
443
444 pub async fn room_knocked(&self, room_id: &RoomId) -> Result<Room> {
448 let room = self.state_store.get_or_create_room(room_id, RoomState::Knocked);
449
450 if room.state() != RoomState::Knocked {
451 let store_guard = self.state_store.lock().lock().await;
452
453 #[cfg(feature = "e2e-encryption")]
456 if let Some(olm_machine) = self.olm_machine().await.as_ref() {
457 olm_machine.store().clear_room_pending_key_bundle(room_id).await?
458 }
459
460 room.update_and_save_room_info_with_store_guard(&store_guard, |mut info| {
461 info.mark_as_knocked();
462 info.mark_state_partially_synced();
463 info.mark_members_missing(); (info, RoomInfoNotableUpdateReasons::MEMBERSHIP)
465 })
466 .await?;
467 }
468
469 Ok(room)
470 }
471
472 pub async fn room_joined(
516 &self,
517 room_id: &RoomId,
518 inviter: Option<OwnedUserId>,
519 ) -> Result<Room> {
520 let room = self.state_store.get_or_create_room(room_id, RoomState::Joined);
521
522 if room.state() != RoomState::Joined {
525 let store_guard = self.state_store_lock().lock().await;
526
527 #[cfg(feature = "e2e-encryption")]
528 {
529 let previous_state = room.state();
540 if previous_state == RoomState::Invited
541 && let Some(inviter) = inviter
542 && let Some(olm_machine) = self.olm_machine().await.as_ref()
543 {
544 olm_machine.store().store_room_pending_key_bundle(room_id, &inviter).await?
545 }
546 }
547 #[cfg(not(feature = "e2e-encryption"))]
548 {
549 let _ = inviter;
551 }
552
553 room.update_and_save_room_info_with_store_guard(&store_guard, |mut info| {
554 info.mark_as_joined();
555 info.mark_state_partially_synced();
556 info.mark_members_missing(); (info, RoomInfoNotableUpdateReasons::MEMBERSHIP)
558 })
559 .await?;
560 }
561
562 Ok(room)
563 }
564
565 pub async fn room_left(&self, room_id: &RoomId) -> Result<()> {
569 let room = self.state_store.get_or_create_room(room_id, RoomState::Left);
570
571 if room.state() != RoomState::Left {
572 let store_guard = self.state_store.lock().lock().await;
573
574 #[cfg(feature = "e2e-encryption")]
577 if let Some(olm_machine) = self.olm_machine().await.as_ref() {
578 olm_machine.store().clear_room_pending_key_bundle(room_id).await?
579 }
580
581 room.update_and_save_room_info_with_store_guard(&store_guard, |mut info| {
582 info.mark_as_left();
583 info.mark_state_partially_synced();
584 info.mark_members_missing(); (info, RoomInfoNotableUpdateReasons::MEMBERSHIP)
586 })
587 .await?;
588 }
589
590 Ok(())
591 }
592
593 pub fn state_store_lock(&self) -> &Mutex<()> {
598 self.state_store.lock()
599 }
600
601 #[instrument(skip_all)]
607 pub async fn receive_sync_response(
608 &self,
609 response: api::sync::sync_events::v3::Response,
610 ) -> Result<SyncResponse> {
611 self.receive_sync_response_with_requested_required_states(
612 response,
613 &RequestedRequiredStates::default(),
614 )
615 .await
616 }
617
618 pub async fn receive_sync_response_with_requested_required_states(
626 &self,
627 response: api::sync::sync_events::v3::Response,
628 requested_required_states: &RequestedRequiredStates,
629 ) -> Result<SyncResponse> {
630 if self.state_store.sync_token.read().await.as_ref() == Some(&response.next_batch) {
634 info!("Got the same sync response twice");
635 return Ok(SyncResponse::default());
636 }
637
638 let now = if enabled!(Level::INFO) { Some(Instant::now()) } else { None };
639
640 let state_store_guard = self.state_store_lock().lock().await;
643
644 let user_id = self
645 .session_meta()
646 .expect("Sync shouldn't run without an authenticated user")
647 .user_id
648 .to_owned();
649
650 #[cfg(feature = "e2e-encryption")]
651 let olm_machine = self.olm_machine().await;
652
653 let mut context = Context::new(StateChanges::new(response.next_batch.clone()));
654
655 #[cfg(feature = "e2e-encryption")]
656 let processors::e2ee::to_device::Output { processed_to_device_events: to_device } =
657 processors::e2ee::to_device::from_sync_v2(
658 &response,
659 olm_machine.as_ref(),
660 &self.decryption_settings,
661 )
662 .await?;
663
664 #[cfg(not(feature = "e2e-encryption"))]
665 let to_device = response
666 .to_device
667 .events
668 .into_iter()
669 .map(|raw| {
670 use matrix_sdk_common::deserialized_responses::{
671 ProcessedToDeviceEvent, ToDeviceUnableToDecryptInfo,
672 ToDeviceUnableToDecryptReason,
673 };
674
675 if let Ok(Some(event_type)) = raw.get_field::<String>("type") {
676 if event_type == "m.room.encrypted" {
677 ProcessedToDeviceEvent::UnableToDecrypt {
678 encrypted_event: raw,
679 utd_info: ToDeviceUnableToDecryptInfo {
680 reason: ToDeviceUnableToDecryptReason::EncryptionIsDisabled,
681 },
682 }
683 } else {
684 ProcessedToDeviceEvent::PlainText(raw)
685 }
686 } else {
687 ProcessedToDeviceEvent::Invalid(raw)
689 }
690 })
691 .collect();
692
693 let mut ambiguity_cache = AmbiguityCache::new(self.state_store.inner.clone());
694 let mut avatar_cache = AvatarCache::new(self.state_store.inner.clone());
695
696 let global_account_data_processor =
697 processors::account_data::global(&response.account_data.events);
698
699 let push_rules = self.get_push_rules(&global_account_data_processor).await?;
700
701 let mut room_updates = RoomUpdates::default();
702 let mut notifications = Default::default();
703
704 let mut updated_members_in_room: BTreeMap<OwnedRoomId, BTreeSet<OwnedUserId>> =
705 BTreeMap::new();
706
707 #[cfg(feature = "e2e-encryption")]
708 let e2ee_context = processors::e2ee::E2EE::new(
709 olm_machine.as_ref(),
710 &self.decryption_settings,
711 self.handle_verification_events,
712 );
713
714 for (room_id, joined_room) in response.rooms.join {
715 let joined_room_update = processors::room::sync_v2::update_joined_room(
716 &mut context,
717 processors::room::RoomCreationData::new(
718 &room_id,
719 requested_required_states,
720 &mut ambiguity_cache,
721 &mut avatar_cache,
722 ),
723 joined_room,
724 &mut updated_members_in_room,
725 processors::notification::Notification::new(
726 &push_rules,
727 &mut notifications,
728 &self.state_store,
729 ),
730 #[cfg(feature = "e2e-encryption")]
731 &e2ee_context,
732 )
733 .await?;
734
735 room_updates.joined.insert(room_id, joined_room_update);
736 }
737
738 for (room_id, left_room) in response.rooms.leave {
739 let left_room_update = processors::room::sync_v2::update_left_room(
740 &mut context,
741 processors::room::RoomCreationData::new(
742 &room_id,
743 requested_required_states,
744 &mut ambiguity_cache,
745 &mut avatar_cache,
746 ),
747 left_room,
748 processors::notification::Notification::new(
749 &push_rules,
750 &mut notifications,
751 &self.state_store,
752 ),
753 #[cfg(feature = "e2e-encryption")]
754 &e2ee_context,
755 )
756 .await?;
757
758 room_updates.left.insert(room_id, left_room_update);
759 }
760
761 for (room_id, invited_room) in response.rooms.invite {
762 let invited_room_update = processors::room::sync_v2::update_invited_room(
763 &mut context,
764 &room_id,
765 &user_id,
766 invited_room,
767 processors::notification::Notification::new(
768 &push_rules,
769 &mut notifications,
770 &self.state_store,
771 ),
772 #[cfg(feature = "e2e-encryption")]
773 &e2ee_context,
774 )
775 .await?;
776
777 room_updates.invited.insert(room_id, invited_room_update);
778 }
779
780 for (room_id, knocked_room) in response.rooms.knock {
781 let knocked_room_update = processors::room::sync_v2::update_knocked_room(
782 &mut context,
783 &room_id,
784 &user_id,
785 knocked_room,
786 processors::notification::Notification::new(
787 &push_rules,
788 &mut notifications,
789 &self.state_store,
790 ),
791 #[cfg(feature = "e2e-encryption")]
792 &e2ee_context,
793 )
794 .await?;
795
796 room_updates.knocked.insert(room_id, knocked_room_update);
797 }
798
799 global_account_data_processor.apply(&mut context, &self.state_store).await;
800
801 context.state_changes.presence = response
802 .presence
803 .events
804 .iter()
805 .filter_map(|e| {
806 let event = e.deserialize().ok()?;
807 Some((event.sender, e.clone()))
808 })
809 .collect();
810
811 context.state_changes.ambiguity_maps = ambiguity_cache.cache;
812
813 processors::changes::save_and_apply(
814 context,
815 &self.state_store,
816 &state_store_guard,
817 &self.ignore_user_list_changes,
818 Some(response.next_batch.clone()),
819 )
820 .await?;
821
822 let mut context = Context::default();
823
824 processors::room::display_name::update_for_rooms(
827 &mut context,
828 &room_updates,
829 &self.state_store,
830 )
831 .await;
832
833 processors::changes::save_only(context, &self.state_store, &state_store_guard).await?;
835
836 for (room_id, member_ids) in updated_members_in_room {
837 if let Some(room) = self.get_room(&room_id) {
838 let _ =
839 room.room_member_updates_sender.send(RoomMembersUpdate::Partial(member_ids));
840 }
841 }
842
843 drop(state_store_guard);
845
846 if enabled!(Level::INFO) {
847 info!("Processed a sync response in {:?}", now.map(|now| now.elapsed()));
848 }
849
850 let response = SyncResponse {
851 rooms: room_updates,
852 presence: response.presence.events,
853 account_data: response.account_data.events,
854 to_device,
855 notifications,
856 };
857
858 Ok(response)
859 }
860
861 #[instrument(skip_all, fields(?room_id))]
873 pub async fn receive_all_members(
874 &self,
875 room_id: &RoomId,
876 request: &api::membership::get_member_events::v3::Request,
877 response: &api::membership::get_member_events::v3::Response,
878 ) -> Result<()> {
879 if request.membership.is_some() || request.not_membership.is_some() || request.at.is_some()
880 {
881 return Err(Error::InvalidReceiveMembersParameters);
885 }
886
887 let Some(room) = self.state_store.room(room_id) else {
888 return Ok(());
890 };
891
892 let mut chunk = Vec::with_capacity(response.chunk.len());
893 let mut context = Context::default();
894
895 #[cfg(feature = "e2e-encryption")]
896 let mut user_ids = BTreeSet::new();
897
898 let mut ambiguity_map: HashMap<DisplayName, BTreeSet<OwnedUserId>> = Default::default();
899
900 for raw_event in &response.chunk {
901 let member = match raw_event.deserialize() {
902 Ok(ev) => ev,
903 Err(e) => {
904 let event_id: Option<String> = raw_event.get_field("event_id").ok().flatten();
905 debug!(event_id, "Failed to deserialize member event: {e}");
906 continue;
907 }
908 };
909
910 #[cfg(feature = "e2e-encryption")]
920 match member.membership() {
921 MembershipState::Join | MembershipState::Invite => {
922 user_ids.insert(member.state_key().to_owned());
923 }
924 _ => (),
925 }
926
927 if let StateEvent::Original(e) = &member
928 && is_member_active(&e.content.membership)
929 && let Some(d) = &e.content.displayname
930 {
931 let display_name = DisplayName::new(d);
932 ambiguity_map.entry(display_name).or_default().insert(member.state_key().clone());
933 }
934
935 let sync_member: SyncRoomMemberEvent = member.clone().into();
936 processors::profiles::upsert_or_delete(&mut context, room_id, &sync_member);
937
938 context
939 .state_changes
940 .state
941 .entry(room_id.to_owned())
942 .or_default()
943 .entry(member.event_type())
944 .or_default()
945 .insert(member.state_key().to_string(), raw_event.clone().cast());
946 chunk.push(member);
947 }
948
949 #[cfg(feature = "e2e-encryption")]
950 processors::e2ee::tracked_users::update(
951 self.olm_machine().await.as_ref(),
952 room.encryption_state(),
953 &user_ids,
954 )
955 .await?;
956
957 context.state_changes.ambiguity_maps.insert(room_id.to_owned(), ambiguity_map);
958
959 {
960 let state_store_guard = self.state_store_lock().lock().await;
961
962 let mut room_info = room.clone_info();
963 room_info.mark_members_synced();
964 context.state_changes.add_room(room_info);
965
966 processors::changes::save_and_apply(
967 context,
968 &self.state_store,
969 &state_store_guard,
970 &self.ignore_user_list_changes,
971 None,
972 )
973 .await?;
974 }
975
976 let _ = room.room_member_updates_sender.send(RoomMembersUpdate::FullReload);
977
978 #[cfg(feature = "e2e-encryption")]
979 if let Some(olm) = self.olm_machine().await.as_ref() {
980 tracing::debug!("Rotating room key due to full member list reload");
985 if let Err(e) = olm.discard_room_key(room_id).await {
986 tracing::warn!("Error discarding room key: {e:?}");
987 }
988 }
989
990 Ok(())
991 }
992
993 pub async fn receive_filter_upload(
1009 &self,
1010 filter_name: &str,
1011 response: &api::filter::create_filter::v3::Response,
1012 ) -> Result<()> {
1013 Ok(self
1014 .state_store
1015 .set_kv_data(
1016 StateStoreDataKey::Filter(filter_name),
1017 StateStoreDataValue::Filter(response.filter_id.clone()),
1018 )
1019 .await?)
1020 }
1021
1022 pub async fn get_filter(&self, filter_name: &str) -> StoreResult<Option<String>> {
1034 let filter = self
1035 .state_store
1036 .get_kv_data(StateStoreDataKey::Filter(filter_name))
1037 .await?
1038 .map(|d| d.into_filter().expect("State store data not a filter"));
1039
1040 Ok(filter)
1041 }
1042
1043 #[cfg(feature = "e2e-encryption")]
1045 pub async fn share_room_key(&self, room_id: &RoomId) -> Result<Vec<Arc<ToDeviceRequest>>> {
1046 match self.olm_machine().await.as_ref() {
1047 Some(o) => {
1048 let Some(room) = self.get_room(room_id) else {
1049 return Err(Error::InsufficientData);
1050 };
1051
1052 let history_visibility = room.history_visibility_or_default();
1053 let Some(room_encryption_event) = room.encryption_settings() else {
1054 return Err(Error::EncryptionNotEnabled);
1055 };
1056
1057 let filter = if history_visibility == HistoryVisibility::Joined {
1060 RoomMemberships::JOIN
1061 } else {
1062 RoomMemberships::ACTIVE
1063 };
1064
1065 let members = self.state_store.get_user_ids(room_id, filter).await?;
1066
1067 let Some(settings) = EncryptionSettings::from_possibly_redacted(
1068 room_encryption_event,
1069 history_visibility,
1070 self.room_key_recipient_strategy.clone(),
1071 ) else {
1072 return Err(Error::EncryptionNotEnabled);
1073 };
1074
1075 Ok(o.share_room_key(room_id, members.iter().map(Deref::deref), settings).await?)
1076 }
1077 None => panic!("Olm machine wasn't started"),
1078 }
1079 }
1080
1081 pub fn get_room(&self, room_id: &RoomId) -> Option<Room> {
1087 self.state_store.room(room_id)
1088 }
1089
1090 pub async fn forget_room(&self, room_id: &RoomId) -> Result<()> {
1098 self.state_store.forget_room(room_id).await?;
1100
1101 Ok(())
1102 }
1103
1104 #[cfg(feature = "e2e-encryption")]
1106 pub async fn olm_machine(&self) -> RwLockReadGuard<'_, Option<OlmMachine>> {
1107 self.olm_machine.read().await
1108 }
1109
1110 pub(crate) async fn get_push_rules(
1116 &self,
1117 global_account_data_processor: &processors::account_data::Global,
1118 ) -> Result<Ruleset> {
1119 let _timer = timer!(Level::TRACE, "get_push_rules");
1120 if let Some(event) = global_account_data_processor
1121 .push_rules()
1122 .and_then(|ev| ev.deserialize_as_unchecked::<PushRulesEvent>().ok())
1123 {
1124 Ok(event.content.global)
1125 } else if let Some(event) = self
1126 .state_store
1127 .get_account_data_event_static::<PushRulesEventContent>()
1128 .await?
1129 .and_then(|ev| ev.deserialize().ok())
1130 {
1131 Ok(event.content.global)
1132 } else if let Some(session_meta) = self.state_store.session_meta() {
1133 Ok(Ruleset::server_default(&session_meta.user_id))
1134 } else {
1135 Ok(Ruleset::new())
1136 }
1137 }
1138
1139 pub fn subscribe_to_ignore_user_list_changes(&self) -> Subscriber<Vec<String>> {
1142 self.ignore_user_list_changes.subscribe()
1143 }
1144
1145 pub fn room_info_notable_update_receiver(&self) -> broadcast::Receiver<RoomInfoNotableUpdate> {
1149 self.state_store.room_info_notable_update_sender.subscribe()
1150 }
1151
1152 pub fn subscribe_to_global_profile_updates(
1158 &self,
1159 ) -> broadcast::Receiver<BTreeSet<OwnedUserId>> {
1160 self.global_profile_updates_sender.subscribe()
1161 }
1162
1163 pub async fn is_user_ignored(&self, user_id: &UserId) -> bool {
1165 match self.state_store.get_account_data_event_static::<IgnoredUserListEventContent>().await
1166 {
1167 Ok(Some(raw_ignored_user_list)) => match raw_ignored_user_list.deserialize() {
1168 Ok(current_ignored_user_list) => {
1169 current_ignored_user_list.content.ignored_users.contains_key(user_id)
1170 }
1171 Err(error) => {
1172 warn!(?error, "Failed to deserialize the ignored user list event");
1173 false
1174 }
1175 },
1176 Ok(None) => false,
1177 Err(error) => {
1178 warn!(?error, "Could not get the ignored user list from the state store");
1179 false
1180 }
1181 }
1182 }
1183
1184 #[cfg(feature = "e2e-encryption")]
1189 pub async fn get_pending_key_bundle_details_for_room(
1190 &self,
1191 room_id: &RoomId,
1192 ) -> Result<Option<RoomPendingKeyBundleDetails>> {
1193 let result = match self.olm_machine().await.as_ref() {
1194 Some(machine) => {
1195 machine.store().get_pending_key_bundle_details_for_room(room_id).await?
1196 }
1197 None => None,
1198 };
1199 Ok(result)
1200 }
1201
1202 pub async fn close_stores(&self) -> Result<()> {
1206 self.state_store.close().await?;
1207 self.event_cache_store.close().await.map_err(Error::EventCacheStore)?;
1208 self.media_store.close().await.map_err(Error::MediaStore)?;
1209
1210 #[cfg(feature = "e2e-encryption")]
1211 self.crypto_store.close().await.map_err(Error::CryptoStore)?;
1212
1213 Ok(())
1214 }
1215
1216 pub async fn reopen_stores(&self) -> Result<()> {
1218 #[cfg(feature = "e2e-encryption")]
1219 self.crypto_store.reopen().await.map_err(Error::CryptoStore)?;
1220
1221 self.media_store.reopen().await.map_err(Error::MediaStore)?;
1222 self.event_cache_store.reopen().await.map_err(Error::EventCacheStore)?;
1223 self.state_store.reopen().await?;
1224
1225 Ok(())
1226 }
1227}
1228
1229#[derive(Debug, Default)]
1241pub struct RequestedRequiredStates {
1242 default: Vec<(StateEventType, String)>,
1243 for_rooms: HashMap<OwnedRoomId, Vec<(StateEventType, String)>>,
1244}
1245
1246impl RequestedRequiredStates {
1247 pub fn new(
1252 default: Vec<(StateEventType, String)>,
1253 for_rooms: HashMap<OwnedRoomId, Vec<(StateEventType, String)>>,
1254 ) -> Self {
1255 Self { default, for_rooms }
1256 }
1257
1258 pub fn for_room(&self, room_id: &RoomId) -> &[(StateEventType, String)] {
1260 self.for_rooms.get(room_id).unwrap_or(&self.default)
1261 }
1262}
1263
1264impl From<&v5::Request> for RequestedRequiredStates {
1265 fn from(request: &v5::Request) -> Self {
1266 let mut default = BTreeSet::new();
1273
1274 for list in request.lists.values() {
1275 default.extend(BTreeSet::from_iter(list.room_details.required_state.iter().cloned()));
1276 }
1277
1278 for room_subscription in request.room_subscriptions.values() {
1279 default.extend(BTreeSet::from_iter(room_subscription.required_state.iter().cloned()));
1280 }
1281
1282 Self { default: default.into_iter().collect(), for_rooms: HashMap::new() }
1283 }
1284}
1285
1286#[derive(Debug, Clone, Default)]
1288#[cfg_attr(feature = "uniffi", derive(uniffi::Enum))]
1289pub enum DmRoomDefinition {
1290 #[default]
1293 MatrixSpec,
1294 TwoMembers,
1297}
1298
1299#[cfg(test)]
1300mod tests {
1301 use std::collections::HashMap;
1302
1303 use assert_matches2::assert_let;
1304 #[cfg(feature = "e2e-encryption")]
1305 use assert_matches2::assert_matches;
1306 use futures_util::FutureExt as _;
1307 use matrix_sdk_common::cross_process_lock::CrossProcessLockConfig;
1308 use matrix_sdk_test::{
1309 BOB, InvitedRoomBuilder, LeftRoomBuilder, SyncResponseBuilder, async_test,
1310 event_factory::EventFactory, ruma_response_from_json,
1311 };
1312 #[cfg(feature = "unstable-msc4426")]
1313 use ruma::profile::{
1314 ProfileFieldValue, StatusProfileField, UserProfileChanges, UserProfileUpdate,
1315 };
1316 use ruma::{
1317 RoomId,
1318 api::client::{self as api, sync::sync_events::v5},
1319 event_id,
1320 events::{StateEventType, room::member::MembershipState},
1321 room_id,
1322 serde::Raw,
1323 user_id,
1324 };
1325 use serde_json::{json, value::to_raw_value};
1326
1327 use super::{BaseClient, RequestedRequiredStates};
1328 use crate::{
1329 DmRoomDefinition, RoomDisplayName, RoomState, SessionMeta,
1330 client::ThreadingSupport,
1331 store::{RoomLoadSettings, StateStoreExt, StoreConfig},
1332 test_utils::logged_in_base_client,
1333 };
1334 #[cfg(feature = "unstable-msc4426")]
1335 use crate::{RoomMemberships, store::StateChanges};
1336
1337 #[test]
1338 fn test_requested_required_states() {
1339 let room_id_0 = room_id!("!r0");
1340 let room_id_1 = room_id!("!r1");
1341
1342 let requested_required_states = RequestedRequiredStates::new(
1343 vec![(StateEventType::RoomAvatar, "".to_owned())],
1344 HashMap::from([(
1345 room_id_0.to_owned(),
1346 vec![
1347 (StateEventType::RoomMember, "foo".to_owned()),
1348 (StateEventType::RoomEncryption, "".to_owned()),
1349 ],
1350 )]),
1351 );
1352
1353 assert_eq!(
1355 requested_required_states.for_room(room_id_0),
1356 &[
1357 (StateEventType::RoomMember, "foo".to_owned()),
1358 (StateEventType::RoomEncryption, "".to_owned()),
1359 ]
1360 );
1361
1362 assert_eq!(
1364 requested_required_states.for_room(room_id_1),
1365 &[(StateEventType::RoomAvatar, "".to_owned()),]
1366 );
1367 }
1368
1369 #[test]
1370 fn test_requested_required_states_from_sync_v5_request() {
1371 let room_id_0 = room_id!("!r0");
1372 let room_id_1 = room_id!("!r1");
1373
1374 let mut request = v5::Request::new();
1376
1377 {
1378 let requested_required_states = RequestedRequiredStates::from(&request);
1379
1380 assert!(requested_required_states.default.is_empty());
1381 assert!(requested_required_states.for_rooms.is_empty());
1382 }
1383
1384 request.lists.insert("foo".to_owned(), {
1386 let mut list = v5::request::List::default();
1387 list.room_details.required_state = vec![
1388 (StateEventType::RoomAvatar, "".to_owned()),
1389 (StateEventType::RoomEncryption, "".to_owned()),
1390 ];
1391
1392 list
1393 });
1394
1395 {
1396 let requested_required_states = RequestedRequiredStates::from(&request);
1397
1398 assert_eq!(
1399 requested_required_states.default,
1400 &[
1401 (StateEventType::RoomAvatar, "".to_owned()),
1402 (StateEventType::RoomEncryption, "".to_owned())
1403 ]
1404 );
1405 assert!(requested_required_states.for_rooms.is_empty());
1406 }
1407
1408 request.lists.insert("bar".to_owned(), {
1410 let mut list = v5::request::List::default();
1411 list.room_details.required_state = vec![
1412 (StateEventType::RoomEncryption, "".to_owned()),
1413 (StateEventType::RoomName, "".to_owned()),
1414 ];
1415
1416 list
1417 });
1418
1419 {
1420 let requested_required_states = RequestedRequiredStates::from(&request);
1421
1422 assert_eq!(
1424 requested_required_states.default,
1425 &[
1426 (StateEventType::RoomAvatar, "".to_owned()),
1427 (StateEventType::RoomEncryption, "".to_owned()),
1428 (StateEventType::RoomName, "".to_owned()),
1429 ]
1430 );
1431 assert!(requested_required_states.for_rooms.is_empty());
1432 }
1433
1434 request.room_subscriptions.insert(room_id_0.to_owned(), {
1436 let mut room_subscription = v5::request::RoomSubscription::default();
1437
1438 room_subscription.required_state = vec![
1439 (StateEventType::RoomJoinRules, "".to_owned()),
1440 (StateEventType::RoomEncryption, "".to_owned()),
1441 ];
1442
1443 room_subscription
1444 });
1445
1446 {
1447 let requested_required_states = RequestedRequiredStates::from(&request);
1448
1449 assert_eq!(
1451 requested_required_states.default,
1452 &[
1453 (StateEventType::RoomAvatar, "".to_owned()),
1454 (StateEventType::RoomEncryption, "".to_owned()),
1455 (StateEventType::RoomJoinRules, "".to_owned()),
1456 (StateEventType::RoomName, "".to_owned()),
1457 ]
1458 );
1459 assert!(requested_required_states.for_rooms.is_empty());
1460 }
1461
1462 request.room_subscriptions.insert(room_id_1.to_owned(), {
1464 let mut room_subscription = v5::request::RoomSubscription::default();
1465
1466 room_subscription.required_state = vec![
1467 (StateEventType::RoomName, "".to_owned()),
1468 (StateEventType::RoomTopic, "".to_owned()),
1469 ];
1470
1471 room_subscription
1472 });
1473
1474 {
1475 let requested_required_states = RequestedRequiredStates::from(&request);
1476
1477 assert_eq!(
1479 requested_required_states.default,
1480 &[
1481 (StateEventType::RoomAvatar, "".to_owned()),
1482 (StateEventType::RoomEncryption, "".to_owned()),
1483 (StateEventType::RoomJoinRules, "".to_owned()),
1484 (StateEventType::RoomName, "".to_owned()),
1485 (StateEventType::RoomTopic, "".to_owned()),
1486 ]
1487 );
1488 }
1489 }
1490
1491 #[async_test]
1492 async fn test_invite_after_leaving() {
1493 let user_id = user_id!("@alice:example.org");
1494 let room_id = room_id!("!test:example.org");
1495
1496 let client = logged_in_base_client(Some(user_id)).await;
1497 let f = EventFactory::new();
1498
1499 let mut sync_builder = SyncResponseBuilder::new();
1500
1501 let response = sync_builder
1502 .add_left_room(
1503 LeftRoomBuilder::new(room_id).add_timeline_event(
1504 EventFactory::new()
1505 .member(user_id)
1506 .membership(MembershipState::Leave)
1507 .display_name("Alice")
1508 .event_id(event_id!("$994173582443PhrSn:example.org")),
1509 ),
1510 )
1511 .build_sync_response();
1512 client.receive_sync_response(response).await.unwrap();
1513 assert_eq!(client.get_room(room_id).unwrap().state(), RoomState::Left);
1514
1515 let response = sync_builder
1516 .add_invited_room(
1517 InvitedRoomBuilder::new(room_id).add_state_event(
1518 f.member(user_id)
1519 .sender(user_id!("@example:example.org"))
1520 .membership(MembershipState::Invite)
1521 .display_name("Alice"),
1522 ),
1523 )
1524 .build_sync_response();
1525 client.receive_sync_response(response).await.unwrap();
1526 assert_eq!(client.get_room(room_id).unwrap().state(), RoomState::Invited);
1527 }
1528
1529 #[async_test]
1530 async fn test_invite_displayname() {
1531 let user_id = user_id!("@alice:example.org");
1532 let room_id = room_id!("!ithpyNKDtmhneaTQja:example.org");
1533
1534 let client = logged_in_base_client(Some(user_id)).await;
1535
1536 let response = ruma_response_from_json(&json!({
1537 "next_batch": "asdkl;fjasdkl;fj;asdkl;f",
1538 "device_one_time_keys_count": {
1539 "signed_curve25519": 50u64
1540 },
1541 "device_unused_fallback_key_types": [
1542 "signed_curve25519"
1543 ],
1544 "rooms": {
1545 "invite": {
1546 "!ithpyNKDtmhneaTQja:example.org": {
1547 "invite_state": {
1548 "events": [
1549 {
1550 "content": {
1551 "creator": "@test:example.org",
1552 "room_version": "9"
1553 },
1554 "sender": "@test:example.org",
1555 "state_key": "",
1556 "type": "m.room.create"
1557 },
1558 {
1559 "content": {
1560 "join_rule": "invite"
1561 },
1562 "sender": "@test:example.org",
1563 "state_key": "",
1564 "type": "m.room.join_rules"
1565 },
1566 {
1567 "content": {
1568 "algorithm": "m.megolm.v1.aes-sha2"
1569 },
1570 "sender": "@test:example.org",
1571 "state_key": "",
1572 "type": "m.room.encryption"
1573 },
1574 {
1575 "content": {
1576 "avatar_url": "mxc://example.org/dcBBDwuWEUrjfrOchvkirUST",
1577 "displayname": "Kyra",
1578 "membership": "join"
1579 },
1580 "sender": "@test:example.org",
1581 "state_key": "@test:example.org",
1582 "type": "m.room.member"
1583 },
1584 {
1585 "content": {
1586 "avatar_url": "mxc://example.org/ABFEXSDrESxovWwEnCYdNcHT",
1587 "displayname": "alice",
1588 "is_direct": true,
1589 "membership": "invite"
1590 },
1591 "origin_server_ts": 1650878657984u64,
1592 "sender": "@test:example.org",
1593 "state_key": "@alice:example.org",
1594 "type": "m.room.member",
1595 "unsigned": {
1596 "age": 14u64
1597 },
1598 "event_id": "$fLDqltg9Puj-kWItLSFVHPGN4YkgpYQf2qImPzdmgrE"
1599 }
1600 ]
1601 }
1602 }
1603 }
1604 }
1605 }));
1606
1607 client.receive_sync_response(response).await.unwrap();
1608
1609 let room = client.get_room(room_id).expect("Room not found");
1610 assert_eq!(room.state(), RoomState::Invited);
1611 assert_eq!(
1612 room.compute_display_name().await.expect("fetching display name failed").into_inner(),
1613 RoomDisplayName::Calculated("Kyra".to_owned())
1614 );
1615 }
1616
1617 #[async_test]
1618 async fn test_deserialization_failure() {
1619 let user_id = user_id!("@alice:example.org");
1620 let room_id = room_id!("!ithpyNKDtmhneaTQja:example.org");
1621
1622 let client = BaseClient::new(
1623 StoreConfig::new(CrossProcessLockConfig::SingleProcess),
1624 ThreadingSupport::Disabled,
1625 DmRoomDefinition::default(),
1626 );
1627 client
1628 .activate(
1629 SessionMeta { user_id: user_id.to_owned(), device_id: "FOOBAR".into() },
1630 RoomLoadSettings::default(),
1631 #[cfg(feature = "e2e-encryption")]
1632 None,
1633 )
1634 .await
1635 .unwrap();
1636
1637 let response = ruma_response_from_json(&json!({
1638 "next_batch": "asdkl;fjasdkl;fj;asdkl;f",
1639 "rooms": {
1640 "join": {
1641 "!ithpyNKDtmhneaTQja:example.org": {
1642 "state": {
1643 "events": [
1644 {
1645 "invalid": "invalid",
1646 },
1647 {
1648 "content": {
1649 "name": "The room name"
1650 },
1651 "event_id": "$143273582443PhrSn:example.org",
1652 "origin_server_ts": 1432735824653u64,
1653 "room_id": "!jEsUZKDJdhlrceRyVU:example.org",
1654 "sender": "@example:example.org",
1655 "state_key": "",
1656 "type": "m.room.name",
1657 "unsigned": {
1658 "age": 1234
1659 }
1660 },
1661 ]
1662 }
1663 }
1664 }
1665 }
1666 }));
1667
1668 client.receive_sync_response(response).await.unwrap();
1669 client
1670 .state_store()
1671 .get_state_event_static::<ruma::events::room::name::RoomNameEventContent>(room_id)
1672 .await
1673 .expect("Failed to fetch state event")
1674 .expect("State event not found")
1675 .deserialize()
1676 .expect("Failed to deserialize state event");
1677 }
1678
1679 #[async_test]
1680 async fn test_invited_members_arent_ignored() {
1681 let user_id = user_id!("@alice:example.org");
1682 let inviter_user_id = user_id!("@bob:example.org");
1683 let room_id = room_id!("!ithpyNKDtmhneaTQja:example.org");
1684
1685 let client = BaseClient::new(
1686 StoreConfig::new(CrossProcessLockConfig::SingleProcess),
1687 ThreadingSupport::Disabled,
1688 DmRoomDefinition::default(),
1689 );
1690 client
1691 .activate(
1692 SessionMeta { user_id: user_id.to_owned(), device_id: "FOOBAR".into() },
1693 RoomLoadSettings::default(),
1694 #[cfg(feature = "e2e-encryption")]
1695 None,
1696 )
1697 .await
1698 .unwrap();
1699
1700 let mut sync_builder = SyncResponseBuilder::new();
1702 let response = sync_builder
1703 .add_joined_room(matrix_sdk_test::JoinedRoomBuilder::new(room_id))
1704 .build_sync_response();
1705 client.receive_sync_response(response).await.unwrap();
1706
1707 let request = api::membership::get_member_events::v3::Request::new(room_id.to_owned());
1710
1711 let raw_member_event = json!({
1712 "content": {
1713 "avatar_url": "mxc://localhost/fewjilfewjil42",
1714 "displayname": "Invited Alice",
1715 "membership": "invite"
1716 },
1717 "event_id": "$151800140517rfvjc:localhost",
1718 "origin_server_ts": 151800140,
1719 "room_id": room_id,
1720 "sender": inviter_user_id,
1721 "state_key": user_id,
1722 "type": "m.room.member",
1723 "unsigned": {
1724 "age": 13374242,
1725 }
1726 });
1727 let response = api::membership::get_member_events::v3::Response::new(vec![Raw::from_json(
1728 to_raw_value(&raw_member_event).unwrap(),
1729 )]);
1730
1731 client.receive_all_members(room_id, &request, &response).await.unwrap();
1733
1734 let room = client.get_room(room_id).unwrap();
1735
1736 let member = room.get_member(user_id).await.expect("ok").expect("exists");
1738
1739 assert_eq!(member.user_id(), user_id);
1740 assert_eq!(member.display_name().unwrap(), "Invited Alice");
1741 assert_eq!(member.avatar_url().unwrap().to_string(), "mxc://localhost/fewjilfewjil42");
1742 }
1743
1744 #[async_test]
1745 async fn test_reinvited_members_get_a_display_name() {
1746 let user_id = user_id!("@alice:example.org");
1747 let inviter_user_id = user_id!("@bob:example.org");
1748 let room_id = room_id!("!ithpyNKDtmhneaTQja:example.org");
1749
1750 let client = BaseClient::new(
1751 StoreConfig::new(CrossProcessLockConfig::SingleProcess),
1752 ThreadingSupport::Disabled,
1753 DmRoomDefinition::default(),
1754 );
1755 client
1756 .activate(
1757 SessionMeta { user_id: user_id.to_owned(), device_id: "FOOBAR".into() },
1758 RoomLoadSettings::default(),
1759 #[cfg(feature = "e2e-encryption")]
1760 None,
1761 )
1762 .await
1763 .unwrap();
1764
1765 let f = EventFactory::new().sender(user_id);
1767 let mut sync_builder = SyncResponseBuilder::new();
1768 let response = sync_builder
1769 .add_joined_room(
1770 matrix_sdk_test::JoinedRoomBuilder::new(room_id)
1771 .add_state_event(f.member(user_id).leave()),
1772 )
1773 .build_sync_response();
1774 client.receive_sync_response(response).await.unwrap();
1775
1776 let request = api::membership::get_member_events::v3::Request::new(room_id.to_owned());
1778
1779 let raw_member_event = json!({
1780 "content": {
1781 "avatar_url": "mxc://localhost/fewjilfewjil42",
1782 "displayname": "Invited Alice",
1783 "membership": "invite"
1784 },
1785 "event_id": "$151800140517rfvjc:localhost",
1786 "origin_server_ts": 151800140,
1787 "room_id": room_id,
1788 "sender": inviter_user_id,
1789 "state_key": user_id,
1790 "type": "m.room.member",
1791 "unsigned": {
1792 "age": 13374242,
1793 }
1794 });
1795 let response = api::membership::get_member_events::v3::Response::new(vec![Raw::from_json(
1796 to_raw_value(&raw_member_event).unwrap(),
1797 )]);
1798
1799 client.receive_all_members(room_id, &request, &response).await.unwrap();
1801
1802 let room = client.get_room(room_id).unwrap();
1803
1804 let member = room.get_member(user_id).await.expect("ok").expect("exists");
1806
1807 assert_eq!(member.user_id(), user_id);
1808 assert_eq!(member.display_name().unwrap(), "Invited Alice");
1809 assert_eq!(member.avatar_url().unwrap().to_string(), "mxc://localhost/fewjilfewjil42");
1810 }
1811
1812 async fn base_client_with_joined_room(room_id: &RoomId) -> BaseClient {
1813 let client = logged_in_base_client(Some(user_id!("@alice:example.org"))).await;
1814
1815 let mut sync_builder = SyncResponseBuilder::new();
1816 let response = sync_builder
1817 .add_joined_room(matrix_sdk_test::JoinedRoomBuilder::new(room_id))
1818 .build_sync_response();
1819 client.receive_sync_response(response).await.unwrap();
1820
1821 client
1822 }
1823
1824 #[async_test]
1825 async fn test_inactive_members_do_not_make_a_display_name_ambiguous() {
1826 let joined_user_id = user_id!("@bob:example.org");
1827 let left_user_id = user_id!("@carol:example.org");
1828 let room_id = room_id!("!ithpyNKDtmhneaTQja:example.org");
1829
1830 let client = base_client_with_joined_room(room_id).await;
1831
1832 let f = EventFactory::new().room(room_id);
1834 let request = api::membership::get_member_events::v3::Request::new(room_id.to_owned());
1835 let response = api::membership::get_member_events::v3::Response::new(vec![
1836 f.member(joined_user_id).display_name("Amandine").into_raw(),
1837 f.member(left_user_id)
1838 .display_name("Amandine")
1839 .membership(MembershipState::Leave)
1840 .into_raw(),
1841 ]);
1842
1843 client.receive_all_members(room_id, &request, &response).await.unwrap();
1844
1845 let room = client.get_room(room_id).unwrap();
1846 let member = room.get_member(joined_user_id).await.expect("ok").expect("exists");
1847
1848 assert_eq!(member.display_name().unwrap(), "Amandine");
1849 assert!(!member.name_ambiguous());
1850 }
1851
1852 #[async_test]
1853 async fn test_active_members_make_a_display_name_ambiguous() {
1854 let joined_user_id = user_id!("@bob:example.org");
1855 let invited_user_id = user_id!("@carol:example.org");
1856 let room_id = room_id!("!ithpyNKDtmhneaTQja:example.org");
1857
1858 let client = base_client_with_joined_room(room_id).await;
1859
1860 let f = EventFactory::new().room(room_id);
1862 let request = api::membership::get_member_events::v3::Request::new(room_id.to_owned());
1863 let response = api::membership::get_member_events::v3::Response::new(vec![
1864 f.member(joined_user_id).display_name("Amandine").into_raw(),
1865 f.member(invited_user_id)
1866 .display_name("Amandine")
1867 .membership(MembershipState::Invite)
1868 .into_raw(),
1869 ]);
1870
1871 client.receive_all_members(room_id, &request, &response).await.unwrap();
1872
1873 let room = client.get_room(room_id).unwrap();
1875
1876 let joined = room.get_member(joined_user_id).await.expect("ok").expect("exists");
1877 assert!(joined.name_ambiguous());
1878
1879 let invited = room.get_member(invited_user_id).await.expect("ok").expect("exists");
1880 assert!(invited.name_ambiguous());
1881 }
1882
1883 #[cfg(feature = "unstable-msc4426")]
1884 #[async_test]
1885 async fn test_room_member_carries_global_profile_status() {
1886 let user_id = user_id!("@alice:example.org");
1887 let room_id = room_id!("!ithpyNKDtmhneaTQja:example.org");
1888
1889 let client = BaseClient::new(
1890 StoreConfig::new(CrossProcessLockConfig::SingleProcess),
1891 ThreadingSupport::Disabled,
1892 DmRoomDefinition::default(),
1893 );
1894 client
1895 .activate(
1896 SessionMeta { user_id: user_id.to_owned(), device_id: "FOOBAR".into() },
1897 RoomLoadSettings::default(),
1898 #[cfg(feature = "e2e-encryption")]
1899 None,
1900 )
1901 .await
1902 .unwrap();
1903
1904 let f = EventFactory::new().sender(user_id);
1906 let mut sync_builder = SyncResponseBuilder::new();
1907 let response = sync_builder
1908 .add_joined_room(
1909 matrix_sdk_test::JoinedRoomBuilder::new(room_id).add_state_event(f.member(user_id)),
1910 )
1911 .build_sync_response();
1912 client.receive_sync_response(response).await.unwrap();
1913
1914 let room = client.get_room(room_id).unwrap();
1915
1916 let member = room.get_member(user_id).await.expect("ok").expect("exists");
1918 assert!(member.status().is_none());
1919
1920 let mut changes = StateChanges::default();
1922 changes.global_profiles.insert(user_id.to_owned(), {
1923 let mut profile_changes = UserProfileChanges::new();
1924 profile_changes.insert_updated_value(ProfileFieldValue::Status(
1925 StatusProfileField::new("Working".to_owned(), "💻".to_owned()),
1926 ));
1927 UserProfileUpdate::Updated(profile_changes)
1928 });
1929 client.state_store().save_changes(&changes).await.unwrap();
1930
1931 let member = room.get_member(user_id).await.expect("ok").expect("exists");
1933 let status = member.status().expect("status is set");
1934 assert_eq!(status.text, "Working");
1935 assert_eq!(status.emoji, "💻");
1936
1937 let members = room.members(RoomMemberships::JOIN).await.unwrap();
1939 let member =
1940 members.iter().find(|m| m.user_id() == user_id).expect("member is in the list");
1941 let status = member.status().expect("status is set");
1942 assert_eq!(status.text, "Working");
1943 assert_eq!(status.emoji, "💻");
1944 }
1945
1946 #[async_test]
1947 async fn test_ignored_user_list_changes() {
1948 let user_id = user_id!("@alice:example.org");
1949 let client = BaseClient::new(
1950 StoreConfig::new(CrossProcessLockConfig::SingleProcess),
1951 ThreadingSupport::Disabled,
1952 DmRoomDefinition::default(),
1953 );
1954
1955 client
1956 .activate(
1957 SessionMeta { user_id: user_id.to_owned(), device_id: "FOOBAR".into() },
1958 RoomLoadSettings::default(),
1959 #[cfg(feature = "e2e-encryption")]
1960 None,
1961 )
1962 .await
1963 .unwrap();
1964
1965 let mut subscriber = client.subscribe_to_ignore_user_list_changes();
1966 assert!(subscriber.next().now_or_never().is_none());
1967
1968 let f = EventFactory::new();
1969 let mut sync_builder = SyncResponseBuilder::new();
1970 let response = sync_builder
1971 .add_global_account_data(f.ignored_user_list([(*BOB).into()]))
1972 .build_sync_response();
1973 client.receive_sync_response(response).await.unwrap();
1974
1975 assert_let!(Some(ignored) = subscriber.next().await);
1976 assert_eq!(ignored, [BOB.to_string()]);
1977
1978 let response = sync_builder
1980 .add_global_account_data(f.ignored_user_list([(*BOB).into()]))
1981 .build_sync_response();
1982 client.receive_sync_response(response).await.unwrap();
1983
1984 assert!(subscriber.next().now_or_never().is_none());
1986
1987 let response =
1989 sync_builder.add_global_account_data(f.ignored_user_list([])).build_sync_response();
1990 client.receive_sync_response(response).await.unwrap();
1991
1992 assert_let!(Some(ignored) = subscriber.next().await);
1993 assert!(ignored.is_empty());
1994 }
1995
1996 #[async_test]
1997 async fn test_is_user_ignored() {
1998 let ignored_user_id = user_id!("@alice:example.org");
1999 let client = logged_in_base_client(None).await;
2000
2001 let mut sync_builder = SyncResponseBuilder::new();
2002 let f = EventFactory::new();
2003 let response = sync_builder
2004 .add_global_account_data(f.ignored_user_list([ignored_user_id.to_owned()]))
2005 .build_sync_response();
2006 client.receive_sync_response(response).await.unwrap();
2007
2008 assert!(client.is_user_ignored(ignored_user_id).await);
2009 }
2010
2011 #[cfg(feature = "e2e-encryption")]
2012 #[async_test]
2013 async fn test_invite_details_are_set() {
2014 let user_id = user_id!("@alice:localhost");
2015 let client = logged_in_base_client(Some(user_id)).await;
2016 let known_room_id = room_id!("!invited:localhost");
2017 let unknown_room_id = room_id!("!unknown:localhost");
2018
2019 let mut sync_builder = SyncResponseBuilder::new();
2020 let response = sync_builder
2021 .add_invited_room(InvitedRoomBuilder::new(known_room_id))
2022 .build_sync_response();
2023 client.receive_sync_response(response).await.unwrap();
2024
2025 let invited_room = client
2028 .get_room(known_room_id)
2029 .expect("The sync should have created a room in the invited state");
2030
2031 assert_eq!(invited_room.state(), RoomState::Invited);
2032 assert!(
2033 client.get_pending_key_bundle_details_for_room(known_room_id).await.unwrap().is_none()
2034 );
2035
2036 let joined_room = client
2038 .room_joined(known_room_id, Some(user_id.to_owned()))
2039 .await
2040 .expect("We should be able to mark a room as joined");
2041
2042 assert_eq!(joined_room.state(), RoomState::Joined);
2044 assert_matches!(
2045 client.get_pending_key_bundle_details_for_room(known_room_id).await,
2046 Ok(Some(details))
2047 );
2048 assert_eq!(details.inviter, user_id);
2049
2050 assert!(client.get_room(unknown_room_id).is_none());
2053 let unknown_room = client
2054 .room_joined(unknown_room_id, Some(user_id.to_owned()))
2055 .await
2056 .expect("We should be able to mark a room as joined");
2057
2058 assert_eq!(unknown_room.state(), RoomState::Joined);
2059 assert!(
2060 client
2061 .get_pending_key_bundle_details_for_room(unknown_room_id)
2062 .await
2063 .unwrap()
2064 .is_none()
2065 );
2066
2067 sync_builder.clear();
2068 let response =
2069 sync_builder.add_left_room(LeftRoomBuilder::new(known_room_id)).build_sync_response();
2070 client.receive_sync_response(response).await.unwrap();
2071
2072 let left_room = client
2074 .get_room(known_room_id)
2075 .expect("The sync should have created a room in the invited state");
2076
2077 assert_eq!(left_room.state(), RoomState::Left);
2078 assert!(
2079 client.get_pending_key_bundle_details_for_room(known_room_id).await.unwrap().is_none()
2080 );
2081 }
2082}