diff --git a/crates/matrix-sdk-ui/src/room_list_service/mod.rs b/crates/matrix-sdk-ui/src/room_list_service/mod.rs index 6fc9d3a634c..3331b58082c 100644 --- a/crates/matrix-sdk-ui/src/room_list_service/mod.rs +++ b/crates/matrix-sdk-ui/src/room_list_service/mod.rs @@ -67,12 +67,14 @@ use matrix_sdk::{ }; pub use room_list::*; use ruma::{ - OwnedRoomId, RoomId, UInt, api::client::sync::sync_events::v5 as http, assign, + OwnedRoomId, RoomId, UInt, + api::{FeatureFlag, client::sync::sync_events::v5 as http}, + assign, events::StateEventType, }; pub use state::*; use thiserror::Error; -use tracing::{debug, error}; +use tracing::{debug, error, warn}; /// The default `required_state` constant value for sliding sync lists and /// sliding sync room subscriptions. @@ -156,12 +158,25 @@ impl RoomListService { })); if client.enabled_thread_subscriptions() { - builder = builder.with_thread_subscriptions_extension( - assign!(http::request::ThreadSubscriptions::default(), { - enabled: Some(true), - limit: Some(ruma::uint!(10)) - }), - ); + let server_features = client + .supported_versions() + .await + .map_err(|err| Error::SlidingSync(err.into()))? + .features; + + if !server_features.contains(&FeatureFlag::from("org.matrix.msc4306")) { + warn!( + "Thread subscriptions extension is requested on the client, but the server doesn't advertise support for it: not enabling." + ); + } else { + debug!("Enabling the thread subscriptions extension"); + builder = builder.with_thread_subscriptions_extension( + assign!(http::request::ThreadSubscriptions::default(), { + enabled: Some(true), + limit: Some(ruma::uint!(10)) + }), + ); + } } if share_pos { diff --git a/crates/matrix-sdk-ui/tests/integration/room_list_service.rs b/crates/matrix-sdk-ui/tests/integration/room_list_service.rs index 0ceb2d8d373..48f0d1198c7 100644 --- a/crates/matrix-sdk-ui/tests/integration/room_list_service.rs +++ b/crates/matrix-sdk-ui/tests/integration/room_list_service.rs @@ -1,4 +1,4 @@ -use std::{ops::Not, sync::Arc}; +use std::{collections::BTreeMap, ops::Not, sync::Arc}; use assert_matches::assert_matches; use eyeball_im::VectorDiff; @@ -2893,3 +2893,196 @@ async fn test_multiple_timeline_init() { // A new timeline for the same room can still be constructed. room.timeline_builder().build().await.unwrap(); } + +#[async_test] +async fn test_thread_subscriptions_extension_enabled_only_if_server_advertises_it() { + let server = MatrixMockServer::new().await; + + { + // The first time, don't advertise support for MSC4306; the extension will NOT + // enabled in this case, despite the client requesting it. + let features_map = BTreeMap::new(); + + server + .mock_versions() + .ok_custom(&["v1.11"], &features_map) + .named("/versions, first time") + .mock_once() + .mount() + .await; + + let client = server + .client_builder() + .no_server_versions() + .on_builder(|b| { + b.with_threading_support(matrix_sdk::ThreadingSupport::Enabled { + with_subscriptions: true, + }) + }) + .build() + .await; + let room_list = RoomListService::new(client.clone()).await.unwrap(); + + let sync = room_list.sync(); + pin_mut!(sync); + + let room_id = room_id!("!r0:bar.org"); + + let mock_server = server.server(); + sync_then_assert_request_and_fake_response! { + [mock_server, room_list, sync] + assert request = { + "conn_id": "room-list", + "extensions": { + "account_data": { + "enabled": true, + }, + "receipts": { + "enabled": true, + "rooms": ["*"], + }, + "typing": { + "enabled": true, + }, + }, + "lists": { + "all_rooms": { + "filters": {}, + "ranges": [ [ 0, 19, ], ], + "required_state": [ + [ "m.room.name", "" ], + [ "m.room.encryption", "" ], + [ "m.room.member", "$LAZY" ], + [ "m.room.member", "$ME" ], + [ "m.room.topic", "", ], + [ "m.room.avatar", "", ], + [ "m.room.canonical_alias", "", ], + [ "m.room.power_levels", "", ], + [ "org.matrix.msc3401.call.member", "*", ], + [ "m.room.join_rules", "", ], + [ "m.room.tombstone", "", ], + [ "m.room.create", "", ], + [ "m.room.history_visibility", "", ], + [ "io.element.functional_members", "", ], + [ "m.space.parent", "*", ], + [ "m.space.child", "*", ], + ], + "timeline_limit": 1, + }, + }, + }, + respond with = { + "pos": "0", + "lists": { + ALL_ROOMS: { + "count": 2, + }, + }, + "rooms": { + room_id: { + "initial": true, + "timeline": [ + timeline_event!("$x0:bar.org" at 0 sec), + ], + "prev_batch": "prev-batch-token" + }, + }, + }, + }; + } + + // Then, advertise support with support for MSC4306; the extension will be + // enabled in this case. + let features_map = BTreeMap::from([("org.matrix.msc4306", true)]); + + server + .mock_versions() + .ok_custom(&["v1.11"], &features_map) + .named("/versions, second time") + .mock_once() + .mount() + .await; + + let client = server + .client_builder() + .no_server_versions() + .on_builder(|b| { + b.with_threading_support(matrix_sdk::ThreadingSupport::Enabled { + with_subscriptions: true, + }) + }) + .build() + .await; + let room_list = RoomListService::new(client.clone()).await.unwrap(); + + let sync = room_list.sync(); + pin_mut!(sync); + + let room_id = room_id!("!r0:bar.org"); + + let mock_server = server.server(); + sync_then_assert_request_and_fake_response! { + [mock_server, room_list, sync] + assert request = { + "conn_id": "room-list", + "extensions": { + "account_data": { + "enabled": true, + }, + "receipts": { + "enabled": true, + "rooms": ["*"], + }, + "typing": { + "enabled": true, + }, + "io.element.msc4308.thread_subscriptions": { + "enabled": true, + "limit": 10, + }, + }, + "lists": { + "all_rooms": { + "filters": {}, + "ranges": [ [ 0, 19, ], ], + "required_state": [ + [ "m.room.name", "" ], + [ "m.room.encryption", "" ], + [ "m.room.member", "$LAZY" ], + [ "m.room.member", "$ME" ], + [ "m.room.topic", "", ], + [ "m.room.avatar", "", ], + [ "m.room.canonical_alias", "", ], + [ "m.room.power_levels", "", ], + [ "org.matrix.msc3401.call.member", "*", ], + [ "m.room.join_rules", "", ], + [ "m.room.tombstone", "", ], + [ "m.room.create", "", ], + [ "m.room.history_visibility", "", ], + [ "io.element.functional_members", "", ], + [ "m.space.parent", "*", ], + [ "m.space.child", "*", ], + ], + "timeline_limit": 1, + }, + }, + }, + respond with = { + "pos": "0", + "lists": { + ALL_ROOMS: { + "count": 2, + }, + }, + "rooms": { + room_id: { + "initial": true, + "timeline": [ + timeline_event!("$x0:bar.org" at 0 sec), + ], + "prev_batch": "prev-batch-token" + }, + }, + }, + }; +} diff --git a/crates/matrix-sdk/src/test_utils/client.rs b/crates/matrix-sdk/src/test_utils/client.rs index c86eadc5bba..2c5ade29b47 100644 --- a/crates/matrix-sdk/src/test_utils/client.rs +++ b/crates/matrix-sdk/src/test_utils/client.rs @@ -54,7 +54,7 @@ impl MockClientBuilder { } } - /// Don't cache server versions in the client. + /// Don't use an initial, cached server versions list in the client. pub fn no_server_versions(mut self) -> Self { self.server_versions = ServerVersions::None; self