Как сделать виджет звонков из браузера на примере XWiki

в 8:11, , рубрики: javascript, SDK, web, Xwiki, звонки из браузера, телефония

Верите ли вы в настоящую любовь? И я сейчас не про то мимолетное увлечение, когда прям с первого взгляда «искра, буря, безумие», а про выстроенную годами усилий, литрами пота и крови любовь. Лично я — верю! И хоть на новой работе я больше не использую XWiki, но время от времени так и хочется провести с ней удивительные мгновения и поделиться новым опытом с дорогими читателями.

На этом лирическое отступление завершается, и мы переходим к практической части.

В этой статье мы расширим функции популярной opensource-замены Confluence (со слов разработчиков) и добавим в XWiki возможность совершить телефонный звонок пользователю прямо из браузера, по нажатию всего одной кнопки на боковой панели.

Поможет нам в этом простой и доступный инструмент для работы с IP-телефонией — Exolve WebSDK. Именно с помощью данной JavaScript (TypeScript) библиотеки мы будем совершать звонки. Кстати, новые пользователи могут протестировать МТС Evolve бесплатно, в рамках тестового баланса, в том числе и Mobile SDK, пусть и с некоторыми ограничениями (подробнее см. в документации).

Приёмы, которые мы используем при внедрении Exolve WebSDK в XWiki, также будут полезны при подключении других сторонних JS библиотек. Поэтому надеюсь, что статья будет интересна не только тем, кто интересуется IP-телефонией, но и фанатам XWiki.

Описание задачи

Если совсем кратко, то мы создаём внутри XWiki виджет, который с помощью WebSDK позволит нам прямо из браузера позвонить другому пользователю XWiki, у которого в профиле есть мобильный телефон. При этом второму пользователю поступит звонок с обычного мобильного номера, и он даже не узнает, что мы разговариваем с ним через компьютер.

Для большей наглядности я подготовил иллюстрацию сценария.

Сценарий работы 

Сценарий работы 

Подготовительные работы

Прежде чем приступить к реализации, убедимся, что всё подготовлено.

Если у вас уже настроено приложение и получен SIP-логин в МТС Exolve, то раздел можно пропустить.

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

Как только все шаги выполнены, перейдите в созданное приложение, откройте раздел «SIP-соединения» и нажмите кнопку «Создать SIP ID». Заполнение полей страницы интуитивно понятно. После создания SIP ID скопируйте себе sipLogin (Юзернейм) и sipPassword (пароль), чтобы указать их далее в скрипте.

Страница созданного SIP ID

Страница созданного SIP ID

Сборка JavaScript скрипта

Как всегда пришла пора дисклеймера.

Я не разработчик, поэтому предложенные подходы не являются эталонными. Некоторые вещи я и вовсе не до конца понимаю, но всё же они работают. Вы всегда можете предложить лучшие варианты в комментариях.

Всё, формальности улажены, можно приступать к делу.

Если вы хотите сверится или сразу попробовать функции в деле, то готовый проект и виджет XWiki можно скачать в GitHub.

Уместно рассказать пару слов о WebSDK. Фактически это JavaScript (TypeScript) библиотека, которую мы можем использовать для создания соединения и совершения вызова.

Более подробно можно ознакомиться в документации:

Должен признаться, что мои познания в области JS остались в тех далёких и прекрасных годах, когда умы разработчиков только захватывал AJAX, а JS использовали для простых скриптов, например онлайн-калькуляторов.

Поэтому я сделал обёртку для SDK, которая будет импортироваться в браузер в качестве глобальной переменной JS. Если у вас есть лучший способ реализации, обязательно напишите в комментариях, мне будет интересно с ним ознакомиться.

Прежде чем начать работу убедитесь, что ваша версия Node.js не ниже 18. Это необходимо для корректной работы SDK.

Создайте пустую папку для проекта и установите SDK командой:

npm install @mts-exolve/web-voice-sdk --save 

Для того, чтобы собрать всё в один JS файл исполняемый в браузере, установите компилятор TypeScript и Webpack командой:

