diff --git a/crates/matrix-sdk-base/src/response_processors.rs b/crates/matrix-sdk-base/src/response_processors.rs index a6a35a5446c..23d09e8f57e 100644 --- a/crates/matrix-sdk-base/src/response_processors.rs +++ b/crates/matrix-sdk-base/src/response_processors.rs @@ -20,7 +20,7 @@ use std::{ use ruma::{ events::{AnyGlobalAccountDataEvent, GlobalAccountDataEventType}, serde::Raw, - OwnedUserId, RoomId, + RoomId, }; use tracing::{debug, instrument, trace, warn}; @@ -94,7 +94,7 @@ impl AccountDataProcessor { for event in events { let AnyGlobalAccountDataEvent::Direct(direct_event) = event else { continue }; - let mut new_dms = HashMap::<&RoomId, HashSet>::new(); + let mut new_dms = HashMap::<&RoomId, HashSet>::new(); for (user_id, rooms) in direct_event.content.iter() { for room_id in rooms { new_dms.entry(room_id).or_default().insert(user_id.clone()); diff --git a/crates/matrix-sdk-base/src/rooms/mod.rs b/crates/matrix-sdk-base/src/rooms/mod.rs index 9aa9b102750..f010465cc10 100644 --- a/crates/matrix-sdk-base/src/rooms/mod.rs +++ b/crates/matrix-sdk-base/src/rooms/mod.rs @@ -125,7 +125,7 @@ pub struct BaseRoomInfo { pub(crate) create: Option>, /// A list of user ids this room is considered as direct message, if this /// room is a DM. - pub(crate) dm_targets: HashSet, + pub(crate) dm_targets: HashSet, /// The `m.room.encryption` event content that enabled E2EE in this room. pub(crate) encryption: Option, /// The guest access policy of this room. diff --git a/crates/matrix-sdk-base/src/rooms/normal.rs b/crates/matrix-sdk-base/src/rooms/normal.rs index 7181d4d4824..cad043603f7 100644 --- a/crates/matrix-sdk-base/src/rooms/normal.rs +++ b/crates/matrix-sdk-base/src/rooms/normal.rs @@ -460,7 +460,7 @@ impl Room { /// only be considered as guidance. We leave members in this list to allow /// us to re-find a DM with a user even if they have left, since we may /// want to re-invite them. - pub fn direct_targets(&self) -> HashSet { + pub fn direct_targets(&self) -> HashSet { self.inner.read().base_info.dm_targets.clone() } diff --git a/crates/matrix-sdk-base/src/sliding_sync/mod.rs b/crates/matrix-sdk-base/src/sliding_sync/mod.rs index 9350492a098..ca4ceef2944 100644 --- a/crates/matrix-sdk-base/src/sliding_sync/mod.rs +++ b/crates/matrix-sdk-base/src/sliding_sync/mod.rs @@ -1331,7 +1331,7 @@ mod tests { create_dm(&client, room_id, user_a_id, user_b_id, MembershipState::Join).await; // (Sanity: B is a direct target, and is in Join state) - assert!(direct_targets(&client, room_id).contains(user_b_id)); + assert!(direct_targets(&client, room_id).contains(user_b_id.as_str())); assert_eq!(membership(&client, room_id, user_b_id).await, MembershipState::Join); // When B leaves @@ -1340,7 +1340,7 @@ mod tests { // Then B is still a direct target, and is in Leave state (B is a direct target // because we want to return to our old DM in the UI even if the other // user left, so we can reinvite them. See https://github.com/matrix-org/matrix-rust-sdk/issues/2017) - assert!(direct_targets(&client, room_id).contains(user_b_id)); + assert!(direct_targets(&client, room_id).contains(user_b_id.as_str())); assert_eq!(membership(&client, room_id, user_b_id).await, MembershipState::Leave); } @@ -1356,7 +1356,7 @@ mod tests { create_dm(&client, room_id, user_a_id, user_b_id, MembershipState::Invite).await; // (Sanity: B is a direct target, and is in Invite state) - assert!(direct_targets(&client, room_id).contains(user_b_id)); + assert!(direct_targets(&client, room_id).contains(user_b_id.as_str())); assert_eq!(membership(&client, room_id, user_b_id).await, MembershipState::Invite); // When B declines the invitation (i.e. leaves) @@ -1365,7 +1365,7 @@ mod tests { // Then B is still a direct target, and is in Leave state (B is a direct target // because we want to return to our old DM in the UI even if the other // user left, so we can reinvite them. See https://github.com/matrix-org/matrix-rust-sdk/issues/2017) - assert!(direct_targets(&client, room_id).contains(user_b_id)); + assert!(direct_targets(&client, room_id).contains(user_b_id.as_str())); assert_eq!(membership(&client, room_id, user_b_id).await, MembershipState::Leave); } @@ -1383,7 +1383,7 @@ mod tests { assert_eq!(membership(&client, room_id, user_a_id).await, MembershipState::Join); // (Sanity: B is a direct target, and is in Join state) - assert!(direct_targets(&client, room_id).contains(user_b_id)); + assert!(direct_targets(&client, room_id).contains(user_b_id.as_str())); assert_eq!(membership(&client, room_id, user_b_id).await, MembershipState::Join); let room = client.get_room(room_id).unwrap(); @@ -1407,7 +1407,7 @@ mod tests { assert_eq!(membership(&client, room_id, user_a_id).await, MembershipState::Join); // (Sanity: B is a direct target, and is in Join state) - assert!(direct_targets(&client, room_id).contains(user_b_id)); + assert!(direct_targets(&client, room_id).contains(user_b_id.as_str())); assert_eq!(membership(&client, room_id, user_b_id).await, MembershipState::Invite); let room = client.get_room(room_id).unwrap(); @@ -2553,8 +2553,8 @@ mod tests { set_room_joined(&mut room_response, user_a_id); let mut response = response_with_room(room_id_1, room_response); let mut direct_content = BTreeMap::new(); - direct_content.insert(user_a_id.to_owned(), vec![room_id_1.to_owned()]); - direct_content.insert(user_b_id.to_owned(), vec![room_id_2.to_owned()]); + direct_content.insert(user_a_id.to_string(), vec![room_id_1.to_owned()]); + direct_content.insert(user_b_id.to_string(), vec![room_id_2.to_owned()]); response .extensions .account_data @@ -2665,7 +2665,7 @@ mod tests { member.membership().clone() } - fn direct_targets(client: &BaseClient, room_id: &RoomId) -> HashSet { + fn direct_targets(client: &BaseClient, room_id: &RoomId) -> HashSet { let room = client.get_room(room_id).expect("Room not found!"); room.direct_targets() } @@ -2725,7 +2725,7 @@ mod tests { room_ids: Vec, ) { let mut direct_content = BTreeMap::new(); - direct_content.insert(user_id, room_ids); + direct_content.insert(user_id.to_string(), room_ids); response .extensions .account_data diff --git a/crates/matrix-sdk-base/src/store/migration_helpers.rs b/crates/matrix-sdk-base/src/store/migration_helpers.rs index 76acb86cf48..a0e23ec218e 100644 --- a/crates/matrix-sdk-base/src/store/migration_helpers.rs +++ b/crates/matrix-sdk-base/src/store/migration_helpers.rs @@ -37,7 +37,7 @@ use ruma::{ }, EmptyStateKey, EventContent, RedactContent, StateEventContent, StateEventType, }, - OwnedRoomId, OwnedUserId, RoomId, + OwnedRoomId, RoomId, }; use serde::{Deserialize, Serialize}; @@ -155,7 +155,7 @@ fn encryption_state_default() -> bool { struct BaseRoomInfoV1 { avatar: Option>, canonical_alias: Option>, - dm_targets: HashSet, + dm_targets: HashSet, encryption: Option, guest_access: Option>, history_visibility: Option>, diff --git a/crates/matrix-sdk-common/src/direct_event.rs b/crates/matrix-sdk-common/src/direct_event.rs new file mode 100644 index 00000000000..34ca90d215b --- /dev/null +++ b/crates/matrix-sdk-common/src/direct_event.rs @@ -0,0 +1,45 @@ +use std::{ + collections::{btree_map, BTreeMap}, + ops::{Deref, DerefMut}, +}; + +use ruma::OwnedRoomId; +use ruma_macros::EventContent; +use serde::{Deserialize, Serialize}; + +#[derive(Clone, Debug, Default, Deserialize, Serialize, EventContent)] +#[allow(clippy::exhaustive_structs)] +#[ruma_event(type = "m.direct", kind = GlobalAccountData)] +pub struct DirectEventContent(pub BTreeMap>); + +impl Deref for DirectEventContent { + type Target = BTreeMap>; + + fn deref(&self) -> &Self::Target { + &self.0 + } +} + +impl DerefMut for DirectEventContent { + fn deref_mut(&mut self) -> &mut Self::Target { + &mut self.0 + } +} + +impl IntoIterator for DirectEventContent { + type Item = (String, Vec); + type IntoIter = btree_map::IntoIter>; + + fn into_iter(self) -> Self::IntoIter { + self.0.into_iter() + } +} + +impl FromIterator<(String, Vec)> for DirectEventContent { + fn from_iter(iter: T) -> Self + where + T: IntoIterator)>, + { + Self(BTreeMap::from_iter(iter)) + } +} diff --git a/crates/matrix-sdk/src/account.rs b/crates/matrix-sdk/src/account.rs index 249bfa3129e..551d63ce42a 100644 --- a/crates/matrix-sdk/src/account.rs +++ b/crates/matrix-sdk/src/account.rs @@ -861,7 +861,7 @@ impl Account { }; for user_id in user_ids { - content.entry(user_id.to_owned()).or_default().push(room_id.to_owned()); + content.entry(user_id.to_string()).or_default().push(room_id.to_owned()); } // TODO: We should probably save the fact that we need to send this out diff --git a/crates/matrix-sdk/src/encryption/mod.rs b/crates/matrix-sdk/src/encryption/mod.rs index a6ecc1f7754..be918094a57 100644 --- a/crates/matrix-sdk/src/encryption/mod.rs +++ b/crates/matrix-sdk/src/encryption/mod.rs @@ -607,7 +607,7 @@ impl Client { // Find the room we share with the `user_id` and only with `user_id` let room = rooms.into_iter().find(|r| { let targets = r.direct_targets(); - targets.len() == 1 && targets.contains(user_id) + targets.len() == 1 && targets.contains(user_id.as_str()) }); trace!(?room, "Found room"); diff --git a/crates/matrix-sdk/src/room/mod.rs b/crates/matrix-sdk/src/room/mod.rs index 2aba1c9b852..30774fb02fd 100644 --- a/crates/matrix-sdk/src/room/mod.rs +++ b/crates/matrix-sdk/src/room/mod.rs @@ -1205,7 +1205,7 @@ impl Room { room_members.retain(|member| member.user_id() != self.own_user_id()); for member in room_members { - let entry = content.entry(member.user_id().to_owned()).or_default(); + let entry = content.entry(member.user_id().to_string()).or_default(); if !entry.iter().any(|room_id| room_id == this_room_id) { entry.push(this_room_id.to_owned()); } diff --git a/crates/matrix-sdk/tests/integration/client.rs b/crates/matrix-sdk/tests/integration/client.rs index 589087296a5..fff1f25e5d9 100644 --- a/crates/matrix-sdk/tests/integration/client.rs +++ b/crates/matrix-sdk/tests/integration/client.rs @@ -496,7 +496,7 @@ async fn test_marking_room_as_dm() { "The body of the PUT /account_data request should be a valid DirectEventContent", ); - let bob_entry = content.get(bob).expect("We should have bob in the direct event content"); + let bob_entry = content.get(bob.as_str()).expect("We should have bob in the direct event content"); assert_eq!(content.len(), 2, "We should have entries for bob and foo"); assert_eq!(bob_entry.len(), 3, "Bob should have 3 direct rooms"); diff --git a/crates/matrix-sdk/tests/integration/room/common.rs b/crates/matrix-sdk/tests/integration/room/common.rs index f742ee3bfe4..3fd6677c210 100644 --- a/crates/matrix-sdk/tests/integration/room/common.rs +++ b/crates/matrix-sdk/tests/integration/room/common.rs @@ -833,7 +833,7 @@ async fn test_is_direct() { // The room is direct now. let direct_targets = room.direct_targets(); assert_eq!(direct_targets.len(), 1); - assert!(direct_targets.contains(*BOB)); + assert!(direct_targets.contains(BOB.as_str())); assert!(room.is_direct().await.unwrap()); // Unset the room as direct. diff --git a/crates/matrix-sdk/tests/integration/room/joined.rs b/crates/matrix-sdk/tests/integration/room/joined.rs index e8f294bb0d0..679ee82d2e2 100644 --- a/crates/matrix-sdk/tests/integration/room/joined.rs +++ b/crates/matrix-sdk/tests/integration/room/joined.rs @@ -21,13 +21,11 @@ use ruma::{ api::client::{membership::Invite3pidInit, receipt::create_receipt::v3::ReceiptType}, assign, event_id, events::{ - receipt::ReceiptThread, - room::message::{RoomMessageEventContent, RoomMessageEventContentWithoutRelation}, - TimelineEventType, + receipt::ReceiptThread, room::message::{RoomMessageEventContent, RoomMessageEventContentWithoutRelation}, TimelineEventType }, int, mxc_uri, owned_event_id, room_id, thirdparty, user_id, OwnedUserId, TransactionId, }; -use serde_json::{json, Value}; +use serde_json::{from_value, json, Value}; use wiremock::{ matchers::{body_json, body_partial_json, header, method, path_regex}, Mock, ResponseTemplate, @@ -630,6 +628,37 @@ async fn test_reset_power_levels() { room.reset_power_levels().await.unwrap(); } +#[async_test] +async fn test_is_direct_invite_by_3pid() { + let (client, server) = logged_in_client_with_server().await; + + let mut sync_builder = SyncResponseBuilder::new(); + sync_builder.add_joined_room(JoinedRoomBuilder::default()); + let data = json!({ + "content": { + "invited@localhost.com": [*DEFAULT_TEST_ROOM_ID], + }, + "event_id": "$757957878228ekrDs:localhost", + "origin_server_ts": 17195787, + "sender": "@example:localhost", + "state_key": "", + "type": "m.direct", + "unsigned": { + "age": 139298 + } + }); + sync_builder.add_global_account_data_bulk(vec![from_value(data).unwrap()]); + + mock_sync(&server, sync_builder.build_json_sync_response(), None).await; + mock_encryption_state(&server, false).await; + + let sync_settings = SyncSettings::new().timeout(Duration::from_millis(3000)); + let _response = client.sync_once(sync_settings).await.unwrap(); + + let room = client.get_room(&DEFAULT_TEST_ROOM_ID).unwrap(); + assert!(room.is_direct().await.unwrap()); +} + #[async_test] async fn test_call_notifications_ring_for_dms() { let (client, server) = logged_in_client_with_server().await; diff --git a/crates/matrix-sdk/tests/integration/room/left.rs b/crates/matrix-sdk/tests/integration/room/left.rs index f2de95649b5..367014326d0 100644 --- a/crates/matrix-sdk/tests/integration/room/left.rs +++ b/crates/matrix-sdk/tests/integration/room/left.rs @@ -70,7 +70,7 @@ async fn test_forget_direct_room() { let room = client.get_room(&DEFAULT_TEST_ROOM_ID).unwrap(); assert_eq!(room.state(), RoomState::Left); assert!(room.is_direct().await.unwrap()); - assert!(room.direct_targets().contains(invited_user_id)); + assert!(room.direct_targets().contains(invited_user_id.as_str())); let direct_account_data = client .account() @@ -80,7 +80,7 @@ async fn test_forget_direct_room() { .expect("no m.direct account data") .deserialize() .expect("failed to deserialize m.direct account data"); - assert_matches!(direct_account_data.get(invited_user_id), Some(invited_user_dms)); + assert_matches!(direct_account_data.get(invited_user_id.as_str()), Some(invited_user_dms)); assert_eq!(invited_user_dms, &[DEFAULT_TEST_ROOM_ID.to_owned()]); Mock::given(method("POST"))