1mod homeserver_config;
17
18#[cfg(feature = "experimental-search")]
19use std::collections::HashMap;
20#[cfg(feature = "sqlite")]
21use std::path::Path;
22#[cfg(any(feature = "experimental-search", feature = "sqlite"))]
23use std::path::PathBuf;
24use std::{collections::BTreeSet, fmt, sync::Arc};
25
26use homeserver_config::*;
27#[cfg(feature = "e2e-encryption")]
28use matrix_sdk_base::crypto::DecryptionSettings;
29#[cfg(feature = "e2e-encryption")]
30use matrix_sdk_base::crypto::{CollectStrategy, TrustRequirement};
31use matrix_sdk_base::{BaseClient, ThreadingSupport, store::StoreConfig};
32use matrix_sdk_common::cross_process_lock::CrossProcessLockConfig;
33#[cfg(feature = "sqlite")]
34use matrix_sdk_sqlite::SqliteStoreConfig;
35#[cfg(not(target_family = "wasm"))]
36use reqwest::Certificate;
37use ruma::{
38 OwnedServerName, ServerName,
39 api::{MatrixVersion, SupportedVersions, error::FromHttpResponseError},
40};
41use thiserror::Error;
42#[cfg(feature = "experimental-search")]
43use tokio::sync::Mutex;
44use tokio::sync::OnceCell;
45use tracing::{Span, debug, field::debug, instrument};
46
47use super::{Client, ClientInner};
48#[cfg(feature = "e2e-encryption")]
49use crate::encryption::EncryptionSettings;
50#[cfg(not(target_family = "wasm"))]
51use crate::http_client::HttpSettings;
52#[cfg(feature = "experimental-search")]
53use crate::search_index::SearchIndex;
54#[cfg(feature = "experimental-search")]
55use crate::search_index::SearchIndexStoreKind;
56use crate::{
57 HttpError, IdParseError,
58 authentication::AuthCtx,
59 client::caches::CachedValue::{Cached, NotSet},
60 config::RequestConfig,
61 error::RumaApiError,
62 http_client::HttpClient,
63 send_queue::SendQueueData,
64 sliding_sync::VersionBuilder as SlidingSyncVersionBuilder,
65};
66
67#[must_use]
110#[derive(Clone, Debug)]
111pub struct ClientBuilder {
112 homeserver_cfg: Option<HomeserverConfig>,
113 sliding_sync_version_builder: SlidingSyncVersionBuilder,
114 http_cfg: Option<HttpConfig>,
115 store_config: BuilderStoreConfig,
116 request_config: RequestConfig,
117 respect_login_well_known: bool,
118 server_versions: Option<BTreeSet<MatrixVersion>>,
119 handle_refresh_tokens: bool,
120 base_client: Option<BaseClient>,
121 #[cfg(feature = "e2e-encryption")]
122 encryption_settings: EncryptionSettings,
123 #[cfg(feature = "e2e-encryption")]
124 room_key_recipient_strategy: CollectStrategy,
125 #[cfg(feature = "e2e-encryption")]
126 decryption_settings: DecryptionSettings,
127 #[cfg(feature = "e2e-encryption")]
128 enable_share_history_on_invite: bool,
129 cross_process_lock_config: CrossProcessLockConfig,
130 threading_support: ThreadingSupport,
131 #[cfg(feature = "experimental-search")]
132 search_index_store_kind: SearchIndexStoreKind,
133}
134
135impl ClientBuilder {
136 const DEFAULT_CROSS_PROCESS_STORE_LOCKS_HOLDER_NAME: &str = "main";
137
138 pub(crate) fn new() -> Self {
139 Self {
140 homeserver_cfg: None,
141 sliding_sync_version_builder: SlidingSyncVersionBuilder::Native,
142 http_cfg: None,
143 store_config: BuilderStoreConfig::Custom(StoreConfig::new(
144 CrossProcessLockConfig::multi_process(
145 Self::DEFAULT_CROSS_PROCESS_STORE_LOCKS_HOLDER_NAME,
146 ),
147 )),
148 request_config: Default::default(),
149 respect_login_well_known: true,
150 server_versions: None,
151 handle_refresh_tokens: false,
152 base_client: None,
153 #[cfg(feature = "e2e-encryption")]
154 encryption_settings: Default::default(),
155 #[cfg(feature = "e2e-encryption")]
156 room_key_recipient_strategy: Default::default(),
157 #[cfg(feature = "e2e-encryption")]
158 decryption_settings: DecryptionSettings {
159 sender_device_trust_requirement: TrustRequirement::Untrusted,
160 },
161 #[cfg(feature = "e2e-encryption")]
162 enable_share_history_on_invite: false,
163 cross_process_lock_config: CrossProcessLockConfig::MultiProcess {
164 holder_name: Self::DEFAULT_CROSS_PROCESS_STORE_LOCKS_HOLDER_NAME.to_owned(),
165 },
166 threading_support: ThreadingSupport::Disabled,
167 #[cfg(feature = "experimental-search")]
168 search_index_store_kind: SearchIndexStoreKind::InMemory,
169 }
170 }
171
172 pub fn homeserver_url(mut self, url: impl AsRef<str>) -> Self {
179 self.homeserver_cfg = Some(HomeserverConfig::HomeserverUrl(url.as_ref().to_owned()));
180 self
181 }
182
183 pub fn server_name(mut self, server_name: &ServerName) -> Self {
193 self.homeserver_cfg = Some(HomeserverConfig::ServerName {
194 server: server_name.to_owned(),
195 protocol: UrlScheme::Https,
197 });
198 self
199 }
200
201 pub fn insecure_server_name_no_tls(mut self, server_name: &ServerName) -> Self {
210 self.homeserver_cfg = Some(HomeserverConfig::ServerName {
211 server: server_name.to_owned(),
212 protocol: UrlScheme::Http,
213 });
214 self
215 }
216
217 pub fn server_name_or_homeserver_url(mut self, server_name_or_url: impl AsRef<str>) -> Self {
228 self.homeserver_cfg = Some(HomeserverConfig::ServerNameOrHomeserverUrl(
229 server_name_or_url.as_ref().to_owned(),
230 ));
231 self
232 }
233
234 pub fn sliding_sync_version_builder(
236 mut self,
237 version_builder: SlidingSyncVersionBuilder,
238 ) -> Self {
239 self.sliding_sync_version_builder = version_builder;
240 self
241 }
242
243 #[cfg(feature = "sqlite")]
245 pub fn sqlite_store(mut self, path: impl AsRef<Path>, passphrase: Option<&str>) -> Self {
246 let sqlite_store_config = SqliteStoreConfig::new(path).passphrase(passphrase);
247 self.store_config =
248 BuilderStoreConfig::Sqlite { config: sqlite_store_config, cache_path: None };
249
250 self
251 }
252
253 #[cfg(feature = "sqlite")]
256 pub fn sqlite_store_with_cache_path(
257 mut self,
258 path: impl AsRef<Path>,
259 cache_path: impl AsRef<Path>,
260 passphrase: Option<&str>,
261 ) -> Self {
262 let sqlite_store_config = SqliteStoreConfig::new(path).passphrase(passphrase);
263 self.store_config = BuilderStoreConfig::Sqlite {
264 config: sqlite_store_config,
265 cache_path: Some(cache_path.as_ref().to_owned()),
266 };
267
268 self
269 }
270
271 #[cfg(feature = "sqlite")]
274 pub fn sqlite_store_with_config_and_cache_path(
275 mut self,
276 config: SqliteStoreConfig,
277 cache_path: Option<impl AsRef<Path>>,
278 ) -> Self {
279 self.store_config = BuilderStoreConfig::Sqlite {
280 config,
281 cache_path: cache_path.map(|cache_path| cache_path.as_ref().to_owned()),
282 };
283
284 self
285 }
286
287 #[cfg(feature = "indexeddb")]
289 pub fn indexeddb_store(mut self, name: &str, passphrase: Option<&str>) -> Self {
290 self.store_config = BuilderStoreConfig::IndexedDb {
291 name: name.to_owned(),
292 passphrase: passphrase.map(ToOwned::to_owned),
293 };
294 self
295 }
296
297 pub fn store_config(mut self, store_config: StoreConfig) -> Self {
321 self.store_config = BuilderStoreConfig::Custom(store_config);
322 self
323 }
324
325 pub fn respect_login_well_known(mut self, value: bool) -> Self {
328 self.respect_login_well_known = value;
329 self
330 }
331
332 pub fn request_config(mut self, request_config: RequestConfig) -> Self {
334 self.request_config = request_config;
335 self
336 }
337
338 #[cfg(not(target_family = "wasm"))]
354 pub fn proxy(mut self, proxy: impl AsRef<str>) -> Self {
355 self.http_settings().proxy = Some(proxy.as_ref().to_owned());
356 self
357 }
358
359 #[cfg(not(target_family = "wasm"))]
361 pub fn disable_ssl_verification(mut self) -> Self {
362 self.http_settings().disable_ssl_verification = true;
363 self
364 }
365
366 #[cfg(not(target_family = "wasm"))]
368 pub fn user_agent(mut self, user_agent: impl AsRef<str>) -> Self {
369 self.http_settings().user_agent = Some(user_agent.as_ref().to_owned());
370 self
371 }
372
373 #[cfg(not(target_family = "wasm"))]
382 pub fn add_root_certificates(mut self, certificates: Vec<Certificate>) -> Self {
383 self.http_settings().additional_root_certificates = certificates;
384 self
385 }
386
387 #[cfg(target_os = "android")]
392 pub fn add_raw_root_certificates(mut self, raw_certificates: Vec<Vec<u8>>) -> Self {
393 self.http_settings().additional_raw_root_certificates = raw_certificates;
394 self
395 }
396
397 #[cfg(not(target_family = "wasm"))]
401 pub fn disable_built_in_root_certificates(mut self) -> Self {
402 self.http_settings().disable_built_in_root_certificates = true;
403 self
404 }
405
406 pub fn http_client(mut self, client: reqwest::Client) -> Self {
416 self.http_cfg = Some(HttpConfig::Custom(client));
417 self
418 }
419
420 pub fn server_versions(mut self, value: impl IntoIterator<Item = MatrixVersion>) -> Self {
425 self.server_versions = Some(value.into_iter().collect());
426 self
427 }
428
429 #[cfg(not(target_family = "wasm"))]
430 fn http_settings(&mut self) -> &mut HttpSettings {
431 self.http_cfg.get_or_insert_with(Default::default).settings()
432 }
433
434 pub fn handle_refresh_tokens(mut self) -> Self {
456 self.handle_refresh_tokens = true;
457 self
458 }
459
460 #[doc(hidden)]
462 pub fn base_client(mut self, base_client: BaseClient) -> Self {
463 self.base_client = Some(base_client);
464 self
465 }
466
467 #[cfg(feature = "e2e-encryption")]
470 pub fn with_encryption_settings(mut self, settings: EncryptionSettings) -> Self {
471 self.encryption_settings = settings;
472 self
473 }
474
475 #[cfg(feature = "e2e-encryption")]
478 pub fn with_room_key_recipient_strategy(mut self, strategy: CollectStrategy) -> Self {
479 self.room_key_recipient_strategy = strategy;
480 self
481 }
482
483 #[cfg(feature = "e2e-encryption")]
485 pub fn with_decryption_settings(mut self, decryption_settings: DecryptionSettings) -> Self {
486 self.decryption_settings = decryption_settings;
487 self
488 }
489
490 #[cfg(feature = "e2e-encryption")]
495 pub fn with_enable_share_history_on_invite(
496 mut self,
497 enable_share_history_on_invite: bool,
498 ) -> Self {
499 self.enable_share_history_on_invite = enable_share_history_on_invite;
500 self
501 }
502
503 pub fn cross_process_store_config(
513 mut self,
514 cross_process_store_config: CrossProcessLockConfig,
515 ) -> Self {
516 self.cross_process_lock_config = cross_process_store_config;
517 self
518 }
519
520 pub fn with_threading_support(mut self, threading_support: ThreadingSupport) -> Self {
524 self.threading_support = threading_support;
525 self
526 }
527
528 #[cfg(feature = "experimental-search")]
530 pub fn search_index_store(mut self, kind: SearchIndexStoreKind) -> Self {
531 self.search_index_store_kind = kind;
532 self
533 }
534
535 #[instrument(skip_all, target = "matrix_sdk::client", fields(homeserver))]
548 pub async fn build(self) -> Result<Client, ClientBuildError> {
549 debug!("Starting to build the Client");
550
551 let homeserver_cfg = self.homeserver_cfg.ok_or(ClientBuildError::MissingHomeserver)?;
552 Span::current().record("homeserver", debug(&homeserver_cfg));
553
554 #[cfg(feature = "rustls-tls")]
555 crate::http_client::rustls::install_default_crypto_provider_if_none_installed();
556
557 #[cfg_attr(target_family = "wasm", allow(clippy::infallible_destructuring_match))]
558 let inner_http_client = match self.http_cfg.unwrap_or_default() {
559 #[cfg(not(target_family = "wasm"))]
560 HttpConfig::Settings(mut settings) => {
561 settings.timeout = self.request_config.timeout;
562 settings.make_client()?
563 }
564 HttpConfig::Custom(c) => c,
565 };
566
567 let base_client = if let Some(base_client) = self.base_client {
568 base_client
569 } else {
570 #[allow(unused_mut)]
571 let mut client = BaseClient::new(
572 build_store_config(self.store_config, &self.cross_process_lock_config).await?,
573 self.threading_support,
574 );
575
576 #[cfg(feature = "e2e-encryption")]
577 {
578 client.room_key_recipient_strategy = self.room_key_recipient_strategy;
579 client.decryption_settings = self.decryption_settings;
580 }
581
582 client
583 };
584
585 let http_client = HttpClient::new(inner_http_client.clone(), self.request_config);
586
587 #[allow(unused_variables)]
588 let HomeserverDiscoveryResult { server, homeserver, supported_versions, well_known } =
589 homeserver_cfg.discover(&http_client).await?;
590
591 let sliding_sync_version = {
592 let supported_versions = match supported_versions {
593 Some(versions) => Some(versions),
594 None if self.sliding_sync_version_builder.needs_get_supported_versions() => {
595 Some(get_supported_versions(&homeserver, &http_client).await?)
596 }
597 None => None,
598 };
599
600 let version = self.sliding_sync_version_builder.build(
601 supported_versions.map(|response| response.as_supported_versions()).as_ref(),
602 )?;
603
604 tracing::info!(?version, "selected sliding sync version");
605
606 version
607 };
608
609 let allow_insecure_oauth = homeserver.scheme() == "http";
610 let auth_ctx = Arc::new(AuthCtx::new(self.handle_refresh_tokens, allow_insecure_oauth));
611
612 let send_queue = Arc::new(SendQueueData::new(true));
614
615 let supported_versions = match self.server_versions {
616 Some(versions) => Cached(SupportedVersions { versions, features: Default::default() }),
617 None => NotSet,
618 };
619 let well_known = match well_known {
620 Some(well_known) => Cached(Some(well_known.into())),
621 None => NotSet,
622 };
623
624 let event_cache = OnceCell::new();
625 let latest_events = OnceCell::new();
626 let thread_subscriptions_catchup = OnceCell::new();
627
628 #[cfg(feature = "experimental-search")]
629 let search_index =
630 SearchIndex::new(Arc::new(Mutex::new(HashMap::new())), self.search_index_store_kind);
631
632 let inner = ClientInner::new(
633 auth_ctx,
634 server,
635 homeserver,
636 sliding_sync_version,
637 http_client,
638 base_client,
639 supported_versions,
640 well_known,
641 self.respect_login_well_known,
642 event_cache,
643 send_queue,
644 latest_events,
645 #[cfg(feature = "e2e-encryption")]
646 self.encryption_settings,
647 #[cfg(feature = "e2e-encryption")]
648 self.enable_share_history_on_invite,
649 self.cross_process_lock_config,
650 #[cfg(feature = "experimental-search")]
651 search_index,
652 thread_subscriptions_catchup,
653 )
654 .await;
655
656 debug!("Done building the Client");
657
658 Ok(Client { inner })
659 }
660}
661
662pub fn sanitize_server_name(s: &str) -> crate::Result<OwnedServerName, IdParseError> {
666 ServerName::parse(
667 s.trim().trim_start_matches("http://").trim_start_matches("https://").trim_end_matches('/'),
668 )
669}
670
671#[allow(clippy::unused_async, unused)] async fn build_store_config(
673 builder_config: BuilderStoreConfig,
674 cross_process_store_config: &CrossProcessLockConfig,
675) -> Result<StoreConfig, ClientBuildError> {
676 #[allow(clippy::infallible_destructuring_match)]
677 let store_config = match builder_config {
678 #[cfg(feature = "sqlite")]
679 BuilderStoreConfig::Sqlite { config, cache_path } => {
680 let store_config = StoreConfig::new(cross_process_store_config.clone())
681 .state_store(
682 matrix_sdk_sqlite::SqliteStateStore::open_with_config(config.clone()).await?,
683 )
684 .event_cache_store({
685 let mut config = config.clone();
686
687 if let Some(ref cache_path) = cache_path {
688 config = config.path(cache_path);
689 }
690
691 matrix_sdk_sqlite::SqliteEventCacheStore::open_with_config(config).await?
692 })
693 .media_store({
694 let mut config = config.clone();
695
696 if let Some(ref cache_path) = cache_path {
697 config = config.path(cache_path);
698 }
699
700 matrix_sdk_sqlite::SqliteMediaStore::open_with_config(config).await?
701 });
702
703 #[cfg(feature = "e2e-encryption")]
704 let store_config = store_config.crypto_store(
705 matrix_sdk_sqlite::SqliteCryptoStore::open_with_config(config).await?,
706 );
707
708 store_config
709 }
710
711 #[cfg(feature = "indexeddb")]
712 BuilderStoreConfig::IndexedDb { name, passphrase } => {
713 build_indexeddb_store_config(
714 &name,
715 passphrase.as_deref(),
716 cross_process_store_config.clone(),
717 )
718 .await?
719 }
720
721 BuilderStoreConfig::Custom(config) => config,
722 };
723 Ok(store_config)
724}
725
726#[cfg(all(target_family = "wasm", feature = "indexeddb"))]
729async fn build_indexeddb_store_config(
730 name: &str,
731 passphrase: Option<&str>,
732 cross_process_store_config: CrossProcessLockConfig,
733) -> Result<StoreConfig, ClientBuildError> {
734 let stores = matrix_sdk_indexeddb::IndexeddbStores::open(name, passphrase).await?;
735 let store_config = StoreConfig::new(cross_process_store_config)
736 .state_store(stores.state)
737 .event_cache_store(stores.event_cache)
738 .media_store(stores.media);
739
740 #[cfg(feature = "e2e-encryption")]
741 let store_config = store_config.crypto_store(stores.crypto);
742
743 Ok(store_config)
744}
745
746#[cfg(all(not(target_family = "wasm"), feature = "indexeddb"))]
747#[allow(clippy::unused_async)]
748async fn build_indexeddb_store_config(
749 _name: &str,
750 _passphrase: Option<&str>,
751 _cross_process_store_config: CrossProcessLockConfig,
752) -> Result<StoreConfig, ClientBuildError> {
753 panic!("the IndexedDB is only available on the 'wasm32' arch")
754}
755
756#[derive(Clone, Debug)]
757enum HttpConfig {
758 #[cfg(not(target_family = "wasm"))]
759 Settings(HttpSettings),
760 Custom(reqwest::Client),
761}
762
763#[cfg(not(target_family = "wasm"))]
764impl HttpConfig {
765 fn settings(&mut self) -> &mut HttpSettings {
766 match self {
767 Self::Settings(s) => s,
768 Self::Custom(_) => {
769 *self = Self::default();
770 match self {
771 Self::Settings(s) => s,
772 Self::Custom(_) => unreachable!(),
773 }
774 }
775 }
776 }
777}
778
779impl Default for HttpConfig {
780 fn default() -> Self {
781 #[cfg(not(target_family = "wasm"))]
782 return Self::Settings(HttpSettings::default());
783
784 #[cfg(target_family = "wasm")]
785 return Self::Custom(reqwest::Client::new());
786 }
787}
788
789#[derive(Clone)]
790enum BuilderStoreConfig {
791 #[cfg(feature = "sqlite")]
792 Sqlite {
793 config: SqliteStoreConfig,
794 cache_path: Option<PathBuf>,
795 },
796 #[cfg(feature = "indexeddb")]
797 IndexedDb {
798 name: String,
799 passphrase: Option<String>,
800 },
801 Custom(StoreConfig),
802}
803
804#[cfg(not(tarpaulin_include))]
805impl fmt::Debug for BuilderStoreConfig {
806 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
807 #[allow(clippy::infallible_destructuring_match)]
808 match self {
809 #[cfg(feature = "sqlite")]
810 Self::Sqlite { config, cache_path, .. } => f
811 .debug_struct("Sqlite")
812 .field("config", config)
813 .field("cache_path", cache_path)
814 .finish_non_exhaustive(),
815
816 #[cfg(feature = "indexeddb")]
817 Self::IndexedDb { name, .. } => {
818 f.debug_struct("IndexedDb").field("name", name).finish_non_exhaustive()
819 }
820
821 Self::Custom(store_config) => f.debug_tuple("Custom").field(store_config).finish(),
822 }
823 }
824}
825
826#[derive(Debug, Error)]
828pub enum ClientBuildError {
829 #[error("No homeserver or user ID was configured")]
831 MissingHomeserver,
832
833 #[error("The supplied server name is invalid")]
835 InvalidServerName,
836
837 #[error("Error looking up the .well-known endpoint on auto-discovery")]
839 AutoDiscovery(FromHttpResponseError<RumaApiError>),
840
841 #[error(transparent)]
843 SlidingSyncVersion(#[from] crate::sliding_sync::VersionBuilderError),
844
845 #[error(transparent)]
847 Url(#[from] url::ParseError),
848
849 #[error(transparent)]
851 Http(#[from] HttpError),
852
853 #[cfg(feature = "indexeddb")]
855 #[error(transparent)]
856 IndexeddbStore(#[from] matrix_sdk_indexeddb::OpenStoreError),
857
858 #[cfg(feature = "sqlite")]
860 #[error(transparent)]
861 SqliteStore(#[from] matrix_sdk_sqlite::OpenStoreError),
862}
863
864#[cfg(all(test, not(target_family = "wasm")))]
866pub(crate) mod tests {
867 use assert_matches::assert_matches;
868 use assert_matches2::assert_let;
869 use matrix_sdk_test::{async_test, test_json};
870 use serde_json::{Value as JsonValue, json_internal};
871 use wiremock::{
872 Mock, MockServer, ResponseTemplate,
873 matchers::{method, path},
874 };
875
876 use super::*;
877 use crate::sliding_sync::Version as SlidingSyncVersion;
878
879 #[test]
880 fn test_sanitize_server_name() {
881 assert_eq!(sanitize_server_name("matrix.org").unwrap().as_str(), "matrix.org");
882 assert_eq!(sanitize_server_name("https://matrix.org").unwrap().as_str(), "matrix.org");
883 assert_eq!(sanitize_server_name("http://matrix.org").unwrap().as_str(), "matrix.org");
884 assert_eq!(
885 sanitize_server_name("https://matrix.server.org").unwrap().as_str(),
886 "matrix.server.org"
887 );
888 assert_eq!(
889 sanitize_server_name("https://matrix.server.org/").unwrap().as_str(),
890 "matrix.server.org"
891 );
892 assert_eq!(
893 sanitize_server_name(" https://matrix.server.org// ").unwrap().as_str(),
894 "matrix.server.org"
895 );
896 assert_matches!(sanitize_server_name("https://matrix.server.org/something"), Err(_))
897 }
898
899 #[async_test]
906 async fn test_discovery_invalid_server() {
907 let mut builder = ClientBuilder::new();
909
910 builder = builder.server_name_or_homeserver_url("⚠️ This won't work 🚫");
912 let error = builder.build().await.unwrap_err();
913
914 assert_matches!(error, ClientBuildError::InvalidServerName);
916 }
917
918 #[async_test]
919 async fn test_discovery_no_server() {
920 let mut builder = ClientBuilder::new();
922
923 builder = builder.server_name_or_homeserver_url("localhost:3456");
925 let error = builder.build().await.unwrap_err();
926
927 println!("{error}");
929 assert_matches!(error, ClientBuildError::Http(_));
930 }
931
932 #[async_test]
933 async fn test_discovery_web_server() {
934 let server = MockServer::start().await;
937 let mut builder = ClientBuilder::new();
938
939 builder = builder.server_name_or_homeserver_url(server.uri());
941 let error = builder.build().await.unwrap_err();
942
943 assert_matches!(error, ClientBuildError::AutoDiscovery(FromHttpResponseError::Server(_)));
945 }
946
947 #[async_test]
948 async fn test_discovery_direct_legacy() {
949 let homeserver = make_mock_homeserver().await;
951 let mut builder = ClientBuilder::new();
952
953 builder = builder.server_name_or_homeserver_url(homeserver.uri());
955 let _client = builder.build().await.unwrap();
956
957 assert!(_client.sliding_sync_version().is_native());
959 }
960
961 #[async_test]
962 async fn test_discovery_well_known_parse_error() {
963 let server = MockServer::start().await;
965 let homeserver = make_mock_homeserver().await;
966 let mut builder = ClientBuilder::new();
967
968 let well_known = make_well_known_json(&homeserver.uri());
969 let bad_json = well_known.to_string().replace(',', "");
970 Mock::given(method("GET"))
971 .and(path("/.well-known/matrix/client"))
972 .respond_with(ResponseTemplate::new(200).set_body_json(bad_json))
973 .mount(&server)
974 .await;
975
976 builder = builder.server_name_or_homeserver_url(server.uri());
978 let error = builder.build().await.unwrap_err();
979
980 assert_matches!(
982 error,
983 ClientBuildError::AutoDiscovery(FromHttpResponseError::Deserialization(_))
984 );
985 }
986
987 #[async_test]
988 async fn test_discovery_well_known_legacy() {
989 let server = MockServer::start().await;
992 let homeserver = make_mock_homeserver().await;
993 let mut builder = ClientBuilder::new();
994
995 Mock::given(method("GET"))
996 .and(path("/.well-known/matrix/client"))
997 .respond_with(
998 ResponseTemplate::new(200).set_body_json(make_well_known_json(&homeserver.uri())),
999 )
1000 .mount(&server)
1001 .await;
1002
1003 builder = builder.server_name_or_homeserver_url(server.uri());
1005 let client = builder.build().await.unwrap();
1006
1007 assert!(client.sliding_sync_version().is_native());
1010 }
1011
1012 #[async_test]
1013 async fn test_sliding_sync_discover_native() {
1014 let homeserver = make_mock_homeserver().await;
1016 let mut builder = ClientBuilder::new();
1017
1018 builder = builder
1021 .server_name_or_homeserver_url(homeserver.uri())
1022 .sliding_sync_version_builder(SlidingSyncVersionBuilder::DiscoverNative);
1023
1024 let client = builder.build().await.unwrap();
1025
1026 assert_matches!(client.sliding_sync_version(), SlidingSyncVersion::Native);
1028 }
1029
1030 #[async_test]
1031 #[cfg(feature = "e2e-encryption")]
1032 async fn test_set_up_decryption_trust_requirement_cross_signed() {
1033 let homeserver = make_mock_homeserver().await;
1034 let builder = ClientBuilder::new()
1035 .server_name_or_homeserver_url(homeserver.uri())
1036 .with_decryption_settings(DecryptionSettings {
1037 sender_device_trust_requirement: TrustRequirement::CrossSigned,
1038 });
1039
1040 let client = builder.build().await.unwrap();
1041 assert_matches!(
1042 client.base_client().decryption_settings.sender_device_trust_requirement,
1043 TrustRequirement::CrossSigned
1044 );
1045 }
1046
1047 #[async_test]
1048 #[cfg(feature = "e2e-encryption")]
1049 async fn test_set_up_decryption_trust_requirement_untrusted() {
1050 let homeserver = make_mock_homeserver().await;
1051
1052 let builder = ClientBuilder::new()
1053 .server_name_or_homeserver_url(homeserver.uri())
1054 .with_decryption_settings(DecryptionSettings {
1055 sender_device_trust_requirement: TrustRequirement::Untrusted,
1056 });
1057
1058 let client = builder.build().await.unwrap();
1059 assert_matches!(
1060 client.base_client().decryption_settings.sender_device_trust_requirement,
1061 TrustRequirement::Untrusted
1062 );
1063 }
1064
1065 async fn make_mock_homeserver() -> MockServer {
1068 let homeserver = MockServer::start().await;
1069 Mock::given(method("GET"))
1070 .and(path("/_matrix/client/versions"))
1071 .respond_with(ResponseTemplate::new(200).set_body_json(&*test_json::VERSIONS))
1072 .mount(&homeserver)
1073 .await;
1074 Mock::given(method("GET"))
1075 .and(path("/_matrix/client/r0/login"))
1076 .respond_with(ResponseTemplate::new(200).set_body_json(&*test_json::LOGIN_TYPES))
1077 .mount(&homeserver)
1078 .await;
1079 homeserver
1080 }
1081
1082 fn make_well_known_json(homeserver_url: &str) -> JsonValue {
1083 ::serde_json::Value::Object({
1084 let mut object = ::serde_json::Map::new();
1085 let _ = object.insert(
1086 "m.homeserver".into(),
1087 json_internal!({
1088 "base_url": homeserver_url
1089 }),
1090 );
1091
1092 object
1093 })
1094 }
1095
1096 #[async_test]
1097 async fn test_cross_process_store_locks_holder_name() {
1098 {
1099 let homeserver = make_mock_homeserver().await;
1100 let client =
1101 ClientBuilder::new().homeserver_url(homeserver.uri()).build().await.unwrap();
1102
1103 assert_let!(
1104 CrossProcessLockConfig::MultiProcess { holder_name } =
1105 client.cross_process_lock_config()
1106 );
1107 assert_eq!(holder_name, "main");
1108 }
1109
1110 {
1111 let homeserver = make_mock_homeserver().await;
1112 let client = ClientBuilder::new()
1113 .homeserver_url(homeserver.uri())
1114 .cross_process_store_config(CrossProcessLockConfig::multi_process("foo"))
1115 .build()
1116 .await
1117 .unwrap();
1118
1119 assert_let!(
1120 CrossProcessLockConfig::MultiProcess { holder_name } =
1121 client.cross_process_lock_config()
1122 );
1123 assert_eq!(holder_name, "foo");
1124 }
1125 }
1126}