use super::{BinaryType, CloseEvent, Result, WebSocket, WebSocketMessage};
use crate::app::Orders;
use std::marker::PhantomData;
use std::rc::Rc;
use wasm_bindgen::{closure::Closure, JsCast, JsValue};
use web_sys::MessageEvent;
#[derive(Default, Debug)]
pub struct Callbacks {
pub on_open: Option<Closure<dyn Fn(JsValue)>>,
pub on_close: Option<Closure<dyn Fn(JsValue)>>,
pub on_error: Option<Closure<dyn Fn(JsValue)>>,
pub on_message: Option<Closure<dyn Fn(MessageEvent)>>,
}
pub struct Builder<'a, U: AsRef<str>, Ms: 'static, O: Orders<Ms>> {
url: U,
orders: &'a O,
callbacks: Callbacks,
protocols: &'a [&'a str],
binary_type: Option<BinaryType>,
phantom: PhantomData<Ms>,
}
impl<'a, U: AsRef<str>, Ms: 'static, O: Orders<Ms>> Builder<'a, U, Ms, O> {
pub(crate) fn new(url: U, orders: &'a O) -> Self {
Self {
url,
orders,
callbacks: Callbacks::default(),
protocols: &[],
binary_type: None,
phantom: PhantomData,
}
}
#[must_use]
pub const fn protocols(mut self, protocols: &'a [&'a str]) -> Self {
self.protocols = protocols;
self
}
#[must_use]
pub const fn use_array_buffers(mut self) -> Self {
self.binary_type = Some(BinaryType::Arraybuffer);
self
}
#[allow(clippy::missing_panics_doc)]
#[must_use]
pub fn on_open<MsU: 'static>(
mut self,
handler: impl FnOnce() -> MsU + Clone + 'static,
) -> Self {
let handler = map_callback_return_to_option_ms!(
dyn Fn(JsValue) -> Option<Ms>,
|_| handler.clone()(),
"WebSocket handler on_open can return only Msg, Option<Msg> or ()!",
Rc
);
let callback = create_js_handler(handler, self.orders);
self.callbacks.on_open = Some(callback);
self
}
#[allow(clippy::missing_panics_doc)]
#[must_use]
pub fn on_close<MsU: 'static>(
mut self,
handler: impl FnOnce(CloseEvent) -> MsU + Clone + 'static,
) -> Self {
let handler = map_callback_return_to_option_ms!(
dyn Fn(JsValue) -> Option<Ms>,
|event: JsValue| { handler.clone()(event.unchecked_into()) },
"WebSocket handler on_close can return only Msg, Option<Msg> or ()!",
Rc
);
let callback = create_js_handler(handler, self.orders);
self.callbacks.on_close = Some(callback);
self
}
#[allow(clippy::missing_panics_doc)]
#[must_use]
pub fn on_error<MsU: 'static>(
mut self,
handler: impl FnOnce() -> MsU + Clone + 'static,
) -> Self {
let handler = map_callback_return_to_option_ms!(
dyn Fn(JsValue) -> Option<Ms>,
|_| handler.clone()(),
"WebSocket handler on_error can return only Msg, Option<Msg> or ()!",
Rc
);
let callback = create_js_handler(handler, self.orders);
self.callbacks.on_error = Some(callback);
self
}
#[allow(clippy::missing_panics_doc)]
#[must_use]
pub fn on_message<MsU: 'static>(
mut self,
handler: impl FnOnce(WebSocketMessage) -> MsU + Clone + 'static,
) -> Self {
let handler = map_callback_return_to_option_ms!(
dyn Fn(MessageEvent) -> Option<Ms>,
|message_event: MessageEvent| {
let message = WebSocketMessage {
data: message_event.data(),
message_event,
};
handler.clone()(message)
},
"WebSocket handler on_message can return only Msg, Option<Msg> or ()!",
Rc
);
let callback = create_js_handler(handler, self.orders);
self.callbacks.on_message = Some(callback);
self
}
pub fn build_and_open(self) -> Result<WebSocket> {
WebSocket::new(
self.url.as_ref(),
self.callbacks,
self.protocols,
self.binary_type,
)
}
}
fn create_js_handler<T: wasm_bindgen::convert::FromWasmAbi + 'static, Ms: 'static>(
handler: Rc<dyn Fn(T) -> Option<Ms>>,
orders: &impl Orders<Ms>,
) -> Closure<dyn Fn(T)> {
let (app, msg_mapper) = (orders.clone_app(), orders.msg_mapper());
let mailbox = app.mailbox();
Closure::wrap(Box::new(move |data| {
#[allow(clippy::redundant_closure)]
mailbox.send(handler(data).map(|msg| msg_mapper(msg)));
}) as Box<dyn Fn(T)>)
}