use std::cell::RefCell;
use std::rc::Rc;
use wasm_bindgen::{prelude::Closure, JsCast, JsValue};
use web_sys::{ErrorEvent, MessageEvent};
use super::{client_events::ClientRequest, Error, HostResult};
type Connection = web_sys::WebSocket;
pub struct WebApi {
conn: Connection,
error_handler: Box<dyn FnMut(Error) + 'static>,
}
impl Drop for WebApi {
fn drop(&mut self) {
let _ = self.conn.close_with_code(1000);
}
}
impl WebApi {
pub fn start<ErrFn>(
conn: Connection,
result_handler: impl FnMut(HostResult) + 'static,
error_handler: ErrFn,
onopen_handler: impl FnOnce() + 'static,
) -> Self
where
ErrFn: FnMut(Error) + Clone + 'static,
{
let eh = Rc::new(RefCell::new(error_handler.clone()));
let result_handler = Rc::new(RefCell::new(result_handler));
let onmessage_callback = Closure::<dyn FnMut(_)>::new(move |e: MessageEvent| {
let value: JsValue = e.data();
let blob: web_sys::Blob = value.into();
let file_reader = web_sys::FileReader::new().unwrap();
let fr_clone = file_reader.clone();
let eh_clone = eh.clone();
let result_handler_clone = result_handler.clone();
let onloadend_callback = Closure::<dyn FnMut()>::new(move || {
let array_buffer = fr_clone
.result()
.unwrap()
.dyn_into::<js_sys::ArrayBuffer>()
.unwrap();
let bytes = js_sys::Uint8Array::new(&array_buffer).to_vec();
let response: HostResult = match bincode::deserialize(&bytes) {
Ok(val) => val,
Err(err) => {
eh_clone.borrow_mut()(Error::ConnectionError(serde_json::json!({
"error": format!("{err}"),
"source": "host response deserialization"
})));
return;
}
};
result_handler_clone.borrow_mut()(response);
});
file_reader.set_onloadend(Some(onloadend_callback.as_ref().unchecked_ref()));
file_reader.read_as_array_buffer(&blob).unwrap();
onloadend_callback.forget();
});
conn.set_onmessage(Some(onmessage_callback.as_ref().unchecked_ref()));
onmessage_callback.forget();
let mut eh = error_handler.clone();
let onerror_callback = Closure::<dyn FnMut(_)>::new(move |e: ErrorEvent| {
let error = format!(
"error: {file}:{lineno}: {msg}",
file = e.filename(),
lineno = e.lineno(),
msg = e.message()
);
eh(Error::ConnectionError(serde_json::json!({
"error": error, "source": "exec error"
})));
});
conn.set_onerror(Some(onerror_callback.as_ref().unchecked_ref()));
onerror_callback.forget();
let onopen_handler = Rc::new(RefCell::new(Some(onopen_handler)));
let onopen_callback = Closure::wrap(Box::new(move || {
if let Some(handler) = onopen_handler.borrow_mut().take() {
handler();
}
}) as Box<dyn FnMut()>);
conn.set_onopen(Some(onopen_callback.as_ref().unchecked_ref()));
onopen_callback.forget();
conn.set_binary_type(web_sys::BinaryType::Blob);
WebApi {
conn,
error_handler: Box::new(error_handler),
}
}
pub async fn send(&mut self, request: ClientRequest<'static>) -> Result<(), Error> {
let send = bincode::serialize(&request)?;
self.conn.send_with_u8_array(&send).map_err(|err| {
let err: serde_json::Value = match serde_wasm_bindgen::from_value(err) {
Ok(e) => e,
Err(e) => {
let e = serde_json::json!({
"error": format!("{e}"),
"origin": "request serialization",
"request": format!("{request:?}"),
});
(self.error_handler)(Error::ConnectionError(e.clone()));
return Error::ConnectionError(e);
}
};
(self.error_handler)(Error::ConnectionError(serde_json::json!({
"error": err,
"origin": "request sending",
"request": format!("{request:?}"),
})));
Error::ConnectionError(err)
})?;
Ok(())
}
pub fn disconnect(self, cause: impl AsRef<str>) {
let _ = self.conn.close_with_code_and_reason(1000, cause.as_ref());
}
}