//! License window commands (port of `legal/showLicenseWindow.js`). The flow is enforced in //! the Core: guidance must be read before `license_show`, `license_show` before accept. use super::{payload, CommandResult}; use crate::core::error::CoreError; use crate::core::legal::LicenseAcceptance; use crate::security::caller::require_license; use parking_lot::Mutex; use serde_json::{json, Value}; use std::sync::Arc; use tauri::{Manager, State, Webview}; pub struct LicenseFlow { pub acceptance: Arc, pub language: String, pub presented: bool, pub shown: bool, pub accepted: bool, pub done: Option>, } #[derive(Default)] pub struct LicenseSlot(pub Mutex>); fn guard(webview: &Webview) -> Result<(), CoreError> { require_license(webview.label(), webview.url().ok()) } #[tauri::command] pub async fn license_read(webview: Webview, slot: State<'_, LicenseSlot>) -> CommandResult { guard(&webview)?; let mut flow = slot.0.lock(); let flow = flow.as_mut().ok_or_else(|| CoreError::plain("License dialog is not active."))?; flow.presented = true; let mut value = json!({ "text": flow.acceptance.text, "guidance": flow.acceptance.guidance }); for (k, v) in &flow.acceptance.identity { value[k] = v.clone(); } value["language"] = json!(flow.language); Ok(value) } #[tauri::command] pub async fn license_show(webview: Webview, slot: State<'_, LicenseSlot>, payload: Option) -> CommandResult { guard(&webview)?; let payload = super::payload(payload); let mut flow = slot.0.lock(); let flow = flow.as_mut().ok_or_else(|| CoreError::plain("License dialog is not active."))?; if !flow.presented || payload.get("guidanceSha256").and_then(Value::as_str) != Some(flow.acceptance.identity_value("guidanceSha256")) { return Err(CoreError::plain("Display the current operational guidance before continuing.")); } flow.shown = true; Ok(json!({ "ok": true })) } #[tauri::command] pub async fn license_accept(webview: Webview, slot: State<'_, LicenseSlot>, payload: Option) -> CommandResult { guard(&webview)?; let payload = super::payload(payload); { let mut flow = slot.0.lock(); let flow = flow.as_mut().ok_or_else(|| CoreError::plain("License dialog is not active."))?; if !flow.shown { return Err(CoreError::plain("Display operational guidance before accepting the license.")); } flow.acceptance.accept(&payload, flow.shown)?; flow.accepted = true; } close_later(&webview); Ok(json!({ "ok": true })) } #[tauri::command] pub async fn license_decline(webview: Webview) -> CommandResult { guard(&webview)?; close_later(&webview); Ok(json!({ "ok": true })) } /// Lets the IPC reply reach the renderer before the window closes. fn close_later(webview: &Webview) { let window = webview.window(); let _ = window.app_handle(); tauri::async_runtime::spawn(async move { tokio::time::sleep(std::time::Duration::from_millis(50)).await; let _ = window.close(); }); } pub fn _unused(value: Option) -> Value { payload(value) }