host: keyword alert settings are the host's (AppConfig::with_alerts, commands::ALERTS)
alerts_commands keeps the owner's AlertSettings (saved in app data, validated by alerts), serves the shell-only alerts_get / alerts_set (testkit::assert_alerts_guards) and builds the script that hands the page runtime the keywords to watch. The app names the file and the runtime member (AlertsConfig); store()/script() are the app's own writers (backup import).
This commit is contained in:
parent
69b67dbece
commit
b3feeb8991
5 changed files with 128 additions and 22 deletions
85
crates/waybetter-host/src/alerts_commands.rs
Normal file
85
crates/waybetter-host/src/alerts_commands.rs
Normal file
|
|
@ -0,0 +1,85 @@
|
|||
//! Keyword alert settings, native side, for every app that sets [`crate::app::AppConfig::alerts`]:
|
||||
//! the shell-only `alerts_get` / `alerts_set` ([`crate::commands::ALERTS`]) over the owner's
|
||||
//! [`AlertSettings`] (saved in app data, validated by [`crate::alerts`]). Site tabs gain no command
|
||||
//! here: native code hands the page runtime the keywords to watch ([`script`]) at every page load
|
||||
//! and whenever they change. What a match looks like and the notification are the app's.
|
||||
|
||||
use crate::alerts::AlertSettings;
|
||||
use crate::app::config;
|
||||
use crate::host::{eval_in_site_tabs, read_json, shell_only, write_json};
|
||||
use crate::tabs::deliver_script;
|
||||
use std::sync::{Mutex, MutexGuard};
|
||||
use tauri::{AppHandle, Manager, Webview};
|
||||
|
||||
/// What an app declares about its alerts: where they are saved and how the page runtime takes the
|
||||
/// keywords (`window.<global>.<member>.set(keywords)`, else parked in `window.<queue>`).
|
||||
#[derive(Clone, Copy, Debug)]
|
||||
pub struct AlertsConfig {
|
||||
pub file: &'static str,
|
||||
pub member: &'static str,
|
||||
pub queue: &'static str,
|
||||
}
|
||||
|
||||
struct AlertState(Mutex<AlertSettings>);
|
||||
|
||||
/// Loads the saved settings. Does nothing unless the app set [`crate::app::AppConfig::alerts`].
|
||||
pub(crate) fn init(app: &AppHandle) {
|
||||
let Some(alerts) = config(app).alerts else { return };
|
||||
let settings = read_json::<AlertSettings>(app, alerts.file).and_then(|settings| settings.validated().ok()).unwrap_or_default();
|
||||
app.manage(AlertState(Mutex::new(settings)));
|
||||
}
|
||||
|
||||
fn state(app: &AppHandle) -> Result<MutexGuard<'_, AlertSettings>, String> {
|
||||
app.try_state::<AlertState>()
|
||||
.ok_or("DATABASE: alert state unavailable; restart safely")?
|
||||
.inner()
|
||||
.0
|
||||
.lock()
|
||||
.map_err(|_| "DATABASE: alert state unavailable; restart safely".into())
|
||||
}
|
||||
|
||||
/// The owner's alert settings.
|
||||
pub fn settings(app: &AppHandle) -> Result<AlertSettings, String> {
|
||||
Ok(state(app)?.clone())
|
||||
}
|
||||
|
||||
/// The script that hands the page runtime the keywords to watch (empty = alerts off).
|
||||
pub fn script(app: &AppHandle) -> String {
|
||||
let (Some(alerts), Ok(settings)) = (config(app).alerts, settings(app)) else { return String::new() };
|
||||
script_for(app, alerts, &settings)
|
||||
}
|
||||
|
||||
fn script_for(app: &AppHandle, alerts: &AlertsConfig, settings: &AlertSettings) -> String {
|
||||
let keywords = serde_json::to_string(settings.active_keywords()).unwrap_or_else(|_| "[]".into());
|
||||
deliver_script(config(app).site.runtime_global, alerts.member, "set", alerts.queue, &keywords)
|
||||
}
|
||||
|
||||
/// Validates, saves and keeps the settings, and does not touch the tabs: for the app's own
|
||||
/// writers (a backup import), which then evaluate [`script`] themselves.
|
||||
pub fn store(app: &AppHandle, settings: AlertSettings) -> Result<AlertSettings, String> {
|
||||
let alerts = config(app).alerts.ok_or_else(|| "UNAVAILABLE: this app has no alerts".to_owned())?;
|
||||
let settings = settings.validated()?;
|
||||
// The lock is held across the write so concurrent saves cannot leave disk and memory disagreeing.
|
||||
let mut current = state(app)?;
|
||||
write_json(app, alerts.file, &settings)?;
|
||||
*current = settings.clone();
|
||||
Ok(settings)
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
pub(crate) async fn alerts_get(webview: Webview, app: AppHandle) -> Result<AlertSettings, String> {
|
||||
shell_only(&webview)?;
|
||||
settings(&app)
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
pub(crate) async fn alerts_set(webview: Webview, app: AppHandle, settings: AlertSettings) -> Result<AlertSettings, String> {
|
||||
shell_only(&webview)?;
|
||||
let settings = store(&app, settings)?;
|
||||
eval_in_site_tabs(&app, config(&app).site, &script(&app));
|
||||
Ok(settings)
|
||||
}
|
||||
|
||||
pub(crate) fn handler() -> impl Fn(tauri::ipc::Invoke<tauri::Wry>) -> bool + Send + Sync + 'static {
|
||||
tauri::generate_handler![alerts_get, alerts_set]
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue