Перейти к основному содержимому
Версия: next

Встраивание чата поддержки в Android-приложение

Для команд мобильных приложений. SDK Android — ru.shtab.helpdesk:helpdesk-android. Проверить встраивание до своей установки можно в песочнице мобильных SDK. iOS — отдельное руководство.

Beta 1.0.0-beta.2. Изменения и известные ограничения версии приходят с комплектом поставки. До 1.0 API может меняться, после — только патчи 1.0.x.

1. Как устроено​

Тонкий натив и весь чат в WebView. SDK открывает свой экран (HelpdeskChatActivity) с WebView, который грузит UI чата с инсталляции — https://<публичный домен>/widget/mobile/. Функции чата приходят в приложение обновлением сервера, без нового SDK и ревью стора.

Если UI инсталляции недоступен — нет сети при открытии чата, сервер старше и не знает /widget/mobile/, UI не поддерживает версию моста SDK (bridge.json), страница не загрузилась или не ответила за 20 с, — SDK показывает вшитую копию UI той же сборки. Она открывается под тем же адресом инсталляции, поэтому сессия, очередь неотправленных сообщений и realtime у обеих копий общие.

Натив даёт UI через мост: chat token приложения, push-токен устройства, открытие обращения из push, выбор файлов, переход по ссылкам в браузер, состояние приложения (в фоне realtime отключается).

2. Требования​

Android8.0 (API 26) и новее
WebViewAndroid System WebView (Chromium) 111 и новее — цель сборки UI чата; с Google Play WebView обновляется сам. На устройствах без Google Play WebView обновляет производитель, и он может быть старше — тогда чат не открывается (см. ниже)
Сборка приложенияcompileSdk 35+, Java 17 (байт-код SDK), Kotlin 2.1+
Зависимости SDKandroidx.core 1.16, androidx.activity 1.10, androidx.webkit 1.18 (Apache-2.0); Firebase SDK не тянет — FCM подключает приложение
Инсталляцияпубличный домен с /widget/mobile/ и public API, размещение приложения (авторизованная зона, «Интеграции → Мобильные приложения»), издатель chat token (TrustedIssuer)

Устаревший WebView​

При открытии чата SDK читает major-версию Chromium WebView (Chrome/<major> из user agent WebView, иначе — версия пакета WebViewCompat.getCurrentWebViewPackage). Если она ниже 111 или WebView не умеет WEB_MESSAGE_LISTENER, UI чата не загружается (UI kit на старом движке остался бы без стилей):

  • экран чата показывает «Чат поддержки недоступен: обновите Android System WebView» без кнопки «Повторить»; пользователь закрывает его «Назад»;
  • логгер приложения (HelpdeskOptions.logger, §9) получает ERROR с кодом webview.version_unsupported (WebView старше 111) или webview.outdated (нет WEB_MESSAGE_LISTENER) — по нему приложение может, например, предложить обновление WebView или другой канал поддержки;
  • версию прочитать не удалось — чат открывается как обычно, логгер получает WARN webview.version_unknown.

3. Подключение​

AAR и POM лежат в офлайн-комплекте релиза: mobile/android/maven/ — каталог Maven. Скопируйте его в репозиторий приложения или в свой Maven.

// settings.gradle.kts
dependencyResolutionManagement {
repositories {
google()
mavenCentral()
maven { url = uri("third-party/shtab-helpdesk-maven") }
}
}

// app/build.gradle.kts
dependencies {
implementation("ru.shtab.helpdesk:helpdesk-android:1.0.0-beta.2")
}

4. Настройка и открытие чата​

class App : Application() {
override fun onCreate() {
super.onCreate()
HelpdeskSdk.configure(
this,
installationUrl = "https://support.example.com", // только origin, https
placementKey = "pk_…", // ключ приложения из консоли
options = HelpdeskOptions(appVersion = BuildConfig.VERSION_NAME),
)
HelpdeskSdk.setChatTokenProvider { callback ->
// Бэкенд приложения подписывает chat token для вошедшего пользователя:
// iss, aud = ключ размещения, sub, exp ≤ 10 мин, jti. Нет пользователя — null.
backend.issueSupportToken { token -> callback.onChatToken(token) }
}
}
}

