Skip to main content

tf_rust_socketio/
lib.rs

1//! Rust-socket.io is a socket.io client written in the Rust Programming Language.
2//! ## Example usage
3//!
4//! ``` rust
5//! use tf_rust_socketio::{ClientBuilder, Payload, RawClient};
6//! use serde_json::json;
7//! use std::time::Duration;
8//!
9//! // define a callback which is called when a payload is received
10//! // this callback gets the payload as well as an instance of the
11//! // socket to communicate with the server
12//! let callback = |payload: Payload, socket: RawClient| {
13//!        match payload {
14//!            Payload::Text(values, _) => println!("Received: {:#?}", values),
15//!            Payload::Binary(bin_data, _) => println!("Received bytes: {:#?}", bin_data),
16//!            // This variant is deprecated, use Payload::Text instead
17//!            #[allow(deprecated)]
18//!            Payload::String(str, _) => println!("Received: {}", str),
19//!        }
20//!        socket.emit("test", json!({"got ack": true})).expect("Server unreachable")
21//! };
22//!
23//! // get a socket that is connected to the admin namespace
24//! let mut socket = ClientBuilder::new("http://localhost:4200/")
25//!      .namespace("/admin")
26//!      .on("test", callback)
27//!      .on("error", |err, _| eprintln!("Error: {:#?}", err))
28//!      .connect()
29//!      .expect("Connection failed");
30//!
31//! // emit to the "foo" event
32//! let json_payload = json!({"token": 123});
33//!
34//! socket.emit("foo", json_payload).expect("Server unreachable");
35//!
36//! // define a callback, that's executed when the ack got acked
37//! let ack_callback = |message: Payload, _: RawClient| {
38//!     println!("Yehaa! My ack got acked?");
39//!     println!("Ack data: {:#?}", message);
40//! };
41//!
42//! let json_payload = json!({"myAckData": 123});
43//!
44//! // emit with an ack
45//! let ack = socket
46//!     .emit_with_ack("test", json_payload, Duration::from_secs(2), ack_callback)
47//!     .expect("Server unreachable");
48//! ```
49//!
50//! The main entry point for using this crate is the [`ClientBuilder`] which provides
51//! a way to easily configure a socket in the needed way. When the `connect` method
52//! is called on the builder, it returns a connected client which then could be used
53//! to emit messages to certain events. One client can only be connected to one namespace.
54//! If you need to listen to the messages in different namespaces you need to
55//! allocate multiple sockets.
56//!
57//! ## Current features
58//!
59//! This implementation now supports all of the features of the socket.io protocol mentioned
60//! [here](https://github.com/socketio/socket.io-protocol).
61//! It generally tries to make use of websockets as often as possible. This means most times
62//! only the opening request uses http and as soon as the server mentions that he is able to use
63//! websockets, an upgrade  is performed. But if this upgrade is not successful or the server
64//! does not mention an upgrade possibility, http-long polling is used (as specified in the protocol specs).
65//!
66//! Here's an overview of possible use-cases:
67//!
68//! - connecting to a server.
69//! - register callbacks for the following event types:
70//!     - open
71//!     - close
72//!     - error
73//!     - message
74//!     - custom events like "foo", "on_payment", etc.
75//! - send JSON data to the server (via `serde_json` which provides safe
76//! handling).
77//! - send JSON data to the server and receive an `ack`.
78//! - send and handle Binary data.
79#![cfg_attr(
80    feature = "async",
81    doc = r#"
82## Async version
83This library provides an ability for being executed in an asynchronous context using `tokio` as
84the execution runtime.
85Please note that the current async implementation is in beta, the interface can be object to
86drastic changes.
87The async `Client` and `ClientBuilder` support a similar interface to the sync version and live
88in the [`asynchronous`] module. In order to enable the support, you need to enable the `async`
89feature flag:
90```toml
91tf_rust_socketio = { version = "^0.4.1", features = ["async"] }
92```
93
94The following code shows the example above in async fashion:
95
96``` rust
97use futures_util::FutureExt;
98use tf_rust_socketio::{
99    asynchronous::{Client, ClientBuilder},
100    Payload,
101};
102use serde_json::json;
103use std::time::Duration;
104
105#[tokio::main]
106async fn main() {
107    // define a callback which is called when a payload is received
108    // this callback gets the payload as well as an instance of the
109    // socket to communicate with the server
110    let callback = |payload: Payload, socket: Client| {
111        async move {
112            match payload {
113                Payload::Text(values, _) => println!("Received: {:#?}", values),
114                Payload::Binary(bin_data, _) => println!("Received bytes: {:#?}", bin_data),
115                // This is deprecated use Payload::Text instead
116                #[allow(deprecated)]
117                Payload::String(str, _) => println!("Received: {}", str),
118            }
119            socket
120                .emit("test", json!({"got ack": true}))
121                .await
122                .expect("Server unreachable");
123        }
124        .boxed()
125    };
126
127    // get a socket that is connected to the admin namespace
128    let socket = ClientBuilder::new("http://localhost:4200/")
129        .namespace("/admin")
130        .on("test", callback)
131        .on("error", |err, _| {
132            async move { eprintln!("Error: {:#?}", err) }.boxed()
133        })
134        .connect()
135        .await
136        .expect("Connection failed");
137
138    // emit to the "foo" event
139    let json_payload = json!({"token": 123});
140    socket
141        .emit("foo", json_payload)
142        .await
143        .expect("Server unreachable");
144
145    // define a callback, that's executed when the ack got acked
146    let ack_callback = |message: Payload, _: Client| {
147        async move {
148            println!("Yehaa! My ack got acked?");
149            println!("Ack data: {:#?}", message);
150        }
151        .boxed()
152    };
153
154    let json_payload = json!({"myAckData": 123});
155    // emit with an ack
156    socket
157        .emit_with_ack("test", json_payload, Duration::from_secs(2), ack_callback)
158        .await
159        .expect("Server unreachable");
160
161    socket.disconnect().await.expect("Disconnect failed");
162}
163```"#
164)]
165#![allow(clippy::rc_buffer)]
166#![warn(clippy::complexity)]
167#![warn(clippy::style)]
168#![warn(clippy::perf)]
169#![warn(clippy::correctness)]
170// Allow large error types - Error enum contains rich context from engineio
171// which is intentional for debugging. Boxing would add indirection overhead.
172#![allow(clippy::result_large_err)]
173
174/// Defines client only structs
175pub mod client;
176/// Deprecated import since 0.3.0-alpha-2, use Event in the crate root instead.
177/// Defines the events that could be sent or received.
178pub mod event;
179pub(crate) mod packet;
180/// Deprecated import since 0.3.0-alpha-2, use Event in the crate root instead.
181/// Defines the types of payload (binary or string), that
182/// could be sent or received.
183pub mod payload;
184mod socket;
185
186/// Deprecated import since 0.3.0-alpha-2, use Error in the crate root instead.
187/// Contains the error type which will be returned with every result in this
188/// crate.
189pub mod error;
190
191#[cfg(test)]
192mod test_concurrent_ack;
193
194#[cfg(test)]
195#[cfg(feature = "async")]
196mod test_async_concurrent_ack;
197
198#[cfg(feature = "async")]
199/// Asynchronous version of the socket.io client. This module contains the async
200/// [`crate::asynchronous::Client`] as well as a builder
201/// ([`crate::asynchronous::ClientBuilder`]) that allows for configuring a client.
202pub mod asynchronous;
203
204pub use error::Error;
205
206pub use {event::CloseReason, event::Event, payload::Payload};
207
208pub use client::{ClientBuilder, RawClient, TransportType};
209
210// TODO: 0.4.0 remove
211#[deprecated(since = "0.3.0-alpha-2", note = "Socket renamed to Client")]
212pub use client::{ClientBuilder as SocketBuilder, RawClient as Socket};
213
214#[cfg(test)]
215pub(crate) mod test {
216    use url::Url;
217
218    /// The socket.io server for testing runs on port 4200
219    const SERVER_URL: &str = "http://localhost:4200";
220
221    pub(crate) fn socket_io_server() -> Url {
222        let url = std::env::var("SOCKET_IO_SERVER").unwrap_or_else(|_| SERVER_URL.to_owned());
223        let mut url = Url::parse(&url).unwrap();
224
225        if url.path() == "/" {
226            url.set_path("/socket.io/");
227        }
228
229        url
230    }
231
232    // The socket.io auth server for testing runs on port 4204
233    const AUTH_SERVER_URL: &str = "http://localhost:4204";
234
235    pub(crate) fn socket_io_auth_server() -> Url {
236        let url =
237            std::env::var("SOCKET_IO_AUTH_SERVER").unwrap_or_else(|_| AUTH_SERVER_URL.to_owned());
238        let mut url = Url::parse(&url).unwrap();
239
240        if url.path() == "/" {
241            url.set_path("/socket.io/");
242        }
243
244        url
245    }
246
247    // The socket.io restart server for testing runs on port 4205
248    const RESTART_SERVER_URL: &str = "http://localhost:4205";
249
250    pub(crate) fn socket_io_restart_server() -> Url {
251        let url = std::env::var("SOCKET_IO_RESTART_SERVER")
252            .unwrap_or_else(|_| RESTART_SERVER_URL.to_owned());
253        let mut url = Url::parse(&url).unwrap();
254
255        if url.path() == "/" {
256            url.set_path("/socket.io/");
257        }
258
259        url
260    }
261
262    // The socket.io restart url auth server for testing runs on port 4206
263    const RESTART_URL_AUTH_SERVER_URL: &str = "http://localhost:4206";
264
265    pub(crate) fn socket_io_restart_url_auth_server() -> Url {
266        let url = std::env::var("SOCKET_IO_RESTART_URL_AUTH_SERVER")
267            .unwrap_or_else(|_| RESTART_URL_AUTH_SERVER_URL.to_owned());
268        let mut url = Url::parse(&url).unwrap();
269
270        if url.path() == "/" {
271            url.set_path("/socket.io/");
272        }
273
274        url
275    }
276}