Using iroh on Wasm Browser Platforms: Complete Configuration Guide
Iroh compiles to WebAssembly for browser targets using the cfg(wasm_browser) flag to disable IP transports and enable relay-only networking with wasm-compatible logging and testing infrastructure.
The iroh networking library from n0-computer maintains a single codebase that targets both native operating systems and WebAssembly browsers. Using iroh on wasm_browser platforms requires understanding how conditional compilation modifies the transport stack, logging system, and test harnesses to accommodate browser sandbox constraints. This guide covers the specific implementation details found in the source code and provides practical examples for building and running iroh in wasm32-unknown-unknown environments.
How the Transport Stack Adapts to wasm_browser
The core transport manager resides in iroh/src/socket/transports.rs, where the Transports struct conditionally exposes IP networking capabilities based on the wasm_browser configuration flag.
When compiling without the flag, the crate builds both IP (IPv4/IPv6) transports alongside relay and custom transports. However, when cfg(wasm_browser) is defined, the IP transport module is completely omitted using #[cfg(not(wasm_browser))], which removes the ip: IpTransports field from the struct. This architectural decision reflects the browser security model, which prohibits direct socket access.
The remaining transport stack consists solely of relay-based connectivity and any custom transports that avoid OS-specific APIs. This means browser-based endpoints cannot open direct peer-to-peer connections and must route all traffic through relay nodes.
Wasm-Compatible Logging Configuration
Standard tracing_subscriber::fmt logging crashes in wasm browser contexts because it attempts to write to stdout. Instead, iroh integrates the wasm_tracing crate for browser-compatible diagnostics.
In iroh/tests/integration.rs, the helper function setup_logging() demonstrates the proper initialization pattern:
- Create a
WasmLayerConfiginstance - Set the maximum log level using
set_max_level() - Install the layer as the global default via
wasm_tracing::set_as_global_default_with_config()
This configuration routes diagnostic output to the browser console rather than standard streams, enabling debugging in web-based environments.
Testing Infrastructure for Browser Targets
The integration test suite in iroh/tests/integration.rs supports dual-target compilation through conditional attributes. When #[cfg(wasm_browser)] is active, the test harness switches from tokio::test to wasm_bindgen_test::wasm_bindgen_test.
This switch allows the same async test code to execute in both native Tokio runtimes and browser JavaScript environments. The comment in the source file explains this "either-or" setup, ensuring CI pipelines can validate functionality across platforms without maintaining separate test files.
Build Configuration for wasm32-unknown-unknown
The wasm_browser flag is not a Cargo feature but a compiler configuration supplied via --cfg wasm_browser when targeting wasm32-unknown-unknown. Cargo automatically defines cfg(target_arch = "wasm32"), and the repository adds the alias during the build process.
Standard build commands for browser deployment:
rustup target add wasm32-unknown-unknown
cargo build --target wasm32-unknown-unknown --features=web
The web feature activates wasm-bindgen and wasm-tracing as dev-dependencies, pulling in the necessary glue code for browser interop. No changes to Cargo.toml are required to enable the wasm_browser configuration itself.
Runtime Behavior and Relay Connectivity
Because IP transports are unavailable in the browser sandbox, endpoints establish connectivity exclusively through relays. The relay implementation in iroh-relay/src/server.rs provides both client and server logic for public relay nodes that bridge browser clients.
Endpoint discovery functions through the DNS-based PKARR resolver located in iroh-dns/src/pkarr.rs, which allows browsers to locate relay URLs without direct DNS socket access. Connections maintain state using QUIC via the noq library, with RelayUrl types from iroh-base/src/relay_url.rs defining the addressing scheme throughout the wasm transport path.
Custom transports remain supported in wasm builds as long as they restrict themselves to Web APIs rather than native OS sockets.
Practical Implementation Examples
Creating a browser-compatible endpoint requires initializing wasm-specific logging and binding through relay presets:
use iroh::{Endpoint, RelayMode};
use iroh::endpoint::presets;
use wasm_tracing::WasmLayerConfig;
use tracing::Level;
// Initialize browser console logging
fn init_logging() {
let mut cfg = WasmLayerConfig::new();
cfg.set_max_level(Level::TRACE);
wasm_tracing::set_as_global_default_with_config(cfg)
.expect("logging init");
}
// Build endpoint using only relay transport
async fn make_endpoint() -> Result<Endpoint, iroh::Error> {
init_logging();
Endpoint::builder(presets::N0)
.relay_mode(RelayMode::Staging)
.bind()
.await
}
Testing bidirectional streams in the browser harness:
const ECHO_ALPN: &[u8] = b"echo";
#[wasm_bindgen_test::wasm_bindgen_test]
async fn wasm_echo_demo() {
let client = make_endpoint().await.unwrap();
let server = make_endpoint().await.unwrap();
// Wait for relay connection establishment
client.metrics()
.wait_for_online()
.await
.expect("server online");
let conn = client.connect(server.id(), ECHO_ALPN).await.unwrap();
let (mut send, mut recv) = conn.open_bi().await.unwrap();
send.write_all(b"Hello, wasm!").await.unwrap();
send.finish().unwrap();
let reply = recv.read_to_end(1024).await.unwrap();
assert_eq!(reply, b"Hello, wasm!");
}
Summary
- The
cfg(wasm_browser)flag iniroh/src/socket/transports.rsgates theIpTransportsfield, restricting browser builds to relay-only connectivity. - Browser environments require
wasm_tracingwithWasmLayerConfiginstead of standardtracing_subscriberloggers. - Tests switch from
tokio::testtowasm_bindgen_test::wasm_bindgen_testwhen the wasm_browser configuration is active. - Build commands target
wasm32-unknown-unknownwith--features=webto include wasm-bindgen dependencies. - Runtime connectivity depends on the relay implementation in
iroh-relay/src/server.rsand PKARR resolution iniroh-dns/src/pkarr.rs.
Frequently Asked Questions
How do I enable the wasm_browser configuration in my Cargo.toml?
You do not enable it through Cargo features. The wasm_browser flag is passed via compiler configuration when building for the wasm target. Use cargo build --target wasm32-unknown-unknown --features=web where the build script supplies --cfg wasm_browser. The web feature only pulls in necessary dependencies like wasm-bindgen and wasm-tracing.
Why can't my wasm browser endpoint connect directly to IP addresses?
Browser WebAssembly runs inside a security sandbox that prohibits raw socket access. In iroh/src/socket/transports.rs, the #[cfg(not(wasm_browser))] gate removes the IpTransports field entirely, forcing all traffic through relay nodes that use WebRTC or WebTransport protocols compatible with browser security models.
Can I use custom transports with iroh in the browser?
Yes, custom transports compile for wasm targets provided they do not depend on OS-specific socket APIs. The transport manager in iroh/src/socket/transports.rs supports pluggable transports alongside the default relay transport when cfg(wasm_browser) is defined.
How do I run integration tests in a headless browser?
Compile and run tests using cargo test --target wasm32-unknown-unknown --features=web. This invokes wasm_bindgen_test which can run in Node.js or browsers via wasm-pack. The test harness automatically handles the asynchronous runtime differences between native Tokio and browser environments.
Have a question about this repo?
These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →