braid_http_rs 0.1.5

Unified Braid Protocol implementation in Rust, including Braid-HTTP, Antimatter CRDT, and BraidFS.
Documentation
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();

        // Register callback in thread-local storage
        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) {
        // Cleanup callback
        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);
        });

        // Spawn a task to handle the subscription stream
        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 // Callback removed, stop loop
                    }
                });
                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()))
    }
}