flare_core/client/transports/
websocket.rs1use crate::client::config::ClientConfig;
6use crate::client::transports::common::{ClientConnectionHelper, ClientMessageObserver};
7use crate::client::transports::{Client, ClientCore};
8#[cfg(not(target_arch = "wasm32"))]
9use crate::common::cert::create_client_config_with_tls;
10use crate::common::error::{FlareError, Result};
11use crate::common::generate_id;
12use crate::common::platform::{sleep, timeout};
13use crate::common::protocol::Frame;
14use crate::transport::connection::Connection;
15use crate::transport::events::{ArcObserver, ConnectionEvent};
16#[cfg(not(target_arch = "wasm32"))]
17use crate::transport::websocket::WebSocketTransport;
18#[cfg(target_arch = "wasm32")]
19use crate::transport::websocket_wasm::WebSocketTransport;
20use async_trait::async_trait;
21use std::sync::Arc;
22use tokio::sync::Mutex;
23#[cfg(not(target_arch = "wasm32"))]
24use tokio_tungstenite::{Connector, connect_async_tls_with_config};
25
26pub struct WebSocketClient {
30 config: ClientConfig,
31 connection: Option<Arc<Mutex<Box<dyn Connection>>>>,
32 connection_id: String,
33 core: ClientCore,
34 reconnect_attempts: u32,
35}
36
37impl WebSocketClient {
38 pub fn new(config: ClientConfig) -> Self {
40 let connection_id = config.connection_id.clone().unwrap_or_else(generate_id);
41 let core = ClientCore::new(&config);
42
43 Self {
44 config,
45 connection: None,
46 connection_id,
47 core,
48 reconnect_attempts: 0,
49 }
50 }
51
52 pub async fn connect_with_config(config: ClientConfig) -> Result<Self> {
54 let mut client = Self::new(config);
55 client.connect().await?;
56 Ok(client)
57 }
58
59 pub fn with_core(config: ClientConfig, core: ClientCore) -> Self {
61 let connection_id = config.connection_id.clone().unwrap_or_else(generate_id);
62 Self {
63 config,
64 connection: None,
65 connection_id,
66 core,
67 reconnect_attempts: 0,
68 }
69 }
70
71 pub async fn establish_network_connection(
75 &mut self,
76 ) -> Result<Arc<Mutex<Box<dyn Connection>>>> {
77 let connection = self.establish_websocket_connection().await?;
78 let connection_arc = Arc::new(Mutex::new(connection));
79 self.connection = Some(Arc::clone(&connection_arc));
81 Ok(connection_arc)
82 }
83
84 async fn internal_connect(&mut self) -> Result<()> {
86 let connection_arc = self.establish_network_connection().await?;
88
89 self.setup_connection_with_observer(connection_arc.clone())
91 .await?;
92
93 self.core
97 .handle_connection_event(&ConnectionEvent::Connected);
98 self.reconnect_attempts = 0;
99
100 Ok(())
101 }
102
103 async fn establish_websocket_connection(&self) -> Result<Box<dyn Connection>> {
105 let url_str = self
106 .config
107 .get_protocol_url(&crate::common::config_types::TransportProtocol::WebSocket);
108
109 #[cfg(not(target_arch = "wasm32"))]
110 {
111 let ws_config = tokio_tungstenite::tungstenite::protocol::WebSocketConfig::default()
114 .max_message_size(Some(10 * 1024 * 1024))
115 .max_frame_size(Some(10 * 1024 * 1024));
116 let tls_connector = self.tls_connector(&url_str)?;
117 let ws_stream_result = timeout(
118 self.config.connect_timeout,
119 connect_async_tls_with_config(&url_str, Some(ws_config), false, tls_connector),
120 )
121 .await
122 .map_err(|_| FlareError::connection_timeout("Connection timeout".to_string()))?;
123
124 let (ws_stream, _) =
125 ws_stream_result.map_err(|e| FlareError::connection_failed(e.to_string()))?;
126
127 let transport = WebSocketTransport::new(ws_stream);
128 Ok(Box::new(transport))
129 }
130
131 #[cfg(target_arch = "wasm32")]
132 {
133 let transport = timeout(
134 self.config.connect_timeout,
135 WebSocketTransport::connect(&url_str),
136 )
137 .await
138 .map_err(|_| FlareError::connection_timeout("Connection timeout".to_string()))??;
139 Ok(Box::new(transport))
140 }
141 }
142
143 async fn setup_connection_with_observer(
145 &mut self,
146 connection: Arc<Mutex<Box<dyn Connection>>>,
147 ) -> Result<()> {
148 let core_arc = Arc::new(self.core.clone());
151 let message_observer = Arc::new(ClientMessageObserver::new(core_arc));
152
153 ClientConnectionHelper::setup_connection_and_send_connect(
155 Arc::clone(&connection),
156 &mut self.core,
157 message_observer,
158 )
159 .await?;
160
161 self.connection = Some(connection);
162 Ok(())
163 }
164
165 async fn send_frame_internal(&self, frame: &Frame) -> Result<()> {
167 ClientConnectionHelper::send_frame_internal(&self.core, self.connection.as_ref(), frame)
168 .await
169 }
170
171 async fn try_reconnect(&mut self) -> Result<()> {
173 if let Some(max) = self.config.max_reconnect_attempts
175 && self.reconnect_attempts >= max
176 {
177 return Err(FlareError::connection_failed(format!(
178 "Max reconnect attempts ({}) exceeded",
179 max
180 )));
181 }
182
183 self.core.state_manager.start_connecting();
184 self.reconnect_attempts += 1;
185
186 sleep(self.config.reconnect_interval).await;
188
189 if let Some(conn) = self.connection.take() {
191 let mut c = conn.lock().await;
192 let _ = c.close().await;
193 }
194
195 self.internal_connect().await
197 }
198
199 pub fn core(&self) -> &ClientCore {
201 &self.core
202 }
203
204 #[cfg(not(target_arch = "wasm32"))]
205 fn tls_connector(&self, url: &str) -> Result<Option<Connector>> {
206 if url.starts_with("ws://") {
207 return Ok(None);
208 }
209 if !self.config.tls.requires_custom_client_tls() {
210 return Ok(None);
211 }
212 if !url.starts_with("wss://") {
213 return Err(FlareError::connection_failed(format!(
214 "WebSocket URL must start with ws:// or wss://, got: {url}"
215 )));
216 }
217 let tls_config = create_client_config_with_tls(&self.config.tls)
218 .map_err(|error| FlareError::connection_failed(error.to_string()))?;
219 Ok(Some(Connector::Rustls(Arc::new(tls_config))))
220 }
221
222 pub async fn send_frame_and_wait(
224 &mut self,
225 frame: &Frame,
226 timeout_duration: std::time::Duration,
227 ) -> Result<Frame> {
228 if frame.message_id.is_empty() {
229 return Err(FlareError::protocol_error(
230 "message_id is empty".to_string(),
231 ));
232 }
233
234 let rx = self.core.register_pending_response(&frame.message_id).await;
235 self.send_frame(frame).await?;
236
237 match timeout(timeout_duration, rx).await {
238 Ok(Ok(resp)) => Ok(resp),
239 Ok(Err(_)) => {
240 self.core.cancel_pending_response(&frame.message_id).await;
241 Err(FlareError::protocol_error(
242 "Response channel closed".to_string(),
243 ))
244 }
245 Err(_) => {
246 self.core.cancel_pending_response(&frame.message_id).await;
247 Err(FlareError::protocol_error(format!(
248 "Response timeout for message_id {}",
249 frame.message_id
250 )))
251 }
252 }
253 }
254}
255
256#[cfg(target_arch = "wasm32")]
257unsafe impl Send for WebSocketClient {}
258#[cfg(target_arch = "wasm32")]
259unsafe impl Sync for WebSocketClient {}
260
261#[cfg(all(test, not(target_arch = "wasm32")))]
262mod tests {
263 use super::*;
264 use crate::common::config_types::TlsConfig;
265 use std::path::PathBuf;
266
267 #[test]
268 fn plain_ws_ignores_custom_client_tls() {
269 let mut config = ClientConfig::new("ws://127.0.0.1:60051/ws".to_string()).websocket();
270 config.tls = TlsConfig::default().with_ca_cert(PathBuf::from("/tmp/flare-ca.crt"));
271 let client = WebSocketClient::new(config);
272
273 let connector = client
274 .tls_connector("ws://127.0.0.1:60051/ws")
275 .expect("plain ws must not require a TLS connector");
276
277 assert!(connector.is_none());
278 }
279
280 #[test]
281 fn secure_wss_uses_custom_client_tls() {
282 let mut config = ClientConfig::new("wss://127.0.0.1:60051/ws".to_string()).websocket();
283 config.tls = TlsConfig::default()
284 .with_spki_sha256_pin("AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=");
285 let client = WebSocketClient::new(config);
286
287 let connector = client
288 .tls_connector("wss://127.0.0.1:60051/ws")
289 .expect("wss should accept custom TLS settings");
290
291 assert!(connector.is_some());
292 }
293}
294
295#[async_trait]
296impl Client for WebSocketClient {
297 async fn connect(&mut self) -> Result<()> {
298 if !self.core.can_connect() {
299 return Err(FlareError::protocol_error(
300 "Cannot connect: state is unavailable".to_string(),
301 ));
302 }
303
304 self.core.state_manager.start_connecting();
305
306 match self.internal_connect().await {
307 Ok(()) => Ok(()),
308 Err(e) => {
309 self.core.state_manager.set_failed();
310 if ClientConnectionHelper::can_reconnect(self.config.max_reconnect_attempts) {
312 self.try_reconnect().await
313 } else {
314 Err(e)
315 }
316 }
317 }
318 }
319
320 fn set_disconnect_requested(&mut self, value: bool) {
321 self.core.set_disconnect_requested(value);
322 }
323
324 async fn disconnect(&mut self) -> Result<()> {
325 ClientConnectionHelper::disconnect_internal(self.connection.take(), &mut self.core).await
326 }
327
328 async fn send_frame(&mut self, frame: &Frame) -> Result<()> {
329 if !self.is_connected()
331 && ClientConnectionHelper::can_reconnect(self.config.max_reconnect_attempts)
332 {
333 self.try_reconnect().await?;
334 }
335
336 self.send_frame_internal(frame).await
337 }
338
339 fn is_connected(&self) -> bool {
340 matches!(
341 self.core.state(),
342 crate::client::connection::ConnectionState::Connected
343 ) && self.connection.is_some()
344 && self.core.is_negotiation_completed()
345 }
346
347 fn add_observer(&mut self, observer: ArcObserver) {
348 self.core.add_observer(observer);
349 }
350
351 fn remove_observer(&mut self, observer: ArcObserver) {
352 self.core.remove_observer(observer);
353 }
354
355 fn connection_id(&self) -> Option<String> {
356 Some(self.connection_id.clone())
357 }
358}