npm install typescript webpack webpack-cli ts-loader --save-dev 

Затем создайте папку src, а в ней файл index.ts

Листинг src/index.ts
import * as WebVoiceSdk from '@mts-exolve/web-voice-sdk';

/**

* Функция для создания экземпляра SIP

* @param {sipParams} SipLogin - логин SIP в ЛК MTS Exolve sipPassword - пароль для данного SIP логина

*/

export function createSipInstance(sipParams: { sipLogin: string, sipPassword: string }) {

 return WebVoiceSdk.createSipInstance(sipParams);

}

Как видите, код очень простой, мы импортируем WebSDK и по сути делаем обёртку для функции, которая создаёт экземпляр для работы с SIP. Затем мы экспортируем обёртку, чтобы потом обращаться к ней как к глобальной переменной в скрипте. 

Исходная функция SDK — WebVoiceSdk.createSipInstance принимает строковые параметры sipLoginb и sipPassword, которые мы получили в предыдущем разделе. Подробнее о работе функции см. в документации

Обратите внимание: в демонстрационном примере логины sipLogin и sipPassword легко обнаружить в исходном коде страницы. Для продуктовых решений необходимо самостоятельно решить вопрос безопасности, например, ограничив доступ к XWiki внутренней сетью или зашифровав логин и пароль в коде bundle.js.

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

Далее нам необходимы файлы конфигурации для компилятора TypeScript и Webpack.

Их я сгенерировал с помощью нейросети ибо не до конца понимаю все настройки, поэтому не буду подробно на них останавливаться.

