1mod homeserver_config;
17
18use std::{fmt, sync::Arc};
19
20use homeserver_config::*;
21use matrix_sdk_base::{store::StoreConfig, BaseClient};
22use ruma::{
23 api::{error::FromHttpResponseError, MatrixVersion},
24 OwnedServerName, ServerName,
25};
26use thiserror::Error;
27use tokio::sync::{broadcast, Mutex, OnceCell};
28use tracing::{debug, field::debug, instrument, Span};
29
30use super::{Client, ClientInner};
31#[cfg(feature = "e2e-encryption")]
32use crate::crypto::{CollectStrategy, TrustRequirement};
33#[cfg(feature = "e2e-encryption")]
34use crate::encryption::EncryptionSettings;
35#[cfg(not(target_arch = "wasm32"))]
36use crate::http_client::HttpSettings;
37use crate::{
38 authentication::{oauth::OAuthCtx, AuthCtx},
39 client::ClientServerCapabilities,
40 config::RequestConfig,
41 error::RumaApiError,
42 http_client::HttpClient,
43 send_queue::SendQueueData,
44 sliding_sync::VersionBuilder as SlidingSyncVersionBuilder,
45 HttpError, IdParseError,
46};
47
48#[must_use]
87#[derive(Clone, Debug)]
88pub struct ClientBuilder {
89 homeserver_cfg: Option<HomeserverConfig>,
90 sliding_sync_version_builder: SlidingSyncVersionBuilder,
91 http_cfg: Option<HttpConfig>,
92 store_config: BuilderStoreConfig,
93 request_config: RequestConfig,
94 respect_login_well_known: bool,
95 server_versions: Option<Box<[MatrixVersion]>>,
96 handle_refresh_tokens: bool,
97 base_client: Option<BaseClient>,
98 #[cfg(feature = "e2e-encryption")]
99 encryption_settings: EncryptionSettings,
100 #[cfg(feature = "e2e-encryption")]
101 room_key_recipient_strategy: CollectStrategy,
102 #[cfg(feature = "e2e-encryption")]
103 decryption_trust_requirement: TrustRequirement,
104 cross_process_store_locks_holder_name: String,
105}
106
107impl ClientBuilder {
108 const DEFAULT_CROSS_PROCESS_STORE_LOCKS_HOLDER_NAME: &str = "main";
109
110 pub(crate) fn new() -> Self {
111 Self {
112 homeserver_cfg: None,
113 sliding_sync_version_builder: SlidingSyncVersionBuilder::Native,
114 http_cfg: None,
115 store_config: BuilderStoreConfig::Custom(StoreConfig::new(
116 Self::DEFAULT_CROSS_PROCESS_STORE_LOCKS_HOLDER_NAME.to_owned(),
117 )),
118 request_config: Default::default(),
119 respect_login_well_known: true,
120 server_versions: None,
121 handle_refresh_tokens: false,
122 base_client: None,
123 #[cfg(feature = "e2e-encryption")]
124 encryption_settings: Default::default(),
125 #[cfg(feature = "e2e-encryption")]
126 room_key_recipient_strategy: Default::default(),
127 #[cfg(feature = "e2e-encryption")]
128 decryption_trust_requirement: TrustRequirement::Untrusted,
129 cross_process_store_locks_holder_name:
130 Self::DEFAULT_CROSS_PROCESS_STORE_LOCKS_HOLDER_NAME.to_owned(),
131 }
132 }
133
134 pub fn homeserver_url(mut self, url: impl AsRef<str>) -> Self {
141 self.homeserver_cfg = Some(HomeserverConfig::HomeserverUrl(url.as_ref().to_owned()));
142 self
143 }
144
145 pub fn server_name(mut self, server_name: &ServerName) -> Self {
155 self.homeserver_cfg = Some(HomeserverConfig::ServerName {
156 server: server_name.to_owned(),
157 protocol: UrlScheme::Https,
159 });
160 self
161 }
162
163 pub fn insecure_server_name_no_tls(mut self, server_name: &ServerName) -> Self {
172 self.homeserver_cfg = Some(HomeserverConfig::ServerName {
173 server: server_name.to_owned(),
174 protocol: UrlScheme::Http,
175 });
176 self
177 }
178
179 pub fn server_name_or_homeserver_url(mut self, server_name_or_url: impl AsRef<str>) -> Self {
190 self.homeserver_cfg = Some(HomeserverConfig::ServerNameOrHomeserverUrl(
191 server_name_or_url.as_ref().to_owned(),
192 ));
193 self
194 }
195
196 pub fn sliding_sync_version_builder(
198 mut self,
199 version_builder: SlidingSyncVersionBuilder,
200 ) -> Self {
201 self.sliding_sync_version_builder = version_builder;
202 self
203 }
204
205 #[cfg(feature = "sqlite")]
207 pub fn sqlite_store(
208 mut self,
209 path: impl AsRef<std::path::Path>,
210 passphrase: Option<&str>,
211 ) -> Self {
212 self.store_config = BuilderStoreConfig::Sqlite {
213 path: path.as_ref().to_owned(),
214 cache_path: None,
215 passphrase: passphrase.map(ToOwned::to_owned),
216 };
217 self
218 }
219
220 #[cfg(feature = "sqlite")]
223 pub fn sqlite_store_with_cache_path(
224 mut self,
225 path: impl AsRef<std::path::Path>,
226 cache_path: impl AsRef<std::path::Path>,
227 passphrase: Option<&str>,
228 ) -> Self {
229 self.store_config = BuilderStoreConfig::Sqlite {
230 path: path.as_ref().to_owned(),
231 cache_path: Some(cache_path.as_ref().to_owned()),
232 passphrase: passphrase.map(ToOwned::to_owned),
233 };
234 self
235 }
236
237 #[cfg(feature = "indexeddb")]
239 pub fn indexeddb_store(mut self, name: &str, passphrase: Option<&str>) -> Self {
240 self.store_config = BuilderStoreConfig::IndexedDb {
241 name: name.to_owned(),
242 passphrase: passphrase.map(ToOwned::to_owned),
243 };
244 self
245 }
246
247 pub fn store_config(mut self, store_config: StoreConfig) -> Self {
269 self.store_config = BuilderStoreConfig::Custom(store_config);
270 self
271 }
272
273 pub fn respect_login_well_known(mut self, value: bool) -> Self {
276 self.respect_login_well_known = value;
277 self
278 }
279
280 pub fn request_config(mut self, request_config: RequestConfig) -> Self {
282 self.request_config = request_config;
283 self
284 }
285
286 #[cfg(not(target_arch = "wasm32"))]
302 pub fn proxy(mut self, proxy: impl AsRef<str>) -> Self {
303 self.http_settings().proxy = Some(proxy.as_ref().to_owned());
304 self
305 }
306
307 #[cfg(not(target_arch = "wasm32"))]
309 pub fn disable_ssl_verification(mut self) -> Self {
310 self.http_settings().disable_ssl_verification = true;
311 self
312 }
313
314 #[cfg(not(target_arch = "wasm32"))]
316 pub fn user_agent(mut self, user_agent: impl AsRef<str>) -> Self {
317 self.http_settings().user_agent = Some(user_agent.as_ref().to_owned());
318 self
319 }
320
321 #[cfg(not(target_arch = "wasm32"))]
330 pub fn add_root_certificates(mut self, certificates: Vec<reqwest::Certificate>) -> Self {
331 self.http_settings().additional_root_certificates = certificates;
332 self
333 }
334
335 #[cfg(not(target_arch = "wasm32"))]
339 pub fn disable_built_in_root_certificates(mut self) -> Self {
340 self.http_settings().disable_built_in_root_certificates = true;
341 self
342 }
343
344 pub fn http_client(mut self, client: reqwest::Client) -> Self {
354 self.http_cfg = Some(HttpConfig::Custom(client));
355 self
356 }
357
358 pub fn server_versions(mut self, value: impl IntoIterator<Item = MatrixVersion>) -> Self {
363 self.server_versions = Some(value.into_iter().collect());
364 self
365 }
366
367 #[cfg(not(target_arch = "wasm32"))]
368 fn http_settings(&mut self) -> &mut HttpSettings {
369 self.http_cfg.get_or_insert_with(Default::default).settings()
370 }
371
372 pub fn handle_refresh_tokens(mut self) -> Self {
394 self.handle_refresh_tokens = true;
395 self
396 }
397
398 #[doc(hidden)]
400 pub fn base_client(mut self, base_client: BaseClient) -> Self {
401 self.base_client = Some(base_client);
402 self
403 }
404
405 #[cfg(feature = "e2e-encryption")]
408 pub fn with_encryption_settings(mut self, settings: EncryptionSettings) -> Self {
409 self.encryption_settings = settings;
410 self
411 }
412
413 #[cfg(feature = "e2e-encryption")]
416 pub fn with_room_key_recipient_strategy(mut self, strategy: CollectStrategy) -> Self {
417 self.room_key_recipient_strategy = strategy;
418 self
419 }
420
421 #[cfg(feature = "e2e-encryption")]
423 pub fn with_decryption_trust_requirement(
424 mut self,
425 trust_requirement: TrustRequirement,
426 ) -> Self {
427 self.decryption_trust_requirement = trust_requirement;
428 self
429 }
430
431 pub fn cross_process_store_locks_holder_name(mut self, holder_name: String) -> Self {
441 self.cross_process_store_locks_holder_name = holder_name;
442 self
443 }
444
445 #[instrument(skip_all, target = "matrix_sdk::client", fields(homeserver))]
458 pub async fn build(self) -> Result<Client, ClientBuildError> {
459 debug!("Starting to build the Client");
460
461 let homeserver_cfg = self.homeserver_cfg.ok_or(ClientBuildError::MissingHomeserver)?;
462 Span::current().record("homeserver", debug(&homeserver_cfg));
463
464 #[cfg_attr(target_arch = "wasm32", allow(clippy::infallible_destructuring_match))]
465 let inner_http_client = match self.http_cfg.unwrap_or_default() {
466 #[cfg(not(target_arch = "wasm32"))]
467 HttpConfig::Settings(mut settings) => {
468 settings.timeout = self.request_config.timeout;
469 settings.make_client()?
470 }
471 HttpConfig::Custom(c) => c,
472 };
473
474 let base_client = if let Some(base_client) = self.base_client {
475 base_client
476 } else {
477 #[allow(unused_mut)]
478 let mut client = BaseClient::new(
479 build_store_config(self.store_config, &self.cross_process_store_locks_holder_name)
480 .await?,
481 );
482
483 #[cfg(feature = "e2e-encryption")]
484 {
485 client.room_key_recipient_strategy = self.room_key_recipient_strategy;
486 client.decryption_trust_requirement = self.decryption_trust_requirement;
487 }
488
489 client
490 };
491
492 let http_client = HttpClient::new(inner_http_client.clone(), self.request_config);
493
494 #[allow(unused_variables)]
495 let HomeserverDiscoveryResult { server, homeserver, supported_versions } =
496 homeserver_cfg.discover(&http_client).await?;
497
498 let sliding_sync_version = {
499 let supported_versions = match supported_versions {
500 Some(versions) => Some(versions),
501 None if self.sliding_sync_version_builder.needs_get_supported_versions() => {
502 Some(get_supported_versions(&homeserver, &http_client).await?)
503 }
504 None => None,
505 };
506
507 let version = self.sliding_sync_version_builder.build(supported_versions.as_ref())?;
508
509 tracing::info!(?version, "selected sliding sync version");
510
511 version
512 };
513
514 let allow_insecure_oauth = homeserver.scheme() == "http";
515
516 let auth_ctx = Arc::new(AuthCtx {
517 handle_refresh_tokens: self.handle_refresh_tokens,
518 refresh_token_lock: Arc::new(Mutex::new(Ok(()))),
519 session_change_sender: broadcast::Sender::new(1),
520 auth_data: OnceCell::default(),
521 tokens: OnceCell::default(),
522 reload_session_callback: OnceCell::default(),
523 save_session_callback: OnceCell::default(),
524 oauth: OAuthCtx::new(allow_insecure_oauth),
525 });
526
527 let send_queue = Arc::new(SendQueueData::new(true));
529
530 let server_capabilities = ClientServerCapabilities {
531 server_versions: self.server_versions,
532 unstable_features: None,
533 };
534
535 let event_cache = OnceCell::new();
536 let inner = ClientInner::new(
537 auth_ctx,
538 server,
539 homeserver,
540 sliding_sync_version,
541 http_client,
542 base_client,
543 server_capabilities,
544 self.respect_login_well_known,
545 event_cache,
546 send_queue,
547 #[cfg(feature = "e2e-encryption")]
548 self.encryption_settings,
549 self.cross_process_store_locks_holder_name,
550 )
551 .await;
552
553 debug!("Done building the Client");
554
555 Ok(Client { inner })
556 }
557}
558
559pub fn sanitize_server_name(s: &str) -> crate::Result<OwnedServerName, IdParseError> {
563 ServerName::parse(
564 s.trim().trim_start_matches("http://").trim_start_matches("https://").trim_end_matches('/'),
565 )
566}
567
568#[allow(clippy::unused_async, unused)] async fn build_store_config(
570 builder_config: BuilderStoreConfig,
571 cross_process_store_locks_holder_name: &str,
572) -> Result<StoreConfig, ClientBuildError> {
573 #[allow(clippy::infallible_destructuring_match)]
574 let store_config = match builder_config {
575 #[cfg(feature = "sqlite")]
576 BuilderStoreConfig::Sqlite { path, cache_path, passphrase } => {
577 let store_config = StoreConfig::new(cross_process_store_locks_holder_name.to_owned())
578 .state_store(
579 matrix_sdk_sqlite::SqliteStateStore::open(&path, passphrase.as_deref()).await?,
580 )
581 .event_cache_store(
582 matrix_sdk_sqlite::SqliteEventCacheStore::open(
583 cache_path.as_ref().unwrap_or(&path),
584 passphrase.as_deref(),
585 )
586 .await?,
587 );
588
589 #[cfg(feature = "e2e-encryption")]
590 let store_config = store_config.crypto_store(
591 matrix_sdk_sqlite::SqliteCryptoStore::open(&path, passphrase.as_deref()).await?,
592 );
593
594 store_config
595 }
596
597 #[cfg(feature = "indexeddb")]
598 BuilderStoreConfig::IndexedDb { name, passphrase } => {
599 build_indexeddb_store_config(
600 &name,
601 passphrase.as_deref(),
602 cross_process_store_locks_holder_name,
603 )
604 .await?
605 }
606
607 BuilderStoreConfig::Custom(config) => config,
608 };
609 Ok(store_config)
610}
611
612#[cfg(all(target_arch = "wasm32", feature = "indexeddb"))]
615async fn build_indexeddb_store_config(
616 name: &str,
617 passphrase: Option<&str>,
618 cross_process_store_locks_holder_name: &str,
619) -> Result<StoreConfig, ClientBuildError> {
620 let cross_process_store_locks_holder_name = cross_process_store_locks_holder_name.to_owned();
621
622 #[cfg(feature = "e2e-encryption")]
623 let store_config = {
624 let (state_store, crypto_store) =
625 matrix_sdk_indexeddb::open_stores_with_name(name, passphrase).await?;
626 StoreConfig::new(cross_process_store_locks_holder_name)
627 .state_store(state_store)
628 .crypto_store(crypto_store)
629 };
630
631 #[cfg(not(feature = "e2e-encryption"))]
632 let store_config = {
633 let state_store = matrix_sdk_indexeddb::open_state_store(name, passphrase).await?;
634 StoreConfig::new(cross_process_store_locks_holder_name).state_store(state_store)
635 };
636
637 let store_config = {
638 tracing::warn!("The IndexedDB backend does not implement an event cache store, falling back to the in-memory event cache store…");
639 store_config.event_cache_store(matrix_sdk_base::event_cache::store::MemoryStore::new())
640 };
641
642 Ok(store_config)
643}
644
645#[cfg(all(not(target_arch = "wasm32"), feature = "indexeddb"))]
646#[allow(clippy::unused_async)]
647async fn build_indexeddb_store_config(
648 _name: &str,
649 _passphrase: Option<&str>,
650 _event_cache_store_lock_holder_name: &str,
651) -> Result<StoreConfig, ClientBuildError> {
652 panic!("the IndexedDB is only available on the 'wasm32' arch")
653}
654
655#[derive(Clone, Debug)]
656enum HttpConfig {
657 #[cfg(not(target_arch = "wasm32"))]
658 Settings(HttpSettings),
659 Custom(reqwest::Client),
660}
661
662#[cfg(not(target_arch = "wasm32"))]
663impl HttpConfig {
664 fn settings(&mut self) -> &mut HttpSettings {
665 match self {
666 Self::Settings(s) => s,
667 Self::Custom(_) => {
668 *self = Self::default();
669 match self {
670 Self::Settings(s) => s,
671 Self::Custom(_) => unreachable!(),
672 }
673 }
674 }
675 }
676}
677
678impl Default for HttpConfig {
679 fn default() -> Self {
680 #[cfg(not(target_arch = "wasm32"))]
681 return Self::Settings(HttpSettings::default());
682
683 #[cfg(target_arch = "wasm32")]
684 return Self::Custom(reqwest::Client::new());
685 }
686}
687
688#[derive(Clone)]
689enum BuilderStoreConfig {
690 #[cfg(feature = "sqlite")]
691 Sqlite {
692 path: std::path::PathBuf,
693 cache_path: Option<std::path::PathBuf>,
694 passphrase: Option<String>,
695 },
696 #[cfg(feature = "indexeddb")]
697 IndexedDb {
698 name: String,
699 passphrase: Option<String>,
700 },
701 Custom(StoreConfig),
702}
703
704#[cfg(not(tarpaulin_include))]
705impl fmt::Debug for BuilderStoreConfig {
706 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
707 #[allow(clippy::infallible_destructuring_match)]
708 match self {
709 #[cfg(feature = "sqlite")]
710 Self::Sqlite { path, .. } => {
711 f.debug_struct("Sqlite").field("path", path).finish_non_exhaustive()
712 }
713 #[cfg(feature = "indexeddb")]
714 Self::IndexedDb { name, .. } => {
715 f.debug_struct("IndexedDb").field("name", name).finish_non_exhaustive()
716 }
717 Self::Custom(store_config) => f.debug_tuple("Custom").field(store_config).finish(),
718 }
719 }
720}
721
722#[derive(Debug, Error)]
724pub enum ClientBuildError {
725 #[error("no homeserver or user ID was configured")]
727 MissingHomeserver,
728
729 #[error("The supplied server name is invalid")]
731 InvalidServerName,
732
733 #[error("Error looking up the .well-known endpoint on auto-discovery")]
735 AutoDiscovery(FromHttpResponseError<RumaApiError>),
736
737 #[error(transparent)]
739 SlidingSyncVersion(#[from] crate::sliding_sync::VersionBuilderError),
740
741 #[error(transparent)]
743 Url(#[from] url::ParseError),
744
745 #[error(transparent)]
747 Http(#[from] HttpError),
748
749 #[cfg(feature = "indexeddb")]
751 #[error(transparent)]
752 IndexeddbStore(#[from] matrix_sdk_indexeddb::OpenStoreError),
753
754 #[cfg(feature = "sqlite")]
756 #[error(transparent)]
757 SqliteStore(#[from] matrix_sdk_sqlite::OpenStoreError),
758}
759
760#[cfg(all(test, not(target_arch = "wasm32")))]
762pub(crate) mod tests {
763 use assert_matches::assert_matches;
764 use matrix_sdk_test::{async_test, test_json};
765 use serde_json::{json_internal, Value as JsonValue};
766 use wiremock::{
767 matchers::{method, path},
768 Mock, MockServer, ResponseTemplate,
769 };
770
771 use super::*;
772 use crate::sliding_sync::Version as SlidingSyncVersion;
773
774 #[test]
775 fn test_sanitize_server_name() {
776 assert_eq!(sanitize_server_name("matrix.org").unwrap().as_str(), "matrix.org");
777 assert_eq!(sanitize_server_name("https://matrix.org").unwrap().as_str(), "matrix.org");
778 assert_eq!(sanitize_server_name("http://matrix.org").unwrap().as_str(), "matrix.org");
779 assert_eq!(
780 sanitize_server_name("https://matrix.server.org").unwrap().as_str(),
781 "matrix.server.org"
782 );
783 assert_eq!(
784 sanitize_server_name("https://matrix.server.org/").unwrap().as_str(),
785 "matrix.server.org"
786 );
787 assert_eq!(
788 sanitize_server_name(" https://matrix.server.org// ").unwrap().as_str(),
789 "matrix.server.org"
790 );
791 assert_matches!(sanitize_server_name("https://matrix.server.org/something"), Err(_))
792 }
793
794 #[async_test]
801 async fn test_discovery_invalid_server() {
802 let mut builder = ClientBuilder::new();
804
805 builder = builder.server_name_or_homeserver_url("⚠️ This won't work 🚫");
807 let error = builder.build().await.unwrap_err();
808
809 assert_matches!(error, ClientBuildError::InvalidServerName);
811 }
812
813 #[async_test]
814 async fn test_discovery_no_server() {
815 let mut builder = ClientBuilder::new();
817
818 builder = builder.server_name_or_homeserver_url("localhost:3456");
820 let error = builder.build().await.unwrap_err();
821
822 println!("{error}");
824 assert_matches!(error, ClientBuildError::Http(_));
825 }
826
827 #[async_test]
828 async fn test_discovery_web_server() {
829 let server = MockServer::start().await;
832 let mut builder = ClientBuilder::new();
833
834 builder = builder.server_name_or_homeserver_url(server.uri());
836 let error = builder.build().await.unwrap_err();
837
838 assert_matches!(error, ClientBuildError::AutoDiscovery(FromHttpResponseError::Server(_)));
840 }
841
842 #[async_test]
843 async fn test_discovery_direct_legacy() {
844 let homeserver = make_mock_homeserver().await;
846 let mut builder = ClientBuilder::new();
847
848 builder = builder.server_name_or_homeserver_url(homeserver.uri());
850 let _client = builder.build().await.unwrap();
851
852 assert!(_client.sliding_sync_version().is_native());
854 }
855
856 #[async_test]
857 async fn test_discovery_well_known_parse_error() {
858 let server = MockServer::start().await;
860 let homeserver = make_mock_homeserver().await;
861 let mut builder = ClientBuilder::new();
862
863 let well_known = make_well_known_json(&homeserver.uri());
864 let bad_json = well_known.to_string().replace(',', "");
865 Mock::given(method("GET"))
866 .and(path("/.well-known/matrix/client"))
867 .respond_with(ResponseTemplate::new(200).set_body_json(bad_json))
868 .mount(&server)
869 .await;
870
871 builder = builder.server_name_or_homeserver_url(server.uri());
873 let error = builder.build().await.unwrap_err();
874
875 assert_matches!(
877 error,
878 ClientBuildError::AutoDiscovery(FromHttpResponseError::Deserialization(_))
879 );
880 }
881
882 #[async_test]
883 async fn test_discovery_well_known_legacy() {
884 let server = MockServer::start().await;
887 let homeserver = make_mock_homeserver().await;
888 let mut builder = ClientBuilder::new();
889
890 Mock::given(method("GET"))
891 .and(path("/.well-known/matrix/client"))
892 .respond_with(
893 ResponseTemplate::new(200).set_body_json(make_well_known_json(&homeserver.uri())),
894 )
895 .mount(&server)
896 .await;
897
898 builder = builder.server_name_or_homeserver_url(server.uri());
900 let client = builder.build().await.unwrap();
901
902 assert!(client.sliding_sync_version().is_native());
905 }
906
907 #[async_test]
908 async fn test_sliding_sync_discover_native() {
909 let homeserver = make_mock_homeserver().await;
911 let mut builder = ClientBuilder::new();
912
913 builder = builder
916 .server_name_or_homeserver_url(homeserver.uri())
917 .sliding_sync_version_builder(SlidingSyncVersionBuilder::DiscoverNative);
918
919 let client = builder.build().await.unwrap();
920
921 assert_matches!(client.sliding_sync_version(), SlidingSyncVersion::Native);
923 }
924
925 #[async_test]
926 #[cfg(feature = "e2e-encryption")]
927 async fn test_set_up_decryption_trust_requirement_cross_signed() {
928 let homeserver = make_mock_homeserver().await;
929 let builder = ClientBuilder::new()
930 .server_name_or_homeserver_url(homeserver.uri())
931 .with_decryption_trust_requirement(TrustRequirement::CrossSigned);
932
933 let client = builder.build().await.unwrap();
934 assert_matches!(
935 client.base_client().decryption_trust_requirement,
936 TrustRequirement::CrossSigned
937 );
938 }
939
940 #[async_test]
941 #[cfg(feature = "e2e-encryption")]
942 async fn test_set_up_decryption_trust_requirement_untrusted() {
943 let homeserver = make_mock_homeserver().await;
944
945 let builder = ClientBuilder::new()
946 .server_name_or_homeserver_url(homeserver.uri())
947 .with_decryption_trust_requirement(TrustRequirement::Untrusted);
948
949 let client = builder.build().await.unwrap();
950 assert_matches!(
951 client.base_client().decryption_trust_requirement,
952 TrustRequirement::Untrusted
953 );
954 }
955
956 async fn make_mock_homeserver() -> MockServer {
959 let homeserver = MockServer::start().await;
960 Mock::given(method("GET"))
961 .and(path("/_matrix/client/versions"))
962 .respond_with(ResponseTemplate::new(200).set_body_json(&*test_json::VERSIONS))
963 .mount(&homeserver)
964 .await;
965 Mock::given(method("GET"))
966 .and(path("/_matrix/client/r0/login"))
967 .respond_with(ResponseTemplate::new(200).set_body_json(&*test_json::LOGIN_TYPES))
968 .mount(&homeserver)
969 .await;
970 homeserver
971 }
972
973 fn make_well_known_json(homeserver_url: &str) -> JsonValue {
974 ::serde_json::Value::Object({
975 let mut object = ::serde_json::Map::new();
976 let _ = object.insert(
977 "m.homeserver".into(),
978 json_internal!({
979 "base_url": homeserver_url
980 }),
981 );
982
983 object
984 })
985 }
986
987 #[async_test]
988 async fn test_cross_process_store_locks_holder_name() {
989 {
990 let homeserver = make_mock_homeserver().await;
991 let client =
992 ClientBuilder::new().homeserver_url(homeserver.uri()).build().await.unwrap();
993
994 assert_eq!(client.cross_process_store_locks_holder_name(), "main");
995 }
996
997 {
998 let homeserver = make_mock_homeserver().await;
999 let client = ClientBuilder::new()
1000 .homeserver_url(homeserver.uri())
1001 .cross_process_store_locks_holder_name("foo".to_owned())
1002 .build()
1003 .await
1004 .unwrap();
1005
1006 assert_eq!(client.cross_process_store_locks_holder_name(), "foo");
1007 }
1008 }
1009}