reconnecting_websocket/
builder.rs

1use std::{fmt::Debug, marker::PhantomData, time::Duration};
2
3use exponential_backoff::Backoff;
4use gloo::net::websocket::{futures::WebSocket, Message};
5
6use crate::{
7    constants::DEFAULT_STABLE_CONNECTION_TIMEOUT, info, Error, Socket, SocketInput, SocketOutput,
8    DEFAULT_BACKOFF_MAX, DEFAULT_BACKOFF_MIN, DEFAULT_MAX_RETRIES,
9};
10
11/// Builder for [`Socket`]
12/// Uses the DEFAULT_* consts for backoff and retry config
13#[derive(Debug)]
14pub struct SocketBuilder<I, O> {
15    url: String,
16    backoff_min: Duration,
17    backoff_max: Option<Duration>,
18    max_retries: u32,
19    stable_timeout: Duration,
20    _phantom: PhantomData<(I, O)>,
21}
22
23impl<I, O> SocketBuilder<I, O>
24where
25    I: SocketInput,
26    O: SocketOutput,
27    Message: TryFrom<I>,
28    <Message as TryFrom<I>>::Error: Debug,
29    <O as TryFrom<Message>>::Error: Debug,
30{
31    /// Create a new builder from the given url with other config set to defaults
32    pub fn new(url: String) -> Self {
33        Self {
34            url,
35            backoff_min: DEFAULT_BACKOFF_MIN,
36            backoff_max: DEFAULT_BACKOFF_MAX,
37            max_retries: DEFAULT_MAX_RETRIES,
38            stable_timeout: DEFAULT_STABLE_CONNECTION_TIMEOUT,
39            _phantom: PhantomData,
40        }
41    }
42
43    /// Update the builder url
44    pub fn set_url(mut self, url: String) -> Self {
45        self.url = url;
46        self
47    }
48
49    /// Update the minimum backoff duration (must be > 0 millis)
50    pub fn set_backoff_min(mut self, backoff_min: Duration) -> Self {
51        self.backoff_min = backoff_min;
52        self
53    }
54
55    /// Update the maximum backoff duration (if set must be < u32::MAX millis)
56    pub fn set_backoff_max(mut self, backoff_max: Option<Duration>) -> Self {
57        self.backoff_max = backoff_max;
58        self
59    }
60
61    /// Update the maximum number of retry attempts
62    pub fn set_max_retries(mut self, max_retries: u32) -> Self {
63        self.max_retries = max_retries;
64        self
65    }
66
67    /// Update the stable timeout. Must be <= u32::MAX millis
68    ///
69    /// This determines how long a connection needs to stay open after a retry before it
70    /// is considered stable and the retry counter is reset to 0
71    pub fn set_stable_timeout(mut self, stable_timeout: Duration) -> Self {
72        self.stable_timeout = stable_timeout;
73        self
74    }
75
76    /// Attempts to create a reconnecting websocket and do the initial open
77    /// It's set up to error at this poing because the kind of errors that can occur here are likely
78    /// fatal (See [`gloo::net::websocket::futures::WebSocket::open`] for details). These could
79    /// be panics but the consumer may want to display the error to the user or fallback to
80    /// plain http
81    pub fn open(self) -> Result<Socket<I, O>, Error<I, O>> {
82        let SocketBuilder { url, backoff_min, backoff_max, max_retries, stable_timeout, .. } = self;
83
84        if backoff_min == Duration::ZERO {
85            return Err(Error::InvalidConfig("backoff_min must be > 0".to_string()));
86        }
87
88        if let Some(max) = backoff_max.as_ref() {
89            if max.as_millis() > (u32::MAX as u128) {
90                return Err(Error::InvalidConfig(
91                    "backoff_max must be <= u32::MAX millis".to_string(),
92                ));
93            }
94        }
95
96        if max_retries == 0 {
97            return Err(Error::InvalidConfig("backoff_retries must be > 0".to_string()));
98        }
99
100        if stable_timeout.as_millis() > (u32::MAX as u128) {
101            return Err(Error::InvalidConfig(
102                "stable_timeout must be <= u32::MAX millis".to_string(),
103            ));
104        }
105        let stable_timeout_millis = stable_timeout.as_millis() as u32;
106
107        info!("Opening reconnecting websocket to {url}");
108        let socket = WebSocket::open(&url)?;
109
110        let backoff = Backoff::new(max_retries, backoff_min, backoff_max);
111
112        Ok(Socket {
113            url,
114            socket: Some(socket),
115            backoff,
116            max_retries,
117            stable_timeout_millis,
118            ..Default::default()
119        })
120    }
121}