Pull to refresh

WebRTC или как я научил нашу CRM звонить на телефоны

Reading time 7 min
Views 35K
Компания, в которой мне довелось работать, занимается продажей услуг по интернету. Каждое утро дежурная смена разбирает общий стек накопившихся заявок и начинается обзвон клиентов для уточнения заказов. В течение дня операторы еще и принимают входящие звонки. До начала моей затеи они использовали для звонков такой десктопный SIP-клиент:



Эта звонилка устанавливалась на компьютере каждого сотрудника, принимала звонки и звонила, куда надо. Чтобы сделать какие-либо изменения в настройках, нужно было обойти все машины и сделать все вручную. При этом, если сотрудник работает удаленно, приходилось его консультировать по телефону, как это все сделать. И часто это было довольно непросто.

Но самым главным траблом было отсутствие интеграции с нашей web-системой и базой данных. Такие, казалось бы простые задачи, как открытие карточки клиента на входящий звонок, сохранение статистики звонков для каждого из сотрудников и мониторинг их активности из административного web интерфейса — все это очень сложно сделать с десктопным софтфоном, даже в том случае он имеет соответствующие возможности интеграции с браузером, например с помощью плагинов.

Возникла идея объединить в одной системе и базе данных всю внутреннюю работу и звонки. Я долго допиливал нашу CRM с функцией встроенной звонилки c записью разговоров.
Для реализации звонков рассмотрел ряд технологий и пришел к выводу, что их не так уж и много. Нашлась пара опенсорсных и коммерческих реализаций, а так же несколько SAAS сервисов, которые не подходили в силу внутренних политик безопасности — обрабатывать звонки через собственный сервер.

В начале пытался использовать sipml5:

image

Документацию пришлось собирать по кускам из сети. В результате получил более-менее рабочий телефон с SIP стэком на стороне браузера:

image

Установка, тестирование и настройки длились около 2 недель, в результате нашел ряд мелких, но неприятных багов, которые так и не удалось обойти, например один из них был связан с настройками Websockets через SSL. А после выхода Chrome браузера 35 версии web телефон отказался работать совсем.

Кроме этого мне не хотелось раскрывать SIP аккаунты операторам, а SIP стэк на стороне браузера предполагает их открытое использование и отправку через Websockets. Даже если Websockets работают через SSL, у потенциального злоумышленника есть возможность отдебажить js код и вытащить SIP пароль. Был вариант — делегировать SIP Digest аутентификацию нашему Web серверу, но до его реализации добраться так и не удалось.

Так выглядят SIP запросы на стороне браузера в отладочной консоли:

image

Полный доступ к SIP стэку из Javascript иметь в общем не плохо. В этом есть свои преимущества, например можно попытаться поправить какой-нибудь интеграционный баг в JS SIP сигналинге. Но здесь есть один нюанс. Чуть более чем 90% SIP вендоров не поддерживают в настоящий момент спецификацию The WebSocket Protocol as a Transport for the Session Initiation Protocol (SIP) RFC 7118 датируемую январем 2014 года по которой работает JS SIP, а это означает что webrtc2sip модуль должен работать как stateful SIP прокси и фактически дублировать поддержку SIP стэка на стороне сервера. Такой расклад показался сильно сложным для дальнейшей работы и поддержки и я решил уйти от SIP стэка на стороне браузера и найти какое-нибудь более простое и понятное API для таких задач с серверной частью, которую можно было бы хостить у себя.

В результате начал тестировать Web Call Server. Это не SAAS и позволяет обрабатывать звонки через свой сервер, что в данном случае и требовалось:

image

По функциям примерно то же самое что и у sipml5, те же WebRTC звонки на SIP и обратно. Есть еще поддержка Flash, но в ней не было необходимости, так как все операторы используют в основном Chrome и Firefox браузер, а тем, кто использует IE, пришлось пересесть на более “правильные” браузеры.

В нагрузку дается софтфон на JS с открытым исходным кодом, который можно перерисовать и адаптировать для web-страницы.

