use js_sys::Array;
use libp2p::identity::Keypair;
use serde::{Deserialize, Serialize};
use serde_wasm_bindgen::to_value;
use tracing::error;
use wasm_bindgen::prelude::*;
use web_sys::BroadcastChannel;
use lumina_node::blockstore::IndexedDbBlockstore;
use lumina_node::network::{canonical_network_bootnodes, network_id};
use lumina_node::node::NodeConfig;
use lumina_node::store::IndexedDbStore;
use crate::error::{Context, Result};
use crate::utils::{
is_safari, js_value_from_display, request_storage_persistence, resolve_dnsaddr_multiaddress,
shared_workers_supported, Network,
};
use crate::worker::commands::{CheckableResponseExt, NodeCommand, SingleHeaderQuery};
use crate::worker::{AnyWorker, WorkerClient};
use crate::wrapper::libp2p::NetworkInfoSnapshot;
const LUMINA_WORKER_NAME: &str = "lumina";
#[wasm_bindgen(js_name = NodeConfig)]
#[derive(Serialize, Deserialize, Debug)]
pub struct WasmNodeConfig {
pub network: Network,
#[wasm_bindgen(getter_with_clone)]
pub bootnodes: Vec<String>,
}
#[wasm_bindgen(js_name = NodeClient)]
struct NodeDriver {
client: WorkerClient,
}
#[wasm_bindgen]
pub enum NodeWorkerKind {
Shared,
Dedicated,
}
#[wasm_bindgen(js_class = NodeClient)]
impl NodeDriver {
#[wasm_bindgen(constructor)]
pub async fn new(
worker_script_url: &str,
worker_type: Option<NodeWorkerKind>,
) -> Result<NodeDriver> {
if !is_safari()? {
if let Err(e) = request_storage_persistence().await {
error!("Error requesting storage persistence: {e}");
}
}
let default_worker_type = if shared_workers_supported().unwrap_or(false) {
NodeWorkerKind::Shared
} else {
NodeWorkerKind::Dedicated
};
let worker = AnyWorker::new(
worker_type.unwrap_or(default_worker_type),
worker_script_url,
LUMINA_WORKER_NAME,
)?;
Ok(Self {
client: WorkerClient::new(worker),
})
}
pub async fn is_running(&self) -> Result<bool> {
let command = NodeCommand::IsRunning;
let response = self.client.exec(command).await?;
let running = response.into_is_running().check_variant()?;
Ok(running)
}
pub async fn start(&self, config: WasmNodeConfig) -> Result<()> {
let command = NodeCommand::StartNode(config);
let response = self.client.exec(command).await?;
response.into_node_started().check_variant()??;
Ok(())
}
pub async fn local_peer_id(&self) -> Result<String> {
let command = NodeCommand::GetLocalPeerId;
let response = self.client.exec(command).await?;
let peer_id = response.into_local_peer_id().check_variant()?;
Ok(peer_id)
}
pub async fn peer_tracker_info(&self) -> Result<JsValue> {
let command = NodeCommand::GetPeerTrackerInfo;
let response = self.client.exec(command).await?;
let peer_info = response.into_peer_tracker_info().check_variant()?;
Ok(to_value(&peer_info)?)
}
pub async fn wait_connected(&self) -> Result<()> {
let command = NodeCommand::WaitConnected { trusted: false };
let response = self.client.exec(command).await?;
let _ = response.into_connected().check_variant()?;
Ok(())
}
pub async fn wait_connected_trusted(&self) -> Result<()> {
let command = NodeCommand::WaitConnected { trusted: true };
let response = self.client.exec(command).await?;
response.into_connected().check_variant()?
}
pub async fn network_info(&self) -> Result<NetworkInfoSnapshot> {
let command = NodeCommand::GetNetworkInfo;
let response = self.client.exec(command).await?;
response.into_network_info().check_variant()?
}
pub async fn listeners(&self) -> Result<Array> {
let command = NodeCommand::GetListeners;
let response = self.client.exec(command).await?;
let listeners = response.into_listeners().check_variant()?;
let result = listeners?.iter().map(js_value_from_display).collect();
Ok(result)
}
pub async fn connected_peers(&self) -> Result<Array> {
let command = NodeCommand::GetConnectedPeers;
let response = self.client.exec(command).await?;
let peers = response.into_connected_peers().check_variant()?;
let result = peers?.iter().map(js_value_from_display).collect();
Ok(result)
}
pub async fn set_peer_trust(&self, peer_id: &str, is_trusted: bool) -> Result<()> {
let command = NodeCommand::SetPeerTrust {
peer_id: peer_id.parse()?,
is_trusted,
};
let response = self.client.exec(command).await?;
response.into_set_peer_trust().check_variant()?
}
pub async fn request_head_header(&self) -> Result<JsValue> {
let command = NodeCommand::RequestHeader(SingleHeaderQuery::Head);
let response = self.client.exec(command).await?;
let header = response.into_header().check_variant()?;
header.into()
}
pub async fn request_header_by_hash(&self, hash: &str) -> Result<JsValue> {
let command = NodeCommand::RequestHeader(SingleHeaderQuery::ByHash(hash.parse()?));
let response = self.client.exec(command).await?;
let header = response.into_header().check_variant()?;
header.into()
}
pub async fn request_header_by_height(&self, height: u64) -> Result<JsValue> {
let command = NodeCommand::RequestHeader(SingleHeaderQuery::ByHeight(height));
let response = self.client.exec(command).await?;
let header = response.into_header().check_variant()?;
header.into()
}
pub async fn request_verified_headers(
&self,
from_header: JsValue,
amount: u64,
) -> Result<Array> {
let command = NodeCommand::GetVerifiedHeaders {
from: from_header,
amount,
};
let response = self.client.exec(command).await?;
let headers = response.into_headers().check_variant()?;
headers.into()
}
pub async fn syncer_info(&self) -> Result<JsValue> {
let command = NodeCommand::GetSyncerInfo;
let response = self.client.exec(command).await?;
let syncer_info = response.into_syncer_info().check_variant()?;
Ok(to_value(&syncer_info?)?)
}
pub async fn get_network_head_header(&self) -> Result<JsValue> {
let command = NodeCommand::LastSeenNetworkHead;
let response = self.client.exec(command).await?;
let header = response.into_last_seen_network_head().check_variant()?;
header.into()
}
pub async fn get_local_head_header(&self) -> Result<JsValue> {
let command = NodeCommand::GetHeader(SingleHeaderQuery::Head);
let response = self.client.exec(command).await?;
let header = response.into_header().check_variant()?;
header.into()
}
pub async fn get_header_by_hash(&self, hash: &str) -> Result<JsValue> {
let command = NodeCommand::GetHeader(SingleHeaderQuery::ByHash(hash.parse()?));
let response = self.client.exec(command).await?;
let header = response.into_header().check_variant()?;
header.into()
}
pub async fn get_header_by_height(&self, height: u64) -> Result<JsValue> {
let command = NodeCommand::GetHeader(SingleHeaderQuery::ByHeight(height));
let response = self.client.exec(command).await?;
let header = response.into_header().check_variant()?;
header.into()
}
pub async fn get_headers(
&self,
start_height: Option<u64>,
end_height: Option<u64>,
) -> Result<Array> {
let command = NodeCommand::GetHeadersRange {
start_height,
end_height,
};
let response = self.client.exec(command).await?;
let headers = response.into_headers().check_variant()?;
headers.into()
}
pub async fn get_sampling_metadata(&self, height: u64) -> Result<JsValue> {
let command = NodeCommand::GetSamplingMetadata { height };
let response = self.client.exec(command).await?;
let metadata = response.into_sampling_metadata().check_variant()?;
Ok(to_value(&metadata?)?)
}
pub async fn close(&self) -> Result<()> {
let command = NodeCommand::CloseWorker;
let response = self.client.exec(command).await?;
response.into_worker_closed().check_variant()?;
Ok(())
}
pub async fn events_channel(&self) -> Result<BroadcastChannel> {
let command = NodeCommand::GetEventsChannelName;
let response = self.client.exec(command).await?;
let name = response.into_events_channel_name().check_variant()?;
Ok(BroadcastChannel::new(&name).unwrap())
}
}
#[wasm_bindgen(js_class = NodeConfig)]
impl WasmNodeConfig {
pub fn default(network: Network) -> WasmNodeConfig {
WasmNodeConfig {
network,
bootnodes: canonical_network_bootnodes(network.into())
.map(|addr| addr.to_string())
.collect::<Vec<_>>(),
}
}
pub(crate) async fn into_node_config(
self,
) -> Result<NodeConfig<IndexedDbBlockstore, IndexedDbStore>> {
let network_id = network_id(self.network.into());
let store = IndexedDbStore::new(network_id)
.await
.context("Failed to open the store")?;
let blockstore = IndexedDbBlockstore::new(&format!("{network_id}-blockstore"))
.await
.context("Failed to open the blockstore")?;
let p2p_local_keypair = Keypair::generate_ed25519();
let mut p2p_bootnodes = Vec::with_capacity(self.bootnodes.len());
for addr in self.bootnodes {
let addr = addr
.parse()
.with_context(|| format!("invalid multiaddr: '{addr}"))?;
let resolved_addrs = resolve_dnsaddr_multiaddress(addr).await?;
p2p_bootnodes.extend(resolved_addrs.into_iter());
}
Ok(NodeConfig {
network_id: network_id.to_string(),
p2p_bootnodes,
p2p_local_keypair,
p2p_listen_on: vec![],
sync_batch_size: 128,
blockstore,
store,
})
}
}