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_family = "wasm"))]
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 #[cfg(feature = "e2e-encryption")]
109 enable_share_history_on_invite: bool,
110 cross_process_store_locks_holder_name: String,
111}
112
113impl ClientBuilder {
114 const DEFAULT_CROSS_PROCESS_STORE_LOCKS_HOLDER_NAME: &str = "main";
115
116 pub(crate) fn new() -> Self {
117 Self {
118 homeserver_cfg: None,
119 sliding_sync_version_builder: SlidingSyncVersionBuilder::Native,
120 http_cfg: None,
121 store_config: BuilderStoreConfig::Custom(StoreConfig::new(
122 Self::DEFAULT_CROSS_PROCESS_STORE_LOCKS_HOLDER_NAME.to_owned(),
123 )),
124 request_config: Default::default(),
125 respect_login_well_known: true,
126 server_versions: None,
127 handle_refresh_tokens: false,
128 base_client: None,
129 #[cfg(feature = "e2e-encryption")]
130 encryption_settings: Default::default(),
131 #[cfg(feature = "e2e-encryption")]
132 room_key_recipient_strategy: Default::default(),
133 #[cfg(feature = "e2e-encryption")]
134 decryption_trust_requirement: TrustRequirement::Untrusted,
135 #[cfg(feature = "e2e-encryption")]
136 enable_share_history_on_invite: false,
137 cross_process_store_locks_holder_name:
138 Self::DEFAULT_CROSS_PROCESS_STORE_LOCKS_HOLDER_NAME.to_owned(),
139 }
140 }
141
142 pub fn homeserver_url(mut self, url: impl AsRef<str>) -> Self {
149 self.homeserver_cfg = Some(HomeserverConfig::HomeserverUrl(url.as_ref().to_owned()));
150 self
151 }
152
153 pub fn server_name(mut self, server_name: &ServerName) -> Self {
163 self.homeserver_cfg = Some(HomeserverConfig::ServerName {
164 server: server_name.to_owned(),
165 protocol: UrlScheme::Https,
167 });
168 self
169 }
170
171 pub fn insecure_server_name_no_tls(mut self, server_name: &ServerName) -> Self {
180 self.homeserver_cfg = Some(HomeserverConfig::ServerName {
181 server: server_name.to_owned(),
182 protocol: UrlScheme::Http,
183 });
184 self
185 }
186
187 pub fn server_name_or_homeserver_url(mut self, server_name_or_url: impl AsRef<str>) -> Self {
198 self.homeserver_cfg = Some(HomeserverConfig::ServerNameOrHomeserverUrl(
199 server_name_or_url.as_ref().to_owned(),
200 ));
201 self
202 }
203
204 pub fn sliding_sync_version_builder(
206 mut self,
207 version_builder: SlidingSyncVersionBuilder,
208 ) -> Self {
209 self.sliding_sync_version_builder = version_builder;
210 self
211 }
212
213 #[cfg(feature = "sqlite")]
215 pub fn sqlite_store(mut self, path: impl AsRef<Path>, passphrase: Option<&str>) -> Self {
216 let sqlite_store_config = SqliteStoreConfig::new(path).passphrase(passphrase);
217 self.store_config =
218 BuilderStoreConfig::Sqlite { config: sqlite_store_config, cache_path: None };
219
220 self
221 }
222
223 #[cfg(feature = "sqlite")]
226 pub fn sqlite_store_with_cache_path(
227 mut self,
228 path: impl AsRef<Path>,
229 cache_path: impl AsRef<Path>,
230 passphrase: Option<&str>,
231 ) -> Self {
232 let sqlite_store_config = SqliteStoreConfig::new(path).passphrase(passphrase);
233 self.store_config = BuilderStoreConfig::Sqlite {
234 config: sqlite_store_config,
235 cache_path: Some(cache_path.as_ref().to_owned()),
236 };
237
238 self
239 }
240
241 #[cfg(feature = "sqlite")]
244 pub fn sqlite_store_with_config_and_cache_path(
245 mut self,
246 config: SqliteStoreConfig,
247 cache_path: Option<impl AsRef<Path>>,
248 ) -> Self {
249 self.store_config = BuilderStoreConfig::Sqlite {
250 config,
251 cache_path: cache_path.map(|cache_path| cache_path.as_ref().to_owned()),
252 };
253
254 self
255 }
256
257 #[cfg(feature = "indexeddb")]
259 pub fn indexeddb_store(mut self, name: &str, passphrase: Option<&str>) -> Self {
260 self.store_config = BuilderStoreConfig::IndexedDb {
261 name: name.to_owned(),
262 passphrase: passphrase.map(ToOwned::to_owned),
263 };
264 self
265 }
266
267 pub fn store_config(mut self, store_config: StoreConfig) -> Self {
289 self.store_config = BuilderStoreConfig::Custom(store_config);
290 self
291 }
292
293 pub fn respect_login_well_known(mut self, value: bool) -> Self {
296 self.respect_login_well_known = value;
297 self
298 }
299
300 pub fn request_config(mut self, request_config: RequestConfig) -> Self {
302 self.request_config = request_config;
303 self
304 }
305
306 #[cfg(not(target_family = "wasm"))]
322 pub fn proxy(mut self, proxy: impl AsRef<str>) -> Self {
323 self.http_settings().proxy = Some(proxy.as_ref().to_owned());
324 self
325 }
326
327 #[cfg(not(target_family = "wasm"))]
329 pub fn disable_ssl_verification(mut self) -> Self {
330 self.http_settings().disable_ssl_verification = true;
331 self
332 }
333
334 #[cfg(not(target_family = "wasm"))]
336 pub fn user_agent(mut self, user_agent: impl AsRef<str>) -> Self {
337 self.http_settings().user_agent = Some(user_agent.as_ref().to_owned());
338 self
339 }
340
341 #[cfg(not(target_family = "wasm"))]
350 pub fn add_root_certificates(mut self, certificates: Vec<reqwest::Certificate>) -> Self {
351 self.http_settings().additional_root_certificates = certificates;
352 self
353 }
354
355 #[cfg(not(target_family = "wasm"))]
359 pub fn disable_built_in_root_certificates(mut self) -> Self {
360 self.http_settings().disable_built_in_root_certificates = true;
361 self
362 }
363
364 pub fn http_client(mut self, client: reqwest::Client) -> Self {
374 self.http_cfg = Some(HttpConfig::Custom(client));
375 self
376 }
377
378 pub fn server_versions(mut self, value: impl IntoIterator<Item = MatrixVersion>) -> Self {
383 self.server_versions = Some(value.into_iter().collect());
384 self
385 }
386
387 #[cfg(not(target_family = "wasm"))]
388 fn http_settings(&mut self) -> &mut HttpSettings {
389 self.http_cfg.get_or_insert_with(Default::default).settings()
390 }
391
392 pub fn handle_refresh_tokens(mut self) -> Self {
414 self.handle_refresh_tokens = true;
415 self
416 }
417
418 #[doc(hidden)]
420 pub fn base_client(mut self, base_client: BaseClient) -> Self {
421 self.base_client = Some(base_client);
422 self
423 }
424
425 #[cfg(feature = "e2e-encryption")]
428 pub fn with_encryption_settings(mut self, settings: EncryptionSettings) -> Self {
429 self.encryption_settings = settings;
430 self
431 }
432
433 #[cfg(feature = "e2e-encryption")]
436 pub fn with_room_key_recipient_strategy(mut self, strategy: CollectStrategy) -> Self {
437 self.room_key_recipient_strategy = strategy;
438 self
439 }
440
441 #[cfg(feature = "e2e-encryption")]
443 pub fn with_decryption_trust_requirement(
444 mut self,
445 trust_requirement: TrustRequirement,
446 ) -> Self {
447 self.decryption_trust_requirement = trust_requirement;
448 self
449 }
450
451 #[cfg(feature = "e2e-encryption")]
456 pub fn with_enable_share_history_on_invite(
457 mut self,
458 enable_share_history_on_invite: bool,
459 ) -> Self {
460 self.enable_share_history_on_invite = enable_share_history_on_invite;
461 self
462 }
463
464 pub fn cross_process_store_locks_holder_name(mut self, holder_name: String) -> Self {
474 self.cross_process_store_locks_holder_name = holder_name;
475 self
476 }
477
478 #[instrument(skip_all, target = "matrix_sdk::client", fields(homeserver))]
491 pub async fn build(self) -> Result<Client, ClientBuildError> {
492 debug!("Starting to build the Client");
493
494 let homeserver_cfg = self.homeserver_cfg.ok_or(ClientBuildError::MissingHomeserver)?;
495 Span::current().record("homeserver", debug(&homeserver_cfg));
496
497 #[cfg_attr(target_family = "wasm", allow(clippy::infallible_destructuring_match))]
498 let inner_http_client = match self.http_cfg.unwrap_or_default() {
499 #[cfg(not(target_family = "wasm"))]
500 HttpConfig::Settings(mut settings) => {
501 settings.timeout = self.request_config.timeout;
502 settings.make_client()?
503 }
504 HttpConfig::Custom(c) => c,
505 };
506
507 let base_client = if let Some(base_client) = self.base_client {
508 base_client
509 } else {
510 #[allow(unused_mut)]
511 let mut client = BaseClient::new(
512 build_store_config(self.store_config, &self.cross_process_store_locks_holder_name)
513 .await?,
514 );
515
516 #[cfg(feature = "e2e-encryption")]
517 {
518 client.room_key_recipient_strategy = self.room_key_recipient_strategy;
519 client.decryption_trust_requirement = self.decryption_trust_requirement;
520 }
521
522 client
523 };
524
525 let http_client = HttpClient::new(inner_http_client.clone(), self.request_config);
526
527 #[allow(unused_variables)]
528 let HomeserverDiscoveryResult { server, homeserver, supported_versions } =
529 homeserver_cfg.discover(&http_client).await?;
530
531 let sliding_sync_version = {
532 let supported_versions = match supported_versions {
533 Some(versions) => Some(versions),
534 None if self.sliding_sync_version_builder.needs_get_supported_versions() => {
535 Some(get_supported_versions(&homeserver, &http_client).await?)
536 }
537 None => None,
538 };
539
540 let version = self.sliding_sync_version_builder.build(supported_versions.as_ref())?;
541
542 tracing::info!(?version, "selected sliding sync version");
543
544 version
545 };
546
547 let allow_insecure_oauth = homeserver.scheme() == "http";
548
549 let auth_ctx = Arc::new(AuthCtx {
550 handle_refresh_tokens: self.handle_refresh_tokens,
551 refresh_token_lock: Arc::new(Mutex::new(Ok(()))),
552 session_change_sender: broadcast::Sender::new(1),
553 auth_data: OnceCell::default(),
554 tokens: OnceCell::default(),
555 reload_session_callback: OnceCell::default(),
556 save_session_callback: OnceCell::default(),
557 oauth: OAuthCtx::new(allow_insecure_oauth),
558 });
559
560 let send_queue = Arc::new(SendQueueData::new(true));
562
563 let server_capabilities = ClientServerCapabilities {
564 server_versions: self.server_versions,
565 unstable_features: None,
566 };
567
568 let event_cache = OnceCell::new();
569 let inner = ClientInner::new(
570 auth_ctx,
571 server,
572 homeserver,
573 sliding_sync_version,
574 http_client,
575 base_client,
576 server_capabilities,
577 self.respect_login_well_known,
578 event_cache,
579 send_queue,
580 #[cfg(feature = "e2e-encryption")]
581 self.encryption_settings,
582 #[cfg(feature = "e2e-encryption")]
583 self.enable_share_history_on_invite,
584 self.cross_process_store_locks_holder_name,
585 )
586 .await;
587
588 debug!("Done building the Client");
589
590 Ok(Client { inner })
591 }
592}
593
594pub fn sanitize_server_name(s: &str) -> crate::Result<OwnedServerName, IdParseError> {
598 ServerName::parse(
599 s.trim().trim_start_matches("http://").trim_start_matches("https://").trim_end_matches('/'),
600 )
601}
602
603#[allow(clippy::unused_async, unused)] async fn build_store_config(
605 builder_config: BuilderStoreConfig,
606 cross_process_store_locks_holder_name: &str,
607) -> Result<StoreConfig, ClientBuildError> {
608 #[allow(clippy::infallible_destructuring_match)]
609 let store_config = match builder_config {
610 #[cfg(feature = "sqlite")]
611 BuilderStoreConfig::Sqlite { config, cache_path } => {
612 let store_config = StoreConfig::new(cross_process_store_locks_holder_name.to_owned())
613 .state_store(
614 matrix_sdk_sqlite::SqliteStateStore::open_with_config(config.clone()).await?,
615 )
616 .event_cache_store({
617 let mut config = config.clone();
618
619 if let Some(cache_path) = cache_path {
620 config = config.path(cache_path);
621 }
622
623 matrix_sdk_sqlite::SqliteEventCacheStore::open_with_config(config).await?
624 });
625
626 #[cfg(feature = "e2e-encryption")]
627 let store_config = store_config.crypto_store(
628 matrix_sdk_sqlite::SqliteCryptoStore::open_with_config(config).await?,
629 );
630
631 store_config
632 }
633
634 #[cfg(feature = "indexeddb")]
635 BuilderStoreConfig::IndexedDb { name, passphrase } => {
636 build_indexeddb_store_config(
637 &name,
638 passphrase.as_deref(),
639 cross_process_store_locks_holder_name,
640 )
641 .await?
642 }
643
644 BuilderStoreConfig::Custom(config) => config,
645 };
646 Ok(store_config)
647}
648
649#[cfg(all(target_family = "wasm", feature = "indexeddb"))]
652async fn build_indexeddb_store_config(
653 name: &str,
654 passphrase: Option<&str>,
655 cross_process_store_locks_holder_name: &str,
656) -> Result<StoreConfig, ClientBuildError> {
657 let cross_process_store_locks_holder_name = cross_process_store_locks_holder_name.to_owned();
658
659 #[cfg(feature = "e2e-encryption")]
660 let store_config = {
661 let (state_store, crypto_store) =
662 matrix_sdk_indexeddb::open_stores_with_name(name, passphrase).await?;
663 StoreConfig::new(cross_process_store_locks_holder_name)
664 .state_store(state_store)
665 .crypto_store(crypto_store)
666 };
667
668 #[cfg(not(feature = "e2e-encryption"))]
669 let store_config = {
670 let state_store = matrix_sdk_indexeddb::open_state_store(name, passphrase).await?;
671 StoreConfig::new(cross_process_store_locks_holder_name).state_store(state_store)
672 };
673
674 let store_config = {
675 tracing::warn!("The IndexedDB backend does not implement an event cache store, falling back to the in-memory event cache store…");
676 store_config.event_cache_store(matrix_sdk_base::event_cache::store::MemoryStore::new())
677 };
678
679 Ok(store_config)
680}
681
682#[cfg(all(not(target_family = "wasm"), feature = "indexeddb"))]
683#[allow(clippy::unused_async)]
684async fn build_indexeddb_store_config(
685 _name: &str,
686 _passphrase: Option<&str>,
687 _event_cache_store_lock_holder_name: &str,
688) -> Result<StoreConfig, ClientBuildError> {
689 panic!("the IndexedDB is only available on the 'wasm32' arch")
690}
691
692#[derive(Clone, Debug)]
693enum HttpConfig {
694 #[cfg(not(target_family = "wasm"))]
695 Settings(HttpSettings),
696 Custom(reqwest::Client),
697}
698
699#[cfg(not(target_family = "wasm"))]
700impl HttpConfig {
701 fn settings(&mut self) -> &mut HttpSettings {
702 match self {
703 Self::Settings(s) => s,
704 Self::Custom(_) => {
705 *self = Self::default();
706 match self {
707 Self::Settings(s) => s,
708 Self::Custom(_) => unreachable!(),
709 }
710 }
711 }
712 }
713}
714
715impl Default for HttpConfig {
716 fn default() -> Self {
717 #[cfg(not(target_family = "wasm"))]
718 return Self::Settings(HttpSettings::default());
719
720 #[cfg(target_family = "wasm")]
721 return Self::Custom(reqwest::Client::new());
722 }
723}
724
725#[derive(Clone)]
726enum BuilderStoreConfig {
727 #[cfg(feature = "sqlite")]
728 Sqlite {
729 config: SqliteStoreConfig,
730 cache_path: Option<std::path::PathBuf>,
731 },
732 #[cfg(feature = "indexeddb")]
733 IndexedDb {
734 name: String,
735 passphrase: Option<String>,
736 },
737 Custom(StoreConfig),
738}
739
740#[cfg(not(tarpaulin_include))]
741impl fmt::Debug for BuilderStoreConfig {
742 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
743 #[allow(clippy::infallible_destructuring_match)]
744 match self {
745 #[cfg(feature = "sqlite")]
746 Self::Sqlite { config, cache_path, .. } => f
747 .debug_struct("Sqlite")
748 .field("config", config)
749 .field("cache_path", cache_path)
750 .finish_non_exhaustive(),
751
752 #[cfg(feature = "indexeddb")]
753 Self::IndexedDb { name, .. } => {
754 f.debug_struct("IndexedDb").field("name", name).finish_non_exhaustive()
755 }
756
757 Self::Custom(store_config) => f.debug_tuple("Custom").field(store_config).finish(),
758 }
759 }
760}
761
762#[derive(Debug, Error)]
764pub enum ClientBuildError {
765 #[error("no homeserver or user ID was configured")]
767 MissingHomeserver,
768
769 #[error("The supplied server name is invalid")]
771 InvalidServerName,
772
773 #[error("Error looking up the .well-known endpoint on auto-discovery")]
775 AutoDiscovery(FromHttpResponseError<RumaApiError>),
776
777 #[error(transparent)]
779 SlidingSyncVersion(#[from] crate::sliding_sync::VersionBuilderError),
780
781 #[error(transparent)]
783 Url(#[from] url::ParseError),
784
785 #[error(transparent)]
787 Http(#[from] HttpError),
788
789 #[cfg(feature = "indexeddb")]
791 #[error(transparent)]
792 IndexeddbStore(#[from] matrix_sdk_indexeddb::OpenStoreError),
793
794 #[cfg(feature = "sqlite")]
796 #[error(transparent)]
797 SqliteStore(#[from] matrix_sdk_sqlite::OpenStoreError),
798}
799
800#[cfg(all(test, not(target_family = "wasm")))]
802pub(crate) mod tests {
803 use assert_matches::assert_matches;
804 use matrix_sdk_test::{async_test, test_json};
805 use serde_json::{json_internal, Value as JsonValue};
806 use wiremock::{
807 matchers::{method, path},
808 Mock, MockServer, ResponseTemplate,
809 };
810
811 use super::*;
812 use crate::sliding_sync::Version as SlidingSyncVersion;
813
814 #[test]
815 fn test_sanitize_server_name() {
816 assert_eq!(sanitize_server_name("matrix.org").unwrap().as_str(), "matrix.org");
817 assert_eq!(sanitize_server_name("https://matrix.org").unwrap().as_str(), "matrix.org");
818 assert_eq!(sanitize_server_name("http://matrix.org").unwrap().as_str(), "matrix.org");
819 assert_eq!(
820 sanitize_server_name("https://matrix.server.org").unwrap().as_str(),
821 "matrix.server.org"
822 );
823 assert_eq!(
824 sanitize_server_name("https://matrix.server.org/").unwrap().as_str(),
825 "matrix.server.org"
826 );
827 assert_eq!(
828 sanitize_server_name(" https://matrix.server.org// ").unwrap().as_str(),
829 "matrix.server.org"
830 );
831 assert_matches!(sanitize_server_name("https://matrix.server.org/something"), Err(_))
832 }
833
834 #[async_test]
841 async fn test_discovery_invalid_server() {
842 let mut builder = ClientBuilder::new();
844
845 builder = builder.server_name_or_homeserver_url("⚠️ This won't work 🚫");
847 let error = builder.build().await.unwrap_err();
848
849 assert_matches!(error, ClientBuildError::InvalidServerName);
851 }
852
853 #[async_test]
854 async fn test_discovery_no_server() {
855 let mut builder = ClientBuilder::new();
857
858 builder = builder.server_name_or_homeserver_url("localhost:3456");
860 let error = builder.build().await.unwrap_err();
861
862 println!("{error}");
864 assert_matches!(error, ClientBuildError::Http(_));
865 }
866
867 #[async_test]
868 async fn test_discovery_web_server() {
869 let server = MockServer::start().await;
872 let mut builder = ClientBuilder::new();
873
874 builder = builder.server_name_or_homeserver_url(server.uri());
876 let error = builder.build().await.unwrap_err();
877
878 assert_matches!(error, ClientBuildError::AutoDiscovery(FromHttpResponseError::Server(_)));
880 }
881
882 #[async_test]
883 async fn test_discovery_direct_legacy() {
884 let homeserver = make_mock_homeserver().await;
886 let mut builder = ClientBuilder::new();
887
888 builder = builder.server_name_or_homeserver_url(homeserver.uri());
890 let _client = builder.build().await.unwrap();
891
892 assert!(_client.sliding_sync_version().is_native());
894 }
895
896 #[async_test]
897 async fn test_discovery_well_known_parse_error() {
898 let server = MockServer::start().await;
900 let homeserver = make_mock_homeserver().await;
901 let mut builder = ClientBuilder::new();
902
903 let well_known = make_well_known_json(&homeserver.uri());
904 let bad_json = well_known.to_string().replace(',', "");
905 Mock::given(method("GET"))
906 .and(path("/.well-known/matrix/client"))
907 .respond_with(ResponseTemplate::new(200).set_body_json(bad_json))
908 .mount(&server)
909 .await;
910
911 builder = builder.server_name_or_homeserver_url(server.uri());
913 let error = builder.build().await.unwrap_err();
914
915 assert_matches!(
917 error,
918 ClientBuildError::AutoDiscovery(FromHttpResponseError::Deserialization(_))
919 );
920 }
921
922 #[async_test]
923 async fn test_discovery_well_known_legacy() {
924 let server = MockServer::start().await;
927 let homeserver = make_mock_homeserver().await;
928 let mut builder = ClientBuilder::new();
929
930 Mock::given(method("GET"))
931 .and(path("/.well-known/matrix/client"))
932 .respond_with(
933 ResponseTemplate::new(200).set_body_json(make_well_known_json(&homeserver.uri())),
934 )
935 .mount(&server)
936 .await;
937
938 builder = builder.server_name_or_homeserver_url(server.uri());
940 let client = builder.build().await.unwrap();
941
942 assert!(client.sliding_sync_version().is_native());
945 }
946
947 #[async_test]
948 async fn test_sliding_sync_discover_native() {
949 let homeserver = make_mock_homeserver().await;
951 let mut builder = ClientBuilder::new();
952
953 builder = builder
956 .server_name_or_homeserver_url(homeserver.uri())
957 .sliding_sync_version_builder(SlidingSyncVersionBuilder::DiscoverNative);
958
959 let client = builder.build().await.unwrap();
960
961 assert_matches!(client.sliding_sync_version(), SlidingSyncVersion::Native);
963 }
964
965 #[async_test]
966 #[cfg(feature = "e2e-encryption")]
967 async fn test_set_up_decryption_trust_requirement_cross_signed() {
968 let homeserver = make_mock_homeserver().await;
969 let builder = ClientBuilder::new()
970 .server_name_or_homeserver_url(homeserver.uri())
971 .with_decryption_trust_requirement(TrustRequirement::CrossSigned);
972
973 let client = builder.build().await.unwrap();
974 assert_matches!(
975 client.base_client().decryption_trust_requirement,
976 TrustRequirement::CrossSigned
977 );
978 }
979
980 #[async_test]
981 #[cfg(feature = "e2e-encryption")]
982 async fn test_set_up_decryption_trust_requirement_untrusted() {
983 let homeserver = make_mock_homeserver().await;
984
985 let builder = ClientBuilder::new()
986 .server_name_or_homeserver_url(homeserver.uri())
987 .with_decryption_trust_requirement(TrustRequirement::Untrusted);
988
989 let client = builder.build().await.unwrap();
990 assert_matches!(
991 client.base_client().decryption_trust_requirement,
992 TrustRequirement::Untrusted
993 );
994 }
995
996 async fn make_mock_homeserver() -> MockServer {
999 let homeserver = MockServer::start().await;
1000 Mock::given(method("GET"))
1001 .and(path("/_matrix/client/versions"))
1002 .respond_with(ResponseTemplate::new(200).set_body_json(&*test_json::VERSIONS))
1003 .mount(&homeserver)
1004 .await;
1005 Mock::given(method("GET"))
1006 .and(path("/_matrix/client/r0/login"))
1007 .respond_with(ResponseTemplate::new(200).set_body_json(&*test_json::LOGIN_TYPES))
1008 .mount(&homeserver)
1009 .await;
1010 homeserver
1011 }
1012
1013 fn make_well_known_json(homeserver_url: &str) -> JsonValue {
1014 ::serde_json::Value::Object({
1015 let mut object = ::serde_json::Map::new();
1016 let _ = object.insert(
1017 "m.homeserver".into(),
1018 json_internal!({
1019 "base_url": homeserver_url
1020 }),
1021 );
1022
1023 object
1024 })
1025 }
1026
1027 #[async_test]
1028 async fn test_cross_process_store_locks_holder_name() {
1029 {
1030 let homeserver = make_mock_homeserver().await;
1031 let client =
1032 ClientBuilder::new().homeserver_url(homeserver.uri()).build().await.unwrap();
1033
1034 assert_eq!(client.cross_process_store_locks_holder_name(), "main");
1035 }
1036
1037 {
1038 let homeserver = make_mock_homeserver().await;
1039 let client = ClientBuilder::new()
1040 .homeserver_url(homeserver.uri())
1041 .cross_process_store_locks_holder_name("foo".to_owned())
1042 .build()
1043 .await
1044 .unwrap();
1045
1046 assert_eq!(client.cross_process_store_locks_holder_name(), "foo");
1047 }
1048 }
1049}