WASM compatibility update

Related to: Usage as a lib #8

1.  feature gating of TLS and other features not supported with WASM.
2. updated documentation.
3. creation of AI slop WASM example for verification.
This commit is contained in:
2025-09-08 12:28:44 -07:00
parent 06cd6d0c82
commit f59c28d966
13 changed files with 900 additions and 53 deletions
+15
View File
@@ -0,0 +1,15 @@
# Build artifacts
/target/
/pkg/
# IDE files
.vscode/
.idea/
# OS files
.DS_Store
Thumbs.db
# Backup files
*~
*.bak
+295
View File
@@ -0,0 +1,295 @@
# This file is automatically @generated by Cargo.
# It is not intended for manual editing.
version = 4
[[package]]
name = "anyhow"
version = "1.0.99"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b0674a1ddeecb70197781e945de4b3b8ffb61fa939a5597bcf48503737663100"
[[package]]
name = "bumpalo"
version = "3.19.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "46c5e41b57b8bba42a04676d81cb89e9ee8e859a1a66f80a5a72e1cb76b34d43"
[[package]]
name = "bytes"
version = "1.10.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d71b6127be86fdcfddb610f7182ac57211d4b18a3e9c82eb2d17662f2227ad6a"
[[package]]
name = "cfg-if"
version = "1.0.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2fd1289c04a9ea8cb22300a459a72a385d7c73d3259e2ed7dcb2af674838cfa9"
[[package]]
name = "console_error_panic_hook"
version = "0.1.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a06aeb73f470f66dcdbf7223caeebb85984942f22f1adb2a088cf9668146bbbc"
dependencies = [
"cfg-if",
"wasm-bindgen",
]
[[package]]
name = "either"
version = "1.15.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "48c757948c5ede0e46177b7add2e67155f70e33c07fea8284df6576da70b3719"
[[package]]
name = "getrandom"
version = "0.2.16"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "335ff9f135e4384c8150d6f27c6daed433577f86b4750418338c01a1a2528592"
dependencies = [
"cfg-if",
"js-sys",
"libc",
"wasi",
"wasm-bindgen",
]
[[package]]
name = "itertools"
version = "0.14.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2b192c782037fadd9cfa75548310488aabdbf3d2da73885b31bd0abd03351285"
dependencies = [
"either",
]
[[package]]
name = "itoa"
version = "1.0.15"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4a5f13b858c8d314ee3e8f639011f7ccefe71f97f96e50151fb991f267928e2c"
[[package]]
name = "js-sys"
version = "0.3.78"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0c0b063578492ceec17683ef2f8c5e89121fbd0b172cbc280635ab7567db2738"
dependencies = [
"once_cell",
"wasm-bindgen",
]
[[package]]
name = "libc"
version = "0.2.175"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6a82ae493e598baaea5209805c49bbf2ea7de956d50d7da0da1164f9c6d28543"
[[package]]
name = "log"
version = "0.4.28"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "34080505efa8e45a4b816c349525ebe327ceaa8559756f0356cba97ef3bf7432"
[[package]]
name = "memchr"
version = "2.7.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "32a282da65faaf38286cf3be983213fcf1d2e2a58700e808f83f4ea9a4804bc0"
[[package]]
name = "once_cell"
version = "1.21.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "42f5e15c9953c5e4ccceeb2e7382a716482c34515315f7b03532b8b4e8393d2d"
[[package]]
name = "proc-macro2"
version = "1.0.101"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "89ae43fd86e4158d6db51ad8e2b80f313af9cc74f5c0e03ccb87de09998732de"
dependencies = [
"unicode-ident",
]
[[package]]
name = "prost"
version = "0.13.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2796faa41db3ec313a31f7624d9286acf277b52de526150b7e69f3debf891ee5"
dependencies = [
"bytes",
"prost-derive",
]
[[package]]
name = "prost-derive"
version = "0.13.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8a56d757972c98b346a9b766e3f02746cde6dd1cd1d1d563472929fdd74bec4d"
dependencies = [
"anyhow",
"itertools",
"proc-macro2",
"quote",
"syn",
]
[[package]]
name = "quote"
version = "1.0.40"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1885c039570dc00dcb4ff087a89e185fd56bae234ddc7f056a945bf36467248d"
dependencies = [
"proc-macro2",
]
[[package]]
name = "rustversion"
version = "1.0.22"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b39cdef0fa800fc44525c84ccb54a029961a8215f9619753635a9c0d2538d46d"
[[package]]
name = "ryu"
version = "1.0.20"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "28d3b2b1366ec20994f1fd18c3c594f05c5dd4bc44d8bb0c1c632c8d6829481f"
[[package]]
name = "serde"
version = "1.0.219"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5f0e2c6ed6606019b4e29e69dbaba95b11854410e5347d525002456dbbb786b6"
dependencies = [
"serde_derive",
]
[[package]]
name = "serde_derive"
version = "1.0.219"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5b0276cf7f2c73365f7157c8123c21cd9a50fbbd844757af28ca1f5925fc2a00"
dependencies = [
"proc-macro2",
"quote",
"syn",
]
[[package]]
name = "serde_json"
version = "1.0.143"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d401abef1d108fbd9cbaebc3e46611f4b1021f714a0597a71f41ee463f5f4a5a"
dependencies = [
"itoa",
"memchr",
"ryu",
"serde",
]
[[package]]
name = "socktop_wasm_test"
version = "0.1.0"
dependencies = [
"console_error_panic_hook",
"getrandom",
"prost",
"serde",
"serde_json",
"wasm-bindgen",
"web-sys",
]
[[package]]
name = "syn"
version = "2.0.106"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ede7c438028d4436d71104916910f5bb611972c5cfd7f89b8300a8186e6fada6"
dependencies = [
"proc-macro2",
"quote",
"unicode-ident",
]
[[package]]
name = "unicode-ident"
version = "1.0.18"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5a5f39404a5da50712a4c1eecf25e90dd62b613502b7e925fd4e4d19b5c96512"
[[package]]
name = "wasi"
version = "0.11.1+wasi-snapshot-preview1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ccf3ec651a847eb01de73ccad15eb7d99f80485de043efb2f370cd654f4ea44b"
[[package]]
name = "wasm-bindgen"
version = "0.2.101"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7e14915cadd45b529bb8d1f343c4ed0ac1de926144b746e2710f9cd05df6603b"
dependencies = [
"cfg-if",
"once_cell",
"rustversion",
"wasm-bindgen-macro",
"wasm-bindgen-shared",
]
[[package]]
name = "wasm-bindgen-backend"
version = "0.2.101"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e28d1ba982ca7923fd01448d5c30c6864d0a14109560296a162f80f305fb93bb"
dependencies = [
"bumpalo",
"log",
"proc-macro2",
"quote",
"syn",
"wasm-bindgen-shared",
]
[[package]]
name = "wasm-bindgen-macro"
version = "0.2.101"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7c3d463ae3eff775b0c45df9da45d68837702ac35af998361e2c84e7c5ec1b0d"
dependencies = [
"quote",
"wasm-bindgen-macro-support",
]
[[package]]
name = "wasm-bindgen-macro-support"
version = "0.2.101"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7bb4ce89b08211f923caf51d527662b75bdc9c9c7aab40f86dcb9fb85ac552aa"
dependencies = [
"proc-macro2",
"quote",
"syn",
"wasm-bindgen-backend",
"wasm-bindgen-shared",
]
[[package]]
name = "wasm-bindgen-shared"
version = "0.2.101"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f143854a3b13752c6950862c906306adb27c7e839f7414cec8fea35beab624c1"
dependencies = [
"unicode-ident",
]
[[package]]
name = "web-sys"
version = "0.3.78"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "77e4b637749ff0d92b8fad63aa1f7cff3cbe125fd49c175cd6345e7272638b12"
dependencies = [
"js-sys",
"wasm-bindgen",
]
+32
View File
@@ -0,0 +1,32 @@
[package]
name = "socktop_wasm_test"
version = "0.1.0"
edition = "2021"
# Make this a standalone package, not part of the parent workspace
[workspace]
[lib]
crate-type = ["cdylib"]
[dependencies]
# Only include the types and configuration, not the networking
serde = { version = "1.0", features = ["derive"] }
serde_json = "1.0"
wasm-bindgen = "0.2"
console_error_panic_hook = "0.1"
# For manual protobuf handling if needed
prost = "0.13"
# Enable JS feature for WASM random number generation
[dependencies.getrandom]
version = "0.2"
features = ["js"]
# WASM-specific dependencies
[dependencies.web-sys]
version = "0.3"
features = [
"console",
]
+216
View File
@@ -0,0 +1,216 @@
# WASM Compatibility Guide for socktop_connector
This directory contains a complete WebAssembly (WASM) compatibility test and implementation guide for the `socktop_connector` library.
## Overview
`socktop_connector` provides **types-only support** for WebAssembly environments. While the networking functionality requires tokio/mio (which don't work in WASM), the core types can be used for serialization and configuration with browser WebSocket APIs.
## What Works in WASM
- ✅ Configuration types (`ConnectorConfig`)
- ✅ Request/Response types (`AgentRequest`, `AgentResponse`)
- ✅ JSON serialization/deserialization of all types
- ✅ Protocol and version configuration builders
- ✅ All type-safe validation and error handling for configurations
## What Doesn't Work in WASM
- ❌ Direct WebSocket connections (tokio/mio incompatibility)
- ❌ TLS certificate handling (rustls incompatibility)
- ❌ All networking functionality (`connect_to_socktop_agent*` functions)
## Quick Test
```bash
# Build the WASM package
wasm-pack build --target web --out-dir pkg
# Serve the test page
basic-http-server . --addr 127.0.0.1:8000
# Open http://127.0.0.1:8000 in your browser
# Check the browser console for test results
```
## WASM Dependencies
The test uses minimal dependencies that work in WASM:
```toml
[dependencies]
socktop_connector = { path = "../socktop_connector", default-features = false }
serde = { version = "1.0", features = ["derive"] }
serde_json = "1.0"
wasm-bindgen = "0.2"
console_error_panic_hook = "0.1"
prost = "0.13" # For protobuf compatibility testing
[dependencies.getrandom]
version = "0.2"
features = ["js"] # Enable browser random number generation
[dependencies.web-sys]
version = "0.3"
features = ["console"] # For console.log
```
**Critical**: Use `default-features = false` to exclude tokio and rustls dependencies that don't work in WASM.
## Implementation Strategy
### 1. Use socktop_connector Types for Configuration
```rust
use wasm_bindgen::prelude::*;
use socktop_connector::{ConnectorConfig, AgentRequest, AgentResponse};
#[wasm_bindgen]
pub fn create_config() -> String {
// Use socktop_connector types for type-safe configuration
let config = ConnectorConfig::new("ws://localhost:3000/ws")
.with_protocols(vec!["socktop".to_string(), "v1".to_string()])
.with_version("13".to_string());
// Return JSON for use with browser WebSocket API
serde_json::to_string(&config).unwrap_or_default()
}
```
### 2. Create Type-Safe Requests
```rust
#[wasm_bindgen]
pub fn create_metrics_request() -> String {
let request = AgentRequest::Metrics;
serde_json::to_string(&request).unwrap_or_default()
}
#[wasm_bindgen]
pub fn create_processes_request() -> String {
let request = AgentRequest::Processes;
serde_json::to_string(&request).unwrap_or_default()
}
```
### 3. Parse Responses with Type Safety
```rust
#[wasm_bindgen]
pub fn parse_metrics_response(json: &str) -> Option<String> {
match serde_json::from_str::<AgentResponse>(json) {
Ok(AgentResponse::Metrics(metrics)) => {
Some(format!("CPU: {}%, Memory: {}MB",
metrics.cpu_total,
metrics.mem_used / 1024 / 1024))
}
_ => None
}
}
```
### 4. Browser Integration
Then in JavaScript:
```javascript
import init, {
create_config,
create_metrics_request,
parse_metrics_response
} from './pkg/socktop_wasm_test.js';
async function run() {
await init();
// Use type-safe configuration
const configJson = create_config();
const config = JSON.parse(configJson);
// Create WebSocket with proper protocols
const ws = new WebSocket(config.url, config.ws_protocols);
ws.onopen = () => {
// Send type-safe requests
ws.send(create_metrics_request());
};
ws.onmessage = (event) => {
// Handle responses with type safety
const result = parse_metrics_response(event.data);
if (result) {
console.log(result);
}
};
}
run();
```
## Benefits of This Approach
1. **Type Safety**: All socktop types work identically in WASM
2. **Validation**: Configuration validation happens in Rust
3. **Maintainability**: Share types between native and WASM code
4. **Performance**: Rust types compile to efficient WASM
5. **Future Proof**: Updates to socktop types automatically work in WASM
## Real-World Usage
For production WASM applications:
1. Use this pattern to create a WASM module that exports configuration and serialization functions
2. Handle WebSocket connections in JavaScript using browser APIs
3. Use the exported functions for type-safe message creation and parsing
4. Leverage socktop's structured error handling for robust applications
- **No TLS dependencies**: Completely avoids rustls/TLS
- **No tokio/mio**: Uses only WASM-compatible dependencies
### ❌ WASM Limitations
- **No native networking**: `tokio-tungstenite` doesn't work in WASM
- **No TLS support**: rustls is not WASM-compatible
- **No file system**: Certificate loading not available
## Architecture for WASM Users
```
WASM Application
├── Use socktop_connector types (✅ this test proves it works)
├── Use browser WebSocket API for networking
└── Handle serialization with socktop message format
```
## Quick Start
1. **Build the WASM package**:
```bash
cd socktop_wasm_test
wasm-pack build --target web --out-dir pkg
```
2. **Start local server**:
```bash
basic-http-server .
```
3. **Open browser** to `http://localhost:8000` and click "Run WASM Test"
## Success Criteria
- ✅ WASM builds without any networking dependencies
- ✅ Core types compile and serialize properly
- ✅ Configuration API works for WebSocket setup
- ✅ No rustls/TLS/tokio/mio dependencies
## Real-World WASM Usage
WASM users should:
1. **Use these types** for message structure compatibility
2. **Use browser WebSocket** for actual connections:
```javascript
const ws = new WebSocket('ws://localhost:3000/ws');
ws.send(JSON.stringify({ request: 'Metrics' }));
```
3. **Handle responses** using the same serialization format
This test proves `socktop_connector`'s **types and patterns** work in WASM, even though the networking must be handled differently.
+136
View File
@@ -0,0 +1,136 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>Socktop Connector WASM Test</title>
<style>
body { font-family: monospace; padding: 20px; background-color: #f5f5f5; }
.container { max-width: 800px; margin: 0 auto; background: white; padding: 20px; border-radius: 8px; box-shadow: 0 2px 4px rgba(0,0,0,0.1); }
.log { margin: 5px 0; padding: 5px; border-radius: 4px; }
.success { color: #0a7c0a; background-color: #e8f5e8; }
.warning { color: #b8860b; background-color: #fdf6e3; }
.error { color: #d2322d; background-color: #f9e6e6; }
.info { color: #0969da; background-color: #e6f3ff; }
button {
background: #0969da;
color: white;
border: none;
padding: 10px 20px;
border-radius: 4px;
cursor: pointer;
font-size: 14px;
margin: 10px 0;
}
button:hover { background: #0757c7; }
button:disabled { background: #ccc; cursor: not-allowed; }
#output {
border: 1px solid #ddd;
border-radius: 4px;
padding: 10px;
min-height: 200px;
background: #fafafa;
font-family: 'Courier New', monospace;
}
.status { font-weight: bold; margin: 10px 0; }
</style>
</head>
<body>
<div class="container">
<h1>🦀 Socktop Connector WASM Test</h1>
<div class="status">
<p><strong>Test Purpose:</strong> Verify socktop_connector works in WebAssembly without TLS dependencies</p>
<p><strong>Status:</strong> <span id="status">Loading WASM module...</span></p>
</div>
<button id="test-btn" disabled>Run WASM Test</button>
<button id="clear-btn">Clear Output</button>
<h3>Output:</h3>
<div id="output"></div>
<h3>ICON LEGEND:</h3>
<ul>
<li>✅ <strong>Success:</strong> No rustls/TLS errors, connector loads in WASM</li>
<li>⚠️ <strong>Expected:</strong> Connection failures without running socktop_agent</li>
<li>❌ <strong>Failure:</strong> Build errors or TLS dependency issues</li>
</ul>
<p><small>💡 <strong>Tip:</strong> To test with real data, start socktop_agent with: <code>cargo run --bin socktop_agent -- --no-tls --port 3000</code></small></p>
</div>
<script type="module">
import init, { test_socktop_connector } from './pkg/socktop_wasm_test.js';
const output = document.getElementById('output');
const testBtn = document.getElementById('test-btn');
const clearBtn = document.getElementById('clear-btn');
const status = document.getElementById('status');
// Capture console output and display it on page
const originalLog = console.log;
const originalError = console.error;
function addLog(text, type = 'info') {
const div = document.createElement('div');
div.className = `log ${type}`;
div.textContent = new Date().toLocaleTimeString() + ' - ' + text;
output.appendChild(div);
output.scrollTop = output.scrollHeight;
}
console.log = function(...args) {
originalLog.apply(console, args);
const text = args.join(' ');
let type = 'info';
if (text.includes('✅')) {
type = 'success';
} else if (text.includes('⚠️')) {
type = 'warning';
} else if (text.includes('❌')) {
type = 'error';
}
addLog(text, type);
};
console.error = function(...args) {
originalError.apply(console, args);
addLog('ERROR: ' + args.join(' '), 'error');
};
clearBtn.onclick = () => {
output.innerHTML = '';
};
async function run() {
try {
await init();
addLog('WASM module initialized successfully!', 'success');
status.textContent = 'Ready to test';
testBtn.disabled = false;
testBtn.onclick = () => {
testBtn.disabled = true;
addLog('=== Starting WASM Test ===', 'info');
try {
test_socktop_connector();
setTimeout(() => {
testBtn.disabled = false;
}, 2000);
} catch (e) {
addLog('Test execution failed: ' + e.message, 'error');
testBtn.disabled = false;
}
};
} catch (e) {
addLog('Failed to initialize WASM: ' + e.message, 'error');
status.textContent = 'Failed to load WASM module';
console.error('WASM initialization error:', e);
}
}
run();
</script>
</body>
</html>
+125
View File
@@ -0,0 +1,125 @@
use wasm_bindgen::prelude::*;
use serde::{Deserialize, Serialize};
// Import the `console.log` function from the browser
#[wasm_bindgen]
extern "C" {
#[wasm_bindgen(js_namespace = console)]
fn log(s: &str);
}
// Define a macro for easier console logging
macro_rules! console_log {
($($t:tt)*) => (log(&format_args!($($t)*).to_string()))
}
// Replicate the core types from socktop_connector for WASM use
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct WasmConnectorConfig {
pub url: String,
pub ws_version: Option<String>,
pub ws_protocols: Option<Vec<String>>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub enum WasmAgentRequest {
Metrics,
Processes,
Disks,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct WasmMetrics {
pub hostname: String,
pub cpu_total: f64,
pub mem_used: u64,
pub mem_total: u64,
}
impl WasmConnectorConfig {
pub fn new(url: impl Into<String>) -> Self {
Self {
url: url.into(),
ws_version: None,
ws_protocols: None,
}
}
pub fn with_version(mut self, version: String) -> Self {
self.ws_version = Some(version);
self
}
pub fn with_protocols(mut self, protocols: Vec<String>) -> Self {
self.ws_protocols = Some(protocols);
self
}
}
// This is the main entry point called from JavaScript
#[wasm_bindgen]
pub fn test_socktop_connector() {
console_error_panic_hook::set_once();
console_log!("🦀 Starting WASM-native socktop test...");
// Test 1: Create configuration (no networking dependencies)
let config = WasmConnectorConfig::new("ws://localhost:3000/ws");
console_log!("✅ WasmConnectorConfig created: {}", config.url);
// Test 2: Test configuration methods
let config_with_protocols = config
.clone()
.with_protocols(vec!["socktop".to_string(), "v1".to_string()]);
console_log!("✅ Config with protocols: {:?}", config_with_protocols.ws_protocols);
let config_with_version = config
.with_version("13".to_string());
console_log!("✅ Config with version: {:?}", config_with_version.ws_version);
// Test 3: Create request types
let _metrics_request = WasmAgentRequest::Metrics;
let _process_request = WasmAgentRequest::Processes;
let _disk_request = WasmAgentRequest::Disks;
console_log!("✅ Request types created successfully");
// Test 4: Test serialization (important for WASM interop)
match serde_json::to_string(&_metrics_request) {
Ok(json) => console_log!("✅ Request serialization works: {}", json),
Err(e) => console_log!("❌ Serialization failed: {}", e),
}
// Test 5: Test example metrics deserialization
let sample_metrics = WasmMetrics {
hostname: "wasm-test-host".to_string(),
cpu_total: 45.2,
mem_used: 8_000_000_000,
mem_total: 16_000_000_000,
};
match serde_json::to_string(&sample_metrics) {
Ok(json) => {
console_log!("✅ Metrics serialization: {}", json);
// Test round-trip
match serde_json::from_str::<WasmMetrics>(&json) {
Ok(parsed) => console_log!("✅ Round-trip successful: hostname={}", parsed.hostname),
Err(e) => console_log!("❌ Deserialization failed: {}", e),
}
}
Err(e) => console_log!("❌ Metrics serialization failed: {}", e),
}
console_log!("");
console_log!("🎉 WASM Compatibility Test Results:");
console_log!("✅ Core types compile and work in WASM");
console_log!("✅ Configuration API works without networking");
console_log!("✅ Serialization/deserialization works");
console_log!("✅ NO rustls/TLS dependencies required");
console_log!("✅ NO tokio/mio dependencies required");
console_log!("");
console_log!("💡 For actual WebSocket connections in WASM:");
console_log!(" • Use browser's WebSocket API directly");
console_log!(" • Or use a WASM-compatible WebSocket crate");
console_log!(" • Use these types for message serialization");
}