Источник:
smarty-code/smarty-backend-stable/smarty-sip/docs/ARCHITECTURE.md
Дата актуальности: 2026-06-13
smarty-sip — подсистема телефонии Smarty CRM, объединяющая SIP-интеграции с внешними провайдерами и WebRTC-конференции между сотрудниками. Обеспечивает: инициирование/приём звонков через SIP-провайдеров, P2P и групповые WebRTC-вызовы, привязку звонков к объектам CRM, управление записями разговоров.
| Сервис | Файл | Назначение |
|---|---|---|
| HTTP API | api-service.js |
REST-эндпоинты для управления звонками и webhook'и SIP-провайдеров |
| SIP events | sip-service.js |
Обработка событий от SIP-провайдеров (очередь calls-sip) |
| WebRTC events | webrtc-service.js |
Обработка клиентских WebRTC-событий и системных таймеров (очередь calls-webrtc) |
| Worker | worker.js |
Общий воркер для событий профилей (online/offline), очередь sipcalls |
| Файл | Роль |
|---|---|
index.js |
Корневой роутер: монтирует sipcall и hooks под префиксом /ws/:_wsId |
sipcall.js |
CRUD звонков, инициирование вызовов (/dial/phone, /dial/object), управление записями |
hooks.js |
Webhook'и для SIP-провайдеров: приём событий без авторизации (по hookToken) |
meetings.js |
[Отключён] Управление встречами/конференциями (не подключён в index.js) |
| Файл | Роль |
|---|---|
index.js |
Диспетчер: online/offline → sipcalls, остальные → calls-webrtc |
callsIndex.js |
Карта обработчиков SIP-событий: incomingCall, answerCall, endCall, callRecordFile*, clearEmployee, phonesActualize |
sip/*.js |
Обработчики SIP-событий (инициализация, ответ, завершение, запись, очистка привязок) |
webrtc/clientEvents.js |
Клиентские WebRTC-действия: create, join, leave, reject, invite, ice, meta |
webrtc/callMissed.js |
Отправка уведомлений о пропущенных звонках |
webrtc/callFinished.js |
Уведомление участников о завершении звонка |
webrtc/callAutoHangup.js |
Автозавершение звонка по таймеру callLimit |
| Файл | Роль |
|---|---|
constants.js |
Лимиты и статусы: PARTICIPANTS_LIMIT=50, DIAL_DELAY=60s, HANGUP_DELAY=30s, callStatusList, callResultList |
callEventWrapper.js |
Обёртка для WebRTC-событий: резолвит employee по _callId/_userId, ловит ошибки → sip_error |
phoneUtils.js |
Нормализация телефонов: normalizePhone (очистка), clearPhone (последние 10 цифр) |
integration/ |
Адаптеры SIP-провайдеров: Zadarma, Mango, Ertelecom, Rostelecom, Telkom, Uis, Flexisip |
integration/base/SipProviderWrapper.js |
Базовый класс провайдера: publicKey, privateKey, domain |
integration/base/SipError.js |
Ошибка SIP-интеграции с контекстом запроса |
| Модель | Коллекция | Роль |
|---|---|---|
SipAccount |
sip_accounts |
Учётная запись SIP-провайдера: provider, ключи, hookToken |
SipCall |
sip_calls |
SIP-звонок: направление, статус, результат, запись, связанные объекты |
SipEmployee |
sip_employees |
Привязка SIP-номера к сотруднику CRM: rawExtId, employeeHistory |
WebRtcCall (conference) |
sip_conferences |
WebRTC-конференция: участники, статус, callLimit, meetingType |
WebRtcParticipant |
sip_conference_participants |
Участник конференции: credentials TURN, статус, длительность |
NormalizedPhones |
normalized_phones |
Индекс телефонов объектов CRM для поиска по звонкам |
┌─────────────┐ POST /dial/phone ┌──────────────┐
│ Клиент │ ─────────────────────────► │ sipcall.js │
└─────────────┘ └──────┬───────┘
│
▼
getSipEmployee → account.api.makeCall()
│
▼
┌───────────────┐
│ SIP-провайдер │
└───────┬───────┘
│
webhook (incoming/outgoing)
│
▼
┌───────────────┐
│ hooks.js │
│ parseEvent() │
└───────┬───────┘
│
emitBgEvent → calls-sip
│
▼
┌──────────────────────────┐
│ sip-service.js │
│ → sipCallInit.js │
│ → SipCall.callUpsert() │
└──────────────────────────┘
┌─────────────┐ socket: create ┌──────────────────┐
│ Инициатор │ ────────────────────► │ webrtc-service │
└─────────────┘ └────────┬─────────┘
│
callEventWrapper → clientEvents.createCall()
│
▼
WebRtcCall.createObject()
(instance-pre-validate: callLimit, участники)
(instance-post-create: addParticipants)
│
emitBgEvent → calls-webrtc
│
▼
┌─────────────┐ socket: join ┌──────────────────┐
│ Участник │ ────────────────────► │ webrtc-service │
└─────────────┘ └────────┬─────────┘
│
joinConference → setStatus('in_process')
(callLimit → webrtc_auto_hangup таймер)
│
▼
socket.io: ice → peer-to-peer
smarty-sip/
├── api-service.js # Точка входа HTTP
├── sip-service.js # Воркер SIP-событий
├── webrtc-service.js # Воркер WebRTC-событий
├── worker.js # Воркер online/offline
├── package.json # main: api-service.js
├── routes/
│ ├── index.js # Корневой роутер
│ ├── sipcall.js # API звонков
│ ├── hooks.js # Webhook'и провайдеров
│ └── meetings.js # [Отключён] Встречи
├── events/
│ ├── index.js # Диспетчер очередей
│ ├── callsIndex.js # Карта SIP-обработчиков
│ ├── sip/ # SIP-события
│ └── webrtc/ # WebRTC-события
├── models/
│ ├── index.js # Экспорт моделей
│ ├── sip_account.js
│ ├── sip_call.js
│ ├── sip_employee.js
│ ├── webrtc_conference.js
│ ├── webrtc_participant.js
│ └── normalized_phones.js
└── lib/
├── constants.js
├── phoneUtils.js
├── callEventWrapper.js
└── integration/ # Адаптеры провайдеров
hookToken — единственный ключ аутентификации webhook'ов. Нет IP-whitelist, нет подписи. Компрометация токена = подделка событий.
callLimit устанавливается один раз при создании конференции (в instance-pre-validate). Если тариф изменится во время звонка — лимит не пересчитается.
participantsCache (WeakMap) в webrtc_conference.js передаёт список участников между instance-pre-validate и instance-post-create. При ошибке между хуками — кэш может содержать stale-данные.
Статусы звонка не идемпотентны. Переход finished → * игнорируется, но pending → pending — нет. Двойной setStatus может вызвать дублирование DBCA-событий.
removeDeadCalls в accessChecker создаёт побочный эффект при read: завершает «мёртвые» звонки. Это нарушает чистоту чтения.
SIP-провайдеры асинхронны. События incomingCall/answerCall/endCall могут приходить в любом порядке. callUpsert использует rawExtId для идемпотентности, но answerCall без звонка — молча игнорируется.
meetings.js не подключён в routes/index.js (строка закомментирована). Код существует, но не работает. MEETING_TYPES импортируется из constants.js, но там не определён — это latent bug.
WebRTC-credentials (TURN) генерируются с таймаутом 8 часов (CREDENTIALS_TIMEOUT). При длительных конференциях могут истечь.
hiddenForEmployees — мягкое удаление для пользователя. Звонок остаётся в БД, но скрывается из списков. Очистка — только при clearEmployee.
phoneToSearch — последние 10 цифр. Для международных номеров возможна коллизия (разные номера с одинаковым хвостом).
PARTICIPANTS_LIMIT).DIAL_DELAY).HANGUP_DELAY).hooks.js) не требуют авторизации — только hookToken в URL.SipAccount.privateKey/publicKey помечены private: true, но хранятся в БД.callToken передаётся клиенту для идентификации сессии.coturn_secret используется для генерации TURN-credentials (HMAC-SHA1).