#![doc(
html_logo_url = "https://raw.githubusercontent.com/Totodore/socketioxide/refs/heads/main/.github/logo_dark.svg"
)]
#![doc(
html_favicon_url = "https://raw.githubusercontent.com/Totodore/socketioxide/refs/heads/main/.github/logo_dark.ico"
)]
#![cfg_attr(docsrs, feature(doc_auto_cfg))]
#![warn(
clippy::all,
clippy::todo,
clippy::empty_enum,
clippy::mem_forget,
clippy::unused_self,
clippy::filter_map_next,
clippy::needless_continue,
clippy::needless_borrow,
clippy::match_wildcard_for_single_variants,
clippy::if_let_mutex,
clippy::await_holding_lock,
clippy::match_on_vec_items,
clippy::imprecise_flops,
clippy::suboptimal_flops,
clippy::lossy_float_literal,
clippy::rest_pat_in_fully_bound_structs,
clippy::fn_params_excessive_bools,
clippy::exit,
clippy::inefficient_to_string,
clippy::linkedlist,
clippy::macro_use_imports,
clippy::option_option,
clippy::verbose_file_reads,
clippy::unnested_or_patterns,
rust_2018_idioms,
future_incompatible,
nonstandard_style,
missing_docs
)]
use requests::{Request, RequestType};
use socketioxide_core::{
Str,
adapter::{BroadcastFlags, BroadcastOptions, RoomParam},
};
mod requests;
#[cfg(any(feature = "msgpack-parser", feature = "common-parser"))]
mod emit;
#[cfg(any(feature = "msgpack-parser", feature = "common-parser"))]
pub use emit::EmitError;
pub trait Driver {
type Error: std::error::Error;
fn emit(&self, channel: String, data: Vec<u8>)
-> impl Future<Output = Result<(), Self::Error>>;
}
#[derive(Clone, Debug)]
pub struct IoEmitter {
opts: BroadcastOptions,
ns: Str,
prefix: Option<String>,
#[cfg(any(feature = "common-parser", feature = "msgpack-parser"))]
parser: emit::Parser,
}
impl Default for IoEmitter {
fn default() -> Self {
let mut io = Self {
opts: Default::default(),
ns: Str::from("/"),
prefix: None,
#[cfg(any(feature = "common-parser", feature = "msgpack-parser"))]
parser: emit::Parser::default(),
};
io.opts.add_flag(BroadcastFlags::Broadcast);
io
}
}
impl IoEmitter {
pub fn new() -> Self {
Self::default()
}
#[cfg(feature = "msgpack-parser")]
pub fn new_msgpack() -> Self {
Self {
parser: emit::Parser::MsgPack,
..Default::default()
}
}
pub fn of(mut self, ns: impl Into<Str>) -> IoEmitter {
self.ns = ns.into();
self
}
pub fn to(mut self, rooms: impl RoomParam) -> IoEmitter {
self.opts.rooms.extend(rooms.into_room_iter());
self
}
pub fn within(self, rooms: impl RoomParam) -> IoEmitter {
self.to(rooms)
}
pub fn except(mut self, rooms: impl RoomParam) -> IoEmitter {
self.opts.except.extend(rooms.into_room_iter());
self
}
pub fn prefix(mut self, prefix: impl Into<String>) -> IoEmitter {
self.prefix = Some(prefix.into());
self
}
}
impl IoEmitter {
pub async fn join<D: Driver>(self, rooms: impl RoomParam, driver: &D) -> Result<(), D::Error> {
let rooms = rooms.into_room_iter().collect();
let chan = self.get_channel();
let data = serialize(self.opts, RequestType::AddSockets(rooms));
driver.emit(chan, data).await
}
pub async fn leave<D: Driver>(self, rooms: impl RoomParam, driver: &D) -> Result<(), D::Error> {
let rooms = rooms.into_room_iter().collect();
let chan = self.get_channel();
let data = serialize(self.opts, RequestType::DelSockets(rooms));
driver.emit(chan, data).await
}
pub async fn disconnect<D: Driver>(self, driver: &D) -> Result<(), D::Error> {
let chan = self.get_channel();
let data = serialize(self.opts, RequestType::DisconnectSockets);
driver.emit(chan, data).await
}
#[cfg(any(feature = "msgpack-parser", feature = "common-parser"))]
pub async fn emit<D: Driver, T: serde::Serialize + ?Sized>(
self,
event: &str,
msg: &T,
driver: &D,
) -> Result<(), emit::EmitError<D>> {
use emit::{EmitError, Parser};
use socketioxide_core::{
packet::{Packet, PacketData},
parser::Parse,
};
let value = match self.parser {
#[cfg(feature = "common-parser")]
Parser::Common => {
socketioxide_parser_common::CommonParser.encode_value(msg, Some(event))
}
#[cfg(feature = "msgpack-parser")]
Parser::MsgPack => {
socketioxide_parser_msgpack::MsgPackParser.encode_value(msg, Some(event))
}
}
.map_err(EmitError::Parser)?;
let chan = self.get_channel();
let packet = Packet {
inner: PacketData::Event(value, None),
ns: self.ns,
};
let data = serialize(self.opts, RequestType::Broadcast(packet));
driver.emit(chan, data).await.map_err(EmitError::Driver)?;
Ok(())
}
}
impl IoEmitter {
fn get_channel(&self) -> String {
let prefix = self.prefix.as_deref().unwrap_or("socket.io");
format!("{}-request#{}#", prefix, &self.ns)
}
}
fn serialize(opts: BroadcastOptions, req_type: RequestType) -> Vec<u8> {
let req = Request::new(req_type, opts);
rmp_serde::to_vec(&req).unwrap()
}