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