Минимальные примеры на нашем сайте написаны так, чтобы любой клиент, даже далекий от web программирования, мог взять куски кода и сделать свой продукт. Но бездумное копирование кода может привести к финансовым потерям. Яркий пример — минимальный код внедрения кнопки Click to Call.

Click to Call («звонок по клику») — кнопка на сайте, которая позволяет пользователям совершить звонок по заранее заданному номеру прямо из браузера. Очень удобный маркетинговый инструмент, который повышает конверсии и прибыль.

Если внедрять кнопку Click to Call по методике описанной здесь, злоумышленник может подменить номер вызываемого абонента или использовать учетные данные вашего SIP сервера, чтобы звонить пингвинам в Антарктиду и пересказывать им «Войну и Мир». И тогда прибыль от внедрения кнопки Click to call может превратиться в огромные убытки.

Передавать параметры подключения к SIP серверу в JS коде небезопасно, поэтому для Production реализации мы рекомендуем хранить параметры подключения на стороне сервера и подставлять их при инициализации звонка. Для этого можно использовать так называемые REST Hook скрипты.

Поймали на крючок

REST Hook — это простые скрипты, которые работают с JSON в теле HTTP-запроса и отдают JSON в теле HTTP ответов. Скрипты REST Hook подменяют собой стандартные приложения WCS API и позволяют обрабатывать данные о коннектах, звонках и видеопотоках на бэкенд сервере.

REST Hook могут быть использованы для следующих целей:

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

 

В этой статье рассмотрим, как можно безопасно передать учетные данные SIP сервера и номер вызываемого абонента для кнопки «Click to Call» с использованием технологии REST Hook.

Архитектура решения

  1. Фронтенд Web сервер — организует интерфейс с пользователем. Отображает web страницу с кнопкой «Click to Call».
  2. WCS — посредник между пользователем и SIP сервером. Конвертирует WebRTC поток от браузера в SIP формат.
  3. Бэкенд сервер — Web сервер, который обеспечивает работу REST Hook.
  4. SIP сервер и SIP телефон.

 

Логически мы разделяем фронтенд, бэкенд и WCS серверы, но физически их можно разместить на одной машине. В этой статье, для упрощения разбора примера, мы используем три отдельные виртуальные машины.

solution-architecture_WCS_SIP_phone_click-to-call_WebSocket_WebRTC_browser

Реализация

Начнем настройку с бэкенд сервера.

Устанавливаем и конфигурируем Nginx и PHP, например Nginx на CentOS 7.

После чего, в файле /etc/nginx/nginx.conf в секции «server» добавляем следующие строки. Эта настройка обеспечит доступность нашего REST Hook скрипта для событий «/connect» и «/call»:

location / {
try_files $uri $uri/ /index.php?$request_uri;
}

nginx_settings_REST-hook_credentials_stream_SIP_phone_Click-to-Call_WCS_WebSocket_WebRTC_browser

В каталоге для файлов 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();
}
?>

php_code_REST-hook_credentials_stream_SIP_phone_Click-to-Call_WCS_WebSocket_WebRTC_browser

В том же каталоге /var/www создаем файл «rest_client_config.json». Исходный код можно найти в конце этой страницы.

В файле «rest_client_config.json» правим секцию «call» . Здесь указываем политику REST метода — перезаписывать данные и какое значение будет перезаписано с помощью скрипта:

"call" : {
"clientExclude" : "",
"restExclude" : "",
"restOnError" : "LOG",
"restPolicy" : "OVERWRITE",
"restOverwrite" : "callee"
},

callee_overwrite_settings_REST-hook_credentials_stream_SIP_phone_click-to-call_WCS_WebSocket_WebRTC_browser

Затем, переходим к 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_result_REST-hook_credentials_stream_SIP_phone_Click-to-Call_WCS_WebSocket_WebRTC_browser

Если вывод утилиты 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 сервера.

WCS_CLI_REST-hook_credentials_stream_SIP_phone_Click-to-Call_WebSocket_WebRTC_browser

После всех этих настроек переходим к фронтенд 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»:

click_call-hook_credentials_stream_SIP_phone_Click-to-Call_WebSocket_WebRTC_browser

Принимаем входящий звонок на программном SIP телефоне и проверяем, что происходит обмен аудио потоками между абонентами:

audio_call_SIP_video_phone_incoming_call_SIP_WCS_WebSocket_browser

Пришлось немного поработать, но результат того стоит. Теперь учетные данные SIP сервера и номер вызываемого абонента защищены от злоумышленников, и можно не переживать, что кто-то использует вашу телефонию в своих интересах.

Ссылки

Наш демо сервер