// Кнопка «Поддержка»
HelpdeskSdk.openChat(activity)
  • Провайдер вызывается при каждом открытии чата и когда сессия чата кончилась; отвечать можно с любого потока, один раз. Токен одноразовый, SDK его не хранит. Нет ответа за 15 с — чат открывается без входа и показывает «Войдите в приложении снова».
  • http:// принимается только в debuggable-сборке (стенд), в release — исключение IllegalArgumentException.
  • В приложении сессия чата начинается только с chat token приложения: cookie, оставшаяся в WebView от прежнего пользователя, сессию не возвращает.
  • HelpdeskEventListener (setEventListener) сообщает о непрочитанных в открытом чате и о том, что chat token приложения отклонён (onSessionExpired).

5. Выход и смена пользователя​

При выходе пользователя из приложения вызовите HelpdeskSdk.logout(): экран чата закрывается, сессия завершается на сервере (сервер отзывает push этого устройства), cookie сессии и очередь неотправленных сообщений в WebView удаляются, обращение из push забывается. Нет сети — сервер узнает при следующем configure. Следующий пользователь входит своим chat token и не видит данных прежнего.

6. Push (FCM)​

Сервер шлёт push без текста: в notification — «Новый ответ — Откройте чат поддержки, чтобы прочитать» на языке размещения, в data — плоские ключи conversation_id и kind (new_reply; тестовый из «Проверка приёма» — test без обращения). Проект Firebase, сервисный аккаунт и канал уведомлений задаются в консоли («Интеграции → Мобильные приложения → Android»).

class Messaging : FirebaseMessagingService() {
override fun onNewToken(token: String) {
Handler(Looper.getMainLooper()).post { HelpdeskSdk.registerPushToken(token) }
}

override fun onMessageReceived(message: RemoteMessage) {
// В фоне уведомление показывает FCM; в foreground — приложение.
if (HelpdeskNotifications.isHelpdeskPush(message.data)) {
HelpdeskNotifications.show(this, message.data, R.drawable.ic_notification,
message.notification?.title, message.notification?.body)
}
}
}

// Launcher-activity: тап по уведомлению приносит ключи push в intent
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
HelpdeskSdk.handleNotification(intent, this) // true — push инсталляции, чат открыт
}

override fun onNewIntent(intent: Intent) {
super.onNewIntent(intent)
HelpdeskSdk.handleNotification(intent, this)
}
  • Канал: HelpdeskNotifications.createChannel(context, "Ответы поддержки") (id shtab_helpdesk, или свой — тот же, что в консоли).
  • Разрешение POST_NOTIFICATIONS (Android 13+) приложение спрашивает само, с объяснением и кнопкой «Отложить» (как в sample).
  • Обращение из push открывается после входа пользователя чата; чужое обращение показывается как «Нет доступа к обращению».
  • Push-токен хранится в SDK (Keystore) и регистрируется для клиента при каждом входе в чат: новый токен, полученный при закрытом чате, уходит на сервер при следующем открытии чата.
  • Проверка без сервера: в debug-сборке HelpdeskDebug.testPushData(context, conversationId) даёт тот же data, что шлёт сервер, а HelpdeskDebug.testNotificationIntent(...) — intent тапа. В release-сборке оба бросают IllegalStateException.

7. Файлы​

Скрепка в чате открывает системный выбор документов: изображения и PDF. Размер сверх лимита отклоняется до загрузки; лимит и типы организации проверяет сервер и объясняет отказ в чате. Файл передаётся странице по одноразовому handle, путь на устройстве UI не видит.

«Скачать» у файла в чате: UI отдаёт байты файла SDK по мосту, SDK сохраняет его сам. Android 10+ — в «Загрузки» (MediaStore, разрешений не нужно), с тостом «Файл сохранён в «Загрузки»». Android 8–9 — системное окно «Сохранить», место выбирает пользователь: SDK не добавляет приложению разрешения хранилища. Копия SDK лежит в cacheDir приложения, пока файл сохраняется, и удаляется с экраном чата.

8. Безопасность​

