feat: Add Mycelium JSON-RPC client
- Adds a new V language client for interacting with the Mycelium JSON-RPC admin API. - Includes comprehensive example code demonstrating all API features. - Implements all methods defined in the Mycelium JSON-RPC spec. - Provides type-safe API with robust error handling. - Uses HTTP transport for communication with the Mycelium node.
This commit is contained in:
257
examples/clients/mycelium_rpc.vsh
Executable file
257
examples/clients/mycelium_rpc.vsh
Executable file
@@ -0,0 +1,257 @@
|
||||
#!/usr/bin/env -S v -n -w -gc none -cc tcc -d use_openssl -enable-globals run
|
||||
|
||||
// Mycelium RPC Client Example
|
||||
// This example demonstrates how to use the new Mycelium JSON-RPC client
|
||||
// to interact with a Mycelium node's admin API
|
||||
import freeflowuniverse.herolib.clients.mycelium_rpc
|
||||
import freeflowuniverse.herolib.installers.net.mycelium_installer
|
||||
import time
|
||||
import os
|
||||
import encoding.base64
|
||||
|
||||
const mycelium_port = 8990
|
||||
|
||||
fn terminate_mycelium() ! {
|
||||
// Try to find and kill any running mycelium process
|
||||
res := os.execute('pkill mycelium')
|
||||
if res.exit_code == 0 {
|
||||
println('Terminated existing mycelium processes')
|
||||
time.sleep(1 * time.second)
|
||||
}
|
||||
}
|
||||
|
||||
fn start_mycelium_node() ! {
|
||||
// Start a mycelium node with JSON-RPC API enabled
|
||||
println('Starting Mycelium node with JSON-RPC API on port ${mycelium_port}...')
|
||||
|
||||
// Create directory for mycelium data
|
||||
os.execute('mkdir -p /tmp/mycelium_rpc_example')
|
||||
|
||||
// Start mycelium in background with both HTTP and JSON-RPC APIs enabled
|
||||
spawn fn () {
|
||||
cmd := 'cd /tmp/mycelium_rpc_example && mycelium --peers tcp://185.69.166.8:9651 quic://[2a02:1802:5e:0:ec4:7aff:fe51:e36b]:9651 tcp://65.109.18.113:9651 --tun-name tun_rpc_example --tcp-listen-port 9660 --quic-listen-port 9661 --api-addr 127.0.0.1:8989 --jsonrpc-addr 127.0.0.1:${mycelium_port}'
|
||||
println('Executing: ${cmd}')
|
||||
result := os.execute(cmd)
|
||||
if result.exit_code != 0 {
|
||||
println('Mycelium failed to start: ${result.output}')
|
||||
}
|
||||
}()
|
||||
|
||||
// Wait for the node to start (JSON-RPC server needs a bit more time)
|
||||
println('Waiting for mycelium to start...')
|
||||
time.sleep(5 * time.second)
|
||||
|
||||
// Check if mycelium is running
|
||||
check_result := os.execute('pgrep mycelium')
|
||||
if check_result.exit_code == 0 {
|
||||
println('Mycelium process is running (PID: ${check_result.output.trim_space()})')
|
||||
} else {
|
||||
println('Warning: Mycelium process not found')
|
||||
}
|
||||
|
||||
// Check what ports are listening
|
||||
port_check := os.execute('lsof -i :${mycelium_port}')
|
||||
if port_check.exit_code == 0 {
|
||||
println('Port ${mycelium_port} is listening:')
|
||||
println(port_check.output)
|
||||
} else {
|
||||
println('Warning: Port ${mycelium_port} is not listening')
|
||||
}
|
||||
}
|
||||
|
||||
fn main() {
|
||||
// Install mycelium if not already installed
|
||||
println('Checking Mycelium installation...')
|
||||
mut installer := mycelium_installer.get()!
|
||||
installer.install()!
|
||||
|
||||
// Clean up any existing processes
|
||||
terminate_mycelium() or {}
|
||||
|
||||
defer {
|
||||
// Clean up on exit
|
||||
terminate_mycelium() or {}
|
||||
os.execute('rm -rf /tmp/mycelium_rpc_example')
|
||||
}
|
||||
|
||||
// Start mycelium node
|
||||
start_mycelium_node()!
|
||||
|
||||
// Create RPC client
|
||||
println('\n=== Creating Mycelium RPC Client ===')
|
||||
mut client := mycelium_rpc.new_client(
|
||||
name: 'example_client'
|
||||
url: 'http://localhost:${mycelium_port}'
|
||||
)!
|
||||
|
||||
println('Connected to Mycelium node at http://localhost:${mycelium_port}')
|
||||
|
||||
// Example 1: Get node information
|
||||
println('\n=== Getting Node Information ===')
|
||||
info := client.get_info() or {
|
||||
println('Error getting node info: ${err}')
|
||||
println('Make sure Mycelium node is running with API enabled')
|
||||
return
|
||||
}
|
||||
println('Node Subnet: ${info.node_subnet}')
|
||||
println('Node Public Key: ${info.node_pubkey}')
|
||||
|
||||
// Example 2: List peers
|
||||
println('\n=== Listing Peers ===')
|
||||
peers := client.get_peers() or {
|
||||
println('Error getting peers: ${err}')
|
||||
return
|
||||
}
|
||||
println('Found ${peers.len} peers:')
|
||||
for i, peer in peers {
|
||||
println('Peer ${i + 1}:')
|
||||
println(' Endpoint: ${peer.endpoint.proto}://${peer.endpoint.socket_addr}')
|
||||
println(' Type: ${peer.peer_type}')
|
||||
println(' Connection State: ${peer.connection_state}')
|
||||
println(' TX Bytes: ${peer.tx_bytes}')
|
||||
println(' RX Bytes: ${peer.rx_bytes}')
|
||||
}
|
||||
|
||||
// Example 3: Get routing information
|
||||
println('\n=== Getting Routing Information ===')
|
||||
|
||||
// Get selected routes
|
||||
routes := client.get_selected_routes() or {
|
||||
println('Error getting selected routes: ${err}')
|
||||
return
|
||||
}
|
||||
println('Selected Routes (${routes.len}):')
|
||||
for route in routes {
|
||||
println(' ${route.subnet} -> ${route.next_hop} (metric: ${route.metric}, seqno: ${route.seqno})')
|
||||
}
|
||||
|
||||
// Get fallback routes
|
||||
fallback_routes := client.get_fallback_routes() or {
|
||||
println('Error getting fallback routes: ${err}')
|
||||
return
|
||||
}
|
||||
println('Fallback Routes (${fallback_routes.len}):')
|
||||
for route in fallback_routes {
|
||||
println(' ${route.subnet} -> ${route.next_hop} (metric: ${route.metric}, seqno: ${route.seqno})')
|
||||
}
|
||||
|
||||
// Example 4: Topic management
|
||||
println('\n=== Topic Management ===')
|
||||
|
||||
// Get default topic action
|
||||
default_action := client.get_default_topic_action() or {
|
||||
println('Error getting default topic action: ${err}')
|
||||
return
|
||||
}
|
||||
println('Default topic action (accept): ${default_action}')
|
||||
|
||||
// Get configured topics
|
||||
topics := client.get_topics() or {
|
||||
println('Error getting topics: ${err}')
|
||||
return
|
||||
}
|
||||
println('Configured topics (${topics.len}):')
|
||||
for topic in topics {
|
||||
println(' - ${topic}')
|
||||
}
|
||||
|
||||
// Example 5: Add a test topic (try different names)
|
||||
println('\n=== Adding Test Topics ===')
|
||||
test_topics := ['example_topic', 'test_with_underscore', 'hello world', 'test', 'a']
|
||||
|
||||
for topic in test_topics {
|
||||
println('Trying to add topic: "${topic}"')
|
||||
add_result := client.add_topic(topic) or {
|
||||
println('Error adding topic "${topic}": ${err}')
|
||||
continue
|
||||
}
|
||||
if add_result {
|
||||
println('Successfully added topic: ${topic}')
|
||||
|
||||
// Try to remove it immediately
|
||||
remove_result := client.remove_topic(topic) or {
|
||||
println('Error removing topic "${topic}": ${err}')
|
||||
continue
|
||||
}
|
||||
if remove_result {
|
||||
println('Successfully removed topic: ${topic}')
|
||||
}
|
||||
break // Stop after first success
|
||||
}
|
||||
}
|
||||
|
||||
// Example 6: Message operations (demonstration only - requires another node)
|
||||
println('\n=== Message Operations (Demo) ===')
|
||||
println('Note: These operations require another Mycelium node to be meaningful')
|
||||
|
||||
// Try to pop a message with a short timeout (will likely return "No message ready" error)
|
||||
message := client.pop_message(false, 1, '') or {
|
||||
println('No messages available (expected): ${err}')
|
||||
mycelium_rpc.InboundMessage{}
|
||||
}
|
||||
|
||||
if message.id != '' {
|
||||
println('Received message:')
|
||||
println(' ID: ${message.id}')
|
||||
println(' From: ${message.src_ip}')
|
||||
println(' Payload: ${base64.decode_str(message.payload)}')
|
||||
}
|
||||
|
||||
// Example 7: Peer management (demonstration)
|
||||
println('\n=== Peer Management Demo ===')
|
||||
|
||||
// Try to add a peer (this is just for demonstration)
|
||||
test_endpoint := 'tcp://127.0.0.1:9999'
|
||||
add_peer_result := client.add_peer(test_endpoint) or {
|
||||
println('Error adding peer (expected if endpoint is invalid): ${err}')
|
||||
false
|
||||
}
|
||||
|
||||
if add_peer_result {
|
||||
println('Successfully added peer: ${test_endpoint}')
|
||||
|
||||
// Remove the test peer
|
||||
remove_peer_result := client.delete_peer(test_endpoint) or {
|
||||
println('Error removing peer: ${err}')
|
||||
false
|
||||
}
|
||||
|
||||
if remove_peer_result {
|
||||
println('Successfully removed test peer')
|
||||
}
|
||||
}
|
||||
|
||||
// Example 8: Get public key from IP (demonstration)
|
||||
println('\n=== Public Key Lookup Demo ===')
|
||||
|
||||
// This will likely fail unless we have a valid mycelium IP
|
||||
if info.node_subnet != '' {
|
||||
// Extract the first IP from the subnet for testing
|
||||
subnet_parts := info.node_subnet.split('::')
|
||||
if subnet_parts.len > 0 {
|
||||
test_ip := subnet_parts[0] + '::1'
|
||||
pubkey_response := client.get_public_key_from_ip(test_ip) or {
|
||||
println('Could not get public key for IP ${test_ip}: ${err}')
|
||||
mycelium_rpc.PublicKeyResponse{}
|
||||
}
|
||||
|
||||
if pubkey_response.node_pub_key != '' {
|
||||
println('Public key for ${test_ip}: ${pubkey_response.node_pub_key}')
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
println('\n=== Mycelium RPC Client Example Completed ===')
|
||||
println('This example demonstrated:')
|
||||
println('- Getting node information')
|
||||
println('- Listing peers and their connection status')
|
||||
println('- Retrieving routing information')
|
||||
println('- Managing topics')
|
||||
println('- Message operations (basic)')
|
||||
println('- Peer management')
|
||||
println('- Public key lookups')
|
||||
println('')
|
||||
println('For full message sending/receiving functionality, you would need')
|
||||
println('multiple Mycelium nodes running and connected to each other.')
|
||||
println('See the Mycelium documentation for more advanced usage.')
|
||||
}
|
||||
Reference in New Issue
Block a user