Сетевые настройки
Сетевые настройки подключения к серверу описываются моделью ChatNetworkConfig.
Инициализация:
Конструктор по умолчанию создаёт модель со стандартными настройками:
var chatNetworkConfig = ChatNetworkConfig()
Можно настраивать отдельные компоненты подключения:
httpConfig: Настройки REST APIwsConfig: Настройки WebSocket APIsslPinning: Настройки SSL‑пиннинга
ChatNetworkConfig — struct, передаётся внутрь ChatConfig по значению. После вызова ChatCenterUISDK(chatConfig:) SDK работает со своей копией; изменения chatConfig.networkConfig или его вложенных полей со стороны интегратора не действуют. Все поля задавайте до передачи ChatConfig в конструктор SDK. Подробнее — Настройки SDK.
Настройки REST API (HTTPConfig)
Параметры HTTP(S)-подключения для REST API:
| Поле | Тип | Обязательный | Описание |
|---|---|---|---|
connectionTimeout | TimeInterval (сек) | Нет (default 30) | Таймаут бездействия REST-запроса: применяется как URLRequest.timeoutInterval и покрывает все фазы (установление соединения + ожидание ответа). Относится только к REST; для WebSocket см. WSConfig.connectionTimeout |
downloadTimeout | TimeInterval (сек) | Нет (default 30) | Таймаут бездействия запроса скачивания файла: применяется как URLRequest.timeoutInterval (та же семантика, что у connectionTimeout) |
uploadTimeout | TimeInterval (сек) | Нет (default 120) | Применяется одновременно как URLSessionConfiguration.timeoutIntervalForRequest (idle-таймаут) и timeoutIntervalForResource (общий лимит на загрузку ресурса). Это значит: для default 120 суммарное время выгрузки файла не должно превышать 120 сек — для больших файлов по медленному каналу требуется увеличить значение. Upload-сессия SDK также жёстко настроена с waitsForConnectivity = true: при отсутствии сети запрос ожидает её восстановления, а не падает с ошибкой немедленно (REST и download — наоборот, падают сразу). Общее время ожидания всё равно ограничено uploadTimeout |
Настройки WebSocket-подключения (WSConfig)
Параметры WS(S)-подключения для real-time-сообщений:
| Поле | Тип | Обязательный | Описание |
|---|---|---|---|
connectionTimeout | TimeInterval (сек) | Нет (default 30) | Таймаут на WebSocket upgrade-запрос: значение применяется и как URLSessionConfiguration.timeoutIntervalForRequest, и как URLRequest.timeoutInterval upgrade-запроса |
sendTimeout | TimeInterval (сек) | Нет (default 20) | Используется одновременно как: (1) интервал таймера, который проверяет статусы неотправленных сообщений (таймер работает пока открыт экран чата и активен диалог; при уходе в фон останавливается); (2) порог, после которого сообщение в статусе .sending помечается как .failed при отсутствии ответа сервера. Если сервер вернул ответ с ошибкой по WebSocket, сообщение становится .failed сразу, минуя таймер. Гарантия «не позже чем через 2 × sendTimeout» относится только к timer-пути |
Настройки SSL пиннинга
При использовании данного функционала не рекомендуется устанавливать только один доверенный сертификат. В случае его отзыва или истечения срока действия SDK перестанет подключаться к серверу. Используйте дополнительные резервные сертификаты и своевременно обновляйте их.
Параметры SSLPinningConfig:
| Поле | Тип | Обязательный | Описание |
|---|---|---|---|
allowUntrustedSSLCertificate | Bool | Нет (default false) | При true SDK принимает любой сертификат сервера без проверки подписи. Небезопасно для продакшена — см. :::danger ниже. Игнорируется, если trustedCertificates непуст — pinning имеет приоритет |
trustedCertificates | [ChatSSLCertificate] | Нет (default []) | Список доверенных сертификатов в формате DER. При его задании SDK полностью обходит SecTrustEvaluateWithError — игнорируются hostname, срок действия и проверка отозванности (OCSP/CRL). Проверяется только, совпадает ли публичный ключ любого сертификата из TLS-цепочки сервера (leaf, промежуточный или root) с одним из ключей списка (public key pinning). Это позволяет пинить как leaf-сертификат, так и intermediate/root CA — последнее переживает ротацию leaf без обновления приложения |
allowUntrustedSSLCertificate = true имеет эффект только при пустом trustedCertificates — и в этом случае SDK принимает любой сертификат сервера, что делает приложение уязвимым к атакам man-in-the-middle (MITM). При непустом trustedCertificates флаг игнорируется (pinning имеет приоритет), поэтому MITM-уязвимости через него нет. Тем не менее, не оставляйте allowUntrustedSSLCertificate = true в боевых сборках: если в одной из конфигураций сборки trustedCertificates окажется пустым (например, не были найдены файлы через Bundle.main.url(forResource:)), приложение лишится защиты без явного сигнала.
trustedCertificates заменяет системную валидацию TLS проверкой публичного ключа — это работает и с обычными CA-подписанными, и с самоподписанными сертификатами, и с сертификатами с истёкшим сроком действия.
Сертификат передается в виде модели ChatSSLCertificate, в которой указывается локальный путь до сертификата:
var chatNetworkConfig = ChatNetworkConfig()
if let mainCert = Bundle.main.url(forResource: "main_cert", withExtension: "cer"),
let backupCert = Bundle.main.url(forResource: "backup_cert", withExtension: "cer") {
chatNetworkConfig.sslPinning.trustedCertificates = [ChatSSLCertificate(contentsOf: mainCert), ChatSSLCertificate(contentsOf: backupCert)]
}
Формат сертификата
ChatSSLCertificate(contentsOf:) ожидает DER-кодированное содержимое — SDK передаёт байты из файла напрямую в SecCertificateCreateWithData, расширение не проверяется (традиционно используются .cer или .der, но .crt или файл без расширения тоже сработают, если содержимое валидно). PEM-сертификаты (-----BEGIN CERTIFICATE-----…) не поддерживаются — сконвертируйте перед добавлением в bundle:
openssl x509 -in cert.pem -outform DER -out cert.cer
Добавьте получившийся .cer в bundle приложения (Build Phases → Copy Bundle Resources). Загрузка через Bundle.main.url(forResource:withExtension:) вернёт nil, если файла нет — как в примере выше, обработайте это явно: молчаливое отсутствие сертификата эквивалентно отключению пиннинга.
Если файл по contentsOf: существует, но не является валидным DER (например, в bundle случайно положили PEM, либо файл повреждён), ChatSSLCertificate сохранит внутренний certificate = nil и не выбросит ошибку из конструктора. SDK пишет сообщение об ошибке в свой лог-поток (видно при ChatLoggerConfig(logLevel: .all) — см. Настройки логирования), но в коде интегратора признака отказа нет.
Эффект отличается от случая «пустой массив trustedCertificates»: массив с битым ChatSSLCertificate не пуст, поэтому SDK всё равно входит в режим pinning. Битые элементы пропускаются циклом и не блокируют валидные: если в массиве есть хотя бы один валидный ChatSSLCertificate с совпадающим публичным ключом, соединение установится. Если же все элементы битые — SDK не находит совпадений и отклоняет все TLS-соединения. Внешне это выглядит как «сеть не работает», а не «pinning выключен».
После добавления сертификата проверяйте, что соединение реально устанавливается, и одновременно — что подмена сервера заведомо «чужим» сертификатом приводит к разрыву.
Диагностика SSL-ошибок
Если соединение не открывается после включения пиннинга:
| Симптом | Возможная причина | Что проверить |
|---|---|---|
Соединение обрывается на handshake (в логе SDK сообщение SSL CERTIFICATES Pinning error - server and pinned certificates are not equals) | Ни один публичный ключ из TLS-цепочки сервера не совпадает с публичными ключами trustedCertificates | Включить ChatLoggerConfig(logLevel: .all) и сравнить hex-значения строк SSL Server public key: (все сертификаты цепочки сервера) и SSL Pinned certificate public key: (приложенные) — хотя бы одна пара должна совпадать. SDK логирует ключи в raw-формате (SecKeyCopyExternalRepresentation: PKCS #1 для RSA, X9.63 для EC), поэтому сторонние команды вроде openssl x509 -pubkey | openssl pkey -pubin -outform DER | openssl dgst -sha256 дадут другой хеш (SubjectPublicKeyInfo) — для сравнения с SDK-логом такие команды не подходят |
| Подключение работало, потом перестало (без обновления приложения) | Серверный сертификат был ротирован, в приложении нет нового | Связаться с поддержкой edna; временный fallback — резервный сертификат, если он был добавлен заранее |
nil после Bundle.main.url(forResource:) | Файл не попал в Copy Bundle Resources или неверно разбиты имя/расширение (forResource: "cert.cer" вместо forResource: "cert", withExtension: "cer") | Проверить target → Build Phases → Copy Bundle Resources (файл должен быть в списке); проверить, что в Bundle.main.url(forResource:withExtension:) имя файла передано без расширения, а расширение — вторым аргументом |
| Ошибка только в Release-сборке | ATS-конфигурация для домена различается между Debug и Release (например, NSAllowsArbitraryLoads: true стоит только в Debug) | Проверить NSAppTransportSecurity в Info.plist обеих сборок; при правильных серверных хостах (HTTPS с действительными сертификатами) спец-настройки ATS не требуются |
Включите подробное логирование SDK (ChatLoggerConfig(logLevel: .all)) — сообщения о SSL-handshake пишутся в общий поток логов и приходят делегату ChatCenterUISDKDelegate через метод chatCenterUI(chatCenter:didLog:).
Ротация сертификатов
Серверный сертификат имеет срок действия. Чтобы избежать blackout приложения при ротации:
- Запросите расписание у поддержки edna — за сколько до истечения вы получите новый сертификат.
- Всегда держите минимум 2 сертификата в
trustedCertificates: текущий + резервный (next). Когда edna ротирует серт, новый уже окажется в trust-листе приложения. - При выкатке версии приложения убедитесь, что в bundle включён новый сертификат, до того как старый перестанет работать на сервере.
- Не используйте
allowUntrustedSSLCertificate = trueв продакшене даже временно как обходной путь — это сводит на нет защиту от MITM на всё время фикса.
Связанные разделы
- Настройки подключения — URL серверов
- Настройки SDK — параметры функционала чата
- Инициализация и настройка SDK — полный пример инициализации
- Решение проблем — диагностика проблем подключения