workflow_websocket/client/
options.rs

1use super::error::Error;
2use super::result::Result;
3use cfg_if::cfg_if;
4use std::fmt::Display;
5use std::str::FromStr;
6use wasm_bindgen::convert::TryFromJsValue;
7use wasm_bindgen::prelude::*;
8use workflow_core::time::Duration;
9
10/// `ConnectionStrategy` specifies how the WebSocket `async fn connect()`
11/// function should behave during the first-time connectivity phase.
12/// @category WebSocket
13#[wasm_bindgen]
14#[derive(Default, Clone, Copy, Debug, Eq, PartialEq)]
15pub enum ConnectStrategy {
16    /// Continuously attempt to connect to the server. This behavior will
17    /// block `connect()` function until the connection is established.
18    #[default]
19    Retry,
20    /// Causes `connect()` to return immediately if the first-time connection
21    /// has failed.
22    Fallback,
23}
24
25impl FromStr for ConnectStrategy {
26    type Err = Error;
27    fn from_str(s: &str) -> Result<Self> {
28        match s {
29            "retry" => Ok(ConnectStrategy::Retry),
30            "fallback" => Ok(ConnectStrategy::Fallback),
31            _ => Err(Error::InvalidConnectStrategyArg(s.to_string())),
32        }
33    }
34}
35
36impl ConnectStrategy {
37    pub fn new(retry: bool) -> Self {
38        if retry {
39            ConnectStrategy::Retry
40        } else {
41            ConnectStrategy::Fallback
42        }
43    }
44
45    pub fn is_fallback(&self) -> bool {
46        matches!(self, ConnectStrategy::Fallback)
47    }
48}
49
50impl TryFrom<JsValue> for ConnectStrategy {
51    type Error = Error;
52    fn try_from(value: JsValue) -> Result<Self> {
53        if value.is_undefined() || value.is_null() {
54            Ok(ConnectStrategy::default())
55        } else if let Some(string) = value.as_string() {
56            Ok(string.parse()?)
57        } else {
58            Ok(ConnectStrategy::try_from_js_value(value)?)
59        }
60    }
61}
62
63///
64/// `ConnectOptions` is used to configure the `WebSocket` connectivity behavior.
65///
66/// @category WebSocket
67#[derive(Clone, Debug)]
68pub struct ConnectOptions {
69    /// Indicates if the `async fn connect()` method should return immediately
70    /// or block until the connection is established.
71    pub block_async_connect: bool,
72    /// [`ConnectStrategy`] used to configure the retry or fallback behavior.
73    pub strategy: ConnectStrategy,
74    /// Optional `url` that will change the current URL of the WebSocket.
75    /// Note that the URL overrides the use of resolver.
76    pub url: Option<String>,
77    /// Optional `timeout` that will change the timeout of the WebSocket connection process.
78    /// `Timeout` is the period after which the async connection attempt is aborted. `Timeout`
79    /// is followed by the retry delay if the [`ConnectionStrategy`] is set to `Retry`.
80    pub connect_timeout: Option<Duration>,
81    /// Retry interval denotes the time to wait before attempting to reconnect.
82    pub retry_interval: Option<Duration>,
83}
84
85pub const DEFAULT_CONNECT_TIMEOUT_MILLIS: u64 = 5_000;
86pub const DEFAULT_CONNECT_RETRY_MILLIS: u64 = 5_000;
87
88impl Default for ConnectOptions {
89    fn default() -> Self {
90        Self {
91            block_async_connect: true,
92            strategy: ConnectStrategy::Retry,
93            url: None,
94            connect_timeout: None,
95            retry_interval: None,
96        }
97    }
98}
99
100impl ConnectOptions {
101    pub fn blocking_fallback() -> Self {
102        Self {
103            block_async_connect: true,
104            strategy: ConnectStrategy::Fallback,
105            url: None,
106            connect_timeout: None,
107            retry_interval: None,
108        }
109    }
110    pub fn blocking_retry() -> Self {
111        Self {
112            block_async_connect: true,
113            strategy: ConnectStrategy::Retry,
114            url: None,
115            connect_timeout: None,
116            retry_interval: None,
117        }
118    }
119
120    pub fn non_blocking_retry() -> Self {
121        Self {
122            block_async_connect: false,
123            strategy: ConnectStrategy::Retry,
124            url: None,
125            connect_timeout: None,
126            retry_interval: None,
127        }
128    }
129
130    pub fn with_url<S: Display>(self, url: S) -> Self {
131        Self {
132            url: Some(url.to_string()),
133            ..self
134        }
135    }
136
137    pub fn with_connect_timeout(self, timeout: Duration) -> Self {
138        Self {
139            connect_timeout: Some(timeout),
140            ..self
141        }
142    }
143
144    pub fn with_retry_interval(self, interval: Duration) -> Self {
145        Self {
146            retry_interval: Some(interval),
147            ..self
148        }
149    }
150
151    pub fn connect_timeout(&self) -> Duration {
152        self.connect_timeout
153            .unwrap_or(Duration::from_millis(DEFAULT_CONNECT_TIMEOUT_MILLIS))
154    }
155
156    pub fn retry_interval(&self) -> Duration {
157        self.retry_interval
158            .unwrap_or(Duration::from_millis(DEFAULT_CONNECT_RETRY_MILLIS))
159    }
160}
161
162cfg_if! {
163    if #[cfg(feature = "wasm32-sdk")] {
164        use js_sys::Object;
165        use wasm_bindgen::JsCast;
166        use workflow_wasm::extensions::object::*;
167
168        #[wasm_bindgen(typescript_custom_section)]
169        const TS_CONNECT_OPTIONS: &'static str = r#"
170
171        /**
172         * `ConnectOptions` is used to configure the `WebSocket` connectivity behavior.
173         * 
174         * @category WebSocket
175         */
176        export interface IConnectOptions {
177            /**
178             * Indicates if the `async fn connect()` method should return immediately
179             * or wait for connection to occur or fail before returning.
180             * (default is `true`)
181             */
182            blockAsyncConnect? : boolean,
183            /**
184             * ConnectStrategy used to configure the retry or fallback behavior.
185             * In retry mode, the WebSocket will continuously attempt to connect to the server.
186             * (default is {link ConnectStrategy.Retry}).
187             */
188            strategy?: ConnectStrategy | string,
189            /** 
190             * A custom URL that will change the current URL of the WebSocket.
191             * If supplied, the URL will override the use of resolver.
192             */
193            url?: string,
194            /**
195             * A custom connection timeout in milliseconds.
196             */
197            timeoutDuration?: number,
198            /** 
199             * A custom retry interval in milliseconds.
200             */
201            retryInterval?: number,
202        }
203        "#;
204
205        #[wasm_bindgen]
206        extern "C" {
207            #[wasm_bindgen(typescript_type = "IConnectOptions | undefined")]
208            pub type IConnectOptions;
209        }
210
211        impl TryFrom<IConnectOptions> for ConnectOptions {
212            type Error = Error;
213            fn try_from(args: IConnectOptions) -> Result<Self> {
214                Self::try_from(&args)
215            }
216        }
217
218        impl TryFrom<&IConnectOptions> for ConnectOptions {
219            type Error = Error;
220            fn try_from(args: &IConnectOptions) -> Result<Self> {
221                let options = if let Some(args) = args.dyn_ref::<Object>() {
222                    let url = args.get_value("url")?.as_string();
223                    let block_async_connect = args
224                        .get_value("blockAsyncConnect")?
225                        .as_bool()
226                        .unwrap_or(true);
227                    let strategy = ConnectStrategy::try_from(args.get_value("strategy")?)?;
228                    let timeout = args
229                        .get_value("timeoutDuration")?
230                        .as_f64()
231                        .map(|f| Duration::from_millis(f as u64));
232                    let retry_interval = args
233                        .get_value("retryInterval")?
234                        .as_f64()
235                        .map(|f| Duration::from_millis(f as u64));
236
237                    ConnectOptions {
238                        block_async_connect,
239                        strategy,
240                        url,
241                        connect_timeout: timeout,
242                        retry_interval,
243                        ..Default::default()
244                    }
245                } else if let Some(retry) = args.as_bool() {
246                    ConnectOptions {
247                        block_async_connect: true,
248                        strategy: ConnectStrategy::new(retry),
249                        url: None,
250                        connect_timeout: None,
251                        retry_interval: None,
252                        ..Default::default()
253                    }
254                } else {
255                    ConnectOptions::default()
256                };
257
258                Ok(options)
259            }
260        }
261    }
262}