From 054b852e2b6a978cd1d6e97dd7c5dc91932df55e Mon Sep 17 00:00:00 2001 From: Yuki Kishimoto Date: Mon, 8 Dec 2025 11:40:19 +0100 Subject: [PATCH 1/3] Update conditional compilation for WASM32 targets Adjust `#[cfg]` attributes to include `target_os = "unknown"` alongside `target_arch = "wasm32"` for improved target specificity. Signed-off-by: Yuki Kishimoto --- Cargo.toml | 4 +-- src/lib.rs | 43 ++++++++++++++++++++------- src/message.rs | 34 ++++++++++----------- src/prelude.rs | 5 +++- src/socket.rs | 80 ++++++++++++++++++++++++++++++++------------------ 5 files changed, 107 insertions(+), 59 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index ad1c454..c02bfc2 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -21,7 +21,7 @@ tor-launch-service = ["tor", "arti-client?/onion-service-service", "dep:tor-hsse futures-util = { version = "0.3", default-features = false, features = ["std", "sink"] } url = { version = "2.5", default-features = false } -[target.'cfg(not(target_arch = "wasm32"))'.dependencies] +[target.'cfg(not(all(target_arch = "wasm32", target_os = "unknown")))'.dependencies] tokio = { version = "1", features = ["net", "time"] } tokio-rustls = { version = "0.26", default-features = false, features = ["ring", "tls12"] } # Required to enable the necessary features for tokio-tungstenite tokio-socks = { version = "0.5", optional = true } @@ -33,7 +33,7 @@ tor-hsservice = { version = "0.28", default-features = false, optional = true } tor-hsrproxy = { version = "0.28", default-features = false, optional = true } tor-rtcompat = { version = "0.28", default-features = false, features = ["rustls", "tokio"], optional = true } -[target.'cfg(target_arch = "wasm32")'.dependencies] +[target.'cfg(all(target_arch = "wasm32", target_os = "unknown"))'.dependencies] async-utility = "0.3" futures = { version = "0.3", default-features = false, features = ["std"] } # TODO: remove this js-sys = "0.3" diff --git a/src/lib.rs b/src/lib.rs index 25fcd55..dcb39c2 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -7,9 +7,15 @@ #![warn(clippy::large_futures)] #![cfg_attr(feature = "default", doc = include_str!("../README.md"))] -#[cfg(all(feature = "socks", not(target_arch = "wasm32")))] +#[cfg(all( + feature = "socks", + not(all(target_arch = "wasm32", target_os = "unknown")) +))] use std::net::SocketAddr; -#[cfg(all(feature = "tor", not(target_arch = "wasm32")))] +#[cfg(all( + feature = "tor", + not(all(target_arch = "wasm32", target_os = "unknown")) +))] use std::path::{Path, PathBuf}; use std::time::Duration; @@ -17,18 +23,18 @@ pub use futures_util; pub use url::{self, Url}; pub mod message; -#[cfg(not(target_arch = "wasm32"))] +#[cfg(not(all(target_arch = "wasm32", target_os = "unknown")))] pub mod native; pub mod prelude; mod socket; -#[cfg(target_arch = "wasm32")] +#[cfg(all(target_arch = "wasm32", target_os = "unknown"))] pub mod wasm; pub use self::message::Message; -#[cfg(not(target_arch = "wasm32"))] +#[cfg(not(all(target_arch = "wasm32", target_os = "unknown")))] pub use self::native::Error; pub use self::socket::WebSocket; -#[cfg(target_arch = "wasm32")] +#[cfg(all(target_arch = "wasm32", target_os = "unknown"))] pub use self::wasm::Error; #[derive(Debug, Clone, Default, PartialEq, Eq, PartialOrd, Ord, Hash)] @@ -37,10 +43,16 @@ pub enum ConnectionMode { #[default] Direct, /// Custom proxy - #[cfg(all(feature = "socks", not(target_arch = "wasm32")))] + #[cfg(all( + feature = "socks", + not(all(target_arch = "wasm32", target_os = "unknown")) + ))] Proxy(SocketAddr), /// Embedded tor client - #[cfg(all(feature = "tor", not(target_arch = "wasm32")))] + #[cfg(all( + feature = "tor", + not(all(target_arch = "wasm32", target_os = "unknown")) + ))] Tor { /// Path for cache and state data /// @@ -58,7 +70,10 @@ impl ConnectionMode { /// Proxy #[inline] - #[cfg(all(feature = "socks", not(target_arch = "wasm32")))] + #[cfg(all( + feature = "socks", + not(all(target_arch = "wasm32", target_os = "unknown")) + ))] pub fn proxy(addr: SocketAddr) -> Self { Self::Proxy(addr) } @@ -68,7 +83,10 @@ impl ConnectionMode { /// This not work on `android` and/or `ios` targets. /// Use [`Connection::tor_with_path`] instead. #[inline] - #[cfg(all(feature = "tor", not(target_arch = "wasm32")))] + #[cfg(all( + feature = "tor", + not(all(target_arch = "wasm32", target_os = "unknown")) + ))] pub fn tor() -> Self { Self::Tor { custom_path: None } } @@ -77,7 +95,10 @@ impl ConnectionMode { /// /// Specify a path where to store data #[inline] - #[cfg(all(feature = "tor", not(target_arch = "wasm32")))] + #[cfg(all( + feature = "tor", + not(all(target_arch = "wasm32", target_os = "unknown")) + ))] pub fn tor_with_path

(data_path: P) -> Self where P: AsRef, diff --git a/src/message.rs b/src/message.rs index 28fe0af..7f3b0f3 100644 --- a/src/message.rs +++ b/src/message.rs @@ -3,14 +3,14 @@ use std::{fmt, str}; -#[cfg(not(target_arch = "wasm32"))] +#[cfg(not(all(target_arch = "wasm32", target_os = "unknown")))] use tokio_tungstenite::tungstenite::protocol::frame::coding::CloseCode; -#[cfg(not(target_arch = "wasm32"))] +#[cfg(not(all(target_arch = "wasm32", target_os = "unknown")))] use tokio_tungstenite::tungstenite::protocol::CloseFrame as TungsteniteCloseFrame; -#[cfg(not(target_arch = "wasm32"))] +#[cfg(not(all(target_arch = "wasm32", target_os = "unknown")))] use tokio_tungstenite::tungstenite::protocol::Message as TungsteniteMessage; -#[cfg(not(target_arch = "wasm32"))] +#[cfg(not(all(target_arch = "wasm32", target_os = "unknown")))] #[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)] pub struct CloseFrame { /// The reason as a code. @@ -29,20 +29,20 @@ pub enum Message { /// A ping message with the specified payload /// /// The payload here must have a length less than 125 bytes - #[cfg(not(target_arch = "wasm32"))] + #[cfg(not(all(target_arch = "wasm32", target_os = "unknown")))] Ping(Vec), /// A pong message with the specified payload /// /// The payload here must have a length less than 125 bytes - #[cfg(not(target_arch = "wasm32"))] + #[cfg(not(all(target_arch = "wasm32", target_os = "unknown")))] Pong(Vec), /// A close message with the optional close frame. - #[cfg(not(target_arch = "wasm32"))] + #[cfg(not(all(target_arch = "wasm32", target_os = "unknown")))] Close(Option), } impl Message { - #[cfg(not(target_arch = "wasm32"))] + #[cfg(not(all(target_arch = "wasm32", target_os = "unknown")))] pub(crate) fn from_native(msg: TungsteniteMessage) -> Self { match msg { TungsteniteMessage::Text(text) => Self::Text(text.to_string()), @@ -62,11 +62,11 @@ impl Message { match self { Self::Text(string) => string.len(), Self::Binary(data) => data.len(), - #[cfg(not(target_arch = "wasm32"))] + #[cfg(not(all(target_arch = "wasm32", target_os = "unknown")))] Self::Ping(data) => data.len(), - #[cfg(not(target_arch = "wasm32"))] + #[cfg(not(all(target_arch = "wasm32", target_os = "unknown")))] Self::Pong(data) => data.len(), - #[cfg(not(target_arch = "wasm32"))] + #[cfg(not(all(target_arch = "wasm32", target_os = "unknown")))] Self::Close(data) => data.as_ref().map(|d| d.reason.len()).unwrap_or(0), } } @@ -82,11 +82,11 @@ impl Message { match self { Self::Text(string) => Some(string.as_str()), Self::Binary(data) => str::from_utf8(data).ok(), - #[cfg(not(target_arch = "wasm32"))] + #[cfg(not(all(target_arch = "wasm32", target_os = "unknown")))] Self::Ping(data) | Self::Pong(data) => str::from_utf8(data).ok(), - #[cfg(not(target_arch = "wasm32"))] + #[cfg(not(all(target_arch = "wasm32", target_os = "unknown")))] Self::Close(None) => Some(""), - #[cfg(not(target_arch = "wasm32"))] + #[cfg(not(all(target_arch = "wasm32", target_os = "unknown")))] Self::Close(Some(frame)) => Some(&frame.reason), } } @@ -102,7 +102,7 @@ impl fmt::Display for Message { } } -#[cfg(not(target_arch = "wasm32"))] +#[cfg(not(all(target_arch = "wasm32", target_os = "unknown")))] impl From for TungsteniteCloseFrame { fn from(frame: CloseFrame) -> Self { Self { @@ -112,7 +112,7 @@ impl From for TungsteniteCloseFrame { } } -#[cfg(not(target_arch = "wasm32"))] +#[cfg(not(all(target_arch = "wasm32", target_os = "unknown")))] impl From for TungsteniteMessage { fn from(msg: Message) -> Self { match msg { @@ -125,7 +125,7 @@ impl From for TungsteniteMessage { } } -#[cfg(not(target_arch = "wasm32"))] +#[cfg(not(all(target_arch = "wasm32", target_os = "unknown")))] impl From for CloseFrame { fn from(frame: TungsteniteCloseFrame) -> Self { Self { diff --git a/src/prelude.rs b/src/prelude.rs index 4e8b739..c4e025a 100644 --- a/src/prelude.rs +++ b/src/prelude.rs @@ -9,6 +9,9 @@ #![doc(hidden)] pub use crate::message::*; -#[cfg(all(feature = "tor", not(target_arch = "wasm32")))] +#[cfg(all( + feature = "tor", + not(all(target_arch = "wasm32", target_os = "unknown")) +))] pub use crate::native::tor::{self, *}; pub use crate::*; diff --git a/src/socket.rs b/src/socket.rs index 8eb03e5..493c20d 100644 --- a/src/socket.rs +++ b/src/socket.rs @@ -6,28 +6,34 @@ use std::pin::Pin; use std::task::{Context, Poll}; use std::time::Duration; -#[cfg(all(feature = "tor", not(target_arch = "wasm32")))] +#[cfg(all( + feature = "tor", + not(all(target_arch = "wasm32", target_os = "unknown")) +))] use arti_client::DataStream; use futures_util::{Sink, Stream}; -#[cfg(not(target_arch = "wasm32"))] +#[cfg(not(all(target_arch = "wasm32", target_os = "unknown")))] use tokio::net::TcpStream; -#[cfg(not(target_arch = "wasm32"))] +#[cfg(not(all(target_arch = "wasm32", target_os = "unknown")))] use tokio_tungstenite::{MaybeTlsStream, WebSocketStream}; use url::Url; -#[cfg(target_arch = "wasm32")] +#[cfg(all(target_arch = "wasm32", target_os = "unknown"))] use crate::wasm::WsStream; use crate::{ConnectionMode, Error, Message}; -#[cfg(not(target_arch = "wasm32"))] +#[cfg(not(all(target_arch = "wasm32", target_os = "unknown")))] type WsStream = WebSocketStream>; pub enum WebSocket { - #[cfg(not(target_arch = "wasm32"))] + #[cfg(not(all(target_arch = "wasm32", target_os = "unknown")))] Tokio(Box>), - #[cfg(all(feature = "tor", not(target_arch = "wasm32")))] + #[cfg(all( + feature = "tor", + not(all(target_arch = "wasm32", target_os = "unknown")) + ))] Tor(Box>), - #[cfg(target_arch = "wasm32")] + #[cfg(all(target_arch = "wasm32", target_os = "unknown"))] Wasm(WsStream), } @@ -37,10 +43,10 @@ impl WebSocket { _mode: &ConnectionMode, timeout: Duration, ) -> Result { - #[cfg(not(target_arch = "wasm32"))] + #[cfg(not(all(target_arch = "wasm32", target_os = "unknown")))] let socket: WebSocket = crate::native::connect(url, _mode, timeout).await?; - #[cfg(target_arch = "wasm32")] + #[cfg(all(target_arch = "wasm32", target_os = "unknown"))] let socket: WebSocket = crate::wasm::connect(url, timeout).await?; Ok(socket) @@ -52,48 +58,60 @@ impl Sink for WebSocket { fn poll_ready(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll> { match self.deref_mut() { - #[cfg(not(target_arch = "wasm32"))] + #[cfg(not(all(target_arch = "wasm32", target_os = "unknown")))] Self::Tokio(s) => Pin::new(s.as_mut()).poll_ready(cx).map_err(Into::into), - #[cfg(all(feature = "tor", not(target_arch = "wasm32")))] + #[cfg(all( + feature = "tor", + not(all(target_arch = "wasm32", target_os = "unknown")) + ))] Self::Tor(s) => Pin::new(s.as_mut()).poll_ready(cx).map_err(Into::into), - #[cfg(target_arch = "wasm32")] + #[cfg(all(target_arch = "wasm32", target_os = "unknown"))] Self::Wasm(s) => Pin::new(s).poll_ready(cx), } } fn start_send(mut self: Pin<&mut Self>, item: Message) -> Result<(), Self::Error> { match self.deref_mut() { - #[cfg(not(target_arch = "wasm32"))] + #[cfg(not(all(target_arch = "wasm32", target_os = "unknown")))] Self::Tokio(s) => Pin::new(s.as_mut()) .start_send(item.into()) .map_err(Into::into), - #[cfg(all(feature = "tor", not(target_arch = "wasm32")))] + #[cfg(all( + feature = "tor", + not(all(target_arch = "wasm32", target_os = "unknown")) + ))] Self::Tor(s) => Pin::new(s.as_mut()) .start_send(item.into()) .map_err(Into::into), - #[cfg(target_arch = "wasm32")] + #[cfg(all(target_arch = "wasm32", target_os = "unknown"))] Self::Wasm(s) => Pin::new(s).start_send(item), } } fn poll_flush(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll> { match self.deref_mut() { - #[cfg(not(target_arch = "wasm32"))] + #[cfg(not(all(target_arch = "wasm32", target_os = "unknown")))] Self::Tokio(s) => Pin::new(s.as_mut()).poll_flush(cx).map_err(Into::into), - #[cfg(all(feature = "tor", not(target_arch = "wasm32")))] + #[cfg(all( + feature = "tor", + not(all(target_arch = "wasm32", target_os = "unknown")) + ))] Self::Tor(s) => Pin::new(s.as_mut()).poll_flush(cx).map_err(Into::into), - #[cfg(target_arch = "wasm32")] + #[cfg(all(target_arch = "wasm32", target_os = "unknown"))] Self::Wasm(s) => Pin::new(s).poll_flush(cx), } } fn poll_close(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll> { match self.deref_mut() { - #[cfg(not(target_arch = "wasm32"))] + #[cfg(not(all(target_arch = "wasm32", target_os = "unknown")))] Self::Tokio(s) => Pin::new(s.as_mut()).poll_close(cx).map_err(Into::into), - #[cfg(all(feature = "tor", not(target_arch = "wasm32")))] + #[cfg(all( + feature = "tor", + not(all(target_arch = "wasm32", target_os = "unknown")) + ))] Self::Tor(s) => Pin::new(s.as_mut()).poll_close(cx).map_err(Into::into), - #[cfg(target_arch = "wasm32")] + #[cfg(all(target_arch = "wasm32", target_os = "unknown"))] Self::Wasm(s) => Pin::new(s).poll_close(cx).map_err(Into::into), } } @@ -104,28 +122,34 @@ impl Stream for WebSocket { fn poll_next(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll> { match self.deref_mut() { - #[cfg(not(target_arch = "wasm32"))] + #[cfg(not(all(target_arch = "wasm32", target_os = "unknown")))] Self::Tokio(s) => Pin::new(s) .poll_next(cx) .map(|i| i.map(|res| res.map(Message::from_native))) .map_err(Into::into), - #[cfg(all(feature = "tor", not(target_arch = "wasm32")))] + #[cfg(all( + feature = "tor", + not(all(target_arch = "wasm32", target_os = "unknown")) + ))] Self::Tor(s) => Pin::new(s) .poll_next(cx) .map(|i| i.map(|res| res.map(Message::from_native))) .map_err(Into::into), - #[cfg(target_arch = "wasm32")] + #[cfg(all(target_arch = "wasm32", target_os = "unknown"))] Self::Wasm(s) => Pin::new(s).poll_next(cx).map_err(Into::into), } } fn size_hint(&self) -> (usize, Option) { match self { - #[cfg(not(target_arch = "wasm32"))] + #[cfg(not(all(target_arch = "wasm32", target_os = "unknown")))] Self::Tokio(s) => s.size_hint(), - #[cfg(all(feature = "tor", not(target_arch = "wasm32")))] + #[cfg(all( + feature = "tor", + not(all(target_arch = "wasm32", target_os = "unknown")) + ))] Self::Tor(s) => s.size_hint(), - #[cfg(target_arch = "wasm32")] + #[cfg(all(target_arch = "wasm32", target_os = "unknown"))] Self::Wasm(s) => s.size_hint(), } } From f3716b3f02153c9b9ee5d04ff254f4862d7bb6f6 Mon Sep 17 00:00:00 2001 From: Yuki Kishimoto Date: Mon, 8 Dec 2025 11:41:28 +0100 Subject: [PATCH 2/3] Bump tokio to 1.42.1 Signed-off-by: Yuki Kishimoto --- Cargo.lock | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 0ffbafd..c77f122 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3176,9 +3176,9 @@ dependencies = [ [[package]] name = "tokio" -version = "1.40.0" +version = "1.42.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e2b070231665d27ad9ec9b8df639893f46727666c6767db40317fbe920a5d998" +checksum = "2209a14885b74764cce87ffa777ffa1b8ce81a3f3166c6f886b83337fe7e077f" dependencies = [ "backtrace", "bytes", From 9697b3b733523f95a316389575eb95ae811d34cb Mon Sep 17 00:00:00 2001 From: Yuki Kishimoto Date: Mon, 8 Dec 2025 11:55:16 +0100 Subject: [PATCH 3/3] Add CI matrix for WASM and conditional WASI SDK setup Extend GitHub Actions workflow by adding a matrix for WASM targets and configuring conditional WASI SDK installation for `wasm32-wasip2`. Adjust `rust-toolchain.toml` to include both `wasm32-wasip2` and `wasm32-unknown-unknown` targets. Signed-off-by: Yuki Kishimoto --- .github/workflows/ci.yml | 44 ++++++++++++++++++++++++++++++++++++---- rust-toolchain.toml | 7 +++++-- 2 files changed, 45 insertions(+), 6 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 242c3a5..df8e392 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -22,8 +22,44 @@ jobs: check: name: Check runs-on: ubuntu-latest + strategy: + matrix: + crate: + - --features tor + - --features socks + - --target wasm32-wasip2 + - --target wasm32-unknown-unknown steps: - - name: Checkout - uses: actions/checkout@v4 - - name: Check - run: make check + - name: Checkout + uses: actions/checkout@v4 + + - name: Install deps + run: sudo apt update && sudo apt install -y libdbus-1-dev pkg-config + + - name: Install WASI SDK + if: "contains(matrix.crate, 'wasm32-wasip2')" + run: | + wget https://github.com/WebAssembly/wasi-sdk/releases/download/wasi-sdk-29/wasi-sdk-29.0-x86_64-linux.tar.gz + tar xvf wasi-sdk-29.0-x86_64-linux.tar.gz + + WASI_SDK="$(pwd)/wasi-sdk-29.0-x86_64-linux" + + echo "WASI_SDK=$WASI_SDK" >> $GITHUB_ENV + echo "CC_wasm32_wasip2=$WASI_SDK/bin/clang" >> $GITHUB_ENV + echo "AR_wasm32_wasip2=$WASI_SDK/bin/llvm-ar" >> $GITHUB_ENV + echo "CFLAGS_wasm32_wasip2=--sysroot=$WASI_SDK/share/wasi-sysroot" >> $GITHUB_ENV + + - name: Rust Cache + uses: Swatinem/rust-cache@v2.7.8 + with: + key: ${{ matrix.crate }} + + - name: Check + run: cargo check ${{ matrix.crate }} + + - name: Clippy + run: cargo clippy ${{ matrix.crate }} -- -D warnings + + - name: Test + if: "!contains(matrix.crate, 'wasm32-unknown-unknown')" + run: cargo test ${{ matrix.crate }} diff --git a/rust-toolchain.toml b/rust-toolchain.toml index dd5a15f..5b02e63 100644 --- a/rust-toolchain.toml +++ b/rust-toolchain.toml @@ -1,5 +1,8 @@ [toolchain] channel = "stable" profile = "minimal" -components = ["clippy", "rust-docs", "rust-src", "rustc", "rustfmt"] -targets = ["wasm32-unknown-unknown"] \ No newline at end of file +components = ["clippy", "rust-docs", "rustfmt"] +targets = [ + "wasm32-wasip2", # WASI + "wasm32-unknown-unknown", # Browser and JS environments +]