1mod homeserver_config;
17
18#[cfg(feature = "sqlite")]
19use std::path::Path;
20use std::{fmt, sync::Arc};
21
22use homeserver_config::*;
23use matrix_sdk_base::{store::StoreConfig, BaseClient};
24#[cfg(feature = "sqlite")]
25use matrix_sdk_sqlite::SqliteStoreConfig;
26use ruma::{
27 api::{error::FromHttpResponseError, MatrixVersion},
28 OwnedServerName, ServerName,
29};
30use thiserror::Error;
31use tokio::sync::{broadcast, Mutex, OnceCell};
32use tracing::{debug, field::debug, instrument, Span};
33
34use super::{Client, ClientInner};
35#[cfg(feature = "e2e-encryption")]
36use crate::crypto::{CollectStrategy, TrustRequirement};
37#[cfg(feature = "e2e-encryption")]
38use crate::encryption::EncryptionSettings;
39#[cfg(not(target_arch = "wasm32"))]
40use crate::http_client::HttpSettings;
41use crate::{
42 authentication::{oauth::OAuthCtx, AuthCtx},
43 client::ClientServerCapabilities,
44 config::RequestConfig,
45 error::RumaApiError,
46 http_client::HttpClient,
47 send_queue::SendQueueData,
48 sliding_sync::VersionBuilder as SlidingSyncVersionBuilder,
49 HttpError, IdParseError,
50};
51
52#[must_use]
91#[derive(Clone, Debug)]
92pub struct ClientBuilder {
93 homeserver_cfg: Option<HomeserverConfig>,
94 sliding_sync_version_builder: SlidingSyncVersionBuilder,
95 http_cfg: Option<HttpConfig>,
96 store_config: BuilderStoreConfig,
97 request_config: RequestConfig,
98 respect_login_well_known: bool,
99 server_versions: Option<Box<[MatrixVersion]>>,
100 handle_refresh_tokens: bool,
101 base_client: Option<BaseClient>,
102 #[cfg(feature = "e2e-encryption")]
103 encryption_settings: EncryptionSettings,
104 #[cfg(feature = "e2e-encryption")]
105 room_key_recipient_strategy: CollectStrategy,
106 #[cfg(feature = "e2e-encryption")]
107 decryption_trust_requirement: TrustRequirement,
108 cross_process_store_locks_holder_name: String,
109}
110
111impl ClientBuilder {
112 const DEFAULT_CROSS_PROCESS_STORE_LOCKS_HOLDER_NAME: &str = "main";
113
114 pub(crate) fn new() -> Self {
115 Self {
116 homeserver_cfg: None,
117 sliding_sync_version_builder: SlidingSyncVersionBuilder::Native,
118 http_cfg: None,
119 store_config: BuilderStoreConfig::Custom(StoreConfig::new(
120 Self::DEFAULT_CROSS_PROCESS_STORE_LOCKS_HOLDER_NAME.to_owned(),
121 )),
122 request_config: Default::default(),
123 respect_login_well_known: true,
124 server_versions: None,
125 handle_refresh_tokens: false,
126 base_client: None,
127 #[cfg(feature = "e2e-encryption")]
128 encryption_settings: Default::default(),
129 #[cfg(feature = "e2e-encryption")]
130 room_key_recipient_strategy: Default::default(),
131 #[cfg(feature = "e2e-encryption")]
132 decryption_trust_requirement: TrustRequirement::Untrusted,
133 cross_process_store_locks_holder_name:
134 Self::DEFAULT_CROSS_PROCESS_STORE_LOCKS_HOLDER_NAME.to_owned(),
135 }
136 }
137
138 pub fn homeserver_url(mut self, url: impl AsRef<str>) -> Self {
145 self.homeserver_cfg = Some(HomeserverConfig::HomeserverUrl(url.as_ref().to_owned()));
146 self
147 }
148
149 pub fn server_name(mut self, server_name: &ServerName) -> Self {
159 self.homeserver_cfg = Some(HomeserverConfig::ServerName {
160 server: server_name.to_owned(),
161 protocol: UrlScheme::Https,
163 });
164 self
165 }
166
167 pub fn insecure_server_name_no_tls(mut self, server_name: &ServerName) -> Self {
176 self.homeserver_cfg = Some(HomeserverConfig::ServerName {
177 server: server_name.to_owned(),
178 protocol: UrlScheme::Http,
179 });
180 self
181 }
182
183 pub fn server_name_or_homeserver_url(mut self, server_name_or_url: impl AsRef<str>) -> Self {
194 self.homeserver_cfg = Some(HomeserverConfig::ServerNameOrHomeserverUrl(
195 server_name_or_url.as_ref().to_owned(),
196 ));
197 self
198 }
199
200 pub fn sliding_sync_version_builder(
202 mut self,
203 version_builder: SlidingSyncVersionBuilder,
204 ) -> Self {
205 self.sliding_sync_version_builder = version_builder;
206 self
207 }
208
209 #[cfg(feature = "sqlite")]
211 pub fn sqlite_store(mut self, path: impl AsRef<Path>, passphrase: Option<&str>) -> Self {
212 let sqlite_store_config = SqliteStoreConfig::new(path).passphrase(passphrase);
213 self.store_config =
214 BuilderStoreConfig::Sqlite { config: sqlite_store_config, cache_path: None };
215
216 self
217 }
218
219 #[cfg(feature = "sqlite")]
222 pub fn sqlite_store_with_cache_path(
223 mut self,
224 path: impl AsRef<Path>,
225 cache_path: impl AsRef<Path>,
226 passphrase: Option<&str>,
227 ) -> Self {
228 let sqlite_store_config = SqliteStoreConfig::new(path).passphrase(passphrase);
229 self.store_config = BuilderStoreConfig::Sqlite {
230 config: sqlite_store_config,
231 cache_path: Some(cache_path.as_ref().to_owned()),
232 };
233
234 self
235 }
236
237 #[cfg(feature = "sqlite")]
240 pub fn sqlite_store_with_config_and_cache_path(
241 mut self,
242 config: SqliteStoreConfig,
243 cache_path: Option<impl AsRef<Path>>,
244 ) -> Self {
245 self.store_config = BuilderStoreConfig::Sqlite {
246 config,
247 cache_path: cache_path.map(|cache_path| cache_path.as_ref().to_owned()),
248 };
249
250 self
251 }
252
253 #[cfg(feature = "indexeddb")]
255 pub fn indexeddb_store(mut self, name: &str, passphrase: Option<&str>) -> Self {
256 self.store_config = BuilderStoreConfig::IndexedDb {
257 name: name.to_owned(),
258 passphrase: passphrase.map(ToOwned::to_owned),
259 };
260 self
261 }
262
263 pub fn store_config(mut self, store_config: StoreConfig) -> Self {
285 self.store_config = BuilderStoreConfig::Custom(store_config);
286 self
287 }
288
289 pub fn respect_login_well_known(mut self, value: bool) -> Self {
292 self.respect_login_well_known = value;
293 self
294 }
295
296 pub fn request_config(mut self, request_config: RequestConfig) -> Self {
298 self.request_config = request_config;
299 self
300 }
301
302 #[cfg(not(target_arch = "wasm32"))]
318 pub fn proxy(mut self, proxy: impl AsRef<str>) -> Self {
319 self.http_settings().proxy = Some(proxy.as_ref().to_owned());
320 self
321 }
322
323 #[cfg(not(target_arch = "wasm32"))]
325 pub fn disable_ssl_verification(mut self) -> Self {
326 self.http_settings().disable_ssl_verification = true;
327 self
328 }
329
330 #[cfg(not(target_arch = "wasm32"))]
332 pub fn user_agent(mut self, user_agent: impl AsRef<str>) -> Self {
333 self.http_settings().user_agent = Some(user_agent.as_ref().to_owned());
334 self
335 }
336
337 #[cfg(not(target_arch = "wasm32"))]
346 pub fn add_root_certificates(mut self, certificates: Vec<reqwest::Certificate>) -> Self {
347 self.http_settings().additional_root_certificates = certificates;
348 self
349 }
350
351 #[cfg(not(target_arch = "wasm32"))]
355 pub fn disable_built_in_root_certificates(mut self) -> Self {
356 self.http_settings().disable_built_in_root_certificates = true;
357 self
358 }
359
360 pub fn http_client(mut self, client: reqwest::Client) -> Self {
370 self.http_cfg = Some(HttpConfig::Custom(client));
371 self
372 }
373
374 pub fn server_versions(mut self, value: impl IntoIterator<Item = MatrixVersion>) -> Self {
379 self.server_versions = Some(value.into_iter().collect());
380 self
381 }
382
383 #[cfg(not(target_arch = "wasm32"))]
384 fn http_settings(&mut self) -> &mut HttpSettings {
385 self.http_cfg.get_or_insert_with(Default::default).settings()
386 }
387
388 pub fn handle_refresh_tokens(mut self) -> Self {
410 self.handle_refresh_tokens = true;
411 self
412 }
413
414 #[doc(hidden)]
416 pub fn base_client(mut self, base_client: BaseClient) -> Self {
417 self.base_client = Some(base_client);
418 self
419 }
420
421 #[cfg(feature = "e2e-encryption")]
424 pub fn with_encryption_settings(mut self, settings: EncryptionSettings) -> Self {
425 self.encryption_settings = settings;
426 self
427 }
428
429 #[cfg(feature = "e2e-encryption")]
432 pub fn with_room_key_recipient_strategy(mut self, strategy: CollectStrategy) -> Self {
433 self.room_key_recipient_strategy = strategy;
434 self
435 }
436
437 #[cfg(feature = "e2e-encryption")]
439 pub fn with_decryption_trust_requirement(
440 mut self,
441 trust_requirement: TrustRequirement,
442 ) -> Self {
443 self.decryption_trust_requirement = trust_requirement;
444 self
445 }
446
447 pub fn cross_process_store_locks_holder_name(mut self, holder_name: String) -> Self {
457 self.cross_process_store_locks_holder_name = holder_name;
458 self
459 }
460
461 #[instrument(skip_all, target = "matrix_sdk::client", fields(homeserver))]
474 pub async fn build(self) -> Result<Client, ClientBuildError> {
475 debug!("Starting to build the Client");
476
477 let homeserver_cfg = self.homeserver_cfg.ok_or(ClientBuildError::MissingHomeserver)?;
478 Span::current().record("homeserver", debug(&homeserver_cfg));
479
480 #[cfg_attr(target_arch = "wasm32", allow(clippy::infallible_destructuring_match))]
481 let inner_http_client = match self.http_cfg.unwrap_or_default() {
482 #[cfg(not(target_arch = "wasm32"))]
483 HttpConfig::Settings(mut settings) => {
484 settings.timeout = self.request_config.timeout;
485 settings.make_client()?
486 }
487 HttpConfig::Custom(c) => c,
488 };
489
490 let base_client = if let Some(base_client) = self.base_client {
491 base_client
492 } else {
493 #[allow(unused_mut)]
494 let mut client = BaseClient::new(
495 build_store_config(self.store_config, &self.cross_process_store_locks_holder_name)
496 .await?,
497 );
498
499 #[cfg(feature = "e2e-encryption")]
500 {
501 client.room_key_recipient_strategy = self.room_key_recipient_strategy;
502 client.decryption_trust_requirement = self.decryption_trust_requirement;
503 }
504
505 client
506 };
507
508 let http_client = HttpClient::new(inner_http_client.clone(), self.request_config);
509
510 #[allow(unused_variables)]
511 let HomeserverDiscoveryResult { server, homeserver, supported_versions } =
512 homeserver_cfg.discover(&http_client).await?;
513
514 let sliding_sync_version = {
515 let supported_versions = match supported_versions {
516 Some(versions) => Some(versions),
517 None if self.sliding_sync_version_builder.needs_get_supported_versions() => {
518 Some(get_supported_versions(&homeserver, &http_client).await?)
519 }
520 None => None,
521 };
522
523 let version = self.sliding_sync_version_builder.build(supported_versions.as_ref())?;
524
525 tracing::info!(?version, "selected sliding sync version");
526
527 version
528 };
529
530 let allow_insecure_oauth = homeserver.scheme() == "http";
531
532 let auth_ctx = Arc::new(AuthCtx {
533 handle_refresh_tokens: self.handle_refresh_tokens,
534 refresh_token_lock: Arc::new(Mutex::new(Ok(()))),
535 session_change_sender: broadcast::Sender::new(1),
536 auth_data: OnceCell::default(),
537 tokens: OnceCell::default(),
538 reload_session_callback: OnceCell::default(),
539 save_session_callback: OnceCell::default(),
540 oauth: OAuthCtx::new(allow_insecure_oauth),
541 });
542
543 let send_queue = Arc::new(SendQueueData::new(true));
545
546 let server_capabilities = ClientServerCapabilities {
547 server_versions: self.server_versions,
548 unstable_features: None,
549 };
550
551 let event_cache = OnceCell::new();
552 let inner = ClientInner::new(
553 auth_ctx,
554 server,
555 homeserver,
556 sliding_sync_version,
557 http_client,
558 base_client,
559 server_capabilities,
560 self.respect_login_well_known,
561 event_cache,
562 send_queue,
563 #[cfg(feature = "e2e-encryption")]
564 self.encryption_settings,
565 self.cross_process_store_locks_holder_name,
566 )
567 .await;
568
569 debug!("Done building the Client");
570
571 Ok(Client { inner })
572 }
573}
574
575pub fn sanitize_server_name(s: &str) -> crate::Result<OwnedServerName, IdParseError> {
579 ServerName::parse(
580 s.trim().trim_start_matches("http://").trim_start_matches("https://").trim_end_matches('/'),
581 )
582}
583
584#[allow(clippy::unused_async, unused)] async fn build_store_config(
586 builder_config: BuilderStoreConfig,
587 cross_process_store_locks_holder_name: &str,
588) -> Result<StoreConfig, ClientBuildError> {
589 #[allow(clippy::infallible_destructuring_match)]
590 let store_config = match builder_config {
591 #[cfg(feature = "sqlite")]
592 BuilderStoreConfig::Sqlite { config, cache_path } => {
593 let store_config = StoreConfig::new(cross_process_store_locks_holder_name.to_owned())
594 .state_store(
595 matrix_sdk_sqlite::SqliteStateStore::open_with_config(config.clone()).await?,
596 )
597 .event_cache_store({
598 let mut config = config.clone();
599
600 if let Some(cache_path) = cache_path {
601 config = config.path(cache_path);
602 }
603
604 matrix_sdk_sqlite::SqliteEventCacheStore::open_with_config(config).await?
605 });
606
607 #[cfg(feature = "e2e-encryption")]
608 let store_config = store_config.crypto_store(
609 matrix_sdk_sqlite::SqliteCryptoStore::open_with_config(config).await?,
610 );
611
612 store_config
613 }
614
615 #[cfg(feature = "indexeddb")]
616 BuilderStoreConfig::IndexedDb { name, passphrase } => {
617 build_indexeddb_store_config(
618 &name,
619 passphrase.as_deref(),
620 cross_process_store_locks_holder_name,
621 )
622 .await?
623 }
624
625 BuilderStoreConfig::Custom(config) => config,
626 };
627 Ok(store_config)
628}
629
630#[cfg(all(target_arch = "wasm32", feature = "indexeddb"))]
633async fn build_indexeddb_store_config(
634 name: &str,
635 passphrase: Option<&str>,
636 cross_process_store_locks_holder_name: &str,
637) -> Result<StoreConfig, ClientBuildError> {
638 let cross_process_store_locks_holder_name = cross_process_store_locks_holder_name.to_owned();
639
640 #[cfg(feature = "e2e-encryption")]
641 let store_config = {
642 let (state_store, crypto_store) =
643 matrix_sdk_indexeddb::open_stores_with_name(name, passphrase).await?;
644 StoreConfig::new(cross_process_store_locks_holder_name)
645 .state_store(state_store)
646 .crypto_store(crypto_store)
647 };
648
649 #[cfg(not(feature = "e2e-encryption"))]
650 let store_config = {
651 let state_store = matrix_sdk_indexeddb::open_state_store(name, passphrase).await?;
652 StoreConfig::new(cross_process_store_locks_holder_name).state_store(state_store)
653 };
654
655 let store_config = {
656 tracing::warn!("The IndexedDB backend does not implement an event cache store, falling back to the in-memory event cache store…");
657 store_config.event_cache_store(matrix_sdk_base::event_cache::store::MemoryStore::new())
658 };
659
660 Ok(store_config)
661}
662
663#[cfg(all(not(target_arch = "wasm32"), feature = "indexeddb"))]
664#[allow(clippy::unused_async)]
665async fn build_indexeddb_store_config(
666 _name: &str,
667 _passphrase: Option<&str>,
668 _event_cache_store_lock_holder_name: &str,
669) -> Result<StoreConfig, ClientBuildError> {
670 panic!("the IndexedDB is only available on the 'wasm32' arch")
671}
672
673#[derive(Clone, Debug)]
674enum HttpConfig {
675 #[cfg(not(target_arch = "wasm32"))]
676 Settings(HttpSettings),
677 Custom(reqwest::Client),
678}
679
680#[cfg(not(target_arch = "wasm32"))]
681impl HttpConfig {
682 fn settings(&mut self) -> &mut HttpSettings {
683 match self {
684 Self::Settings(s) => s,
685 Self::Custom(_) => {
686 *self = Self::default();
687 match self {
688 Self::Settings(s) => s,
689 Self::Custom(_) => unreachable!(),
690 }
691 }
692 }
693 }
694}
695
696impl Default for HttpConfig {
697 fn default() -> Self {
698 #[cfg(not(target_arch = "wasm32"))]
699 return Self::Settings(HttpSettings::default());
700
701 #[cfg(target_arch = "wasm32")]
702 return Self::Custom(reqwest::Client::new());
703 }
704}
705
706#[derive(Clone)]
707enum BuilderStoreConfig {
708 #[cfg(feature = "sqlite")]
709 Sqlite {
710 config: SqliteStoreConfig,
711 cache_path: Option<std::path::PathBuf>,
712 },
713 #[cfg(feature = "indexeddb")]
714 IndexedDb {
715 name: String,
716 passphrase: Option<String>,
717 },
718 Custom(StoreConfig),
719}
720
721#[cfg(not(tarpaulin_include))]
722impl fmt::Debug for BuilderStoreConfig {
723 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
724 #[allow(clippy::infallible_destructuring_match)]
725 match self {
726 #[cfg(feature = "sqlite")]
727 Self::Sqlite { config, cache_path, .. } => f
728 .debug_struct("Sqlite")
729 .field("config", config)
730 .field("cache_path", cache_path)
731 .finish_non_exhaustive(),
732
733 #[cfg(feature = "indexeddb")]
734 Self::IndexedDb { name, .. } => {
735 f.debug_struct("IndexedDb").field("name", name).finish_non_exhaustive()
736 }
737
738 Self::Custom(store_config) => f.debug_tuple("Custom").field(store_config).finish(),
739 }
740 }
741}
742
743#[derive(Debug, Error)]
745pub enum ClientBuildError {
746 #[error("no homeserver or user ID was configured")]
748 MissingHomeserver,
749
750 #[error("The supplied server name is invalid")]
752 InvalidServerName,
753
754 #[error("Error looking up the .well-known endpoint on auto-discovery")]
756 AutoDiscovery(FromHttpResponseError<RumaApiError>),
757
758 #[error(transparent)]
760 SlidingSyncVersion(#[from] crate::sliding_sync::VersionBuilderError),
761
762 #[error(transparent)]
764 Url(#[from] url::ParseError),
765
766 #[error(transparent)]
768 Http(#[from] HttpError),
769
770 #[cfg(feature = "indexeddb")]
772 #[error(transparent)]
773 IndexeddbStore(#[from] matrix_sdk_indexeddb::OpenStoreError),
774
775 #[cfg(feature = "sqlite")]
777 #[error(transparent)]
778 SqliteStore(#[from] matrix_sdk_sqlite::OpenStoreError),
779}
780
781#[cfg(all(test, not(target_arch = "wasm32")))]
783pub(crate) mod tests {
784 use assert_matches::assert_matches;
785 use matrix_sdk_test::{async_test, test_json};
786 use serde_json::{json_internal, Value as JsonValue};
787 use wiremock::{
788 matchers::{method, path},
789 Mock, MockServer, ResponseTemplate,
790 };
791
792 use super::*;
793 use crate::sliding_sync::Version as SlidingSyncVersion;
794
795 #[test]
796 fn test_sanitize_server_name() {
797 assert_eq!(sanitize_server_name("matrix.org").unwrap().as_str(), "matrix.org");
798 assert_eq!(sanitize_server_name("https://matrix.org").unwrap().as_str(), "matrix.org");
799 assert_eq!(sanitize_server_name("http://matrix.org").unwrap().as_str(), "matrix.org");
800 assert_eq!(
801 sanitize_server_name("https://matrix.server.org").unwrap().as_str(),
802 "matrix.server.org"
803 );
804 assert_eq!(
805 sanitize_server_name("https://matrix.server.org/").unwrap().as_str(),
806 "matrix.server.org"
807 );
808 assert_eq!(
809 sanitize_server_name(" https://matrix.server.org// ").unwrap().as_str(),
810 "matrix.server.org"
811 );
812 assert_matches!(sanitize_server_name("https://matrix.server.org/something"), Err(_))
813 }
814
815 #[async_test]
822 async fn test_discovery_invalid_server() {
823 let mut builder = ClientBuilder::new();
825
826 builder = builder.server_name_or_homeserver_url("⚠️ This won't work 🚫");
828 let error = builder.build().await.unwrap_err();
829
830 assert_matches!(error, ClientBuildError::InvalidServerName);
832 }
833
834 #[async_test]
835 async fn test_discovery_no_server() {
836 let mut builder = ClientBuilder::new();
838
839 builder = builder.server_name_or_homeserver_url("localhost:3456");
841 let error = builder.build().await.unwrap_err();
842
843 println!("{error}");
845 assert_matches!(error, ClientBuildError::Http(_));
846 }
847
848 #[async_test]
849 async fn test_discovery_web_server() {
850 let server = MockServer::start().await;
853 let mut builder = ClientBuilder::new();
854
855 builder = builder.server_name_or_homeserver_url(server.uri());
857 let error = builder.build().await.unwrap_err();
858
859 assert_matches!(error, ClientBuildError::AutoDiscovery(FromHttpResponseError::Server(_)));
861 }
862
863 #[async_test]
864 async fn test_discovery_direct_legacy() {
865 let homeserver = make_mock_homeserver().await;
867 let mut builder = ClientBuilder::new();
868
869 builder = builder.server_name_or_homeserver_url(homeserver.uri());
871 let _client = builder.build().await.unwrap();
872
873 assert!(_client.sliding_sync_version().is_native());
875 }
876
877 #[async_test]
878 async fn test_discovery_well_known_parse_error() {
879 let server = MockServer::start().await;
881 let homeserver = make_mock_homeserver().await;
882 let mut builder = ClientBuilder::new();
883
884 let well_known = make_well_known_json(&homeserver.uri());
885 let bad_json = well_known.to_string().replace(',', "");
886 Mock::given(method("GET"))
887 .and(path("/.well-known/matrix/client"))
888 .respond_with(ResponseTemplate::new(200).set_body_json(bad_json))
889 .mount(&server)
890 .await;
891
892 builder = builder.server_name_or_homeserver_url(server.uri());
894 let error = builder.build().await.unwrap_err();
895
896 assert_matches!(
898 error,
899 ClientBuildError::AutoDiscovery(FromHttpResponseError::Deserialization(_))
900 );
901 }
902
903 #[async_test]
904 async fn test_discovery_well_known_legacy() {
905 let server = MockServer::start().await;
908 let homeserver = make_mock_homeserver().await;
909 let mut builder = ClientBuilder::new();
910
911 Mock::given(method("GET"))
912 .and(path("/.well-known/matrix/client"))
913 .respond_with(
914 ResponseTemplate::new(200).set_body_json(make_well_known_json(&homeserver.uri())),
915 )
916 .mount(&server)
917 .await;
918
919 builder = builder.server_name_or_homeserver_url(server.uri());
921 let client = builder.build().await.unwrap();
922
923 assert!(client.sliding_sync_version().is_native());
926 }
927
928 #[async_test]
929 async fn test_sliding_sync_discover_native() {
930 let homeserver = make_mock_homeserver().await;
932 let mut builder = ClientBuilder::new();
933
934 builder = builder
937 .server_name_or_homeserver_url(homeserver.uri())
938 .sliding_sync_version_builder(SlidingSyncVersionBuilder::DiscoverNative);
939
940 let client = builder.build().await.unwrap();
941
942 assert_matches!(client.sliding_sync_version(), SlidingSyncVersion::Native);
944 }
945
946 #[async_test]
947 #[cfg(feature = "e2e-encryption")]
948 async fn test_set_up_decryption_trust_requirement_cross_signed() {
949 let homeserver = make_mock_homeserver().await;
950 let builder = ClientBuilder::new()
951 .server_name_or_homeserver_url(homeserver.uri())
952 .with_decryption_trust_requirement(TrustRequirement::CrossSigned);
953
954 let client = builder.build().await.unwrap();
955 assert_matches!(
956 client.base_client().decryption_trust_requirement,
957 TrustRequirement::CrossSigned
958 );
959 }
960
961 #[async_test]
962 #[cfg(feature = "e2e-encryption")]
963 async fn test_set_up_decryption_trust_requirement_untrusted() {
964 let homeserver = make_mock_homeserver().await;
965
966 let builder = ClientBuilder::new()
967 .server_name_or_homeserver_url(homeserver.uri())
968 .with_decryption_trust_requirement(TrustRequirement::Untrusted);
969
970 let client = builder.build().await.unwrap();
971 assert_matches!(
972 client.base_client().decryption_trust_requirement,
973 TrustRequirement::Untrusted
974 );
975 }
976
977 async fn make_mock_homeserver() -> MockServer {
980 let homeserver = MockServer::start().await;
981 Mock::given(method("GET"))
982 .and(path("/_matrix/client/versions"))
983 .respond_with(ResponseTemplate::new(200).set_body_json(&*test_json::VERSIONS))
984 .mount(&homeserver)
985 .await;
986 Mock::given(method("GET"))
987 .and(path("/_matrix/client/r0/login"))
988 .respond_with(ResponseTemplate::new(200).set_body_json(&*test_json::LOGIN_TYPES))
989 .mount(&homeserver)
990 .await;
991 homeserver
992 }
993
994 fn make_well_known_json(homeserver_url: &str) -> JsonValue {
995 ::serde_json::Value::Object({
996 let mut object = ::serde_json::Map::new();
997 let _ = object.insert(
998 "m.homeserver".into(),
999 json_internal!({
1000 "base_url": homeserver_url
1001 }),
1002 );
1003
1004 object
1005 })
1006 }
1007
1008 #[async_test]
1009 async fn test_cross_process_store_locks_holder_name() {
1010 {
1011 let homeserver = make_mock_homeserver().await;
1012 let client =
1013 ClientBuilder::new().homeserver_url(homeserver.uri()).build().await.unwrap();
1014
1015 assert_eq!(client.cross_process_store_locks_holder_name(), "main");
1016 }
1017
1018 {
1019 let homeserver = make_mock_homeserver().await;
1020 let client = ClientBuilder::new()
1021 .homeserver_url(homeserver.uri())
1022 .cross_process_store_locks_holder_name("foo".to_owned())
1023 .build()
1024 .await
1025 .unwrap();
1026
1027 assert_eq!(client.cross_process_store_locks_holder_name(), "foo");
1028 }
1029 }
1030}