Files
Anton Budylin ea171ed95a first commit
2026-04-14 10:12:51 +03:00

10 KiB
Raw Permalink Blame History

Миграция с Tinode SDK на собственный HTTP-клиент

Обзор

Проект lastochka-android-compose был переведён с использования внешней библиотеки tinodesdk на собственную реализацию HTTP/WebSocket клиента на базе OkHttp.

Причины миграции

  1. tinodesdk не собирался — зависимости Jackson, ICU4J, Java-WebSocket не были указаны в build.gradle.kts
  2. API SDK устарел — код приложения использовал методы, которых нет в актуальной версии SDK
  3. Конфликт версий — Kotlin 1.9.25 несовместим с Compose Compiler 1.5.14, а kapt падал с ошибками stub generation
  4. Избыточность — SDK тянул 100+ файлов Java-кода, из которых приложению нужны ~15 методов

Архитектура нового клиента

┌─────────────────────────────────────────────┐
│  UI Layer (Compose Screens + ViewModels)    │
│  ─ ChatListScreen, ChatScreen, AuthScreen   │
├─────────────────────────────────────────────┤
│  ChatRepository                             │
│  ─ Единая точка доступа: TinodeClient + Room│
├─────────────────────────────────────────────┤
│  TinodeClient (high-level)                  │
│  ─ Управление сессией, авторизация, топики   │
│  ─ Flow событий: events (SharedFlow)        │
│  ─ Callback состояния: observeConnectionState│
├─────────────────────────────────────────────┤
│  TinodeHttpClient (low-level)               │
│  ─ OkHttp WebSocket: wss://host/ws           │
│  ─ JSON сериализация: Gson                   │
│  ─ ID генерация, Base64, timeout handling    │
├─────────────────────────────────────────────┤
│  TinodeProtocol (data models)               │
│  ─ Client: Hi, Acc, Login, Sub, Pub, Note    │
│  ─ Server: Ctrl, Data, Meta, Pres, Info      │
└─────────────────────────────────────────────┘

Протокол Tinode (WebSocket)

Инициализация сессии

Client → { "id": "abc123", "hi": { "ver": "1", "ua": "lastochka-android/1.0" } }
Server ← { "ctrl": { "id": "abc123", "code": 200 } }

Аутентификация (login)

Client → { "id": "def456", "login": { "scheme": "basic", "secret": "base64(user:pass)" } }
Server ← { "ctrl": { "id": "def456", "code": 200, "params": { "user": "usr...", "token": "..." } } }

Регистрация (acc)

Client → { "id": "ghi789", "acc": { "user": "username", "scheme": "basic", "secret": "base64(user:pass)", "login": true, "desc": { "public": { "fn": "Display Name" } } } }
Server ← { "ctrl": { "id": "ghi789", "code": 201, "params": { "user": "usr...", "token": "..." } } }

Подписка на топик (sub)

Client → { "id": "jkl012", "sub": { "topic": "usrAbCdEf", "get": { "data": { "since": 0, "limit": 50 } } } }
Server ← { "ctrl": { "id": "jkl012", "code": 200 } }
Server ← { "data": { "topic": "usrAbCdEf", "from": "usrOther", "seq": 5, "content": { "txt": "Hello!" } } }

Отправка сообщения (pub)

Client → { "id": "mno345", "pub": { "topic": "usrAbCdEf", "content": { "txt": "Hi there!" } } }
Server ← { "ctrl": { "id": "mno345", "code": 200 } }

Typing indicator (note)

Client → { "id": "pqr678", "note": { "topic": "usrAbCdEf", "what": "kp" } }

Read receipt (note)

Client → { "id": "stu901", "note": { "topic": "usrAbCdEf", "what": "read", "seq": 5 } }

Мета-запрос (get)

Client → { "id": "vwx234", "get": { "topic": "me", "desc": {}, "sub": {} } }
Server ← { "meta": { "id": "vwx234", "topic": "me", "sub": [ { "topic": "usrAbCdEf", "unread": 3, "public": { "fn": "Chat Name" } }, ... ] } }

Модели данных

Client Messages

Класс Назначение
ClientMsgHi Инициализация сессии
ClientMsgAcc Регистрация нового пользователя
ClientMsgLogin Вход по логину/паролю или токену
ClientMsgSub Подписка на топик (чат)
ClientMsgPub Отправка сообщения
ClientMsgNote Typing indicator / read receipt
ClientMsgLeave Покидание топика
ClientMsgGet Запрос метаданных
ClientMsgSet Обновление метаданных

