use std::{future::Future, pin::Pin, sync::Arc, time::Duration};
use dashmap::DashMap;
use futures_util::{SinkExt, StreamExt};
use serde::Serialize;
use tokio::{
sync::{Notify, mpsc::Sender},
time::sleep,
};
use tokio_tungstenite::{connect_async, tungstenite::Message};
use tokio_util::sync::CancellationToken;
use crate::{
error::Result,
utils::send_data::{Payload, Request, Response},
};
type EventType = Arc<
DashMap<String, Arc<dyn Fn(Payload) -> Pin<Box<dyn Future<Output = ()> + Send>> + Send + Sync>>,
>;
#[derive(Clone)]
pub struct Client {
sender: Sender<Message>,
token: CancellationToken,
events: EventType,
}
impl Client {
pub async fn connect(url: &str) -> Result<Self> {
let (stream, _) = connect_async(url).await?;
let (mut write, mut read) = stream.split();
let events: EventType = Arc::new(DashMap::new());
events.insert(
"error".to_string(),
Arc::new(|payload| {
Box::pin(async move {
eprintln!("error: {}", payload.raw());
})
}),
);
let read_event = events.clone();
let (tx, mut rx) = tokio::sync::mpsc::channel::<Message>(100);
let read_tx = tx.clone();
let heartbeat = tx.clone();
let token = CancellationToken::new();
let read_token = token.clone();
let wait_token = token.clone();
let write_token = token.clone();
let heartbeat_token = token.clone();
let notify = Arc::new(Notify::new());
let heartbeat_notify = notify.clone();
let read_notify = notify.clone();
tokio::spawn(async move {
let heartbeat = heartbeat;
loop {
tokio::select! {
_ = sleep(Duration::from_secs(30)) => {}
_ = heartbeat_token.cancelled() => break
}
let _ = heartbeat.send(Message::Ping(Vec::new().into())).await;
tokio::select! {
res = tokio::time::timeout(Duration::from_secs(10), heartbeat_notify.notified()) => {
if res.is_err() {
let _ = heartbeat.send(Message::Close(None)).await;
heartbeat_token.cancel();
break
}
}
_ = heartbeat_token.cancelled() => break
}
}
});
tokio::spawn(async move {
let read_event = read_event.clone();
loop {
tokio::select! {
mass = read.next() => {
match mass {
Some(Ok(Message::Text(text))) => {
let parsed = match serde_json::from_str::<Request>(&text) {
Ok(v) => v,
Err(_) => continue
};
if let Some(handler) = read_event.get(&parsed.event) {
let payload = Payload::new(parsed.data);
handler(payload).await;
}
}
Some(Ok(Message::Close(_))) => {
read_token.cancel();
break
}
Some(Ok(Message::Ping(data))) => {
let _ = read_tx.send(Message::Pong(data)).await;
}
Some(Ok(Message::Pong(_))) => {
read_notify.notify_one();
},
Some(Ok(_)) => (),
None | Some(Err(_)) => {
read_token.cancel();
break
}
}
}
_ = read_token.cancelled() => break
}
}
if let Some(handler) = read_event.get("disconnect") {
handler(Payload::new(serde_json::Value::Null)).await;
}
});
tokio::spawn(async move {
loop {
tokio::select! {
mass = rx.recv() => {
match mass {
Some(m) => {
let is_close = matches!(m, Message::Close(_));
match write.send(m).await {
Ok(_v) => {
if is_close {
write_token.cancel();
break;
}
}
Err(_) => {
write_token.cancel();
break
}
}
}
None => {
write_token.cancel();
break
}
}
}
_ = write_token.cancelled() => break
}
}
});
Ok(Self {
sender: tx,
token: wait_token,
events,
})
}
pub fn on<F, Fut>(&self, event: &str, callback: F)
where
F: Fn(Payload) -> Fut + Send + Sync + 'static,
Fut: Future<Output = ()> + Send + 'static,
{
self.events
.insert(event.to_string(), Arc::new(move |v| Box::pin(callback(v))));
}
pub async fn emit<T: Serialize>(&self, event: &str, data: T) -> Result<()> {
let json = serde_json::to_string(&Response {
event: event.to_string(),
data,
})?;
match self.sender.send(Message::Text(json.into())).await {
Ok(_) => Ok(()),
Err(_) => Err(crate::error::RustWaveError::Internal(
"Message sending error".to_string(),
)),
}
}
pub async fn close(&self) {
let _ = self.sender.send(Message::Close(None)).await;
}
pub async fn wait(&self) {
self.token.cancelled().await;
println!("Disconnected");
}
}