- PVSM.RU - https://www.pvsm.ru -

IP АТС в облаке своими руками за 10 минут

IP АТС в облаке своими руками за 10 минутМногие из читателей Хабра знакомы с современными IP АТС в облаке, такими как Манго, Oktell, Октолайн и другими. Все они предлагают различные функции и тарифные планы, чтобы удовлетворить самых разных клиентов и соответствовать их требованиям, но всегда есть ряд тех кто все равно выбирает Asterisk, так как хочется иметь возможности по кастомизации и интеграции, которые кроме как Asterisk мало какая система способна предложить. В дополение к самому Asteriskу еще потребуется человек, который будет способен все настроить и его поддерживать, отдельное подключение к оператору связи по SIP или через VoIP-шлюз и так далее. Когда мы начали создавать свою облачную платформу для разработки комуникационных приложений VoxImplant [1], то, конечно же, знали, что одним из популярных сценариев ее использования будет IP АТС, поэтому реализовали весь необходимый для этого функционал. В отличие от случая с Asterisk человеку, который решит сделать свою IP АТС на базе VoxImplant, понадобится лишь знание Javascript, ознакомление с этой статьей и свободных 10-15 минут, чтобы получить на выходе первую рабочую версию АТС, которую потом в дальнейшем можно будет интегрировать со своими сервисами и кастомизировать в соответствии со своими требованиями. Подробнее об этом код катом.

Сначала определимся с архитектурой и функциональностью АТС, сразу отмечу что в данной статье мы рассмотрим базовую версию, которая подойдет для организации телефонии в офисе в ряде случаев, а при желании скрипты можно будет кастомизировать в соответствии со своими требованиями, но об этом позже. Мы будем строить АТС в соответствии со следующей схемой:

IP АТС в облаке своими руками за 10 минут

Пользователи АТС могут совершать звонки друг другу и на реальные телефонные номера, отдельная группа пользователей (Операторы) могут также принимать входящие звонки, приходящие на АТС с обычных телефонов (об этом далее) или по SIP, соответственно они могут переводить вызовы на обычных пользователей и друг другу при необходимости. SIP-телефон — софтфон или железный телефон, с поддержкой SIP, Web SDK/Mobile SDK — клиентское приложение для браузера или смартфона, сделанное с помощью соответствующего SDK от VoxImplant.

Итак, для создания IP АТС нам потребуется бесплатный аккаунт разработчика VoxImplant, который можно получить тут https://voximplant.com/sign-up [2]
После создания и активации аккаунта станет можно зайти в панель управления https://manage.voximplant.com/ [3], где преимущественно и будет происходить вся дальнейшая наша работа. Для начала нам потребуется создать пользователей (Users), которые будут соответствовать пользователям и операторам АТС, при создании пользователя можно выбрать будет ли у него отдельный лицевой счет VoxImplant (Separate account balance) или же при его звонках сумма будет списываться с общего счета аккаунта (по умолчанию) — пока ничего не меняем, делаем по умолчанию. ВАЖНО: Используйте для логин/username 3х значные цифровые коды (101, 102, 103 и т.д.) — в своих скриптах мы будем исходить из такого формата. Теперь можно приступать непосредственно к созданию функционала АТС, серверные приложения VoxImplant представляют собой набор сценариев, по которым обрабатываются звонки, проходящие через платформу. Сценарии эти пишутся на обычном Javascript, в котором доступны несколько неймспейсов и классов для работы с функциями VoxImplant (подробнее можно посмотреть по ссылке [4]). Сценарии создаются и редактируются в разделе Scenarios. Всего у нас будет предусмотрено 3 типа сценариев обработки звонков: для входящих, для исходящих и для звонков между пользователями, назовем их PBX in, PBX out и PBX local соответственно. Начнем с самого простого PBX local:

VoxEngine.forwardCallToUser();

«И это все?» — спросите вы :) Да, это все, так как forwardCallToUser [5] — это одна из helper-функций, которую мы написали для ускорения и облегчения создания приложений. По сути, за этой функцией скрывается кусочек Javascript-кода, а-ля:

