WebSocket чата --- эксплуатация и API продакшена

Обновлено: Aug 31, 2026Раздел: Интеграции

В этом разделе описано, как работают 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-токен (как в приложении).
  • После upgrade API проверяет пользователя и что интеграция существует, имеет тип 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 и понятная модель масштабирования.

  1. Тот же origin, что и REST

    Виджет в браузере и панель управления формируют URL вида wss://<API_HOST>/api/v1/chat/.... Убедитесь, что API_PUBLIC_URL / публичный DNS указывают на тот же хост с этим процессом (или стабильный балансировщик перед ним).

  2. TLS и ``wss:``

    На HTTPS-сайте виджет должен использовать WSS. Прокси нужны валидные сертификаты; путь к API совпадает с обычным HTTPS.

  3. Прокси: поддержка 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.

  1. Файрвол / группы безопасности

    Те же правила, что и для обычного HTTPS API (например, входящий 443 на балансировщик). Исходящие --- к БД, Keycloak, S3 --- по вашей архитектуре; WebSocket ничего не меняет.

  2. Несколько реплик API (важно)

    Текущий хаб чата живёт только в памяти процесса. Соединение владельца на реплике A не увидит рассылку, выполненную на реплике B, если другой под обработал сообщение.

    Варианты:

  • временно запустить одну реплику API для надёжного реального времени;

  • или включить sticky sessions (привязка клиента к поду) по cookie/IP на балансировщике --- это помогает только если вебхуки создания сообщений и оба WebSocket попадают на один экземпляр (ненадёжно при нескольких воркерах);

  • настоящее горизонтальное масштабирование реального времени требует общего брокера (Redis Pub/Sub и т. д.) --- не реализовано в текущей кодовой базе.

    Задокументируйте выбранную стратегию в вашем руководстве по эксплуатации.

  1. Сторона сайта (не 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