From ab7ece7a351fe783b4c3de9b94ed90d95eff7a40 Mon Sep 17 00:00:00 2001 From: Yuki Kishimoto Date: Tue, 23 Jan 2024 19:20:45 +0100 Subject: [PATCH] Refactoring --- webln-js/src/request_invoice.rs | 6 +- webln/src/lib.rs | 271 +++++++++++++++++--------------- 2 files changed, 150 insertions(+), 127 deletions(-) diff --git a/webln-js/src/request_invoice.rs b/webln-js/src/request_invoice.rs index 86a6e9d..0c53dba 100644 --- a/webln-js/src/request_invoice.rs +++ b/webln-js/src/request_invoice.rs @@ -73,8 +73,8 @@ impl From for JsRequestInvoiceResponse { #[wasm_bindgen(js_class = RequestInvoiceResponse)] impl JsRequestInvoiceResponse { - #[wasm_bindgen(getter)] - pub fn invoice(&self) -> String { - self.inner.invoice.clone() + #[wasm_bindgen(getter, js_name = paymentRequest)] + pub fn payment_request(&self) -> String { + self.inner.payment_request.clone() } } diff --git a/webln/src/lib.rs b/webln/src/lib.rs index f854090..9034ba9 100644 --- a/webln/src/lib.rs +++ b/webln/src/lib.rs @@ -91,6 +91,24 @@ impl From for Error { } } +/// Get value from object key +fn get_value_by_key(obj: &Object, key: &str) -> Result { + Reflect::get(obj, &JsValue::from_str(key)) + .map_err(|_| Error::ObjectKeyNotFound(key.to_string())) +} + +trait Deserialize: Sized { + fn deserialize(value: JsValue) -> Result; +} + +impl Deserialize for bool { + fn deserialize(value: JsValue) -> Result { + value + .as_bool() + .ok_or_else(|| Error::TypeMismatch(String::from("expected a bool"))) + } +} + /// Get Info Node Response #[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)] pub struct GetInfoNode { @@ -179,6 +197,36 @@ pub struct GetInfoResponse { pub methods: Vec, } +impl Deserialize for GetInfoResponse { + fn deserialize(value: JsValue) -> Result { + let get_info_obj: Object = value.dyn_into().map_err(|_| Error::SomethingGoneWrong)?; + + let node_obj: Object = get_value_by_key(&get_info_obj, "node")? + .dyn_into() + .map_err(|_| Error::SomethingGoneWrong)?; + + // Extract data + let alias: Option = get_value_by_key(&node_obj, "alias")?.as_string(); + let pubkey: Option = get_value_by_key(&node_obj, "pubkey")?.as_string(); + let color: Option = get_value_by_key(&node_obj, "color")?.as_string(); + let methods_array: Array = get_value_by_key(&get_info_obj, "methods")?.into(); + let methods: Vec = methods_array + .into_iter() + .filter_map(|m| m.as_string()) + .map(|m| GetInfoMethod::from(m.as_str())) + .collect(); + + Ok(Self { + node: GetInfoNode { + alias, + pubkey, + color, + }, + methods, + }) + } +} + /// Keysend args #[derive(Debug, Clone, Copy, PartialEq, Eq)] pub struct KeysendArgs { @@ -199,6 +247,16 @@ pub struct SendPaymentResponse { pub preimage: String, } +impl Deserialize for SendPaymentResponse { + fn deserialize(value: JsValue) -> Result { + let send_payment_obj: Object = value.dyn_into().map_err(|_| Error::SomethingGoneWrong)?; + let preimage = get_value_by_key(&send_payment_obj, "preimage")? + .as_string() + .ok_or_else(|| Error::TypeMismatch(String::from("expected a string [preimage]")))?; + Ok(Self { preimage }) + } +} + /// Send Multi Payment Single response #[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)] pub struct SendMultiPaymentSingle { @@ -217,6 +275,24 @@ pub struct SendMultiPaymentError { pub message: String, } +impl Deserialize for SendMultiPaymentError { + fn deserialize(value: JsValue) -> Result { + let error_obj: Object = value.dyn_into().map_err(|_| Error::SomethingGoneWrong)?; + let payment_request = get_value_by_key(&error_obj, "paymentRequest")? + .as_string() + .ok_or_else(|| { + Error::TypeMismatch(String::from("expected a string [paymentRequest]")) + })?; + let message = get_value_by_key(&error_obj, "message")? + .as_string() + .ok_or_else(|| Error::TypeMismatch(String::from("expected a string [message]")))?; + Ok(Self { + payment_request, + message, + }) + } +} + /// Send Payment Response #[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)] pub struct SendMultiPaymentResponse { @@ -226,6 +302,29 @@ pub struct SendMultiPaymentResponse { pub errors: Vec, } +impl Deserialize for SendMultiPaymentResponse { + fn deserialize(value: JsValue) -> Result { + let obj: Object = value.dyn_into().map_err(|_| Error::SomethingGoneWrong)?; + + // let js_payments: Array = self + // .get_value_by_key(&obj, "payments")? + // .dyn_into()?; + let js_errors: Array = get_value_by_key(&obj, "errors")?.dyn_into()?; + + // Deserialize errors + let mut errors: Vec = + Vec::with_capacity(js_errors.length() as usize); + for error in js_errors.into_iter() { + errors.push(SendMultiPaymentError::deserialize(error)?); + } + + Ok(Self { + payments: Vec::new(), // TODO + errors, + }) + } +} + /// Request invoice args /// /// **All amounts are denominated in SAT.** @@ -334,7 +433,20 @@ impl TryFrom<&RequestInvoiceArgs> for Object { #[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)] pub struct RequestInvoiceResponse { /// BOLT-11 invoice - pub invoice: String, + pub payment_request: String, +} + +impl Deserialize for RequestInvoiceResponse { + fn deserialize(value: JsValue) -> Result { + let obj: Object = value.dyn_into().map_err(|_| Error::SomethingGoneWrong)?; + Ok(Self { + payment_request: get_value_by_key(&obj, "paymentRequest")? + .as_string() + .ok_or_else(|| { + Error::TypeMismatch(String::from("expected a string [paymentRequest]")) + })?, + }) + } } /// Sign Message Response @@ -346,6 +458,19 @@ pub struct SignMessageResponse { pub signature: String, } +impl Deserialize for SignMessageResponse { + fn deserialize(value: JsValue) -> Result { + let obj: Object = value.dyn_into().map_err(|_| Error::SomethingGoneWrong)?; + let message: String = get_value_by_key(&obj, "message")? + .as_string() + .ok_or_else(|| Error::TypeMismatch(String::from("expected a string [message]")))?; + let signature: String = get_value_by_key(&obj, "signature")? + .as_string() + .ok_or_else(|| Error::TypeMismatch(String::from("expected a string [signature]")))?; + Ok(Self { message, signature }) + } +} + /// Balance Response #[derive(Debug, Clone, PartialEq, PartialOrd)] pub struct BalanceResponse { @@ -355,6 +480,19 @@ pub struct BalanceResponse { pub currency: Option, } +impl Deserialize for BalanceResponse { + fn deserialize(value: JsValue) -> Result { + let balance_response_obj: Object = + value.dyn_into().map_err(|_| Error::SomethingGoneWrong)?; + let balance: f64 = get_value_by_key(&balance_response_obj, "balance")? + .as_f64() + .ok_or_else(|| Error::TypeMismatch(String::from("expected a number [balance]")))?; + let currency: Option = + get_value_by_key(&balance_response_obj, "currency")?.as_string(); + Ok(Self { balance, currency }) + } +} + /// WebLN instance #[derive(Debug, Clone)] pub struct WebLN { @@ -381,21 +519,13 @@ impl WebLN { .map_err(|_| Error::NamespaceNotFound(name.to_string())) } - /// Get value from object key - fn get_value_by_key(&self, obj: &Object, key: &str) -> Result { - Reflect::get(obj, &JsValue::from_str(key)) - .map_err(|_| Error::ObjectKeyNotFound(key.to_string())) - } - /// Check if `webln` is enabled without explicitly enabling it through `webln.enable()` /// (which may cause a confirmation popup in some providers) pub async fn is_enabled(&self) -> Result { let func: Function = self.get_func(&self.webln_obj, IS_ENABLED)?; let promise: Promise = Promise::resolve(&func.call0(&self.webln_obj)?); let result: JsValue = JsFuture::from(promise).await?; - result - .as_bool() - .ok_or_else(|| Error::TypeMismatch(String::from("expected a bool"))) + bool::deserialize(result) } /// To begin interacting with WebLN APIs you'll first need to enable the provider. @@ -413,32 +543,7 @@ impl WebLN { let func: Function = self.get_func(&self.webln_obj, GET_INFO)?; let promise: Promise = Promise::resolve(&func.call0(&self.webln_obj)?); let result: JsValue = JsFuture::from(promise).await?; - let get_info_obj: Object = result.dyn_into().map_err(|_| Error::SomethingGoneWrong)?; - - let node_obj: Object = self - .get_value_by_key(&get_info_obj, "node")? - .dyn_into() - .map_err(|_| Error::SomethingGoneWrong)?; - - // Extract data - let alias: Option = self.get_value_by_key(&node_obj, "alias")?.as_string(); - let pubkey: Option = self.get_value_by_key(&node_obj, "pubkey")?.as_string(); - let color: Option = self.get_value_by_key(&node_obj, "color")?.as_string(); - let methods_array: Array = self.get_value_by_key(&get_info_obj, "methods")?.into(); - let methods: Vec = methods_array - .into_iter() - .filter_map(|m| m.as_string()) - .map(|m| GetInfoMethod::from(m.as_str())) - .collect(); - - Ok(GetInfoResponse { - node: GetInfoNode { - alias, - pubkey, - color, - }, - methods, - }) + GetInfoResponse::deserialize(result) } /// Request the user to send a keysend payment. @@ -460,14 +565,7 @@ impl WebLN { let promise: Promise = Promise::resolve(&func.call1(&self.webln_obj, &keysend_obj.into())?); let result: JsValue = JsFuture::from(promise).await?; - let send_payment_obj: Object = result.dyn_into().map_err(|_| Error::SomethingGoneWrong)?; - - Ok(SendPaymentResponse { - preimage: self - .get_value_by_key(&send_payment_obj, "preimage")? - .as_string() - .ok_or_else(|| Error::TypeMismatch(String::from("expected a string [preimage]")))?, - }) + SendPaymentResponse::deserialize(result) } /// Request that the user creates an invoice to be used by the web app @@ -476,22 +574,11 @@ impl WebLN { args: &RequestInvoiceArgs, ) -> Result { let func: Function = self.get_func(&self.webln_obj, MAKE_INVOICE)?; - let request_invoice_obj: Object = args.try_into()?; - let promise: Promise = Promise::resolve(&func.call1(&self.webln_obj, &request_invoice_obj.into())?); let result: JsValue = JsFuture::from(promise).await?; - let request_invoice_response_obj: Object = - result.dyn_into().map_err(|_| Error::SomethingGoneWrong)?; - Ok(RequestInvoiceResponse { - invoice: self - .get_value_by_key(&request_invoice_response_obj, "paymentRequest")? - .as_string() - .ok_or_else(|| { - Error::TypeMismatch(String::from("expected a string [paymentRequest]")) - })?, - }) + RequestInvoiceResponse::deserialize(result) } /// Request that the user sends a payment for an invoice. @@ -505,13 +592,7 @@ impl WebLN { let func: Function = self.get_func(&self.webln_obj, SEND_PAYMENT)?; let promise: Promise = Promise::resolve(&func.call1(&self.webln_obj, &invoice.into())?); let result: JsValue = JsFuture::from(promise).await?; - let send_payment_obj: Object = result.dyn_into().map_err(|_| Error::SomethingGoneWrong)?; - Ok(SendPaymentResponse { - preimage: self - .get_value_by_key(&send_payment_obj, "preimage")? - .as_string() - .ok_or_else(|| Error::TypeMismatch(String::from("expected a string [preimage]")))?, - }) + SendPaymentResponse::deserialize(result) } /// Request that the user sends multiple payments. @@ -530,41 +611,7 @@ impl WebLN { let func: Function = self.get_func(&self.webln_obj, SEND_MULTI_PAYMENT)?; let promise: Promise = Promise::resolve(&func.call1(&self.webln_obj, &invoices.into())?); let result: JsValue = JsFuture::from(promise).await?; - let send_multi_payment_obj: Object = - result.dyn_into().map_err(|_| Error::SomethingGoneWrong)?; - - // let js_payments: Array = self - // .get_value_by_key(&send_multi_payment_obj, "payments")? - // .dyn_into()?; - let js_errors: Array = self - .get_value_by_key(&send_multi_payment_obj, "errors")? - .dyn_into()?; - - // Deserialize errors - let mut errors: Vec = - Vec::with_capacity(js_errors.length() as usize); - for error in js_errors.into_iter() { - let error_obj: Object = error.dyn_into().map_err(|_| Error::SomethingGoneWrong)?; - let payment_request = self - .get_value_by_key(&error_obj, "paymentRequest")? - .as_string() - .ok_or_else(|| { - Error::TypeMismatch(String::from("expected a string [paymentRequest]")) - })?; - let message = self - .get_value_by_key(&error_obj, "message")? - .as_string() - .ok_or_else(|| Error::TypeMismatch(String::from("expected a string [message]")))?; - errors.push(SendMultiPaymentError { - payment_request, - message, - }); - } - - Ok(SendMultiPaymentResponse { - payments: Vec::new(), // TODO - errors, - }) + SendMultiPaymentResponse::deserialize(result) } /// Request that the user sends a payment for an invoice. @@ -594,19 +641,7 @@ impl WebLN { let func: Function = self.get_func(&self.webln_obj, SIGN_MESSAGE)?; let promise: Promise = Promise::resolve(&func.call1(&self.webln_obj, &message.into())?); let result: JsValue = JsFuture::from(promise).await?; - let sign_message_response_obj: Object = - result.dyn_into().map_err(|_| Error::SomethingGoneWrong)?; - - // Extract data - let signature: String = self - .get_value_by_key(&sign_message_response_obj, "signature")? - .as_string() - .ok_or_else(|| Error::TypeMismatch(String::from("expected a string [signature]")))?; - - Ok(SignMessageResponse { - message: message.to_string(), - signature, - }) + SignMessageResponse::deserialize(result) } /// Fetch the balance of the current account. @@ -614,18 +649,6 @@ impl WebLN { let func: Function = self.get_func(&self.webln_obj, GET_BALANCE)?; let promise: Promise = Promise::resolve(&func.call0(&self.webln_obj)?); let result: JsValue = JsFuture::from(promise).await?; - let balance_response_obj: Object = - result.dyn_into().map_err(|_| Error::SomethingGoneWrong)?; - - // Extract data - let balance: f64 = self - .get_value_by_key(&balance_response_obj, "balance")? - .as_f64() - .ok_or_else(|| Error::TypeMismatch(String::from("expected a number [balance]")))?; - let currency: Option = self - .get_value_by_key(&balance_response_obj, "currency")? - .as_string(); - - Ok(BalanceResponse { balance, currency }) + BalanceResponse::deserialize(result) } }