Merge pull request #4780 from matrix-org/stefan/invitesRoomSummaryFallback

Invites room summary fallback
This commit is contained in:
Stefan Ceriu
2025-03-11 11:02:23 +02:00
committed by GitHub
7 changed files with 100 additions and 38 deletions

View File

@@ -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<Option<Arc<Room>>, 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<Option<Arc<Room>>, ClientError> {
let user_id = UserId::parse(user_id)?;
let sdk_room = self.inner.get_dm_room(&user_id);

View File

@@ -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<RoomMember> {
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.
///

View File

@@ -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<Arc<Room>, 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<String>) -> Result<Arc<RoomPreview>, ClientError> {

View File

@@ -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.

View File

@@ -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

View File

@@ -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,
})))
}
}

View File

@@ -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;