WebView SDK

Безопасная интеграция платформы «Сосед Соседу» в мобильное приложение управляющей компании

🔒
Архитектура безопасности: Bearer-токены никогда не передаются в URL. Все данные между нативным приложением и WebView шифруются AES-256-GCM. Одноразовые launch-токены TTL 10 секунд.

SDK позволяет встроить полную функциональность платформы в ваше существующее приложение УК:

  • Биржа соседских услуг с аукционами и эскроу
  • Чаты подъезда / дома / ЖК (FAFM chat-core)
  • Верификация жильцов и профили
  • Push-уведомления через FCM/APNs
  • Deep links на конкретные запросы и чаты

Требования

ПараметрЗначение
AndroidAPI 24+ (Android 7.0), WebView 80+
iOSiOS 13+, WKWebView
Хранилище токеновAndroid Keystore / iOS Keychain (обязательно)
Certificate PinningOkHttp / TrustKit
App SecretВыдаётся при подключении ЖК в ЛК УК
TLS1.2+ (1.3 рекомендуется)

Быстрый старт

Полная интеграция состоит из 4 шагов:

  1. Зарегистрировать устройство — получить access_token, refresh_token, session_key
  2. Получить launch_token — одноразовый токен для открытия WebView (TTL 10 сек)
  3. Открыть WebView — передать ?lt=..., получить HttpOnly-куки от сервера
  4. Настроить зашифрованный канал — передать session_key через postMessage

Регистрация устройства

Вызывается один раз при первом запуске или после полного выхода пользователя.

Запрос

POST /api/sdk/register Аутентификация: X-App-Signature
// Headers
X-App-Signature: "HMAC-SHA256(app_secret, device_fingerprint + timestamp)"
X-Device-Id:     "<device_fingerprint>"
X-App-Version:   "2.1.0"
Content-Type:    "application/json"

// Body
{
  "user_id":           "uuid-из-авторизации-ук",
  "device_fingerprint": "ANDROID_ID_or_IDFV",
  "platform":          "android" // или "ios"
}

Ответ 200

Успешная регистрация
{
  "access_token":  "eyJhbGci...",  // JWT, TTL 15 мин
  "refresh_token": "rt_abc123...", // opaque, TTL 30 дней
  "session_key":   "base64(AES-256)", // ключ сеанса
  "expires_in":    900,
  "token_type":    "bearer"
}

Как вычислить X-App-Signature

Kotlin (Android)
fun computeSignature(appSecret: String, deviceFp: String, timestamp: Long): String {
  val message = "$deviceFp$timestamp"
  val mac = Mac.getInstance("HmacSHA256")
  mac.init(SecretKeySpec(appSecret.toByteArray(), "HmacSHA256"))
  return Base64.encodeToString(mac.doFinal(message.toByteArray()), Base64.NO_WRAP)
}
⚠️
Rate limit: 1 регистрация / устройство / 5 минут. При превышении — 429 Too Many Requests.

Открытие WebView

Шаг 1 — получить launch_token

POST /api/sdk/launch
// Headers
Authorization: "Bearer <access_token>"

// Body
{
  "target":             "request/123", // или "chat/abc", "/"
  "device_fingerprint": "ANDROID_ID_or_IDFV"
}

// Ответ
{
  "launch_token": "lt_one_time_abc...",
  "ttl": 10  // секунд
}

Шаг 2 — открыть WebView

Kotlin (Android)
// НЕ передавать access_token в URL!
val url = "https://sosed-sosedu.ru/sdk/launch?lt=${launchToken}"
webView.loadUrl(url)

// Обязательные настройки WebView
webView.settings.apply {
  javaScriptEnabled = true
  domStorageEnabled = true
  allowFileAccess = false
  allowContentAccess = false
}

// Кастомный User-Agent с package name
webView.settings.userAgentString =
  "SosedSoseduSDK/1.0 (com.yourcompany.app; android)"
ℹ️
Сервер установит HttpOnly + Secure + SameSite=Strict куки автоматически. Вам не нужно управлять сессией вручную. launch_token удаляется после первого использования.

Что проверяет сервер при GET /sdk/launch

  1. Валидность launch_token (одноразовый, TTL 10 сек)
  2. Совпадение device_fingerprint с зарегистрированным
  3. Заголовок User-Agent содержит SDK-идентификатор
  4. Заголовок X-Requested-With = package name вашего приложения

