use super::bridge::{BRIDGE_CANCELED, Bridge, RpcError};
use crate::error::LxAppError;
use crate::page::Page;
use serde::Serialize;
use serde_json::Value;
use std::collections::{HashMap, HashSet};
use std::sync::atomic::{AtomicU64, Ordering};
use std::sync::{Mutex, OnceLock};
use tokio::sync::oneshot;
static REGISTRY: OnceLock<ViewCallRegistry> = OnceLock::new();
struct ViewCallRegistry {
pending: Mutex<HashMap<String, PendingViewCallEntry>>,
counter: AtomicU64,
}
struct PendingViewCallEntry {
page_path: String,
tx: oneshot::Sender<Result<Value, RpcError>>,
}
pub(crate) struct PendingViewCall {
pub id: String,
pub rx: oneshot::Receiver<Result<Value, RpcError>>,
}
impl ViewCallRegistry {
fn new() -> Self {
Self {
pending: Mutex::new(HashMap::new()),
counter: AtomicU64::new(1),
}
}
}
fn registry() -> &'static ViewCallRegistry {
REGISTRY.get_or_init(ViewCallRegistry::new)
}
#[derive(Serialize)]
struct ViewReq {
v: u8,
kind: &'static str,
id: String,
method: String,
#[serde(skip_serializing_if = "Option::is_none")]
params: Option<Value>,
cap: String,
}
pub(crate) fn call_view(
page: &Page,
method: &str,
params: Option<Value>,
) -> Result<PendingViewCall, LxAppError> {
let reg = registry();
let seq = reg.counter.fetch_add(1, Ordering::Relaxed);
let id = format!("lv_{}", seq);
let page_path = page.path();
let cap = Bridge::required_cap_for_method(method);
let msg = ViewReq {
v: 2,
kind: "req",
id: id.clone(),
method: method.to_string(),
params,
cap,
};
let json = serde_json::to_string(&msg)?;
let controller = page
.webview_controller()
.ok_or_else(|| LxAppError::WebView("WebView not ready".to_string()))?;
let (tx, rx) = oneshot::channel();
reg.pending
.lock()
.unwrap()
.insert(id.clone(), PendingViewCallEntry { page_path, tx });
if let Err(e) = controller.post_message(&json) {
reg.pending.lock().unwrap().remove(&id);
return Err(LxAppError::WebView(e.to_string()));
}
Ok(PendingViewCall { id, rx })
}
pub(crate) fn resolve_view_call(
id: &str,
source_page_path: Option<&str>,
result: Result<Value, RpcError>,
) -> bool {
let reg = registry();
let entry = {
let mut pending = reg.pending.lock().unwrap();
if let Some(path) = source_page_path
&& let Some(existing) = pending.get(id)
&& existing.page_path != path
{
return false;
}
pending.remove(id)
};
if let Some(entry) = entry {
let _ = entry.tx.send(result);
return true;
}
false
}
pub(crate) fn cancel_view_call(id: &str, message: Option<String>) {
let reg = registry();
let entry = reg.pending.lock().unwrap().remove(id);
if let Some(entry) = entry {
let _ = entry.tx.send(Err(RpcError::new(BRIDGE_CANCELED, message)));
}
}
pub(crate) fn cancel_view_calls_for_pages(paths: &[String], reason: &str) {
if paths.is_empty() {
return;
}
let reg = registry();
let path_set: HashSet<&str> = paths.iter().map(String::as_str).collect();
let entries = {
let mut pending = reg.pending.lock().unwrap();
let ids: Vec<String> = pending
.iter()
.filter(|(_, entry)| path_set.contains(entry.page_path.as_str()))
.map(|(id, _)| id.clone())
.collect();
ids.into_iter()
.filter_map(|id| pending.remove(&id))
.collect::<Vec<_>>()
};
for entry in entries {
let _ = entry.tx.send(Err(RpcError::new(
BRIDGE_CANCELED,
Some(reason.to_string()),
)));
}
}