wsdom_core/
link.rs

1use std::{
2    collections::HashMap,
3    sync::{Arc, Mutex},
4    task::{Poll, Waker},
5};
6
7/// A WSDOM client.
8///
9/// You can use this to call JS functions on the JS client (the web browser).
10/// Every JsValue holds a Browser object which they internally use for calling methods, etc.
11///
12/// Browser uses Arc internally, so cloning is cheap and a cloned Browser points to the same client.
13///
14/// ## Use with Integration Library
15///
16/// You can obtain Browser from the WSDOM integration library (for example, `wsdom-axum`).
17///
18/// ## Manual Usage
19///
20/// If there is no WSDOM integration library for your framework,
21/// you can instead create Browser manually with the `new()` method.
22///
23/// Manually created Browsers need to be "driven"
24/// -   Browser implements the [Stream][futures_core::Stream] trait with [String].
25///     You must take items from the stream and send it to the WSDOM JS client
26///     over WebSocket or other transport of your choice.
27/// -   Browser has a `receive_incoming_message(msg: String)` method.
28///     Everything sent by the WSDOM JS client must be fed into this method.
29///
30/// The `counter-manual` example in our repo shows manual usage with Tokio.
31#[derive(Clone, Debug)]
32pub struct Browser(pub(crate) Arc<Mutex<BrowserInternal>>);
33
34impl Browser {
35    /// Create a new Browser object.
36    ///
37    /// This is only needed if you intend to go the "manual" route described above.
38    pub fn new() -> Self {
39        let link = BrowserInternal {
40            retrievals: HashMap::new(),
41            last_id: 1,
42            commands_buf: String::new(),
43            outgoing_waker: None,
44            dead: ErrorState::NoError,
45        };
46        Self(Arc::new(Mutex::new(link)))
47    }
48    /// Receive a message sent from the WSDOM JS client.
49    ///
50    /// This is only needed if you intend to go the "manual" route described above.
51    /// If you use an integration library, messages are handled automatically.
52    pub fn receive_incoming_message(&self, message: String) {
53        self.0.lock().unwrap().receive(message);
54    }
55    /// If the Browser has errored, this will return the error.
56    ///
57    /// The [Error] type is not [Clone], so after the first call returning `Some(_)`,
58    /// this method will return `None`.
59    pub fn take_error(&self) -> Option<Error> {
60        let mut link = self.0.lock().unwrap();
61        match std::mem::replace(&mut link.dead, ErrorState::ErrorTaken) {
62            ErrorState::NoError => {
63                link.dead = ErrorState::NoError;
64                None
65            }
66            ErrorState::Error(e) => Some(e),
67            ErrorState::ErrorTaken => None,
68        }
69    }
70}
71
72/// The stream of messages that should be sent over WebSocket (or your transport of choice) to the JavaScript WSDOM client.
73impl futures_core::Stream for Browser {
74    type Item = String;
75
76    fn poll_next(
77        self: std::pin::Pin<&mut Self>,
78        cx: &mut std::task::Context<'_>,
79    ) -> Poll<Option<Self::Item>> {
80        let this = self.get_mut();
81        let mut link = this.0.lock().unwrap();
82
83        if !matches!(&link.dead, ErrorState::NoError) {
84            return Poll::Ready(None);
85        }
86
87        let new_waker = cx.waker();
88        if !link
89            .outgoing_waker
90            .as_ref()
91            .is_some_and(|w| new_waker.will_wake(w))
92        {
93            link.outgoing_waker = Some(new_waker.to_owned());
94        }
95        if !link.commands_buf.is_empty() {
96            Poll::Ready(Some(std::mem::take(&mut link.commands_buf)))
97        } else {
98            Poll::Pending
99        }
100    }
101}
102
103#[derive(Debug)]
104pub struct BrowserInternal {
105    pub(crate) retrievals: HashMap<u64, RetrievalState>,
106    last_id: u64,
107    commands_buf: String,
108    outgoing_waker: Option<Waker>,
109    dead: ErrorState,
110}
111
112/// Error that could happen in WSDOM.
113///
114/// Currently, the only errors that could happen are from [serde] serialization and deserialization.
115#[derive(Debug)]
116pub enum Error {
117    CommandSerialize(std::fmt::Error),
118    DataDeserialize(serde_json::Error),
119}
120#[derive(Debug)]
121enum ErrorState {
122    NoError,
123    Error(Error),
124    ErrorTaken,
125}
126
127#[derive(Debug)]
128pub(crate) struct RetrievalState {
129    pub(crate) waker: Waker,
130    pub(crate) last_value: String,
131    pub(crate) times: usize,
132}
133
134impl BrowserInternal {
135    pub fn receive(&mut self, message: String) {
136        match message
137            .split_once(':')
138            .and_then(|(id, _)| id.parse::<u64>().ok())
139        {
140            Some(id) => match self.retrievals.get_mut(&id) {
141                Some(s) => {
142                    s.times += 1;
143                    s.last_value = message;
144                    s.waker.wake_by_ref();
145                }
146                _ => {}
147            },
148            None => {}
149        }
150    }
151    pub fn raw_commands_buf(&mut self) -> &mut String {
152        &mut self.commands_buf
153    }
154    pub(crate) fn get_new_id(&mut self) -> u64 {
155        self.last_id += 1;
156        self.last_id
157    }
158    pub(crate) fn kill(&mut self, err: Error) {
159        if matches!(self.dead, ErrorState::NoError) {
160            self.dead = ErrorState::Error(err);
161        }
162    }
163    pub(crate) fn wake_outgoing(&mut self) {
164        if let Some(waker) = self.outgoing_waker.as_ref() {
165            waker.wake_by_ref();
166        }
167    }
168    pub(crate) fn wake_outgoing_lazy(&mut self) {
169        self.wake_outgoing();
170    }
171}
172
173struct InvalidReturn;
174impl std::fmt::Debug for InvalidReturn {
175    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
176        f.debug_struct("InvalidReturn").finish()
177    }
178}
179impl std::fmt::Display for InvalidReturn {
180    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
181        std::fmt::Debug::fmt(self, f)
182    }
183}
184impl std::error::Error for InvalidReturn {}