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/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",
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/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
+]
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