[Из песочницы] WebRTC или как я научил нашу CRM звонить на телефоны
Компания, в которой мне довелось работать, занимается продажей услуг по интернету. Каждое утро дежурная смена разбирает общий стек накопившихся заявок и начинается обзвон клиентов для уточнения заказов. В течение дня операторы еще и принимают входящие звонки. До начала моей затеи они использовали для звонков такой десктопный SIP-клиент:
Эта звонилка устанавливалась на компьютере каждого сотрудника, принимала звонки и звонила, куда надо. Чтобы сделать какие-либо изменения в настройках, нужно было обойти все машины и сделать все вручную. При этом, если сотрудник работает удаленно, приходилось его консультировать по телефону, как это все сделать. И часто это было довольно непросто.
Но самым главным траблом было отсутствие интеграции с нашей web-системой и базой данных. Такие, казалось бы простые задачи, как открытие карточки клиента на входящий звонок, сохранение статистики звонков для каждого из сотрудников и мониторинг их активности из административного web интерфейса — все это очень сложно сделать с десктопным софтфоном, даже в том случае он имеет соответствующие возможности интеграции с браузером, например с помощью плагинов.
Возникла идея объединить в одной системе и базе данных всю внутреннюю работу и звонки. Я долго допиливал нашу CRM с функцией встроенной звонилки c записью разговоров.Для реализации звонков рассмотрел ряд технологий и пришел к выводу, что их не так уж и много. Нашлась пара опенсорсных и коммерческих реализаций, а так же несколько SAAS сервисов, которые не подходили в силу внутренних политик безопасности — обрабатывать звонки через собственный сервер.
В начале пытался использовать sipml5:
Документацию пришлось собирать по кускам из сети. В результате получил более-менее рабочий телефон с SIP стэком на стороне браузера:
Установка, тестирование и настройки длились около 2 недель, в результате нашел ряд мелких, но неприятных багов, которые так и не удалось обойти, например один из них был связан с настройками Websockets через SSL. А после выхода Chrome браузера 35 версии web телефон отказался работать совсем.
Кроме этого мне не хотелось раскрывать SIP аккаунты операторам, а SIP стэк на стороне браузера предполагает их открытое использование и отправку через Websockets. Даже если Websockets работают через SSL, у потенциального злоумышленника есть возможность отдебажить js код и вытащить SIP пароль. Был вариант — делегировать SIP Digest аутентификацию нашему Web серверу, но до его реализации добраться так и не удалось.
Так выглядят SIP запросы на стороне браузера в отладочной консоли:
Полный доступ к 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 и позволяет обрабатывать звонки через свой сервер, что в данном случае и требовалось:
По функциям примерно то же самое что и у sipml5, те же WebRTC звонки на SIP и обратно. Есть еще поддержка Flash, но в ней не было необходимости, так как все операторы используют в основном Chrome и Firefox браузер, а тем, кто использует IE, пришлось пересесть на более «правильные» браузеры.
В нагрузку дается софтфон на JS с открытым исходным кодом, который можно перерисовать и адаптировать для web-страницы.
Основное отличие от sipml5 — это взаимодействие с сервером через API, а не через SIP over Websockets. Т.е. SIP стэка на стороне браузера нет. Он располагается только на стороне сервера. Это немного облегчило задачу front-end разработчику, т.к. SIP стэк на стороне браузера повергал его в смятение, а при работе с Javascript API и CSS стало возможным сосредоточиться на интерфейсной части.
Итак, как я все это внедрял.
1. Взял вот такой сервер на Amazon EC2: Памяти и дискового пространства много не требуется. Разве что для логов. А вычислительные мощности CPU в таких задачах могут быть важны, поэтому взял не самый слабый инстанс.
2. Поднял Apache для web-интерфейса, установил и запустил WCS сервер.
3. На странице хрома появился стандартный web-телефон, код которого находится на github.Интерфейс телефона мне не очень понравился, сразу же решил его редизайнить, а отладочная консоль справа оказалась вполне полезной. Жаль, что позже ее пришлось убрать, чтобы не пугать нормального пользователя.
4. Протестировал web-телефон на способность звонить. Использовал для этого наши прежние SIP-аккаунты. Все работает, как надо. И на мобильники звонит, и на SIP-телефоны, и удерживание звонков и трансфер, и блэкджек и…
Похожим образом работает созвон с мобильным телефоном.
5. Адаптировал код web-телефона для своей web-CRM, перерисовал его дизайн и теперь он выглядит так:
На адаптации стоит остановиться подробнее, т.к. перерисовкой дизайна дело не ограничилось.Первой же серьезной задачей стала автоматическая регистрация 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 помощью документации и примеров удалось выяснить, что все это работает примерно так:
1) При создании токена на стороне CRM используется алгоритм шифрования AES, которым зашифровывается строка, включающая SIP логин и пароль пользователя, а так же другую необходимую информацию.
Ключ шифрования известен только нашему серверу, где развернута CRM, а так же WCS серверу. Кроме того, срок действия токена задается специальным атрибутом expires для того, чтобы не было возможности им повторно воспользоваться.Криптование токена происходит в AES CTR mode. Ниже пример c openssl, в котором происходит генерация шифрованного токена с передачей SIP пароля:
echo -ne '
В результате получил что-то вроде:
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:
В этом случае SIP логин и пароль знают только сама CRM и Web Call Server. На браузер эти данные в открытом виде не попадают.Таким образом мне удалось внедрить телефон в страницу оператора, не заставляя его хранить два разных аккаунта — один для CRM другой для SIP, потому что это очень неудобно. Теперь сразу после загрузки страницы вызывается loginByToken и телефон переходит в состояние готовности.
Некоторые результаты внедрения браузерных звонков:
1. Звонки теперь делаются с сайта и принимаются на сайте, где все действия фиксируются в системе.
2. Стало возможным прослушивание записанных разговоров, что помогает разрешать конфликтные ситуации с клиентами и разногласия между сотрудниками. Это важно для нашего распределенного офиса.
3. Количество принятых звонков увеличилось примерно на 20%. Стало понятно, что операторы не всегда поднимали трубку при звонке клиента.На текущий момент можно сказать, что все работает, как задумано. Проблемные ситуации удалось разрешить без серьезного погружения в SIP матчасть.
Из недостатков можно отметить невозможность установки под Windows. Кстати, с установкой под Linux и интеграцией тоже пришлось повозиться и, похоже, что осилит ее только продвинутый пользователь/разработчик.
WebRTC аудио звонки работают стабильно и без каких-либо дополнительных браузерных плагинов, типа Flash Player. Так что можно сказать, что мне удалось реализовать задуманную интеграцию и две недели работы были потрачены не зря.