use crate::error::WasmCoreError;
use crate::storage::wasm_client_traits::WasmClientStorage;
use crate::storage::ClientStorage;
use js_sys::Promise;
use nym_client_core::client::base_client::storage::helpers::set_active_gateway;
use nym_client_core::client::base_client::storage::GatewaysDetailsStore;
use nym_client_core::client::replies::reply_storage::browser_backend;
use nym_client_core::config;
use nym_client_core::error::ClientCoreError;
use nym_client_core::init::helpers::gateways_for_init;
use nym_client_core::init::types::GatewaySelectionSpecification;
use nym_client_core::init::{
self, setup_gateway,
types::{GatewaySetup, InitialisationResult},
};
use nym_sphinx::addressing::clients::Recipient;
use nym_sphinx::anonymous_replies::requests::AnonymousSenderTag;
use nym_topology::wasm_helpers::WasmFriendlyNymTopology;
use nym_topology::{EpochRewardedSet, NymTopology, RoutingNode};
use nym_validator_client::client::IdentityKey;
use nym_validator_client::{nym_api::NymApiClientExt, UserAgent};
use nym_wasm_utils::error::PromisableResult;
use rand::thread_rng;
use url::Url;
use wasm_bindgen::prelude::wasm_bindgen;
use wasm_bindgen_futures::future_to_promise;
pub use nym_credential_storage::ephemeral_storage::EphemeralStorage as EphemeralCredentialStorage;
use nym_topology::provider_trait::ToTopologyMetadata;
use nym_wasm_utils::{console_log, console_warn};
pub fn setup_reply_surb_storage_backend(config: config::ReplySurbs) -> browser_backend::Backend {
browser_backend::Backend::new(
config.minimum_reply_surb_storage_threshold,
config.maximum_reply_surb_storage_threshold,
)
}
pub fn parse_recipient(recipient: &str) -> Result<Recipient, WasmCoreError> {
Recipient::try_from_base58_string(recipient).map_err(|source| {
WasmCoreError::MalformedRecipient {
raw: recipient.to_string(),
source,
}
})
}
pub fn parse_sender_tag(tag: &str) -> Result<AnonymousSenderTag, WasmCoreError> {
AnonymousSenderTag::try_from_base58_string(tag).map_err(|source| {
WasmCoreError::MalformedSenderTag {
raw: tag.to_string(),
source,
}
})
}
pub async fn current_network_topology_async(
nym_api_url: String,
) -> Result<WasmFriendlyNymTopology, WasmCoreError> {
let url: Url = match nym_api_url.parse() {
Ok(url) => url,
Err(source) => {
return Err(WasmCoreError::MalformedUrl {
raw: nym_api_url,
source,
})
}
};
let api_client = nym_http_api_client::Client::builder(url.clone())
.map_err(|_err| WasmCoreError::MalformedUrl {
raw: nym_api_url.to_string(),
source: url::ParseError::EmptyHost,
})?
.build()
.map_err(|_err| WasmCoreError::MalformedUrl {
raw: nym_api_url.to_string(),
source: url::ParseError::EmptyHost,
})?;
let rewarded_set = api_client.get_current_rewarded_set().await?;
let mixnodes_res = api_client
.get_all_basic_active_mixing_assigned_nodes_with_metadata()
.await?;
let metadata = mixnodes_res.metadata;
let mixnodes = mixnodes_res.nodes;
let gateways_res = api_client
.get_all_basic_entry_assigned_nodes_with_metadata()
.await?;
if !gateways_res.metadata.consistency_check(&metadata) {
console_warn!("inconsistent nodes metadata between mixnodes and gateways calls! {metadata:?} and {:?}", gateways_res.metadata);
return Err(WasmCoreError::UnavailableNetworkTopology);
}
let gateways = gateways_res.nodes;
let epoch_rewarded_set: EpochRewardedSet = rewarded_set.into();
let topology = NymTopology::new(
metadata.to_topology_metadata(),
epoch_rewarded_set,
Vec::new(),
)
.with_skimmed_nodes(&mixnodes)
.with_skimmed_nodes(&gateways);
Ok(topology.into())
}
#[wasm_bindgen(js_name = "currentNetworkTopology")]
pub fn current_network_topology(nym_api_url: String) -> Promise {
future_to_promise(async move {
current_network_topology_async(nym_api_url)
.await
.map(|topology| serde_wasm_bindgen::to_value(&topology).unwrap())
.into_promise_result()
})
}
pub async fn setup_gateway_wasm(
client_store: &ClientStorage,
force_tls: bool,
chosen_gateway: Option<IdentityKey>,
gateways: Vec<RoutingNode>,
) -> Result<InitialisationResult, WasmCoreError> {
let setup = if client_store
.get_active_gateway_id()
.await?
.active_gateway_id_bs58
.is_some()
{
GatewaySetup::MustLoad { gateway_id: None }
} else {
let selection_spec =
GatewaySelectionSpecification::new(chosen_gateway.clone(), None, force_tls, false);
GatewaySetup::New {
specification: selection_spec,
available_gateways: gateways,
}
};
init::setup_gateway(setup, client_store, client_store)
.await
.map_err(Into::into)
}
pub async fn setup_gateway_from_api(
client_store: &ClientStorage,
force_tls: bool,
chosen_gateway: Option<IdentityKey>,
nym_apis: &[Url],
minimum_performance: u8,
ignore_epoch_roles: bool,
) -> Result<InitialisationResult, WasmCoreError> {
let gateways = gateways_for_init(
nym_apis,
None,
minimum_performance,
ignore_epoch_roles,
None,
)
.await?;
setup_gateway_wasm(client_store, force_tls, chosen_gateway, gateways).await
}
pub async fn current_gateways_wasm(
nym_apis: &[Url],
user_agent: Option<UserAgent>,
minimum_performance: u8,
ignore_epoch_roles: bool,
) -> Result<Vec<RoutingNode>, ClientCoreError> {
gateways_for_init(
nym_apis,
user_agent,
minimum_performance,
ignore_epoch_roles,
None,
)
.await
}
pub async fn setup_from_topology(
explicit_gateway: Option<IdentityKey>,
force_tls: bool,
topology: &NymTopology,
client_store: &ClientStorage,
) -> Result<InitialisationResult, WasmCoreError> {
let gateways = topology.entry_capable_nodes().cloned().collect::<Vec<_>>();
setup_gateway_wasm(client_store, force_tls, explicit_gateway, gateways).await
}
pub async fn generate_new_client_keys(store: &ClientStorage) -> Result<(), WasmCoreError> {
let mut rng = thread_rng();
init::generate_new_client_keys(&mut rng, store).await?;
Ok(())
}
#[allow(clippy::too_many_arguments)]
pub async fn add_gateway(
preferred_gateway: Option<IdentityKey>,
latency_based_selection: Option<bool>,
force_tls: bool,
nym_apis: &[Url],
user_agent: UserAgent,
min_performance: u8,
ignore_epoch_roles: bool,
storage: &ClientStorage,
) -> Result<(), WasmCoreError> {
let selection_spec = GatewaySelectionSpecification::new(
preferred_gateway.clone(),
latency_based_selection,
force_tls,
false,
);
let preferred_gateway = preferred_gateway
.as_ref()
.map(|g| g.parse())
.transpose()
.map_err(|source| WasmCoreError::InvalidGatewayIdentity { source })?;
let registered_gateways = storage.all_gateways_identities().await.map_err(|source| {
ClientCoreError::GatewaysDetailsStoreError {
source: Box::new(source),
}
})?;
if let Some(user_chosen) = preferred_gateway {
if registered_gateways.contains(&user_chosen) {
return Err(ClientCoreError::AlreadyRegistered {
gateway_id: user_chosen.to_base58_string(),
}
.into());
}
}
let available_gateways = current_gateways_wasm(
nym_apis,
Some(user_agent),
min_performance,
ignore_epoch_roles,
)
.await?;
let available_gateways = available_gateways
.into_iter()
.filter(|g| !registered_gateways.contains(&g.identity()))
.collect::<Vec<_>>();
if available_gateways.is_empty() {
return Err(ClientCoreError::NoNewGatewaysAvailable.into());
}
let gateway_setup = GatewaySetup::New {
specification: selection_spec,
available_gateways,
};
let init_details = setup_gateway(gateway_setup, storage, storage).await?;
let gateway = init_details.gateway_id().to_base58_string();
set_active_gateway(storage, &gateway).await?;
console_log!("finished registration with gateway {gateway}");
Ok(())
}