core: wasm groundwork — target-split deps + gate native-only transports

First real step toward the web/WASM target. The dependency graph is now
target-conditional: the legacy transports' OS-socket deps (tokio net, mdns-sd,
webrtc) + UniFFI + native reqwest live under cfg(not(wasm32)); wasm32 gets a
minimal tokio (sync/macros), the reqwest fetch backend, wasm-bindgen-futures, and
iroh with default-features off. The SSE/RTC/LAN transport modules are gated to
native (they need OS sockets). getrandom's wasm rng is wired (build-cfg for 0.4 +
the js-feature alias trick for the transitive 0.2).

Effect: `cargo build --target wasm32-unknown-unknown --features iroh-transport`
goes from "deps won't compile at all" to 15 well-understood errors, isolated to
four structural items: UniFFI (decouple the FFI layer for wasm), iroh's wasm
feature set (presets/gossip-net), the tokio runtime (→ spawn_local + ?Send), and
reqwest .timeout(). Native builds (default + iroh + examples) are byte-unaffected
— all verified clean.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
2026-06-23 16:28:15 -05:00
parent d855bbbfa7
commit 55340fe8f6
3 changed files with 41 additions and 12 deletions

2
core/Cargo.lock generated
View File

@@ -4852,6 +4852,7 @@ dependencies = [
"bytes",
"chacha20poly1305",
"futures-util",
"getrandom 0.2.17",
"getrandom 0.4.3",
"iroh",
"iroh-blobs",
@@ -4863,6 +4864,7 @@ dependencies = [
"sha2 0.11.0",
"tokio",
"uniffi",
"wasm-bindgen-futures",
"webrtc",
]

View File

@@ -15,31 +15,50 @@ name = "tethercore"
name = "uniffi-bindgen"
path = "src/bin/uniffi-bindgen.rs"
# Common, wasm-safe dependencies (no OS sockets / threads).
[dependencies]
uniffi = { version = "0.28", features = ["cli"] }
serde = { version = "1", features = ["derive"] }
serde_json = "1"
reqwest = { version = "0.12", default-features = false, features = ["rustls-tls", "stream", "json"] }
tokio = { version = "1", features = ["rt-multi-thread", "time", "sync", "macros", "net", "io-util"] }
futures-util = "0.3"
webrtc = "0.17.1"
mdns-sd = "0.20.0"
sha2 = "0.11.0"
bytes = "1.12.0"
chacha20poly1305 = "0.10.1"
base64 = "0.22.1"
getrandom = "0.4.3"
# M4: the iroh Mesh, behind the `iroh-transport` feature (off by default, so the
# UniFFI/xcframework/agent builds are untouched). When on, IrohTransport replaces
# the hand-rolled SSE/RTC/LAN stack.
iroh = { version = "1", optional = true }
iroh-gossip = { version = "0.101", optional = true }
iroh-blobs = { version = "0.103", optional = true }
[features]
default = []
iroh-transport = ["dep:iroh", "dep:iroh-gossip", "dep:iroh-blobs"]
# ── Native (desktop/mobile): legacy transports + UniFFI + full tokio ──
# The hand-rolled SSE/RTC/LAN stack and its OS-socket deps live here; none of it
# compiles to wasm. iroh (behind the feature) takes full default features.
[target.'cfg(not(target_arch = "wasm32"))'.dependencies]
uniffi = { version = "0.28", features = ["cli"] }
reqwest = { version = "0.12", default-features = false, features = ["rustls-tls", "stream", "json"] }
tokio = { version = "1", features = ["rt-multi-thread", "time", "sync", "macros", "net", "io-util"] }
webrtc = "0.17.1"
mdns-sd = "0.20.0"
getrandom = "0.4.3"
iroh = { version = "1", optional = true }
iroh-gossip = { version = "0.101", optional = true }
iroh-blobs = { version = "0.103", optional = true }
# ── wasm32 (browser): iroh-only, no OS sockets / UniFFI ──
# Browser iroh is relay-only; reqwest uses fetch; the runtime is the JS event
# loop (wasm-bindgen-futures) instead of a tokio runtime.
[target.'cfg(target_arch = "wasm32")'.dependencies]
tokio = { version = "1", features = ["sync", "macros"] }
reqwest = { version = "0.12", default-features = false, features = ["json"] }
# wasm rng: getrandom 0.4 uses a build-cfg (--cfg getrandom_backend="wasm_js"),
# but a transitive getrandom 0.2 needs the `js` *feature* — enable it via the
# rename-alias trick (cargo unifies it with the transitive copy).
getrandom = "0.4.3"
getrandom_02 = { package = "getrandom", version = "0.2", features = ["js"] }
wasm-bindgen-futures = "0.4"
iroh = { version = "1", optional = true, default-features = false }
iroh-gossip = { version = "0.101", optional = true, default-features = false }
iroh-blobs = { version = "0.103", optional = true, default-features = false }
[dev-dependencies]
# Iroh spikes (examples/iroh_*.rs) link iroh directly regardless of the feature.
iroh = "1"

View File

@@ -32,7 +32,10 @@ use tokio::sync::mpsc::{UnboundedReceiver, UnboundedSender};
mod crypto;
#[cfg(feature = "iroh-transport")]
mod iroh;
// Legacy transports use OS sockets (tokio net / mdns / webrtc) — native only.
#[cfg(not(target_arch = "wasm32"))]
mod lan;
#[cfg(not(target_arch = "wasm32"))]
mod rtc;
use crypto::Crypto;
@@ -698,14 +701,17 @@ impl Dedup {
// ---------------------------------------------------------------------------
// SSE transport — the reliable floor: server relay over text/event-stream.
// Native only (reqwest streaming / bytes_stream isn't on the wasm fetch backend).
// ---------------------------------------------------------------------------
#[cfg(not(target_arch = "wasm32"))]
struct SseTransport {
cfg: Config,
http: reqwest::Client,
events: EventTx,
}
#[cfg(not(target_arch = "wasm32"))]
impl Transport for SseTransport {
fn name(&self) -> &'static str {
"sse"
@@ -757,6 +763,7 @@ impl Transport for SseTransport {
}
}
#[cfg(not(target_arch = "wasm32"))]
impl SseTransport {
async fn stream_once(&self, running: &AtomicBool) -> Result<(), String> {
let url = format!(
@@ -816,6 +823,7 @@ impl SseTransport {
}
}
#[cfg(not(target_arch = "wasm32"))]
async fn post(http: &reqwest::Client, cfg: &Config, msg: &Message) -> Result<(), String> {
let url = format!("{}/api/send", cfg.server.trim_end_matches('/'));
let kind = if msg.kind.is_empty() { "clipboard" } else { msg.kind.as_str() };