[Из песочницы] Javascript-клиент для звонков через voximplant

Вступление
Некоторое время назад появилась необходимость интегрировать программный продукт с сервисом voximplant.

Поскольку еще ни разу не приходилось с ним работать, наверное, как и многие, начал искать готовые рабочие примеры интеграции. И каково же было мое удивление, когда оказалось, что информации не так уж и много. А примеров качественного кода и вовсе не было найдено.

После оценки стартовых позиций, было принято волевое решение писать код интеграции с воксимплантом самому. За основу был взят код из хабрапоста: нагромождать текст процессом написания кода считаю нецелесообразным, поэтому перейду сразу к описанию результата.

Способы использования и код
В основном, код заточен под два способа использования:

  1. звонки с сайта на контактный номер;
  2. звонки пользователям (например, с панели администратора).


Конечно же, это совершенно одна и та же задача. Единственное, в первом случае стоит инициализировать клиент для звонков только тогда, когда пользователь непосредственно решил позвонить (поскольку в этот момент появится диалоговое окно браузера о подтверждении доступа к микрофону). Во втором же случае можно клиент инициализировать сразу, поскольку предполагается частое совершение звонков.

js код клиента
/**
 * @constructor
 * @param {String} aLogin_str Логин для приложения voximplant. Обязательный параметр
 * @param {String} aPassword_str Пароль для приложения voximplant. Обязательный параметр
 * @param {Object} aOptOptions_obj Набор необязательных параметров, которые принимаются для метода VoxImplant.getInstance().init(). Список параметров тут: http://voximplant.com/docs/references/websdk/VoxImplant.Config.html
 * 
 * */
