use console::style;
use serde::{Deserialize, Serialize};
use socketioxide::{ParserConfig, SocketIo, TransportType, layer::SocketIoLayer};
use socketioxide_parser_common::CommonParser;
use socketioxide_parser_msgpack::MsgPackParser;
use std::collections::HashSet;
use std::str::FromStr;
use sword_core::{
ByteConfig, Config, ConfigItem, ConfigRegistrar, State, TimeConfig,
inventory_submit,
};
use sword_layers::DisplayConfig;
#[derive(Debug, Clone, Serialize, Deserialize, Default)]
#[serde(default)]
pub struct SocketIoServerConfig {
pub enabled: bool,
pub ack_timeout: Option<TimeConfig>,
pub connect_timeout: Option<TimeConfig>,
pub max_buffer_size: Option<usize>,
pub max_payload: Option<ByteConfig>,
pub ping_interval: Option<TimeConfig>,
pub ping_timeout: Option<TimeConfig>,
pub req_path: Option<String>,
#[serde(alias = "transport")]
pub transports: Option<Vec<String>>,
#[serde(default)]
pub parser: SocketIoParser,
pub ws_read_buffer_size: Option<usize>,
pub display: bool,
}
impl DisplayConfig for SocketIoServerConfig {
fn display(&self) {
if !self.enabled {
return;
}
println!("\n{}", style("Socket.IO Server Configuration:").bold());
println!(" ↳ Enabled: {}", self.enabled);
let mut timeout_parts = Vec::new();
if let Some(ack) = &self.ack_timeout {
timeout_parts.push(format!("ack: {}", ack.raw));
}
if let Some(connect) = &self.connect_timeout {
timeout_parts.push(format!("connect: {}", connect.raw));
}
if let Some(ping) = &self.ping_timeout {
timeout_parts.push(format!("ping: {}", ping.raw));
}
if !timeout_parts.is_empty() {
println!(" ↳ Timeouts: {}", timeout_parts.join(" - "));
}
let mut limit_parts = Vec::new();
if let Some(buffer) = &self.max_buffer_size {
limit_parts.push(format!("buffer: {buffer} packets"));
}
if let Some(payload) = &self.max_payload {
limit_parts.push(format!("payload: {}", payload.raw));
}
if !limit_parts.is_empty() {
println!(" ↳ Limits: {}", limit_parts.join(" - "));
}
let mut connection_parts = Vec::new();
if let Some(transports) = &self.transports {
connection_parts.push(format!("transports: {}", transports.join(", ")));
}
connection_parts.push(format!("parser: {}", self.parser));
if let Some(interval) = &self.ping_interval {
connection_parts.push(format!("ping interval: {}", interval.raw));
}
if let Some(path) = &self.req_path {
connection_parts.push(format!("path: {path}"));
}
if !connection_parts.is_empty() {
println!(" ↳ Connection: {}", connection_parts.join(" - "));
}
if let Some(ws_size) = &self.ws_read_buffer_size {
println!(" ↳ WebSocket: read buffer {ws_size} bytes");
}
}
}
#[derive(Debug, Clone, Copy)]
pub enum SocketIoParser {
Common(CommonParser),
MsgPack(MsgPackParser),
}
impl Default for SocketIoParser {
fn default() -> Self {
SocketIoParser::Common(CommonParser)
}
}
impl FromStr for SocketIoParser {
type Err = String;
fn from_str(s: &str) -> Result<Self, Self::Err> {
match s.to_lowercase().as_str() {
"common" => Ok(SocketIoParser::Common(CommonParser)),
"msgpack" => Ok(SocketIoParser::MsgPack(MsgPackParser)),
_ => Err(format!("invalid Socket.IO parser: {s}")),
}
}
}
impl std::fmt::Display for SocketIoParser {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
SocketIoParser::Common(_) => write!(f, "common"),
SocketIoParser::MsgPack(_) => write!(f, "msgpack"),
}
}
}
impl Serialize for SocketIoParser {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: serde::Serializer,
{
serializer.serialize_str(&self.to_string())
}
}
impl<'de> Deserialize<'de> for SocketIoParser {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: serde::Deserializer<'de>,
{
let s = String::deserialize(deserializer)?;
SocketIoParser::from_str(&s).map_err(serde::de::Error::custom)
}
}
pub struct SocketIoServerLayer;
impl SocketIoServerLayer {
#[allow(clippy::new_ret_no_self)]
pub fn new(config: &SocketIoServerConfig) -> (SocketIoLayer, SocketIo) {
let mut layer_builder = SocketIo::builder();
if let Some(ack_timeout) = &config.ack_timeout {
layer_builder = layer_builder.ack_timeout(ack_timeout.parsed);
}
if let Some(connect_timeout) = &config.connect_timeout {
layer_builder = layer_builder.connect_timeout(connect_timeout.parsed);
}
if let Some(max_buffer_size) = &config.max_buffer_size {
layer_builder = layer_builder.max_buffer_size(*max_buffer_size);
}
if let Some(max_payload) = &config.max_payload {
layer_builder = layer_builder.max_payload(max_payload.parsed as u64);
}
if let Some(ping_interval) = &config.ping_interval {
layer_builder = layer_builder.ping_interval(ping_interval.parsed);
}
if let Some(ping_timeout) = &config.ping_timeout {
layer_builder = layer_builder.ping_timeout(ping_timeout.parsed);
}
if let Some(req_path) = &config.req_path {
layer_builder = layer_builder.req_path(req_path.clone());
}
if let Some(transports) = &config.transports {
let parsed_transports = transports
.iter()
.collect::<HashSet<_>>()
.iter()
.filter_map(|t| match t.as_str() {
"polling" => Some(TransportType::Polling),
"websocket" => Some(TransportType::Websocket),
_ => None,
})
.collect::<Vec<_>>();
match parsed_transports.len() {
1 => {
layer_builder = layer_builder.transports([parsed_transports[0]])
}
2 => {
layer_builder = layer_builder
.transports([parsed_transports[0], parsed_transports[1]])
}
_ => {}
};
}
let parser_config = match config.parser {
SocketIoParser::Common(_) => ParserConfig::common(),
SocketIoParser::MsgPack(_) => ParserConfig::msgpack(),
};
layer_builder = layer_builder.with_parser(parser_config);
if let Some(ws_read_buffer_size) = config.ws_read_buffer_size {
layer_builder = layer_builder.ws_read_buffer_size(ws_read_buffer_size);
}
let (layer, io) = layer_builder.build_layer();
(layer, io)
}
}
impl ConfigItem for SocketIoServerConfig {
fn toml_key() -> &'static str {
"socketio-server"
}
fn register(state: &State, config: &Config) {
state.insert(config.get_or_default::<Self>());
}
}
inventory_submit! {[
ConfigRegistrar::new(|state, config| {
SocketIoServerConfig::register(state, config)
})
]}