1pub(crate) mod custody;
19pub(crate) mod envelope;
20pub(crate) mod identity_custody;
21pub(crate) mod keys;
22pub(crate) mod oauth;
23
24pub(crate) mod blob {
25 pub(crate) use coven_core::blob::*;
26 pub(crate) mod transition {
27 pub use coven_core::blob::transition::*;
28 }
29}
30
31pub(crate) mod clock {
32 pub(crate) use coven_core::clock::*;
33}
34
35pub(crate) mod config {
36 pub(crate) use coven_core::config::*;
37}
38
39pub(crate) mod database {
40 pub(crate) use coven_core::database::*;
41}
42
43pub(crate) mod encryption {
44 pub(crate) use coven_core::encryption::*;
45}
46
47pub(crate) mod id_provider {
48 pub(crate) use coven_core::id_provider::*;
49}
50
51pub(crate) mod join_code {
52 pub(crate) use coven_core::join_code::*;
53
54 pub fn generate_join_request(email: Option<String>) -> Result<String, crate::keys::KeyError> {
63 let keypair = crate::keys::mint_pending_identity()?;
64 Ok(coven_core::join_code::generate_join_request_for_keypair(
65 &keypair, email,
66 ))
67 }
68
69 pub fn abandon_join_request(request_code: &str) -> Result<(), crate::keys::KeyError> {
73 let request = decode_join_request(request_code)
74 .map_err(|e| crate::keys::KeyError::Crypto(e.to_string()))?;
75 crate::keys::discard_pending_identity(&request.public_key)
76 }
77}
78
79#[cfg(feature = "oauth-providers")]
86pub async fn fetch_account_email(
87 provider: crate::config::CloudProvider,
88 tokens: &oauth::OAuthTokens,
89) -> Result<String, oauth::OAuthError> {
90 use crate::config::CloudProvider;
91 use crate::storage::cloud::account_email;
92
93 let result = match provider {
94 CloudProvider::GoogleDrive => account_email::fetch_google(tokens).await,
95 CloudProvider::Dropbox => account_email::fetch_dropbox(tokens).await,
96 CloudProvider::OneDrive => account_email::fetch_onedrive(tokens).await,
97 other => {
98 return Err(oauth::OAuthError::AccountFetch(format!(
99 "{other:?} does not use OAuth; account email is only fetched for OAuth providers"
100 )))
101 }
102 };
103 result.map_err(|e| oauth::OAuthError::AccountFetch(e.to_string()))
104}
105
106pub(crate) mod store_dir {
107 pub(crate) use coven_core::store_dir::*;
108}
109
110pub(crate) mod local_blob {
111 pub(crate) use coven_core::local_blob::*;
112}
113
114pub(crate) mod migration {
115 pub(crate) use coven_core::migration::*;
116}
117
118pub(crate) mod storage {
119 pub(crate) mod cloud {
120 pub(crate) use coven_core::storage::cloud::*;
121
122 pub(crate) mod s3_common {
123 pub(crate) use coven_core::storage::cloud::s3_common::*;
124 }
125
126 #[cfg(feature = "oauth-providers")]
127 pub(crate) mod account_email;
128 pub(crate) mod cloudkit;
129 #[cfg(feature = "oauth-providers")]
130 pub(crate) mod dropbox;
131 #[cfg(feature = "oauth-providers")]
132 pub(crate) mod google_drive;
133 #[cfg(feature = "oauth-providers")]
134 mod http;
135 #[cfg(feature = "oauth-providers")]
136 mod key_encoding;
137 #[cfg(feature = "oauth-providers")]
138 mod oauth_rest;
139 #[cfg(feature = "oauth-providers")]
140 pub(crate) mod oauth_session;
141 #[cfg(feature = "oauth-providers")]
142 pub(crate) mod onedrive;
143 #[cfg(feature = "oauth-providers")]
144 mod resumable;
145 pub(crate) mod s3;
146 pub(crate) mod setup;
147 #[cfg(feature = "oauth-providers")]
148 mod sharing;
149
150 #[cfg(feature = "oauth-providers")]
151 fn require_oauth_token(
152 key_service: &crate::keys::StoreKeys,
153 provider_name: &str,
154 ) -> Result<String, CloudHomeError> {
155 match key_service.get_cloud_home_credentials().map_err(|e| {
156 CloudHomeError::Configuration(format!("{provider_name} credentials error: {e}"))
157 })? {
158 Some(crate::keys::CloudHomeCredentials::OAuth { token_json }) => Ok(token_json),
159 _ => Err(CloudHomeError::Configuration(format!(
160 "{provider_name} OAuth token not in keyring"
161 ))),
162 }
163 }
164
165 #[cfg(feature = "oauth-providers")]
166 fn parse_oauth_tokens(
167 key_service: &crate::keys::StoreKeys,
168 provider_name: &str,
169 ) -> Result<crate::oauth::OAuthTokens, CloudHomeError> {
170 let token_json = require_oauth_token(key_service, provider_name)?;
171 serde_json::from_str(&token_json).map_err(|e| {
172 CloudHomeError::Configuration(format!("invalid OAuth token JSON: {e}"))
173 })
174 }
175
176 pub async fn create_cloud_home(
181 config: &crate::config::Config,
182 key_service: &crate::keys::StoreKeys,
183 clock: crate::clock::ClockRef,
184 ) -> Result<Box<dyn CloudHome>, CloudHomeError> {
185 create_cloud_home_with_cloudkit(config, key_service, clock, None).await
186 }
187
188 pub(crate) async fn create_cloud_home_with_cloudkit(
189 config: &crate::config::Config,
190 key_service: &crate::keys::StoreKeys,
191 clock: crate::clock::ClockRef,
192 cloudkit_ops: Option<std::sync::Arc<dyn cloudkit::CloudKitOps>>,
193 ) -> Result<Box<dyn CloudHome>, CloudHomeError> {
194 use crate::config::CloudProvider;
195
196 #[cfg(not(feature = "oauth-providers"))]
197 let _ = &clock;
198
199 match config.cloud_home.provider {
200 Some(CloudProvider::S3) | None => {
201 let bucket = config.cloud_home.s3_bucket.clone().ok_or_else(|| {
202 CloudHomeError::Configuration("S3 bucket not configured".to_string())
203 })?;
204 let region = config.cloud_home.s3_region.clone().ok_or_else(|| {
205 CloudHomeError::Configuration("S3 region not configured".to_string())
206 })?;
207 let endpoint = config.cloud_home.s3_endpoint.clone();
208
209 let (access_key, secret_key) =
210 match key_service.get_cloud_home_credentials().map_err(|e| {
211 CloudHomeError::Configuration(format!("S3 credentials error: {e}"))
212 })? {
213 Some(crate::keys::CloudHomeCredentials::S3 {
214 access_key,
215 secret_key,
216 }) => (access_key, secret_key),
217 _ => {
218 return Err(CloudHomeError::Configuration(
219 "S3 credentials not in keyring".to_string(),
220 ))
221 }
222 };
223
224 let s3 = s3::S3CloudHome::new(
225 bucket,
226 region,
227 endpoint,
228 access_key,
229 secret_key,
230 config.cloud_home.s3_key_prefix.clone(),
231 config.cloud_home.s3_exact_slots,
232 )
233 .await?;
234 Ok(Box::new(s3))
235 }
236 #[cfg(feature = "oauth-providers")]
237 Some(CloudProvider::GoogleDrive) => {
238 let folder_id = config
239 .cloud_home
240 .google_drive_folder_id
241 .clone()
242 .ok_or_else(|| {
243 CloudHomeError::Configuration(
244 "Google Drive folder ID not configured".to_string(),
245 )
246 })?;
247 let tokens = parse_oauth_tokens(key_service, "Google Drive")?;
248 Ok(Box::new(google_drive::GoogleDriveCloudHome::new(
249 folder_id,
250 tokens,
251 key_service.clone(),
252 clock,
253 )?))
254 }
255 #[cfg(feature = "oauth-providers")]
256 Some(CloudProvider::Dropbox) => {
257 let folder_path =
258 config
259 .cloud_home
260 .dropbox_folder_path
261 .clone()
262 .ok_or_else(|| {
263 CloudHomeError::Configuration(
264 "Dropbox folder path not configured".to_string(),
265 )
266 })?;
267 let tokens = parse_oauth_tokens(key_service, "Dropbox")?;
268 Ok(Box::new(dropbox::DropboxCloudHome::new(
269 folder_path,
270 tokens,
271 key_service.clone(),
272 clock,
273 )?))
274 }
275 #[cfg(feature = "oauth-providers")]
276 Some(CloudProvider::OneDrive) => {
277 let drive_id =
278 config.cloud_home.onedrive_drive_id.clone().ok_or_else(|| {
279 CloudHomeError::Configuration(
280 "OneDrive drive ID not configured".to_string(),
281 )
282 })?;
283 let folder_id =
284 config
285 .cloud_home
286 .onedrive_folder_id
287 .clone()
288 .ok_or_else(|| {
289 CloudHomeError::Configuration(
290 "OneDrive folder ID not configured".to_string(),
291 )
292 })?;
293 let tokens = parse_oauth_tokens(key_service, "OneDrive")?;
294 Ok(Box::new(onedrive::OneDriveCloudHome::new(
295 drive_id,
296 folder_id,
297 tokens,
298 key_service.clone(),
299 clock,
300 )?))
301 }
302 #[cfg(not(feature = "oauth-providers"))]
303 Some(
304 CloudProvider::GoogleDrive | CloudProvider::Dropbox | CloudProvider::OneDrive,
305 ) => Err(CloudHomeError::Configuration(
306 "OAuth cloud providers are not supported in this build".to_string(),
307 )),
308 Some(CloudProvider::CloudKit) => {
309 let ops = cloudkit_ops.ok_or_else(|| {
310 CloudHomeError::Configuration("CloudKit driver not provided".to_string())
311 })?;
312 match (
313 config.cloud_home.cloudkit_owner_name.as_ref(),
314 config.cloud_home.cloudkit_zone_name.as_ref(),
315 ) {
316 (None, None) => {
317 Ok(Box::new(cloudkit::CloudKitCloudHome::new_private(ops)))
318 }
319 (Some(owner_name), Some(zone_name)) => {
320 Ok(Box::new(cloudkit::CloudKitCloudHome::new_shared(
321 ops,
322 owner_name.clone(),
323 zone_name.clone(),
324 )))
325 }
326 _ => Err(CloudHomeError::Configuration(
327 "CloudKit share config requires both cloudkit_owner_name and cloudkit_zone_name"
328 .to_string(),
329 )),
330 }
331 }
332 }
333 }
334
335 #[cfg(test)]
336 mod tests {
337 use super::*;
338 use crate::clock::FixedClock;
339 use crate::config::{CloudProvider, Config, HomeStorage};
340 use crate::keys::StoreKeys;
341 use crate::storage::cloud::cloudkit::{
342 CloudKitAcceptedShareRecord, CloudKitAtomicCreateBatch, CloudKitOps,
343 CloudKitProviderIdentity, CloudKitRecordCreate, CloudKitRecordVersion,
344 CloudKitScope, CloudKitShare,
345 };
346 use crate::store_dir::StoreDir;
347 use std::sync::Mutex;
348
349 struct ScopeRecordingOps {
354 seen: Mutex<Vec<CloudKitScope>>,
355 }
356
357 impl ScopeRecordingOps {
358 fn new() -> Self {
359 Self {
360 seen: Mutex::new(Vec::new()),
361 }
362 }
363 }
364
365 impl CloudKitOps for ScopeRecordingOps {
366 fn provider_identity(
367 &self,
368 scope: &CloudKitScope,
369 ) -> Result<CloudKitProviderIdentity, CloudHomeError> {
370 let (owner_name, zone_name) = match scope {
371 CloudKitScope::Private => ("test-owner", "test-zone"),
372 CloudKitScope::Shared {
373 owner_name,
374 zone_name,
375 } => (owner_name.as_str(), zone_name.as_str()),
376 };
377 Ok(CloudKitProviderIdentity {
378 container_id: "iCloud.test.coven".to_string(),
379 environment: crate::CloudKitEnvironment::Development,
380 owner_name: owner_name.to_string(),
381 zone_name: zone_name.to_string(),
382 current_user_record_name: "test-user".to_string(),
383 })
384 }
385
386 fn accepted_read_write_share(
387 &self,
388 _scope: &CloudKitScope,
389 ) -> Result<CloudKitAcceptedShareRecord, CloudHomeError> {
390 Err(CloudHomeError::NotFound(
391 "accepted CloudKit share".to_string(),
392 ))
393 }
394
395 fn write_record(
396 &self,
397 _scope: &CloudKitScope,
398 _key: &str,
399 _data: Vec<u8>,
400 ) -> Result<(), CloudHomeError> {
401 unimplemented!("not exercised by these tests")
402 }
403 fn read_record(
404 &self,
405 _scope: &CloudKitScope,
406 _key: &str,
407 ) -> Result<Vec<u8>, CloudHomeError> {
408 unimplemented!("not exercised by these tests")
409 }
410 fn list_records(
411 &self,
412 scope: &CloudKitScope,
413 _prefix: &str,
414 ) -> Result<Vec<String>, CloudHomeError> {
415 self.seen.lock().unwrap().push(scope.clone());
416 Ok(Vec::new())
417 }
418 fn delete_record(
419 &self,
420 _scope: &CloudKitScope,
421 _key: &str,
422 ) -> Result<(), CloudHomeError> {
423 unimplemented!("not exercised by these tests")
424 }
425 fn record_exists(
426 &self,
427 _scope: &CloudKitScope,
428 _key: &str,
429 ) -> Result<bool, CloudHomeError> {
430 unimplemented!("not exercised by these tests")
431 }
432 fn read_versioned_record(
433 &self,
434 _scope: &CloudKitScope,
435 _key: &str,
436 ) -> Result<crate::storage::cloud::CloudVersionedHead, CloudHomeError>
437 {
438 unimplemented!("not exercised by these tests")
439 }
440 fn create_record(
441 &self,
442 _scope: &CloudKitScope,
443 _key: &str,
444 _data: Vec<u8>,
445 ) -> Result<
446 crate::storage::cloud::CloudVersionedHead,
447 crate::storage::cloud::CloudHeadCreateError,
448 > {
449 unimplemented!("not exercised by these tests")
450 }
451 fn replace_record(
452 &self,
453 _scope: &CloudKitScope,
454 _key: &str,
455 _expected: &crate::storage::cloud::CloudHeadVersion,
456 _data: Vec<u8>,
457 ) -> Result<
458 crate::storage::cloud::CloudVersionedHead,
459 crate::storage::cloud::CloudHeadReplaceError,
460 > {
461 unimplemented!("not exercised by these tests")
462 }
463 fn begin_atomic_create(
464 &self,
465 _scope: &CloudKitScope,
466 ) -> Result<CloudKitAtomicCreateBatch, CloudHomeError> {
467 unimplemented!("not exercised by these tests")
468 }
469 fn stage_atomic_create_record(
470 &self,
471 _scope: &CloudKitScope,
472 _batch: &CloudKitAtomicCreateBatch,
473 _record: CloudKitRecordCreate,
474 ) -> Result<(), CloudHomeError> {
475 unimplemented!("not exercised by these tests")
476 }
477 fn commit_atomic_create(
478 &self,
479 _scope: &CloudKitScope,
480 _batch: &CloudKitAtomicCreateBatch,
481 ) -> Result<Vec<CloudKitRecordVersion>, CloudHomeError> {
482 unimplemented!("not exercised by these tests")
483 }
484 fn discard_atomic_create(
485 &self,
486 _scope: &CloudKitScope,
487 _batch: &CloudKitAtomicCreateBatch,
488 ) -> Result<(), CloudHomeError> {
489 unimplemented!("not exercised by these tests")
490 }
491 fn delete_record_versions(
492 &self,
493 _scope: &CloudKitScope,
494 _records: &[CloudKitRecordVersion],
495 ) -> Result<(), CloudHomeError> {
496 unimplemented!("not exercised by these tests")
497 }
498 fn grant_share(
499 &self,
500 _member_pubkey: &str,
501 ) -> Result<CloudKitShare, CloudHomeError> {
502 unimplemented!("not exercised by these tests")
503 }
504 fn share_for_member(
505 &self,
506 _member_pubkey: &str,
507 ) -> Result<Option<CloudKitShare>, CloudHomeError> {
508 unimplemented!("not exercised by these tests")
509 }
510 fn revoke_share(&self, _member_pubkey: &str) -> Result<(), CloudHomeError> {
511 unimplemented!("not exercised by these tests")
512 }
513 fn accept_share(&self, _share_url: &str) -> Result<CloudKitShare, CloudHomeError> {
514 unimplemented!("not exercised by these tests")
515 }
516 }
517
518 fn cloudkit_config(owner_zone: Option<(&str, &str)>) -> Config {
519 let mut config = Config::with_defaults(
520 "store-1".to_string(),
521 "device-1".to_string(),
522 StoreDir::new("unused-store-dir"),
523 "CloudKit Store".to_string(),
524 );
525 config.cloud_home.provider = Some(CloudProvider::CloudKit);
526 config.cloud_home.storage = HomeStorage::Opaque;
527 if let Some((owner, zone)) = owner_zone {
528 config.cloud_home.cloudkit_owner_name = Some(owner.to_string());
529 config.cloud_home.cloudkit_zone_name = Some(zone.to_string());
530 }
531 config
532 }
533
534 #[tokio::test]
537 async fn neither_owner_nor_zone_builds_a_private_home() {
538 let config = cloudkit_config(None);
539 let key_service = StoreKeys::new(config.store_id.clone());
540 let ops = std::sync::Arc::new(ScopeRecordingOps::new());
541 let clock: crate::clock::ClockRef =
542 std::sync::Arc::new(FixedClock(chrono::Utc::now()));
543
544 let home = create_cloud_home_with_cloudkit(
545 &config,
546 &key_service,
547 clock,
548 Some(ops.clone()),
549 )
550 .await
551 .expect("private CloudKit config builds a home");
552 home.list("").await.expect("list against the built home");
553
554 assert_eq!(
555 ops.seen.lock().unwrap().as_slice(),
556 [CloudKitScope::Private]
557 );
558 }
559
560 #[tokio::test]
563 async fn both_owner_and_zone_build_a_shared_home() {
564 let config = cloudkit_config(Some(("owner-name", "zone-name")));
565 let key_service = StoreKeys::new(config.store_id.clone());
566 let ops = std::sync::Arc::new(ScopeRecordingOps::new());
567 let clock: crate::clock::ClockRef =
568 std::sync::Arc::new(FixedClock(chrono::Utc::now()));
569
570 let home = create_cloud_home_with_cloudkit(
571 &config,
572 &key_service,
573 clock,
574 Some(ops.clone()),
575 )
576 .await
577 .expect("shared CloudKit config builds a home");
578 home.list("").await.expect("list against the built home");
579
580 assert_eq!(
581 ops.seen.lock().unwrap().as_slice(),
582 [CloudKitScope::Shared {
583 owner_name: "owner-name".to_string(),
584 zone_name: "zone-name".to_string(),
585 }]
586 );
587 }
588
589 #[tokio::test]
595 async fn mixed_owner_zone_is_a_configuration_error() {
596 let mut config = cloudkit_config(None);
597 config.cloud_home.cloudkit_owner_name = Some("owner-name".to_string());
598 let key_service = StoreKeys::new(config.store_id.clone());
599 let ops = std::sync::Arc::new(ScopeRecordingOps::new());
600 let clock: crate::clock::ClockRef =
601 std::sync::Arc::new(FixedClock(chrono::Utc::now()));
602
603 let result =
604 create_cloud_home_with_cloudkit(&config, &key_service, clock, Some(ops)).await;
605 match result {
606 Ok(_) => panic!("mixed owner/zone must not build a home"),
607 Err(CloudHomeError::Configuration(message)) => {
608 assert!(message.contains("cloudkit_owner_name"), "{message}");
609 assert!(message.contains("cloudkit_zone_name"), "{message}");
610 }
611 Err(other) => panic!("expected Configuration error, got {other:?}"),
612 }
613 }
614 }
615 }
616
617 pub(crate) mod local;
618}
619
620pub(crate) mod sync {
621 pub(crate) use coven_core::sync::*;
622
623 pub(crate) mod join;
624 #[cfg(test)]
625 mod join_tests;
626 pub(crate) mod restore;
627 #[cfg(test)]
628 mod restore_tests;
629 pub(crate) mod sync_loop;
630 pub(crate) mod sync_manager;
631}
632
633mod coven;
634mod handle;
635mod keyring_backend;
636mod read_handle;
637
638pub use coven::{
643 Coven, CovenBuilder, CovenConfig, CovenError, CovenResult, SqlContext, WriteBatch,
644};
645pub use handle::CovenHandle;
646pub use read_handle::CovenReadHandle;
647
648pub use coven_core::rusqlite;
653
654pub use coven_core::{BlobDecl, Migration, MigrationStep, RowIdentity, SyncedTable};
656
657pub use coven_core::sync::storage::CloudKitEnvironment;
659pub use coven_core::{
660 CloudHomeConfig, CloudProvider, Config, ConfigError, CustomS3ExactSlots, CustomS3Serial,
661 HomeStorage, WritePolicy,
662};
663
664pub use coven_core::{
666 BlobCacheError, BlobRef, BlobReplacement, BlobScope, BlobTransitionObserver, CacheFill,
667 Provenance, RowBlobAuthority, RowBlobRef,
668};
669
670pub use coven_core::{ChangeOp, RowChange};
672
673pub use coven_core::{
680 DbError, EncryptionError, MasterKeyring, SealError, StoreDir, StoreLayout, CHUNK_SIZE,
681};
682
683#[cfg(any(test, feature = "test-utils"))]
688pub use coven_core::EncryptionService;
689
690pub use coven_core::{Hlc, Timestamp, UpdatedAtStamper};
692
693pub use coven_core::sync::device_join::{
698 DeviceJoinAbandonment, DeviceJoinAction, DeviceJoinActivation, DeviceJoinAuthorization,
699 DeviceJoinCancellation, DeviceJoinCleanupActivation, DeviceJoinCleanupProgress,
700 DeviceJoinCleanupReceipt, DeviceJoinError, DeviceJoinJournalDatabase, DeviceJoinJournalRecord,
701 DeviceJoinOffer, DeviceJoinProducer, DeviceJoinProducerWriteRevocation, DeviceJoinReadiness,
702 DeviceJoinRole, DeviceJoinStatus, DeviceProviderAccessAdministrator,
703 DeviceProviderAccessRequest, DeviceProviderAdmission, DeviceProviderAdmissionApproval,
704 DeviceProviderAdmissionCompletion, DeviceProviderReadiness, DeviceRegistrationRequest,
705 JoinedStore, JoinerJoinClosure, JoinerJoinTerminal, ProviderAdminJoinClosure,
706 ProviderAdminJoinTerminal, ProviderReadyDeviceBootstrap, ProvisionalDeviceBootstrap,
707};
708pub use coven_core::sync::membership::MembershipCoord;
709pub use coven_core::sync::store_commit::{DeviceJoinAttemptId, DeviceJoinAttemptRef};
710pub use coven_core::{
711 Audience, CircleId, CircleInfo, CircleOperationInfo, CircleOperationState, CircleRole,
712};
713pub use coven_core::{MemberInfo, MemberRole};
714
715pub use coven_core::{Clock, ClockRef, IdProvider, IdRef, SystemClock, UuidProvider};
717#[cfg(any(test, feature = "test-utils"))]
718pub use coven_core::{FixedClock, SequentialIdProvider, SteppingClock};
719
720pub use coven_core::{
722 decode_invite_code_info, decode_join_request, decode_restore_code_info, JoinCodeError,
723};
724
725#[cfg(any(test, feature = "test-utils"))]
729pub use coven_core::CloudCipher;
730
731pub use coven_core::storage::cloud::{
734 write_cloud_object_stream, CloudFileReadError, CloudObjectStream, ExactSlotStorage, ObjectSlot,
735 PhysicalObjectLocator,
736};
737pub use coven_core::sync::provider::{
738 CloudKitAcceptedShare, CrossPrincipalProbeReceipt, ExactSlotProbeReceipt, ProviderAdminChange,
739 ProviderAdminGrantId, ProviderAdminGrantRecord, ProviderAdminMembershipChange,
740 ProviderAdminState, ProviderCapabilityProof, ProviderProbeId, SerialCoordinationProbeReceipt,
741};
742pub use coven_core::sync::storage::{
743 AwsPrincipal, GoogleDriveCorpus, ProviderDeviceBinding, ProviderPrincipalId,
744 ResolvedProviderBinding, S3EndpointBinding, StoreProviderBinding,
745};
746pub use coven_core::{
747 BlobBody, BoxPartSink, CloudAccessOutcome, CloudAccessState, CloudHeadCreateError,
748 CloudHeadReplaceError, CloudHeadVersion, CloudHome, CloudHomeError, CloudHomeJoinInfo,
749 CloudVersionedHead, PartSink, UploadProgress,
750};
751
752pub use coven_core::{
756 AffectedRow, DeviceActivity, HeldStoreCoordinate, HeldStorePosition, HeldStorePositionReason,
757 ObjectHash, PendingBranch, PendingBranchId, PendingWrite, PublishedPosition,
758 SerializationConflict, StoreBatchCommitRef, StoreCommitCoord, StoreSerialPredecessor,
759 SyncLoopAlerts, SyncLoopSuccess, WriteBlock, WriteId, WriteReceipt, WriteResolution,
760 WriteStatus,
761};
762pub use sync::sync_loop::SyncLoopStatus;
763
764#[cfg(any(test, feature = "test-utils"))]
766pub use coven_core::InMemoryCloudHome;
767
768pub use blob::transition::{MakeLocalError, MakeRemoteError};
769pub use custody::{rewrap_passphrase_custody, KeyCustody, Passphrase};
770pub use identity_custody::{rewrap_passphrase_identity_custody, IdentityCustody};
771pub use join_code::{abandon_join_request, generate_join_request};
772pub use keys::{
773 keyring_service, set_keyring_service, CloudHomeCredentials, DeviceIdentityCustody,
774 IdentityError, KeyError, MasterKeyCustody, MasterKeyError, StoreKeys, UserKeypair,
775};
776
777#[cfg(any(test, feature = "test-utils"))]
782pub use keys::entry_for_test;
783
784pub use oauth::{set_oauth_client_creds, OAuthClientCreds, OAuthClientCredsConflict, OAuthTokens};
785pub use storage::cloud::setup::generate_restore_code;
786pub use storage::cloud::{
787 cloudkit::{
788 CloudKitAcceptedShareRecord, CloudKitAtomicCreateBatch, CloudKitOps,
789 CloudKitProviderIdentity, CloudKitRecordCreate, CloudKitRecordVersion, CloudKitScope,
790 CloudKitShare, CloudKitShareAcceptance, CloudKitSharePermission,
791 },
792 create_cloud_home,
793 s3::S3CloudHome,
794};
795pub use storage::local::BlobStore;
796pub use sync::join::{BootstrapError, DeviceJoinClient};
797pub use sync::restore::{restore_from_cloud, restore_from_code, RestoreSource};
798pub use sync::restore_code::{
799 ActivatedContinuation, OwnerRecoveryAuthority, RestoreAuthority, RestoreCode,
800};
801pub use sync::sync_manager::SyncError;
802
803#[cfg(feature = "oauth-providers")]
804pub use oauth::{
805 authorize_provider, build_authorize_request_for_provider, exchange_code_for_provider,
806 OAuthClientCredsError,
807};
808
809#[cfg(any(test, feature = "oauth-providers"))]
815pub use oauth::OAuthError;
816
817#[cfg(feature = "oauth-providers")]
818pub use storage::cloud::setup::{sign_in_dropbox, sign_in_google_drive, sign_in_onedrive};