use std::cell::RefCell;
use std::rc::Rc;
use websock_proto::Bytes;
use websock_proto::{CloseFrame, ConnectOptions, Error, Message, Result};
use futures_channel::{mpsc, oneshot};
use futures_util::StreamExt;
use wasm_bindgen::JsCast;
use wasm_bindgen::prelude::*;
pub async fn connect(url: &str, opts: ConnectOptions) -> Result<Connection> {
opts.limits.validate()?;
let max_message_size = opts.limits.max_message_size;
let ws = if opts.protocols.is_empty() {
web_sys::WebSocket::new(url).map_err(js_err)?
} else {
let arr = js_sys::Array::new();
for p in &opts.protocols {
arr.push(&JsValue::from_str(p));
}
web_sys::WebSocket::new_with_str_sequence(url, &arr).map_err(js_err)?
};
ws.set_binary_type(web_sys::BinaryType::Arraybuffer);
let (tx, rx) = mpsc::channel::<Result<Message>>(64);
let (open_tx, open_rx) = oneshot::channel::<Result<()>>();
let open_tx_cell: Rc<RefCell<Option<oneshot::Sender<Result<()>>>>> =
Rc::new(RefCell::new(Some(open_tx)));
let open_tx_cell_onopen = Rc::clone(&open_tx_cell);
let wait_onopen = Closure::<dyn FnMut()>::new(move || {
if let Some(tx) = open_tx_cell_onopen.borrow_mut().take() {
let _ = tx.send(Ok(()));
}
});
ws.set_onopen(Some(wait_onopen.as_ref().unchecked_ref()));
let open_tx_cell_onerror = Rc::clone(&open_tx_cell);
let wait_onerror = Closure::<dyn FnMut(web_sys::Event)>::new(move |_e: web_sys::Event| {
if let Some(tx) = open_tx_cell_onerror.borrow_mut().take() {
let _ = tx.send(Err(Error::Other("websocket error (before open)".into())));
}
});
ws.set_onerror(Some(wait_onerror.as_ref().unchecked_ref()));
let open_tx_cell_onclose = Rc::clone(&open_tx_cell);
let wait_onclose =
Closure::<dyn FnMut(web_sys::CloseEvent)>::new(move |_e: web_sys::CloseEvent| {
if let Some(tx) = open_tx_cell_onclose.borrow_mut().take() {
let _ = tx.send(Err(Error::Closed));
}
});
ws.set_onclose(Some(wait_onclose.as_ref().unchecked_ref()));
let open_res = open_rx.await;
ws.set_onopen(None);
ws.set_onerror(None);
ws.set_onclose(None);
drop(wait_onopen);
drop(wait_onerror);
drop(wait_onclose);
match open_res {
Ok(Ok(())) => {}
Ok(Err(e)) => return Err(e),
Err(_) => return Err(Error::Other("onopen waiter dropped".into())),
}
let mut tx_msg = tx.clone();
let ws_onmessage = ws.clone();
let onmessage =
Closure::<dyn FnMut(web_sys::MessageEvent)>::new(move |e: web_sys::MessageEvent| {
let data = e.data();
if let Some(s) = data.as_string() {
if s.len() > max_message_size {
let _ = tx_msg.try_send(Err(Error::Protocol(
"websocket message exceeds max_message_size".into(),
)));
tx_msg.close_channel();
let _ = ws_onmessage.close();
return;
}
if tx_msg.try_send(Ok(Message::Text(s))).is_err() {
tx_msg.close_channel();
let _ = ws_onmessage.close();
}
return;
}
if data.is_instance_of::<js_sys::ArrayBuffer>() {
let ab: js_sys::ArrayBuffer = data.unchecked_into();
let u8arr = js_sys::Uint8Array::new(&ab);
if u8arr.length() as usize > max_message_size {
let _ = tx_msg.try_send(Err(Error::Protocol(
"websocket message exceeds max_message_size".into(),
)));
tx_msg.close_channel();
let _ = ws_onmessage.close();
return;
}
let mut buf = vec![0u8; u8arr.length() as usize];
u8arr.copy_to(&mut buf);
if tx_msg
.try_send(Ok(Message::Binary(Bytes::from(buf))))
.is_err()
{
tx_msg.close_channel();
let _ = ws_onmessage.close();
}
return;
}
let _ = tx_msg.try_send(Err(Error::Protocol("unsupported message type".into())));
tx_msg.close_channel();
let _ = ws_onmessage.close();
});
ws.set_onmessage(Some(onmessage.as_ref().unchecked_ref()));
let mut tx_err = tx.clone();
let onerror = Closure::<dyn FnMut(web_sys::Event)>::new(move |_e: web_sys::Event| {
if tx_err
.try_send(Err(Error::Other("websocket error".into())))
.is_err()
{
tx_err.close_channel();
}
});
ws.set_onerror(Some(onerror.as_ref().unchecked_ref()));
let close_frame = Rc::new(RefCell::new(None));
let close_frame_handler = Rc::clone(&close_frame);
let mut tx_close = tx;
let onclose = Closure::<dyn FnMut(web_sys::CloseEvent)>::new(move |e: web_sys::CloseEvent| {
*close_frame_handler.borrow_mut() = Some(CloseFrame {
code: e.code(),
reason: e.reason(),
});
let _ = tx_close.try_send(Err(Error::Closed));
tx_close.close_channel();
});
ws.set_onclose(Some(onclose.as_ref().unchecked_ref()));
let negotiated_subprotocol = ws.protocol();
Ok(Connection {
ws: Rc::new(ws),
rx: Some(rx),
max_write_buffer_size: opts.limits.max_write_buffer_size,
negotiated_subprotocol,
close_frame,
_onmessage: Some(onmessage),
_onerror: Some(onerror),
_onclose: Some(onclose),
})
}
pub struct Connection {
pub(crate) ws: Rc<web_sys::WebSocket>,
pub(crate) rx: Option<mpsc::Receiver<Result<Message>>>,
pub(crate) max_write_buffer_size: usize,
pub(crate) negotiated_subprotocol: String,
pub(crate) close_frame: Rc<RefCell<Option<CloseFrame>>>,
pub(crate) _onmessage: Option<Closure<dyn FnMut(web_sys::MessageEvent)>>,
pub(crate) _onerror: Option<Closure<dyn FnMut(web_sys::Event)>>,
pub(crate) _onclose: Option<Closure<dyn FnMut(web_sys::CloseEvent)>>,
}
impl Connection {
pub async fn send(&mut self, msg: Message) -> Result<()> {
match msg {
Message::Text(s) => {
check_send_capacity(&self.ws, s.len(), self.max_write_buffer_size)?;
self.ws.send_with_str(&s).map_err(js_err)?;
}
Message::Binary(b) => {
check_send_capacity(&self.ws, b.len(), self.max_write_buffer_size)?;
self.ws.send_with_u8_array(b.as_ref()).map_err(js_err)?;
}
}
Ok(())
}
pub async fn recv(&mut self) -> Result<Message> {
let rx = self.rx.as_mut().ok_or(Error::Closed)?;
rx.next().await.ok_or(Error::Closed)?
}
pub async fn close(&mut self) -> Result<()> {
if self.ws.ready_state() == web_sys::WebSocket::CLOSED {
self.rx = None;
return Ok(());
}
if self.ws.ready_state() != web_sys::WebSocket::CLOSING {
self.ws.close().map_err(js_err)?;
}
let result = loop {
let next = match self.rx.as_mut() {
Some(rx) => rx.next().await,
None => return Ok(()),
};
match next {
Some(Ok(_)) => continue,
Some(Err(Error::Closed)) | None => break Ok(()),
Some(Err(error)) => break Err(error),
}
};
self.rx = None;
result
}
pub fn negotiated_subprotocol(&self) -> Option<&str> {
(!self.negotiated_subprotocol.is_empty()).then_some(self.negotiated_subprotocol.as_str())
}
pub fn close_frame(&self) -> Option<CloseFrame> {
self.close_frame.borrow().clone()
}
}
impl websock_proto::WebSocketConnection for Connection {
fn send<'a>(&'a mut self, msg: Message) -> websock_proto::LocalBoxFuture<'a, Result<()>> {
Box::pin(async move { Connection::send(self, msg).await })
}
fn recv<'a>(&'a mut self) -> websock_proto::LocalBoxFuture<'a, Result<Message>> {
Box::pin(async move { Connection::recv(self).await })
}
fn close<'a>(&'a mut self) -> websock_proto::LocalBoxFuture<'a, Result<()>> {
Box::pin(async move { Connection::close(self).await })
}
}
impl Drop for Connection {
fn drop(&mut self) {
if Rc::strong_count(&self.ws) != 1 {
return;
}
if self._onmessage.is_some() {
self.ws.set_onmessage(None);
}
if self._onerror.is_some() {
self.ws.set_onerror(None);
}
if self._onclose.is_some() {
self.ws.set_onclose(None);
}
self.ws.set_onopen(None);
let _ = self.ws.close();
}
}
pub(crate) fn js_err(e: JsValue) -> Error {
Error::Other(format!("{e:?}"))
}
pub(crate) fn check_send_capacity(
ws: &web_sys::WebSocket,
message_len: usize,
max_write_buffer_size: usize,
) -> Result<()> {
let buffered = usize::try_from(ws.buffered_amount()).unwrap_or(usize::MAX);
if buffered.saturating_add(message_len) > max_write_buffer_size {
return Err(Error::Other("websocket write buffer limit exceeded".into()));
}
Ok(())
}