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::CloudVersionedObject, CloudHomeError>
437 {
438 unimplemented!("not exercised by these tests")
439 }
440
441 fn begin_atomic_create(
442 &self,
443 _scope: &CloudKitScope,
444 ) -> Result<CloudKitAtomicCreateBatch, CloudHomeError> {
445 unimplemented!("not exercised by these tests")
446 }
447 fn stage_atomic_create_record(
448 &self,
449 _scope: &CloudKitScope,
450 _batch: &CloudKitAtomicCreateBatch,
451 _record: CloudKitRecordCreate,
452 ) -> Result<(), CloudHomeError> {
453 unimplemented!("not exercised by these tests")
454 }
455 fn commit_atomic_create(
456 &self,
457 _scope: &CloudKitScope,
458 _batch: &CloudKitAtomicCreateBatch,
459 ) -> Result<Vec<CloudKitRecordVersion>, CloudHomeError> {
460 unimplemented!("not exercised by these tests")
461 }
462 fn discard_atomic_create(
463 &self,
464 _scope: &CloudKitScope,
465 _batch: &CloudKitAtomicCreateBatch,
466 ) -> Result<(), CloudHomeError> {
467 unimplemented!("not exercised by these tests")
468 }
469 fn delete_record_versions(
470 &self,
471 _scope: &CloudKitScope,
472 _records: &[CloudKitRecordVersion],
473 ) -> Result<(), CloudHomeError> {
474 unimplemented!("not exercised by these tests")
475 }
476 fn grant_share(
477 &self,
478 _member_pubkey: &str,
479 ) -> Result<CloudKitShare, CloudHomeError> {
480 unimplemented!("not exercised by these tests")
481 }
482 fn share_for_member(
483 &self,
484 _member_pubkey: &str,
485 ) -> Result<Option<CloudKitShare>, CloudHomeError> {
486 unimplemented!("not exercised by these tests")
487 }
488 fn revoke_share(&self, _member_pubkey: &str) -> Result<(), CloudHomeError> {
489 unimplemented!("not exercised by these tests")
490 }
491 fn accept_share(&self, _share_url: &str) -> Result<CloudKitShare, CloudHomeError> {
492 unimplemented!("not exercised by these tests")
493 }
494 }
495
496 fn cloudkit_config(owner_zone: Option<(&str, &str)>) -> Config {
497 let mut config = Config::with_defaults(
498 "store-1".to_string(),
499 "device-1".to_string(),
500 StoreDir::new("unused-store-dir"),
501 "CloudKit Store".to_string(),
502 );
503 config.cloud_home.provider = Some(CloudProvider::CloudKit);
504 config.cloud_home.storage = HomeStorage::Opaque;
505 if let Some((owner, zone)) = owner_zone {
506 config.cloud_home.cloudkit_owner_name = Some(owner.to_string());
507 config.cloud_home.cloudkit_zone_name = Some(zone.to_string());
508 }
509 config
510 }
511
512 #[tokio::test]
515 async fn neither_owner_nor_zone_builds_a_private_home() {
516 let config = cloudkit_config(None);
517 let key_service = StoreKeys::new(config.store_id.clone());
518 let ops = std::sync::Arc::new(ScopeRecordingOps::new());
519 let clock: crate::clock::ClockRef =
520 std::sync::Arc::new(FixedClock(chrono::Utc::now()));
521
522 let home = create_cloud_home_with_cloudkit(
523 &config,
524 &key_service,
525 clock,
526 Some(ops.clone()),
527 )
528 .await
529 .expect("private CloudKit config builds a home");
530 home.list("").await.expect("list against the built home");
531
532 assert_eq!(
533 ops.seen.lock().unwrap().as_slice(),
534 [CloudKitScope::Private]
535 );
536 }
537
538 #[tokio::test]
541 async fn both_owner_and_zone_build_a_shared_home() {
542 let config = cloudkit_config(Some(("owner-name", "zone-name")));
543 let key_service = StoreKeys::new(config.store_id.clone());
544 let ops = std::sync::Arc::new(ScopeRecordingOps::new());
545 let clock: crate::clock::ClockRef =
546 std::sync::Arc::new(FixedClock(chrono::Utc::now()));
547
548 let home = create_cloud_home_with_cloudkit(
549 &config,
550 &key_service,
551 clock,
552 Some(ops.clone()),
553 )
554 .await
555 .expect("shared CloudKit config builds a home");
556 home.list("").await.expect("list against the built home");
557
558 assert_eq!(
559 ops.seen.lock().unwrap().as_slice(),
560 [CloudKitScope::Shared {
561 owner_name: "owner-name".to_string(),
562 zone_name: "zone-name".to_string(),
563 }]
564 );
565 }
566
567 #[tokio::test]
573 async fn mixed_owner_zone_is_a_configuration_error() {
574 let mut config = cloudkit_config(None);
575 config.cloud_home.cloudkit_owner_name = Some("owner-name".to_string());
576 let key_service = StoreKeys::new(config.store_id.clone());
577 let ops = std::sync::Arc::new(ScopeRecordingOps::new());
578 let clock: crate::clock::ClockRef =
579 std::sync::Arc::new(FixedClock(chrono::Utc::now()));
580
581 let result =
582 create_cloud_home_with_cloudkit(&config, &key_service, clock, Some(ops)).await;
583 match result {
584 Ok(_) => panic!("mixed owner/zone must not build a home"),
585 Err(CloudHomeError::Configuration(message)) => {
586 assert!(message.contains("cloudkit_owner_name"), "{message}");
587 assert!(message.contains("cloudkit_zone_name"), "{message}");
588 }
589 Err(other) => panic!("expected Configuration error, got {other:?}"),
590 }
591 }
592 }
593 }
594
595 pub(crate) mod local;
596}
597
598pub(crate) mod sync {
599 pub(crate) use coven_core::sync::*;
600
601 pub(crate) mod device_join_transport;
602 #[cfg(test)]
603 mod device_join_transport_tests;
604 pub(crate) mod join;
605 #[cfg(test)]
606 mod join_tests;
607 pub(crate) mod restore;
608 #[cfg(test)]
609 mod restore_tests;
610 pub(crate) mod sync_loop;
611 pub(crate) mod sync_manager;
612}
613
614mod circles;
615mod coven;
616mod handle;
617mod keyring_backend;
618mod read_handle;
619
620pub use coven::{
625 Coven, CovenBuilder, CovenConfig, CovenError, CovenResult, SqlContext, WriteBatch,
626};
627pub use handle::CovenHandle;
628pub use read_handle::CovenReadHandle;
629
630pub use coven_core::rusqlite;
635
636pub use coven_core::{BlobDecl, Migration, MigrationStep, RowIdentity, SyncedTable};
638
639pub use coven_core::sync::storage::CloudKitEnvironment;
641pub use coven_core::{
642 CloudHomeConfig, CloudProvider, Config, ConfigError, CustomS3ExactSlots, HomeStorage,
643};
644
645pub use coven_core::{
647 BlobCacheError, BlobRef, BlobReplacement, BlobScope, BlobTransitionObserver, CacheFill,
648 Provenance, RowBlobAuthority, RowBlobRef,
649};
650
651pub use coven_core::{ChangeOp, RowChange};
653
654pub use coven_core::{
661 DbError, EncryptionError, MasterKeyring, SealError, StoreDir, StoreLayout, CHUNK_SIZE,
662};
663
664#[cfg(any(test, feature = "test-utils"))]
669pub use coven_core::EncryptionService;
670
671pub use coven_core::{Hlc, Timestamp, UpdatedAtStamper};
673
674pub use circles::{CircleError, Circles};
679pub use coven_core::sync::membership::MembershipCoord;
680pub use coven_core::sync::store::{
681 DeviceJoinAbandonment, DeviceJoinAction, DeviceJoinActivation, DeviceJoinCancellation,
682 DeviceJoinCleanupActivation, DeviceJoinCleanupProgress, DeviceJoinCleanupReceipt,
683 DeviceJoinError, DeviceJoinJournalDatabase, DeviceJoinJournalRecord, DeviceJoinOffer,
684 DeviceJoinProducer, DeviceJoinProducerWriteRevocation, DeviceJoinReadiness, DeviceJoinRole,
685 DeviceJoinStatus, DeviceJoinWriteRevocationExecutor, DeviceProviderAccessAdministrator,
686 DeviceProviderAccessRequest, DeviceProviderAdmission, DeviceProviderAdmissionApproval,
687 DeviceProviderAdmissionCompletion, DeviceProviderReadiness, DeviceRegistrationRequest,
688 JoinedStore, JoinerJoinClosure, JoinerJoinTerminal, ProviderAdminJoinClosure,
689 ProviderAdminJoinTerminal, ProviderReadyDeviceBootstrap, ProviderWriteAuthorityRef,
690 ProvisionalDeviceBootstrap,
691};
692pub use coven_core::sync::store::{
695 abandon_device_join_via_transport, cancel_device_join_via_transport, drive_device_join,
696 DeviceJoinApproval, DeviceJoinApprovalPolicy, DeviceJoinDriveOutcome, DeviceJoinOfferBundle,
697 DeviceJoinRoles, DeviceJoinStep, DeviceJoinTransport, DeviceJoinTransportError,
698 DeviceJoinTransportKind, DeviceJoinTransportParams, DeviceJoinTransportTiming,
699};
700pub use coven_core::sync::store_commit::{DeviceJoinAttemptId, DeviceJoinAttemptRef};
701pub use coven_core::{
702 Audience, Circle, CircleCloseParticipant, CircleCloseSettlement, CircleCloseStatus,
703 CircleControlCoord, CircleEpochCloseId, CircleId, CircleMemberInfo, CircleOperationBlock,
704 CircleOperationId, CircleOperationInfo, CircleOperationKind, CircleOperationState, CircleRole,
705 CircleState, StoreDeviceId,
706};
707pub use coven_core::{MemberInfo, MemberRole, MembershipConflictChoice, MembershipConflictInfo};
708
709pub use coven_core::{Clock, ClockRef, IdProvider, IdRef, SystemClock, UuidProvider};
711#[cfg(any(test, feature = "test-utils"))]
712pub use coven_core::{FixedClock, SequentialIdProvider, SteppingClock};
713
714pub use coven_core::{
716 decode_invite_code_info, decode_join_request, decode_restore_code_info, JoinCodeError,
717};
718
719#[cfg(any(test, feature = "test-utils"))]
723pub use coven_core::CloudCipher;
724
725pub use coven_core::storage::cloud::{
728 write_cloud_object_stream, CloudFileReadError, CloudObjectStream, ExactSlotStorage, ObjectSlot,
729 PhysicalObjectLocator,
730};
731pub use coven_core::sync::provider::{
732 CloudKitAcceptedShare, CrossPrincipalProbeReceipt, ExactSlotProbeReceipt,
733 ProviderAccessLocator, ProviderAccessWithdrawal, ProviderAdminChange, ProviderAdminGrantId,
734 ProviderAdminGrantRecord, ProviderAdminMembershipChange, ProviderAdminState,
735 ProviderCapabilityProof, ProviderProbeId,
736};
737pub use coven_core::sync::storage::{
738 AwsPrincipal, GoogleDriveCorpus, ProviderDeviceBinding, ProviderPrincipalId,
739 ResolvedProviderBinding, S3EndpointBinding, StoreProviderBinding,
740};
741pub use coven_core::{
742 BlobBody, BoxPartSink, CloudAccessOutcome, CloudAccessState, CloudHome, CloudHomeError,
743 CloudHomeJoinInfo, CloudObjectVersion, CloudVersionedObject, PartSink, UploadProgress,
744};
745
746pub use coven_core::{
750 AffectedRow, DeviceActivity, HeldStoreCoordinate, HeldStorePosition, HeldStorePositionReason,
751 ObjectHash, PendingWrite, PublishedPosition, StoreBatchCommitRef, StoreCommitCoord,
752 SyncLoopAlerts, SyncLoopSuccess, WriteBlock, WriteId, WriteReceipt, WriteResolution,
753 WriteStatus,
754};
755pub use sync::sync_loop::SyncLoopStatus;
756
757#[cfg(any(test, feature = "test-utils"))]
759pub use coven_core::InMemoryCloudHome;
760
761pub use blob::transition::{MakeLocalError, MakeRemoteError};
762pub use custody::{rewrap_passphrase_custody, KeyCustody, Passphrase};
763pub use identity_custody::{rewrap_passphrase_identity_custody, IdentityCustody};
764pub use join_code::{abandon_join_request, generate_join_request};
765pub use keys::{
766 keyring_service, set_keyring_service, CloudHomeCredentials, DeviceIdentityCustody,
767 IdentityError, KeyError, MasterKeyCustody, MasterKeyError, StoreKeys, UserKeypair,
768};
769
770#[cfg(any(test, feature = "test-utils"))]
775pub use keys::entry_for_test;
776
777pub use oauth::{set_oauth_client_creds, OAuthClientCreds, OAuthClientCredsConflict, OAuthTokens};
778pub use storage::cloud::setup::generate_restore_code;
779pub use storage::cloud::{
780 cloudkit::{
781 CloudKitAcceptedShareRecord, CloudKitAtomicCreateBatch, CloudKitOps,
782 CloudKitProviderIdentity, CloudKitRecordCreate, CloudKitRecordVersion, CloudKitScope,
783 CloudKitShare, CloudKitShareAcceptance, CloudKitSharePermission,
784 },
785 create_cloud_home,
786 s3::S3CloudHome,
787};
788pub use storage::local::BlobStore;
789pub use sync::device_join_transport::DeviceJoinTransportOutcome;
790pub use sync::join::{BootstrapError, DeviceJoinClient};
791pub use sync::restore::{restore_from_cloud, restore_from_code, RestoreSource};
792pub use sync::restore_code::{
793 ActivatedContinuation, OwnerRecoveryAuthority, RestoreAuthority, RestoreCode,
794};
795pub use sync::sync_manager::SyncError;
796
797#[cfg(feature = "oauth-providers")]
798pub use oauth::{
799 authorize_provider, build_authorize_request_for_provider, exchange_code_for_provider,
800 OAuthClientCredsError,
801};
802
803#[cfg(any(test, feature = "oauth-providers"))]
809pub use oauth::OAuthError;
810
811#[cfg(feature = "oauth-providers")]
812pub use storage::cloud::setup::{sign_in_dropbox, sign_in_google_drive, sign_in_onedrive};