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 http;
11pub mod config;
12pub mod error;
13pub mod types;
14
15#[cfg(feature = "websocket")]
16pub mod ws;
17
18/// SMS Client with HTTP and optional WebSocket support.
19#[derive(Clone, Debug)]
20pub struct Client {
21    http: std::sync::Arc<http::HttpClient>,
22
23    #[cfg(feature = "websocket")]
24    ws_client: std::sync::Arc<tokio::sync::RwLock<Option<ws::WebsocketClient>>>,
25
26    #[cfg(feature = "websocket")]
27    ws_config: Option<config::WebsocketConfig>
28}
29impl Client {
30
31    /// Create an SMS client with a connection config.
32    pub fn new(config: config::ClientConfig) -> ClientResult<Self> {
33        let http = http::HttpClient::new(config.http)?;
34        Ok(Self {
35            http: std::sync::Arc::new(http),
36
37            #[cfg(feature = "websocket")]
38            ws_client: std::sync::Arc::new(tokio::sync::RwLock::new(None)),
39
40            #[cfg(feature = "websocket")]
41            ws_config: config.websocket
42        })
43    }
44
45    /// Borrow the inner HTTP client.
46    pub fn http(&self) -> &http::HttpClient {
47        &self.http
48    }
49
50    /// Get a cloned Arc to the HTTP client for use in async contexts.
51    pub fn http_arc(&self) -> std::sync::Arc<http::HttpClient> {
52        std::sync::Arc::clone(&self.http)
53    }
54
55    /// Set the callback for incoming WebSocket messages.
56    /// This must be called before starting the WebSocket connection.
57    #[cfg(feature = "websocket")]
58    pub async fn on_message<F>(&self, callback: F) -> ClientResult<()>
59    where
60        F: Fn(ws::types::WebsocketMessage, std::sync::Arc<Self>) + Send + Sync + 'static,
61    {
62        let mut ws_guard = self.create_or_get_ws_client().await?;
63        if let Some(ws_client) = ws_guard.as_mut() {
64            let client = std::sync::Arc::new(self.clone());
65            ws_client.on_message(move |msg| {
66                callback(msg, std::sync::Arc::clone(&client));
67            });
68        }
69        Ok(())
70    }
71
72    /// Set the callback for incoming WebSocket messages (simple version without client copy).
73    /// This must be called before starting the WebSocket connection.
74    #[cfg(feature = "websocket")]
75    pub async fn on_message_simple<F>(&self, callback: F) -> ClientResult<()>
76    where
77        F: Fn(ws::types::WebsocketMessage) + Send + Sync + 'static,
78    {
79        let mut ws_guard = self.create_or_get_ws_client().await?;
80        if let Some(ws_client) = ws_guard.as_mut() {
81            ws_client.on_message(callback);
82        }
83        Ok(())
84    }
85
86    /// Start the WebSocket connection.
87    #[cfg(feature = "websocket")]
88    pub async fn start_background_websocket(&self) -> ClientResult<()> {
89        let mut ws_guard = self.create_or_get_ws_client().await?;
90        if let Some(ws_client) = ws_guard.as_mut() {
91            ws_client.start_background().await?;
92        }
93        Ok(())
94    }
95
96    /// Start the WebSocket connection and block until closed.
97    #[cfg(feature = "websocket")]
98    pub async fn start_blocking_websocket(&self) -> ClientResult<()> {
99        let mut ws_guard = self.create_or_get_ws_client().await?;
100        if let Some(ws_client) = ws_guard.as_mut() {
101            ws_client.start_blocking().await?;
102        }
103        Ok(())
104    }
105
106    /// Stop the WebSocket connection.
107    #[cfg(feature = "websocket")]
108    pub async fn stop_background_websocket(&self) -> ClientResult<()> {
109        let mut ws_guard = self.ws_client.write().await;
110
111        if let Some(ws_client) = ws_guard.as_mut() {
112            ws_client.stop_background().await?;
113        }
114
115        Ok(())
116    }
117
118    /// Check if the WebSocket is currently connected.
119    #[cfg(feature = "websocket")]
120    pub async fn is_websocket_connected(&self) -> bool {
121        let ws_guard = self.ws_client.read().await;
122
123        if let Some(ws_client) = ws_guard.as_ref() {
124            ws_client.is_connected().await
125        } else {
126            false
127        }
128    }
129
130    /// Force a WebSocket reconnection.
131    #[cfg(feature = "websocket")]
132    pub async fn reconnect_websocket(&self) -> ClientResult<()> {
133        let ws_guard = self.ws_client.read().await;
134
135        if let Some(ws_client) = ws_guard.as_ref() {
136            ws_client.reconnect().await?;
137            Ok(())
138        } else {
139            Err(ClientError::NoWebsocketClient)
140        }
141    }
142
143    /// Create or return existing websocket client guard.
144    #[cfg(feature = "websocket")]
145    async fn create_or_get_ws_client(&self) -> ClientResult<tokio::sync::RwLockWriteGuard<'_, Option<ws::WebsocketClient>>> {
146        let mut ws_guard = self.ws_client.write().await;
147        if ws_guard.is_none() {
148            let config = match self.ws_config.clone() {
149                Some(config) => config,
150                None => return Err(ClientError::MissingConfiguration("WebsocketConfig"))
151            };
152
153            let ws_client = ws::WebsocketClient::new(config);
154            *ws_guard = Some(ws_client);
155        }
156
157        Ok(ws_guard)
158    }
159}
160impl Drop for Client {
161    fn drop(&mut self) {
162        // The WebSocket client will handle its own cleanup in its Drop impl
163        // This is just here to ensure proper cleanup ordering.
164    }
165}