Основное отличие от sipml5 — это взаимодействие с сервером через API, а не через SIP over Websockets. Т.е. SIP стэка на стороне браузера нет. Он располагается только на стороне сервера. Это немного облегчило задачу front-end разработчику, т.к. SIP стэк на стороне браузера повергал его в смятение, а при работе с Javascript API и CSS стало возможным сосредоточиться на интерфейсной части.

image

Итак, как я все это внедрял.

1. Взял вот такой сервер на Amazon EC2:
Памяти и дискового пространства много не требуется. Разве что для логов. А вычислительные мощности CPU в таких задачах могут быть важны, поэтому взял не самый слабый инстанс.

image

2. Поднял Apache для web-интерфейса, установил и запустил WCS сервер.

image

3. На странице хрома появился стандартный web-телефон, код которого находится на github.
Интерфейс телефона мне не очень понравился, сразу же решил его редизайнить, а отладочная консоль справа оказалась вполне полезной. Жаль, что позже ее пришлось убрать, чтобы не пугать нормального пользователя.

image

4. Протестировал web-телефон на способность звонить. Использовал для этого наши прежние SIP-аккаунты. Все работает, как надо. И на мобильники звонит, и на SIP-телефоны, и удерживание звонков и трансфер, и блэкджек и…

image

Похожим образом работает созвон с мобильным телефоном.

5. Адаптировал код web-телефона для своей web-CRM, перерисовал его дизайн и теперь он выглядит так:

image

На адаптации стоит остановиться подробнее, т.к. перерисовкой дизайна дело не ограничилось.
Первой же серьезной задачей стала автоматическая регистрация web-телефона на SIP сервере. В противном случае, оператору пришлось бы вводить SIP логин и пароль повторно, уже после того, как он ввел логин и пароль для CRM системы. Встал вопрос, как интегрировать.

Оказалось, что в API есть для этого специальная функция loginByToken:

function loginByToken(token) {
           trace("Phone - loginByToken "+ token);
           connectingViewBeClosed = false;
           var result = flashphoner.loginByToken(flashphonerLoader.urlServer, token, document.URL);
           closeLoginView();
           openConnectingView("Connecting...", 0);
}

Для того чтобы разобраться, как эта функция работает, пришлось хорошо постараться.
C помощью документации и примеров удалось выяснить, что все это работает примерно так:

image

1) При создании токена на стороне CRM используется алгоритм шифрования AES, которым зашифровывается строка, включающая SIP логин и пароль пользователя, а так же другую необходимую информацию.

Ключ шифрования известен только нашему серверу, где развернута CRM, а так же WCS серверу. Кроме того, срок действия токена задается специальным атрибутом expires для того, чтобы не было возможности им повторно воспользоваться.
Криптование токена происходит в AES CTR mode. Ниже пример c openssl, в котором происходит генерация шифрованного токена с передачей SIP пароля:

 echo -ne '<root status="ok" description="test" registerRequired="true" login="user5" authenticationName="user5" password="password" outboundProxy="proxy.my" domain="proxy.my" port="5060" visibleName="AAA" api_key="App1" expires="1394839040761100000"/>' | openssl enc -aes-128-ctr -nosalt -K 8263D535FFFFFFFF7B0F60 -iv 00000000000000000000000000000000 | xxd -p


В результате получил что-то вроде:

 CRM:cf4693eedaafda1390b261dcf29d45bd3556d64b1f69cd84db8c3ac8721e7e139b80be75e39da18154e897596e9317084faee0d24d6a6197b62a93a2647b263059167b2664179a5866738260c77372e04fe22104ebe1c7530e9215f50d111fd24384755d28d06673e866159c0b6b83289c045619e8481f9c2a6b56b182f393a7dea06b38b7856436895402a5b40f0525a17822ae0f3204b606e4f0169d1ca9176e8e1b696683d12c7db8208946c204e94f3c8ff285f2bcef4ca9b12187cf541ce37d508d3663ef65f944b01db9aea5c0f10002a376d051cbf1b19bc34f76b6d2a4e1ad1450ae412b51b3af1d3860167f5416b3d2c9eeff94d60b82279e8685beb543893e8a09dee640d7366e478d0d1ee7368e0b63b511


