matrix_sdk_ffi/
client_builder.rs

1use std::{fs, num::NonZeroUsize, path::Path, sync::Arc, time::Duration};
2
3use futures_util::StreamExt;
4use matrix_sdk::{
5    authentication::qrcode::{self, DeviceCodeErrorResponseType, LoginFailureReason},
6    crypto::{
7        types::qr_login::{LoginQrCodeDecodeError, QrCodeModeData},
8        CollectStrategy, TrustRequirement,
9    },
10    encryption::{BackupDownloadStrategy, EncryptionSettings},
11    event_cache::EventCacheError,
12    reqwest::Certificate,
13    ruma::{ServerName, UserId},
14    sliding_sync::{
15        Error as MatrixSlidingSyncError, VersionBuilder as MatrixSlidingSyncVersionBuilder,
16        VersionBuilderError,
17    },
18    Client as MatrixClient, ClientBuildError as MatrixClientBuildError, HttpError, IdParseError,
19    RumaApiError,
20};
21use ruma::api::error::{DeserializationError, FromHttpResponseError};
22use tracing::{debug, error};
23use zeroize::Zeroizing;
24
25use super::{client::Client, RUNTIME};
26use crate::{
27    authentication::OidcConfiguration, client::ClientSessionDelegate, error::ClientError,
28    helpers::unwrap_or_clone_arc, task_handle::TaskHandle,
29};
30
31/// A list of bytes containing a certificate in DER or PEM form.
32pub type CertificateBytes = Vec<u8>;
33
34#[derive(Debug, Clone)]
35enum HomeserverConfig {
36    Url(String),
37    ServerName(String),
38    ServerNameOrUrl(String),
39}
40
41/// Data for the QR code login mechanism.
42///
43/// The [`QrCodeData`] can be serialized and encoded as a QR code or it can be
44/// decoded from a QR code.
45#[derive(Debug, uniffi::Object)]
46pub struct QrCodeData {
47    inner: qrcode::QrCodeData,
48}
49
50#[matrix_sdk_ffi_macros::export]
51impl QrCodeData {
52    /// Attempt to decode a slice of bytes into a [`QrCodeData`] object.
53    ///
54    /// The slice of bytes would generally be returned by a QR code decoder.
55    #[uniffi::constructor]
56    pub fn from_bytes(bytes: Vec<u8>) -> Result<Arc<Self>, QrCodeDecodeError> {
57        Ok(Self { inner: qrcode::QrCodeData::from_bytes(&bytes)? }.into())
58    }
59}
60
61/// Error type for the decoding of the [`QrCodeData`].
62#[derive(Debug, thiserror::Error, uniffi::Error)]
63#[uniffi(flat_error)]
64pub enum QrCodeDecodeError {
65    #[error("Error decoding QR code: {error:?}")]
66    Crypto {
67        #[from]
68        error: LoginQrCodeDecodeError,
69    },
70}
71
72#[derive(Debug, thiserror::Error, uniffi::Error)]
73pub enum HumanQrLoginError {
74    #[error("Linking with this device is not supported.")]
75    LinkingNotSupported,
76    #[error("The sign in was cancelled.")]
77    Cancelled,
78    #[error("The sign in was not completed in the required time.")]
79    Expired,
80    #[error("A secure connection could not have been established between the two devices.")]
81    ConnectionInsecure,
82    #[error("The sign in was declined.")]
83    Declined,
84    #[error("An unknown error has happened.")]
85    Unknown,
86    #[error("The homeserver doesn't provide sliding sync in its configuration.")]
87    SlidingSyncNotAvailable,
88    #[error("Unable to use OIDC as the supplied client metadata is invalid.")]
89    OidcMetadataInvalid,
90    #[error("The other device is not signed in and as such can't sign in other devices.")]
91    OtherDeviceNotSignedIn,
92}
93
94impl From<qrcode::QRCodeLoginError> for HumanQrLoginError {
95    fn from(value: qrcode::QRCodeLoginError) -> Self {
96        use qrcode::{QRCodeLoginError, SecureChannelError};
97
98        match value {
99            QRCodeLoginError::LoginFailure { reason, .. } => match reason {
100                LoginFailureReason::UnsupportedProtocol => HumanQrLoginError::LinkingNotSupported,
101                LoginFailureReason::AuthorizationExpired => HumanQrLoginError::Expired,
102                LoginFailureReason::UserCancelled => HumanQrLoginError::Cancelled,
103                _ => HumanQrLoginError::Unknown,
104            },
105
106            QRCodeLoginError::Oauth(e) => {
107                if let Some(e) = e.as_request_token_error() {
108                    match e {
109                        DeviceCodeErrorResponseType::AccessDenied => HumanQrLoginError::Declined,
110                        DeviceCodeErrorResponseType::ExpiredToken => HumanQrLoginError::Expired,
111                        _ => HumanQrLoginError::Unknown,
112                    }
113                } else {
114                    HumanQrLoginError::Unknown
115                }
116            }
117
118            QRCodeLoginError::SecureChannel(e) => match e {
119                SecureChannelError::Utf8(_)
120                | SecureChannelError::MessageDecode(_)
121                | SecureChannelError::Json(_)
122                | SecureChannelError::RendezvousChannel(_) => HumanQrLoginError::Unknown,
123                SecureChannelError::SecureChannelMessage { .. }
124                | SecureChannelError::Ecies(_)
125                | SecureChannelError::InvalidCheckCode => HumanQrLoginError::ConnectionInsecure,
126                SecureChannelError::InvalidIntent => HumanQrLoginError::OtherDeviceNotSignedIn,
127            },
128
129            QRCodeLoginError::UnexpectedMessage { .. }
130            | QRCodeLoginError::CrossProcessRefreshLock(_)
131            | QRCodeLoginError::DeviceKeyUpload(_)
132            | QRCodeLoginError::SessionTokens(_)
133            | QRCodeLoginError::UserIdDiscovery(_)
134            | QRCodeLoginError::SecretImport(_) => HumanQrLoginError::Unknown,
135        }
136    }
137}
138
139/// Enum describing the progress of the QR-code login.
140#[derive(Debug, Default, Clone, uniffi::Enum)]
141pub enum QrLoginProgress {
142    /// The login process is starting.
143    #[default]
144    Starting,
145    /// We established a secure channel with the other device.
146    EstablishingSecureChannel {
147        /// The check code that the device should display so the other device
148        /// can confirm that the channel is secure as well.
149        check_code: u8,
150        /// The string representation of the check code, will be guaranteed to
151        /// be 2 characters long, preserving the leading zero if the
152        /// first digit is a zero.
153        check_code_string: String,
154    },
155    /// We are waiting for the login and for the OAuth 2.0 authorization server
156    /// to give us an access token.
157    WaitingForToken { user_code: String },
158    /// The login has successfully finished.
159    Done,
160}
161
162#[matrix_sdk_ffi_macros::export(callback_interface)]
163pub trait QrLoginProgressListener: Sync + Send {
164    fn on_update(&self, state: QrLoginProgress);
165}
166
167impl From<qrcode::LoginProgress> for QrLoginProgress {
168    fn from(value: qrcode::LoginProgress) -> Self {
169        use qrcode::LoginProgress;
170
171        match value {
172            LoginProgress::Starting => Self::Starting,
173            LoginProgress::EstablishingSecureChannel { check_code } => {
174                let check_code = check_code.to_digit();
175
176                Self::EstablishingSecureChannel {
177                    check_code,
178                    check_code_string: format!("{check_code:02}"),
179                }
180            }
181            LoginProgress::WaitingForToken { user_code } => Self::WaitingForToken { user_code },
182            LoginProgress::Done => Self::Done,
183        }
184    }
185}
186
187#[derive(Debug, thiserror::Error, uniffi::Error)]
188#[uniffi(flat_error)]
189pub enum ClientBuildError {
190    #[error("The supplied server name is invalid.")]
191    InvalidServerName,
192    #[error(transparent)]
193    ServerUnreachable(HttpError),
194    #[error(transparent)]
195    WellKnownLookupFailed(RumaApiError),
196    #[error(transparent)]
197    WellKnownDeserializationError(DeserializationError),
198    #[error(transparent)]
199    #[allow(dead_code)] // rustc's drunk, this is used
200    SlidingSync(MatrixSlidingSyncError),
201    #[error(transparent)]
202    SlidingSyncVersion(VersionBuilderError),
203    #[error(transparent)]
204    Sdk(MatrixClientBuildError),
205    #[error(transparent)]
206    EventCache(#[from] EventCacheError),
207    #[error("Failed to build the client: {message}")]
208    Generic { message: String },
209}
210
211impl From<MatrixClientBuildError> for ClientBuildError {
212    fn from(e: MatrixClientBuildError) -> Self {
213        match e {
214            MatrixClientBuildError::InvalidServerName => ClientBuildError::InvalidServerName,
215            MatrixClientBuildError::Http(e) => ClientBuildError::ServerUnreachable(e),
216            MatrixClientBuildError::AutoDiscovery(FromHttpResponseError::Server(e)) => {
217                ClientBuildError::WellKnownLookupFailed(e)
218            }
219            MatrixClientBuildError::AutoDiscovery(FromHttpResponseError::Deserialization(e)) => {
220                ClientBuildError::WellKnownDeserializationError(e)
221            }
222            MatrixClientBuildError::SlidingSyncVersion(e) => {
223                ClientBuildError::SlidingSyncVersion(e)
224            }
225            _ => ClientBuildError::Sdk(e),
226        }
227    }
228}
229
230impl From<IdParseError> for ClientBuildError {
231    fn from(e: IdParseError) -> ClientBuildError {
232        ClientBuildError::Generic { message: format!("{e:#}") }
233    }
234}
235
236impl From<std::io::Error> for ClientBuildError {
237    fn from(e: std::io::Error) -> ClientBuildError {
238        ClientBuildError::Generic { message: format!("{e:#}") }
239    }
240}
241
242impl From<url::ParseError> for ClientBuildError {
243    fn from(e: url::ParseError) -> ClientBuildError {
244        ClientBuildError::Generic { message: format!("{e:#}") }
245    }
246}
247
248impl From<ClientError> for ClientBuildError {
249    fn from(e: ClientError) -> ClientBuildError {
250        ClientBuildError::Generic { message: format!("{e:#}") }
251    }
252}
253
254#[derive(Clone, uniffi::Object)]
255pub struct ClientBuilder {
256    session_paths: Option<SessionPaths>,
257    username: Option<String>,
258    homeserver_cfg: Option<HomeserverConfig>,
259    passphrase: Zeroizing<Option<String>>,
260    user_agent: Option<String>,
261    sliding_sync_version_builder: SlidingSyncVersionBuilder,
262    proxy: Option<String>,
263    disable_ssl_verification: bool,
264    disable_automatic_token_refresh: bool,
265    cross_process_store_locks_holder_name: Option<String>,
266    enable_oidc_refresh_lock: bool,
267    session_delegate: Option<Arc<dyn ClientSessionDelegate>>,
268    additional_root_certificates: Vec<Vec<u8>>,
269    disable_built_in_root_certificates: bool,
270    encryption_settings: EncryptionSettings,
271    room_key_recipient_strategy: CollectStrategy,
272    decryption_trust_requirement: TrustRequirement,
273    request_config: Option<RequestConfig>,
274
275    /// Whether to enable use of the event cache store, for reloading events
276    /// when building timelines et al.
277    use_event_cache_persistent_storage: bool,
278}
279
280#[matrix_sdk_ffi_macros::export]
281impl ClientBuilder {
282    #[uniffi::constructor]
283    pub fn new() -> Arc<Self> {
284        Arc::new(Self {
285            session_paths: None,
286            username: None,
287            homeserver_cfg: None,
288            passphrase: Zeroizing::new(None),
289            user_agent: None,
290            sliding_sync_version_builder: SlidingSyncVersionBuilder::None,
291            proxy: None,
292            disable_ssl_verification: false,
293            disable_automatic_token_refresh: false,
294            cross_process_store_locks_holder_name: None,
295            enable_oidc_refresh_lock: false,
296            session_delegate: None,
297            additional_root_certificates: Default::default(),
298            disable_built_in_root_certificates: false,
299            encryption_settings: EncryptionSettings {
300                auto_enable_cross_signing: false,
301                backup_download_strategy:
302                    matrix_sdk::encryption::BackupDownloadStrategy::AfterDecryptionFailure,
303                auto_enable_backups: false,
304            },
305            room_key_recipient_strategy: Default::default(),
306            decryption_trust_requirement: TrustRequirement::Untrusted,
307            request_config: Default::default(),
308            use_event_cache_persistent_storage: false,
309        })
310    }
311
312    /// Whether to use the event cache persistent storage or not.
313    ///
314    /// This is a temporary feature flag, for testing the event cache's
315    /// persistent storage. Follow new developments in https://github.com/matrix-org/matrix-rust-sdk/issues/3280.
316    ///
317    /// This is disabled by default. When disabled, a one-time cleanup is
318    /// performed when creating the client, and it will clear all the events
319    /// previously stored in the event cache.
320    ///
321    /// When enabled, it will attempt to store events in the event cache as
322    /// they're received, and reuse them when reconstructing timelines.
323    pub fn use_event_cache_persistent_storage(self: Arc<Self>, value: bool) -> Arc<Self> {
324        let mut builder = unwrap_or_clone_arc(self);
325        builder.use_event_cache_persistent_storage = value;
326        Arc::new(builder)
327    }
328
329    pub fn cross_process_store_locks_holder_name(
330        self: Arc<Self>,
331        holder_name: String,
332    ) -> Arc<Self> {
333        let mut builder = unwrap_or_clone_arc(self);
334        builder.cross_process_store_locks_holder_name = Some(holder_name);
335        Arc::new(builder)
336    }
337
338    pub fn enable_oidc_refresh_lock(self: Arc<Self>) -> Arc<Self> {
339        let mut builder = unwrap_or_clone_arc(self);
340        builder.enable_oidc_refresh_lock = true;
341        Arc::new(builder)
342    }
343
344    pub fn set_session_delegate(
345        self: Arc<Self>,
346        session_delegate: Box<dyn ClientSessionDelegate>,
347    ) -> Arc<Self> {
348        let mut builder = unwrap_or_clone_arc(self);
349        builder.session_delegate = Some(session_delegate.into());
350        Arc::new(builder)
351    }
352
353    /// Sets the paths that the client will use to store its data and caches.
354    /// Both paths **must** be unique per session as the SDK stores aren't
355    /// capable of handling multiple users, however it is valid to use the
356    /// same path for both stores on a single session.
357    ///
358    /// Leaving this unset tells the client to use an in-memory data store.
359    pub fn session_paths(self: Arc<Self>, data_path: String, cache_path: String) -> Arc<Self> {
360        let mut builder = unwrap_or_clone_arc(self);
361        builder.session_paths = Some(SessionPaths { data_path, cache_path });
362        Arc::new(builder)
363    }
364
365    pub fn username(self: Arc<Self>, username: String) -> Arc<Self> {
366        let mut builder = unwrap_or_clone_arc(self);
367        builder.username = Some(username);
368        Arc::new(builder)
369    }
370
371    pub fn server_name(self: Arc<Self>, server_name: String) -> Arc<Self> {
372        let mut builder = unwrap_or_clone_arc(self);
373        builder.homeserver_cfg = Some(HomeserverConfig::ServerName(server_name));
374        Arc::new(builder)
375    }
376
377    pub fn homeserver_url(self: Arc<Self>, url: String) -> Arc<Self> {
378        let mut builder = unwrap_or_clone_arc(self);
379        builder.homeserver_cfg = Some(HomeserverConfig::Url(url));
380        Arc::new(builder)
381    }
382
383    pub fn server_name_or_homeserver_url(self: Arc<Self>, server_name_or_url: String) -> Arc<Self> {
384        let mut builder = unwrap_or_clone_arc(self);
385        builder.homeserver_cfg = Some(HomeserverConfig::ServerNameOrUrl(server_name_or_url));
386        Arc::new(builder)
387    }
388
389    pub fn passphrase(self: Arc<Self>, passphrase: Option<String>) -> Arc<Self> {
390        let mut builder = unwrap_or_clone_arc(self);
391        builder.passphrase = Zeroizing::new(passphrase);
392        Arc::new(builder)
393    }
394
395    pub fn user_agent(self: Arc<Self>, user_agent: String) -> Arc<Self> {
396        let mut builder = unwrap_or_clone_arc(self);
397        builder.user_agent = Some(user_agent);
398        Arc::new(builder)
399    }
400
401    pub fn sliding_sync_version_builder(
402        self: Arc<Self>,
403        version_builder: SlidingSyncVersionBuilder,
404    ) -> Arc<Self> {
405        let mut builder = unwrap_or_clone_arc(self);
406        builder.sliding_sync_version_builder = version_builder;
407        Arc::new(builder)
408    }
409
410    pub fn proxy(self: Arc<Self>, url: String) -> Arc<Self> {
411        let mut builder = unwrap_or_clone_arc(self);
412        builder.proxy = Some(url);
413        Arc::new(builder)
414    }
415
416    pub fn disable_ssl_verification(self: Arc<Self>) -> Arc<Self> {
417        let mut builder = unwrap_or_clone_arc(self);
418        builder.disable_ssl_verification = true;
419        Arc::new(builder)
420    }
421
422    pub fn disable_automatic_token_refresh(self: Arc<Self>) -> Arc<Self> {
423        let mut builder = unwrap_or_clone_arc(self);
424        builder.disable_automatic_token_refresh = true;
425        Arc::new(builder)
426    }
427
428    pub fn add_root_certificates(
429        self: Arc<Self>,
430        certificates: Vec<CertificateBytes>,
431    ) -> Arc<Self> {
432        let mut builder = unwrap_or_clone_arc(self);
433        builder.additional_root_certificates = certificates;
434
435        Arc::new(builder)
436    }
437
438    /// Don't trust any system root certificates, only trust the certificates
439    /// provided through
440    /// [`add_root_certificates`][ClientBuilder::add_root_certificates].
441    pub fn disable_built_in_root_certificates(self: Arc<Self>) -> Arc<Self> {
442        let mut builder = unwrap_or_clone_arc(self);
443        builder.disable_built_in_root_certificates = true;
444        Arc::new(builder)
445    }
446
447    pub fn auto_enable_cross_signing(
448        self: Arc<Self>,
449        auto_enable_cross_signing: bool,
450    ) -> Arc<Self> {
451        let mut builder = unwrap_or_clone_arc(self);
452        builder.encryption_settings.auto_enable_cross_signing = auto_enable_cross_signing;
453        Arc::new(builder)
454    }
455
456    /// Select a strategy to download room keys from the backup. By default
457    /// we download after a decryption failure.
458    ///
459    /// Take a look at the [`BackupDownloadStrategy`] enum for more options.
460    pub fn backup_download_strategy(
461        self: Arc<Self>,
462        backup_download_strategy: BackupDownloadStrategy,
463    ) -> Arc<Self> {
464        let mut builder = unwrap_or_clone_arc(self);
465        builder.encryption_settings.backup_download_strategy = backup_download_strategy;
466        Arc::new(builder)
467    }
468
469    /// Automatically create a backup version if no backup exists.
470    pub fn auto_enable_backups(self: Arc<Self>, auto_enable_backups: bool) -> Arc<Self> {
471        let mut builder = unwrap_or_clone_arc(self);
472        builder.encryption_settings.auto_enable_backups = auto_enable_backups;
473        Arc::new(builder)
474    }
475
476    /// Set the strategy to be used for picking recipient devices when sending
477    /// an encrypted message.
478    pub fn room_key_recipient_strategy(self: Arc<Self>, strategy: CollectStrategy) -> Arc<Self> {
479        let mut builder = unwrap_or_clone_arc(self);
480        builder.room_key_recipient_strategy = strategy;
481        Arc::new(builder)
482    }
483
484    /// Set the trust requirement to be used when decrypting events.
485    pub fn room_decryption_trust_requirement(
486        self: Arc<Self>,
487        trust_requirement: TrustRequirement,
488    ) -> Arc<Self> {
489        let mut builder = unwrap_or_clone_arc(self);
490        builder.decryption_trust_requirement = trust_requirement;
491        Arc::new(builder)
492    }
493
494    /// Add a default request config to this client.
495    pub fn request_config(self: Arc<Self>, config: RequestConfig) -> Arc<Self> {
496        let mut builder = unwrap_or_clone_arc(self);
497        builder.request_config = Some(config);
498        Arc::new(builder)
499    }
500
501    pub async fn build(self: Arc<Self>) -> Result<Arc<Client>, ClientBuildError> {
502        let builder = unwrap_or_clone_arc(self);
503        let mut inner_builder = MatrixClient::builder();
504
505        if let Some(holder_name) = &builder.cross_process_store_locks_holder_name {
506            inner_builder =
507                inner_builder.cross_process_store_locks_holder_name(holder_name.clone());
508        }
509
510        if let Some(session_paths) = &builder.session_paths {
511            let data_path = Path::new(&session_paths.data_path);
512            let cache_path = Path::new(&session_paths.cache_path);
513
514            debug!(
515                data_path = %data_path.to_string_lossy(),
516                cache_path = %cache_path.to_string_lossy(),
517                "Creating directories for data and cache stores.",
518            );
519
520            fs::create_dir_all(data_path)?;
521            fs::create_dir_all(cache_path)?;
522
523            inner_builder = inner_builder.sqlite_store_with_cache_path(
524                data_path,
525                cache_path,
526                builder.passphrase.as_deref(),
527            );
528        } else {
529            debug!("Not using a store path.");
530        }
531
532        // Determine server either from URL, server name or user ID.
533        inner_builder = match builder.homeserver_cfg {
534            Some(HomeserverConfig::Url(url)) => inner_builder.homeserver_url(url),
535            Some(HomeserverConfig::ServerName(server_name)) => {
536                let server_name = ServerName::parse(server_name)?;
537                inner_builder.server_name(&server_name)
538            }
539            Some(HomeserverConfig::ServerNameOrUrl(server_name_or_url)) => {
540                inner_builder.server_name_or_homeserver_url(server_name_or_url)
541            }
542            None => {
543                if let Some(username) = builder.username {
544                    let user = UserId::parse(username)?;
545                    inner_builder.server_name(user.server_name())
546                } else {
547                    return Err(ClientBuildError::Generic {
548                        message: "Failed to build: One of homeserver_url, server_name, server_name_or_homeserver_url or username must be called.".to_owned(),
549                    });
550                }
551            }
552        };
553
554        let mut certificates = Vec::new();
555
556        for certificate in builder.additional_root_certificates {
557            // We don't really know what type of certificate we may get here, so let's try
558            // first one type, then the other.
559            match Certificate::from_der(&certificate) {
560                Ok(cert) => {
561                    certificates.push(cert);
562                }
563                Err(der_error) => {
564                    let cert = Certificate::from_pem(&certificate).map_err(|pem_error| {
565                        ClientBuildError::Generic {
566                            message: format!("Failed to add a root certificate as DER ({der_error:?}) or PEM ({pem_error:?})"),
567                        }
568                    })?;
569                    certificates.push(cert);
570                }
571            }
572        }
573
574        inner_builder = inner_builder.add_root_certificates(certificates);
575
576        if builder.disable_built_in_root_certificates {
577            inner_builder = inner_builder.disable_built_in_root_certificates();
578        }
579
580        if let Some(proxy) = builder.proxy {
581            inner_builder = inner_builder.proxy(proxy);
582        }
583
584        if builder.disable_ssl_verification {
585            inner_builder = inner_builder.disable_ssl_verification();
586        }
587
588        if !builder.disable_automatic_token_refresh {
589            inner_builder = inner_builder.handle_refresh_tokens();
590        }
591
592        if let Some(user_agent) = builder.user_agent {
593            inner_builder = inner_builder.user_agent(user_agent);
594        }
595
596        inner_builder = inner_builder
597            .with_encryption_settings(builder.encryption_settings)
598            .with_room_key_recipient_strategy(builder.room_key_recipient_strategy)
599            .with_decryption_trust_requirement(builder.decryption_trust_requirement);
600
601        match builder.sliding_sync_version_builder {
602            SlidingSyncVersionBuilder::None => {
603                inner_builder = inner_builder
604                    .sliding_sync_version_builder(MatrixSlidingSyncVersionBuilder::None)
605            }
606            SlidingSyncVersionBuilder::Native => {
607                inner_builder = inner_builder
608                    .sliding_sync_version_builder(MatrixSlidingSyncVersionBuilder::Native)
609            }
610            SlidingSyncVersionBuilder::DiscoverNative => {
611                inner_builder = inner_builder
612                    .sliding_sync_version_builder(MatrixSlidingSyncVersionBuilder::DiscoverNative)
613            }
614        }
615
616        if let Some(config) = builder.request_config {
617            let mut updated_config = matrix_sdk::config::RequestConfig::default();
618            if let Some(retry_limit) = config.retry_limit {
619                updated_config = updated_config.retry_limit(retry_limit);
620            }
621            if let Some(timeout) = config.timeout {
622                updated_config = updated_config.timeout(Duration::from_millis(timeout));
623            }
624            if let Some(max_concurrent_requests) = config.max_concurrent_requests {
625                if max_concurrent_requests > 0 {
626                    updated_config = updated_config.max_concurrent_requests(NonZeroUsize::new(
627                        max_concurrent_requests as usize,
628                    ));
629                }
630            }
631            if let Some(retry_timeout) = config.retry_timeout {
632                updated_config = updated_config.retry_timeout(Duration::from_millis(retry_timeout));
633            }
634            inner_builder = inner_builder.request_config(updated_config);
635        }
636
637        let sdk_client = inner_builder.build().await?;
638
639        if builder.use_event_cache_persistent_storage {
640            // Enable the persistent storage \o/
641            sdk_client.event_cache().enable_storage()?;
642        } else {
643            // Get rid of all the previous events, if any.
644            let store = sdk_client
645                .event_cache_store()
646                .lock()
647                .await
648                .map_err(EventCacheError::LockingStorage)?;
649            store.clear_all_rooms_chunks().await.map_err(EventCacheError::Storage)?;
650        }
651
652        Ok(Arc::new(
653            Client::new(sdk_client, builder.enable_oidc_refresh_lock, builder.session_delegate)
654                .await?,
655        ))
656    }
657
658    /// Finish the building of the client and attempt to log in using the
659    /// provided [`QrCodeData`].
660    ///
661    /// This method will build the client and immediately attempt to log the
662    /// client in using the provided [`QrCodeData`] using the login
663    /// mechanism described in [MSC4108]. As such this methods requires OAuth
664    /// 2.0 support as well as sliding sync support.
665    ///
666    /// The usage of the progress_listener is required to transfer the
667    /// [`CheckCode`] to the existing client.
668    ///
669    /// [MSC4108]: https://github.com/matrix-org/matrix-spec-proposals/pull/4108
670    pub async fn build_with_qr_code(
671        self: Arc<Self>,
672        qr_code_data: &QrCodeData,
673        oidc_configuration: &OidcConfiguration,
674        progress_listener: Box<dyn QrLoginProgressListener>,
675    ) -> Result<Arc<Client>, HumanQrLoginError> {
676        let QrCodeModeData::Reciprocate { server_name } = &qr_code_data.inner.mode_data else {
677            return Err(HumanQrLoginError::OtherDeviceNotSignedIn);
678        };
679
680        let builder = self.server_name_or_homeserver_url(server_name.to_owned());
681
682        let client = builder.build().await.map_err(|e| match e {
683            ClientBuildError::SlidingSync(_) => HumanQrLoginError::SlidingSyncNotAvailable,
684            _ => {
685                error!("Couldn't build the client {e:?}");
686                HumanQrLoginError::Unknown
687            }
688        })?;
689
690        let client_metadata =
691            oidc_configuration.try_into().map_err(|_| HumanQrLoginError::OidcMetadataInvalid)?;
692
693        let oidc = client.inner.oidc();
694        let login = oidc.login_with_qr_code(&qr_code_data.inner, client_metadata);
695
696        let mut progress = login.subscribe_to_progress();
697
698        // We create this task, which will get cancelled once it's dropped, just in case
699        // the progress stream doesn't end.
700        let _progress_task = TaskHandle::new(RUNTIME.spawn(async move {
701            while let Some(state) = progress.next().await {
702                progress_listener.on_update(state.into());
703            }
704        }));
705
706        login.await?;
707
708        Ok(client)
709    }
710}
711
712#[derive(Clone)]
713/// The store paths the client will use when built.
714struct SessionPaths {
715    /// The path that the client will use to store its data.
716    data_path: String,
717    /// The path that the client will use to store its caches. This path can be
718    /// the same as the data path if you prefer to keep everything in one place.
719    cache_path: String,
720}
721
722#[derive(Clone, uniffi::Record)]
723/// The config to use for HTTP requests by default in this client.
724pub struct RequestConfig {
725    /// Max number of retries.
726    retry_limit: Option<u64>,
727    /// Timeout for a request in milliseconds.
728    timeout: Option<u64>,
729    /// Max number of concurrent requests. No value means no limits.
730    max_concurrent_requests: Option<u64>,
731    /// Base delay between retries.
732    retry_timeout: Option<u64>,
733}
734
735#[derive(Clone, uniffi::Enum)]
736pub enum SlidingSyncVersionBuilder {
737    None,
738    Native,
739    DiscoverNative,
740}