mod backoff_delayer;
mod heartbeat;
mod reconnect_handle;
pub mod rpc_session;
pub mod websocket;
use std::str::FromStr;
use derive_more::with_trait::{AsRef, Display, From};
use medea_client_api_proto::{
CloseDescription, CloseReason as CloseByServerReason, Credential, MemberId,
RoomId,
};
use tracerr::Traced;
use url::Url;
#[cfg(feature = "mockable")]
pub use self::rpc_session::MockRpcSession;
#[doc(inline)]
pub use self::{
backoff_delayer::BackoffDelayer,
heartbeat::{Heartbeat, IdleTimeout, PingInterval},
reconnect_handle::{ReconnectError, ReconnectHandleImpl},
rpc_session::{
RpcSession, SessionError, SessionState, WebSocketRpcSession,
},
websocket::{ClientDisconnect, RpcEvent, WebSocketRpcClient},
};
use crate::{platform, utils::Caused};
#[derive(AsRef, Clone, Debug, Eq, From, PartialEq)]
#[as_ref(forward)]
pub struct ApiUrl(Url);
#[derive(Clone, Debug, Eq, PartialEq)]
pub struct ConnectionInfo {
url: ApiUrl,
room_id: RoomId,
member_id: MemberId,
credential: Credential,
}
impl ConnectionInfo {
#[must_use]
pub const fn url(&self) -> &ApiUrl {
&self.url
}
#[must_use]
pub const fn room_id(&self) -> &RoomId {
&self.room_id
}
#[must_use]
pub const fn member_id(&self) -> &MemberId {
&self.member_id
}
#[must_use]
pub const fn credential(&self) -> &Credential {
&self.credential
}
}
#[derive(Caused, Clone, Copy, Debug, Display)]
#[cause(error = platform::Error)]
pub enum ConnectionInfoParseError {
#[display("Failed to parse provided URL: {_0}")]
UrlParse(url::ParseError),
#[display("Provided URL doesn't have important segments")]
NotEnoughSegments,
#[display("Provided URL does not contain auth token")]
NoToken,
}
impl FromStr for ConnectionInfo {
type Err = Traced<ConnectionInfoParseError>;
fn from_str(string: &str) -> Result<Self, Self::Err> {
use ConnectionInfoParseError as E;
let mut url = Url::parse(string)
.map_err(|err| tracerr::new!(E::UrlParse(err)))?;
let credential = url
.query_pairs()
.find(|(key, _)| key.as_ref() == "token")
.ok_or_else(|| tracerr::new!(E::NoToken))?
.1
.clone()
.into();
url.set_fragment(None);
url.set_query(None);
let mut segments = url
.path_segments()
.ok_or_else(|| tracerr::new!(E::NotEnoughSegments))?
.rev();
let member_id = segments
.next()
.ok_or_else(|| tracerr::new!(E::NotEnoughSegments))?
.to_owned()
.into();
let room_id = segments
.next()
.ok_or_else(|| tracerr::new!(E::NotEnoughSegments))?
.to_owned()
.into();
if let Ok(mut s) = url.path_segments_mut() {
_ = s.pop().pop();
}
Ok(Self { url: url.into(), room_id, member_id, credential })
}
}
#[derive(Copy, Clone, Debug, Display, Eq, PartialEq)]
pub enum CloseReason {
ByServer(CloseByServerReason),
#[display("{reason}")]
ByClient {
reason: ClientDisconnect,
},
}
#[derive(Clone, Debug, PartialEq)]
pub enum ClosedStateReason {
NeverConnected,
CouldNotEstablish(platform::TransportError),
ConnectionLost(ConnectionLostReason),
FirstServerMsgIsNotRpcSettings,
ClosedForReconnection,
}
#[derive(Clone, Copy, Debug, Display, Eq, PartialEq)]
pub enum ConnectionLostReason {
WithMessage(CloseMsg),
Idle,
}
#[derive(Caused, Clone, Debug, Display, From)]
#[cause(error = platform::Error)]
pub enum RpcClientError {
#[display("Connection failed: {_0}")]
RpcTransportError(#[cause] platform::TransportError),
#[display("RpcClient unexpectedly gone")]
RpcClientGone,
#[display("Connection failed: {_0:?}")]
ConnectionFailed(ClosedStateReason),
}
#[derive(Clone, Copy, Debug, Display, Eq, PartialEq)]
pub enum CloseMsg {
#[display("Normal. Code: {_0}, Reason: {_1}")]
Normal(u16, CloseByServerReason),
#[display("Abnormal. Code: {_0}")]
Abnormal(u16),
}
impl From<(u16, String)> for CloseMsg {
fn from((code, reason): (u16, String)) -> Self {
match code {
1000 => serde_json::from_str::<CloseDescription>(&reason)
.map_or(Self::Abnormal(code), |desc| {
Self::Normal(code, desc.reason)
}),
_ => Self::Abnormal(code),
}
}
}