Preparing search index...

    Best Practices for WASM Plugins

    WASM plugins support hot-reload without server restart:

    1. Build new WASM binary: cargo build --target wasm32-wasip1 --release
    2. Copy to plugin directory: cp target/wasm32-wasip1/release/*.wasm ~/.signalk/...
    3. In Admin UI: ServerPlugin Config → Click Reload button

    During reload:

    • stop() is called on old instance
    • Subscriptions are preserved
    • Deltas are buffered (not lost)
    • New instance is loaded
    • start() is called with saved config
    • Buffered deltas are replayed

    If a WASM plugin crashes:

    1. First crash: Automatic restart after 1 second
    2. Second crash: Restart after 2 seconds
    3. Third crash: Restart after 4 seconds
    4. After 3 crashes: Plugin disabled, admin notification

    Report errors to admin UI:

    fn handle_error(err: &str) {
    sk_set_error(&format!("Error: {}", err));
    }
    [profile.release]
    opt-level = "z" # Optimize for size
    lto = true # Enable link-time optimization
    strip = true # Strip debug symbols

    Use wasm-opt for further optimization:

    wasm-opt -Oz plugin.wasm -o plugin.wasm
    
    fn start(config_ptr: *const u8, config_len: usize) -> i32 {
    match initialize_plugin(config_ptr, config_len) {
    Ok(_) => {
    sk_set_status("Started");
    0 // Success
    }
    Err(e) => {
    sk_set_error(&format!("Failed to start: {}", e));
    1 // Error
    }
    }
    }
    use serde::{Deserialize, Serialize};

    #[derive(Deserialize)]
    struct Config {
    #[serde(default)]
    enabled: bool,
    }

    fn parse_config(json: &str) -> Result<Config, serde_json::Error> {
    serde_json::from_str(json)
    }
    • Avoid large allocations
    • Clear buffers after use
    • Use streaming for large data

    WASM plugins running in Node.js have ~64KB buffer limitations for stdin/stdout operations. This is a fundamental limitation of the Node.js WASI implementation, not a Signal K restriction.

    Impact:

    • Small JSON responses (< 64KB): Work fine in pure WASM
    • Medium data (64KB - 1MB): May freeze or fail
    • Large data (> 1MB): Will fail or freeze the server

    Hybrid Architecture Pattern

    For plugins that need to handle large data volumes (logs, file streaming, large JSON responses), use a hybrid approach:

    • WASM Plugin: Registers HTTP endpoints and provides configuration UI
    • Node.js Handler: Server intercepts specific endpoints and handles I/O directly in Node.js
    • Result: Can handle unlimited data without memory constraints

    Use this pattern when your plugin needs to:

    • Return large JSON responses (> 64KB)
    • Process large file uploads
    • Handle streaming data
    • Clear status messages
    • Descriptive error messages
    • Comprehensive JSON schema for configuration
    fn debug_log(message: &str) {
    unsafe {
    sk_debug(message.as_ptr(), message.len());
    }
    }
    1. Build with debug symbols: cargo build --target wasm32-wasip1
    2. Use wasmtime for local testing:
    wasmtime --dir /tmp::/ plugin.wasm
    
    # Linux/macOS
    DEBUG=signalk:wasm:* signalk-server

    Issue: Plugin doesn't load Solution: Check wasmManifest path in package.json

    Issue: Capability errors Solution: Ensure required capabilities declared in package.json

    Issue: Crashes on start Solution: Check server logs for error details

    Check if your plugin:

    • ✅ Processes deltas
    • ✅ Reads/writes configuration
    • ✅ Uses data model APIs
    • ✅ Registers REST endpoints
    • ❌ Uses serial ports (planned but not there yet)
    • ✅ Makes HTTP requests (via as-fetch in AssemblyScript)
    • ✅ Uses UDP/TCP sockets (rawSockets capability)

    Convert TypeScript/JavaScript logic to Rust:

    Before (Node.js):

    plugin.start = function (config) {
    app.handleMessage('my-plugin', {
    updates: [{ values: [{ path: 'foo', value: 'bar' }] }]
    })
    }

    After (WASM/Rust):

    fn start(config_ptr: *const u8, config_len: usize) -> i32 {
    let delta = json!({
    "updates": [{ "values": [{ "path": "foo", "value": "bar" }] }]
    });
    sk_emit_delta(&delta.to_string());
    0
    }

    Use migration helper to copy existing data to VFS:

    fn first_run_migration() {
    // Server provides migration API
    // Copies files from ~/.signalk/plugin-config-data/{id}/
    // to ~/.signalk/plugin-config-data/{id}/vfs/data/
    }

    The following example plugins are available in the repository: