feat: Improve getting homeserver capabilities
This extracts the `/capabilities` logic to its own `HomeserverCapabilities` component in the SDK that can be manually asked to fetch, cache locally and return these capabilities.
This commit is contained in:
committed by
Jorge Martin Espinosa
parent
ca91b6a278
commit
5d2eab119d
@@ -47,6 +47,8 @@ All notable changes to this project will be documented in this file.
|
||||
accepts a `SyncListenerV2` callback that receives a `SyncResponseV2`
|
||||
after each successful sync.
|
||||
([#6359](https://github.com/matrix-org/matrix-rust-sdk/pull/6359))
|
||||
- Added `HomeserverCapabilities` and `Client::homeserver_capabilities()` to get the capabilities
|
||||
of the homeserver. ([#6371](https://github.com/matrix-org/matrix-rust-sdk/pull/6371))
|
||||
- Expose `Room.send_state_event_raw()` for sending arbitrary state events
|
||||
through the FFI layer.
|
||||
([#6350](https://github.com/matrix-org/matrix-rust-sdk/pull/6350))
|
||||
|
||||
@@ -2154,6 +2154,10 @@ impl Client {
|
||||
}
|
||||
}))))
|
||||
}
|
||||
|
||||
pub fn homeserver_capabilities(&self) -> HomeserverCapabilities {
|
||||
HomeserverCapabilities::new(self.inner.homeserver_capabilities())
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "experimental-element-recent-emojis")]
|
||||
@@ -3039,6 +3043,74 @@ impl From<matrix_sdk::StoreSizes> for StoreSizes {
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(uniffi::Object)]
|
||||
pub struct HomeserverCapabilities {
|
||||
inner: matrix_sdk::HomeserverCapabilities,
|
||||
}
|
||||
|
||||
impl HomeserverCapabilities {
|
||||
pub(crate) fn new(capabilities: matrix_sdk::HomeserverCapabilities) -> Self {
|
||||
Self { inner: capabilities }
|
||||
}
|
||||
}
|
||||
|
||||
#[matrix_sdk_ffi_macros::export]
|
||||
impl HomeserverCapabilities {
|
||||
pub async fn refresh(&self) -> Result<(), ClientError> {
|
||||
Ok(self.inner.refresh().await?)
|
||||
}
|
||||
|
||||
pub async fn can_change_password(&self) -> Result<bool, ClientError> {
|
||||
Ok(self.inner.can_change_password().await?)
|
||||
}
|
||||
|
||||
pub async fn can_change_displayname(&self) -> Result<bool, ClientError> {
|
||||
Ok(self.inner.can_change_displayname().await?)
|
||||
}
|
||||
|
||||
pub async fn can_change_avatar(&self) -> Result<bool, ClientError> {
|
||||
Ok(self.inner.can_change_avatar().await?)
|
||||
}
|
||||
|
||||
pub async fn can_change_thirdparty_ids(&self) -> Result<bool, ClientError> {
|
||||
Ok(self.inner.can_change_thirdparty_ids().await?)
|
||||
}
|
||||
|
||||
pub async fn can_get_login_token(&self) -> Result<bool, ClientError> {
|
||||
Ok(self.inner.can_get_login_token().await?)
|
||||
}
|
||||
|
||||
pub async fn extended_profile_fields(&self) -> Result<ExtendedProfileFields, ClientError> {
|
||||
let profile_fields = self.inner.extended_profile_fields().await?;
|
||||
Ok(ExtendedProfileFields {
|
||||
enabled: profile_fields.enabled,
|
||||
allowed: profile_fields
|
||||
.allowed
|
||||
.unwrap_or_default()
|
||||
.iter()
|
||||
.map(ToString::to_string)
|
||||
.collect(),
|
||||
disallowed: profile_fields
|
||||
.disallowed
|
||||
.unwrap_or_default()
|
||||
.iter()
|
||||
.map(ToString::to_string)
|
||||
.collect(),
|
||||
})
|
||||
}
|
||||
|
||||
pub async fn forgets_room_when_leaving(&self) -> Result<bool, ClientError> {
|
||||
Ok(self.inner.forgets_room_when_leaving().await?)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(uniffi::Record)]
|
||||
pub struct ExtendedProfileFields {
|
||||
pub enabled: bool,
|
||||
pub allowed: Vec<String>,
|
||||
pub disallowed: Vec<String>,
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use ruma::{
|
||||
|
||||
@@ -24,6 +24,7 @@ use matrix_sdk_common::{ROOM_VERSION_FALLBACK, ROOM_VERSION_RULES_FALLBACK};
|
||||
use ruma::{
|
||||
CanonicalJsonObject, EventId, MilliSecondsSinceUnixEpoch, OwnedEventId, OwnedMxcUri,
|
||||
OwnedRoomId, OwnedTransactionId, OwnedUserId, RoomId, TransactionId, UserId,
|
||||
api::client::discovery::get_capabilities::v3::Capabilities,
|
||||
canonical_json::{RedactedBecause, redact},
|
||||
events::{
|
||||
AnyGlobalAccountDataEvent, AnyRoomAccountDataEvent, AnyStrippedStateEvent,
|
||||
@@ -92,6 +93,7 @@ struct MemoryStoreInner {
|
||||
seen_knock_requests: BTreeMap<OwnedRoomId, BTreeMap<OwnedEventId, OwnedUserId>>,
|
||||
thread_subscriptions: BTreeMap<OwnedRoomId, BTreeMap<OwnedEventId, StoredThreadSubscription>>,
|
||||
thread_subscriptions_catchup_tokens: Option<Vec<ThreadSubscriptionCatchupToken>>,
|
||||
homeserver_capabilities: Option<Capabilities>,
|
||||
}
|
||||
|
||||
/// In-memory, non-persistent implementation of the `StateStore`.
|
||||
@@ -195,6 +197,10 @@ impl StateStore for MemoryStore {
|
||||
.thread_subscriptions_catchup_tokens
|
||||
.clone()
|
||||
.map(StateStoreDataValue::ThreadSubscriptionsCatchupTokens),
|
||||
StateStoreDataKey::HomeserverCapabilities => inner
|
||||
.homeserver_capabilities
|
||||
.clone()
|
||||
.map(StateStoreDataValue::HomeserverCapabilities),
|
||||
})
|
||||
}
|
||||
|
||||
@@ -270,6 +276,13 @@ impl StateStore for MemoryStore {
|
||||
"Session data is not a list of thread subscription catchup tokens",
|
||||
));
|
||||
}
|
||||
StateStoreDataKey::HomeserverCapabilities => {
|
||||
inner.homeserver_capabilities = Some(
|
||||
value
|
||||
.into_homeserver_capabilities()
|
||||
.expect("Session data is not a homeserver capabilities"),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
@@ -304,6 +317,7 @@ impl StateStore for MemoryStore {
|
||||
StateStoreDataKey::ThreadSubscriptionsCatchupTokens => {
|
||||
inner.thread_subscriptions_catchup_tokens = None;
|
||||
}
|
||||
StateStoreDataKey::HomeserverCapabilities => inner.homeserver_capabilities = None,
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@@ -28,8 +28,11 @@ use ruma::{
|
||||
OwnedTransactionId, OwnedUserId, RoomId, TransactionId, UserId,
|
||||
api::{
|
||||
SupportedVersions,
|
||||
client::discovery::discover_homeserver::{
|
||||
self, HomeserverInfo, IdentityServerInfo, RtcFocusInfo, TileServerInfo,
|
||||
client::discovery::{
|
||||
discover_homeserver::{
|
||||
self, HomeserverInfo, IdentityServerInfo, RtcFocusInfo, TileServerInfo,
|
||||
},
|
||||
get_capabilities::v3::Capabilities,
|
||||
},
|
||||
},
|
||||
events::{
|
||||
@@ -1172,6 +1175,9 @@ pub enum StateStoreDataValue {
|
||||
/// See documentation of [`ThreadSubscriptionCatchupToken`] for more
|
||||
/// details.
|
||||
ThreadSubscriptionsCatchupTokens(Vec<ThreadSubscriptionCatchupToken>),
|
||||
|
||||
/// The capabilities the homeserver supports or disables.
|
||||
HomeserverCapabilities(Capabilities),
|
||||
}
|
||||
|
||||
/// Tokens to use when catching up on thread subscriptions.
|
||||
@@ -1373,6 +1379,12 @@ impl StateStoreDataValue {
|
||||
) -> Option<Vec<ThreadSubscriptionCatchupToken>> {
|
||||
as_variant!(self, Self::ThreadSubscriptionsCatchupTokens)
|
||||
}
|
||||
|
||||
/// Get this value if it is the data for the capabilities the homeserver
|
||||
/// supports or disables.
|
||||
pub fn into_homeserver_capabilities(self) -> Option<Capabilities> {
|
||||
as_variant!(self, Self::HomeserverCapabilities)
|
||||
}
|
||||
}
|
||||
|
||||
/// A key for key-value data.
|
||||
@@ -1415,6 +1427,9 @@ pub enum StateStoreDataKey<'a> {
|
||||
|
||||
/// A list of thread subscriptions catchup tokens.
|
||||
ThreadSubscriptionsCatchupTokens,
|
||||
|
||||
/// A list of capabilities that the homeserver supports.
|
||||
HomeserverCapabilities,
|
||||
}
|
||||
|
||||
impl StateStoreDataKey<'_> {
|
||||
@@ -1460,6 +1475,9 @@ impl StateStoreDataKey<'_> {
|
||||
/// [`ThreadSubscriptionsCatchupTokens`][Self::ThreadSubscriptionsCatchupTokens] variant.
|
||||
pub const THREAD_SUBSCRIPTIONS_CATCHUP_TOKENS: &'static str =
|
||||
"thread_subscriptions_catchup_tokens";
|
||||
|
||||
/// Key prefix to use for the homeserver's [`Capabilities`].
|
||||
pub const HOMESERVER_CAPABILITIES: &'static str = "homeserver_capabilities";
|
||||
}
|
||||
|
||||
/// Compare two thread subscription changes bump stamps, given a fixed room and
|
||||
|
||||
@@ -42,6 +42,7 @@ use matrix_sdk_store_encryption::{Error as EncryptionError, StoreCipher};
|
||||
use ruma::{
|
||||
CanonicalJsonObject, EventId, MilliSecondsSinceUnixEpoch, OwnedEventId, OwnedMxcUri,
|
||||
OwnedRoomId, OwnedTransactionId, OwnedUserId, RoomId, TransactionId, UserId,
|
||||
api::client::discovery::get_capabilities::v3::Capabilities,
|
||||
canonical_json::{RedactedBecause, redact},
|
||||
events::{
|
||||
AnyGlobalAccountDataEvent, AnyRoomAccountDataEvent, AnySyncStateEvent,
|
||||
@@ -506,6 +507,9 @@ impl IndexeddbStateStore {
|
||||
StateStoreDataKey::ThreadSubscriptionsCatchupTokens => {
|
||||
self.encode_key(keys::KV, StateStoreDataKey::THREAD_SUBSCRIPTIONS_CATCHUP_TOKENS)
|
||||
}
|
||||
StateStoreDataKey::HomeserverCapabilities => {
|
||||
self.encode_key(keys::KV, StateStoreDataKey::HOMESERVER_CAPABILITIES)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -668,6 +672,10 @@ impl_state_store!({
|
||||
.map(|f| self.deserialize_value::<Vec<ThreadSubscriptionCatchupToken>>(&f))
|
||||
.transpose()?
|
||||
.map(StateStoreDataValue::ThreadSubscriptionsCatchupTokens),
|
||||
StateStoreDataKey::HomeserverCapabilities => value
|
||||
.map(|f| self.deserialize_value::<Capabilities>(&f))
|
||||
.transpose()?
|
||||
.map(StateStoreDataValue::HomeserverCapabilities),
|
||||
};
|
||||
|
||||
Ok(value)
|
||||
@@ -719,6 +727,11 @@ impl_state_store!({
|
||||
.into_thread_subscriptions_catchup_tokens()
|
||||
.expect("Session data is not a list of thread subscription catchup tokens"),
|
||||
),
|
||||
StateStoreDataKey::HomeserverCapabilities => self.serialize_value(
|
||||
&value
|
||||
.into_homeserver_capabilities()
|
||||
.expect("Session data is not a homeserver capabilities"),
|
||||
),
|
||||
};
|
||||
|
||||
let tx = self.inner.transaction(keys::KV).with_mode(TransactionMode::Readwrite).build()?;
|
||||
|
||||
@@ -515,6 +515,9 @@ impl SqliteStateStore {
|
||||
StateStoreDataKey::ThreadSubscriptionsCatchupTokens => {
|
||||
Cow::Borrowed(StateStoreDataKey::THREAD_SUBSCRIPTIONS_CATCHUP_TOKENS)
|
||||
}
|
||||
StateStoreDataKey::HomeserverCapabilities => {
|
||||
Cow::Borrowed(StateStoreDataKey::HOMESERVER_CAPABILITIES)
|
||||
}
|
||||
};
|
||||
|
||||
self.encode_key(keys::KV_BLOB, &*key_s)
|
||||
@@ -1161,6 +1164,9 @@ impl StateStore for SqliteStateStore {
|
||||
self.deserialize_value(&data)?,
|
||||
)
|
||||
}
|
||||
StateStoreDataKey::HomeserverCapabilities => {
|
||||
StateStoreDataValue::HomeserverCapabilities(self.deserialize_value(&data)?)
|
||||
}
|
||||
})
|
||||
})
|
||||
.transpose()
|
||||
@@ -1211,6 +1217,11 @@ impl StateStore for SqliteStateStore {
|
||||
.into_thread_subscriptions_catchup_tokens()
|
||||
.expect("Session data is not a list of thread subscription catchup tokens"),
|
||||
)?,
|
||||
StateStoreDataKey::HomeserverCapabilities => self.serialize_value(
|
||||
&value
|
||||
.into_homeserver_capabilities()
|
||||
.expect("Session data is not the homeserver capabilities"),
|
||||
)?,
|
||||
};
|
||||
|
||||
self.write()
|
||||
|
||||
@@ -8,6 +8,9 @@ All notable changes to this project will be documented in this file.
|
||||
|
||||
### Features
|
||||
|
||||
- [**breaking**] Added `HomeserverCapabilities` and `Client::homeserver_capabilities()` to get the capabilities
|
||||
of the homeserver. This replaces `Client::get_capabilities()`.
|
||||
([#6371](https://github.com/matrix-org/matrix-rust-sdk/pull/6371))
|
||||
- [**breaking**] `matrix_sdk::error::Error` has a new variant `Timeout` which occurs when
|
||||
a cross-signing reset does not succeed after some period of time.
|
||||
([#6325](https://github.com/matrix-org/matrix-rust-sdk/pull/6325))
|
||||
|
||||
@@ -437,8 +437,8 @@ impl Account {
|
||||
|
||||
/// Set the given field of our own user's profile.
|
||||
///
|
||||
/// [`Client::get_capabilities()`] should be called first to check it the
|
||||
/// field can be set on the homeserver.
|
||||
/// [`Client::homeserver_capabilities()`] should be called first to check it
|
||||
/// the field can be set on the homeserver.
|
||||
///
|
||||
/// # Arguments
|
||||
///
|
||||
@@ -457,8 +457,8 @@ impl Account {
|
||||
|
||||
/// Delete the given field of our own user's profile.
|
||||
///
|
||||
/// [`Client::get_capabilities()`] should be called first to check it the
|
||||
/// field can be modified on the homeserver.
|
||||
/// [`Client::homeserver_capabilities()`] should be called first to check it
|
||||
/// the field can be modified on the homeserver.
|
||||
///
|
||||
/// # Arguments
|
||||
///
|
||||
|
||||
@@ -0,0 +1,258 @@
|
||||
use matrix_sdk_base::{StateStoreDataKey, StateStoreDataValue};
|
||||
use ruma::api::client::{
|
||||
discovery::{
|
||||
get_capabilities,
|
||||
get_capabilities::v3::{Capabilities, ProfileFieldsCapability},
|
||||
},
|
||||
profile::ProfileFieldName,
|
||||
};
|
||||
use tracing::warn;
|
||||
|
||||
use crate::{Client, HttpResult};
|
||||
|
||||
/// Helper to check what [`Capabilities`] are supported by the homeserver.
|
||||
///
|
||||
/// Spec: <https://spec.matrix.org/latest/client-server-api/#capabilities-negotiation>
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct HomeserverCapabilities {
|
||||
client: Client,
|
||||
}
|
||||
|
||||
impl HomeserverCapabilities {
|
||||
/// Creates a new [`HomeserverCapabilities`] instance.
|
||||
pub fn new(client: Client) -> Self {
|
||||
Self { client }
|
||||
}
|
||||
|
||||
/// Forces a refresh of the cached value using the `/capabilities` endpoint.
|
||||
pub async fn refresh(&self) -> crate::Result<()> {
|
||||
self.get_and_cache_remote_capabilities().await?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Returns whether the user can change their password or not.
|
||||
pub async fn can_change_password(&self) -> crate::Result<bool> {
|
||||
let capabilities = self.load_or_fetch_homeserver_capabilities().await?;
|
||||
Ok(capabilities.change_password.enabled)
|
||||
}
|
||||
|
||||
/// Returns whether the user can change their display name or not.
|
||||
///
|
||||
/// This will first check the `m.profile_fields` capability and use it if
|
||||
/// present, or fall back to `m.set_displayname` otherwise.
|
||||
///
|
||||
/// Spec: <https://spec.matrix.org/latest/client-server-api/#mset_displayname-capability>
|
||||
pub async fn can_change_displayname(&self) -> crate::Result<bool> {
|
||||
let capabilities = self.load_or_fetch_homeserver_capabilities().await?;
|
||||
if let Some(profile_fields) = capabilities.profile_fields
|
||||
&& profile_fields.enabled
|
||||
{
|
||||
let allowed = profile_fields.allowed.unwrap_or_default();
|
||||
let disallowed = profile_fields.disallowed.unwrap_or_default();
|
||||
return Ok(allowed.contains(&ProfileFieldName::DisplayName)
|
||||
|| !disallowed.contains(&ProfileFieldName::DisplayName));
|
||||
}
|
||||
#[allow(deprecated)]
|
||||
Ok(capabilities.set_displayname.enabled)
|
||||
}
|
||||
|
||||
/// Returns whether the user can change their avatar or not.
|
||||
///
|
||||
/// This will first check the `m.profile_fields` capability and use it if
|
||||
/// present, or fall back to `m.set_avatar_url` otherwise.
|
||||
///
|
||||
/// Spec: <https://spec.matrix.org/latest/client-server-api/#mset_avatar_url-capability>
|
||||
pub async fn can_change_avatar(&self) -> crate::Result<bool> {
|
||||
let capabilities = self.load_or_fetch_homeserver_capabilities().await?;
|
||||
if let Some(profile_fields) = capabilities.profile_fields
|
||||
&& profile_fields.enabled
|
||||
{
|
||||
let allowed = profile_fields.allowed.unwrap_or_default();
|
||||
let disallowed = profile_fields.disallowed.unwrap_or_default();
|
||||
return Ok(allowed.contains(&ProfileFieldName::AvatarUrl)
|
||||
|| !disallowed.contains(&ProfileFieldName::AvatarUrl));
|
||||
}
|
||||
#[allow(deprecated)]
|
||||
Ok(capabilities.set_avatar_url.enabled)
|
||||
}
|
||||
|
||||
/// Returns whether the user can add, remove, or change 3PID associations on
|
||||
/// their account.
|
||||
///
|
||||
/// Spec: <https://spec.matrix.org/latest/client-server-api/#m3pid_changes-capability>
|
||||
pub async fn can_change_thirdparty_ids(&self) -> crate::Result<bool> {
|
||||
let capabilities = self.load_or_fetch_homeserver_capabilities().await?;
|
||||
Ok(capabilities.thirdparty_id_changes.enabled)
|
||||
}
|
||||
|
||||
/// Returns whether the user is able to use `POST /login/get_token` to
|
||||
/// generate single-use, time-limited tokens to log unauthenticated
|
||||
/// clients into their account.
|
||||
///
|
||||
/// When not listed, clients SHOULD assume the user is unable to generate
|
||||
/// tokens.
|
||||
///
|
||||
/// Spec: <https://spec.matrix.org/latest/client-server-api/#mget_login_token-capability>
|
||||
pub async fn can_get_login_token(&self) -> crate::Result<bool> {
|
||||
let capabilities = self.load_or_fetch_homeserver_capabilities().await?;
|
||||
Ok(capabilities.get_login_token.enabled)
|
||||
}
|
||||
|
||||
/// Returns which profile fields the user is able to change.
|
||||
///
|
||||
/// Spec: <https://spec.matrix.org/latest/client-server-api/#mprofile_fields-capability>
|
||||
pub async fn extended_profile_fields(&self) -> crate::Result<ProfileFieldsCapability> {
|
||||
let capabilities = self.load_or_fetch_homeserver_capabilities().await?;
|
||||
if let Some(profile_fields) = capabilities.profile_fields {
|
||||
return Ok(profile_fields);
|
||||
}
|
||||
Ok(ProfileFieldsCapability::new(false))
|
||||
}
|
||||
|
||||
/// Returns whether or not the server automatically forgets rooms which the
|
||||
/// user has left.
|
||||
///
|
||||
/// Spec: <https://spec.matrix.org/latest/client-server-api/#mforget_forced_upon_leave-capability>
|
||||
pub async fn forgets_room_when_leaving(&self) -> crate::Result<bool> {
|
||||
let capabilities = self.load_or_fetch_homeserver_capabilities().await?;
|
||||
Ok(capabilities.forget_forced_upon_leave.enabled)
|
||||
}
|
||||
|
||||
/// Gets the supported [`Capabilities`] either from the local cache or from
|
||||
/// the homeserver using the `/capabilities` endpoint if the data is not
|
||||
/// cached.
|
||||
///
|
||||
/// To ensure you get updated values, you should call [`Self::refresh`]
|
||||
/// instead.
|
||||
async fn load_or_fetch_homeserver_capabilities(&self) -> crate::Result<Capabilities> {
|
||||
match self.client.state_store().get_kv_data(StateStoreDataKey::HomeserverCapabilities).await
|
||||
{
|
||||
Ok(Some(stored)) => {
|
||||
if let Some(capabilities) = stored.into_homeserver_capabilities() {
|
||||
return Ok(capabilities);
|
||||
}
|
||||
}
|
||||
Ok(None) => {
|
||||
// fallthrough: cache is empty
|
||||
}
|
||||
Err(err) => {
|
||||
warn!("error when loading cached homeserver capabilities: {err}");
|
||||
// fallthrough to network.
|
||||
}
|
||||
}
|
||||
|
||||
Ok(self.get_and_cache_remote_capabilities().await?)
|
||||
}
|
||||
|
||||
/// Gets and caches the capabilities of the homeserver.
|
||||
async fn get_and_cache_remote_capabilities(&self) -> HttpResult<Capabilities> {
|
||||
let res = self.client.send(get_capabilities::v3::Request::new()).await?;
|
||||
|
||||
if let Err(err) = self
|
||||
.client
|
||||
.state_store()
|
||||
.set_kv_data(
|
||||
StateStoreDataKey::HomeserverCapabilities,
|
||||
StateStoreDataValue::HomeserverCapabilities(res.capabilities.clone()),
|
||||
)
|
||||
.await
|
||||
{
|
||||
warn!("error when caching homeserver capabilities: {err}");
|
||||
}
|
||||
|
||||
Ok(res.capabilities)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(all(not(target_family = "wasm"), test))]
|
||||
mod tests {
|
||||
use matrix_sdk_test::async_test;
|
||||
|
||||
use super::*;
|
||||
use crate::test_utils::mocks::MatrixMockServer;
|
||||
|
||||
#[async_test]
|
||||
async fn test_refresh_always_updates_capabilities() {
|
||||
let server = MatrixMockServer::new().await;
|
||||
let client = server.client_builder().build().await;
|
||||
|
||||
// Set the expected capabilities to something we can check
|
||||
let mut expected_capabilities = Capabilities::default();
|
||||
expected_capabilities.change_password.enabled = true;
|
||||
server
|
||||
.mock_get_homeserver_capabilities()
|
||||
.ok_with_capabilities(expected_capabilities)
|
||||
.mock_once()
|
||||
.mount()
|
||||
.await;
|
||||
|
||||
// Refresh the capabilities
|
||||
let capabilities = client.homeserver_capabilities();
|
||||
capabilities.refresh().await.expect("refreshing capabilities failed");
|
||||
|
||||
// Check the values we get are updated
|
||||
assert!(capabilities.can_change_password().await.expect("checking capabilities failed"));
|
||||
|
||||
let mut expected_capabilities = Capabilities::default();
|
||||
expected_capabilities.change_password.enabled = false;
|
||||
server
|
||||
.mock_get_homeserver_capabilities()
|
||||
.ok_with_capabilities(expected_capabilities)
|
||||
.mock_once()
|
||||
.mount()
|
||||
.await;
|
||||
|
||||
// Check the values we get are not updated without a refresh, they're loaded
|
||||
// from the cache
|
||||
assert!(capabilities.can_change_password().await.expect("checking capabilities failed"));
|
||||
|
||||
// Do another refresh to make sure we get the updated values
|
||||
capabilities.refresh().await.expect("refreshing capabilities failed");
|
||||
|
||||
// Check the values we get are updated
|
||||
assert!(!capabilities.can_change_password().await.expect("checking capabilities failed"));
|
||||
}
|
||||
|
||||
#[async_test]
|
||||
async fn test_get_functions_refresh_the_data_if_not_available_or_use_cache_if_available() {
|
||||
let server = MatrixMockServer::new().await;
|
||||
let client = server.client_builder().build().await;
|
||||
|
||||
// Set the expected capabilities to something we can check
|
||||
let mut expected_capabilities = Capabilities::default();
|
||||
let mut profile_fields = ProfileFieldsCapability::new(true);
|
||||
profile_fields.allowed = Some(vec![ProfileFieldName::DisplayName]);
|
||||
expected_capabilities.profile_fields = Some(profile_fields);
|
||||
server
|
||||
.mock_get_homeserver_capabilities()
|
||||
.ok_with_capabilities(expected_capabilities)
|
||||
// Ensure it's called just once
|
||||
.mock_once()
|
||||
.mount()
|
||||
.await;
|
||||
|
||||
// Refresh the capabilities
|
||||
let capabilities = client.homeserver_capabilities();
|
||||
|
||||
// Check the values we get are updated
|
||||
assert!(capabilities.can_change_displayname().await.expect("checking capabilities failed"));
|
||||
|
||||
// Now revert the previous mock so we can check we're getting the cached value
|
||||
// instead of this one
|
||||
let mut expected_capabilities = Capabilities::default();
|
||||
let mut profile_fields = ProfileFieldsCapability::new(true);
|
||||
profile_fields.disallowed = Some(vec![ProfileFieldName::DisplayName]);
|
||||
expected_capabilities.profile_fields = Some(profile_fields);
|
||||
server
|
||||
.mock_get_homeserver_capabilities()
|
||||
.ok_with_capabilities(expected_capabilities)
|
||||
// Ensure it's not called, since we'd be using the cached values instead
|
||||
.never()
|
||||
.mount()
|
||||
.await;
|
||||
|
||||
// Check the values we get are not updated without a refresh, they're loaded
|
||||
// from the cache
|
||||
assert!(capabilities.can_change_displayname().await.expect("checking capabilities failed"));
|
||||
}
|
||||
}
|
||||
@@ -59,7 +59,6 @@ use ruma::{
|
||||
directory::{get_public_rooms, get_public_rooms_filtered},
|
||||
discovery::{
|
||||
discover_homeserver::{self, RtcFocusInfo},
|
||||
get_capabilities::{self, v3::Capabilities},
|
||||
get_supported_versions,
|
||||
},
|
||||
error::{ErrorKind, UnknownTokenErrorData},
|
||||
@@ -98,7 +97,10 @@ use crate::{
|
||||
AuthCtx, AuthData, ReloadSessionCallback, SaveSessionCallback, matrix::MatrixAuth,
|
||||
oauth::OAuth,
|
||||
},
|
||||
client::thread_subscriptions::ThreadSubscriptionCatchup,
|
||||
client::{
|
||||
homeserver_capabilities::HomeserverCapabilities,
|
||||
thread_subscriptions::ThreadSubscriptionCatchup,
|
||||
},
|
||||
config::{RequestConfig, SyncToken},
|
||||
deduplicating_handler::DeduplicatingHandler,
|
||||
error::HttpResult,
|
||||
@@ -129,6 +131,7 @@ use crate::{
|
||||
mod builder;
|
||||
pub(crate) mod caches;
|
||||
pub(crate) mod futures;
|
||||
pub(crate) mod homeserver_capabilities;
|
||||
pub(crate) mod thread_subscriptions;
|
||||
|
||||
pub use self::builder::{ClientBuildError, ClientBuilder, sanitize_server_name};
|
||||
@@ -567,30 +570,10 @@ impl Client {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Get the capabilities of the homeserver.
|
||||
///
|
||||
/// This method should be used to check what features are supported by the
|
||||
/// homeserver.
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// ```no_run
|
||||
/// # use matrix_sdk::Client;
|
||||
/// # use url::Url;
|
||||
/// # async {
|
||||
/// # let homeserver = Url::parse("http://example.com")?;
|
||||
/// let client = Client::new(homeserver).await?;
|
||||
///
|
||||
/// let capabilities = client.get_capabilities().await?;
|
||||
///
|
||||
/// if capabilities.change_password.enabled {
|
||||
/// // Change password
|
||||
/// }
|
||||
/// # anyhow::Ok(()) };
|
||||
/// ```
|
||||
pub async fn get_capabilities(&self) -> HttpResult<Capabilities> {
|
||||
let res = self.send(get_capabilities::v3::Request::new()).await?;
|
||||
Ok(res.capabilities)
|
||||
/// Retrieves a helper component to access the [`HomeserverCapabilities`]
|
||||
/// supported or disabled by the homeserver.
|
||||
pub fn homeserver_capabilities(&self) -> HomeserverCapabilities {
|
||||
HomeserverCapabilities::new(self.clone())
|
||||
}
|
||||
|
||||
/// Get the server vendor information from the federation API.
|
||||
|
||||
@@ -1052,7 +1052,11 @@ mod tests {
|
||||
// All of Client's async methods that do network requests (and
|
||||
// possibly some that don't) are `!Send` on wasm. We obviously want
|
||||
// to be able to use them in event handlers.
|
||||
let _caps = client.get_capabilities().await.map_err(|e| anyhow::anyhow!("{}", e))?;
|
||||
client
|
||||
.homeserver_capabilities()
|
||||
.refresh()
|
||||
.await
|
||||
.map_err(|e| anyhow::anyhow!("{}", e))?;
|
||||
anyhow::Ok(())
|
||||
});
|
||||
}
|
||||
|
||||
@@ -65,6 +65,7 @@ pub mod widget;
|
||||
|
||||
pub use account::Account;
|
||||
pub use authentication::{AuthApi, AuthSession, SessionTokens};
|
||||
pub use client::homeserver_capabilities::HomeserverCapabilities;
|
||||
#[cfg(feature = "experimental-search")]
|
||||
pub mod search_index;
|
||||
pub use client::{
|
||||
|
||||
@@ -35,6 +35,7 @@ use ruma::{
|
||||
DeviceId, EventId, MilliSecondsSinceUnixEpoch, MxcUri, OwnedDeviceId, OwnedEventId,
|
||||
OwnedOneTimeKeyId, OwnedRoomId, OwnedUserId, RoomId, ServerName, UserId,
|
||||
api::client::{
|
||||
discovery::get_capabilities::v3::Capabilities,
|
||||
profile::{ProfileFieldName, ProfileFieldValue},
|
||||
receipt::create_receipt::v3::ReceiptType,
|
||||
room::Visibility,
|
||||
@@ -1685,6 +1686,15 @@ impl MatrixMockServer {
|
||||
Mock::given(method("GET")).and(path(format!("/_matrix/client/v3/profile/{user_id}")));
|
||||
self.mock_endpoint(mock, GetProfileEndpoint)
|
||||
}
|
||||
|
||||
/// Create a prebuilt mock for the endpoint used to get the capabilities of
|
||||
/// the homeserver.
|
||||
pub fn mock_get_homeserver_capabilities(
|
||||
&self,
|
||||
) -> MockEndpoint<'_, GetHomeserverCapabilitiesEndpoint> {
|
||||
let mock = Mock::given(method("GET")).and(path("/_matrix/client/v3/capabilities"));
|
||||
self.mock_endpoint(mock, GetHomeserverCapabilitiesEndpoint)
|
||||
}
|
||||
}
|
||||
|
||||
/// A specification for a push rule ID.
|
||||
@@ -4916,3 +4926,15 @@ impl<'a> MockEndpoint<'a, GetProfileEndpoint> {
|
||||
self.respond_with(ResponseTemplate::new(200).set_body_json(profile))
|
||||
}
|
||||
}
|
||||
|
||||
/// A prebuilt mock for `GET /_matrix/client/*/capabilities`.
|
||||
pub struct GetHomeserverCapabilitiesEndpoint;
|
||||
|
||||
impl<'a> MockEndpoint<'a, GetHomeserverCapabilitiesEndpoint> {
|
||||
/// Returns a successful empty response.
|
||||
pub fn ok_with_capabilities(self, capabilities: Capabilities) -> MatrixMock<'a> {
|
||||
self.respond_with(ResponseTemplate::new(200).set_body_json(json!({
|
||||
"capabilities": capabilities,
|
||||
})))
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user