golang

Новый народный мессенджер России ч.2. Ловите Ласточку

  • вторник, 14 апреля 2026 г. в 00:01:49
https://habr.com/ru/articles/1022914/

Несколько недель назад я описал на Хабре идею «честного российского мессенджера» с открытым кодом и прозрачным compliance. Та статья была про «зачем». Эта — про «как получилось». Web-клиент и Android-приложение полностью переписаны и работают в production, серверная часть развёрнута и обслуживает реальные подключения. iOS пока не трогали. Ниже — разбор архитектуры, решения, которые мы приняли, грабли, на которые наступили, и открытый набор людей в проект.

Что готово

Backend (Tinode) — Личные чаты, группы до 200К, каналы, typing indicators, доставка/прочтение

Web UI — Чаты, группы, изображения, темная тема, базовые настройки

Мобильное приложение Android — Чаты, группы, изображения, темная тема, базовые настройки

Почему форк Tinode, а не свой сервер

Tinode (github.com/tinode/chat) — это ~13 тысяч звёзд на GitHub, разработка с 2015 года, и из коробки он даёт практически всё, что нужно мессенджеру: личный и групповой чаты, каналы, typing indicators, delivery confirmations, Drafty (формат rich-текста), кластеризацию, WebRTC-сигнализацию, push-уведомления.Написать такое с нуля — это полгода работы команды из 3-4 бэкендеров. С форком — берёшь рабочий сервер и кастомизируешь под себя.

Цена этого решения: GPL v3. Весь производный код сервера — тоже GPL. Это значит, что если вы хотите сделать проприетарный форк — не получится. Для нас это не проблема: проект открытый. Но это важно учитывать тем, кто рассматривает аналогичный подход для коммерческого продукта.

Почему свой Web UI, а не форк tinode/webapp

Оригинальный tinode/webapp написан на Angular-подобном фреймворке с собственной системой шаблонов. Кастомизировать его под другой дизайн-язык — боль. Мы переписали на React 18 + TypeScript + Tailwind CSS + Zustand.Результат: glassmorphism-дизайн, тёмная тема, отзывчивый интерфейс, полный контроль над UX. Стоимость — примерно 2-3 недели работы одного фронтендера.

Почему свой Android, а не форк tindroid

Оригинальный tindroid — на Java. Мы сделали Kotlin + Jetpack Compose + Hilt + Room. Но самое интересное — мы не используем официальный Tinode SDK на Android. Вместо этого — собственный WebSocket-клиент на OkHttp с ручной реализацией протокола.Зачем? Полный контроль над жизненным циклом соединения, корреляцией запросов и обработкой ошибок. Официальный SDK — отличная библиотека, но для production-приложения с offline-first и push-уведомлениями нам нужна была предсказуемость на каждом уровне.

Протокол: WebSocket + JSON, и это неплохо

Tinode работает поверх постоянного WebSocket-соединения. Каждое сообщение — JSON-объект. Вот как выглядит базовый обмен:

// Клиент → Сервер: подключение
{ "hi": { "id": "abc123", "ver": "0.25", "ua": "Lastochka/1.0" } }

// Сервер → Клиент: подтверждение
{ "ctrl": { "code": 200, "text": "connected", "id": "abc123" } }

// Клиент → Сервер: аутентификация
{ "login": { "scheme": "token", "secret": "eyJhbGc..." } }

// Сервер → Клиент: успех
{ "ctrl": { "code": 200, "text": "ok", "id": "login_req_1" } }

Типы топиков (чатов):

Префикс

Тип

Назначение

usr*

P2P

Личная переписка

grp*

Group

Группы до 200 000 участников

me

Meta

Список чатов пользователя + уведомления

fnd

Find

Поиск пользователей по тегам

Каждый топик — это не просто «чат», а абстракция с собственной системой прав. Tinode использует bitmask-модель:

J — Join      R — Read      W — Write     P — Presence
A — Approve   S — Sharing   D — Delete    O — Owner

Это элегантно решает массу задач. Например, «замутить» чат — значит установить режим N (no access для уведомлений). «Закрепить» — записать timestamp пина в приватное поле (pvt) подписки. Никаких дополнительных таблиц.

Грабли, на которые мы наступили

1. Base64 для изображений — удобно, но дорого

Web-клиент сжимает изображения через Canvas API (max 1920px, JPEG 85%), конвертирует в base64 и отправляет прямо через WebSocket внутри Drafty-сообщения. Это работает, но base64 увеличивает размер данных на ~33%. Изображение 2 МБ превращается в 2,7 МБ по сети.Для MVP — приемлемо. Для production — переходим на presigned URL через MinIO. Планируется в следующем спринте.

