diff --git a/bindings/matrix-sdk-ffi/src/client.rs b/bindings/matrix-sdk-ffi/src/client.rs
index 43e46cec290..d3a27801bad 100644
--- a/bindings/matrix-sdk-ffi/src/client.rs
+++ b/bindings/matrix-sdk-ffi/src/client.rs
@@ -886,6 +886,24 @@ impl Client {
self.inner.rooms().into_iter().map(|room| Arc::new(Room::new(room))).collect()
}
+ /// Get a room by its ID.
+ ///
+ /// # Arguments
+ ///
+ /// * `room_id` - The ID of the room to get.
+ ///
+ /// # Returns
+ ///
+ /// A `Result` containing an optional room, or a `ClientError`.
+ /// This method will not initialize the room's timeline or populate it with
+ /// events.
+ pub fn get_room(&self, room_id: String) -> Result>, ClientError> {
+ let room_id = RoomId::parse(room_id)?;
+ let sdk_room = self.inner.get_room(&room_id);
+ let room = sdk_room.map(|room| Arc::new(Room::new(room)));
+ Ok(room)
+ }
+
pub fn get_dm_room(&self, user_id: String) -> Result >, ClientError> {
let user_id = UserId::parse(user_id)?;
let sdk_room = self.inner.get_dm_room(&user_id);
diff --git a/bindings/matrix-sdk-ffi/src/room.rs b/bindings/matrix-sdk-ffi/src/room.rs
index 8a481300040..bfe3d896d61 100644
--- a/bindings/matrix-sdk-ffi/src/room.rs
+++ b/bindings/matrix-sdk-ffi/src/room.rs
@@ -161,21 +161,6 @@ impl Room {
self.inner.active_room_call_participants().iter().map(|u| u.to_string()).collect()
}
- /// For rooms one is invited to, retrieves the room member information for
- /// the user who invited the logged-in user to a room.
- pub async fn inviter(&self) -> Option {
- if self.inner.state() == RoomState::Invited {
- self.inner
- .invite_details()
- .await
- .ok()
- .and_then(|a| a.inviter)
- .and_then(|m| m.try_into().ok())
- } else {
- None
- }
- }
-
/// Forces the currently active room key, which is used to encrypt messages,
/// to be rotated.
///
diff --git a/bindings/matrix-sdk-ffi/src/room_list.rs b/bindings/matrix-sdk-ffi/src/room_list.rs
index a566dac2905..08edd781418 100644
--- a/bindings/matrix-sdk-ffi/src/room_list.rs
+++ b/bindings/matrix-sdk-ffi/src/room_list.rs
@@ -573,24 +573,6 @@ impl RoomListItem {
self.inner.inner_room().state().into()
}
- /// Builds a `Room` FFI from an invited room without initializing its
- /// internal timeline.
- ///
- /// An error will be returned if the room is a state different than invited.
- ///
- /// ⚠️ Holding on to this room instance after it has been joined is not
- /// safe. Use `full_room` instead.
- #[deprecated(note = "Please use `preview_room` instead.")]
- fn invited_room(&self) -> Result, RoomListError> {
- if !matches!(self.membership(), Membership::Invited) {
- return Err(RoomListError::IncorrectRoomMembership {
- expected: vec![Membership::Invited],
- actual: self.membership(),
- });
- }
- Ok(Arc::new(Room::new(self.inner.inner_room().clone())))
- }
-
/// Builds a `RoomPreview` from a room list item. This is intended for
/// invited, knocked or banned rooms.
async fn preview_room(&self, via: Vec) -> Result, ClientError> {
diff --git a/bindings/matrix-sdk-ffi/src/room_preview.rs b/bindings/matrix-sdk-ffi/src/room_preview.rs
index e91459257d8..d9e7ba697a1 100644
--- a/bindings/matrix-sdk-ffi/src/room_preview.rs
+++ b/bindings/matrix-sdk-ffi/src/room_preview.rs
@@ -51,11 +51,23 @@ impl RoomPreview {
/// Leave the room if the room preview state is either joined, invited or
/// knocked.
///
+ /// If rejecting an invite then also forget it as an extra layer of
+ /// protection against spam attacks.
+ ///
/// Will return an error otherwise.
pub async fn leave(&self) -> Result<(), ClientError> {
let room =
self.client.get_room(&self.inner.room_id).context("missing room for a room preview")?;
- room.leave().await.map_err(Into::into)
+
+ let should_forget = matches!(room.state(), matrix_sdk::RoomState::Invited);
+
+ room.leave().await.map_err(ClientError::from)?;
+
+ if should_forget {
+ _ = self.forget().await;
+ }
+
+ Ok(())
}
/// Get the user who created the invite, if any.
diff --git a/crates/matrix-sdk/src/room_preview.rs b/crates/matrix-sdk/src/room_preview.rs
index d56709baef4..315643a4c30 100644
--- a/crates/matrix-sdk/src/room_preview.rs
+++ b/crates/matrix-sdk/src/room_preview.rs
@@ -31,7 +31,7 @@ use ruma::{
use tokio::try_join;
use tracing::{instrument, warn};
-use crate::{room_directory_search::RoomDirectorySearch, Client, Room};
+use crate::{room_directory_search::RoomDirectorySearch, Client, Error, Room};
/// The preview of a room, be it invited/joined/left, or not.
#[derive(Debug, Clone)]
@@ -172,8 +172,21 @@ impl RoomPreview {
}
}
- // Resort to using the room state endpoint, as well as the joined members one.
- Self::from_state_events(client, &room_id).await
+ // Try using the room state endpoint, as well as the joined members one.
+ match Self::from_state_events(client, &room_id).await {
+ Ok(res) => return Ok(res),
+ Err(err) => {
+ warn!("error when building room preview from state events: {err}");
+ }
+ }
+
+ // Finally, if everything else fails, try to build the room from information
+ // that the client itself might have about it.
+ if let Some(room) = client.get_room(&room_id) {
+ Ok(Self::from_known_room(&room).await)
+ } else {
+ Err(Error::InsufficientData)
+ }
}
/// Get a [`RoomPreview`] by searching in the room directory for the
diff --git a/crates/matrix-sdk/src/test_utils/mocks/mod.rs b/crates/matrix-sdk/src/test_utils/mocks/mod.rs
index 6e71e9dad03..9d0683b9cd3 100644
--- a/crates/matrix-sdk/src/test_utils/mocks/mod.rs
+++ b/crates/matrix-sdk/src/test_utils/mocks/mod.rs
@@ -989,6 +989,13 @@ impl MatrixMockServer {
.and(path_regex(r"^/_matrix/client/v3/keys/signatures/upload"));
self.mock_endpoint(mock, UploadCrossSigningSignaturesEndpoint).expect_default_access_token()
}
+
+ /// Creates a prebuilt mock for the endpoint used to leave a room.
+ pub fn mock_room_leave(&self) -> MockEndpoint<'_, RoomLeaveEndpoint> {
+ let mock =
+ Mock::given(method("POST")).and(path_regex(r"^/_matrix/client/v3/rooms/.*/leave"));
+ self.mock_endpoint(mock, RoomLeaveEndpoint).expect_default_access_token()
+ }
}
/// Parameter to [`MatrixMockServer::sync_room`].
@@ -2498,3 +2505,16 @@ impl<'a> MockEndpoint<'a, UploadCrossSigningSignaturesEndpoint> {
self.respond_with(ResponseTemplate::new(200).set_body_json(json!({})))
}
}
+
+/// A prebuilt mock for the room leave endpoint.
+pub struct RoomLeaveEndpoint;
+
+impl<'a> MockEndpoint<'a, RoomLeaveEndpoint> {
+ /// Returns a successful response with some default data for the given room
+ /// id.
+ pub fn ok(self, room_id: &RoomId) -> MatrixMock<'a> {
+ self.respond_with(ResponseTemplate::new(200).set_body_json(json!({
+ "room_id": room_id,
+ })))
+ }
+}
diff --git a/crates/matrix-sdk/tests/integration/room_preview.rs b/crates/matrix-sdk/tests/integration/room_preview.rs
index 9b05d6fe9a1..eadcf3f9e62 100644
--- a/crates/matrix-sdk/tests/integration/room_preview.rs
+++ b/crates/matrix-sdk/tests/integration/room_preview.rs
@@ -1,5 +1,9 @@
+use assert_matches::assert_matches;
use js_int::uint;
-use matrix_sdk::{config::SyncSettings, test_utils::logged_in_client_with_server};
+use matrix_sdk::{
+ config::SyncSettings,
+ test_utils::{logged_in_client_with_server, mocks::MatrixMockServer},
+};
use matrix_sdk_base::RoomState;
use matrix_sdk_test::{
async_test, InvitedRoomBuilder, JoinedRoomBuilder, KnockedRoomBuilder, SyncResponseBuilder,
@@ -50,6 +54,34 @@ async fn test_room_preview_leave_invited() {
assert_eq!(client.get_room(room_id).unwrap().state(), RoomState::Left);
}
+#[async_test]
+async fn test_room_preview_invite_leave_room_summary_msc3266_disabled() {
+ let server = MatrixMockServer::new().await;
+ let client = server.client_builder().build().await;
+ let room_id = room_id!("!room:localhost");
+
+ server.sync_room(&client, InvitedRoomBuilder::new(room_id)).await;
+
+ // A preview should be built from the sync data above
+ let preview = client
+ .get_room_preview(room_id.into(), Vec::new())
+ .await
+ .expect("Room preview should be retrieved");
+
+ assert_eq!(preview.room_id, room_id);
+ assert_matches!(preview.state.unwrap(), RoomState::Invited);
+
+ server.mock_room_leave().ok(room_id).expect(1).mount().await;
+
+ client.get_room(room_id).unwrap().leave().await.unwrap();
+
+ assert_matches!(client.get_room(room_id).unwrap().state(), RoomState::Left);
+ assert_matches!(
+ client.get_room_preview(room_id.into(), Vec::new()).await.unwrap().state.unwrap(),
+ RoomState::Left
+ );
+}
+
#[async_test]
async fn test_room_preview_leave_knocked() {
let (client, server) = logged_in_client_with_server().await;