twetch_sdk/chat/
conversation.rs

1use crate::{api::Api, chat::message::Message, wallet::Wallet};
2use anyhow::Result;
3use base64;
4use bsv_wasm::{ECIESCiphertext, Hash, PrivateKey, PublicKey, ECIES};
5use serde::*;
6use serde_json::json;
7use wasm_bindgen::prelude::*;
8
9#[wasm_bindgen]
10#[derive(Debug, Default, Clone, PartialEq, Eq, Serialize, Deserialize)]
11pub struct Conversation {
12    id: String,
13    key: String,
14}
15
16impl Conversation {
17    pub async fn create(token: String, user_ids: Vec<String>) -> Result<Conversation> {
18        let api = Api { token };
19        let pubkeys = api
20            .list_pubkeys(user_ids.clone())
21            .await?
22            .as_array()
23            .unwrap()
24            .clone();
25
26        let key = Conversation::generate_key();
27
28        let mut conversation_users = Vec::new();
29
30        for e in 0..user_ids.len() {
31            let pubkey = pubkeys[e]
32                .get("publicKey")
33                .unwrap()
34                .as_str()
35                .unwrap()
36                .to_string();
37            conversation_users.push(json!({
38                "userId": user_ids[e],
39                "encryptedKey": Conversation::encrypt_key(key.clone(), pubkey).unwrap()
40            }))
41        }
42
43        let payload =
44            json!({ "conversationUsers": serde_json::to_value(conversation_users).unwrap() })
45                .to_string();
46
47        let res = api.create_conversation(payload).await?;
48
49        Ok(Conversation {
50            id: res.get("id").unwrap().as_str().unwrap().to_string(),
51            key,
52        })
53    }
54
55    pub async fn create_message(&self, token: String, description: String) -> Result<Message> {
56        Message::create(
57            self.key.clone(),
58            self.id.clone(),
59            "1".to_string(),
60            description,
61            token,
62        )
63        .await
64    }
65}
66
67#[wasm_bindgen]
68impl Conversation {
69    #[wasm_bindgen(js_name = generateKey)]
70    pub fn generate_key() -> String {
71        Hash::sha_256(&PrivateKey::from_random().to_bytes())
72            .to_hex()
73            .chars()
74            .skip(32)
75            .take(32)
76            .collect()
77    }
78
79    #[wasm_bindgen(js_name = encrypt)]
80    pub fn encrypt_key(key: String, pubkey: String) -> Option<String> {
81        let public_key = match PublicKey::from_hex(&pubkey) {
82            Ok(v) => v,
83            Err(_) => return None,
84        };
85
86        let encrypted = match ECIES::encrypt_with_ephemeral_private_key(key.as_bytes(), &public_key)
87        {
88            Ok(v) => v.to_bytes(),
89            Err(_) => return None,
90        };
91
92        Some(base64::encode_config(encrypted, base64::STANDARD))
93    }
94
95    #[wasm_bindgen(js_name = decrypt)]
96    pub fn decrypt_key(encrypted_key: String, seed: String) -> Option<Vec<u8>> {
97        let wallet = Wallet::new(seed);
98        let xpriv = match wallet.xpriv_account() {
99            Some(v) => v,
100            None => return None,
101        };
102
103        let encrypted_key_buf = match base64::decode_config(encrypted_key, base64::STANDARD) {
104            Ok(v) => v,
105            Err(_) => return None,
106        };
107
108        let ciphertext = match ECIESCiphertext::from_bytes(&encrypted_key_buf, true) {
109            Ok(v) => v,
110            Err(_) => return None,
111        };
112
113        let pubkey = match ciphertext.extract_public_key() {
114            Ok(v) => v,
115            Err(_) => return None,
116        };
117
118        let decrypted = match ECIES::decrypt(&ciphertext, &xpriv.get_private_key(), &pubkey) {
119            Ok(v) => v,
120            Err(_) => return None,
121        };
122
123        return Some(decrypted);
124    }
125}