Skip to main content

bybit/ws/
mod.rs

1mod callback;
2pub mod future;
3pub mod option;
4pub mod private;
5pub mod response;
6pub mod spot;
7
8use callback::Arg;
9use callback::Callback;
10use log::*;
11use serde::Serialize;
12use std::net::TcpStream;
13use std::sync::mpsc::Receiver;
14use std::{sync::mpsc, thread, time::Duration};
15use tungstenite::{connect, stream::MaybeTlsStream, Message, WebSocket};
16
17use crate::error::Result;
18use crate::util::millis;
19use crate::util::sign;
20use crate::FutureRole;
21
22use self::future::FutureWebSocketApiClientBuilder;
23use self::option::OptionWebSocketApiClientBuilder;
24use self::private::PrivateWebSocketApiClientBuilder;
25use self::spot::SpotWebSocketApiClientBuilder;
26
27/// A factory to create different kind of websocket api clients (spot / future / option / private).
28pub struct WebSocketApiClient;
29
30impl WebSocketApiClient {
31    /// Get a builder for building spot websocket api client.
32    pub fn spot() -> SpotWebSocketApiClientBuilder {
33        SpotWebSocketApiClientBuilder::new()
34    }
35
36    /// Get a builder for building inverse future websocket api client.
37    pub fn future_inverse() -> FutureWebSocketApiClientBuilder {
38        FutureWebSocketApiClientBuilder::new(FutureRole::Inverse)
39    }
40
41    /// Get a builder for building linear future websocket api client.
42    pub fn future_linear() -> FutureWebSocketApiClientBuilder {
43        FutureWebSocketApiClientBuilder::new(FutureRole::Linear)
44    }
45
46    /// Get a builder for building option websocket api client.
47    pub fn option() -> OptionWebSocketApiClientBuilder {
48        OptionWebSocketApiClientBuilder::new()
49    }
50
51    /// Get a builder for building private websocket api client.
52    pub fn private() -> PrivateWebSocketApiClientBuilder {
53        PrivateWebSocketApiClientBuilder::new()
54    }
55}
56
57struct Subscriber {
58    topics: Vec<String>,
59}
60
61impl Subscriber {
62    fn new() -> Self {
63        Self { topics: Vec::new() }
64    }
65
66    fn topics(&self) -> &Vec<String> {
67        &self.topics
68    }
69
70    fn sub_orderbook(&mut self, symbol: &str, depth: u16) {
71        self.sub(format!("orderbook.{depth}.{symbol}"));
72    }
73
74    fn sub_ticker(&mut self, symbol: &str) {
75        self.sub(format!("tickers.{symbol}"));
76    }
77
78    fn sub_trade(&mut self, symbol: &str) {
79        self.sub(format!("publicTrade.{symbol}"));
80    }
81
82    fn sub_kline(&mut self, symbol: &str, interval: &str) {
83        self.sub(format!("kline.{interval}.{symbol}"));
84    }
85
86    fn sub_liquidation(&mut self, symbol: &str) {
87        self.sub(format!("liquidation.{symbol}"));
88    }
89
90    fn sub_lt_kline(&mut self, symbol: &str, interval: &str) {
91        self.sub(format!("kline_lt.{interval}.{symbol}"));
92    }
93
94    fn sub_lt_ticker(&mut self, symbol: &str) {
95        self.sub(format!("tickers_lt.{symbol}"));
96    }
97
98    fn sub_lt_nav(&mut self, symbol: &str) {
99        self.sub(format!("lt.{symbol}"));
100    }
101
102    fn sub_position(&mut self) {
103        self.sub("position".to_string());
104    }
105
106    fn sub_execution(&mut self) {
107        self.sub("execution".to_string());
108    }
109
110    fn sub_order(&mut self) {
111        self.sub("order".to_string());
112    }
113
114    fn sub_wallet(&mut self) {
115        self.sub("wallet".to_string());
116    }
117
118    fn sub_greek(&mut self) {
119        self.sub("greeks".to_string());
120    }
121
122    fn sub(&mut self, topic: String) {
123        self.topics.push(topic);
124    }
125}
126
127#[derive(Serialize)]
128struct Op<'a> {
129    op: &'a str,
130    args: Vec<String>,
131}
132
133struct Credentials {
134    api_key: String,
135    secret: String,
136}
137
138fn run<A, C>(
139    uri: &str,
140    topics: &Vec<String>,
141    credentials: Option<&Credentials>,
142    mut callback: C,
143) -> Result<()>
144where
145    A: Arg,
146    C: Callback<A>,
147{
148    let (mut ws, _) = connect(uri)?;
149
150    // Set read timeout to the underlying TCP stream.
151    //
152    // Read and write are both in the main thread loop. A blocking read call
153    // will starve writing that causes ping op message can't be sent on time.
154    // Read timeout mitigate this situation.
155    set_read_timeout(&ws);
156
157    // Authenticate
158    if let Some(credentials) = credentials {
159        let req = auth_req(credentials);
160        ws.write_message(Message::Text(req))?;
161    }
162
163    // Subscribe
164    ws.write_message(Message::Text(subscription(topics)))?;
165
166    let rx = ping();
167    loop {
168        // Ping
169        if let Ok(ping) = rx.try_recv() {
170            ws.write_message(Message::Text(ping.into()))?
171        }
172
173        match ws.read_message() {
174            Ok(msg) => match msg {
175                Message::Text(content) => {
176                    debug!("Received: {}", content);
177                    match serde_json::from_str(&content) {
178                        Ok(res) => callback(res),
179                        Err(e) => error!("Error: {}", e),
180                    }
181                }
182                _ => {}
183            },
184            Err(e) => match e {
185                tungstenite::Error::Io(ref ee) => {
186                    if ee.kind() != std::io::ErrorKind::WouldBlock
187                        && ee.kind() != std::io::ErrorKind::TimedOut
188                    {
189                        Err(e)?
190                    }
191                }
192                _ => Err(e)?,
193            },
194        }
195    }
196}
197
198fn set_read_timeout(ws: &WebSocket<MaybeTlsStream<TcpStream>>) {
199    match ws.get_ref() {
200        MaybeTlsStream::Plain(s) => {
201            s.set_read_timeout(Some(Duration::from_secs(10))).unwrap();
202        }
203        MaybeTlsStream::NativeTls(t) => {
204            t.get_ref()
205                .set_read_timeout(Some(Duration::from_secs(10)))
206                .unwrap();
207        }
208        _ => unreachable!(),
209    };
210}
211
212fn auth_req(credentials: &Credentials) -> String {
213    let expires = millis() + 10000;
214    let val = format!("GET/realtime{}", expires);
215    let signature = sign(&credentials.secret, &val);
216    let auth_req = Op {
217        op: "auth",
218        args: vec![credentials.api_key.clone(), expires.to_string(), signature],
219    };
220    serde_json::to_string(&auth_req).unwrap()
221}
222
223fn subscription(topics: &Vec<String>) -> String {
224    let sub = Op {
225        op: "subscribe",
226        args: topics.clone(),
227    };
228    serde_json::to_string(&sub).unwrap()
229}
230
231fn ping() -> Receiver<&'static str> {
232    let (tx, rx) = mpsc::channel();
233    thread::spawn(move || loop {
234        if let Err(_) = tx.send("{\"op\":\"ping\"}") {
235            break;
236        };
237        thread::sleep(Duration::from_secs(20));
238    });
239    rx
240}