Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(socketioxide/v4): implement SocketIO V4 (closes #51) #52

Merged
merged 29 commits into from
Oct 17, 2023
Merged
Show file tree
Hide file tree
Changes from 6 commits
Commits
Show all changes
29 commits
Select commit Hold shift + click to select a range
cd6da81
feat: add protocol versions
sleeyax Jul 2, 2023
a715bb9
feat: send default namespace to client
sleeyax Jul 2, 2023
80b1280
feat: exclude payload from v4 CONNECT packet
sleeyax Jul 3, 2023
2e707c6
feat: enforce at least one feature to be enabled
sleeyax Jul 3, 2023
abf75d0
refactor: remove unused sid parameter
sleeyax Jul 3, 2023
e45263b
feat: don't send auth payload in v4 CONNECT packet
sleeyax Jul 3, 2023
983117d
Merge branch 'main' into pr/sleeyax/52
Totodore Sep 20, 2023
87d7a97
fix(socketio): postmerge fix
Totodore Sep 20, 2023
345d0c3
fix(clippy): manual implementation of .map
Totodore Sep 20, 2023
890fd55
feat(socketio/v4): fix default connect packet for v4 protocol
Totodore Sep 20, 2023
5b81a71
Merge branch 'main' into pr/sleeyax/52
Totodore Oct 14, 2023
a27434c
fix(socketio/client): json empty object rather than null
Totodore Oct 14, 2023
1747355
fix(socketio): move `ProtocolVersion` to lib root
Totodore Oct 14, 2023
b4e2ea7
feat(socketio/client): move `connect_timeout` task to fn
Totodore Oct 14, 2023
c13573c
chore(ci): add socket.io v4 e2e test suite
Totodore Oct 14, 2023
85cb428
feat(socketio/packet): connect error as string for v4
Totodore Oct 14, 2023
bf927bd
Merge branch 'main' into pr/sleeyax/52
Totodore Oct 16, 2023
ba23247
Revert "feat(socketio/packet): connect error as string for v4"
Totodore Oct 16, 2023
4e7f66f
chore(socketio/client): remove unused line
Totodore Oct 16, 2023
f06105b
feat(socketio): make connect packet hold a raw string rather than a v…
Totodore Oct 16, 2023
a641ffd
feat(socketio/client): add root namespace guard
Totodore Oct 17, 2023
c185b24
feat(socketio/client): fix auth packet deserialisation to {}
Totodore Oct 17, 2023
45a608e
feat(socketio/client): fix auth packet deserialisation to {}
Totodore Oct 17, 2023
ba9943f
chore(ci): merge all ci files to one
Totodore Oct 17, 2023
0521cc4
fix(socketio/packet): fix packet encoding
Totodore Oct 17, 2023
9863396
fix(socketio/packet): fix packet encoding
Totodore Oct 17, 2023
3918723
fix(socketio/packet): fix packet encoding
Totodore Oct 17, 2023
8ff0354
fix(socketio/packet): fix packet encoding
Totodore Oct 17, 2023
91c6d54
doc: improve readme
Totodore Oct 17, 2023
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 6 additions & 4 deletions e2e/src/socketioxide.rs
Original file line number Diff line number Diff line change
Expand Up @@ -26,8 +26,9 @@ async fn main() -> Result<(), Box<dyn std::error::Error>> {
let ns = Namespace::builder()
.add("/", |socket| async move {
info!("Socket.IO connected: {:?} {:?}", socket.ns(), socket.sid);
let data: Value = socket.handshake.data().unwrap();
socket.emit("auth", data).ok();
if let Some(data) = socket.handshake.data::<Value>() {
socket.emit("auth", data.unwrap()).ok();
}

socket.on("message", |socket, data: Value, bin, _| async move {
info!("Received event: {:?} {:?}", data, bin);
Expand All @@ -41,8 +42,9 @@ async fn main() -> Result<(), Box<dyn std::error::Error>> {
})
.add("/custom", |socket| async move {
info!("Socket.IO connected on: {:?} {:?}", socket.ns(), socket.sid);
let data: Value = socket.handshake.data().unwrap();
socket.emit("auth", data).ok();
if let Some(data) = socket.handshake.data::<Value>() {
socket.emit("auth", data.unwrap()).ok();
}
})
.build();

Expand Down
2 changes: 1 addition & 1 deletion examples/src/chat/handlers.rs
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ struct Auth {

pub async fn handler(socket: Arc<Socket<LocalAdapter>>) {
info!("Socket connected on / with id: {}", socket.sid);
if let Ok(data) = socket.handshake.data::<Auth>() {
if let Some(Ok(data)) = socket.handshake.data::<Auth>() {
info!("Nickname: {:?}", data.nickname);
socket.extensions.insert(data.nickname);
socket.emit("message", "Welcome to the chat!").ok();
Expand Down
4 changes: 2 additions & 2 deletions examples/src/socketio-echo/axum_echo.rs
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ async fn main() -> Result<(), Box<dyn std::error::Error>> {
let ns = Namespace::builder()
.add("/", |socket| async move {
info!("Socket.IO connected: {:?} {:?}", socket.ns(), socket.sid);
let data: Value = socket.handshake.data().unwrap();
let data: Value = socket.handshake.data().unwrap().unwrap();
socket.emit("auth", data).ok();

socket.on("message", |socket, data: Value, bin, _| async move {
Expand All @@ -27,7 +27,7 @@ async fn main() -> Result<(), Box<dyn std::error::Error>> {
})
.add("/custom", |socket| async move {
info!("Socket.IO connected on: {:?} {:?}", socket.ns(), socket.sid);
let data: Value = socket.handshake.data().unwrap();
let data: Value = socket.handshake.data().unwrap().unwrap();
socket.emit("auth", data).ok();
})
.build();
Expand Down
4 changes: 2 additions & 2 deletions examples/src/socketio-echo/hyper_echo.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ async fn main() -> Result<(), Box<dyn std::error::Error>> {
let ns = Namespace::builder()
.add("/", |socket| async move {
info!("Socket.IO connected: {:?} {:?}", socket.ns(), socket.sid);
let data: Value = socket.handshake.data().unwrap();
let data: Value = socket.handshake.data().unwrap().unwrap();
socket.emit("auth", data).ok();

socket.on("message", |socket, data: Value, bin, _| async move {
Expand All @@ -26,7 +26,7 @@ async fn main() -> Result<(), Box<dyn std::error::Error>> {
})
.add("/custom", |socket| async move {
info!("Socket.IO connected on: {:?} {:?}", socket.ns(), socket.sid);
let data: Value = socket.handshake.data().unwrap();
let data: Value = socket.handshake.data().unwrap().unwrap();
socket.emit("auth", data).ok();
})
.build();
Expand Down
4 changes: 2 additions & 2 deletions examples/src/socketio-echo/warp_echo.rs
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ async fn main() -> Result<(), Box<dyn std::error::Error>> {
let ns = Namespace::builder()
.add("/", |socket| async move {
info!("Socket.IO connected: {:?} {:?}", socket.ns(), socket.sid);
let data: Value = socket.handshake.data().unwrap();
let data: Value = socket.handshake.data().unwrap().unwrap();
socket.emit("auth", data).ok();

socket.on("message", |socket, data: Value, bin, _| async move {
Expand All @@ -27,7 +27,7 @@ async fn main() -> Result<(), Box<dyn std::error::Error>> {
})
.add("/custom", |socket| async move {
info!("Socket.IO connected on: {:?} {:?}", socket.ns(), socket.sid);
let data: Value = socket.handshake.data().unwrap();
let data: Value = socket.handshake.data().unwrap().unwrap();
socket.emit("auth", data).ok();
})
.build();
Expand Down
7 changes: 6 additions & 1 deletion socketioxide/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ license = "MIT"
readme = "../Readme.md"

[dependencies]
engineioxide = { path = "../engineioxide", version = "0.3.0" }
engineioxide = { path = "../engineioxide", version = "0.3.0", default-features = false }
futures = "0.3.27"
tokio = "1.26.0"
serde = { version = "1.0.155", features = ["derive"] }
Expand All @@ -35,3 +35,8 @@ dashmap = "5.4.0"
[dev-dependencies]
axum = "0.6.18"
tracing-subscriber = "0.3.17"

[features]
default = ["v5"]
v5 = ["engineioxide/v4"]
v4 = ["engineioxide/v3"]
14 changes: 12 additions & 2 deletions socketioxide/src/client.rs
Original file line number Diff line number Diff line change
Expand Up @@ -59,16 +59,17 @@ impl<A: Adapter> Client<A> {
/// Called when a socket connects to a new namespace
fn sock_connect(
&self,
auth: Value,
auth: Option<Value>,
ns_path: String,
socket: &EIoSocket<Self>,
) -> Result<(), SendError> {
debug!("auth: {:?}", auth);
let handshake = Handshake::new(auth, socket.req_data.clone());
let sid = socket.sid;
if let Some(ns) = self.get_ns(&ns_path) {
let protocol = socket.protocol;
let socket = ns.connect(sid, socket.tx.clone(), handshake, self.config.clone());
socket.send(Packet::connect(ns_path.clone(), sid))?;
socket.send(Packet::connect(ns_path.clone(), sid, protocol))?;
Ok(())
} else {
socket
Expand Down Expand Up @@ -117,6 +118,15 @@ impl<A: Adapter> EngineIoHandler for Client<A> {

fn on_connect(&self, socket: &EIoSocket<Self>) {
debug!("eio socket connect {}", socket.sid);

// Connecting the client to the default namespace is mandatory if the SocketIO protocol is v4.
#[cfg(feature = "v4")]
{
if socket.protocol == engineioxide::service::ProtocolVersion::V3 {
debug!("Connecting to default namespace");
self.sock_connect(None, "/".into(), socket).unwrap();
}
}
}
fn on_disconnect(&self, socket: &EIoSocket<Self>) {
debug!("eio socket disconnect {}", socket.sid);
Expand Down
14 changes: 9 additions & 5 deletions socketioxide/src/handshake.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,13 +8,13 @@ use crate::errors::Error;
/// Handshake informations bound to a socket
#[derive(Debug)]
pub struct Handshake {
pub(crate) auth: serde_json::Value,
pub(crate) auth: Option<serde_json::Value>,
pub issued: SystemTime,
pub req: Arc<SocketReq>,
}

impl Handshake {
pub(crate) fn new(auth: serde_json::Value, req: Arc<SocketReq>) -> Self {
pub(crate) fn new(auth: Option<serde_json::Value>, req: Arc<SocketReq>) -> Self {
Self {
auth,
req,
Expand All @@ -24,16 +24,20 @@ impl Handshake {
/// Extract the data from the handshake.
///
/// It is cloned and deserialized from a json::Value to the given type.
pub fn data<T: DeserializeOwned>(&self) -> Result<T, Error> {
Ok(serde_json::from_value(self.auth.clone())?)
pub fn data<T: DeserializeOwned>(&self) -> Option<Result<T, Error>> {
if let Some(auth) = &self.auth {
Some(serde_json::from_value(auth.clone()).map_err(Error::from))
} else {
None
}
}
}

#[cfg(test)]
impl Handshake {
pub fn new_dummy() -> Self {
Self {
auth: serde_json::json!({}),
auth: Some(serde_json::json!({})),
issued: SystemTime::now(),
req: Arc::new(SocketReq {
headers: Default::default(),
Expand Down
3 changes: 3 additions & 0 deletions socketioxide/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,9 @@
//! }
//! ```

#[cfg(not(any(feature = "v4", feature = "v5")))]
compile_error!("At least one protocol version must be enabled");

pub mod adapter;
pub mod retryer;

Expand Down
52 changes: 45 additions & 7 deletions socketioxide/src/packet.rs
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
use engineioxide::service::ProtocolVersion;
use itertools::{Itertools, PeekingNext};
use serde::{de::DeserializeOwned, Deserialize, Serialize};
use serde_json::{json, Value};
Expand All @@ -16,16 +17,49 @@ pub struct Packet {
}

impl Packet {
pub fn connect(ns: String, sid: Sid) -> Self {
#[cfg(all(feature = "v5", feature = "v4"))]
pub fn connect(ns: String, sid: Sid, protocol: ProtocolVersion) -> Self {
// Decide which SocketIO packet format to use based on the current EngineIO protocol version.
match protocol {
ProtocolVersion::V3 => Self::connect_v4(ns),
ProtocolVersion::V4 => Self::connect_v5(ns, sid),
}
}

#[cfg(feature = "v5")]
#[cfg(not(feature = "v4"))]
pub fn connect(ns: String, sid: Sid, _: ProtocolVersion) -> Self {
Self::connect_v5(ns, sid)
}

#[cfg(feature = "v4")]
#[cfg(not(feature = "v5"))]
pub fn connect(ns: String, _: Sid, _: ProtocolVersion) -> Self {
Self::connect_v4(ns)
}

/// Sends a connect packet without payload.
#[cfg(feature = "v4")]
fn connect_v4(ns: String) -> Self {
Self {
inner: PacketData::Connect(None),
ns,
}
}

/// Sends a connect packet with payload.
#[cfg(feature = "v5")]
fn connect_v5(ns: String, sid: Sid) -> Self {
let val = serde_json::to_value(ConnectPacket {
sid: sid.to_string(),
})
.unwrap();
Self {
inner: PacketData::Connect(val),
inner: PacketData::Connect(Some(val)),
ns,
}
}

pub fn disconnect(ns: String) -> Self {
Self {
inner: PacketData::Disconnect,
Expand Down Expand Up @@ -88,7 +122,7 @@ impl Packet {
/// | BINARY_ACK | 6 | Used to [acknowledge](#acknowledgement) an event (the response includes binary data). |
#[derive(Debug, Clone, PartialEq)]
pub enum PacketData {
Connect(Value),
Connect(Option<Value>),
Disconnect,
Event(String, Value, Option<i64>),
EventAck(Value, i64),
Expand Down Expand Up @@ -196,7 +230,11 @@ impl TryInto<String> for Packet {
}

match self.inner {
PacketData::Connect(data) => res.push_str(&serde_json::to_string(&data)?),
PacketData::Connect(data) => {
if let Some(payload) = data {
res.push_str(&serde_json::to_string(&payload)?);
}
}
PacketData::Disconnect => (),
PacketData::Event(event, data, ack) => {
if let Some(ack) = ack {
Expand Down Expand Up @@ -338,7 +376,7 @@ impl TryFrom<String> for Packet {

let data = chars.as_str();
let inner = match index {
'0' => PacketData::Connect(deserialize_packet(data)?.unwrap_or_else(|| json!({}))),
'0' => PacketData::Connect(deserialize_packet(data)?.unwrap_or_default()),
'1' => PacketData::Disconnect,
'2' => {
let (event, payload) = deserialize_event_packet(data)?;
Expand Down Expand Up @@ -404,7 +442,7 @@ mod test {
assert_eq!(
Packet {
ns: "/".to_string(),
inner: PacketData::Connect(json!({ "token": "123"}))
inner: PacketData::Connect(Some(json!({ "token": "123"})))
},
packet.unwrap()
);
Expand All @@ -417,7 +455,7 @@ mod test {
assert_eq!(
Packet {
ns: "/admin™".to_owned(),
inner: PacketData::Connect(json!({ "token™": "123" }))
inner: PacketData::Connect(Some(json!({ "token™": "123" })))
},
packet.unwrap()
);
Expand Down