2. Session expiry без явного уведомления

Токен аутентификации имеет TTL 14 дней. Когда он истекает, сервер возвращает 401. Но Android-клиент обнаруживает это не по коду ошибки, а косвенно: если после логина по токену список подписок топика me пуст — значит, токен принят, но сессия мертва. Это triggering forced logout.Костыль? Да. В идеале сервер должен присылать явное уведомление об истечении сессии. В Tinode это работает неочевидно. Мы написали обёртку, которая обрабатывает это на клиенте, но это — технический долг.

3. Compliance под GPL

Compliance-сервис — отдельный Go-бинарник. Он общается с Tinode только через HTTP на внутренней сети. Это позволяет ему не попадать под GPL v3, поскольку это не производный код, а отдельная программа, взаимодействующая по сети.Но грань тонка. Если compliance начнёт импортировать пакеты Tinode — он станет производной работой. Мы тщательно следим за границей модулей. Это архитектурное ограничение, которое нужно держать в голове.

Android: offline-first и Room

Архитектура Android-приложения заслуживает отдельной статьи, но коротко:StateFlow + MVVM + Repository + Room — стандартный стек, но есть нюансы.

Корреляция запросов через WebSocket

Поскольку WebSocket — асинхронный двунаправленный канал, ответ может прийти в любой момент. Мы сопоставляем запросы и ответы через уникальный ID:

private val pendingRequests = mutableMapOf<String, (ServerMessage) -> Unit>()

suspend fun sendAndWait(request: Request): ServerMessage =
    suspendCancellableCoroutine { continuation ->
        val id = generateUniqueId()
        pendingRequests[id] = { response ->
            continuation.resume(response)
        }
        // таймаут 30 секунд
    }

Это работает как синхронный вызов REST API, но поверх WebSocket.

Room V1 → V2 без потери данных

Когда мы добавили reply-функциональность, потребовались два новых столбца: replyToSeq и replyToContent. Миграция написана вручную:

val MIGRATION_1_2 = object : Migration(1, 2) {
    override fun migrate(database: SupportSQLiteDatabase) {
        database.execSQL("ALTER TABLE messages ADD COLUMN reply_to_seq INTEGER")
        database.execSQL("ALTER TABLE messages ADD COLUMN reply_to_content TEXT")
    }
}

fallbackToDestructiveMigration удалён. Данные пользователей сохраняются при обновлении.

Инфраструктура: Nginx на хосте, не в Docker

Production-стек развёрнут через Docker Compose: PostgreSQL, Redis, MinIO, Tinode, Compliance. Но Nginx — системный, на хосте.Причина: проксирование WebSocket требует тонкой настройки таймаутов (proxy_read_timeout 86400s — 24 часа). В Docker это работает, но при рестарте контейнера Nginx теряет upstream. Системный Nginx с конфигурацией, указывающей на хост-машину, надёжнее.

Два домена:

TLS — Let’s Encrypt, Certbot с автоматическим обновлением.

Открытый набор

Проект «Ласточка» — open source. Код скоро будет доступен в Github, лицензия GPL v3 для серверной части. Кто хочет поучаствовать в проекте пишите.

Что вы получите:

  • Реальный production-проект с живыми пользователями

  • Полный доступ к кодовой базе и принятию решений

  • Упоминание в списке контрибьюторов

  • Возможность вписать строчку в резюме: «участвовал в запуске мессенджера»

Спасибо что дочитали до этого места, дальше уже не техническая, но важная информация


Правовое поле: ОРИ, 3 года хранения, идентификация

Compliance — это не «фича», которую мы прикрутили для галочки. Это юридическая необходимость, которая напрямую влияет на архитектуру. Вот ключевые требования к мессенджерам в России в 2026 году.

Организатор распространения информации (ОРИ)

Если сервис позволяет пользователям обмениваться сообщениями, он подпадает под определение ОРИ. После включения в реестр Роскомнадзора:

  • Хранение данных: 3 года. С 1 января 2026 года необходимо хранить сведения о действиях пользователей на территории РФ в течение 3 лет с момента их окончания (ранее — 1 год). Сами сообщения хранятся до 6 месяцев.

  • Предоставление данных. По запросу ФСБ — информация, необходимая для декодирования передаваемых сообщений, а также сведения о пользователях.

  • Штрафы. За неисполнение обязанностей, включая отказ от добровольного включения в реестр ОРИ — до 300 тыс. руб. для юрлиц, при повторном нарушении — до 1 млн руб. (ч. 1 ст. 13.31 КоАП РФ).

Идентификация и противодействие мошенничеству

