#[cfg(feature = "live")]
use futures::{stream::SplitStream, FutureExt, Stream, StreamExt};
use reqwest::Client;
#[cfg(feature = "live")]
use std::{
collections::VecDeque,
future::Future,
pin::Pin,
task::{Context, Poll},
};
#[cfg(feature = "live")]
type WebSocketStream = async_tungstenite::WebSocketStream<async_tungstenite::tokio::ConnectStream>;
#[cfg(feature = "live")]
use async_tungstenite::tungstenite::Error as WsError;
#[cfg(feature = "live")]
use crate::ws_protocol;
#[cfg(feature = "live")]
type WsResult<T> = Result<T, WsError>;
pub fn new_client() -> reqwest::Result<Client> {
const USER_AGENT: &str = concat!(env!("CARGO_PKG_NAME"), "/", env!("CARGO_PKG_VERSION"));
trace!("user agent name: {}", USER_AGENT);
Client::builder()
.user_agent(USER_AGENT)
.cookie_store(true)
.build()
}
#[cfg(feature = "live")]
pub struct LiveConnection {
room_id: u64,
heartbeat_future: Pin<Box<dyn Future<Output = WsResult<()>> + Send>>,
read: SplitStream<WebSocketStream>,
buffered_msg: VecDeque<ws_protocol::Packet>,
}
#[cfg(feature = "live")]
impl LiveConnection {
pub async fn new(url: &str, room_id: u64, token: String) -> WsResult<Self> {
let (websocket, _http) = async_tungstenite::tokio::connect_async(url).await?;
let (write, read) = websocket.split();
let heartbeat_future = Box::pin(async move {
use futures::prelude::*;
let mut write = write;
write
.send(ws_protocol::Packet::auth(room_id, &token).into())
.await?;
loop {
tokio::time::sleep(std::time::Duration::from_secs(30)).await;
debug!("sending heartbeat...");
write
.send(ws_protocol::Packet::heartbeat().into())
.await
.map_err(|e| {
debug!("failed to send heartbeat: {:?}", e);
e
})?;
}
});
Ok(Self {
room_id,
heartbeat_future,
read,
buffered_msg: VecDeque::new(),
})
}
}
#[cfg(feature = "live")]
impl Stream for LiveConnection {
type Item = crate::Result<ws_protocol::Packet>;
fn poll_next(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Option<Self::Item>> {
match self.heartbeat_future.poll_unpin(cx) {
Poll::Ready(Err(e)) => {
warn!("The heartbeat future exited unexpectedly: {:?}", e);
return Poll::Ready(Some(Err(e.into())));
}
Poll::Ready(Ok(_)) => unreachable!(),
Poll::Pending => {}
}
if let Some(msg) = self.buffered_msg.pop_front() {
return Poll::Ready(Some(Ok(msg)));
}
match self.read.poll_next_unpin(cx) {
Poll::Ready(Some(Ok(ws_message))) => {
let msgs = ws_protocol::Packet::from_ws_message(ws_message, self.room_id)?;
self.buffered_msg.extend(msgs);
match self.buffered_msg.pop_front() {
Some(msg) => Poll::Ready(Some(Ok(msg))),
None => Poll::Pending,
}
}
Poll::Pending => Poll::Pending,
Poll::Ready(Some(Err(e))) => Poll::Ready(Some(Err(e.into()))),
Poll::Ready(None) => Poll::Ready(None),
}
}
}