1use futures_util::stream::{SplitSink, SplitStream};
2use futures_util::{SinkExt, StreamExt};
3use std::sync::{Arc, OnceLock};
4use std::time::Duration;
5use tokio::net::TcpStream;
6
7use tokio_tungstenite::{
8 MaybeTlsStream, WebSocketStream, connect_async_with_config,
9 tungstenite::{
10 Bytes, Message, Utf8Bytes,
11 protocol::{CloseFrame, WebSocketConfig},
12 },
13};
14
15use crate::ws::error::{UnreadableFrame, WsClientError, WsResult, WsTransportConfigError};
16use crate::ws::types::{
17 AcceptQuoteMessage, AddChannelsData, AddMintsData, AuthChallengeData, BatchQuotesMessage,
18 CancelAllQuotesMessage, CancelQuoteData, CancelRfqData, ClaimReferralCodeData, ClientMessage,
19 CloseCode, GetActiveRfqsMessage, GetExpiriesMessage, GetIndicativePricesMessage,
20 GetMakerPositionsMessage, GetMarketDescriptorsMessage, GetMarketsForMakerMessage,
21 GetMarketsMessage, GetMmSummaryMessage, GetMyActiveRfqsMessage, GetMyQuotesMessage,
22 GetMyReferralInfoData, GetOrderStatusMessage, GetPositionsMessage, GetSubscriptionsMessage,
23 GetTokensMessage, HelloData, IndicativePricesResponseMessage, Lane, QuoteMessage,
24 RedeemInviteData, RemoveChannelsData, RemoveMintsData, ReplaceQuoteMessage, ResumeAuthData,
25 RfqRequestMessage, ServerMessage, StartAuthData, SubmitSignedSponsoredTxData, SubscribeData,
26 UnsubscribeData, WsChannel, parse_server_message,
27};
28use uuid::Uuid;
29
30type Socket = WebSocketStream<MaybeTlsStream<TcpStream>>;
31pub(crate) type WsSink = SplitSink<Socket, Message>;
32
33macro_rules! ws_method {
34 ($name:ident, $variant:ident, $data:ty) => {
35 pub async fn $name(&mut self, data: $data) -> WsResult<()> {
36 self.send(&ClientMessage::$variant(data)).await
37 }
38 };
39}
40
41macro_rules! ws_method_request_id {
42 ($name:ident, $variant:ident, $msg:ident) => {
43 pub async fn $name(&mut self) -> WsResult<()> {
44 self.send(&ClientMessage::$variant($msg {
45 request_id: Uuid::new_v4(),
46 limit: 200,
47 cursor: crate::ws::types::Cursor::Start {},
48 }))
49 .await
50 }
51 };
52}
53
54pub struct WsClient {
62 stream: Socket,
63 server_close: Arc<OnceLock<ServerClose>>,
64}
65
66pub(crate) struct WsReader {
69 stream: SplitStream<Socket>,
70 server_close: Arc<OnceLock<ServerClose>>,
71}
72
73#[derive(Debug, Clone, PartialEq, Eq)]
75pub struct ServerClose {
76 pub code: u16,
77 pub reason: String,
78}
79
80impl ServerClose {
81 #[must_use]
83 pub const fn close_code(&self) -> Option<CloseCode> {
84 CloseCode::from_code(self.code)
85 }
86}
87
88fn record_close(slot: &OnceLock<ServerClose>, frame: Option<CloseFrame>) {
89 if let Some(frame) = frame {
90 let _ = slot.set(ServerClose {
91 code: u16::from(frame.code),
92 reason: frame.reason.to_string(),
93 });
94 }
95}
96
97#[allow(clippy::large_enum_variant)]
98pub(crate) enum WsFrame {
99 Message(ServerMessage),
100 Ping(Bytes),
101 Pong,
102}
103
104#[derive(Debug, Clone)]
105pub struct PreparedClientMessage {
106 text: Utf8Bytes,
107 lane: Lane,
108 subscription: Option<Box<crate::ws::types::SubscriptionChange>>,
109}
110
111impl PreparedClientMessage {
112 pub fn new(message: &ClientMessage) -> Result<Self, serde_json::Error> {
113 Ok(Self {
114 text: serde_json::to_string(message)?.into(),
115 lane: message.lane(),
116 subscription: crate::ws::types::SubscriptionChange::from_message(message).map(Box::new),
117 })
118 }
119
120 pub(crate) fn take_subscription(
121 &mut self,
122 ) -> Option<Box<crate::ws::types::SubscriptionChange>> {
123 self.subscription.take()
124 }
125
126 pub(crate) const fn lane(&self) -> Lane {
127 self.lane
128 }
129
130 pub(crate) fn into_text(self) -> Utf8Bytes {
131 self.text
132 }
133
134 pub fn as_str(&self) -> &str {
135 &self.text
136 }
137
138 #[must_use]
139 pub fn len(&self) -> usize {
140 self.text.len()
141 }
142
143 #[must_use]
144 pub fn is_empty(&self) -> bool {
145 self.text.is_empty()
146 }
147}
148
149#[derive(Debug, Clone, Copy)]
150pub struct WsTransportConfig {
151 pub connect_timeout: Duration,
152 pub read_buffer_size: usize,
153 pub write_buffer_size: usize,
154 pub max_write_buffer_size: usize,
155 pub max_message_size: usize,
156 pub max_frame_size: usize,
157 pub tcp_nodelay: bool,
158}
159
160impl Default for WsTransportConfig {
161 fn default() -> Self {
162 Self {
163 connect_timeout: Duration::from_secs(10),
164 read_buffer_size: 64 * 1024,
165 write_buffer_size: 0,
166 max_write_buffer_size: 1024 * 1024,
167 max_message_size: 2 * 1024 * 1024,
168 max_frame_size: 2 * 1024 * 1024,
169 tcp_nodelay: true,
170 }
171 }
172}
173
174impl WsTransportConfig {
175 pub fn validate(self) -> Result<(), WsTransportConfigError> {
180 if self.connect_timeout.is_zero() {
181 return Err(WsTransportConfigError::ZeroConnectTimeout);
182 }
183 if self.max_write_buffer_size <= self.write_buffer_size {
184 return Err(WsTransportConfigError::InvalidWriteBufferRange);
185 }
186 if self.max_message_size == 0 || self.max_frame_size == 0 {
187 return Err(WsTransportConfigError::ZeroMessageOrFrameLimit);
188 }
189 Ok(())
190 }
191}
192
193impl WsClient {
194 pub async fn connect(url: &str) -> WsResult<Self> {
195 Self::connect_with_config(url, WsTransportConfig::default()).await
196 }
197
198 pub async fn connect_with_config(url: &str, config: WsTransportConfig) -> WsResult<Self> {
199 config.validate()?;
200 let websocket_config = WebSocketConfig::default()
201 .read_buffer_size(config.read_buffer_size)
202 .write_buffer_size(config.write_buffer_size)
203 .max_write_buffer_size(config.max_write_buffer_size)
204 .max_message_size(Some(config.max_message_size))
205 .max_frame_size(Some(config.max_frame_size));
206 let connect = connect_async_with_config(url, Some(websocket_config), config.tcp_nodelay);
207 let (stream, _response) = tokio::time::timeout(config.connect_timeout, connect)
208 .await
209 .map_err(|_| WsClientError::ConnectTimeout)??;
210 Ok(Self {
211 stream,
212 server_close: Arc::default(),
213 })
214 }
215
216 pub async fn send(&mut self, msg: &ClientMessage) -> WsResult<()> {
217 let prepared = PreparedClientMessage::new(msg)?;
218 self.send_prepared(&prepared).await?;
219 Ok(())
220 }
221
222 pub async fn send_prepared(&mut self, message: &PreparedClientMessage) -> WsResult<()> {
223 self.stream
224 .send(Message::Text(message.text.clone()))
225 .await?;
226 Ok(())
227 }
228
229 pub async fn send_prepared_owned(&mut self, message: PreparedClientMessage) -> WsResult<()> {
231 self.stream.send(Message::Text(message.text)).await?;
232 Ok(())
233 }
234
235 ws_method!(send_hello, Hello, HelloData);
236 ws_method!(resume_auth, ResumeAuth, ResumeAuthData);
237 ws_method!(auth_challenge, AuthChallenge, AuthChallengeData);
238 ws_method!(quote, Quote, QuoteMessage);
239 ws_method!(replace_quote, ReplaceQuote, ReplaceQuoteMessage);
240 ws_method!(batch_quotes, BatchQuotes, BatchQuotesMessage);
241 ws_method!(subscribe, Subscribe, SubscribeData);
242 ws_method!(unsubscribe, Unsubscribe, UnsubscribeData);
243 ws_method!(get_my_quotes, GetMyQuotes, GetMyQuotesMessage);
244 ws_method!(
245 get_maker_positions,
246 GetMakerPositions,
247 GetMakerPositionsMessage
248 );
249 ws_method!(
250 get_markets_for_maker,
251 GetMarketsForMaker,
252 GetMarketsForMakerMessage
253 );
254 ws_method!(rfq_request, RfqRequest, RfqRequestMessage);
255 ws_method!(accept_quote, AcceptQuote, AcceptQuoteMessage);
256 ws_method!(
257 submit_signed_sponsored_tx,
258 SubmitSignedSponsoredTx,
259 SubmitSignedSponsoredTxData
260 );
261 ws_method!(
262 indicative_prices_response,
263 IndicativePricesResponse,
264 IndicativePricesResponseMessage
265 );
266 ws_method!(
267 get_indicative_prices,
268 GetIndicativePrices,
269 GetIndicativePricesMessage
270 );
271 ws_method!(
272 get_market_descriptors,
273 GetMarketDescriptors,
274 GetMarketDescriptorsMessage
275 );
276 ws_method!(get_tokens, GetTokens, GetTokensMessage);
277 ws_method!(get_expiries, GetExpiries, GetExpiriesMessage);
278 ws_method!(get_order_status, GetOrderStatus, GetOrderStatusMessage);
279 ws_method!(redeem_invite, RedeemInvite, RedeemInviteData);
280 ws_method!(
281 claim_referral_code,
282 ClaimReferralCode,
283 ClaimReferralCodeData
284 );
285 ws_method!(
286 get_my_referral_info,
287 GetMyReferralInfo,
288 GetMyReferralInfoData
289 );
290
291 ws_method!(get_mm_summary, GetMmSummary, GetMmSummaryMessage);
292 ws_method!(
293 get_position_summary,
294 GetPositionSummary,
295 crate::ws::types::GetPositionSummaryMessage
296 );
297 ws_method_request_id!(get_subscriptions, GetSubscriptions, GetSubscriptionsMessage);
298 ws_method_request_id!(get_markets, GetMarkets, GetMarketsMessage);
299
300 ws_method!(get_positions, GetPositions, GetPositionsMessage);
301 ws_method!(get_my_active_rfqs, GetMyActiveRfqs, GetMyActiveRfqsMessage);
302 ws_method!(get_active_rfqs, GetActiveRfqs, GetActiveRfqsMessage);
303
304 pub async fn ping(&mut self) -> WsResult<()> {
305 self.send(&ClientMessage::Ping).await
306 }
307
308 pub async fn logout(&mut self) -> WsResult<()> {
309 self.send(&ClientMessage::Logout).await
310 }
311
312 pub async fn start_auth(&mut self, pubkey: crate::Address32) -> WsResult<()> {
313 self.send(&ClientMessage::StartAuth(StartAuthData { pubkey }))
314 .await
315 }
316
317 pub async fn cancel_quote(&mut self, rfq_id: Uuid) -> WsResult<()> {
318 self.send(&ClientMessage::CancelQuote(CancelQuoteData {
319 rfq_id,
320 request_id: Uuid::new_v4(),
321 }))
322 .await
323 }
324
325 pub async fn cancel_all_quotes(
326 &mut self,
327 market: crate::ws::types::QueryFilter<crate::Address32>,
328 ) -> WsResult<()> {
329 self.send(&ClientMessage::CancelAllQuotes(CancelAllQuotesMessage {
330 request_id: Uuid::new_v4(),
331 market,
332 }))
333 .await
334 }
335
336 pub async fn cancel_rfq(&mut self, rfq_id: Uuid) -> WsResult<()> {
337 self.send(&ClientMessage::CancelRfq(CancelRfqData {
338 rfq_id,
339 request_id: Uuid::new_v4(),
340 }))
341 .await
342 }
343
344 pub async fn add_mints(
345 &mut self,
346 underlying_mints: Vec<crate::Address32>,
347 quote_mints: Vec<crate::Address32>,
348 ) -> WsResult<()> {
349 self.send(&ClientMessage::AddMints(AddMintsData {
350 request_id: Uuid::new_v4(),
351 underlying_mints,
352 quote_mints,
353 }))
354 .await
355 }
356
357 pub async fn remove_mints(
358 &mut self,
359 underlying_mints: Vec<crate::Address32>,
360 quote_mints: Vec<crate::Address32>,
361 ) -> WsResult<()> {
362 self.send(&ClientMessage::RemoveMints(RemoveMintsData {
363 request_id: Uuid::new_v4(),
364 underlying_mints,
365 quote_mints,
366 }))
367 .await
368 }
369
370 pub async fn add_channels(&mut self, channels: Vec<WsChannel>) -> WsResult<()> {
371 self.send(&ClientMessage::AddChannels(AddChannelsData {
372 request_id: Uuid::new_v4(),
373 channels,
374 }))
375 .await
376 }
377
378 pub async fn remove_channels(&mut self, channels: Vec<WsChannel>) -> WsResult<()> {
379 self.send(&ClientMessage::RemoveChannels(RemoveChannelsData {
380 request_id: Uuid::new_v4(),
381 channels,
382 }))
383 .await
384 }
385
386 pub async fn send_text(&mut self, text: impl Into<String>) -> WsResult<()> {
387 self.stream.send(Message::Text(text.into().into())).await?;
388 Ok(())
389 }
390
391 #[must_use]
393 pub fn server_close(&self) -> Option<&ServerClose> {
394 self.server_close.get()
395 }
396
397 pub(crate) fn server_close_slot(&self) -> Arc<OnceLock<ServerClose>> {
398 Arc::clone(&self.server_close)
399 }
400
401 pub(crate) async fn await_server_close(&mut self, grace: Duration) {
403 let _ = tokio::time::timeout(grace, async {
404 while let Some(Ok(_)) = self.next().await {}
405 })
406 .await;
407 }
408
409 pub async fn next(&mut self) -> Option<Result<ServerMessage, WsClientError>> {
410 loop {
411 let msg = match self.stream.next().await {
412 Some(Ok(msg)) => msg,
413 Some(Err(err)) => return Some(Err(err.into())),
414 None => return None,
415 };
416
417 match msg {
418 Message::Text(text) => {
419 return Some(parse_frame(text.as_bytes()));
420 }
421 Message::Binary(bin) => {
422 return Some(parse_frame(&bin));
423 }
424 Message::Ping(payload) => {
425 if let Err(err) = self.stream.send(Message::Pong(payload)).await {
426 return Some(Err(err.into()));
427 }
428 }
429 Message::Pong(_) => {}
430 Message::Close(frame) => {
431 record_close(&self.server_close, frame);
432 return None;
433 }
434 Message::Frame(_) => {}
435 }
436 }
437 }
438
439 pub async fn close(mut self) -> WsResult<()> {
440 self.stream.close(None).await?;
441 Ok(())
442 }
443
444 pub(crate) fn split(self) -> (WsSink, WsReader) {
445 let (sink, stream) = self.stream.split();
446 (
447 sink,
448 WsReader {
449 stream,
450 server_close: self.server_close,
451 },
452 )
453 }
454}
455
456impl WsReader {
457 pub(crate) async fn await_server_close(&mut self, grace: Duration) {
459 let _ = tokio::time::timeout(grace, async {
460 while let Some(Ok(_)) = self.next().await {}
461 })
462 .await;
463 }
464
465 pub(crate) async fn next(&mut self) -> Option<WsResult<WsFrame>> {
466 loop {
467 let msg = match self.stream.next().await {
468 Some(Ok(msg)) => msg,
469 Some(Err(err)) => return Some(Err(err.into())),
470 None => return None,
471 };
472 let frame = match msg {
473 Message::Text(text) => parse_frame(text.as_bytes()).map(WsFrame::Message),
474 Message::Binary(bin) => parse_frame(&bin).map(WsFrame::Message),
475 Message::Ping(payload) => Ok(WsFrame::Ping(payload)),
476 Message::Pong(_) => Ok(WsFrame::Pong),
477 Message::Close(frame) => {
478 record_close(&self.server_close, frame);
479 return None;
480 }
481 Message::Frame(_) => continue,
482 };
483 return Some(frame);
484 }
485 }
486}
487
488fn parse_frame(raw: &[u8]) -> WsResult<ServerMessage> {
489 let parsed = match std::str::from_utf8(raw) {
490 Ok(text) => parse_server_message(text),
491 Err(_) => serde_json::from_slice::<ServerMessage>(raw),
492 };
493 parsed.map_err(|error| {
494 WsClientError::UnreadableFrame(Box::new(UnreadableFrame::from_raw(raw, &error)))
495 })
496}