use std::sync::Arc;
use tauri::{Manager, Runtime, State};
use victauri_core::{IpcCall, WindowState};
use crate::VictauriState;
pub const PAGE_EVAL_PREFIX: &str = "page:";
pub const MAX_PAGE_PENDING_EVALS: usize = 25;
pub const MAX_PAGE_PENDING_EVALS_PER_WINDOW: usize = 10;
pub const MAX_PAGE_WINDOW_QUERIES: usize = 4;
pub static PAGE_WINDOW_QUERY_SLOTS: tokio::sync::Semaphore =
tokio::sync::Semaphore::const_new(MAX_PAGE_WINDOW_QUERIES);
pub fn page_window_query<T>(f: impl FnOnce() -> T) -> Result<T, String> {
let _slot = PAGE_WINDOW_QUERY_SLOTS.try_acquire().map_err(|_| {
format!(
"too many concurrent page-originated window queries (limit \
{MAX_PAGE_WINDOW_QUERIES}); retry when one has finished"
)
})?;
Ok(f())
}
pub const PAGE_IPC_LOG_DEFAULT: usize = 100;
pub const PAGE_IPC_LOG_MAX: usize = 1_000;
pub const PAGE_INTEGRITY_MAX_ENTRIES: usize = 100;
fn page_eval_window(id: &str) -> Option<&str> {
let rest = id.strip_prefix(PAGE_EVAL_PREFIX)?;
rest.get(36..)?.strip_prefix(':')
}
pub async fn reserve_page_eval(
state: &VictauriState,
label: &str,
tx: tokio::sync::oneshot::Sender<String>,
) -> Result<crate::PendingSlot, String> {
let id = format!("{PAGE_EVAL_PREFIX}{}:{label}", uuid::Uuid::new_v4());
let mut pending = state.pending_evals.lock().await;
let mut page_pending = 0;
let mut window_pending = 0;
for key in pending.keys().filter(|k| k.starts_with(PAGE_EVAL_PREFIX)) {
page_pending += 1;
if page_eval_window(key) == Some(label) {
window_pending += 1;
}
}
if window_pending >= MAX_PAGE_PENDING_EVALS_PER_WINDOW {
return Err(format!(
"too many concurrent page-originated evals from window '{label}' \
(limit {MAX_PAGE_PENDING_EVALS_PER_WINDOW})"
));
}
if page_pending >= MAX_PAGE_PENDING_EVALS {
return Err(format!(
"too many concurrent page-originated evals (limit {MAX_PAGE_PENDING_EVALS})"
));
}
if pending.len() >= crate::mcp::MAX_PENDING_EVALS {
return Err(format!(
"too many concurrent evals (limit {})",
crate::mcp::MAX_PENDING_EVALS
));
}
let slot = crate::PendingSlot::insert(&state.pending_evals, &mut pending, id, tx);
slot.bind_window(Some(label));
Ok(slot)
}
#[tauri::command]
pub async fn victauri_eval_js<R: Runtime>(
webview: tauri::WebviewWindow<R>,
state: State<'_, Arc<VictauriState>>,
code: String,
) -> Result<String, String> {
let (tx, rx) = tokio::sync::oneshot::channel();
let slot = reserve_page_eval(&state, webview.label(), tx).await?;
let id = serde_json::to_string(slot.id()).map_err(|e| e.to_string())?;
let inject = crate::js_bridge::page_eval_script(&id, &code);
if let Err(e) = crate::bridge::WebviewBridge::eval_webview(
webview.app_handle(),
Some(webview.label()),
&inject,
) {
return Err(format!("eval failed: {e}"));
}
match tokio::time::timeout(state.eval_timeout, rx).await {
Ok(Ok(result)) => Ok(result),
Ok(Err(_)) => Err("eval callback channel closed".to_string()),
Err(_) => Err(format!(
"eval timed out after {}s",
state.eval_timeout.as_secs()
)),
}
}
#[tauri::command]
pub fn victauri_eval_callback<R: Runtime>(
webview: tauri::Webview<R>,
state: State<'_, Arc<VictauriState>>,
id: String,
result: String,
) -> Result<(), String> {
if id == "__victauri_bridge_ready__" {
state
.bridge_ready
.store(true, std::sync::atomic::Ordering::Release);
state.bridge_notify.notify_waiters();
let nonce = (!result.is_empty()).then_some(result.as_str());
state.page_loads.record_load(webview.label(), nonce);
return Ok(());
}
accept_eval_result(
Arc::clone(state.inner()),
webview.label(),
|| crate::bridge::default_window_label(webview.app_handle()),
id,
result,
);
Ok(())
}
fn accept_eval_result(
state: Arc<VictauriState>,
caller: &str,
default_label: impl FnOnce() -> Option<String>,
id: String,
result: String,
) -> bool {
if !crate::eval_result_accepted(&id, caller, default_label) {
tracing::debug!(
window = caller,
"ignored an eval result from a window the eval was not sent to (or for no pending eval)"
);
return false;
}
deliver_eval_result(state, id, result);
true
}
fn deliver_eval_result(state: Arc<VictauriState>, id: String, result: String) {
if let Ok(mut pending) = state.pending_evals.try_lock() {
if let Some(tx) = pending.remove(&id) {
let _ = tx.send(result);
}
return;
}
tauri::async_runtime::spawn(async move {
if let Some(tx) = state.pending_evals.lock().await.remove(&id) {
let _ = tx.send(result);
}
});
}
#[tauri::command]
pub async fn victauri_dom_snapshot<R: Runtime>(
webview: tauri::WebviewWindow<R>,
state: State<'_, Arc<VictauriState>>,
) -> Result<String, String> {
let (tx, rx) = tokio::sync::oneshot::channel();
let slot = reserve_page_eval(&state, webview.label(), tx).await?;
let id = serde_json::to_string(slot.id()).map_err(|e| e.to_string())?;
let inject = crate::js_bridge::page_eval_script(&id, "return window.__VICTAURI__?.snapshot()");
if let Err(e) = crate::bridge::WebviewBridge::eval_webview(
webview.app_handle(),
Some(webview.label()),
&inject,
) {
return Err(format!("snapshot eval failed: {e}"));
}
match tokio::time::timeout(state.eval_timeout, rx).await {
Ok(Ok(result)) => Ok(result),
Ok(Err(_)) => Err("snapshot callback channel closed".to_string()),
Err(_) => Err(format!(
"snapshot timed out after {}s",
state.eval_timeout.as_secs()
)),
}
}
#[tauri::command]
pub fn victauri_get_window_state<R: Runtime>(
app: tauri::AppHandle<R>,
label: Option<String>,
) -> Result<Vec<WindowState>, String> {
page_window_query(|| crate::bridge::WebviewBridge::get_window_states(&app, label.as_deref()))
}
#[tauri::command]
pub fn victauri_list_windows<R: Runtime>(app: tauri::AppHandle<R>) -> Result<Vec<String>, String> {
page_window_query(|| crate::bridge::WebviewBridge::list_window_labels(&app))
}
#[tauri::command]
pub fn victauri_get_ipc_log(
state: State<'_, Arc<VictauriState>>,
limit: Option<usize>,
) -> Result<Vec<IpcCall>, String> {
Ok(page_ipc_log(&state, limit))
}
fn page_ipc_log(state: &VictauriState, limit: Option<usize>) -> Vec<IpcCall> {
let limit = limit.unwrap_or(PAGE_IPC_LOG_DEFAULT).min(PAGE_IPC_LOG_MAX);
state.event_log.recent_ipc_calls(limit)
}
#[tauri::command]
pub fn victauri_get_registry(
state: State<'_, Arc<VictauriState>>,
query: Option<String>,
) -> Result<serde_json::Value, String> {
let commands = match query {
Some(q) => state.registry.search(&q),
None => state.registry.list(),
};
serde_json::to_value(commands).map_err(|e| e.to_string())
}
#[tauri::command]
pub fn victauri_get_memory_stats() -> Result<serde_json::Value, String> {
Ok(crate::memory::current_stats())
}
#[tauri::command]
pub fn victauri_verify_state(
_state: State<'_, Arc<VictauriState>>,
frontend_state: serde_json::Value,
backend_state: serde_json::Value,
) -> Result<serde_json::Value, String> {
let result = victauri_core::verify_state(frontend_state, backend_state);
serde_json::to_value(result).map_err(|e| e.to_string())
}
#[tauri::command]
pub fn victauri_detect_ghost_commands(
state: State<'_, Arc<VictauriState>>,
) -> Result<serde_json::Value, String> {
serde_json::to_value(page_ghost_report(&state)).map_err(|e| e.to_string())
}
fn page_ghost_report(state: &VictauriState) -> victauri_core::GhostCommandReport {
let frontend_commands: Vec<String> = state.event_log.ipc_command_names().into_iter().collect();
victauri_core::detect_ghost_commands(&frontend_commands, &state.registry)
}
#[tauri::command]
pub fn victauri_check_ipc_integrity(
state: State<'_, Arc<VictauriState>>,
stale_threshold_ms: Option<i64>,
) -> Result<serde_json::Value, String> {
serde_json::to_value(page_integrity_report(&state, stale_threshold_ms))
.map_err(|e| e.to_string())
}
fn page_integrity_report(
state: &VictauriState,
stale_threshold_ms: Option<i64>,
) -> victauri_core::IpcIntegrityReport {
let mut report =
victauri_core::check_ipc_integrity(&state.event_log, stale_threshold_ms.unwrap_or(5000));
keep_newest(&mut report.stale_calls, PAGE_INTEGRITY_MAX_ENTRIES);
keep_newest(&mut report.error_calls, PAGE_INTEGRITY_MAX_ENTRIES);
report
}
fn keep_newest<T>(v: &mut Vec<T>, max: usize) {
let excess = v.len().saturating_sub(max);
v.drain(..excess);
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn page_window_queries_are_budgeted() {
let held: Vec<_> = (0..MAX_PAGE_WINDOW_QUERIES)
.map(|_| PAGE_WINDOW_QUERY_SLOTS.try_acquire().unwrap())
.collect();
let refused = page_window_query(|| 1);
assert!(
refused
.as_ref()
.is_err_and(|e| e.contains("page-originated window queries")),
"{refused:?}"
);
drop(held);
assert_eq!(page_window_query(|| 7), Ok(7));
assert_eq!(
PAGE_WINDOW_QUERY_SLOTS.available_permits(),
MAX_PAGE_WINDOW_QUERIES
);
}
fn busy_state(calls: usize) -> VictauriState {
let mut state = VictauriState::for_tests();
state.event_log = victauri_core::EventLog::new(calls);
let body = serde_json::json!({ "payload": "x".repeat(1024) });
for i in 0..calls {
let result = if i % 2 == 0 {
victauri_core::IpcResult::Ok(body.clone())
} else {
victauri_core::IpcResult::Err(format!("failed: {}", "e".repeat(256)))
};
state
.event_log
.push(victauri_core::AppEvent::Ipc(IpcCall::new(
format!("c{i}"),
format!("cmd{}", i % 20),
chrono::Utc::now(),
result,
Some(1),
1024,
"main",
)));
}
state
}
#[test]
fn page_ipc_queries_do_bounded_work_on_a_full_log() {
let state = busy_state(10_000);
let time = |f: &dyn Fn()| {
let started = std::time::Instant::now();
for _ in 0..5 {
f();
}
started.elapsed() / 5
};
let full_clone = time(&|| drop(state.event_log.ipc_calls()));
let log = time(&|| drop(page_ipc_log(&state, None)));
let ghosts = time(&|| drop(page_ghost_report(&state)));
let integrity = time(&|| drop(page_integrity_report(&state, None)));
eprintln!(
"10,000-call log: full clone {full_clone:?}; get_ipc_log {log:?}; detect_ghost_commands {ghosts:?}; check_ipc_integrity {integrity:?}"
);
assert_eq!(page_ipc_log(&state, None).len(), PAGE_IPC_LOG_DEFAULT);
assert_eq!(
page_ipc_log(&state, Some(usize::MAX)).len(),
PAGE_IPC_LOG_MAX
);
let newest = page_ipc_log(&state, Some(3));
let ids: Vec<&str> = newest.iter().map(|c| c.id.as_str()).collect();
assert_eq!(ids, ["c9997", "c9998", "c9999"]);
let integrity = page_integrity_report(&state, None);
assert_eq!(integrity.total_calls, 10_000);
assert_eq!(integrity.errored, 5_000);
assert_eq!(integrity.error_calls.len(), PAGE_INTEGRITY_MAX_ENTRIES);
assert_eq!(
integrity.error_calls.last().map(|c| c.id.as_str()),
Some("c9999"),
"the newest entries are kept"
);
let ghosts = page_ghost_report(&state);
assert_eq!(ghosts.frontend_only.len(), 20);
}
#[tokio::test]
async fn eval_results_are_accepted_only_from_the_target_window() {
let state = Arc::new(VictauriState::for_tests());
let no_default = || None;
let (tx, mut rx) = tokio::sync::oneshot::channel();
let slot = reserve_page_eval(&state, "main", tx).await.unwrap();
let id = slot.id().to_string();
assert!(!accept_eval_result(
Arc::clone(&state),
"other",
no_default,
id.clone(),
"forged".into()
));
assert!(
rx.try_recv().is_err(),
"another window's result was delivered"
);
assert!(accept_eval_result(
Arc::clone(&state),
"main",
no_default,
id,
"real".into()
));
assert_eq!(rx.await.unwrap(), "real");
drop(slot);
let (tx, mut rx) = tokio::sync::oneshot::channel();
let slot = {
let mut pending = state.pending_evals.lock().await;
crate::PendingSlot::insert(&state.pending_evals, &mut pending, "agent-1".into(), tx)
};
slot.bind_window(None);
let main_is_default = || Some("main".to_string());
assert!(!accept_eval_result(
Arc::clone(&state),
"other",
main_is_default,
"agent-1".into(),
"x".into()
));
assert!(rx.try_recv().is_err());
slot.bind_window(Some("side"));
assert!(!accept_eval_result(
Arc::clone(&state),
"main",
main_is_default,
"agent-1".into(),
"x".into()
));
assert!(accept_eval_result(
Arc::clone(&state),
"side",
main_is_default,
"agent-1".into(),
"ok".into()
));
assert_eq!(rx.await.unwrap(), "ok");
let (tx, _rx) = tokio::sync::oneshot::channel();
let unbound = {
let mut pending = state.pending_evals.lock().await;
crate::PendingSlot::insert(&state.pending_evals, &mut pending, "agent-2".into(), tx)
};
assert!(!accept_eval_result(
Arc::clone(&state),
"main",
main_is_default,
"agent-2".into(),
"x".into()
));
assert!(!accept_eval_result(
Arc::clone(&state),
"main",
main_is_default,
"nope".into(),
"x".into()
));
drop(unbound);
drop(slot);
assert!(!crate::eval_result_accepted("agent-1", "side", || None));
}
#[test]
fn only_the_commands_that_wait_for_the_page_are_async() {
let source = include_str!("tools.rs");
let async_commands: Vec<&str> = source
.lines()
.filter_map(|l| l.trim_start().strip_prefix("pub async fn victauri_"))
.map(|rest| rest.split(['<', '(']).next().unwrap_or(rest))
.collect();
assert_eq!(
async_commands,
vec!["eval_js", "dom_snapshot"],
"only victauri_eval_js / victauri_dom_snapshot may be async commands"
);
assert!(
source.contains("pub fn victauri_eval_callback<R: Runtime>("),
"victauri_eval_callback must stay a synchronous command"
);
}
fn crate_sources() -> Vec<(std::path::PathBuf, String)> {
fn walk(dir: &std::path::Path, out: &mut Vec<(std::path::PathBuf, String)>) {
for entry in std::fs::read_dir(dir).unwrap() {
let path = entry.unwrap().path();
if path.is_dir() {
walk(&path, out);
} else if path.extension().is_some_and(|e| e == "rs") {
let source = std::fs::read_to_string(&path).unwrap();
out.push((path, source));
}
}
}
let mut out = Vec::new();
walk(
&std::path::Path::new(env!("CARGO_MANIFEST_DIR")).join("src"),
&mut out,
);
out
}
#[test]
fn every_registered_command_is_sync_except_the_page_waiters() {
let sources = crate_sources();
for (path, source) in &sources {
for line in source.lines() {
let attr: String = line.chars().filter(|c| !c.is_whitespace()).collect();
if attr.starts_with("#[tauri::command(") {
assert!(
!attr.contains("async"),
"{}: async command attribute `{}` — see \
only_the_commands_that_wait_for_the_page_are_async",
path.display(),
line.trim()
);
}
}
}
let lib = include_str!("lib.rs");
let start = lib
.find("generate_handler![")
.expect("lib.rs registers its commands with generate_handler!");
let list = &lib[start + "generate_handler![".len()..];
let list = &list[..list.find(']').expect("generate_handler! list is closed")];
let registered: Vec<&str> = list
.split(',')
.map(str::trim)
.filter(|s| !s.is_empty())
.collect();
assert!(registered.len() >= 10, "parsed {registered:?}");
for path in ®istered {
let name = path.rsplit("::").next().unwrap_or(path);
let sync_def = format!("pub fn {name}");
let async_def = format!("pub async fn {name}");
let defs: Vec<bool> = sources
.iter()
.flat_map(|(_, s)| s.lines())
.map(str::trim_start)
.filter_map(|l| {
let is_def = |d: &str| {
l.strip_prefix(d)
.is_some_and(|rest| rest.starts_with(['<', '(']))
};
if is_def(&async_def) {
Some(true)
} else if is_def(&sync_def) {
Some(false)
} else {
None
}
})
.collect();
assert_eq!(defs.len(), 1, "command {path}: expected one definition");
let may_be_async = matches!(name, "victauri_eval_js" | "victauri_dom_snapshot");
assert!(
!defs[0] || may_be_async,
"registered command {path} is async — only the page waiters may be"
);
}
}
}