Описание

Пакет Ecss-call-api (далее адаптер) представляет из себя proxy-сервер для работы сторонних Клиентов с SSW. Для его работы требуется:

  1. NodeJs >= 14.20
  2. ecss-user >= 14.12.1

Ecss-call-api позволяет обрабатывать запросы как с веб-сайтов, так и со сторонних API-сервисов.

Особенности работы proxy-сервера с CRM системами.

Интеграции, которые подключаются к Ecss-call-api могут быть двух типов:

  1. Service - интеграция регистрирует одно подключение к серверу и в рамках него выполняется обработка событий SSW для всех пользователей интеграции. При регистрации номер телефона оператора не указывается.
  2. Widget - пользователи самостоятельно могут подключаться к адаптеру и обрабатывать события SSW. Сделано для реализации функционала "Виджетов". При регистрации требуется указать номер телефона оператора.

Для того чтобы можно было добавить самоподписанный сертификат, существует страница при переходе на которую браузер предложит добавить исключение безопасности. Страница по умолчанию доступна по адресу, на котором был установлен пакет Ecss-call-api. Порт 8089. Таким образом в адресной строке браузера нужно ввести:

https://<host>:8089/  где <host> — имя или адрес ecss-call-api, указанный при установке пакета.

После добавления исключения безопасности откроется тестовая страница ECSS Call API:

Порядок действий для клиентов:

  1. Регистрация Клиента на адаптере. При успешной регистрации возвращается бессрочный JWT-токен.
  2. Подключение Клиента к адаптеру с помощью WebSocket соединения (необходимо передать  JWT-токен в заголовке Authorization).
    Если соединение будет разорвано, то выполнение команд без новой регистрации будет отклонено.
  3. После подключения Клиента к адаптеру, он может отправлять / принимать сообщения, обязательно указывая при этом 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.

Данные, требуемые при установке пакета:

  1. SSW Server address - IPv4 хоста где установлен SSW. Пример: 10.25.88.73;
  2. IPv4 host address - IPv4 текущего устройства куда устанавливается адаптер. Пример: 10.25.88.73;
  3. 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
CODE

При ошибках в подключении к 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"
}
CODE

Адаптер отслеживает все события связанные с 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
CODE

На данный момент есть два типа селекторов:

  1. Симметричный селектор (Конференция) - все участники слышат всех. Также передачу аудио потоков можно менять с помощью команд.
  2. Ассиметричный селектор (Аудиенция) - участники делятся на определённые роли которые соотносятся друг к другу в определённом на SSW порядке.

EcssCallApi работает с Аудиенциями. Роли для Аудиенций:

  1. Ведущий (master). Управляет селектором. Может менять другим роли. Слышит всех.
  2. Докладчик (reporter). Докладывает информацию собравшимся. Слышит таких же Докладчиков и Ведущего / Ведущих.
  3. Консультант (consultant). Помощники Ведущего. Слышат всех.

Запуск аудиенций:

  1. Создать аудиенцию - audition.register.
  2. Добавить участников в аудиенцию - 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>
}
CODE

Метод - 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>"
}
CODE

Метод - 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>
}
CODE

Метод - audition.participants.add

Добавить участников в телеконференцию. Участник может быть либо оператором / номером (agent) либо участником звонка (caller).

Запрос для агента (agent):
{
    "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"
    }
}
Запрос для участника звонка (caller):
{
    "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>
 
}
CODE

Метод - 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
}
CODE

Метод - 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"
 
}
CODE

Уведомления клиентов 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"
            },
        ],    
    }
}