function VoximaplantClient(aLogin_str, aPassword_str, aOptOptions_obj) {
    var lLogin_str = aLogin_str;
    var lPassword_str = aPassword_str;
    var lOptions_obj = {
        micRequired: true,
        progressTone: true,
        progressToneCountry: "RU",
        showDebugInfo: false
    };
    if (aOptOptions_obj && aOptOptions_obj instanceof Object) {
        for (var i in aOptOptions_obj) {
            lOptions_obj[i] = aOptOptions_obj[i];
        }
    }

    /**
     * Статус, указывающий, что VoximaplantClient еще не выполнял метод init()
     */
    this.NOT_INTIALIZED = 0;
        
        /**
     * Статус, указывающий, что VoximaplantClient уже выполнил метод init()
     */
    this.INTIALIZED = 1;
        
        /**
     * Статус, указывающий, что у пользователя доступен микрофон. Если микрофон не доступен, статус будет INTIALIZED
     */
    this.MIC_VERIFIED = 2;
        
        /**
     * Статус, указывающий, что клиент подключился к серверу voximplant. Если по каким-то причинам подключиться не удалось, статус будет: MIC_VERIFIED
     */
    this.CONNECTED = 3;
        
        /**
     * Статус, указывающий, что клиент успешно авторизовался на сервере voximplant. 
         * Если по каким-то причинам авторизоваться не удалось, статус будет: CONNECTED
         * Единственный статус, при котором возможно совершать звонки
     */
    this.LOGGED_IN = 4;
        
        /**
     * Статус, указывающий, что у клиента подключился к серверу voximplant, но по каким-то причинам подключение разорвалось.
     */
    this.CONNECTION_CLOSED = 5;
    
    /**
     * При начале обработки звонка воксимплантом, обрабатывается эта функция, если ей присвоили значение
     */
    this.onCallingStartedEvent = null;
    
    /**
     * При завершении звонка, обрабатывается эта функция, если ей присвоили значение
     */
    this.onCallingCompletedEvent = null;
    
    /**
     * При окончании работы функции VoximplantClient::init(), обрабатывается эта функция, если ей присвоили значение
     */
    this.onInitializationCompletedEvent = null;
    var self = this;
    var tryToExecuteInitializationCompletedEventHandler = function() {
        if (self.onInitializationCompletedEvent && typeof self.onInitializationCompletedEvent === 'function') {
            self.onInitializationCompletedEvent();
        }
    };

    var lStatus_int = this.NOT_INTIALIZED;
    var lInstance_vx_obj = null;
    var lCall_vx_obj = null;
    
    this.init = function () {
        if (lStatus_int < this.INTIALIZED) {
            lInstance_vx_obj = VoxImplant.getInstance();
            var self = this;
            lInstance_vx_obj.addEventListener(VoxImplant.Events.SDKReady, function() {
                lStatus_int = self.INTIALIZED;
                lInstance_vx_obj.addEventListener(VoxImplant.Events.MicAccessResult, function(e) {
                    if (e.result) {
                        lStatus_int = self.MIC_VERIFIED;
                    } else {
                        tryToExecuteInitializationCompletedEventHandler();
                    }
                });
                if (!lInstance_vx_obj.connected()) {
                    lInstance_vx_obj.addEventListener(VoxImplant.Events.ConnectionEstablished, function() {
                        lStatus_int = self.CONNECTED;
                        lInstance_vx_obj.addEventListener(VoxImplant.Events.AuthResult, function(e) {
                            if (e.result) {
                                lStatus_int = self.LOGGED_IN;
                            }
                            tryToExecuteInitializationCompletedEventHandler();
                        });
                        lInstance_vx_obj.login(lLogin_str, lPassword_str);
                    });
                    lInstance_vx_obj.addEventListener(VoxImplant.Events.ConnectionClosed, function() {
                        lStatus_int = self.CONNECTION_CLOSED;
                    });
                    lInstance_vx_obj.addEventListener(VoxImplant.Events.ConnectionFailed, function() {
                        lStatus_int = self.CONNECTION_CLOSED;
                        tryToExecuteInitializationCompletedEventHandler();
                    });
                    lInstance_vx_obj.connect();
                }
            });
            lInstance_vx_obj.init(lOptions_obj);
        }
    };
    
    /**
     * Получить текущий статус VoximplantClient. 
         * Доступные статусы: NOT_INTIALIZED, INTIALIZED, MIC_VERIFIED, CONNECTED, LOGGED_IN, CONNECTION_CLOSED
         * @return {Integer}
     */
    this.getStatus = function() {
        return lStatus_int;
    };
    
    /**
     * Проверяем, разрешен ли звонок
         * @return {Boolean}
     */
    this.isCallingAllowed = function() {
        return lStatus_int === self.LOGGED_IN;
    };
    
    /**
     * Звоним на номер
         * 
         * @param{String} aNumber телефонный номер на который звоним
     */
    this.callToNumber = function(aNumber) {
        if (!this.isCallingAllowed() || lCall_vx_obj !== null) {
            return;
        }
        aNumber = aNumber.replace(/[^0-9]/g, '');
        lCall_vx_obj = lInstance_vx_obj.call(aNumber);
        var self = this;
        var closeCallFunc = function() {
            lCall_vx_obj.removeEventListener(VoxImplant.CallEvents.Failed, closeCallFunc);
            lCall_vx_obj.removeEventListener(VoxImplant.CallEvents.Disconnected, closeCallFunc);
            if (lCall_vx_obj.state() !== 'ENDED') {
                lCall_vx_obj.hangup();
            }
            lCall_vx_obj = null;

            if (self.onCallingCompletedEvent && typeof self.onCallingCompletedEvent === 'function') {
                self.onCallingCompletedEvent();
            }
        };
        lCall_vx_obj.addEventListener(VoxImplant.CallEvents.Failed, closeCallFunc);
        lCall_vx_obj.addEventListener(VoxImplant.CallEvents.Disconnected, closeCallFunc);

        if (this.onCallingStartedEvent && typeof this.onCallingStartedEvent === 'function') {
            this.onCallingStartedEvent();
        }
    };
    
    /**
     * Завершить звонок
     */
    this.hangUp = function() {
        if (!this.isCallingAllowed() || lCall_vx_obj === null) {
            return;
        }
        lCall_vx_obj.hangup();
    };
}



Так как первый вариант использования более комплексный, реализуем его. Для начала создадим такую html-форму для звонков:

2759533fad5b4fdbbdae33fe4d7e594f.png

код html-формы


        
                
                
                
                Voximplant test page
                
        
        
                
Позвонить на номер:


Дальше подключим несколько скриптов: скрипт WebSDK voximplant’а, библиотека jquery, наш voximplantclient, а также скрипт, демонстрирующий работу с voximplantclient.







По-моему код получился настолько самодокументируемым и понятным, что комментировать здесь нечего. В итоге получаем:

e0fb2ec415224481ae9880424b1c8e4e.gif

Достоинства и недостатки
К достоинствам следует отнести:

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


Также и имеются недостатки:

  • Неточное описание состояний. Например, когда после инициализации объект находится в состоянии MIC_VERIFIED, на самом деле это означает, что была произведена попытка соединения с сервером voximplant, но соединение не удалось. Да, пришлось пожертвовать неточностью в замен простоты и времени разработки;
  • Клиент имеет функционал только для звонков;
  • Неточность вызова обработчика onCallingStartedEvent. Когда срабатывает данный обработчик, означает, что начался звонок написанным классом, а не начался звонок непосредственно на телефон дозвона;


Для поставленных задач класс всецело оправдал себя. Конечно же, если этих возможностей мало — всегда можно расширить функционал. Поэтому, всем желающим код доступен на гитхабе.

Думаю, кому-нибудь да будет полезно.

© Habrahabr.ru