use bee::api::{CollectionUploadOptions, DownloadOptions, FileUploadOptions, UploadOptions};
use bee::swarm::{BatchId, PrivateKey, PublicKey, Reference, Topic};
use bee::Client;
pub struct LiteClient {
client: Client,
write: Option<Writer>,
}
struct Writer {
client: Client,
batch: BatchId,
}
pub struct ShareInfo {
pub file_ref: String,
pub history: String,
pub grantee_ref: String,
pub grantee_history: String,
pub publisher: String,
}
impl LiteClient {
pub fn read(endpoint: &str) -> anyhow::Result<Self> {
Ok(Self {
client: Client::new(endpoint)?,
write: None,
})
}
pub fn with_write(mut self, node_endpoint: &str, stamp: &str) -> anyhow::Result<Self> {
let batch: BatchId = stamp.parse()?;
self.write = Some(Writer {
client: Client::new(node_endpoint)?,
batch,
});
Ok(self)
}
pub async fn cat(&self, reference: &str) -> anyhow::Result<Vec<u8>> {
let r: Reference = reference.parse()?;
let (bytes, _headers) = self.client.file().download_file(&r, None).await?;
Ok(bytes.to_vec())
}
pub async fn cat_path(&self, reference: &str, path: &str) -> anyhow::Result<Vec<u8>> {
let r: Reference = reference.parse()?;
let (bytes, _headers) = self.client.file().download_file_path(&r, path, None).await?;
Ok(bytes.to_vec())
}
pub async fn bytes(&self, reference: &str) -> anyhow::Result<Vec<u8>> {
let r: Reference = reference.parse()?;
let bytes = self.client.file().download_data(&r, None).await?;
Ok(bytes.to_vec())
}
fn writer(&self) -> anyhow::Result<&Writer> {
self.write.as_ref().ok_or_else(|| {
anyhow::anyhow!("uploads need a write endpoint — set --node and --stamp (or $BEE_NODE/$BEE_STAMP)")
})
}
pub async fn up_file(&self, name: &str, content_type: &str, data: Vec<u8>) -> anyhow::Result<String> {
let w = self.writer()?;
let res = w
.client
.file()
.upload_file(&w.batch, data, name, content_type, None)
.await?;
Ok(res.reference.to_hex())
}
pub async fn up_bytes(&self, data: Vec<u8>) -> anyhow::Result<String> {
let w = self.writer()?;
let res = w.client.file().upload_data(&w.batch, data, None).await?;
Ok(res.reference.to_hex())
}
pub async fn up_dir(&self, folder: &str, index_document: Option<&str>) -> anyhow::Result<String> {
let w = self.writer()?;
let opts = CollectionUploadOptions {
index_document: index_document.map(|s| s.to_string()),
..Default::default()
};
let res = w.client.file().upload_collection(&w.batch, folder, Some(&opts)).await?;
Ok(res.reference.to_hex())
}
pub async fn publish(&self, key_hex: &str, topic: &str, reference: &str) -> anyhow::Result<String> {
let w = self.writer()?;
let key = PrivateKey::from_hex(key_hex)?;
let t = Topic::from_string(topic);
let r: Reference = reference.parse()?;
w.client
.file()
.update_feed_with_reference(&w.batch, &key, &t, &r, None)
.await?;
let owner = key.public_key()?.address();
let manifest = w.client.file().create_feed_manifest(&w.batch, &owner, &t).await?;
Ok(manifest.to_hex())
}
pub async fn share(
&self,
name: &str,
content_type: &str,
data: Vec<u8>,
grantees: &[String],
) -> anyhow::Result<ShareInfo> {
let w = self.writer()?;
let opts = FileUploadOptions {
base: UploadOptions {
act: Some(true),
..Default::default()
},
content_type: Some(content_type.to_string()),
..Default::default()
};
let up = w
.client
.file()
.upload_file(&w.batch, data, name, content_type, Some(&opts))
.await?;
let history = up
.history_address
.ok_or_else(|| anyhow::anyhow!("upload returned no ACT history address"))?;
let created = w.client.api().create_grantees(&w.batch, grantees).await?;
let publisher = w.client.debug().addresses().await?.public_key;
Ok(ShareInfo {
file_ref: up.reference.to_hex(),
history: history.to_hex(),
grantee_ref: created.reference,
grantee_history: created.history_reference,
publisher,
})
}
pub async fn revoke(
&self,
grantee_ref: &str,
history: &str,
remove: &[String],
) -> anyhow::Result<(String, String)> {
let w = self.writer()?;
let gr = Reference::from_hex(grantee_ref)?;
let h = Reference::from_hex(history)?;
let patched = w.client.api().patch_grantees(&w.batch, &gr, &h, &[], remove).await?;
Ok((patched.reference, patched.history_reference))
}
pub async fn grantees(&self, grantee_ref: &str) -> anyhow::Result<Vec<String>> {
let gr = Reference::from_hex(grantee_ref)?;
Ok(self.client.api().get_grantees(&gr).await?)
}
pub async fn fetch_act(&self, file_ref: &str, publisher: &str, history: &str) -> anyhow::Result<Vec<u8>> {
let r = Reference::from_hex(file_ref)?;
let pk = PublicKey::from_hex(publisher)?;
let h = Reference::from_hex(history)?;
let now = std::time::SystemTime::now()
.duration_since(std::time::UNIX_EPOCH)
.map(|d| d.as_secs() as i64)
.unwrap_or(0);
let opts = DownloadOptions {
act_publisher: Some(pk),
act_history_address: Some(h),
act_timestamp: Some(now),
..Default::default()
};
let (body, _headers) = self.client.file().download_file(&r, Some(&opts)).await?;
Ok(body.to_vec())
}
}
pub fn generate_key() -> anyhow::Result<(String, String, String)> {
let mut b = [0u8; 32];
loop {
getrandom::getrandom(&mut b).map_err(|e| anyhow::anyhow!("rng: {e}"))?;
if let Ok(key) = PrivateKey::new(&b) {
let pubk = key.public_key()?;
return Ok((key.to_hex(), pubk.address().to_hex(), pubk.compressed_hex()?));
}
}
}