1#![allow(deprecated)]
3
4use std::{num::NonZeroUsize, sync::Arc, time::Duration};
5
6#[cfg(not(target_family = "wasm"))]
7use matrix_sdk::reqwest::Certificate;
8use matrix_sdk::{
9 encryption::{BackupDownloadStrategy, EncryptionSettings},
10 event_cache::EventCacheError,
11 ruma::{ServerName, UserId},
12 sliding_sync::{
13 Error as MatrixSlidingSyncError, VersionBuilder as MatrixSlidingSyncVersionBuilder,
14 VersionBuilderError,
15 },
16 Client as MatrixClient, ClientBuildError as MatrixClientBuildError, HttpError, IdParseError,
17 RumaApiError, ThreadingSupport,
18};
19use matrix_sdk_base::crypto::{CollectStrategy, DecryptionSettings, TrustRequirement};
20use ruma::api::error::{DeserializationError, FromHttpResponseError};
21use tracing::debug;
22
23use super::client::Client;
24#[cfg(any(feature = "sqlite", feature = "indexeddb"))]
25use crate::store;
26use crate::{
27 client::ClientSessionDelegate,
28 error::ClientError,
29 helpers::unwrap_or_clone_arc,
30 store::{StoreBuilder, StoreBuilderOutcome},
31};
32
33pub type CertificateBytes = Vec<u8>;
35
36#[derive(Debug, Clone)]
37enum HomeserverConfig {
38 Url(String),
39 ServerName(String),
40 ServerNameOrUrl(String),
41}
42
43#[derive(Debug, thiserror::Error, uniffi::Error)]
44#[uniffi(flat_error)]
45pub enum ClientBuildError {
46 #[error("The supplied server name is invalid.")]
47 InvalidServerName,
48 #[error(transparent)]
49 ServerUnreachable(HttpError),
50 #[error(transparent)]
51 WellKnownLookupFailed(RumaApiError),
52 #[error(transparent)]
53 WellKnownDeserializationError(DeserializationError),
54 #[error(transparent)]
55 #[allow(dead_code)] SlidingSync(MatrixSlidingSyncError),
57 #[error(transparent)]
58 SlidingSyncVersion(VersionBuilderError),
59 #[error(transparent)]
60 Sdk(MatrixClientBuildError),
61 #[error(transparent)]
62 EventCache(#[from] EventCacheError),
63 #[error("Failed to build the client: {message}")]
64 Generic { message: String },
65}
66
67impl From<MatrixClientBuildError> for ClientBuildError {
68 fn from(e: MatrixClientBuildError) -> Self {
69 match e {
70 MatrixClientBuildError::InvalidServerName => ClientBuildError::InvalidServerName,
71 MatrixClientBuildError::Http(e) => ClientBuildError::ServerUnreachable(e),
72 MatrixClientBuildError::AutoDiscovery(FromHttpResponseError::Server(e)) => {
73 ClientBuildError::WellKnownLookupFailed(e)
74 }
75 MatrixClientBuildError::AutoDiscovery(FromHttpResponseError::Deserialization(e)) => {
76 ClientBuildError::WellKnownDeserializationError(e)
77 }
78 MatrixClientBuildError::SlidingSyncVersion(e) => {
79 ClientBuildError::SlidingSyncVersion(e)
80 }
81 _ => ClientBuildError::Sdk(e),
82 }
83 }
84}
85
86impl From<IdParseError> for ClientBuildError {
87 fn from(e: IdParseError) -> ClientBuildError {
88 ClientBuildError::Generic { message: format!("{e:#}") }
89 }
90}
91
92impl From<std::io::Error> for ClientBuildError {
93 fn from(e: std::io::Error) -> ClientBuildError {
94 ClientBuildError::Generic { message: format!("{e:#}") }
95 }
96}
97
98impl From<url::ParseError> for ClientBuildError {
99 fn from(e: url::ParseError) -> ClientBuildError {
100 ClientBuildError::Generic { message: format!("{e:#}") }
101 }
102}
103
104impl From<ClientError> for ClientBuildError {
105 fn from(e: ClientError) -> ClientBuildError {
106 ClientBuildError::Generic { message: format!("{e:#}") }
107 }
108}
109
110#[derive(Clone, uniffi::Object)]
111pub struct ClientBuilder {
112 store: Option<StoreBuilder>,
113 system_is_memory_constrained: bool,
114 username: Option<String>,
115 homeserver_cfg: Option<HomeserverConfig>,
116 sliding_sync_version_builder: SlidingSyncVersionBuilder,
117 disable_automatic_token_refresh: bool,
118 cross_process_store_locks_holder_name: Option<String>,
119 enable_oidc_refresh_lock: bool,
120 session_delegate: Option<Arc<dyn ClientSessionDelegate>>,
121 encryption_settings: EncryptionSettings,
122 room_key_recipient_strategy: CollectStrategy,
123 decryption_settings: DecryptionSettings,
124 enable_share_history_on_invite: bool,
125 request_config: Option<RequestConfig>,
126
127 #[cfg(not(target_family = "wasm"))]
128 user_agent: Option<String>,
129 #[cfg(not(target_family = "wasm"))]
130 proxy: Option<String>,
131 #[cfg(not(target_family = "wasm"))]
132 disable_ssl_verification: bool,
133 #[cfg(not(target_family = "wasm"))]
134 disable_built_in_root_certificates: bool,
135 #[cfg(not(target_family = "wasm"))]
136 additional_root_certificates: Vec<Vec<u8>>,
137
138 threading_support: ThreadingSupport,
139}
140
141const DEFAULT_READ_TIMEOUT: Duration = Duration::from_secs(60);
145
146#[matrix_sdk_ffi_macros::export]
147impl ClientBuilder {
148 #[uniffi::constructor]
149 pub fn new() -> Arc<Self> {
150 Arc::new(Self {
151 store: None,
152 system_is_memory_constrained: false,
153 username: None,
154 homeserver_cfg: None,
155 #[cfg(not(target_family = "wasm"))]
156 user_agent: None,
157 sliding_sync_version_builder: SlidingSyncVersionBuilder::None,
158 #[cfg(not(target_family = "wasm"))]
159 proxy: None,
160 #[cfg(not(target_family = "wasm"))]
161 disable_ssl_verification: false,
162 disable_automatic_token_refresh: false,
163 cross_process_store_locks_holder_name: None,
164 enable_oidc_refresh_lock: false,
165 session_delegate: None,
166 #[cfg(not(target_family = "wasm"))]
167 additional_root_certificates: Default::default(),
168 #[cfg(not(target_family = "wasm"))]
169 disable_built_in_root_certificates: false,
170 encryption_settings: EncryptionSettings {
171 auto_enable_cross_signing: false,
172 backup_download_strategy:
173 matrix_sdk::encryption::BackupDownloadStrategy::AfterDecryptionFailure,
174 auto_enable_backups: false,
175 },
176 room_key_recipient_strategy: Default::default(),
177 decryption_settings: DecryptionSettings {
178 sender_device_trust_requirement: TrustRequirement::Untrusted,
179 },
180 enable_share_history_on_invite: false,
181 request_config: Default::default(),
182 threading_support: ThreadingSupport::Disabled,
183 })
184 }
185
186 pub fn cross_process_store_locks_holder_name(
187 self: Arc<Self>,
188 holder_name: String,
189 ) -> Arc<Self> {
190 let mut builder = unwrap_or_clone_arc(self);
191 builder.cross_process_store_locks_holder_name = Some(holder_name);
192 Arc::new(builder)
193 }
194
195 pub fn enable_oidc_refresh_lock(self: Arc<Self>) -> Arc<Self> {
196 let mut builder = unwrap_or_clone_arc(self);
197 builder.enable_oidc_refresh_lock = true;
198 Arc::new(builder)
199 }
200
201 pub fn set_session_delegate(
202 self: Arc<Self>,
203 session_delegate: Box<dyn ClientSessionDelegate>,
204 ) -> Arc<Self> {
205 let mut builder = unwrap_or_clone_arc(self);
206 builder.session_delegate = Some(session_delegate.into());
207 Arc::new(builder)
208 }
209
210 pub fn system_is_memory_constrained(self: Arc<Self>) -> Arc<Self> {
218 let mut builder = unwrap_or_clone_arc(self);
219 builder.system_is_memory_constrained = true;
220 Arc::new(builder)
221 }
222
223 pub fn username(self: Arc<Self>, username: String) -> Arc<Self> {
224 let mut builder = unwrap_or_clone_arc(self);
225 builder.username = Some(username);
226 Arc::new(builder)
227 }
228
229 pub fn server_name(self: Arc<Self>, server_name: String) -> Arc<Self> {
230 let mut builder = unwrap_or_clone_arc(self);
231 builder.homeserver_cfg = Some(HomeserverConfig::ServerName(server_name));
232 Arc::new(builder)
233 }
234
235 pub fn homeserver_url(self: Arc<Self>, url: String) -> Arc<Self> {
236 let mut builder = unwrap_or_clone_arc(self);
237 builder.homeserver_cfg = Some(HomeserverConfig::Url(url));
238 Arc::new(builder)
239 }
240
241 pub fn server_name_or_homeserver_url(self: Arc<Self>, server_name_or_url: String) -> Arc<Self> {
242 let mut builder = unwrap_or_clone_arc(self);
243 builder.homeserver_cfg = Some(HomeserverConfig::ServerNameOrUrl(server_name_or_url));
244 Arc::new(builder)
245 }
246
247 pub fn sliding_sync_version_builder(
248 self: Arc<Self>,
249 version_builder: SlidingSyncVersionBuilder,
250 ) -> Arc<Self> {
251 let mut builder = unwrap_or_clone_arc(self);
252 builder.sliding_sync_version_builder = version_builder;
253 Arc::new(builder)
254 }
255
256 pub fn disable_automatic_token_refresh(self: Arc<Self>) -> Arc<Self> {
257 let mut builder = unwrap_or_clone_arc(self);
258 builder.disable_automatic_token_refresh = true;
259 Arc::new(builder)
260 }
261
262 pub fn auto_enable_cross_signing(
263 self: Arc<Self>,
264 auto_enable_cross_signing: bool,
265 ) -> Arc<Self> {
266 let mut builder = unwrap_or_clone_arc(self);
267 builder.encryption_settings.auto_enable_cross_signing = auto_enable_cross_signing;
268 Arc::new(builder)
269 }
270
271 pub fn backup_download_strategy(
276 self: Arc<Self>,
277 backup_download_strategy: BackupDownloadStrategy,
278 ) -> Arc<Self> {
279 let mut builder = unwrap_or_clone_arc(self);
280 builder.encryption_settings.backup_download_strategy = backup_download_strategy;
281 Arc::new(builder)
282 }
283
284 pub fn auto_enable_backups(self: Arc<Self>, auto_enable_backups: bool) -> Arc<Self> {
286 let mut builder = unwrap_or_clone_arc(self);
287 builder.encryption_settings.auto_enable_backups = auto_enable_backups;
288 Arc::new(builder)
289 }
290
291 pub fn room_key_recipient_strategy(self: Arc<Self>, strategy: CollectStrategy) -> Arc<Self> {
294 let mut builder = unwrap_or_clone_arc(self);
295 builder.room_key_recipient_strategy = strategy;
296 Arc::new(builder)
297 }
298
299 pub fn decryption_settings(
301 self: Arc<Self>,
302 decryption_settings: DecryptionSettings,
303 ) -> Arc<Self> {
304 let mut builder = unwrap_or_clone_arc(self);
305 builder.decryption_settings = decryption_settings;
306 Arc::new(builder)
307 }
308
309 pub fn enable_share_history_on_invite(
314 self: Arc<Self>,
315 enable_share_history_on_invite: bool,
316 ) -> Arc<Self> {
317 let mut builder = unwrap_or_clone_arc(self);
318 builder.enable_share_history_on_invite = enable_share_history_on_invite;
319 Arc::new(builder)
320 }
321
322 pub fn request_config(self: Arc<Self>, config: RequestConfig) -> Arc<Self> {
324 let mut builder = unwrap_or_clone_arc(self);
325 builder.request_config = Some(config);
326 Arc::new(builder)
327 }
328
329 pub fn threads_enabled(
332 self: Arc<Self>,
333 enabled: bool,
334 thread_subscriptions: bool,
335 ) -> Arc<Self> {
336 let mut builder = unwrap_or_clone_arc(self);
337 let support = if enabled {
338 ThreadingSupport::Enabled { with_subscriptions: thread_subscriptions }
339 } else {
340 ThreadingSupport::Disabled
341 };
342 builder.threading_support = support;
343 Arc::new(builder)
344 }
345
346 pub fn in_memory_store(self: Arc<Self>) -> Arc<Self> {
348 let mut builder = unwrap_or_clone_arc(self);
349 builder.store = Some(StoreBuilder::InMemory);
350 Arc::new(builder)
351 }
352
353 pub async fn build(self: Arc<Self>) -> Result<Arc<Client>, ClientBuildError> {
354 let builder = unwrap_or_clone_arc(self);
355 let mut inner_builder = MatrixClient::builder();
356
357 if let Some(holder_name) = &builder.cross_process_store_locks_holder_name {
358 inner_builder =
359 inner_builder.cross_process_store_locks_holder_name(holder_name.clone());
360 }
361
362 let store_path = if let Some(store) = &builder.store {
363 match store.build()? {
364 #[cfg(feature = "sqlite")]
365 StoreBuilderOutcome::Sqlite { config, cache_path, store_path: data_path } => {
366 inner_builder = inner_builder
367 .sqlite_store_with_config_and_cache_path(config, Some(cache_path));
368
369 Some(data_path)
370 }
371 #[cfg(feature = "indexeddb")]
372 StoreBuilderOutcome::IndexedDb { name, passphrase } => {
373 inner_builder = inner_builder.indexeddb_store(&name, passphrase.as_deref());
374
375 None
376 }
377
378 StoreBuilderOutcome::InMemory => None,
379 }
380 } else {
381 debug!("Not using a session store");
382 None
383 };
384
385 inner_builder = match builder.homeserver_cfg {
387 Some(HomeserverConfig::Url(url)) => inner_builder.homeserver_url(url),
388 Some(HomeserverConfig::ServerName(server_name)) => {
389 let server_name = ServerName::parse(server_name)?;
390 inner_builder.server_name(&server_name)
391 }
392 Some(HomeserverConfig::ServerNameOrUrl(server_name_or_url)) => {
393 inner_builder.server_name_or_homeserver_url(server_name_or_url)
394 }
395 None => {
396 if let Some(username) = builder.username {
397 let user = UserId::parse(username)?;
398 inner_builder.server_name(user.server_name())
399 } else {
400 return Err(ClientBuildError::Generic {
401 message: "Failed to build: One of homeserver_url, server_name, server_name_or_homeserver_url or username must be called.".to_owned(),
402 });
403 }
404 }
405 };
406
407 #[cfg(not(target_family = "wasm"))]
408 {
409 let mut certificates = Vec::new();
410
411 for certificate in builder.additional_root_certificates {
412 match Certificate::from_der(&certificate) {
415 Ok(cert) => {
416 certificates.push(cert);
417 }
418 Err(der_error) => {
419 let cert = Certificate::from_pem(&certificate).map_err(|pem_error| {
420 ClientBuildError::Generic {
421 message: format!("Failed to add a root certificate as DER ({der_error:?}) or PEM ({pem_error:?})"),
422 }
423 })?;
424 certificates.push(cert);
425 }
426 }
427 }
428
429 inner_builder = inner_builder.add_root_certificates(certificates);
430
431 if builder.disable_built_in_root_certificates {
432 inner_builder = inner_builder.disable_built_in_root_certificates();
433 }
434
435 if let Some(proxy) = builder.proxy {
436 inner_builder = inner_builder.proxy(proxy);
437 }
438
439 if builder.disable_ssl_verification {
440 inner_builder = inner_builder.disable_ssl_verification();
441 }
442
443 if let Some(user_agent) = builder.user_agent {
444 inner_builder = inner_builder.user_agent(user_agent);
445 }
446 }
447
448 if !builder.disable_automatic_token_refresh {
449 inner_builder = inner_builder.handle_refresh_tokens();
450 }
451
452 inner_builder = inner_builder
453 .with_encryption_settings(builder.encryption_settings)
454 .with_room_key_recipient_strategy(builder.room_key_recipient_strategy)
455 .with_decryption_settings(builder.decryption_settings)
456 .with_enable_share_history_on_invite(builder.enable_share_history_on_invite);
457
458 match builder.sliding_sync_version_builder {
459 SlidingSyncVersionBuilder::None => {
460 inner_builder = inner_builder
461 .sliding_sync_version_builder(MatrixSlidingSyncVersionBuilder::None)
462 }
463 SlidingSyncVersionBuilder::Native => {
464 inner_builder = inner_builder
465 .sliding_sync_version_builder(MatrixSlidingSyncVersionBuilder::Native)
466 }
467 SlidingSyncVersionBuilder::DiscoverNative => {
468 inner_builder = inner_builder
469 .sliding_sync_version_builder(MatrixSlidingSyncVersionBuilder::DiscoverNative)
470 }
471 }
472
473 if let Some(config) = builder.request_config {
474 let mut updated_config = matrix_sdk::config::RequestConfig::default();
475 if let Some(retry_limit) = config.retry_limit {
476 updated_config =
477 updated_config.retry_limit(retry_limit.try_into().unwrap_or(usize::MAX));
478 }
479 if let Some(timeout) = config.timeout {
480 updated_config = updated_config.timeout(Duration::from_millis(timeout));
481 }
482 updated_config = updated_config.read_timeout(DEFAULT_READ_TIMEOUT);
483 if let Some(max_concurrent_requests) = config.max_concurrent_requests {
484 if max_concurrent_requests > 0 {
485 updated_config = updated_config.max_concurrent_requests(NonZeroUsize::new(
486 max_concurrent_requests as usize,
487 ));
488 }
489 }
490 if let Some(max_retry_time) = config.max_retry_time {
491 updated_config =
492 updated_config.max_retry_time(Duration::from_millis(max_retry_time));
493 }
494 inner_builder = inner_builder.request_config(updated_config);
495 }
496
497 inner_builder = inner_builder.with_threading_support(builder.threading_support);
498
499 let sdk_client = inner_builder.build().await?;
500
501 Ok(Arc::new(
502 Client::new(
503 sdk_client,
504 builder.enable_oidc_refresh_lock,
505 builder.session_delegate,
506 store_path,
507 )
508 .await?,
509 ))
510 }
511}
512
513#[cfg(feature = "sqlite")]
514#[matrix_sdk_ffi_macros::export]
515impl ClientBuilder {
516 pub fn sqlite_store(self: Arc<Self>, config: Arc<store::SqliteStoreBuilder>) -> Arc<Self> {
518 let mut builder = unwrap_or_clone_arc(self);
519 builder.store = Some(StoreBuilder::Sqlite(unwrap_or_clone_arc(config)));
520 Arc::new(builder)
521 }
522
523 #[deprecated = "Use `ClientBuilder::session_store_with_sqlite` instead"]
530 pub fn session_paths(self: Arc<Self>, data_path: String, cache_path: String) -> Arc<Self> {
531 let mut builder = unwrap_or_clone_arc(self);
532 builder.store =
533 Some(StoreBuilder::Sqlite(store::SqliteStoreBuilder::raw_new(data_path, cache_path)));
534 Arc::new(builder)
535 }
536}
537
538#[cfg(feature = "indexeddb")]
539#[matrix_sdk_ffi_macros::export]
540impl ClientBuilder {
541 pub fn indexeddb_store(
543 self: Arc<Self>,
544 config: Arc<store::IndexedDbStoreBuilder>,
545 ) -> Arc<Self> {
546 let mut builder = unwrap_or_clone_arc(self);
547 builder.store = Some(StoreBuilder::IndexedDb(unwrap_or_clone_arc(config)));
548 Arc::new(builder)
549 }
550}
551
552#[matrix_sdk_ffi_macros::export]
553impl ClientBuilder {
554 pub fn proxy(self: Arc<Self>, url: String) -> Arc<Self> {
555 let mut builder = unwrap_or_clone_arc(self);
556 #[cfg(not(target_family = "wasm"))]
557 {
558 builder.proxy = Some(url);
559 }
560 Arc::new(builder)
561 }
562
563 pub fn disable_ssl_verification(self: Arc<Self>) -> Arc<Self> {
564 let mut builder = unwrap_or_clone_arc(self);
565 #[cfg(not(target_family = "wasm"))]
566 {
567 builder.disable_ssl_verification = true;
568 }
569 Arc::new(builder)
570 }
571
572 pub fn add_root_certificates(
573 self: Arc<Self>,
574 certificates: Vec<CertificateBytes>,
575 ) -> Arc<Self> {
576 let mut builder = unwrap_or_clone_arc(self);
577
578 #[cfg(not(target_family = "wasm"))]
579 {
580 builder.additional_root_certificates = certificates;
581 }
582
583 Arc::new(builder)
584 }
585
586 pub fn disable_built_in_root_certificates(self: Arc<Self>) -> Arc<Self> {
590 let mut builder = unwrap_or_clone_arc(self);
591 #[cfg(not(target_family = "wasm"))]
592 {
593 builder.disable_built_in_root_certificates = true;
594 }
595 Arc::new(builder)
596 }
597
598 pub fn user_agent(self: Arc<Self>, user_agent: String) -> Arc<Self> {
599 let mut builder = unwrap_or_clone_arc(self);
600 #[cfg(not(target_family = "wasm"))]
601 {
602 builder.user_agent = Some(user_agent);
603 }
604 Arc::new(builder)
605 }
606}
607
608#[derive(Clone, uniffi::Record)]
610pub struct RequestConfig {
611 retry_limit: Option<u64>,
613 timeout: Option<u64>,
615 max_concurrent_requests: Option<u64>,
617 max_retry_time: Option<u64>,
619}
620
621#[derive(Clone, uniffi::Enum)]
622pub enum SlidingSyncVersionBuilder {
623 None,
624 Native,
625 DiscoverNative,
626}