use std::cell::RefCell;
use std::collections::HashMap;
use std::sync::Arc;
use wasm_bindgen::prelude::*;
use crate::antimatter::crdt_trait::PrunableCrdt;
use crate::antimatter::json_crdt::JsonCrdt;
use crate::antimatter::messages::{Message, Patch};
use crate::antimatter::AntimatterCrdt;
use crate::core::client::{BraidClient, Subscription as NativeSubscription};
use crate::core::traits::WasmRuntime;
use crate::core::types::{BraidRequest, Update};
use uuid::Uuid;
thread_local! {
static CALLBACKS: RefCell<HashMap<String, js_sys::Function>> = RefCell::new(HashMap::new());
static SUB_CALLBACKS: RefCell<HashMap<String, js_sys::Function>> = RefCell::new(HashMap::new());
}
#[wasm_bindgen]
pub struct AntimatterWasm {
inner: AntimatterCrdt<JsonCrdt>,
id: String,
}
#[wasm_bindgen]
impl AntimatterWasm {
#[wasm_bindgen(constructor)]
pub fn new(
id: Option<String>,
initial_text: &str,
send_cb: js_sys::Function,
) -> AntimatterWasm {
let instance_id = id.clone().unwrap_or_else(|| Uuid::new_v4().to_string());
let id_for_crdt = id.clone();
let id_for_cb = instance_id.clone();
CALLBACKS.with(|callbacks| {
callbacks.borrow_mut().insert(id_for_cb.clone(), send_cb);
});
let cb = move |msg: Message| {
CALLBACKS.with(|callbacks| {
if let Some(f) = callbacks.borrow().get(&id_for_cb) {
let this = JsValue::NULL;
if let Ok(val) = serde_wasm_bindgen::to_value(&msg) {
let _ = f.call1(&this, &val);
}
}
});
};
let runtime = Arc::new(WasmRuntime);
let crdt = JsonCrdt::with_content(&id_for_crdt.unwrap_or_default(), initial_text);
let inner = AntimatterCrdt::new(id, crdt, Arc::new(cb), runtime);
AntimatterWasm {
inner,
id: instance_id,
}
}
pub fn receive(&mut self, msg_val: JsValue) -> Result<JsValue, JsValue> {
let msg: Message = serde_wasm_bindgen::from_value(msg_val)
.map_err(|e| JsValue::from_str(&e.to_string()))?;
let patches = self
.inner
.receive(msg)
.map_err(|e| JsValue::from_str(&e.to_string()))?;
serde_wasm_bindgen::to_value(&patches).map_err(|e| JsValue::from_str(&e.to_string()))
}
pub fn subscribe(&mut self, conn: String) {
self.inner.subscribe(conn);
}
pub fn disconnect(&mut self, conn: String, create_fissure: bool) {
self.inner.disconnect(conn, create_fissure);
}
pub fn update(&mut self, patches_val: JsValue) -> Result<String, JsValue> {
let patches: Vec<Patch> = serde_wasm_bindgen::from_value(patches_val)
.map_err(|e| JsValue::from_str(&e.to_string()))?;
Ok(self.inner.update(patches))
}
pub fn ackme(&mut self) -> String {
self.inner.ackme()
}
pub fn get_content(&self) -> String {
self.inner.crdt.get_content()
}
}
impl Drop for AntimatterWasm {
fn drop(&mut self) {
CALLBACKS.with(|callbacks| {
callbacks.borrow_mut().remove(&self.id);
});
}
}
#[wasm_bindgen]
pub struct SubscriptionWasm {
id: String,
}
#[wasm_bindgen]
impl SubscriptionWasm {
pub fn cancel(&self) {
SUB_CALLBACKS.with(|callbacks| {
callbacks.borrow_mut().remove(&self.id);
});
}
}
#[wasm_bindgen]
pub struct BraidClientWasm {
inner: BraidClient,
}
#[wasm_bindgen]
impl BraidClientWasm {
#[wasm_bindgen(constructor)]
pub fn new() -> Result<BraidClientWasm, JsValue> {
Ok(BraidClientWasm {
inner: BraidClient::new().map_err(|e| JsValue::from_str(&e.to_string()))?,
})
}
pub async fn subscribe(
&self,
url: String,
on_update: js_sys::Function,
) -> Result<SubscriptionWasm, JsValue> {
let mut sub = self
.inner
.subscribe(&url, BraidRequest::new().subscribe())
.await
.map_err(|e| JsValue::from_str(&e.to_string()))?;
let sub_id = Uuid::new_v4().to_string();
let id_for_cb = sub_id.clone();
SUB_CALLBACKS.with(|callbacks| {
callbacks.borrow_mut().insert(id_for_cb.clone(), on_update);
});
wasm_bindgen_futures::spawn_local(async move {
while let Some(result) = sub.next().await {
let stop = SUB_CALLBACKS.with(|callbacks| {
if let Some(f) = callbacks.borrow().get(&id_for_cb) {
match result {
Ok(update) => {
if let Ok(val) = serde_wasm_bindgen::to_value(&update) {
let _ = f.call1(&JsValue::NULL, &val);
}
}
Err(e) => {
let _ = f.call1(&JsValue::NULL, &JsValue::from_str(&e.to_string()));
}
}
false
} else {
true }
});
if stop {
break;
}
}
});
Ok(SubscriptionWasm { id: sub_id })
}
pub async fn get(&self, url: String) -> Result<JsValue, JsValue> {
let resp = self
.inner
.get(&url)
.await
.map_err(|e| JsValue::from_str(&e.to_string()))?;
serde_wasm_bindgen::to_value(&resp).map_err(|e| JsValue::from_str(&e.to_string()))
}
pub async fn put(&self, url: String, body: String) -> Result<JsValue, JsValue> {
let resp = self
.inner
.put(&url, &body, BraidRequest::new())
.await
.map_err(|e| JsValue::from_str(&e.to_string()))?;
serde_wasm_bindgen::to_value(&resp).map_err(|e| JsValue::from_str(&e.to_string()))
}
}