#![cfg_attr(target_arch = "wasm32", no_main)] use hero_websocket_client::CircleWsClientBuilder; #[cfg(not(target_arch = "wasm32"))] use std::env; #[cfg(not(target_arch = "wasm32"))] use std::path::Path; #[cfg(not(target_arch = "wasm32"))] use std::io::{self, Write}; #[cfg(target_arch = "wasm32")] use wasm_bindgen::prelude::*; #[cfg(target_arch = "wasm32")] use web_sys::{console, window}; #[cfg(target_arch = "wasm32")] use wasm_bindgen_futures::spawn_local; #[cfg(not(target_arch = "wasm32"))] use clap::{Arg, ArgAction, Command}; #[cfg(not(target_arch = "wasm32"))] use dotenv::dotenv; #[cfg(not(target_arch = "wasm32"))] use env_logger; #[cfg(not(target_arch = "wasm32"))] use tokio; #[cfg(not(target_arch = "wasm32"))] use log::{error, info}; #[derive(Debug)] struct Args { ws_url: String, script: Option, script_path: Option, verbose: u8, no_timestamp: bool, } #[cfg(not(target_arch = "wasm32"))] fn parse_args() -> Args { let matches = Command::new("circles_client") .version("0.1.0") .about("WebSocket client for Circles server") .arg( Arg::new("url") .help("WebSocket server URL") .required(true) .index(1), ) .arg( Arg::new("script") .short('s') .long("script") .value_name("SCRIPT") .help("Rhai script to execute") .conflicts_with("script_path"), ) .arg( Arg::new("script_path") .short('f') .long("file") .value_name("FILE") .help("Path to Rhai script file") .conflicts_with("script"), ) .arg( Arg::new("verbose") .short('v') .long("verbose") .help("Increase verbosity (can be used multiple times)") .action(ArgAction::Count), ) .arg( Arg::new("no_timestamp") .long("no-timestamp") .help("Remove timestamps from log output") .action(ArgAction::SetTrue), ) .get_matches(); Args { ws_url: matches.get_one::("url").unwrap().clone(), script: matches.get_one::("script").cloned(), script_path: matches.get_one::("script_path").cloned(), verbose: matches.get_count("verbose"), no_timestamp: matches.get_flag("no_timestamp"), } } #[cfg(not(target_arch = "wasm32"))] fn setup_logging(verbose: u8, no_timestamp: bool) { let log_level = match verbose { 0 => "warn,hero_websocket_client=info", 1 => "info,hero_websocket_client=debug", 2 => "debug", _ => "trace", }; std::env::set_var("RUST_LOG", log_level); // Configure env_logger with or without timestamps if no_timestamp { env_logger::Builder::from_default_env() .format_timestamp(None) .init(); } else { env_logger::init(); } } #[cfg(not(target_arch = "wasm32"))] fn load_private_key() -> Result> { // Try to load from .env file first if let Ok(_) = dotenv() { if let Ok(key) = env::var("PRIVATE_KEY") { return Ok(key); } } // Try to load from cmd/.env file let cmd_env_path = Path::new("cmd/.env"); if cmd_env_path.exists() { dotenv::from_path(cmd_env_path)?; if let Ok(key) = env::var("PRIVATE_KEY") { return Ok(key); } } Err("PRIVATE_KEY not found in environment or .env files".into()) } #[cfg(target_arch = "wasm32")] async fn run_interactive_mode(client: hero_websocket_client::CircleWsClient) -> Result<(), Box> { console::log_1(&"Entering interactive mode. Type 'exit' or 'quit' to leave.".into()); console::log_1(&"šŸ”„ Interactive mode - Enter Rhai scripts (type 'exit' or 'quit' to leave):\n".into()); // In wasm32, we need to use browser's console for input/output let window = window().expect("Window not available"); let input = window.prompt_with_message("Enter Rhai script (or 'exit' to quit):") .map_err(|e| format!("Failed to get input: {:#?}", e))? // Use debug formatting .unwrap_or_default(); // Handle empty or exit cases if input == "exit" || input == "quit" { console::log_1(&"šŸ‘‹ Goodbye!".into()); return Ok(()); } // Execute the script match client.play(input).await { Ok(result) => { console::log_1(&format!("šŸ“¤ Result: {}", result.output).into()); } Err(e) => { console::log_1(&format!("āŒ Script execution failed: {}", e).into()); } } Ok(()) } #[cfg(target_arch = "wasm32")] async fn execute_script(client: hero_websocket_client::CircleWsClient, script: String) -> Result<(), Box> { console::log_1(&format!("Executing script: {}", script).into()); match client.play(script).await { Ok(result) => { console::log_1(&result.output.into()); Ok(()) } Err(e) => { console::log_1(&format!("Script execution failed: {}", e).into()); Err(e.into()) } } } #[cfg(target_arch = "wasm32")] pub async fn start_client(url: &str, script: Option) -> Result<(), Box> { // Build client let mut client = CircleWsClientBuilder::new(url.to_string()) .build(); // Connect to WebSocket server console::log_1(&"šŸ”Œ Connecting to WebSocket server...".into()); if let Err(e) = client.connect().await { console::log_1(&format!("āŒ Failed to connect: {}", e).into()); return Err(e.into()); } console::log_1(&"āœ… Connected successfully".into()); // Authenticate with server if let Err(e) = client.authenticate().await { console::log_1(&format!("āŒ Authentication failed: {}", e).into()); return Err(e.into()); } console::log_1(&"āœ… Authentication successful".into()); // Handle script execution if let Some(script) = script { execute_script(client, script).await } else { run_interactive_mode(client).await } } #[cfg(not(target_arch = "wasm32"))] async fn execute_script(client: hero_websocket_client::CircleWsClient, script: String) -> Result<(), Box> { info!("Executing script: {}", script); match client.play(script).await { Ok(result) => { println!("{}", result.output); Ok(()) } Err(e) => { error!("Script execution failed: {}", e); Err(e.into()) } } } #[cfg(not(target_arch = "wasm32"))] async fn load_script_from_file(path: &str) -> Result> { let script = tokio::fs::read_to_string(path).await?; Ok(script) } #[cfg(not(target_arch = "wasm32"))] async fn run_interactive_mode(client: hero_websocket_client::CircleWsClient) -> Result<(), Box> { println!("\nšŸ”„ Interactive mode - Enter Rhai scripts (type 'exit' or 'quit' to leave):\n"); loop { print!("Enter Rhai script (or 'exit' to quit): "); io::stdout().flush()?; let mut input = String::new(); io::stdin().read_line(&mut input)?; let input = input.trim().to_string(); if input == "exit" || input == "quit" { println!("\nšŸ‘‹ Goodbye!"); return Ok(()); } match client.play(input).await { Ok(result) => { println!("\nšŸ“¤ Result: {}", result.output); } Err(e) => { error!("āŒ Script execution failed: {}", e); println!("\nāŒ Script execution failed: {}", e); } } println!(); } } #[cfg(not(target_arch = "wasm32"))] #[tokio::main] async fn main() -> Result<(), Box> { let args = parse_args(); setup_logging(args.verbose, args.no_timestamp); info!("šŸš€ Starting Circles WebSocket client"); info!("šŸ“” Connecting to: {}", args.ws_url); // Load private key from environment let private_key = match load_private_key() { Ok(key) => { info!("šŸ”‘ Private key loaded from environment"); key } Err(e) => { error!("āŒ Failed to load private key: {}", e); eprintln!("Error: {}", e); eprintln!("Please set PRIVATE_KEY in your environment or create a cmd/.env file with:"); eprintln!("PRIVATE_KEY=your_private_key_here"); std::process::exit(1); } }; // Build client with private key let mut client = CircleWsClientBuilder::new(args.ws_url.clone()) .with_keypair(private_key) .build(); // Connect to WebSocket server info!("šŸ”Œ Connecting to WebSocket server..."); if let Err(e) = client.connect().await { error!("āŒ Failed to connect: {}", e); eprintln!("Connection failed: {}", e); std::process::exit(1); } info!("āœ… Connected successfully"); // Authenticate with server info!("šŸ” Authenticating with server..."); match client.authenticate().await { Ok(true) => { info!("āœ… Authentication successful"); println!("šŸ” Authentication successful"); } Ok(false) => { error!("āŒ Authentication failed"); eprintln!("Authentication failed"); std::process::exit(1); } Err(e) => { error!("āŒ Authentication error: {}", e); eprintln!("Authentication error: {}", e); std::process::exit(1); } } // Determine execution mode let result = if let Some(script) = args.script { // Execute provided script and exit execute_script(client, script).await } else if let Some(script_path) = args.script_path { // Load script from file and execute match load_script_from_file(&script_path).await { Ok(script) => execute_script(client, script).await, Err(e) => { error!("āŒ Failed to load script from file '{}': {}", script_path, e); eprintln!("Failed to load script file: {}", e); std::process::exit(1); } } } else { // Enter interactive mode run_interactive_mode(client).await }; // Handle any errors from execution if let Err(e) = result { error!("āŒ Execution failed: {}", e); std::process::exit(1); } info!("šŸ Client finished successfully"); Ok(()) }