Где живёт WebSocket
Chat WebSocket --- работа и продакшен API
Этот раздел описывает, как устроены WebSocket-каналы чата (JSBOX) в Mailoo API, какие события по ним ходят, и что нужно настроить на публичном хосте 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]{.title-ref} с 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
Query-параметры: integrationId, token.
token--- JWT доступа Keycloak (тот же, что уходит вAuthorization: Bearerдля REST). В dev при настроенном обходе может приниматься dev-токен (как в приложении).- После
upgradeAPI проверяет пользователя и то, что интеграция существует, тип JSBOX, и принадлежит этому пользователю. - При успехе клиент регистрируется в in-memory hub по
integrationIdи получает кадр{"type":"connected","integrationId":"..."}.
Посетитель: /api/v1/chat/visitor-ws
Query-параметры: projectUid, integrationId, sessionPublicId.
- API ключа нет: доверие строится на том, что сессия чата уже есть в БД (создаётся первым сообщением через webhook) и совпадают
publicId,integrationIdиproject.uid. - При успехе --- регистрация в hub по паре
integrationId+sessionPublicId, ответ{"type":"connected","sessionPublicId":"..."}.
Хаб и события
Реализация --- модуль in-memory (карты подключений по интеграции и по сессии). При появлении нового сообщения в чате (входящее от посетителя или исходящий ответ владельца) сервер рассылает JSON одного вида, например:
{
"type": "chat.message",
"payload": {
"messageId": "...",
"integrationId": "...",
"sessionPublicId": "...",
"messageType": "INBOUND|OUTBOUND",
"content": "...",
"senderEmail": "...",
"senderName": null,
"createdAt": "..."
}
}
Клиенты используют это, чтобы обновить UI без полного опроса REST. Если WebSocket недоступен, приложение может опираться на polling (как в консоли чата или виджете).
Что сделать для продакшена (публичный API Mailoo)
Ниже --- чеклист для команды, которая выносит Mailoo API в интернет. Отдельный «WebSocket-порт» не публикуется: важны корректный reverse proxy, 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; }В
../../deployment{.interpreted-text role="doc"} приведён общий пример API за nginx; для чата при необходимости добавьте к блоку API те же директивы Upgrade/Connection и таймауты, если они ещё не заданы.
-
Firewall / security groups
Достаточно правил, как для обычного HTTPS API (например входящий 443 на балансировщик). Исходящие соединения к БД, Keycloak, S3 --- по вашей архитектуре, от WebSocket это не отличается.
-
Несколько реплик API (важно)
Текущий chat hub хранится только в памяти процесса. Подключение владельца на реплике A не увидит рассылку, выполненную на реплике B, если сообщение обработал другой под.
Варианты:
-
временно держать одну реплику API для стабильного real-time;
-
или включить sticky sessions (привязка клиента к поду) по cookie/по IP на балансировщике --- это смягчает проблему только если и webhook создания сообщения, и оба WebSocket попадают на один инстанс (ненадёжно при нескольких воркерах);
-
полноценное горизонтальное масштабирование real-time потребует общего брокера (Redis Pub/Sub и т.п.) --- в текущей кодовой базе это не реализовано.
Для продакшена зафиксируйте выбранную стратегию в runbook.
-
Сторона сайта (не API)
Next.js BFF и env вроде
MAILOO_CHAT_WS_ORIGIN/ база API должны указывать на публичный хост API, с которого доступен WSS. Это настраивается у приложения сайта, а не «отдельной публикацией» WS.
Краткая сводка
Вопрос Ответ
Отдельный порт для WebSocket? Нет, тот же сервис и путь, что у REST (через 443 и прокси).
Что обязательно на прокси? Upgrade/WebSocket, таймауты, TLS для wss.
Нужно ли что-то «пробрасывать» кроме HTTP(S)? Нет, если уже публикуете API по HTTPS.
Несколько подов API? In-memory hub: нужна одна реплика, sticky sessions или будущая шина событий.
См. также
chat-widget{.interpreted-text role="doc"} --- REST, ключи, CORS, сессии../../api/overview{.interpreted-text role="doc"} --- обзор API и упоминание WebSocket../../api/authentication{.interpreted-text role="doc"} --- Bearer и API keys../../deployment{.interpreted-text role="doc"} --- примеры nginx и окружения