Add decoy SNI plumbing, bundle wintun.dll, fix flag icons, add docs

- Прокидываем sni (decoy-хост) по обновлённому UDL через весь стек:
  guest-js -> Tauri-команда -> desktop/mobile -> Kotlin spawnSession.
  Список серверов пока статичный (backend еще не отдает sni), но логика
  похода в реальный /api/v1/nodes оставлена под флагом на будущее.
- Бандлим wintun.dll в Windows-инсталлятор через bundle.resources в
  tauri.windows.conf.json - раньше он копировался только для
  локального cargo run/tauri dev, но не попадал в NSIS/MSI.
- Чиним падение мобильной сборки: гитигнорённые UniFFI Kotlin-биндинги
  отстали от обновленного UDL (не хватало параметра sni).
- Флаги стран - локальные SVG вместо react-country-flag в CDN-режиме:
  тот бился об CSP и не грузился офлайн.
- Добавляем дерево README.md по структуре проекта и точечные
  комментарии в неочевидных местах кода.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
Kirill
2026-07-02 19:29:29 +07:00
parent 16760c16ea
commit c852315d34
27 changed files with 464 additions and 57 deletions
+17
View File
@@ -0,0 +1,17 @@
# `src/store/` — Zustand-стейт
| Стор | Назначение |
|---|---|
| `useVpnStore.ts` | Статус подключения (`idle`/`connecting`/`connected`). `init()` подписывается на событие по-разному в зависимости от платформы: на десктопе — `listen("status-change")` из `@tauri-apps/api/event` (прямой emit из Rust), на мобильных — `setupVpnStatusListener` из `vpn-plugin` (нативный мост плагина). См. `src/lib/platform.ts`. |
| `useTrafficStore.ts` | RX/TX счётчики трафика, та же desktop/mobile развилка по источнику событий, что и в `useVpnStore`. `reset()` — для сброса при смене сервера. |
| `useSettingsStore.ts` | Настройки пользователя (killSwitch, excludedApps, excludedDomains, stealthMode) с `persist`-middleware — хранится в `localStorage` под ключом `netrunner-settings`. |
| `useNodesStore.ts` | Список серверов и выбранный узел. **Сейчас работает в статическом режиме** (`USE_STATIC_NODES = true`) — бэкенд `/api/v1/nodes` ещё не отдаёт поле `sni` (decoy-домен), поэтому используется захардкоженный `STATIC_NODES`. Вся логика похода в реальный API (`fetchNodes`, фолбэк на дефолтный узел при ошибке/пустом ответе) уже написана в `loadNodes()` и включится сама, если выставить `USE_STATIC_NODES = false` — трогать её не нужно, только флаг. `getSelectedNode()` — текущий выбранный узел или наименее загруженный по умолчанию. |
## Зачем такое разделение источников событий (desktop/mobile)
Rust-часть плагина (`src-tauri/src/plugins/vpn-plugin`) на десктопе эмитит
события напрямую через `AppHandle::emit` (обычный Tauri-канал), а на
Android/iOS — через нативный мост `PluginHandle` (Kotlin `trigger(...)`
`addPluginListener`). Это два разных транспорта с разным API на JS-стороне,
отсюда и `if (isDesktop) {...} else {...}` в обоих стораx. Подробности —
[../../src-tauri/src/plugins/vpn-plugin/README.md](../../src-tauri/src/plugins/vpn-plugin/README.md).