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

Fix WASM clients #93

Merged
merged 16 commits into from
Oct 16, 2023
3 changes: 2 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,8 @@ and this project adheres to [Semantic Versioning](http://semver.org/).
- Use [`tokio-tungstenite-wasm`](https://github.com/TannerRogalsky/tokio-tungstenite-wasm) errors internally to better support cross-platform clients.
- Use [`enfync`](https://github.com/UkoeHB/enfync) runtime handles internally to better support cross-platform clients. Default clients continue to use tokio.
- Add `ClientConnector` abstraction for connecting clients and add `ezsockets::client::connect_with`.
- Add `ClientConnectorWasm` and `wasm_client` feature.
- Add `ClientConnectorWasm` and `wasm_client` feature. Added `chat-client-wasm` example to show a WASM client that compiles. It currently only listens to the chat and can't input anything since browser does not have a terminal.
- Refactor `Socket` and `Client` to not depend on `tokio` when compiling to WASM. This is a breaking change as the `Client` API now exposes `async_channel` error types instead of `tokio` error types, and `Client::call_with()` now takes an `async_channel::Sender` instead of a `tokio` oneshot.


Migration guide:
Expand Down
34 changes: 22 additions & 12 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -7,49 +7,58 @@ description = "WebSockets server & client made easy"
readme = "README.md"
repository = "https://github.com/gbaranski/ezsockets"
license = "MIT"
keywords = ["websocket", "networking", "async"]
keywords = ["websocket", "networking", "async", "wasm"]
categories = ["asynchronous", "network-programming", "web-programming::websocket"]

# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[lib]
crate-type = ["cdylib", "rlib"]

[dependencies]
async-channel = "1.9.0"
async-trait = "0.1.52"
atomic_enum = "0.2.0"
base64 = "0.21.0"
enfync = "0.1.0"
futures = "0.3.21"
futures-util = { version = "0.3.25", default-features = false }
http = "0.2.8"
tokio = { version = "1.17.0", features = ["sync", "macros", "time"] }
tracing = "0.1.31"
tungstenite = "0.20.0"
url = "2.2.2"
cfg-if = "1.0.0"

axum = { version = "0.6.1", optional = true }
axum-core = { version = "0.3.0", optional = true }
bytes = { version = "1.3.0", optional = true }
futures-util = { version = "0.3.25", default-features = false, features = ["alloc"], optional = true }
fragile = { version = "2.0", optional = true }
http-body = { version = "0.4.5", optional = true }
hyper = { version = "0.14.23", optional = true }
sha-1 = { version = "0.10.1", optional = true }
tokio-tungstenite = { version = "0.20.0", optional = true }

tokio-tungstenite-wasm = { version = "0.2.0", optional = true }
tokio-tungstenite-wasm = { version = "0.2.1", optional = true }

tokio-tungstenite = { version = "0.20.0", optional = true }
tokio-rustls = { version = "0.24.1", optional = true }
tokio-native-tls = { version = "0.3.1", optional = true }

[target.'cfg(not(target_family = "wasm"))'.dependencies]
tokio = { version = "1.17.0", features = ["sync", "macros", "time"] }

[target.'cfg(target_family = "wasm")'.dependencies]
getrandom = { version = "0.2", features = ["js"] }
wasm-bindgen-futures = "0.4"
wasmtimer = "0.2.0"

[features]
default = ["native_client", "server"]

client = ["tokio-tungstenite-wasm"]
native_client = ["client", "tokio-tungstenite", "tokio/rt"]
wasm_client = ["client", "tokio-tungstenite-wasm"]

tungstenite_common = ["tokio-tungstenite"]
native_client = ["client", "tokio/rt", "tokio-tungstenite"]
wasm_client = ["client", "fragile"]

server = ["tungstenite_common", "tokio-tungstenite-wasm", "tokio/rt"]
server = ["tokio-tungstenite", "tokio-tungstenite-wasm", "tokio/rt"]
tungstenite = ["server"]
axum = ["server", "dep:axum", "axum-core", "bytes", "futures-util", "http-body", "hyper", "sha-1"]
axum = ["server", "dep:axum", "axum-core", "bytes", "futures-util/alloc", "http-body", "hyper", "sha-1"]

tls = []
native-tls = ["tls", "tokio-native-tls", "tokio-tungstenite/native-tls"]
Expand All @@ -69,6 +78,7 @@ rustdoc-args = ["--cfg", "docsrs"]
[workspace]
members = [
"examples/chat-client",
"examples/chat-client-wasm",
"examples/chat-server",
"examples/chat-server-axum",
"examples/echo-server",
Expand Down
3 changes: 2 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,11 +15,12 @@ Creating a WebSocket server or a client in Rust can be troublesome. This crate f
View the full documentation at [docs.rs/ezsockets](http://docs.rs/ezsockets)

## Examples
- [`simple-client`](https://github.com/gbaranski/ezsockets/tree/master/examples/chat-client) - a simplest WebSocket client which uses stdin as input.
- [`simple-client`](https://github.com/gbaranski/ezsockets/tree/master/examples/simple-client) - a simplest WebSocket client which uses stdin as input.
- [`echo-server`](https://github.com/gbaranski/ezsockets/tree/master/examples/echo-server) - server that echoes back every message it receives.
- [`echo-server`](https://github.com/gbaranski/ezsockets/tree/master/examples/echo-server-native-tls) - same as `echo-server`, but with `native-tls`.
- [`counter-server`](https://github.com/gbaranski/ezsockets/tree/master/examples/counter-server) - server that increments global value every second and shares it with client
- [`chat-client`](https://github.com/gbaranski/ezsockets/tree/master/examples/chat-client) - chat client for `chat-server` and `chat-server-axum` examples
- [`wasm-client`](https://github.com/gbaranski/ezsockets/tree/master/examples/chat-client-wasm) - chat client for `chat-server` and `chat-server-axum` examples that runs in the browser (only listens to the chat)
- [`chat-server`](https://github.com/gbaranski/ezsockets/tree/master/examples/chat-server) - chat server with support of rooms
- [`chat-server-axum`](https://github.com/gbaranski/ezsockets/tree/master/examples/chat-server-axum) - same as above, but using `axum` as a back-end

Expand Down
16 changes: 16 additions & 0 deletions examples/chat-client-wasm/Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
[package]
name = "ezsockets-chat-client-wasm"
version = "0.1.0"
edition = "2021"

[dependencies]
async-trait = "0.1.52"
console_error_panic_hook = "0.1"
ezsockets = { path = "../../", default-features = false, features = ["wasm_client"] }
tracing = "0.1.32"
tracing-subscriber = "0.3.9"
tracing-wasm = "0.2"
url = "2.2.2"
wasm-bindgen = "0.2"
wasm-bindgen-futures = "0.4"
wasmtimer = "0.2.0"
7 changes: 7 additions & 0 deletions examples/chat-client-wasm/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
1. install [wasm-server-runner](https://github.com/jakobhellermann/wasm-server-runner)
2. `cd` to this directory
3. `cargo run --target wasm32-unknown-unknown`
4. paste the address provided into a new browser window
5. open the browser window console to view the chat logs

Note: This demo does not currently have a way to pass messages into the chat.
63 changes: 63 additions & 0 deletions examples/chat-client-wasm/src/main.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
#![allow(unused_imports)]

use async_trait::async_trait;
use ezsockets::{ClientConfig, RawMessage, SocketConfig};
use std::io::BufRead;
use std::sync::Arc;
use std::time::Duration;

struct Client {}

#[async_trait]
impl ezsockets::ClientExt for Client {
type Call = ();

async fn on_text(&mut self, text: String) -> Result<(), ezsockets::Error> {
tracing::info!("received message: {text}");
Ok(())
}

async fn on_binary(&mut self, bytes: Vec<u8>) -> Result<(), ezsockets::Error> {
tracing::info!("received bytes: {bytes:?}");
Ok(())
}

async fn on_call(&mut self, call: Self::Call) -> Result<(), ezsockets::Error> {
let () = call;
Ok(())
}
}

#[cfg(target_family = "wasm")]
#[wasm_bindgen::prelude::wasm_bindgen(main)]
async fn main() -> Result<(), wasm_bindgen::JsValue> {
// setup tracing
console_error_panic_hook::set_once();
tracing_wasm::set_as_global_default();

// make client
// - note: we use a hacky custom Ping/Pong protocol to keep the client alive (see the 'chat-server' example
// for the Ping side via SessionExt::on_binary())
let config = ClientConfig::new("ws://localhost:8080/websocket").socket_config(SocketConfig {
heartbeat_ping_msg_fn: Arc::new(|_t: Duration| RawMessage::Binary("ping".into())),
..Default::default()
});
let (_client, mut handle) = ezsockets::connect_with(
|_client| Client {},
config,
ezsockets::ClientConnectorWasm::default(),
);

// collect inputs: todo

// keep main alive until it is manually terminated
handle.extract().await.unwrap().unwrap();

Ok(())
}

#[cfg(not(target_family = "wasm"))]
fn main() {
// need per-package targets https://github.com/rust-lang/cargo/issues/9406
unreachable!()
}
2 changes: 0 additions & 2 deletions examples/chat-client/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,6 @@ name = "ezsockets-chat-client"
version = "0.1.0"
edition = "2021"

# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html

[dependencies]
async-trait = "0.1.52"
ezsockets = { path = "../../", features = ["client"] }
Expand Down
2 changes: 0 additions & 2 deletions examples/chat-server-axum/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,6 @@ name = "ezsockets-chat-server-axum"
version = "0.1.0"
edition = "2021"

# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html

[dependencies]
async-trait = "0.1.52"
axum = "0.6.1"
Expand Down
2 changes: 0 additions & 2 deletions examples/chat-server/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,6 @@ name = "ezsockets-chat-server"
version = "0.1.0"
edition = "2021"

# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html

[dependencies]
async-trait = "0.1.52"
ezsockets = { path = "../../", features = ["tungstenite"] }
Expand Down
11 changes: 8 additions & 3 deletions examples/chat-server/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -42,9 +42,10 @@ impl ezsockets::ServerExt for ChatServer {
) -> Result<Session, Option<CloseFrame>> {
let id = (0..).find(|i| !self.sessions.contains_key(i)).unwrap_or(0);
let session = Session::create(
|_handle| SessionActor {
|session_handle| SessionActor {
id,
server: self.handle.clone(),
session: session_handle,
room: DEFAULT_ROOM.to_string(),
},
id,
Expand Down Expand Up @@ -129,6 +130,7 @@ impl ezsockets::ServerExt for ChatServer {
struct SessionActor {
id: SessionID,
server: Server<ChatServer>,
session: Session,
room: String,
}

Expand Down Expand Up @@ -168,8 +170,11 @@ impl ezsockets::SessionExt for SessionActor {
Ok(())
}

async fn on_binary(&mut self, _bytes: Vec<u8>) -> Result<(), Error> {
unimplemented!()
async fn on_binary(&mut self, bytes: Vec<u8>) -> Result<(), Error> {
// echo bytes back (we use this for a hacky ping/pong protocol for the wasm client demo)
tracing::info!("echoing bytes: {bytes:?}");
self.session.binary("pong".as_bytes())?;
Ok(())
}

async fn on_call(&mut self, call: Self::Call) -> Result<(), Error> {
Expand Down
2 changes: 0 additions & 2 deletions examples/counter-server/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,6 @@ name = "ezsockets-counter-server"
version = "0.1.0"
edition = "2021"

# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html

[dependencies]
async-trait = "0.1.52"
ezsockets = { path = "../../", features = ["tungstenite"] }
Expand Down
2 changes: 0 additions & 2 deletions examples/echo-server-native-tls/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,6 @@ name = "ezsocket-echo-server-native-tls"
version = "0.1.0"
edition = "2021"

# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html

[dependencies]
async-trait = "0.1.52"
ezsockets = { path = "../../", features = ["tungstenite", "native-tls"] }
Expand Down
2 changes: 0 additions & 2 deletions examples/echo-server/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,6 @@ name = "ezsocket-echo-server"
version = "0.1.0"
edition = "2021"

# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html

[dependencies]
async-trait = "0.1.52"
ezsockets = { path = "../../", features = ["tungstenite"] }
Expand Down
2 changes: 0 additions & 2 deletions examples/simple-client/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,6 @@ name = "ezsockets-simple-client"
version = "0.1.0"
edition = "2021"

# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html

[dependencies]
async-trait = "0.1.52"
ezsockets = { path = "../../", features = ["rustls"] }
Expand Down
Loading
Loading