Skip to main content

musli_web/
macros.rs

1/// Defines the implementation of a `tokio-tungstenite` integration.
2///
3/// This is shared between the supported versions, which only differ in the
4/// crate they are linked against, the name of the generated implementation
5/// type, and the documented version.
6#[cfg(any(feature = "tungstenite029", feature = "tungstenite030"))]
7macro_rules! tungstenite_impl {
8    ($krate:ident, $impl_ty:ident, $version:literal) => {
9        use alloc::boxed::Box;
10        use alloc::string::{String, ToString};
11
12        use core::future::{Future, poll_fn};
13        use core::pin::Pin;
14        use core::task::{Context, Poll};
15
16        use std::io;
17
18        use bytes::Bytes;
19        use futures_core03::Stream;
20        use futures_sink03::Sink;
21        use tokio::io::{AsyncRead, AsyncWrite};
22        use tokio::net::TcpStream;
23        use $krate::tungstenite::client::IntoClientRequest;
24        use $krate::tungstenite::error::ProtocolError;
25        use $krate::tungstenite::handshake::client::Request;
26        use $krate::tungstenite::http::header::{HeaderMap, HeaderName, HeaderValue};
27        use $krate::tungstenite::protocol::Message as WsMessage;
28        use $krate::tungstenite::{Error, Utf8Bytes};
29        use $krate::{MaybeTlsStream, WebSocketStream, client_async, connect_async};
30
31        use crate::api::Mode;
32        use crate::client::{ClientImpl, EmptyCallback, Message, ServiceBuilder, SocketImpl};
33
34        /// A byte stream a websocket can be established over, see
35        /// [`connect_with()`].
36        ///
37        /// Implemented for anything which is [`AsyncRead`] and [`AsyncWrite`],
38        /// such as a `TcpStream`, a `UnixStream`, or one half of
39        /// `tokio::io::duplex`.
40        ///
41        /// [`AsyncRead`]: <https://docs.rs/tokio/1/tokio/io/trait.AsyncRead.html>
42        /// [`AsyncWrite`]: <https://docs.rs/tokio/1/tokio/io/trait.AsyncWrite.html>
43        pub trait ByteStream: 'static + Send + Unpin + AsyncRead + AsyncWrite {}
44
45        impl<S> ByteStream for S where S: 'static + Send + Unpin + AsyncRead + AsyncWrite {}
46
47        /// A boxed future opening a [`ByteStream`].
48        type OpenFuture = Pin<Box<dyn Future<Output = io::Result<Box<dyn ByteStream>>> + Send>>;
49
50        /// The socket type used by this implementation.
51        #[doc(hidden)]
52        pub enum Socket {
53            /// A socket connected to a url.
54            Url(Box<WebSocketStream<MaybeTlsStream<TcpStream>>>),
55            /// A socket established over a caller-supplied stream.
56            Stream(Box<WebSocketStream<Box<dyn ByteStream>>>),
57        }
58
59        /// A caller-supplied way of opening the stream a websocket is
60        /// established over, see [`connect_with()`].
61        #[doc(hidden)]
62        pub struct Connector {
63            url: String,
64            open: Box<dyn FnMut() -> OpenFuture + Send>,
65        }
66
67        #[doc = concat!("The public facing API for use with `tokio-tungstenite` `", $version, "`.")]
68        pub mod prelude {
69            pub mod ws {
70                //! Organization module prefixing all exported items with `ws` for
71                //! convenient namespacing.
72
73                pub use crate::api::ChannelId;
74                pub use crate::client::{
75                    Channel, EmptyCallback, Error, Handle, Listener, Packet, RawPacket,
76                    RequestBuilder, State, StateListener,
77                };
78
79                use core::future::Future;
80
81                use super::super::$impl_ty;
82
83                /// Implementation alias for [`connect`].
84                ///
85                /// [`connect`]: super::super::connect
86                #[inline]
87                pub fn connect(url: impl AsRef<str>) -> ServiceBuilder<EmptyCallback> {
88                    super::super::connect(url)
89                }
90
91                /// Implementation alias for [`connect_with`].
92                ///
93                /// [`connect_with`]: super::super::connect_with
94                #[inline]
95                pub fn connect_with<F, O, S>(
96                    url: impl AsRef<str>,
97                    open: F,
98                ) -> ServiceBuilder<EmptyCallback>
99                where
100                    F: 'static + Send + FnMut() -> O,
101                    O: 'static + Send + Future<Output = std::io::Result<S>>,
102                    S: super::super::ByteStream,
103                {
104                    super::super::connect_with(url, open)
105                }
106
107                /// Implementation alias for [`Service`].
108                ///
109                /// [`Service`]: crate::client::Service
110                pub type Service = crate::client::Service<$impl_ty>;
111
112                /// Implementation alias for [`ServiceBuilder`].
113                ///
114                /// [`ServiceBuilder`]: crate::client::ServiceBuilder
115                pub type ServiceBuilder<C> = crate::client::ServiceBuilder<$impl_ty, C>;
116            }
117        }
118
119        #[doc = concat!("Client implementation for `tokio-tungstenite` `", $version, "`.")]
120        ///
121        /// See [`connect()`].
122        #[derive(Clone, Copy)]
123        pub enum $impl_ty {}
124
125        /// Construct a new [`ServiceBuilder`] which will connect to `url`.
126        ///
127        /// Note that no connection is established until [`Service::run`] or
128        /// [`Service::try_connect`] is called.
129        ///
130        /// [`Service::run`]: crate::client::Service::run
131        /// [`Service::try_connect`]: crate::client::Service::try_connect
132        #[inline]
133        pub fn connect(url: impl AsRef<str>) -> ServiceBuilder<$impl_ty, EmptyCallback> {
134            crate::client::connect(url)
135        }
136
137        /// Construct a new [`ServiceBuilder`] which establishes its websocket
138        /// over a stream opened by `open`, instead of connecting to `url`.
139        ///
140        /// `open` is called for every connection attempt, including
141        /// reconnects, so it can for example connect a `tokio::net::UnixStream`.
142        /// An error it returns is treated like any other failure to connect:
143        /// retried with a backoff by [`Service::run`], or returned by
144        /// [`Service::try_connect`].
145        ///
146        /// `url` is the request the websocket handshake is performed with, such
147        /// as `ws://localhost/ws`. It names the host and path the server sees,
148        /// but is never connected to.
149        ///
150        /// Note that no connection is established until [`Service::run`] or
151        /// [`Service::try_connect`] is called.
152        ///
153        /// [`Service::run`]: crate::client::Service::run
154        /// [`Service::try_connect`]: crate::client::Service::try_connect
155        pub fn connect_with<F, O, S>(
156            url: impl AsRef<str>,
157            mut open: F,
158        ) -> ServiceBuilder<$impl_ty, EmptyCallback>
159        where
160            F: 'static + Send + FnMut() -> O,
161            O: 'static + Send + Future<Output = io::Result<S>>,
162            S: ByteStream,
163        {
164            let url = url.as_ref();
165
166            let connector = Connector {
167                url: url.to_string(),
168                open: Box::new(move || {
169                    let future = open();
170
171                    Box::pin(async move {
172                        let stream = future.await?;
173                        Ok(Box::new(stream) as Box<dyn ByteStream>)
174                    })
175                }),
176            };
177
178            crate::client::connect_with(url, connector)
179        }
180
181        impl crate::client::sealed_client::Sealed for $impl_ty {}
182
183        /// The headers which make up the websocket handshake itself, which
184        /// the client sets and which cannot be added through
185        /// [`ServiceBuilder::header`].
186        ///
187        /// [`ServiceBuilder::header`]: crate::client::ServiceBuilder::header
188        const HANDSHAKE_HEADERS: [&str; 5] = [
189            "host",
190            "connection",
191            "upgrade",
192            "sec-websocket-key",
193            "sec-websocket-version",
194        ];
195
196        /// Build the handshake request for `url` with `headers` added to it.
197        fn request(url: &str, headers: &HeaderMap) -> Result<Request, Error> {
198            let mut request = url.into_client_request()?;
199
200            for (name, value) in headers {
201                request.headers_mut().append(name, value.clone());
202            }
203
204            Ok(request)
205        }
206
207        impl ClientImpl for $impl_ty {
208            type Error = Error;
209            type Socket = Socket;
210            type Connector = Connector;
211            type Headers = HeaderMap;
212
213            fn header(headers: &mut HeaderMap, name: &str, value: &str) -> Result<(), Error> {
214                let name = HeaderName::from_bytes(name.as_bytes())?;
215
216                if HANDSHAKE_HEADERS.contains(&name.as_str()) {
217                    return Err(Error::Protocol(ProtocolError::InvalidHeader(name.into())));
218                }
219
220                let value = HeaderValue::from_str(value)?;
221                headers.append(name, value);
222                Ok(())
223            }
224
225            #[inline]
226            async fn connect(url: &str, headers: &HeaderMap) -> Result<Self::Socket, Self::Error> {
227                let (socket, _) = connect_async(request(url, headers)?).await?;
228                Ok(Socket::Url(Box::new(socket)))
229            }
230
231            #[inline]
232            fn connect_with<'a>(
233                connector: &'a mut Self::Connector,
234                headers: &'a HeaderMap,
235            ) -> impl Future<Output = Result<Self::Socket, Self::Error>> + Send + 'a {
236                let open = (connector.open)();
237
238                async move {
239                    let request = request(connector.url.as_str(), headers)?;
240                    let stream = open.await.map_err(Error::Io)?;
241                    let (socket, _) = client_async(request, stream).await?;
242                    Ok(Socket::Stream(Box::new(socket)))
243                }
244            }
245        }
246
247        impl Socket {
248            #[inline]
249            fn poll_next_message(
250                &mut self,
251                cx: &mut Context<'_>,
252            ) -> Poll<Option<Result<WsMessage, Error>>> {
253                match self {
254                    Socket::Url(socket) => Pin::new(&mut **socket).poll_next(cx),
255                    Socket::Stream(socket) => Pin::new(&mut **socket).poll_next(cx),
256                }
257            }
258
259            #[inline]
260            fn poll_ready(&mut self, cx: &mut Context<'_>) -> Poll<Result<(), Error>> {
261                match self {
262                    Socket::Url(socket) => Pin::new(&mut **socket).poll_ready(cx),
263                    Socket::Stream(socket) => Pin::new(&mut **socket).poll_ready(cx),
264                }
265            }
266
267            #[inline]
268            fn start_send(&mut self, message: WsMessage) -> Result<(), Error> {
269                match self {
270                    Socket::Url(socket) => Pin::new(&mut **socket).start_send(message),
271                    Socket::Stream(socket) => Pin::new(&mut **socket).start_send(message),
272                }
273            }
274
275            #[inline]
276            fn poll_flush(&mut self, cx: &mut Context<'_>) -> Poll<Result<(), Error>> {
277                match self {
278                    Socket::Url(socket) => Pin::new(&mut **socket).poll_flush(cx),
279                    Socket::Stream(socket) => Pin::new(&mut **socket).poll_flush(cx),
280                }
281            }
282
283            #[inline]
284            fn poll_close(&mut self, cx: &mut Context<'_>) -> Poll<Result<(), Error>> {
285                match self {
286                    Socket::Url(socket) => Pin::new(&mut **socket).poll_close(cx),
287                    Socket::Stream(socket) => Pin::new(&mut **socket).poll_close(cx),
288                }
289            }
290        }
291
292        impl crate::client::sealed_socket::Sealed for Socket {}
293
294        impl SocketImpl for Socket {
295            type Error = Error;
296
297            #[inline]
298            fn recv(
299                &mut self,
300            ) -> impl Future<Output = Option<Result<Message, Self::Error>>> + Send + '_ {
301                poll_fn(move |cx| {
302                    self.poll_next_message(cx)
303                        .map(|message| message.map(|message| message.map(convert)))
304                })
305            }
306
307            #[inline]
308            fn send(
309                &mut self,
310                mode: Mode,
311                data: &[u8],
312            ) -> impl Future<Output = Result<(), Self::Error>> + Send + '_ {
313                let message = match mode {
314                    Mode::Binary => WsMessage::Binary(Bytes::copy_from_slice(data)),
315                    // NB: A text frame is only ever written in a mode which guarantees
316                    // that everything in it is valid UTF-8, so the lossy path is never
317                    // taken.
318                    Mode::Text => WsMessage::Text(Utf8Bytes::from(&*String::from_utf8_lossy(data))),
319                };
320
321                async move {
322                    poll_fn(|cx| self.poll_ready(cx)).await?;
323                    self.start_send(message)?;
324                    poll_fn(|cx| self.poll_flush(cx)).await
325                }
326            }
327
328            #[inline]
329            fn close(&mut self) -> impl Future<Output = Result<(), Self::Error>> + Send + '_ {
330                poll_fn(move |cx| self.poll_close(cx))
331            }
332        }
333
334        /// Convert a tungstenite message into a message understood by the client.
335        #[inline]
336        fn convert(message: WsMessage) -> Message {
337            match message {
338                WsMessage::Binary(data) => Message::Binary(data),
339                WsMessage::Text(data) => Message::Text(Bytes::from(data)),
340                WsMessage::Ping(..) => Message::Ping,
341                WsMessage::Pong(..) => Message::Pong,
342                WsMessage::Close(..) => Message::Close,
343                // NB: Raw frames are never produced while reading, so this is a
344                // protocol violation which tears the connection down.
345                WsMessage::Frame(..) => Message::Unsupported,
346            }
347        }
348    };
349}
350
351/// Implements the integration for a version of `yew`.
352///
353/// This is shared between the supported versions, which only differ in the
354/// crate they are linked against.
355///
356/// NB: yew's own `#[hook]` and `#[derive(Properties)]` macros expand to paths
357/// through `::yew`, which this crate does not link under that name, so the
358/// hooks here implement `Hook` by hand.
359#[cfg(any(feature = "yew022", feature = "yew023"))]
360macro_rules! yew_impl {
361    ($krate:ident) => {
362        use alloc::rc::Rc;
363        use core::cell::{Cell, RefCell};
364        use core::fmt;
365        use core::mem;
366
367        use $krate::functional::{
368            Hook, HookContext, use_context, use_force_update, use_memo, use_mut_ref,
369        };
370        use $krate::{Callback, Component, Context, ContextProvider};
371
372        use crate::web03::prelude::ws::{
373            Channel, ChannelEvent, Handle, KeyedRequests, Request, Requests, State,
374        };
375
376        impl<I> crate::web::Callback<I> for Callback<I>
377        where
378            I: 'static,
379        {
380            #[inline]
381            fn call(&self, result: I) {
382                self.emit(result);
383            }
384        }
385
386        /// The message a missing [`HandleProvider`] panics with.
387        const MISSING_PROVIDER: &str = concat!(
388            "no ws::Handle in context: wrap the component tree in musli_web::",
389            stringify!($krate),
390            "::HandleProvider"
391        );
392
393        /// Provides a [`ws::Handle`] to every component below it.
394        ///
395        /// This is yew's [`ContextProvider`] for [`ws::Handle`], so it takes
396        /// the handle as its `context` property. Components below it get the
397        /// handle with [`use_handle`] in a function component, or
398        /// [`ContextExt::handle`] in a struct component.
399        ///
400        /// The handle is usually that of a [`ws::Service`] the root component
401        /// keeps for as long as the application runs, see the [module
402        /// documentation](self) for an example.
403        ///
404        /// [`ws::Handle`]: crate::web::Handle
405        /// [`ws::Service`]: crate::web::Service
406        pub type HandleProvider = ContextProvider<Handle>;
407
408        /// Get the [`ws::Handle`] provided by the closest [`HandleProvider`]
409        /// above the component.
410        ///
411        /// The component renders again if the provided handle changes.
412        ///
413        /// # Panics
414        ///
415        /// Panics if the component is not below a [`HandleProvider`]. Use
416        /// `use_context::<ws::Handle>()` to get an `Option` instead.
417        ///
418        /// [`ws::Handle`]: crate::web::Handle
419        ///
420        /// # Examples
421        ///
422        #[doc = concat!("```\n# extern crate ", stringify!($krate), " as yew;")]
423        /// use yew::prelude::*;
424        #[doc = concat!("use musli_web::", stringify!($krate), "::use_handle;")]
425        ///
426        /// #[function_component(Status)]
427        /// fn status() -> Html {
428        ///     let handle = use_handle();
429        ///     html!(<p>{format!("Connected {} time(s)", handle.generation())}</p>)
430        /// }
431        /// ```
432        pub fn use_handle() -> impl Hook<Output = Handle> {
433            struct HookProvider;
434
435            impl Hook for HookProvider {
436                type Output = Handle;
437
438                fn run(self, ctx: &mut HookContext) -> Self::Output {
439                    match use_context::<Handle>().run(ctx) {
440                        Some(handle) => handle,
441                        None => panic!("{MISSING_PROVIDER}"),
442                    }
443                }
444            }
445
446            HookProvider
447        }
448
449        /// Keep a [`ws::Request`] for as long as the component is mounted.
450        ///
451        /// A request is cancelled when its [`ws::Request`] is dropped, so a
452        /// function component needs somewhere to keep it between renders.
453        /// Store a sent request with [`UseRequestHandle::set`], which cancels
454        /// the request stored before it. The stored request is cancelled when
455        /// the component is unmounted.
456        ///
457        /// [`ws::Request`]: crate::web::Request
458        ///
459        /// # Examples
460        ///
461        #[doc = concat!("```\n# extern crate ", stringify!($krate), " as yew;")]
462        /// use yew::prelude::*;
463        /// use musli_web::web03::prelude::*;
464        #[doc = concat!("use musli_web::", stringify!($krate), "::{use_handle, use_request};")]
465        ///
466        /// mod api {
467        ///     use musli::{Decode, Encode};
468        ///     use musli_web::api;
469        ///
470        ///     #[derive(Encode, Decode)]
471        ///     pub struct HelloRequest<'de> {
472        ///         pub message: &'de str,
473        ///     }
474        ///
475        ///     #[derive(Encode, Decode)]
476        ///     pub struct HelloResponse<'de> {
477        ///         pub message: &'de str,
478        ///     }
479        ///
480        ///     api::define! {
481        ///         pub type Hello;
482        ///
483        ///         impl Endpoint for Hello {
484        ///             impl<'de> Request for HelloRequest<'de>;
485        ///             type Response<'de> = HelloResponse<'de>;
486        ///         }
487        ///     }
488        /// }
489        ///
490        /// #[function_component(Hello)]
491        /// fn hello() -> Html {
492        ///     let handle = use_handle();
493        ///     let request = use_request();
494        ///     let response = use_state(String::new);
495        ///
496        ///     let onclick = {
497        ///         let response = response.clone();
498        ///
499        ///         move |_: MouseEvent| {
500        ///             let response = response.clone();
501        ///
502        ///             request.set(
503        ///                 handle
504        ///                     .request()
505        ///                     .body(api::HelloRequest { message: "Hello!" })
506        ///                     .on_packet(move |packet: Result<ws::Packet<api::Hello>, ws::Error>| {
507        ///                         if let Ok(r) = packet.and_then(|p| Ok(p.decode()?.message.to_owned())) {
508        ///                             response.set(r);
509        ///                         }
510        ///                     })
511        ///                     .send(),
512        ///             );
513        ///         }
514        ///     };
515        ///
516        ///     html! {
517        ///         <>
518        ///             <button {onclick}>{"Say hello"}</button>
519        ///             <p>{(*response).clone()}</p>
520        ///         </>
521        ///     }
522        /// }
523        /// ```
524        pub fn use_request() -> impl Hook<Output = UseRequestHandle> {
525            struct HookProvider;
526
527            impl Hook for HookProvider {
528                type Output = UseRequestHandle;
529
530                fn run(self, ctx: &mut HookContext) -> Self::Output {
531                    UseRequestHandle {
532                        inner: use_mut_ref(Request::new).run(ctx),
533                    }
534                }
535            }
536
537            HookProvider
538        }
539
540        /// The handle returned by [`use_request`].
541        ///
542        /// Cloning it is cheap and every clone refers to the same stored
543        /// request, so it can be moved into callbacks.
544        #[derive(Clone)]
545        pub struct UseRequestHandle {
546            inner: Rc<RefCell<Request>>,
547        }
548
549        impl UseRequestHandle {
550            /// Store `request`, cancelling the request stored before it.
551            pub fn set(&self, request: Request) {
552                let old = mem::replace(&mut *self.inner.borrow_mut(), request);
553                // NB: Dropped after the borrow ends, since cancelling the old
554                // request drops its callback.
555                drop(old);
556            }
557
558            /// Cancel the stored request, if any.
559            pub fn clear(&self) {
560                self.set(Request::new());
561            }
562
563            /// Whether the stored request is still waiting for its response.
564            ///
565            /// This does not make the component render again by itself; keep
566            /// track of what the response callback was told for that.
567            pub fn is_pending(&self) -> bool {
568                self.inner.borrow().is_pending()
569            }
570        }
571
572        /// Handles are equal if they refer to the same stored request.
573        impl PartialEq for UseRequestHandle {
574            #[inline]
575            fn eq(&self, other: &Self) -> bool {
576                Rc::ptr_eq(&self.inner, &other.inner)
577            }
578        }
579
580        impl fmt::Debug for UseRequestHandle {
581            fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
582                f.debug_struct("UseRequestHandle")
583                    .field("is_pending", &self.is_pending())
584                    .finish()
585            }
586        }
587
588        /// Keep a set of [`ws::Requests`] for as long as the component is
589        /// mounted.
590        ///
591        /// This is [`use_request`] for a component which keeps several
592        /// requests in flight at once: storing a request with
593        /// [`UseRequestsHandle::push`] leaves the requests stored before it
594        /// alone, and each one is forgotten once it completes. Every request
595        /// still pending is cancelled when the component is unmounted.
596        ///
597        /// [`ws::Requests`]: crate::web::Requests
598        ///
599        /// # Examples
600        ///
601        #[doc = concat!("```\n# extern crate ", stringify!($krate), " as yew;")]
602        /// use yew::prelude::*;
603        /// use musli_web::web03::prelude::*;
604        #[doc = concat!("use musli_web::", stringify!($krate), "::{use_handle, use_requests};")]
605        ///
606        /// mod api {
607        ///     use musli::{Decode, Encode};
608        ///     use musli_web::api;
609        ///
610        ///     #[derive(Encode, Decode)]
611        ///     pub struct LikeRequest {
612        ///         pub id: u32,
613        ///     }
614        ///
615        ///     api::define! {
616        ///         pub type Like;
617        ///
618        ///         impl Endpoint for Like {
619        ///             impl Request for LikeRequest;
620        ///             type Response<'de> = ();
621        ///         }
622        ///     }
623        /// }
624        ///
625        /// #[function_component(Like)]
626        /// fn like() -> Html {
627        ///     let handle = use_handle();
628        ///     let requests = use_requests();
629        ///
630        ///     let onclick = move |_: MouseEvent| {
631        ///         // Clicking again while an earlier like is in flight sends
632        ///         // another one, without cancelling the first.
633        ///         requests.push(
634        ///             handle
635        ///                 .request()
636        ///                 .body(api::LikeRequest { id: 42 })
637        ///                 .on_packet(|packet: Result<ws::Packet<api::Like>, ws::Error>| {
638        ///                     if let Err(error) = packet {
639        ///                         tracing::error!("Like failed: {error}");
640        ///                     }
641        ///                 })
642        ///                 .send(),
643        ///         );
644        ///     };
645        ///
646        ///     html! {
647        ///         <button {onclick}>{"Like"}</button>
648        ///     }
649        /// }
650        /// ```
651        pub fn use_requests() -> impl Hook<Output = UseRequestsHandle> {
652            struct HookProvider;
653
654            impl Hook for HookProvider {
655                type Output = UseRequestsHandle;
656
657                fn run(self, ctx: &mut HookContext) -> Self::Output {
658                    UseRequestsHandle {
659                        inner: use_mut_ref(Requests::new).run(ctx),
660                    }
661                }
662            }
663
664            HookProvider
665        }
666
667        /// The handle returned by [`use_requests`].
668        ///
669        /// Cloning it is cheap and every clone refers to the same set of
670        /// requests, so it can be moved into callbacks.
671        #[derive(Clone)]
672        pub struct UseRequestsHandle {
673            inner: Rc<RefCell<Requests>>,
674        }
675
676        impl UseRequestsHandle {
677            /// Keep `request` until it completes, see [`Requests::push`].
678            ///
679            /// [`Requests::push`]: crate::web::Requests::push
680            pub fn push(&self, request: Request) {
681                self.inner.borrow_mut().push(request);
682            }
683
684            /// Cancel every pending request.
685            pub fn clear(&self) {
686                let old = mem::take(&mut *self.inner.borrow_mut());
687                // NB: Dropped after the borrow ends, since cancelling the old
688                // requests drops their callbacks.
689                drop(old);
690            }
691
692            /// The number of requests which are still pending.
693            ///
694            /// This does not make the component render again by itself; keep
695            /// track of what the response callbacks were told for that.
696            pub fn len(&self) -> usize {
697                self.inner.borrow().len()
698            }
699
700            /// Whether no request is pending.
701            pub fn is_empty(&self) -> bool {
702                self.inner.borrow().is_empty()
703            }
704        }
705
706        /// Handles are equal if they refer to the same set of requests.
707        impl PartialEq for UseRequestsHandle {
708            #[inline]
709            fn eq(&self, other: &Self) -> bool {
710                Rc::ptr_eq(&self.inner, &other.inner)
711            }
712        }
713
714        impl fmt::Debug for UseRequestsHandle {
715            fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
716                f.debug_struct("UseRequestsHandle")
717                    .field("len", &self.len())
718                    .finish()
719            }
720        }
721
722        /// Keep a set of [`ws::KeyedRequests`] for as long as the component
723        /// is mounted.
724        ///
725        /// This keeps at most one request in flight for each key: storing a
726        /// request with [`UseKeyedRequestsHandle::insert`] cancels the request
727        /// stored under the same key before it, and leaves the other keys
728        /// alone. Each request is forgotten once it completes, and every
729        /// request still pending is cancelled when the component is
730        /// unmounted.
731        ///
732        /// [`ws::KeyedRequests`]: crate::web::KeyedRequests
733        ///
734        /// # Examples
735        ///
736        #[doc = concat!("```\n# extern crate ", stringify!($krate), " as yew;")]
737        /// # extern crate web_sys03 as web_sys;
738        /// use web_sys::HtmlInputElement;
739        /// use yew::prelude::*;
740        /// use musli_web::web03::prelude::*;
741        #[doc = concat!("use musli_web::", stringify!($krate), "::{use_handle, use_keyed_requests};")]
742        ///
743        /// mod api {
744        ///     use musli::{Decode, Encode};
745        ///     use musli_web::api;
746        ///
747        ///     #[derive(Encode, Decode)]
748        ///     pub struct ToggleRequest {
749        ///         pub id: u32,
750        ///         pub on: bool,
751        ///     }
752        ///
753        ///     api::define! {
754        ///         pub type Toggle;
755        ///
756        ///         impl Endpoint for Toggle {
757        ///             impl Request for ToggleRequest;
758        ///             type Response<'de> = ();
759        ///         }
760        ///     }
761        /// }
762        ///
763        /// #[derive(Properties, PartialEq)]
764        /// struct Props {
765        ///     ids: Vec<u32>,
766        /// }
767        ///
768        /// #[function_component(Toggles)]
769        /// fn toggles(props: &Props) -> Html {
770        ///     let handle = use_handle();
771        ///     let requests = use_keyed_requests::<u32>();
772        ///
773        ///     let toggle = move |id: u32, on: bool| {
774        ///         // Toggling an id again cancels its earlier toggle, but a
775        ///         // toggle of another id stays in flight.
776        ///         requests.insert(
777        ///             id,
778        ///             handle
779        ///                 .request()
780        ///                 .body(api::ToggleRequest { id, on })
781        ///                 .on_packet(|packet: Result<ws::Packet<api::Toggle>, ws::Error>| {
782        ///                     if let Err(error) = packet {
783        ///                         tracing::error!("Toggle failed: {error}");
784        ///                     }
785        ///                 })
786        ///                 .send(),
787        ///         );
788        ///     };
789        ///
790        ///     html! {
791        ///         {for props.ids.iter().copied().map(|id| {
792        ///             let toggle = toggle.clone();
793        ///
794        ///             let onchange = move |e: Event| {
795        ///                 toggle(id, e.target_unchecked_into::<HtmlInputElement>().checked())
796        ///             };
797        ///
798        ///             html!(<input type="checkbox" {onchange} />)
799        ///         })}
800        ///     }
801        /// }
802        /// ```
803        pub fn use_keyed_requests<K>() -> impl Hook<Output = UseKeyedRequestsHandle<K>>
804        where
805            K: 'static,
806        {
807            struct HookProvider<K>(core::marker::PhantomData<K>);
808
809            impl<K> Hook for HookProvider<K>
810            where
811                K: 'static,
812            {
813                type Output = UseKeyedRequestsHandle<K>;
814
815                fn run(self, ctx: &mut HookContext) -> Self::Output {
816                    UseKeyedRequestsHandle {
817                        inner: use_mut_ref(KeyedRequests::new).run(ctx),
818                    }
819                }
820            }
821
822            HookProvider(core::marker::PhantomData)
823        }
824
825        /// The handle returned by [`use_keyed_requests`].
826        ///
827        /// Cloning it is cheap and every clone refers to the same set of
828        /// requests, so it can be moved into callbacks.
829        pub struct UseKeyedRequestsHandle<K> {
830            inner: Rc<RefCell<KeyedRequests<K>>>,
831        }
832
833        impl<K> UseKeyedRequestsHandle<K>
834        where
835            K: Eq + core::hash::Hash,
836        {
837            /// Keep `request` under `key` until it completes, cancelling the
838            /// request stored under `key` before it, see
839            /// [`KeyedRequests::insert`].
840            ///
841            /// [`KeyedRequests::insert`]: crate::web::KeyedRequests::insert
842            pub fn insert(&self, key: K, request: Request) {
843                self.inner.borrow_mut().insert(key, request);
844            }
845
846            /// Cancel the request stored under `key`, returning whether it
847            /// was still pending.
848            pub fn remove<Q>(&self, key: &Q) -> bool
849            where
850                K: core::borrow::Borrow<Q>,
851                Q: ?Sized + Eq + core::hash::Hash,
852            {
853                self.inner.borrow_mut().remove(key)
854            }
855
856            /// Whether the request stored under `key` is still pending.
857            ///
858            /// This does not make the component render again by itself; keep
859            /// track of what the response callbacks were told for that.
860            pub fn is_pending<Q>(&self, key: &Q) -> bool
861            where
862                K: core::borrow::Borrow<Q>,
863                Q: ?Sized + Eq + core::hash::Hash,
864            {
865                self.inner.borrow().is_pending(key)
866            }
867        }
868
869        impl<K> UseKeyedRequestsHandle<K> {
870            /// Cancel every pending request.
871            pub fn clear(&self) {
872                let old = mem::take(&mut *self.inner.borrow_mut());
873                // NB: Dropped after the borrow ends, since cancelling the old
874                // requests drops their callbacks.
875                drop(old);
876            }
877
878            /// The number of keys whose request is still pending.
879            pub fn len(&self) -> usize {
880                self.inner.borrow().len()
881            }
882
883            /// Whether no request is pending.
884            pub fn is_empty(&self) -> bool {
885                self.inner.borrow().is_empty()
886            }
887        }
888
889        impl<K> Clone for UseKeyedRequestsHandle<K> {
890            #[inline]
891            fn clone(&self) -> Self {
892                Self {
893                    inner: self.inner.clone(),
894                }
895            }
896        }
897
898        /// Handles are equal if they refer to the same set of requests.
899        impl<K> PartialEq for UseKeyedRequestsHandle<K> {
900            #[inline]
901            fn eq(&self, other: &Self) -> bool {
902                Rc::ptr_eq(&self.inner, &other.inner)
903            }
904        }
905
906        impl<K> fmt::Debug for UseKeyedRequestsHandle<K>
907        where
908            K: fmt::Debug,
909        {
910            fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
911                f.debug_tuple("UseKeyedRequestsHandle")
912                    .field(&*self.inner.borrow())
913                    .finish()
914            }
915        }
916
917        /// Open a [`ws::Channel`] through the [`ws::Handle`] from
918        /// [`use_handle`], for as long as the component is mounted.
919        ///
920        /// The channel opens again by itself every time the connection does,
921        /// and the component renders again every time it opens, closes or
922        /// fails to open. Since channels [compare equal] by their current id, an
923        /// effect which depends on the channel runs again every time it opens,
924        /// which is the point at which to send the requests which need it:
925        ///
926        /// ```text
927        /// use_effect_with(channel.clone(), move |channel| {
928        ///     if channel.is_open() {
929        ///         request.set(channel.request()./* ... */.send());
930        ///     }
931        /// });
932        /// ```
933        ///
934        /// A request made in response to the user can instead be queued with
935        /// [`Channel::when_open`], so that it is sent even if the channel
936        /// happens to be closed at the time.
937        ///
938        /// A new channel is opened if the provided handle changes. The channel
939        /// is closed when the component is unmounted and every clone of it has
940        /// been dropped. To be told why a channel failed to open, open it with
941        /// [`Handle::channel`] and [`ChannelBuilder::on_change`] instead.
942        ///
943        /// # Panics
944        ///
945        /// Panics if the component is not below a [`HandleProvider`].
946        ///
947        /// [`ws::Channel`]: crate::web::Channel
948        /// [`ws::Handle`]: crate::web::Handle
949        /// [compare equal]: crate::web::Channel#impl-PartialEq-for-Channel%3CH%3E
950        /// [`Channel::when_open`]: crate::web::Channel::when_open
951        /// [`Handle::channel`]: crate::web::Handle::channel
952        /// [`ChannelBuilder::on_change`]: crate::web::ChannelBuilder::on_change
953        ///
954        /// # Examples
955        ///
956        #[doc = concat!("```\n# extern crate ", stringify!($krate), " as yew;")]
957        /// use yew::prelude::*;
958        /// use musli_web::web03::prelude::*;
959        #[doc = concat!("use musli_web::", stringify!($krate), "::{use_channel, use_request};")]
960        ///
961        /// mod api {
962        ///     use musli::{Decode, Encode};
963        ///     use musli_web::api;
964        ///
965        ///     #[derive(Encode, Decode)]
966        ///     pub struct Subscribe;
967        ///
968        ///     #[derive(Encode, Decode)]
969        ///     pub struct Subscribed {
970        ///         pub count: u32,
971        ///     }
972        ///
973        ///     api::define! {
974        ///         pub type Counter;
975        ///
976        ///         impl Endpoint for Counter {
977        ///             impl Request for Subscribe;
978        ///             type Response<'de> = Subscribed;
979        ///         }
980        ///     }
981        /// }
982        ///
983        /// #[function_component(Counter)]
984        /// fn counter() -> Html {
985        ///     let channel = use_channel();
986        ///     let request = use_request();
987        ///     let count = use_state(|| None::<u32>);
988        ///
989        ///     {
990        ///         let count = count.clone();
991        ///
992        ///         use_effect_with(channel.clone(), move |channel| {
993        ///             if channel.is_open() {
994        ///                 request.set(
995        ///                     channel
996        ///                         .request()
997        ///                         .body(api::Subscribe)
998        ///                         .on_packet(move |packet: Result<ws::Packet<api::Counter>, ws::Error>| {
999        ///                             if let Ok(response) = packet.and_then(|p| p.decode()) {
1000        ///                                 count.set(Some(response.count));
1001        ///                             }
1002        ///                         })
1003        ///                         .send(),
1004        ///                 );
1005        ///             }
1006        ///         });
1007        ///     }
1008        ///
1009        ///     match (channel.is_open(), *count) {
1010        ///         (false, _) => html!(<p>{"Connecting..."}</p>),
1011        ///         (true, None) => html!(<p>{"Loading..."}</p>),
1012        ///         (true, Some(count)) => html!(<p>{format!("Count: {count}")}</p>),
1013        ///     }
1014        /// }
1015        /// ```
1016        pub fn use_channel() -> impl Hook<Output = Channel> {
1017            struct HookProvider;
1018
1019            impl Hook for HookProvider {
1020                type Output = Channel;
1021
1022                fn run(self, ctx: &mut HookContext) -> Self::Output {
1023                    let handle = use_handle().run(ctx);
1024                    let update = use_force_update().run(ctx);
1025
1026                    let channel = use_memo(handle, move |handle| {
1027                        handle
1028                            .channel()
1029                            .on_change(move |_: ChannelEvent| update.force_update())
1030                            .open()
1031                    })
1032                    .run(ctx);
1033
1034                    Channel::clone(&channel)
1035                }
1036            }
1037
1038            HookProvider
1039        }
1040
1041        /// Follow whether the connection of the [`ws::Handle`] from
1042        /// [`use_handle`] is open.
1043        ///
1044        /// This is `Some` with the connection's [generation] while it is open,
1045        /// and `None` while it is not. The component renders again when it
1046        /// changes, so an effect which depends on it runs again every time the
1047        /// connection opens, which is the point at which to fetch what the
1048        /// component shows again, see [refetching after a reconnect].
1049        ///
1050        /// # Panics
1051        ///
1052        /// Panics if the component is not below a [`HandleProvider`].
1053        ///
1054        /// [`ws::Handle`]: crate::web::Handle
1055        /// [generation]: crate::web::Handle::generation
1056        /// [refetching after a reconnect]: crate::web#refetching-after-a-reconnect
1057        ///
1058        /// # Examples
1059        ///
1060        #[doc = concat!("```\n# extern crate ", stringify!($krate), " as yew;")]
1061        /// use yew::prelude::*;
1062        #[doc = concat!("use musli_web::", stringify!($krate), "::{use_handle, use_open, use_request};")]
1063        ///
1064        /// #[function_component(Status)]
1065        /// fn status() -> Html {
1066        ///     let handle = use_handle();
1067        ///     let open = use_open();
1068        ///     let request = use_request();
1069        ///
1070        ///     use_effect_with(open, move |open| {
1071        ///         if open.is_some() {
1072        ///             // Send the requests this component shows the result of
1073        ///             // with `handle.request()` and store them in `request`.
1074        ///         }
1075        ///     });
1076        ///
1077        ///     match open {
1078        ///         Some(generation) => html!(<p>{format!("Connection #{generation}")}</p>),
1079        ///         None => html!(<p>{"Disconnected"}</p>),
1080        ///     }
1081        /// }
1082        /// ```
1083        pub fn use_open() -> impl Hook<Output = Option<u64>> {
1084            struct HookProvider;
1085
1086            impl Hook for HookProvider {
1087                type Output = Option<u64>;
1088
1089                fn run(self, ctx: &mut HookContext) -> Self::Output {
1090                    let handle = use_handle().run(ctx);
1091                    let update = use_force_update().run(ctx);
1092
1093                    let state = use_memo(handle, move |handle| {
1094                        let open = Rc::new(Cell::new(None));
1095
1096                        let (state, listener) = handle.on_state_change({
1097                            let open = open.clone();
1098                            let handle = handle.clone();
1099
1100                            move |state: State| {
1101                                let new = state.is_open().then(|| handle.generation());
1102
1103                                if open.replace(new) != new {
1104                                    update.force_update();
1105                                }
1106                            }
1107                        });
1108
1109                        open.set(state.is_open().then(|| handle.generation()));
1110                        (open, listener)
1111                    })
1112                    .run(ctx);
1113
1114                    state.0.get()
1115                }
1116            }
1117
1118            HookProvider
1119        }
1120
1121        mod sealed {
1122            pub trait Sealed {}
1123            impl<C> Sealed for super::Context<C> where C: super::Component {}
1124        }
1125
1126        /// Extension methods to get the [`ws::Handle`] provided by a
1127        /// [`HandleProvider`] in a struct component.
1128        ///
1129        /// These look the handle up when called and are not told if the
1130        /// provided handle changes, which suits the usual case of a handle
1131        /// which the root component keeps for as long as the application runs.
1132        /// To be told about changes, use [`Scope::context`] instead.
1133        ///
1134        /// [`ws::Handle`]: crate::web::Handle
1135        #[doc = concat!("[`Scope::context`]: ", stringify!($krate), "::html::Scope::context")]
1136        ///
1137        /// # Examples
1138        ///
1139        #[doc = concat!("```\n# extern crate ", stringify!($krate), " as yew;")]
1140        /// use yew::prelude::*;
1141        /// use musli_web::web03::prelude::*;
1142        #[doc = concat!("use musli_web::", stringify!($krate), "::ContextExt;")]
1143        ///
1144        /// struct Status {
1145        ///     handle: ws::Handle,
1146        /// }
1147        ///
1148        /// impl Component for Status {
1149        ///     type Message = ();
1150        ///     type Properties = ();
1151        ///
1152        ///     fn create(ctx: &Context<Self>) -> Self {
1153        ///         Self {
1154        ///             handle: ctx.handle(),
1155        ///         }
1156        ///     }
1157        ///
1158        ///     fn view(&self, _: &Context<Self>) -> Html {
1159        ///         html!(<p>{format!("Connected {} time(s)", self.handle.generation())}</p>)
1160        ///     }
1161        /// }
1162        /// ```
1163        pub trait ContextExt: sealed::Sealed {
1164            /// Get the [`ws::Handle`] provided by the closest
1165            /// [`HandleProvider`] above the component.
1166            ///
1167            /// # Panics
1168            ///
1169            /// Panics if the component is not below a [`HandleProvider`]. Use
1170            /// [`ContextExt::try_handle`] to get an `Option` instead.
1171            ///
1172            /// [`ws::Handle`]: crate::web::Handle
1173            fn handle(&self) -> Handle;
1174
1175            /// Get the [`ws::Handle`] provided by the closest
1176            /// [`HandleProvider`] above the component, or `None` if there is
1177            /// none.
1178            ///
1179            /// [`ws::Handle`]: crate::web::Handle
1180            fn try_handle(&self) -> Option<Handle>;
1181        }
1182
1183        impl<C> ContextExt for Context<C>
1184        where
1185            C: Component,
1186        {
1187            #[inline]
1188            fn handle(&self) -> Handle {
1189                match self.try_handle() {
1190                    Some(handle) => handle,
1191                    None => panic!("{MISSING_PROVIDER}"),
1192                }
1193            }
1194
1195            #[inline]
1196            fn try_handle(&self) -> Option<Handle> {
1197                let (handle, _) = self.link().context::<Handle>(Callback::noop())?;
1198                Some(handle)
1199            }
1200        }
1201    };
1202}