musli_web/
web03.rs

1//! Client side implementation for [`web-sys`] `0.3.x`.
2//!
3//! [`web-sys`]: <https://docs.rs/web-sys/0.3>
4//!
5//! # Examples
6//!
7//! This example uses [`yew021`]:
8//!
9//! [`yew021`]: crate::yew021
10//!
11//! ```no_run
12//! use musli_web::web03::prelude::*;
13//!
14//! mod api {
15//!     use musli::{Decode, Encode};
16//!     use musli_web::api;
17//!
18//!     #[derive(Encode, Decode)]
19//!     pub struct HelloRequest<'de> {
20//!         pub message: &'de str,
21//!     }
22//!
23//!     #[derive(Encode, Decode)]
24//!     pub struct HelloResponse<'de> {
25//!         pub message: &'de str,
26//!     }
27//!
28//!     api::define! {
29//!         pub type Hello;
30//!
31//!         impl Endpoint for Hello {
32//!             impl<'de> Request for HelloRequest<'de>;
33//!             type Response<'de> = HelloResponse<'de>;
34//!         }
35//!     }
36//! }
37//!
38//! let service = ws::connect(ws::Connect::location("ws"))
39//!     .on_error(|error| {
40//!         tracing::error!("WebSocket error: {error}");
41//!     })
42//!     .build();
43//!
44//! service.connect();
45//!
46//! let request = service
47//!     .handle()
48//!     .request()
49//!     .body(api::HelloRequest {
50//!         message: "Hello!",
51//!     })
52//!     .on_raw_packet(move |packet: Result<ws::RawPacket, ws::Error>| {
53//!         match packet {
54//!             Ok(packet) => {
55//!                 if let Ok(response) = packet.decode::<api::HelloResponse>() {
56//!                     tracing::info!("Response: {}", response.message);
57//!                 }
58//!             }
59//!             Err(error) => {
60//!                 tracing::error!("Request error: {error}");
61//!             }
62//!         }
63//!     })
64//!     .send();
65//! ```
66
67use alloc::rc::Rc;
68use alloc::rc::Weak;
69
70use wasm_bindgen02::JsCast;
71use wasm_bindgen02::closure::Closure;
72use web_sys03::Performance;
73use web_sys03::Window;
74use web_sys03::js_sys::Function;
75use web_sys03::js_sys::{ArrayBuffer, Math, Uint8Array};
76use web_sys03::{BinaryType, CloseEvent, ErrorEvent, MessageEvent, WebSocket, window};
77
78use crate::web::{
79    Connect, EmptyCallback, Error, Location, PerformanceImpl, ServiceBuilder, Shared, SocketImpl,
80    WebImpl, WindowImpl,
81};
82
83pub mod prelude {
84    //! The public facing API for use with yew `0.2.1` and web-sys `0.3.x`.
85
86    pub mod ws {
87        //! Organization module prefixing all exported items with `ws` for
88        //! convenient namespacing.
89
90        pub use crate::web::{
91            Connect, EmptyCallback, Error, Listener, Packet, RawPacket, Request, State,
92            StateListener,
93        };
94        use crate::web03::Web03Impl;
95
96        /// Implementation alias for [`connect`].
97        ///
98        /// [`connect`]: crate::web03::connect
99        pub fn connect(connect: Connect) -> ServiceBuilder<EmptyCallback> {
100            crate::web03::connect(connect)
101        }
102
103        /// Implementation alias for [`Service`].
104        ///
105        /// [`Service`]: crate::web::Service
106        pub type Service = crate::web::Service<Web03Impl>;
107
108        /// Implementation alias for [`Handle`].
109        ///
110        /// [`Handle`]: crate::web::Handle
111        pub type Handle = crate::web::Handle<Web03Impl>;
112
113        /// Implementation alias for [`RequestBuilder`].
114        ///
115        /// [`RequestBuilder`]: crate::web::RequestBuilder
116        pub type RequestBuilder<'a, B, C> = crate::web::RequestBuilder<'a, Web03Impl, B, C>;
117
118        /// Implementation alias for [`ServiceBuilder`].
119        ///
120        /// [`ServiceBuilder`]: crate::web::ServiceBuilder
121        pub type ServiceBuilder<C> = crate::web::ServiceBuilder<Web03Impl, C>;
122    }
123}
124
125/// Handles for websocket implementation.
126#[doc(hidden)]
127pub struct Handles {
128    open: Closure<dyn Fn()>,
129    close: Closure<dyn Fn(CloseEvent)>,
130    message: Closure<dyn Fn(MessageEvent)>,
131    error: Closure<dyn Fn(ErrorEvent)>,
132}
133
134/// WebSocket implementation for web-sys `0.3.x`.
135///
136/// See [`connect()`].
137#[derive(Clone, Copy)]
138pub enum Web03Impl {}
139
140impl crate::web::sealed_socket::Sealed for WebSocket {}
141
142impl SocketImpl for WebSocket {
143    type Handles = Handles;
144
145    #[inline]
146    fn new(url: &str, handles: &Self::Handles) -> Result<Self, Error> {
147        let this = WebSocket::new(url)?;
148        this.set_binary_type(BinaryType::Arraybuffer);
149        this.set_onopen(Some(handles.open.as_ref().unchecked_ref()));
150        this.set_onclose(Some(handles.close.as_ref().unchecked_ref()));
151        this.set_onmessage(Some(handles.message.as_ref().unchecked_ref()));
152        this.set_onerror(Some(handles.error.as_ref().unchecked_ref()));
153        Ok(this)
154    }
155
156    #[inline]
157    fn send(&self, data: &[u8]) -> Result<(), Error> {
158        self.send_with_u8_array(data)?;
159        Ok(())
160    }
161
162    #[inline]
163    fn close(self) -> Result<(), Error> {
164        WebSocket::close(&self)?;
165        Ok(())
166    }
167}
168
169impl crate::web::sealed_performance::Sealed for Performance {}
170
171impl PerformanceImpl for Performance {
172    #[inline]
173    fn now(&self) -> f64 {
174        Performance::now(self)
175    }
176}
177
178impl crate::web::sealed_window::Sealed for Window {}
179
180impl WindowImpl for Window {
181    type Performance = Performance;
182    type Timeout = Timeout;
183
184    #[inline]
185    fn new() -> Result<Self, Error> {
186        let Some(window) = window() else {
187            return Err(Error::msg("No window in web-sys 0.3.x context"));
188        };
189
190        Ok(window)
191    }
192
193    #[inline]
194    fn performance(&self) -> Result<Self::Performance, Error> {
195        let Some(performance) = Window::performance(self) else {
196            return Err(Error::msg("No window.performance in web-sys 0.3.x context"));
197        };
198
199        Ok(performance)
200    }
201
202    #[inline]
203    fn location(&self) -> Result<Location, Error> {
204        let location = Window::location(self);
205
206        Ok(Location {
207            protocol: location.protocol()?,
208            host: location.hostname()?,
209            port: location.port()?,
210        })
211    }
212
213    #[inline]
214    fn set_timeout(
215        &self,
216        millis: u32,
217        callback: impl FnOnce() + 'static,
218    ) -> Result<Self::Timeout, Error> {
219        let closure = Closure::once(callback);
220
221        let id = self.set_timeout_with_callback_and_timeout_and_arguments_0(
222            closure.as_ref().unchecked_ref::<Function>(),
223            millis as i32,
224        )?;
225
226        Ok(Timeout {
227            window: self.clone(),
228            id: Some(id),
229            closure: Some(closure),
230        })
231    }
232}
233
234pub struct Timeout {
235    window: Window,
236    id: Option<i32>,
237    #[allow(dead_code)]
238    closure: Option<Closure<dyn FnMut()>>,
239}
240
241impl Drop for Timeout {
242    /// Disposes of the timeout, dually cancelling this timeout by calling
243    /// `clearTimeout` directly.
244    fn drop(&mut self) {
245        if let Some(id) = self.id.take() {
246            self.window.clear_timeout_with_handle(id);
247        }
248    }
249}
250
251impl crate::web::sealed_web::Sealed for Web03Impl {}
252
253impl WebImpl for Web03Impl {
254    type Window = Window;
255    type Handles = Handles;
256    type Socket = WebSocket;
257
258    #[inline]
259    fn random(range: u32) -> u32 {
260        ((Math::random() * range as f64).round() as u32).min(range)
261    }
262
263    #[inline]
264    #[allow(private_interfaces)]
265    fn handles(shared: &Weak<Shared<Self>>) -> Self::Handles {
266        let open = {
267            let shared = shared.clone();
268
269            Closure::new(move || {
270                if let Some(shared) = shared.upgrade() {
271                    shared.web03_open();
272                }
273            })
274        };
275
276        let close = {
277            let shared = shared.clone();
278
279            Closure::new(move |e: CloseEvent| {
280                if let Some(shared) = shared.upgrade() {
281                    shared.web03_close(e);
282                }
283            })
284        };
285
286        let message = {
287            let shared = shared.clone();
288
289            Closure::new(move |e: MessageEvent| {
290                if let Some(shared) = shared.upgrade() {
291                    shared.web03_message(e);
292                }
293            })
294        };
295
296        let error = {
297            let shared = shared.clone();
298
299            Closure::new(move |e: ErrorEvent| {
300                if let Some(shared) = shared.upgrade() {
301                    shared.web03_error(e);
302                }
303            })
304        };
305
306        Self::Handles {
307            open,
308            close,
309            message,
310            error,
311        }
312    }
313}
314
315/// Construct a new [`ServiceBuilder`] associated with the given [`Connect`]
316/// strategy.
317#[inline]
318pub fn connect(connect: Connect) -> ServiceBuilder<Web03Impl, EmptyCallback> {
319    crate::web::connect(connect)
320}
321
322impl Shared<Web03Impl> {
323    fn web03_open(&self) {
324        tracing::debug!("Open event");
325
326        self.set_open();
327    }
328
329    fn web03_close(self: &Rc<Self>, e: CloseEvent) {
330        tracing::debug!(code = e.code(), reason = e.reason(), "Close event");
331
332        if let Err(e) = self.close() {
333            self.on_error.call(e);
334        }
335    }
336
337    fn web03_message(self: &Rc<Shared<Web03Impl>>, e: MessageEvent) {
338        tracing::debug!("Message event");
339
340        let Ok(array_buffer) = e.data().dyn_into::<ArrayBuffer>() else {
341            self.on_error
342                .call(Error::msg("Expected message as ArrayBuffer"));
343            return;
344        };
345
346        let array = Uint8Array::new(&array_buffer);
347        let needed = array.length() as usize;
348
349        let mut buf = self.next_buffer(needed);
350
351        // SAFETY: We've sized the buffer appropriately above.
352        unsafe {
353            array.raw_copy_to_ptr(buf.data.as_mut_ptr());
354            buf.data.set_len(needed);
355        }
356
357        if let Err(e) = self.message(buf) {
358            self.on_error.call(e);
359        }
360    }
361
362    fn web03_error(self: &Rc<Self>, e: ErrorEvent) {
363        tracing::debug!(message = e.message(), "Error event");
364
365        if let Err(e) = self.close() {
366            self.on_error.call(e);
367        }
368    }
369}