use crate::{error::Error, fetcher::fetch_rounds, interface};
use async_std::channel::{unbounded, Receiver, RecvError, Sender};
use ethers::{
providers::{Http, Provider},
types::Address,
};
use js_sys::Function;
use serde_wasm_bindgen::{from_value, to_value};
use std::str::FromStr;
use wasm_bindgen::{prelude::wasm_bindgen, JsValue};
use wasm_bindgen_futures::spawn_local;
use workflow_rs::core::cfg_if;
#[derive(Clone)]
pub struct Configuration {
pub fetch_interval_seconds: u64,
pub contracts: Vec<(String, Address)>,
pub provider: Provider<Http>,
pub call_timeout: std::time::Duration,
}
#[derive(Clone)]
pub struct Rustlink {
pub configuration: Configuration,
pub reflector: Reflector,
pub termination_send: Sender<()>,
pub termination_recv: Receiver<()>,
pub shutdown_send: Sender<()>,
pub shutdown_recv: Receiver<()>,
}
#[derive(Clone)]
pub enum Reflector {
Sender(Sender<Round>),
}
pub type Round = interface::Round;
impl Rustlink {
pub fn try_new(
rpc_url: &str,
fetch_interval_seconds: u64,
reflector: Reflector,
contracts: Vec<(String, String)>,
call_timeout: std::time::Duration,
) -> Result<Self, Error> {
let provider = Provider::try_from(rpc_url).expect("Invalid RPC URL");
let (termination_send, termination_recv) = unbounded::<()>();
let (shutdown_send, shutdown_recv) = unbounded::<()>();
let parsed_contracts = contracts
.iter()
.map(|(identifier, address)| {
(
identifier.clone(),
Address::from_str(address).expect("Invalid contract address specified"),
)
})
.collect();
Ok(Rustlink {
configuration: Configuration {
fetch_interval_seconds,
provider,
contracts: parsed_contracts,
call_timeout,
},
reflector,
termination_send,
termination_recv,
shutdown_send,
shutdown_recv,
})
}
pub fn start(&self) {
#[cfg(not(target_arch = "wasm32"))]
tokio::task::spawn(fetch_rounds(self.clone()));
#[cfg(target_arch = "wasm32")]
async_std::task::block_on(fetch_rounds(self.clone()));
}
pub async fn stop(&self) -> Result<(), RecvError> {
self.termination_send.send(()).await.unwrap();
self.shutdown_recv.recv().await
}
}
#[wasm_bindgen]
pub struct RustlinkJS {
rustlink: Rustlink,
callback: Function,
receiver: Receiver<Round>,
}
cfg_if! {
if #[cfg(target_arch = "wasm32")] {
#[wasm_bindgen(typescript_custom_section)]
const TS_CONTRACTS: &'static str = r#"
/**
* A contract tuple containing an identifier and a contract address.
*
* **Order matters.**
* Example
* ```typescript
* let contracts=[["Ethereum","0x9ef1B8c0E4F7dc8bF5719Ea496883DC6401d5b2e"]]
* ```
*/
export type Contract = [string,string]
"#;
}
}
#[wasm_bindgen]
extern "C" {
#[wasm_bindgen(extends = js_sys::Function, typescript_type = "Contract[]")]
pub type Contracts;
}
#[wasm_bindgen]
impl RustlinkJS {
#[wasm_bindgen(constructor)]
pub fn new(
rpc_url: &str,
fetch_interval_seconds: u64,
contracts: Contracts,
callback: Function,
call_timeout_seconds: u64,
) -> Self {
let contracts: Vec<(String, String)> = from_value(contracts.into()).unwrap();
let (sender, receiver) = async_std::channel::unbounded();
let reflector = Reflector::Sender(sender);
let rustlink = Rustlink::try_new(
rpc_url,
fetch_interval_seconds,
reflector,
contracts,
std::time::Duration::from_secs(call_timeout_seconds),
)
.map_err(|e| JsValue::from_str(&format!("{}", e)))
.unwrap();
RustlinkJS {
rustlink,
callback,
receiver,
}
}
#[wasm_bindgen]
pub fn start(&self) {
self.rustlink.start();
let receiver = self.receiver.clone();
let callback = self.callback.clone();
spawn_local(async move {
while let Ok(round) = receiver.recv().await {
let this = JsValue::NULL; let arg_js = to_value(&round).unwrap();
let _ = callback.call1(&this, &arg_js);
}
});
}
#[wasm_bindgen]
pub async fn stop(&self) -> Result<(), JsValue> {
self.rustlink
.stop()
.await
.map_err(|e| JsValue::from_str(&format!("Shutdown error: {}", e)))
}
}