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 and WASM Integration

Loading…

JavaScript and WASM Integration

Relevant source files

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:

ComponentTypeRole
RpcWasmClientRust structManages RPC dispatcher, endpoint, and connection state
static_muxio_write_bytesJavaScript-callable functionEmits bytes from Rust to JavaScript
MUXIO_STATIC_RPC_CLIENT_REFThread-local singletonStores the static client instance

The RpcWasmClient structure extensions/muxio-wasm-rpc-client/src/rpc_wasm_client.rs:16-24 contains:

  • dispatcher: An Arc<tokio::sync::Mutex<RpcDispatcher>> for request/response correlation
  • endpoint: An Arc<RpcServiceEndpoint<()>> for handling incoming RPC calls
  • emit_callback: An Arc<dyn Fn(Vec<u8>)> that bridges to JavaScript
  • state_change_handler: Callback for connection state changes
  • is_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:

  1. WASM runs single-threaded, making thread-local storage equivalent to global storage
  2. Multiple JavaScript functions may need access to the same client instance
  3. 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 value
  • Arc<RpcWasmClient>: Allows cloning references without copying the entire client

Initialization and Access Functions

Three functions manage the static client lifecycle:

FunctionPurposeIdempotency
init_static_client()Creates the client if not presentYes - multiple calls are safe
get_static_client()Retrieves the current clientN/A - read-only
with_static_client_async()Executes async operations with the clientN/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:

MethodJavaScript EventPurpose
handle_connect()WebSocket.onopenSets connected state, invokes state change handler
read_bytes(&[u8])WebSocket.onmessageProcesses incoming binary data
handle_disconnect()WebSocket.onclose / WebSocket.onerrorClears 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::store with SeqCst ordering for thread-safe state change
  • Handler invocation : Calls registered state change callback if present
  • Async execution : Returns Future that 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 TypeJavaScript TypeConversion Method
Vec<u8>Uint8ArrayAutomatic 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 Arc from thread-local storage
  • Executes user closure : Awaits the provided async function
  • Converts result : Transforms Result<T, String> to JavaScript Promise
  • 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)

  1. Application code calls RPC method on RpcWasmClient
  2. RpcServiceCallerInterface implementation encodes request
  3. RpcDispatcher serializes and chunks data
  4. emit_callback invokes static_muxio_write_bytes
  5. JavaScript bridge sends bytes via WebSocket.send()

Inbound Flow (Server → JavaScript → WASM)

  1. JavaScript receives WebSocket.onmessage event
  2. Bridge calls RpcWasmClient.read_bytes(&[u8])
  3. Dispatcher parses frames and routes to endpoint
  4. Endpoint dispatches to registered handlers
  5. Handlers execute and return responses
  6. 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:

MethodPurposeReturn Type
get_dispatcher()Returns dispatcher for request/response managementArc<Mutex<RpcDispatcher>>
get_emit_fn()Returns callback for byte emissionArc<dyn Fn(Vec<u8>)>
is_connected()Checks current connection statusbool
set_state_change_handler()Registers connection state callbackasync 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:

ModuleContentsPurpose
rpc_wasm_clientRpcWasmClient structCore client implementation
static_libStatic client utilitiesSingleton pattern helpers
Root (lib.rs)Re-exportsSimplified 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:

AspectRpcWasmClientRpcClient
RuntimeSingle-threaded WASMMulti-threaded Tokio
Storagethread_local! + RefCellArc + background tasks
TransportJavaScript WebSocket APItokio-tungstenite
EmitCallback to static_muxio_write_bytesChannel to writer task
LifecycleManual JS event handlersAutomatic via async tasks
Initializationinit_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