В этом разделе описано, как работают WebSocket-каналы чата (JSBOX) в API Mailoo, какие события они передают и что необходимо настроить на публичном хосте API в продакшене (прокси, TLS, масштабирование).
Обзор интеграции чата (REST, ключи, BFF): chat-widget{.interpreted-text role="doc"} и chat-widget-nextjs-example{.interpreted-text role="doc"}.
::: {.contents local=""} :::
Где находится WebSocket
WebSocket не слушает на отдельном порту. Тот же процесс Node.js, обслуживающий HTTP (Hono), создаёт http.Server и при upgrade подключает клиентов к ws (WebSocketServer с noServer: true). Пути зафиксированы в коде сервера:
- Владелец (панель управления) ---
/api/v1/chat/ws - Посетитель (виджет) ---
/api/v1/chat/visitor-ws
В продакшене клиенты подключаются к тому же хосту и схеме, что и REST API, например wss://api.example.com/api/v1/chat/ws?.... Отдельный «порт WebSocket» не нужен --- HTTPS/WSS на публикуемом API-сервисе (обычно 443 за балансировщиком нагрузки) достаточно.
Два канала и аутентификация
Владелец (панель управления): /api/v1/chat/ws
Параметры запроса: integrationId, token.
token--- JWT доступа Keycloak (тот же, чтоAuthorization: Bearerдля REST). В разработке настроенный обход может принимать dev-токен (как в приложении).- После
upgradeAPI проверяет пользователя и что интеграция существует, имеет тип JSBOX и принадлежит этому пользователю. - При успехе клиент регистрируется во внутреннем хабе по
integrationIdи получает{"type":"connected","integrationId":"..."}.
Посетитель: /api/v1/chat/visitor-ws
Параметры запроса: projectUid, integrationId, sessionPublicId.
- Без API-ключа: доверие основано на существующей чат-сессии в БД (созданной первым сообщением вебхука) с совпадающими
publicId,integrationIdиproject.uid. - При успехе --- регистрация в хабе для
integrationId+sessionPublicId, ответ{"type":"connected","sessionPublicId":"..."}.
Хаб и события
Реализация --- модуль в оперативной памяти (карты соединений по интеграциям и сессиям). Когда появляется новое чат-сообщение (входящее от посетителя или исходящий ответ владельца), сервер рассылает JSON вида:
{
"type": "chat.message",
"payload": {
"messageId": "...",
"integrationId": "...",
"sessionPublicId": "...",
"messageType": "INBOUND|OUTBOUND",
"content": "...",
"senderEmail": "...",
"senderName": null,
"createdAt": "..."
}
}
Клиенты используют это для обновления UI без полного REST-поллинга. Если WebSocket недоступен, приложение может использовать поллинг (как в консоли чата или виджете).
Чек-лист для продакшена (публичный API Mailoo)
Чек-лист для команд, публикующих API Mailoo в интернете. Отдельный «порт WebSocket» не публикуется: нужен корректный обратный прокси, TLS и понятная модель масштабирования.
-
Тот же origin, что и REST
Виджет в браузере и панель управления формируют URL вида
wss://<API_HOST>/api/v1/chat/.... Убедитесь, чтоAPI_PUBLIC_URL/ публичный DNS указывают на тот же хост с этим процессом (или стабильный балансировщик перед ним). -
TLS и ``wss:``
На HTTPS-сайте виджет должен использовать WSS. Прокси нужны валидные сертификаты; путь к API совпадает с обычным HTTPS.
-
Прокси: поддержка Upgrade
Для
/api/v1/chat/wsи/api/v1/chat/visitor-ws(и для API в целом, если WS использует тот жеlocation) необходимо:
-
HTTP/1.1 к upstream;
-
Заголовки
UpgradeиConnection(типичный паттерн WebSocket); -
Увеличенные таймауты чтения для долгоживущих соединений (десятки минут / часы), иначе прокси разорвёт неактивные сокеты.
Пример фрагмента nginx (тот же upstream, что и REST):
location / { proxy_pass http://api_backend; proxy_http_version 1.1; proxy_set_header Upgrade $http_upgrade; proxy_set_header Connection "upgrade"; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; proxy_set_header X-Forwarded-Proto $scheme; proxy_read_timeout 3600s; proxy_send_timeout 3600s; }Убедитесь, что ваш обратный прокси (или облачный балансировщик) передаёт заголовки WebSocket Upgrade/Connection и использует длинные таймауты чтения/отправки для location API.
-
Файрвол / группы безопасности
Те же правила, что и для обычного HTTPS API (например, входящий 443 на балансировщик). Исходящие --- к БД, Keycloak, S3 --- по вашей архитектуре; WebSocket ничего не меняет.
-
Несколько реплик API (важно)
Текущий хаб чата живёт только в памяти процесса. Соединение владельца на реплике A не увидит рассылку, выполненную на реплике B, если другой под обработал сообщение.
Варианты:
-
временно запустить одну реплику API для надёжного реального времени;
-
или включить sticky sessions (привязка клиента к поду) по cookie/IP на балансировщике --- это помогает только если вебхуки создания сообщений и оба WebSocket попадают на один экземпляр (ненадёжно при нескольких воркерах);
-
настоящее горизонтальное масштабирование реального времени требует общего брокера (Redis Pub/Sub и т. д.) --- не реализовано в текущей кодовой базе.
Задокументируйте выбранную стратегию в вашем руководстве по эксплуатации.
-
Сторона сайта (не API)
BFF Next.js и переменные, такие как
MAILOO_CHAT_WS_ORIGIN/ базовый URL API, должны указывать на публичный хост API, обслуживающий WSS. Это настраивается на приложении сайта, а не как отдельный шаг публикации WebSocket.
Краткая сводка
Вопрос Ответ
Отдельный порт WebSocket? Нет --- тот же сервис и путь, что и REST (через 443 и прокси).
Что должен поддерживать прокси? Upgrade/WebSocket, длинные таймауты, TLS для wss.
Нужно ли открывать что-то кроме HTTP(S)? Нет, если API уже опубликован через HTTPS.
Несколько подов API? Хаб в памяти: одна реплика, sticky sessions или будущая шина событий.
См. также
chat-widget{.interpreted-text role="doc"} --- REST, ключи, CORS, сессииchat-widget-nextjs-example{.interpreted-text role="doc"} --- BFF Next.js и origin WebSocket- OpenAPI:
https://api.mailoo.app/docs/v1