1mod homeserver_config;
17
18#[cfg(feature = "sqlite")]
19use std::path::Path;
20use std::{collections::BTreeSet, fmt, sync::Arc};
21
22use homeserver_config::*;
23#[cfg(feature = "e2e-encryption")]
24use matrix_sdk_base::crypto::DecryptionSettings;
25use matrix_sdk_base::{store::StoreConfig, BaseClient, ThreadingSupport};
26#[cfg(feature = "sqlite")]
27use matrix_sdk_sqlite::SqliteStoreConfig;
28use ruma::{
29 api::{error::FromHttpResponseError, MatrixVersion, SupportedVersions},
30 OwnedServerName, ServerName,
31};
32use thiserror::Error;
33use tokio::sync::{broadcast, Mutex, OnceCell};
34use tracing::{debug, field::debug, instrument, Span};
35
36use super::{Client, ClientInner};
37#[cfg(feature = "e2e-encryption")]
38use crate::crypto::{CollectStrategy, TrustRequirement};
39#[cfg(feature = "e2e-encryption")]
40use crate::encryption::EncryptionSettings;
41#[cfg(not(target_family = "wasm"))]
42use crate::http_client::HttpSettings;
43use crate::{
44 authentication::{oauth::OAuthCtx, AuthCtx},
45 client::{
46 CachedValue::{Cached, NotSet},
47 ClientServerInfo,
48 },
49 config::RequestConfig,
50 error::RumaApiError,
51 http_client::HttpClient,
52 send_queue::SendQueueData,
53 sliding_sync::VersionBuilder as SlidingSyncVersionBuilder,
54 HttpError, IdParseError,
55};
56
57#[must_use]
96#[derive(Clone, Debug)]
97pub struct ClientBuilder {
98 homeserver_cfg: Option<HomeserverConfig>,
99 sliding_sync_version_builder: SlidingSyncVersionBuilder,
100 http_cfg: Option<HttpConfig>,
101 store_config: BuilderStoreConfig,
102 request_config: RequestConfig,
103 respect_login_well_known: bool,
104 server_versions: Option<BTreeSet<MatrixVersion>>,
105 handle_refresh_tokens: bool,
106 base_client: Option<BaseClient>,
107 #[cfg(feature = "e2e-encryption")]
108 encryption_settings: EncryptionSettings,
109 #[cfg(feature = "e2e-encryption")]
110 room_key_recipient_strategy: CollectStrategy,
111 #[cfg(feature = "e2e-encryption")]
112 decryption_settings: DecryptionSettings,
113 #[cfg(feature = "e2e-encryption")]
114 enable_share_history_on_invite: bool,
115 cross_process_store_locks_holder_name: String,
116 threading_support: ThreadingSupport,
117}
118
119impl ClientBuilder {
120 const DEFAULT_CROSS_PROCESS_STORE_LOCKS_HOLDER_NAME: &str = "main";
121
122 pub(crate) fn new() -> Self {
123 Self {
124 homeserver_cfg: None,
125 sliding_sync_version_builder: SlidingSyncVersionBuilder::Native,
126 http_cfg: None,
127 store_config: BuilderStoreConfig::Custom(StoreConfig::new(
128 Self::DEFAULT_CROSS_PROCESS_STORE_LOCKS_HOLDER_NAME.to_owned(),
129 )),
130 request_config: Default::default(),
131 respect_login_well_known: true,
132 server_versions: None,
133 handle_refresh_tokens: false,
134 base_client: None,
135 #[cfg(feature = "e2e-encryption")]
136 encryption_settings: Default::default(),
137 #[cfg(feature = "e2e-encryption")]
138 room_key_recipient_strategy: Default::default(),
139 #[cfg(feature = "e2e-encryption")]
140 decryption_settings: DecryptionSettings {
141 sender_device_trust_requirement: TrustRequirement::Untrusted,
142 },
143 #[cfg(feature = "e2e-encryption")]
144 enable_share_history_on_invite: false,
145 cross_process_store_locks_holder_name:
146 Self::DEFAULT_CROSS_PROCESS_STORE_LOCKS_HOLDER_NAME.to_owned(),
147 threading_support: ThreadingSupport::Disabled,
148 }
149 }
150
151 pub fn homeserver_url(mut self, url: impl AsRef<str>) -> Self {
158 self.homeserver_cfg = Some(HomeserverConfig::HomeserverUrl(url.as_ref().to_owned()));
159 self
160 }
161
162 pub fn server_name(mut self, server_name: &ServerName) -> Self {
172 self.homeserver_cfg = Some(HomeserverConfig::ServerName {
173 server: server_name.to_owned(),
174 protocol: UrlScheme::Https,
176 });
177 self
178 }
179
180 pub fn insecure_server_name_no_tls(mut self, server_name: &ServerName) -> Self {
189 self.homeserver_cfg = Some(HomeserverConfig::ServerName {
190 server: server_name.to_owned(),
191 protocol: UrlScheme::Http,
192 });
193 self
194 }
195
196 pub fn server_name_or_homeserver_url(mut self, server_name_or_url: impl AsRef<str>) -> Self {
207 self.homeserver_cfg = Some(HomeserverConfig::ServerNameOrHomeserverUrl(
208 server_name_or_url.as_ref().to_owned(),
209 ));
210 self
211 }
212
213 pub fn sliding_sync_version_builder(
215 mut self,
216 version_builder: SlidingSyncVersionBuilder,
217 ) -> Self {
218 self.sliding_sync_version_builder = version_builder;
219 self
220 }
221
222 #[cfg(feature = "sqlite")]
224 pub fn sqlite_store(mut self, path: impl AsRef<Path>, passphrase: Option<&str>) -> Self {
225 let sqlite_store_config = SqliteStoreConfig::new(path).passphrase(passphrase);
226 self.store_config =
227 BuilderStoreConfig::Sqlite { config: sqlite_store_config, cache_path: None };
228
229 self
230 }
231
232 #[cfg(feature = "sqlite")]
235 pub fn sqlite_store_with_cache_path(
236 mut self,
237 path: impl AsRef<Path>,
238 cache_path: impl AsRef<Path>,
239 passphrase: Option<&str>,
240 ) -> Self {
241 let sqlite_store_config = SqliteStoreConfig::new(path).passphrase(passphrase);
242 self.store_config = BuilderStoreConfig::Sqlite {
243 config: sqlite_store_config,
244 cache_path: Some(cache_path.as_ref().to_owned()),
245 };
246
247 self
248 }
249
250 #[cfg(feature = "sqlite")]
253 pub fn sqlite_store_with_config_and_cache_path(
254 mut self,
255 config: SqliteStoreConfig,
256 cache_path: Option<impl AsRef<Path>>,
257 ) -> Self {
258 self.store_config = BuilderStoreConfig::Sqlite {
259 config,
260 cache_path: cache_path.map(|cache_path| cache_path.as_ref().to_owned()),
261 };
262
263 self
264 }
265
266 #[cfg(feature = "indexeddb")]
268 pub fn indexeddb_store(mut self, name: &str, passphrase: Option<&str>) -> Self {
269 self.store_config = BuilderStoreConfig::IndexedDb {
270 name: name.to_owned(),
271 passphrase: passphrase.map(ToOwned::to_owned),
272 };
273 self
274 }
275
276 pub fn store_config(mut self, store_config: StoreConfig) -> Self {
298 self.store_config = BuilderStoreConfig::Custom(store_config);
299 self
300 }
301
302 pub fn respect_login_well_known(mut self, value: bool) -> Self {
305 self.respect_login_well_known = value;
306 self
307 }
308
309 pub fn request_config(mut self, request_config: RequestConfig) -> Self {
311 self.request_config = request_config;
312 self
313 }
314
315 #[cfg(not(target_family = "wasm"))]
331 pub fn proxy(mut self, proxy: impl AsRef<str>) -> Self {
332 self.http_settings().proxy = Some(proxy.as_ref().to_owned());
333 self
334 }
335
336 #[cfg(not(target_family = "wasm"))]
338 pub fn disable_ssl_verification(mut self) -> Self {
339 self.http_settings().disable_ssl_verification = true;
340 self
341 }
342
343 #[cfg(not(target_family = "wasm"))]
345 pub fn user_agent(mut self, user_agent: impl AsRef<str>) -> Self {
346 self.http_settings().user_agent = Some(user_agent.as_ref().to_owned());
347 self
348 }
349
350 #[cfg(not(target_family = "wasm"))]
359 pub fn add_root_certificates(mut self, certificates: Vec<reqwest::Certificate>) -> Self {
360 self.http_settings().additional_root_certificates = certificates;
361 self
362 }
363
364 #[cfg(not(target_family = "wasm"))]
368 pub fn disable_built_in_root_certificates(mut self) -> Self {
369 self.http_settings().disable_built_in_root_certificates = true;
370 self
371 }
372
373 pub fn http_client(mut self, client: reqwest::Client) -> Self {
383 self.http_cfg = Some(HttpConfig::Custom(client));
384 self
385 }
386
387 pub fn server_versions(mut self, value: impl IntoIterator<Item = MatrixVersion>) -> Self {
392 self.server_versions = Some(value.into_iter().collect());
393 self
394 }
395
396 #[cfg(not(target_family = "wasm"))]
397 fn http_settings(&mut self) -> &mut HttpSettings {
398 self.http_cfg.get_or_insert_with(Default::default).settings()
399 }
400
401 pub fn handle_refresh_tokens(mut self) -> Self {
423 self.handle_refresh_tokens = true;
424 self
425 }
426
427 #[doc(hidden)]
429 pub fn base_client(mut self, base_client: BaseClient) -> Self {
430 self.base_client = Some(base_client);
431 self
432 }
433
434 #[cfg(feature = "e2e-encryption")]
437 pub fn with_encryption_settings(mut self, settings: EncryptionSettings) -> Self {
438 self.encryption_settings = settings;
439 self
440 }
441
442 #[cfg(feature = "e2e-encryption")]
445 pub fn with_room_key_recipient_strategy(mut self, strategy: CollectStrategy) -> Self {
446 self.room_key_recipient_strategy = strategy;
447 self
448 }
449
450 #[cfg(feature = "e2e-encryption")]
452 pub fn with_decryption_settings(mut self, decryption_settings: DecryptionSettings) -> Self {
453 self.decryption_settings = decryption_settings;
454 self
455 }
456
457 #[cfg(feature = "e2e-encryption")]
462 pub fn with_enable_share_history_on_invite(
463 mut self,
464 enable_share_history_on_invite: bool,
465 ) -> Self {
466 self.enable_share_history_on_invite = enable_share_history_on_invite;
467 self
468 }
469
470 pub fn cross_process_store_locks_holder_name(mut self, holder_name: String) -> Self {
480 self.cross_process_store_locks_holder_name = holder_name;
481 self
482 }
483
484 pub fn with_threading_support(mut self, threading_support: ThreadingSupport) -> Self {
488 self.threading_support = threading_support;
489 self
490 }
491
492 #[instrument(skip_all, target = "matrix_sdk::client", fields(homeserver))]
505 pub async fn build(self) -> Result<Client, ClientBuildError> {
506 debug!("Starting to build the Client");
507
508 let homeserver_cfg = self.homeserver_cfg.ok_or(ClientBuildError::MissingHomeserver)?;
509 Span::current().record("homeserver", debug(&homeserver_cfg));
510
511 #[cfg_attr(target_family = "wasm", allow(clippy::infallible_destructuring_match))]
512 let inner_http_client = match self.http_cfg.unwrap_or_default() {
513 #[cfg(not(target_family = "wasm"))]
514 HttpConfig::Settings(mut settings) => {
515 settings.timeout = self.request_config.timeout;
516 settings.make_client()?
517 }
518 HttpConfig::Custom(c) => c,
519 };
520
521 let base_client = if let Some(base_client) = self.base_client {
522 base_client
523 } else {
524 #[allow(unused_mut)]
525 let mut client = BaseClient::new(
526 build_store_config(self.store_config, &self.cross_process_store_locks_holder_name)
527 .await?,
528 self.threading_support,
529 );
530
531 #[cfg(feature = "e2e-encryption")]
532 {
533 client.room_key_recipient_strategy = self.room_key_recipient_strategy;
534 client.decryption_settings = self.decryption_settings;
535 }
536
537 client
538 };
539
540 let http_client = HttpClient::new(inner_http_client.clone(), self.request_config);
541
542 #[allow(unused_variables)]
543 let HomeserverDiscoveryResult { server, homeserver, supported_versions, well_known } =
544 homeserver_cfg.discover(&http_client).await?;
545
546 let sliding_sync_version = {
547 let supported_versions = match supported_versions {
548 Some(versions) => Some(versions),
549 None if self.sliding_sync_version_builder.needs_get_supported_versions() => {
550 Some(get_supported_versions(&homeserver, &http_client).await?)
551 }
552 None => None,
553 };
554
555 let version = self.sliding_sync_version_builder.build(
556 supported_versions.map(|response| response.as_supported_versions()).as_ref(),
557 )?;
558
559 tracing::info!(?version, "selected sliding sync version");
560
561 version
562 };
563
564 let allow_insecure_oauth = homeserver.scheme() == "http";
565
566 let auth_ctx = Arc::new(AuthCtx {
567 handle_refresh_tokens: self.handle_refresh_tokens,
568 refresh_token_lock: Arc::new(Mutex::new(Ok(()))),
569 session_change_sender: broadcast::Sender::new(1),
570 auth_data: OnceCell::default(),
571 tokens: OnceCell::default(),
572 reload_session_callback: OnceCell::default(),
573 save_session_callback: OnceCell::default(),
574 oauth: OAuthCtx::new(allow_insecure_oauth),
575 });
576
577 let send_queue = Arc::new(SendQueueData::new(true));
579
580 let server_info = ClientServerInfo {
581 supported_versions: match self.server_versions {
582 Some(versions) => {
583 Cached(SupportedVersions { versions, features: Default::default() })
584 }
585 None => NotSet,
586 },
587 well_known: Cached(well_known.map(Into::into)),
588 };
589
590 let event_cache = OnceCell::new();
591 let latest_events = OnceCell::new();
592
593 let inner = ClientInner::new(
594 auth_ctx,
595 server,
596 homeserver,
597 sliding_sync_version,
598 http_client,
599 base_client,
600 server_info,
601 self.respect_login_well_known,
602 event_cache,
603 send_queue,
604 latest_events,
605 #[cfg(feature = "e2e-encryption")]
606 self.encryption_settings,
607 #[cfg(feature = "e2e-encryption")]
608 self.enable_share_history_on_invite,
609 self.cross_process_store_locks_holder_name,
610 )
611 .await;
612
613 debug!("Done building the Client");
614
615 Ok(Client { inner })
616 }
617}
618
619pub fn sanitize_server_name(s: &str) -> crate::Result<OwnedServerName, IdParseError> {
623 ServerName::parse(
624 s.trim().trim_start_matches("http://").trim_start_matches("https://").trim_end_matches('/'),
625 )
626}
627
628#[allow(clippy::unused_async, unused)] async fn build_store_config(
630 builder_config: BuilderStoreConfig,
631 cross_process_store_locks_holder_name: &str,
632) -> Result<StoreConfig, ClientBuildError> {
633 #[allow(clippy::infallible_destructuring_match)]
634 let store_config = match builder_config {
635 #[cfg(feature = "sqlite")]
636 BuilderStoreConfig::Sqlite { config, cache_path } => {
637 let store_config = StoreConfig::new(cross_process_store_locks_holder_name.to_owned())
638 .state_store(
639 matrix_sdk_sqlite::SqliteStateStore::open_with_config(config.clone()).await?,
640 )
641 .event_cache_store({
642 let mut config = config.clone();
643
644 if let Some(cache_path) = cache_path {
645 config = config.path(cache_path);
646 }
647
648 matrix_sdk_sqlite::SqliteEventCacheStore::open_with_config(config).await?
649 });
650
651 #[cfg(feature = "e2e-encryption")]
652 let store_config = store_config.crypto_store(
653 matrix_sdk_sqlite::SqliteCryptoStore::open_with_config(config).await?,
654 );
655
656 store_config
657 }
658
659 #[cfg(feature = "indexeddb")]
660 BuilderStoreConfig::IndexedDb { name, passphrase } => {
661 build_indexeddb_store_config(
662 &name,
663 passphrase.as_deref(),
664 cross_process_store_locks_holder_name,
665 )
666 .await?
667 }
668
669 BuilderStoreConfig::Custom(config) => config,
670 };
671 Ok(store_config)
672}
673
674#[cfg(all(target_family = "wasm", feature = "indexeddb"))]
677async fn build_indexeddb_store_config(
678 name: &str,
679 passphrase: Option<&str>,
680 cross_process_store_locks_holder_name: &str,
681) -> Result<StoreConfig, ClientBuildError> {
682 let cross_process_store_locks_holder_name = cross_process_store_locks_holder_name.to_owned();
683
684 #[cfg(feature = "e2e-encryption")]
685 let store_config = {
686 let (state_store, crypto_store) =
687 matrix_sdk_indexeddb::open_stores_with_name(name, passphrase).await?;
688 StoreConfig::new(cross_process_store_locks_holder_name)
689 .state_store(state_store)
690 .crypto_store(crypto_store)
691 };
692
693 #[cfg(not(feature = "e2e-encryption"))]
694 let store_config = {
695 let state_store = matrix_sdk_indexeddb::open_state_store(name, passphrase).await?;
696 StoreConfig::new(cross_process_store_locks_holder_name).state_store(state_store)
697 };
698
699 let store_config = {
700 tracing::warn!(
701 "The IndexedDB backend does not implement an event cache store, \
702 falling back to the in-memory event cache store…"
703 );
704 store_config.event_cache_store(matrix_sdk_base::event_cache::store::MemoryStore::new())
705 };
706
707 Ok(store_config)
708}
709
710#[cfg(all(not(target_family = "wasm"), feature = "indexeddb"))]
711#[allow(clippy::unused_async)]
712async fn build_indexeddb_store_config(
713 _name: &str,
714 _passphrase: Option<&str>,
715 _event_cache_store_lock_holder_name: &str,
716) -> Result<StoreConfig, ClientBuildError> {
717 panic!("the IndexedDB is only available on the 'wasm32' arch")
718}
719
720#[derive(Clone, Debug)]
721enum HttpConfig {
722 #[cfg(not(target_family = "wasm"))]
723 Settings(HttpSettings),
724 Custom(reqwest::Client),
725}
726
727#[cfg(not(target_family = "wasm"))]
728impl HttpConfig {
729 fn settings(&mut self) -> &mut HttpSettings {
730 match self {
731 Self::Settings(s) => s,
732 Self::Custom(_) => {
733 *self = Self::default();
734 match self {
735 Self::Settings(s) => s,
736 Self::Custom(_) => unreachable!(),
737 }
738 }
739 }
740 }
741}
742
743impl Default for HttpConfig {
744 fn default() -> Self {
745 #[cfg(not(target_family = "wasm"))]
746 return Self::Settings(HttpSettings::default());
747
748 #[cfg(target_family = "wasm")]
749 return Self::Custom(reqwest::Client::new());
750 }
751}
752
753#[derive(Clone)]
754enum BuilderStoreConfig {
755 #[cfg(feature = "sqlite")]
756 Sqlite {
757 config: SqliteStoreConfig,
758 cache_path: Option<std::path::PathBuf>,
759 },
760 #[cfg(feature = "indexeddb")]
761 IndexedDb {
762 name: String,
763 passphrase: Option<String>,
764 },
765 Custom(StoreConfig),
766}
767
768#[cfg(not(tarpaulin_include))]
769impl fmt::Debug for BuilderStoreConfig {
770 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
771 #[allow(clippy::infallible_destructuring_match)]
772 match self {
773 #[cfg(feature = "sqlite")]
774 Self::Sqlite { config, cache_path, .. } => f
775 .debug_struct("Sqlite")
776 .field("config", config)
777 .field("cache_path", cache_path)
778 .finish_non_exhaustive(),
779
780 #[cfg(feature = "indexeddb")]
781 Self::IndexedDb { name, .. } => {
782 f.debug_struct("IndexedDb").field("name", name).finish_non_exhaustive()
783 }
784
785 Self::Custom(store_config) => f.debug_tuple("Custom").field(store_config).finish(),
786 }
787 }
788}
789
790#[derive(Debug, Error)]
792pub enum ClientBuildError {
793 #[error("no homeserver or user ID was configured")]
795 MissingHomeserver,
796
797 #[error("The supplied server name is invalid")]
799 InvalidServerName,
800
801 #[error("Error looking up the .well-known endpoint on auto-discovery")]
803 AutoDiscovery(FromHttpResponseError<RumaApiError>),
804
805 #[error(transparent)]
807 SlidingSyncVersion(#[from] crate::sliding_sync::VersionBuilderError),
808
809 #[error(transparent)]
811 Url(#[from] url::ParseError),
812
813 #[error(transparent)]
815 Http(#[from] HttpError),
816
817 #[cfg(feature = "indexeddb")]
819 #[error(transparent)]
820 IndexeddbStore(#[from] matrix_sdk_indexeddb::OpenStoreError),
821
822 #[cfg(feature = "sqlite")]
824 #[error(transparent)]
825 SqliteStore(#[from] matrix_sdk_sqlite::OpenStoreError),
826}
827
828#[cfg(all(test, not(target_family = "wasm")))]
830pub(crate) mod tests {
831 use assert_matches::assert_matches;
832 use matrix_sdk_test::{async_test, test_json};
833 use serde_json::{json_internal, Value as JsonValue};
834 use wiremock::{
835 matchers::{method, path},
836 Mock, MockServer, ResponseTemplate,
837 };
838
839 use super::*;
840 use crate::sliding_sync::Version as SlidingSyncVersion;
841
842 #[test]
843 fn test_sanitize_server_name() {
844 assert_eq!(sanitize_server_name("matrix.org").unwrap().as_str(), "matrix.org");
845 assert_eq!(sanitize_server_name("https://matrix.org").unwrap().as_str(), "matrix.org");
846 assert_eq!(sanitize_server_name("http://matrix.org").unwrap().as_str(), "matrix.org");
847 assert_eq!(
848 sanitize_server_name("https://matrix.server.org").unwrap().as_str(),
849 "matrix.server.org"
850 );
851 assert_eq!(
852 sanitize_server_name("https://matrix.server.org/").unwrap().as_str(),
853 "matrix.server.org"
854 );
855 assert_eq!(
856 sanitize_server_name(" https://matrix.server.org// ").unwrap().as_str(),
857 "matrix.server.org"
858 );
859 assert_matches!(sanitize_server_name("https://matrix.server.org/something"), Err(_))
860 }
861
862 #[async_test]
869 async fn test_discovery_invalid_server() {
870 let mut builder = ClientBuilder::new();
872
873 builder = builder.server_name_or_homeserver_url("⚠️ This won't work 🚫");
875 let error = builder.build().await.unwrap_err();
876
877 assert_matches!(error, ClientBuildError::InvalidServerName);
879 }
880
881 #[async_test]
882 async fn test_discovery_no_server() {
883 let mut builder = ClientBuilder::new();
885
886 builder = builder.server_name_or_homeserver_url("localhost:3456");
888 let error = builder.build().await.unwrap_err();
889
890 println!("{error}");
892 assert_matches!(error, ClientBuildError::Http(_));
893 }
894
895 #[async_test]
896 async fn test_discovery_web_server() {
897 let server = MockServer::start().await;
900 let mut builder = ClientBuilder::new();
901
902 builder = builder.server_name_or_homeserver_url(server.uri());
904 let error = builder.build().await.unwrap_err();
905
906 assert_matches!(error, ClientBuildError::AutoDiscovery(FromHttpResponseError::Server(_)));
908 }
909
910 #[async_test]
911 async fn test_discovery_direct_legacy() {
912 let homeserver = make_mock_homeserver().await;
914 let mut builder = ClientBuilder::new();
915
916 builder = builder.server_name_or_homeserver_url(homeserver.uri());
918 let _client = builder.build().await.unwrap();
919
920 assert!(_client.sliding_sync_version().is_native());
922 }
923
924 #[async_test]
925 async fn test_discovery_well_known_parse_error() {
926 let server = MockServer::start().await;
928 let homeserver = make_mock_homeserver().await;
929 let mut builder = ClientBuilder::new();
930
931 let well_known = make_well_known_json(&homeserver.uri());
932 let bad_json = well_known.to_string().replace(',', "");
933 Mock::given(method("GET"))
934 .and(path("/.well-known/matrix/client"))
935 .respond_with(ResponseTemplate::new(200).set_body_json(bad_json))
936 .mount(&server)
937 .await;
938
939 builder = builder.server_name_or_homeserver_url(server.uri());
941 let error = builder.build().await.unwrap_err();
942
943 assert_matches!(
945 error,
946 ClientBuildError::AutoDiscovery(FromHttpResponseError::Deserialization(_))
947 );
948 }
949
950 #[async_test]
951 async fn test_discovery_well_known_legacy() {
952 let server = MockServer::start().await;
955 let homeserver = make_mock_homeserver().await;
956 let mut builder = ClientBuilder::new();
957
958 Mock::given(method("GET"))
959 .and(path("/.well-known/matrix/client"))
960 .respond_with(
961 ResponseTemplate::new(200).set_body_json(make_well_known_json(&homeserver.uri())),
962 )
963 .mount(&server)
964 .await;
965
966 builder = builder.server_name_or_homeserver_url(server.uri());
968 let client = builder.build().await.unwrap();
969
970 assert!(client.sliding_sync_version().is_native());
973 }
974
975 #[async_test]
976 async fn test_sliding_sync_discover_native() {
977 let homeserver = make_mock_homeserver().await;
979 let mut builder = ClientBuilder::new();
980
981 builder = builder
984 .server_name_or_homeserver_url(homeserver.uri())
985 .sliding_sync_version_builder(SlidingSyncVersionBuilder::DiscoverNative);
986
987 let client = builder.build().await.unwrap();
988
989 assert_matches!(client.sliding_sync_version(), SlidingSyncVersion::Native);
991 }
992
993 #[async_test]
994 #[cfg(feature = "e2e-encryption")]
995 async fn test_set_up_decryption_trust_requirement_cross_signed() {
996 let homeserver = make_mock_homeserver().await;
997 let builder = ClientBuilder::new()
998 .server_name_or_homeserver_url(homeserver.uri())
999 .with_decryption_settings(DecryptionSettings {
1000 sender_device_trust_requirement: TrustRequirement::CrossSigned,
1001 });
1002
1003 let client = builder.build().await.unwrap();
1004 assert_matches!(
1005 client.base_client().decryption_settings.sender_device_trust_requirement,
1006 TrustRequirement::CrossSigned
1007 );
1008 }
1009
1010 #[async_test]
1011 #[cfg(feature = "e2e-encryption")]
1012 async fn test_set_up_decryption_trust_requirement_untrusted() {
1013 let homeserver = make_mock_homeserver().await;
1014
1015 let builder = ClientBuilder::new()
1016 .server_name_or_homeserver_url(homeserver.uri())
1017 .with_decryption_settings(DecryptionSettings {
1018 sender_device_trust_requirement: TrustRequirement::Untrusted,
1019 });
1020
1021 let client = builder.build().await.unwrap();
1022 assert_matches!(
1023 client.base_client().decryption_settings.sender_device_trust_requirement,
1024 TrustRequirement::Untrusted
1025 );
1026 }
1027
1028 async fn make_mock_homeserver() -> MockServer {
1031 let homeserver = MockServer::start().await;
1032 Mock::given(method("GET"))
1033 .and(path("/_matrix/client/versions"))
1034 .respond_with(ResponseTemplate::new(200).set_body_json(&*test_json::VERSIONS))
1035 .mount(&homeserver)
1036 .await;
1037 Mock::given(method("GET"))
1038 .and(path("/_matrix/client/r0/login"))
1039 .respond_with(ResponseTemplate::new(200).set_body_json(&*test_json::LOGIN_TYPES))
1040 .mount(&homeserver)
1041 .await;
1042 homeserver
1043 }
1044
1045 fn make_well_known_json(homeserver_url: &str) -> JsonValue {
1046 ::serde_json::Value::Object({
1047 let mut object = ::serde_json::Map::new();
1048 let _ = object.insert(
1049 "m.homeserver".into(),
1050 json_internal!({
1051 "base_url": homeserver_url
1052 }),
1053 );
1054
1055 object
1056 })
1057 }
1058
1059 #[async_test]
1060 async fn test_cross_process_store_locks_holder_name() {
1061 {
1062 let homeserver = make_mock_homeserver().await;
1063 let client =
1064 ClientBuilder::new().homeserver_url(homeserver.uri()).build().await.unwrap();
1065
1066 assert_eq!(client.cross_process_store_locks_holder_name(), "main");
1067 }
1068
1069 {
1070 let homeserver = make_mock_homeserver().await;
1071 let client = ClientBuilder::new()
1072 .homeserver_url(homeserver.uri())
1073 .cross_process_store_locks_holder_name("foo".to_owned())
1074 .build()
1075 .await
1076 .unwrap();
1077
1078 assert_eq!(client.cross_process_store_locks_holder_name(), "foo");
1079 }
1080 }
1081}