Транспорт: WebSocket, резервный HTTP и переподключение
Как виджет общается с сервером чата: адреса, подпротокол rc.v1, формат кадров, коды ошибок и закрытия, лимиты, переподключение с seq и удалением дубликатов, резервный HTTP через SSE и длинный опрос.
Эта страница описывает обмен между виджетом в браузере и сервером чата. Она нужна для устранения неполадок (вкладка сети инструментов разработчика), правил прокси и межсетевых экранов и Content Security Policy.
Важно: сетевой протокол — интерфейс между нашим виджетом и нашим сервером. Собственные клиенты, использующие протокол напрямую, официально не поддерживаются; поля могут расширяться в версии 1. Для собственных интерфейсов используйте JavaScript API, для серверных подключений — вебхуки и серверный API.
Адреса
- WebSocket:
wss://ws.reactive.chat/rc_wss/ - Резервный HTTP, офлайн-форма, загрузки: тот же адрес с
https://, то естьhttps://ws.reactive.chat/rc_wss/h/…,https://ws.reactive.chat/api/offline/<SITE_KEY>иhttps://ws.reactive.chat/rc_wss/upload/… - Файлы виджета:
https://cdn.reactive.chat/
Как виджет находит адрес WebSocket:
data-rc-ws-urlв теге загрузчика,window.ReactiveChatSettings.wsUrlилиReactiveChat('boot', { wsUrl }): если задано, используется это значение.- Иначе выводится из расположения
loader.js:wss://ws.+ последние две части домена +/rc_wss/.cdn.reactive.chatпревращается вwss://ws.reactive.chat/rc_wss/.
Отсюда две ловушки:
- Если
loader.jsпредоставляется с вашего домена, виджет выводит адрес в вашем домене. Тогда задайтеdata-rc-ws-url(см. Быстрый старт). - С двухчастными окончаниями вроде
.co.ukвывод неверен. Явно задайтеdata-rc-ws-url.
Маршрут WebSocket не обрабатывает параметры запроса.
Установление подключения
- Момент: виджет подключается не при загрузке страницы, а при первом настоящем взаимодействии (движение мыши, нажатие, клавиша, колесо, касание, фокус). Подключается сразу, если окно открыто или открывается, известен разговор посетителя либо есть неотправленные сообщения. С
data-rc-connect="load"на любом элементе (обычно теге загрузчика) илиwindow.ReactiveChatSettings.connect = 'load'подключается при загрузке, как прежде. Правила со временем после просмотра страницы отсчитываются от первого взаимодействия. - Подпротокол: виджет открывает
new WebSocket(url, 'rc.v1'), сервер возвращаетrc.v1. Версия протокола не в адресе, а в каждом кадре (v: 1). - Только текстовые кадры, UTF-8; один кадр — одно сообщение. Без сжатия (
permessage-deflateотключён на сервере). - Рукопожатие: после
openвиджет отправляетinitне позже 5 секунд; сервер отвечаетinit_ok. Еслиopenнет за 8 секунд, попытка неудачна; еслиinit_okнет за 10 секунд, виджет переподключается. - Проверка источника: сервер сравнивает имя хоста заголовка
Originс доменами проекта (Разрешённые источники в аккаунте). Схема и порт не учитываются; поддомены подходят (www.your-company.comсоответствуетyour-company.com,notyour-company.com— нет). При отказе:origin_denied, затем закрытие 1008, без повтора. Если в проекте нет домена, источник не проверяется; всегда указывайте домены.
Формат кадра
Каждое сообщение в обе стороны имеет вид:
{ "v": 1, "t": "msg", "id": "01JAB3C4D5E6F7G8H9JKMNPQRS", "ts": 1792500000000, "d": { } }
- v (целое, обязательно) — версия протокола, сейчас
1. - t (строка, обязательно) — тип сообщения,
[a-z0-9_]{1,24}. - id (строка, обязательно) — ULID: 26 символов Crockford Base32, только прописные. Также ключ удаления дубликатов.
- ts (целое, обязательно) — миллисекунды с 1970 года, только для диагностики.
- d (объект, обязательно) — полезная нагрузка, никогда не
null, при пустом значении{}.
Неизвестные типы и поля молча игнорируются: старые виджеты остаются совместимы с новыми серверами.
Обзор типов сообщений
От виджета серверу:
- init — регистрация подключения: ключ сайта, ID сеанса, посетителя, вкладки и разговора, язык, согласие, подписанная идентификация при наличии (
user), контекст страницы (ctx), точка продолжения (resume). - msg — сообщение посетителя (текст, до 5 вложений, ответ на быстрые ответы или формы).
- typing — индикатор набора (
start/stop). Содержимое поля никогда не передаётся. - read — прочитано до
seq. - ctx — изменение контекста, например при навигации SPA или после
identify. - csat — оценка (палец вверх/вниз, комментарий до 1000 символов).
- upload_req — запрос разрешения загрузки (имя, MIME-тип, размер, SHA-256).
- transcript, forget, consent — переписка на почту, удаление своих данных, выдача/отзыв согласия.
- ping — поддержание связи.
От сервера виджету:
- init_ok — подтверждённые ID, история, дизайн, функции и лимиты (ниже), результат проверки личности (
identity.verified). - msg — сообщения (включая эхо собственного сообщения посетителя с
client_id). - bot_typing, bot_stream — ассистент думает или пишет;
bot_streamвсегда передаёт весь видимый пока текст, не чаще раза в 150 мс. - typing, read, handoff, queue_pos, proactive, conv_list, error, pong.
Коды ошибок
Ошибка приходит кадром t: "error" с d.code, d.message (английский, только для разработчиков), d.retry (no, immediate, backoff, after_reinit) и при необходимости d.retry_after_ms, d.detail. Список закрытый; неизвестный код трактуется как internal.
- bad_json — некорректный JSON; после трёх случаев закрытие 1008.
- bad_envelope — нарушение формата (также ULID отклоняется более чем на 24 ч,
detail.reason: stale_or_future). - unsupported_version — версию нельзя согласовать; закрытие 4001.
- unknown_site_key — ключ сайта неизвестен, проект приостановлен или архивирован; закрытие 4002.
- origin_denied — источника нет среди доменов проекта; закрытие 1008.
- visitor_blocked — посетитель заблокирован; закрытие 1008.
- auth_failed — проверка личности не пройдена,
detail.reason:no_hash,bad_hash,bad_exp,expired,exp_too_far. Сеанс продолжается анонимно. - not_initialized / already_initialized — сообщение до
initили второйinit. - init_timeout — нет
initза 5 с; закрытие 4004. - conversation_not_found / conversation_closed
- rate_limited — ограничение частоты, с
retry_after_ms. При повторах на одном соединении сервер закрывает с 4008. - payload_too_large — кадр больше 64 КиБ.
- msg_too_long — текст длиннее лимита тарифа,
detail.max. - unsupported_kind, attachment_invalid
- upload_disabled, upload_too_large, upload_mime_denied, upload_expired
- consent_required — действие требует согласия.
- dnd_active — действует «Не беспокоить».
- session_conflict — слишком много соединений сеанса (более 3); закрытие 4003, вкладка становится ведомой.
- internal, unavailable — временно, повтор с задержкой.
Коды закрытия
- 1000, 1001, 1006, 1011, 1013, 4004 — виджет переподключается с задержкой.
- 1008 — источник отклонён, повторно неверный JSON, блокировка защитой от злоупотреблений. Без повтора на этой странице.
- 4001 — версия не согласована. Без повтора.
- 4002 — ключ сайта неизвестен или проект неактивен. Без повтора.
- 4008 — повторное ограничение частоты. Виджет ждёт
retry_after_msи переподключается. - 4003 — конфликт сеанса; вкладка работает ведомой другой вкладке.
Ограничения подключения
Сервер передаёт обязательные значения в init_ok.limits и init_ok.features:
- heartbeat_ms 25 000 —
pingчерез 25 с после последнего исходящего сообщения. - pong_timeout_ms 10 000 — два отсутствующих
pongвызывают переподключение. - idle_timeout_ms 70 000 — сервер закрывает после 70 с без входящего кадра (1001).
- max_frame_bytes 65 536 — большие кадры:
payload_too_large. - msg_per_10s 20 и frames_per_10s 120 — корзина токенов на соединение; сверх неё
rate_limited. - reconnect_cap_ms 30 000 — максимальная задержка между повторами.
- dedup_window_ms 86 400 000 — 24 часа устранения дубликатов.
- features.max_msg_len — символы (кодовые точки Unicode) сообщения посетителя: 1000 на Free, 4000 на платных тарифах. Слишком длинные тексты виджет не отправляет.
- features.max_upload_bytes, features.allowed_mime — см. Ограничения и квоты.
Переподключение, порядок, дубликаты
- Задержка:
min(30000, 500 × 2^(attempt−1))мс, случайное значение из верхней половины (попытка 1: 250–500 мс, с попытки 7: 15–30 с). В фоновых вкладках втрое дольше.retry_after_msсервера задаёт нижнюю границу. Число попыток не ограничено; счётчик сбрасывается только наinit_ok. Если браузер сообщаетonline, попытка немедленная; без сети виджет ждёт. - Порядок: у каждого сообщения разговора непрерывный
seqс 1. Показ поseq, не по времени. При переподключении виджет отправляетresume.last_seqи до 50 ожидающих ID; сервер возвращает всё после него (до 100 сообщений, иначеhas_more) и повторяет подтверждения уже полученных сообщений. - Без дубликатов: сервер помнит каждый
idсообщения 24 часа. Повтор не сохраняется, а получает то же подтверждение. «Отправить снова» использует тот жеid. - Состояния исходящего сообщения:
pending→sent→delivered(эхо сервера) →read;failedпосле 5 попыток, 24 ч илиretry: "no". - Офлайн-буфер: при обрыве сообщения попадают в буфер (до 50 записей, 128 КиБ, 24 ч), с согласием в
localStorage, без него вsessionStorage, и отправляются при следующемinit_ok. - Несколько вкладок, одно подключение: вкладки сайта выбирают ведущую (Web Locks, иначе сигнал жизни в
localStorage); подключение держит только ведущая, остальные общаются черезBroadcastChannel.
Резервный канал HTTP
Если сеть не пропускает WebSocket, чат работает через обычные HTTPS-запросы, с теми же кадрами и ограничениями. Только виджет; интерфейс операторов всегда использует WebSocket. Короткая версия для редакторов — Резервный канал HTTP.
Когда включается:
- браузер не поддерживает WebSocket;
- две попытки WebSocket подряд неудачны до первого установленного WebSocket на этой странице (8 секунд без результата — неудача);
- в
sessionStorageесть записьrc_httpмладше 30 минут (следующие страницы той же вкладки); - принудительно через
data-rc-transport="http"(на любом элементе в документе при запуске) илиwindow.ReactiveChatSettings = { transport: 'http' }до загрузчика.ReactiveChat('boot', { transport: 'http' })не действует: тип подключения фиксируется при запуске.
Без fetch в браузере резервного режима нет. При обрыве существующего WebSocket виджет снова использует WebSocket. После резервного режима он один раз на страницу незаметно проверяет WebSocket; если доступен, удаляет rc_http, и следующая страница снова использует WebSocket.
Запросы (под https://ws.reactive.chat/rc_wss/h/):
- POST h/auf — открывает соединение. Ответ
{"v":1,"cid":"…","tok":"…","poll_ms":25000,"idle_ms":60000}.tok— краткоживущий токен соединения (32 символа). - GET h/<cid>/sse?tok=… — Server-Sent Events,
id:иdata:на кадр, сигнал поддержания связи каждые 15 с. Пропущенные кадры доставляются черезLast-Event-ID. - GET h/<cid>/poll?tok=…&nach=<n> — длинный опрос; сервер держит запрос до 25 с и отвечает
{"v":1,"n":…,"rahmen":[…]}. - POST h/<cid>/tx?tok=… — отправка,
text/plain, один кадр или массив до 20 кадров; ответ 204. - POST h/<cid>/zu?tok=… — закрытие (через
sendBeacon).
Сначала виджет пробует SSE; если за 5 секунд не пришёл первый кадр (например, из-за буферизации прокси), переключается на длинный опрос. Ошибки вроде 404 (unbekannt), 403, 413 или 429 трактует как обрыв и повторяет с задержкой. После 60 секунд без запроса сервер закрывает соединение. CORS: Access-Control-Allow-Origin повторяет источник без учётных данных; проверка источника — в init, как для WebSocket.
Требования на вашей стороне:
connect-srcCSP содержит адрес чата сwss://иhttps://.- Межсетевые экраны и прокси разрешают запросы длительностью не менее 30 секунд.
- Собственный прокси передаёт
/rc_wss/без буферизации (nginxproxy_buffering offиproxy_cache off, Apacheflushpackets=on, Caddyflush_interval -1) и не записывает/rc_wss/h/в журнал доступа (токен в запросе). См. Собственный домен.
Хранится в браузере: только запись rc_http в sessionStorage со временем HTTPS-подключения, без персональных данных.
Какой маршрут активен
Во вкладке сети инструментов разработчика:
- WebSocket: подключение типа WebSocket к
wss://ws.reactive.chat/rc_wss/(или вашему поддомену). В кадрах видныinit,init_ok,ping/pongи сообщения. - Резервный HTTP: сначала
h/auf, затем длительный…/sse(или повторные…/poll) и по одному…/txна отправленное сообщение.
Далее: Ограничения и квоты · Устранение неполадок
Обновлено: