initial commit
This commit is contained in:
668
clients/openrpc/src/wasm.rs
Normal file
668
clients/openrpc/src/wasm.rs
Normal file
@@ -0,0 +1,668 @@
|
||||
//! WASM-compatible OpenRPC client for Hero Supervisor
|
||||
//!
|
||||
//! This module provides a WASM-compatible client library for interacting with the Hero Supervisor
|
||||
//! OpenRPC server using browser-native fetch APIs.
|
||||
|
||||
use wasm_bindgen::prelude::*;
|
||||
use wasm_bindgen_futures::JsFuture;
|
||||
use web_sys::{Request, RequestInit, RequestMode, Response, Headers};
|
||||
use serde::{Deserialize, Serialize};
|
||||
// use std::collections::HashMap; // Unused
|
||||
use thiserror::Error;
|
||||
use uuid::Uuid;
|
||||
// use js_sys::Promise; // Unused
|
||||
|
||||
/// WASM-compatible client for communicating with Hero Supervisor OpenRPC server
|
||||
#[wasm_bindgen]
|
||||
pub struct WasmSupervisorClient {
|
||||
server_url: String,
|
||||
}
|
||||
|
||||
/// Error types for WASM client operations
|
||||
#[derive(Error, Debug)]
|
||||
pub enum WasmClientError {
|
||||
#[error("Network error: {0}")]
|
||||
Network(String),
|
||||
|
||||
#[error("Serialization error: {0}")]
|
||||
Serialization(#[from] serde_json::Error),
|
||||
|
||||
#[error("JavaScript error: {0}")]
|
||||
JavaScript(String),
|
||||
|
||||
#[error("Server error: {message}")]
|
||||
Server { message: String },
|
||||
|
||||
#[error("Invalid response format")]
|
||||
InvalidResponse,
|
||||
}
|
||||
|
||||
/// Result type for WASM client operations
|
||||
pub type WasmClientResult<T> = Result<T, WasmClientError>;
|
||||
|
||||
/// JSON-RPC request structure
|
||||
#[derive(Serialize)]
|
||||
struct JsonRpcRequest {
|
||||
jsonrpc: String,
|
||||
method: String,
|
||||
params: serde_json::Value,
|
||||
id: u32,
|
||||
}
|
||||
|
||||
/// JSON-RPC response structure
|
||||
#[derive(Deserialize)]
|
||||
struct JsonRpcResponse {
|
||||
jsonrpc: String,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
result: Option<serde_json::Value>,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
error: Option<JsonRpcError>,
|
||||
id: u32,
|
||||
}
|
||||
|
||||
/// JSON-RPC error structure
|
||||
#[derive(Deserialize)]
|
||||
struct JsonRpcError {
|
||||
code: i32,
|
||||
message: String,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
data: Option<serde_json::Value>,
|
||||
}
|
||||
|
||||
/// Types of runners supported by the supervisor
|
||||
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
|
||||
#[wasm_bindgen]
|
||||
pub enum WasmRunnerType {
|
||||
SALRunner,
|
||||
OSISRunner,
|
||||
VRunner,
|
||||
}
|
||||
|
||||
/// Job type enumeration that maps to runner types
|
||||
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
|
||||
#[wasm_bindgen]
|
||||
pub enum WasmJobType {
|
||||
SAL,
|
||||
OSIS,
|
||||
V,
|
||||
}
|
||||
|
||||
/// Job structure for creating and managing jobs
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
#[wasm_bindgen]
|
||||
pub struct WasmJob {
|
||||
id: String,
|
||||
caller_id: String,
|
||||
context_id: String,
|
||||
payload: String,
|
||||
runner_name: String,
|
||||
executor: String,
|
||||
timeout_secs: u64,
|
||||
env_vars: String, // JSON string of HashMap<String, String>
|
||||
created_at: String,
|
||||
updated_at: String,
|
||||
}
|
||||
|
||||
#[wasm_bindgen]
|
||||
impl WasmSupervisorClient {
|
||||
/// Create a new WASM supervisor client
|
||||
#[wasm_bindgen(constructor)]
|
||||
pub fn new(server_url: String) -> Self {
|
||||
console_log::init_with_level(log::Level::Info).ok();
|
||||
Self { server_url }
|
||||
}
|
||||
|
||||
/// Get the server URL
|
||||
#[wasm_bindgen(getter)]
|
||||
pub fn server_url(&self) -> String {
|
||||
self.server_url.clone()
|
||||
}
|
||||
|
||||
/// Test connection using OpenRPC discovery method
|
||||
pub async fn discover(&self) -> Result<JsValue, JsValue> {
|
||||
let result = self.call_method("rpc.discover", serde_json::Value::Null).await;
|
||||
match result {
|
||||
Ok(value) => Ok(wasm_bindgen::JsValue::from_str(&value.to_string())),
|
||||
Err(e) => Err(JsValue::from_str(&e.to_string())),
|
||||
}
|
||||
}
|
||||
|
||||
/// Register a new runner to the supervisor with secret authentication
|
||||
pub async fn register_runner(&self, secret: &str, name: &str, queue: &str) -> Result<String, JsValue> {
|
||||
let params = serde_json::json!([{
|
||||
"secret": secret,
|
||||
"name": name,
|
||||
"queue": queue
|
||||
}]);
|
||||
|
||||
match self.call_method("register_runner", params).await {
|
||||
Ok(result) => {
|
||||
// Extract the runner name from the result
|
||||
if let Some(runner_name) = result.as_str() {
|
||||
Ok(runner_name.to_string())
|
||||
} else {
|
||||
Err(JsValue::from_str("Invalid response format: expected runner name"))
|
||||
}
|
||||
},
|
||||
Err(e) => Err(JsValue::from_str(&e.to_string())),
|
||||
}
|
||||
}
|
||||
|
||||
/// Create a job (fire-and-forget, non-blocking)
|
||||
#[wasm_bindgen]
|
||||
pub async fn create_job(&self, secret: String, job: WasmJob) -> Result<String, JsValue> {
|
||||
// Backend expects RunJobParams struct with secret and job fields - wrap in array like register_runner
|
||||
let params = serde_json::json!([{
|
||||
"secret": secret,
|
||||
"job": {
|
||||
"id": job.id,
|
||||
"caller_id": job.caller_id,
|
||||
"context_id": job.context_id,
|
||||
"payload": job.payload,
|
||||
"runner_name": job.runner_name,
|
||||
"executor": job.executor,
|
||||
"timeout": {
|
||||
"secs": job.timeout_secs,
|
||||
"nanos": 0
|
||||
},
|
||||
"env_vars": serde_json::from_str::<serde_json::Value>(&job.env_vars).unwrap_or(serde_json::json!({})),
|
||||
"created_at": job.created_at,
|
||||
"updated_at": job.updated_at
|
||||
}
|
||||
}]);
|
||||
|
||||
match self.call_method("create_job", params).await {
|
||||
Ok(result) => {
|
||||
if let Some(job_id) = result.as_str() {
|
||||
Ok(job_id.to_string())
|
||||
} else {
|
||||
Ok(result.to_string())
|
||||
}
|
||||
}
|
||||
Err(e) => Err(JsValue::from_str(&format!("Failed to create job: {:?}", e)))
|
||||
}
|
||||
}
|
||||
|
||||
/// Run a job on a specific runner (blocking, returns result)
|
||||
#[wasm_bindgen]
|
||||
pub async fn run_job(&self, secret: String, job: WasmJob) -> Result<String, JsValue> {
|
||||
// Backend expects RunJobParams struct with secret and job fields - wrap in array like register_runner
|
||||
let params = serde_json::json!([{
|
||||
"secret": secret,
|
||||
"job": {
|
||||
"id": job.id,
|
||||
"caller_id": job.caller_id,
|
||||
"context_id": job.context_id,
|
||||
"payload": job.payload,
|
||||
"runner_name": job.runner_name,
|
||||
"executor": job.executor,
|
||||
"timeout": {
|
||||
"secs": job.timeout_secs,
|
||||
"nanos": 0
|
||||
},
|
||||
"env_vars": serde_json::from_str::<serde_json::Value>(&job.env_vars).unwrap_or(serde_json::json!({})),
|
||||
"created_at": job.created_at,
|
||||
"updated_at": job.updated_at
|
||||
}
|
||||
}]);
|
||||
|
||||
match self.call_method("run_job", params).await {
|
||||
Ok(result) => {
|
||||
if let Some(result_str) = result.as_str() {
|
||||
Ok(result_str.to_string())
|
||||
} else {
|
||||
Ok(result.to_string())
|
||||
}
|
||||
},
|
||||
Err(e) => Err(JsValue::from_str(&e.to_string())),
|
||||
}
|
||||
}
|
||||
|
||||
/// List all runner IDs
|
||||
pub async fn list_runners(&self) -> Result<Vec<String>, JsValue> {
|
||||
match self.call_method("list_runners", serde_json::Value::Null).await {
|
||||
Ok(result) => {
|
||||
if let Ok(runners) = serde_json::from_value::<Vec<String>>(result) {
|
||||
Ok(runners)
|
||||
} else {
|
||||
Err(JsValue::from_str("Invalid response format for list_runners"))
|
||||
}
|
||||
},
|
||||
Err(e) => Err(JsValue::from_str(&e.to_string())),
|
||||
}
|
||||
}
|
||||
|
||||
/// List all job IDs from Redis
|
||||
pub async fn list_jobs(&self) -> Result<Vec<String>, JsValue> {
|
||||
match self.call_method("list_jobs", serde_json::Value::Null).await {
|
||||
Ok(result) => {
|
||||
if let Ok(jobs) = serde_json::from_value::<Vec<String>>(result) {
|
||||
Ok(jobs)
|
||||
} else {
|
||||
Err(JsValue::from_str("Invalid response format for list_jobs"))
|
||||
}
|
||||
},
|
||||
Err(e) => Err(JsValue::from_str(&e.to_string())),
|
||||
}
|
||||
}
|
||||
|
||||
/// Get a job by job ID
|
||||
pub async fn get_job(&self, job_id: &str) -> Result<WasmJob, JsValue> {
|
||||
let params = serde_json::json!([job_id]);
|
||||
match self.call_method("get_job", params).await {
|
||||
Ok(result) => {
|
||||
// Convert the Job result to WasmJob
|
||||
if let Ok(job_value) = serde_json::from_value::<serde_json::Value>(result) {
|
||||
// Extract fields from the job
|
||||
let id = job_value.get("id").and_then(|v| v.as_str()).unwrap_or("").to_string();
|
||||
let caller_id = job_value.get("caller_id").and_then(|v| v.as_str()).unwrap_or("").to_string();
|
||||
let context_id = job_value.get("context_id").and_then(|v| v.as_str()).unwrap_or("").to_string();
|
||||
let payload = job_value.get("payload").and_then(|v| v.as_str()).unwrap_or("").to_string();
|
||||
let runner_name = job_value.get("runner_name").and_then(|v| v.as_str()).unwrap_or("").to_string();
|
||||
let executor = job_value.get("executor").and_then(|v| v.as_str()).unwrap_or("").to_string();
|
||||
let timeout_secs = job_value.get("timeout").and_then(|v| v.get("secs")).and_then(|v| v.as_u64()).unwrap_or(30);
|
||||
let env_vars = job_value.get("env_vars").map(|v| v.to_string()).unwrap_or_else(|| "{}".to_string());
|
||||
let created_at = job_value.get("created_at").and_then(|v| v.as_str()).unwrap_or("").to_string();
|
||||
let updated_at = job_value.get("updated_at").and_then(|v| v.as_str()).unwrap_or("").to_string();
|
||||
|
||||
Ok(WasmJob {
|
||||
id,
|
||||
caller_id,
|
||||
context_id,
|
||||
payload,
|
||||
runner_name,
|
||||
executor,
|
||||
timeout_secs,
|
||||
env_vars,
|
||||
created_at,
|
||||
updated_at,
|
||||
})
|
||||
} else {
|
||||
Err(JsValue::from_str("Invalid response format for get_job"))
|
||||
}
|
||||
},
|
||||
Err(e) => Err(JsValue::from_str(&e.to_string())),
|
||||
}
|
||||
}
|
||||
|
||||
/// Ping a runner by dispatching a ping job to its queue
|
||||
#[wasm_bindgen]
|
||||
pub async fn ping_runner(&self, runner_id: &str) -> Result<String, JsValue> {
|
||||
let params = serde_json::json!([runner_id]);
|
||||
|
||||
match self.call_method("ping_runner", params).await {
|
||||
Ok(result) => {
|
||||
if let Some(job_id) = result.as_str() {
|
||||
Ok(job_id.to_string())
|
||||
} else {
|
||||
Ok(result.to_string())
|
||||
}
|
||||
}
|
||||
Err(e) => Err(JsValue::from_str(&format!("Failed to ping runner: {:?}", e)))
|
||||
}
|
||||
}
|
||||
|
||||
/// Stop a job by ID
|
||||
#[wasm_bindgen]
|
||||
pub async fn stop_job(&self, job_id: &str) -> Result<(), JsValue> {
|
||||
let params = serde_json::json!([job_id]);
|
||||
|
||||
match self.call_method("stop_job", params).await {
|
||||
Ok(_) => Ok(()),
|
||||
Err(e) => Err(JsValue::from_str(&format!("Failed to stop job: {:?}", e)))
|
||||
}
|
||||
}
|
||||
|
||||
/// Delete a job by ID
|
||||
#[wasm_bindgen]
|
||||
pub async fn delete_job(&self, job_id: &str) -> Result<(), JsValue> {
|
||||
let params = serde_json::json!([job_id]);
|
||||
|
||||
match self.call_method("delete_job", params).await {
|
||||
Ok(_) => Ok(()),
|
||||
Err(e) => Err(JsValue::from_str(&format!("Failed to delete job: {:?}", e)))
|
||||
}
|
||||
}
|
||||
|
||||
/// Remove a runner from the supervisor
|
||||
pub async fn remove_runner(&self, actor_id: &str) -> Result<(), JsValue> {
|
||||
let params = serde_json::json!([actor_id]);
|
||||
match self.call_method("remove_runner", params).await {
|
||||
Ok(_) => Ok(()),
|
||||
Err(e) => Err(JsValue::from_str(&e.to_string())),
|
||||
}
|
||||
}
|
||||
|
||||
/// Start a specific runner
|
||||
pub async fn start_runner(&self, actor_id: &str) -> Result<(), JsValue> {
|
||||
let params = serde_json::json!([actor_id]);
|
||||
match self.call_method("start_runner", params).await {
|
||||
Ok(_) => Ok(()),
|
||||
Err(e) => Err(JsValue::from_str(&e.to_string())),
|
||||
}
|
||||
}
|
||||
|
||||
/// Stop a specific runner
|
||||
pub async fn stop_runner(&self, actor_id: &str, force: bool) -> Result<(), JsValue> {
|
||||
let params = serde_json::json!([actor_id, force]);
|
||||
self.call_method("stop_runner", params)
|
||||
.await
|
||||
.map_err(|e| JsValue::from_str(&e.to_string()))?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Get a specific runner by ID
|
||||
pub async fn get_runner(&self, actor_id: &str) -> Result<JsValue, JsValue> {
|
||||
let params = serde_json::json!([actor_id]);
|
||||
let result = self.call_method("get_runner", params)
|
||||
.await
|
||||
.map_err(|e| JsValue::from_str(&e.to_string()))?;
|
||||
// Convert the serde_json::Value to a JsValue via string serialization
|
||||
let json_string = serde_json::to_string(&result)
|
||||
.map_err(|e| JsValue::from_str(&e.to_string()))?;
|
||||
Ok(js_sys::JSON::parse(&json_string)
|
||||
.map_err(|e| JsValue::from_str("Failed to parse JSON"))?)
|
||||
}
|
||||
|
||||
/// Add a secret to the supervisor
|
||||
pub async fn add_secret(&self, admin_secret: &str, secret_type: &str, secret_value: &str) -> Result<(), JsValue> {
|
||||
let params = serde_json::json!([{
|
||||
"admin_secret": admin_secret,
|
||||
"secret_type": secret_type,
|
||||
"secret_value": secret_value
|
||||
}]);
|
||||
match self.call_method("add_secret", params).await {
|
||||
Ok(_) => Ok(()),
|
||||
Err(e) => Err(JsValue::from_str(&e.to_string())),
|
||||
}
|
||||
}
|
||||
|
||||
/// Remove a secret from the supervisor
|
||||
pub async fn remove_secret(&self, admin_secret: &str, secret_type: &str, secret_value: &str) -> Result<(), JsValue> {
|
||||
let params = serde_json::json!([{
|
||||
"admin_secret": admin_secret,
|
||||
"secret_type": secret_type,
|
||||
"secret_value": secret_value
|
||||
}]);
|
||||
match self.call_method("remove_secret", params).await {
|
||||
Ok(_) => Ok(()),
|
||||
Err(e) => Err(JsValue::from_str(&e.to_string())),
|
||||
}
|
||||
}
|
||||
|
||||
/// List secrets (returns supervisor info including secret counts)
|
||||
pub async fn list_secrets(&self, admin_secret: &str) -> Result<JsValue, JsValue> {
|
||||
let params = serde_json::json!([{
|
||||
"admin_secret": admin_secret
|
||||
}]);
|
||||
match self.call_method("list_secrets", params).await {
|
||||
Ok(result) => {
|
||||
// Convert serde_json::Value to JsValue
|
||||
let result_str = serde_json::to_string(&result)
|
||||
.map_err(|e| JsValue::from_str(&e.to_string()))?;
|
||||
Ok(js_sys::JSON::parse(&result_str)
|
||||
.map_err(|e| JsValue::from_str(&format!("JSON parse error: {:?}", e)))?)
|
||||
},
|
||||
Err(e) => Err(JsValue::from_str(&e.to_string())),
|
||||
}
|
||||
}
|
||||
|
||||
/// Get supervisor information including secret counts
|
||||
pub async fn get_supervisor_info(&self, admin_secret: &str) -> Result<JsValue, JsValue> {
|
||||
let params = serde_json::json!({
|
||||
"admin_secret": admin_secret
|
||||
});
|
||||
|
||||
match self.call_method("get_supervisor_info", params).await {
|
||||
Ok(result) => {
|
||||
let result_str = serde_json::to_string(&result)
|
||||
.map_err(|e| JsValue::from_str(&format!("Serialization error: {:?}", e)))?;
|
||||
Ok(js_sys::JSON::parse(&result_str)
|
||||
.map_err(|e| JsValue::from_str(&format!("JSON parse error: {:?}", e)))?)
|
||||
},
|
||||
Err(e) => Err(JsValue::from_str(&format!("Failed to get supervisor info: {:?}", e))),
|
||||
}
|
||||
}
|
||||
|
||||
/// List admin secrets (returns actual secret values)
|
||||
pub async fn list_admin_secrets(&self, admin_secret: &str) -> Result<Vec<String>, JsValue> {
|
||||
let params = serde_json::json!({
|
||||
"admin_secret": admin_secret
|
||||
});
|
||||
|
||||
match self.call_method("list_admin_secrets", params).await {
|
||||
Ok(result) => {
|
||||
let secrets: Vec<String> = serde_json::from_value(result)
|
||||
.map_err(|e| JsValue::from_str(&format!("Failed to parse admin secrets: {:?}", e)))?;
|
||||
Ok(secrets)
|
||||
},
|
||||
Err(e) => Err(JsValue::from_str(&format!("Failed to list admin secrets: {:?}", e))),
|
||||
}
|
||||
}
|
||||
|
||||
/// List user secrets (returns actual secret values)
|
||||
pub async fn list_user_secrets(&self, admin_secret: &str) -> Result<Vec<String>, JsValue> {
|
||||
let params = serde_json::json!({
|
||||
"admin_secret": admin_secret
|
||||
});
|
||||
|
||||
match self.call_method("list_user_secrets", params).await {
|
||||
Ok(result) => {
|
||||
let secrets: Vec<String> = serde_json::from_value(result)
|
||||
.map_err(|e| JsValue::from_str(&format!("Failed to parse user secrets: {:?}", e)))?;
|
||||
Ok(secrets)
|
||||
},
|
||||
Err(e) => Err(JsValue::from_str(&format!("Failed to list user secrets: {:?}", e))),
|
||||
}
|
||||
}
|
||||
|
||||
/// List register secrets (returns actual secret values)
|
||||
pub async fn list_register_secrets(&self, admin_secret: &str) -> Result<Vec<String>, JsValue> {
|
||||
let params = serde_json::json!({
|
||||
"admin_secret": admin_secret
|
||||
});
|
||||
|
||||
match self.call_method("list_register_secrets", params).await {
|
||||
Ok(result) => {
|
||||
let secrets: Vec<String> = serde_json::from_value(result)
|
||||
.map_err(|e| JsValue::from_str(&format!("Failed to parse register secrets: {:?}", e)))?;
|
||||
Ok(secrets)
|
||||
},
|
||||
Err(e) => Err(JsValue::from_str(&format!("Failed to list register secrets: {:?}", e))),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[wasm_bindgen]
|
||||
impl WasmJob {
|
||||
/// Create a new job with default values
|
||||
#[wasm_bindgen(constructor)]
|
||||
pub fn new(id: String, payload: String, executor: String, runner_name: String) -> Self {
|
||||
let now = js_sys::Date::new_0().to_iso_string().as_string().unwrap();
|
||||
Self {
|
||||
id,
|
||||
caller_id: "wasm_client".to_string(),
|
||||
context_id: "wasm_context".to_string(),
|
||||
payload,
|
||||
runner_name,
|
||||
executor,
|
||||
timeout_secs: 30,
|
||||
env_vars: "{}".to_string(),
|
||||
created_at: now.clone(),
|
||||
updated_at: now,
|
||||
}
|
||||
}
|
||||
|
||||
/// Set the caller ID
|
||||
#[wasm_bindgen(setter)]
|
||||
pub fn set_caller_id(&mut self, caller_id: String) {
|
||||
self.caller_id = caller_id;
|
||||
}
|
||||
|
||||
/// Set the context ID
|
||||
#[wasm_bindgen(setter)]
|
||||
pub fn set_context_id(&mut self, context_id: String) {
|
||||
self.context_id = context_id;
|
||||
}
|
||||
|
||||
/// Set the timeout in seconds
|
||||
#[wasm_bindgen(setter)]
|
||||
pub fn set_timeout_secs(&mut self, timeout_secs: u64) {
|
||||
self.timeout_secs = timeout_secs;
|
||||
}
|
||||
|
||||
/// Set environment variables as JSON string
|
||||
#[wasm_bindgen(setter)]
|
||||
pub fn set_env_vars(&mut self, env_vars: String) {
|
||||
self.env_vars = env_vars;
|
||||
}
|
||||
|
||||
/// Generate a new UUID for the job
|
||||
#[wasm_bindgen]
|
||||
pub fn generate_id(&mut self) {
|
||||
self.id = Uuid::new_v4().to_string();
|
||||
}
|
||||
|
||||
/// Get the job ID
|
||||
#[wasm_bindgen(getter)]
|
||||
pub fn id(&self) -> String {
|
||||
self.id.clone()
|
||||
}
|
||||
|
||||
/// Get the caller ID
|
||||
#[wasm_bindgen(getter)]
|
||||
pub fn caller_id(&self) -> String {
|
||||
self.caller_id.clone()
|
||||
}
|
||||
|
||||
/// Get the context ID
|
||||
#[wasm_bindgen(getter)]
|
||||
pub fn context_id(&self) -> String {
|
||||
self.context_id.clone()
|
||||
}
|
||||
|
||||
/// Get the payload
|
||||
#[wasm_bindgen(getter)]
|
||||
pub fn payload(&self) -> String {
|
||||
self.payload.clone()
|
||||
}
|
||||
|
||||
/// Get the job type
|
||||
#[wasm_bindgen(getter)]
|
||||
pub fn executor(&self) -> String {
|
||||
self.executor.clone()
|
||||
}
|
||||
|
||||
/// Get the runner name
|
||||
#[wasm_bindgen(getter)]
|
||||
pub fn runner_name(&self) -> String {
|
||||
self.runner_name.clone()
|
||||
}
|
||||
|
||||
/// Get the timeout in seconds
|
||||
#[wasm_bindgen(getter)]
|
||||
pub fn timeout_secs(&self) -> u64 {
|
||||
self.timeout_secs
|
||||
}
|
||||
|
||||
/// Get the environment variables as JSON string
|
||||
#[wasm_bindgen(getter)]
|
||||
pub fn env_vars(&self) -> String {
|
||||
self.env_vars.clone()
|
||||
}
|
||||
|
||||
/// Get the created timestamp
|
||||
#[wasm_bindgen(getter)]
|
||||
pub fn created_at(&self) -> String {
|
||||
self.created_at.clone()
|
||||
}
|
||||
|
||||
/// Get the updated timestamp
|
||||
#[wasm_bindgen(getter)]
|
||||
pub fn updated_at(&self) -> String {
|
||||
self.updated_at.clone()
|
||||
}
|
||||
}
|
||||
|
||||
impl WasmSupervisorClient {
|
||||
/// Internal method to make JSON-RPC calls
|
||||
async fn call_method(&self, method: &str, params: serde_json::Value) -> WasmClientResult<serde_json::Value> {
|
||||
let request = JsonRpcRequest {
|
||||
jsonrpc: "2.0".to_string(),
|
||||
method: method.to_string(),
|
||||
params,
|
||||
id: 1,
|
||||
};
|
||||
|
||||
let body = serde_json::to_string(&request)?;
|
||||
|
||||
// Create headers
|
||||
let headers = Headers::new().map_err(|e| WasmClientError::JavaScript(format!("{:?}", e)))?;
|
||||
headers.set("Content-Type", "application/json")
|
||||
.map_err(|e| WasmClientError::JavaScript(format!("{:?}", e)))?;
|
||||
|
||||
// Create request init
|
||||
let opts = RequestInit::new();
|
||||
opts.set_method("POST");
|
||||
opts.set_headers(&headers);
|
||||
opts.set_body(&JsValue::from_str(&body));
|
||||
opts.set_mode(RequestMode::Cors);
|
||||
|
||||
// Create request
|
||||
let request = Request::new_with_str_and_init(&self.server_url, &opts)
|
||||
.map_err(|e| WasmClientError::JavaScript(format!("{:?}", e)))?;
|
||||
|
||||
// Get window and fetch
|
||||
let window = web_sys::window().ok_or_else(|| WasmClientError::JavaScript("No window object".to_string()))?;
|
||||
let resp_value = JsFuture::from(window.fetch_with_request(&request)).await
|
||||
.map_err(|e| WasmClientError::Network(format!("{:?}", e)))?;
|
||||
|
||||
// Convert to Response
|
||||
let resp: Response = resp_value.dyn_into()
|
||||
.map_err(|e| WasmClientError::JavaScript(format!("{:?}", e)))?;
|
||||
|
||||
// Check if response is ok
|
||||
if !resp.ok() {
|
||||
return Err(WasmClientError::Network(format!("HTTP {}: {}", resp.status(), resp.status_text())));
|
||||
}
|
||||
|
||||
// Get response text
|
||||
let text_promise = resp.text()
|
||||
.map_err(|e| WasmClientError::JavaScript(format!("{:?}", e)))?;
|
||||
let text_value = JsFuture::from(text_promise).await
|
||||
.map_err(|e| WasmClientError::Network(format!("{:?}", e)))?;
|
||||
let text = text_value.as_string()
|
||||
.ok_or_else(|| WasmClientError::InvalidResponse)?;
|
||||
|
||||
// Parse JSON-RPC response
|
||||
let response: JsonRpcResponse = serde_json::from_str(&text)?;
|
||||
|
||||
if let Some(error) = response.error {
|
||||
return Err(WasmClientError::Server {
|
||||
message: format!("{}: {}", error.code, error.message),
|
||||
});
|
||||
}
|
||||
|
||||
// For void methods, null result is valid
|
||||
Ok(response.result.unwrap_or(serde_json::Value::Null))
|
||||
}
|
||||
}
|
||||
|
||||
/// Initialize the WASM client library (call manually if needed)
|
||||
pub fn init() {
|
||||
console_log::init_with_level(log::Level::Info).ok();
|
||||
log::info!("Hero Supervisor WASM OpenRPC Client initialized");
|
||||
}
|
||||
|
||||
/// Utility function to create a job from JavaScript
|
||||
/// Create a new job (convenience function for JavaScript)
|
||||
#[wasm_bindgen]
|
||||
pub fn create_job(id: String, payload: String, executor: String, runner_name: String) -> WasmJob {
|
||||
WasmJob::new(id, payload, executor, runner_name)
|
||||
}
|
||||
|
||||
/// Utility function to create a client from JavaScript
|
||||
#[wasm_bindgen]
|
||||
pub fn create_client(server_url: String) -> WasmSupervisorClient {
|
||||
WasmSupervisorClient::new(server_url)
|
||||
}
|
||||
Reference in New Issue
Block a user