use dashmap::DashSet;
use futures_util::{Stream, StreamExt};
use std::pin::Pin;
use std::sync::Arc;
use std::task::{Context, Poll};
use tokio::sync::mpsc;
use tokio::sync::Mutex;
use tokio::task::JoinHandle;
use ws_reconnect_client::{MessageStream, WebSocketClient, WebSocketClientBuilder, send_subscription, WsWriter};
use crate::{Result, websocket::{MarketSubscriptionRequest, WebSocketMessage}};
use super::common::spawn_forwarding_task_with_reconnect;
async fn reconnect_market_stream(
client: &WebSocketClient<WebSocketMessage>,
subscribed_tokens: &DashSet<String>,
writer_arc: &Arc<Mutex<Option<WsWriter>>>,
) -> Result<MessageStream<WebSocketMessage>> {
let (mut writer, reader) = client.connect().await?;
let current_tokens: Vec<String> = subscribed_tokens.iter().map(|e| e.clone()).collect();
if !current_tokens.is_empty() {
let subscription = MarketSubscriptionRequest::new(current_tokens);
send_subscription(&mut writer, &subscription).await?;
}
*writer_arc.lock().await = Some(writer);
Ok(MessageStream::new(reader, writer_arc.clone(), 5))
}
pub struct ChannelStream {
pub(crate) rx: mpsc::UnboundedReceiver<Result<WebSocketMessage>>,
}
impl Stream for ChannelStream {
type Item = Result<WebSocketMessage>;
fn poll_next(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Option<Self::Item>> {
self.rx.poll_recv(cx)
}
}
pub struct MarketWebSocket {
client: WebSocketClient<WebSocketMessage>,
subscribed_tokens: DashSet<String>,
stream_tx: Arc<Mutex<Option<mpsc::UnboundedSender<Result<WebSocketMessage>>>>>,
writer: Arc<Mutex<Option<ws_reconnect_client::WsWriter>>>,
task_handle: Arc<Mutex<Option<JoinHandle<()>>>>,
}
impl MarketWebSocket {
pub fn new(websocket_url: impl Into<String>) -> Self {
let url = format!("{}/market", websocket_url.into());
let client = WebSocketClientBuilder::new(url)
.ping_interval(5)
.build();
Self {
client,
subscribed_tokens: DashSet::new(),
stream_tx: Arc::new(Mutex::new(None)),
writer: Arc::new(Mutex::new(None)),
task_handle: Arc::new(Mutex::new(None)),
}
}
pub fn with_client(client: WebSocketClient<WebSocketMessage>) -> Self {
Self {
client,
subscribed_tokens: DashSet::new(),
stream_tx: Arc::new(Mutex::new(None)),
writer: Arc::new(Mutex::new(None)),
task_handle: Arc::new(Mutex::new(None)),
}
}
pub async fn connect(&self) -> Result<ChannelStream> {
let (tx, rx) = mpsc::unbounded_channel();
let (mut writer, reader) = self.client.connect().await?;
let current_tokens = self.get_subscribed_tokens();
if !current_tokens.is_empty() {
let subscription = MarketSubscriptionRequest::new(current_tokens);
send_subscription(&mut writer, &subscription).await?;
}
*self.writer.lock().await = Some(writer);
let message_stream = MessageStream::new(reader, self.writer.clone(), 5);
if let Some(old_handle) = self.task_handle.lock().await.take() {
old_handle.abort();
}
let client_clone = self.client.clone();
let subscribed_tokens_clone = self.subscribed_tokens.clone();
let writer_arc = self.writer.clone();
let handle = spawn_forwarding_task_with_reconnect(
message_stream,
tx.clone(),
"Market",
move || {
let client = client_clone.clone();
let tokens = subscribed_tokens_clone.clone();
let writer = writer_arc.clone();
async move { reconnect_market_stream(&client, &tokens, &writer).await }
},
);
*self.task_handle.lock().await = Some(handle);
*self.stream_tx.lock().await = Some(tx);
Ok(ChannelStream { rx })
}
pub async fn add_token(&self, token_id: String) -> Result<()> {
self.add_tokens(vec![token_id]).await
}
pub async fn add_tokens(&self, token_ids: Vec<String>) -> Result<()> {
for token_id in token_ids {
self.subscribed_tokens.insert(token_id);
}
if self.stream_tx.lock().await.is_some() {
self.reconnect().await?;
}
Ok(())
}
async fn reconnect(&self) -> Result<()> {
let tx = self.stream_tx.lock().await.as_ref()
.ok_or_else(|| crate::PolymarketError::InvalidConfig("Cannot reconnect: not connected".to_string()))?
.clone();
let (mut writer, reader) = self.client.connect().await?;
let current_tokens = self.get_subscribed_tokens();
if !current_tokens.is_empty() {
let subscription = MarketSubscriptionRequest::new(current_tokens);
send_subscription(&mut writer, &subscription).await?;
}
*self.writer.lock().await = Some(writer);
let mut message_stream = MessageStream::new(reader, self.writer.clone(), 5);
if let Some(old_handle) = self.task_handle.lock().await.take() {
old_handle.abort();
}
let tx_clone = tx.clone();
let handle = tokio::spawn(async move {
while let Some(msg_result) = message_stream.next().await {
let sdk_result = msg_result.map_err(|e| e.into());
if tx_clone.send(sdk_result).is_err() {
break;
}
}
});
*self.task_handle.lock().await = Some(handle);
Ok(())
}
pub async fn remove_token(&self, token_id: String) -> Result<()> {
self.remove_tokens(vec![token_id]).await
}
pub async fn remove_tokens(&self, token_ids: Vec<String>) -> Result<()> {
for token_id in &token_ids {
self.subscribed_tokens.remove(token_id);
}
if self.stream_tx.lock().await.is_some() {
self.reconnect().await?;
}
Ok(())
}
pub fn get_subscribed_tokens(&self) -> Vec<String> {
self.subscribed_tokens
.iter()
.map(|entry| entry.clone())
.collect()
}
pub async fn listen<F>(&self, token_ids: Vec<String>, mut handler: F) -> Result<()>
where
F: FnMut(WebSocketMessage) -> Result<()>,
{
for token_id in token_ids {
self.subscribed_tokens.insert(token_id);
}
let subscription = MarketSubscriptionRequest::new(self.get_subscribed_tokens());
self.client
.listen(Some(subscription), |msg| {
handler(msg).map_err(|e| ws_reconnect_client::WebSocketError::HandlerError(e.to_string()))
})
.await?;
Ok(())
}
}