diff --git a/crates/sp_client/Cargo.toml b/crates/sp_client/Cargo.toml index 2155567..f64543a 100644 --- a/crates/sp_client/Cargo.toml +++ b/crates/sp_client/Cargo.toml @@ -8,6 +8,7 @@ name = "sdk_client" crate-type = ["cdylib"] [dependencies] +# sp_client= { path = "../../../sp-client" } sp_client= { git = "https://github.com/Sosthene00/sp-client", branch = "sp_client" } anyhow = "1.0" serde = { version = "1.0.188", features = ["derive"] } @@ -18,7 +19,8 @@ wasm-logger = "0.2.0" rand = "0.8.5" log = "0.4.6" tsify = { git = "https://github.com/Sosthene00/tsify", branch = "next" } -sdk_common = { git = "https://git.4nkweb.com/4nk/sdk_common.git", branch = "demo" } +sdk_common = { path = "../../../sdk_common" } +#sdk_common = { git = "https://git.4nkweb.com/4nk/sdk_common.git", branch = "demo" } shamir = { git = "https://github.com/Sosthene00/shamir", branch = "master" } img-parts = "0.3.0" diff --git a/crates/sp_client/src/api.rs b/crates/sp_client/src/api.rs index 9d0d59c..73bedb8 100644 --- a/crates/sp_client/src/api.rs +++ b/crates/sp_client/src/api.rs @@ -2,20 +2,22 @@ use std::any::Any; use std::collections::HashMap; use std::io::Write; use std::str::FromStr; +use std::string::FromUtf8Error; use std::sync::{Mutex, OnceLock, PoisonError}; use log::debug; -use rand::Rng; +use rand::{Fill, Rng}; use anyhow::Error as AnyhowError; -use sdk_common::crypto::AnkSharedSecret; +use sdk_common::crypto::{ + AeadCore, Aes256Decryption, Aes256Encryption, Aes256Gcm, AnkSharedSecret, KeyInit, Purpose, +}; use serde_json::Error as SerdeJsonError; use shamir::SecretData; use sp_client::bitcoin::consensus::{deserialize, serialize}; use sp_client::bitcoin::hex::{parse, DisplayHex, FromHex, HexToBytesError}; -use sp_client::bitcoin::secp256k1::ecdh::SharedSecret; use sp_client::bitcoin::secp256k1::{PublicKey, SecretKey}; -use sp_client::bitcoin::{OutPoint, Transaction, Txid}; +use sp_client::bitcoin::{Amount, OutPoint, Transaction, Txid}; use sp_client::silentpayments::Error as SpError; use serde::{Deserialize, Serialize}; @@ -25,15 +27,15 @@ use wasm_bindgen::convert::FromWasmAbi; use wasm_bindgen::prelude::*; use sdk_common::network::{AnkFlag, AnkNetworkMsg, NewTxMessage}; +use sdk_common::silentpayments::{ + check_transaction, create_transaction_for_address_with_shared_secret, +}; use sp_client::spclient::{derive_keys_from_seed, OutputList, OwnedOutput, SpClient}; use sp_client::spclient::{SpWallet, SpendKey}; -use crate::images; -use crate::silentpayments::{ - check_transaction, create_transaction_for_address_with_shared_secret, ScannedTransaction, -}; use crate::user::{lock_connected_user, User, UserWallets, CONNECTED_USER}; +use crate::{images, lock_scanned_transactions, Txid2Secrets}; use crate::process::Process; @@ -94,6 +96,14 @@ impl From for ApiError { } } +impl From for ApiError { + fn from(value: FromUtf8Error) -> Self { + ApiError { + message: value.to_string(), + } + } +} + impl Into for ApiError { fn into(self) -> JsValue { JsValue::from_str(&self.message) @@ -143,7 +153,10 @@ pub fn generate_sp_wallet( #[wasm_bindgen] pub fn get_recover_address() -> ApiResult { if let Ok(my_wallets) = lock_connected_user() { - Ok(my_wallets.try_get_recover()?.get_client().get_receiving_address()) + Ok(my_wallets + .try_get_recover()? + .get_client() + .get_receiving_address()) } else { Err(ApiError { message: "Unknown user pre_id".to_owned(), @@ -154,7 +167,10 @@ pub fn get_recover_address() -> ApiResult { #[wasm_bindgen] pub fn get_main_address() -> ApiResult { if let Ok(my_wallets) = lock_connected_user() { - Ok(my_wallets.try_get_main()?.get_client().get_receiving_address()) + Ok(my_wallets + .try_get_main()? + .get_client() + .get_receiving_address()) } else { Err(ApiError { message: "Unknown user pre_id".to_owned(), @@ -322,9 +338,28 @@ pub fn check_transaction_for_silent_payments( let tx = deserialize::(&Vec::from_hex(&tx_hex)?)?; let tweak_data = PublicKey::from_str(&tweak_data_hex)?; - let txid = check_transaction(&tx, blockheight, tweak_data)?; + let mut connected_user = lock_connected_user()?; + if let Ok(recover) = connected_user.try_get_mut_recover() { + if let Ok(txid) = check_transaction(&tx, recover, blockheight, tweak_data) { + return Ok(txid); + } + } - Ok(txid) + if let Ok(main) = connected_user.try_get_mut_main() { + if let Ok(txid) = check_transaction(&tx, main, blockheight, tweak_data) { + return Ok(txid); + } + } + + if let Ok(revoke) = connected_user.try_get_mut_revoke() { + if let Ok(txid) = check_transaction(&tx, revoke, blockheight, tweak_data) { + return Ok(txid); + } + } + + Err(ApiError { + message: "No output found".to_owned(), + }) } #[derive(Tsify, Serialize, Deserialize)] @@ -341,14 +376,16 @@ pub fn parse_network_msg(raw: String) -> ApiResult { match ank_msg.flag { AnkFlag::NewTx => { let tx_message = serde_json::from_str::(&ank_msg.content)?; - let tx = deserialize::(&Vec::from_hex(&tx_message.transaction)?)?; if tx_message.tweak_data.is_none() { return Err(ApiError { message: "Missing tweak_data".to_owned(), }); } - let partial_tweak = PublicKey::from_str(&tx_message.tweak_data.unwrap())?; - let txid = check_transaction(&tx, 0, partial_tweak)?; + let txid = check_transaction_for_silent_payments( + tx_message.transaction, + 0, + tx_message.tweak_data.unwrap(), + )?; return Ok(parseNetworkMsgReturn { topic: AnkFlag::NewTx.as_str().to_owned(), message: txid, @@ -376,7 +413,27 @@ pub fn get_outpoints_for_user() -> ApiResult { if connected_user.is_not_empty() { Ok(outputs_list(connected_user.get_all_outputs())) } else { - Err(ApiError { message: "No user logged in".to_owned() }) + Err(ApiError { + message: "No user logged in".to_owned(), + }) + } +} + +#[wasm_bindgen] +pub fn get_available_amount_for_user(recover: bool) -> ApiResult { + let connected_user = lock_connected_user()?; + if recover { + if let Ok(recover_wallet) = connected_user.try_get_recover() { + Ok(recover_wallet.get_outputs().get_balance().to_sat()) + } else { + Err(ApiError { + message: "User doesn't have recover wallet available".to_owned(), + }) + } + } else { + Err(ApiError { + message: "No user logged in".to_owned(), + }) } } @@ -403,28 +460,117 @@ pub fn is_tx_owned_by_user(pre_id: String, tx: String) -> ApiResult { #[tsify(into_wasm_abi, from_wasm_abi)] #[allow(non_camel_case_types)] pub struct createNotificationTransactionReturn { + pub txid: String, pub transaction: String, - pub transaction2secret: ScannedTransaction, + pub address2secret: HashMap, } #[wasm_bindgen] pub fn create_notification_transaction( recipient: String, message: String, + fee_rate: u32, ) -> ApiResult { let sp_address: SilentPaymentAddress = recipient.try_into()?; - let (transaction, shared_secret) = - create_transaction_for_address_with_shared_secret(sp_address, message)?; + let connected_user = lock_connected_user()?; + let sp_wallet: &SpWallet; + if sp_address.is_testnet() { + sp_wallet = connected_user.try_get_recover()?; + } else { + sp_wallet = connected_user.try_get_main()?; + } + + let (transaction, shared_secret) = create_transaction_for_address_with_shared_secret( + sp_address, + sp_wallet, + message, + Amount::from_sat(fee_rate.into()), + )?; + + let mut address2secret: Vec<(String, AnkSharedSecret)> = vec![]; + address2secret.push((sp_address.into(), shared_secret)); // update our cache - - let mut transaction2secret = ScannedTransaction::new(); - transaction2secret.get_mut().insert(transaction.txid(), vec![shared_secret]); + lock_scanned_transactions()? + .insert(transaction.txid(), address2secret.clone()); Ok(createNotificationTransactionReturn { + txid: transaction.txid().to_string(), transaction: serialize(&transaction).to_lower_hex_string(), - transaction2secret, + address2secret: address2secret.into_iter().collect() }) } + +#[derive(Tsify, Serialize, Deserialize)] +#[tsify(into_wasm_abi, from_wasm_abi)] +#[allow(non_camel_case_types)] +pub struct encryptWithNewKeyResult { + pub cipher: String, + pub key: String, +} + +#[wasm_bindgen] +pub fn encrypt_with_key(plaintext: String, key: String) -> ApiResult { + let nonce = Aes256Gcm::generate_nonce(&mut rand::thread_rng()); + + let mut aes_key = [0u8;32]; + aes_key.copy_from_slice(&Vec::from_hex(&key)?); + + // encrypt + let aes_enc = Aes256Encryption::import_key( + Purpose::Arbitrary, + plaintext.into_bytes(), + aes_key, + nonce.into(), + )?; + + let cipher = aes_enc.encrypt_with_aes_key()?; + Ok(String::from_utf8(cipher)?) +} + +#[wasm_bindgen] +pub fn encrypt_with_new_key(plaintext: String) -> ApiResult { + let mut rng = rand::thread_rng(); + + // generate new key + let aes_key = Aes256Gcm::generate_key(&mut rng); + let nonce = Aes256Gcm::generate_nonce(&mut rng); + + // encrypt + let aes_enc = Aes256Encryption::import_key( + Purpose::Arbitrary, + plaintext.into_bytes(), + aes_key.into(), + nonce.into(), + )?; + + let cipher = aes_enc.encrypt_with_aes_key()?; + + Ok(encryptWithNewKeyResult { + cipher: cipher.to_lower_hex_string(), + key: aes_key.to_lower_hex_string(), + }) +} + +#[wasm_bindgen] +pub fn try_decrypt_with_key( + cipher: String, + key: String, +) -> ApiResult { + let key_bin = Vec::from_hex(&key)?; + if key_bin.len() != 32 { + return Err(ApiError { message: "key of invalid lenght".to_owned() }); + } + let mut aes_key = [0u8;32]; + aes_key.copy_from_slice(&Vec::from_hex(&key)?); + let aes_dec = Aes256Decryption::new( + Purpose::Arbitrary, + Vec::from_hex(&cipher)?, + aes_key + )?; + + let plain = String::from_utf8(aes_dec.decrypt_with_key()?)?; + Ok(plain) +} diff --git a/crates/sp_client/src/lib.rs b/crates/sp_client/src/lib.rs index c0056a6..8d7155c 100644 --- a/crates/sp_client/src/lib.rs +++ b/crates/sp_client/src/lib.rs @@ -1,16 +1,31 @@ #![allow(warnings)] use anyhow::Error; +use sdk_common::crypto::AnkSharedSecret; +use serde::{Deserialize, Serialize}; +use sp_client::bitcoin::Txid; +use sp_client::silentpayments::sending::SilentPaymentAddress; +use std::collections::HashMap; use std::fmt::Debug; -use std::sync::{Mutex, MutexGuard}; +use std::sync::{Mutex, MutexGuard, OnceLock}; +use tsify::Tsify; mod Prd_list; pub mod api; mod images; mod peers; mod process; -mod silentpayments; mod user; +pub type Txid2Secrets = HashMap>; + +pub static TRANSACTIONCACHE: OnceLock> = OnceLock::new(); + +pub fn lock_scanned_transactions() -> Result, Error> { + TRANSACTIONCACHE + .get_or_init(|| Mutex::new(Txid2Secrets::new())) + .lock_anyhow() +} + pub(crate) trait MutexExt { fn lock_anyhow(&self) -> Result, Error>; } diff --git a/crates/sp_client/src/silentpayments.rs b/crates/sp_client/src/silentpayments.rs deleted file mode 100644 index aee9616..0000000 --- a/crates/sp_client/src/silentpayments.rs +++ /dev/null @@ -1,217 +0,0 @@ -// This file should move to common - -use std::collections::{HashMap, HashSet}; -use std::io::Write; -use std::iter::Once; -use std::sync::{Mutex, MutexGuard, OnceLock}; - -use anyhow::{Error, Result}; - -use log::debug; -use rand::Rng; -use sdk_common::crypto::{Aes256Encryption, Aes256Gcm, AeadCore, Purpose}; -use serde::{Deserialize, Serialize}; -use sp_client::bitcoin::policy::DUST_RELAY_TX_FEE; -use sp_client::bitcoin::secp256k1::ecdh::shared_secret_point; -use sp_client::bitcoin::{block, Amount, OutPoint, Txid}; -use sp_client::silentpayments::sending::SilentPaymentAddress; -use sp_client::silentpayments::utils::receiving::calculate_shared_secret; -use sp_client::spclient::{OutputList, OwnedOutput, Recipient, SpClient, SpWallet}; -use sp_client::silentpayments::bitcoin_hashes::{sha256, Hash, HashEngine}; -use sp_client::{ - bitcoin::{ - secp256k1::{PublicKey, Scalar, XOnlyPublicKey}, - Transaction, - }, - silentpayments::receiving::Label, -}; -use tsify::Tsify; - -use crate::user::{lock_connected_user, CONNECTED_USER}; -use crate::MutexExt; - -#[derive(Debug, Serialize, Deserialize, Tsify)] -#[tsify(into_wasm_abi, from_wasm_abi)] -pub struct ScannedTransaction(HashMap>); - -impl ScannedTransaction { - pub fn new() -> Self { - Self(HashMap::new()) - } - - pub fn get_mut(&mut self) -> &mut HashMap> { - &mut self.0 - } -} - -pub static TRANSACTIONCACHE: OnceLock> = OnceLock::new(); - -pub fn lock_scanned_transactions() -> Result> { - TRANSACTIONCACHE - .get_or_init(|| Mutex::new(ScannedTransaction::new())) - .lock_anyhow() -} - -type FoundOutputs = HashMap, HashMap>; - -#[derive(Debug)] -pub struct SharedPoint([u8; 64]); - -impl SharedPoint { - pub fn as_inner(&self) -> &[u8; 64] { - &self.0 - } -} - -#[derive(Debug, Serialize, Deserialize, Tsify)] -#[tsify(from_wasm_abi, into_wasm_abi)] -pub struct SharedSecret { - secret: [u8; 32], - shared_with: Option, // SilentPaymentAddress -} - -impl SharedSecret { - pub fn new(secret: [u8;32], shared_with: Option) -> Result { - if let Some(ref shared) = shared_with { - if let Ok(_) = SilentPaymentAddress::try_from(shared.as_str()) { - Ok(Self { - secret, - shared_with - }) - } else { - Err(Error::msg("Invalid silent payment address")) - } - } else { - Ok(Self { - secret, - shared_with: None - }) - } - } -} - -pub fn create_transaction_for_address_with_shared_secret( - sp_address: SilentPaymentAddress, - message: String, -) -> Result<(Transaction, SharedSecret)> { - let connected_user = lock_connected_user()?; - - let sp_wallet = if sp_address.is_testnet() { - connected_user.try_get_recover()? - } else { - if let Ok(main) = connected_user.try_get_main() { - main - } else { - return Err(Error::msg("Can't spend on mainnet")); - } - }; - - let available_outpoints = sp_wallet.get_outputs().to_spendable_list(); - - // Here we need to add more heuristics about which outpoint we spend - // For now let's keep it simple - - let mut inputs: HashMap = HashMap::new(); - - let total_available = - available_outpoints - .into_iter() - .try_fold(Amount::from_sat(0), |acc, (outpoint, output)| { - let new_total = acc + output.amount; - inputs.insert(outpoint, output); - if new_total > Amount::from_sat(1000) { - Err(new_total) - } else { - Ok(new_total) - } - }); - - match total_available { - Err(total) => log::debug!("Spending {} outputs totaling {} sats", inputs.len(), total), - Ok(_) => return Err(Error::msg("Not enought fund available")), - } - - let recipient = Recipient { - address: sp_address.into(), - amount: Amount::from_sat(1000), - nb_outputs: 1, - }; - - let mut new_psbt = sp_wallet - .get_client() - .create_new_psbt(inputs, vec![recipient], Some(message.as_bytes()))?; - log::debug!("Created psbt: {}", new_psbt); - SpClient::set_fees(&mut new_psbt, Amount::from_sat(1000), sp_address.into())?; - - let partial_secret = sp_wallet - .get_client() - .get_partial_secret_from_psbt(&new_psbt)?; - - // This wouldn't work with many recipients in the same transaction - // each address (or more precisely each scan public key) would have its own point - let shared_point = shared_secret_point(&sp_address.get_scan_key(), &partial_secret); - - // The shared_point *must* be hashed to produce a proper ecdh secret - let mut eng = sha256::HashEngine::default(); - eng.write_all(&shared_point); - let shared_secret = sha256::Hash::from_engine(eng); - - // encrypt the message with the new shared_secret - let message_encryption = Aes256Encryption::import_key( - Purpose::Arbitrary, - message.into_bytes(), - shared_secret.to_byte_array(), - Aes256Gcm::generate_nonce(&mut rand::thread_rng()).into(), - )?; - - let cipher = message_encryption.encrypt_with_aes_key()?; - - sp_client::spclient::SpClient::replace_op_return_with(&mut new_psbt, &cipher)?; - - sp_wallet - .get_client() - .fill_sp_outputs(&mut new_psbt, partial_secret)?; - log::debug!("Definitive psbt: {}", new_psbt); - let mut aux_rand = [0u8; 32]; - rand::thread_rng().fill(&mut aux_rand); - let mut signed = sp_wallet.get_client().sign_psbt(new_psbt, &aux_rand)?; - log::debug!("signed psbt: {}", signed); - SpClient::finalize_psbt(&mut signed)?; - - let final_tx = signed.extract_tx()?; - - Ok((final_tx, SharedSecret { - secret: shared_secret.to_byte_array(), - shared_with: Some(sp_address.into()) - })) -} - -// This need to go -pub fn check_transaction( - tx: &Transaction, - blockheight: u32, - tweak_data: PublicKey, -) -> Result { - // first check that we haven't scanned this transaction yet - if let Some((txid, _)) = lock_scanned_transactions()?.0.get_key_value(&tx.txid()) { - let err_msg = format!("Already scanned tx {}", txid); - return Err(Error::msg(err_msg)); - } - - let mut connected_user = lock_connected_user()?; - - let txid = tx.txid().to_string(); - if connected_user.try_get_mut_recover()?.update_wallet_with_transaction(tx, blockheight, tweak_data)? > 0 { - return Ok(txid); - } - - if connected_user.try_get_mut_main()?.update_wallet_with_transaction(tx, blockheight, tweak_data)? > 0 { - return Ok(txid); - } - - if connected_user.try_get_mut_revoke()?.update_wallet_with_transaction(tx, blockheight, tweak_data)? > 0 { - return Ok(txid); - } - - return Err(Error::msg("No new outputs found")); -} diff --git a/crates/sp_client/src/user.rs b/crates/sp_client/src/user.rs index 37de212..758c4f2 100644 --- a/crates/sp_client/src/user.rs +++ b/crates/sp_client/src/user.rs @@ -52,7 +52,11 @@ pub struct UserWallets { } impl UserWallets { - pub fn new(main: Option, recover: Option, revoke: Option) -> Self { + pub fn new( + main: Option, + recover: Option, + revoke: Option, + ) -> Self { Self { main, recover, diff --git a/src/index.ts b/src/index.ts index f0304ea..d379dd8 100644 --- a/src/index.ts +++ b/src/index.ts @@ -1,7 +1,7 @@ import Services from './services'; import { WebSocketClient } from './websockets'; -const wsurl = "ws://192.168.1.44:8090"; +const wsurl = "ws://localhost:8090"; document.addEventListener('DOMContentLoaded', async () => { try { const services = await Services.getInstance(); diff --git a/src/services.ts b/src/services.ts index 5a3344e..802c089 100644 --- a/src/services.ts +++ b/src/services.ts @@ -1,4 +1,4 @@ -import { createUserReturn, User, Process, createNotificationTransactionReturn, parse_network_msg, outputs_list, parseNetworkMsgReturn, FaucetMessage, AnkFlag, NewTxMessage } from '../dist/pkg/sdk_client'; +import { createUserReturn, User, Process, createNotificationTransactionReturn, parse_network_msg, outputs_list, parseNetworkMsgReturn, FaucetMessage, AnkFlag, NewTxMessage, encryptWithNewKeyResult, AnkSharedSecret } from '../dist/pkg/sdk_client'; import IndexedDB from './database' import { WebSocketClient } from './websockets'; @@ -74,6 +74,26 @@ class Services { public async sendMessage(event: Event): Promise { event.preventDefault(); + const services = await Services.getInstance(); + let availableAmt: number = 0; + + // check available amount + try { + availableAmt = await services.sdkClient.get_available_amount_for_user(true); + } catch (error) { + console.error('Failed to get available amount'); + return; + } + + if (availableAmt < 2000) { + try { + await services.obtainTokenWithFaucet(this.sp_address!); + } catch (error) { + console.error('Failed to obtain faucet token:', error); + return; + } + } + const spAddressElement = document.getElementById("sp_address") as HTMLInputElement; const messageElement = document.getElementById("message") as HTMLInputElement; @@ -84,11 +104,18 @@ class Services { const recipientSpAddress = spAddressElement.value; const message = messageElement.value; - const services = await Services.getInstance(); let notificationInfo = await services.notify_address_for_message(recipientSpAddress, message); if (notificationInfo) { - notificationInfo.transaction + console.info('Successfully sent notification transaction'); + // Save the secret to db + // encrypt the message(s) + services.encryptData(message, notificationInfo.address2secret); + // encrypt the key + + // add peers list + // add processes list + // send message (transaction in envelope) } console.log(notificationInfo); } @@ -151,7 +178,7 @@ class Services { } try { - this.sp_address = services.sdkClient.get_receiving_address(user.pre_id); + this.sp_address = services.sdkClient.get_recover_address(); if (this.sp_address) { console.info('Using sp_address:', this.sp_address); await services.obtainTokenWithFaucet(this.sp_address); @@ -196,12 +223,14 @@ class Services { const user = await services.getUserInfo(); if (user) { services.sdkClient.login_user(password, user.pre_id, user.recover_data, user.shares, user.outputs); - this.sp_address = services.sdkClient.get_receiving_address(user?.pre_id); + this.sp_address = services.sdkClient.get_recover_address(); } } catch (error) { console.error(error); } + console.info(this.sp_address); + // TODO: check blocks since last_scan and update outputs await services.displaySendMessage(); @@ -341,6 +370,7 @@ class Services { let user = await services.getUserInfo(); if (user) { user.outputs = latest_outputs; + // console.warn(user); await services.updateUser(user); } } catch (error) { @@ -772,7 +802,8 @@ class Services { } try { - let notificationInfo: createNotificationTransactionReturn = services.sdkClient.create_notification_transaction(user, sp_address, message); + const feeRate = 1000; + let notificationInfo: createNotificationTransactionReturn = services.sdkClient.create_notification_transaction(sp_address, message, feeRate); const flag: AnkFlag = "NewTx"; const newTxMsg: NewTxMessage = { 'transaction': notificationInfo.transaction, @@ -785,6 +816,37 @@ class Services { return null } } + + public async encryptData(data: string, sharedSecret: Record): Promise { + const services = await Services.getInstance(); + let msg_cipher: encryptWithNewKeyResult; + try { + msg_cipher = services.sdkClient.encrypt_with_new_key(data); + } catch (error) { + throw error; + } + + let res = new Map(); + for (const [recipient, secret] of Object.entries(sharedSecret)) { + try { + const key = secret.secret; + const encryptedKey = await services.sdkClient.encrypt_with_key(msg_cipher.key, key); + res.set(recipient, encryptedKey); + } catch (error) { + throw new Error(`Failed to encrypt key for recipient ${recipient}: ${error}`); + } + } +} + + public async decryptData(cipher: string, key: string): Promise { + const services = await Services.getInstance(); + try { + let res = services.sdkClient.try_decrypt_with_key(cipher, key); + return res; + } catch (error) { + throw error; + } + } } export default Services; diff --git a/src/websockets.ts b/src/websockets.ts index c94b4af..c4fc134 100644 --- a/src/websockets.ts +++ b/src/websockets.ts @@ -22,7 +22,6 @@ class WebSocketClient { // Listen for messages this.ws.addEventListener('message', (event) => { const msgData = event.data; - console.log(msgData); (async () => { if (typeof(msgData) === 'string') {