Skip to main content

im_core/core/
mod.rs

1use std::sync::Arc;
2
3#[cfg(feature = "sqlite")]
4use tokio::sync::OnceCell;
5
6mod bootstrap;
7mod client;
8pub(crate) mod options;
9
10pub use self::bootstrap::{
11    CoreBootstrap, LocalStateStatus, MigrationReport, PathCheck, PathValidationReport,
12};
13pub use self::client::ImClient;
14pub use self::options::{IdentitySecretStoragePolicy, ImCoreOpenOptions, ImCoreSecretVaultOptions};
15
16pub(crate) struct ImCoreInner {
17    pub(crate) sdk_config: crate::ImCoreConfig,
18    pub(crate) sdk_paths: crate::ImCorePaths,
19    pub(crate) identity_secret_storage_policy: IdentitySecretStoragePolicy,
20    pub(crate) identity_vault: Option<options::IdentityVaultContext>,
21    #[cfg(feature = "sqlite")]
22    pub(crate) local_state_db: OnceCell<crate::internal::local_state::actor::LocalStateDb>,
23}
24
25#[derive(Clone)]
26pub struct ImCore {
27    inner: Arc<ImCoreInner>,
28}
29
30impl ImCore {
31    pub async fn open(
32        sdk_config: crate::ImCoreConfig,
33        sdk_paths: crate::ImCorePaths,
34    ) -> crate::ImResult<Self> {
35        Self::new(sdk_config, sdk_paths)
36    }
37
38    pub async fn open_with_options(
39        sdk_config: crate::ImCoreConfig,
40        sdk_paths: crate::ImCorePaths,
41        options: ImCoreOpenOptions,
42    ) -> crate::ImResult<Self> {
43        Self::new_with_options(sdk_config, sdk_paths, options)
44    }
45
46    pub fn new(
47        sdk_config: crate::ImCoreConfig,
48        sdk_paths: crate::ImCorePaths,
49    ) -> crate::ImResult<Self> {
50        Self::new_with_options(sdk_config, sdk_paths, ImCoreOpenOptions::default())
51    }
52
53    pub fn new_with_options(
54        sdk_config: crate::ImCoreConfig,
55        sdk_paths: crate::ImCorePaths,
56        options: ImCoreOpenOptions,
57    ) -> crate::ImResult<Self> {
58        if sdk_config.did_domain.trim().is_empty() {
59            return Err(crate::ImError::invalid_input(
60                Some("did_domain".to_string()),
61                "DID domain must not be empty",
62            ));
63        }
64        let identity_vault = options
65            .identity_secret_vault
66            .map(options::IdentityVaultContext::from_options)
67            .transpose()?
68            .map(|context| context.with_policy(options.identity_secret_storage_policy));
69        if matches!(
70            options.identity_secret_storage_policy,
71            IdentitySecretStoragePolicy::VaultRequired
72        ) && identity_vault.is_none()
73        {
74            return Err(crate::ImError::LocalStateUnavailable {
75                detail: "identity secret storage policy is VaultRequired but no identity secret vault was provided".to_owned(),
76            });
77        }
78        Ok(Self {
79            inner: Arc::new(ImCoreInner {
80                sdk_config,
81                sdk_paths,
82                identity_secret_storage_policy: options.identity_secret_storage_policy,
83                identity_vault,
84                #[cfg(feature = "sqlite")]
85                local_state_db: OnceCell::new(),
86            }),
87        })
88    }
89
90    pub fn identities(&self) -> crate::identity::IdentityRegistry<'_> {
91        crate::identity::IdentityRegistry::new(self)
92    }
93
94    pub fn bootstrap(&self) -> CoreBootstrap<'_> {
95        CoreBootstrap::new(self)
96    }
97
98    pub fn client(&self, selector: crate::identity::IdentitySelector) -> crate::ImResult<ImClient> {
99        let runtime = self.identities().load_runtime(selector)?;
100        Ok(ImClient::new(self.inner.clone(), runtime))
101    }
102
103    pub async fn client_async(
104        &self,
105        selector: crate::identity::IdentitySelector,
106    ) -> crate::ImResult<ImClient> {
107        let runtime = self.identities().load_runtime_async(selector).await?;
108        Ok(ImClient::new(self.inner.clone(), runtime))
109    }
110
111    pub fn client_with_identity_material(
112        &self,
113        material: crate::identity::HostedIdentityMaterial,
114    ) -> crate::ImResult<ImClient> {
115        let identity_id = crate::ids::IdentityId::parse(&material.identity_id)?;
116        let did = crate::ids::Did::parse(&material.did)?;
117        let handle = material
118            .handle
119            .as_deref()
120            .map(|handle| crate::ids::Handle::parse(handle, &self.inner.sdk_config().did_domain))
121            .transpose()?;
122        let key_provider = std::sync::Arc::new(
123            crate::internal::key_provider::HostedKeyMaterialProvider::new(&material)?,
124        );
125        let runtime = crate::internal::identity_runtime::ClientIdentityRuntime {
126            summary: crate::identity::IdentitySummary {
127                id: identity_id.clone(),
128                did: did.clone(),
129                handle,
130                display_name: material.display_name,
131                local_alias: None,
132                device_id: None,
133                is_default: false,
134                readiness: crate::identity::IdentityReadiness {
135                    ready_for_auth: true,
136                    ready_for_messaging: true,
137                    missing: Vec::new(),
138                },
139            },
140            did_document_path: std::path::PathBuf::new(),
141            private_key_path: std::path::PathBuf::new(),
142            e2ee_agreement_private_key_path: std::path::PathBuf::new(),
143            auth_state_path: std::path::PathBuf::new(),
144            key_provider,
145            owner: crate::internal::identity_runtime::LocalOwnerContext {
146                identity_id,
147                current_did: did,
148            },
149        };
150        Ok(ImClient::new(self.inner.clone(), runtime))
151    }
152
153    pub(crate) fn inner(&self) -> &ImCoreInner {
154        &self.inner
155    }
156}
157
158impl ImCoreInner {
159    pub(crate) fn sdk_config(&self) -> &crate::ImCoreConfig {
160        &self.sdk_config
161    }
162
163    pub(crate) fn sdk_paths(&self) -> &crate::ImCorePaths {
164        &self.sdk_paths
165    }
166
167    pub(crate) fn identity_secret_storage_policy(&self) -> IdentitySecretStoragePolicy {
168        self.identity_secret_storage_policy
169    }
170
171    pub(crate) fn identity_vault(&self) -> Option<&options::IdentityVaultContext> {
172        self.identity_vault.as_ref()
173    }
174
175    #[cfg(feature = "sqlite")]
176    pub(crate) async fn local_state_db(
177        &self,
178    ) -> crate::ImResult<crate::internal::local_state::actor::LocalStateDb> {
179        self.local_state_db
180            .get_or_try_init(|| {
181                crate::internal::local_state::actor::LocalStateDb::open(
182                    self.sdk_paths.local_state.sqlite_path.clone(),
183                )
184            })
185            .await
186            .cloned()
187    }
188}
189
190#[cfg(all(test, feature = "sqlite"))]
191mod tests {
192    use super::*;
193
194    #[tokio::test]
195    async fn local_state_db_concurrent_first_open_shares_actor() {
196        let root = tempfile::tempdir().unwrap();
197        let core = ImCore::new(
198            crate::ImCoreConfig {
199                service_base_url: crate::ServiceEndpoint::parse("https://example.test").unwrap(),
200                did_domain: "awiki.test".to_owned(),
201                user_service_endpoint: None,
202                message_service_endpoint: None,
203                mail_service_endpoint: None,
204                anp_service_endpoint: None,
205                anp_service_did: None,
206                ca_bundle: None,
207                transport_policy: crate::MessageTransportPolicy::HttpOnly,
208            },
209            crate::ImCorePaths {
210                identities: crate::IdentityRegistryPaths {
211                    identity_root_dir: root.path().join("identities"),
212                    registry_path: root.path().join("identities").join("registry.json"),
213                    default_identity_path: Some(root.path().join("identities").join("default")),
214                },
215                local_state: crate::LocalStatePaths {
216                    sqlite_path: root.path().join("local").join("im.sqlite"),
217                },
218                runtime: crate::RuntimePaths {
219                    cache_dir: root.path().join("cache"),
220                    temp_dir: root.path().join("tmp"),
221                },
222            },
223        )
224        .unwrap();
225
226        let inner = core.inner.clone();
227        let mut tasks = Vec::new();
228        for _ in 0..8 {
229            let inner = inner.clone();
230            tasks.push(tokio::spawn(async move {
231                let db = inner.local_state_db().await.unwrap();
232                db.current_schema_version().await.unwrap()
233            }));
234        }
235
236        for task in tasks {
237            assert_eq!(
238                task.await.unwrap(),
239                crate::internal::local_state::schema::SCHEMA_VERSION
240            );
241        }
242        assert!(core.inner().local_state_db.get().is_some());
243    }
244}