Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

GitHub

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

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:

ComponentTypePurpose
dispatcherArc<Mutex<RpcDispatcher>>Manages request correlation and binary framing
endpointArc<RpcServiceEndpoint<()>>Handles incoming RPC requests from the host
emit_callbackArc<dyn Fn(Vec<u8>)>Sends binary data to JavaScript
state_change_handlerArc<Mutex<Option<Box<dyn Fn(RpcTransportState)>>>>Notifies application of connection state changes
is_connectedArc<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:

DirectionMechanismTrigger
WASM → JavaScriptemit_callback() invoked by clientRPC request or response needs to be sent
JavaScript → WASMread_bytes() called by JS glueWebSocket 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_connected atomic flag to true
  • Invokes state_change_handler with RpcTransportState::Connected
  • Enables RPC calls by making is_connected() return true

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:

  1. 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
  2. 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
  3. Synchronous Sending (lines 107-120):

    • Re-acquires dispatcher lock
    • Calls dispatcher.respond() for each response
    • Invokes emit_callback to 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_handler with RpcTransportState::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

FunctionPurposeReturns
init_static_client()Initializes the static client (idempotent)Option<Arc<RpcWasmClient>>
get_static_client()Retrieves the initialized clientOption<Arc<RpcWasmClient>>
with_static_client_async()Executes async closure with client, returns JS PromisePromise

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:

  1. Retrieve static client from thread-local storage
  2. Execute user-provided async closure with client
  3. Convert Rust Result<T, String> to JavaScript Promise
  4. 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:

DependencyVersionPurpose
wasm-bindgen0.2.100Core FFI bindings between WASM and JavaScript
wasm-bindgen-futures0.4.50Converts Rust futures to JavaScript Promises
js-sys0.3.77Bindings 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>> → JavaScript Promise<T>
  • Ok(value) → Promise resolves with value
  • Err(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 TypeJavaScript TypeConversion Method
Vec<u8>Uint8ArrayAutomatic via wasm-bindgen
StringstringJsValue::from_str()
Custom structObjectImplement 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 Blob or ArrayBuffer depending on configuration
  • WASM expects Uint8Array (maps to Rust &[u8])
  • Conversion is asynchronous for Blob types

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 Arc for 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 'static lifetime 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_handler and is_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