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