Листинг tsconfig.json
{

 "compilerOptions": {

   "module": "commonjs",

   "target": "es5",

   "outDir": "dist",

   "sourceMap": true,

   "moduleResolution": "node",

   "esModuleInterop": true,

   "forceConsistentCasingInFileNames": true

 },

 "include": [

   "src"

 ]

Листинг webpack.config.js
const path = require('path');

module.exports = {

 entry: path.resolve(__dirname, 'src', 'index.ts'),

 output: {

   filename: 'bundle.js',

   path: path.resolve(__dirname, 'dist'),

   library: 'WebVoiceSdk',

   libraryTarget: 'var',

 },

 module: {

   rules: [

     {

       test: /.ts$/,

       exclude: /node_modules/,

       use: 'ts-loader',

     },

   ],

 },

 resolve: {

   extensions: ['.ts', '.js'],

 },

};

Единственное на что обращу внимание – параметры: 

library: 'WebVoiceSdk' и libraryTarget: 'var' позволят нам использовать обертку как глобальную переменную, а не как модуль.

Несмотря на то, что package.json должен создаться автоматически, дополним его вызовом скрипта build:

 "scripts": {
   "build": "webpack"
 },

Теперь можно вызывать сборку командой npm run build

В качестве альтернативы можно запускать сборку командой npx webpack

Полный листинг  package.json
{
 "scripts": {
   "build": "webpack"
 },
  "dependencies": {

   "@mts-exolve/web-voice-sdk": "^1.9.0"
 },

 "devDependencies": {
   "ts-loader": "^9.5.1",
   "typescript": "^5.6.2",
   "webpack": "^5.95.0",
   "webpack-cli": "^5.1.4"
 }
 }

У нас все готово для сборки командой npm run build.

После её запуска и сборки без ошибок, в папке /dist появится файл bundle.js

Осталось проверить работоспособность скрипта перед импортом его в XWiki.

Создайте в корне проекта файл index.html

Полный листинг index.html
<!DOCTYPE html>

<html>

 <head>
   <title>Web Voice SDK Example</title>
   <script src="https://requirejs.org/docs/release/2.3.7/minified/require.js"></script>

 </head>

 <body>

   <script>

     // Используем WebVoiceSdk.createSipInstance

     requirejs(["dist/bundle"], function () {

       //This function is called when dist/bundle is loaded.

        window.sipInstance = WebVoiceSdk.createSipInstance({

         sipLogin: "Ваш SIP логин",

         sipPassword: "Ваш SIP пароль",

       });

       window.sipInstance.register();

     });   

   </script>

   <input

   id="PhoneToCall"

   placeholder="Введите номер"

   /> 

   <input

     value="Позвонить"

     type="button"

     id="startCallButton"

     onclick="window.sipSession = window.sipInstance.call(document.getElementById('PhoneToCall').value)"

   />

   <input

     value="Отбой вызова"

     type="button"

     id="stopCallButton"

     onclick="window.sipSession.terminate()"

   />

 </body>

</html>

Файл достаточно простой, но у вас может возникнуть вопрос: почему мы импортируем WebVoiceSdk с помощью require.js, а не напрямую.

На самом деле, в данном случае скрипт bundle.js можно загрузить напрямую, поскольку с очень высокой вероятностью скрипт успеет загрузится до попытки создать объект с помощью WebVoiceSdk.createSipInstance. Но в XWiki данный скрипт точно не успеет загрузится. Поэтому для унификации я решил реализовать его загрузку через require.js. Это гарантирует создание объекта только после завершения загрузки скрипта и убережет от ошибок.

Обратите внимание, помимо создания объекта мы еще регистрируем соединение функцией window.sipInstance.register(). Несмотря на то, что в документации написано, что метод необходим для получения входящих звонков, исходящий звонок у меня без вызова этого метода не сработал. 

Для удобства тестирования у нас есть поле ввода для указания номера телефона (в формате 71112223344) и кнопки старта и отбоя вызова.

Для простоты объект SIP соединения хранится в глобальной переменной, поэтому при нажатии на кнопку «Позвонить» мы просто обращаемся к методу совершения вызова call (“номер телефона”) из WebSDK (см. документацию)

Для того, чтобы звонок можно было сбросить, мы сохраняем сессию звонка в новую глобальную переменную window.sipSession. 

И при нажатии на кнопку «Отбой вызова» вызываем метод terminate(), который по сути завершает сессию и прекращает вызов.

На всякий случай сверим структуру файлов.

В итоге должно получиться следующее дерево проекта:

xwiki_web_sdk

├── node_modules ─ установленные модули не показаны

├── dist

│   └── bundle.js ─ готовый скрипт для работы в браузере

├── index.html ─ страница для проверки сборки

├── src

│   └── index.ts ─ Исходник

├── tsconfig.json ─ Файлы конфигурации

├── package.json

└── webpack.config.js

Осталось всё протестировать.

Откройте index.html в браузере, введите номер и нажмите позвонить.

Обязательно предоставьте разрешение на доступ к микрофону.

Как сделать виджет звонков из браузера на примере XWiki - 3

Если всё успешно, то на ваш номер поступит вызов.

Теперь мы готовы внедрить скрипт в XWiki.

Интеграция скрипта в XWiki

Я надеюсь, что у вас уже установлена XWiki. Но если нет, то для целей тестирования всегда можно скачать предустановленную версию. Для её запуска необходимо только распаковать архив и запустить соответствующий .bat /.sh файл. Напомню, что логин и пароль администратора по умолчанию будет Admin и admin соответственно.

Для этой статьи я использовал версию 15.10.12 с предустановленным Standard Flavor (ссылка на страницу загрузки) и включил в настройках русский язык.

Далее я считаю, что вы успешно запустили XWiki и авторизовались как администратор.

Прежде чем приступить к работе убедитесь что в настройках профиля у вас выбран тип пользователя «Продвинутый». Это необходимо для работы с объектами.

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

Перейдите в панель администрирования XWiki в раздел  «Настройки интерфейса → Мастер панелей» и  нажмите на ссылку «Перейти к панелям» (можно просто перейти по адресу {host}/xwiki/bin/view/Panels/).

Создайте новую панель. Я для своей выбрал название PhoneCall.

Как сделать виджет звонков из браузера на примере XWiki - 4

На странице редактирования оставьте настройки без изменений. Можете указать свое описание панели и изменить категорию на tools, но это все не критично. Нажмите «Сохранить и посмотреть».

Сразу добавим объект JS расширения, в который копируем код из budle.js. 

В меню редактирования выберете «Объекты». Добавьте новый объект JavaScriptExtension и скопируйте в него полностью код из bundle.js. В поле «политика кэширования» установите пока «без кеширования»

Остальные настройки можно не изменять. 

Как сделать виджет звонков из браузера на примере XWiki - 5

Сохраните и закройте страницу.

Кстати существуют и другие способы добавить скрипт, например с помощью WebJar.

Но как гласит один популярный мем:

Как сделать виджет звонков из браузера на примере XWiki - 6

Если вам любопытно, то с другими вариантами интеграции JS библиотек можно ознакомится в документации XWiki.

После возвращения на страницу панели, перейдите в режим редактирования страницы и в поле содержимое вставьте следующий код:

Скрипт для панели
{{velocity}}

$xwiki.jsx.use('Panels.PhoneCall')

## credentials to Web SDK

#set ($sipLogin = 'Ваш SIP логин' )

#set ($sipPassword = 'Ваш пароль к SIP логину' )

## Draw header

#panelheader('PhoneCall')

## Get some user's properties if it's user profil page

#set ($userObj = $doc.getObject('XWiki.XWikiUsers'))

#if ($userObj)

#set ($firstName = $userObj.getValue('first_name'))

#set ($lastName = $userObj.getValue('last_name'))

#set ($phone = $userObj.getValue('phone'))

    #if ($firstName != "")

        **Имя**: $firstName

    #end

    #if ($lastName != "")

        **Фамилия**: $lastName

    #end

    #if ($phone != "")

        **Телефон**: $phone

        {{html}}

            <script> 

                require.config({

                paths: {

                    'web-voice': '$xwiki.getURL("Panels.PhoneCall", "jsx", "language=$xcontext.language")'

                }

                });

                

                

                requirejs(['web-voice'], function () {

                        window.sipInstance = WebVoiceSdk.createSipInstance({

                        sipLogin: "$sipLogin",

                        sipPassword: "$sipPassword",

                        });

                        window.sipInstance.register();

                    });   

                

                </script>

                

                <input

                value="Позвонить"

                type="button"

                id="startCallButton"

                onclick="window.sipSession = window.sipInstance.call('$phone')"

                />

                

                <input

                value="Отбой вызова"

                type="button"

                id="stopCallButton"

                onclick="window.sipSession.terminate()"

                />

        

        {{/html}}

    #else

    В профиле не заполнен

    номер телефона

    #end

#else

        Перейдите на страницу

        профиля пользователя

        для совершения звонка.

#end

#panelfooter()

{{/velocity}}

Давайте разберем код подробнее.

Обратите внимание, мы используем в скрипте Velocity, JS, и HTML.

Весь скрипт у нас обернут в тег {{velocity}}, что позволяет удобно использовать функции и переменные XWiki. 

Разберем некоторые фрагменты:

$xwiki.jsx.use('Panels.PhoneCall') — импортирует JavaScriptExtension который мы создали чуть ранее, Panels.PhoneCall — это адрес страницы с панелью, который можно посмотреть во вкладке «Информация».

## credentials to Web SDK
#set ($sipLogin = 'Ваш SIP логин' )
#set ($sipPassword = 'Ваш пароль к SIP логину' )

Чтобы не лазить по всему листингу, в шапку мы вынесем логи и пароль к SIP ID.
Далее по коду эти Velocity переменные мы вставим в JS обработчики.

Не забудьте перенести сюда ваши логин и пароль.

#panelheader('PhoneCall') — выводит заголовок панели.

#set ($userObj = $doc.getObject('XWiki.XWikiUsers'))получает объект с профилем пользователя, если он есть. 

#if ($userObj)

#set ($firstName = $userObj.getValue('first_name'))

#set ($lastName = $userObj.getValue('last_name'))

#set ($phone = $userObj.getValue('phone'))

    #if ($firstName != "")

        **Имя**: $firstName

    #end

    #if ($lastName != "")

        **Фамилия**: $lastName
    #end

Проверяем, перешли ли мы на страницу профиля. Если перешли, то также проверяем заполнение имени и фамилии в профиле, чтобы не показывать пустые поля.

Дальше проверяем заполнение номера телефона.

   #if ($phone != "")

        **Телефон**: $phone

        {{html}}

            <script> 

                require.config({

                paths: {

                    'web-voice': '$xwiki.getURL("Panels.PhoneCall", "jsx", "language=$xcontext.language")'

                }

                });       

                requirejs(['web-voice'], function () {

                        window.sipInstance = WebVoiceSdk.createSipInstance({

                        sipLogin: "$sipLogin",

                        sipPassword: "$sipPassword",

                        });

                        window.sipInstance.register();

                    });                  

                </script>               

                <input

                value="Позвонить"

                type="button"

                id="startCallButton"

                onclick="window.sipSession = window.sipInstance.call('$phone')"

                />

                <input

                value="Отбой вызова"

                type="button"

                id="stopCallButton"

                onclick="window.sipSession.terminate()"

                />

        {{/html}}

    #else

    В профиле не заполнен

    номер телефона

    #end

Если телефон заполнен, то с помощью тега {{html}} вставляем код очень похожий на index.html в предыдущем разделе.

На что стоит обратить внимание.

Во-первых, поскольку {{html}} лежит внутри обработчика {{velocity}} то мы можем вставлять Velocity код в любом месте.

Например, в 

sipLogin: "$sipLogin",

sipPassword: "$sipPassword"

По сути в JS переменные подставляем значения из Velocity переменных.

Второе отличие: это немного более сложная конфигурация библиотеки require.js

 require.config({

     paths: {

       'web-voice': '$xwiki.getURL("Panels.PhoneCall", "jsx", "language=$xcontext.language")'

            }

          });

Она встроена в XWiki по умолчанию и нам остается только сконфигурировать путь до JSX объекта, получив URL до JS кода с помощью функции  $xwiki.getURL("Panels.PhoneCall", "jsx", "language=$xcontext.language")'.

Теперь мы будем уверены, что рано или поздно скрипт для создания WebVoiceSdk.createSipInstance загрузится.

 #else

    В профиле не заполнен

    номер телефона

    #end

#else

        Перейдите на страницу

        профиля пользователя

        для совершения звонка.

#end

#panelfooter()

{{/velocity}}

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

И в самом конце мы выводим футер панели и закрываем тег {{/velocity}}.

Сохраняем и закрываем режим редактирования.

Если все прошло успешно, то на странице с предпросмотром панели мы увидим текст о том, что необходимо перейти на страницу профиля.

Как сделать виджет звонков из браузера на примере XWiki - 7

Добавим панель в интерфейс XWiki.

Вернитесь обратно в раздел «Настройки интерфейса → Мастер панелей». Откройте вкладку  «Список панелей». И просто перетащите панель PhoneCall на левый сайдбар.

Сохраните изменения.

Теперь перейдём на страницу профиля, например профиля Admin.

Заполним поле с номером телефона и почти всё готово.

Однако, если вы используете для тестирования локальную версию XWiki, возможно у вас по умолчанию будет запрещён доступ к микрофону. Я смог его разблокировать, перейдя в chrome://flags/ и добавив адрес XWiki в исключения флага Insecure origins treated as secure. 

С подготовкой теперь точно всё, осталось проверить работоспособность.

Как сделать виджет звонков из браузера на примере XWiki - 8

Вс1 прошло успешно, на мой телефон поступил вызов.

Как сделать виджет звонков из браузера на примере XWiki - 9

Спасибо всем, кто прочитал до конца. Надеюсь статья вам понравилась. Мне будет приятно прочитать конструктивные комментарии.

Автор: BosonBeard

Источник

* - обязательные к заполнению поля


https://ajax.googleapis.com/ajax/libs/jquery/3.4.1/jquery.min.js