wasm: gate UniFFI + reqwest timeout + enable gossip net (15 → 3 errors)

- UniFFI is the native FFI surface; cfg_attr all of it (setup_scaffolding, Record
  derives, export/callback_interface/constructor) to cfg(not(wasm32)). The wasm
  binding layer will be wasm-bindgen instead.
- rendezvous_register: reqwest Builder::timeout isn't on the wasm fetch backend —
  cfg it (browser bounds the request itself).
- iroh-gossip on wasm needs default-features=false + features=["net"] for the net
  module (Gossip/GossipSender/api).

wasm32 build now down to the two genuinely structural blockers: the tokio
multi-thread runtime (new_multi_thread → spawn_local) and the iroh wasm endpoint
(presets::N0 is native-only — pulls ring/portmapper). Native unaffected (verified).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
2026-06-23 17:08:54 -05:00
parent 9febee6a60
commit 3b1244ad06
3 changed files with 22 additions and 11 deletions

View File

@@ -56,7 +56,7 @@ 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-gossip = { version = "0.101", optional = true, default-features = false, features = ["net"] }
iroh-blobs = { version = "0.103", optional = true, default-features = false }
[dev-dependencies]

View File

@@ -99,11 +99,15 @@ async fn rendezvous_register(server: &str, room: &str, my_id: &str) -> Vec<Endpo
}
let url = format!("{}/rendezvous/register", server.trim_end_matches('/'));
let body = serde_json::json!({ "room": room, "id": my_id });
// Bounded — a stuck rendezvous must never wedge the transport.
// Bounded — a stuck rendezvous must never wedge the transport. On wasm the
// fetch backend has no Builder::timeout (the browser bounds it instead).
#[cfg(not(target_arch = "wasm32"))]
let client = reqwest::Client::builder()
.timeout(std::time::Duration::from_secs(8))
.build()
.unwrap_or_default();
#[cfg(target_arch = "wasm32")]
let client = reqwest::Client::new();
let mut req = client.post(&url).json(&body);
// Optional bearer token, matching the rendezvous's TETHER_RENDEZVOUS_TOKEN.
if let Ok(token) = std::env::var("TETHER_RENDEZVOUS_TOKEN") {

View File

@@ -40,11 +40,15 @@ mod rtc;
use crypto::Crypto;
// UniFFI is the native FFI surface (Swift/Kotlin). On wasm the bindings come
// from wasm-bindgen instead, so gate all of it out for wasm32.
#[cfg(not(target_arch = "wasm32"))]
uniffi::setup_scaffolding!();
/// 1:1 with the Go server `Message`, minus the RTC `signal` blob.
/// `type` is renamed to `kind` (reserved word in the target languages).
#[derive(Clone, Debug, Serialize, Deserialize, uniffi::Record)]
#[derive(Clone, Debug, Serialize, Deserialize)]
#[cfg_attr(not(target_arch = "wasm32"), derive(uniffi::Record))]
pub struct Message {
#[serde(rename = "type", default)]
pub kind: String,
@@ -67,7 +71,7 @@ pub struct Message {
/// Implemented on the foreign side (Swift/Kotlin) or natively (desktop agent).
/// Callbacks fire on the engine's runtime thread — marshal to the UI thread
/// on the consumer side.
#[uniffi::export(callback_interface)]
#[cfg_attr(not(target_arch = "wasm32"), uniffi::export(callback_interface))]
pub trait MessageHandler: Send + Sync {
fn on_message(&self, msg: Message);
fn on_status(&self, connected: bool);
@@ -78,7 +82,8 @@ pub trait MessageHandler: Send + Sync {
/// A tether device discovered on the local network (mDNS). Surfaced to the UI
/// for an AirDrop-style "nearby devices" list. `room` lets the UI offer to pair
/// (join that device's room).
#[derive(Clone, Debug, uniffi::Record)]
#[derive(Clone, Debug)]
#[cfg_attr(not(target_arch = "wasm32"), derive(uniffi::Record))]
pub struct Peer {
pub id: String, // stable device id (`from`)
pub name: String, // friendly name, e.g. "Patrick's MacBook"
@@ -94,7 +99,8 @@ type Discovered = Arc<Mutex<HashMap<String, (Peer, Instant)>>>;
/// A delivery acknowledgement: device `from` (named `name`) received a clipboard
/// whose text matches `text`. Lets the sender show "delivered to <name>" instead
/// of asking. Auto-sent by the engine on every inbound clipboard.
#[derive(Clone, Debug, uniffi::Record)]
#[derive(Clone, Debug)]
#[cfg_attr(not(target_arch = "wasm32"), derive(uniffi::Record))]
pub struct Receipt {
pub from: String, // device that received it
pub name: String, // its friendly name
@@ -136,7 +142,7 @@ fn directly_covered(direct: &DirectSet, present: &PresentSet) -> bool {
/// Defined once here and exported so every client (Swift/Kotlin/agent) derives
/// the SAME room — that's what puts all of one account's devices together across
/// LAN, relay, and RTC. Empty account → empty (caller falls back to a device id).
#[uniffi::export]
#[cfg_attr(not(target_arch = "wasm32"), uniffi::export)]
pub fn room_for_account(account: String) -> String {
use sha2::{Digest, Sha256};
if account.is_empty() {
@@ -194,7 +200,8 @@ pub(crate) struct IncomingFile {
}
/// Progress of an in-flight file transfer, for the UI.
#[derive(Clone, Debug, uniffi::Record)]
#[derive(Clone, Debug)]
#[cfg_attr(not(target_arch = "wasm32"), derive(uniffi::Record))]
pub struct Transfer {
pub name: String,
pub received: u64, // bytes transferred so far
@@ -320,7 +327,7 @@ trait Transport: Send + Sync {
fn send_file(&self, _rt: &Handle, _id: String, _name: String, _mime: String, _data: Vec<u8>) {}
}
#[derive(uniffi::Object)]
#[cfg_attr(not(target_arch = "wasm32"), derive(uniffi::Object))]
pub struct Engine {
cfg: Config,
rt: Arc<tokio::runtime::Runtime>,
@@ -349,7 +356,7 @@ pub struct Engine {
iroh_id: Arc<Mutex<Option<String>>>,
}
#[uniffi::export]
#[cfg_attr(not(target_arch = "wasm32"), uniffi::export)]
impl Engine {
/// `server` is the bus base URL, e.g. "http://192.168.11.233:8765".
/// `from` is this device's stable id (echo suppression); `source` a label.
@@ -357,7 +364,7 @@ impl Engine {
/// `account` is a shared identity (Apple/Google `sub`); devices with the
/// same non-empty account auto-pair on the LAN regardless of room. Pass ""
/// when signed out.
#[uniffi::constructor]
#[cfg_attr(not(target_arch = "wasm32"), uniffi::constructor)]
pub fn new(
server: String,
room: String,