[Из песочницы] WebRTC или как я научил нашу CRM звонить на телефоны

Компания, в которой мне довелось работать, занимается продажей услуг по интернету. Каждое утро дежурная смена разбирает общий стек накопившихся заявок и начинается обзвон клиентов для уточнения заказов. В течение дня операторы еще и принимают входящие звонки. До начала моей затеи они использовали для звонков такой десктопный SIP-клиент: 58e3c6a09f97951c4a98ea5bc6539b1f.png

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

Но самым главным траблом было отсутствие интеграции с нашей 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 '' | openssl enc -aes-128-ctr -nosalt -K 8263D535FFFFFFFF7B0F60 -iv 00000000000000000000000000000000 | xxd -p

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

CRM: cf4693eedaafda1390b261dcf29d45bd3556d64b1f69cd84db8c3ac8721e7e139b80be75e39da18154e897596e9317084faee0d24d6a6197b62a93a2647b263059167b2664179a5866738260c77372e04fe22104ebe1c7530e9215f50d111fd24384755d28d06673e866159c0b6b83289c045619e8481f9c2a6b56b182f393a7dea06b38b7856436895402a5b40f0525a17822ae0f3204b606e4f0169d1ca9176e8e1b696683d12c7db8208946c204e94f3c8ff285f2bcef4ca9b12187cf541ce37d508d3663ef65f944b01db9aea5c0f10002a376d051cbf1b19bc34f76b6d2a4e1ad1450ae412b51b3af1d3860167f5416b3d2c9eeff94d60b82279e8685beb543893e8a09dee640d7366e478d0d1ee7368e0b63b511

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

CRM: cf4693eed…

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

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

CRM=8263D535FFFFFFFF7B0F60

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

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

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

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

REGISTER sip: sipnet.ru; lr SIP/2.0Call-ID: 345ec5157b1a66de3a3a275bdba36197@192.168.1.90CSeq: 2 REGISTERFrom: ; tag=73a499a8To: Via: SIP/2.0/UDP 192.168.1.90:30000; branch=z9hG4bK2622ce723c34760d6a3f43dd631329e1Max-Forwards: 70User-Agent: WebRTCAllow: UPDATE, MESSAGE, BYE, ACK, REFER, INVITE, NOTIFY, INFO, OPTIONS, CANCELContact: ; expires=3600Expires: 3600Authorization: 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=00000001Content-Length: 0

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

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

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

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

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

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

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

© Habrahabr.ru