Newer
Older
//! This crate offers 2 (wasm-only) websocket clients.
//! The first client offered is the [`EventClient`]. This client is event based and gives you the most control.
//! ```
//! use log::{error, info, Level};
//! use wasm_bindgen::JsValue;
//! use wasm_sockets;
//! use console_log;
//! use console_error_panic_hook;
//! use std::panic;
//! panic::set_hook(Box::new(console_error_panic_hook::hook));
//! // console_log and log macros are used instead of println!
//! // so that messages can be seen in the browser console
//! console_log::init_with_level(Level::Trace).expect("Failed to enable logging");
//! info!("Creating connection");
//!
//! let mut client = wasm_sockets::EventClient::new("wss://echo.websocket.org")?;
//! client.set_on_error(Some(Box::new(|error| {
//! error!("{:#?}", error);
//! })));
//! client.set_on_connection(Some(Box::new(|client: &wasm_sockets::EventClient, e| {
//! info!("{:#?}", client.status);
//! info!("Sending message...");
//! client.send_string("Hello, World!").unwrap();
//! client.send_binary(vec![20]).unwrap();
//! })));
//! client.set_on_close(Some(Box::new(|| {
//! info!("Connection closed");
//! })));
//! client.set_on_message(Some(Box::new(
//! |client: &wasm_sockets::EventClient, message: wasm_sockets::Message| {
//! info!("New Message: {:#?}", message);
//! },
//! )));
//!
//! info!("Connection successfully created");
//! Ok(())
//! }
//! ```
//! The second client offered is the [`PollingClient`]. This client is ideal for games, because it is designed to be used with a loop.
//! This client is also much simpler than the [`EventClient`]. However, you can access the main [`EventClient`] that it is using
//! if you want access to lower level control.
//! ```
//! use log::{info, Level};
//! use std::cell::RefCell;
//! use std::panic;
//! use std::rc::Rc;
//! use wasm_bindgen::prelude::*;
//! panic::set_hook(Box::new(console_error_panic_hook::hook));
//! // console_log and log macros are used instead of println!
//! // so that messages can be seen in the browser console
//! console_log::init_with_level(Level::Trace).expect("Failed to enable logging");
//! info!("Creating connection");
//!
//! // Client is wrapped in an Rc<RefCell<>> so it can be used within setInterval
//! // This isn't required when being used within a game engine
//! let client = Rc::new(RefCell::new(wasm_sockets::PollingClient::new(
//! "wss://echo.websocket.org",
//! )?));
//!
//! let f = Closure::wrap(Box::new(move || {
//! if client.borrow().status() == ConnectionStatus::Connected {
//! info!("Sending message");
//! client.borrow().send_string("Hello, World!").unwrap();
//! }
//! // receive() gives you all new websocket messages since receive() was last called
//! info!("New messages: {:#?}", client.borrow_mut().receive());
//! }) as Box<dyn FnMut()>);
//!
//! // Start non-blocking game loop
//! setInterval(&f, 100);
//! f.forget();
//!
//! Ok(())
//! }
//! // Bind setInterval to make a basic game loop
//! #[wasm_bindgen]
//! extern "C" {
//! fn setInterval(closure: &Closure<dyn FnMut()>, time: u32) -> i32;
//! }
//! ```
use log::{debug, error, info, trace, warn, Level};
use std::cell::RefCell;
use std::rc::Rc;
use std::sync::{Arc, Condvar, Mutex};
use wasm_bindgen::prelude::*;
use wasm_bindgen::JsCast;
use web_sys::{ErrorEvent, MessageEvent, WebSocket};
/// Disconnected from a server without an error
Disconnected,
/// Message is a representation of a websocket message that can be sent or recieved
}
// TODO: Replace unwraps and JsValue with custom error type
/// Create a new PollingClient and connect to a WebSocket URL
///
/// Note: An Ok() from this function does not mean the connection has succeeded.
/// ```
/// PollingClient::new("wss://echo.websocket.org")?;
/// ```
// Create connection
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 |c, e| {
*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(e);
})));
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 |c: &EventClient, m: Message| {
data_ref.borrow_mut().push(m);
})));
/// Get all new WebSocket messages that were received since this function was last called
/// ```
/// println!("New messages: {:#?}", client.receive());
/// ```
pub fn receive(&mut self) -> Vec<Message> {
let data = (*self.data.borrow()).clone();
(*self.data.borrow_mut()).clear();
data
}
/// Get the client's current connection status
/// ```
/// println!("Current status: {:#?}", client.status());
/// ```
pub fn status(&self) -> ConnectionStatus {
self.status.borrow().clone()
}
/// Send a text message to the server
/// ```
/// client.send_string("Hello server!")?;
/// ```
pub fn send_string(&self, message: &str) -> Result<(), JsValue> {
self.event_client.send_string(message)
}
/// Send a binary message to the server
/// ```
/// client.send_binary(vec![0x2, 0xF])?;
/// ```
pub fn send_binary(&self, message: Vec<u8>) -> Result<(), JsValue> {
self.event_client.send_binary(message)
}
/// The raw web_sys WebSocket object this client is using
pub on_error: Rc<RefCell<Option<Box<dyn Fn(ErrorEvent) -> ()>>>>,
pub on_connection: Rc<RefCell<Option<Box<dyn Fn(&EventClient, JsValue) -> ()>>>>,
pub on_message: Rc<RefCell<Option<Box<dyn Fn(&EventClient, Message) -> ()>>>>,
/// The function bound to the on_close event
pub on_close: Rc<RefCell<Option<Box<dyn Fn() -> ()>>>>,
/// Create a new EventClient and connect to a WebSocket URL
///
/// Note: An Ok() from this function does not mean the connection has succeeded.
/// ```
/// EventClient::new("wss://echo.websocket.org")?;
/// ```
pub fn new(url: &str) -> Result<Self, JsValue> {
// Create connection
let ws: web_sys::WebSocket = WebSocket::new(url)?;
// For small binary messages, like CBOR, Arraybuffer is more efficient than Blob handling
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(e.clone());
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, JsValue) -> ()>>>> =
Rc::new(RefCell::new(None));
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 connection_ref = connection.clone();
url: Rc::new(RefCell::new(url.to_string())),
connection: connection.clone(),
on_error: on_error.clone(),
on_connection: on_connection.clone(),
status: status.clone(),
let onopen_callback = Closure::wrap(Box::new(move |v| {
*ref_status.borrow_mut() = ConnectionStatus::Connected;
if let Some(f) = &*on_connection_ref.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 client_ref2 = client.clone();
let onmessage_callback = Closure::wrap(Box::new(move |e: MessageEvent| {
// Process different types of message data
if let Ok(abuf) = e.data().dyn_into::<js_sys::ArrayBuffer>() {
// Received arraybuffer
trace!("message event, received arraybuffer: {:?}", abuf);
// Convert arraybuffer to vec
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>() {
// Received blob data
trace!("message event, received blob: {:?}", blob);
let fr = web_sys::FileReader::new().unwrap();
let fr_c = fr.clone();
// create onLoadEnd callback
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 {
// Got unknown data
panic!("Unknown data: {:#?}", e.data());
}
}) as Box<dyn FnMut(MessageEvent)>);
// set message event handler on WebSocket
connection
.borrow()
.set_onmessage(Some(onmessage_callback.as_ref().unchecked_ref()));
// forget the callback to keep it alive
onmessage_callback.forget();
Ok(Self {
url: Rc::new(RefCell::new(url.to_string())),
connection,
on_error,
on_connection,
/// Set an on_error event handler.
/// This handler will be run when the client disconnects from the server due to an error.
/// This will overwrite the previous handler.
/// You can set [None](std::option) to disable the on_error handler.
/// ```
/// client.set_on_error(Some(Box::new(|error| {
/// panic!("Error: {:#?}", error);
/// })));
/// ```
pub fn set_on_error(&mut self, f: Option<Box<dyn Fn(ErrorEvent) -> ()>>) {
*self.on_error.borrow_mut() = f;
}
/// Set an on_connection event handler.
/// This handler will be run when the client successfully connects to a server.
/// This will overwrite the previous handler.
/// You can set [None](std::option) to disable the on_connection handler.
/// ```
/// client.set_on_connection(Some(Box::new(|c, v| {
/// info!("Connected: {:#?}", e);
/// })));
/// ```
pub fn set_on_connection(&mut self, f: Option<Box<dyn Fn(&EventClient, JsValue) -> ()>>) {
/// Set an on_message event handler.
/// This handler will be run when the client receives a message from a server.
/// This will overwrite the previous handler.
/// You can set [None](std::option) to disable the on_message handler.
/// ```
/// client.set_on_message(Some(Box::new(
/// |c, m| {
/// info!("New Message: {:#?}", m);
/// },
/// )));
/// ```
pub fn set_on_message(&mut self, f: Option<Box<dyn Fn(&EventClient, Message) -> ()>>) {
/// Set an on_close event handler.
/// This handler will be run when the client disconnects from a server without an error.
/// This will overwrite the previous handler.
/// You can set [None](std::option) to disable the on_close handler.
/// ```
/// client.set_on_close(Some(Box::new(|| {
/// info!("Closed");
/// })));
/// ```
pub fn set_on_close(&mut self, f: Option<Box<dyn Fn() -> ()>>) {
*self.on_close.borrow_mut() = f;
}
/// Send a text message to the server
/// ```
/// client.send_string("Hello server!")?;
/// ```
pub fn send_string(&self, message: &str) -> Result<(), JsValue> {
/// Send a binary message to the server
/// ```
/// client.send_binary(vec![0x2, 0xF])?;
/// ```
pub fn send_binary(&self, message: Vec<u8>) -> Result<(), JsValue> {
self.connection