matrix_sdk/client/
homeserver_capabilities.rs1use matrix_sdk_base::{StateStoreDataKey, StateStoreDataValue};
2use ruma::{
3 api::client::discovery::{
4 get_capabilities,
5 get_capabilities::v3::{
6 AccountModerationCapability, Capabilities, ProfileFieldsCapability,
7 RoomVersionsCapability,
8 },
9 },
10 profile::ProfileFieldName,
11};
12use tracing::warn;
13
14use crate::{Client, HttpResult};
15
16#[derive(Debug, Clone)]
20pub struct HomeserverCapabilities {
21 client: Client,
22}
23
24impl HomeserverCapabilities {
25 pub fn new(client: Client) -> Self {
27 Self { client }
28 }
29
30 pub async fn refresh(&self) -> crate::Result<()> {
32 self.get_and_cache_remote_capabilities().await?;
33 Ok(())
34 }
35
36 pub async fn can_change_password(&self) -> crate::Result<bool> {
38 let capabilities = self.load_or_fetch_homeserver_capabilities().await?;
39 Ok(capabilities.change_password.enabled)
40 }
41
42 pub async fn can_change_displayname(&self) -> crate::Result<bool> {
49 let capabilities = self.load_or_fetch_homeserver_capabilities().await?;
50 if let Some(profile_fields) = capabilities.profile_fields
51 && profile_fields.enabled
52 {
53 let allowed = profile_fields.allowed.unwrap_or_default();
54 let disallowed = profile_fields.disallowed.unwrap_or_default();
55 return Ok(allowed.contains(&ProfileFieldName::DisplayName)
56 || !disallowed.contains(&ProfileFieldName::DisplayName));
57 }
58 #[allow(deprecated)]
59 Ok(capabilities.set_displayname.enabled)
60 }
61
62 pub async fn can_change_avatar(&self) -> crate::Result<bool> {
69 let capabilities = self.load_or_fetch_homeserver_capabilities().await?;
70 if let Some(profile_fields) = capabilities.profile_fields
71 && profile_fields.enabled
72 {
73 let allowed = profile_fields.allowed.unwrap_or_default();
74 let disallowed = profile_fields.disallowed.unwrap_or_default();
75 return Ok(allowed.contains(&ProfileFieldName::AvatarUrl)
76 || !disallowed.contains(&ProfileFieldName::AvatarUrl));
77 }
78 #[allow(deprecated)]
79 Ok(capabilities.set_avatar_url.enabled)
80 }
81
82 pub async fn can_change_thirdparty_ids(&self) -> crate::Result<bool> {
87 let capabilities = self.load_or_fetch_homeserver_capabilities().await?;
88 Ok(capabilities.thirdparty_id_changes.enabled)
89 }
90
91 pub async fn can_get_login_token(&self) -> crate::Result<bool> {
100 let capabilities = self.load_or_fetch_homeserver_capabilities().await?;
101 Ok(capabilities.get_login_token.enabled)
102 }
103
104 pub async fn extended_profile_fields(&self) -> crate::Result<ProfileFieldsCapability> {
108 let capabilities = self.load_or_fetch_homeserver_capabilities().await?;
109 if let Some(profile_fields) = capabilities.profile_fields {
110 return Ok(profile_fields);
111 }
112 Ok(ProfileFieldsCapability::new(false))
113 }
114
115 pub async fn room_versions(&self) -> crate::Result<RoomVersionsCapability> {
119 let capabilities = self.load_or_fetch_homeserver_capabilities().await?;
120 Ok(capabilities.room_versions)
121 }
122
123 pub async fn account_moderation(&self) -> crate::Result<AccountModerationCapability> {
127 let capabilities = self.load_or_fetch_homeserver_capabilities().await?;
128 Ok(capabilities.account_moderation)
129 }
130
131 pub async fn forgets_room_when_leaving(&self) -> crate::Result<bool> {
136 let capabilities = self.load_or_fetch_homeserver_capabilities().await?;
137 Ok(capabilities.forget_forced_upon_leave.enabled)
138 }
139
140 async fn load_or_fetch_homeserver_capabilities(&self) -> crate::Result<Capabilities> {
147 match self.client.state_store().get_kv_data(StateStoreDataKey::HomeserverCapabilities).await
148 {
149 Ok(Some(stored)) => {
150 if let Some(capabilities) = stored.into_homeserver_capabilities() {
151 return Ok(capabilities);
152 }
153 }
154 Ok(None) => {
155 }
157 Err(err) => {
158 warn!("error when loading cached homeserver capabilities: {err}");
159 }
161 }
162
163 Ok(self.get_and_cache_remote_capabilities().await?)
164 }
165
166 async fn get_and_cache_remote_capabilities(&self) -> HttpResult<Capabilities> {
168 let res = self.client.send(get_capabilities::v3::Request::new()).await?;
169
170 if let Err(err) = self
171 .client
172 .state_store()
173 .set_kv_data(
174 StateStoreDataKey::HomeserverCapabilities,
175 StateStoreDataValue::HomeserverCapabilities(res.capabilities.clone()),
176 )
177 .await
178 {
179 warn!("error when caching homeserver capabilities: {err}");
180 }
181
182 Ok(res.capabilities)
183 }
184}
185
186#[cfg(all(not(target_family = "wasm"), test))]
187mod tests {
188 use matrix_sdk_test::async_test;
189
190 use super::*;
191 use crate::test_utils::mocks::MatrixMockServer;
192
193 #[async_test]
194 async fn test_refresh_always_updates_capabilities() {
195 let server = MatrixMockServer::new().await;
196 let client = server.client_builder().build().await;
197
198 let mut expected_capabilities = Capabilities::default();
200 expected_capabilities.change_password.enabled = true;
201 server
202 .mock_get_homeserver_capabilities()
203 .ok_with_capabilities(expected_capabilities)
204 .mock_once()
205 .mount()
206 .await;
207
208 let capabilities = client.homeserver_capabilities();
210 capabilities.refresh().await.expect("refreshing capabilities failed");
211
212 assert!(capabilities.can_change_password().await.expect("checking capabilities failed"));
214
215 let mut expected_capabilities = Capabilities::default();
216 expected_capabilities.change_password.enabled = false;
217 server
218 .mock_get_homeserver_capabilities()
219 .ok_with_capabilities(expected_capabilities)
220 .mock_once()
221 .mount()
222 .await;
223
224 assert!(capabilities.can_change_password().await.expect("checking capabilities failed"));
227
228 capabilities.refresh().await.expect("refreshing capabilities failed");
230
231 assert!(!capabilities.can_change_password().await.expect("checking capabilities failed"));
233 }
234
235 #[async_test]
236 async fn test_get_functions_refresh_the_data_if_not_available_or_use_cache_if_available() {
237 let server = MatrixMockServer::new().await;
238 let client = server.client_builder().build().await;
239
240 let mut expected_capabilities = Capabilities::default();
242 let mut profile_fields = ProfileFieldsCapability::new(true);
243 profile_fields.allowed = Some(vec![ProfileFieldName::DisplayName]);
244 expected_capabilities.profile_fields = Some(profile_fields);
245 server
246 .mock_get_homeserver_capabilities()
247 .ok_with_capabilities(expected_capabilities)
248 .mock_once()
250 .mount()
251 .await;
252
253 let capabilities = client.homeserver_capabilities();
255
256 assert!(capabilities.can_change_displayname().await.expect("checking capabilities failed"));
258
259 let mut expected_capabilities = Capabilities::default();
262 let mut profile_fields = ProfileFieldsCapability::new(true);
263 profile_fields.disallowed = Some(vec![ProfileFieldName::DisplayName]);
264 expected_capabilities.profile_fields = Some(profile_fields);
265 server
266 .mock_get_homeserver_capabilities()
267 .ok_with_capabilities(expected_capabilities)
268 .never()
270 .mount()
271 .await;
272
273 assert!(capabilities.can_change_displayname().await.expect("checking capabilities failed"));
276 }
277}