Обновление токенов

POST /api/sdk/refresh
// Headers
X-Device-Id: "<device_fingerprint>"

// Body
{
  "refresh_token": "rt_старый..."
}

// Ответ 200
{
  "access_token":  "eyJ новый...",
  "refresh_token": "rt_новый...", // старый стал недействителен
  "expires_in":    900
}

// 401 — refresh истёк, требуется повторный логин в приложении УК

Правила обновления:

  • Превентивно — каждые 10 минут (если приложение активно)
  • Реактивно — при получении 401 от любого запроса из WebView
  • Мьютекс — только один параллельный refresh-запрос
  • Ротация — каждый refresh выдаёт новый refresh_token
Swift (iOS) — Token Manager
actor TokenManager {
  private var isRefreshing = false
  private var waiters: [CheckedContinuation<String, Error>] = []

  func getValidToken() async throws -> String {
    if let token = Keychain.read("access_token"), !isExpired(token) {
      return token
    }
    if isRefreshing {
      return try await withCheckedThrowingContinuation { waiters.append($0) }
    }
    isRefreshing = true
    defer { isRefreshing = false }
    let newToken = try await performRefresh()
    waiters.forEach { $0.resume(returning: newToken) }
    waiters.removeAll()
    return newToken
  }
}

Зашифрованный канал (Encrypted Bridge)

Все данные между нативным приложением и WebView шифруются AES-256-GCM.

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

  1. WebView загружается и сигнализирует о готовности: postMessage({type:"ready"})
  2. Нативное приложение передаёт session_key через postMessage (не через URL!)
  3. WebView сохраняет ключ в sessionStorage (очищается при закрытии)
  4. Все дальнейшие сообщения шифруются

Формат сообщений

JavaScript (WebView → Native)
// Отправка зашифрованного события
async function sendEvent(payload) {
  const { iv, ciphertext, tag } = await encryptAESGCM(payload, sessionKey);
  window.ReactNativeWebView?.postMessage(JSON.stringify({
    v: 1,
    nonce: crypto.randomUUID(),      // защита от replay
    ts: Date.now(),
    iv, data: ciphertext, tag
  }));
}

// Примеры событий из WebView
sendEvent({ type: "new_bid",     request_id: "abc", amount: 500 });
sendEvent({ type: "deal_done",   request_id: "abc" });
sendEvent({ type: "new_message", chat_id: "xyz", count: 3 });
sendEvent({ type: "error",       code: "SESSION_EXPIRED" });
Kotlin (Native → WebView)
// Отправка команды в WebView
suspend fun sendCommand(webView: WebView, command: Map<String, Any>) {
  val encrypted = AesGcm.encrypt(command.toJson(), sessionKey)
  val payload = mapOf(
    "v" to 1,
    "nonce" to UUID.randomUUID().toString(),
    "ts" to System.currentTimeMillis(),
    "iv" to encrypted.iv,
    "data" to encrypted.ciphertext,
    "tag" to encrypted.tag
  )
  withContext(Dispatchers.Main) {
    webView.evaluateJavascript(
      "window.dispatchEvent(new MessageEvent('message',{data:'${payload.toJson()}'}))",
      null
    )
  }
}

// Команды в WebView
sendCommand(webView, mapOf("type" to "navigate", "target" to "chat/xyz"))
sendCommand(webView, mapOf("type" to "refresh_token", "new_token" to newJwt))

Защита от replay-атак

Каждое сообщение содержит nonce (UUID) и ts (timestamp). Сервер отклоняет:

  • Повторные nonce (хранится в памяти 60 секунд)
  • Сообщения с ts старше 30 секунд

Матрица угроз

УгрозаЗащита
Перехват URL с токеномlaunch_token — одноразовый, TTL 10 сек, не содержит access_token
Копирование URL в браузерПроверка User-Agent, X-Requested-With, device_fingerprint
Перехват postMessageAES-256-GCM шифрование с session_key
Повторное использование refresh_tokenРотация при каждом refresh, старый недействителен немедленно
Извлечение токенов с устройстваAndroid Keystore / iOS Keychain (аппаратное хранилище)
Подмена нативного приложенияHMAC-SHA256 с app_secret (обфусцирован в коде)
MITM-атакаTLS 1.3 + Certificate Pinning
XSS в WebViewCSP-заголовки, X-Frame-Options: SAMEORIGIN, sanitize всех входящих данных
Утечка access_token в логиТокен никогда не в URL, только в Authorization header + HttpOnly куки
Replay-атакаnonce + ts в каждом сообщении, TTL 30 секунд

