matrix_sdk_ffi/
client_builder.rs

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