This documentation is part of the "Projects with Books" initiative at zenOSmosis.
The source code for this project is available on GitHub.
JavaScript/WASM Integration
Relevant source files
- extensions/muxio-rpc-service/Cargo.toml
- extensions/muxio-tokio-rpc-client/src/lib.rs
- extensions/muxio-wasm-rpc-client/Cargo.toml
- 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 document provides a detailed guide to integrating the muxio-wasm-rpc-client with JavaScript host environments. It covers the architecture of the WASM client, the callback-based communication model that bridges Rust and JavaScript, WebSocket event handling patterns, and the static client pattern used for WASM exports.
For information about the WASM client's core RPC capabilities, see WASM RPC Client. For general cross-platform deployment strategies, see Cross-Platform Deployment.
WASM Client Architecture
The RpcWasmClient provides a WebAssembly-compatible RPC client that bridges between Rust's async runtime and JavaScript's event-driven WebSocket APIs. Unlike native clients that manage their own WebSocket connections, the WASM client relies on JavaScript to handle the actual network operations and deliver events to Rust callbacks.
Core Structure
The RpcWasmClient struct contains five key components:
| Component | Type | Purpose |
|---|---|---|
dispatcher | Arc<Mutex<RpcDispatcher>> | Manages request correlation and binary framing |
endpoint | Arc<RpcServiceEndpoint<()>> | Handles incoming RPC requests from the host |
emit_callback | Arc<dyn Fn(Vec<u8>)> | Sends binary data to JavaScript |
state_change_handler | Arc<Mutex<Option<Box<dyn Fn(RpcTransportState)>>>> | Notifies application of connection state changes |
is_connected | Arc<AtomicBool> | Tracks current connection status |
Sources: extensions/muxio-wasm-rpc-client/src/rpc_wasm_client.rs:16-24
WASM/JavaScript Boundary Architecture
Sources: extensions/muxio-wasm-rpc-client/src/rpc_wasm_client.rs:16-34 extensions/muxio-wasm-rpc-client/src/static_lib/static_client.rs:9-36
Callback-Based Communication Model
The WASM client uses a callback-based architecture to send data to JavaScript because WASM cannot directly call JavaScript WebSocket APIs. During initialization, the client receives an emit_callback closure that bridges to JavaScript:
sequenceDiagram
participant App as "WASM Application"
participant Client as "RpcWasmClient"
participant EmitCb as "emit_callback"
participant WasmBind as "wasm-bindgen"
participant JS as "JavaScript Glue"
participant WS as "WebSocket"
App->>Client: call_rpc_method(request)
Client->>Client: dispatcher.request()
Client->>EmitCb: emit_callback(bytes)
EmitCb->>WasmBind: static_muxio_write_bytes(bytes)
WasmBind->>JS: invoke JS function
JS->>WS: ws.send(bytes)
WS->>WS: transmit over network
When the client needs to send data (either RPC requests or responses), it invokes this callback with the binary payload. The callback implementation typically forwards to a JavaScript function via wasm-bindgen.
Emit Callback Flow
Sources: extensions/muxio-wasm-rpc-client/src/rpc_wasm_client.rs:27-35 extensions/muxio-wasm-rpc-client/src/rpc_wasm_client.rs:149-151
Bidirectional Data Flow
The communication model is inherently bidirectional:
| Direction | Mechanism | Trigger |
|---|---|---|
| WASM → JavaScript | emit_callback() invoked by client | RPC request or response needs to be sent |
| JavaScript → WASM | read_bytes() called by JS glue | WebSocket onmessage event fires |
Sources: extensions/muxio-wasm-rpc-client/src/rpc_wasm_client.rs:48-121
WebSocket Event Handling
The WASM client exposes three public methods that JavaScript glue code must call in response to WebSocket events. These methods implement the contract between JavaScript's event-driven WebSocket API and Rust's async model.
Connection Lifecycle Methods
handle_connect
Called when the JavaScript WebSocket's onopen event fires. Updates internal connection state and notifies any registered state change handlers:
Implementation:
- Sets
is_connectedatomic flag totrue - Invokes
state_change_handlerwithRpcTransportState::Connected - Enables RPC calls by making
is_connected()returntrue
read_bytes
Called when the JavaScript WebSocket's onmessage event delivers binary data. This method performs a three-stage processing pipeline:
Three-Stage Processing Pipeline:
Stage Details:
-
Synchronous Reading (lines 54-81):
- Acquires dispatcher lock briefly
- Calls
dispatcher.read_bytes(bytes)to parse binary frames - Identifies finalized requests via
is_rpc_request_finalized() - Extracts requests with
delete_rpc_request()for processing - Critical: Releases lock before async processing
-
Asynchronous Processing (lines 85-103):
- Creates futures for each request handler
- Calls
process_single_prebuffered_request()for each - Uses
join_all()to execute handlers concurrently - No dispatcher lock held during user handler execution
-
Synchronous Sending (lines 107-120):
- Re-acquires dispatcher lock
- Calls
dispatcher.respond()for each response - Invokes
emit_callbackto send response chunks to JavaScript - Releases lock after all responses are queued
This architecture prevents deadlocks and allows concurrent request processing while handlers execute.
Sources: extensions/muxio-wasm-rpc-client/src/rpc_wasm_client.rs:48-121
handle_disconnect
Called when the JavaScript WebSocket's onclose or onerror event fires:
Implementation:
- Uses
swap()to atomically check and update connection state - Invokes
state_change_handlerwithRpcTransportState::Disconnected - Calls
dispatcher.fail_all_pending_requests()to complete pending futures with errors - Prevents redundant disconnect handling via atomic swap
Sources: extensions/muxio-wasm-rpc-client/src/rpc_wasm_client.rs:124-134
Event Handling Integration Map
Sources: extensions/muxio-wasm-rpc-client/src/rpc_wasm_client.rs:38-134
Static Client Pattern
The WASM client provides a static client pattern to simplify integration with wasm-bindgen exported functions. This pattern addresses a common WASM challenge: exported functions cannot easily access instance data.
Thread-Local Static Client
The static client is stored in a thread-local variable:
This allows any exported function to access the client without requiring explicit passing through the JavaScript boundary.
Initialization and Access Functions
| Function | Purpose | Returns |
|---|---|---|
init_static_client() | Initializes the static client (idempotent) | Option<Arc<RpcWasmClient>> |
get_static_client() | Retrieves the initialized client | Option<Arc<RpcWasmClient>> |
with_static_client_async() | Executes async closure with client, returns JS Promise | Promise |
Sources: extensions/muxio-wasm-rpc-client/src/static_lib/static_client.rs:13-82
Static Client Initialization Flow
Sources: extensions/muxio-wasm-rpc-client/src/static_lib/static_client.rs:25-36
with_static_client_async Pattern
The with_static_client_async() function provides a convenient pattern for exported async RPC methods:
Usage Pattern:
- Retrieve static client from thread-local storage
- Execute user-provided async closure with client
- Convert Rust
Result<T, String>to JavaScriptPromise - Handle initialization errors by rejecting promise
This eliminates boilerplate in every exported function.
Sources: extensions/muxio-wasm-rpc-client/src/static_lib/static_client.rs:54-72
wasm-bindgen Bridge
The WASM client relies on wasm-bindgen to create the bridge between Rust and JavaScript. Key dependencies enable this integration:
| Dependency | Version | Purpose |
|---|---|---|
wasm-bindgen | 0.2.100 | Core FFI bindings between WASM and JavaScript |
wasm-bindgen-futures | 0.4.50 | Converts Rust futures to JavaScript Promises |
js-sys | 0.3.77 | Bindings to JavaScript standard types |
Sources: extensions/muxio-wasm-rpc-client/Cargo.toml:14-16
Promise Conversion
JavaScript expects Promise objects for asynchronous operations. The wasm-bindgen-futures::future_to_promise function handles this conversion:
Conversion Details:
- Rust
Future<Output = Result<T, E>>→ JavaScriptPromise<T> Ok(value)→ Promise resolves withvalueErr(e)→ Promise rejects with error string
Sources: extensions/muxio-wasm-rpc-client/src/static_lib/static_client.rs:60-71
Type Marshaling
Types must be marshaled across the WASM/JavaScript boundary:
| Rust Type | JavaScript Type | Conversion Method |
|---|---|---|
Vec<u8> | Uint8Array | Automatic via wasm-bindgen |
String | string | JsValue::from_str() |
| Custom struct | Object | Implement From<T> for JsValue |
Result<T, E> | Promise<T> | future_to_promise() |
graph TB
subgraph "Required JavaScript Components"
INIT["initializeMuxio()\n- Create WebSocket\n- Call initStaticClient()"]
OPEN_HANDLER["onopen handler\n- Call handleConnect()"]
MESSAGE_HANDLER["onmessage handler\n- Extract bytes\n- Call readBytes()"]
CLOSE_HANDLER["onclose/onerror\n- Call handleDisconnect()"]
WRITE_BRIDGE["writeBytes(bytes)\n- Call ws.send()"]
end
subgraph "WASM Exported Functions"
INIT_STATIC["initStaticClient()"]
HANDLE_CONNECT["handleConnect()"]
READ_BYTES["readBytes(bytes)"]
HANDLE_DISCONNECT["handleDisconnect()"]
end
INIT --> INIT_STATIC
OPEN_HANDLER --> HANDLE_CONNECT
MESSAGE_HANDLER --> READ_BYTES
CLOSE_HANDLER --> HANDLE_DISCONNECT
INIT_STATIC -.registers.-> WRITE_BRIDGE
JavaScript Glue Code Requirements
JavaScript developers must implement glue code that bridges native WebSocket events to the WASM client's Rust interface. The following sections detail the required components.
Minimal WebSocket Integration
Sources: extensions/muxio-wasm-rpc-client/src/static_lib/static_client.rs:1-82 extensions/muxio-wasm-rpc-client/src/rpc_wasm_client.rs:38-134
Binary Data Handling
WebSocket binary messages must be converted to byte arrays before passing to WASM:
Key Considerations:
- WebSocket can deliver
BloborArrayBufferdepending on configuration - WASM expects
Uint8Array(maps to Rust&[u8]) - Conversion is asynchronous for
Blobtypes
State Management Bridge
The JavaScript code should respond to connection state changes:
The WASM client provides is_connected() for synchronous state queries and set_state_change_handler() for reactive updates.
Sources: extensions/muxio-wasm-rpc-client/src/rpc_wasm_client.rs:136-181
Complete Integration Example
The following example demonstrates a complete WASM/JavaScript integration showing both the Rust WASM module exports and the JavaScript glue code.
Rust WASM Module Exports
JavaScript Glue Code
Sources: extensions/muxio-wasm-rpc-client/src/static_lib/static_client.rs:25-82 extensions/muxio-wasm-rpc-client/src/rpc_wasm_client.rs:16-181
Integration Architecture Summary
Sources: All files in extensions/muxio-wasm-rpc-client/
Key Integration Considerations
Thread Safety
- Thread-local storage: The static client uses
thread_local!macro, making it single-threaded - Arc usage: Internal components use
Arcfor safe sharing between callbacks - No Send/Sync requirements: JavaScript is single-threaded, eliminating concurrency concerns
Memory Management
- Ownership: Binary data crosses the boundary by value (copied)
- Lifetime: The emit callback has
'staticlifetime to avoid lifetime issues - Cleanup: WebSocket disconnect triggers
fail_all_pending_requests()to prevent memory leaks
Error Propagation
- Rust → JavaScript:
Result<T, String>converts to rejected Promise - JavaScript → Rust: Invalid bytes trigger error logs via
tracing::error! - Connection errors: Propagate through
state_change_handlerandis_connected()checks
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
Dismiss
Refresh this wiki
Enter email to refresh