Skip to main content

web_rpc/
client.rs

1use std::{
2    cell::{Cell, RefCell},
3    collections::HashMap,
4    pin::Pin,
5    rc::Rc,
6    task::{Context, Poll},
7};
8
9use futures_channel::{mpsc, oneshot};
10use futures_core::{future::LocalBoxFuture, Future, Stream};
11use futures_util::{future, FutureExt, StreamExt};
12use js_sys::Array;
13use serde::Serialize;
14
15use crate::{port::Port, Dispatcher, MessageHeader};
16
17#[doc(hidden)]
18pub trait Client {
19    type Response;
20}
21
22#[doc(hidden)]
23pub type CallbackMap<Response> = HashMap<u32, oneshot::Sender<(Response, Array)>>;
24
25#[doc(hidden)]
26pub type StreamCallbackMap<Response> = HashMap<u32, mpsc::UnboundedSender<(Response, Array)>>;
27
28/// Everything a generated client holds. Clones share the maps and the sequence counter, so
29/// clones of one client never collide.
30#[doc(hidden)]
31pub struct State<Response> {
32    pub callbacks: Rc<RefCell<CallbackMap<Response>>>,
33    pub stream_callbacks: Rc<RefCell<StreamCallbackMap<Response>>>,
34    pub port: Port,
35    pub listener: Rc<gloo_events::EventListener>,
36    pub dispatcher: Dispatcher,
37    pub sequence: Rc<Cell<u32>>,
38}
39
40impl<Response> Clone for State<Response> {
41    fn clone(&self) -> Self {
42        Self {
43            callbacks: self.callbacks.clone(),
44            stream_callbacks: self.stream_callbacks.clone(),
45            port: self.port.clone(),
46            listener: self.listener.clone(),
47            dispatcher: self.dispatcher.clone(),
48            sequence: self.sequence.clone(),
49        }
50    }
51}
52
53impl<Response: 'static> State<Response> {
54    /// Post a request and return its sequence number.
55    pub fn send(&self, request: &impl Serialize, post_args: &Array, transfer_args: &Array) -> u32 {
56        let sequence = self.sequence.get();
57        self.sequence.set(sequence.wrapping_add(1));
58        crate::post_message(
59            &self.port,
60            MessageHeader::Request(sequence),
61            request,
62            post_args,
63            transfer_args,
64        );
65        sequence
66    }
67
68    /// Await the response to a request sent with [`State::send`].
69    pub fn request<T: 'static>(
70        &self,
71        sequence: u32,
72        decode: impl FnOnce(Response, Array) -> T + 'static,
73    ) -> RequestFuture<T> {
74        let (response_tx, response_rx) = oneshot::channel();
75        self.callbacks.borrow_mut().insert(sequence, response_tx);
76        let result = response_rx.map(move |received| {
77            let (response, js_values) = received.expect("web_rpc: the response channel closed");
78            decode(response, js_values)
79        });
80        let callbacks = self.callbacks.clone();
81        let port = self.port.clone();
82        RequestFuture {
83            result: future::select(result.boxed_local(), self.dispatcher.clone())
84                .map(|selected| match selected {
85                    future::Either::Left((result, _)) => result,
86                    future::Either::Right(_) => {
87                        unreachable!(
88                            "web_rpc: the dispatcher completed while a request was pending"
89                        )
90                    }
91                })
92                .boxed_local(),
93            _listener: self.listener.clone(),
94            abort: AbortOnDrop::new(move || {
95                callbacks.borrow_mut().remove(&sequence);
96                crate::post_header(&port, MessageHeader::Abort(sequence));
97            }),
98        }
99    }
100
101    /// Receive the items of a stream started with [`State::send`].
102    pub fn stream<T: 'static>(
103        &self,
104        sequence: u32,
105        mut decode: impl FnMut(Response, Array) -> T + 'static,
106    ) -> StreamReceiver<T> {
107        let (item_tx, item_rx) = mpsc::unbounded();
108        self.stream_callbacks.borrow_mut().insert(sequence, item_tx);
109        let items = item_rx.map(move |(response, js_values)| decode(response, js_values));
110        let stream_callbacks = self.stream_callbacks.clone();
111        let port = self.port.clone();
112        StreamReceiver {
113            items: Box::pin(items),
114            dispatcher: self.dispatcher.clone(),
115            _listener: self.listener.clone(),
116            abort: AbortOnDrop::new(move || {
117                stream_callbacks.borrow_mut().remove(&sequence);
118                crate::post_header(&port, MessageHeader::Abort(sequence));
119            }),
120        }
121    }
122}
123
124/// Runs `abort` when dropped, unless disarmed first.
125struct AbortOnDrop {
126    active: bool,
127    abort: Box<dyn Fn()>,
128}
129
130impl AbortOnDrop {
131    fn new(abort: impl Fn() + 'static) -> Self {
132        Self {
133            active: true,
134            abort: Box::new(abort),
135        }
136    }
137
138    fn fire(&mut self) {
139        if self.active {
140            self.active = false;
141            (self.abort)();
142        }
143    }
144
145    fn disarm(&mut self) {
146        self.active = false;
147    }
148}
149
150impl Drop for AbortOnDrop {
151    fn drop(&mut self) {
152        self.fire();
153    }
154}
155
156/// This future represents a RPC request that is currently being executed. Note that
157/// dropping this future will result in the RPC request being cancelled
158#[must_use = "Either await this future or remove the return type from the RPC method"]
159pub struct RequestFuture<T: 'static> {
160    result: LocalBoxFuture<'static, T>,
161    _listener: Rc<gloo_events::EventListener>,
162    abort: AbortOnDrop,
163}
164
165impl<T> Future for RequestFuture<T> {
166    type Output = T;
167
168    fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
169        let polled = self.result.poll_unpin(cx);
170        if polled.is_ready() {
171            self.abort.disarm();
172        }
173        polled
174    }
175}
176
177/// A stream of items from a streaming RPC method. Dropping this will send an
178/// abort to the server, cancelling the stream. Call [`close`](StreamReceiver::close)
179/// to stop the server while still draining buffered items.
180pub struct StreamReceiver<T: 'static> {
181    items: Pin<Box<dyn Stream<Item = T>>>,
182    dispatcher: Dispatcher,
183    _listener: Rc<gloo_events::EventListener>,
184    abort: AbortOnDrop,
185}
186
187impl<T> StreamReceiver<T> {
188    /// Stop the server from producing more items. Buffered items can still
189    /// be drained by continuing to poll the stream.
190    pub fn close(&mut self) {
191        self.abort.fire();
192    }
193}
194
195impl<T> Stream for StreamReceiver<T> {
196    type Item = T;
197
198    fn poll_next(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Option<Self::Item>> {
199        match self.items.as_mut().poll_next(cx) {
200            Poll::Ready(Some(item)) => Poll::Ready(Some(item)),
201            Poll::Ready(None) => {
202                self.abort.disarm();
203                Poll::Ready(None)
204            }
205            Poll::Pending => match self.dispatcher.poll_unpin(cx) {
206                Poll::Ready(_) => {
207                    unreachable!("web_rpc: the dispatcher completed while a stream was open")
208                }
209                Poll::Pending => Poll::Pending,
210            },
211        }
212    }
213}