From 5c0c7a48fa7c1d97ca71cef93d274ef179d5d91e Mon Sep 17 00:00:00 2001 From: Alex Coats Date: Mon, 21 Aug 2023 12:41:55 -0400 Subject: [PATCH 01/12] simplify nodejs client and wallet --- bindings/nodejs/src/client.rs | 149 +++++++++++----------- bindings/nodejs/src/wallet.rs | 234 +++++++++++++++++----------------- 2 files changed, 190 insertions(+), 193 deletions(-) diff --git a/bindings/nodejs/src/client.rs b/bindings/nodejs/src/client.rs index 13697a3c0c..542affbe3b 100644 --- a/bindings/nodejs/src/client.rs +++ b/bindings/nodejs/src/client.rs @@ -1,7 +1,7 @@ // Copyright 2023 IOTA Stiftung // SPDX-License-Identifier: Apache-2.0 -use std::sync::Arc; +use std::sync::{Arc, RwLock}; use iota_sdk_bindings_core::{ call_client_method as rust_call_client_method, @@ -9,20 +9,18 @@ use iota_sdk_bindings_core::{ listen_mqtt as rust_listen_mqtt, ClientMethod, Response, Result, }; use neon::prelude::*; -use tokio::sync::RwLock; type JsCallback = Root>; -// Wrapper so we can destroy the ClientMethodHandler -pub type ClientMethodHandlerWrapperInner = Arc>>; -// Wrapper because we can't impl Finalize on ClientMethodHandlerWrapperInner -pub struct ClientMethodHandlerWrapper(pub ClientMethodHandlerWrapperInner); +pub type SharedClientMethodHandler = Arc>>; + +#[derive(Clone)] pub struct ClientMethodHandler { channel: Channel, client: Client, } -impl Finalize for ClientMethodHandlerWrapper {} +impl Finalize for ClientMethodHandler {} impl ClientMethodHandler { pub fn new(channel: Channel, options: String) -> Result { @@ -60,71 +58,69 @@ impl ClientMethodHandler { } } -pub fn create_client(mut cx: FunctionContext) -> JsResult> { +pub fn create_client(mut cx: FunctionContext) -> JsResult> { let options = cx.argument::(0)?; let options = options.value(&mut cx); let channel = cx.channel(); let method_handler = ClientMethodHandler::new(channel, options) .or_else(|e| cx.throw_error(serde_json::to_string(&Response::Error(e)).expect("json to string error")))?; - Ok(cx.boxed(ClientMethodHandlerWrapper(Arc::new(RwLock::new(Some(method_handler)))))) + Ok(cx.boxed(Arc::new(RwLock::new(Some(method_handler))))) } -pub fn destroy_client(mut cx: FunctionContext) -> JsResult { - let method_handler = Arc::clone(&cx.argument::>(0)?.0); - let channel = cx.channel(); - let (deferred, promise) = cx.promise(); - crate::RUNTIME.spawn(async move { - *method_handler.write().await = None; - deferred.settle_with(&channel, move |mut cx| Ok(cx.undefined())); - }); - Ok(promise) +pub fn destroy_client(mut cx: FunctionContext) -> JsResult { + match cx.argument::>(0)?.write() { + Ok(mut lock) => *lock = None, + Err(e) => { + return cx + .throw_error(serde_json::to_string(&Response::Panic(e.to_string())).expect("json to string error")); + } + } + Ok(cx.undefined()) } pub fn call_client_method(mut cx: FunctionContext) -> JsResult { - let method = cx.argument::(0)?; - let method = method.value(&mut cx); - let method_handler = Arc::clone(&cx.argument::>(1)?.0); - let callback = cx.argument::(2)?.root(&mut cx); - - let (sender, receiver) = std::sync::mpsc::channel(); - crate::RUNTIME.spawn(async move { - if let Some(method_handler) = &*method_handler.read().await { - let (response, is_error) = method_handler.call_method(method).await; - method_handler.channel.send(move |mut cx| { - let cb = callback.into_inner(&mut cx); - let this = cx.undefined(); - - let args = [ - if is_error { - cx.string(response.clone()).upcast::() - } else { - cx.undefined().upcast::() - }, - cx.string(response).upcast::(), - ]; - - cb.call(&mut cx, this, args)?; - - Ok(()) - }); - } else { - // Notify that the client got destroyed - // Safe to unwrap because the receiver is waiting on it - sender.send(()).unwrap(); + match cx.argument::>(1)?.read() { + Ok(lock) => { + let method_handler = lock.clone(); + let method = cx.argument::(0)?; + let method = method.value(&mut cx); + let callback = cx.argument::(2)?.root(&mut cx); + if let Some(method_handler) = method_handler { + crate::RUNTIME.spawn(async move { + let (response, is_error) = method_handler.call_method(method).await; + method_handler.channel.send(move |mut cx| { + let cb = callback.into_inner(&mut cx); + let this = cx.undefined(); + + let args = [ + if is_error { + cx.string(response.clone()).upcast::() + } else { + cx.undefined().upcast::() + }, + cx.string(response).upcast::(), + ]; + + cb.call(&mut cx, this, args)?; + Ok(()) + }); + }); + + Ok(cx.undefined()) + } else { + // Notify that the client got destroyed + cx.throw_error( + serde_json::to_string(&Response::Panic("Client was destroyed".to_string())) + .expect("json to string error"), + ) + } } - }); - - if receiver.recv().is_ok() { - return cx.throw_error( - serde_json::to_string(&Response::Panic("Client got destroyed".to_string())).expect("json to string error"), - ); + Err(e) => cx.throw_error(serde_json::to_string(&Response::Panic(e.to_string())).expect("json to string error")), } - - Ok(cx.undefined()) } // MQTT -pub fn listen_mqtt(mut cx: FunctionContext) -> JsResult { +pub fn listen_mqtt(mut cx: FunctionContext) -> JsResult { let js_arr_handle: Handle = cx.argument(0)?; let vec: Vec> = js_arr_handle.to_vec(&mut cx)?; let mut topics = Vec::with_capacity(vec.len()); @@ -134,25 +130,28 @@ pub fn listen_mqtt(mut cx: FunctionContext) -> JsResult { } let callback = Arc::new(cx.argument::(1)?.root(&mut cx)); - let method_handler = Arc::clone(&cx.argument::>(2)?.0); - let (deferred, promise) = cx.promise(); - - crate::RUNTIME.spawn(async move { - if let Some(method_handler) = &*method_handler.read().await { - let channel0 = method_handler.channel.clone(); - let channel1 = method_handler.channel.clone(); - rust_listen_mqtt(&method_handler.client, topics, move |event_data| { - call_event_callback(&channel0, event_data, callback.clone()) - }) - .await; - - deferred.settle_with(&channel1, move |mut cx| Ok(cx.undefined())); - } else { - panic!("Client got destroyed") - } - }); - Ok(promise) + match cx.argument::>(2)?.read() { + Ok(lock) => { + let method_handler = lock.clone(); + if let Some(method_handler) = method_handler { + crate::RUNTIME.spawn(async move { + rust_listen_mqtt(&method_handler.client, topics, move |event_data| { + call_event_callback(&method_handler.channel, event_data, callback.clone()) + }) + .await; + }); + Ok(cx.undefined()) + } else { + // Notify that the client got destroyed + cx.throw_error( + serde_json::to_string(&Response::Panic("Client was destroyed".to_string())) + .expect("json to string error"), + ) + } + } + Err(e) => cx.throw_error(serde_json::to_string(&Response::Panic(e.to_string())).expect("json to string error")), + } } fn call_event_callback(channel: &neon::event::Channel, event_data: String, callback: Arc) { diff --git a/bindings/nodejs/src/wallet.rs b/bindings/nodejs/src/wallet.rs index ad3ae44380..52beea747b 100644 --- a/bindings/nodejs/src/wallet.rs +++ b/bindings/nodejs/src/wallet.rs @@ -1,7 +1,7 @@ // Copyright 2023 IOTA Stiftung // SPDX-License-Identifier: Apache-2.0 -use std::sync::Arc; +use std::sync::{Arc, RwLock}; use iota_sdk_bindings_core::{ call_wallet_method as rust_call_wallet_method, @@ -12,24 +12,22 @@ use iota_sdk_bindings_core::{ Response, Result, WalletMethod, WalletOptions, }; use neon::prelude::*; -use tokio::sync::RwLock; use crate::{ - client::{ClientMethodHandler, ClientMethodHandlerWrapper}, + client::{ClientMethodHandler, SharedClientMethodHandler}, secret_manager::SecretManagerMethodHandler, }; -// Wrapper so we can destroy the WalletMethodHandler -pub type WalletMethodHandlerWrapperInner = Arc>>; -// Wrapper because we can't impl Finalize on WalletMethodHandlerWrapperInner -pub struct WalletMethodHandlerWrapper(pub WalletMethodHandlerWrapperInner); -impl Finalize for WalletMethodHandlerWrapper {} +pub type SharedWalletMethodHandler = Arc>>; +#[derive(Clone)] pub struct WalletMethodHandler { channel: Channel, wallet: Wallet, } +impl Finalize for WalletMethodHandler {} + type JsCallback = Root>; impl WalletMethodHandler { @@ -68,8 +66,6 @@ impl WalletMethodHandler { } } -impl Finalize for WalletMethodHandler {} - fn call_event_callback(channel: &neon::event::Channel, event_data: Event, callback: Arc) { channel.send(move |mut cx| { let cb = (*callback).to_inner(&mut cx); @@ -86,57 +82,56 @@ fn call_event_callback(channel: &neon::event::Channel, event_data: Event, callba }); } -pub fn create_wallet(mut cx: FunctionContext) -> JsResult> { +pub fn create_wallet(mut cx: FunctionContext) -> JsResult> { let options = cx.argument::(0)?; let options = options.value(&mut cx); let channel = cx.channel(); let method_handler = WalletMethodHandler::new(channel, options) .or_else(|e| cx.throw_error(serde_json::to_string(&Response::Error(e)).expect("json to string error")))?; - Ok(cx.boxed(WalletMethodHandlerWrapper(Arc::new(RwLock::new(Some(method_handler)))))) + Ok(cx.boxed(Arc::new(RwLock::new(Some(method_handler))))) } pub fn call_wallet_method(mut cx: FunctionContext) -> JsResult { - let method = cx.argument::(0)?; - let method = method.value(&mut cx); - let method_handler = Arc::clone(&cx.argument::>(1)?.0); - let callback = cx.argument::(2)?.root(&mut cx); - - let (sender, receiver) = std::sync::mpsc::channel(); - crate::RUNTIME.spawn(async move { - if let Some(method_handler) = &*method_handler.read().await { - let (response, is_error) = method_handler.call_method(method).await; - method_handler.channel.send(move |mut cx| { - let cb = callback.into_inner(&mut cx); - let this = cx.undefined(); - - let args = [ - if is_error { - cx.string(response.clone()).upcast::() - } else { - cx.undefined().upcast::() - }, - cx.string(response).upcast::(), - ]; - - cb.call(&mut cx, this, args)?; - - Ok(()) - }); - } else { - // Notify that the wallet got destroyed - // Safe to unwrap because the receiver is waiting on it - sender.send(()).unwrap(); + match cx.argument::>(1)?.read() { + Ok(lock) => { + let method_handler = lock.clone(); + let method = cx.argument::(0)?; + let method = method.value(&mut cx); + let callback = cx.argument::(2)?.root(&mut cx); + if let Some(method_handler) = method_handler { + crate::RUNTIME.spawn(async move { + let (response, is_error) = method_handler.call_method(method).await; + method_handler.channel.send(move |mut cx| { + let cb = callback.into_inner(&mut cx); + let this = cx.undefined(); + + let args = [ + if is_error { + cx.string(response.clone()).upcast::() + } else { + cx.undefined().upcast::() + }, + cx.string(response).upcast::(), + ]; + + cb.call(&mut cx, this, args)?; + + Ok(()) + }); + }); + + Ok(cx.undefined()) + } else { + // Notify that the wallet was destroyed + cx.throw_error( + serde_json::to_string(&Response::Panic("Wallet was destroyed".to_string())) + .expect("json to string error"), + ) + } } - }); - - if receiver.recv().is_ok() { - return cx.throw_error( - serde_json::to_string(&Response::Panic("Wallet got destroyed".to_string())).expect("json to string error"), - ); + Err(e) => cx.throw_error(serde_json::to_string(&Response::Panic(e.to_string())).expect("json to string error")), } - - Ok(cx.undefined()) } pub fn listen_wallet(mut cx: FunctionContext) -> JsResult { @@ -149,86 +144,89 @@ pub fn listen_wallet(mut cx: FunctionContext) -> JsResult { WalletEventType::try_from(event_type.value(&mut cx) as u8).or_else(|e| cx.throw_error(e))?; event_types.push(wallet_event_type); } - let callback = Arc::new(cx.argument::(1)?.root(&mut cx)); - let method_handler = Arc::clone(&cx.argument::>(2)?.0); - - crate::RUNTIME.spawn(async move { - if let Some(method_handler) = &*method_handler.read().await { - let channel = method_handler.channel.clone(); - method_handler - .wallet - .listen(event_types, move |event_data| { - call_event_callback(&channel, event_data.clone(), callback.clone()) - }) - .await; - } else { - panic!("Wallet got destroyed") - } - }); - Ok(cx.undefined()) + match cx.argument::>(2)?.read() { + Ok(lock) => { + let method_handler = lock.clone(); + if let Some(method_handler) = method_handler { + crate::RUNTIME.spawn(async move { + method_handler + .wallet + .listen(event_types, move |event_data| { + call_event_callback(&method_handler.channel, event_data.clone(), callback.clone()) + }) + .await; + }); + + Ok(cx.undefined()) + } else { + // Notify that the wallet was destroyed + cx.throw_error( + serde_json::to_string(&Response::Panic("Wallet was destroyed".to_string())) + .expect("json to string error"), + ) + } + } + Err(e) => cx.throw_error(serde_json::to_string(&Response::Panic(e.to_string())).expect("json to string error")), + } } -pub fn destroy_wallet(mut cx: FunctionContext) -> JsResult { - let method_handler = Arc::clone(&cx.argument::>(0)?.0); - let channel = cx.channel(); - let (deferred, promise) = cx.promise(); - crate::RUNTIME.spawn(async move { - *method_handler.write().await = None; - deferred.settle_with(&channel, move |mut cx| Ok(cx.undefined())); - }); - Ok(promise) +pub fn destroy_wallet(mut cx: FunctionContext) -> JsResult { + match cx.argument::>(0)?.write() { + Ok(mut lock) => *lock = None, + Err(e) => { + return cx + .throw_error(serde_json::to_string(&Response::Panic(e.to_string())).expect("json to string error")); + } + } + Ok(cx.undefined()) } -pub fn get_client(mut cx: FunctionContext) -> JsResult { - let method_handler = Arc::clone(&cx.argument::>(0)?.0); - let channel = cx.channel(); - - let (deferred, promise) = cx.promise(); - crate::RUNTIME.spawn(async move { - if let Some(method_handler) = &*method_handler.read().await { - let client_method_handler = - ClientMethodHandler::new_with_client(channel.clone(), method_handler.wallet.client().clone()); - deferred.settle_with(&channel, move |mut cx| { - Ok(cx.boxed(ClientMethodHandlerWrapper(Arc::new(RwLock::new(Some( - client_method_handler, - )))))) - }); - } else { - deferred.settle_with(&channel, move |mut cx| { - cx.error( - serde_json::to_string(&Response::Panic("Wallet got destroyed".to_string())) +pub fn get_client(mut cx: FunctionContext) -> JsResult> { + match cx.argument::>(0)?.read() { + Ok(lock) => { + if let Some(method_handler) = &*lock { + let client_method_handler = + ClientMethodHandler::new_with_client(cx.channel(), method_handler.wallet.client().clone()); + + Ok(cx.boxed(Arc::new(RwLock::new(Some(client_method_handler))))) + } else { + // Notify that the wallet was destroyed + cx.throw_error( + serde_json::to_string(&Response::Panic("Wallet was destroyed".to_string())) .expect("json to string error"), ) - }); + } } - }); - - Ok(promise) + Err(e) => { + return cx + .throw_error(serde_json::to_string(&Response::Panic(e.to_string())).expect("json to string error")); + } + } } -pub fn get_secret_manager(mut cx: FunctionContext) -> JsResult { - let method_handler = Arc::clone(&cx.argument::>(0)?.0); - let channel = cx.channel(); - - let (deferred, promise) = cx.promise(); - crate::RUNTIME.spawn(async move { - if let Some(method_handler) = &*method_handler.read().await { - let secret_manager_method_handler = SecretManagerMethodHandler::new_with_secret_manager( - channel.clone(), - method_handler.wallet.get_secret_manager().clone(), - ); - deferred.settle_with(&channel, move |mut cx| Ok(cx.boxed(secret_manager_method_handler))); - } else { - deferred.settle_with(&channel, move |mut cx| { - cx.error( - serde_json::to_string(&Response::Panic("Wallet got destroyed".to_string())) +pub fn get_secret_manager(mut cx: FunctionContext) -> JsResult>> { + match cx.argument::>(0)?.read() { + Ok(lock) => { + if let Some(method_handler) = &*lock { + let secret_manager_method_handler = SecretManagerMethodHandler::new_with_secret_manager( + cx.channel(), + method_handler.wallet.get_secret_manager().clone(), + ); + + Ok(cx.boxed(secret_manager_method_handler)) + } else { + // Notify that the wallet was destroyed + cx.throw_error( + serde_json::to_string(&Response::Panic("Wallet was destroyed".to_string())) .expect("json to string error"), ) - }); + } } - }); - - Ok(promise) + Err(e) => { + return cx + .throw_error(serde_json::to_string(&Response::Panic(e.to_string())).expect("json to string error")); + } + } } From 9daae9755680b6e7d151ba30c736ee7195669ee5 Mon Sep 17 00:00:00 2001 From: Alex Coats Date: Wed, 23 Aug 2023 10:56:16 -0400 Subject: [PATCH 02/12] got -> was --- bindings/nodejs-old/src/message_handler.rs | 4 ++-- bindings/nodejs/src/client.rs | 4 ++-- bindings/nodejs/tests/wallet/wallet.spec.ts | 8 ++++---- bindings/python/src/wallet.rs | 8 ++++---- bindings/wasm/src/wallet.rs | 6 +++--- 5 files changed, 15 insertions(+), 15 deletions(-) diff --git a/bindings/nodejs-old/src/message_handler.rs b/bindings/nodejs-old/src/message_handler.rs index b32f2b0d5b..6a4dc0aac2 100644 --- a/bindings/nodejs-old/src/message_handler.rs +++ b/bindings/nodejs-old/src/message_handler.rs @@ -138,7 +138,7 @@ pub fn send_message(mut cx: FunctionContext) -> JsResult { Ok(()) }); } else { - panic!("Message handler got destroyed") + panic!("Message handler was destroyed") } }); @@ -169,7 +169,7 @@ pub fn listen(mut cx: FunctionContext) -> JsResult { }) .await; } else { - panic!("Message handler got destroyed") + panic!("Message handler was destroyed") } }); diff --git a/bindings/nodejs/src/client.rs b/bindings/nodejs/src/client.rs index 542affbe3b..a8d4caa56e 100644 --- a/bindings/nodejs/src/client.rs +++ b/bindings/nodejs/src/client.rs @@ -108,7 +108,7 @@ pub fn call_client_method(mut cx: FunctionContext) -> JsResult { Ok(cx.undefined()) } else { - // Notify that the client got destroyed + // Notify that the client was destroyed cx.throw_error( serde_json::to_string(&Response::Panic("Client was destroyed".to_string())) .expect("json to string error"), @@ -143,7 +143,7 @@ pub fn listen_mqtt(mut cx: FunctionContext) -> JsResult { }); Ok(cx.undefined()) } else { - // Notify that the client got destroyed + // Notify that the client was destroyed cx.throw_error( serde_json::to_string(&Response::Panic("Client was destroyed".to_string())) .expect("json to string error"), diff --git a/bindings/nodejs/tests/wallet/wallet.spec.ts b/bindings/nodejs/tests/wallet/wallet.spec.ts index cef6e037d7..84eac0d7f3 100644 --- a/bindings/nodejs/tests/wallet/wallet.spec.ts +++ b/bindings/nodejs/tests/wallet/wallet.spec.ts @@ -166,16 +166,16 @@ describe('Wallet', () => { try { const accounts = await wallet.getAccounts(); - throw 'Should return an error because the wallet got destroyed'; + throw 'Should return an error because the wallet was destroyed'; } catch (err: any) { - expect(err).toContain('Wallet got destroyed'); + expect(err).toContain('Wallet was destroyed'); } try { const client = await wallet.getClient(); - throw 'Should return an error because the wallet got destroyed'; + throw 'Should return an error because the wallet was destroyed'; } catch (err: any) { - expect(err).toContain('Wallet got destroyed'); + expect(err).toContain('Wallet was destroyed'); } removeDir(storagePath) }, 35000); diff --git a/bindings/python/src/wallet.rs b/bindings/python/src/wallet.rs index 3a5e1ed57e..2dd2f39733 100644 --- a/bindings/python/src/wallet.rs +++ b/bindings/python/src/wallet.rs @@ -49,7 +49,7 @@ pub fn call_wallet_method(wallet: &Wallet, method: String) -> Result { let response = crate::block_on(async { match wallet.wallet.read().await.as_ref() { Some(wallet) => rust_call_wallet_method(wallet, method).await, - None => Response::Panic("wallet got destroyed".into()), + None => Response::Panic("wallet was destroyed".into()), } }); @@ -77,7 +77,7 @@ pub fn listen_wallet(wallet: &Wallet, events: Vec, handler: PyObject) { .read() .await .as_ref() - .expect("wallet got destroyed") + .expect("wallet was destroyed") .listen(rust_events, move |event| { let event_string = serde_json::to_string(&event).expect("json to string error"); Python::with_gil(|py| { @@ -101,7 +101,7 @@ pub fn get_client_from_wallet(wallet: &Wallet) -> Result { .map(|w| w.client().clone()) .ok_or_else(|| { Error::from( - serde_json::to_string(&Response::Panic("wallet got destroyed".into())) + serde_json::to_string(&Response::Panic("wallet was destroyed".into())) .expect("json to string error") .as_str(), ) @@ -123,7 +123,7 @@ pub fn get_secret_manager_from_wallet(wallet: &Wallet) -> Result .map(|w| w.get_secret_manager().clone()) .ok_or_else(|| { Error::from( - serde_json::to_string(&Response::Panic("wallet got destroyed".into())) + serde_json::to_string(&Response::Panic("wallet was destroyed".into())) .expect("json to string error") .as_str(), ) diff --git a/bindings/wasm/src/wallet.rs b/bindings/wasm/src/wallet.rs index 891065368b..dcd6104674 100644 --- a/bindings/wasm/src/wallet.rs +++ b/bindings/wasm/src/wallet.rs @@ -54,7 +54,7 @@ pub async fn get_client(method_handler: &WalletMethodHandler) -> Result Result< let secret_manager = wallet .as_ref() - .ok_or_else(|| "wallet got destroyed".to_string())? + .ok_or_else(|| "wallet was destroyed".to_string())? .get_secret_manager() .clone(); @@ -82,7 +82,7 @@ pub async fn call_wallet_method_async(method: String, method_handler: &WalletMet let wallet = method_handler.wallet.lock().await; let method: WalletMethod = serde_json::from_str(&method).map_err(|err| err.to_string())?; - let response = call_wallet_method(wallet.as_ref().expect("wallet got destroyed"), method).await; + let response = call_wallet_method(wallet.as_ref().expect("wallet was destroyed"), method).await; match response { Response::Error(e) => Err(e.to_string().into()), Response::Panic(p) => Err(p.into()), From ea1d2f76600ba76c50988230a402bae8f6e70c79 Mon Sep 17 00:00:00 2001 From: Brord van Wierst Date: Wed, 30 Aug 2023 15:01:57 +0200 Subject: [PATCH 03/12] fixed client on node side --- .../lib/wallet/wallet-method-handler.ts | 22 ++++++++++--------- bindings/nodejs/lib/wallet/wallet.ts | 2 +- bindings/nodejs/src/wallet.rs | 4 ++-- bindings/nodejs/tests/wallet/wallet.spec.ts | 6 ++--- 4 files changed, 18 insertions(+), 16 deletions(-) diff --git a/bindings/nodejs/lib/wallet/wallet-method-handler.ts b/bindings/nodejs/lib/wallet/wallet-method-handler.ts index edf904c2d0..8cfc8ecbd0 100644 --- a/bindings/nodejs/lib/wallet/wallet-method-handler.ts +++ b/bindings/nodejs/lib/wallet/wallet-method-handler.ts @@ -84,16 +84,18 @@ export class WalletMethodHandler { return destroyWallet(this.methodHandler); } - async getClient(): Promise { - return new Promise((resolve, reject) => { - getClientFromWallet(this.methodHandler).then((result: any) => { - if (result.message !== undefined) { - reject(JSON.parse(result.message).payload); - } else { - resolve(new Client(result)); - } - }); - }); + getClient(): Client { + try { + let result = getClientFromWallet(this.methodHandler); + return new Client(result); + } catch(error: any) { + if (error.message !== undefined) { + error = JSON.parse(error.message).payload; + } else { + error = JSON.parse(error.toString()).payload; + } + throw error; + }; } async getSecretManager(): Promise { diff --git a/bindings/nodejs/lib/wallet/wallet.ts b/bindings/nodejs/lib/wallet/wallet.ts index 318f221bc0..2fe967aac5 100644 --- a/bindings/nodejs/lib/wallet/wallet.ts +++ b/bindings/nodejs/lib/wallet/wallet.ts @@ -141,7 +141,7 @@ export class Wallet { /** * Get client. */ - async getClient(): Promise { + getClient(): Client { return this.methodHandler.getClient(); } diff --git a/bindings/nodejs/src/wallet.rs b/bindings/nodejs/src/wallet.rs index 52beea747b..b937377249 100644 --- a/bindings/nodejs/src/wallet.rs +++ b/bindings/nodejs/src/wallet.rs @@ -200,8 +200,8 @@ pub fn get_client(mut cx: FunctionContext) -> JsResult { - return cx - .throw_error(serde_json::to_string(&Response::Panic(e.to_string())).expect("json to string error")); + cx + .throw_error(serde_json::to_string(&Response::Panic(e.to_string())).expect("json to string error")) } } } diff --git a/bindings/nodejs/tests/wallet/wallet.spec.ts b/bindings/nodejs/tests/wallet/wallet.spec.ts index 84eac0d7f3..feaac94b84 100644 --- a/bindings/nodejs/tests/wallet/wallet.spec.ts +++ b/bindings/nodejs/tests/wallet/wallet.spec.ts @@ -118,7 +118,7 @@ describe('Wallet', () => { expect(account.getMetadata().index).toStrictEqual(0); - const client = await wallet.getClient(); + const client = wallet.getClient(); const localPoW = await client.getLocalPow(); expect(localPoW).toBeTruthy(); @@ -165,14 +165,14 @@ describe('Wallet', () => { await wallet.destroy(); try { - const accounts = await wallet.getAccounts(); + const _accounts = await wallet.getAccounts(); throw 'Should return an error because the wallet was destroyed'; } catch (err: any) { expect(err).toContain('Wallet was destroyed'); } try { - const client = await wallet.getClient(); + const _client = wallet.getClient(); throw 'Should return an error because the wallet was destroyed'; } catch (err: any) { expect(err).toContain('Wallet was destroyed'); From 808220370178b1fb84bebac35eee2fd31293f24c Mon Sep 17 00:00:00 2001 From: Brord van Wierst Date: Thu, 31 Aug 2023 16:45:24 +0200 Subject: [PATCH 04/12] wasm --- bindings/wasm/package.json | 2 +- bindings/wasm/src/wallet.rs | 74 +++++++++++++++++++++---------------- 2 files changed, 43 insertions(+), 33 deletions(-) diff --git a/bindings/wasm/package.json b/bindings/wasm/package.json index 7fe829bbe0..da4936dbad 100644 --- a/bindings/wasm/package.json +++ b/bindings/wasm/package.json @@ -57,7 +57,7 @@ "typedoc": "^0.23.9", "typedoc-plugin-markdown": "^3.13.4", "typescript": "^4.7.4", - "wasm-opt": "^1.3.0" + "wasm-opt": "^1.4.0" }, "resolutions": { "decode-uri-component": "^0.2.1", diff --git a/bindings/wasm/src/wallet.rs b/bindings/wasm/src/wallet.rs index dcd6104674..3be06b063e 100644 --- a/bindings/wasm/src/wallet.rs +++ b/bindings/wasm/src/wallet.rs @@ -1,7 +1,7 @@ // Copyright 2023 IOTA Stiftung // SPDX-License-Identifier: Apache-2.0 -use std::sync::Arc; +use std::sync::{Arc, RwLock}; use iota_sdk_bindings_core::{ call_wallet_method, @@ -12,8 +12,7 @@ use iota_sdk_bindings_core::{ Response, WalletMethod, WalletOptions, }; use tokio::sync::{ - mpsc::{unbounded_channel, UnboundedReceiver, UnboundedSender}, - Mutex, + mpsc::{unbounded_channel, UnboundedReceiver, UnboundedSender} }; use wasm_bindgen::{prelude::wasm_bindgen, JsValue}; @@ -22,7 +21,27 @@ use crate::{client::ClientMethodHandler, secret_manager::SecretManagerMethodHand /// The Wallet method handler. #[wasm_bindgen(js_name = WalletMethodHandler)] pub struct WalletMethodHandler { - wallet: Arc>>, + wallet: Arc>>, +} + +macro_rules! wallet_pre { + ($method_handler:ident) => { + match $method_handler.wallet.read() { + Ok(handler) => { + if let Some(wallet) = handler.clone() { + Ok(wallet) + } else { + // Notify that the wallet was destroyed + Err(serde_json::to_string(&Response::Panic("Wallet was destroyed".to_string())) + .expect("json to string error"), + ).into() + } + } + Err(e) => { + Err(serde_json::to_string(&Response::Panic(e.to_string())).expect("json to string error").into()) + } + } + }; } /// Creates a method handler with the given options. @@ -38,38 +57,34 @@ pub fn create_wallet(options: String) -> Result { .map_err(|e| e.to_string())?; Ok(WalletMethodHandler { - wallet: Arc::new(Mutex::new(Some(wallet_method_handler))), + wallet: Arc::new(RwLock::new(Some(wallet_method_handler))), }) } #[wasm_bindgen(js_name = destroyWallet)] pub async fn destroy_wallet(method_handler: &WalletMethodHandler) -> Result<(), JsValue> { - *method_handler.wallet.lock().await = None; + match method_handler.wallet.write() { + Ok(mut lock) => *lock = None, + Err(e) => { + return Err(serde_json::to_string(&Response::Panic(e.to_string())).expect("json to string error").into()); + } + }; Ok(()) } #[wasm_bindgen(js_name = getClientFromWallet)] pub async fn get_client(method_handler: &WalletMethodHandler) -> Result { - let wallet = method_handler.wallet.lock().await; + let wallet = wallet_pre!(method_handler)?; - let client = wallet - .as_ref() - .ok_or_else(|| "wallet was destroyed".to_string())? - .client() - .clone(); + let client = wallet.client().clone(); Ok(ClientMethodHandler { client }) } #[wasm_bindgen(js_name = getSecretManagerFromWallet)] pub async fn get_secret_manager(method_handler: &WalletMethodHandler) -> Result { - let wallet = method_handler.wallet.lock().await; - - let secret_manager = wallet - .as_ref() - .ok_or_else(|| "wallet was destroyed".to_string())? - .get_secret_manager() - .clone(); + let wallet = wallet_pre!(method_handler)?; + let secret_manager = wallet.get_secret_manager().clone(); Ok(SecretManagerMethodHandler { secret_manager }) } @@ -79,12 +94,12 @@ pub async fn get_secret_manager(method_handler: &WalletMethodHandler) -> Result< /// Returns an error if the response itself is an error or panic. #[wasm_bindgen(js_name = callWalletMethodAsync)] pub async fn call_wallet_method_async(method: String, method_handler: &WalletMethodHandler) -> Result { - let wallet = method_handler.wallet.lock().await; + let wallet = wallet_pre!(method_handler)?; let method: WalletMethod = serde_json::from_str(&method).map_err(|err| err.to_string())?; - let response = call_wallet_method(wallet.as_ref().expect("wallet was destroyed"), method).await; + let response = call_wallet_method(&wallet, method).await; match response { - Response::Error(e) => Err(e.to_string().into()), + Response::Error(e) => Err(serde_json::to_string(&Response::Panic(e.to_string())).expect("json to string error").into()), Response::Panic(p) => Err(p.into()), _ => Ok(serde_json::to_string(&response).map_err(|e| e.to_string())?), } @@ -114,16 +129,11 @@ pub async fn listen_wallet( } let (tx, mut rx): (UnboundedSender, UnboundedReceiver) = unbounded_channel(); - method_handler - .wallet - .lock() - .await - .as_ref() - .expect("wallet not initialised") - .listen(event_types, move |wallet_event| { - tx.send(wallet_event.clone()).unwrap(); - }) - .await; + let wallet = wallet_pre!(method_handler)?; + wallet.listen(event_types, move |wallet_event| { + tx.send(wallet_event.clone()).unwrap(); + }) + .await; // Spawn on the same thread a continuous loop to check the channel wasm_bindgen_futures::spawn_local(async move { From ce3d24da3dee1e398783d747c2943721249dc99d Mon Sep 17 00:00:00 2001 From: Brord van Wierst Date: Thu, 31 Aug 2023 16:46:06 +0200 Subject: [PATCH 05/12] removed await for client --- .../nodejs/examples/how_tos/alias_wallet/request-funds.ts | 6 ++---- .../nodejs/examples/how_tos/alias_wallet/transaction.ts | 4 ++-- .../how_tos/nft_collection/01_mint_collection_nft.ts | 2 +- bindings/nodejs/examples/how_tos/nfts/mint_nft.ts | 2 +- .../examples/how_tos/simple_transaction/request-funds.ts | 4 +--- 5 files changed, 7 insertions(+), 11 deletions(-) diff --git a/bindings/nodejs/examples/how_tos/alias_wallet/request-funds.ts b/bindings/nodejs/examples/how_tos/alias_wallet/request-funds.ts index 534f723ce8..581d0a44b6 100644 --- a/bindings/nodejs/examples/how_tos/alias_wallet/request-funds.ts +++ b/bindings/nodejs/examples/how_tos/alias_wallet/request-funds.ts @@ -43,11 +43,9 @@ async function run() { // Get Alias address const aliasAddress = Utils.aliasIdToBech32( aliasId, - await (await wallet.getClient()).getBech32Hrp(), + await wallet.getClient().getBech32Hrp(), ); - const faucetResponse = await ( - await wallet.getClient() - ).requestFundsFromFaucet(faucetUrl, aliasAddress); + const faucetResponse = await wallet.getClient().requestFundsFromFaucet(faucetUrl, aliasAddress); console.log(faucetResponse); await new Promise((resolve) => setTimeout(resolve, 10000)); diff --git a/bindings/nodejs/examples/how_tos/alias_wallet/transaction.ts b/bindings/nodejs/examples/how_tos/alias_wallet/transaction.ts index 0ffe4ef766..f55a48a2b4 100644 --- a/bindings/nodejs/examples/how_tos/alias_wallet/transaction.ts +++ b/bindings/nodejs/examples/how_tos/alias_wallet/transaction.ts @@ -50,7 +50,7 @@ async function run() { // Get Alias address const aliasAddress = Utils.aliasIdToBech32( aliasId, - await (await wallet.getClient()).getBech32Hrp(), + await wallet.getClient().getBech32Hrp(), ); // Find first output unlockable by the alias address @@ -60,7 +60,7 @@ async function run() { }, ]; const input = ( - await (await wallet.getClient()).basicOutputIds(queryParameters) + await wallet.getClient().basicOutputIds(queryParameters) ).items[0]; const params = [ diff --git a/bindings/nodejs/examples/how_tos/nft_collection/01_mint_collection_nft.ts b/bindings/nodejs/examples/how_tos/nft_collection/01_mint_collection_nft.ts index 2a294b0f3c..b97b038e08 100644 --- a/bindings/nodejs/examples/how_tos/nft_collection/01_mint_collection_nft.ts +++ b/bindings/nodejs/examples/how_tos/nft_collection/01_mint_collection_nft.ts @@ -41,7 +41,7 @@ async function run() { // Get the id we generated with `00_mint_issuer_nft` const issuerNftId: NftId = process.argv[2]; - const bech32Hrp = await (await wallet.getClient()).getBech32Hrp(); + const bech32Hrp = await wallet.getClient().getBech32Hrp(); const issuer = Utils.nftIdToBech32(issuerNftId, bech32Hrp); const nftMintParams = []; diff --git a/bindings/nodejs/examples/how_tos/nfts/mint_nft.ts b/bindings/nodejs/examples/how_tos/nfts/mint_nft.ts index 5ce684ca48..4589f16421 100644 --- a/bindings/nodejs/examples/how_tos/nfts/mint_nft.ts +++ b/bindings/nodejs/examples/how_tos/nfts/mint_nft.ts @@ -76,7 +76,7 @@ async function run() { console.log('Minted NFT 1'); // Build an NFT manually by using the `NftOutputBuilder` - const client = await wallet.getClient(); + const client = wallet.getClient(); const hexAddress = Utils.bech32ToHex(senderAddress); const output = await client.buildNftOutput({ diff --git a/bindings/nodejs/examples/how_tos/simple_transaction/request-funds.ts b/bindings/nodejs/examples/how_tos/simple_transaction/request-funds.ts index 2a86b934ef..24bc86eab0 100644 --- a/bindings/nodejs/examples/how_tos/simple_transaction/request-funds.ts +++ b/bindings/nodejs/examples/how_tos/simple_transaction/request-funds.ts @@ -29,9 +29,7 @@ async function run() { const address = (await account.addresses())[0].address; console.log(address); - const faucetResponse = await ( - await wallet.getClient() - ).requestFundsFromFaucet(faucetUrl, address); + const faucetResponse = await wallet.getClient().requestFundsFromFaucet(faucetUrl, address); console.log(faucetResponse); } catch (error) { console.error('Error: ', error); From 20ad6fd9fc30574f85faebd61b7b73a6afc920da Mon Sep 17 00:00:00 2001 From: Brord van Wierst Date: Thu, 31 Aug 2023 16:53:09 +0200 Subject: [PATCH 06/12] lint n format --- .../examples/how_tos/alias_wallet/request-funds.ts | 4 +++- .../examples/how_tos/alias_wallet/transaction.ts | 5 ++--- .../how_tos/simple_transaction/request-funds.ts | 4 +++- bindings/nodejs/lib/wallet/wallet-method-handler.ts | 11 +++++------ 4 files changed, 13 insertions(+), 11 deletions(-) diff --git a/bindings/nodejs/examples/how_tos/alias_wallet/request-funds.ts b/bindings/nodejs/examples/how_tos/alias_wallet/request-funds.ts index 581d0a44b6..07f490c3e4 100644 --- a/bindings/nodejs/examples/how_tos/alias_wallet/request-funds.ts +++ b/bindings/nodejs/examples/how_tos/alias_wallet/request-funds.ts @@ -45,7 +45,9 @@ async function run() { aliasId, await wallet.getClient().getBech32Hrp(), ); - const faucetResponse = await wallet.getClient().requestFundsFromFaucet(faucetUrl, aliasAddress); + const faucetResponse = await wallet + .getClient() + .requestFundsFromFaucet(faucetUrl, aliasAddress); console.log(faucetResponse); await new Promise((resolve) => setTimeout(resolve, 10000)); diff --git a/bindings/nodejs/examples/how_tos/alias_wallet/transaction.ts b/bindings/nodejs/examples/how_tos/alias_wallet/transaction.ts index f55a48a2b4..65980068ba 100644 --- a/bindings/nodejs/examples/how_tos/alias_wallet/transaction.ts +++ b/bindings/nodejs/examples/how_tos/alias_wallet/transaction.ts @@ -59,9 +59,8 @@ async function run() { address: aliasAddress, }, ]; - const input = ( - await wallet.getClient().basicOutputIds(queryParameters) - ).items[0]; + const input = (await wallet.getClient().basicOutputIds(queryParameters)) + .items[0]; const params = [ { diff --git a/bindings/nodejs/examples/how_tos/simple_transaction/request-funds.ts b/bindings/nodejs/examples/how_tos/simple_transaction/request-funds.ts index 24bc86eab0..f1f680fc88 100644 --- a/bindings/nodejs/examples/how_tos/simple_transaction/request-funds.ts +++ b/bindings/nodejs/examples/how_tos/simple_transaction/request-funds.ts @@ -29,7 +29,9 @@ async function run() { const address = (await account.addresses())[0].address; console.log(address); - const faucetResponse = await wallet.getClient().requestFundsFromFaucet(faucetUrl, address); + const faucetResponse = await wallet + .getClient() + .requestFundsFromFaucet(faucetUrl, address); console.log(faucetResponse); } catch (error) { console.error('Error: ', error); diff --git a/bindings/nodejs/lib/wallet/wallet-method-handler.ts b/bindings/nodejs/lib/wallet/wallet-method-handler.ts index ec2b6170f6..37139315a5 100644 --- a/bindings/nodejs/lib/wallet/wallet-method-handler.ts +++ b/bindings/nodejs/lib/wallet/wallet-method-handler.ts @@ -109,16 +109,15 @@ export class WalletMethodHandler { */ getClient(): Client { try { - let result = getClientFromWallet(this.methodHandler); + const result = getClientFromWallet(this.methodHandler); return new Client(result); - } catch(error: any) { + } catch (error: any) { if (error.message !== undefined) { - error = JSON.parse(error.message).payload; + throw Error(JSON.parse(error.message).payload); } else { - error = JSON.parse(error.toString()).payload; + throw Error(JSON.parse(error.toString()).payload); } - throw error; - }; + } } /** From b75c5bd22a4d6b60b0811b7ef3d26bc46163756a Mon Sep 17 00:00:00 2001 From: Brord van Wierst Date: Thu, 31 Aug 2023 19:18:10 +0200 Subject: [PATCH 07/12] fixed test and client error return --- .../lib/client/client-method-handler.ts | 22 +++++++++++++++++-- bindings/nodejs/tests/client/examples.spec.ts | 20 +++++++++++++++++ bindings/nodejs/tests/wallet/wallet.spec.ts | 4 ++-- 3 files changed, 42 insertions(+), 4 deletions(-) diff --git a/bindings/nodejs/lib/client/client-method-handler.ts b/bindings/nodejs/lib/client/client-method-handler.ts index fb28a4a006..6b90dc39c5 100644 --- a/bindings/nodejs/lib/client/client-method-handler.ts +++ b/bindings/nodejs/lib/client/client-method-handler.ts @@ -39,9 +39,27 @@ export class ClientMethodHandler { */ async callMethod(method: __ClientMethods__): Promise { return callClientMethodAsync( - JSON.stringify(method), + // mapToObject is required to convert maps to array since they otherwise get serialized as `[{}]` even if not empty + JSON.stringify(method, function mapToObject(_key, value) { + if (value instanceof Map) { + return Object.fromEntries(value); + } else { + return value; + } + }), this.methodHandler, - ); + ).catch((error: Error) => { + try { + if (error.message !== undefined) { + error = JSON.parse(error.message).payload; + } else { + error = JSON.parse(error.toString()).payload; + } + } catch (e) { + console.error(e); + } + return Promise.reject(error); + }); } /** diff --git a/bindings/nodejs/tests/client/examples.spec.ts b/bindings/nodejs/tests/client/examples.spec.ts index 2a135c1a3d..fadb8bd3c7 100644 --- a/bindings/nodejs/tests/client/examples.spec.ts +++ b/bindings/nodejs/tests/client/examples.spec.ts @@ -185,4 +185,24 @@ describe.skip('Main examples', () => { expect(blockIdAndBlock[0]).toBeValidBlockId(); }); + + it('destroy', async () => { + const client = new Client({ + nodes: [ + { + url: process.env.NODE_URL || 'http://localhost:14265', + }, + ], + localPow: true, + }); + + await client.destroy(); + + try { + const _info = await client.getInfo(); + throw 'Should return an error because the client was destroyed'; + } catch (err: any) { + expect(err.message).toContain('Client was destroyed'); + } + }) }); diff --git a/bindings/nodejs/tests/wallet/wallet.spec.ts b/bindings/nodejs/tests/wallet/wallet.spec.ts index feaac94b84..166a08e632 100644 --- a/bindings/nodejs/tests/wallet/wallet.spec.ts +++ b/bindings/nodejs/tests/wallet/wallet.spec.ts @@ -168,14 +168,14 @@ describe('Wallet', () => { const _accounts = await wallet.getAccounts(); throw 'Should return an error because the wallet was destroyed'; } catch (err: any) { - expect(err).toContain('Wallet was destroyed'); + expect(err.message).toContain('Wallet was destroyed'); } try { const _client = wallet.getClient(); throw 'Should return an error because the wallet was destroyed'; } catch (err: any) { - expect(err).toContain('Wallet was destroyed'); + expect(err.message).toContain('Wallet was destroyed'); } removeDir(storagePath) }, 35000); From a4aa255ec4c1724d224f36c9ecff8c8454589569 Mon Sep 17 00:00:00 2001 From: Brord van Wierst Date: Thu, 31 Aug 2023 20:20:20 +0200 Subject: [PATCH 08/12] fixed error and comment --- bindings/nodejs/lib/client/client-method-handler.ts | 4 ++-- bindings/nodejs/lib/wallet/wallet-method-handler.ts | 5 +++-- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/bindings/nodejs/lib/client/client-method-handler.ts b/bindings/nodejs/lib/client/client-method-handler.ts index 6b90dc39c5..139a3da7fb 100644 --- a/bindings/nodejs/lib/client/client-method-handler.ts +++ b/bindings/nodejs/lib/client/client-method-handler.ts @@ -51,9 +51,9 @@ export class ClientMethodHandler { ).catch((error: Error) => { try { if (error.message !== undefined) { - error = JSON.parse(error.message).payload; + error = Error(JSON.parse(error.message).payload); } else { - error = JSON.parse(error.toString()).payload; + error = Error(JSON.parse(error.toString()).payload); } } catch (e) { console.error(e); diff --git a/bindings/nodejs/lib/wallet/wallet-method-handler.ts b/bindings/nodejs/lib/wallet/wallet-method-handler.ts index 37139315a5..8c45ddef37 100644 --- a/bindings/nodejs/lib/wallet/wallet-method-handler.ts +++ b/bindings/nodejs/lib/wallet/wallet-method-handler.ts @@ -42,6 +42,7 @@ export class WalletMethodHandler { * Call a wallet method on the Rust backend. * * @param method The wallet method to call. + * @returns A promise that resolves to a JSON string response holding the result of the wallet method. */ async callMethod(method: __Method__): Promise { return callWalletMethodAsync( @@ -57,9 +58,9 @@ export class WalletMethodHandler { ).catch((error: Error) => { try { if (error.message !== undefined) { - error = JSON.parse(error.message).payload; + error = Error(JSON.parse(error.message).payload); } else { - error = JSON.parse(error.toString()).payload; + error = Error(JSON.parse(error.toString()).payload); } } catch (e) { console.error(e); From daa1785b770057bfa3bbb93531dca84175d0a344 Mon Sep 17 00:00:00 2001 From: Brord van Wierst Date: Thu, 7 Sep 2023 09:51:17 +0200 Subject: [PATCH 09/12] Simplify nodejs and wasm bindings p. 2 (#1146) * unify error handling and responses * wasm * its something * secret manager alignment * error on callback fix * listen error in wasm, madelisten async again --- bindings/nodejs/lib/bindings.ts | 32 +++--- .../lib/client/client-method-handler.ts | 48 +++++---- bindings/nodejs/lib/client/client.ts | 4 +- bindings/nodejs/lib/index.ts | 34 ++++++ .../secret-manager-method-handler.ts | 30 ++++-- .../lib/wallet/wallet-method-handler.ts | 66 ++++++------ bindings/nodejs/lib/wallet/wallet.ts | 4 +- bindings/nodejs/src/client.rs | 10 +- bindings/nodejs/src/secret_manager.rs | 2 +- bindings/nodejs/src/wallet.rs | 18 ++-- bindings/wasm/lib/bindings.ts | 12 ++- bindings/wasm/src/client.rs | 102 ++++++++++++------ bindings/wasm/src/lib.rs | 6 +- bindings/wasm/src/secret_manager.rs | 47 ++++---- bindings/wasm/src/wallet.rs | 87 ++++++++------- 15 files changed, 313 insertions(+), 189 deletions(-) diff --git a/bindings/nodejs/lib/bindings.ts b/bindings/nodejs/lib/bindings.ts index 86e960f1c5..7ab5f8fd21 100644 --- a/bindings/nodejs/lib/bindings.ts +++ b/bindings/nodejs/lib/bindings.ts @@ -34,7 +34,7 @@ const callClientMethodAsync = ( handler: ClientMethodHandler, ): Promise => new Promise((resolve, reject) => { - callClientMethod(method, handler, (error: Error, result: string) => { + callClientMethod(method, handler, (error: any, result: string) => { if (error) { reject(error); } else { @@ -51,7 +51,7 @@ const callSecretManagerMethodAsync = ( callSecretManagerMethod( method, handler, - (error: Error, result: string) => { + (error: any, result: string) => { if (error) { reject(error); } else { @@ -75,15 +75,21 @@ const listenWalletAsync = ( callback: (error: Error, event: Event) => void, handler: WalletMethodHandler, ): Promise => { - listenWallet( - eventTypes, - function (err: any, data: string) { - const parsed = JSON.parse(data); - callback(err, new Event(parsed.accountIndex, parsed.event)); - }, - handler, - ); - return Promise.resolve(); + return new Promise((resolve) => { + listenWallet( + eventTypes, + function (err: any, data: string) { + const parsed = JSON.parse(data); + callback( + // Send back raw error instead of parsing + err, + new Event(parsed.accountIndex, parsed.event), + ); + }, + handler, + ); + resolve(); + }); }; const callWalletMethodAsync = ( @@ -91,7 +97,7 @@ const callWalletMethodAsync = ( handler: WalletMethodHandler, ): Promise => new Promise((resolve, reject) => { - callWalletMethod(method, handler, (error: Error, result: string) => { + callWalletMethod(method, handler, (error: any, result: string) => { if (error) { reject(error); } else { @@ -110,8 +116,8 @@ export { callSecretManagerMethodAsync, callUtilsMethod, callWalletMethodAsync, - destroyWallet, listenWalletAsync, + destroyWallet, getClientFromWallet, getSecretManagerFromWallet, listenMqtt, diff --git a/bindings/nodejs/lib/client/client-method-handler.ts b/bindings/nodejs/lib/client/client-method-handler.ts index 139a3da7fb..fdc3a59a94 100644 --- a/bindings/nodejs/lib/client/client-method-handler.ts +++ b/bindings/nodejs/lib/client/client-method-handler.ts @@ -1,6 +1,7 @@ // Copyright 2023 IOTA Stiftung // SPDX-License-Identifier: Apache-2.0 +import { errorHandle } from '..'; import { callClientMethodAsync, createClient, @@ -19,16 +20,24 @@ export class ClientMethodHandler { * @param options client options or a client method handler. */ constructor(options: IClientOptions | ClientMethodHandler) { - // The rust client object is not extensible - if (Object.isExtensible(options)) { - this.methodHandler = createClient(JSON.stringify(options)); - } else { - this.methodHandler = options as ClientMethodHandler; + try { + // The rust client object is not extensible + if (Object.isExtensible(options)) { + this.methodHandler = createClient(JSON.stringify(options)); + } else { + this.methodHandler = options as ClientMethodHandler; + } + } catch (error: any) { + throw errorHandle(error); } } - async destroy() { - return destroyClient(this.methodHandler); + destroy(): void { + try { + destroyClient(this.methodHandler); + } catch (error: any) { + throw errorHandle(error); + } } /** @@ -48,17 +57,8 @@ export class ClientMethodHandler { } }), this.methodHandler, - ).catch((error: Error) => { - try { - if (error.message !== undefined) { - error = Error(JSON.parse(error.message).payload); - } else { - error = Error(JSON.parse(error.toString()).payload); - } - } catch (e) { - console.error(e); - } - return Promise.reject(error); + ).catch((error: any) => { + throw errorHandle(error); }); } @@ -68,10 +68,18 @@ export class ClientMethodHandler { * @param topics The topics to listen to. * @param callback The callback to be called when an MQTT event is received. */ - async listen( + listen( topics: string[], callback: (error: Error, result: string) => void, ): Promise { - return listenMqtt(topics, callback, this.methodHandler); + try { + return listenMqtt(topics, callback, this.methodHandler).catch( + (error: any) => { + throw errorHandle(error); + }, + ); + } catch (error: any) { + throw errorHandle(error); + } } } diff --git a/bindings/nodejs/lib/client/client.ts b/bindings/nodejs/lib/client/client.ts index 94d00edb84..cc0f22f363 100644 --- a/bindings/nodejs/lib/client/client.ts +++ b/bindings/nodejs/lib/client/client.ts @@ -68,8 +68,8 @@ export class Client { this.methodHandler = new ClientMethodHandler(options); } - async destroy() { - return this.methodHandler.destroy(); + destroy() { + this.methodHandler.destroy(); } /** diff --git a/bindings/nodejs/lib/index.ts b/bindings/nodejs/lib/index.ts index 59d996af3c..6f6588b16d 100644 --- a/bindings/nodejs/lib/index.ts +++ b/bindings/nodejs/lib/index.ts @@ -39,3 +39,37 @@ export * from './types'; export * from './utils'; export * from './wallet'; export * from './logger'; + +// For future reference to see what we return from rust as a serialized string +export type Result = { + // "error" | "panic" or other binding method response name + type: string; + payload: { + // All method names from types/bridge/__name__.name + // Or all variants of rust sdk Error type + type: string; + // If "ok", json payload + payload?: string; + // If !"ok", error + error?: string; + }; +}; + +function errorHandle(error: any): Error { + if (error instanceof TypeError) { + // neon or other bindings lib related error + throw error; + } else if (error instanceof Error) { + const err: Result = JSON.parse(error.message); + if (err.type == 'panic') { + return Error(err.payload.toString()); + } else { + return Error(err.payload.error); + } + } else { + // Something bad happened! Make sure we dont double parse + return TypeError(error); + } +} + +export { errorHandle }; diff --git a/bindings/nodejs/lib/secret_manager/secret-manager-method-handler.ts b/bindings/nodejs/lib/secret_manager/secret-manager-method-handler.ts index 639aa6a036..7e502f4b22 100644 --- a/bindings/nodejs/lib/secret_manager/secret-manager-method-handler.ts +++ b/bindings/nodejs/lib/secret_manager/secret-manager-method-handler.ts @@ -1,6 +1,7 @@ // Copyright 2021-2023 IOTA Stiftung // SPDX-License-Identifier: Apache-2.0 +import { errorHandle } from '..'; import { callSecretManagerMethodAsync, createSecretManager, @@ -19,11 +20,17 @@ export class SecretManagerMethodHandler { * @param options A secret manager type or a secret manager method handler. */ constructor(options: SecretManagerType | SecretManagerMethodHandler) { - // The rust secret manager object is not extensible - if (Object.isExtensible(options)) { - this.methodHandler = createSecretManager(JSON.stringify(options)); - } else { - this.methodHandler = options as SecretManagerMethodHandler; + try { + // The rust secret manager object is not extensible + if (Object.isExtensible(options)) { + this.methodHandler = createSecretManager( + JSON.stringify(options), + ); + } else { + this.methodHandler = options as SecretManagerMethodHandler; + } + } catch (error: any) { + throw errorHandle(error); } } @@ -35,9 +42,18 @@ export class SecretManagerMethodHandler { */ async callMethod(method: __SecretManagerMethods__): Promise { return callSecretManagerMethodAsync( - JSON.stringify(method), + // mapToObject is required to convert maps to array since they otherwise get serialized as `[{}]` even if not empty + JSON.stringify(method, function mapToObject(_key, value) { + if (value instanceof Map) { + return Object.fromEntries(value); + } else { + return value; + } + }), this.methodHandler, - ); + ).catch((error: any) => { + throw errorHandle(error); + }); } } diff --git a/bindings/nodejs/lib/wallet/wallet-method-handler.ts b/bindings/nodejs/lib/wallet/wallet-method-handler.ts index 8c45ddef37..bc12b7468f 100644 --- a/bindings/nodejs/lib/wallet/wallet-method-handler.ts +++ b/bindings/nodejs/lib/wallet/wallet-method-handler.ts @@ -19,9 +19,11 @@ import type { } from '../types/wallet'; import { Client } from '../client'; import { SecretManager } from '../secret_manager'; +import { errorHandle } from '..'; // The WalletMethodHandler class interacts with methods with the rust bindings. export class WalletMethodHandler { + // External rust object methodHandler: any; /** @@ -35,7 +37,19 @@ export class WalletMethodHandler { secretManager: options?.secretManager, }; - this.methodHandler = createWallet(JSON.stringify(walletOptions)); + try { + this.methodHandler = createWallet(JSON.stringify(walletOptions)); + } catch (error: any) { + throw errorHandle(error); + } + } + + destroy(): void { + try { + destroyWallet(this.methodHandler); + } catch (error: any) { + throw errorHandle(error); + } } /** @@ -55,17 +69,8 @@ export class WalletMethodHandler { } }), this.methodHandler, - ).catch((error: Error) => { - try { - if (error.message !== undefined) { - error = Error(JSON.parse(error.message).payload); - } else { - error = Error(JSON.parse(error.toString()).payload); - } - } catch (e) { - console.error(e); - } - return Promise.reject(error); + ).catch((error: any) => { + throw errorHandle(error); }); } @@ -98,11 +103,13 @@ export class WalletMethodHandler { eventTypes: WalletEventType[], callback: (error: Error, event: Event) => void, ): Promise { - return listenWalletAsync(eventTypes, callback, this.methodHandler); - } - - async destroy(): Promise { - return destroyWallet(this.methodHandler); + return listenWalletAsync( + eventTypes, + callback, + this.methodHandler, + ).catch((error: any) => { + throw errorHandle(error); + }); } /** @@ -113,28 +120,19 @@ export class WalletMethodHandler { const result = getClientFromWallet(this.methodHandler); return new Client(result); } catch (error: any) { - if (error.message !== undefined) { - throw Error(JSON.parse(error.message).payload); - } else { - throw Error(JSON.parse(error.toString()).payload); - } + throw errorHandle(error); } } /** * Get the secret manager associated with the wallet. */ - async getSecretManager(): Promise { - return new Promise((resolve, reject) => { - getSecretManagerFromWallet(this.methodHandler).then( - (result: any) => { - if (result.message !== undefined) { - reject(JSON.parse(result.message).payload); - } else { - resolve(new SecretManager(result)); - } - }, - ); - }); + getSecretManager(): SecretManager { + try { + let result = getSecretManagerFromWallet(this.methodHandler); + return new SecretManager(result); + } catch (error: any) { + throw errorHandle(error); + } } } diff --git a/bindings/nodejs/lib/wallet/wallet.ts b/bindings/nodejs/lib/wallet/wallet.ts index 0c5d7687e8..6522554841 100644 --- a/bindings/nodejs/lib/wallet/wallet.ts +++ b/bindings/nodejs/lib/wallet/wallet.ts @@ -81,7 +81,7 @@ export class Wallet { /** * Destroy the Wallet and drop its database connection. */ - async destroy(): Promise { + destroy(): void { return this.methodHandler.destroy(); } @@ -151,7 +151,7 @@ export class Wallet { /** * Get secret manager. */ - async getSecretManager(): Promise { + getSecretManager(): SecretManager { return this.methodHandler.getSecretManager(); } diff --git a/bindings/nodejs/src/client.rs b/bindings/nodejs/src/client.rs index a8d4caa56e..837fa6155c 100644 --- a/bindings/nodejs/src/client.rs +++ b/bindings/nodejs/src/client.rs @@ -50,10 +50,10 @@ impl ClientMethodHandler { (msg, is_err) } - Err(e) => { - log::error!("{:?}", e); - (format!("Couldn't parse to method with error - {e:?}"), true) - } + Err(e) => ( + serde_json::to_string(&Response::Error(e.into())).expect("json to string error"), + true, + ), } } } @@ -94,7 +94,7 @@ pub fn call_client_method(mut cx: FunctionContext) -> JsResult { let args = [ if is_error { - cx.string(response.clone()).upcast::() + cx.error(response.clone())?.upcast::() } else { cx.undefined().upcast::() }, diff --git a/bindings/nodejs/src/secret_manager.rs b/bindings/nodejs/src/secret_manager.rs index 7c5531638f..072cb452fa 100644 --- a/bindings/nodejs/src/secret_manager.rs +++ b/bindings/nodejs/src/secret_manager.rs @@ -88,7 +88,7 @@ pub fn call_secret_manager_method(mut cx: FunctionContext) -> JsResult() + cx.error(response.clone())?.upcast::() } else { cx.undefined().upcast::() }, diff --git a/bindings/nodejs/src/wallet.rs b/bindings/nodejs/src/wallet.rs index b937377249..f66a443e0a 100644 --- a/bindings/nodejs/src/wallet.rs +++ b/bindings/nodejs/src/wallet.rs @@ -55,13 +55,10 @@ impl WalletMethodHandler { (msg, is_err) } - Err(e) => { - log::error!("{:?}", e); - ( - serde_json::to_string(&Response::Error(e.into())).expect("json to string error"), - true, - ) - } + Err(e) => ( + serde_json::to_string(&Response::Error(e.into())).expect("json to string error"), + true, + ), } } } @@ -108,7 +105,7 @@ pub fn call_wallet_method(mut cx: FunctionContext) -> JsResult { let args = [ if is_error { - cx.string(response.clone()).upcast::() + cx.error(response.clone())?.upcast::() } else { cx.undefined().upcast::() }, @@ -199,10 +196,7 @@ pub fn get_client(mut cx: FunctionContext) -> JsResult { - cx - .throw_error(serde_json::to_string(&Response::Panic(e.to_string())).expect("json to string error")) - } + Err(e) => cx.throw_error(serde_json::to_string(&Response::Panic(e.to_string())).expect("json to string error")), } } diff --git a/bindings/wasm/lib/bindings.ts b/bindings/wasm/lib/bindings.ts index fded283753..10fad52d44 100644 --- a/bindings/wasm/lib/bindings.ts +++ b/bindings/wasm/lib/bindings.ts @@ -9,7 +9,8 @@ import { __UtilsMethods__ } from './utils'; // Import needs to be in a single line, otherwise it breaks // prettier-ignore // @ts-ignore: path is set to match runtime transpiled js path when bundled. -import { initLogger, createClient, destroyClient, createSecretManager, createWallet, callClientMethodAsync, callSecretManagerMethodAsync, callUtilsMethodRust, callWalletMethodAsync, destroyWallet, listenWalletAsync, getClientFromWallet, getSecretManagerFromWallet, listenMqtt, migrateStrongholdSnapshotV2ToV3 } from '../wasm/iota_sdk_wasm'; +import { initLogger, createClient, destroyClient, createSecretManager, createWallet, callClientMethodAsync, callSecretManagerMethodAsync, callUtilsMethodRust, callWalletMethodAsync, destroyWallet, listenWalletAsync, getClientFromWallet, getSecretManagerFromWallet as getSecretManagerFromWalletRust, listenMqtt, migrateStrongholdSnapshotV2ToV3 } from '../wasm/iota_sdk_wasm'; +import { SecretManagerMethodHandler, WalletMethodHandler } from '.'; const callUtilsMethod = (method: __UtilsMethods__): any => { const response = JSON.parse(callUtilsMethodRust(JSON.stringify(method))); @@ -20,6 +21,15 @@ const callUtilsMethod = (method: __UtilsMethods__): any => { } }; +const getSecretManagerFromWallet = ( + method: WalletMethodHandler, +): SecretManagerMethodHandler => { + // TODO figure out why this one is extensible but client isnt + let res = getSecretManagerFromWalletRust(method); + Object.preventExtensions(res); + return res; +}; + export { initLogger, createClient, diff --git a/bindings/wasm/src/client.rs b/bindings/wasm/src/client.rs index 5abf7d79fc..d704983512 100644 --- a/bindings/wasm/src/client.rs +++ b/bindings/wasm/src/client.rs @@ -1,45 +1,87 @@ // Copyright 2023 IOTA Stiftung // SPDX-License-Identifier: Apache-2.0 +use std::sync::{Arc, RwLock}; + use iota_sdk_bindings_core::{ call_client_method, iota_sdk::client::{Client, ClientBuilder}, ClientMethod, Response, }; -use wasm_bindgen::{prelude::wasm_bindgen, JsCast, JsValue}; -use wasm_bindgen_futures::future_to_promise; +use wasm_bindgen::{prelude::wasm_bindgen, JsError}; -use crate::{ArrayString, PromiseString}; +use crate::ArrayString; /// The Client method handler. #[wasm_bindgen(js_name = ClientMethodHandler)] pub struct ClientMethodHandler { - pub(crate) client: Client, + pub(crate) client: Arc>>, +} + +impl ClientMethodHandler { + pub(crate) fn new(client: Client) -> Self { + Self { + client: Arc::new(RwLock::new(Some(client))), + } + } +} + +macro_rules! client_pre { + ($method_handler:ident) => { + match $method_handler.client.read() { + Ok(handler) => { + if let Some(client) = handler.clone() { + Ok(client) + } else { + // Notify that the client was destroyed + Err(JsError::new( + &serde_json::to_string(&Response::Panic("Client was destroyed".to_string())) + .expect("json to string error"), + )) + } + } + Err(e) => Err(JsError::new( + &serde_json::to_string(&Response::Panic(e.to_string())).expect("json to string error"), + )), + } + }; } /// Creates a method handler with the given client options. #[wasm_bindgen(js_name = createClient)] #[allow(non_snake_case)] -pub fn create_client(clientOptions: String) -> Result { - let runtime = tokio::runtime::Builder::new_current_thread() - .build() - .map_err(|err| err.to_string())?; +pub fn create_client(clientOptions: String) -> Result { + let runtime = tokio::runtime::Builder::new_current_thread().build().map_err(|err| { + JsError::new(&serde_json::to_string(&Response::Panic(err.to_string())).expect("json to string error")) + })?; let client = runtime.block_on(async move { ClientBuilder::new() .from_json(&clientOptions) - .map_err(|err| err.to_string())? + .map_err(|err| { + JsError::new(&serde_json::to_string(&Response::Panic(err.to_string())).expect("json to string error")) + })? .finish() .await - .map_err(|err| err.to_string()) + .map_err(|err| { + JsError::new(&serde_json::to_string(&Response::Panic(err.to_string())).expect("json to string error")) + }) })?; - Ok(ClientMethodHandler { client }) + Ok(ClientMethodHandler::new(client)) } /// Necessary for compatibility with the node.js bindings. #[wasm_bindgen(js_name = destroyClient)] -pub async fn destroy_client(_client_method_handler: &ClientMethodHandler) -> Result<(), JsValue> { +pub fn destroy_client(client_method_handler: &ClientMethodHandler) -> Result<(), JsError> { + match client_method_handler.client.write() { + Ok(mut lock) => *lock = None, + Err(e) => { + return Err(JsError::new( + &serde_json::to_string(&Response::Panic(e.to_string())).expect("json to string error"), + )); + } + }; Ok(()) } @@ -48,22 +90,19 @@ pub async fn destroy_client(_client_method_handler: &ClientMethodHandler) -> Res /// Returns an error if the response itself is an error or panic. #[wasm_bindgen(js_name = callClientMethodAsync)] #[allow(non_snake_case)] -pub fn call_client_method_async(method: String, methodHandler: &ClientMethodHandler) -> Result { - let client: Client = methodHandler.client.clone(); - - let promise: js_sys::Promise = future_to_promise(async move { - let method: ClientMethod = serde_json::from_str(&method).map_err(|err| err.to_string())?; +pub async fn call_client_method_async(method: String, methodHandler: &ClientMethodHandler) -> Result { + let client = client_pre!(methodHandler)?; - let response = call_client_method(&client, method).await; - let ser = JsValue::from(serde_json::to_string(&response).map_err(|err| err.to_string())?); - match response { - Response::Error(_) | Response::Panic(_) => Err(ser), - _ => Ok(ser), - } - }); + let method: ClientMethod = serde_json::from_str(&method).map_err(|err| { + JsError::new(&serde_json::to_string(&Response::Panic(err.to_string())).expect("json to string error")) + })?; - // WARNING: this does not validate the return type. Check carefully. - Ok(promise.unchecked_into()) + let response = call_client_method(&client, method).await; + let ser = serde_json::to_string(&response).expect("json to string error"); + match response { + Response::Error(_) | Response::Panic(_) => Err(JsError::new(&ser)), + _ => Ok(ser), + } } /// MQTT is not supported for WebAssembly bindings. @@ -71,8 +110,11 @@ pub fn call_client_method_async(method: String, methodHandler: &ClientMethodHand /// Throws an error if called, only included for compatibility /// with the Node.js bindings TypeScript definitions. #[wasm_bindgen(js_name = listenMqtt)] -pub fn listen_mqtt(_topics: ArrayString, _callback: &js_sys::Function) -> Result<(), JsValue> { - let js_error = js_sys::Error::new("Client MQTT not supported for WebAssembly"); - - Err(JsValue::from(js_error)) +pub fn listen_mqtt(_topics: ArrayString, _callback: &js_sys::Function) -> Result<(), JsError> { + Err(JsError::new( + &serde_json::to_string(&Response::Panic( + "Client MQTT not supported for WebAssembly".to_string(), + )) + .expect("json to string error"), + )) } diff --git a/bindings/wasm/src/lib.rs b/bindings/wasm/src/lib.rs index cf5528046a..15e944e990 100644 --- a/bindings/wasm/src/lib.rs +++ b/bindings/wasm/src/lib.rs @@ -8,12 +8,12 @@ pub mod secret_manager; pub mod utils; pub mod wallet; -use wasm_bindgen::{prelude::wasm_bindgen, JsValue}; +use wasm_bindgen::{prelude::wasm_bindgen, JsError}; /// Initializes the console error panic hook for better panic messages. /// Gets automatically called when using wasm #[wasm_bindgen(start)] -pub fn start() -> Result<(), JsValue> { +pub fn start() -> Result<(), JsError> { console_error_panic_hook::set_once(); Ok(()) } @@ -22,7 +22,7 @@ pub fn start() -> Result<(), JsValue> { /// /// Calling this will enable all rust logs to be show #[wasm_bindgen(js_name = initLogger)] -pub async fn init_logger(_config: String) -> Result<(), JsValue> { +pub async fn init_logger(_config: String) -> Result<(), JsError> { wasm_logger::init(wasm_logger::Config::default()); Ok(()) } diff --git a/bindings/wasm/src/secret_manager.rs b/bindings/wasm/src/secret_manager.rs index ab08a2b2df..5eb89f921b 100644 --- a/bindings/wasm/src/secret_manager.rs +++ b/bindings/wasm/src/secret_manager.rs @@ -9,10 +9,7 @@ use iota_sdk_bindings_core::{ Response, SecretManagerMethod, }; use tokio::sync::RwLock; -use wasm_bindgen::{prelude::wasm_bindgen, JsCast, JsValue}; -use wasm_bindgen_futures::future_to_promise; - -use crate::PromiseString; +use wasm_bindgen::{prelude::wasm_bindgen, JsError, JsValue}; /// The SecretManager method handler. #[wasm_bindgen(js_name = SecretManagerMethodHandler)] @@ -20,6 +17,12 @@ pub struct SecretManagerMethodHandler { pub(crate) secret_manager: Arc>, } +impl SecretManagerMethodHandler { + pub(crate) fn new(secret_manager: Arc>) -> Self { + Self { secret_manager } + } +} + /// Creates a method handler with the given secret_manager options. #[wasm_bindgen(js_name = createSecretManager)] #[allow(non_snake_case)] @@ -37,24 +40,21 @@ pub fn create_secret_manager(options: String) -> Result Result { +) -> Result { let secret_manager = methodHandler.secret_manager.clone(); - let promise: js_sys::Promise = future_to_promise(async move { - let method: SecretManagerMethod = serde_json::from_str(&method).map_err(|err| err.to_string())?; + let method: SecretManagerMethod = serde_json::from_str(&method).map_err(|err| { + JsError::new(&serde_json::to_string(&Response::Panic(err.to_string())).expect("json to string error")) + })?; - let response = call_secret_manager_method(&secret_manager, method).await; - let ser = JsValue::from(serde_json::to_string(&response).map_err(|err| err.to_string())?); - match response { - Response::Error(_) | Response::Panic(_) => Err(ser), - _ => Ok(ser), - } - }); - - // WARNING: this does not validate the return type. Check carefully. - Ok(promise.unchecked_into()) + let response = call_secret_manager_method(&secret_manager, method).await; + let ser = serde_json::to_string(&response).expect("json to string error"); + match response { + Response::Error(_) | Response::Panic(_) => Err(JsError::new(&ser)), + _ => Ok(ser), + } } /// Stronghold snapshot migration is not supported for WebAssembly bindings. @@ -69,8 +69,11 @@ pub fn migrate_stronghold_snapshot_v2_to_v3( _rounds: u32, _new_path: Option, _new_password: Option, -) -> Result<(), JsValue> { - let js_error = js_sys::Error::new("Stronghold snapshot migration is not supported for WebAssembly"); - - Err(JsValue::from(js_error)) +) -> Result<(), JsError> { + Err(JsError::new( + &serde_json::to_string(&Response::Panic( + "Stronghold snapshot migration is not supported for WebAssembly".to_string(), + )) + .expect("json to string error"), + )) } diff --git a/bindings/wasm/src/wallet.rs b/bindings/wasm/src/wallet.rs index 3be06b063e..4ee6128109 100644 --- a/bindings/wasm/src/wallet.rs +++ b/bindings/wasm/src/wallet.rs @@ -11,10 +11,8 @@ use iota_sdk_bindings_core::{ }, Response, WalletMethod, WalletOptions, }; -use tokio::sync::{ - mpsc::{unbounded_channel, UnboundedReceiver, UnboundedSender} -}; -use wasm_bindgen::{prelude::wasm_bindgen, JsValue}; +use tokio::sync::mpsc::{unbounded_channel, UnboundedReceiver, UnboundedSender}; +use wasm_bindgen::{prelude::wasm_bindgen, JsError, JsValue}; use crate::{client::ClientMethodHandler, secret_manager::SecretManagerMethodHandler}; @@ -32,14 +30,15 @@ macro_rules! wallet_pre { Ok(wallet) } else { // Notify that the wallet was destroyed - Err(serde_json::to_string(&Response::Panic("Wallet was destroyed".to_string())) + Err(JsError::new( + &serde_json::to_string(&Response::Panic("Wallet was destroyed".to_string())) .expect("json to string error"), - ).into() + )) } } - Err(e) => { - Err(serde_json::to_string(&Response::Panic(e.to_string())).expect("json to string error").into()) - } + Err(e) => Err(JsError::new( + &serde_json::to_string(&Response::Panic(e.to_string())).expect("json to string error"), + )), } }; } @@ -47,14 +46,18 @@ macro_rules! wallet_pre { /// Creates a method handler with the given options. #[wasm_bindgen(js_name = createWallet)] #[allow(non_snake_case)] -pub fn create_wallet(options: String) -> Result { - let wallet_options = serde_json::from_str::(&options).map_err(|e| e.to_string())?; +pub fn create_wallet(options: String) -> Result { + let wallet_options = serde_json::from_str::(&options).map_err(|e| { + JsError::new(&serde_json::to_string(&Response::Panic(e.to_string())).expect("json to string error")) + })?; let wallet_method_handler = tokio::runtime::Builder::new_current_thread() .build() .unwrap() .block_on(async move { wallet_options.build().await }) - .map_err(|e| e.to_string())?; + .map_err(|e| { + JsError::new(&serde_json::to_string(&Response::Panic(e.to_string())).expect("json to string error")) + })?; Ok(WalletMethodHandler { wallet: Arc::new(RwLock::new(Some(wallet_method_handler))), @@ -62,46 +65,50 @@ pub fn create_wallet(options: String) -> Result { } #[wasm_bindgen(js_name = destroyWallet)] -pub async fn destroy_wallet(method_handler: &WalletMethodHandler) -> Result<(), JsValue> { +pub fn destroy_wallet(method_handler: &WalletMethodHandler) -> Result<(), JsError> { match method_handler.wallet.write() { Ok(mut lock) => *lock = None, Err(e) => { - return Err(serde_json::to_string(&Response::Panic(e.to_string())).expect("json to string error").into()); + return Err(JsError::new( + &serde_json::to_string(&Response::Panic(e.to_string())).expect("json to string error"), + )); } }; Ok(()) } #[wasm_bindgen(js_name = getClientFromWallet)] -pub async fn get_client(method_handler: &WalletMethodHandler) -> Result { +pub fn get_client(method_handler: &WalletMethodHandler) -> Result { let wallet = wallet_pre!(method_handler)?; let client = wallet.client().clone(); - Ok(ClientMethodHandler { client }) + Ok(ClientMethodHandler::new(client)) } #[wasm_bindgen(js_name = getSecretManagerFromWallet)] -pub async fn get_secret_manager(method_handler: &WalletMethodHandler) -> Result { +pub fn get_secret_manager(method_handler: &WalletMethodHandler) -> Result { let wallet = wallet_pre!(method_handler)?; - let secret_manager = wallet.get_secret_manager().clone(); + let mngr = wallet.get_secret_manager().clone(); - Ok(SecretManagerMethodHandler { secret_manager }) + Ok(SecretManagerMethodHandler::new(mngr)) } /// Handles a method, returns the response as a JSON-encoded string. /// /// Returns an error if the response itself is an error or panic. #[wasm_bindgen(js_name = callWalletMethodAsync)] -pub async fn call_wallet_method_async(method: String, method_handler: &WalletMethodHandler) -> Result { +pub async fn call_wallet_method_async(method: String, method_handler: &WalletMethodHandler) -> Result { let wallet = wallet_pre!(method_handler)?; - let method: WalletMethod = serde_json::from_str(&method).map_err(|err| err.to_string())?; + let method: WalletMethod = serde_json::from_str(&method).map_err(|err| JsError::new(&err.to_string()))?; let response = call_wallet_method(&wallet, method).await; match response { - Response::Error(e) => Err(serde_json::to_string(&Response::Panic(e.to_string())).expect("json to string error").into()), - Response::Panic(p) => Err(p.into()), - _ => Ok(serde_json::to_string(&response).map_err(|e| e.to_string())?), + Response::Error(e) => Err(JsError::new( + &serde_json::to_string(&Response::Panic(e.to_string())).expect("json to string error"), + )), + Response::Panic(p) => Err(JsError::new(&p)), + _ => Ok(serde_json::to_string(&response).map_err(|e| JsError::new(&e.to_string()))?), } } @@ -118,33 +125,39 @@ pub async fn listen_wallet( vec: js_sys::Array, callback: js_sys::Function, method_handler: &WalletMethodHandler, -) -> Result { +) -> Result { let mut event_types = Vec::with_capacity(vec.length() as _); for event_type in vec.keys() { // We know the built-in iterator for set elements won't throw // exceptions, so just unwrap the element. let event_type = event_type.unwrap().as_f64().unwrap() as u8; - let wallet_event_type = WalletEventType::try_from(event_type).map_err(JsValue::from)?; + let wallet_event_type = WalletEventType::try_from(event_type).map_err(|e| { + JsError::new(&serde_json::to_string(&Response::Panic(e.to_string())).expect("json to string error")) + })?; event_types.push(wallet_event_type); } let (tx, mut rx): (UnboundedSender, UnboundedReceiver) = unbounded_channel(); let wallet = wallet_pre!(method_handler)?; - wallet.listen(event_types, move |wallet_event| { - tx.send(wallet_event.clone()).unwrap(); - }) - .await; + wallet + .listen(event_types, move |wallet_event| { + tx.send(wallet_event.clone()).unwrap(); + }) + .await; // Spawn on the same thread a continuous loop to check the channel wasm_bindgen_futures::spawn_local(async move { while let Some(wallet_event) = rx.recv().await { - callback - .call1( - &JsValue::NULL, - &JsValue::from(serde_json::to_string(&wallet_event).unwrap()), - ) - // Safe to unwrap, our callback has no return - .unwrap(); + let res = callback.call2( + &JsValue::NULL, + &JsValue::UNDEFINED, + &JsValue::from(serde_json::to_string(&wallet_event).unwrap()), + ); + // Call callback again with the error this time, to prevent wasm crashing. + // This does mean the callback is called a second time instead of once. + if let Err(e) = res { + callback.call2(&JsValue::NULL, &e, &JsValue::UNDEFINED).unwrap(); + } } // No more links to the unbounded_channel, exit loop }); From 1d18374560dc41b7fc25e40650de1e16f4503d05 Mon Sep 17 00:00:00 2001 From: Brord van Wierst Date: Thu, 7 Sep 2023 19:03:29 +0200 Subject: [PATCH 10/12] final changes --- .../nodejs/lib/client/client-method-handler.ts | 12 ++++-------- bindings/nodejs/lib/client/client.ts | 6 +++--- bindings/nodejs/lib/index.ts | 17 ++++++++++++----- bindings/nodejs/src/client.rs | 7 ++++++- bindings/nodejs/tests/client/examples.spec.ts | 2 +- bindings/nodejs/tests/wallet/wallet.spec.ts | 16 ++++++++-------- bindings/wasm/lib/bindings.ts | 2 +- bindings/wasm/src/client.rs | 6 +++--- bindings/wasm/src/secret_manager.rs | 14 +++++++++----- 9 files changed, 47 insertions(+), 35 deletions(-) diff --git a/bindings/nodejs/lib/client/client-method-handler.ts b/bindings/nodejs/lib/client/client-method-handler.ts index fdc3a59a94..5fc500873b 100644 --- a/bindings/nodejs/lib/client/client-method-handler.ts +++ b/bindings/nodejs/lib/client/client-method-handler.ts @@ -5,7 +5,7 @@ import { errorHandle } from '..'; import { callClientMethodAsync, createClient, - listenMqtt, + listenMqtt as listenMqttRust, destroyClient, } from '../bindings'; import type { IClientOptions, __ClientMethods__ } from '../types/client'; @@ -68,16 +68,12 @@ export class ClientMethodHandler { * @param topics The topics to listen to. * @param callback The callback to be called when an MQTT event is received. */ - listen( + listenMqtt( topics: string[], callback: (error: Error, result: string) => void, - ): Promise { + ): void { try { - return listenMqtt(topics, callback, this.methodHandler).catch( - (error: any) => { - throw errorHandle(error); - }, - ); + listenMqttRust(topics, callback, this.methodHandler); } catch (error: any) { throw errorHandle(error); } diff --git a/bindings/nodejs/lib/client/client.ts b/bindings/nodejs/lib/client/client.ts index cc0f22f363..825c971ace 100644 --- a/bindings/nodejs/lib/client/client.ts +++ b/bindings/nodejs/lib/client/client.ts @@ -1146,11 +1146,11 @@ export class Client { * * @param topics An array of MQTT topics to listen to. */ - async listenMqtt( + listenMqtt( topics: string[], callback: (error: Error, result: string) => void, - ): Promise { - return this.methodHandler.listen(topics, callback); + ): void { + return this.methodHandler.listenMqtt(topics, callback); } /** diff --git a/bindings/nodejs/lib/index.ts b/bindings/nodejs/lib/index.ts index 6f6588b16d..70dc25992f 100644 --- a/bindings/nodejs/lib/index.ts +++ b/bindings/nodejs/lib/index.ts @@ -60,14 +60,21 @@ function errorHandle(error: any): Error { // neon or other bindings lib related error throw error; } else if (error instanceof Error) { - const err: Result = JSON.parse(error.message); - if (err.type == 'panic') { - return Error(err.payload.toString()); - } else { - return Error(err.payload.error); + try { + const err: Result = JSON.parse(error.message); + if (err.type == 'panic') { + return Error(err.payload.toString()); + } else { + return Error(err.payload.error); + } + } catch (err: any) { + // json error, SyntaxError, we must have send a bad error + return error; } } else { // Something bad happened! Make sure we dont double parse + //Possible scenarios: + // - "json to string error: x" return TypeError(error); } } diff --git a/bindings/nodejs/src/client.rs b/bindings/nodejs/src/client.rs index 837fa6155c..0f4e3696d1 100644 --- a/bindings/nodejs/src/client.rs +++ b/bindings/nodejs/src/client.rs @@ -126,7 +126,9 @@ pub fn listen_mqtt(mut cx: FunctionContext) -> JsResult { let mut topics = Vec::with_capacity(vec.len()); for topic_string in vec { let topic = topic_string.downcast::(&mut cx).unwrap(); - topics.push(Topic::new(topic.value(&mut cx).as_str()).expect("invalid MQTT topic")); + topics.push(Topic::new(topic.value(&mut cx).as_str()).or_else(|e| { + cx.throw_error(serde_json::to_string(&Response::Error(e.into())).expect("json to string error")) + })?); } let callback = Arc::new(cx.argument::(1)?.root(&mut cx)); @@ -157,7 +159,10 @@ pub fn listen_mqtt(mut cx: FunctionContext) -> JsResult { fn call_event_callback(channel: &neon::event::Channel, event_data: String, callback: Arc) { channel.send(move |mut cx| { let cb = (*callback).to_inner(&mut cx); + // instance of class we call on, its a global fn so "undefined" let this = cx.undefined(); + + // callback is fn(err, event) let args = [ cx.undefined().upcast::(), cx.string(event_data).upcast::(), diff --git a/bindings/nodejs/tests/client/examples.spec.ts b/bindings/nodejs/tests/client/examples.spec.ts index fadb8bd3c7..07ddad1db7 100644 --- a/bindings/nodejs/tests/client/examples.spec.ts +++ b/bindings/nodejs/tests/client/examples.spec.ts @@ -196,7 +196,7 @@ describe.skip('Main examples', () => { localPow: true, }); - await client.destroy(); + client.destroy(); try { const _info = await client.getInfo(); diff --git a/bindings/nodejs/tests/wallet/wallet.spec.ts b/bindings/nodejs/tests/wallet/wallet.spec.ts index 166a08e632..5459721925 100644 --- a/bindings/nodejs/tests/wallet/wallet.spec.ts +++ b/bindings/nodejs/tests/wallet/wallet.spec.ts @@ -36,8 +36,8 @@ describe('Wallet', () => { expect(account.getMetadata().index).toStrictEqual(0); - await wallet.destroy() - removeDir(storagePath) + wallet.destroy(); + removeDir(storagePath); }, 8000); it('generate address', async () => { @@ -85,8 +85,8 @@ describe('Wallet', () => { 'tst1qzp37j45rkfmqn05fapq66vyw0vkmz5zqhmeuey5fked0wt4ry43jeqp2wv', ); - await wallet.destroy() - removeDir(storagePath) + wallet.destroy(); + removeDir(storagePath); }, 8000); it('recreate wallet', async () => { @@ -123,13 +123,13 @@ describe('Wallet', () => { const localPoW = await client.getLocalPow(); expect(localPoW).toBeTruthy(); - await wallet.destroy(); + wallet.destroy(); const recreatedWallet = new Wallet(walletOptions); const accounts = await recreatedWallet.getAccounts(); expect(accounts.length).toStrictEqual(1); - await recreatedWallet.destroy() + recreatedWallet.destroy(); removeDir(storagePath) }, 20000); @@ -162,7 +162,7 @@ describe('Wallet', () => { expect(account.getMetadata().index).toStrictEqual(0); - await wallet.destroy(); + wallet.destroy(); try { const _accounts = await wallet.getAccounts(); @@ -177,7 +177,7 @@ describe('Wallet', () => { } catch (err: any) { expect(err.message).toContain('Wallet was destroyed'); } - removeDir(storagePath) + removeDir(storagePath); }, 35000); }) diff --git a/bindings/wasm/lib/bindings.ts b/bindings/wasm/lib/bindings.ts index 10fad52d44..972d806e7c 100644 --- a/bindings/wasm/lib/bindings.ts +++ b/bindings/wasm/lib/bindings.ts @@ -25,7 +25,7 @@ const getSecretManagerFromWallet = ( method: WalletMethodHandler, ): SecretManagerMethodHandler => { // TODO figure out why this one is extensible but client isnt - let res = getSecretManagerFromWalletRust(method); + const res = getSecretManagerFromWalletRust(method); Object.preventExtensions(res); return res; }; diff --git a/bindings/wasm/src/client.rs b/bindings/wasm/src/client.rs index d704983512..f341c29480 100644 --- a/bindings/wasm/src/client.rs +++ b/bindings/wasm/src/client.rs @@ -59,12 +59,12 @@ pub fn create_client(clientOptions: String) -> Result Result { - let secret_manager_dto = serde_json::from_str::(&options).map_err(|err| err.to_string())?; - let secret_manager = SecretManager::try_from(secret_manager_dto).map_err(|err| err.to_string())?; + let secret_manager_dto = serde_json::from_str::(&options).map_err(|err| { + JsError::new(&serde_json::to_string(&Response::Error(err.into())).expect("json to string error")) + })?; + let secret_manager = SecretManager::try_from(secret_manager_dto).map_err(|err| { + JsError::new(&serde_json::to_string(&Response::Error(err.into())).expect("json to string error")) + })?; Ok(SecretManagerMethodHandler { secret_manager: Arc::new(RwLock::new(secret_manager)), @@ -44,12 +48,12 @@ pub async fn call_secret_manager_method_async( method: String, methodHandler: &SecretManagerMethodHandler, ) -> Result { - let secret_manager = methodHandler.secret_manager.clone(); + let secret_manager = &methodHandler.secret_manager; let method: SecretManagerMethod = serde_json::from_str(&method).map_err(|err| { - JsError::new(&serde_json::to_string(&Response::Panic(err.to_string())).expect("json to string error")) + JsError::new(&serde_json::to_string(&Response::Error(err.into())).expect("json to string error")) })?; - let response = call_secret_manager_method(&secret_manager, method).await; + let response = call_secret_manager_method(secret_manager, method).await; let ser = serde_json::to_string(&response).expect("json to string error"); match response { Response::Error(_) | Response::Panic(_) => Err(JsError::new(&ser)), From d87a331bc495c9e8d84918abdc00472899ccf407 Mon Sep 17 00:00:00 2001 From: Brord van Wierst Date: Thu, 7 Sep 2023 19:25:25 +0200 Subject: [PATCH 11/12] removed unused PromiseString --- bindings/wasm/src/lib.rs | 3 --- 1 file changed, 3 deletions(-) diff --git a/bindings/wasm/src/lib.rs b/bindings/wasm/src/lib.rs index 15e944e990..066808b9ec 100644 --- a/bindings/wasm/src/lib.rs +++ b/bindings/wasm/src/lib.rs @@ -31,7 +31,4 @@ pub async fn init_logger(_config: String) -> Result<(), JsError> { extern "C" { #[wasm_bindgen(typescript_type = "string[]")] pub type ArrayString; - - #[wasm_bindgen(typescript_type = "Promise")] - pub type PromiseString; } From d846c688fa21e5ead4e10e8dd66b8559f1320e14 Mon Sep 17 00:00:00 2001 From: Brord van Wierst Date: Thu, 7 Sep 2023 19:26:52 +0200 Subject: [PATCH 12/12] lint --- bindings/nodejs/lib/wallet/wallet-method-handler.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bindings/nodejs/lib/wallet/wallet-method-handler.ts b/bindings/nodejs/lib/wallet/wallet-method-handler.ts index bc12b7468f..073a42dc4c 100644 --- a/bindings/nodejs/lib/wallet/wallet-method-handler.ts +++ b/bindings/nodejs/lib/wallet/wallet-method-handler.ts @@ -129,7 +129,7 @@ export class WalletMethodHandler { */ getSecretManager(): SecretManager { try { - let result = getSecretManagerFromWallet(this.methodHandler); + const result = getSecretManagerFromWallet(this.methodHandler); return new SecretManager(result); } catch (error: any) { throw errorHandle(error);