#[cfg(test)]
mod tests;
use log::{error, trace};
use std::cell::RefCell;
use std::rc::Rc;
use thiserror::Error;
#[cfg(target_arch = "wasm32")]
use wasm_bindgen::prelude::*;
#[cfg(target_arch = "wasm32")]
use wasm_bindgen::JsCast;
use web_sys::{ErrorEvent, MessageEvent, WebSocket};
#[cfg(not(target_arch = "wasm32"))]
compile_error!("wasm-sockets can only compile to WASM targets");
#[derive(Debug, Clone, PartialEq)]
pub enum ConnectionStatus {
Connecting,
Connected,
Error,
Disconnected,
}
#[derive(Debug, Clone)]
pub enum Message {
Text(String),
Binary(Vec<u8>),
}
#[cfg(target_arch = "wasm32")]
pub struct PollingClient {
pub url: String,
pub event_client: EventClient,
pub status: Rc<RefCell<ConnectionStatus>>,
data: Rc<RefCell<Vec<Message>>>,
}
#[cfg(target_arch = "wasm32")]
impl PollingClient {
pub fn new(url: &str) -> Result<Self, WebSocketError> {
let mut client = EventClient::new(url)?;
let data = Rc::new(RefCell::new(vec![]));
let data_ref = data.clone();
let status = Rc::new(RefCell::new(ConnectionStatus::Connecting));
let status_ref = status.clone();
client.set_on_connection(Some(Box::new(move |_client| {
*status_ref.borrow_mut() = ConnectionStatus::Connected;
})));
let status_ref = status.clone();
client.set_on_error(Some(Box::new(move |e| {
*status_ref.borrow_mut() = ConnectionStatus::Error;
})));
let status_ref = status.clone();
client.set_on_close(Some(Box::new(move || {
*status_ref.borrow_mut() = ConnectionStatus::Disconnected;
})));
client.set_on_message(Some(Box::new(move |_client: &EventClient, m: Message| {
data_ref.borrow_mut().push(m);
})));
Ok(Self {
url: url.to_string(),
event_client: client,
status,
data,
})
}
pub fn receive(&mut self) -> Vec<Message> {
let data = (*self.data.borrow()).clone();
(*self.data.borrow_mut()).clear();
data
}
pub fn status(&self) -> ConnectionStatus {
self.status.borrow().clone()
}
pub fn send_string(&self, message: &str) -> Result<(), JsValue> {
self.event_client.send_string(message)
}
pub fn send_binary(&self, message: Vec<u8>) -> Result<(), JsValue> {
self.event_client.send_binary(message)
}
}
#[derive(Debug, Clone, Error)]
pub enum WebSocketError {
#[error("Failed to create websocket connection: {0}")]
ConnectionCreationError(String),
}
#[cfg(target_arch = "wasm32")]
pub struct EventClient {
pub url: Rc<RefCell<String>>,
connection: Rc<RefCell<web_sys::WebSocket>>,
pub status: Rc<RefCell<ConnectionStatus>>,
pub on_error: Rc<RefCell<Option<Box<dyn Fn(ErrorEvent) -> ()>>>>,
pub on_connection: Rc<RefCell<Option<Box<dyn Fn(&EventClient) -> ()>>>>,
pub on_message: Rc<RefCell<Option<Box<dyn Fn(&EventClient, Message) -> ()>>>>,
pub on_close: Rc<RefCell<Option<Box<dyn Fn() -> ()>>>>,
}
#[cfg(target_arch = "wasm32")]
impl EventClient {
pub fn new(url: &str) -> Result<Self, WebSocketError> {
let ws: web_sys::WebSocket = match WebSocket::new(url) {
Ok(ws) => ws,
Err(e) => Err(WebSocketError::ConnectionCreationError(
"Failed to connect".into(),
))?,
};
ws.set_binary_type(web_sys::BinaryType::Arraybuffer);
let status = Rc::new(RefCell::new(ConnectionStatus::Connecting));
let ref_status = status.clone();
let on_error: Rc<RefCell<Option<Box<dyn Fn(ErrorEvent) -> ()>>>> =
Rc::new(RefCell::new(None));
let on_error_ref = on_error.clone();
let onerror_callback = Closure::wrap(Box::new(move |e: ErrorEvent| {
*ref_status.borrow_mut() = ConnectionStatus::Error;
if let Some(f) = &*on_error_ref.borrow() {
f.as_ref()(e);
}
}) as Box<dyn FnMut(ErrorEvent)>);
ws.set_onerror(Some(onerror_callback.as_ref().unchecked_ref()));
onerror_callback.forget();
let on_close: Rc<RefCell<Option<Box<dyn Fn() -> ()>>>> = Rc::new(RefCell::new(None));
let on_close_ref = on_close.clone();
let ref_status = status.clone();
let onclose_callback = Closure::wrap(Box::new(move || {
*ref_status.borrow_mut() = ConnectionStatus::Disconnected;
if let Some(f) = &*on_close_ref.borrow() {
f.as_ref()();
}
}) as Box<dyn FnMut()>);
ws.set_onclose(Some(onclose_callback.as_ref().unchecked_ref()));
onclose_callback.forget();
let on_connection: Rc<RefCell<Option<Box<dyn Fn(&EventClient) -> ()>>>> =
Rc::new(RefCell::new(None));
let on_connection_ref = on_connection.clone();
let on_message: Rc<RefCell<Option<Box<dyn Fn(&EventClient, Message) -> ()>>>> =
Rc::new(RefCell::new(None));
let on_message_ref = on_message.clone();
let ref_status = status.clone();
let connection = Rc::new(RefCell::new(ws));
let client = Rc::new(RefCell::new(Self {
url: Rc::new(RefCell::new(url.to_string())),
connection: connection.clone(),
on_error: on_error.clone(),
on_connection: on_connection.clone(),
status: status.clone(),
on_message: on_message.clone(),
on_close: on_close.clone(),
}));
let client_ref = client.clone();
let onopen_callback = Closure::wrap(Box::new(move |_| {
*ref_status.borrow_mut() = ConnectionStatus::Connected;
if let Some(f) = &*on_connection_ref.borrow() {
f.as_ref()(&*client_ref.clone().borrow());
}
}) as Box<dyn FnMut(JsValue)>);
connection
.borrow_mut()
.set_onopen(Some(onopen_callback.as_ref().unchecked_ref()));
onopen_callback.forget();
let client_ref = client.clone();
let onmessage_callback = Closure::wrap(Box::new(move |e: MessageEvent| {
if let Ok(abuf) = e.data().dyn_into::<js_sys::ArrayBuffer>() {
trace!("message event, received arraybuffer: {:?}", abuf);
let array = js_sys::Uint8Array::new(&abuf).to_vec();
if let Some(f) = &*on_message_ref.borrow() {
f.as_ref()(&*client_ref.clone().borrow(), Message::Binary(array));
}
} else if let Ok(blob) = e.data().dyn_into::<web_sys::Blob>() {
trace!("message event, received blob: {:?}", blob);
let fr = web_sys::FileReader::new().unwrap();
let fr_c = fr.clone();
let cbref = on_message_ref.clone();
let cbfref = client_ref.clone();
let onloadend_cb = Closure::wrap(Box::new(move |_e: web_sys::ProgressEvent| {
let array = js_sys::Uint8Array::new(&fr_c.result().unwrap()).to_vec();
if let Some(f) = &*cbref.borrow() {
f.as_ref()(&*cbfref.clone().borrow(), Message::Binary(array));
}
})
as Box<dyn FnMut(web_sys::ProgressEvent)>);
fr.set_onloadend(Some(onloadend_cb.as_ref().unchecked_ref()));
fr.read_as_array_buffer(&blob).expect("blob not readable");
onloadend_cb.forget();
} else if let Ok(txt) = e.data().dyn_into::<js_sys::JsString>() {
if let Some(f) = &*on_message_ref.borrow() {
f.as_ref()(&*client_ref.clone().borrow(), Message::Text(txt.into()));
}
} else {
panic!("Unknown data: {:#?}", e.data());
}
}) as Box<dyn FnMut(MessageEvent)>);
connection
.borrow()
.set_onmessage(Some(onmessage_callback.as_ref().unchecked_ref()));
onmessage_callback.forget();
Ok(Self {
url: Rc::new(RefCell::new(url.to_string())),
connection,
on_error,
on_connection,
on_message,
on_close,
status: status,
})
}
pub fn set_on_error(&mut self, f: Option<Box<dyn Fn(ErrorEvent) -> ()>>) {
*self.on_error.borrow_mut() = f;
}
pub fn set_on_connection(&mut self, f: Option<Box<dyn Fn(&EventClient) -> ()>>) {
*self.on_connection.borrow_mut() = f;
}
pub fn set_on_message(&mut self, f: Option<Box<dyn Fn(&EventClient, Message) -> ()>>) {
*self.on_message.borrow_mut() = f;
}
pub fn set_on_close(&mut self, f: Option<Box<dyn Fn() -> ()>>) {
*self.on_close.borrow_mut() = f;
}
pub fn send_string(&self, message: &str) -> Result<(), JsValue> {
self.connection.borrow().send_with_str(message)
}
pub fn send_binary(&self, message: Vec<u8>) -> Result<(), JsValue> {
self.connection
.borrow()
.send_with_u8_array(message.as_slice())
}
}