Overview
The transport layer provides a runtime-agnostic abstraction for network connections. It handles raw byte transmission without knowledge of WhatsApp’s protocol framing. The transport system consists of two main traits:- Transport - Represents an active connection for sending/receiving raw bytes
- TransportFactory - Creates new transport instances and event streams
Transport Trait
TheTransport trait represents an active network connection as a simple byte pipe.
MaybeSendSync is Send + Sync on native targets and carries no bounds on wasm32. This means Arc<dyn Transport> remains thread-safe on native, while implementations backed by !Send JS handles (e.g. a browser WebSocket) compile on the wasm port without wrapping.Methods
send
Sends raw bytes through the transport. The caller is responsible for any protocol framing.data- Raw bytes to send
Ok(())on successErr(anyhow::Error)on failure
disconnect
Gracefully closes the connection.resource_report
Best-effort per-session footprint of this transport: read/write framing buffers plus a TLS/noise session-state estimate. Defaulted toNone (“not reported”) — a transport that can introspect its buffers overrides it.
Some(TransportResourceReport)with any subset ofread_buffer_bytes,write_buffer_bytes,tls_state_bytesfilled in (eachOption<u64>)None(default) if the transport doesn’t report
Client::resource_report(). The bundled Tokio WebSocket transport (below) overrides this with static estimates, since tokio-websockets and rustls don’t expose their live buffer sizes.
TransportFactory Trait
Creates new transport instances and associated event streams.Like
Transport, TransportFactory uses MaybeSendSync so that factory implementations holding !Send JS state compile on wasm32. On native the bound is Send + Sync as before.Methods
create_transport
Establishes a new connection and returns both the transport handle and an event receiver.Arc<dyn Transport>- The transport instance for sending dataasync_channel::Receiver<TransportEvent>- Stream of transport events
TransportEvent
Events produced by the transport layer:Since v0.6
Disconnected carries a DisconnectReason instead of being a unit variant (a breaking change for custom transports — update your match arms and the value you emit). It surfaces why the socket closed in logs, so server-initiated closes can be told apart from local shutdowns.Event Types
Connected
Emitted immediately after successful connection establishment.DataReceived
Emitted when raw data is received from the server.bytes: Bytes- Raw data received (from thebytescrate)
Disconnected
Emitted when the connection is closed (gracefully or due to error). The attachedDisconnectReason says why:
DisconnectReason implements Display for human-readable logging. Custom transports should emit the most specific variant they can determine.
is_clean_shutdown() tells a benign, server-initiated stream recycle (the normal WhatsApp reconnect path) apart from a genuine transport failure, so consumers of events::Disconnected don’t have to parse logs to classify a disconnect. It’s deliberately conservative — anything ambiguous returns false (treated as a real failure):
Tokio WebSocket transport
The default transport implementation usingtokio-websockets for async WebSocket connections.
Features
- Async I/O - Built on Tokio runtime
- TLS support - Uses rustls with webpki-roots for certificate validation
- Split architecture - Separate read/write paths for efficiency
- Generic over streams - Works with any
AsyncRead + AsyncWritestream type - Automatic reconnection - Handled by higher-level Client code
- Development mode - Optional
danger-skip-tls-verifyfeature
TokioWebSocketTransportFactory
The default factory handles DNS resolution, TCP connection, and TLS. For custom connection logic, usefrom_websocket directly.
with_connector
Connector instead of the built-in default. This is the primary extension point for custom TLS configuration — for example, adding custom CA certificates or client certificates.
For full proxy support, implement TransportFactory directly and use from_websocket instead.
Parameters:
connector- Atokio_websockets::Connector(re-exported aswhatsapp_rust::transport::Connector)
Use
default_tls_connector() to inspect or replicate the default TLS configuration as a starting point before customizing.default_tls_connector
TokioWebSocketTransportFactory. Uses rustls with webpki_roots for certificate validation and ring as the crypto provider.
This is useful as a starting point when you need to inspect or replicate the default TLS configuration before customizing it via with_connector.
Usage with Bot builder
from_websocket
Wraps an already-upgradedWebSocketStream into a Transport + event channel. This is useful when you need custom connection strategies such as IPv4 preference, TCP keepalive tuning, or connecting through a proxy.
ws- An already-connectedWebSocketStreamover any async stream type
Arc<dyn Transport>- The transport instance for sending dataasync_channel::Receiver<TransportEvent>- Stream of transport events
Connected event before the read pump starts — ensuring it always precedes any DataReceived events.
Example — IPv4-only connection with TCP keepalive:
TokioWebSocketTransportFactory itself delegates to from_websocket internally. Use the factory when the default DNS/TCP/TLS behavior is sufficient, and from_websocket when you need full control over the connection.Proxy support
The transport layer provides two approaches for proxy support, depending on your needs: Option 1: ImplementTransportFactory directly — for full control over the connection (SOCKS5, HTTP CONNECT, etc.). Establish a WebSocket through the proxy yourself, then wrap it with from_websocket:
with_connector — for custom TLS only (no proxy routing). The factory still handles DNS and TCP, but you control the TLS layer:
TLS configuration
By default, the transport validates TLS certificates using webpki-roots:Development mode (skip TLS verification)
Enable thedanger-skip-tls-verify feature to disable certificate verification:
Connection settings
The default WebSocket URL is:The
Client wraps create_transport() in a 20-second timeout matching WhatsApp Web’s connect timeout. The transport itself does not enforce this timeout — it is applied at the client layer. If you use a custom transport, be aware that the client will abort the connection attempt after 20 seconds regardless of the transport’s own timeout behavior.WsTransport resource report
WsTransport overrides Transport::resource_report with static, documented estimates rather than a live measurement, since tokio-websockets and rustls don’t surface their buffer sizes: read_buffer_bytes: 16 KiB, write_buffer_bytes: 16 KiB, tls_state_bytes: 32 KiB (record buffers + key schedule for one TLS session). These give a realistic order-of-magnitude for the transport’s per-session contribution — tens of KiB — rather than an exact figure.
Internal architecture
The transport is generic over anyAsyncRead + AsyncWrite stream, split into separate read/write paths:
tokio::select! with a shutdown watch channel to ensure clean termination:
Implementing custom transports
You can implement custom transports for different runtimes or protocols.Example: mock transport for testing
Example: TCP transport (no TLS)
Best Practices
- Thread Safety - On native, implementations must be
Send + Sync(enforced viaMaybeSendSync). Onwasm32,MaybeSendSynccarries no bounds, so!Sendimplementations backed by JS handles compile. - Error Handling - Return descriptive errors from
send() - Graceful Shutdown - Implement proper cleanup in
disconnect() - Event Channel Size - Use bounded channels with reasonable capacity
- Read Task - Spawn a separate task for receiving data
- Resource Cleanup - Ensure sockets/resources are closed on drop
Testing Transports
Unit test example
See Also
- Storage Traits - Storage backend abstraction
- HTTP Client Trait - HTTP client abstraction
- Client API - Main client interface