wip
This commit is contained in:
		
							
								
								
									
										4
									
								
								interfaces/websocket/client/Cargo.lock
									
									
									
										generated
									
									
									
								
							
							
						
						
									
										4
									
								
								interfaces/websocket/client/Cargo.lock
									
									
									
										generated
									
									
									
								
							@@ -595,7 +595,7 @@ dependencies = [
 | 
			
		||||
 "log",
 | 
			
		||||
 "once_cell",
 | 
			
		||||
 "redis",
 | 
			
		||||
 "rhai_dispatcher",
 | 
			
		||||
 "rhai_supervisor",
 | 
			
		||||
 "rustls",
 | 
			
		||||
 "rustls-pemfile",
 | 
			
		||||
 "serde",
 | 
			
		||||
@@ -1765,7 +1765,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
 | 
			
		||||
checksum = "2b15c43186be67a4fd63bee50d0303afffcef381492ebe2c5d87f324e1b8815c"
 | 
			
		||||
 | 
			
		||||
[[package]]
 | 
			
		||||
name = "rhai_dispatcher"
 | 
			
		||||
name = "rhai_supervisor"
 | 
			
		||||
version = "0.1.0"
 | 
			
		||||
dependencies = [
 | 
			
		||||
 "chrono",
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										4
									
								
								interfaces/websocket/server/Cargo.lock
									
									
									
										generated
									
									
									
								
							
							
						
						
									
										4
									
								
								interfaces/websocket/server/Cargo.lock
									
									
									
										generated
									
									
									
								
							@@ -584,7 +584,7 @@ dependencies = [
 | 
			
		||||
 "once_cell",
 | 
			
		||||
 "rand 0.8.5",
 | 
			
		||||
 "redis",
 | 
			
		||||
 "rhai_dispatcher",
 | 
			
		||||
 "rhai_supervisor",
 | 
			
		||||
 "rustls",
 | 
			
		||||
 "rustls-pemfile",
 | 
			
		||||
 "secp256k1",
 | 
			
		||||
@@ -1769,7 +1769,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
 | 
			
		||||
checksum = "2b15c43186be67a4fd63bee50d0303afffcef381492ebe2c5d87f324e1b8815c"
 | 
			
		||||
 | 
			
		||||
[[package]]
 | 
			
		||||
name = "rhai_dispatcher"
 | 
			
		||||
name = "rhai_supervisor"
 | 
			
		||||
version = "0.1.0"
 | 
			
		||||
dependencies = [
 | 
			
		||||
 "chrono",
 | 
			
		||||
 
 | 
			
		||||
@@ -44,7 +44,7 @@ redis = { workspace = true }
 | 
			
		||||
uuid = { workspace = true }
 | 
			
		||||
tokio = { workspace = true }
 | 
			
		||||
chrono = { workspace = true }
 | 
			
		||||
hero_dispatcher = { path = "../../../core/dispatcher" }
 | 
			
		||||
hero_supervisor = { path = "../../../core/supervisor" }
 | 
			
		||||
hero_job = { path = "../../../core/job" }
 | 
			
		||||
thiserror = { workspace = true }
 | 
			
		||||
heromodels = { path = "../../../../db/heromodels" }
 | 
			
		||||
 
 | 
			
		||||
@@ -4,9 +4,14 @@ An OpenRPC WebSocket Server to interface with the [cores](../../core) of authori
 | 
			
		||||
 | 
			
		||||
- [OpenRPC Specification](openrpc.json) defines the API.
 | 
			
		||||
- There are RPC Operations specified to authorize a websocket connection.
 | 
			
		||||
- Authorized clients can execute Rhai scripts on the server.
 | 
			
		||||
- Authorized clients can manage jobs.
 | 
			
		||||
- The server uses the [supervisor] to dispatch [jobs] to the [workers]. 
 | 
			
		||||
 | 
			
		||||
## Circles
 | 
			
		||||
 | 
			
		||||
Circles are contexts which a hero can act in. Each circle has a unique public key and a set of members. 
 | 
			
		||||
The server offers a separate path for each circle.
 | 
			
		||||
 | 
			
		||||
## Authentication
 | 
			
		||||
 | 
			
		||||
The server provides a robust authentication mechanism to ensure that only authorized clients can execute scripts. The entire flow is handled over the WebSocket connection using two dedicated JSON-RPC methods:
 | 
			
		||||
 
 | 
			
		||||
@@ -1,7 +1,5 @@
 | 
			
		||||
use hero_websocket_server::{ServerBuilder, TlsConfigError};
 | 
			
		||||
use hero_websocket_server::{ServerBuilder, ServerConfig};
 | 
			
		||||
use clap::Parser;
 | 
			
		||||
use dotenv::dotenv;
 | 
			
		||||
use log::info;
 | 
			
		||||
 | 
			
		||||
#[derive(Parser, Debug)]
 | 
			
		||||
#[clap(author, version, about, long_about = None)]
 | 
			
		||||
@@ -39,14 +37,62 @@ struct Args {
 | 
			
		||||
    #[clap(long, help = "Enable webhook handling")]
 | 
			
		||||
    webhooks: bool,
 | 
			
		||||
 | 
			
		||||
    #[clap(long, value_parser, help = "Worker ID for the server")]
 | 
			
		||||
    worker_id: String,
 | 
			
		||||
    #[clap(short, long, value_parser, help = "Path to configuration file")]
 | 
			
		||||
    config: Option<String>,
 | 
			
		||||
 | 
			
		||||
    #[clap(long, help = "Generate a sample configuration file")]
 | 
			
		||||
    generate_config: bool,
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
#[actix_web::main]
 | 
			
		||||
async fn main() -> std::io::Result<()> {
 | 
			
		||||
    let args = Args::parse();
 | 
			
		||||
 | 
			
		||||
    // Handle config file generation
 | 
			
		||||
    if args.generate_config {
 | 
			
		||||
        let sample_config = ServerConfig::create_sample();
 | 
			
		||||
        let config_path = "config.json";
 | 
			
		||||
        match sample_config.to_file(config_path) {
 | 
			
		||||
            Ok(_) => {
 | 
			
		||||
                println!("✅ Sample configuration file generated: {}", config_path);
 | 
			
		||||
                println!("📝 Edit the file to customize your server configuration.");
 | 
			
		||||
                return Ok(());
 | 
			
		||||
            }
 | 
			
		||||
            Err(e) => {
 | 
			
		||||
                eprintln!("❌ Failed to generate config file: {}", e);
 | 
			
		||||
                std::process::exit(1);
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    // Load configuration from file if provided, otherwise use CLI args
 | 
			
		||||
    let config = if let Some(config_path) = &args.config {
 | 
			
		||||
        match ServerConfig::from_file(config_path) {
 | 
			
		||||
            Ok(config) => {
 | 
			
		||||
                println!("📄 Loaded configuration from: {}", config_path);
 | 
			
		||||
                config
 | 
			
		||||
            }
 | 
			
		||||
            Err(e) => {
 | 
			
		||||
                eprintln!("❌ Failed to load config file {}: {}", config_path, e);
 | 
			
		||||
                std::process::exit(1);
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
    } else {
 | 
			
		||||
        // Create config from CLI arguments
 | 
			
		||||
        ServerConfig {
 | 
			
		||||
            host: args.host.clone(),
 | 
			
		||||
            port: args.port,
 | 
			
		||||
            redis_url: args.redis_url.clone(),
 | 
			
		||||
            auth: args.auth,
 | 
			
		||||
            tls: args.tls,
 | 
			
		||||
            cert: args.cert.clone(),
 | 
			
		||||
            key: args.key.clone(),
 | 
			
		||||
            tls_port: args.tls_port,
 | 
			
		||||
            webhooks: args.webhooks,
 | 
			
		||||
            circles: std::collections::HashMap::new(), // Empty circles when using CLI
 | 
			
		||||
        }
 | 
			
		||||
    };
 | 
			
		||||
 | 
			
		||||
    // Configure logging based on verbosity level
 | 
			
		||||
    let log_config = match args.verbose {
 | 
			
		||||
        0 => {
 | 
			
		||||
@@ -78,39 +124,14 @@ async fn main() -> std::io::Result<()> {
 | 
			
		||||
        env_logger::init();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    // Validate TLS configuration
 | 
			
		||||
    if args.tls && (args.cert.is_none() || args.key.is_none()) {
 | 
			
		||||
        eprintln!("Error: TLS is enabled but certificate or key path is missing");
 | 
			
		||||
        eprintln!("Use --cert and --key to specify certificate and key files");
 | 
			
		||||
    // Validate configuration
 | 
			
		||||
    if let Err(e) = config.validate() {
 | 
			
		||||
        eprintln!("❌ Configuration validation failed: {}", e);
 | 
			
		||||
        std::process::exit(1);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    let mut builder = ServerBuilder::new()
 | 
			
		||||
        .host(args.host.clone())
 | 
			
		||||
        .port(args.port)
 | 
			
		||||
        .redis_url(args.redis_url.clone())
 | 
			
		||||
        .worker_id(args.worker_id.clone());
 | 
			
		||||
 | 
			
		||||
    if args.auth {
 | 
			
		||||
        builder = builder.with_auth();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    if args.tls {
 | 
			
		||||
        if let (Some(cert), Some(key)) = (args.cert.clone(), args.key.clone()) {
 | 
			
		||||
            builder = builder.with_tls(cert, key);
 | 
			
		||||
        } else {
 | 
			
		||||
            eprintln!("Error: TLS is enabled but --cert or --key is missing.");
 | 
			
		||||
            std::process::exit(1);
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    if let Some(tls_port) = args.tls_port {
 | 
			
		||||
        builder = builder.with_tls_port(tls_port);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    if args.webhooks {
 | 
			
		||||
        builder = builder.with_webhooks();
 | 
			
		||||
    }
 | 
			
		||||
    // Build server from configuration
 | 
			
		||||
    let builder = ServerBuilder::new().from_config(config.clone());
 | 
			
		||||
 | 
			
		||||
    let server = match builder.build() {
 | 
			
		||||
        Ok(server) => server,
 | 
			
		||||
@@ -122,27 +143,36 @@ async fn main() -> std::io::Result<()> {
 | 
			
		||||
 | 
			
		||||
    println!("🚀 Starting Circles WebSocket Server");
 | 
			
		||||
    println!("📋 Configuration:");
 | 
			
		||||
    println!("   Host: {}", args.host);
 | 
			
		||||
    println!("   Port: {}", args.port);
 | 
			
		||||
    if let Some(tls_port) = args.tls_port {
 | 
			
		||||
    println!("   Host: {}", config.host);
 | 
			
		||||
    println!("   Port: {}", config.port);
 | 
			
		||||
    println!("   Redis URL: {}", config.redis_url);
 | 
			
		||||
    if let Some(tls_port) = config.tls_port {
 | 
			
		||||
        println!("   TLS Port: {}", tls_port);
 | 
			
		||||
    }
 | 
			
		||||
    println!("   Authentication: {}", if args.auth { "ENABLED" } else { "DISABLED" });
 | 
			
		||||
    println!("   TLS/WSS: {}", if args.tls { "ENABLED" } else { "DISABLED" });
 | 
			
		||||
    println!("   Webhooks: {}", if args.webhooks { "ENABLED" } else { "DISABLED" });
 | 
			
		||||
    println!("   Authentication: {}", if config.auth { "ENABLED" } else { "DISABLED" });
 | 
			
		||||
    println!("   TLS/WSS: {}", if config.tls { "ENABLED" } else { "DISABLED" });
 | 
			
		||||
    println!("   Webhooks: {}", if config.webhooks { "ENABLED" } else { "DISABLED" });
 | 
			
		||||
    println!("   Circles configured: {}", config.circles.len());
 | 
			
		||||
    
 | 
			
		||||
    if args.tls {
 | 
			
		||||
        if let (Some(cert), Some(key)) = (&args.cert, &args.key) {
 | 
			
		||||
    if config.tls {
 | 
			
		||||
        if let (Some(cert), Some(key)) = (&config.cert, &config.key) {
 | 
			
		||||
            println!("   Certificate: {}", cert);
 | 
			
		||||
            println!("   Private Key: {}", key);
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
    
 | 
			
		||||
    if args.webhooks {
 | 
			
		||||
    if config.webhooks {
 | 
			
		||||
        println!("   Webhook secrets loaded from environment variables:");
 | 
			
		||||
        println!("     - STRIPE_WEBHOOK_SECRET");
 | 
			
		||||
        println!("     - IDENFY_WEBHOOK_SECRET");
 | 
			
		||||
    }
 | 
			
		||||
    
 | 
			
		||||
    if config.auth && !config.circles.is_empty() {
 | 
			
		||||
        println!("   Configured circles:");
 | 
			
		||||
        for (circle_name, members) in &config.circles {
 | 
			
		||||
            println!("     - {}: {} members", circle_name, members.len());
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
    println!();
 | 
			
		||||
 | 
			
		||||
    let (server_task, _server_handle) = server.spawn_circle_server()?;
 | 
			
		||||
 
 | 
			
		||||
@@ -90,7 +90,7 @@ sequenceDiagram
 | 
			
		||||
    participant HS as HttpServer
 | 
			
		||||
    participant WH as Webhook Handler
 | 
			
		||||
    participant WV as Webhook Verifier
 | 
			
		||||
    participant RC as RhaiDispatcher
 | 
			
		||||
    participant RC as RhaiSupervisor
 | 
			
		||||
    participant Redis as Redis
 | 
			
		||||
 | 
			
		||||
    WS->>+HS: POST /webhooks/{provider}/{circle_pk}
 | 
			
		||||
@@ -102,7 +102,7 @@ sequenceDiagram
 | 
			
		||||
    
 | 
			
		||||
    alt Signature Valid
 | 
			
		||||
        WH->>WH: Parse webhook payload (heromodels types)
 | 
			
		||||
        WH->>+RC: Create RhaiDispatcher with caller_id
 | 
			
		||||
        WH->>+RC: Create RhaiSupervisor with caller_id
 | 
			
		||||
        RC->>+Redis: Execute webhook script
 | 
			
		||||
        Redis-->>-RC: Script result
 | 
			
		||||
        RC-->>-WH: Execution result
 | 
			
		||||
@@ -128,6 +128,6 @@ sequenceDiagram
 | 
			
		||||
| **Connection Type** | Persistent, bidirectional | HTTP request/response |
 | 
			
		||||
| **Authentication** | secp256k1 signature-based | HMAC signature verification |
 | 
			
		||||
| **State Management** | Stateful sessions via CircleWs actor | Stateless HTTP requests |
 | 
			
		||||
| **Script Execution** | Direct via authenticated session | Via RhaiDispatcher with provider caller_id |
 | 
			
		||||
| **Script Execution** | Direct via authenticated session | Via RhaiSupervisor with provider caller_id |
 | 
			
		||||
| **Use Case** | Interactive client applications | External service notifications |
 | 
			
		||||
| **Data Types** | JSON-RPC messages | Provider-specific webhook payloads (heromodels) |
 | 
			
		||||
@@ -19,8 +19,8 @@ graph TB
 | 
			
		||||
        E[Webhook Handler]
 | 
			
		||||
        F[Stripe Verifier]
 | 
			
		||||
        G[iDenfy Verifier]
 | 
			
		||||
        H[Script Dispatcher]
 | 
			
		||||
        I[RhaiDispatcherBuilder]
 | 
			
		||||
        H[Script Supervisor]
 | 
			
		||||
        I[RhaiSupervisorBuilder]
 | 
			
		||||
    end
 | 
			
		||||
    
 | 
			
		||||
    subgraph "Configuration"
 | 
			
		||||
@@ -92,8 +92,8 @@ sequenceDiagram
 | 
			
		||||
    participant WS as Webhook Service
 | 
			
		||||
    participant CS as Circle Server
 | 
			
		||||
    participant WV as Webhook Verifier
 | 
			
		||||
    participant SD as Script Dispatcher
 | 
			
		||||
    participant RC as RhaiDispatcher
 | 
			
		||||
    participant SD as Script Supervisor
 | 
			
		||||
    participant RC as RhaiSupervisor
 | 
			
		||||
    participant RW as Rhai Worker
 | 
			
		||||
 | 
			
		||||
    WS->>CS: POST /webhooks/stripe/{circle_pk}
 | 
			
		||||
@@ -113,7 +113,7 @@ sequenceDiagram
 | 
			
		||||
    
 | 
			
		||||
    alt Verification Success
 | 
			
		||||
        CS->>SD: Dispatch appropriate script
 | 
			
		||||
        SD->>RC: Create RhaiDispatcherBuilder
 | 
			
		||||
        SD->>RC: Create RhaiSupervisorBuilder
 | 
			
		||||
        RC->>RC: Set caller_id="stripe" or "idenfy"
 | 
			
		||||
        RC->>RC: Set recipient_id=circle_pk
 | 
			
		||||
        RC->>RC: Set script="stripe_webhook_received" or "idenfy_webhook_received"
 | 
			
		||||
@@ -248,8 +248,8 @@ heromodels/src/models/
 | 
			
		||||
### Key Architectural Changes
 | 
			
		||||
- **Type Organization**: Webhook payload types moved to `heromodels` library for reusability
 | 
			
		||||
- **Modular Handlers**: Separate handler files for each webhook provider
 | 
			
		||||
- **Simplified Architecture**: Removed unnecessary dispatcher complexity
 | 
			
		||||
- **Direct Script Execution**: Handlers directly use `RhaiDispatcher` for script execution
 | 
			
		||||
- **Simplified Architecture**: Removed unnecessary supervisor complexity
 | 
			
		||||
- **Direct Script Execution**: Handlers directly use `RhaiSupervisor` for script execution
 | 
			
		||||
 | 
			
		||||
### Modified Files
 | 
			
		||||
- `src/lib.rs` - Add webhook routes and module imports
 | 
			
		||||
 
 | 
			
		||||
@@ -1,5 +1,5 @@
 | 
			
		||||
use std::collections::HashMap;
 | 
			
		||||
use crate::{Server, TlsConfigError};
 | 
			
		||||
use crate::{Server, TlsConfigError, ServerConfig};
 | 
			
		||||
 | 
			
		||||
/// ServerBuilder for constructing Server instances with a fluent API
 | 
			
		||||
pub struct ServerBuilder {
 | 
			
		||||
@@ -12,7 +12,7 @@ pub struct ServerBuilder {
 | 
			
		||||
    tls_port: Option<u16>,
 | 
			
		||||
    enable_auth: bool,
 | 
			
		||||
    enable_webhooks: bool,
 | 
			
		||||
    circle_worker_id: String,
 | 
			
		||||
 | 
			
		||||
    circles: HashMap<String, Vec<String>>,
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@@ -28,7 +28,7 @@ impl ServerBuilder {
 | 
			
		||||
            tls_port: None,
 | 
			
		||||
            enable_auth: false,
 | 
			
		||||
            enable_webhooks: false,
 | 
			
		||||
            circle_worker_id: "default".to_string(),
 | 
			
		||||
 | 
			
		||||
            circles: HashMap::new(),
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
@@ -48,10 +48,7 @@ impl ServerBuilder {
 | 
			
		||||
        self
 | 
			
		||||
    }
 | 
			
		||||
    
 | 
			
		||||
    pub fn worker_id(mut self, worker_id: impl Into<String>) -> Self {
 | 
			
		||||
        self.circle_worker_id = worker_id.into();
 | 
			
		||||
        self
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
    pub fn with_tls(mut self, cert_path: String, key_path: String) -> Self {
 | 
			
		||||
        self.enable_tls = true;
 | 
			
		||||
@@ -79,6 +76,21 @@ impl ServerBuilder {
 | 
			
		||||
        self.circles = circles;
 | 
			
		||||
        self
 | 
			
		||||
    }
 | 
			
		||||
    
 | 
			
		||||
    /// Load configuration from a ServerConfig instance
 | 
			
		||||
    pub fn from_config(mut self, config: ServerConfig) -> Self {
 | 
			
		||||
        self.host = config.host;
 | 
			
		||||
        self.port = config.port;
 | 
			
		||||
        self.redis_url = config.redis_url;
 | 
			
		||||
        self.enable_auth = config.auth;
 | 
			
		||||
        self.enable_tls = config.tls;
 | 
			
		||||
        self.cert_path = config.cert;
 | 
			
		||||
        self.key_path = config.key;
 | 
			
		||||
        self.tls_port = config.tls_port;
 | 
			
		||||
        self.enable_webhooks = config.webhooks;
 | 
			
		||||
        self.circles = config.circles;
 | 
			
		||||
        self
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    pub fn build(self) -> Result<Server, TlsConfigError> {
 | 
			
		||||
        Ok(Server {
 | 
			
		||||
@@ -91,13 +103,13 @@ impl ServerBuilder {
 | 
			
		||||
            tls_port: self.tls_port,
 | 
			
		||||
            enable_auth: self.enable_auth,
 | 
			
		||||
            enable_webhooks: self.enable_webhooks,
 | 
			
		||||
            circle_worker_id: self.circle_worker_id,
 | 
			
		||||
 | 
			
		||||
            circle_name: "default".to_string(),
 | 
			
		||||
            circle_public_key: "default".to_string(),
 | 
			
		||||
            circles: self.circles,
 | 
			
		||||
            nonce_store: HashMap::new(),
 | 
			
		||||
            authenticated_pubkey: None,
 | 
			
		||||
            dispatcher: None,
 | 
			
		||||
            supervisor: None,
 | 
			
		||||
        })
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -1,7 +1,7 @@
 | 
			
		||||
use crate::Server;
 | 
			
		||||
use actix::prelude::*;
 | 
			
		||||
use actix_web_actors::ws;
 | 
			
		||||
use hero_dispatcher::{Dispatcher, ScriptType};
 | 
			
		||||
use hero_supervisor::{Supervisor, ScriptType};
 | 
			
		||||
use serde_json::{json, Value};
 | 
			
		||||
use std::time::Duration;
 | 
			
		||||
 | 
			
		||||
@@ -82,7 +82,7 @@ impl Server {
 | 
			
		||||
            }
 | 
			
		||||
        };
 | 
			
		||||
 | 
			
		||||
        let dispatcher = match self.dispatcher.clone() {
 | 
			
		||||
        let supervisor = match self.supervisor.clone() {
 | 
			
		||||
            Some(d) => d,
 | 
			
		||||
            None => {
 | 
			
		||||
                let err_resp = JsonRpcResponse {
 | 
			
		||||
@@ -90,7 +90,7 @@ impl Server {
 | 
			
		||||
                    result: None,
 | 
			
		||||
                    error: Some(JsonRpcError {
 | 
			
		||||
                        code: -32603,
 | 
			
		||||
                        message: "Internal error: dispatcher not available".to_string(),
 | 
			
		||||
                        message: "Internal error: supervisor not available".to_string(),
 | 
			
		||||
                        data: None,
 | 
			
		||||
                    }),
 | 
			
		||||
                    id: client_rpc_id,
 | 
			
		||||
@@ -102,7 +102,7 @@ impl Server {
 | 
			
		||||
 | 
			
		||||
        let client_rpc_id_clone = client_rpc_id.clone();
 | 
			
		||||
        let fut = async move {
 | 
			
		||||
            dispatcher.start_job(&job_id).await
 | 
			
		||||
            supervisor.start_job(&job_id).await
 | 
			
		||||
        };
 | 
			
		||||
 | 
			
		||||
        ctx.spawn(
 | 
			
		||||
@@ -190,7 +190,7 @@ impl Server {
 | 
			
		||||
            }
 | 
			
		||||
        };
 | 
			
		||||
 | 
			
		||||
        let dispatcher = match self.dispatcher.clone() {
 | 
			
		||||
        let supervisor = match self.supervisor.clone() {
 | 
			
		||||
            Some(d) => d,
 | 
			
		||||
            None => {
 | 
			
		||||
                let err_resp = JsonRpcResponse {
 | 
			
		||||
@@ -198,7 +198,7 @@ impl Server {
 | 
			
		||||
                    result: None,
 | 
			
		||||
                    error: Some(JsonRpcError {
 | 
			
		||||
                        code: -32603,
 | 
			
		||||
                        message: "Internal error: dispatcher not available".to_string(),
 | 
			
		||||
                        message: "Internal error: supervisor not available".to_string(),
 | 
			
		||||
                        data: None,
 | 
			
		||||
                    }),
 | 
			
		||||
                    id: client_rpc_id,
 | 
			
		||||
@@ -210,7 +210,7 @@ impl Server {
 | 
			
		||||
 | 
			
		||||
        let client_rpc_id_clone = client_rpc_id.clone();
 | 
			
		||||
        let fut = async move {
 | 
			
		||||
            dispatcher.get_job_status(&job_id).await
 | 
			
		||||
            supervisor.get_job_status(&job_id).await
 | 
			
		||||
        };
 | 
			
		||||
 | 
			
		||||
        ctx.spawn(
 | 
			
		||||
@@ -279,7 +279,7 @@ impl Server {
 | 
			
		||||
            return;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        let dispatcher = match self.dispatcher.clone() {
 | 
			
		||||
        let supervisor = match self.supervisor.clone() {
 | 
			
		||||
            Some(d) => d,
 | 
			
		||||
            None => {
 | 
			
		||||
                let err_resp = JsonRpcResponse {
 | 
			
		||||
@@ -287,7 +287,7 @@ impl Server {
 | 
			
		||||
                    result: None,
 | 
			
		||||
                    error: Some(JsonRpcError {
 | 
			
		||||
                        code: -32603,
 | 
			
		||||
                        message: "Internal error: dispatcher not available".to_string(),
 | 
			
		||||
                        message: "Internal error: supervisor not available".to_string(),
 | 
			
		||||
                        data: None,
 | 
			
		||||
                    }),
 | 
			
		||||
                    id: client_rpc_id,
 | 
			
		||||
@@ -299,7 +299,7 @@ impl Server {
 | 
			
		||||
 | 
			
		||||
        let client_rpc_id_clone = client_rpc_id.clone();
 | 
			
		||||
        let fut = async move {
 | 
			
		||||
            dispatcher.list_jobs().await
 | 
			
		||||
            supervisor.list_jobs().await
 | 
			
		||||
        };
 | 
			
		||||
 | 
			
		||||
        ctx.spawn(
 | 
			
		||||
@@ -403,7 +403,7 @@ impl Server {
 | 
			
		||||
            }
 | 
			
		||||
        };
 | 
			
		||||
 | 
			
		||||
        let dispatcher = match self.dispatcher.clone() {
 | 
			
		||||
        let supervisor = match self.supervisor.clone() {
 | 
			
		||||
            Some(d) => d,
 | 
			
		||||
            None => {
 | 
			
		||||
                let err_resp = JsonRpcResponse {
 | 
			
		||||
@@ -411,7 +411,7 @@ impl Server {
 | 
			
		||||
                    result: None,
 | 
			
		||||
                    error: Some(JsonRpcError {
 | 
			
		||||
                        code: -32603,
 | 
			
		||||
                        message: "Internal error: dispatcher not available".to_string(),
 | 
			
		||||
                        message: "Internal error: supervisor not available".to_string(),
 | 
			
		||||
                        data: None,
 | 
			
		||||
                    }),
 | 
			
		||||
                    id: client_rpc_id,
 | 
			
		||||
@@ -423,7 +423,7 @@ impl Server {
 | 
			
		||||
 | 
			
		||||
        let client_rpc_id_clone = client_rpc_id.clone();
 | 
			
		||||
        let fut = async move {
 | 
			
		||||
            dispatcher
 | 
			
		||||
            supervisor
 | 
			
		||||
                .new_job()
 | 
			
		||||
                .context_id(&circle_pk)
 | 
			
		||||
                .script_type(ScriptType::RhaiSAL)
 | 
			
		||||
@@ -518,7 +518,7 @@ impl Server {
 | 
			
		||||
            }
 | 
			
		||||
        };
 | 
			
		||||
 | 
			
		||||
        let dispatcher = match self.dispatcher.clone() {
 | 
			
		||||
        let supervisor = match self.supervisor.clone() {
 | 
			
		||||
            Some(d) => d,
 | 
			
		||||
            None => {
 | 
			
		||||
                let err_resp = JsonRpcResponse {
 | 
			
		||||
@@ -526,7 +526,7 @@ impl Server {
 | 
			
		||||
                    result: None,
 | 
			
		||||
                    error: Some(JsonRpcError {
 | 
			
		||||
                        code: -32603,
 | 
			
		||||
                        message: "Internal error: dispatcher not available".to_string(),
 | 
			
		||||
                        message: "Internal error: supervisor not available".to_string(),
 | 
			
		||||
                        data: None,
 | 
			
		||||
                    }),
 | 
			
		||||
                    id: client_rpc_id,
 | 
			
		||||
@@ -538,7 +538,7 @@ impl Server {
 | 
			
		||||
 | 
			
		||||
        let client_rpc_id_clone = client_rpc_id.clone();
 | 
			
		||||
        let fut = async move {
 | 
			
		||||
            dispatcher.get_job_output(&job_id).await
 | 
			
		||||
            supervisor.get_job_output(&job_id).await
 | 
			
		||||
        };
 | 
			
		||||
 | 
			
		||||
        ctx.spawn(
 | 
			
		||||
@@ -625,7 +625,7 @@ impl Server {
 | 
			
		||||
            }
 | 
			
		||||
        };
 | 
			
		||||
 | 
			
		||||
        let dispatcher = match self.dispatcher.clone() {
 | 
			
		||||
        let supervisor = match self.supervisor.clone() {
 | 
			
		||||
            Some(d) => d,
 | 
			
		||||
            None => {
 | 
			
		||||
                let err_resp = JsonRpcResponse {
 | 
			
		||||
@@ -633,7 +633,7 @@ impl Server {
 | 
			
		||||
                    result: None,
 | 
			
		||||
                    error: Some(JsonRpcError {
 | 
			
		||||
                        code: -32603,
 | 
			
		||||
                        message: "Internal error: dispatcher not available".to_string(),
 | 
			
		||||
                        message: "Internal error: supervisor not available".to_string(),
 | 
			
		||||
                        data: None,
 | 
			
		||||
                    }),
 | 
			
		||||
                    id: client_rpc_id,
 | 
			
		||||
@@ -645,7 +645,7 @@ impl Server {
 | 
			
		||||
 | 
			
		||||
        let client_rpc_id_clone = client_rpc_id.clone();
 | 
			
		||||
        let fut = async move {
 | 
			
		||||
            dispatcher.get_job_logs(&job_id).await
 | 
			
		||||
            supervisor.get_job_logs(&job_id).await
 | 
			
		||||
        };
 | 
			
		||||
 | 
			
		||||
        ctx.spawn(
 | 
			
		||||
@@ -733,7 +733,7 @@ impl Server {
 | 
			
		||||
            }
 | 
			
		||||
        };
 | 
			
		||||
 | 
			
		||||
        let dispatcher = match self.dispatcher.clone() {
 | 
			
		||||
        let supervisor = match self.supervisor.clone() {
 | 
			
		||||
            Some(d) => d,
 | 
			
		||||
            None => {
 | 
			
		||||
                let err_resp = JsonRpcResponse {
 | 
			
		||||
@@ -741,7 +741,7 @@ impl Server {
 | 
			
		||||
                    result: None,
 | 
			
		||||
                    error: Some(JsonRpcError {
 | 
			
		||||
                        code: -32603,
 | 
			
		||||
                        message: "Internal error: dispatcher not available".to_string(),
 | 
			
		||||
                        message: "Internal error: supervisor not available".to_string(),
 | 
			
		||||
                        data: None,
 | 
			
		||||
                    }),
 | 
			
		||||
                    id: client_rpc_id,
 | 
			
		||||
@@ -753,7 +753,7 @@ impl Server {
 | 
			
		||||
 | 
			
		||||
        let client_rpc_id_clone = client_rpc_id.clone();
 | 
			
		||||
        let fut = async move {
 | 
			
		||||
            dispatcher.stop_job(&job_id).await
 | 
			
		||||
            supervisor.stop_job(&job_id).await
 | 
			
		||||
        };
 | 
			
		||||
 | 
			
		||||
        ctx.spawn(
 | 
			
		||||
@@ -840,7 +840,7 @@ impl Server {
 | 
			
		||||
            }
 | 
			
		||||
        };
 | 
			
		||||
 | 
			
		||||
        let dispatcher = match self.dispatcher.clone() {
 | 
			
		||||
        let supervisor = match self.supervisor.clone() {
 | 
			
		||||
            Some(d) => d,
 | 
			
		||||
            None => {
 | 
			
		||||
                let err_resp = JsonRpcResponse {
 | 
			
		||||
@@ -848,7 +848,7 @@ impl Server {
 | 
			
		||||
                    result: None,
 | 
			
		||||
                    error: Some(JsonRpcError {
 | 
			
		||||
                        code: -32603,
 | 
			
		||||
                        message: "Internal error: dispatcher not available".to_string(),
 | 
			
		||||
                        message: "Internal error: supervisor not available".to_string(),
 | 
			
		||||
                        data: None,
 | 
			
		||||
                    }),
 | 
			
		||||
                    id: client_rpc_id,
 | 
			
		||||
@@ -860,7 +860,7 @@ impl Server {
 | 
			
		||||
 | 
			
		||||
        let client_rpc_id_clone = client_rpc_id.clone();
 | 
			
		||||
        let fut = async move {
 | 
			
		||||
            dispatcher.delete_job(&job_id).await
 | 
			
		||||
            supervisor.delete_job(&job_id).await
 | 
			
		||||
        };
 | 
			
		||||
 | 
			
		||||
        ctx.spawn(
 | 
			
		||||
@@ -929,7 +929,7 @@ impl Server {
 | 
			
		||||
            return;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        let dispatcher = match self.dispatcher.clone() {
 | 
			
		||||
        let supervisor = match self.supervisor.clone() {
 | 
			
		||||
            Some(d) => d,
 | 
			
		||||
            None => {
 | 
			
		||||
                let err_resp = JsonRpcResponse {
 | 
			
		||||
@@ -937,7 +937,7 @@ impl Server {
 | 
			
		||||
                    result: None,
 | 
			
		||||
                    error: Some(JsonRpcError {
 | 
			
		||||
                        code: -32603,
 | 
			
		||||
                        message: "Internal error: dispatcher not available".to_string(),
 | 
			
		||||
                        message: "Internal error: supervisor not available".to_string(),
 | 
			
		||||
                        data: None,
 | 
			
		||||
                    }),
 | 
			
		||||
                    id: client_rpc_id,
 | 
			
		||||
@@ -949,7 +949,7 @@ impl Server {
 | 
			
		||||
 | 
			
		||||
        let client_rpc_id_clone = client_rpc_id.clone();
 | 
			
		||||
        let fut = async move {
 | 
			
		||||
            dispatcher.clear_all_jobs().await
 | 
			
		||||
            supervisor.clear_all_jobs().await
 | 
			
		||||
        };
 | 
			
		||||
 | 
			
		||||
        ctx.spawn(
 | 
			
		||||
 
 | 
			
		||||
@@ -3,7 +3,7 @@ use actix_web::{web, App, Error, HttpRequest, HttpResponse, HttpServer};
 | 
			
		||||
use actix_web_actors::ws;
 | 
			
		||||
use log::{info, error}; // Added error for better logging
 | 
			
		||||
use once_cell::sync::Lazy;
 | 
			
		||||
use hero_dispatcher::{Dispatcher, DispatcherBuilder, DispatcherError};
 | 
			
		||||
use hero_supervisor::{Supervisor, SupervisorBuilder, SupervisorError};
 | 
			
		||||
use hero_job::{Job, JobStatus};
 | 
			
		||||
use rustls::pki_types::PrivateKeyDer;
 | 
			
		||||
use rustls::ServerConfig as RustlsServerConfig;
 | 
			
		||||
@@ -211,7 +211,7 @@ pub struct Server {
 | 
			
		||||
    pub circles: HashMap<String, Vec<String>>,
 | 
			
		||||
    nonce_store: HashMap<String, NonceResponse>,
 | 
			
		||||
    authenticated_pubkey: Option<String>,
 | 
			
		||||
    pub dispatcher: Option<Dispatcher>,
 | 
			
		||||
    pub supervisor: Option<Supervisor>,
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
impl Server {
 | 
			
		||||
@@ -552,15 +552,15 @@ impl Server {
 | 
			
		||||
 | 
			
		||||
                let fut = async move {
 | 
			
		||||
                    let caller_id = public_key.unwrap_or_else(|| "anonymous".to_string());
 | 
			
		||||
                    match DispatcherBuilder::new()
 | 
			
		||||
                    match SupervisorBuilder::new()
 | 
			
		||||
                        .redis_url(&redis_url_clone)
 | 
			
		||||
                        .caller_id(&caller_id)
 | 
			
		||||
                        .build() {
 | 
			
		||||
                        Ok(hero_dispatcher) => {
 | 
			
		||||
                            hero_dispatcher
 | 
			
		||||
                        Ok(hero_supervisor) => {
 | 
			
		||||
                            hero_supervisor
 | 
			
		||||
                                .new_job()
 | 
			
		||||
                                    .context_id(&circle_pk_clone)
 | 
			
		||||
                                    .script_type(hero_dispatcher::ScriptType::RhaiSAL)
 | 
			
		||||
                                    .script_type(hero_supervisor::ScriptType::RhaiSAL)
 | 
			
		||||
                                    .script(&script_content)
 | 
			
		||||
                                    .timeout(TASK_TIMEOUT_DURATION)
 | 
			
		||||
                                    .await_response()
 | 
			
		||||
@@ -574,7 +574,7 @@ impl Server {
 | 
			
		||||
                    fut.into_actor(self)
 | 
			
		||||
                        .map(move |res, _act, ctx_inner| match res {
 | 
			
		||||
                            Ok(output) => {
 | 
			
		||||
                                // The dispatcher returns the actual string output from job execution
 | 
			
		||||
                                // The supervisor returns the actual string output from job execution
 | 
			
		||||
                                let result_value = PlayResult { output };
 | 
			
		||||
                                let resp = JsonRpcResponse {
 | 
			
		||||
                                    jsonrpc: "2.0".to_string(),
 | 
			
		||||
@@ -586,7 +586,7 @@ impl Server {
 | 
			
		||||
                            }
 | 
			
		||||
                            Err(e) => {
 | 
			
		||||
                                let (code, message) = match e {
 | 
			
		||||
                                    DispatcherError::Timeout(task_id) => (
 | 
			
		||||
                                    SupervisorError::Timeout(task_id) => (
 | 
			
		||||
                                        -32002,
 | 
			
		||||
                                        format!(
 | 
			
		||||
                                            "Timeout waiting for Rhai script (task: {})",
 | 
			
		||||
 
 | 
			
		||||
		Reference in New Issue
	
	Block a user