use tauri::{AppHandle, WebviewWindow, command, Runtime, Manager, Listener};
use std::sync::{Arc, Mutex};
use serde_json::Value as JsonValue;
use crate::models::{ExecuteRequest, MockConfig};
use crate::Result;
use crate::mock_store::MockStore;
#[command]
pub(crate) async fn execute<R: Runtime>(
window: WebviewWindow<R>,
request: ExecuteRequest,
) -> Result<JsonValue> {
log::info!("[WDIO Plugin] Execute request - script: {}", request.script);
log::info!("[WDIO Plugin] Execute request - args: {:?}", request.args);
let script = if !request.args.is_empty() {
let args_json = serde_json::to_string(&request.args)
.map_err(|e| crate::Error::SerializationError(format!("Failed to serialize args: {}", e)))?;
format!("(function() {{ const __wdio_args = {}; return ({}); }})()", args_json, request.script)
} else {
request.script
};
log::info!("[WDIO Plugin] Prepared script: {}", script);
use std::sync::mpsc;
use std::time::Duration;
let (tx, rx) = mpsc::channel();
use std::time::{SystemTime, UNIX_EPOCH};
let timestamp = SystemTime::now()
.duration_since(UNIX_EPOCH)
.unwrap()
.as_nanos();
let event_id = format!("wdio:execute:{}", timestamp);
let app_handle = window.app_handle().clone();
let result_tx = tx.clone();
let error_tx = tx;
let listener_id = app_handle.listen(&event_id, move |event| {
log::info!("[WDIO Plugin] Received event payload: {}", event.payload());
if let Ok(payload) = serde_json::from_str::<serde_json::Value>(event.payload()) {
log::info!("[WDIO Plugin] Parsed payload: {:?}", payload);
if let Some(result) = payload.get("result") {
log::info!("[WDIO Plugin] Got result field: {:?}", result);
if let Some(json_str) = result.as_str() {
log::info!("[WDIO Plugin] Result is string: {}", json_str);
match serde_json::from_str::<serde_json::Value>(json_str) {
Ok(parsed) => {
log::info!("[WDIO Plugin] Successfully parsed result: {:?}", parsed);
let _ = result_tx.send(Ok(parsed));
}
Err(e) => {
log::error!("[WDIO Plugin] Failed to parse result JSON: {}", e);
let _ = error_tx.send(Err(crate::Error::ExecuteError(
format!("Failed to parse result JSON: {}", e)
)));
}
}
} else {
log::info!("[WDIO Plugin] Result is not a string, using as-is");
let _ = result_tx.send(Ok(result.clone()));
}
} else if let Some(error) = payload.get("error") {
log::error!("[WDIO Plugin] Got error field: {:?}", error);
let _ = error_tx.send(Err(crate::Error::ExecuteError(
error.as_str().unwrap_or("Unknown error").to_string()
)));
} else {
log::warn!("[WDIO Plugin] Payload has neither result nor error field!");
}
} else {
log::error!("[WDIO Plugin] Failed to parse event payload as JSON");
}
});
let script_with_return = format!(
r#"
(async () => {{
try {{
const result = await ({});
const jsonResult = JSON.stringify(result);
// Use Tauri event API to send result back to Rust
if (window.__TAURI__?.event?.emit) {{
window.__TAURI__.event.emit('{}', {{ result: jsonResult }});
}} else {{
// Fallback: try importing from @tauri-apps/api/event
const {{ emit }} = await import('@tauri-apps/api/event');
emit('{}', {{ result: jsonResult }});
}}
}} catch (error) {{
const errorMsg = error.message || String(error);
if (window.__TAURI__?.event?.emit) {{
window.__TAURI__.event.emit('{}', {{ error: errorMsg }});
}} else {{
const {{ emit }} = await import('@tauri-apps/api/event');
emit('{}', {{ error: errorMsg }});
}}
}}
}})()
"#,
script, event_id, event_id, event_id, event_id
);
window
.eval(&script_with_return)
.map_err(|e| crate::Error::ExecuteError(e.to_string()))?;
match rx.recv_timeout(Duration::from_secs(30)) {
Ok(Ok(result)) => {
app_handle.unlisten(listener_id);
Ok(result)
}
Ok(Err(e)) => {
app_handle.unlisten(listener_id);
Err(e)
}
Err(_) => {
app_handle.unlisten(listener_id);
Err(crate::Error::ExecuteError("Timeout waiting for execute result".to_string()))
}
}
}
#[command]
pub(crate) async fn set_mock<R: Runtime>(
app: AppHandle<R>,
command: String,
config: MockConfig,
) -> Result<()> {
let mock_store = app
.try_state::<Arc<Mutex<MockStore>>>()
.ok_or_else(|| crate::Error::MockError("Mock store not found".to_string()))?;
let mut store = mock_store
.lock()
.map_err(|e| crate::Error::MockError(format!("Failed to lock mock store: {}", e)))?;
store.set_mock(command, config);
Ok(())
}
#[command]
pub(crate) async fn get_mock<R: Runtime>(
app: AppHandle<R>,
command: String,
) -> Result<Option<MockConfig>> {
let mock_store = app
.try_state::<Arc<Mutex<MockStore>>>()
.ok_or_else(|| crate::Error::MockError("Mock store not found".to_string()))?;
let store = mock_store
.lock()
.map_err(|e| crate::Error::MockError(format!("Failed to lock mock store: {}", e)))?;
Ok(store.get_mock(&command).cloned())
}
#[command]
pub(crate) async fn clear_mocks<R: Runtime>(app: AppHandle<R>) -> Result<()> {
let mock_store = app
.try_state::<Arc<Mutex<MockStore>>>()
.ok_or_else(|| crate::Error::MockError("Mock store not found".to_string()))?;
let mut store = mock_store
.lock()
.map_err(|e| crate::Error::MockError(format!("Failed to lock mock store: {}", e)))?;
store.clear_mocks();
Ok(())
}
#[command]
pub(crate) async fn reset_mocks<R: Runtime>(app: AppHandle<R>) -> Result<()> {
let mock_store = app
.try_state::<Arc<Mutex<MockStore>>>()
.ok_or_else(|| crate::Error::MockError("Mock store not found".to_string()))?;
let mut store = mock_store
.lock()
.map_err(|e| crate::Error::MockError(format!("Failed to lock mock store: {}", e)))?;
store.reset_mocks();
Ok(())
}
#[command]
pub(crate) async fn restore_mocks<R: Runtime>(app: AppHandle<R>) -> Result<()> {
let mock_store = app
.try_state::<Arc<Mutex<MockStore>>>()
.ok_or_else(|| crate::Error::MockError("Mock store not found".to_string()))?;
let mut store = mock_store
.lock()
.map_err(|e| crate::Error::MockError(format!("Failed to lock mock store: {}", e)))?;
store.reset_mocks();
Ok(())
}