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

Add Wasm build target #85

Open
wants to merge 14 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 13 commits
Commits
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
12 changes: 12 additions & 0 deletions .github/workflows/ci.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -80,3 +80,15 @@ jobs:
with:
github_token: ${{ secrets.github_token }}
locale: "US"

wasm:
env:
PROJECT_ID: "dummy"
name: "wasm"
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- name: Install wasm-pack
run: curl https://rustwasm.github.io/wasm-pack/installer/init.sh -sSf | sh
- name: "wasm-pack"
run: cd wasm_websocket_demo && wasm-pack build --target web
4 changes: 4 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -8,3 +8,7 @@ Cargo.lock

# These are backup files generated by rustfmt
**/*.rs.bk

.idea/

pkg/
2 changes: 1 addition & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ authors = ["WalletConnect Team"]
license = "Apache-2.0"

[workspace]
members = ["blockchain_api", "relay_client", "relay_rpc"]
members = ["blockchain_api", "relay_client", "relay_rpc", "wasm_websocket_demo"]

[features]
default = ["full"]
Expand Down
20 changes: 13 additions & 7 deletions relay_client/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,8 @@ edition = "2021"
license = "Apache-2.0"

[features]
default = ["tokio-tungstenite/native-tls"]
rustls = ["tokio-tungstenite/rustls-tls-native-roots"]
default = ["tokio-tungstenite/native-tls", "tokio-tungstenite/url"]
rustls = ["tokio-tungstenite/rustls-tls-native-roots", "tokio-tungstenite/url"]

[dependencies]
relay_rpc = { path = "../relay_rpc" }
Expand All @@ -16,19 +16,25 @@ serde = { version = "1.0", features = ["derive"] }
serde_json = "1.0"
serde_qs = "0.10"
pin-project = "1.0"
chrono = { version = "0.4", default-features = false, features = ["alloc", "std"] }
chrono = { version = "0.4", default-features = false, features = ["alloc", "std", "wasmbind", "wasm-bindgen"] }
url = "2.3"
http = "1.0.0"

# HTTP client dependencies.
reqwest = { version = "0.12.2", features = ["json"] }

# WebSocket client dependencies.
tokio = { version = "1.22", features = ["rt", "time", "sync", "macros", "rt-multi-thread"] }
tokio-tungstenite = "0.21.0"
tokio = { version = "1", features = ["rt", "time", "sync", "macros"] }
futures-channel = "0.3"
tokio-stream = "0.1"
tokio-util = "0.7"

[target.'cfg(not(target_arch = "wasm32"))'.dependencies]
tokio-tungstenite = { version = "0.24" }

[target.'cfg(target_arch = "wasm32")'.dependencies]
tokio-tungstenite-wasm = { version = "0.3" }
wasm-bindgen-futures = { version = "0.4" }
web-sys = { version = "0.3" , features = ["ConsoleEvent"] }
getrandom = { version = "0.2" , features = ["wasm-bindgen", "js"]}

[lints.clippy]
indexing_slicing = "deny"
15 changes: 15 additions & 0 deletions relay_client/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -104,6 +104,7 @@ impl ConnectionOptions {
Ok(url)
}

#[cfg(not(target_arch = "wasm32"))]
fn as_ws_request(&self) -> Result<HttpRequest<()>, RequestBuildError> {
use {
crate::websocket::WebsocketClientError,
Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

tokio_tungstenite::tungstenite::client::IntoClientRequest, is not available in wasm target

Expand All @@ -121,6 +122,20 @@ impl ConnectionOptions {
Ok(request)
}

#[cfg(target_arch = "wasm32")]
fn as_ws_request(&self) -> Result<HttpRequest<()>, RequestBuildError> {
use crate::websocket::WebsocketClientError;

let url = self.as_url()?;
let mut request = HttpRequest::builder()
.uri(format!("{}", url))
.body(())
.map_err(WebsocketClientError::HttpErr)?;

self.update_request_headers(request.headers_mut())?;
Ok(request)
}

fn update_request_headers(&self, headers: &mut HeaderMap) -> Result<(), RequestBuildError> {
if let Authorization::Header(token) = &self.auth {
let value = format!("Bearer {token}")
Expand Down
26 changes: 17 additions & 9 deletions relay_client/src/websocket.rs
Original file line number Diff line number Diff line change
Expand Up @@ -28,15 +28,15 @@ use {
oneshot,
},
};
pub use {
fetch::*,
inbound::*,
outbound::*,
stream::*,
tokio_tungstenite::tungstenite::protocol::CloseFrame,
Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

CloseFrame from tokio_tungstenite has different structure when wasm

};

pub use {fetch::*, inbound::*, outbound::*, stream::*};
#[cfg(not(target_arch = "wasm32"))]
pub type TransportError = tokio_tungstenite::tungstenite::Error;
#[cfg(not(target_arch = "wasm32"))]
pub use tokio_tungstenite::tungstenite::protocol::CloseFrame;
#[cfg(target_arch = "wasm32")]
pub type TransportError = tokio_tungstenite_wasm::Error;
#[cfg(target_arch = "wasm32")]
pub use tokio_tungstenite_wasm::CloseFrame;

#[derive(Debug, thiserror::Error)]
pub enum WebsocketClientError {
Expand All @@ -52,6 +52,9 @@ pub enum WebsocketClientError {
#[error("Websocket transport error: {0}")]
Transport(TransportError),

#[error("Url error: {0}")]
HttpErr(http::Error),

#[error("Not connected")]
NotConnected,
}
Expand Down Expand Up @@ -146,7 +149,12 @@ impl Client {
{
let (control_tx, control_rx) = mpsc::unbounded_channel();

tokio::spawn(connection_event_loop(control_rx, handler));
let fut = connection_event_loop(control_rx, handler);
#[cfg(target_arch = "wasm32")]
wasm_bindgen_futures::spawn_local(fut);

#[cfg(not(target_arch = "wasm32"))]
tokio::spawn(fut);
Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No support for tokio::spawn in wasm


Self { control_tx }
}
Expand Down
6 changes: 4 additions & 2 deletions relay_client/src/websocket/inbound.rs
Original file line number Diff line number Diff line change
@@ -1,13 +1,15 @@
#[cfg(not(target_arch = "wasm32"))]
use tokio_tungstenite::tungstenite::Message;
#[cfg(target_arch = "wasm32")]
use tokio_tungstenite_wasm::Message;
use {
crate::ClientError,
relay_rpc::{
domain::MessageId,
rpc::{self, ErrorResponse, Payload, Response, ServiceRequest, SuccessfulResponse},
},
tokio::sync::mpsc::UnboundedSender,
tokio_tungstenite::tungstenite::Message,
};

/// The lower-level inbound RPC request.
///
/// Provides access to the request payload (via [`InboundRequest::data()`]) and
Expand Down
62 changes: 47 additions & 15 deletions relay_client/src/websocket/stream.rs
Original file line number Diff line number Diff line change
@@ -1,3 +1,12 @@
#[cfg(not(target_arch = "wasm32"))]
use tokio_tungstenite::{
connect_async,
tungstenite::{protocol::CloseFrame, Message},
MaybeTlsStream,
WebSocketStream,
};
#[cfg(target_arch = "wasm32")]
use tokio_tungstenite_wasm::{connect as connect_async, CloseFrame, Message, WebSocketStream};
use {
super::{
inbound::InboundRequest,
Expand All @@ -17,26 +26,22 @@ use {
pin::Pin,
task::{Context, Poll},
},
tokio::{
net::TcpStream,
sync::{
mpsc,
mpsc::{UnboundedReceiver, UnboundedSender},
oneshot,
},
},
tokio_tungstenite::{
connect_async,
tungstenite::{protocol::CloseFrame, Message},
MaybeTlsStream,
WebSocketStream,
tokio::sync::{
mpsc,
mpsc::{UnboundedReceiver, UnboundedSender},
oneshot,
},
};

#[cfg(not(target_arch = "wasm32"))]
pub type SocketStream = WebSocketStream<MaybeTlsStream<TcpStream>>;
#[cfg(not(target_arch = "wasm32"))]
use tokio::net::TcpStream;
#[cfg(target_arch = "wasm32")]
pub type SocketStream = WebSocketStream;

/// Opens a connection to the Relay and returns [`ClientStream`] for the
/// connection.
#[cfg(not(target_arch = "wasm32"))]
pub async fn create_stream(request: HttpRequest<()>) -> Result<ClientStream, WebsocketClientError> {
let (socket, _) = connect_async(request)
.await
Expand All @@ -45,6 +50,16 @@ pub async fn create_stream(request: HttpRequest<()>) -> Result<ClientStream, Web
Ok(ClientStream::new(socket))
}

#[cfg(target_arch = "wasm32")]
pub async fn create_stream(request: HttpRequest<()>) -> Result<ClientStream, WebsocketClientError> {
let url = format!("{}", request.uri());
let socket = connect_async(url)
Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

connect function in wasm only support URL

.await
.map_err(WebsocketClientError::ConnectionFailed)?;

Ok(ClientStream::new(socket))
}

/// Possible events produced by the [`ClientStream`].
///
/// The events are produced by polling [`ClientStream`] in a loop.
Expand Down Expand Up @@ -140,6 +155,7 @@ impl ClientStream {
}

/// Closes the connection.
#[cfg(not(target_arch = "wasm32"))]
pub async fn close(&mut self, frame: Option<CloseFrame<'static>>) -> Result<(), ClientError> {
self.close_frame = frame.clone();
self.socket
Expand All @@ -148,6 +164,15 @@ impl ClientStream {
.map_err(|err| WebsocketClientError::ClosingFailed(err).into())
}

#[cfg(target_arch = "wasm32")]
pub async fn close(&mut self, frame: Option<CloseFrame<'static>>) -> Result<(), ClientError> {
self.close_frame = frame.clone();
self.socket
.close()
.await
.map_err(|err| WebsocketClientError::ClosingFailed(err).into())
}

fn parse_inbound(&mut self, result: Result<Message, TransportError>) -> Option<StreamEvent> {
match result {
Ok(message) => match &message {
Expand Down Expand Up @@ -223,7 +248,7 @@ impl ClientStream {
self.close_frame = frame.clone();
Some(StreamEvent::ConnectionClosed(frame.clone()))
}

#[cfg(not(target_arch = "wasm32"))]
_ => None,
},

Expand Down Expand Up @@ -269,6 +294,7 @@ impl Stream for ClientStream {
type Item = StreamEvent;

fn poll_next(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Option<Self::Item>> {
#[cfg(not(target_arch = "wasm32"))]
if self.socket.is_terminated() {
return Poll::Ready(None);
}
Expand Down Expand Up @@ -300,9 +326,15 @@ impl Stream for ClientStream {
}

impl FusedStream for ClientStream {
#[cfg(not(target_arch = "wasm32"))]
fn is_terminated(&self) -> bool {
self.socket.is_terminated()
}

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No access to TcpSocket, so cannot determine if connected

#[cfg(target_arch = "wasm32")]
fn is_terminated(&self) -> bool {
false
}
}

impl Drop for ClientStream {
Expand Down
2 changes: 2 additions & 0 deletions relay_rpc/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,8 @@ rand = "0.8"
chrono = { version = "0.4", default-features = false, features = [
"std",
"clock",
"wasmbind",
"wasm-bindgen",
] }
regex = "1.7"
once_cell = "1.16"
Expand Down
2 changes: 1 addition & 1 deletion relay_rpc/src/jwt.rs
Original file line number Diff line number Diff line change
Expand Up @@ -67,7 +67,7 @@ impl Default for JwtHeader<'_> {
}
}

impl<'a> JwtHeader<'a> {
impl JwtHeader<'_> {
Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Clippy warned me about this

pub fn is_valid(&self) -> bool {
self.typ == JWT_HEADER_TYP && self.alg == JWT_HEADER_ALG
}
Expand Down
38 changes: 38 additions & 0 deletions wasm_websocket_demo/Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
[package]
name = "wasm_websocket_demo"
version = "0.1.0"
edition = "2021"
authors = ["WalletConnect Team"]
license = "Apache-2.0"

[build]
target = "wasm32-unknown-uknown"

[lib]
crate-type = ["cdylib"]

[dependencies]
anyhow = "1"
data-encoding = "2"
futures = "0.3"
rand = "0.8.5"
getrandom = { version = "0.2", features = ["js"] }
wasm-bindgen = { version = "0.2", features = ["serde-serialize"] }
wasm-bindgen-futures = "0.4"
web-sys = { version = "0.3", features = [
"Element", "HtmlElement", "Window",
"WebSocket", "console", "Event",
"Document", "Crypto", "CryptoKey",
"DateTimeValue", "SubtleCrypto", "Performance",
"TimeEvent"
] }
walletconnect_sdk = { path = "../" }
console_error_panic_hook = { version = "0.1" }
web-time = { version = "1", features = ["serde"] }
gloo-timers = { version = "0.3", features = ["futures"] }

[package.metadata.wasm-pack.profile.dev]
wasm-bindgen = { debug-js-glue = true, demangle-name-section = true }
wasm-opt = false


29 changes: 29 additions & 0 deletions wasm_websocket_demo/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
# Wasm Websocket Client Example

## Quickstart

1. Set the PROJECT_ID env:

```shell
export PROJECT_ID="my-project-id"
```

2. Install wasm-pack

```shell
cargo install wasm-pack
```

3. Install basic-http-server

```shell
cargo install basic-http-server
```

4. Build

```shell
wasm-pack build --target web --dev
```

Visit [http://localhost:4000](http://localhost:4000)
Binary file added wasm_websocket_demo/favicon.ico
Binary file not shown.
Loading