cleanup and refactor
This commit is contained in:
@@ -25,9 +25,10 @@ hero-job = { git = "https://git.ourworld.tf/herocode/job.git" }
|
||||
[target.'cfg(not(target_arch = "wasm32"))'.dependencies]
|
||||
jsonrpsee = { version = "0.24", features = ["http-client", "macros"] }
|
||||
tokio = { version = "1.0", features = ["full"] }
|
||||
hero-supervisor = { path = "../core" }
|
||||
# hero-supervisor = { path = "../core" } # Removed to break cyclic dependency
|
||||
hero-job-client = { git = "https://git.ourworld.tf/herocode/job.git" }
|
||||
env_logger = "0.11"
|
||||
http = "1.0"
|
||||
|
||||
# WASM-specific dependencies
|
||||
[target.'cfg(target_arch = "wasm32")'.dependencies]
|
||||
|
||||
@@ -23,39 +23,24 @@ tokio = { version = "1.0", features = ["full"] }
|
||||
## Quick Start
|
||||
|
||||
```rust
|
||||
use hero_supervisor_openrpc_client::{
|
||||
SupervisorClient, RunnerConfig, RunnerType, ProcessManagerType, JobBuilder, JobType
|
||||
};
|
||||
use std::path::PathBuf;
|
||||
use std::time::Duration;
|
||||
use hero_supervisor_openrpc_client::{SupervisorClient, JobBuilder};
|
||||
|
||||
#[tokio::main]
|
||||
async fn main() -> Result<(), Box<dyn std::error::Error>> {
|
||||
// Create a client
|
||||
let client = SupervisorClient::new("http://127.0.0.1:3030")?;
|
||||
// Create a client with admin secret
|
||||
let client = SupervisorClient::new("http://127.0.0.1:3030", "your-admin-secret")?;
|
||||
|
||||
// Add a runner
|
||||
let config = RunnerConfig {
|
||||
actor_id: "my_actor".to_string(),
|
||||
runner_type: RunnerType::OSISRunner,
|
||||
binary_path: PathBuf::from("/path/to/actor/binary"),
|
||||
db_path: "/path/to/db".to_string(),
|
||||
redis_url: "redis://localhost:6379".to_string(),
|
||||
};
|
||||
// Register a runner (runner must be started externally)
|
||||
client.register_runner("admin-secret", "my_runner").await?;
|
||||
|
||||
client.add_runner(config, ProcessManagerType::Simple).await?;
|
||||
|
||||
// Start the runner
|
||||
client.start_runner("my_actor").await?;
|
||||
|
||||
// Create and queue a job
|
||||
// Create and run a job
|
||||
let job = JobBuilder::new()
|
||||
.caller_id("my_client")
|
||||
.context_id("example_context")
|
||||
.payload("print('Hello from Hero Supervisor!');")
|
||||
.job_type(JobType::OSIS)
|
||||
.runner("my_actor")
|
||||
.timeout(Duration::from_secs(60))
|
||||
.payload("echo 'Hello from Hero Supervisor!'")
|
||||
.executor("bash")
|
||||
.runner("my_runner")
|
||||
.timeout(60)
|
||||
.build()?;
|
||||
|
||||
client.queue_job_to_runner("my_actor", job).await?;
|
||||
@@ -83,11 +68,11 @@ let client = SupervisorClient::new("http://127.0.0.1:3030")?;
|
||||
### Runner Management
|
||||
|
||||
```rust
|
||||
// Add a runner
|
||||
client.add_runner(config, ProcessManagerType::Simple).await?;
|
||||
// Register a runner
|
||||
client.register_runner("admin-secret", "my_runner").await?;
|
||||
|
||||
// Remove a runner
|
||||
client.remove_runner("actor_id").await?;
|
||||
client.remove_runner("admin-secret", "my_runner").await?;
|
||||
|
||||
// List all runners
|
||||
let runners = client.list_runners().await?;
|
||||
@@ -150,10 +135,9 @@ let statuses = client.get_all_runner_status().await?;
|
||||
- `V` - V job type
|
||||
- `Python` - Python job type
|
||||
|
||||
### ProcessManagerType
|
||||
### Runner Management
|
||||
|
||||
- `Simple` - Direct process spawning
|
||||
- `Tmux(String)` - Tmux session-based management
|
||||
Runners are expected to be started and managed externally. The supervisor only tracks which runners are registered and queues jobs to them via Redis.
|
||||
|
||||
### ProcessStatus
|
||||
|
||||
|
||||
102
client/src/builder.rs
Normal file
102
client/src/builder.rs
Normal file
@@ -0,0 +1,102 @@
|
||||
//! Builder pattern for WasmSupervisorClient to ensure proper configuration
|
||||
//!
|
||||
//! This module provides a type-safe builder that guarantees a client cannot be
|
||||
//! created without a secret, preventing authentication issues.
|
||||
|
||||
use crate::wasm::WasmSupervisorClient;
|
||||
|
||||
/// Builder for WasmSupervisorClient that enforces secret requirement
|
||||
#[derive(Clone)]
|
||||
pub struct WasmSupervisorClientBuilder {
|
||||
server_url: Option<String>,
|
||||
secret: Option<String>,
|
||||
}
|
||||
|
||||
impl WasmSupervisorClientBuilder {
|
||||
/// Create a new builder
|
||||
pub fn new() -> Self {
|
||||
Self {
|
||||
server_url: None,
|
||||
secret: None,
|
||||
}
|
||||
}
|
||||
|
||||
/// Set the server URL
|
||||
pub fn server_url(mut self, url: impl Into<String>) -> Self {
|
||||
self.server_url = Some(url.into());
|
||||
self
|
||||
}
|
||||
|
||||
/// Set the authentication secret (required)
|
||||
pub fn secret(mut self, secret: impl Into<String>) -> Self {
|
||||
self.secret = Some(secret.into());
|
||||
self
|
||||
}
|
||||
|
||||
/// Build the client
|
||||
///
|
||||
/// Returns Err if server_url or secret is not set
|
||||
pub fn build(self) -> Result<WasmSupervisorClient, String> {
|
||||
let server_url = self.server_url.ok_or("Server URL is required")?;
|
||||
let secret = self.secret.ok_or("Secret is required for authenticated client")?;
|
||||
|
||||
if secret.is_empty() {
|
||||
return Err("Secret cannot be empty".to_string());
|
||||
}
|
||||
|
||||
Ok(WasmSupervisorClient::new(server_url, secret))
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for WasmSupervisorClientBuilder {
|
||||
fn default() -> Self {
|
||||
Self::new()
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn test_builder_requires_all_fields() {
|
||||
let builder = WasmSupervisorClientBuilder::new();
|
||||
assert!(builder.build().is_err());
|
||||
|
||||
let builder = WasmSupervisorClientBuilder::new()
|
||||
.server_url("http://localhost:3030");
|
||||
assert!(builder.build().is_err());
|
||||
|
||||
let builder = WasmSupervisorClientBuilder::new()
|
||||
.secret("test-secret");
|
||||
assert!(builder.build().is_err());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_builder_success() {
|
||||
let builder = WasmSupervisorClientBuilder::new()
|
||||
.server_url("http://localhost:3030")
|
||||
.secret("test-secret");
|
||||
assert!(builder.build().is_ok());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_build_error_messages() {
|
||||
let result = WasmSupervisorClientBuilder::new().build();
|
||||
assert!(result.is_err());
|
||||
assert_eq!(result.unwrap_err(), "Server URL is required");
|
||||
|
||||
let result = WasmSupervisorClientBuilder::new()
|
||||
.server_url("http://localhost:3030")
|
||||
.build();
|
||||
assert!(result.is_err());
|
||||
assert_eq!(result.unwrap_err(), "Secret is required for authenticated client");
|
||||
|
||||
let result = WasmSupervisorClientBuilder::new()
|
||||
.server_url("http://localhost:3030")
|
||||
.secret("")
|
||||
.build();
|
||||
assert!(result.is_err());
|
||||
assert_eq!(result.unwrap_err(), "Secret cannot be empty");
|
||||
}
|
||||
}
|
||||
@@ -9,10 +9,18 @@ use serde_json;
|
||||
#[cfg(target_arch = "wasm32")]
|
||||
pub mod wasm;
|
||||
|
||||
// Builder module for type-safe client construction
|
||||
#[cfg(target_arch = "wasm32")]
|
||||
pub mod builder;
|
||||
|
||||
// Re-export WASM types for convenience
|
||||
#[cfg(target_arch = "wasm32")]
|
||||
pub use wasm::{WasmSupervisorClient, WasmJobType, WasmRunnerType, create_job_canonical_repr, sign_job_canonical};
|
||||
|
||||
// Re-export builder for convenience
|
||||
#[cfg(target_arch = "wasm32")]
|
||||
pub use builder::WasmSupervisorClientBuilder;
|
||||
|
||||
// Native client dependencies
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
use jsonrpsee::{
|
||||
@@ -21,15 +29,20 @@ use jsonrpsee::{
|
||||
rpc_params,
|
||||
};
|
||||
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
use http::{HeaderMap, HeaderName, HeaderValue};
|
||||
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
use std::path::PathBuf;
|
||||
|
||||
/// Client for communicating with Hero Supervisor OpenRPC server
|
||||
/// Requires authentication secret for all operations
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
#[derive(Clone)]
|
||||
pub struct SupervisorClient {
|
||||
client: HttpClient,
|
||||
server_url: String,
|
||||
secret: Option<String>,
|
||||
secret: String,
|
||||
}
|
||||
|
||||
/// Error types for client operations
|
||||
@@ -159,21 +172,39 @@ pub struct LogInfoWrapper {
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
pub struct SupervisorInfo {
|
||||
pub server_url: String,
|
||||
pub admin_secrets_count: usize,
|
||||
pub user_secrets_count: usize,
|
||||
pub register_secrets_count: usize,
|
||||
pub runners_count: usize,
|
||||
}
|
||||
|
||||
/// API Key information
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
pub struct ApiKey {
|
||||
pub key: String,
|
||||
pub name: String,
|
||||
pub scope: String,
|
||||
pub created_at: String,
|
||||
}
|
||||
|
||||
/// Auth verification response
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
pub struct AuthVerifyResponse {
|
||||
pub scope: String,
|
||||
pub name: Option<String>,
|
||||
pub created_at: Option<String>,
|
||||
}
|
||||
|
||||
/// Simple ProcessStatus type for native builds to avoid service manager dependency
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
pub type ProcessStatus = ProcessStatusWrapper;
|
||||
|
||||
/// Re-export types from supervisor crate for native builds
|
||||
// Types duplicated from supervisor-core to avoid cyclic dependency
|
||||
// These match the types in hero-supervisor but are defined here independently
|
||||
|
||||
/// Runner status information (duplicated to avoid cyclic dependency)
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
pub use hero_supervisor::RunnerStatus;
|
||||
pub type RunnerStatus = ProcessStatusWrapper;
|
||||
|
||||
/// Log information (duplicated to avoid cyclic dependency)
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
pub use hero_supervisor::runner::LogInfo;
|
||||
pub type LogInfo = LogInfoWrapper;
|
||||
|
||||
/// Type aliases for WASM compatibility
|
||||
#[cfg(target_arch = "wasm32")]
|
||||
@@ -183,40 +214,87 @@ pub type RunnerStatus = ProcessStatusWrapper;
|
||||
#[cfg(target_arch = "wasm32")]
|
||||
pub type LogInfo = LogInfoWrapper;
|
||||
|
||||
/// Builder for SupervisorClient
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
impl SupervisorClient {
|
||||
/// Create a new supervisor client
|
||||
pub fn new(server_url: impl Into<String>) -> ClientResult<Self> {
|
||||
let server_url = server_url.into();
|
||||
|
||||
let client = HttpClientBuilder::default()
|
||||
.request_timeout(std::time::Duration::from_secs(30))
|
||||
.build(&server_url)
|
||||
.map_err(|e| ClientError::Http(e.to_string()))?;
|
||||
|
||||
Ok(Self {
|
||||
client,
|
||||
server_url,
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct SupervisorClientBuilder {
|
||||
url: Option<String>,
|
||||
secret: Option<String>,
|
||||
timeout: Option<std::time::Duration>,
|
||||
}
|
||||
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
impl SupervisorClientBuilder {
|
||||
/// Create a new builder
|
||||
pub fn new() -> Self {
|
||||
Self {
|
||||
url: None,
|
||||
secret: None,
|
||||
})
|
||||
timeout: Some(std::time::Duration::from_secs(30)),
|
||||
}
|
||||
}
|
||||
|
||||
/// Create a new supervisor client with authentication secret
|
||||
pub fn with_secret(server_url: impl Into<String>, secret: impl Into<String>) -> ClientResult<Self> {
|
||||
let server_url = server_url.into();
|
||||
let secret = secret.into();
|
||||
/// Set the server URL
|
||||
pub fn url(mut self, url: impl Into<String>) -> Self {
|
||||
self.url = Some(url.into());
|
||||
self
|
||||
}
|
||||
|
||||
/// Set the authentication secret
|
||||
pub fn secret(mut self, secret: impl Into<String>) -> Self {
|
||||
self.secret = Some(secret.into());
|
||||
self
|
||||
}
|
||||
|
||||
/// Set the request timeout (default: 30 seconds)
|
||||
pub fn timeout(mut self, timeout: std::time::Duration) -> Self {
|
||||
self.timeout = Some(timeout);
|
||||
self
|
||||
}
|
||||
|
||||
/// Build the SupervisorClient
|
||||
pub fn build(self) -> ClientResult<SupervisorClient> {
|
||||
let server_url = self.url
|
||||
.ok_or_else(|| ClientError::Http("URL is required".to_string()))?;
|
||||
let secret = self.secret
|
||||
.ok_or_else(|| ClientError::Http("Secret is required".to_string()))?;
|
||||
|
||||
// Create headers with Authorization bearer token
|
||||
let mut headers = HeaderMap::new();
|
||||
let auth_value = format!("Bearer {}", secret);
|
||||
headers.insert(
|
||||
HeaderName::from_static("authorization"),
|
||||
HeaderValue::from_str(&auth_value)
|
||||
.map_err(|e| ClientError::Http(format!("Invalid auth header: {}", e)))?
|
||||
);
|
||||
|
||||
let client = HttpClientBuilder::default()
|
||||
.request_timeout(std::time::Duration::from_secs(30))
|
||||
.request_timeout(self.timeout.unwrap_or(std::time::Duration::from_secs(30)))
|
||||
.set_headers(headers)
|
||||
.build(&server_url)
|
||||
.map_err(|e| ClientError::Http(e.to_string()))?;
|
||||
|
||||
Ok(Self {
|
||||
Ok(SupervisorClient {
|
||||
client,
|
||||
server_url,
|
||||
secret: Some(secret),
|
||||
secret,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
impl Default for SupervisorClientBuilder {
|
||||
fn default() -> Self {
|
||||
Self::new()
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
impl SupervisorClient {
|
||||
/// Create a builder for SupervisorClient
|
||||
pub fn builder() -> SupervisorClientBuilder {
|
||||
SupervisorClientBuilder::new()
|
||||
}
|
||||
|
||||
/// Get the server URL
|
||||
pub fn server_url(&self) -> &str {
|
||||
@@ -233,32 +311,27 @@ impl SupervisorClient {
|
||||
Ok(result)
|
||||
}
|
||||
|
||||
/// Register a new runner to the supervisor with secret authentication
|
||||
/// Register a new runner to the supervisor
|
||||
/// The runner name is also used as the queue name
|
||||
/// Authentication via Authorization header (set during client creation)
|
||||
pub async fn register_runner(
|
||||
&self,
|
||||
secret: &str,
|
||||
name: &str,
|
||||
) -> ClientResult<()> {
|
||||
let params = serde_json::json!({
|
||||
"secret": secret,
|
||||
"name": name
|
||||
});
|
||||
let _: String = self
|
||||
) -> ClientResult<String> {
|
||||
let result: String = self
|
||||
.client
|
||||
.request("register_runner", rpc_params![params])
|
||||
.request("runner.register", rpc_params![name])
|
||||
.await.map_err(|e| ClientError::JsonRpc(e))?;
|
||||
Ok(())
|
||||
Ok(result)
|
||||
}
|
||||
|
||||
/// Create a new job without queuing it to a runner
|
||||
/// Authentication via Authorization header (set during client creation)
|
||||
pub async fn jobs_create(
|
||||
&self,
|
||||
secret: &str,
|
||||
job: Job,
|
||||
) -> ClientResult<String> {
|
||||
let params = serde_json::json!({
|
||||
"secret": secret,
|
||||
"job": job
|
||||
});
|
||||
|
||||
@@ -280,14 +353,13 @@ impl SupervisorClient {
|
||||
|
||||
/// Run a job on the appropriate runner and wait for the result (blocking)
|
||||
/// This method queues the job and waits for completion before returning
|
||||
/// The secret is sent via Authorization header (set during client creation)
|
||||
pub async fn job_run(
|
||||
&self,
|
||||
secret: &str,
|
||||
job: Job,
|
||||
timeout: Option<u64>,
|
||||
) -> ClientResult<JobRunResponse> {
|
||||
let mut params = serde_json::json!({
|
||||
"secret": secret,
|
||||
"job": job
|
||||
});
|
||||
|
||||
@@ -304,13 +376,12 @@ impl SupervisorClient {
|
||||
|
||||
/// Start a job without waiting for the result (non-blocking)
|
||||
/// This method queues the job and returns immediately with the job_id
|
||||
/// Authentication via Authorization header (set during client creation)
|
||||
pub async fn job_start(
|
||||
&self,
|
||||
secret: &str,
|
||||
job: Job,
|
||||
) -> ClientResult<JobStartResponse> {
|
||||
let params = serde_json::json!({
|
||||
"secret": secret,
|
||||
"job": job
|
||||
});
|
||||
|
||||
@@ -340,14 +411,11 @@ impl SupervisorClient {
|
||||
}
|
||||
|
||||
/// Remove a runner from the supervisor
|
||||
pub async fn remove_runner(&self, secret: &str, actor_id: &str) -> ClientResult<()> {
|
||||
let params = serde_json::json!({
|
||||
"secret": secret,
|
||||
"actor_id": actor_id
|
||||
});
|
||||
/// Authentication via Authorization header (set during client creation)
|
||||
pub async fn remove_runner(&self, actor_id: &str) -> ClientResult<()> {
|
||||
let _: () = self
|
||||
.client
|
||||
.request("remove_runner", rpc_params![params])
|
||||
.request("runner.remove", rpc_params![actor_id])
|
||||
.await.map_err(|e| ClientError::JsonRpc(e))?;
|
||||
Ok(())
|
||||
}
|
||||
@@ -356,60 +424,40 @@ impl SupervisorClient {
|
||||
pub async fn list_runners(&self) -> ClientResult<Vec<String>> {
|
||||
let runners: Vec<String> = self
|
||||
.client
|
||||
.request("list_runners", rpc_params![])
|
||||
.request("runner.list", rpc_params![])
|
||||
.await.map_err(|e| ClientError::JsonRpc(e))?;
|
||||
Ok(runners)
|
||||
}
|
||||
|
||||
/// Start a specific runner
|
||||
pub async fn start_runner(&self, secret: &str, actor_id: &str) -> ClientResult<()> {
|
||||
let params = serde_json::json!({
|
||||
"secret": secret,
|
||||
"actor_id": actor_id
|
||||
});
|
||||
/// Authentication via Authorization header (set during client creation)
|
||||
pub async fn start_runner(&self, actor_id: &str) -> ClientResult<()> {
|
||||
let _: () = self
|
||||
.client
|
||||
.request("start_runner", rpc_params![params])
|
||||
.await.map_err(|e| ClientError::JsonRpc(e))?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Stop a specific runner
|
||||
pub async fn stop_runner(&self, secret: &str, actor_id: &str, force: bool) -> ClientResult<()> {
|
||||
let params = serde_json::json!({
|
||||
"secret": secret,
|
||||
"actor_id": actor_id,
|
||||
"force": force
|
||||
});
|
||||
let _: () = self
|
||||
.client
|
||||
.request("stop_runner", rpc_params![params])
|
||||
.request("runner.start", rpc_params![actor_id])
|
||||
.await.map_err(|e| ClientError::JsonRpc(e))?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Add a runner to the supervisor
|
||||
pub async fn add_runner(&self, secret: &str, config: RunnerConfig) -> ClientResult<()> {
|
||||
/// Authentication via Authorization header (set during client creation)
|
||||
pub async fn add_runner(&self, config: RunnerConfig) -> ClientResult<()> {
|
||||
let params = serde_json::json!({
|
||||
"secret": secret,
|
||||
"config": config
|
||||
});
|
||||
let _: () = self
|
||||
.client
|
||||
.request("add_runner", rpc_params![params])
|
||||
.request("runner.add", rpc_params![params])
|
||||
.await.map_err(|e| ClientError::JsonRpc(e))?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Get status of a specific runner
|
||||
pub async fn get_runner_status(&self, secret: &str, actor_id: &str) -> ClientResult<RunnerStatus> {
|
||||
let params = serde_json::json!({
|
||||
"secret": secret,
|
||||
"actor_id": actor_id
|
||||
});
|
||||
/// Authentication via Authorization header (set during client creation)
|
||||
pub async fn get_runner_status(&self, actor_id: &str) -> ClientResult<RunnerStatus> {
|
||||
let status: RunnerStatus = self
|
||||
.client
|
||||
.request("get_runner_status", rpc_params![params])
|
||||
.request("runner.status", rpc_params![actor_id])
|
||||
.await.map_err(|e| ClientError::JsonRpc(e))?;
|
||||
Ok(status)
|
||||
}
|
||||
@@ -458,9 +506,8 @@ impl SupervisorClient {
|
||||
}
|
||||
|
||||
/// Run a job on a specific runner
|
||||
pub async fn run_job(&self, secret: &str, job: Job) -> ClientResult<JobResult> {
|
||||
pub async fn run_job(&self, job: Job) -> ClientResult<JobResult> {
|
||||
let params = serde_json::json!({
|
||||
"secret": secret,
|
||||
"job": job
|
||||
});
|
||||
|
||||
@@ -519,12 +566,10 @@ impl SupervisorClient {
|
||||
/// Add a secret to the supervisor
|
||||
pub async fn add_secret(
|
||||
&self,
|
||||
admin_secret: &str,
|
||||
secret_type: &str,
|
||||
secret_value: &str,
|
||||
) -> ClientResult<()> {
|
||||
let params = serde_json::json!({
|
||||
"admin_secret": admin_secret,
|
||||
"secret_type": secret_type,
|
||||
"secret_value": secret_value
|
||||
});
|
||||
@@ -539,12 +584,10 @@ impl SupervisorClient {
|
||||
/// Remove a secret from the supervisor
|
||||
pub async fn remove_secret(
|
||||
&self,
|
||||
admin_secret: &str,
|
||||
secret_type: &str,
|
||||
secret_value: &str,
|
||||
) -> ClientResult<()> {
|
||||
let params = serde_json::json!({
|
||||
"admin_secret": admin_secret,
|
||||
"secret_type": secret_type,
|
||||
"secret_value": secret_value
|
||||
});
|
||||
@@ -557,10 +600,8 @@ impl SupervisorClient {
|
||||
}
|
||||
|
||||
/// List secrets (returns supervisor info including secret counts)
|
||||
pub async fn list_secrets(&self, admin_secret: &str) -> ClientResult<SupervisorInfo> {
|
||||
let params = serde_json::json!({
|
||||
"admin_secret": admin_secret
|
||||
});
|
||||
pub async fn list_secrets(&self) -> ClientResult<SupervisorInfo> {
|
||||
let params = serde_json::json!({});
|
||||
|
||||
let info: SupervisorInfo = self
|
||||
.client
|
||||
@@ -570,9 +611,8 @@ impl SupervisorClient {
|
||||
}
|
||||
|
||||
/// Stop a running job
|
||||
pub async fn job_stop(&self, secret: &str, job_id: &str) -> ClientResult<()> {
|
||||
pub async fn job_stop(&self, job_id: &str) -> ClientResult<()> {
|
||||
let params = serde_json::json!({
|
||||
"secret": secret,
|
||||
"job_id": job_id
|
||||
});
|
||||
|
||||
@@ -582,9 +622,8 @@ impl SupervisorClient {
|
||||
}
|
||||
|
||||
/// Delete a job from the system
|
||||
pub async fn job_delete(&self, secret: &str, job_id: &str) -> ClientResult<()> {
|
||||
pub async fn job_delete(&self, job_id: &str) -> ClientResult<()> {
|
||||
let params = serde_json::json!({
|
||||
"secret": secret,
|
||||
"job_id": job_id
|
||||
});
|
||||
|
||||
@@ -594,11 +633,58 @@ impl SupervisorClient {
|
||||
}
|
||||
|
||||
/// Get supervisor information including secret counts
|
||||
pub async fn get_supervisor_info(&self, admin_secret: &str) -> ClientResult<SupervisorInfo> {
|
||||
pub async fn get_supervisor_info(&self) -> ClientResult<SupervisorInfo> {
|
||||
let info: SupervisorInfo = self
|
||||
.client
|
||||
.request("get_supervisor_info", rpc_params![admin_secret])
|
||||
.request("supervisor.info", rpc_params![])
|
||||
.await.map_err(|e| ClientError::JsonRpc(e))?;
|
||||
Ok(info)
|
||||
}
|
||||
|
||||
/// Get a job by ID
|
||||
pub async fn get_job(&self, job_id: &str) -> ClientResult<Job> {
|
||||
let job: Job = self
|
||||
.client
|
||||
.request("job.get", rpc_params![job_id])
|
||||
.await.map_err(|e| ClientError::JsonRpc(e))?;
|
||||
Ok(job)
|
||||
}
|
||||
|
||||
// ========== Auth/API Key Methods ==========
|
||||
|
||||
/// Verify the current API key
|
||||
pub async fn auth_verify(&self) -> ClientResult<AuthVerifyResponse> {
|
||||
let response: AuthVerifyResponse = self
|
||||
.client
|
||||
.request("auth.verify", rpc_params![])
|
||||
.await.map_err(|e| ClientError::JsonRpc(e))?;
|
||||
Ok(response)
|
||||
}
|
||||
|
||||
/// Create a new API key (admin only)
|
||||
pub async fn auth_create_key(&self, name: String, scope: String) -> ClientResult<ApiKey> {
|
||||
let api_key: ApiKey = self
|
||||
.client
|
||||
.request("auth.key.create", rpc_params![name, scope])
|
||||
.await.map_err(|e| ClientError::JsonRpc(e))?;
|
||||
Ok(api_key)
|
||||
}
|
||||
|
||||
/// Remove an API key (admin only)
|
||||
pub async fn auth_remove_key(&self, key: String) -> ClientResult<bool> {
|
||||
let removed: bool = self
|
||||
.client
|
||||
.request("auth.key.remove", rpc_params![key])
|
||||
.await.map_err(|e| ClientError::JsonRpc(e))?;
|
||||
Ok(removed)
|
||||
}
|
||||
|
||||
/// List all API keys (admin only)
|
||||
pub async fn auth_list_keys(&self) -> ClientResult<Vec<ApiKey>> {
|
||||
let keys: Vec<ApiKey> = self
|
||||
.client
|
||||
.request("auth.key.list", rpc_params![])
|
||||
.await.map_err(|e| ClientError::JsonRpc(e))?;
|
||||
Ok(keys)
|
||||
}
|
||||
}
|
||||
@@ -14,11 +14,12 @@ use thiserror::Error;
|
||||
use uuid::Uuid;
|
||||
|
||||
/// WASM-compatible client for communicating with Hero Supervisor OpenRPC server
|
||||
/// Requires authentication secret for all operations
|
||||
#[wasm_bindgen]
|
||||
#[derive(Clone)]
|
||||
pub struct WasmSupervisorClient {
|
||||
server_url: String,
|
||||
secret: Option<String>,
|
||||
secret: String,
|
||||
}
|
||||
|
||||
/// Error types for WASM client operations
|
||||
@@ -124,24 +125,20 @@ pub use hero_job::JobBuilder;
|
||||
|
||||
#[wasm_bindgen]
|
||||
impl WasmSupervisorClient {
|
||||
/// Create a new WASM supervisor client without authentication
|
||||
/// Create a new WASM supervisor client with authentication secret
|
||||
#[wasm_bindgen(constructor)]
|
||||
pub fn new(server_url: String) -> Self {
|
||||
pub fn new(server_url: String, secret: String) -> Self {
|
||||
console_log::init_with_level(log::Level::Info).ok();
|
||||
Self {
|
||||
server_url,
|
||||
secret: None,
|
||||
secret,
|
||||
}
|
||||
}
|
||||
|
||||
/// Create a new WASM supervisor client with authentication secret
|
||||
/// Alias for new() to maintain backward compatibility
|
||||
#[wasm_bindgen]
|
||||
pub fn with_secret(server_url: String, secret: String) -> Self {
|
||||
console_log::init_with_level(log::Level::Info).ok();
|
||||
Self {
|
||||
server_url,
|
||||
secret: Some(secret),
|
||||
}
|
||||
Self::new(server_url, secret)
|
||||
}
|
||||
|
||||
/// Get the server URL
|
||||
@@ -183,12 +180,9 @@ impl WasmSupervisorClient {
|
||||
}
|
||||
|
||||
/// Verify the client's stored API key
|
||||
/// Uses the secret that was set when creating the client with with_secret()
|
||||
/// Uses the secret that was set when creating the client
|
||||
pub async fn auth_verify_self(&self) -> Result<JsValue, JsValue> {
|
||||
let key = self.secret.as_ref()
|
||||
.ok_or_else(|| JsValue::from_str("Client not authenticated - use with_secret() to create authenticated client"))?;
|
||||
|
||||
self.auth_verify(key.clone()).await
|
||||
self.auth_verify(self.secret.clone()).await
|
||||
}
|
||||
|
||||
/// Create a new API key (admin only)
|
||||
@@ -721,16 +715,10 @@ impl WasmSupervisorClient {
|
||||
headers.set("Content-Type", "application/json")
|
||||
.map_err(|e| WasmClientError::JavaScript(format!("{:?}", e)))?;
|
||||
|
||||
// Add Authorization header if secret is present
|
||||
if let Some(secret) = &self.secret {
|
||||
let auth_value = format!("Bearer {}", secret);
|
||||
web_sys::console::log_1(&format!("🔐 WASM Client: Setting Authorization header: Bearer {}...", &secret[..secret.len().min(8)]).into());
|
||||
headers.set("Authorization", &auth_value)
|
||||
.map_err(|e| WasmClientError::JavaScript(format!("{:?}", e)))?;
|
||||
web_sys::console::log_1(&"✅ WASM Client: Authorization header set successfully".into());
|
||||
} else {
|
||||
web_sys::console::log_1(&"⚠️ WASM Client: NO SECRET - Authorization header NOT set".into());
|
||||
}
|
||||
// Add Authorization header with secret
|
||||
let auth_value = format!("Bearer {}", self.secret);
|
||||
headers.set("Authorization", &auth_value)
|
||||
.map_err(|e| WasmClientError::JavaScript(format!("{:?}", e)))?;
|
||||
|
||||
// Create request init
|
||||
let opts = RequestInit::new();
|
||||
@@ -787,8 +775,8 @@ pub fn init() {
|
||||
|
||||
/// Utility function to create a client from JavaScript
|
||||
#[wasm_bindgen]
|
||||
pub fn create_client(server_url: String) -> WasmSupervisorClient {
|
||||
WasmSupervisorClient::new(server_url)
|
||||
pub fn create_client(server_url: String, secret: String) -> WasmSupervisorClient {
|
||||
WasmSupervisorClient::new(server_url, secret)
|
||||
}
|
||||
|
||||
/// Sign a job's canonical representation with a private key
|
||||
|
||||
Reference in New Issue
Block a user