1use crate::wasm_client_storage::WasmClientStorage;
2use crate::wasm_key_locker::WasmKeyLockerManager;
3use crate::wasm_transport::WasmTransportFactory;
4use crate::wasm_try;
5use hashiverse_lib::client::args::Args;
6use hashiverse_lib::client::hashiverse_client::HashiverseClient;
7use hashiverse_lib::client::key_locker::key_locker::KeyLockerManager;
8use hashiverse_lib::tools::buckets::{BucketLocation, BucketType};
9use hashiverse_lib::tools::time::TimeMillis;
10use hashiverse_lib::tools::time_provider::time_provider::RealTimeProvider;
11use hashiverse_lib::tools::parallel_pow_generator::{ParallelPowGenerator, StubParallelPowGenerator};
12use hashiverse_lib::tools::runtime_services::RuntimeServices;
13use hashiverse_lib::tools::types::Id;
14use log::warn;
15use serde::{Deserialize, Serialize};
16use std::sync::Arc;
17use anyhow::anyhow;
18use tsify::Tsify;
19use wasm_bindgen::prelude::*;
20use wasm_bindgen::JsValue;
21use bytes::Bytes;
22use hashiverse_lib::protocol::posting::encoded_post::EncodedPostV1;
23
24#[wasm_bindgen]
25pub struct HashiverseClientWasm {
27 logged_in: bool,
28 hashiverse_client: HashiverseClient,
29}
30
31#[wasm_bindgen]
32impl HashiverseClientWasm {
33 async fn create_from_xxx(logged_in: bool, key_locker: Arc<dyn hashiverse_lib::client::key_locker::key_locker::KeyLocker>) -> anyhow::Result<Self> {
34 let time_provider: Arc<dyn hashiverse_lib::tools::time_provider::time_provider::TimeProvider> = Arc::new(RealTimeProvider::default());
35 let transport_factory: Arc<dyn hashiverse_lib::transport::transport::TransportFactory> = Arc::new(WasmTransportFactory::default());
36 let client_storage = WasmClientStorage::new().await?;
37 let pow_generator: Arc<dyn ParallelPowGenerator> = match crate::get_wasm_parallel_pow_generator() {
38 Some(g) => g as Arc<dyn ParallelPowGenerator>,
39 None => {
40 warn!("No native PoW generator available, falling back to StubParallelPowGenerator");
41 Arc::new(StubParallelPowGenerator::new())
42 }
43 };
44 let runtime_services = Arc::new(RuntimeServices { time_provider, transport_factory, pow_generator });
45 let hashiverse_client = HashiverseClient::new(runtime_services, client_storage, key_locker, Args::new()).await?;
46 Ok(Self { logged_in, hashiverse_client })
47 }
48
49 #[wasm_bindgen]
50 pub async fn create_from_keyphrase(key_phrase: String) -> Result<Self, JsValue> {
51 wasm_try!({
52 let logged_in = !key_phrase.is_empty();
53 let key_locker_manager = WasmKeyLockerManager::new().await?;
54 let key_locker = key_locker_manager.create(key_phrase).await?;
55 Self::create_from_xxx(logged_in, key_locker).await?
56 })
57 }
58
59 #[wasm_bindgen]
60 pub async fn create_from_stored_key(client_id_hex: String) -> Result<Self, JsValue> {
61 wasm_try!({
62 let key_locker_manager = WasmKeyLockerManager::new().await?;
63 let key_locker = key_locker_manager.switch(client_id_hex).await?;
64 Self::create_from_xxx(true, key_locker).await?
65 })
66 }
67
68 #[wasm_bindgen]
69 pub fn logged_in(&self) -> bool {
70 self.logged_in
71 }
72
73 #[wasm_bindgen]
74 pub async fn list_stored_key_ids_v1(&self) -> Result<Vec<String>, JsValue> {
75 wasm_try!({
76 let key_locker_manager = WasmKeyLockerManager::new().await?;
77 key_locker_manager.list().await?
78 })
79 }
80
81 #[wasm_bindgen]
82 pub async fn delete_stored_key_v1(&self, key_public: String) -> Result<(), JsValue> {
83 wasm_try!({
84 let key_locker_manager = WasmKeyLockerManager::new().await?;
85 key_locker_manager.delete(key_public).await?;
86 })
87 }
88
89 #[wasm_bindgen]
90 pub async fn delete_all_stored_keys_v1(&self) -> Result<(), JsValue> {
91 wasm_try!({
92 let key_locker_manager = WasmKeyLockerManager::new().await?;
93 key_locker_manager.reset().await?;
94 })
95 }
96
97 #[wasm_bindgen]
98 pub fn get_client_id(&self) -> String {
99 self.hashiverse_client.client_id().id_hex()
100 }
101
102 #[wasm_bindgen]
103 pub async fn client_storage_reset(&self) -> Result<(), JsValue> {
104 wasm_try!({
105 self.hashiverse_client.client_storage_reset().await?;
106 })
107 }
108
109 #[wasm_bindgen]
110 pub async fn post_v1(&self, post: &str) -> Result<Post, JsValue> {
111 wasm_try!({
112 let (commit_tokens, (encoded_post, raw_bytes)) = self.hashiverse_client.submit_post(post).await?;
113 let bucket_location = &commit_tokens[0].bucket_location;
114 let client_id = encoded_post.header.client_id()?;
115 let encoded_post_header_hex = hex::encode(EncodedPostV1::bytes_without_body(raw_bytes)?);
116 Post {
117 post_id: encoded_post.post_id.to_hex_str(),
118 time_millis: encoded_post.header.time_millis.0,
119 client_id: client_id.id_hex(),
120 bucket_location: bucket_location.to_html_attr(),
121 post: encoded_post.post,
122 encoded_post_header_hex,
123 healed: false,
124 }
125 })
126 }
127
128 fn meta_post_manager(&self) -> &hashiverse_lib::client::meta_post::meta_post_manager::MetaPostManager {
129 self.hashiverse_client.meta_post_manager()
130 }
131
132 pub async fn set_bio(&self, nickname: String, status: String, selfie: String, avatar: String) -> Result<(), JsValue> {
133 wasm_try!({
134 self.meta_post_manager().set_bio(nickname, status, selfie, avatar).await?;
135 })
136 }
137
138 #[wasm_bindgen]
139 pub async fn submit_feedback_v1(&self, bucket_location: String, post_id: String, feedback_type: u8) -> Result<(), JsValue> {
140 wasm_try!({
141 let bucket_location = BucketLocation::from_html_attr(&bucket_location)?;
142 let post_id = Id::from_hex_str(&post_id)?;
143 self.hashiverse_client.submit_feedback(bucket_location, post_id, feedback_type).await?;
144 })
145 }
146
147 #[wasm_bindgen]
148 pub async fn get_post_v1(&self, bucket_location: String, post_id: String) -> Result<Post, JsValue> {
150 wasm_try!({
151 let bucket_location = BucketLocation::from_html_attr(&bucket_location)?;
152 let post_id = Id::from_hex_str(&post_id)?;
153 let (bucket_location, post, raw_bytes, healed) = self.hashiverse_client.get_post(bucket_location, &post_id).await?;
154 let client_id = post.header.client_id()?;
155 let encoded_post_header_hex = hex::encode(EncodedPostV1::bytes_without_body(raw_bytes)?);
156 Post {
157 post_id: post.post_id.to_hex_str(),
158 time_millis: post.header.time_millis.0,
159 client_id: client_id.id_hex(),
160 bucket_location: bucket_location.to_html_attr(),
161 post: post.post,
162 encoded_post_header_hex,
163 healed,
164 }
165 })
166 }
167
168 #[wasm_bindgen]
169 pub async fn get_post_feedbacks_v1(&self, bucket_location: String, post_id: String) -> Result<Vec<u32>, JsValue> {
173 wasm_try!({
174 let bucket_location = BucketLocation::from_html_attr(&bucket_location)?;
175 let post_id = Id::from_hex_str(&post_id)?;
176 let post_feedbacks = self.hashiverse_client.get_post_feedbacks(bucket_location, post_id).await?;
177 post_feedbacks.iter().map(|&feedback| feedback.min(u32::MAX as u64) as u32).collect()
178 })
179 }
180
181 #[wasm_bindgen]
182 pub async fn get_bio(&self, id: String) -> Result<Bio, JsValue> {
183 wasm_try!({
184 let meta_post_public = self.meta_post_manager().get_meta_post_public(Id::from_hex_str(&id)?).await?;
185 match meta_post_public {
186 Some(meta_post_public) => Bio {
187 client_id: id,
188 nickname: meta_post_public.nickname.value.unwrap_or_default(),
189 status: meta_post_public.status.value.unwrap_or_default(),
190 selfie: meta_post_public.selfie.value.unwrap_or_default(),
191 avatar: meta_post_public.avatar.value.unwrap_or_default(),
192 },
193 None => Bio {
194 client_id: id,
195 nickname: "".to_string(),
196 status: "".to_string(),
197 selfie: "".to_string(),
198 avatar: "".to_string(),
199 },
200 }
201 })
202 }
203
204 #[wasm_bindgen]
205 pub async fn get_all_bios(&self) -> Result<Vec<Bio>, JsValue> {
206 wasm_try!({
207 let meta_post_publics = self.meta_post_manager().get_all_meta_post_publics().await?;
208 meta_post_publics.into_iter()
209 .map(|(client_id, meta_post_public)| Bio {
210 client_id,
211 nickname: meta_post_public.nickname.value.unwrap_or_default(),
212 status: meta_post_public.status.value.unwrap_or_default(),
213 selfie: meta_post_public.selfie.value.unwrap_or_default(),
214 avatar: meta_post_public.avatar.value.unwrap_or_default(),
215 })
216 .collect()
217 })
218 }
219
220 #[wasm_bindgen]
221 pub async fn get_all_known_peers_v1(&self) -> Result<Vec<PeerInfoV1>, JsValue> {
222 wasm_try!({
223 self.hashiverse_client.get_all_known_peers().await
224 .into_iter()
225 .map(|peer| PeerInfoV1 {
226 peer_id_hex: peer.id.to_hex_str(),
227 address: peer.address,
228 version: peer.version,
229 timestamp_millis: peer.timestamp.0,
230 pow_initial: peer.pow_initial.pow.0,
231 pow_current_day: peer.pow_current_day.pow.0,
232 pow_current_month: peer.pow_current_month.pow.0,
233 })
234 .collect::<Vec<_>>()
235 })
236 }
237
238 #[wasm_bindgen]
239 pub async fn get_peer_stats_v1(&self, peer_id_hex: String) -> Result<JsValue, JsValue> {
240 wasm_try!({
241 let peer_id = Id::from_hex_str(&peer_id_hex)?;
242 let doc = self.hashiverse_client.fetch_peer_stats(&peer_id).await?;
243 let serializer = serde_wasm_bindgen::Serializer::new().serialize_maps_as_objects(true);
244 doc.serialize(&serializer).map_err(|e| anyhow!("serde_wasm_bindgen error: {}", e))?
245 })
246 }
247
248 #[wasm_bindgen]
249 pub async fn get_active_pow_jobs_v1(&self) -> Result<Vec<PowJobStatusV1>, JsValue> {
250 wasm_try!({
251 self.hashiverse_client.active_pow_jobs()
252 .into_iter()
253 .map(|job| PowJobStatusV1 {
254 label: job.label,
255 pow_min: job.pow_min.0,
256 best_pow_so_far: job.best_pow_so_far.0,
257 })
258 .collect::<Vec<_>>()
259 })
260 }
261
262 fn post_process_timeline_posts(&self, encoded_posts: Vec<(BucketLocation, EncodedPostV1, Bytes, bool)>, oldest_processed_time_millis: TimeMillis) -> anyhow::Result<SingleTimelineGetMoreV1Response> {
263 let response = SingleTimelineGetMoreV1Response {
264 oldest_processed_time_millis: if oldest_processed_time_millis == TimeMillis::MAX { None } else { Some(oldest_processed_time_millis.0) },
265 posts: encoded_posts
266 .into_iter()
267 .filter_map(|(bucket_location, post, raw_bytes, healed)| {
268 let client_id = match post.header.client_id() {
269 Ok(client_id) => client_id,
270 Err(e) => {
271 warn!("Skipping post with bad client_id in header: {}", e);
272 return None;
273 }
274 };
275 let encoded_post_header_hex = match EncodedPostV1::bytes_without_body(raw_bytes) {
276 Ok(header_bytes) => hex::encode(header_bytes),
277 Err(e) => {
278 warn!("Skipping post with bad header bytes: {}", e);
279 return None;
280 }
281 };
282 Some(Post {
283 post_id: post.post_id.to_hex_str(),
284 time_millis: post.header.time_millis.0,
285 client_id: client_id.id_hex(),
286 bucket_location: bucket_location.to_html_attr(),
287 post: post.post,
288 encoded_post_header_hex,
289 healed,
290 })
291 })
292 .collect(),
293 };
294
295 Ok(response)
296 }
297
298
299 #[wasm_bindgen]
300 pub async fn single_timeline_reset(&self) -> Result<(), JsValue> {
301 wasm_try!({
302 self.hashiverse_client.single_timeline_reset().await?;
303 })
304 }
305
306 async fn single_timeline_get_more(&self, bucket_type: BucketType, base_id: &Id) -> anyhow::Result<SingleTimelineGetMoreV1Response> {
307 let (encoded_posts, oldest_processed_time_millis) = self.hashiverse_client.single_timeline_get_more(bucket_type, base_id).await?;
308 self.post_process_timeline_posts(encoded_posts, oldest_processed_time_millis)
309 }
310
311 #[wasm_bindgen]
312 pub async fn single_timeline_get_more_me_v1(&self) -> Result<SingleTimelineGetMoreV1Response, JsValue> {
313 wasm_try!({
314 let id = self.hashiverse_client.client_id().id;
315 self.single_timeline_get_more(BucketType::User, &id).await?
316 })
317 }
318
319 #[wasm_bindgen]
320 pub async fn single_timeline_get_more_me_mentioned_v1(&self) -> Result<SingleTimelineGetMoreV1Response, JsValue> {
321 wasm_try!({
322 let id = self.hashiverse_client.client_id().id;
323 self.single_timeline_get_more(BucketType::Mention, &id).await?
324 })
325 }
326
327 #[wasm_bindgen]
328 pub async fn single_timeline_get_more_hashtag_v1(&self, hashtag: String) -> Result<SingleTimelineGetMoreV1Response, JsValue> {
329 wasm_try!({
330 let id = Id::from_hashtag_str(&hashtag)?;
331 self.single_timeline_get_more(BucketType::Hashtag, &id).await?
332 })
333 }
334
335 #[wasm_bindgen]
336 pub async fn single_timeline_get_more_user_v1(&self, client_id_hex: String) -> Result<SingleTimelineGetMoreV1Response, JsValue> {
337 wasm_try!({
338 let id = Id::from_hex_str(&client_id_hex)?;
339 self.single_timeline_get_more(BucketType::User, &id).await?
340 })
341 }
342
343 #[wasm_bindgen]
344 pub async fn single_timeline_get_more_user_mentioned_v1(&self, client_id_hex: String) -> Result<SingleTimelineGetMoreV1Response, JsValue> {
345 wasm_try!({
346 let id = Id::from_hex_str(&client_id_hex)?;
347 self.single_timeline_get_more(BucketType::Mention, &id).await?
348 })
349 }
350
351 #[wasm_bindgen]
352 pub async fn single_timeline_get_more_reply_to_post_v1(&self, post_id: String) -> Result<SingleTimelineGetMoreV1Response, JsValue> {
353 wasm_try!({
354 let id = Id::from_hex_str(&post_id)?;
355 self.single_timeline_get_more(BucketType::ReplyToPost, &id).await?
356 })
357 }
358
359 #[wasm_bindgen]
360 pub async fn single_timeline_get_more_sequel_v1(&self, post_id: String) -> Result<SingleTimelineGetMoreV1Response, JsValue> {
361 wasm_try!({
362 let id = Id::from_hex_str(&post_id)?;
363 self.single_timeline_get_more(BucketType::Sequel, &id).await?
364 })
365 }
366
367 #[wasm_bindgen]
368 pub async fn multiple_timeline_reset(&self) -> Result<(), JsValue> {
369 wasm_try!({
370 self.hashiverse_client.multiple_timeline_reset().await?;
371 })
372 }
373
374 async fn multiple_timeline_get_more(&self, bucket_type: BucketType, base_ids: &Vec<Id>) -> anyhow::Result<SingleTimelineGetMoreV1Response> {
375 let (encoded_posts, oldest_processed_time_millis) = self.hashiverse_client.multiple_timeline_get_more(bucket_type, base_ids).await?;
376 self.post_process_timeline_posts(encoded_posts, oldest_processed_time_millis)
377 }
378
379 #[wasm_bindgen]
380 pub async fn multiple_timeline_get_more_followed_users(&self) -> Result<SingleTimelineGetMoreV1Response, JsValue> {
381 wasm_try!({
382 let ids = self.meta_post_manager().get_followed_client_ids().await?;
383 self.multiple_timeline_get_more(BucketType::User, &ids).await?
384 })
385 }
386
387 #[wasm_bindgen]
388 pub async fn get_followed_client_ids_v1(&self) -> Result<Vec<String>, JsValue> {
389 wasm_try!({
390 let ids = self.meta_post_manager().get_followed_client_ids().await?;
391 ids.into_iter().map(|id| id.to_hex_str()).collect()
392 })
393 }
394
395 #[wasm_bindgen]
396 pub async fn set_followed_client_ids_v1(&self, client_ids: JsValue) -> Result<(), JsValue> {
397 wasm_try!({
398 let client_id_strs: Vec<String> = serde_wasm_bindgen::from_value(client_ids).map_err(|e| anyhow!("serde_wasm_bindgen::from_value error: {}", e))?;
399 let ids = client_id_strs.iter().map(|s| Id::from_hex_str(s)).collect::<anyhow::Result<Vec<_>>>()?;
400 self.meta_post_manager().set_followed_client_ids(ids).await?;
401 })
402 }
403
404 #[wasm_bindgen]
405 pub async fn set_followed_client_id_v1(&self, client_id: String, is_followed: bool) -> Result<(), JsValue> {
406 wasm_try!({
407 let id = Id::from_hex_str(&client_id)?;
408 self.meta_post_manager().set_followed_client_id(id, is_followed).await?;
409 })
410 }
411
412 #[wasm_bindgen]
413 pub async fn multiple_timeline_get_more_followed_hashtags(&self) -> Result<SingleTimelineGetMoreV1Response, JsValue> {
414 wasm_try!({
415 let hashtags = self.meta_post_manager().get_followed_hashtags().await?;
416 let ids = hashtags.iter().map(|h| Id::from_hashtag_str(h)).collect::<anyhow::Result<Vec<_>>>()?;
417 self.multiple_timeline_get_more(BucketType::Hashtag, &ids).await?
418 })
419 }
420
421 #[wasm_bindgen]
422 pub async fn get_followed_hashtags_v1(&self) -> Result<Vec<String>, JsValue> {
423 wasm_try!({
424 self.meta_post_manager().get_followed_hashtags().await?
425 })
426 }
427
428 #[wasm_bindgen]
429 pub async fn set_followed_hashtags_v1(&self, hashtags: JsValue) -> Result<(), JsValue> {
430 wasm_try!({
431 let hashtags: Vec<String> = serde_wasm_bindgen::from_value(hashtags).map_err(|e| anyhow!("serde_wasm_bindgen::from_value error: {}", e))?;
432 self.meta_post_manager().set_followed_hashtags(hashtags).await?;
433 })
434 }
435
436 #[wasm_bindgen]
437 pub async fn set_followed_hashtag_v1(&self, hashtag: String, is_followed: bool) -> Result<(), JsValue> {
438 wasm_try!({
439 self.meta_post_manager().set_followed_hashtag(hashtag, is_followed).await?;
440 })
441 }
442
443 #[wasm_bindgen]
448 pub async fn submit_meta_post_v1(&self) -> Result<(), JsValue> {
449 wasm_try!({
450 self.hashiverse_client.submit_meta_post().await?;
451 })
452 }
453
454 #[wasm_bindgen]
455 pub async fn ensure_meta_post_in_current_bucket_v1(&self) -> Result<(), JsValue> {
456 wasm_try!({
457 self.hashiverse_client.ensure_meta_post_in_current_bucket().await?;
458 })
459 }
460
461 #[wasm_bindgen]
466 pub async fn get_content_thresholds_v1(&self) -> Result<JsValue, JsValue> {
467 wasm_try!({
468 let thresholds = self.meta_post_manager().get_content_thresholds().await?;
469 let thresholds_js: std::collections::HashMap<String, u32> = thresholds.into_iter().map(|(k, v)| (k.to_string(), v)).collect();
473 let serializer = serde_wasm_bindgen::Serializer::new().serialize_maps_as_objects(true);
474 thresholds_js.serialize(&serializer).map_err(|e| anyhow!("serde_wasm_bindgen error: {}", e))?
475 })
476 }
477
478 #[wasm_bindgen]
479 pub async fn set_content_thresholds_v1(&self, thresholds: JsValue) -> Result<(), JsValue> {
480 wasm_try!({
481 let thresholds_str: std::collections::HashMap<String, u32> = serde_wasm_bindgen::from_value(thresholds).map_err(|e| anyhow!("serde_wasm_bindgen error: {}", e))?;
482 let thresholds: std::collections::HashMap<u8, u32> = thresholds_str.into_iter()
483 .map(|(k, v)| Ok((k.parse::<u8>().map_err(|e| anyhow!("invalid feedback_type key: {}", e))?, v)))
484 .collect::<anyhow::Result<_>>()?;
485 self.meta_post_manager().set_content_thresholds(thresholds).await?;
486 })
487 }
488
489 #[wasm_bindgen]
494 pub async fn get_skip_warnings_for_followed_v1(&self) -> Result<bool, JsValue> {
495 wasm_try!({
496 self.meta_post_manager().get_skip_warnings_for_followed().await?
497 })
498 }
499
500 #[wasm_bindgen]
501 pub async fn set_skip_warnings_for_followed_v1(&self, value: bool) -> Result<(), JsValue> {
502 wasm_try!({
503 self.meta_post_manager().set_skip_warnings_for_followed(value).await?;
504 })
505 }
506
507 #[wasm_bindgen]
508 pub async fn fetch_url_preview_v1(&self, url: String) -> Result<UrlPreview, JsValue> {
509 wasm_try!({
510 let preview = self.hashiverse_client.fetch_url_preview(&url).await?;
511 UrlPreview {
512 url: preview.url,
513 title: preview.title,
514 description: preview.description,
515 image_url: preview.image_url,
516 }
517 })
518 }
519
520 #[wasm_bindgen]
521 pub async fn fetch_trending_hashtags_v1(&self, limit: u16) -> Result<TrendingHashtagsFetchResponse, JsValue> {
522 wasm_try!({
523 let response = self.hashiverse_client.fetch_trending_hashtags(limit).await?;
524 TrendingHashtagsFetchResponse {
525 trending_hashtags: response.trending_hashtags.into_iter().map(|entry| TrendingHashtag {
526 hashtag: entry.hashtag,
527 count: entry.count,
528 }).collect(),
529 }
530 })
531 }
532}
533
534#[derive(Tsify, Serialize, Deserialize)]
535#[tsify(into_wasm_abi)]
536pub struct SingleTimelineGetMoreV1Response {
537 pub posts: Vec<Post>,
538 pub oldest_processed_time_millis: Option<i64>,
539}
540
541#[derive(Tsify, Serialize, Deserialize)]
542#[tsify(into_wasm_abi)]
543pub struct Post {
544 pub post_id: String,
545 pub time_millis: i64,
546 pub client_id: String,
547 pub bucket_location: String,
548 pub post: String,
549 pub encoded_post_header_hex: String, pub healed: bool, }
552
553#[derive(Tsify, Serialize, Deserialize)]
554#[tsify(into_wasm_abi)]
555pub struct Bio {
556 pub client_id: String,
557 pub nickname: String,
558 pub status: String,
559 pub selfie: String,
560 pub avatar: String,
561}
562
563#[derive(Tsify, Serialize, Deserialize)]
564#[tsify(into_wasm_abi)]
565pub struct UrlPreview {
566 pub url: String,
567 pub title: String,
568 pub description: String,
569 pub image_url: String,
570}
571
572#[derive(Tsify, Serialize, Deserialize)]
573#[tsify(into_wasm_abi)]
574pub struct TrendingHashtag {
575 pub hashtag: String,
576 pub count: u64,
577}
578
579#[derive(Tsify, Serialize, Deserialize)]
580#[tsify(into_wasm_abi)]
581pub struct TrendingHashtagsFetchResponse {
582 pub trending_hashtags: Vec<TrendingHashtag>,
583}
584
585#[derive(Tsify, Serialize, Deserialize)]
586#[tsify(into_wasm_abi)]
587pub struct PeerInfoV1 {
588 pub peer_id_hex: String,
589 pub address: String,
590 pub version: String,
591 pub timestamp_millis: i64,
592 pub pow_initial: u8,
593 pub pow_current_day: u8,
594 pub pow_current_month: u8,
595}
596
597#[derive(Tsify, Serialize, Deserialize)]
598#[tsify(into_wasm_abi)]
599pub struct PowJobStatusV1 {
600 pub label: String,
601 pub pow_min: u8,
602 pub best_pow_so_far: u8,
603}