С 1 июня 2025 года действуют новые нормы:

  • Обязательная идентификация по номеру телефона. Мессенджеры обязаны идентифицировать абонентов на основании договора с оператором связи. Передача сообщений от анонимных пользователей запрещена.

  • Блокировка противоправного контента. В течение суток после получения требования Роскомнадзора мессенджер обязан ограничить для указанного пользователя возможность отправки сообщений с противоправной информацией.

Российское ПО и платформы

Государство активно стимулирует использование отечественных решений:

  • Предустановка MAX. С 1 сентября 2025 года на все продаваемые в России смартфоны, планшеты и ПК должен предустанавливаться российский мессенджер MAX (ранее — VK Мессенджер).

  • Запрет на иностранные мессенджеры. Банкам, госорганам, операторам связи, маркетплейсам и другим организациям запрещено использовать иностранные мессенджеры (Telegram, WhatsApp, Viber) для общения с клиентами и передачи персональных данных.

  • MAX для бизнеса. С 1 сентября 2026 года торговые сети обязаны принимать через MAX данные о возрасте, льготном статусе и дисконтных картах. Отказ — нарушение.

  • Двухфакторная аутентификация. С 1 сентября 2026 года для подтверждения юридически и финансово значимых действий планируется использовать 2FA через СМС и MAX.

Потенциальные изменения

В феврале 2026 года в Госдуму внесён законопроект, запрещающий блокировать мессенджеры и соцсети целиком. Предполагается блокировка конкретного противоправного контента, а не всего сервиса.

Как это влияет на архитектуру «Ласточки»

Именно эти требования определяют наше ключевое архитектурное решение — compliance-сервис как отдельный бинарник с append-only аудит-логом.

Почему это не «просто middleware в Tinode»:

  1. Хранение 3 года. Tinode хранит метаданные в PostgreSQL, но не заточен под 3-летнюю неизменяемую историю. Нам нужна отдельная таблица с триггерами на UPDATE/DELETE — любая попытка модификации вызывает RAISE EXCEPTION. Это уровень базы, а не приложения.

  2. Аудит-лог — юридический документ. Каждый запрос ФСБ, каждый доступ к данным пользователя должен быть зафиксирован: кто, когда, на каком основании. И эта запись должна быть нестираемой — не только политикой, но и технически.

  3. TOTP 2FA для регуляторов. Government API требует двухфакторной аутентификации. Это не «обычный» пользовательский логин — это отдельный поток с OTP-валидацией, rate limiting (максимум 5 попыток TOTP в минуту) и constant-time comparison токенов.

  4. Идентификация по номеру телефона. Tinode поддерживает basic-auth и token-auth, но не интеграцию с операторами связи. Это слой поверх — отдельный endpoint, который проверяет номер через SMS-OTP перед созданием аккаунта.

  5. Блокировка контента за сутки. Это означает, что compliance-сервис должен уметь мгновенно отозвать права на отправку сообщений у конкретного пользователя. Tinode-система прав (JRWAPSDO) позволяет это, но нужен быстрый pipeline: запрос от РКН → compliance → revoke W (Write) у пользователя.

Всё это — не функциональность мессенджера, а юридическая обвязка. И именно поэтому она вынесена в отдельный сервис:

Tinode (мессенджер, GPL v3)
    │
    │ HTTP POST /internal/audit
    │ (только события: auth, gov-запросы)
    ▼
Compliance (наш код, не GPL)
    │
    ├── PostgreSQL (audit_log — append-only)
    ├── TOTP 2FA middleware
    ├── Rate limiter (5 attempts/min)
    └── Government API (2FA-protected endpoints)

Эта граница — не параноидальная архитектурная чистота. Это лицензионная необходимость: GPL v3 Tinode не должен «заражать» compliance-код, который в будущем может потребовать отдельной лицензии. И это безопасность: даже если злоумышленник получит доступ к Tinode, он не сможет подделать аудит-лог, потому что тот лежит в отдельной базе с триггерами.

Заключение

«Ласточка» — это не попытка убить какой-либо (вы сами понимаете какой) мессенджер, это просто свободная альтернатива российского мессенджера, открытая и понятная.

Технически проект работает. Web и Android общаются через WebSocket. Сервер в production. Инфраструктура развёрнута и документирована. Если вам интересна идея открытого мессенджера с прозрачной архитектурой и честным compliance — welcome. Код, задачи и обсуждения — в репозитории (скоро обновлю ссылку)

Да, багов полно, но приложение умеет отправлять сообщения и изображения.


Сайт проекта

Как со мной связаться в телеграм - @Anton_Budylin