use anyhow::{anyhow, Result};
use noosphere_core::{
authority::Authorization,
data::{Did, Mnemonic},
};
use noosphere_storage::StorageConfig;
use std::{collections::BTreeMap, path::PathBuf, sync::Arc};
use noosphere_core::context::{SphereContext, SphereCursor};
use tokio::sync::Mutex;
use url::Url;
use crate::{
key::KeyStorage,
platform::{PlatformKeyStorage, PlatformSphereChannel},
sphere::{SphereChannel, SphereContextBuilder, SphereReceipt},
};
pub type NoosphereStorageConfig = StorageConfig;
#[derive(Clone)]
pub enum NoosphereStoragePath {
Scoped(PathBuf),
Unscoped(PathBuf),
}
#[derive(Clone)]
pub struct NoosphereStorage {
pub path: NoosphereStoragePath,
pub config: NoosphereStorageConfig,
}
#[derive(Clone)]
pub enum NoosphereSecurity {
Insecure {
path: PathBuf,
},
Opaque,
}
#[derive(Clone)]
pub enum NoosphereNetwork {
Http {
gateway_api: Option<Url>,
ipfs_gateway_url: Option<Url>,
},
}
#[derive(Clone)]
pub struct NoosphereContextConfiguration {
pub storage: NoosphereStorage,
pub security: NoosphereSecurity,
pub network: NoosphereNetwork,
}
#[derive(Clone)]
pub struct NoosphereContext {
configuration: NoosphereContextConfiguration,
sphere_channels: Arc<Mutex<BTreeMap<Did, PlatformSphereChannel>>>,
}
impl NoosphereContext {
pub fn new(configuration: NoosphereContextConfiguration) -> Result<Self> {
Ok(NoosphereContext {
configuration,
sphere_channels: Default::default(),
})
}
async fn key_storage(&self) -> Result<PlatformKeyStorage> {
#[cfg(target_arch = "wasm32")]
{
match &self.configuration.security {
NoosphereSecurity::Opaque { .. } => PlatformKeyStorage::new("noosphere-keys").await,
_ => return Err(anyhow!("Unsupported configuration!")),
}
}
#[cfg(not(target_arch = "wasm32"))]
match &self.configuration.security {
NoosphereSecurity::Insecure { path, .. } => PlatformKeyStorage::new(path),
_ => Err(anyhow!("Unsupported configuration!")),
}
}
fn sphere_storage_path(&self) -> &PathBuf {
match &self.configuration.storage.path {
NoosphereStoragePath::Scoped(path) => path,
NoosphereStoragePath::Unscoped(path) => path,
}
}
fn gateway_api(&self) -> Option<&Url> {
match &self.configuration.network {
NoosphereNetwork::Http { gateway_api, .. } => gateway_api.as_ref(),
}
}
fn ipfs_gateway_url(&self) -> Option<&Url> {
match &self.configuration.network {
NoosphereNetwork::Http {
ipfs_gateway_url, ..
} => ipfs_gateway_url.as_ref(),
}
}
pub async fn create_key(&self, key_name: &str) -> Result<()> {
if key_name.is_empty() {
return Err(anyhow!("Key name must not be empty."));
}
let key_storage = self.key_storage().await?;
key_storage.create_key(key_name).await?;
Ok(())
}
pub async fn has_key(&self, key_name: &str) -> Result<bool> {
let key_storage = self.key_storage().await?;
Ok(key_storage.read_key(key_name).await?.is_some())
}
pub async fn recover_sphere(
&self,
local_key_name: &str,
sphere_id: &Did,
mnemonic: &Mnemonic,
) -> Result<()> {
{
let mut sphere_contexts = self.sphere_channels.lock().await;
sphere_contexts.remove(sphere_id);
}
let artifacts = SphereContextBuilder::default()
.recover_sphere(sphere_id)
.at_storage_path(self.sphere_storage_path())
.with_storage_config(&self.configuration.storage.config)
.using_scoped_storage_layout()
.reading_keys_from(self.key_storage().await?)
.using_mnemonic(Some(mnemonic))
.using_key(local_key_name)
.syncing_to(self.gateway_api())
.reading_ipfs_from(self.ipfs_gateway_url())
.build()
.await?;
let context = SphereContext::from(artifacts);
let sphere_identity = context.identity().to_owned();
let mut sphere_contexts = self.sphere_channels.lock().await;
sphere_contexts.insert(
sphere_identity.clone(),
SphereChannel::new(
SphereCursor::latest(Arc::new(context.clone())),
Arc::new(Mutex::new(context)),
),
);
Ok(())
}
pub async fn create_sphere(&self, owner_key_name: &str) -> Result<SphereReceipt> {
let artifacts = SphereContextBuilder::default()
.create_sphere()
.at_storage_path(self.sphere_storage_path())
.with_storage_config(&self.configuration.storage.config)
.using_scoped_storage_layout()
.reading_keys_from(self.key_storage().await?)
.using_key(owner_key_name)
.syncing_to(self.gateway_api())
.reading_ipfs_from(self.ipfs_gateway_url())
.build()
.await?;
let mnemonic = artifacts.require_mnemonic()?.to_owned();
let context = SphereContext::from(artifacts);
let sphere_identity = context.identity().to_owned();
let mut sphere_contexts = self.sphere_channels.lock().await;
sphere_contexts.insert(
sphere_identity.clone(),
SphereChannel::new(
SphereCursor::latest(Arc::new(context.clone())),
Arc::new(Mutex::new(context)),
),
);
Ok(SphereReceipt {
identity: sphere_identity,
mnemonic: mnemonic.into(),
})
}
pub async fn join_sphere(
&self,
sphere_identity: &Did,
local_key_name: &str,
authorization: Option<&Authorization>,
) -> Result<()> {
let artifacts = SphereContextBuilder::default()
.join_sphere(sphere_identity)
.at_storage_path(self.sphere_storage_path())
.with_storage_config(&self.configuration.storage.config)
.using_scoped_storage_layout()
.reading_keys_from(self.key_storage().await?)
.using_key(local_key_name)
.authorized_by(authorization)
.syncing_to(self.gateway_api())
.reading_ipfs_from(self.ipfs_gateway_url())
.build()
.await?;
let context = SphereContext::from(artifacts);
let sphere_identity = context.identity().to_owned();
let mut sphere_contexts = self.sphere_channels.lock().await;
sphere_contexts.insert(
sphere_identity,
SphereChannel::new(
SphereCursor::latest(Arc::new(context.clone())),
Arc::new(Mutex::new(context)),
),
);
Ok(())
}
pub async fn get_sphere_channel(&self, sphere_identity: &Did) -> Result<PlatformSphereChannel> {
let mut contexts = self.sphere_channels.lock().await;
if !contexts.contains_key(sphere_identity) {
let artifacts = SphereContextBuilder::default()
.open_sphere(Some(sphere_identity))
.at_storage_path(self.sphere_storage_path())
.with_storage_config(&self.configuration.storage.config)
.using_scoped_storage_layout()
.reading_keys_from(self.key_storage().await?)
.syncing_to(self.gateway_api())
.reading_ipfs_from(self.ipfs_gateway_url())
.build()
.await?;
let context = SphereContext::from(artifacts);
contexts.insert(
sphere_identity.to_owned(),
SphereChannel::new(
SphereCursor::latest(Arc::new(context.clone())),
Arc::new(Mutex::new(context)),
),
);
}
Ok(contexts
.get(sphere_identity)
.ok_or_else(|| anyhow!("Context was not initialized!"))?
.clone())
}
}