//! Tauri command layer: thin adapters between the CH-J API (`src/api`) and the Rust Core. //! Every command first resolves the calling window (security layer) and only then touches //! a service. Core commands accept only the main window; module commands only registered //! module windows with the required manifest permission; license commands only the //! license window. pub mod app; pub mod availability; pub mod diagnostics; pub mod license; pub mod module_api; pub mod modules; pub mod profiles; #[cfg(feature = "smoke")] pub mod smoke; pub mod ssh; pub mod updates; pub mod vault; use crate::core::app::{Core, CoreSlot}; use crate::core::error::{CoreError, CoreResult}; use crate::security::caller::{require_main, require_module, ModuleContext}; use serde_json::{json, Value}; use std::sync::Arc; use tauri::Webview; pub type CommandResult = Result; pub fn payload(value: Option) -> Value { match value { Some(Value::Object(map)) => Value::Object(map), _ => json!({}), } } pub fn text<'a>(payload: &'a Value, key: &str) -> &'a str { payload.get(key).and_then(Value::as_str).unwrap_or("") } /// Core command guard (Electron `assertTrustedSender`). pub fn main_core(webview: &Webview, slot: &CoreSlot) -> CoreResult> { require_main(webview.label(), webview.url().ok())?; slot.get() } /// Wraps a core command result: failures are logged like `registerCoreIpc.handle`. pub fn logged(core: &Core, channel: &str, result: CoreResult) -> CommandResult { if let Err(error) = &result { core.log_ipc_failure(channel, error); } result } /// `{ok, value}` / `{ok:false, error}` envelope (SSH, biometrics, diagnostics). pub fn envelope(result: CoreResult, default_code: &str, limit: Option) -> Value { match result { Ok(value) => json!({ "ok": true, "value": value }), Err(error) => { let mut body = serde_json::to_value(&error).unwrap_or_else(|_| json!({})); if body["code"].as_str().map(str::is_empty).unwrap_or(true) { body["code"] = json!(default_code); } if let Some(limit) = limit { body = json!({ "code": body["code"], "message": error.message.chars().take(limit).collect::() }); } json!({ "ok": false, "error": body }) } } } /// Module command guard: registered module window + manifest permission. pub fn module_call(webview: &Webview, slot: &CoreSlot, permission: Option<&str>) -> CoreResult<(Arc, Arc)> { let core = slot.get()?; let context = require_module(&core.windows, webview.label(), webview.url().ok())?; if let Some(permission) = permission { context.require(permission)?; } Ok((core, context)) } /// The 0.1.0 plugin runtime exposed error codes inside the message for these channels, /// because Electron dropped custom error properties. pub fn module_error(channel: &str, error: CoreError) -> CoreError { let valid_code = error.code.len() >= 3 && error.code.len() <= 64 && error.code.chars().next().map(|c| c.is_ascii_uppercase()).unwrap_or(false) && error.code.chars().all(|c| c.is_ascii_uppercase() || c.is_ascii_digit() || c == '_'); let expose = error.code.starts_with("HASH_") || (["plugin:users:", "plugin:system:", "plugin:files:", "plugin:disks:"].iter().any(|p| channel.starts_with(p)) && valid_code); if expose { let message = error.prefixed_message(); CoreError { message, ..error } } else { error } } pub fn module_result(channel: &str, result: CoreResult) -> CommandResult { result.map_err(|e| module_error(channel, e)) }