VoxEngine.addEventListener(AppEvents.CallAlerting, function(e) {
    var newCall = this.callUser(e.destination, e.callerid, e.displayName);
    VoxEngine.easyProcess(e.call, newCall);
});

Где easyProcess — еще одна из helper-функций, более подробно со всеми функциями можно ознакомиться в документации к VoxEngine [4], так как у нас сейчас не стоит задача изучить VoxImplant досконально, то продолжим без углубления в нюансы. Следующий скрипт (PBX out) отвечает за отправку исходящих звонков на обычные телефонные номера:

VoxEngine.addEventListener(AppEvents.CallAlerting, function (e) {
    var call2 = VoxEngine.callPSTN(e.destination.substring(1), "74957893798");
    VoxEngine.easyProcess(e.call, call2);
});

В данном коде мы используем функцию callPSTN [6], чтобы перенаправить звонок в телефонную сеть с указанием Caller ID — 74957893798, который нужно предварительно авторизовать в разделе Settings -> Caller IDs, чтобы АОН при звонке показывал что это мы звоним. Для первого параметра просто откидывает префикс из 1 цифры (позже мы настроим эту цифру во время привязки сценариев к приложению), которая сигнализирует что звонок надо пропустить в ТфОП, то есть номер вводится в виде 974952200022, после того как 9 откинули останется 74952200022 — обычный московский номер. Тут тоже все достаточно просто, теперь переходим к самому интересному сценарию — обработка входящих звонков (PBX in), я буду добавлять «мясо» по частям, чтобы было понятнее:

// Переменные доступные сессии
var callerid,
    displayName;

// При поступлении звонка на платформу всегда первым происходит событие AppEvents.CallAlerting
VoxEngine.addEventListener(AppEvents.CallAlerting, function(e) {
    // запомним Caller ID
    callerid = e.callerid;
    // и displayName - актуально для SIP
    displayName = e.displayName;
    // Включим обработчик тоновых сигналов (чтобы можно было донабрать добавочный сразу)
    e.call.handleTones(true);
    // Ответим со стороны платформы на входящий звонок, чтобы установить связь между звонящим и платформой
    e.call.answer();
});

Соединение с платформой мы разрешили, теперь нужно заняться обработкой звонка, внутри предыдущего листенера добавляем обработку события соединения звонка:

// Переменные для указания текущей таймзоны, и рабочих часов офиса для разных дней недели
var GMT_offset = 4,
    workingHours = [
        [0, 0], // Sun
        [10, 19], // Mon
        [10, 19], // Tue
        [10, 19], // Wed
        [10, 19], // Thu
        [10, 19], // Fri
        [0, 0] // Sat
    ],
    nonWorkingHours = false,
    workingHoursGreetingURL = 'http://yourdomain.com/ivr.mp3', //<=== ПОМЕНЯТЬ НА РЕАЛЬНЫЙ URL с MP3-файлом приветствия (рабочее время)
    nonWorkingHoursGreetingURL = 'http://yourdomain.com/ivr_nwh.mp3';  //<=== ПОМЕНЯТЬ НА РЕАЛЬНЫЙ URL с MP3-файлом приветствия (нерабочее время)

e.call.addEventListener(CallEvents.Connected, function(e) {
    // См. день/час для текущей таймзоны, указанной в GMT_offset (GMT+4 для Москвы)
     var d = new Date(new Date().getTime() + GMT_offset * 3600 * 1000),
        day = d.getUTCDay(),
        hour = d.getUTCHours();
    Logger.write("Day: " + day + " Hour: " + hour);
    if (hour >= workingHours[day][0] && hour < workingHours[day][1]) {
        /**
        * Звонок в рабочие часы
        * проигрываем стандартное приветствие
        */
       e.call.startPlayback(workingHoursGreetingURL, false);
       e.call.record();
   } else {
       /** 
        * Звонок в нерабочее время
        * проигрываем специальное приветствие
        */
        nonWorkingHours = true;
        e.call.startPlayback(nonWorkingHoursGreetingURL, false);
    }
});

