Источник:
smarty-code/smarty-backend-stable/smarty-socket/docs/API.md
Актуальность: 2026-06-13
Протокол: socket.io v4 поверх WebSocket (Engine.IO v4)
Namespace:/(default)
Аутентификация происходит при каждом подключении через middleware io.use(authHandler).
| Параметр | Источник | Описание |
|---|---|---|
sid |
HTTP-заголовок Cookie: sid=... или Authorization: Bearer <token> |
Идентификатор сессии |
account |
MongoDB (sessions → User/Employee/WorkspaceApi) | Объект аккаунта |
platform |
User-Agent / x-user-device / MongoDB device |
Платформа клиента |
Валидация:
validateAccount(account) — выбрасывает ошибку, если аккаунт не найден/заблокирован.WorkspaceApi → ForbiddenError ('restricted api endpoint') — API-ключи не допускаются.Результат — поля в socket.request:
{
sessionId: String, // sid из сессии
user: User|Employee, // объект аккаунта
connectionId: String, // JSON.stringify([socket.id, sid])
platform: String // 'web'|'android'|'ios'|'windows'|'test'
}
| Ситуация | Поведение socket.io |
|---|---|
| Нет сессии / невалидный sid | next(error) → клиент получает connect_error |
| Аккаунт заблокирован | next(ForbiddenError) → connect_error |
| WorkspaceApi ключ | next(ForbiddenError('restricted api endpoint')) → connect_error |
joinПрисоединение сотрудника к рабочим комнатам. Вызывается клиентом сразу после connect.
| Поле | Тип | Обязательно | Описание |
|---|---|---|---|
_id |
ObjectId | да | ID сотрудника (Employee) |
_wsId |
ObjectId | да | ID воркспейса |
_user |
ObjectId | да | ID пользователя (User) |
auth_email |
String | нет | Email (для логирования) |
sessionId |
String | нет | ID сессии (передаётся из socket.request.sessionId) |
Побочные эффекты:
clearWsState(employee, sessionId) — убирает старый socketId из activeSocketSessions, помечает sync_sessions resync: true.socket.join([employee_{_id}, ws_{_wsId}, session_{sessionId}]) — подписка на комнаты.isOnlineCheck(socket) — если сотрудник стал онлайн:
redis.set('online-user:{_user}', 'true')redis.set('online-employee:{_id}', 'true')Employee.setOnlineStatus(user, true)io.to('ws_{_wsId}').emit('employeeOnlineStatusChanged', ...) — уведомление воркспейсуconnectionsSet.add(connectionId) — инкремент счётчика в Redis SET.Валидация: validateAccount(employee) — сотрудник должен существовать и быть активным.
userConnectПодключение обычного пользователя (не сотрудника).
| Поле | Тип | Обязательно | Описание |
|---|---|---|---|
_id |
ObjectId | да | ID пользователя |
Побочные эффекты:
socket.join('user_{_id}') — подписка на комнату пользователя.isOnlineCheck(socket) — аналогично join, но для пользователя.pongКлиентский heartbeat (не путать с Engine.IO pong). Обновляет время последней активности.
| Поле | Тип | Обязательно | Описание |
|---|---|---|---|
| — | — | — | Без payload |
Побочные эффекты:
User.updateLastActivityTime(socket.request.user._id) — обновляет lastActivityTime в MongoDB.Частота: Клиент отправляет pong в ответ на серверный пинг (каждые 5 сек, pingInterval: 5000).
leaveОтключение сотрудника от комнат (logout / выход из воркспейса).
| Поле | Тип | Обязательно | Описание |
|---|---|---|---|
(аналогично join) |
Object | да | Данные сотрудника |
Побочные эффекты:
socket.leave([employee_{_id}, ws_{_wsId}, session_{sessionId}]) — отписка от комнат.isOnlineCheck(socket) — если сотрудник стал оффлайн:
redis.del('online-user:{_user}')redis.del('online-employee:{_id}')Employee.setOnlineStatus(user, false)io.to('ws_{_wsId}').emit('employeeOnlineStatusChanged', ...)disconnectСтандартное событие socket.io. Обрабатывается внутри socketEventsHandler при разрыве соединения.
Побочные эффекты:
connectionsSet.remove(connectionId) — декремент счётчика.isOnlineCheck(socket) — пересчёт онлайн-статуса (если это была последняя сессия сотрудника → offline).employeeOnlineStatusChangedРассылается в комнату ws_{_wsId} при изменении онлайн-статуса любого сотрудника воркспейса.
| Поле | Тип | Описание |
|---|---|---|
_id |
ObjectId | ID сотрудника |
affectedFields |
Object | { isOnline: Boolean } |
_wsId |
ObjectId | ID воркспейса |
Источник: socketEventsHandler.js → sendOnlineStatus.
sessionTerminatedПринудительное отключение клиента. Перед disconnect клиент получает это событие с причиной.
| Поле | Тип | Описание |
|---|---|---|
reason |
String | Причина отключения |
Источник: events.js → disconnectClients (через RabbitMQ команды).
Клиентское поведение: Клиент должен обработать это событие и показать пользователю причину (например, «Сессия завершена администратором»).
dataChangedУниверсальное событие для синхронизации данных. Генерируется серверными сервисами и транслируется через socket-emitter.
| Поле | Тип | Описание |
|---|---|---|
type |
String | Тип изменения (например, 'dataChanged') |
collectionName |
String | Имя коллекции MongoDB |
data |
Object | Изменённые данные |
_wsId |
ObjectId | ID воркспейса |
Адресация: io.to('ws_{_wsId}').emit('dataChanged', ...) или io.to('employee_{_id}').emit(...).
Источники: smarty-notification, smarty-dialog, smarty-sip, smarty-ext-messenger и др. через socket-emitter.
@socket.io/redis-emitter)Любой микросервис может отправить событие через Redis без прямого доступа к socket.io Server:
const io = require('../../lib/utils/socket-emitter');
// Всем в воркспейс
io.to('ws_XXX').emit('eventName', payload);
// Конкретному сотруднику
io.to('employee_XXX').emit('eventName', payload);
// Конкретной сессии
io.to('session_XXX').emit('eventName', payload);
// Конкретному пользователю
io.to('user_XXX').emit('eventName', payload);
// Всем подключённым (broadcast)
io.emit('eventName', payload);
Ограничения:
smarty-socket.| Комната | Формат | Назначение | TTL |
|---|---|---|---|
employee_{_id} |
employee_ + ObjectId |
Все соединения сотрудника | Пока есть хотя бы 1 сокет |
ws_{_wsId} |
ws_ + ObjectId |
Все сотрудники воркспейса | Пока есть хотя бы 1 сокет |
session_{sessionId} |
session_ + sid |
Конкретная сессия | Пока сокет жив |
user_{_id} |
user_ + ObjectId |
Пользователь (не сотрудник) | Пока сокет жив |
| Коллекция | Роль |
|---|---|
activeSocketSessions (в User/Employee) |
Массив socketId — текущие соединения. Pull при disconnect. |
sync_sessions |
Таблица синхронизации. isOnline: true/false, resync: true/false. |
socket-commandsВходящие команды из очереди socket-commands:
msg.type |
msg.data |
Эффект |
|---|---|---|
disconnect-session |
{ sessionId, reason } |
Отключает все сокеты в комнате session_{sessionId}. Перед disconnect эмитит sessionTerminated. |
disconnect-other-sessions |
{ _user, sessionId, reason } |
Отключает все сокеты в session_{sessionId} и user_{_user}, кроме sessionId. Перед disconnect эмитит sessionTerminated. |
Используется для:
| Код / Тип | Причина | Источник |
|---|---|---|
ForbiddenError |
WorkspaceApi пытается подключиться | authHandler.js |
Error (validateAccount) |
Аккаунт не найден / заблокирован | authHandler.js |
Error (нет сессии) |
Невалидный / отсутствующий sid | getAccountFromRequest |
reason |
Контекст |
|---|---|
'forced disconnect' |
Дефолтная причина в disconnectClients |
| Пользовательское значение | Передаётся в msg.data.reason из RabbitMQ |
| Событие | Причина |
|---|---|
transport close |
Клиент закрыл соединение / сеть пропала |
ping timeout |
Клиент не ответил на ping (дефолт socket.io v4: 20 сек) |
forced server close |
Сервер вызвал socket.disconnect(true) |
Эти поведения не являются частью формального контракта, но существующий код может на них полагаться.
connectionId формат. JSON.stringify([socket.id, sid]) — клиентский код может парсить этот JSON для извлечения socketId. Изменение формата сломает такие зависимости.
pong — нестандартное имя. Клиент отправляет socket.io-событие 'pong' для heartbeat. Engine.IO также использует pong-пакеты (код 3) для keepalive. Оба работают параллельно; сервер различает их по уровню (transport vs application).
isOnline — не атомарный. Значение isOnline в MongoDB обновляется после find — между ними состояние может измениться. Клиенты, читающие isOnline напрямую из БД, могут видеть устаревшее значение.
employeeOnlineStatusChanged — только для изменившихся. Событие эмитится только для сотрудников, у которых isOnline действительно изменился. Если сотрудник уже онлайн (другая вкладка), повторный join не генерирует событие.
activeSocketSessions — массив socketId. Может содержать «мёртвые» socketId, если disconnect не прошёл грациозно. clearWsState при reconnect чистит их.
Redis SET socket_connections — глобальный счётчик. clear() вызывается при старте сервиса — если запущено несколько инстансов, старт нового очищает счётчик для всех.
updateStat — статистика. Каждый emit в комнату ws_{_wsId} вызывает updateStat(room, type, data) из smarty-notification. Это побочный эффект, неявный в контракте.
chained в HandlersMap. RabbitMQ-сообщения с полем chained запускают цепочку — после обработки текущего сообщения публикуется следующее из массива. Не используется в текущих командах socket-commands, но поддерживается фреймворком.