//! Мост «Rust → веб-клиент». //! //! Обратное направление (страница → обёртка) идёт через типизированные команды //! (`commands.rs`), а события платформы передаются вызовом единственной функции //! `window.__glchatDesktop.receive(...)` в самой странице. Отдельного канала //! событий нет намеренно: сообщений мало, а типы у них общие с `web/src/lib/platform.ts`. use serde::Serialize; use tauri::{AppHandle, Manager, Runtime}; use crate::settings::Settings; use crate::state::AppState; /// Метка главного окна (единственное окно приложения). pub const WEB_WINDOW: &str = "main"; /// Сообщение веб-клиенту: то, что платформа сообщает странице. #[derive(Debug, Clone, Serialize)] #[serde(tag = "type", rename_all = "camelCase")] pub enum WebMessage { /// Нажат или отпущен глобальный push-to-talk. Ptt { active: bool }, /// Переключить микрофон (глобальный шорткат). ToggleMic, /// Переключить «не слышу» (глобальный шорткат). ToggleDeafen, /// Открыть deep link: приглашение или сервер с комнатой. DeepLink { url: String }, /// Настройки обёртки изменились (обновлена страница настроек). Settings { settings: Settings }, /// Доступно обновление — клиент показывает уведомление в интерфейсе. UpdateAvailable { version: String }, } /// Отправляет сообщение в веб-клиент. /// /// Если клиент ещё не загрузился, deep link ставится в очередь и будет отдан /// после `desktop_ready` (docs/client-tauri.md §5). Остальные сообщения в этот /// момент терять не страшно: они относятся к активному взаимодействию. pub fn send(app: &AppHandle, message: &WebMessage) -> bool { let state = app.state::(); if !state.web_ready() { if let WebMessage::DeepLink { url } = message { state.push_deep_link(url.clone()); } return false; } dispatch(app, message) } /// Немедленная доставка без проверки готовности клиента. pub fn dispatch(app: &AppHandle, message: &WebMessage) -> bool { let Some(window) = app.get_webview_window(WEB_WINDOW) else { return false; }; let Ok(payload) = serde_json::to_string(message) else { log::warn!("не удалось сериализовать сообщение веб-клиенту"); return false; }; let script = format!("window.__glchatDesktop && window.__glchatDesktop.receive({payload});"); match window.eval(&script) { Ok(()) => true, Err(error) => { log::debug!("веб-клиент не принял сообщение: {error}"); false } } }