2026-09-22 21:46:01 +03:00
|
|
|
|
//! Мост «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 },
|
2026-09-22 23:17:27 +03:00
|
|
|
|
/// Обёртка перехватила закрытие окна: в клиенте есть несохранённые
|
|
|
|
|
|
/// изменения, он показывает тряску и диалог «Сохранить / Выйти без
|
|
|
|
|
|
/// сохранения» (docs/client-tauri.md §5).
|
|
|
|
|
|
CloseRequested,
|
|
|
|
|
|
/// Открыть палитру команд: глобальный Ctrl/Cmd+K при неактивном окне.
|
|
|
|
|
|
OpenPalette,
|
2026-09-22 21:46:01 +03:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/// Отправляет сообщение в веб-клиент.
|
|
|
|
|
|
///
|
|
|
|
|
|
/// Если клиент ещё не загрузился, deep link ставится в очередь и будет отдан
|
|
|
|
|
|
/// после `desktop_ready` (docs/client-tauri.md §5). Остальные сообщения в этот
|
|
|
|
|
|
/// момент терять не страшно: они относятся к активному взаимодействию.
|
|
|
|
|
|
pub fn send<R: Runtime>(app: &AppHandle<R>, message: &WebMessage) -> bool {
|
|
|
|
|
|
let state = app.state::<AppState>();
|
|
|
|
|
|
if !state.web_ready() {
|
|
|
|
|
|
if let WebMessage::DeepLink { url } = message {
|
|
|
|
|
|
state.push_deep_link(url.clone());
|
|
|
|
|
|
}
|
|
|
|
|
|
return false;
|
|
|
|
|
|
}
|
|
|
|
|
|
dispatch(app, message)
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/// Немедленная доставка без проверки готовности клиента.
|
|
|
|
|
|
pub fn dispatch<R: Runtime>(app: &AppHandle<R>, 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
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|