1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88
//! WebSocket client for waterfall data.
use std::cell::RefCell;
use std::rc::Rc;
use wasm_bindgen::prelude::*;
use wasm_bindgen::JsCast;
use web_sys::{CloseEvent, MessageEvent, WebSocket, Window};
use crate::waterfall::Waterfall;
/// WebSocket client for waterfall data.
///
/// Implements a WebSocket client that receives messages containing waterfall
/// data and submits the data to the waterfall by calling
/// [Waterfall::put_waterfall_spectrum].
pub struct WebSocketClient {}
struct WebSocketData {
url: String,
// Closure that handles onmessage
onmessage: JsValue,
// Closure that handles onclose. It is inside a RefCell<Option<>> because
// the closure is self-referential, in the sense that to try a reconnection,
// the onclose closure needs access to the onclose closure, in order to
// assign it to the onclose of the new websocket.
onclose: RefCell<Option<JsValue>>,
}
impl WebSocketClient {
/// Starts the WebSocket client.
///
/// The client is given shared mutable access to the [`Waterfall`].
///
/// This function creates and registers the appropriate on-message handler
/// for the WebSocket client. No further interaction with the
/// `WebSocketClient` returned by this function is needed and it can be
/// dropped immediately.
pub fn start(window: &Window, waterfall: Rc<RefCell<Waterfall>>) -> Result<(), JsValue> {
let location = window.location();
let hostname = location.hostname()?;
let port = location.port()?;
let data = Rc::new(WebSocketData {
url: format!("ws://{hostname}:{port}/waterfall"),
onmessage: onmessage(waterfall).into_js_value(),
onclose: RefCell::new(None),
});
data.setup_onclose();
// initiate first connection
data.connect()?;
Ok(())
}
}
fn onmessage(waterfall: Rc<RefCell<Waterfall>>) -> Closure<dyn Fn(MessageEvent)> {
Closure::new(move |event: MessageEvent| {
let data = match event.data().dyn_into::<js_sys::ArrayBuffer>() {
Ok(x) => x,
Err(e) => {
web_sys::console::error_1(&e);
return;
}
};
waterfall
.borrow_mut()
.put_waterfall_spectrum(&js_sys::Float32Array::new(&data));
})
}
impl WebSocketData {
fn connect(&self) -> Result<(), JsValue> {
let ws = WebSocket::new(&self.url)?;
ws.set_binary_type(web_sys::BinaryType::Arraybuffer);
ws.set_onmessage(Some(self.onmessage.unchecked_ref()));
// by this point onclose shouldn't be None
ws.set_onclose(Some(
self.onclose.borrow().as_ref().unwrap().unchecked_ref(),
));
Ok(())
}
fn setup_onclose(self: &Rc<Self>) {
let data = Rc::clone(self);
let closure = Closure::<dyn Fn(CloseEvent)>::new(move |_: CloseEvent| {
data.connect().unwrap();
});
*self.onclose.borrow_mut() = Some(closure.into_js_value());
}
}