Где живёт WebSocket

Last updated: Apr 1, 2026Section: Integrations

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

  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;
    }
    

    В ../../deployment{.interpreted-text role="doc"} приведён общий пример API за nginx; для чата при необходимости добавьте к блоку API те же директивы Upgrade/Connection и таймауты, если они ещё не заданы.

  1. Firewall / security groups

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

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

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

    Варианты:

  • временно держать одну реплику API для стабильного real-time;

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

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

    Для продакшена зафиксируйте выбранную стратегию в runbook.

  1. Сторона сайта (не 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 и окружения
📚