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
32pub type CertificateBytes = Vec<u8>;
34
35#[derive(Debug, Clone)]
36enum HomeserverConfig {
37 Url(String),
38 ServerName(String),
39 ServerNameOrUrl(String),
40}
41
42#[derive(Debug, uniffi::Object)]
47pub struct QrCodeData {
48 inner: qrcode::QrCodeData,
49}
50
51#[matrix_sdk_ffi_macros::export]
52impl QrCodeData {
53 #[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#[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#[derive(Debug, Default, Clone, uniffi::Enum)]
142pub enum QrLoginProgress {
143 #[default]
145 Starting,
146 EstablishingSecureChannel {
148 check_code: u8,
151 check_code_string: String,
155 },
156 WaitingForToken { user_code: String },
159 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)] 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 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 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 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 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 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 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 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 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 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 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 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 sdk_client.event_cache().enable_storage()?;
643 } else {
644 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 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 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)]
716struct SessionPaths {
718 data_path: String,
720 cache_path: String,
723}
724
725#[derive(Clone, uniffi::Record)]
726pub struct RequestConfig {
728 retry_limit: Option<u64>,
730 timeout: Option<u64>,
732 max_concurrent_requests: Option<u64>,
734 retry_timeout: Option<u64>,
736}
737
738#[derive(Clone, uniffi::Enum)]
739pub enum SlidingSyncVersionBuilder {
740 None,
741 Native,
742 DiscoverNative,
743}