Слева название нашего приложения “CRM”, а справа созданый ранее токен.
Вставляю этот токен в конфиг flashphoner.xml web-телефона в таком виде:

<token>CRM:cf4693eed...</token> 


В этом случае процедура автоматической регистрации по токену начнется сразу же после перезагрузки страницы.
2 и 3) loginByToken и расшифровка.

На стороне сервера в конфиге прописаны ключи шифрования для AES:

 CRM=8263D535FFFFFFFF7B0F60


Таким образом, когда приходит токен с префиксом “CRM:” для его расшифровки используется соответствующий ключ.

В результате расшифровки WCS сервер получает зашифрованную ранее строку:

<root status="ok" description="test" registerRequired="true" login="user5" authenticationName="user5" password="password" outboundProxy="proxy.my" domain="proxy.my" port="5060" visibleName="AAA" api_key="App1" expires="1394839040761100000"/>  


и из этой XML строки берет все данные необходимые для SIP регистрации.

3) Как только сервер расшифровал данные, он посылает SIP REGISTER запрос на SIP и на 401 ответ отдает уже нормальную Digest аутентификацию с использованием расшифрованных на предыдущем шаге SIP логина и пароля.

 
REGISTER sip:sipnet.ru;lr SIP/2.0
Call-ID: 345ec5157b1a66de3a3a275bdba36197@192.168.1.90
CSeq: 2 REGISTER
From: <sip:crm1@sipnet.ru>;tag=73a499a8
To: <sip:crm1@sipnet.ru>
Via: SIP/2.0/UDP 192.168.1.90:30000;branch=z9hG4bK2622ce723c34760d6a3f43dd631329e1
Max-Forwards: 70
User-Agent: WebRTC
Allow: UPDATE,MESSAGE,BYE,ACK,REFER,INVITE,NOTIFY,INFO,OPTIONS,CANCEL
Contact: <sip:crm1@192.168.1.90:30000>;expires=3600
Expires: 3600
Authorization: Digest
username="crm1",realm="etc.tario.ru",nonce="4A0674BEDF81E0B3F65D",uri="sip:sipnet.ru;lr",response="0762b862c544007f4fb7c43277312a3d",algorithm=MD5,opaque="opaq",qop=auth,cnonce="1234567890",nc=00000001
Content-Length: 0


В этом случае SIP логин и пароль знают только сама CRM и Web Call Server. На браузер эти данные в открытом виде не попадают.
Таким образом мне удалось внедрить телефон в страницу оператора, не заставляя его хранить два разных аккаунта — один для CRM другой для SIP, потому что это очень неудобно. Теперь сразу после загрузки страницы вызывается loginByToken и телефон переходит в состояние готовности.

Некоторые результаты внедрения браузерных звонков:

1. Звонки теперь делаются с сайта и принимаются на сайте, где все действия фиксируются в системе.

2. Стало возможным прослушивание записанных разговоров, что помогает разрешать конфликтные ситуации с клиентами и разногласия между сотрудниками. Это важно для нашего распределенного офиса.

3. Количество принятых звонков увеличилось примерно на 20%. Стало понятно, что операторы не всегда поднимали трубку при звонке клиента.
На текущий момент можно сказать, что все работает, как задумано. Проблемные ситуации удалось разрешить без серьезного погружения в SIP матчасть.

Из недостатков можно отметить невозможность установки под Windows. Кстати, с установкой под Linux и интеграцией тоже пришлось повозиться и, похоже, что осилит ее только продвинутый пользователь/разработчик.

WebRTC аудио звонки работают стабильно и без каких-либо дополнительных браузерных плагинов, типа Flash Player. Так что можно сказать, что мне удалось реализовать задуманную интеграцию и две недели работы были потрачены не зря.
Tags:
Hubs:
+30
Comments 26
Comments Comments 26

Articles