1use bee::api::{CollectionUploadOptions, DownloadOptions, FileUploadOptions, UploadOptions};
10use bee::swarm::{BatchId, PrivateKey, PublicKey, Reference, Topic};
11use bee::Client;
12
13pub struct LiteClient {
16 client: Client,
17 write: Option<Writer>,
18}
19
20struct Writer {
21 client: Client,
22 batch: BatchId,
23}
24
25pub struct ShareInfo {
28 pub file_ref: String,
29 pub history: String,
30 pub grantee_ref: String,
31 pub grantee_history: String,
32 pub publisher: String,
33}
34
35impl LiteClient {
36 pub fn read(endpoint: &str) -> anyhow::Result<Self> {
39 Ok(Self {
40 client: Client::new(endpoint)?,
41 write: None,
42 })
43 }
44
45 pub fn with_write(mut self, node_endpoint: &str, stamp: &str) -> anyhow::Result<Self> {
48 let batch: BatchId = stamp.parse()?;
49 self.write = Some(Writer {
50 client: Client::new(node_endpoint)?,
51 batch,
52 });
53 Ok(self)
54 }
55
56 pub async fn cat(&self, reference: &str) -> anyhow::Result<Vec<u8>> {
60 let r: Reference = reference.parse()?;
61 let (bytes, _headers) = self.client.file().download_file(&r, None).await?;
62 Ok(bytes.to_vec())
63 }
64
65 pub async fn cat_path(&self, reference: &str, path: &str) -> anyhow::Result<Vec<u8>> {
67 let r: Reference = reference.parse()?;
68 let (bytes, _headers) = self.client.file().download_file_path(&r, path, None).await?;
69 Ok(bytes.to_vec())
70 }
71
72 pub async fn bytes(&self, reference: &str) -> anyhow::Result<Vec<u8>> {
74 let r: Reference = reference.parse()?;
75 let bytes = self.client.file().download_data(&r, None).await?;
76 Ok(bytes.to_vec())
77 }
78
79 fn writer(&self) -> anyhow::Result<&Writer> {
82 self.write.as_ref().ok_or_else(|| {
83 anyhow::anyhow!("uploads need a write endpoint — set --node and --stamp (or $BEE_NODE/$BEE_STAMP)")
84 })
85 }
86
87 pub async fn up_file(&self, name: &str, content_type: &str, data: Vec<u8>) -> anyhow::Result<String> {
90 let w = self.writer()?;
91 let res = w
92 .client
93 .file()
94 .upload_file(&w.batch, data, name, content_type, None)
95 .await?;
96 Ok(res.reference.to_hex())
97 }
98
99 pub async fn up_bytes(&self, data: Vec<u8>) -> anyhow::Result<String> {
101 let w = self.writer()?;
102 let res = w.client.file().upload_data(&w.batch, data, None).await?;
103 Ok(res.reference.to_hex())
104 }
105
106 pub async fn up_dir(&self, folder: &str, index_document: Option<&str>) -> anyhow::Result<String> {
111 let w = self.writer()?;
112 let opts = CollectionUploadOptions {
113 index_document: index_document.map(|s| s.to_string()),
114 ..Default::default()
115 };
116 let res = w.client.file().upload_collection(&w.batch, folder, Some(&opts)).await?;
117 Ok(res.reference.to_hex())
118 }
119
120 pub async fn publish(&self, key_hex: &str, topic: &str, reference: &str) -> anyhow::Result<String> {
128 let w = self.writer()?;
129 let key = PrivateKey::from_hex(key_hex)?;
130 let t = Topic::from_string(topic);
131 let r: Reference = reference.parse()?;
132 w.client
133 .file()
134 .update_feed_with_reference(&w.batch, &key, &t, &r, None)
135 .await?;
136 let owner = key.public_key()?.address();
137 let manifest = w.client.file().create_feed_manifest(&w.batch, &owner, &t).await?;
138 Ok(manifest.to_hex())
139 }
140
141 pub async fn share(
147 &self,
148 name: &str,
149 content_type: &str,
150 data: Vec<u8>,
151 grantees: &[String],
152 ) -> anyhow::Result<ShareInfo> {
153 let w = self.writer()?;
154 let opts = FileUploadOptions {
155 base: UploadOptions {
156 act: Some(true),
157 ..Default::default()
158 },
159 content_type: Some(content_type.to_string()),
160 ..Default::default()
161 };
162 let up = w
163 .client
164 .file()
165 .upload_file(&w.batch, data, name, content_type, Some(&opts))
166 .await?;
167 let history = up
168 .history_address
169 .ok_or_else(|| anyhow::anyhow!("upload returned no ACT history address"))?;
170 let created = w.client.api().create_grantees(&w.batch, grantees).await?;
171 let publisher = w.client.debug().addresses().await?.public_key;
172 Ok(ShareInfo {
173 file_ref: up.reference.to_hex(),
174 history: history.to_hex(),
175 grantee_ref: created.reference,
176 grantee_history: created.history_reference,
177 publisher,
178 })
179 }
180
181 pub async fn revoke(
185 &self,
186 grantee_ref: &str,
187 history: &str,
188 remove: &[String],
189 ) -> anyhow::Result<(String, String)> {
190 let w = self.writer()?;
191 let gr = Reference::from_hex(grantee_ref)?;
192 let h = Reference::from_hex(history)?;
193 let patched = w.client.api().patch_grantees(&w.batch, &gr, &h, &[], remove).await?;
194 Ok((patched.reference, patched.history_reference))
195 }
196
197 pub async fn grantees(&self, grantee_ref: &str) -> anyhow::Result<Vec<String>> {
199 let gr = Reference::from_hex(grantee_ref)?;
200 Ok(self.client.api().get_grantees(&gr).await?)
201 }
202
203 pub async fn fetch_act(&self, file_ref: &str, publisher: &str, history: &str) -> anyhow::Result<Vec<u8>> {
207 let r = Reference::from_hex(file_ref)?;
208 let pk = PublicKey::from_hex(publisher)?;
209 let h = Reference::from_hex(history)?;
210 let now = std::time::SystemTime::now()
211 .duration_since(std::time::UNIX_EPOCH)
212 .map(|d| d.as_secs() as i64)
213 .unwrap_or(0);
214 let opts = DownloadOptions {
215 act_publisher: Some(pk),
216 act_history_address: Some(h),
217 act_timestamp: Some(now),
218 ..Default::default()
219 };
220 let (body, _headers) = self.client.file().download_file(&r, Some(&opts)).await?;
221 Ok(body.to_vec())
222 }
223}
224
225pub fn generate_key() -> anyhow::Result<(String, String, String)> {
229 let mut b = [0u8; 32];
230 loop {
231 getrandom::getrandom(&mut b).map_err(|e| anyhow::anyhow!("rng: {e}"))?;
232 if let Ok(key) = PrivateKey::new(&b) {
233 let pubk = key.public_key()?;
234 return Ok((key.to_hex(), pubk.address().to_hex(), pubk.compressed_hex()?));
235 }
236 }
237}