From 2331abd860b8b30e0af187c14346b58cd85e4e70 Mon Sep 17 00:00:00 2001 From: Yuki Kishimoto Date: Fri, 26 Jan 2024 11:01:05 +0100 Subject: [PATCH 1/4] sdk: add check for event size limit --- crates/nostr-sdk/src/relay/mod.rs | 105 +++++++++++++++++++----------- 1 file changed, 67 insertions(+), 38 deletions(-) diff --git a/crates/nostr-sdk/src/relay/mod.rs b/crates/nostr-sdk/src/relay/mod.rs index f119f3fdd..e6028380c 100644 --- a/crates/nostr-sdk/src/relay/mod.rs +++ b/crates/nostr-sdk/src/relay/mod.rs @@ -57,6 +57,9 @@ const PING_INTERVAL: u64 = 55; /// [`Relay`] error #[derive(Debug, Error)] pub enum Error { + /// MessageHandle error + #[error(transparent)] + MessageHandle(#[from] MessageHandleError), /// Negentropy error #[error(transparent)] Negentropy(#[from] negentropy::Error), @@ -122,6 +125,22 @@ pub enum Error { /// Unknown negentropy error #[error("unknown negentropy error")] UnknownNegentropyError, + /// Relay message too large + #[error("Received message too large: size={size}, max_size={max_size}")] + RelayMessageTooLarge { + /// Message size + size: usize, + /// Max message size + max_size: usize, + }, + /// Event too large + #[error("Received event too large: size={size}, max_size={max_size}")] + EventTooLarge { + /// Event size + size: usize, + /// Max event size + max_size: usize, + }, } /// Relay connection status @@ -760,43 +779,39 @@ impl Relay { thread::spawn(async move { tracing::debug!("Relay Message Thread Started"); - async fn func(relay: &Relay, data: Vec) -> bool { + async fn func(relay: &Relay, data: Vec) -> Result { let size: usize = data.len(); let max_size: usize = relay.limits.messages.max_size as usize; relay.stats.add_bytes_received(size); - if size <= max_size { - match RawRelayMessage::from_json(&data) { - Ok(msg) => { - tracing::trace!( - "Received message from {}: {:?}", - relay.url, - msg - ); - if let Err(err) = relay - .pool_sender - .send(RelayPoolMessage::ReceivedMsg { - relay_url: relay.url(), - msg, - }) - .await - { - tracing::error!( - "Impossible to send ReceivedMsg to pool: {}", - &err - ); - return true; // Exit - }; - } - Err(e) => match e { - MessageHandleError::EmptyMsg => (), - _ => tracing::error!("{e}: {}", String::from_utf8_lossy(&data)), - }, - }; - } else { - tracing::error!("Received message too large from {}: size={size}, max_size={max_size}", relay.url); + + if size > max_size { + return Err(Error::RelayMessageTooLarge { size, max_size }); } - false + let msg = RawRelayMessage::from_json(&data)?; + tracing::trace!("Received message from {}: {:?}", relay.url, msg); + + if let RawRelayMessage::Event { event, .. } = &msg { + let size: usize = event.to_string().as_bytes().len(); + let max_size: usize = relay.limits.events.max_size as usize; + if size > max_size { + return Err(Error::EventTooLarge { size, max_size }); + } + } + + if let Err(err) = relay + .pool_sender + .send(RelayPoolMessage::ReceivedMsg { + relay_url: relay.url(), + msg, + }) + .await + { + tracing::error!("Impossible to send ReceivedMsg to pool: {}", &err); + return Ok(true); // Exit + }; + + Ok(false) } #[cfg(not(target_arch = "wasm32"))] @@ -825,9 +840,16 @@ impl Relay { }, _ => { let data: Vec = msg.into_data(); - let exit: bool = func(&relay, data).await; - if exit { - break; + match func(&relay, data).await { + Ok(exit) => { + if exit { + break; + } + } + Err(e) => tracing::error!( + "Impossible to handle relay message from {}: {e}", + relay.url + ), } } } @@ -837,9 +859,16 @@ impl Relay { #[cfg(target_arch = "wasm32")] while let Some(msg) = ws_rx.next().await { let data: Vec = msg.as_ref().to_vec(); - let exit: bool = func(&relay, data).await; - if exit { - break; + match func(&relay, data).await { + Ok(exit) => { + if exit { + break; + } + } + Err(e) => tracing::error!( + "Impossible to handle relay message from {}: {e}", + relay.url + ), } } From 3f8714e47143e8aa39b658e4ed6880965676f496 Mon Sep 17 00:00:00 2001 From: Yuki Kishimoto Date: Fri, 26 Jan 2024 12:58:57 +0100 Subject: [PATCH 2/4] sdk: improve negentropy reconciliation Increase `Limits` Add `NegentropyDirection` --- crates/nostr-sdk/src/lib.rs | 6 +- crates/nostr-sdk/src/relay/limits.rs | 6 +- crates/nostr-sdk/src/relay/mod.rs | 175 +++++++++++++++++--------- crates/nostr-sdk/src/relay/options.rs | 88 +++++-------- 4 files changed, 154 insertions(+), 121 deletions(-) diff --git a/crates/nostr-sdk/src/lib.rs b/crates/nostr-sdk/src/lib.rs index 1f769c73f..b429dfd50 100644 --- a/crates/nostr-sdk/src/lib.rs +++ b/crates/nostr-sdk/src/lib.rs @@ -40,9 +40,9 @@ pub mod util; pub use self::client::blocking; pub use self::client::{Client, ClientBuilder, ClientSigner, Options}; pub use self::relay::{ - ActiveSubscription, FilterOptions, InternalSubscriptionId, NegentropyOptions, Relay, - RelayConnectionStats, RelayOptions, RelayPoolNotification, RelayPoolOptions, RelaySendOptions, - RelayStatus, + ActiveSubscription, FilterOptions, InternalSubscriptionId, NegentropyDirection, + NegentropyOptions, Relay, RelayConnectionStats, RelayOptions, RelayPoolNotification, + RelayPoolOptions, RelaySendOptions, RelayStatus, }; #[cfg(feature = "blocking")] diff --git a/crates/nostr-sdk/src/relay/limits.rs b/crates/nostr-sdk/src/relay/limits.rs index 4383e5972..13503010e 100644 --- a/crates/nostr-sdk/src/relay/limits.rs +++ b/crates/nostr-sdk/src/relay/limits.rs @@ -16,8 +16,10 @@ pub struct Limits { impl Default for Limits { fn default() -> Self { Self { - messages: MessagesLimits { max_size: 128_000 }, - events: EventsLimits { max_size: 65_536 }, + messages: MessagesLimits { + max_size: 5_250_000, + }, + events: EventsLimits { max_size: 70_000 }, } } } diff --git a/crates/nostr-sdk/src/relay/mod.rs b/crates/nostr-sdk/src/relay/mod.rs index e6028380c..8211d5b48 100644 --- a/crates/nostr-sdk/src/relay/mod.rs +++ b/crates/nostr-sdk/src/relay/mod.rs @@ -7,7 +7,6 @@ use std::collections::{HashMap, HashSet}; #[cfg(not(target_arch = "wasm32"))] use std::net::SocketAddr; -use std::ops::Mul; use std::sync::atomic::{AtomicBool, Ordering}; use std::sync::Arc; use std::time::Duration; @@ -40,9 +39,13 @@ mod stats; pub use self::limits::Limits; pub use self::options::{ - FilterOptions, NegentropyOptions, RelayOptions, RelayPoolOptions, RelaySendOptions, + FilterOptions, NegentropyDirection, NegentropyOptions, RelayOptions, RelayPoolOptions, + RelaySendOptions, +}; +use self::options::{ + MAX_ADJ_RETRY_SEC, MIN_RETRY_SEC, NEGENTROPY_BATCH_SIZE_DOWN, NEGENTROPY_HIGH_WATER_UP, + NEGENTROPY_LOW_WATER_UP, }; -use self::options::{MAX_ADJ_RETRY_SEC, MIN_RETRY_SEC}; pub use self::pool::{RelayPoolMessage, RelayPoolNotification}; pub use self::stats::RelayConnectionStats; #[cfg(feature = "blocking")] @@ -1508,10 +1511,12 @@ impl Relay { items: Vec<(EventId, Timestamp)>, opts: NegentropyOptions, ) -> Result<(), Error> { + // Check if read option is disabled if !self.opts.get_read() { return Err(Error::ReadDisabled); } + // Check if relay is connected if !self.is_connected().await && self.stats.attempts() > 1 && self.stats.uptime() < MIN_UPTIME @@ -1519,20 +1524,17 @@ impl Relay { return Err(Error::NotConnected); } - let id_size: usize = 32; - - let mut negentropy = Negentropy::new(id_size, Some(4_096))?; - + // Compose negentropy struct, add items and seal + let mut negentropy = Negentropy::new(32, Some(20_000))?; for (id, timestamp) in items.into_iter() { let id = Bytes::from_slice(id.as_bytes()); negentropy.add_item(timestamp.as_u64(), id)?; } - negentropy.seal()?; + // Send initial negentropy message let sub_id = SubscriptionId::generate(); let open_msg = ClientMessage::neg_open(&mut negentropy, &sub_id, filter)?; - self.send_msg(open_msg, Some(Duration::from_secs(10))) .await?; @@ -1580,6 +1582,16 @@ impl Relay { .await .ok_or(Error::Timeout)??; + let do_up: bool = opts.direction.do_up(); + let do_down: bool = opts.direction.do_down(); + let mut in_flight_up: HashSet = HashSet::new(); + let mut in_flight_down = false; + let mut sync_done = false; + let mut have_ids: Vec = Vec::new(); + let mut need_ids: Vec = Vec::new(); + let down_sub_id: SubscriptionId = SubscriptionId::generate(); + + // Start reconciliation while let Ok(notification) = notifications.recv().await { match notification { RelayPoolNotification::Message { relay_url, message } => { @@ -1591,56 +1603,18 @@ impl Relay { } => { if subscription_id == sub_id { let query: Bytes = Bytes::from_hex(message)?; - let mut have_ids: Vec = Vec::new(); - let mut need_ids: Vec = Vec::new(); let msg: Option = negentropy.reconcile_with_ids( &query, &mut have_ids, &mut need_ids, )?; - if opts.bidirectional { - let ids = have_ids - .into_iter() - .filter_map(|id| EventId::from_slice(&id).ok()); - let filter = Filter::new().ids(ids); - let events: Vec = - self.database.query(vec![filter], Order::Desc).await?; - let msgs: Vec = - events.into_iter().map(ClientMessage::event).collect(); - if let Err(e) = self - .batch_msg(msgs, Some(opts.batch_send_timeout)) - .await - { - tracing::error!("negentropy reconciliation: impossible to batch events to {}: {e}", self.url); - } - } - - if need_ids.is_empty() { - tracing::info!( - "Negentropy reconciliation terminated for {}", - self.url - ); - break; + if !do_up { + have_ids.clear(); } - let ids = need_ids - .into_iter() - .filter_map(|id| EventId::from_slice(&id).ok()); - let filter = Filter::new().ids(ids); - if !filter.ids.is_empty() { - let timeout: Duration = opts.static_get_events_timeout - + opts - .relative_get_events_timeout - .mul(filter.ids.len() as u32); - self.get_events_of( - vec![filter], - timeout, - FilterOptions::ExitOnEOSE, - ) - .await?; - } else { - tracing::warn!("negentropy reconciliation: tried to send empty filters to {}", self.url); + if !do_down { + need_ids.clear(); } match msg { @@ -1658,13 +1632,7 @@ impl Relay { ) .await?; } - None => { - tracing::info!( - "Negentropy reconciliation terminated for {}", - self.url - ); - break; - } + None => sync_done = true, } } } @@ -1676,8 +1644,87 @@ impl Relay { return Err(Error::NegentropyReconciliation(code)); } } + RelayMessage::Ok { + event_id, + status, + message, + } => { + if in_flight_up.remove(&event_id) && !status { + tracing::error!( + "Unable to upload event {event_id} to {}: {message}", + self.url + ); + } + } + RelayMessage::EndOfStoredEvents(id) => { + if id == down_sub_id { + in_flight_down = false; + } + } _ => (), } + + // Get/Send events + if do_up + && !have_ids.is_empty() + && in_flight_up.len() <= NEGENTROPY_LOW_WATER_UP + { + let mut num_sent = 0; + + while !have_ids.is_empty() + && in_flight_up.len() < NEGENTROPY_HIGH_WATER_UP + { + if let Some(id) = have_ids.pop() { + if let Ok(event_id) = EventId::from_slice(&id) { + match self.database.event_by_id(event_id).await { + Ok(event) => { + in_flight_up.insert(event_id); + self.send_msg(ClientMessage::event(event), None) + .await?; + num_sent += 1; + } + Err(e) => tracing::error!("Couldn't upload event: {e}"), + } + } + } + } + + if num_sent > 0 { + tracing::info!( + "Negentropy UP: {} events ({} remaining)", + num_sent, + have_ids.len() + ); + } + } + + if do_down && !need_ids.is_empty() && !in_flight_down { + let mut ids: Vec = + Vec::with_capacity(NEGENTROPY_BATCH_SIZE_DOWN); + + while !need_ids.is_empty() && ids.len() < NEGENTROPY_BATCH_SIZE_DOWN { + if let Some(id) = need_ids.pop() { + if let Ok(event_id) = EventId::from_slice(&id) { + ids.push(event_id); + } + } + } + + tracing::info!( + "Negentropy DOWN: {} events ({} remaining)", + ids.len(), + need_ids.len() + ); + + let filter = Filter::new().ids(ids); + self.send_msg( + ClientMessage::req(down_sub_id.clone(), vec![filter]), + None, + ) + .await?; + + in_flight_down = true + } } } RelayPoolNotification::RelayStatus { relay_url, status } => { @@ -1688,8 +1735,20 @@ impl Relay { RelayPoolNotification::Stop | RelayPoolNotification::Shutdown => break, _ => (), }; + + if sync_done + && have_ids.is_empty() + && need_ids.is_empty() + && in_flight_up.is_empty() + && !in_flight_down + { + break; + } } + tracing::info!("Negentropy reconciliation terminated for {}", self.url); + + // Close negentropy let close_msg = ClientMessage::NegClose { subscription_id: sub_id, }; diff --git a/crates/nostr-sdk/src/relay/options.rs b/crates/nostr-sdk/src/relay/options.rs index 66ba02a7c..06fd9e092 100644 --- a/crates/nostr-sdk/src/relay/options.rs +++ b/crates/nostr-sdk/src/relay/options.rs @@ -13,6 +13,9 @@ use crate::client::options::DEFAULT_SEND_TIMEOUT; pub const DEFAULT_RETRY_SEC: u64 = 10; pub const MIN_RETRY_SEC: u64 = 5; pub const MAX_ADJ_RETRY_SEC: u64 = 60; +pub const NEGENTROPY_HIGH_WATER_UP: usize = 100; +pub const NEGENTROPY_LOW_WATER_UP: usize = 50; +pub const NEGENTROPY_BATCH_SIZE_DOWN: usize = 50; /// [`Relay`](super::Relay) options #[derive(Debug, Clone)] @@ -259,40 +262,39 @@ impl RelayPoolOptions { } } +/// Negentropy Sync direction +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub enum NegentropyDirection { + /// Send events to relay + Up, + /// Get events from relay + Down, + /// Both send and get events from relay (bidirectional sync) + Both, +} + +impl NegentropyDirection { + pub(super) fn do_up(&self) -> bool { + matches!(self, Self::Up | Self::Both) + } + + pub(super) fn do_down(&self) -> bool { + matches!(self, Self::Down | Self::Both) + } +} + /// Negentropy reconciliation options #[derive(Debug, Clone, Copy)] pub struct NegentropyOptions { - /// Timeout to check if negentropy it's supported (default: 10 secs) - pub initial_timeout: Duration, - // /// Timeout for messages from relay (default: 10) - // - // If relay reply only to first messages, this timeout will stop the loop. - // pub recv_timeout: Duration, - /// Static timeout to get needed events from relay (default: 10) - /// - /// This timeout is added to `relative_get_events_timeout` - pub static_get_events_timeout: Duration, - /// Relative timeout to get needed events from relay (default: 250 ms per event) - /// - /// This timeout is added to `static_get_events_timeout` - pub relative_get_events_timeout: Duration, - /// Timeout for sending events to relay (default: 30 secs) - pub batch_send_timeout: Duration, - /// Bidirectional Sync (default: false) - /// - /// If `true`, perform the set reconciliation on each side. - pub bidirectional: bool, + pub(super) initial_timeout: Duration, + pub(super) direction: NegentropyDirection, } impl Default for NegentropyOptions { fn default() -> Self { Self { initial_timeout: Duration::from_secs(10), - // recv_timeout: Duration::from_secs(600), - static_get_events_timeout: Duration::from_secs(10), - relative_get_events_timeout: Duration::from_millis(250), - batch_send_timeout: Duration::from_secs(30), - bidirectional: false, + direction: NegentropyDirection::Down, } } } @@ -309,41 +311,11 @@ impl NegentropyOptions { self } - // /// Timeout for messages from relay (default: 10) - // - // If relay reply only to first messages, this timeout will stop the loop. - // pub fn recv_timeout(mut self, recv_timeout: Duration) -> Self { - // self.recv_timeout = recv_timeout; - // self - // } - - /// Static timeout to get needed events from relay (default: 10) - /// - /// This timeout is added to `relative_get_events_timeout` - pub fn static_get_events_timeout(mut self, static_get_events_timeout: Duration) -> Self { - self.static_get_events_timeout = static_get_events_timeout; - self - } - - /// Relative timeout to get needed events from relay (default: 250 ms per event) - /// - /// This timeout is added to `static_get_events_timeout` - pub fn relative_get_events_timeout(mut self, relative_get_events_timeout: Duration) -> Self { - self.relative_get_events_timeout = relative_get_events_timeout; - self - } - - /// Timeout for sending events to relay (default: 30 secs) - pub fn batch_send_timeout(mut self, batch_send_timeout: Duration) -> Self { - self.batch_send_timeout = batch_send_timeout; - self - } - - /// Bidirectional Sync (default: false) + /// Negentropy Sync direction (default: down) /// /// If `true`, perform the set reconciliation on each side. - pub fn bidirectional(mut self, bidirectional: bool) -> Self { - self.bidirectional = bidirectional; + pub fn direction(mut self, direction: NegentropyDirection) -> Self { + self.direction = direction; self } } From 60b2de491c3459dc4c0f04b64cb0d34bed6bf7cc Mon Sep 17 00:00:00 2001 From: Yuki Kishimoto Date: Fri, 26 Jan 2024 13:09:18 +0100 Subject: [PATCH 3/4] ffi(nostr-sdk): add `NegentopyOptions` --- bindings/nostr-sdk-ffi/src/client/mod.rs | 11 ++-- .../src/{relay.rs => relay/mod.rs} | 2 + bindings/nostr-sdk-ffi/src/relay/options.rs | 65 +++++++++++++++++++ 3 files changed, 72 insertions(+), 6 deletions(-) rename bindings/nostr-sdk-ffi/src/{relay.rs => relay/mod.rs} (99%) create mode 100644 bindings/nostr-sdk-ffi/src/relay/options.rs diff --git a/bindings/nostr-sdk-ffi/src/client/mod.rs b/bindings/nostr-sdk-ffi/src/client/mod.rs index fd52c2cac..cab3db0fe 100644 --- a/bindings/nostr-sdk-ffi/src/client/mod.rs +++ b/bindings/nostr-sdk-ffi/src/client/mod.rs @@ -14,7 +14,6 @@ use nostr_ffi::{ }; use nostr_sdk::client::blocking::Client as ClientSdk; use nostr_sdk::relay::RelayPoolNotification as RelayPoolNotificationSdk; -use nostr_sdk::NegentropyOptions; use uniffi::Object; mod builder; @@ -25,6 +24,7 @@ pub use self::builder::ClientBuilder; pub use self::options::Options; pub use self::signer::ClientSigner; use crate::error::Result; +use crate::relay::options::NegentropyOptions; use crate::{NostrDatabase, Relay}; #[derive(Object)] @@ -255,11 +255,10 @@ impl Client { )) } - pub fn reconcile(&self, filter: Arc) -> Result<()> { - Ok(self.inner.reconcile( - filter.as_ref().deref().clone(), - NegentropyOptions::default(), - )?) + pub fn reconcile(&self, filter: Arc, opts: Arc) -> Result<()> { + Ok(self + .inner + .reconcile(filter.as_ref().deref().clone(), **opts)?) } pub fn handle_notifications(self: Arc, handler: Box) { diff --git a/bindings/nostr-sdk-ffi/src/relay.rs b/bindings/nostr-sdk-ffi/src/relay/mod.rs similarity index 99% rename from bindings/nostr-sdk-ffi/src/relay.rs rename to bindings/nostr-sdk-ffi/src/relay/mod.rs index 5b0d22a87..1868cbc8e 100644 --- a/bindings/nostr-sdk-ffi/src/relay.rs +++ b/bindings/nostr-sdk-ffi/src/relay/mod.rs @@ -11,6 +11,8 @@ use nostr_ffi::{ClientMessage, Event, Filter, RelayInformationDocument, Timestam use nostr_sdk::{block_on, relay, FilterOptions}; use uniffi::{Enum, Object}; +pub mod options; + use crate::error::Result; #[derive(Object)] diff --git a/bindings/nostr-sdk-ffi/src/relay/options.rs b/bindings/nostr-sdk-ffi/src/relay/options.rs new file mode 100644 index 000000000..275931082 --- /dev/null +++ b/bindings/nostr-sdk-ffi/src/relay/options.rs @@ -0,0 +1,65 @@ +// Copyright (c) 2022-2023 Yuki Kishimoto +// Copyright (c) 2023-2024 Rust Nostr Developers +// Distributed under the MIT software license + +use std::ops::Deref; +use std::sync::Arc; +use std::time::Duration; + +use nostr_ffi::helper::unwrap_or_clone_arc; +use uniffi::{Enum, Object}; + +#[derive(Enum)] +pub enum NegentropyDirection { + Up, + Down, + Both, +} + +impl From for nostr_sdk::NegentropyDirection { + fn from(value: NegentropyDirection) -> Self { + match value { + NegentropyDirection::Up => Self::Up, + NegentropyDirection::Down => Self::Down, + NegentropyDirection::Both => Self::Both, + } + } +} + +#[derive(Clone, Object)] +pub struct NegentropyOptions { + inner: nostr_sdk::NegentropyOptions, +} + +impl Deref for NegentropyOptions { + type Target = nostr_sdk::NegentropyOptions; + + fn deref(&self) -> &Self::Target { + &self.inner + } +} + +#[uniffi::export] +impl NegentropyOptions { + /// New default options + #[uniffi::constructor] + pub fn new() -> Self { + Self { + inner: nostr_sdk::NegentropyOptions::new(), + } + } + + /// Timeout to check if negentropy it's supported (default: 10 secs) + pub fn initial_timeout(self: Arc, timeout: Duration) -> Self { + let mut builder = unwrap_or_clone_arc(self); + builder.inner = builder.inner.initial_timeout(timeout); + builder + } + + /// Negentropy Sync direction (default: down) + pub fn direction(self: Arc, direction: NegentropyDirection) -> Self { + let mut builder = unwrap_or_clone_arc(self); + builder.inner = builder.inner.direction(direction.into()); + builder + } +} From 4e5efb43faa93921e4417359a4a7e77007880e3d Mon Sep 17 00:00:00 2001 From: Yuki Kishimoto Date: Fri, 26 Jan 2024 13:18:14 +0100 Subject: [PATCH 4/4] js(nostr-sdk): add `JsNegentropyOptions` --- bindings/nostr-sdk-js/src/client/mod.rs | 5 +- bindings/nostr-sdk-js/src/relay/mod.rs | 2 + bindings/nostr-sdk-js/src/relay/options.rs | 68 ++++++++++++++++++++++ 3 files changed, 73 insertions(+), 2 deletions(-) create mode 100644 bindings/nostr-sdk-js/src/relay/options.rs diff --git a/bindings/nostr-sdk-js/src/client/mod.rs b/bindings/nostr-sdk-js/src/client/mod.rs index 4ce8f39ae..462302fda 100644 --- a/bindings/nostr-sdk-js/src/client/mod.rs +++ b/bindings/nostr-sdk-js/src/client/mod.rs @@ -25,6 +25,7 @@ pub use self::signer::JsClientSigner; use self::zapper::{JsZapDetails, JsZapEntity}; use crate::abortable::JsAbortHandle; use crate::database::JsNostrDatabase; +use crate::relay::options::JsNegentropyOptions; use crate::relay::{JsRelay, JsRelayArray}; #[wasm_bindgen(js_name = Client)] @@ -525,9 +526,9 @@ impl JsClient { /// Negentropy reconciliation /// /// - pub async fn reconcile(&self, filter: &JsFilter) -> Result<()> { + pub async fn reconcile(&self, filter: &JsFilter, opts: &JsNegentropyOptions) -> Result<()> { self.inner - .reconcile(filter.deref().clone(), NegentropyOptions::default()) + .reconcile(filter.deref().clone(), **opts) .await .map_err(into_err) } diff --git a/bindings/nostr-sdk-js/src/relay/mod.rs b/bindings/nostr-sdk-js/src/relay/mod.rs index 480d6a451..1354e8f74 100644 --- a/bindings/nostr-sdk-js/src/relay/mod.rs +++ b/bindings/nostr-sdk-js/src/relay/mod.rs @@ -7,6 +7,8 @@ use nostr_sdk::prelude::*; use nostr_sdk::relay::Relay; use wasm_bindgen::prelude::*; +pub mod options; + #[wasm_bindgen] extern "C" { #[wasm_bindgen(typescript_type = "JsRelay[]")] diff --git a/bindings/nostr-sdk-js/src/relay/options.rs b/bindings/nostr-sdk-js/src/relay/options.rs new file mode 100644 index 000000000..f2c06e2fb --- /dev/null +++ b/bindings/nostr-sdk-js/src/relay/options.rs @@ -0,0 +1,68 @@ +// Copyright (c) 2022-2023 Yuki Kishimoto +// Copyright (c) 2023-2024 Rust Nostr Developers +// Distributed under the MIT software license + +use core::ops::Deref; + +use nostr_sdk::{NegentropyDirection, NegentropyOptions}; +use wasm_bindgen::prelude::*; + +use crate::duration::JsDuration; + +#[wasm_bindgen(js_name = NegentropyDirection)] +pub enum JsNegentropyDirection { + Up, + Down, + Both, +} + +impl From for NegentropyDirection { + fn from(value: JsNegentropyDirection) -> Self { + match value { + JsNegentropyDirection::Up => Self::Up, + JsNegentropyDirection::Down => Self::Down, + JsNegentropyDirection::Both => Self::Both, + } + } +} + +#[wasm_bindgen(js_name = NegentropyOptions)] +pub struct JsNegentropyOptions { + inner: NegentropyOptions, +} + +impl Deref for JsNegentropyOptions { + type Target = NegentropyOptions; + + fn deref(&self) -> &Self::Target { + &self.inner + } +} + +impl From for JsNegentropyOptions { + fn from(inner: NegentropyOptions) -> Self { + Self { inner } + } +} + +#[wasm_bindgen(js_class = NegentropyOptions)] +impl JsNegentropyOptions { + /// New default options + #[wasm_bindgen(constructor)] + pub fn new() -> Self { + Self { + inner: NegentropyOptions::new(), + } + } + + /// Timeout to check if negentropy it's supported (default: 10 secs) + #[wasm_bindgen(js_name = initialTimeout)] + pub fn initial_timeout(self, timeout: JsDuration) -> Self { + self.inner.initial_timeout(*timeout).into() + } + + /// Negentropy Sync direction (default: down) + pub fn direction(self, direction: JsNegentropyDirection) -> Self { + self.inner.direction(direction.into()).into() + } +}