sms_client/
lib.rs

1//! A client library for SMS-API, via HTTP and an optional websocket connection.
2//! <https://github.com/morgverd/sms-api>
3
4#![deny(missing_docs)]
5#![deny(unsafe_code)]
6#![warn(clippy::all, clippy::pedantic)]
7
8use crate::error::*;
9
10pub mod config;
11pub mod error;
12pub mod types;
13
14#[cfg(feature = "http")]
15pub mod http;
16
17#[cfg(feature = "websocket")]
18pub mod ws;
19
20/// SMS Client with HTTP and optional WebSocket support.
21#[derive(Clone, Debug)]
22pub struct Client {
23
24    #[cfg(feature = "http")]
25    http: Option<std::sync::Arc<http::HttpClient>>,
26
27    #[cfg(feature = "websocket")]
28    ws_client: std::sync::Arc<tokio::sync::RwLock<Option<ws::WebSocketClient>>>,
29
30    #[cfg(feature = "websocket")]
31    ws_config: (Option<config::WebSocketConfig>, Option<config::TLSConfig>)
32}
33impl Client {
34
35    /// Create an SMS client with a connection config.
36    pub fn new(config: config::ClientConfig) -> ClientResult<Self> {
37        let tls = config.tls;
38
39        #[cfg(feature = "websocket-tls-rustls")]
40        let _ = rustls::crypto::CryptoProvider::install_default(
41            rustls::crypto::aws_lc_rs::default_provider()
42        );
43
44        Ok(Self {
45
46            #[cfg(feature = "http")]
47            http: config.http.map(|config| http::HttpClient::new(config, &tls).map(std::sync::Arc::new)).transpose()?,
48
49            #[cfg(feature = "websocket")]
50            ws_client: std::sync::Arc::new(tokio::sync::RwLock::new(None)),
51
52            #[cfg(feature = "websocket")]
53            ws_config: (config.websocket, tls)
54        })
55    }
56
57    /// Borrow the optional inner HTTP client.
58    #[cfg(feature = "http")]
59    pub fn http(&self) -> ClientResult<&http::HttpClient> {
60        match &self.http {
61            Some(http) => Ok(http),
62            None => Err(ClientError::ConfigError("HttpClient"))
63        }
64    }
65
66    /// Get a cloned Arc to the optional HTTP client for use in async contexts.
67    #[cfg(feature = "http")]
68    pub fn http_arc(&self) -> ClientResult<std::sync::Arc<http::HttpClient>> {
69        match &self.http {
70            Some(http) => Ok(http.clone()),
71            None => Err(ClientError::ConfigError("HttpClient"))
72        }
73    }
74
75    /// Set the callback for incoming WebSocket messages. The callback will include the WebSocket
76    /// message and an Arc to the current Client allowing for easy use within the callback!
77    /// This must be called before starting the WebSocket connection.
78    ///
79    /// # Example
80    /// ```
81    /// use sms_client::http::types::HttpOutgoingSmsMessage;
82    /// use sms_client::ws::types::WebsocketMessage;
83    /// use sms_client::Client;
84    /// use log::info;
85    ///
86    /// #[tokio::main]
87    /// async fn main() {
88    ///     let client: Client = unimplemented!("See other examples");
89    ///
90    ///     client.on_message(move |message, client| {
91    ///         match message {
92    ///             WebsocketMessage::IncomingMessage(sms) => {
93    ///                 // Can access client.http() here!
94    ///             },
95    ///             _ => { }
96    ///         }
97    ///     }).await?
98    /// }
99    /// ```
100    #[cfg(feature = "websocket")]
101    pub async fn on_message<F>(&self, callback: F) -> ClientResult<()>
102    where
103        F: Fn(ws::types::WebsocketMessage, std::sync::Arc<Self>) + Send + Sync + 'static,
104    {
105        let mut ws_guard = self.create_or_get_ws_client().await?;
106        if let Some(ws_client) = ws_guard.as_mut() {
107            let client = std::sync::Arc::new(self.clone());
108            ws_client.on_message(move |msg| {
109                callback(msg, std::sync::Arc::clone(&client));
110            });
111        }
112        Ok(())
113    }
114
115    /// Set the callback for incoming WebSocket messages (simple version without client copy).
116    /// This must be called before starting the WebSocket connection.
117    ///
118    /// # Example
119    /// ```
120    /// use sms_client::Client;
121    /// use sms_client::ws::types::WebsocketMessage;
122    /// use log::info;
123    ///
124    /// #[tokio::main]
125    /// async fn main() {
126    ///     let client: Client = unimplemented!("See other examples");
127    ///
128    ///     client.on_message_simple(move |message| {
129    ///         match message {
130    ///             WebsocketMessage::OutgoingMessage(sms) => info!("Outgoing message: {:?}", sms),
131    ///             _ => { }
132    ///         }
133    ///     }).await?
134    /// }
135    /// ```
136    #[cfg(feature = "websocket")]
137    pub async fn on_message_simple<F>(&self, callback: F) -> ClientResult<()>
138    where
139        F: Fn(ws::types::WebsocketMessage) + Send + Sync + 'static,
140    {
141        let mut ws_guard = self.create_or_get_ws_client().await?;
142        if let Some(ws_client) = ws_guard.as_mut() {
143            ws_client.on_message(callback);
144        }
145        Ok(())
146    }
147
148    /// Start the WebSocket connection.
149    #[cfg(feature = "websocket")]
150    pub async fn start_background_websocket(&self) -> ClientResult<()> {
151        let mut ws_guard = self.create_or_get_ws_client().await?;
152        if let Some(ws_client) = ws_guard.as_mut() {
153            ws_client.start_background().await?;
154        }
155        Ok(())
156    }
157
158    /// Start the WebSocket connection and block until closed.
159    #[cfg(feature = "websocket")]
160    pub async fn start_blocking_websocket(&self) -> ClientResult<()> {
161        let mut ws_guard = self.create_or_get_ws_client().await?;
162        if let Some(ws_client) = ws_guard.as_mut() {
163            ws_client.start_blocking().await?;
164        }
165        Ok(())
166    }
167
168    /// Stop the WebSocket connection.
169    #[cfg(feature = "websocket")]
170    pub async fn stop_background_websocket(&self) -> ClientResult<()> {
171        let mut ws_guard = self.ws_client.write().await;
172
173        if let Some(ws_client) = ws_guard.as_mut() {
174            ws_client.stop_background().await?;
175        }
176
177        Ok(())
178    }
179
180    /// Check if the WebSocket is currently connected.
181    #[cfg(feature = "websocket")]
182    pub async fn is_websocket_connected(&self) -> bool {
183        let ws_guard = self.ws_client.read().await;
184
185        if let Some(ws_client) = ws_guard.as_ref() {
186            ws_client.is_connected().await
187        } else {
188            false
189        }
190    }
191
192    /// Force a WebSocket reconnection.
193    #[cfg(feature = "websocket")]
194    pub async fn reconnect_websocket(&self) -> ClientResult<()> {
195        let ws_guard = self.ws_client.read().await;
196
197        if let Some(ws_client) = ws_guard.as_ref() {
198            ws_client.reconnect().await?;
199            Ok(())
200        } else {
201            Err(ClientError::NoWebsocketClient)
202        }
203    }
204
205    /// Create or return existing websocket client guard.
206    #[cfg(feature = "websocket")]
207    async fn create_or_get_ws_client(&self) -> ClientResult<tokio::sync::RwLockWriteGuard<'_, Option<ws::WebSocketClient>>> {
208        let mut ws_guard = self.ws_client.write().await;
209        if ws_guard.is_none() {
210            let (ws_config, tls_config) = match self.ws_config.clone() {
211                (Some(config), tls) => (config, tls),
212                _ => return Err(ClientError::ConfigError("WebsocketConfig"))
213            };
214
215            let ws_client = ws::WebSocketClient::new(ws_config, tls_config);
216            *ws_guard = Some(ws_client);
217        }
218
219        Ok(ws_guard)
220    }
221}
222impl Drop for Client {
223    fn drop(&mut self) {
224        // The WebSocket client will handle its own cleanup in its Drop impl
225        // This is just here to ensure proper cleanup ordering.
226    }
227}