use futures::{sink::SinkExt, Sink, Stream}; use std::pin::Pin; use std::str::FromStr; use std::task::{Context, Poll}; use tokio_stream::StreamExt; use xmpp_parsers::{ns, Element, Jid}; use crate::xmpp_codec::Packet; use crate::xmpp_stream::{add_stanza_id, XMPPStream}; use crate::{client_login, AsyncServerConfig, Error, ServerConnector}; /// A simple XMPP client connection /// /// This implements the `futures` crate's [`Stream`](#impl-Stream) and /// [`Sink`](#impl-Sink) traits. pub struct Client { stream: XMPPStream, } impl Client { /// Start a new XMPP client and wait for a usable session pub async fn new>(jid: &str, password: P) -> Result { let jid = Jid::from_str(jid)?; Self::new_with_jid(jid, password.into()).await } /// Start a new client given that the JID is already parsed. pub async fn new_with_jid(jid: Jid, password: String) -> Result { Self::new_with_jid_connector(AsyncServerConfig::UseSrv, jid, password).await } } impl Client { /// Start a new client given that the JID is already parsed. pub async fn new_with_jid_connector( connector: C, jid: Jid, password: String, ) -> Result { let stream = client_login(connector, jid, password).await?; Ok(Client { stream }) } /// Get direct access to inner XMPP Stream pub fn into_inner(self) -> XMPPStream { self.stream } /// Get the client's bound JID (the one reported by the XMPP /// server). pub fn bound_jid(&self) -> &Jid { &self.stream.jid } /// Send stanza pub async fn send_stanza(&mut self, stanza: E) -> Result<(), Error> where E: Into, { self.send(Packet::Stanza(add_stanza_id( stanza.into(), ns::JABBER_CLIENT, ))) .await } /// End connection by sending `` /// /// You may expect the server to respond with the same. This /// client will then drop its connection. pub async fn end(mut self) -> Result<(), Error> { self.send(Packet::StreamEnd).await?; // Wait for stream end from server while let Some(Ok(_)) = self.next().await {} Ok(()) } } /// Incoming XMPP events /// /// In an `async fn` you may want to use this with `use /// futures::stream::StreamExt;` impl Stream for Client { type Item = Result; /// Low-level read on the XMPP stream fn poll_next(mut self: Pin<&mut Self>, cx: &mut Context) -> Poll> { loop { match Pin::new(&mut self.stream).poll_next(cx) { Poll::Pending => return Poll::Pending, Poll::Ready(Some(Ok(Packet::Stanza(stanza)))) => { return Poll::Ready(Some(Ok(stanza))) } Poll::Ready(Some(Ok(Packet::Text(_)))) => { // Ignore, retry } Poll::Ready(_) => // Unexpected and errors, just end { return Poll::Ready(None) } } } } } /// Outgoing XMPP packets /// /// See `send_stanza()` for an `async fn` impl Sink for Client { type Error = Error; fn start_send(mut self: Pin<&mut Self>, item: Packet) -> Result<(), Self::Error> { Pin::new(&mut self.stream).start_send(item) } fn poll_ready(mut self: Pin<&mut Self>, cx: &mut Context) -> Poll> { Pin::new(&mut self.stream).poll_ready(cx) } fn poll_flush(mut self: Pin<&mut Self>, cx: &mut Context) -> Poll> { Pin::new(&mut self.stream).poll_flush(cx) } fn poll_close(mut self: Pin<&mut Self>, cx: &mut Context) -> Poll> { Pin::new(&mut self.stream).poll_close(cx) } }