dobrovolskiy.com
EN
← Заметки2026-08-30 · Дмитрий Добровольский

RYTA: один веб-бандл для Android, PWA и мини-приложения мессенджера с OTA-обновлениями без релизов в сторе

Кейс RYTA 3.0 — маркетплейса грузоперевозок и спецтехники: оболочка Kotlin WebView с OTA-движком (бандлы с проверкой SHA-256), бэкенд на FastAPI, подтверждение телефона входящим звонком, платежи Т-Банка и бот в мессенджере MAX, зеркалящий весь интерфейс.

RYTA 3.0 — маркетплейс, где перевозчики и грузовладельцы публикуют грузы, транспорт и спецтехнику как точки на собственной карте. Один веб-бандл работает внутри тонкой оболочки Kotlin WebView на Android, как PWA на web.ryta.online и как бот плюс мини-приложение в мессенджере MAX. Android-приложение обновляет себя по воздуху с бэкенда на FastAPI атомарной заменой бандла с проверкой хэша — больше 300 бандлов выпущено без единого релиза в сторе.

Ограничение, которое определило всё

У RYTA была старая версия на .NET с живыми пользователями и объявлениями. Новую нужно было выпустить быстро, менять ежедневно в недели запуска и довести до людей на Android, в вебе и внутри мессенджера MAX — не дожидаясь проверки в сторе каждый раз, когда сдвинулась кнопка. Это исключает классическое нативное приложение. И исключает инструменты в духе Capacitor с чужим сервисом обновлений: путь обновления должен был быть нашим.

Один бандл, три оболочки

Приложение — PWA на чистом JavaScript с картой MapLibre GL на PMTiles из нашего собственного гео-стека. Один и тот же бандл:

Код приложения живёт по модели core template: прикладной код никогда не правит файлы ядра, улучшения ядра подтягиваются скриптом по манифесту, так что оболочка и движок обновлений общие с другими приложениями на том же шаблоне.

Движок OTA

При старте оболочка спрашивает бэкенд о более новом бандле в своём канале, скачивает, проверяет SHA-256 и атомарно подменяет с откатом, если новый бандл не запускается. Каналы дают поэтапную раскатку; порог минимальной версии нативной оболочки не даёт бандлу запуститься там, где нет нужной возможности. Так выпущено больше 300 бандлов.

Личность без паролей

Пользователь подтверждает номер телефона звонком на номер 8-800. Виртуальная АТС Ростелекома шлёт на бэкенд вебхук с HMAC-подписью, бэкенд сопоставляет звонящего с ожидающим запросом. Ни SMS-кодов, ни паролей; у одного человека может быть много устройств. Подписки привязаны к подтверждённому телефону.

Бэкенд и деньги

FastAPI с SQLAlchemy на PostgreSQL, MinIO для фотографий, нативно собранные push-сообщения FCM только для личных сообщений — чтобы не было веерных рассылок, тематические чаты и личные диалоги. Платные услуги идут через эквайринг Т-Банка (Init плюс подписанный вебхук уведомлений) по прайс-листу из админки. Админка на SvelteKit: пользователи, объявления, биллинг, пуши, ошибки клиентов, темы, справочники и свой выбор иконок с конвертацией Android vector XML в SVG.

Клиентский журнал диагностики выгружает ошибки прямо в админку; скрипты релиза ловят расхождение окружений; контейнер redroid даёт виртуальное Android-устройство для тестов; скрипт генерирует скриншоты, подписи и графику для Play Store.

Цифры

Около 36 600 строк в 116 файлах: бэкенд, PWA, Android-оболочка, админка и MAX-бот; пользователи и объявления перенесены из .NET-системы; хостинг — свой Docker/Caddy с приватным реестром.

Что сделал бы иначе

Сделал бы журнал диагностики до первой беты, а не после первого сообщения об ошибке с телефона, которого я не видел. И определил бы политику каналов (кому какой бандл) заранее: натягивать её на пользователей, которые уже в поле, неудобно.

Вопросы

Google Play нормально относится к WebView-приложениям?

Да, если это настоящее приложение, а не обёртка над чужим сайтом. RYTA везёт бандл внутри APK и работает офлайн; обновление веб-слоя по воздуху — широко используемый приём.

Почему подтверждение телефона входящим звонком, а не SMS-кодом?

Доставка SMS в регионе ненадёжна и стоит денег за сообщение; звонок на 8-800 бесплатен для пользователя, его не перехватить тем же способом, а вебхук АТС отдаёт бэкенду номер звонящего напрямую.

Подойдёт ли такой подход для моего приложения?

Если продукт — в основном интерфейс поверх API и должен часто меняться, оболочка плюс OTA-бандлы обычно самый быстрый путь. Оболочку, движок обновлений и бэкенд можно поднять вживую за несколько почасовых сессий.

Нужно что-то похожее?

Делаю это вживую на звонке в Zoom / Яндекс Телемост, вы смотрите на экран, таймер останавливается по вашему слову. Первые 15 минут бесплатно.

Ещё проекты

RYTA: маркетплейс грузоперевозок и спецтехникиОфлайн-ориентированная биржа грузов и транспорта с OTA-обновлениями для Android, веба и мессенджера MAX