deft 0.15.0

Cross platform ui framework
use crate as deft;
use crate::js::{JsError, ToJsValue};
use crate::js_weak_value;
use anyhow::Error;
use deft_macros::{js_methods, mrc_object};
use futures_util::stream::{SplitSink, SplitStream};
use futures_util::{SinkExt, StreamExt};
use quick_js::JsValue;
use std::cell::RefCell;
use std::collections::HashMap;
use std::io;
use std::io::ErrorKind;
use std::sync::Arc;
use tokio::net::TcpStream;
use tokio::sync::Mutex;
use tokio_tungstenite::tungstenite::Message;
use tokio_tungstenite::{connect_async, MaybeTlsStream, WebSocketStream};

thread_local! {
    pub static NEXT_ID: RefCell<u64> = RefCell::new(0);
    pub static CONNECTIONS: RefCell<HashMap<u64, WsConnection>> = RefCell::new(HashMap::new());
}

type WsStream = WebSocketStream<MaybeTlsStream<TcpStream>>;

#[mrc_object]
pub struct WsConnection {
    id: u64,
    writer: Arc<Mutex<SplitSink<WsStream, Message>>>,
    reader: Arc<Mutex<SplitStream<WsStream>>>,
}

unsafe impl Send for WsConnection {}
unsafe impl Sync for WsConnection {}

js_weak_value!(WsConnection, WsConnectionWeak);

#[js_methods]
impl WsConnection {
    #[js_func]
    pub async fn connect(url: String) -> Result<WsConnection, JsError> {
        let id = NEXT_ID.with_borrow_mut(|id| {
            *id += 1;
            *id - 1
        });
        let (socket, _) = connect_async(url)
            .await
            .map_err(|e| io::Error::new(ErrorKind::Other, e))?;
        let (writer, reader) = socket.split();
        let ws_conn = WsConnectionData {
            id,
            reader: Arc::new(Mutex::new(reader)),
            writer: Arc::new(Mutex::new(writer)),
        }
        .to_ref();
        CONNECTIONS.with_borrow_mut(|map| {
            map.insert(id, ws_conn.clone());
        });
        Ok(ws_conn)
    }

    #[js_func]
    pub fn close(&self) {
        CONNECTIONS.with_borrow_mut(|map| {
            map.remove(&self.id);
        });
    }

    #[js_func]
    pub async fn read(&self) -> Result<(String, JsValue), JsError> {
        let mut reader = self.inner.reader.lock().await;
        if let Some(result) = reader.next().await {
            let msg = result?;
            let (ty, data) = match msg {
                Message::Text(v) => ("text", v.to_js_value()?),
                Message::Binary(v) => ("binary", v.to_js_value()?),
                Message::Ping(v) => ("ping", v.to_js_value()?),
                Message::Pong(v) => ("pong", v.to_js_value()?),
                Message::Close(_) => ("close", JsValue::Undefined),
                Message::Frame(_frame) => ("frame", _frame.into_data().to_js_value()?),
            };
            Ok((ty.to_string(), data))
        } else {
            Err(JsError::from_str("connection closed"))
        }
    }

    #[js_func]
    pub async fn send_str(&self, data: String) -> Result<JsValue, Error> {
        let mut writer = self.inner.writer.lock().await;
        writer.send(Message::Text(data)).await?;
        Ok(JsValue::Undefined)
    }
}