Розробка мобільного додатку для внутрішньої комунікації працівників
Корпоративний мессенджер — технічно складніше, ніж здається. Реальний реалтайм, E2E-шифрування, push при закритому додатку, синхронізація історії між пристроями, групові чати з тисячами учасників, статусами прочитання, обміна медіа. Це не один CRUD з WebSocket — це окрема інфраструктура.
Транспортний шар: WebSocket vs XMPP vs власний протокол
Три реальні варіанти для корпоративного мессенджера:
WebSocket + власний протокол. Найпоширеніший підхід. Клієнт тримає постійне WebSocket-з'єднання, сервер пушить події. Мінус: самостійно реалізовуєте reconnect, heartbeat, acknowledgement, чергу повідомлень при втраті зв'язку.
XMPP (Extensible Messaging and Presence Protocol). Зрілий протокол з багатою екосистемою. Сервери: Ejabberd, OpenFire, Prosody. Бібліотеки для мобільного: XMPPFramework (iOS), Smack (Android). XMPP із коробки дає presence (онлайн-статуси), MUC (групові чати), стандартизоване E2E через OMEMO. Мінус: складніше кастомізувати протокол під нестандартні вимоги.
Matrix + Element SDK. Open-source, federated, E2E за замовчуванням через Megolm. Homeserver: Synapse або Dendrite. Клієнтські SDK: matrix-ios-sdk, matrix-android-sdk2. Підходить, якщо потрібна федерація між організаціями.
Для типового корпоративного проекту з нуля — WebSocket + власний backend-протокол (NestJS + Redis Pub/Sub + PostgreSQL). Вибирайте XMPP, якщо потрібна інтеграція з третьосторонніми XMPP-клієнтами або зріла реалізація OMEMO.
Реалтайм-архітектура на клієнті
Ключова задача: WebSocket-з'єднання повинне пережити переклад додатку у фон. На iOS — з'єднання закривається при фоновому режимі (протягом ~30 секунд). На Android — убивається агресивно doze та battery optimization.
Для iOS: тримаємо URLSessionWebSocketTask на рівні AppDelegate, але foreground-reconnect через applicationWillEnterForeground. Push через APNs з content-available: 1 для фонового пробудження — не WebSocket, але дозволяє отримати нове повідомлення та показати сповіщення через UNNotificationServiceExtension.
На Android: WebSocket у Service (foreground service з постійним сповіщенням) — єдиний надійний спосіб утримувати з'єднання живим на всіх виробниках. На деяких Android OEM (Xiaomi, Huawei, OPPO) foreground service убивається системою навіть з увімкненим автозапуском. Рішення: Firebase FCM як fallback для гарантованої доставки + власний WebSocket для реалтайму.
class MessagingService : Service() {
private lateinit var webSocketClient: OkHttpClient
private var webSocket: WebSocket? = null
override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
startForeground(NOTIFICATION_ID, buildNotification())
connectWebSocket()
return START_STICKY // Перезапускаємо при убийстві системою
}
private fun connectWebSocket() {
val request = Request.Builder()
.url("wss://api.company.com/ws")
.header("Authorization", "Bearer ${tokenManager.token}")
.build()
webSocket = webSocketClient.newWebSocket(request, object : WebSocketListener() {
override fun onMessage(webSocket: WebSocket, text: String) {
val message = Json.decodeFromString<IncomingMessage>(text)
messageRepository.save(message)
notificationManager.showIfNeeded(message)
}
override fun onFailure(webSocket: WebSocket, t: Throwable, response: Response?) {
scheduleReconnect(delay = 5_000L)
}
})
}
}
E2E-шифрування
Signal Protocol — де-факто стандарт для E2E в мессенджерах. Використовується в Signal, WhatsApp, Skype. Для мобільного: libsignal-protocol-java (Android), SignalProtocolKit (iOS).
Протокол основан на Double Ratchet Algorithm: кожне повідомлення шифрується новим ключем, компрометація одного ключа не розкриває інші (forward secrecy).
// Шифрування вихідного повідомлення
val sessionCipher = SessionCipher(signalStore, recipientAddress)
val encryptedMessage = sessionCipher.encrypt(plaintext.toByteArray())
val payload = Base64.encodeToString(encryptedMessage.serialize(), Base64.NO_WRAP)
Для групових чатів — Sender Key Distribution: один ключ шифрує для всієї групи, набагато ефективніше, ніж шифрування для кожного учасника окремо.
Критичний момент: ключі зберігаються лише на пристрої. Якщо користувач видалив додаток або змінив пристрій — історія повідомлень недоступна. Це trade-off, про який потрібно чесно попередити замовника. Рішення — опціональний бекап ключів через iCloud Keychain / Google Drive з дозволу користувача.
Зберігання та синхронізація історії
Локальна база — Room (Android) / Core Data (iOS). Повідомлення зберігаються з полями id, conversationId, senderId, encryptedContent, timestamp, status (sent / delivered / read).
Синхронізація при переустановці: якщо E2E включено без резервної копії ключів — історія недоступна. Якщо резервна копія існує або E2E опціональний — запитуємо історію з сервера постранично (cursor-based pagination).
Статуси доставки та прочитання — окремий механізм. Read receipt: отримувач надсилає ack з messageId через WebSocket. Відправник оновлює статус у локальній базі. При оффлайні — read receipts накопичуються та надсилаються пакетом при відновленні з'єднання.
Медіа та файли
Завантаження медіа — через окремий HTTP endpoint, не через WebSocket. Для зображень: спочатку завантажуємо thumbnail (JPEG, 200px, якість 40), потім за запитом — оригінал. Відео: HLS-стрімінг або поступове завантаження через AVPlayer / ExoPlayer.
Для великих файлів — chunked upload з можливістю возобновлення (S3 Multipart Upload). Індикатор прогресу через okhttp3.MultipartBody з кастомним RequestBody, який повідомляє про прогрес.
Медіа кешується локально через Glide (Android) / Kingfisher (iOS) з обмеженням розміру кеша. Старі медіафайли видаляються за LRU-політикою.
Push-сповіщення та бейджи
Push при закритому додатку — через FCM (Android) / APNs (iOS). На iOS UNNotificationServiceExtension дозволяє розшифрувати вміст сповіщення перед показом (для E2E-повідомлень) та оновити бейдж.
Для Android — FCM high-priority message з content-available. На Android 13+ потрібен явний запит дозволу POST_NOTIFICATIONS — без цього push не прийде.
Лічильник непрочитаних (бейдж) на іконці додатку: на iOS UNUserNotificationCenter.setBadgeCount() (iOS 16+) або через push-payload badge. На Android — ShortcutBadger library, оскільки немає нативного API для бейджів (кожен лончер має свій механізм).
Етапи та сроки
| Етап | Вміст | Срок |
|---|---|---|
| Проектування | Архітектура протоколу, E2E-рішення, база даних | 2–3 тижні |
| Backend | WebSocket-сервер, API, сховище | паралельно з клієнтом |
| iOS + Android | Реалтайм, UI, медіа, push | 10–14 тижнів |
| E2E | Signal Protocol або OMEMO | +3–4 тижні |
| Тестування | Навантаження, реалтайм, edge-кейси | 2–3 тижні |
MVP без E2E (базові чати, push, історія) — 3–4 місяці. З E2E та груповими чатами — 5–7 місяців. Вартість розраховується індивідуально.