Обработчик на соединение звонка повесили, теперь нужно повесить обработчик на разъединение:

e.call.addEventListener(CallEvents.Disconnected, function(e) {
    // Завершаем сессию
    VoxEngine.terminate();
});

И обработчик завершения проигрывания нашего приветствия:

// Таймаут чтобы дать возможность ввести добавочный после проигрывания приветствия
var TIMEOUT = 3000,
    operatorTimer;

e.call.addEventListener(CallEvents.PlaybackFinished, function(e) {

    // Если звонок пришел в рабочее время, то даем какое-то время чтобы ввести добавочный, прежде чем соединить с оператором
    if (!nonWorkingHours) {
        operatorTimer = setTimeout(function() {
            forwardCallToOperator(e.call);
        }, TIMEOUT);
    } else VoxEngine.terminate(); // если звонок в нерабочее время - просто завершаем сессию 
});

Вы, наверное, заметили функцию forwardCallToOperator, мы к ней вернемся сразу как подключим обработку нажатий кнопок на телефоне для ввода добавочного номера. Ранее мы уже включили обработчик с помощью вызова e.call.handleTones(true), теперь надо его объявить:

// Переменная для хранения введенных цифр
var input = '';

e.call.addEventListener(CallEvents.ToneReceived, function(e) {
    // При нажатии клавиши останавливаем проигрывание нашего приветствия
    e.call.stopPlayback();
    // добавляем введенную цифру
    input += e.tone;
    if (input.length == 3) {
        // исходим из того что пользователям были выданы 3х значные логины и отправляем вызов на пользователя
        forwardCallToExtension(e.call, input);
    }
});

Ну а теперь пришло время самых интересных функций — forwardCallToExtension и forwardCallToOperator:

var operators = ['101', '102', '103'], // <== Здесь указываем пользователей АТС, которые будут операторами - на приеме входящих
    operatorCalls = {},
    nOperatorCalls = 0,
    activeOperatorCall;

function forwardCallToExtension(call, ext) {
    clearTimeout(operatorTimer);
    // выключаем обработку нажатий на кнопки телефона
    call.handleTones(false);
    // проигрываем гудки (длительность и тональность принятые в России)
    call.playProgressTone("RU");
    // звоним пользователю, соответствующему введенному добавочному номеру
    var call2 = VoxEngine.callUser(ext, callerid, displayName);
    // Вешаем обработчики
    call2.addEventListener(CallEvents.Failed, VoxEngine.terminate);
    call2.addEventListener(CallEvents.Connected, function(e) {
        // если дозвонились - соединяем звонящего с пользователем АТС
        VoxEngine.sendMediaBetween(call, call2);
    });
    call2.addEventListener(CallEvents.Disconnected, VoxEngine.terminate);
}

function forwardCallToOperator(call) {
    // выключаем обработку нажатий на кнопки телефона
    call.handleTones(false);
    nOperatorCalls = 0;
    // проигрываем гудки (длительность и тональность принятые в России)
    call.playProgressTone("RU");
    // звоним сразу на всех операторов параллельно, соединяем с первым свободным, вешаем обработчики.
    for (var i in operators) {
        var j = operators[i];
        nOperatorCalls++;
        operatorCalls[j] = VoxEngine.callUser(j, callerid, displayName);
        operatorCalls[j].addEventListener(CallEvents.Failed, function(e) {
            if (typeof activeOperatorCall == "undefined") {
                delete operatorCalls[e.call.number()];
                nOperatorCalls--;
                if (nOperatorCalls == 0) {
                    call.hangup();
                }
            }
        });

        operatorCalls[j].addEventListener(CallEvents.Connected, function(e) {
            delete operatorCalls[e.call.number()];
            activeOperatorCall = e.call;
            VoxEngine.sendMediaBetween(call, e.call);
            activeOperatorCall.addEventListener(CallEvents.Disconnected, VoxEngine.terminate);
            for (var i in operatorCalls) {
                operatorCalls[i].hangup();
            }
            operatorCalls = {};
        });
    }
}

