Skip to content

Событийная система

Файлы: backend/src/events/mod.rs, events/tauri_bridge.rs, api/sse.rs

Push-уведомления от бекенда к фронтенду. EventBus — broadcast-канал, через который менеджеры и фоновые задачи отправляют события. Два транспорта доставляют их в UI: Tauri bridge (desktop) и SSE (headless/web).

Архитектура

plantuml Diagram

AppEvent

rust
pub struct AppEvent {
    pub name: String,
    pub payload: serde_json::Value,
}

EventBus — обёртка над tokio::sync::broadcast::Sender<AppEvent>:

rust
pub struct EventBus { tx: broadcast::Sender<AppEvent> }

impl EventBus {
    pub fn new(capacity: usize) -> Self;           // capacity = 256
    pub fn emit<T: Serialize>(&self, name: &str, payload: &T) -> Result<()>;
    pub fn subscribe(&self) -> broadcast::Receiver<AppEvent>;
}

Транспорты

Tauri Bridge (desktop)

Файл: backend/src/events/tauri_bridge.rs

Спавнится при запуске Tauri-приложения (#[cfg(not(feature = "headless"))]). Подписывается на EventBus и перенаправляет каждое событие через app.emit():

rust
pub fn spawn_tauri_bridge(app: AppHandle, event_bus: EventBus) {
    let mut rx = event_bus.subscribe();
    tauri::async_runtime::spawn(async move {
        loop {
            match rx.recv().await {
                Ok(event) => { app.emit(&event.name, event.payload)?; }
                Err(RecvError::Lagged(n)) => { /* dropped events */ }
                Err(RecvError::Closed) => break,
            }
        }
    });
}

SSE (headless / web)

Файл: backend/src/api/sse.rs

Endpoint GET /api/events для headless-режима. Стримит AppEvent как Server-Sent Events с JSON payload. Фронтенд подключается через EventSource (shim tauri-event-shim.ts).

Реестр событий

Vault

СобытиеИсточникPayloadFrontend
vault_lockedVaultManager.lock(){}useVaultStore
vault_unlockedVaultManager.unlock_with_pin(), unlock_with_recovery_phrase(){}useVaultStore

Pairing

СобытиеИсточникPayloadFrontend
qr_pairing_requestPairingManager{ token, device_name, device_type }AddDeviceContent.vue
pairing_requestPairingManagerPendingPairingRequest (peerid, initiator*, pin, device_count)AddDeviceContent.vue
pairing_confirmationPairingManager{ approved, pin, confirmer_device?, confirmer_identity?, error? }AddDeviceContent.vue

Sharing — пиры

СобытиеИсточникPayloadFrontend
sharing_peer_discoveredSharingManager{ identity_id, identity_display_name, device_name }useDevicesStore
peer_onlineSharingManager{ device_id, device_name?, identity_id?, storage?, timestamp? }useDevicesStore
peer_offlineSharingManager{ device_id, device_name, timestamp }useDevicesStore
device_revokedSharingManager{ old_identity_id, reason }

Sharing — пространства и файлы

СобытиеИсточникPayloadFrontend
space_receivedSharingManager{ space_id, space_name, from_identity_id, from_identity_name, share_type, auto_accepted }useSpacesStore, MainLayout
devices_updatedSharingManager{ added_count?, removed_device_id?, source }useDevicesStore
spaces_updatedSharingManager{ added_count?, space_id?, deleted_space_id?, source }
file_sync_progressSharingManager{ space_id, file_hash, offset, total_size, progress_pct }useSpacesStore, useSyncStore
file_sync_completeSharingManager{ space_id, file_hash, total_size }useSpacesStore, useSyncStore

P2P Network

СобытиеИсточникPayloadFrontend
listening_addrP2P networkString (multiaddr)
peer_addedP2P network{ peer_id, count }
peer_removedP2P networkString (peer_id)

S3 Sync

СобытиеИсточникPayloadFrontend
sync_progresssync_cmd.rs{ action, file, progress, completed, total }

Frontend: подписка на события

Паттерн Store Listener

Pinia stores используют startListener() / stopListener() для подписки:

typescript
// entities/device/useDevicesStore.ts
let unlistenPeerOnline: UnlistenFn | null = null;
let unlistenPeerOffline: UnlistenFn | null = null;

async function startPeerStatusListener() {
  unlistenPeerOnline = await listen('peer_online', (event) => {
    // Обновить статус устройства
  });
  unlistenPeerOffline = await listen('peer_offline', (event) => {
    // Пометить устройство как offline
  });
}

function stopPeerStatusListener() {
  unlistenPeerOnline?.();
  unlistenPeerOffline?.();
}

SSE shim (headless)

В headless-режиме @tauri-apps/api/event подменяется на tauri-event-shim.ts, который использует EventSource для подключения к GET /api/events. Подробнее: Frontend API.

Потоки событий

vault_locked: auto-lock → UI redirect

plantuml Diagram

vault_unlocked: unlock → cache lifecycle + UI update

plantuml Diagram

file_sync: P2P chunk transfer → progress UI

plantuml Diagram