REST hooks для WebRTC Click to Call. Опыт внедрения
- четверг, 8 июля 2021 г. в 00:38:57
Кнопка "Click to Call" на сайте — это "инновация", которой уже около 10 лет. Технологии под капотом изменились, а принцип остался прежним - кликаем по кнопке на странице сайта, запускается JavaScript, который запрашивает доступ к микрофону и устанавливает соединение с сервером — WebRTC SIP шлюзом. Далее одна клиент-серверная нога — это браузер-шлюз, вторая нога может быть сколь угодно длинной и через цепочку SIP proxy может соединяться в конечном счете с мобильным или стационарным телефоном. Таким образом, браузер превращается, в каком-то смысле, в софтфон и становится полноправным участником VoIP телефонии.
Звонок в один клик очень удобен для пользователей, которые заходят на сайт через мобильные браузеры, а таких, на сегодняшний день, большинство. Кроме удобства для клиентов, еще есть возможность сэкономить. Можно настроить так, что звонок с кнопки "Click to Call" будет тарифицироваться, как звонок внутри вашей АТС, т.е. в большинстве случаев бесплатно. Экономия, по сравнению с подключением и ежемесячным обслуживанием номера 8-800, достаточно большая.
У нас на сайте есть минимальный пример внедрения такой кнопки. В примере все хорошо - код простой, бери, копируй и радуйся. Но есть одно НО! Передавать параметры подключения к SIP серверу в JS коде небезопасно, злоумышленник легко может подменить номер вызываемого абонента или использовать учетные данные вашего SIP сервера, чтобы звонить пингвинам в Антарктиду и пересказывать им "Войну и Мир". И тогда прибыль от размещения на сайте кнопки "Click to call" может превратиться в огромные убытки.
Поэтому для Production реализации мы рекомендуем хранить параметры подключения на стороне сервера и подставлять их при инициализации звонка. Для этого можно использовать REST Hook скрипты.
REST Hook — это простые скрипты, которые работают с JSON в теле HTTP-запроса и отдают JSON в теле HTTP ответов. Скрипты REST Hook подменяют собой стандартные приложения WCS API и позволяют обрабатывать данные о коннектах, звонках и видеопотоках на бэкенд сервере.
REST Hook могут быть использованы для следующих целей:
Аутентификация коннектов к серверу по токену или по паролю
Получение в реальном времени информации о коннектах, дисконнектах, начале и завершении потоков, звонков, и т.д.
Переопределение данных, переданных с клиента. Например, можно переопределить и скрыть реальное имя потока или направление звонка.
Реализация кастомного сигналинга с передачей данных через WebSockets, например рассылка текстового сообщения в чате всем подключенным клиентам.
В этой статье рассмотрим, как можно безопасно передать учетные данные SIP сервера и номер вызываемого абонента для кнопки "Click to Call" с использованием технологии REST Hook.
Фронтенд Web сервер — организует интерфейс с пользователем. Отображает web страницу с кнопкой "Click to Call".
WCS — посредник между пользователем и SIP сервером. Конвертирует WebRTC поток от браузера в SIP формат.
Бэкенд сервер — Web сервер, который обеспечивает работу REST Hook.
SIP сервер и SIP телефон.
Логически мы разделяем фронтенд, бэкенд и WCS серверы, но физически их можно разместить на одной машине. В этой статье, для упрощения разбора примера, мы используем три отдельные виртуальные машины.
Начнем настройку с бэкенд сервера.
Устанавливаем и конфигурируем Nginx и PHP, например Nginx на CentOS 7.
После чего, в файле /etc/nginx/nginx.conf в секции «server» добавляем следующие строки. Эта настройка обеспечит доступность нашего REST Hook скрипта для событий "/connect" и "/call":
location / {
try_files $uri $uri/ /index.php?$request_uri;
}
В каталоге для файлов web сервера (у нас /var/www ) создаем файл «index.php», в нем размещаем основной код создаваемого REST Hook. Этот скрипт будет реализовывать проверку доступа по домену и передавать параметры подключения к SIP серверу и номер вызываемого абонента:
<?php
$api_method = array_pop(explode("/", $_SERVER['REQUEST_URI']));
$incoming_data = json_decode(file_get_contents('php://input'), true);
$domain = "yourdomain.com";
switch($api_method) {
case"connect":
$origin = $incoming_data['origin'];
//logs
error_log("sessionId: " . $incoming_data['sessionId']);
error_log("origin: " . $origin);
$found = strpos($origin, $domain);
if ($found !== false){
error_log("User authorized by domain " . $domain);
}else{
error_log("User not authorized by domain: " . $domain . " Connection failed with 403 status.");
ubnormalResponse(403);
}
$rest_client_config = json_decode(file_get_contents('rest_client_config.json'), true);
$incoming_data['restClientConfig'] = $rest_client_config;
$incoming_data['sipLogin'] = "10001";
$incoming_data['sipAuthenticationName'] = "10001";
$incoming_data['sipPassword'] = "Password_123";
$incoming_data['sipDomain'] = "172.16.30.156";
$incoming_data['sipOutboundProxy'] = "172.16.30.156";
$incoming_data['sipPort'] = "5060";
break;
case "call":
// Callee Number
$incoming_data['callee'] = "10002";
break;
}
header('Content-Type: application/json');
echo json_encode($incoming_data);
function ubnormalResponse($code) {
if ($code == 403) {
header('HTTP/1.1 403 Forbidden', true, $code);
} else {
header(':', true, $code);
}
die();
}
?>
В том же каталоге /var/www создаем файл «rest_client_config.json». Исходный код можно найти в конце этой страницы.
В файле «rest_client_config.json» правим секцию «call» . Здесь указываем политику REST метода - перезаписывать данные и какое значение будет перезаписано с помощью скрипта:
"call" : {
"clientExclude" : "",
"restExclude" : "",
"restOnError" : "LOG",
"restPolicy" : "OVERWRITE",
"restOverwrite" : "callee"
},
Затем, переходим к WCS серверу. Предполагаем, что у вас уже есть установленный и настроенный экземпляр WCS. Если нет, то устанавливаем по этой инструкции. WCS можно запустить как виртуальный инстанс на Amazon, Google Cloud и DigitalOcean, или как контейнер в Docker.
В консоли вашего сервера с WCS проверяем доступность скрипта REST Hook для событий /connect и /call с помощью утилиты "Curl":
curl http://172.16.30.123/connect
curl http://172.16.30.123/call
замените 172.16.30.123 на IP адрес или доменное имя вашего бэкенд web сервера.
Если вывод утилиты Curl содержит нужную информацию — параметры для SIP подключения и номер вызываемого абонента — значит REST Hook мы настроили правильно.
Заходим в CLI WCS сервера:
ssh -p 2001 admin@localhost
в этой команде ничего менять не нужно, пароль по умолчанию: admin
и меняем приложение по умолчанию для обработки событий "/connect" и "/call" на наш новый REST Hook скрипт с помощью команды:
update app -l http://172.16.30.123/ defaultApp
замените 172.16.30.123 на IP адрес или доменное имя вашего бэкенд web сервера.
После всех этих настроек переходим к фронтенд web серверу.
Создаем на фронтенде два пустых файла Click-to-Call-min.html и Click-to-Call-min.js. Эти файлы будут содержать минимальный код для реализации кнопки «Click to call».
HTML код:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<script type="text/javascript" src="https://flashphoner.com/downloads/builds/flashphoner_client/wcs_api-2.0/current/flashphoner.js"></script>
<script type="text/javascript" src="Click-to-Call-min.js"></script>
</head>
<body onload="init_page()">
<input type="button" id="callBtn" type="button" Value="Call"/>
<div id="remoteAudio"></div>
<div id="localAudio"></div>
</body>
</html>
Из JS кода минимального примера убираем данные для подключения к SIP серверу и номер вызываемого абонента.
JS код:
var SESSION_STATUS = Flashphoner.constants.SESSION_STATUS;
var CALL_STATUS = Flashphoner.constants.CALL_STATUS;
var localAudio;
var remoteAudio;
function init_page(){
Flashphoner.init({});
localAudio = document.getElementById("localAudio");
remoteAudio = document.getElementById("remoteAudio");
connect();
}
function connect() {
var url = "wss://172.16.30.124:8443"
var sipOptions = {
registerRequired: true
};
var connectionOptions = {
urlServer: url,
sipOptions: sipOptions
};
console.log("Create new session with url " + url);
Flashphoner.createSession(connectionOptions).on(SESSION_STATUS.ESTABLISHED, function (session) {
console.log(SESSION_STATUS.ESTABLISHED);
}).on(SESSION_STATUS.REGISTERED, function (session) {
console.log(SESSION_STATUS.REGISTERED);
});
callBtn.onclick = call
}
function call(session) {
var constraints = {
audio: true,
video: false
};
var session = Flashphoner.getSessions()[0];
var outCall = session.createCall({
remoteVideoDisplay: remoteAudio,
localVideoDisplay: localAudio,
constraints: constraints,
stripCodecs: "SILK"
});
outCall.call();
callBtn.value = "Hangup";
callBtn.onclick = function () {
callBtn.value = "Call";
outCall.hangup();
connect();
}
}
Для тестирования нам понадобятся:
Тестовый стенд, который мы собрали выше (фронтeнд, WCS, бэкенд);
SIP сервер
Два SIP аккаунта
Браузер
Программный SIP телефон
Данные для подключения SIP аккаунта 10001 мы передаем в JS коде страницы с кнопкой "Click to Call" при помощи REST Hook. Кнопка "Click to Call" запрограммирована сделать вызов на номер 10002. Учетные данные для аккаунта 10002 внесем в программный SIP телефон.
Открываем созданную на фронтенд web сервере HTML страницу и нажимаем кнопку "Call":
Принимаем входящий звонок на программном SIP телефоне и проверяем, что происходит обмен аудио потоками между абонентами:
Пришлось немного поработать, но результат того стоит. Теперь учетные данные SIP сервера и номер вызываемого абонента защищены от злоумышленников, и можно не переживать, что кто-то использует вашу телефонию в своих интересах.
Онлайн звонок с сайта по кнопке
Файл настроек flashphoner.properties
Документация по быстрому развертыванию Web Call Server