ЧтоГде
chat token приложенияне хранится: используется один раз
сессия чатакороткий bearer — в памяти страницы; cookie сессии (HttpOnly) — в хранилище cookie WebView, удаляется при logout
push-токен, обращение из push, отложенный logoutSharedPreferences ru.shtab.helpdesk, AES-256-GCM ключом Android Keystore
неотправленные сообщенияIndexedDB WebView (origin инсталляции), удаляется при logout
  • Ошибка TLS — отказ: SDK не переопределяет onReceivedSslError, «продолжить всё равно» нет.
  • Мост принимает сообщения только из главного фрейма origin инсталляции; ссылки и любые переходы с него открываются в браузере.
  • Значения SDK не нужны в резервной копии (ключ Keystore в неё не попадает, восстановленные значения не читаются и удаляются): исключите ru.shtab.helpdesk.xml из dataExtractionRules/fullBackupContent.

Certificate pinning (по умолчанию выключено)​

HelpdeskOptions(certificatePins = setOf("sha256/<основной ключ>", "sha256/<резервный ключ>"))

Пины — SHA-256 SubjectPublicKeyInfo в base64 (ключ сервера или CA заказчика, всегда с резервным). С пинами SDK сам проверяет сертификат при чтении bridge.json, при logout и загружает страницу и ассеты UI своим соединением; не совпали — чат не открывается («Не удалось проверить сервер поддержки»).

Ограничение WebView. Запросы public API (fetch) и WebSocket realtime делает сам WebView: у Android WebView нет обработчика, где SDK мог бы проверить сертификат успешного TLS-соединения. Для них добавьте те же пины в Network Security Config приложения — распространяется ли он на WebView-трафик, ещё подтверждается на устройстве:

<network-security-config>
<domain-config>
<domain includeSubdomains="false">support.example.com</domain>
<pin-set expiration="2027-12-31">
<pin digest="SHA-256">основной ключ base64</pin>
<pin digest="SHA-256">резервный ключ base64</pin>
</pin-set>
</domain-config>
</network-security-config>

Остаточный риск. Пины SDK доказывают только то, что пиннованный ключ видит собственное соединение SDK. Обмен chat token и выдача bearer (POST /public/api/v1/session/*), остальные POST public API и WebSocket realtime проверяет TLS платформы. MITM, который отличает соединения WebView от соединений SDK (у них разный TLS-отпечаток) и пропускает последние, пинами SDK не обнаруживается. Поэтому обязательно:

  • не добавляйте <certificates src="user"/> в base-config и в domain-config домена инсталляции — только в <debug-overrides>. Тогда при targetSdk ≥ 24 ни приложение, ни WebView не доверяют пользовательским CA, включая CA, поставленные MDM (они ложатся в пользовательское хранилище), и для перехвата нужен системный CA (root, прошивка) или неверно выпущенный публичный сертификат;
  • при включении пинов добавьте тот же pin-set в Network Security Config (пример выше): действует ли он на трафик WebView, ещё подтверждается на устройстве — до подтверждения считайте POST и WebSocket чата защищёнными только доверием платформы;
  • устройства с root и пользовательскими прошивками этой защиты не дают; если это неприемлемо, проверяйте целостность устройства средствами приложения до открытия чата.

9. Диагностика​

HelpdeskOptions(logger = HelpdeskLogger { level, code -> … }) — только коды, без текста, токенов и персональных данных; по умолчанию WARN/ERROR — в Logcat (тег ShtabHelpdesk). Основные коды:

КодЧто значит
ui.installationоткрыт UI инсталляции
ui.bundled.offline, …unreachable, …manifest_unavailable, …manifest_malformed, …bridge_unsupported, …page_failed, …no_handshake, …bridge_refusedоткрыт вшитый UI и почему
tls.pin_mismatchсертификат не совпал с пинами
token.no_provider, token.timeout, token.provider_failed, token.malformedchat token не получен
ui.push.register_failedсервер не принял push-токен (например, гость)
logout.deferredlogout будет отправлен при следующем configure
save.failed, save.busy, save.no_picker, save.out_of_order, save.wrong_size, save.refused, save.bad_piece, save.write_failedфайл из чата не сохранён: копия, окно «Сохранить» уже открыто или его нет, части от UI не сошлись
webview.version_unsupported, webview.outdatedWebView старше 111 / без WEB_MESSAGE_LISTENER: чат не загружен, экран просит обновить Android System WebView (§2)
webview.version_unknownверсию WebView не прочитать — чат открыт без проверки версии
webview.renderer_goneпроцесс WebView упал (экран закрывается, приложение живо)