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