Skip to main content

web_rpc/
interface.rs

1use futures_channel::{mpsc, oneshot};
2use futures_util::future;
3use wasm_bindgen::{JsCast, JsValue};
4
5/// An interface represents a [`crate::port::Port`] that has been fully initialised and has
6/// verified that the other end of the channel is ready to receive messages.
7pub struct Interface {
8    pub(crate) port: crate::port::Port,
9    pub(crate) listener: gloo_events::EventListener,
10    pub(crate) messages_rx: mpsc::UnboundedReceiver<js_sys::Array>,
11}
12
13impl Interface {
14    /// Create a new interface from anything that implements `Into<Port>`, for example, a
15    /// [`web_sys::MessagePort`], a [`web_sys::Worker`], or a
16    /// [`web_sys::DedicatedWorkerGlobalScope`]. This function is async and resolves to the new
17    /// interface instance once the other side of the channel is ready.
18    ///
19    /// The transport is used, not owned: a [`web_sys::MessagePort`] must have been
20    /// [`start`](web_sys::MessagePort::start)ed by its owner before it is passed here, or the
21    /// handshake never completes.
22    pub async fn new(port: impl Into<crate::port::Port>) -> Self {
23        let port = port.into();
24        let (dispatcher_tx, dispatcher_rx) = mpsc::unbounded();
25        let (ready_tx, ready_rx) = oneshot::channel();
26        let mut ready_tx = Option::from(ready_tx);
27        let listener =
28            gloo_events::EventListener::new(port.event_target(), "message", move |event| {
29                let message = event.unchecked_ref::<web_sys::MessageEvent>().data();
30                match message.dyn_into::<js_sys::Array>() {
31                    /* default path, enqueue the message for deserialization by the dispatcher */
32                    Ok(array) => {
33                        let _ = dispatcher_tx.unbounded_send(array);
34                    }
35                    /* handshake path */
36                    Err(_) => {
37                        if let Some(ready_tx) = ready_tx.take() {
38                            let _ = ready_tx.send(());
39                        }
40                    }
41                }
42            });
43        /* poll other end of the channel */
44        let port_cloned = port.clone();
45        let poll = std::pin::pin!(async move {
46            loop {
47                port_cloned
48                    .post_message(&JsValue::NULL, &JsValue::UNDEFINED)
49                    .unwrap();
50                gloo_timers::future::TimeoutFuture::new(10).await;
51            }
52        });
53        future::select(ready_rx, poll).await;
54        /* at this point we know the other end's listener is available, but we may
55        need to send one last message to indicate that we are available */
56        port.post_message(&JsValue::NULL, &JsValue::UNDEFINED)
57            .unwrap();
58        /* return the interface */
59        Self {
60            messages_rx: dispatcher_rx,
61            listener,
62            port,
63        }
64    }
65}