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
31pub type CertificateBytes = Vec<u8>;
33
34#[derive(Debug, Clone)]
35enum HomeserverConfig {
36 Url(String),
37 ServerName(String),
38 ServerNameOrUrl(String),
39}
40
41#[derive(Debug, uniffi::Object)]
46pub struct QrCodeData {
47 inner: qrcode::QrCodeData,
48}
49
50#[matrix_sdk_ffi_macros::export]
51impl QrCodeData {
52 #[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#[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#[derive(Debug, Default, Clone, uniffi::Enum)]
141pub enum QrLoginProgress {
142 #[default]
144 Starting,
145 EstablishingSecureChannel {
147 check_code: u8,
150 check_code_string: String,
154 },
155 WaitingForToken { user_code: String },
158 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)] 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 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 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 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 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 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 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 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 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 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 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 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 sdk_client.event_cache().enable_storage()?;
642 } else {
643 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 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 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)]
713struct SessionPaths {
715 data_path: String,
717 cache_path: String,
720}
721
722#[derive(Clone, uniffi::Record)]
723pub struct RequestConfig {
725 retry_limit: Option<u64>,
727 timeout: Option<u64>,
729 max_concurrent_requests: Option<u64>,
731 retry_timeout: Option<u64>,
733}
734
735#[derive(Clone, uniffi::Enum)]
736pub enum SlidingSyncVersionBuilder {
737 None,
738 Native,
739 DiscoverNative,
740}