antenna-client-web 0.1.1

Web-over-WASM platform implementation for the antenna P2P mesh protocol.
Documentation
use antenna_protocol::UserMsgPayload;
use anyhow::{Result, anyhow};
use wasm_bindgen::JsCast;
use wasm_bindgen::prelude::*;

pub struct DataChannelManager {
    data_channel: web_sys::RtcDataChannel,
    callback_buffer: Vec<Closure<dyn FnMut(JsValue)>>,
}

impl DataChannelManager {
    pub fn new(pc: &web_sys::RtcPeerConnection, label: &str) -> Self {
        let data_channel = pc.create_data_channel(label);
        data_channel.set_binary_type(web_sys::RtcDataChannelType::Arraybuffer);
        Self {
            data_channel,
            callback_buffer: Vec::new(),
        }
    }

    pub fn from_existing(dc: web_sys::RtcDataChannel) -> Self {
        dc.set_binary_type(web_sys::RtcDataChannelType::Arraybuffer);
        Self {
            data_channel: dc,
            callback_buffer: Vec::new(),
        }
    }

    pub fn setup_on_open<F: Fn() + 'static>(&mut self, on_open: F) {
        let cb = Closure::<dyn FnMut(JsValue)>::wrap(Box::new(move |_: JsValue| {
            on_open();
        }));
        self.data_channel
            .set_onopen(Some(cb.as_ref().unchecked_ref()));
        self.callback_buffer.push(cb);
    }

    pub fn setup_on_message<F: Fn(Vec<u8>) + 'static>(&mut self, on_message: F) {
        let cb = Closure::<dyn FnMut(JsValue)>::wrap(Box::new(move |event: JsValue| {
            let event: web_sys::MessageEvent = event.unchecked_into();
            if let Ok(ab) = event.data().dyn_into::<js_sys::ArrayBuffer>() {
                let bytes = js_sys::Uint8Array::new(&ab).to_vec();
                on_message(bytes);
            } else if let Some(text) = event.data().as_string() {
                on_message(text.into_bytes());
            }
        }));
        self.data_channel
            .set_onmessage(Some(cb.as_ref().unchecked_ref()));
        self.callback_buffer.push(cb);
    }

    pub fn setup_on_close<F: Fn() + 'static>(&mut self, on_close: F) {
        let cb = Closure::<dyn FnMut(JsValue)>::wrap(Box::new(move |_: JsValue| {
            on_close();
        }));
        self.data_channel
            .set_onclose(Some(cb.as_ref().unchecked_ref()));
        self.callback_buffer.push(cb);
    }

    pub fn send_data<Msg: UserMsgPayload>(&self, data: &Msg) -> Result<()> {
        let bytes =
            serde_json::to_vec(data).map_err(|e| anyhow!("Failed to serialize message: {e}"))?;

        self.data_channel
            .send_with_u8_array(&bytes)
            .map_err(|e| anyhow!("Failed to send data: {:?}", e))
    }

    pub fn close(&self) {
        self.data_channel.set_onopen(None);
        self.data_channel.set_onmessage(None);
        self.data_channel.set_onclose(None);
        self.data_channel.close();
    }
}

impl Drop for DataChannelManager {
    fn drop(&mut self) {
        self.data_channel.set_onopen(None);
        self.data_channel.set_onmessage(None);
        self.data_channel.set_onclose(None);
        self.close();
    }
}