Server Messages

Класс Назначение
CtrlPacket Ответ управления (code 200 = OK, 201 = Created)
DataPacket Входящее сообщение (content.txt)
MetaPacket Метаданные (список чатов из me)
PresPacket Присутствие (online/offline)
InfoPacket Typing / read уведомления

Ключевые файлы

Файл Описание
TinodeHttpClient.kt Низкоуровневый WebSocket-клиент (OkHttp)
TinodeClient.kt Высокоуровневый клиент (сессия, auth, state)
TinodeProtocol.kt Все модели протокола
ChatRepository.kt Repository: TinodeClient + Room DB
TinodeClient.kt (app) Обёртка для UI с Flow событий

Изменения в зависимостях

Удалены

implementation(project(":tinodesdk"))

Добавлены

implementation("com.squareup.okhttp3:okhttp:4.12.0")
implementation("com.google.android.material:material:1.12.0")

Изменены

Зависимость Было Стало
Kotlin 1.9.24 1.9.25
Compose Compiler 1.5.14 1.5.15
Room compiler kapt ksp
Hilt compiler kapt ksp

Сборка проекта

# Установка JAVA_HOME (если не задана)
set JAVA_HOME=C:\Program Files\Android\Android Studio\jbr

# Сборка debug APK
cd D:\Projects\Messenger\dev\lastochka-android-compose
gradlew.bat assembleDebug

# Результат: app/build/outputs/apk/debug/app-debug.apk

API клиента

Подключение

val client = TinodeClient(
    context = context,
    appName = "Ласточка",
    apiKey = "AQEAAAABAAD_...",
    hostName = "app.lastochka-m.ru",
    useTLS = true
)
client.connect()

Авторизация

// Вход
val result = client.login(username, password)

// Регистрация
val result = client.register(username, password, displayName)

// Проверка username
val free = client.checkUsername(username)

Работа с чатами

// Список чатов
val subs = client.getMeTopic()
val contacts = client.getContacts(subs)

// Подписка на чат
client.subscribeTopic(topicName)

// Отправка сообщения
client.sendTextMessage(topicName, "Hello!")

// Typing indicator
client.sendTyping(topicName)

// Read receipt
client.markAsRead(topicName, seqId)

Наблюдение за событиями

// Поток входящих сообщений
lifecycleScope.launch {
    client.events.collect { event ->
        when (event) {
            is TinodeEvent.NewMessage -> handleNewMessage(event.data)
            is TinodeEvent.Presence -> handlePresence(event.data)
            is TinodeEvent.Meta -> handleMeta(event.data)
            else -> {}
        }
    }
}

// Состояние подключения (callback)
client.observeConnectionState { state ->
    when (state) {
        TinodeConnState.Connected -> showConnected()
        TinodeConnState.Authenticated -> showAuthenticated()
        TinodeConnState.Disconnected -> showDisconnected()
        TinodeConnState.Error -> showError()
        else -> {}
    }
}

Миграция с tinodesdk

Старый API (tinodesdk) Новый API (TinodeClient)
tinode.loginBasic(user, pass, true) client.login(user, pass)
tinode.registerNewBasic(...) client.register(user, pass, name)
tinode.getMeTopic() client.getMeTopic()
topic.publish(Drafty().plain(text)) client.sendTextMessage(topic, text)
topic.noteRead(seq) client.markAsRead(topic, seq)
topic.noteKeyPress() client.sendTyping(topic)
tinode.setListener { ... } client.events.collect { ... }
tinode.isAuthRequired && tinode.authToken != null client.isAuthenticated()
tinode.disconnect() client.disconnect()

Известные ограничения

  1. Нет поддержки Drafty — сообщения отправляются как plain text ({"txt": "..."})
  2. Нет загрузки файлов — только текстовые сообщения
  3. Нет видеозвонков — протокол сигнализации не реализован
  4. Нет push-уведомлений — Firebase Messaging не подключён
  5. Нет локального кэширования сообщений — используется только Room для прочитанных

Планы

  • Добавить Drafty парсер для форматированных сообщений
  • Загрузка и отправка файлов/изображений
  • Push-уведомления через Firebase
  • Offline-кэш сообщений
  • Видеозвонки (WebRTC)
  • Шифрование E2E