REST hooks для WebRTC Click to Call. Опыт внедрения

Моя цель - предложение широкого ассортимента товаров и услуг на постоянно высоком качестве обслуживания по самым выгодным ценам.

Кнопка "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.

Что нужно для работы?

  1. Фронтенд Web сервер — организует интерфейс с пользователем. Отображает web страницу с кнопкой "Click to Call".

  2. WCS — посредник между пользователем и SIP сервером. Конвертирует WebRTC поток от браузера в SIP формат.

  3. Бэкенд сервер — Web сервер, который обеспечивает работу REST Hook.

  4. 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();
	}
}

Алло, девушка, соедините

Для тестирования нам понадобятся:

  1. Тестовый стенд, который мы собрали выше (фронтeнд, WCS, бэкенд);

  2. SIP сервер

  3. Два SIP аккаунта

  4. Браузер

  5. Программный SIP телефон

Данные для подключения SIP аккаунта 10001 мы передаем в JS коде страницы с кнопкой "Click to Call" при помощи REST Hook. Кнопка "Click to Call" запрограммирована сделать вызов на номер 10002. Учетные данные для аккаунта 10002 внесем в программный SIP телефон.

Открываем созданную на фронтенд web сервере HTML страницу и нажимаем кнопку "Call":

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

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

Ссылки

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

Онлайн звонок с сайта по кнопке

Web-SIP телефон в браузере

Файл настроек flashphoner.properties

REST hooks

Документация по быстрому развертыванию Web Call Server

Звонок на мобильный телефон через SIP сервер

Web SDK Click to Call

Android Click to Call

iOS Click to Call

Источник: https://habr.com/ru/company/flashphoner/blog/566360/


Интересные статьи

Интересные статьи

Данная статья выражает мое личное мнение, основанное на опыте подготовки и сдаче экзамена на сертификат PMP (Project Management Professional) от Project Management Instittute.Вся информац...
Я начал программировать на Go после достаточно продолжительного периода программирования на PHP. Полагаю судя по последним тенденциям, мой случай далеко не единичный. Go ...
Хочу поделиться с вами моим первым успешным опытом восстановления полной работоспособности базы данных Postgres. С СУБД Postgres я познакомился пол года назад, до этого опыта администрирования ба...
Доброго времени прочтения, уважаемые читатели Хабра. Увы, мы все смертны, даже программисты. Когда ставят диагноз — не знаешь куда бежать. Попробую описать свой опыт… Мне поставили страшненьки...
Меня зовут Владимир, я занимаюсь мобильным фронтендом в Яндекс.Почте. В нашем приложении уже была тёмная тема, но недожатая: мы умели перекрашивать интерфейс и простые письма. Но письма с форма...