Все готово, объединяем наши части и получаем полный скрипт [7]. Дело осталось за малым — создать приложение VoxImplant и подключить к нему пользователей и наши сценарии. Делается это следующим нехитрым образом:
1. В разделе Приложения/Applications создаем приложение, называем его и указываем пользователей, которые смогут им пользоваться.
IP АТС в облаке своими руками за 10 минут
2. После создания приложения сразу выбираем редактирование
3. В табе Rules/Правила с помощью кнопки Add Rule создаем 3 новых правила: local, out, in
При создании правила local, учитывая что имена пользователям вы задали в виде 101, 102, 103 и т.д. в поле Pattern указываем 1[0-9]{2} и, используя drag'n'drop, перетаскиваем сценарий PBX local из Available в Assigned, чтобы назначить какой сценарий должен быть вызван если подключенные к нашей АТС пользователи решать позвонить на внутренний номер своего коллеги. Сохраняем.
IP АТС в облаке своими руками за 10 минут
Аналогично для правила out в поле Pattern указываем 9[0-9]+ (помните ту самую 9, которую мы отпиливаем с помощью e.destination.substring(1) в методе callPSTN) и перетаскиваем в Assigned сценарий PBX out. Сохраняем.
И последнее правило in: в Pattern указываем что-то в духе (74957893798|100) и перетаскиваем PBX in в сторону Assigned. Сохраняем. 74957893798 -это просто пример, здесь должен быть номер телефона, который вы можете подключить к своему приложению в разделе Phone numbers. А 100 позволяет позвонить на нашу АТС по SIP, используя URL вида sip:100@appname.accountname.voximplant.com, где appname — название вашего приложения, которые вы указали во время его создания, а accountname — имя аккаунта VoxImplant, которое вы указали при регистрации. Если у вас уже есть купленный номер, поддерживающий форвардинг по SIP, то вы сможете его подключить к АТС, например, направив вызовы на тот SIP URI (sip:100@appname.accountname.voximplant.com), который мы сделали для входящих звонков по SIP.
IP АТС в облаке своими руками за 10 минут
4. Сохраняем приложение.

Теперь наша АТС готова к подключению SIP-телефонов (или клиентских приложений на базе VoxImplant SDK), приему и обработке вызовов, локальным звонкам между пользователями АТС и исходящим звонками на реальные номера.

P.S. Готовые скрипты из статьи выложены на GitHub https://github.com/voximplant/pbx [8]. Готовый веб-телефон на базе VoxImplant Web SDK можно найти по адресу http://webphone.voximplant.com/?account=accountname&app=appname [9], не забудьте заменить accountname и appname на свои названия. Ну и X-lite/Bria никто не отменял, если нужен более-менее вменяемый SIP-софтфон.

Автор: aylarov

Источник [10]


Сайт-источник PVSM.RU: https://www.pvsm.ru

Путь до страницы источника: https://www.pvsm.ru/javascript/55241

Ссылки в тексте:

[1] VoxImplant: http://voximplant.com

[2] https://voximplant.com/sign-up: https://voximplant.com/sign-up

[3] https://manage.voximplant.com/: https://manage.voximplant.com/

[4] по ссылке: http://voximplant.com/docs/references/appengine/

[5] forwardCallToUser: http://voximplant.com/docs/references/appengine/VoxEngine.html#forwardCallToUser

[6] callPSTN: http://voximplant.com/docs/references/appengine/VoxEngine.html#callPSTN

[7] полный скрипт: https://raw2.github.com/voximplant/pbx/master/pbx_in.js

[8] https://github.com/voximplant/pbx: https://github.com/voximplant/pbx

[9] http://webphone.voximplant.com/?account=accountname&app=appname: http://webphone.voximplant.com/?account=accountname&app=appname

[10] Источник: http://habrahabr.ru/post/212713/