matrix_sdk/client/
homeserver_capabilities.rs1use std::sync::Arc;
2
3use matrix_sdk_base::{StateStoreDataKey, StateStoreDataValue, StoreError, ttl::TtlValue};
4use ruma::{
5 api::{
6 Metadata,
7 client::{
8 discovery::get_capabilities::{
9 self,
10 v3::{
11 AccountModerationCapability, Capabilities, ProfileFieldsCapability,
12 RoomVersionsCapability,
13 },
14 },
15 profile::delete_profile_field,
16 },
17 },
18 profile::ProfileFieldName,
19};
20use tracing::{debug, warn};
21
22use crate::{Client, HttpError, HttpResult, client::caches::CachedValue};
23
24#[derive(Debug, Clone)]
28pub struct HomeserverCapabilities {
29 client: Client,
30}
31
32impl HomeserverCapabilities {
33 pub fn new(client: Client) -> Self {
35 Self { client }
36 }
37
38 pub async fn refresh(&self) -> crate::Result<()> {
40 self.get_and_cache_remote_capabilities().await?;
41 Ok(())
42 }
43
44 pub async fn can_change_password(&self) -> crate::Result<bool> {
46 let capabilities = self.load_or_fetch_homeserver_capabilities().await?;
47 Ok(capabilities.change_password.enabled)
48 }
49
50 pub async fn can_change_displayname(&self) -> crate::Result<bool> {
57 let capabilities = self.profile_capabilities().await?;
58
59 if let Some(profile_fields) = capabilities.profile_fields {
60 Ok(profile_fields.can_set_field(&ProfileFieldName::DisplayName))
61 } else {
62 Ok(capabilities.set_displayname)
63 }
64 }
65
66 pub async fn can_change_avatar(&self) -> crate::Result<bool> {
73 let capabilities = self.profile_capabilities().await?;
74
75 if let Some(profile_fields) = capabilities.profile_fields {
76 Ok(profile_fields.can_set_field(&ProfileFieldName::AvatarUrl))
77 } else {
78 Ok(capabilities.set_avatar_url)
79 }
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 Ok(self
109 .profile_capabilities()
110 .await?
111 .profile_fields
112 .unwrap_or_else(|| 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 homeserver_capabilities_cached(&self) -> Result<Option<Capabilities>, StoreError> {
142 let capabilities_cache = &self.client.inner.caches.homeserver_capabilities;
143
144 let value = if let CachedValue::Cached(cached) = capabilities_cache.value() {
145 cached
146 } else if let Some(stored) = self
147 .client
148 .state_store()
149 .get_kv_data(StateStoreDataKey::HomeserverCapabilities)
150 .await?
151 .and_then(|value| value.into_homeserver_capabilities())
152 {
153 capabilities_cache.set_value(stored.clone());
155
156 stored
157 } else {
158 return Ok(None);
159 };
160
161 if value.has_expired() {
163 debug!("spawning task to refresh homeserver capabilities cache");
164
165 let homeserver_capabilities = self.clone();
166 self.client.task_monitor().spawn_finite_task(
167 "refresh homeserver capabilities cache",
168 async move {
169 if let Err(error) =
170 homeserver_capabilities.get_and_cache_remote_capabilities().await
171 {
172 warn!("failed to refresh homeserver capabilities cache: {error}");
173 }
174 },
175 );
176 }
177
178 Ok(Some(value.into_data()))
179 }
180
181 async fn load_or_fetch_homeserver_capabilities(&self) -> crate::Result<Capabilities> {
188 match self.homeserver_capabilities_cached().await {
189 Ok(Some(capabilities)) => {
190 return Ok(capabilities);
191 }
192 Ok(None) => {
193 }
195 Err(err) => {
196 warn!("error when loading cached homeserver capabilities: {err}");
197 }
199 }
200
201 Ok(self.get_and_cache_remote_capabilities().await?)
202 }
203
204 async fn get_and_cache_remote_capabilities(&self) -> HttpResult<Capabilities> {
206 let capabilities_cache = &self.client.inner.caches.homeserver_capabilities;
207
208 let mut capabilities_guard = match capabilities_cache.refresh_lock.try_lock() {
209 Ok(guard) => guard,
210 Err(_) => {
211 let guard = capabilities_cache.refresh_lock.lock().await;
214
215 if let Err(error) = guard.as_ref() {
216 return Err(HttpError::Cached(error.clone()));
218 }
219
220 if let CachedValue::Cached(value) = capabilities_cache.value()
222 && !value.has_expired()
223 {
224 return Ok(value.into_data());
225 }
226
227 guard
230 }
231 };
232
233 let capabilities = match self.client.send(get_capabilities::v3::Request::new()).await {
234 Ok(response) => {
235 *capabilities_guard = Ok(());
236 TtlValue::new(response.capabilities)
237 }
238 Err(error) => {
239 let error = Arc::new(error);
240 *capabilities_guard = Err(error.clone());
241 return Err(HttpError::Cached(error));
242 }
243 };
244
245 if let Err(err) = self
246 .client
247 .state_store()
248 .set_kv_data(
249 StateStoreDataKey::HomeserverCapabilities,
250 StateStoreDataValue::HomeserverCapabilities(capabilities.clone()),
251 )
252 .await
253 {
254 warn!("error when caching homeserver capabilities: {err}");
255 }
256
257 capabilities_cache.set_value(capabilities.clone());
258
259 Ok(capabilities.into_data())
260 }
261
262 async fn profile_capabilities(&self) -> crate::Result<ProfileCapabilities> {
264 let capabilities = self.load_or_fetch_homeserver_capabilities().await?;
265
266 let profile_fields = match capabilities.profile_fields {
267 Some(profile_fields) => Some(profile_fields),
268 None => {
269 if self.homeserver_supports_extended_profile_fields().await? {
278 Some(ProfileFieldsCapability::new(true))
279 } else {
280 None
281 }
282 }
283 };
284
285 #[allow(deprecated)]
286 Ok(ProfileCapabilities {
287 profile_fields,
288 set_displayname: capabilities.set_displayname.enabled,
289 set_avatar_url: capabilities.set_avatar_url.enabled,
290 })
291 }
292
293 async fn homeserver_supports_extended_profile_fields(&self) -> crate::Result<bool> {
298 let supported_versions = self.client.supported_versions().await?;
299 Ok(delete_profile_field::v3::Request::PATH_BUILDER.is_supported(&supported_versions))
302 }
303}
304
305struct ProfileCapabilities {
307 profile_fields: Option<ProfileFieldsCapability>,
310 set_displayname: bool,
312 set_avatar_url: bool,
314}
315
316#[cfg(all(not(target_family = "wasm"), test))]
317mod tests {
318 use std::time::Duration;
319
320 use assert_matches::assert_matches;
321 use matrix_sdk_base::sleep::sleep;
322 use matrix_sdk_test::async_test;
323 #[allow(deprecated)]
324 use ruma::api::{
325 MatrixVersion,
326 client::discovery::get_capabilities::v3::{
327 SetAvatarUrlCapability, SetDisplayNameCapability,
328 },
329 };
330
331 use super::*;
332 use crate::test_utils::mocks::MatrixMockServer;
333
334 #[async_test]
335 async fn test_refresh_always_updates_capabilities() {
336 let server = MatrixMockServer::new().await;
337 let client = server.client_builder().build().await;
338
339 let mut expected_capabilities = Capabilities::default();
341 expected_capabilities.change_password.enabled = true;
342 server
343 .mock_get_homeserver_capabilities()
344 .ok_with_capabilities(expected_capabilities)
345 .mock_once()
346 .mount()
347 .await;
348
349 let capabilities = client.homeserver_capabilities();
351 capabilities.refresh().await.expect("refreshing capabilities failed");
352
353 assert!(capabilities.can_change_password().await.expect("checking capabilities failed"));
355
356 let mut expected_capabilities = Capabilities::default();
357 expected_capabilities.change_password.enabled = false;
358 server
359 .mock_get_homeserver_capabilities()
360 .ok_with_capabilities(expected_capabilities)
361 .mock_once()
362 .mount()
363 .await;
364
365 assert!(capabilities.can_change_password().await.expect("checking capabilities failed"));
368
369 capabilities.refresh().await.expect("refreshing capabilities failed");
371
372 assert!(!capabilities.can_change_password().await.expect("checking capabilities failed"));
374 }
375
376 #[async_test]
377 async fn test_get_functions_refresh_the_data_if_not_available_or_use_cache_if_available() {
378 let server = MatrixMockServer::new().await;
379 let client = server.client_builder().build().await;
380
381 let mut expected_capabilities = Capabilities::default();
383 let mut profile_fields = ProfileFieldsCapability::new(true);
384 profile_fields.allowed = Some(vec![ProfileFieldName::DisplayName]);
385 expected_capabilities.profile_fields = Some(profile_fields);
386 server
387 .mock_get_homeserver_capabilities()
388 .ok_with_capabilities(expected_capabilities)
389 .mock_once()
391 .mount()
392 .await;
393
394 let capabilities = client.homeserver_capabilities();
396
397 assert!(capabilities.can_change_displayname().await.expect("checking capabilities failed"));
399
400 let mut expected_capabilities = Capabilities::default();
403 let mut profile_fields = ProfileFieldsCapability::new(true);
404 profile_fields.disallowed = Some(vec![ProfileFieldName::DisplayName]);
405 expected_capabilities.profile_fields = Some(profile_fields);
406 server
407 .mock_get_homeserver_capabilities()
408 .ok_with_capabilities(expected_capabilities)
409 .expect(1)
410 .mount()
411 .await;
412
413 assert!(capabilities.can_change_displayname().await.expect("checking capabilities failed"));
416
417 let capabilities_data =
419 capabilities.homeserver_capabilities_cached().await.unwrap().unwrap();
420 let mut ttl_value = TtlValue::new(capabilities_data);
421 ttl_value.expire();
422 client.inner.caches.homeserver_capabilities.set_value(ttl_value);
423
424 capabilities.homeserver_capabilities_cached().await.unwrap().unwrap();
426
427 sleep(Duration::from_secs(1)).await;
430 assert_matches!(client.inner.caches.homeserver_capabilities.value(), CachedValue::Cached(value) if !value.has_expired());
431 }
432
433 #[async_test]
434 #[allow(deprecated)]
435 async fn test_deprecated_profile_fields_capabilities() {
436 let server = MatrixMockServer::new().await;
437
438 let mut capabilities = Capabilities::new();
441 capabilities.profile_fields.take();
442 capabilities.set_displayname = SetDisplayNameCapability::new(true);
443 capabilities.set_avatar_url = SetAvatarUrlCapability::new(false);
444 server
445 .mock_get_homeserver_capabilities()
446 .ok_with_capabilities(capabilities)
447 .expect(2)
449 .mount()
450 .await;
451
452 let client =
456 server.client_builder().server_versions(vec![MatrixVersion::V1_12]).build().await;
457 let capabilities_api = client.homeserver_capabilities();
458 assert!(
459 capabilities_api
460 .can_change_displayname()
461 .await
462 .expect("checking displayname capability failed")
463 );
464 assert!(
465 !capabilities_api.can_change_avatar().await.expect("checking avatar capability failed")
466 );
467 assert!(
468 !capabilities_api
469 .extended_profile_fields()
470 .await
471 .expect("checking profile fields capability failed")
472 .enabled
473 );
474
475 let client =
478 server.client_builder().server_versions(vec![MatrixVersion::V1_16]).build().await;
479 let capabilities_api = client.homeserver_capabilities();
480 assert!(
481 capabilities_api
482 .can_change_displayname()
483 .await
484 .expect("checking displayname capability failed")
485 );
486 assert!(
487 capabilities_api.can_change_avatar().await.expect("checking avatar capability failed")
488 );
489 assert!(
490 capabilities_api
491 .extended_profile_fields()
492 .await
493 .expect("checking profile fields capability failed")
494 .enabled
495 );
496 }
497
498 #[async_test]
499 #[allow(deprecated)]
500 async fn test_extended_profile_fields_capabilities_enabled() {
501 let server = MatrixMockServer::new().await;
502
503 let mut capabilities = Capabilities::new();
506 capabilities.profile_fields = Some(ProfileFieldsCapability::new(true));
507 capabilities.set_displayname = SetDisplayNameCapability::new(true);
508 capabilities.set_avatar_url = SetAvatarUrlCapability::new(false);
509 server
510 .mock_get_homeserver_capabilities()
511 .ok_with_capabilities(capabilities)
512 .expect(2)
514 .mount()
515 .await;
516
517 let client =
521 server.client_builder().server_versions(vec![MatrixVersion::V1_12]).build().await;
522 let capabilities_api = client.homeserver_capabilities();
523 assert!(
524 capabilities_api
525 .can_change_displayname()
526 .await
527 .expect("checking displayname capability failed")
528 );
529 assert!(
530 capabilities_api.can_change_avatar().await.expect("checking avatar capability failed")
531 );
532 assert!(
533 capabilities_api
534 .extended_profile_fields()
535 .await
536 .expect("checking profile fields capability failed")
537 .enabled
538 );
539
540 let client =
543 server.client_builder().server_versions(vec![MatrixVersion::V1_16]).build().await;
544 let capabilities_api = client.homeserver_capabilities();
545 assert!(
546 capabilities_api
547 .can_change_displayname()
548 .await
549 .expect("checking displayname capability failed")
550 );
551 assert!(
552 capabilities_api.can_change_avatar().await.expect("checking avatar capability failed")
553 );
554 assert!(
555 capabilities_api
556 .extended_profile_fields()
557 .await
558 .expect("checking profile fields capability failed")
559 .enabled
560 );
561 }
562
563 #[async_test]
564 #[allow(deprecated)]
565 async fn test_extended_profile_fields_capabilities_disabled() {
566 let server = MatrixMockServer::new().await;
567
568 let mut capabilities = Capabilities::new();
571 capabilities.profile_fields = Some(ProfileFieldsCapability::new(false));
572 capabilities.set_displayname = SetDisplayNameCapability::new(true);
573 capabilities.set_avatar_url = SetAvatarUrlCapability::new(false);
574 server
575 .mock_get_homeserver_capabilities()
576 .ok_with_capabilities(capabilities)
577 .expect(2)
579 .mount()
580 .await;
581
582 let client =
586 server.client_builder().server_versions(vec![MatrixVersion::V1_12]).build().await;
587 let capabilities_api = client.homeserver_capabilities();
588 assert!(
589 !capabilities_api
590 .can_change_displayname()
591 .await
592 .expect("checking displayname capability failed")
593 );
594 assert!(
595 !capabilities_api.can_change_avatar().await.expect("checking avatar capability failed")
596 );
597 assert!(
598 !capabilities_api
599 .extended_profile_fields()
600 .await
601 .expect("checking profile fields capability failed")
602 .enabled
603 );
604
605 let client =
608 server.client_builder().server_versions(vec![MatrixVersion::V1_16]).build().await;
609 let capabilities_api = client.homeserver_capabilities();
610 assert!(
611 !capabilities_api
612 .can_change_displayname()
613 .await
614 .expect("checking displayname capability failed")
615 );
616 assert!(
617 !capabilities_api.can_change_avatar().await.expect("checking avatar capability failed")
618 );
619 assert!(
620 !capabilities_api
621 .extended_profile_fields()
622 .await
623 .expect("checking profile fields capability failed")
624 .enabled
625 );
626 }
627
628 #[async_test]
629 #[allow(deprecated)]
630 async fn test_fine_grained_extended_profile_fields_capabilities() {
631 let server = MatrixMockServer::new().await;
632
633 let mut profile_fields = ProfileFieldsCapability::new(true);
636 profile_fields.allowed = Some(vec![ProfileFieldName::AvatarUrl]);
637 let mut capabilities = Capabilities::new();
638 capabilities.profile_fields = Some(profile_fields);
639 capabilities.set_displayname = SetDisplayNameCapability::new(true);
640 capabilities.set_avatar_url = SetAvatarUrlCapability::new(false);
641 server
642 .mock_get_homeserver_capabilities()
643 .ok_with_capabilities(capabilities)
644 .expect(2)
646 .mount()
647 .await;
648
649 let client =
653 server.client_builder().server_versions(vec![MatrixVersion::V1_12]).build().await;
654 let capabilities_api = client.homeserver_capabilities();
655 assert!(
656 !capabilities_api
657 .can_change_displayname()
658 .await
659 .expect("checking displayname capability failed")
660 );
661 assert!(
662 capabilities_api.can_change_avatar().await.expect("checking avatar capability failed")
663 );
664 assert!(
665 capabilities_api
666 .extended_profile_fields()
667 .await
668 .expect("checking profile fields capability failed")
669 .enabled
670 );
671
672 let client =
675 server.client_builder().server_versions(vec![MatrixVersion::V1_16]).build().await;
676 let capabilities_api = client.homeserver_capabilities();
677 assert!(
678 !capabilities_api
679 .can_change_displayname()
680 .await
681 .expect("checking displayname capability failed")
682 );
683 assert!(
684 capabilities_api.can_change_avatar().await.expect("checking avatar capability failed")
685 );
686 assert!(
687 capabilities_api
688 .extended_profile_fields()
689 .await
690 .expect("checking profile fields capability failed")
691 .enabled
692 );
693 }
694}