From 6205bcb93dd2c96b0a760665ed5b7f4ca06ae502 Mon Sep 17 00:00:00 2001 From: benthecarman Date: Tue, 17 Oct 2023 18:28:06 -0500 Subject: [PATCH] WIP: Notification client --- mutiny-core/src/lib.rs | 39 +++++++++++++++ mutiny-core/src/nodemanager.rs | 4 ++ mutiny-core/src/notifications.rs | 84 ++++++++++++++++++++++++++++++++ mutiny-wasm/src/lib.rs | 66 +++++++++++++++++++++++++ 4 files changed, 193 insertions(+) create mode 100644 mutiny-core/src/notifications.rs diff --git a/mutiny-core/src/lib.rs b/mutiny-core/src/lib.rs index 25a8d6ca5..c41a35011 100644 --- a/mutiny-core/src/lib.rs +++ b/mutiny-core/src/lib.rs @@ -31,6 +31,7 @@ mod networking; mod node; pub mod nodemanager; pub mod nostr; +pub mod notifications; mod onchain; mod peermanager; pub mod redshift; @@ -52,6 +53,7 @@ pub use crate::ldkstorage::{CHANNEL_MANAGER_KEY, MONITORS_PREFIX_KEY}; use crate::auth::MutinyAuthClient; use crate::labels::{Contact, LabelStorage}; use crate::nostr::nwc::{NwcProfileTag, SpendingConditions}; +use crate::notifications::MutinyNotificationClient; use crate::storage::{MutinyStorage, DEVICE_ID_KEY, EXPECTED_NETWORK_KEY, NEED_FULL_SYNC_KEY}; use crate::{error::MutinyError, nostr::ReservedProfile}; use crate::{nodemanager::NodeManager, nostr::ProfileType}; @@ -59,6 +61,8 @@ use crate::{nostr::NostrManager, utils::sleep}; use ::nostr::key::XOnlyPublicKey; use ::nostr::{Event, Kind, Metadata}; use bip39::Mnemonic; +use bitcoin::hashes::hex::ToHex; +use bitcoin::hashes::{sha256, Hash}; use bitcoin::secp256k1::PublicKey; use bitcoin::util::bip32::ExtendedPrivKey; use bitcoin::Network; @@ -84,6 +88,7 @@ pub struct MutinyWalletConfig { auth_client: Option>, subscription_url: Option, scorer_url: Option, + notification_url: Option, do_not_connect_peers: bool, skip_device_lock: bool, pub safe_mode: bool, @@ -101,6 +106,7 @@ impl MutinyWalletConfig { auth_client: Option>, subscription_url: Option, scorer_url: Option, + notification_url: Option, skip_device_lock: bool, ) -> Self { Self { @@ -111,6 +117,7 @@ impl MutinyWalletConfig { user_esplora_url, user_rgs_url, scorer_url, + notification_url, lsp_url, auth_client, subscription_url, @@ -140,6 +147,7 @@ pub struct MutinyWallet { pub storage: S, pub node_manager: Arc>, pub nostr: Arc>, + pub notification_client: Option>, } impl MutinyWallet { @@ -161,6 +169,31 @@ impl MutinyWallet { NodeManager::start_sync(node_manager.clone()); + let notification_client = match config.notification_url.clone() { + Some(url) => { + let client = match config.auth_client.clone() { + Some(auth_client) => MutinyNotificationClient::new_authenticated( + auth_client, + url, + node_manager.logger.clone(), + ), + None => { + // hash key and use that as identifier + let hash = sha256::Hash::hash(&config.xprivkey.private_key.secret_bytes()); + let identifier_key = hash.to_hex(); + MutinyNotificationClient::new_unauthenticated( + url, + identifier_key, + node_manager.logger.clone(), + ) + } + }; + + Some(Arc::new(client)) + } + None => None, + }; + // create nostr manager let nostr = Arc::new(NostrManager::from_mnemonic( node_manager.xprivkey, @@ -173,6 +206,7 @@ impl MutinyWallet { storage, node_manager, nostr, + notification_client, }; #[cfg(not(test))] @@ -606,6 +640,7 @@ mod tests { None, None, None, + None, false, ); let mw = MutinyWallet::new(storage.clone(), config) @@ -636,6 +671,7 @@ mod tests { None, None, None, + None, false, ); let mut mw = MutinyWallet::new(storage.clone(), config) @@ -672,6 +708,7 @@ mod tests { None, None, None, + None, false, ); let mut mw = MutinyWallet::new(storage.clone(), config) @@ -709,6 +746,7 @@ mod tests { None, None, None, + None, false, ); let mw = MutinyWallet::new(storage.clone(), config) @@ -734,6 +772,7 @@ mod tests { None, None, None, + None, false, ); let mw2 = MutinyWallet::new(storage2.clone(), config2.clone()) diff --git a/mutiny-core/src/nodemanager.rs b/mutiny-core/src/nodemanager.rs index ee7fc10c1..493b55c18 100644 --- a/mutiny-core/src/nodemanager.rs +++ b/mutiny-core/src/nodemanager.rs @@ -2692,6 +2692,7 @@ mod tests { None, None, None, + None, false, ); NodeManager::new(c, storage.clone()) @@ -2722,6 +2723,7 @@ mod tests { None, None, None, + None, false, ); let nm = NodeManager::new(c, storage) @@ -2773,6 +2775,7 @@ mod tests { None, None, None, + None, false, ); let c = c.with_safe_mode(); @@ -2809,6 +2812,7 @@ mod tests { None, None, None, + None, false, ); let nm = NodeManager::new(c, storage) diff --git a/mutiny-core/src/notifications.rs b/mutiny-core/src/notifications.rs new file mode 100644 index 000000000..6ceba1ea8 --- /dev/null +++ b/mutiny-core/src/notifications.rs @@ -0,0 +1,84 @@ +use crate::auth::MutinyAuthClient; +use crate::{error::MutinyError, logging::MutinyLogger}; +use anyhow::anyhow; +use lightning::util::logger::*; +use lightning::{log_error, log_info}; +use reqwest::{Method, Url}; +use serde_json::{json, Value}; +use std::sync::Arc; + +#[derive(Clone)] +pub struct MutinyNotificationClient { + auth_client: Option>, + client: Option, + url: String, + id: Option, + pub logger: Arc, +} + +impl MutinyNotificationClient { + pub fn new_authenticated( + auth_client: Arc, + url: String, + logger: Arc, + ) -> Self { + log_info!(logger, "Creating authenticated notification client"); + Self { + auth_client: Some(auth_client), + client: None, + url, + id: None, // we get this from the auth client + logger, + } + } + + pub fn new_unauthenticated( + url: String, + identifier_key: String, + logger: Arc, + ) -> Self { + log_info!(logger, "Creating unauthenticated notification client"); + Self { + auth_client: None, + client: Some(reqwest::Client::new()), + url, + id: Some(identifier_key), + logger, + } + } + + async fn make_request( + &self, + method: Method, + url: Url, + body: Option, + ) -> Result { + match (self.auth_client.as_ref(), self.client.as_ref()) { + (Some(auth), _) => auth.request(method, url, body).await, + (None, Some(client)) => { + let mut request = client.request(method, url); + if let Some(body) = body { + request = request.json(&body); + } + request.send().await.map_err(|e| { + log_error!(self.logger, "Error making request: {e}"); + MutinyError::Other(anyhow!("Error making request: {e}")) + }) + } + (None, None) => unreachable!("No auth client or http client"), + } + } + + pub async fn register(&self, info: Value) -> Result<(), MutinyError> { + let url = Url::parse(&format!("{}/register", self.url)).map_err(|e| { + log_error!(self.logger, "Error parsing register url: {e}"); + MutinyError::InvalidArgumentsError + })?; + + let body = json!({"id": self.id, "info": info}); + + self.make_request(Method::POST, url, Some(body)).await?; + + Ok(()) + } +} diff --git a/mutiny-wasm/src/lib.rs b/mutiny-wasm/src/lib.rs index 60cea72eb..d5eac419c 100644 --- a/mutiny-wasm/src/lib.rs +++ b/mutiny-wasm/src/lib.rs @@ -30,6 +30,7 @@ use lnurl::lnurl::LnUrl; use mutiny_core::auth::MutinyAuthClient; use mutiny_core::lnurlauth::AuthManager; use mutiny_core::nostr::nwc::{BudgetedSpendingConditions, NwcProfileTag, SpendingConditions}; +use mutiny_core::notifications::MutinyNotificationClient; use mutiny_core::redshift::RedshiftManager; use mutiny_core::redshift::RedshiftRecipient; use mutiny_core::scb::EncryptedSCB; @@ -80,6 +81,7 @@ impl MutinyWallet { subscription_url: Option, storage_url: Option, scorer_url: Option, + notification_url: Option, do_not_connect_peers: Option, skip_device_lock: Option, safe_mode: Option, @@ -174,6 +176,7 @@ impl MutinyWallet { auth_client, subscription_url, scorer_url, + notification_url, skip_device_lock.unwrap_or(false), ); @@ -1356,6 +1359,64 @@ impl MutinyWallet { Ok(self.inner.reset_onchain_tracker().await?) } + /// Register the wallet for web-push notifications + #[wasm_bindgen] + pub async fn register_web_push(&self, info: JsValue) -> Result<(), MutinyJsError> { + match self.inner.notification_client.as_ref() { + Some(client) => { + let info = info.into_serde()?; + client.register(info).await?; + } + None => return Err(MutinyJsError::NotFound), + } + + Ok(()) + } + + /// test function for now, will delete in future + #[wasm_bindgen] + pub async fn test_register_web_push( + auth_url: String, + notification_url: String, + info: JsValue, + ) -> Result<(), MutinyJsError> { + let logger = Arc::new(MutinyLogger::default()); + let storage = IndexedDbStorage::new(None, None, None, logger.clone()).await?; + + let mnemonic = storage.get_mnemonic()?.unwrap(); + + let seed = mnemonic.to_seed(""); + let xprivkey = ExtendedPrivKey::new_master(Network::Signet, &seed).unwrap(); + + let auth_manager = AuthManager::new(xprivkey).unwrap(); + + let lnurl_client = Arc::new( + lnurl::Builder::default() + .build_async() + .expect("failed to make lnurl client"), + ); + + let auth_client = Arc::new(MutinyAuthClient::new( + auth_manager, + lnurl_client, + logger.clone(), + auth_url, + )); + + let notification_client = MutinyNotificationClient::new_authenticated( + auth_client, + notification_url, + logger.clone(), + ); + + let info = info.into_serde()?; + notification_client.register(info).await?; + + log::info!("Registered!"); + + Ok(()) + } + /// Exports the current state of the node manager to a json object. #[wasm_bindgen] pub async fn export_json(password: Option) -> Result { @@ -1485,6 +1546,7 @@ mod tests { None, None, None, + None, ) .await .expect("mutiny wallet should initialize"); @@ -1519,6 +1581,7 @@ mod tests { None, None, None, + None, ) .await .unwrap(); @@ -1556,6 +1619,7 @@ mod tests { None, None, None, + None, ) .await .expect("mutiny wallet should initialize"); @@ -1621,6 +1685,7 @@ mod tests { None, None, None, + None, ) .await .expect("mutiny wallet should initialize"); @@ -1674,6 +1739,7 @@ mod tests { None, None, None, + None, ) .await .expect("mutiny wallet should initialize");