Certificate Pinning

Android — OkHttp
val pinner = CertificatePinner.Builder()
  .add("sosed-sosedu.ru", "sha256/ХЕश_СЕРТИФИКАТА_BASE64=")
  .add("sosed-sosedu.ru", "sha256/BACKUP_PIN_BASE64=") // резервный пин
  .build()

val client = OkHttpClient.Builder()
  .certificatePinner(pinner)
  .build()
iOS — TrustKit
let trustKitConfig: [String: Any] = [
  kTSKSwizzleNetworkDelegates: false,
  kTSKPinnedDomains: [
    "sosed-sosedu.ru": [
      kTSKPublicKeyHashes: [
        "ХЕШ_СЕРТИФИКАТА_BASE64=",
        "BACKUP_PIN_BASE64="
      ]
    ]
  ]
]
TrustKit.initSharedInstance(withConfiguration: trustKitConfig)
⚠️
Ротация сертификатов: Обновляйте пины через обновление приложения заблаговременно (за 30 дней до истечения). Мы уведомим вас за 60 дней до смены сертификата.


Push-уведомления

Push-payload всегда зашифрован. Расшифровка происходит в нативном приложении.

Структура зашифрованного push-payload
{
  "data": {
    "v":    "1",
    "blob": "AES-256-GCM-ciphertext-base64",
    "iv":   "base64",
    "tag":  "base64",
    "url":  "request/123"  // target для deep link
  }
}

// После расшифровки blob:
{
  "type":    "new_bid",
  "title":   "Новый отклик на ваш запрос",
  "body":    "Иван предложил 400₽",
  "request_id": "123"
}

Типы push-событий

typeКогдаПриоритет
sosSOS от соседа (дозор, экстренная ситуация)Максимальный
uk_broadcastОфициальное от УКВысокий
new_bidОтклик на аукционСредний
deal_statusИзменение статуса сделкиСредний
new_messageНовое сообщение в чатеОбычный
verificationРешение по верификацииВысокий

События WebView

WebView отправляет события в нативное приложение через зашифрованный postMessage.

typeДанныеОписание
readyWebView готов принять session_key
new_messagechat_id, countНепрочитанные сообщения
new_bidrequest_id, amount, providerНовый отклик на аукцион
deal_donerequest_id, escrow_idСделка завершена
navigate_backНажата кнопка «Назад» в WebView
session_expiredСессия устарела, нужен новый launch_token
errorcode, messageОшибка в WebView

API Reference

МетодПутьОписание
POST/api/sdk/registerРегистрация устройства
POST/api/sdk/launchПолучить launch_token
GET/sdk/launch?lt=…Открыть WebView (устанавливает куки)
POST/api/sdk/refreshОбновить access_token
POST/api/sdk/logoutИнвалидировать все токены устройства

Коды ошибок

HTTPКодОписание
401TOKEN_EXPIREDaccess_token истёк — выполните refresh
401REFRESH_EXPIREDrefresh_token истёк — требуется повторный логин
401INVALID_SIGNATUREНеверный X-App-Signature
403DEVICE_MISMATCHdevice_fingerprint не совпадает
403LAUNCH_TOKEN_USEDlaunch_token уже использован или истёк
403UA_MISMATCHUser-Agent не соответствует SDK
429RATE_LIMITEDПревышен rate limit регистрации
503SERVICE_UNAVAILABLEПлановое обслуживание

Changelog

v1.0.0 — 2026-06-23

  • Первый публичный релиз SDK
  • Bearer-токены с ротацией через refresh
  • Одноразовые launch_token (TTL 10 сек)
  • AES-256-GCM зашифрованный postMessage-канал
  • Certificate Pinning для Android и iOS
  • Push-payload шифрование
  • Deep links: request, chat, profile, create, verification
📧
Нужна помощь? Напишите нам на sdk@sosed-sosedu.ru или в Telegram @soseddev. Мы поддерживаем интеграцию на всех этапах.
© 2026 Сосед Соседу · WebView SDK v1.0
Главная Для УК Поддержка