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;
213
214 if let Err(error) = guard.as_ref() {
215 return Err(HttpError::Cached(error.clone()));
217 }
218
219 if let CachedValue::Cached(value) = capabilities_cache.value()
221 && !value.has_expired()
222 {
223 return Ok(value.into_data());
224 }
225
226 guard
228 }
229 };
230
231 let capabilities = match self.client.send(get_capabilities::v3::Request::new()).await {
232 Ok(response) => {
233 *capabilities_guard = Ok(());
234 TtlValue::new(response.capabilities)
235 }
236 Err(error) => {
237 let error = Arc::new(error);
238 *capabilities_guard = Err(error.clone());
239 return Err(HttpError::Cached(error));
240 }
241 };
242
243 if let Err(err) = self
244 .client
245 .state_store()
246 .set_kv_data(
247 StateStoreDataKey::HomeserverCapabilities,
248 StateStoreDataValue::HomeserverCapabilities(capabilities.clone()),
249 )
250 .await
251 {
252 warn!("error when caching homeserver capabilities: {err}");
253 }
254
255 capabilities_cache.set_value(capabilities.clone());
256
257 Ok(capabilities.into_data())
258 }
259
260 async fn profile_capabilities(&self) -> crate::Result<ProfileCapabilities> {
262 let capabilities = self.load_or_fetch_homeserver_capabilities().await?;
263
264 let profile_fields = match capabilities.profile_fields {
265 Some(profile_fields) => Some(profile_fields),
266 None => {
267 if self.homeserver_supports_extended_profile_fields().await? {
274 Some(ProfileFieldsCapability::new(true))
275 } else {
276 None
277 }
278 }
279 };
280
281 #[allow(deprecated)]
282 Ok(ProfileCapabilities {
283 profile_fields,
284 set_displayname: capabilities.set_displayname.enabled,
285 set_avatar_url: capabilities.set_avatar_url.enabled,
286 })
287 }
288
289 async fn homeserver_supports_extended_profile_fields(&self) -> crate::Result<bool> {
294 let supported_versions = self.client.supported_versions().await?;
295 Ok(delete_profile_field::v3::Request::PATH_BUILDER.is_supported(&supported_versions))
298 }
299}
300
301struct ProfileCapabilities {
303 profile_fields: Option<ProfileFieldsCapability>,
306 set_displayname: bool,
308 set_avatar_url: bool,
310}
311
312#[cfg(all(not(target_family = "wasm"), test))]
313mod tests {
314 use std::time::Duration;
315
316 use assert_matches::assert_matches;
317 use matrix_sdk_base::sleep::sleep;
318 use matrix_sdk_test::async_test;
319 #[allow(deprecated)]
320 use ruma::api::{
321 MatrixVersion,
322 client::discovery::get_capabilities::v3::{
323 SetAvatarUrlCapability, SetDisplayNameCapability,
324 },
325 };
326
327 use super::*;
328 use crate::test_utils::mocks::MatrixMockServer;
329
330 #[async_test]
331 async fn test_refresh_always_updates_capabilities() {
332 let server = MatrixMockServer::new().await;
333 let client = server.client_builder().build().await;
334
335 let mut expected_capabilities = Capabilities::default();
337 expected_capabilities.change_password.enabled = true;
338 server
339 .mock_get_homeserver_capabilities()
340 .ok_with_capabilities(expected_capabilities)
341 .mock_once()
342 .mount()
343 .await;
344
345 let capabilities = client.homeserver_capabilities();
347 capabilities.refresh().await.expect("refreshing capabilities failed");
348
349 assert!(capabilities.can_change_password().await.expect("checking capabilities failed"));
351
352 let mut expected_capabilities = Capabilities::default();
353 expected_capabilities.change_password.enabled = false;
354 server
355 .mock_get_homeserver_capabilities()
356 .ok_with_capabilities(expected_capabilities)
357 .mock_once()
358 .mount()
359 .await;
360
361 assert!(capabilities.can_change_password().await.expect("checking capabilities failed"));
364
365 capabilities.refresh().await.expect("refreshing capabilities failed");
367
368 assert!(!capabilities.can_change_password().await.expect("checking capabilities failed"));
370 }
371
372 #[async_test]
373 async fn test_get_functions_refresh_the_data_if_not_available_or_use_cache_if_available() {
374 let server = MatrixMockServer::new().await;
375 let client = server.client_builder().build().await;
376
377 let mut expected_capabilities = Capabilities::default();
379 let mut profile_fields = ProfileFieldsCapability::new(true);
380 profile_fields.allowed = Some(vec![ProfileFieldName::DisplayName]);
381 expected_capabilities.profile_fields = Some(profile_fields);
382 server
383 .mock_get_homeserver_capabilities()
384 .ok_with_capabilities(expected_capabilities)
385 .mock_once()
387 .mount()
388 .await;
389
390 let capabilities = client.homeserver_capabilities();
392
393 assert!(capabilities.can_change_displayname().await.expect("checking capabilities failed"));
395
396 let mut expected_capabilities = Capabilities::default();
399 let mut profile_fields = ProfileFieldsCapability::new(true);
400 profile_fields.disallowed = Some(vec![ProfileFieldName::DisplayName]);
401 expected_capabilities.profile_fields = Some(profile_fields);
402 server
403 .mock_get_homeserver_capabilities()
404 .ok_with_capabilities(expected_capabilities)
405 .expect(1)
406 .mount()
407 .await;
408
409 assert!(capabilities.can_change_displayname().await.expect("checking capabilities failed"));
412
413 let capabilities_data =
415 capabilities.homeserver_capabilities_cached().await.unwrap().unwrap();
416 let mut ttl_value = TtlValue::new(capabilities_data);
417 ttl_value.expire();
418 client.inner.caches.homeserver_capabilities.set_value(ttl_value);
419
420 capabilities.homeserver_capabilities_cached().await.unwrap().unwrap();
422
423 sleep(Duration::from_secs(1)).await;
425 assert_matches!(client.inner.caches.homeserver_capabilities.value(), CachedValue::Cached(value) if !value.has_expired());
426 }
427
428 #[async_test]
429 #[allow(deprecated)]
430 async fn test_deprecated_profile_fields_capabilities() {
431 let server = MatrixMockServer::new().await;
432
433 let mut capabilities = Capabilities::new();
436 capabilities.profile_fields.take();
437 capabilities.set_displayname = SetDisplayNameCapability::new(true);
438 capabilities.set_avatar_url = SetAvatarUrlCapability::new(false);
439 server
440 .mock_get_homeserver_capabilities()
441 .ok_with_capabilities(capabilities)
442 .expect(2)
444 .mount()
445 .await;
446
447 let client =
451 server.client_builder().server_versions(vec![MatrixVersion::V1_12]).build().await;
452 let capabilities_api = client.homeserver_capabilities();
453 assert!(
454 capabilities_api
455 .can_change_displayname()
456 .await
457 .expect("checking displayname capability failed")
458 );
459 assert!(
460 !capabilities_api.can_change_avatar().await.expect("checking avatar capability failed")
461 );
462 assert!(
463 !capabilities_api
464 .extended_profile_fields()
465 .await
466 .expect("checking profile fields capability failed")
467 .enabled
468 );
469
470 let client =
473 server.client_builder().server_versions(vec![MatrixVersion::V1_16]).build().await;
474 let capabilities_api = client.homeserver_capabilities();
475 assert!(
476 capabilities_api
477 .can_change_displayname()
478 .await
479 .expect("checking displayname capability failed")
480 );
481 assert!(
482 capabilities_api.can_change_avatar().await.expect("checking avatar capability failed")
483 );
484 assert!(
485 capabilities_api
486 .extended_profile_fields()
487 .await
488 .expect("checking profile fields capability failed")
489 .enabled
490 );
491 }
492
493 #[async_test]
494 #[allow(deprecated)]
495 async fn test_extended_profile_fields_capabilities_enabled() {
496 let server = MatrixMockServer::new().await;
497
498 let mut capabilities = Capabilities::new();
501 capabilities.profile_fields = Some(ProfileFieldsCapability::new(true));
502 capabilities.set_displayname = SetDisplayNameCapability::new(true);
503 capabilities.set_avatar_url = SetAvatarUrlCapability::new(false);
504 server
505 .mock_get_homeserver_capabilities()
506 .ok_with_capabilities(capabilities)
507 .expect(2)
509 .mount()
510 .await;
511
512 let client =
516 server.client_builder().server_versions(vec![MatrixVersion::V1_12]).build().await;
517 let capabilities_api = client.homeserver_capabilities();
518 assert!(
519 capabilities_api
520 .can_change_displayname()
521 .await
522 .expect("checking displayname capability failed")
523 );
524 assert!(
525 capabilities_api.can_change_avatar().await.expect("checking avatar capability failed")
526 );
527 assert!(
528 capabilities_api
529 .extended_profile_fields()
530 .await
531 .expect("checking profile fields capability failed")
532 .enabled
533 );
534
535 let client =
538 server.client_builder().server_versions(vec![MatrixVersion::V1_16]).build().await;
539 let capabilities_api = client.homeserver_capabilities();
540 assert!(
541 capabilities_api
542 .can_change_displayname()
543 .await
544 .expect("checking displayname capability failed")
545 );
546 assert!(
547 capabilities_api.can_change_avatar().await.expect("checking avatar capability failed")
548 );
549 assert!(
550 capabilities_api
551 .extended_profile_fields()
552 .await
553 .expect("checking profile fields capability failed")
554 .enabled
555 );
556 }
557
558 #[async_test]
559 #[allow(deprecated)]
560 async fn test_extended_profile_fields_capabilities_disabled() {
561 let server = MatrixMockServer::new().await;
562
563 let mut capabilities = Capabilities::new();
566 capabilities.profile_fields = Some(ProfileFieldsCapability::new(false));
567 capabilities.set_displayname = SetDisplayNameCapability::new(true);
568 capabilities.set_avatar_url = SetAvatarUrlCapability::new(false);
569 server
570 .mock_get_homeserver_capabilities()
571 .ok_with_capabilities(capabilities)
572 .expect(2)
574 .mount()
575 .await;
576
577 let client =
581 server.client_builder().server_versions(vec![MatrixVersion::V1_12]).build().await;
582 let capabilities_api = client.homeserver_capabilities();
583 assert!(
584 !capabilities_api
585 .can_change_displayname()
586 .await
587 .expect("checking displayname capability failed")
588 );
589 assert!(
590 !capabilities_api.can_change_avatar().await.expect("checking avatar capability failed")
591 );
592 assert!(
593 !capabilities_api
594 .extended_profile_fields()
595 .await
596 .expect("checking profile fields capability failed")
597 .enabled
598 );
599
600 let client =
603 server.client_builder().server_versions(vec![MatrixVersion::V1_16]).build().await;
604 let capabilities_api = client.homeserver_capabilities();
605 assert!(
606 !capabilities_api
607 .can_change_displayname()
608 .await
609 .expect("checking displayname capability failed")
610 );
611 assert!(
612 !capabilities_api.can_change_avatar().await.expect("checking avatar capability failed")
613 );
614 assert!(
615 !capabilities_api
616 .extended_profile_fields()
617 .await
618 .expect("checking profile fields capability failed")
619 .enabled
620 );
621 }
622
623 #[async_test]
624 #[allow(deprecated)]
625 async fn test_fine_grained_extended_profile_fields_capabilities() {
626 let server = MatrixMockServer::new().await;
627
628 let mut profile_fields = ProfileFieldsCapability::new(true);
631 profile_fields.allowed = Some(vec![ProfileFieldName::AvatarUrl]);
632 let mut capabilities = Capabilities::new();
633 capabilities.profile_fields = Some(profile_fields);
634 capabilities.set_displayname = SetDisplayNameCapability::new(true);
635 capabilities.set_avatar_url = SetAvatarUrlCapability::new(false);
636 server
637 .mock_get_homeserver_capabilities()
638 .ok_with_capabilities(capabilities)
639 .expect(2)
641 .mount()
642 .await;
643
644 let client =
648 server.client_builder().server_versions(vec![MatrixVersion::V1_12]).build().await;
649 let capabilities_api = client.homeserver_capabilities();
650 assert!(
651 !capabilities_api
652 .can_change_displayname()
653 .await
654 .expect("checking displayname capability failed")
655 );
656 assert!(
657 capabilities_api.can_change_avatar().await.expect("checking avatar capability failed")
658 );
659 assert!(
660 capabilities_api
661 .extended_profile_fields()
662 .await
663 .expect("checking profile fields capability failed")
664 .enabled
665 );
666
667 let client =
670 server.client_builder().server_versions(vec![MatrixVersion::V1_16]).build().await;
671 let capabilities_api = client.homeserver_capabilities();
672 assert!(
673 !capabilities_api
674 .can_change_displayname()
675 .await
676 .expect("checking displayname capability failed")
677 );
678 assert!(
679 capabilities_api.can_change_avatar().await.expect("checking avatar capability failed")
680 );
681 assert!(
682 capabilities_api
683 .extended_profile_fields()
684 .await
685 .expect("checking profile fields capability failed")
686 .enabled
687 );
688 }
689}