ECSS-Call-API
Описание
Пакет Ecss-call-api (далее адаптер) представляет из себя proxy-сервер для работы сторонних Клиентов с SSW. Для его работы требуется:
- NodeJs >= 14.20
- ecss-user >= 14.12.1
Ecss-call-api позволяет обрабатывать запросы как с веб-сайтов, так и со сторонних API-сервисов.
Особенности работы proxy-сервера с CRM системами.
Интеграции, которые подключаются к Ecss-call-api могут быть двух типов:
- Service - интеграция регистрирует одно подключение к серверу и в рамках него выполняется обработка событий SSW для всех пользователей интеграции. При регистрации номер телефона оператора не указывается.
- Widget - пользователи самостоятельно могут подключаться к адаптеру и обрабатывать события SSW. Сделано для реализации функционала "Виджетов". При регистрации требуется указать номер телефона оператора.
Для того чтобы можно было добавить самоподписанный сертификат, существует страница при переходе на которую браузер предложит добавить исключение безопасности. Страница по умолчанию доступна по адресу, на котором был установлен пакет Ecss-call-api. Порт 8089. Таким образом в адресной строке браузера нужно ввести:
https://<host>:8089/ где <host> — имя или адрес ecss-call-api, указанный при установке пакета.
После добавления исключения безопасности откроется тестовая страница ECSS Call API:

Порядок действий для клиентов:
- Регистрация Клиента на адаптере. При успешной регистрации возвращается бессрочный JWT-токен.
- Подключение Клиента к адаптеру с помощью WebSocket соединения (необходимо передать JWT-токен в заголовке Authorization).
Если соединение будет разорвано, то выполнение команд без новой регистрации будет отклонено. - После подключения Клиента к адаптеру, он может отправлять / принимать сообщения, обязательно указывая при этом JWT-токен в заголовке Bearer.
Команды выполняются посредством выполнения POST запросов.
События принимаются с помощью WebSocket сообщений.
Для поддержки WebSocket соединения, Клиент периодически должен отправлять событие heartbeat на адаптер.
{ "event": "heartbeat", "data": "ping" }
Настройка Ecss-call-api
Приложение можно установить из репозитория (http://apt.ngn.eltex.loc/bionic/3.14/unstable/main) с помощью команды sudo apt install ecss-call-api. Настройки адаптера лежат в файле /etc/ecss/ecss-call-api/config.env.
По умолчанию, сервис работает на порту 8088.
Данные, требуемые при установке пакета:
- SSW Server address - IPv4 хоста где установлен SSW. Пример: 10.25.88.73;
- IPv4 host address - IPv4 текущего устройства куда устанавливается адаптер. Пример: 10.25.88.73;
- Call-api HTTPS port - порт на который будут приходить все запросы к адаптеру. По-умолчанию - 8088;
EcssCallApi поддерживает отправку SIP-сообщений через WebSocket. Точка доступа для подключения- /api/v1/sip . Для подключения к Sip сокету, требуется передать параметр user с логином, адресом (IPv4) где расположен SIP-адаптер, и портом для подключения.
Пример:
http://<ecss-call-api_host>:<port>/api/v1/sip?user=3333@123.123.123.123:5060
При ошибках в подключении к SIP-сокету, WebSocket соединение завершается. Во всех остальных случаях, EcssCallApi выступает как прокси-сервис для отправки SIP-сообщений на сторону SSW. Для поддержки WebSocket соединения открытым, надо периодически отправлять событие heartbeat.
Для использования WebRTC на SSW, надо установить следующие тэги в ecss-media-server:
<transport bind-addr="127.0.0.1" /> - поменять локальный адрес на внешний. enable-ice-transport="yes" stun-server="" ice-update="no" agressive-ice="yes"
Также необходимо включить свойства nat_traversal, core_forking:
domain/<DOMAIN>/sip/user/set <GROUP> <LOGIN>@<DOMAIN> nat_traversal true domain/<DOMAIN>/iface/user-set <OWNER> <GROUP> <LOGIN>@<DOMAIN> core_forking true
Настройки адаптера валидируются приложением, и если будут указаны неверно - приложение не запустится.
Чтобы заново настроить адаптер, выполните команду dpkg-reconfigure ecss-call-api.
Протокол общения с Ecss-call-api
Запросы с командами на выполнение отправляются по http с помощью POST-запросов. Формат запроса - JSON-rpc. Status ответа всегда 200, независимо от его результата. Более подробную информацию о результате выполнения можно узнать в теле ответа. В примерах запросов все параметры являются обязательными, если не указано иное.
{
"jsonrpc": "2.0",
"result": {
"content": <any>[] //коллекция вложенных данных.
<data> // опционально, информация по результату
},
"id": <number> //id запроса, неотъемлемая часть протокола.
}
{
"jsonrpc": "2.0",
"error": {
"code": -32601,
"message": "Method not found"
},
"id": <number>
}
Пример ответа с ошибкой валидации команды:
{
"jsonrpc": "2.0",
"error": {
"code": -32602,
"message": "Wrong command params. Validation property: domain, value: undefined, failed with reasons: domain must be a string "
},
"id": "3"
}
Адаптер отслеживает все события связанные с id Клиентов.
{
"event": <event_type> // тип события"
"message": <string> // текстовое сообщение. Опционально.
"data": {} // полезная информация по событию
"subscriber": <string> // подписчик события либо команды
}
Коды и краткое описание ошибок:
- -32400 - "Bad Request" - Используется, если параметры прошли валидацию, но при реализации команды возникла ошибка. Сделан для соответствия с типом ошибки на ssw.
- -32401 - "Unauthorized" - Используется, если клиент не указал токен авторизации для выполнения команд.
- -32404 - "Not found" - Используется, если параметры прошли валидацию, но при реализации команды возникла ошибка. Сделан для соответствия с типом ошибки на ssw.
- -32502 - "Bad Gateaway" - Ошибка при отправке запроса на неверный адрес ssw.
- -32503 - "Service unavailable" - Предупреждение о состоянии адаптера. Отправляется если подключение к ssw не активно, либо нет подписчиков Интеграции.
- -32601 - "Not Implemented" - Ошибка указывающая, что выполнение такой команды пока еще не реализовано.
- -32602 - "Wrong command params" - Используется, если параметры прошли валидацию, но при реализации команды возникла ошибка. Сделан для соответствия с типом ошибки на ssw.
- -32603 - "Internal server error" - Непредвиденная ошибка на ssw либо адаптере.
Команды для работы с CallCenter API.
Регистрация Клиента
Метод - integration.register
Регистрирует Интеграцию на адаптере. Для виджета нужно указать еще
phone_number: . Результат выполнения команды - токен авторизации на адаптере.
{
"jsonrpc": "2.0",
"id": "3",
"method": "integration.register",
"params": {
"name": "111111.bitrix24.ru",
"client_id": "service",
"api_key": "9YPzJsasbWPUAJ9zv9Uz7iSrsgzPWUPUS09gv9wYU7YGsJ48srssgWP005ui9Ufz",
"domain": "test_domain",
"phone_number": "3011" // Опционально.
}
}
{
"jsonrpc": "2.0",
"result": {
"token": <token_string>
},
"id": "3"
}
Управление звонками
Метод - call.make
Выполнить звонок на номер.
{
"jsonrpc": "2.0",
"method": "call.make",
"params": {
"from_number": 3009, // номер инициатора звонка
"to_number": 3010, // номер вызываемого оператора
"agent_id": 3009 // id инициатора.
},
"id": "3"
}
{
"jsonrpc": "2.0",
"result": null,
"id": "3"
}
Метод - call.answer
Ответить на входящий звонок. Требуется указать id-сессии звонка (передаётся в событии conversations_events)
{
"jsonrpc": "2.0",
"method": "call.answer",
"params": {
"agent_id": 3009,
"conversation_id": "0664a0d350c8"
},
"id": "3"
}
{
"jsonrpc": "2.0",
"result": null,
"id": "3"
}
Метод - call.reject
Отклонить входящий звонок. Требуется указать id-сессии звонка (передаётся в событии conversations_events).
{
"jsonrpc": "2.0",
"method": "call.reject",
"params": {
"agent_id": 3009,
"conversation_id": "0664a0d350c8"
},
"id": "3"
}
{
"jsonrpc": "2.0",
"result": null,
"id": "3"
}
Уведомления клиентов CallCenter API.
conversations_event
Событие происходящее в момент активного звонка. Одновременно приходят события in / out типов звонков.
{
"event": "conversations_event",
"data": {
"conversations": [
{
"id": "06658820a08e1640", //id направления
"status": "released", // статус звонка alerting /talking /released
"direction": "out", //направление звонка
"callId": "0665882096eca924", //id звонка
"callRef": "7003157022781815139", //ссылка на запись
"digits": "3009", //номер вызываемого
"remoteDigits": "3010", //номер вызывающего
"displayName": "", //вспомогательная информация
"remoteDisplayName": "", //вспомогательная информация
"answerTime": "2021/11/22 12:06:27", //системная информация
"releaseInitiator": "system", //системная информация
"workitemId": null //системная информация
}
]
},
"subscriber": "service"
}
authentication
Событие авторизации Клиента.
{
"event": "authentication",
"message": "<string>" //Текст сообщения
}
connection_state
Событие состояния подключения Интеграции к SSW.
{
"event": "connection_state",
"message": "<string>" //INACTIVE|CONNECTING|CONNECTED
}
Порядок подключения к Conference API
Для работы с Conference API на алиасах участников должны быть установлены следующие свойства:
- teleconference\role = manager,
- teleconference\password = <password>
Пример:
domain/virtual_domain/alias/set 3009 local 3009@virtual_domain teleconference\role manager
domain/virtual_domain/alias/set 3009 local 3009@virtual_domain teleconference\password 1234
На данный момент есть два типа селекторов:
- Симметричный селектор (Конференция) - все участники слышат всех. Также передачу аудио потоков можно менять с помощью команд.
- Ассиметричный селектор (Аудиенция) - участники делятся на определённые роли которые соотносятся друг к другу в определённом на SSW порядке.
EcssCallApi работает с Аудиенциями. Роли для Аудиенций:
- Ведущий (master). Управляет селектором. Может менять другим роли. Слышит всех.
- Докладчик (reporter). Докладывает информацию собравшимся. Слышит таких же Докладчиков и Ведущего / Ведущих.
- Консультант (consultant). Помощники Ведущего. Слышат всех.
Запуск аудиенций:
- Создать аудиенцию - audition.register.
- Добавить участников в аудиенцию - audition.participants.add.
Команды для работы с Conference API
Метод - audition.register
Регистрирует Клиента на адаптере. Результат выполнения команды - токен авторизации на адаптере и роль в телеконференции (manager). После регистрации, клиент автоматически будет подписан на уведомляния о новых конференция.
{
"jsonrpc": "2.0",
"id": <number>,
"method": audition.register,
"params": {
"login": <string>, // номер алиаса
"domain": <string>, // домен где создан алиас
"password": <string> // пароль который указан в teleconference\password для алиаса
}
}
В ответе будет JWT-токен и роль участника.
{
"jsonrpc": "2.0",
"result": {
"token": <string>, // JWT токен
"role": <string> // manager по умолчанию
},
"id": <number>
}
Метод - audition.create
Создание Аудиенции. Результат успешного выполнения - id аудиенции. После создания, клиент автоматически будет подписан на уведомляния о состоянии конференции.
{
"jsonrpc": "2.0",
"id": "<number>",
"method": "audition.create",
"params": {
"name": <string>, // Имя конференции. Опционально.
"agent_id": <number>, // Id оператора (алиас).
"description": <string>, // Описание конференции. Опционально
"workitem_id": <string>, // Id карточки проишествия, если будет необходимо. Опционально
"owners": [<number>] // Список алиасов сотрудников, которые будут управлять конференцией.
},
}
Ответ:
{
"jsonrpc": "2.0",
"result": {
"id": "<string>"
},
"id": "<number>"
}
Метод - audition.info
Получить список операторов с установленным свойством teleconference\role в значении manager.
{
"jsonrpc": "2.0",
"id": "<number>",
"method": "audition.info",
"params": {
"agent_id": <number>
}
}
Ответ:
{
"jsonrpc": "2.0",
"result": {
"managers": [<manager>] // Список активных веб-менеджеров.
},
"id": <number>
}
<manager>{
"conference_role": "manager" | "member",
"phone_number": <number>
}
Метод - audition.participants.add
Добавить участников в телеконференцию. Участник может быть либо оператором / номером (agent) либо участником звонка (caller).
{
"jsonrpc": "2.0",
"method": "audition.participants.add",
"id": <number>,
"params": {
"agent_id": <number>,
"conference_id": <string>,
"participants": <agent>
}
}
{
"type": "agent",
"phone_number": <number>,
"options": {
"role": "master" |"consultant"| "reporter"
}
}
{
"jsonrpc": "2.0",
"method": "audition.participants.add",
"id": <number>,
"params": {
"agent_id": <number>,
"conference_id": <string>,
"participants": <caller>]
}
}
{
"type": "caller",
"options": {
"call_id": "<string>", // Id разговора
"tag": "<string>", // tag плеча разговора. Для исходящего (для клиента) вызова из tag поля To из SIP диалога (например, из ответа на INVITE). Для входящего (для клиента) вызова из tag поля From из SIP диалога (например, из INVITE)
"side": "local|remote", // Плечо разговора. Указывает кого подключаем: инициатора разговора или ответчика
"role": "master"|"consultant" | "reporter" // Роль в Аудиенции
}
}
Ответ:
{
"jsonrpc": "2.0", "result": null, "id": <number>
}
Метод - audition.participants.remove
Удалить участника из конференции.
{
"jsonrpc": "2.0",
"id": <number>,
"method": "audition.participants.remove",
"params": {
"agent_id": <number>,
"conference_id": <string>,
"participants": <number>
}
}
Ответ:
{
"jsonrpc": "2.0",
"id": <number>,
"result": null
}
Метод - audition.participants.update
Обновить свойства участников конференции. Все свойства являются опциональными. В ответ получаем список с результатами выполнения команд для каждого участника.
{
"jsonrpc": "2.0",
"id": "<number>",
"method": "audition.participants.update",
"params": {
"agent_id": <number>,
"conference_id": <string>,
"participants": [<participant>]
}
}
{
"phone_number": <number>,
"options": {
"role": "master | consultant | reporter", // Роль в аудиенции
"microphone_volume": "<number>", // от -50 до 50
"speaker_volume": "<number>", // Статус управления конференцией
"status": "owner" | "member" // owner - Управляющий аудиенцией, member -Участник аудиенции
}
}
Ответ:
{
"jsonrpc": "2.0",
"id": <number>,
"result": [<command_result>],
}
// command_result
{
"phone_number": <number>, "result": "ok" | "error"
}
Уведомления клиентов Conference API.
После подписки на события active_conferences / conference_status начинает приходить состояние телеконференций. Сначала передаётся событие с типом full, для инициализации текущего состояния, а после этого - с типом partial для обновления изначального состояния.
active_conferences
{
"event": "active_conferences",
"data": {
"type": "full",
"added": "[]"
}
}
{
"event": "active_conferences",
"data": {
"type": "partial",
"added": [
{
"meeting_id": "067836c84a716436",
"launch_time": "2022/06/26 06:19:48",
"meeting_name": "SomeConf",
"meeting_description": "ECSS default conference",
"template_description": "ECSS default conference"
}
],
"deleted": [
{
"meeting_id": "067836c84a716436",
"launch_time": "2022/06/26 06:19:48",
"meeting_name": "SomeConf",
"meeting_description": "ECSS default conference",
"template_description": "Ecss conference template"
}
]
}
}
conference_status
Уведомление о изменении статуса отслеживаемой конференции.
Поддержаны следующие типы события: full / partial / volume / event_alerting / event_seizure / event_answer / event_release.
Событие с типом volume передаётся для отображения уровня громкости на клиенте.
Событие с типом event_alerting указывает на вызов нового участника конференции.
Событие с типом event_seizure передаётся для указания о подключении участника разговора в конференцию. Событие с типом event_answer передаётся когда подключаемый абонент ответил на вызов.
Событие с типом event_release передаётся когда абонент отключился.
{
"event": "conference_status",
"data": {
"meeting_id": "0677ade57d613efe",
"type": "full",
"data": {
"ss": "conference",
"meeting_name": "someConference",
"meeting_launch_time": "2022/06/20 08:04:39",
"duration": 0,
"groups": [],
"members": [],
"numbers": [],
"max_members_count": 16,
"min_volume_level": 0,
"max_volume_level": 20,
"answered_members_count": 0,
"voice_on_members_count": 0
}
}
}
{
"event": "conference_status",
"data": {
"meeting_id": "0677ade57d613efe",
"type": "partial",
"data": {
"numbers": [
{
"microphone_level": 0,
"loudspeaker_level": 0,
"amplua": "consultant",
"voice_status": "on-voice",
"line_status": "connected",
"hold": false,
"seizure_timestamp": "2022/06/26 06:19:58",
"answer_timestamp": "2022/06/26 06:20:07",
"id": "3009",
"number": "3009"
}
],
"members": [
{
"microphone_level": 0,
"loudspeaker_level": 0,
"amplua": "consultant",
"voice_status": "on-voice",
"line_status": "connected",
"hold": false,
"seizure_timestamp": "2022/06/26 06:19:58",
"answer_timestamp": "2022/06/26 06:20:07",
"id": "3011",
"number": "3011"
}
],
"answered_members_count": 1,
"voice_on_members_count": 1,
"owners": [
"3011"
]
}
}
}
{
"event": "conference_status",
"data": {
"meeting_id": "0677ade57d613efe",
"type": "volume",
"data": [
{
"id": "3011",
"volume_level": 5,
},
],
}
}
{
"event": "conference_status",
"data": {
"meeting_id": "0677ade57d613efe",
"type": "event_seizure",
"data": [
{
"time": "2022/09/26 08:53:06",
"initial_type": "member",
"call_direction": "incoming",
"id": "3010",
"number": "3010"
},
],
}
}
{
"event": "conference_status",
"data": {
"meeting_id": "0677ade57d613efe",
"type": "event_alerting",
"data": [
{
"time": "2022/09/26 08:53:06",
"initial_type": "member",
"id": "3010",
"number": "3010"
},
],
}
}
{
"event": "conference_status",
"data": {
"meeting_id": "0677ade57d613efe",
"type": "event_answer",
"data": [
{
"time": "2022/09/26 08:53:06",
"initial_type": "member",
"id": "3010",
"number": "3010"
},
],
}
}
{
"event": "conference_status",
"data": {
"meeting_id": "0677ade57d613efe",
"type": "event_release",
"data": [
{
"time": "2022/09/26 08:53:06",
"initial_type": "member",
"cause": "conversationTimeout",
"cause_description": "Exceeded the limit on call duration",
"id": "3010",
"number": "3010"
},
],
}
}