This documentation is part of the "Projects with Books" initiative at zenOSmosis.
The source code for this project is available on GitHub.
JavaScript and WASM Integration
Loading…
JavaScript and WASM Integration
Relevant source files
- README.md
- extensions/muxio-tokio-rpc-client/src/lib.rs
- extensions/muxio-wasm-rpc-client/src/lib.rs
- extensions/muxio-wasm-rpc-client/src/rpc_wasm_client.rs
- extensions/muxio-wasm-rpc-client/src/static_lib/static_client.rs
Purpose and Scope
This page documents the WebAssembly (WASM) integration layer that enables muxio RPC clients to run in browser environments. It covers the bridge architecture between Rust WASM code and JavaScript, the static client pattern used for managing singleton instances, and the specific integration points required for WebSocket communication.
For general information about the WASM RPC Client platform implementation, see WASM RPC Client. For cross-platform deployment strategies, see Cross-Platform Deployment.
Sources : extensions/muxio-wasm-rpc-client/src/rpc_wasm_client.rs:1-182 extensions/muxio-wasm-rpc-client/src/static_lib/static_client.rs:1-82
WASM Bridge Architecture
The muxio WASM integration uses a byte-passing bridge pattern to connect Rust code compiled to WebAssembly with JavaScript host environments. This design is lightweight and avoids complex FFI patterns by restricting communication to byte arrays (Vec<u8> in Rust, Uint8Array in JavaScript).
Core Bridge Components
The bridge consists of three primary components:
| Component | Type | Role |
|---|---|---|
RpcWasmClient | Rust struct | Manages RPC dispatcher, endpoint, and connection state |
static_muxio_write_bytes | JavaScript-callable function | Emits bytes from Rust to JavaScript |
MUXIO_STATIC_RPC_CLIENT_REF | Thread-local singleton | Stores the static client instance |
The RpcWasmClient structure extensions/muxio-wasm-rpc-client/src/rpc_wasm_client.rs:16-24 contains:
dispatcher: AnArc<tokio::sync::Mutex<RpcDispatcher>>for request/response correlationendpoint: AnArc<RpcServiceEndpoint<()>>for handling incoming RPC callsemit_callback: AnArc<dyn Fn(Vec<u8>)>that bridges to JavaScriptstate_change_handler: Callback for connection state changesis_connected: Atomic boolean tracking connection status
Diagram: WASM-JavaScript Bridge Architecture
graph TB
subgraph "JavaScript/Browser Environment"
JS_WS["WebSocket API\n(Browser Native)"]
JS_BRIDGE["JavaScript Bridge Layer\nstatic_muxio_write_bytes"]
JS_APP["Web Application Code"]
end
subgraph "WASM Module (Rust Compiled)"
WASM_CLIENT["RpcWasmClient"]
WASM_DISP["RpcDispatcher"]
WASM_EP["RpcServiceEndpoint"]
STATIC_REF["MUXIO_STATIC_RPC_CLIENT_REF\nthread_local RefCell"]
WASM_CLIENT -->|owns Arc| WASM_DISP
WASM_CLIENT -->|owns Arc| WASM_EP
STATIC_REF -->|stores Arc| WASM_CLIENT
end
JS_WS -->|onmessage bytes| JS_BRIDGE
JS_BRIDGE -->|read_bytes &[u8]| WASM_CLIENT
WASM_CLIENT -->|emit_callback Vec<u8>| JS_BRIDGE
JS_BRIDGE -->|send Uint8Array| JS_WS
JS_APP -->|RPC method calls| WASM_CLIENT
WASM_CLIENT -->|responses| JS_APP
JS_WS -->|onopen| WASM_CLIENT
JS_WS -->|onclose/onerror| WASM_CLIENT
Sources : extensions/muxio-wasm-rpc-client/src/rpc_wasm_client.rs:16-35 extensions/muxio-wasm-rpc-client/src/static_lib/static_client.rs:9-11
Static Client Pattern
The WASM client uses a singleton pattern implemented with thread_local! storage and RefCell for interior mutability. This pattern is necessary because:
- WASM runs single-threaded, making thread-local storage equivalent to global storage
- Multiple JavaScript functions may need access to the same client instance
- The client must survive across multiple JavaScript->Rust function call boundaries
Thread-Local Storage Implementation
The static client is stored in MUXIO_STATIC_RPC_CLIENT_REF extensions/muxio-wasm-rpc-client/src/static_lib/static_client.rs:9-11:
Key characteristics:
thread_local!: Creates a separate instance per thread (single thread in WASM)RefCell<Option<Arc<T>>>: Enables runtime-checked mutable borrowing with optional valueArc<RpcWasmClient>: Allows cloning references without copying the entire client
Initialization and Access Functions
Three functions manage the static client lifecycle:
| Function | Purpose | Idempotency |
|---|---|---|
init_static_client() | Creates the client if not present | Yes - multiple calls are safe |
get_static_client() | Retrieves the current client | N/A - read-only |
with_static_client_async() | Executes async operations with the client | N/A - wrapper |
The init_static_client() function extensions/muxio-wasm-rpc-client/src/static_lib/static_client.rs:25-36 checks for existing initialization before creating a new instance:
Diagram: Static Client Initialization Flow
Sources : extensions/muxio-wasm-rpc-client/src/static_lib/static_client.rs:25-36
JavaScript Integration Points
The WASM client exposes specific methods designed to be called from JavaScript in response to WebSocket events. These methods bridge the gap between JavaScript’s event-driven API and Rust’s async/await model.
Connection Lifecycle Methods
Three methods handle WebSocket lifecycle events:
| Method | JavaScript Event | Purpose |
|---|---|---|
handle_connect() | WebSocket.onopen | Sets connected state, invokes state change handler |
read_bytes(&[u8]) | WebSocket.onmessage | Processes incoming binary data |
handle_disconnect() | WebSocket.onclose / WebSocket.onerror | Clears connected state, fails pending requests |
handle_connect Implementation
The handle_connect() method extensions/muxio-wasm-rpc-client/src/rpc_wasm_client.rs:37-44 updates connection state and notifies handlers:
Key operations:
- Atomic flag update : Uses
AtomicBool::storewithSeqCstordering for thread-safe state change - Handler invocation : Calls registered state change callback if present
- Async execution : Returns
Futurethat JavaScript must await
read_bytes Implementation
The read_bytes() method extensions/muxio-wasm-rpc-client/src/rpc_wasm_client.rs:48-121 implements a three-stage processing pipeline:
Stage 1: Synchronous Reading (dispatcher lock held briefly)
- Acquires dispatcher mutex
- Calls
dispatcher.read_bytes(bytes)to parse frames - Extracts finalized requests from dispatcher
- Releases dispatcher lock
Stage 2: Asynchronous Handler Execution (no locks held)
- Calls
process_single_prebuffered_request()for each request - User handlers execute concurrently via
join_all() - Dispatcher remains unlocked during handler execution
Stage 3: Synchronous Response Writing (dispatcher lock re-acquired)
- Re-acquires dispatcher mutex
- Calls
dispatcher.respond()for each result - Emits response bytes via
emit_callback - Releases dispatcher lock
graph TB
JS_MSG["JavaScript: WebSocket.onmessage"]
subgraph "Stage 1: Synchronous Reading"
LOCK1["Acquire dispatcher lock"]
READ["dispatcher.read_bytes(bytes)"]
EXTRACT["Extract finalized requests"]
UNLOCK1["Release dispatcher lock"]
end
subgraph "Stage 2: Async Handler Execution"
PROCESS["process_single_prebuffered_request()"]
HANDLERS["User handlers execute"]
JOIN["join_all()
responses"]
end
subgraph "Stage 3: Synchronous Response"
LOCK2["Re-acquire dispatcher lock"]
RESPOND["dispatcher.respond()"]
EMIT["emit_callback(bytes)"]
UNLOCK2["Release dispatcher lock"]
end
JS_MSG --> LOCK1
LOCK1 --> READ
READ --> EXTRACT
EXTRACT --> UNLOCK1
UNLOCK1 --> PROCESS
PROCESS --> HANDLERS
HANDLERS --> JOIN
JOIN --> LOCK2
LOCK2 --> RESPOND
RESPOND --> EMIT
EMIT --> UNLOCK2
UNLOCK2 --> JS_EMIT["JavaScript: WebSocket.send()"]
This staged approach prevents deadlocks by ensuring user handlers never execute while the dispatcher is locked.
Diagram: read_bytes Processing Pipeline
Sources : extensions/muxio-wasm-rpc-client/src/rpc_wasm_client.rs:48-121
handle_disconnect Implementation
The handle_disconnect() method extensions/muxio-wasm-rpc-client/src/rpc_wasm_client.rs:123-134 performs cleanup:
Key operations:
- Atomic swap : Uses
swap()to atomically test-and-set connection flag - Conditional execution : Only processes if connection was previously established
- Error propagation : Calls
fail_all_pending_requests()to reject outstanding futures - State notification : Invokes registered disconnect handler
Sources : extensions/muxio-wasm-rpc-client/src/rpc_wasm_client.rs:37-44 extensions/muxio-wasm-rpc-client/src/rpc_wasm_client.rs:48-121 extensions/muxio-wasm-rpc-client/src/rpc_wasm_client.rs:123-134
wasm-bindgen Integration
The WASM client uses wasm-bindgen to generate JavaScript bindings for Rust code. Key integration patterns include:
Type Conversions
| Rust Type | JavaScript Type | Conversion Method |
|---|---|---|
Vec<u8> | Uint8Array | Automatic via wasm-bindgen |
Result<T, String> | Promise<T> | future_to_promise() |
T: Into<JsValue> | Any JS value | .into() conversion |
Promise-Based Async Functions
The with_static_client_async() helper extensions/muxio-wasm-rpc-client/src/static_lib/static_client.rs:54-72 wraps Rust async functions for JavaScript consumption:
This pattern:
- Retrieves static client : Clones
Arcfrom thread-local storage - Executes user closure : Awaits the provided async function
- Converts result : Transforms
Result<T, String>to JavaScriptPromise - Error handling : Converts Rust errors to rejected promises
Diagram: JavaScript-Rust Promise Interop
sequenceDiagram
participant JS as "JavaScript Code"
participant BINDGEN as "wasm-bindgen Layer"
participant WRAPPER as "with_static_client_async()"
participant TLS as "Thread-Local Storage"
participant CLOSURE as "User Closure<F>"
participant CLIENT as "RpcWasmClient"
JS->>BINDGEN: Call exported WASM function
BINDGEN->>WRAPPER: Invoke wrapper function
WRAPPER->>TLS: Get static client
TLS->>WRAPPER: Arc<RpcWasmClient>
WRAPPER->>CLOSURE: Execute f(client).await
CLOSURE->>CLIENT: Perform RPC operations
CLIENT->>CLOSURE: Result<T, String>
CLOSURE->>WRAPPER: Return result
alt "Success"
WRAPPER->>BINDGEN: Ok(value.into())
BINDGEN->>JS: Promise resolves with value
else "Error"
WRAPPER->>BINDGEN: Err(JsValue)
BINDGEN->>JS: Promise rejects with error
end
Sources : extensions/muxio-wasm-rpc-client/src/static_lib/static_client.rs:54-72
Bidirectional Data Flow
The WASM integration enables bidirectional RPC communication, where both JavaScript and WASM can initiate calls and handle responses.
Outbound Flow (WASM → JavaScript → Server)
- Application code calls RPC method on
RpcWasmClient RpcServiceCallerInterfaceimplementation encodes requestRpcDispatcherserializes and chunks dataemit_callbackinvokesstatic_muxio_write_bytes- JavaScript bridge sends bytes via
WebSocket.send()
Inbound Flow (Server → JavaScript → WASM)
- JavaScript receives
WebSocket.onmessageevent - Bridge calls
RpcWasmClient.read_bytes(&[u8]) - Dispatcher parses frames and routes to endpoint
- Endpoint dispatches to registered handlers
- Handlers execute and return responses
- Responses emitted back through
emit_callback
Diagram: Complete Bidirectional Message Flow
graph TB
subgraph "JavaScript Layer"
JS_APP["Web Application"]
WS_API["WebSocket API"]
BRIDGE_OUT["static_muxio_write_bytes"]
BRIDGE_IN["read_bytes handler"]
end
subgraph "WASM Client (RpcWasmClient)"
CALLER["RpcServiceCallerInterface"]
ENDPOINT["RpcServiceEndpoint"]
DISPATCHER["RpcDispatcher"]
EMIT["emit_callback"]
end
subgraph "Server"
SERVER["Tokio RPC Server"]
end
JS_APP -->|Call RPC method| CALLER
CALLER -->|Encode request| DISPATCHER
DISPATCHER -->|Serialize frames| EMIT
EMIT -->|Vec<u8>| BRIDGE_OUT
BRIDGE_OUT -->|Uint8Array| WS_API
WS_API -->|Binary message| SERVER
SERVER -->|Binary response| WS_API
WS_API -->|onmessage event| BRIDGE_IN
BRIDGE_IN -->|&[u8]| DISPATCHER
DISPATCHER -->|Route request| ENDPOINT
ENDPOINT -->|Invoke handler| ENDPOINT
ENDPOINT -->|Response| DISPATCHER
DISPATCHER -->|Serialize frames| EMIT
DISPATCHER -->|Deliver result| CALLER
CALLER -->|Return typed result| JS_APP
Sources : extensions/muxio-wasm-rpc-client/src/rpc_wasm_client.rs:16-35 extensions/muxio-wasm-rpc-client/src/rpc_wasm_client.rs:48-121
RpcServiceCallerInterface Implementation
The RpcWasmClient implements the RpcServiceCallerInterface trait extensions/muxio-wasm-rpc-client/src/rpc_wasm_client.rs:154-181 enabling it to be used interchangeably with RpcClient for Tokio environments. This trait provides:
| Method | Purpose | Return Type |
|---|---|---|
get_dispatcher() | Returns dispatcher for request/response management | Arc<Mutex<RpcDispatcher>> |
get_emit_fn() | Returns callback for byte emission | Arc<dyn Fn(Vec<u8>)> |
is_connected() | Checks current connection status | bool |
set_state_change_handler() | Registers connection state callback | async fn |
The implementation delegates to internal methods:
This implementation ensures that WASM and Tokio clients share the same interface, enabling code reuse at the application layer. The trait abstraction is documented in detail in Service Caller Interface.
Sources : extensions/muxio-wasm-rpc-client/src/rpc_wasm_client.rs:154-181 extensions/muxio-wasm-rpc-client/src/lib.rs:1-10
Module Structure and Re-exports
The muxio-wasm-rpc-client crate is organized with clear module boundaries:
| Module | Contents | Purpose |
|---|---|---|
rpc_wasm_client | RpcWasmClient struct | Core client implementation |
static_lib | Static client utilities | Singleton pattern helpers |
Root (lib.rs) | Re-exports | Simplified imports |
The root module extensions/muxio-wasm-rpc-client/src/lib.rs:1-10 re-exports key types for convenience:
This allows consumers to import all necessary types from a single crate:
Sources : extensions/muxio-wasm-rpc-client/src/lib.rs:1-10
Comparison with Tokio Client
While both RpcWasmClient and RpcClient implement RpcServiceCallerInterface, their internal architectures differ due to platform constraints:
| Aspect | RpcWasmClient | RpcClient |
|---|---|---|
| Runtime | Single-threaded WASM | Multi-threaded Tokio |
| Storage | thread_local! + RefCell | Arc + background tasks |
| Transport | JavaScript WebSocket API | tokio-tungstenite |
| Emit | Callback to static_muxio_write_bytes | Channel to writer task |
| Lifecycle | Manual JS event handlers | Automatic via async tasks |
| Initialization | init_static_client() | RpcClient::new() |
Despite these differences, both clients expose identical RPC method calling interfaces, enabling cross-platform application code. The platform-specific details are encapsulated behind the RpcServiceCallerInterface abstraction.
Sources : extensions/muxio-wasm-rpc-client/src/rpc_wasm_client.rs:16-35 extensions/muxio-tokio-rpc-client/src/lib.rs:1-8 README.md:48-49