diff --git a/Cargo.lock b/Cargo.lock index af5a246..a19fd36 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -237,7 +237,6 @@ dependencies = [ "bitcoin", "console_error_panic_hook", "getrandom 0.2.16", - "gloo-timers", "serde", "serde-wasm-bindgen", "wasm-bindgen", @@ -496,18 +495,6 @@ version = "0.31.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "07e28edb80900c19c28f1072f2e8aeca7fa06b23cd4169cefe1af5aa3260783f" -[[package]] -name = "gloo-timers" -version = "0.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bbb143cf96099802033e0d4f4963b19fd2e0b728bcf076cd9cf7f6634f092994" -dependencies = [ - "futures-channel", - "futures-core", - "js-sys", - "wasm-bindgen", -] - [[package]] name = "hashbrown" version = "0.14.5" diff --git a/Cargo.toml b/Cargo.toml index b2877cb..e12a7fc 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -34,11 +34,12 @@ wasm-bindgen-futures = { version = "0.4.50", optional = true } anyhow = { version = "1.0.98", default-features = false } serde = { version = "1.0.219", default-features = false, features = ["derive"] } serde-wasm-bindgen = "0.6.5" -web-sys = { version = "0.3.77", default-features = false } +web-sys = { version = "0.3.77", default-features = false, features = [ + "Window", +] } # Compatibility to compile to WASM getrandom = { version = "0.2.16", features = ["js"] } -gloo-timers = { version = "0.3.0", features = ["futures"] } # Bitcoin dependencies bdk_wallet = { version = "2.0.0" } diff --git a/src/bitcoin/esplora_client.rs b/src/bitcoin/esplora_client.rs index a4c474a..149f34b 100644 --- a/src/bitcoin/esplora_client.rs +++ b/src/bitcoin/esplora_client.rs @@ -6,18 +6,25 @@ use bdk_wallet::{ chain::spk_client::{FullScanRequest as BdkFullScanRequest, SyncRequest as BdkSyncRequest}, KeychainKind, }; -use wasm_bindgen::prelude::wasm_bindgen; +use wasm_bindgen::{ + prelude::{wasm_bindgen, Closure}, + JsCast, JsValue, +}; +use wasm_bindgen_futures::JsFuture; +use web_sys::js_sys::{Function, Promise}; use crate::{ result::JsResult, types::{FeeEstimates, FullScanRequest, SyncRequest, Transaction, Txid, Update}, }; -use std::time::Duration; +use std::{ + future::Future, + pin::Pin, + task::{Context, Poll}, + time::Duration, +}; use bdk_esplora::esplora_client::Sleeper; -use gloo_timers::future::{sleep, TimeoutFuture}; - -use crate::utils::SendSyncWrapper; #[wasm_bindgen] pub struct EsploraClient { @@ -27,8 +34,10 @@ pub struct EsploraClient { #[wasm_bindgen] impl EsploraClient { #[wasm_bindgen(constructor)] - pub fn new(url: &str) -> JsResult { - let client = Builder::new(url).build_async_with_sleeper::()?; + pub fn new(url: &str, max_retries: usize) -> JsResult { + let client = Builder::new(url) + .max_retries(max_retries) + .build_async_with_sleeper::()?; Ok(EsploraClient { client }) } @@ -65,13 +74,34 @@ impl EsploraClient { } } -#[derive(Clone)] +struct WebSleep(JsFuture); + +impl Future for WebSleep { + type Output = (); + fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<()> { + // delegate to the inner JsFuture + Pin::new(&mut self.get_mut().0).poll(cx).map(|_| ()) + } +} + +// SAFETY: Wasm is single-threaded; the value is never accessed concurrently. +unsafe impl Send for WebSleep {} + +#[derive(Clone, Copy)] struct WebSleeper; impl Sleeper for WebSleeper { - type Sleep = SendSyncWrapper; + type Sleep = WebSleep; fn sleep(dur: Duration) -> Self::Sleep { - SendSyncWrapper(sleep(dur)) + let ms = dur.as_millis() as i32; + let promise = Promise::new(&mut |resolve, _reject| { + let cb = Closure::once_into_js(move || resolve.call0(&JsValue::NULL).unwrap()); + web_sys::window() + .unwrap() + .set_timeout_with_callback_and_timeout_and_arguments_0(cb.unchecked_ref::(), ms) + .unwrap(); + }); + WebSleep(JsFuture::from(promise)) } } diff --git a/src/utils/future.rs b/src/utils/future.rs deleted file mode 100644 index 61d8e2d..0000000 --- a/src/utils/future.rs +++ /dev/null @@ -1,24 +0,0 @@ -use std::future::Future; -use std::pin::Pin; -use std::task::{Context, Poll}; - -// Wrap a future that is not `Send` or `Sync` and make it `Send` and `Sync` -pub struct SendSyncWrapper(pub F); - -unsafe impl Send for SendSyncWrapper {} -unsafe impl Sync for SendSyncWrapper {} - -impl Future for SendSyncWrapper -where - F: Future, -{ - type Output = F::Output; - - fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll { - // SAFETY: Since we're in a single-threaded WASM environment, this is safe. - unsafe { - let this = self.get_unchecked_mut(); - Pin::new_unchecked(&mut this.0).poll(cx) - } - } -} diff --git a/src/utils/mod.rs b/src/utils/mod.rs index da66655..96ba485 100644 --- a/src/utils/mod.rs +++ b/src/utils/mod.rs @@ -1,12 +1,10 @@ mod descriptor; -mod future; #[cfg(feature = "debug")] mod panic_hook; pub mod result; pub use descriptor::*; -pub use future::SendSyncWrapper; #[cfg(feature = "debug")] pub use panic_hook::set_panic_hook; diff --git a/tests/browser.rs b/tests/browser.rs index 2dcb50a..241f4c3 100644 --- a/tests/browser.rs +++ b/tests/browser.rs @@ -23,7 +23,7 @@ async fn test_browser() { "wpkh(tprv8ZgxMBicQKsPe2qpAuh1K1Hig72LCoP4JgNxZM2ZRWHZYnpuw5oHoGBsQm7Qb8mLgPpRJVn3hceWgGQRNbPD6x1pp2Qme2YFRAPeYh7vmvE/84'/1'/0'/0/*)#a6kgzlgq".into(), "wpkh(tprv8ZgxMBicQKsPe2qpAuh1K1Hig72LCoP4JgNxZM2ZRWHZYnpuw5oHoGBsQm7Qb8mLgPpRJVn3hceWgGQRNbPD6x1pp2Qme2YFRAPeYh7vmvE/84'/1'/0'/1/*)#vwnfl2cc".into(), ).expect("wallet"); - let blockchain_client = EsploraClient::new("https://mutinynet.com/api").expect("esplora_client"); + let blockchain_client = EsploraClient::new("https://mutinynet.com/api", 6).expect("esplora_client"); let block_height = wallet.latest_checkpoint().height(); assert_eq!(block_height, 0); diff --git a/tests/node/integration/esplora.test.ts b/tests/node/integration/esplora.test.ts index 9e2de11..610c064 100644 --- a/tests/node/integration/esplora.test.ts +++ b/tests/node/integration/esplora.test.ts @@ -28,7 +28,7 @@ describe("Esplora client", () => { let feeRate: FeeRate; let wallet: Wallet; - const esploraClient = new EsploraClient(esploraUrl); + const esploraClient = new EsploraClient(esploraUrl, 0); it("creates a new wallet", () => { wallet = Wallet.create(network, externalDescriptor, internalDescriptor);