Система из коробки включает в себя интеграции с различными провайдерами IP-телефонии. Если нужной именно вам
интеграции не нашлось, вы можете воспользоваться данным API для создания собственной интеграции. Для этого вам нужно
создать пользовательский модуль и реализовать несколько функций в скриптах модуля на языке TypeScript.
Встроенные и пользовательские модули интеграции с IP-телефонией по функционалу принципиально ничем не отличаются.
Вашему модулю будут доступны следующие возможности:
инициация исходящего звонка из системы через контекстное меню поля типа Телефон;
отображение карточки звонка с краткой информацией по клиенту и кнопками управления вызовом,
когда пользователь системы принимает входящий или совершает исходящий звонок;
открытие карточки сводной информации по клиенту;
автоматическое создание и сохранение элемента приложения, если звонок был пропущен, а номера нет в базе;
регистрация пропущенных звонков;
сохранение в ленту объекта сообщения о входящем или исходящем звонке. Если разговор состоялся, то к сообщению
добавляется ссылка на запись звонка. Её можно воспроизвести в интерфейсе системы, а также оставить
комментарий, содержащий, к примеру, резюме звонка;
автоматическое создание элемента приложения Звонок в разделе Телефония для каждого входящего
и исходящего звонка;
сопоставление учётных записей пользователей IP-телефонии и пользователей системы для маршрутизации и
корректного отражения информации об операторе, принявшем или осуществившем звонок.
Прежде чем реализовывать модуль интеграции, обратите внимание на следующие ограничения API:
На стороне системы не осуществляется маршрутизация звонка во избежание конфликтов с настройками маршрутизации
на стороне телефонии и на стороне системы. Такую возможность предоставляет практически каждый провайдер: после
того, как звонок маршрутизирован на сотрудника или группу, входящий вызов отображается в системе для указанных
пользователей.
На текущий момент обработка событий телефонии производится путем доставки сообщений на определенную HTTP-ссылку
(Webhook). Если провайдер телефонии не предоставляет возможность указать ссылку для доставки сообщений, то необходимо
создать промежуточный сервис, который преобразует данные провайдера телефонии в запрос на сервер системы. Для этого
вы можете создать переносимый сервис в модуле.
Для записей звонков сохраняется только ссылка на файл. Обычно сами записи хранятся у провайдера телефонии.
Он предоставляет только ссылку. Вам следует обратить внимание на политику хранения файлов на
серверах провайдера телефонии. Файлы могут быть доступны ограниченное время.
Для создания модуля интеграции вам необходимо в скриптах пользовательского модуля реализовать следующие функции:
// Проверить соединение с телефонией. Вызывается при нажатии кнопки Проверить соединение на странице модуля// Возвращает статус проверки соединения, который отобразится для пользователя на странице модуляasyncfunctionVoipTestConnection(): Promise<VoipTestConnectionResult> { }
// Обработать запрос от провайдера IP-телефонии. Вызывается, когда происходит запрос на вебхук модуля// В параметре `request` содержится информация об HTTP-запросе, в том числе HTTP-заголовки и содержимое тела запроса// Функция возвращает результат обработки запроса, на основании которого система будет показывать уведомления// о входящем звонке, сохранять запись звонка и т. д.asyncfunctionVoipParseWebhookRequest(request: FetchRequest): Promise<VoipWebhookParseResult> { }
// Получить список пользователей со стороны провайдера IP-телефонии// Вызывается при сопоставлении пользователей телефонии с пользователями системы после нажатия кнопки Настроить на странице модуля// Возвращает список пользователей провайдера IP-телефонииasyncfunctionVoipGetMembers(): Promise<VoipMember[]> { }
// Сгенерировать исходящий звонок. Функция вызывается, когда пользователь системы нажимает кнопку звонка// в контекстном меню поля типа Телефон или кнопку Перезвонить в карточке звонка. В параметре// `srcPhone` указывается номер звонящего пользователя, в `dstPhone` — номер, на который нужно позвонить,// `ext` — добавочный номер. Рекомендуется настроить АТС или SIP-софтфон таким образом, чтобы добавочный номер// автоматически набирался тональным набором после ответа на вызов другой стороной.asyncfunctionVoipGenerateCall(srcPhone: string, dstPhone: string, ext?: string): Promise<void> { }
// Получить ссылку на запись звонка. Функция вызывается, когда пользователь системы проигрывает запись звонка из// ленты элемента приложения или при сохранении ссылки на запись звонка в контекст элемента приложения Звонок// раздела Телефония. В параметре `callData` содержатся данные, которые были указаны при сохранении// информации о записи звонка из функции `VoipParseWebhookRequest`. Функция должна возвращать ссылку на файлasyncfunctionVoipGetCallLink(callData: any): Promise<string> { }
// Вызывается автоматически при изменении ссылки на вебхук (например, при обновлении токена)// В параметре `webhookUrl` указывается абсолютная ссылка на вебхук модуля интеграции. Функция не обязательна к реализацииasyncfunctionVoipOnWebhookUpdated(webhookUrl: string): Promise<void> { }
После реализации этих функций и сохранения скрипта на странице модуля вы найдете новый раздел под названием
Настройки телефонии. В этом разделе будет указана ссылка на вебхук — при отправке данных на эту ссылку будет
вызываться функция VoipParseWebhookRequest из скрипта. В этой функции вы должны обработать запрос и вернуть из
неё экземпляр VoipWebhookParseResult, на основании которого система отобразит уведомления о входящем звонке или
сохранит информацию о записи звонка. Саму ссылку на вебхук вам необходимо указать в настройках вашего провайдера
телефонии. Ссылка обрабатывает HTTP-запросы типа GET и POST. Также в ссылке должен присутствовать обязательный
параметр token.
Рекомендуем вам ознакомиться со статьей справки
«Интеграция с IP-телефонией через пользовательский модуль».
Внутри статьи вы найдете заготовку модуля и пример реального использования API на основе интеграции с IP-телефонией
Гравител.
При получении входящего звонка или совершении исходящего звонка у пользователя системы в правом нижнем углу
появляется карточка с информацией о текущем звонке.
В зависимости от состояния звонка, в этой карточке могут отображаться различные кнопки: Принять звонок,
Завершить звонок, Переназначить и так далее. Чтобы эти кнопки отображались,
реализуйте следующие функции в пользовательском модуле телефонии:
// Принять звонок. Функция вызывается, когда в карточке звонка пользователь системы нажимает кнопку Принять звонок// После этого начинают передаваться голосовые данные между оператором и клиентом// В параметре `srcPhone` указывается номер телефона звонящего, в `dstPhone` — номер, на который производится звонокasyncfunctionVoipAcceptCall(srcPhone: string, dstPhone: string): Promise<void> { }
// Завершить звонок. Функция вызывается, когда в карточке звонка пользователь системы нажимает кнопку Завершить звонок// Звонок между оператором и клиентом завершается// В параметре `srcPhone` указывается номер телефона звонящего, в `dstPhone` — номер, на который производится звонокasyncfunctionVoipHangupCall(srcPhone: string, dstPhone: string): Promise<void> { }
// Переназначить звонок. Функция вызывается, когда в карточке звонка пользователь системы нажимает кнопку Переназначить// Звонок текущего оператора завершается, и клиент соединяется с другим оператором// В параметре `srcPhone` указывается номер телефона звонящего, в `dstPhone` — номер, на который производится звонок,// `redirectTo` — внутренний номер оператора, на которого переназначается звонокasyncfunctionVoipRedirectCall(srcPhone: string, dstPhone: string, redirectTo: string): Promise<void> { }
// Поставить звонок на ожидание. Функция вызывается, когда в карточке звонка пользователь системы// нажимает кнопку Поставить на ожидание. Передача голосовых данных между оператором и клиентом прекращается без завершения звонка// В параметре `srcPhone` указывается номер телефона звонящего, в `dstPhone` — номер, на который производится звонок,// `hold` — значение `true`. Оно указывает, что нужно поставить звонок на ожидание, иначе — снять с ожиданияasyncfunctionVoipHoldCall(srcPhone: string, dstPhone: string, hold: boolean): Promise<void> { }
Если в пользовательском модуле телефонии функция не реализована, соответствующая кнопка не будет отображаться
в карточке звонка.
Если провайдер телефонии поддерживает технологию WebRTC, то можно совершать звонки напрямую из браузера с помощью встроенной
в систему CTI-панели. Она позволяет работать со звонками из единого интерфейса. Установка SIP-софтфона в таком случае не нужна.
Функционал CTI-панели доступен только при наличии у пользователя лицензии ELMA365 Omni отдельно или в составе другой лицензии.
Интеграция телефонии в CTI-панели системы реализуется через виджет пользовательского модуля. Когда сотрудник выбирает
провайдера телефонии в CTI-панели, автоматически создаётся экземпляр виджета. В его клиентских скриптах вызывается
функция connect для инициализации подключения к провайдеру телефонии.
Виджет позволяет:
устанавливать канал связи для обмена сообщениями протокола сигнализации. Например, для телефонии Asterisk используется протокол
SIP over WebSocket (RFC 7118), позволяющий передавать SIP-сигнализацию через WebSocket-соединение;
обрабатывать события от CTI-панели и отправлять их в телефонию. Например, когда пользователь системы нажимает кнопку
исходящего звонка в CTI-панели, вызывается метод BrowserVoipClient.call. В результате, в виджете
должен инициироваться исходящий звонок по протоколу телефонии, а в систему возвращаться объект с информацией о звонке.
Подробнее о типах данных читайте в примере ниже;
обрабатывать события от телефонии и отправлять их в CTI-панель. Например, когда от провайдера телефонии поступает сигнал
о новом входящем звонке, в виджете должен вызываться метод BrowserVoipEventSink.callReceived, чтобы в CTI-панели отобразилась
информация о входящем звонке;
настраивать WebRTC-соединение для передачи медиапотока между браузером пользователя и телефонией. В частности, в виджете
должен выполняться обмен SDP-описаниями и ICE-кандидатами, а также должно контролироваться состояние WebRTC-сессии.
Для реализации виджета взаимодействия с CTI-панелью выполните следующие шаги:
В пользовательском модуле телефонии создайте виджет
со следующими параметрами:
Название — добавьте имя виджета;
Код виджета — укажите значение browser_voip_client. По этому коду в системе определяется, какой конкретно виджет из модуля
нужно использовать для интеграции с CTI-панелью;
Расширение — оставьте пустым.
В открывшемся дизайнере интерфейсов на вкладке Скрипты добавьте клиентские скрипты с описанием интерфейсов
для работы с CTI-панелью:
управление звонками через CTI-панель. Все методы интерфейса BrowserVoipClient, кроме getCallId, обязательны для реализации:
// Установленное подключение к телефонии// @template TIncomingCall Входящий звонок// @template TOutgoingCall Исходящий звонокinterface BrowserVoipClient<TIncomingCall = unknown, TOutgoingCall = unknown> {
// Отключиться от телефонии. Вызывается системой, когда пользователь выключает CTI-панель
disconnect(): Promise<void>;
// Совершить исходящий звонок. Вызывается системой, когда пользователь вводит номер// в номеронабирателе и нажимает кнопку Совершить исходящий звонок. Также может// вызываться при звонке через контекстное меню поля Номер телефона// @param phoneNumber Номер телефона, на который нужно позвонить// @param ext Добавочный номер, который нужно автоматически набрать в тональном режиме// после установки соединения// @returns Объект, представляющий собой звонок. В этом объекте виджет может хранить// любые данные звонка// Объект будет передан в другие методы интерфейса `BrowserVoipClient`, когда в CTI-панели нужно// изменить состояние звонка
call(phoneNumber: string, ext?: string): Promise<TOutgoingCall>;
// Принять входящий звонок. Вызывается системой, когда пользователь нажал кнопку// принятия входящего звонка. Она появляется после того, как пришло событие телефонии о// новом входящем звонке. Подробнее читайте в описании `BrowserVoipEventSink.callReceived`// @param invite Входящий звонок, который был указан в аргументе// метода `BrowserVoipEventSink.callReceived`
accept(invite: TIncomingCall): void;
// Завершить звонок, т. е. повесить трубку. Также вызывается, если нужно отклонить входящий звонок// @param call Звонок
hangup(call: TIncomingCall | TOutgoingCall): void;
// Поставить звонок на удержание// @param call Звонок
hold(call: TIncomingCall | TOutgoingCall): void;
// Снять звонок с удержания// @param call Звонок
unhold(call: TIncomingCall | TOutgoingCall): void;
// Произвести слепой перевод (blind transfer) на указанный номер телефона. В результате вызова метода// ожидается, что переданный звонок будет завершён// @param call Звонок// @param redirectPhoneNumber Номер телефона, на который нужно произвести слепой перевод
blindTransfer(call: TIncomingCall | TOutgoingCall, redirectPhoneNumber: string): void;
// Завершить перевод с сопровождением (attended transfer). В результате вызова метода ожидается, что// два указанных абонента будут соединены друг с другом, а звонок `transferor` перейдёт в// состояние Звонок завершён// @param transferor Звонок текущего пользователя с клиентом, которого нужно перевести// на сотрудника `caller`// @param caller Звонок текущего пользователя с сотрудником, на которого будет переведён звонок
attendedTransfer(transferor: TIncomingCall | TOutgoingCall, caller: TIncomingCall | TOutgoingCall): void;
// Отправить DTMF-сигнал. Вызывается системой при вводе пользователем DTMF-кода в интерфейсе CTI-панели// @param call Звонок// @param key Сигнал для отправки. Например: '1', '5', '*', '#'// @returns Promise, который разрешается после отправки сигнала
sendDtmf(call: TIncomingCall | TOutgoingCall, key: string): Promise<void>;
// Получить уникальный идентификатор звонка на стороне телефонии. Данный идентификатор должен быть// одинаковым на стороне клиента в браузере и в событии, которое генерируется функцией// `VoipParseWebhookRequest` при обработке события через вебхук. ID звонка используется системой для// обновления информации о звонке дополнительными данными с сервера. Например, к звонку в CTI-панели// может быть привязан элемент приложения Звонок раздела Телефония// @param call Звонок// @returns Идентификатор звонка или null, если идентификатор недоступен
getCallId?(call: TIncomingCall | TOutgoingCall): string | null;
}
отправка уведомлений в CTI-панель:
// Объект, через который соединение с телефонией `BrowserVoipClient` может отправлять уведомления// об изменении состояния звонков в CTI-панель// @template TIncomingCall Входящий звонок// @template TOutgoingCall Исходящий звонокinterface BrowserVoipEventSink<TIncomingCall = unknown, TOutgoingCall = unknown> {
// Уведомить о новом входящем звонке. В CTI-панели пользователю отобразится предложение// принять или отклонить входящий звонок// @param invite Объект с информацией о входящем звонке. Это значение будет передано как есть в// `BrowserVoipClient.accept` или `BrowserVoipClient.hangup`// @param phoneNumber Номер телефона, с которого нам звонят
callReceived(invite: TIncomingCall, phoneNumber: string): void;
// Уведомить о завершении звонка. В CTI-панели звонок перейдёт в состояние Звонок завершён// @param call Звонок
callFinished(call: TIncomingCall | TOutgoingCall): void;
// Уведомить об успешной установке соединения между двумя абонентами для указанного звонка.// Ожидается, что оба абонента могут слышать друг друга. В CTI-панели звонок перейдёт в состояние// Идёт разговор// @param call Звонок
callEstablished(call: TIncomingCall | TOutgoingCall): void;
// Отправить событие потери соединения с телефонией. Этот метод следует вызывать, когда// произошла критическая ошибка, из-за которой автоматически восстановить соединение// с телефонией невозможно// @param reason Причина ошибки, которую увидит пользователь
disconnected(reason: string): void;
}
Также на вкладке Скрипты в клиентских скриптах реализуйте функцию connect. Она вызывается автоматически после того, как
пользователь системы включит CTI-панель. При успешном подключении к телефонии с помощью этой функции возвращается реализация интерфейса
BrowserVoipClient. В дальнейшем в системе будут вызываться методы этого объекта в зависимости от действий пользователя в CTI-панели.
Метод также принимает параметр типа BrowserVoipClientCredentials, в котором находится внутренний номер, логин и пароль
для подключения к телефонии. Администратор системы заполняет эту информацию на странице настроек модуля телефонии. Объект
BrowserVoipEventSink используется для отправки событий в CTI-панель, например, получение нового входящего звонка или
изменение состояния звонка.
asyncfunctionconnect(credentials: BrowserVoipClientCredentials, eventSink: BrowserVoipEventSink): Promise<BrowserVoipClient> {
const client: BrowserVoipClient = {
// Реализуйте интерфейс BrowserVoipClient
};
try {
// Подключитесь к каналу сигнализации телефонии, используя данные из аргумента `credentials`
...
// На момент возврата данных из метода экземпляр `BrowserVoipClient` должен быть подключён к телефонииreturn client;
} catch {
// В случае ошибки соединения закройте все подключения к телефонии
...
// Пробросьте исключение, чтобы в CTI-панели пользователь увидел// сообщение об ошибке подключения телефонииthrow;
}
}
Сохраните и опубликуйте виджет. После этого при включённом модуле пользователи смогут выбрать провайдера телефонии в CTI-панели.
Чтобы сотрудники могли подключаться к телефонии через CTI-панель, администратор системы должен заполнить
в настройках пользовательского модуля логин и пароль для каждого пользователя телефонии. При большом количестве
сотрудников это может быть трудоёмко. Чтобы повысить безопасность и снизить нагрузку на администратора системы,
рекомендуется автоматически получать учётные данные из телефонии или стороннего сервиса. Схема работы
может выглядеть следующим образом:
На вкладке Настройки пользовательского модуля телефонии создайте поля со следующими кодами:
voip_supports_single_sign_on с типом данных Выбор «да/нет». Укажите значение по умолчанию для этого поля — Да или
вынесите его на форму настроек модуля. Когда в этом поле указано значение Да, в системе не отображаются
поля для ввода логина и пароля пользователя телефонии при настройке CTI-панели;
api_key с типом данных Строка. В этом поле хранится API-ключ для аутентификации в телефонии.
Откройте виджет интеграции с CTI-панелью в дизайнере интерфейсов и на вкладке Контекст
добавьте переменные со следующими кодами:
extension с типом данных Строка. Используется, чтобы передать внутренний номер пользователя,
для которого нужно получить токен;
auth_token с типом данных Строка. Используется, чтобы получить токен для подключения к телефонии.
На вкладке Скрипты в метод connect клиентских скриптов добавьте вызов серверной функции. С её помощью отправляется
запрос на получение учётных данных для подключения к телефонии. Текущего пользователя можно получить вызовом
метода System.users.getCurrentUser(). Полученные учётные данные сохраните в контекстной переменной виджета auth_token.
В клиентских скриптах получите результат и используйте эти данные для подключения к телефонии.
// Клиентские скриптыasyncfunctionconnect(credentials: BrowserVoipClientCredentials, eventSink: BrowserVoipEventSink): Promise<BrowserVoipClient> {
// Сохраняем внутренний номер пользователя в переменную, чтобы серверный скрипт имел к нему доступ
Context.data.extension = credentials.extension;
await Server.rpc.getAuthToken();
// Вызов getAuthToken() сохранит токен в контекстной переменнойconst token = Context.data.auth_token;
if (!token) {
thrownewError('Не удалось получить токен авторизации');
}
// Используйте `token` для подключения к телефонии
...
}
// Серверные скриптыasyncfunctiongetAuthToken(): Promise<void> {
const currentUser = await System.users.getCurrentUser();
const extension = Context.data.extension;
// API-ключ для стороннего сервиса, обычно заполняется на странице настроек модуляconst apiKey = (await Namespace.getParams()).data.api_key;
const response = await fetch('https://example.com/auth', {
method: 'POST',
body: JSON.stringify({
userId: currentUser.id,
extension: extension,
apiKey: apiKey,
}),
});
if (response.ok) {
const token = await response.text();
Context.data.auth_token = token;
}
}
Интеграция с IP-телефонией
Система из коробки включает в себя интеграции с различными провайдерами IP-телефонии. Если нужной именно вам интеграции не нашлось, вы можете воспользоваться данным API для создания собственной интеграции. Для этого вам нужно создать пользовательский модуль и реализовать несколько функций в скриптах модуля на языке TypeScript.
Встроенные и пользовательские модули интеграции с IP-телефонией по функционалу принципиально ничем не отличаются. Вашему модулю будут доступны следующие возможности:
Ограничения
Прежде чем реализовывать модуль интеграции, обратите внимание на следующие ограничения API:
Реализация
Для создания модуля интеграции вам необходимо в скриптах пользовательского модуля реализовать следующие функции:
// Проверить соединение с телефонией. Вызывается при нажатии кнопки Проверить соединение на странице модуля // Возвращает статус проверки соединения, который отобразится для пользователя на странице модуля async function VoipTestConnection(): Promise<VoipTestConnectionResult> { } // Обработать запрос от провайдера IP-телефонии. Вызывается, когда происходит запрос на вебхук модуля // В параметре `request` содержится информация об HTTP-запросе, в том числе HTTP-заголовки и содержимое тела запроса // Функция возвращает результат обработки запроса, на основании которого система будет показывать уведомления // о входящем звонке, сохранять запись звонка и т. д. async function VoipParseWebhookRequest(request: FetchRequest): Promise<VoipWebhookParseResult> { } // Получить список пользователей со стороны провайдера IP-телефонии // Вызывается при сопоставлении пользователей телефонии с пользователями системы после нажатия кнопки Настроить на странице модуля // Возвращает список пользователей провайдера IP-телефонии async function VoipGetMembers(): Promise<VoipMember[]> { } // Сгенерировать исходящий звонок. Функция вызывается, когда пользователь системы нажимает кнопку звонка // в контекстном меню поля типа Телефон или кнопку Перезвонить в карточке звонка. В параметре // `srcPhone` указывается номер звонящего пользователя, в `dstPhone` — номер, на который нужно позвонить, // `ext` — добавочный номер. Рекомендуется настроить АТС или SIP-софтфон таким образом, чтобы добавочный номер // автоматически набирался тональным набором после ответа на вызов другой стороной. async function VoipGenerateCall(srcPhone: string, dstPhone: string, ext?: string): Promise<void> { } // Получить ссылку на запись звонка. Функция вызывается, когда пользователь системы проигрывает запись звонка из // ленты элемента приложения или при сохранении ссылки на запись звонка в контекст элемента приложения Звонок // раздела Телефония. В параметре `callData` содержатся данные, которые были указаны при сохранении // информации о записи звонка из функции `VoipParseWebhookRequest`. Функция должна возвращать ссылку на файл async function VoipGetCallLink(callData: any): Promise<string> { } // Вызывается автоматически при изменении ссылки на вебхук (например, при обновлении токена) // В параметре `webhookUrl` указывается абсолютная ссылка на вебхук модуля интеграции. Функция не обязательна к реализации async function VoipOnWebhookUpdated(webhookUrl: string): Promise<void> { }После реализации этих функций и сохранения скрипта на странице модуля вы найдете новый раздел под названием Настройки телефонии. В этом разделе будет указана ссылка на вебхук — при отправке данных на эту ссылку будет вызываться функция
VoipParseWebhookRequestиз скрипта. В этой функции вы должны обработать запрос и вернуть из неё экземплярVoipWebhookParseResult, на основании которого система отобразит уведомления о входящем звонке или сохранит информацию о записи звонка. Саму ссылку на вебхук вам необходимо указать в настройках вашего провайдера телефонии. Ссылка обрабатывает HTTP-запросы типаGETиPOST. Также в ссылке должен присутствовать обязательный параметрtoken.Рекомендуем вам ознакомиться со статьей справки «Интеграция с IP-телефонией через пользовательский модуль». Внутри статьи вы найдете заготовку модуля и пример реального использования API на основе интеграции с IP-телефонией Гравител.
Реализация поддержки кнопок в карточке звонка
При получении входящего звонка или совершении исходящего звонка у пользователя системы в правом нижнем углу появляется карточка с информацией о текущем звонке. В зависимости от состояния звонка, в этой карточке могут отображаться различные кнопки: Принять звонок, Завершить звонок, Переназначить и так далее. Чтобы эти кнопки отображались, реализуйте следующие функции в пользовательском модуле телефонии:
// Принять звонок. Функция вызывается, когда в карточке звонка пользователь системы нажимает кнопку Принять звонок // После этого начинают передаваться голосовые данные между оператором и клиентом // В параметре `srcPhone` указывается номер телефона звонящего, в `dstPhone` — номер, на который производится звонок async function VoipAcceptCall(srcPhone: string, dstPhone: string): Promise<void> { } // Завершить звонок. Функция вызывается, когда в карточке звонка пользователь системы нажимает кнопку Завершить звонок // Звонок между оператором и клиентом завершается // В параметре `srcPhone` указывается номер телефона звонящего, в `dstPhone` — номер, на который производится звонок async function VoipHangupCall(srcPhone: string, dstPhone: string): Promise<void> { } // Переназначить звонок. Функция вызывается, когда в карточке звонка пользователь системы нажимает кнопку Переназначить // Звонок текущего оператора завершается, и клиент соединяется с другим оператором // В параметре `srcPhone` указывается номер телефона звонящего, в `dstPhone` — номер, на который производится звонок, // `redirectTo` — внутренний номер оператора, на которого переназначается звонок async function VoipRedirectCall(srcPhone: string, dstPhone: string, redirectTo: string): Promise<void> { } // Поставить звонок на ожидание. Функция вызывается, когда в карточке звонка пользователь системы // нажимает кнопку Поставить на ожидание. Передача голосовых данных между оператором и клиентом прекращается без завершения звонка // В параметре `srcPhone` указывается номер телефона звонящего, в `dstPhone` — номер, на который производится звонок, // `hold` — значение `true`. Оно указывает, что нужно поставить звонок на ожидание, иначе — снять с ожидания async function VoipHoldCall(srcPhone: string, dstPhone: string, hold: boolean): Promise<void> { }Если в пользовательском модуле телефонии функция не реализована, соответствующая кнопка не будет отображаться в карточке звонка.
Реализация поддержки CTI-панели для звонков через браузер
Если провайдер телефонии поддерживает технологию WebRTC, то можно совершать звонки напрямую из браузера с помощью встроенной в систему CTI-панели. Она позволяет работать со звонками из единого интерфейса. Установка SIP-софтфона в таком случае не нужна.
Функционал CTI-панели доступен только при наличии у пользователя лицензии ELMA365 Omni отдельно или в составе другой лицензии.
Взаимодействие CTI-панели и телефонии
Интеграция телефонии в CTI-панели системы реализуется через виджет пользовательского модуля. Когда сотрудник выбирает провайдера телефонии в CTI-панели, автоматически создаётся экземпляр виджета. В его клиентских скриптах вызывается функция
connectдля инициализации подключения к провайдеру телефонии. Виджет позволяет:BrowserVoipClient.call. В результате, в виджете должен инициироваться исходящий звонок по протоколу телефонии, а в систему возвращаться объект с информацией о звонке. Подробнее о типах данных читайте в примере ниже;BrowserVoipEventSink.callReceived, чтобы в CTI-панели отобразилась информация о входящем звонке;Настройка пользовательского виджета для работы с CTI-панелью
Для реализации виджета взаимодействия с CTI-панелью выполните следующие шаги:
В пользовательском модуле телефонии создайте виджет со следующими параметрами:
browser_voip_client. По этому коду в системе определяется, какой конкретно виджет из модуля нужно использовать для интеграции с CTI-панелью;В открывшемся дизайнере интерфейсов на вкладке Скрипты добавьте клиентские скрипты с описанием интерфейсов для работы с CTI-панелью:
данные для подключения к телефонии:
interface BrowserVoipClientCredentials { // Внутренний номер пользователя телефонии extension: string; // Логин пользователя телефонии username: string; // Пароль пользователя телефонии password: string; }управление звонками через CTI-панель. Все методы интерфейса
BrowserVoipClient, кромеgetCallId, обязательны для реализации:// Установленное подключение к телефонии // @template TIncomingCall Входящий звонок // @template TOutgoingCall Исходящий звонок interface BrowserVoipClient<TIncomingCall = unknown, TOutgoingCall = unknown> { // Отключиться от телефонии. Вызывается системой, когда пользователь выключает CTI-панель disconnect(): Promise<void>; // Совершить исходящий звонок. Вызывается системой, когда пользователь вводит номер // в номеронабирателе и нажимает кнопку Совершить исходящий звонок. Также может // вызываться при звонке через контекстное меню поля Номер телефона // @param phoneNumber Номер телефона, на который нужно позвонить // @param ext Добавочный номер, который нужно автоматически набрать в тональном режиме // после установки соединения // @returns Объект, представляющий собой звонок. В этом объекте виджет может хранить // любые данные звонка // Объект будет передан в другие методы интерфейса `BrowserVoipClient`, когда в CTI-панели нужно // изменить состояние звонка call(phoneNumber: string, ext?: string): Promise<TOutgoingCall>; // Принять входящий звонок. Вызывается системой, когда пользователь нажал кнопку // принятия входящего звонка. Она появляется после того, как пришло событие телефонии о // новом входящем звонке. Подробнее читайте в описании `BrowserVoipEventSink.callReceived` // @param invite Входящий звонок, который был указан в аргументе // метода `BrowserVoipEventSink.callReceived` accept(invite: TIncomingCall): void; // Завершить звонок, т. е. повесить трубку. Также вызывается, если нужно отклонить входящий звонок // @param call Звонок hangup(call: TIncomingCall | TOutgoingCall): void; // Поставить звонок на удержание // @param call Звонок hold(call: TIncomingCall | TOutgoingCall): void; // Снять звонок с удержания // @param call Звонок unhold(call: TIncomingCall | TOutgoingCall): void; // Произвести слепой перевод (blind transfer) на указанный номер телефона. В результате вызова метода // ожидается, что переданный звонок будет завершён // @param call Звонок // @param redirectPhoneNumber Номер телефона, на который нужно произвести слепой перевод blindTransfer(call: TIncomingCall | TOutgoingCall, redirectPhoneNumber: string): void; // Завершить перевод с сопровождением (attended transfer). В результате вызова метода ожидается, что // два указанных абонента будут соединены друг с другом, а звонок `transferor` перейдёт в // состояние Звонок завершён // @param transferor Звонок текущего пользователя с клиентом, которого нужно перевести // на сотрудника `caller` // @param caller Звонок текущего пользователя с сотрудником, на которого будет переведён звонок attendedTransfer(transferor: TIncomingCall | TOutgoingCall, caller: TIncomingCall | TOutgoingCall): void; // Отправить DTMF-сигнал. Вызывается системой при вводе пользователем DTMF-кода в интерфейсе CTI-панели // @param call Звонок // @param key Сигнал для отправки. Например: '1', '5', '*', '#' // @returns Promise, который разрешается после отправки сигнала sendDtmf(call: TIncomingCall | TOutgoingCall, key: string): Promise<void>; // Получить уникальный идентификатор звонка на стороне телефонии. Данный идентификатор должен быть // одинаковым на стороне клиента в браузере и в событии, которое генерируется функцией // `VoipParseWebhookRequest` при обработке события через вебхук. ID звонка используется системой для // обновления информации о звонке дополнительными данными с сервера. Например, к звонку в CTI-панели // может быть привязан элемент приложения Звонок раздела Телефония // @param call Звонок // @returns Идентификатор звонка или null, если идентификатор недоступен getCallId?(call: TIncomingCall | TOutgoingCall): string | null; }отправка уведомлений в CTI-панель:
// Объект, через который соединение с телефонией `BrowserVoipClient` может отправлять уведомления // об изменении состояния звонков в CTI-панель // @template TIncomingCall Входящий звонок // @template TOutgoingCall Исходящий звонок interface BrowserVoipEventSink<TIncomingCall = unknown, TOutgoingCall = unknown> { // Уведомить о новом входящем звонке. В CTI-панели пользователю отобразится предложение // принять или отклонить входящий звонок // @param invite Объект с информацией о входящем звонке. Это значение будет передано как есть в // `BrowserVoipClient.accept` или `BrowserVoipClient.hangup` // @param phoneNumber Номер телефона, с которого нам звонят callReceived(invite: TIncomingCall, phoneNumber: string): void; // Уведомить о завершении звонка. В CTI-панели звонок перейдёт в состояние Звонок завершён // @param call Звонок callFinished(call: TIncomingCall | TOutgoingCall): void; // Уведомить об успешной установке соединения между двумя абонентами для указанного звонка. // Ожидается, что оба абонента могут слышать друг друга. В CTI-панели звонок перейдёт в состояние // Идёт разговор // @param call Звонок callEstablished(call: TIncomingCall | TOutgoingCall): void; // Отправить событие потери соединения с телефонией. Этот метод следует вызывать, когда // произошла критическая ошибка, из-за которой автоматически восстановить соединение // с телефонией невозможно // @param reason Причина ошибки, которую увидит пользователь disconnected(reason: string): void; }Также на вкладке Скрипты в клиентских скриптах реализуйте функцию
connect. Она вызывается автоматически после того, как пользователь системы включит CTI-панель. При успешном подключении к телефонии с помощью этой функции возвращается реализация интерфейсаBrowserVoipClient. В дальнейшем в системе будут вызываться методы этого объекта в зависимости от действий пользователя в CTI-панели. Метод также принимает параметр типаBrowserVoipClientCredentials, в котором находится внутренний номер, логин и пароль для подключения к телефонии. Администратор системы заполняет эту информацию на странице настроек модуля телефонии. ОбъектBrowserVoipEventSinkиспользуется для отправки событий в CTI-панель, например, получение нового входящего звонка или изменение состояния звонка.async function connect(credentials: BrowserVoipClientCredentials, eventSink: BrowserVoipEventSink): Promise<BrowserVoipClient> { const client: BrowserVoipClient = { // Реализуйте интерфейс BrowserVoipClient }; try { // Подключитесь к каналу сигнализации телефонии, используя данные из аргумента `credentials` ... // На момент возврата данных из метода экземпляр `BrowserVoipClient` должен быть подключён к телефонии return client; } catch { // В случае ошибки соединения закройте все подключения к телефонии ... // Пробросьте исключение, чтобы в CTI-панели пользователь увидел // сообщение об ошибке подключения телефонии throw; } }Сохраните и опубликуйте виджет. После этого при включённом модуле пользователи смогут выбрать провайдера телефонии в CTI-панели.
Автоматическая аутентификация в телефонии (SSO)
Чтобы сотрудники могли подключаться к телефонии через CTI-панель, администратор системы должен заполнить в настройках пользовательского модуля логин и пароль для каждого пользователя телефонии. При большом количестве сотрудников это может быть трудоёмко. Чтобы повысить безопасность и снизить нагрузку на администратора системы, рекомендуется автоматически получать учётные данные из телефонии или стороннего сервиса. Схема работы может выглядеть следующим образом:
voip_supports_single_sign_onс типом данных Выбор «да/нет». Укажите значение по умолчанию для этого поля — Да или вынесите его на форму настроек модуля. Когда в этом поле указано значение Да, в системе не отображаются поля для ввода логина и пароля пользователя телефонии при настройке CTI-панели;api_keyс типом данных Строка. В этом поле хранится API-ключ для аутентификации в телефонии.extensionс типом данных Строка. Используется, чтобы передать внутренний номер пользователя, для которого нужно получить токен;auth_tokenс типом данных Строка. Используется, чтобы получить токен для подключения к телефонии.connectклиентских скриптов добавьте вызов серверной функции. С её помощью отправляется запрос на получение учётных данных для подключения к телефонии. Текущего пользователя можно получить вызовом методаSystem.users.getCurrentUser(). Полученные учётные данные сохраните в контекстной переменной виджетаauth_token.// Клиентские скрипты async function connect(credentials: BrowserVoipClientCredentials, eventSink: BrowserVoipEventSink): Promise<BrowserVoipClient> { // Сохраняем внутренний номер пользователя в переменную, чтобы серверный скрипт имел к нему доступ Context.data.extension = credentials.extension; await Server.rpc.getAuthToken(); // Вызов getAuthToken() сохранит токен в контекстной переменной const token = Context.data.auth_token; if (!token) { throw new Error('Не удалось получить токен авторизации'); } // Используйте `token` для подключения к телефонии ... } // Серверные скрипты async function getAuthToken(): Promise<void> { const currentUser = await System.users.getCurrentUser(); const extension = Context.data.extension; // API-ключ для стороннего сервиса, обычно заполняется на странице настроек модуля const apiKey = (await Namespace.getParams()).data.api_key; const response = await fetch('https://example.com/auth', { method: 'POST', body: JSON.stringify({ userId: currentUser.id, extension: extension, apiKey: apiKey, }), }); if (response.ok) { const token = await response.text(); Context.data.auth_token = token; } }