├── gen
└── .keep
├── .gitignore
├── .env_sample
├── readme-img1.png
├── composer.json
├── src
├── assets
│ ├── js
│ │ ├── test_browser
│ │ │ ├── utils
│ │ │ │ ├── status.js
│ │ │ │ ├── webrtc_utils.js
│ │ │ │ └── bowser.js
│ │ │ ├── test_cases
│ │ │ │ ├── test_browser.js
│ │ │ │ ├── test_micro.js
│ │ │ │ ├── test_devices.js
│ │ │ │ ├── test_camera.js
│ │ │ │ ├── test_room.js
│ │ │ │ └── test_network.js
│ │ │ ├── JitsiTestEvent.js
│ │ │ ├── Statistics.js
│ │ │ ├── TestResults.js
│ │ │ ├── ui.js
│ │ │ └── JitsiTestBrowser.js
│ │ ├── lang.js
│ │ └── app.js
│ └── css
│ │ ├── animations.css
│ │ └── app.css
├── lang
│ ├── en.php
│ └── fr.php
└── index.html
├── docker-compose.yml
├── docker
├── webrtctest.conf
└── entrypoint.sh
├── Dockerfile
├── README.md
├── composer.lock
├── LICENSE
└── scripts
└── make-release.php
/gen/.keep:
--------------------------------------------------------------------------------
1 | Here so git can track the folder
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | gen/**
2 | !gen/.keep
3 | vendor/**
4 | src/debug/**
5 | .env
--------------------------------------------------------------------------------
/.env_sample:
--------------------------------------------------------------------------------
1 | #Web server
2 | SERVER_NAME=127.0.0.1:8443
3 | TURN_CRED_ENDPOINT=XXXXXXXXX
--------------------------------------------------------------------------------
/readme-img1.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/daimoc/JitsiMeetTestBrowserTool/main/readme-img1.png
--------------------------------------------------------------------------------
/composer.json:
--------------------------------------------------------------------------------
1 | {
2 | "require": {
3 | "matthiasmullie/minify": "^1.3"
4 | }
5 | }
6 |
--------------------------------------------------------------------------------
/src/assets/js/test_browser/utils/status.js:
--------------------------------------------------------------------------------
1 | /**
2 | * Enum for test running statuses
3 | *
4 | * @type {{PAUSED: number, STOPPED: number, PROCESSING: number, WAITING: number, ENDED: number}}
5 | */
6 | window.TestStatuses = {
7 | WAITING: 0,
8 | PROCESSING: 1,
9 | PAUSED: 2,
10 | STOPPED: 3,
11 | ENDED: 4,
12 | };
--------------------------------------------------------------------------------
/src/assets/js/lang.js:
--------------------------------------------------------------------------------
1 | window.lang = {
2 | dictionary: "",
3 |
4 | get: function(key){
5 | if(this.dictionary[key] !== undefined) {
6 | return this.dictionary[key]
7 | }else{
8 | console.error(`Translation not found: ${key}`)
9 | return `{${key}}`;
10 | }
11 | }
12 | }
--------------------------------------------------------------------------------
/docker-compose.yml:
--------------------------------------------------------------------------------
1 | services:
2 | jwt_server:
3 | image: jitsimeettestbrowsertool
4 | build:
5 | context: jitsimeettestbrowsertool
6 | container_name: jitsimeettestbrowsertool
7 | ports:
8 | - "8080:80"
9 | - "8443:443"
10 | logging:
11 | driver: syslog
12 | options:
13 | tag: "jitsimeettestbrowsertool"
14 | env_file:
15 | - .env
16 |
--------------------------------------------------------------------------------
/src/assets/css/animations.css:
--------------------------------------------------------------------------------
1 | .progress-container {
2 | height: 0.2rem;
3 | width: 90%;
4 | border-radius: 0.4rem;
5 |
6 | background: #35476b;
7 | }
8 |
9 | .progress-container .progress {
10 | height: 100%;
11 | width: 0;
12 | border-radius: 0.4rem;
13 |
14 | background: #039be5;
15 |
16 | transition: width 0.4s ease;
17 | }
18 |
19 | /* Blink */
20 | .blink {
21 | animation: blinker 1s linear infinite;
22 | }
23 |
24 | @keyframes blinker {
25 | 50% {
26 | opacity: 0;
27 | }
28 | }
--------------------------------------------------------------------------------
/docker/webrtctest.conf:
--------------------------------------------------------------------------------
1 |
2 | ServerName ${SERVER_NAME}
3 |
4 | SSLEngine on
5 | SSLCertificateFile /etc/apache2/apache.pem
6 |
7 | alias /WebRTCTest /usr/local/WebRTCTestBrowser/gen/1.0.0/index.html
8 |
9 |
10 |
11 | AuthType None
12 | Require all granted
13 |
14 |
15 |
16 | AuthType None
17 | Require all granted
18 |
19 |
20 | LogLevel warn
21 |
--------------------------------------------------------------------------------
/docker/entrypoint.sh:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 |
3 | CONFIG_DIR="/usr/local/WebRTCTestBrowser"
4 | export config='$config'
5 | echo "ServerName $SERVER_NAME"
6 |
7 | echo "Generating config File from template and .env file"
8 |
9 | echo "Apache"
10 | cat $CONFIG_DIR/webrtctest.conf | envsubst > /etc/apache2/sites-available/webrtctest.conf
11 |
12 | echo "Config Generation Done"
13 |
14 | echo "Dealing with certificate files"
15 | if [ ! -f /etc/apache2/apache.pem ]
16 | then
17 | echo "Generate self signed certificate for apache"
18 | openssl req -x509 -newkey rsa:4096 -keyout key.pem -out cert.pem -days 10000 -nodes -subj '/CN='$SERVER_NAME
19 | cat key.pem cert.pem > /etc/apache2/apache.pem
20 | rm key.pem cert.pem
21 | fi
22 |
23 | a2ensite webrtctest.conf && a2enmod ssl
24 |
25 | cd $CONFIG_DIR/scripts
26 | php make-release.php --release 1.0.0 --debug --turn-endpoint $TURN_CRED_ENDPOINT
27 |
28 | #Start Apache
29 | /usr/sbin/apache2ctl -DFOREGROUND
30 |
--------------------------------------------------------------------------------
/src/assets/js/test_browser/test_cases/test_browser.js:
--------------------------------------------------------------------------------
1 | /**
2 | * TestCase: test_browser
3 | */
4 |
5 | if (!window.hasOwnProperty('JitsiTestBrowser'))
6 | window.JitsiTestBrowser = {};
7 |
8 | /**
9 | * Test browser case
10 | */
11 | window.JitsiTestBrowser.test_browser = {
12 |
13 | /**
14 | * Run test
15 | *
16 | * @return {Promise<*>}
17 | */
18 | run: function () {
19 | return new Promise(res => {
20 | console.log("> Running test_browser");
21 | window.JitsiTestEvents.dispatch('run', {"status": window.TestStatuses.PROCESSING, "context": "test_browser"});
22 |
23 | let utils = new WebRTCUtils();
24 | let status = "fail";
25 |
26 | if (navigator.mediaDevices !== undefined && utils.isPeerConnectionSupported() && !utils.isBannedBrowser()) {
27 | status = "success";
28 | }
29 |
30 | window.JitsiTestBrowser.runner.resolve(res, {"result": status}, "test_browser");
31 | });
32 | }
33 | }
--------------------------------------------------------------------------------
/src/assets/js/test_browser/JitsiTestEvent.js:
--------------------------------------------------------------------------------
1 | /**
2 | * List of Jitsi tests events
3 | */
4 | window.JitsiTestEvents = {
5 |
6 | /**
7 | * Test running event
8 | */
9 | run: new Event('run'),
10 |
11 |
12 | /**
13 | * Network stat event
14 | */
15 | networkStat: new Event('network_stat'),
16 |
17 |
18 | dispatch: function(eventName, data = undefined){
19 | let event;
20 | switch (eventName){
21 | case 'run':
22 | event = this.run;
23 | break;
24 |
25 | case 'network_stat':
26 | event = this.networkStat;
27 | break;
28 |
29 | default:
30 | console.error(`Unknwon event: ${event}`)
31 | return;
32 | }
33 |
34 | for (const [key, value] of Object.entries(data)) {
35 | event[key] = value;
36 | }
37 |
38 | document.dispatchEvent(event);
39 |
40 | // Reset entities
41 | this[eventName] = new Event(eventName);
42 | }
43 | }
--------------------------------------------------------------------------------
/src/assets/js/test_browser/test_cases/test_micro.js:
--------------------------------------------------------------------------------
1 | /**
2 | * TestCase: test_micro
3 | */
4 |
5 | if (!window.hasOwnProperty('JitsiTestBrowser'))
6 | window.JitsiTestBrowser = {};
7 |
8 | /**
9 | * Test micro case
10 | */
11 | window.JitsiTestBrowser.test_micro = {
12 |
13 | /**
14 | * Run test
15 | *
16 | * @return {Promise<*>}
17 | */
18 | run: function () {
19 | return new Promise(res => {
20 | console.log("> Running test_micro");
21 | window.JitsiTestEvents.dispatch('run', {"status": window.TestStatuses.PROCESSING, "context": "test_micro"});
22 |
23 | let utils = new WebRTCUtils();
24 |
25 | utils.getDefaultMediaCapture("audio", function (result) {
26 | window.JitsiTestBrowser.runner.resolve(res, {"result": "success", 'details': result}, "test_micro");
27 |
28 | }, function (error) {
29 | window.JitsiTestBrowser.runner.resolve(res, {"result": "fail", 'details': error}, "test_micro");
30 | });
31 | });
32 | }
33 | }
--------------------------------------------------------------------------------
/Dockerfile:
--------------------------------------------------------------------------------
1 | FROM debian:stable-slim
2 |
3 | RUN apt update && apt-get -y install curl gettext-base\
4 | && apt-get update\
5 | && apt-get install -y --install-recommends apache2\
6 | && apt-get -y install php php-mysql php-mbstring php-gmp composer zip unzip php-zip
7 |
8 |
9 | RUN mkdir /usr/local/WebRTCTestBrowser
10 | COPY ./scripts /usr/local/WebRTCTestBrowser/scripts
11 | COPY ./src /usr/local/WebRTCTestBrowser/src
12 | COPY ./gen /usr/local/WebRTCTestBrowser/gen
13 | COPY ./docker/webrtctest.conf /usr/local/WebRTCTestBrowser/webrtctest.conf
14 |
15 | COPY composer.json /usr/local/WebRTCTestBrowser/composer.json
16 | COPY composer.lock /usr/local/WebRTCTestBrowser/composer.lock
17 |
18 | RUN cd /usr/local/WebRTCTestBrowser\
19 | && composer install -n \
20 | && rm -rf /var/lib/apt/lists/*
21 |
22 | EXPOSE 80 443
23 |
24 | # redirect apache logs to docker stdout/stderr
25 | RUN ln -sf /proc/1/fd/1 /var/log/apache2/access.log
26 | RUN ln -sf /proc/1/fd/2 /var/log/apache2/error.log
27 |
28 | COPY ./docker/entrypoint.sh /var/
29 | RUN chmod +x /var/entrypoint.sh
30 |
31 | ENTRYPOINT ["/bin/bash", "/var/entrypoint.sh"]
--------------------------------------------------------------------------------
/src/assets/js/test_browser/test_cases/test_devices.js:
--------------------------------------------------------------------------------
1 | /**
2 | * TestCase: test_devices
3 | */
4 |
5 | if (!window.hasOwnProperty('JitsiTestBrowser'))
6 | window.JitsiTestBrowser = {};
7 |
8 | /**
9 | * Test devices case
10 | */
11 | window.JitsiTestBrowser.test_devices = {
12 |
13 | /**
14 | * Run test
15 | *
16 | * @return {Promise<*>}
17 | */
18 | run: function () {
19 | return new Promise(resolve => {
20 | console.log("> Running test_devices");
21 | window.JitsiTestEvents.dispatch('run', {
22 | "status": window.TestStatuses.PROCESSING,
23 | "context": "test_devices"
24 | });
25 |
26 | let utils = new WebRTCUtils();
27 |
28 | utils.getListDevices(function (result) {
29 | window.JitsiTestBrowser.runner.resolve(resolve, {"result": "success", 'details': result}, "test_devices");
30 |
31 | }, function (error) {
32 | window.JitsiTestBrowser.runner.resolve(resolve, {"result": "fail", 'details': error}, "test_devices");
33 | });
34 | });
35 | }
36 | }
--------------------------------------------------------------------------------
/src/assets/js/test_browser/test_cases/test_camera.js:
--------------------------------------------------------------------------------
1 | /**
2 | * TestCase: test_camera
3 | */
4 |
5 | if (!window.hasOwnProperty('JitsiTestBrowser'))
6 | window.JitsiTestBrowser = {};
7 |
8 | /**
9 | * Test camera case
10 | */
11 | window.JitsiTestBrowser.test_camera = {
12 |
13 | /**
14 | * Run test
15 | *
16 | * @return {Promise<*>}
17 | */
18 | run: function () {
19 | return new Promise(res => {
20 | console.log("> Running test_camera");
21 | window.JitsiTestEvents.dispatch('run', {"status": window.TestStatuses.PROCESSING, "context": "test_camera"});
22 |
23 | let utils = new WebRTCUtils();
24 |
25 | utils.getDefaultMediaCapture("video", function (result) {
26 | window.JitsiTestBrowser.runner.resolve(res, {
27 | "result": "success",
28 | 'details': result
29 | }, "test_camera")
30 | }, function(error){
31 | window.JitsiTestBrowser.runner.resolve(res, {
32 | "result": "fail",
33 | 'details': error
34 | }, "test_camera")
35 | });
36 | });
37 | }
38 | }
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | #
JitsiTestBrowserTool
2 |
3 |
4 |
5 | This tools allows you to make a release for Jitsi test browser page (minify js/css files, pack the app in one file).
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 | > #### /!\ Not working yet!
14 | >
15 | > We are currently working on it, stay in touch ;)
16 |
17 |
18 | # Getting started
19 | ## Environment
20 | ### Requirements
21 |
22 | To make a release, you need to have at lease:
23 | * php (7.4+)
24 | * composer (1.10+)
25 |
26 | Then, use **composer install** to get the dependencies.
27 |
28 |
29 | ## Make a new release
30 |
31 | To make a new release, you need to run the **make-release.php** scripts.
32 |
33 | > To get more information, use **"php make-release.php --help"**
34 |
35 | Once done, you will find your unique index.html file containing the test tool.
36 |
37 |
38 | ## Translate the app
39 |
40 | By default, this app is on **english**.
41 | Supported translations are:
42 | * fr (French)
43 | * en (English)
44 |
45 | If you want have your own translation, just copy the **lang/en.php** file into your language (example: **de.php**), then edit the translations.
46 |
47 | Then, re run the make-release.php script with the parameter **--lang=de**
48 |
--------------------------------------------------------------------------------
/src/assets/js/test_browser/Statistics.js:
--------------------------------------------------------------------------------
1 | /**
2 | * Show test result
3 | */
4 |
5 | if (!window.hasOwnProperty('JitsiTestBrowser'))
6 | window.JitsiTestBrowser = {};
7 |
8 | /**
9 | * Show test result
10 | */
11 | window.JitsiTestBrowser.Statistics = {
12 |
13 | /**
14 | * Statistics content
15 | */
16 | statistics: {},
17 |
18 | /**
19 | * Add a statistic
20 | *
21 | * @param testCase
22 | * @param data
23 | */
24 | addStat: function(testCase, data){
25 | if (!Object.keys(this.statistics).includes(testCase)){
26 | this.statistics[testCase] = data;
27 | }else{
28 | this.statistics[testCase].push(data);
29 | }
30 | },
31 |
32 | /**
33 | * Export statistics
34 | */
35 | export: function(){
36 | // Prepare file name
37 | let current = new Date();
38 | let file = `${current.getFullYear()}-${current.getMonth()}-${current.getDate()}_${current.getHours()}-${current.getMinutes()}_test-brower-results.json`;
39 |
40 | // Create fake link to force download
41 | let fakeLink = document.createElement('a');
42 | fakeLink.setAttribute('href', "data:application/json," + encodeURIComponent(JSON.stringify(window.JitsiTestBrowser.Statistics.statistics)));
43 | fakeLink.setAttribute('download', `${file}`);
44 |
45 | document
46 | .querySelector('body')
47 | .append(fakeLink);
48 |
49 | fakeLink.addEventListener('click', function(){
50 | // Remove fake link
51 | this.remove();
52 | });
53 |
54 | // Force download
55 | fakeLink.click();
56 | },
57 |
58 |
59 | /**
60 | * Clear statistics
61 | *
62 | * @param testCase
63 | */
64 | reset: function(testCase = undefined){
65 | if (testCase === undefined){
66 | this.statistics = {};
67 | }else{
68 | if (this.statistics.hasOwnProperty(testCase)){
69 | delete this.statistics[testCase]
70 | }
71 | }
72 | }
73 | }
--------------------------------------------------------------------------------
/src/assets/js/test_browser/utils/webrtc_utils.js:
--------------------------------------------------------------------------------
1 | class WebRTCUtils {
2 |
3 |
4 | /**
5 | * Test if RTCPeerConnection API is present
6 | * @returns {boolean}
7 | */
8 | isPeerConnectionSupported = function () {
9 | let rtcConfig = {};
10 | try {
11 | return new RTCPeerConnection(rtcConfig) !== undefined;
12 | } catch (err) {
13 | console.log("Peer connection not supported")
14 | return false;
15 | }
16 | }
17 |
18 | /**
19 | * Test if Browser support is part of banned browser
20 | * @returns {boolean}
21 | */
22 | isBannedBrowser = function () {
23 | return (bowser.msie || bowser.safari || bowser.msedge);
24 | }
25 |
26 | /**
27 | * try to capture a media by its type(audio or video)
28 | * @param {string} mediaType - selected media type : audio|video
29 | * @param {function} success - callback that handles capture track labels
30 | * @param {function} error - callback on error
31 | */
32 | getDefaultMediaCapture = function (mediaType, success, error) {
33 | const mediaStreamConstraints = {
34 | video: mediaType === "video",
35 | audio: mediaType === "audio"
36 | };
37 |
38 | navigator.mediaDevices.getUserMedia(mediaStreamConstraints)
39 | .then(function (mediaStream) {
40 | /* use the stream */
41 | let trackLabels = "";
42 | mediaStream.getTracks().forEach(function (track) {
43 | trackLabels += track.label;
44 | track.stop();
45 | });
46 | success(trackLabels);
47 | })
48 | .catch(function (err) {
49 | error(err);
50 | });
51 | }
52 |
53 |
54 | /**
55 | * Get All Media devices seen by the navigator
56 | *
57 | * @param {function} success - callback that handles a mediaInfo object containing media labels
58 | * @param {function} error - callback on error
59 | */
60 | getListDevices = function(success, error){
61 | navigator.mediaDevices.enumerateDevices()
62 | .then(function (devices){
63 | let mediaInfo = {
64 | audioinput:[],
65 | audiooutput:[],
66 | videoinput :[]
67 | };
68 | console.log(devices);
69 | devices.forEach(function(deviceInfo) {
70 | if (deviceInfo.kind === 'audioinput')
71 | mediaInfo.audioinput.push(deviceInfo.label);
72 | else if (deviceInfo.kind === 'audiooutput')
73 | mediaInfo.audiooutput.push(deviceInfo.label);
74 | else if (deviceInfo.kind === 'videoinput')
75 | mediaInfo.videoinput.push(deviceInfo.label);
76 | });
77 | success(mediaInfo);
78 | })
79 | .catch(function(err) {
80 | error(err);
81 | });
82 | }
83 |
84 |
85 | /**
86 | * Wait function
87 | *
88 | * @param delay Delay to wait
89 | * @return {Promise<*>}
90 | */
91 | wait = function (delay) {
92 | if (!delay) delay = 1000;
93 | return new Promise(r => setTimeout(r, delay))
94 | }
95 | }
--------------------------------------------------------------------------------
/src/assets/js/test_browser/TestResults.js:
--------------------------------------------------------------------------------
1 | /**
2 | * Show test result
3 | */
4 |
5 | if (!window.hasOwnProperty('JitsiTestBrowser'))
6 | window.JitsiTestBrowser = {};
7 |
8 | /**
9 | * Show test result
10 | */
11 | window.JitsiTestBrowser.TestResults = {
12 |
13 |
14 | /**
15 | * List of test cases which did not passed
16 | */
17 | testsOnError: [],
18 |
19 | /**
20 | * Show test devices results
21 | *
22 | * @param data
23 | */
24 | test_devices: function(data){
25 | let container = document.getElementById('devices_results');
26 |
27 | if (data.result === 'success' && data.details){
28 | // Success
29 | let sub = container.querySelector('div[data-result="success"]')
30 | sub.classList.remove('hide');
31 |
32 | // Remove default
33 | container.querySelector('div[data-result="default"]')
34 | .classList.add('hide');
35 |
36 | ['audioinput', 'audiooutput', 'videoinput'].forEach(function (element){
37 | if (data.details.hasOwnProperty(element)) {
38 | let elementContainer = sub.querySelector(`div[data-result="${element}"] > div[data-result="content"]`);
39 | elementContainer.innerHTML = '';
40 | let component = document.createElement('div');
41 | component.innerHTML = data.details[element].join('
');
42 | elementContainer.append(component);
43 | }
44 | });
45 |
46 | }else{
47 | // Fail
48 | let sub = container.querySelector('div[data-result="fail"]')
49 | sub.classList.remove('hide');
50 | }
51 | },
52 |
53 |
54 | /**
55 | * Show final results
56 | */
57 | test_global: function() {
58 | let container = document.getElementById('global_results');
59 |
60 | // Remove default
61 | container.querySelector('div[data-result="default"]')
62 | .classList.add('hide');
63 |
64 | if (window.JitsiTestBrowser.TestResults.testsOnError.length > 0){
65 | // Got errors
66 | let sub = container.querySelector('div[data-result="fail"]')
67 | sub.classList.remove('hide');
68 |
69 | // Show test failed
70 |
71 | window.JitsiTestBrowser.TestResults.testsOnError.forEach(testCase => {
72 | sub.querySelector(`[data-test-case="${testCase}"]`).classList.remove('hide');
73 | })
74 | }else{
75 | // All test passed successfully
76 | let sub = container.querySelector('div[data-result="success"]')
77 | sub.classList.remove('hide');
78 | }
79 |
80 | // Show action buttons
81 | container.querySelector('div[data-content="action_buttons"]').classList.remove('hide');
82 | },
83 |
84 |
85 | /**
86 | * Default rendering function
87 | *
88 | * @param testCase
89 | * @param data
90 | */
91 | defaultRendering: function (testCase, data) {
92 | let container = document.getElementById(document.getElementById(testCase).getAttribute('data-results'));
93 |
94 | // Remove default
95 | container.querySelector('div[data-result="default"]')
96 | .classList.add('hide');
97 |
98 | if (data.result === 'success'){
99 | // Success
100 | let sub = container.querySelector('div[data-result="success"]')
101 | sub.classList.remove('hide');
102 |
103 | // Show result title
104 | sub.querySelector('div[data-result="title"]')
105 | .classList.remove('hide');
106 |
107 | if (data.details){
108 | sub.querySelector('div[data-result="data"]')
109 | .append(data.details)
110 | }
111 |
112 | }else{
113 | // Fail
114 | let sub = container.querySelector('div[data-result="fail"]')
115 | sub.classList.remove('hide');
116 | }
117 |
118 | container.scrollIntoView({ behavior: 'smooth', block: 'end'})
119 | }
120 | }
--------------------------------------------------------------------------------
/src/lang/en.php:
--------------------------------------------------------------------------------
1 | For the best user experience always use last sable version of your browser.';
22 | $lang['browser_test_fail']= 'Your browser is not compatible with Jitsi Meet.
Please use one these recommended Browsers.';
23 |
24 | // Test devices
25 | $lang['devices'] = 'Devices';
26 | $lang['devices_title'] = 'Test access to your devices';
27 | $lang['devices_test_success'] = 'Detected media devices :';
28 | $lang['devices_test_fail']= 'Unable to list your media devices (Audio and Video).';
29 | $lang['devices_audio_input_label']='Audio input:';
30 | $lang['devices_audio_output_label']='Audio output:';
31 | $lang['devices_video_label']='Vidéo input:';
32 |
33 | // Test camera
34 | $lang['camera'] = 'Camera';
35 | $lang['camera_title'] = 'Test access to your camera';
36 | $lang['camera_test_success'] = 'Your camera is correctly detected.';
37 | $lang['camera_test_success_default'] = 'The camera used by default is:';
38 |
39 | // Test micro
40 | $lang['micro'] = 'Microphone';
41 | $lang['micro_title'] = 'Test access to your microphone';
42 | $lang['micro_test_success'] = 'Your microphone is correctly detected.';
43 | $lang['micro_test_success_default'] = 'The microphone used by default is:';
44 |
45 | // Test network
46 | $lang['network'] = 'Network';
47 | $lang['network_title'] = 'Test your network connection';
48 | $lang['network_test_fail']= 'Unable to establish a WebRTC media connection to our test server.
You should be connected on media restricted network.
Please ask you local support to check your network filter rules.';
49 |
50 | // Test room
51 | $lang['room'] = 'Room';
52 | $lang['room_title'] = 'Test direct access to a test conference';
53 | $lang['room_test_fail']= 'Unable to detect the connection of your echo media stream.
Please wait 30 seconds before ending the test room.
Please verify that you have check your browser permission.
Please verify your local network filter rules to our media servers. ';
54 | $lang['room_test_success']= 'Echo media Stream was connected';
55 |
56 | // Home page
57 | $lang['home_disclaimer'] = 'This tool allows to check the following cases:';
58 | $lang['home_browser'] = 'Browser compatibility';
59 | $lang['home_devices'] = 'Access to your devices';
60 | $lang['home_camera'] = 'Access to your camera video';
61 | $lang['home_micro'] = 'Access to your microphone';
62 | $lang['home_network'] = 'Network connections (WebRTC, TCP, UDP)';
63 | $lang['home_room'] = 'Direct access to a conference test room';
64 |
65 | // Network test
66 | $lang['websocket'] = 'WebSocket';
67 | $lang['udp'] = 'UDP';
68 | $lang['tcp'] = 'TCP';
69 | $lang['bitrate'] = 'Bitrate:';
70 | $lang['average_bitrate'] = 'Average:';
71 | $lang['packetlost'] = 'Packet lost:';
72 | $lang['framerate'] = 'Framerate:';
73 | $lang['droppedframes'] = 'Dropped frames:';
74 | $lang['jitter'] = 'Jitter:';
75 |
76 | // Buttons
77 | $lang['run_all_tests'] = 'Start testing';
78 |
79 | // Results
80 | $lang['results'] = 'Results';
81 | $lang['results_shown_there'] = 'Results of this test will be shown here.';
82 | $lang['all_results_shown_there'] = 'Result of tests will be shown here.';
83 | $lang['global_test_fail'] = 'Some problems occurred while executing tests';
84 | $lang['following_test_failed'] = 'Following test failed:';
85 | $lang['global_more_information'] = 'For more details, you can consult the result of each test.';
86 |
87 | // Final results
88 | $lang['global_test_success'] = 'Your work station is compatible with our service';
89 | $lang['global_test_message'] = 'You can fully use the RendezVous service.
If you want, you can export tests result or restart the tests using the buttons below.';
--------------------------------------------------------------------------------
/src/lang/fr.php:
--------------------------------------------------------------------------------
1 | Pour bénéficier d\'une meilleure expérience utilisateur nous vous recommandons de toujours utiliser les dernières versions stables des navigateurs.';
22 | $lang['browser_test_fail']= 'Votre navigateur n\'est pas compatible avec Jitsi Meet.
Veuillez utiliser l\'un des navigateurs suivant : Navigateurs.';
23 |
24 | // Test devices
25 | $lang['devices'] = 'Périphériques';
26 | $lang['devices_title'] = 'Test de l\'accès à vos périphériques';
27 | $lang['devices_test_success'] = 'Périphériques média détéctés :';
28 | $lang['devices_test_fail'] = 'Impossible de lister vos périphériques de capture de média (Audio et Vidéo).';
29 | $lang['devices_audio_input_label']='Capture Audio :';
30 | $lang['devices_audio_output_label']='Sortie Audio :';
31 | $lang['devices_video_label']='Capture Vidéo :';
32 |
33 | // Test camera
34 | $lang['camera'] = 'Caméra';
35 | $lang['camera_title'] = 'Test de l\'accès à votre caméra';
36 | $lang['camera_test_success'] = 'Votre caméra est correctement détectée.';
37 | $lang['camera_test_success_default'] = 'La caméra utilisée par défaut est :';
38 |
39 | // Test micro
40 | $lang['micro'] = 'Microphone';
41 | $lang['micro_title'] = 'Test de l\'accès à votre microphone';
42 | $lang['micro_test_success'] = 'Votre microphone est correctement détecté.';
43 | $lang['micro_test_success_default'] = 'Le microphone utilisé par défaut est :';
44 |
45 | // Test network
46 | $lang['network'] = 'Réseau';
47 | $lang['network_title'] = 'Test de votre connexion réseau';
48 | $lang['network_test_fail'] = 'Impossible de réaliser une connexion média WebRCT vers notre serveur de test.
Vous devez vous trouver dans un environnement ou la trafic WebRTC est filtré.
Veuillez vérifier auprès de vos responsable informatiques locaux les règles de filtrages des connexions';
49 |
50 | // Test room
51 | $lang['room'] = 'Conférence';
52 | $lang['room_title'] = 'Test de l\'accès direct à une conference de test';
53 | $lang['room_test_fail'] = 'Impossible de detecter la connection votre flux média.
Veuillez attendre au moins 30 secondes avant d\'arrêter le test.
Veuillez vérifier vos permissions de navigateur.
Veuillez vérifier vos règles de filtrages réseaux vers nos media servers.';
54 | $lang['room_test_success']= 'Votre flux média a bien été connecté.';
55 |
56 | // Home page
57 | $lang['home_disclaimer'] = 'Cet outil vous permet de vérifier les cas suivant:';
58 | $lang['home_browser'] = 'Compatibilité de votre navigateur';
59 | $lang['home_devices'] = 'Accès à vos périphériques';
60 | $lang['home_camera'] = 'Accès à votre caméra';
61 | $lang['home_micro'] = 'Accès à votre microphone';
62 | $lang['home_network'] = 'Connexions réseaux (WebRTC, TCP, UDP)';
63 | $lang['home_room'] = 'Accès direct à une conférence de test';
64 |
65 | // Network test
66 | $lang['websocket'] = 'WebSocket';
67 | $lang['udp'] = 'UDP';
68 | $lang['tcp'] = 'TCP';
69 | $lang['bitrate'] = 'Bitrate :';
70 | $lang['average_bitrate'] = 'Average:';
71 | $lang['packetlost'] = 'Packetlost :';
72 | $lang['framerate'] = 'Framerate :';
73 | $lang['droppedframes'] = 'Dropped frames :';
74 | $lang['jitter'] = 'Jitter :';
75 |
76 | // Buttons
77 | $lang['run_all_tests'] = 'Démarrer le test';
78 |
79 | // Results
80 | $lang['results'] = 'Résultats';
81 | $lang['results_shown_there'] = 'Les résultats de ce test s\'afficheront ici.';
82 | $lang['all_results_shown_there'] = 'Le résultat des tests s\'affichera ici.';
83 | $lang['global_test_fail'] = 'Des problèmes sont survenus lors de l\'exécution des tests';
84 | $lang['following_test_failed'] = 'Les tests suivant n\'ont pas été réussi :';
85 | $lang['global_more_information'] = 'Pour plus de détails, vous pouvez consulter le résultat de chaque test.';
86 |
87 | $lang['global_test_success'] = 'Votre poste de travail est compatible avec notre service';
88 | $lang['global_test_message'] = 'Vous pouvez pleinement utiliser le services RendezVous.
Si vous le souhaitez, vous pouvez exporter les résultats ou relancer les tests en utilisant les boutons ci-dessous.';
89 |
--------------------------------------------------------------------------------
/src/assets/js/test_browser/ui.js:
--------------------------------------------------------------------------------
1 | /**
2 | * TestCase: test_browser
3 | */
4 |
5 | if (!window.hasOwnProperty('JitsiTestBrowser'))
6 | window.JitsiTestBrowser = {};
7 |
8 | /**
9 | * Test browser case
10 | *
11 | * @type {{swapPanes: Window.JitsiTestBrowser.UI.swapPanes, showResult: Window.JitsiTestBrowser.UI.showResult}}
12 | */
13 | window.JitsiTestBrowser.UI = {
14 |
15 | /**
16 | * Swap between right panes
17 | *
18 | * @param id ID of the pane to show
19 | * @param withMenu if true, also swap "is-active" class of left menu item
20 | */
21 | swapPanes: function(id, withMenu = true){
22 | // Hide all
23 | document.querySelectorAll(".test-result").forEach(function (element){
24 | element.classList.add('hide');
25 | });
26 | // Show asked one
27 | const referer = document.getElementById(id).getAttribute('data-refer-to');
28 | document.getElementById(referer).classList.remove('hide')
29 |
30 | if (withMenu){
31 | document.querySelectorAll(".menu-item, .menu-item-small").forEach(function (element){
32 | element.classList.remove('is-active');
33 | });
34 | document.querySelectorAll(`[data-pane="${id}"]`).forEach(function(element){
35 | element.classList.add('is-active');
36 | });
37 | }
38 | },
39 |
40 |
41 | /**
42 | * Update UI according to result got from test (
43 | *
44 | * @param result
45 | * @param testCase
46 | */
47 | showResult: function (testCase, data = {}){
48 | if (window.JitsiTestBrowser.TestResults.hasOwnProperty(testCase)){
49 | // Specific handling for this result
50 | window.JitsiTestBrowser.TestResults[testCase](data);
51 |
52 | }else {
53 | window.JitsiTestBrowser.TestResults.defaultRendering(testCase, data);
54 | }
55 | },
56 |
57 |
58 | /**
59 | * Show specific loader
60 | *
61 | * @param context
62 | * @param show
63 | */
64 | showLoader: function(context, show = true){
65 | let container = document.querySelector(`div.result-status[data-context="${context}"] > i[data-loader]`);
66 | container.classList = '';
67 | container.classList.add('fas', 'fa-spinner', 'fa-spin');
68 |
69 | if (!show){
70 | container.classList.add('hide')
71 | }
72 | },
73 |
74 |
75 | /**
76 | * Show specific test status
77 | *
78 | * @param context
79 | * @param show
80 | */
81 | showStatus: function(context, status = false, show = true){
82 | let mainContainer = document.querySelector(`div.result-status[data-context="${context}"]`);
83 | mainContainer.classList.remove('hide');
84 |
85 | let container = mainContainer.querySelector(`i[data-loader]`);
86 | container.classList = '';
87 | container.classList.add('fa-solid', 'fa-arrow-right-long');
88 |
89 |
90 | let sub = document.querySelector(`div.result-status[data-context="${context}"] > span`);
91 | if (show){
92 | sub.classList.remove('hide');
93 | }else{
94 | sub.classList.add('hide');
95 | }
96 |
97 | // Show status
98 | if (status !== false) {
99 | let statusContainer = sub.querySelector(`span[data-status="${status === "success" ? "OK" : "KO"}"]`);
100 | statusContainer.classList.remove('hide');
101 | }
102 | },
103 |
104 | /**
105 | * Blink identfied element
106 | *
107 | * @param id
108 | * @param blink
109 | */
110 | blink: function(id, blink){
111 | let element = document.getElementById(id);
112 | if (blink) {
113 | element.classList.add('blink');
114 | }else{
115 | element.classList.remove('blink');
116 | }
117 | },
118 |
119 |
120 | /**
121 | * Update network test status
122 | *
123 | * @param networkComponent
124 | * @param status
125 | */
126 | updateNetworkStatus: function(networkComponent, status){
127 | let container = document.getElementById(`media_connectivity_${networkComponent}`);
128 | let sub = container.querySelector('span[data-content="status_icon"]');
129 | let icon = sub.querySelector('i');
130 |
131 | container.classList.remove('test-success', 'test-fail');
132 | sub.classList.remove('hide');
133 | icon.classList = '';
134 |
135 | switch (status){
136 | case 'success':
137 | icon.classList.add('fa-solid', 'fa-check');
138 | container.classList.add('test-success');
139 |
140 | break;
141 |
142 | case 'fail':
143 | icon.classList.add('fa-solid', 'fa-circle-exclamation');
144 | container.classList.add('test-fail');
145 | break;
146 |
147 | case 'processing':
148 | icon.classList.add('fas', 'fa-spinner', 'fa-spin');
149 | break;
150 |
151 | default:
152 | console.error(`Unknown status: ${status}`)
153 | }
154 | }
155 | }
--------------------------------------------------------------------------------
/composer.lock:
--------------------------------------------------------------------------------
1 | {
2 | "_readme": [
3 | "This file locks the dependencies of your project to a known state",
4 | "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
5 | "This file is @generated automatically"
6 | ],
7 | "content-hash": "e7806260d775937d439159cde5165225",
8 | "packages": [
9 | {
10 | "name": "matthiasmullie/minify",
11 | "version": "1.3.68",
12 | "source": {
13 | "type": "git",
14 | "url": "https://github.com/matthiasmullie/minify.git",
15 | "reference": "c00fb02f71b2ef0a5f53fe18c5a8b9aa30f48297"
16 | },
17 | "dist": {
18 | "type": "zip",
19 | "url": "https://api.github.com/repos/matthiasmullie/minify/zipball/c00fb02f71b2ef0a5f53fe18c5a8b9aa30f48297",
20 | "reference": "c00fb02f71b2ef0a5f53fe18c5a8b9aa30f48297",
21 | "shasum": ""
22 | },
23 | "require": {
24 | "ext-pcre": "*",
25 | "matthiasmullie/path-converter": "~1.1",
26 | "php": ">=5.3.0"
27 | },
28 | "require-dev": {
29 | "friendsofphp/php-cs-fixer": "~2.0",
30 | "matthiasmullie/scrapbook": "dev-master",
31 | "phpunit/phpunit": ">=4.8"
32 | },
33 | "suggest": {
34 | "psr/cache-implementation": "Cache implementation to use with Minify::cache"
35 | },
36 | "bin": [
37 | "bin/minifycss",
38 | "bin/minifyjs"
39 | ],
40 | "type": "library",
41 | "autoload": {
42 | "psr-4": {
43 | "MatthiasMullie\\Minify\\": "src/"
44 | }
45 | },
46 | "notification-url": "https://packagist.org/downloads/",
47 | "license": [
48 | "MIT"
49 | ],
50 | "authors": [
51 | {
52 | "name": "Matthias Mullie",
53 | "email": "minify@mullie.eu",
54 | "homepage": "http://www.mullie.eu",
55 | "role": "Developer"
56 | }
57 | ],
58 | "description": "CSS & JavaScript minifier, in PHP. Removes whitespace, strips comments, combines files (incl. @import statements and small assets in CSS files), and optimizes/shortens a few common programming patterns.",
59 | "homepage": "http://www.minifier.org",
60 | "keywords": [
61 | "JS",
62 | "css",
63 | "javascript",
64 | "minifier",
65 | "minify"
66 | ],
67 | "funding": [
68 | {
69 | "url": "https://github.com/matthiasmullie",
70 | "type": "github"
71 | }
72 | ],
73 | "time": "2022-04-19T08:28:56+00:00"
74 | },
75 | {
76 | "name": "matthiasmullie/path-converter",
77 | "version": "1.1.3",
78 | "source": {
79 | "type": "git",
80 | "url": "https://github.com/matthiasmullie/path-converter.git",
81 | "reference": "e7d13b2c7e2f2268e1424aaed02085518afa02d9"
82 | },
83 | "dist": {
84 | "type": "zip",
85 | "url": "https://api.github.com/repos/matthiasmullie/path-converter/zipball/e7d13b2c7e2f2268e1424aaed02085518afa02d9",
86 | "reference": "e7d13b2c7e2f2268e1424aaed02085518afa02d9",
87 | "shasum": ""
88 | },
89 | "require": {
90 | "ext-pcre": "*",
91 | "php": ">=5.3.0"
92 | },
93 | "require-dev": {
94 | "phpunit/phpunit": "~4.8"
95 | },
96 | "type": "library",
97 | "autoload": {
98 | "psr-4": {
99 | "MatthiasMullie\\PathConverter\\": "src/"
100 | }
101 | },
102 | "notification-url": "https://packagist.org/downloads/",
103 | "license": [
104 | "MIT"
105 | ],
106 | "authors": [
107 | {
108 | "name": "Matthias Mullie",
109 | "email": "pathconverter@mullie.eu",
110 | "homepage": "http://www.mullie.eu",
111 | "role": "Developer"
112 | }
113 | ],
114 | "description": "Relative path converter",
115 | "homepage": "http://github.com/matthiasmullie/path-converter",
116 | "keywords": [
117 | "converter",
118 | "path",
119 | "paths",
120 | "relative"
121 | ],
122 | "time": "2019-02-05T23:41:09+00:00"
123 | }
124 | ],
125 | "packages-dev": [],
126 | "aliases": [],
127 | "minimum-stability": "stable",
128 | "stability-flags": [],
129 | "prefer-stable": false,
130 | "prefer-lowest": false,
131 | "platform": [],
132 | "platform-dev": [],
133 | "plugin-api-version": "1.1.0"
134 | }
135 |
--------------------------------------------------------------------------------
/src/assets/js/test_browser/JitsiTestBrowser.js:
--------------------------------------------------------------------------------
1 | /**
2 | * TestCase: test_browser
3 | */
4 |
5 | if (!window.hasOwnProperty('JitsiTestBrowser'))
6 | window.JitsiTestBrowser = {};
7 |
8 | /**
9 | * Global test runner
10 | */
11 | window.JitsiTestBrowser.runner = {
12 |
13 | /**
14 | * True if processing all tests (not a single one)
15 | */
16 | all_processing: false,
17 |
18 | /**
19 | * True if force stop on failures
20 | */
21 | stop_on_failures: false,
22 |
23 |
24 | /**
25 | * Current process status
26 | */
27 | status: 0,
28 |
29 |
30 | /**
31 | * List of test cases
32 | */
33 | testCases: [
34 | 'test_browser', 'test_devices', 'test_camera', 'test_micro', 'test_network', 'test_room'
35 | ],
36 |
37 |
38 | run: function(){
39 | this.all_processing = true;
40 | /**
41 | * Get promise to chain tests
42 | *
43 | * @param testCase
44 | * @return {Promise}
45 | */
46 | function getPromise(testCase) {
47 | return new Promise(resolve => {
48 | // Show right panel
49 | window.JitsiTestBrowser.UI.swapPanes(testCase);
50 |
51 | // Run test
52 | window.JitsiTestBrowser[testCase].run()
53 | .then(function (data) {
54 | // Default show result
55 | window.JitsiTestBrowser.UI.showResult(testCase, data);
56 |
57 | // Add statistics
58 | if (!window.JitsiTestBrowser[testCase].hasOwnProperty('pushStatistics'))
59 | window.JitsiTestBrowser.Statistics.addStat(testCase, data);
60 |
61 | if (data.result === 'fail' && window.JitsiTestBrowser.runner.stop_on_failures){
62 | window.JitsiTestBrowser.status = window.TestStatuses.STOPPED;
63 | }
64 |
65 | resolve();
66 | })
67 | .catch(function(reason){
68 | echo(reason, testCase);
69 | });
70 | })
71 | }
72 | /**
73 | * Async function run tests
74 | *
75 | * @return {Promise}
76 | */
77 | async function runTests() {
78 | const nbTestCases = window.JitsiTestBrowser.runner.testCases.length;
79 | let cpt = 1;
80 |
81 | for (const templateName of window.JitsiTestBrowser.runner.testCases) {
82 | if (window.JitsiTestBrowser.runner.stop_on_failures && window.JitsiTestBrowser.status === window.TestStatuses.STOPPED){
83 | window.JitsiTestBrowser.runner.all_processing = false;
84 | window.JitsiTestEvents.dispatch('run', {"status": window.TestStatuses.ENDED});
85 | // Stop on failures
86 | return;
87 | }
88 | window.JitsiTestBrowser.UI.blink(templateName, true);
89 | await getPromise(templateName);
90 | await window.JitsiTestBrowser.runner.wait();
91 | window.JitsiTestBrowser.UI.blink(templateName, false);
92 |
93 | // Update progress
94 | document.querySelector(".progress").style.width = `${100*cpt/nbTestCases}%`;
95 | cpt++;
96 |
97 | }
98 |
99 | // Show final results
100 | window.JitsiTestBrowser.UI.showResult('test_global');
101 | window.JitsiTestBrowser.UI.swapPanes('test_global');
102 |
103 | window.JitsiTestBrowser.runner.all_processing = false;
104 |
105 | // Update status
106 | window.JitsiTestEvents.dispatch('run', {"status": window.TestStatuses.ENDED});
107 | }
108 |
109 | runTests()
110 | },
111 |
112 |
113 | /**
114 | * Reset
115 | */
116 | reset: function(testCase){
117 | // Reset stop on failures checkbox
118 | document.getElementById('stop_on_failures').removeAttribute('checked')
119 |
120 | // Show default message
121 | document.querySelectorAll('[data-result="default"]').forEach(function(element){
122 | element.classList.remove('hide');
123 | });
124 |
125 | // Hide results
126 | document.querySelectorAll('[data-result="success"], [data-result="fail"]').forEach(function(element){
127 | element.classList.add('hide');
128 | });
129 | document.querySelectorAll('div.result-status[data-context]').forEach(function(element){
130 | element.classList.add('hide');
131 | });
132 |
133 | // Show first test case
134 | if (testCase)
135 | window.JitsiTestBrowser.UI.swapPanes(testCase);
136 |
137 | },
138 |
139 | /**
140 | * Wait function
141 | *
142 | * @param delay Delay to wait
143 | *
144 | * @returns {Promise}
145 | */
146 | wait: function(delay = 1000){
147 | return new Promise(r => setTimeout(r, delay))
148 | },
149 |
150 |
151 | /**
152 | * Final resolve function
153 | *
154 | * @param res
155 | * @param data
156 | */
157 | resolve: function(res, data, context){
158 | window.JitsiTestEvents.dispatch('run', {"status": window.TestStatuses.ENDED, "context": context, "data" : data});
159 | res(data)
160 | }
161 | }
--------------------------------------------------------------------------------
/src/assets/css/app.css:
--------------------------------------------------------------------------------
1 | /* General */
2 | html, body{
3 | background: #fff;
4 | margin: 0;
5 | padding: 0;
6 | height:100%;
7 | width: 100%;
8 | font-family: Helvetica,Arial,sans-serif;
9 | color: #35476b;
10 | }
11 |
12 | body {
13 | display: flex;
14 | flex-direction: column;
15 | min-height: 100%;
16 | margin:0;
17 | }
18 |
19 |
20 |
21 | ul li{
22 | line-height: 1.5rem;
23 | padding-left: 0.5rem;
24 | }
25 | .hide{
26 | display: none !important;
27 | }
28 | .hidden{
29 | visibility: hidden !important;
30 | }
31 | .error-title{
32 | font-weight: bold;
33 | font-style: italic;
34 | font-size: large;
35 | color: red;
36 | display: block;
37 | margin:1rem;
38 | }
39 | .error-title::before{
40 | font-family: "Font Awesome 6 Free";
41 | content: "\f06a";
42 | display: inline-block;
43 | padding-right: 3px;
44 | vertical-align: middle;
45 | font-weight: 900;
46 | font-style: normal;
47 | }
48 |
49 |
50 | /* Main & panels */
51 | div#main{
52 | flex: 1 1 auto;
53 | display: flex;
54 | flex-direction: row;
55 | }
56 | div#left-pane{
57 | background: #5f7bbf;
58 | min-width: 5.6rem;
59 | flex: 0 1 0;
60 | display: flex;
61 | align-items: flex-start;
62 | justify-content: flex-start;
63 | flex-direction: column;
64 | }
65 | div#right-pane{
66 | flex: 1 1 0;
67 | display:flex;
68 | flex-direction:column;
69 | padding-left:1rem;
70 | }
71 | div#right-pane h1{
72 | font-style: italic;
73 | margin-bottom: 1rem;
74 | margin-top: 0.7rem;
75 | }
76 | div#right-pane h2{
77 | font-style: italic;
78 | margin-left: 1rem;
79 | margin-bottom: 0.5rem;
80 | }
81 |
82 |
83 | /* Menu item */
84 | .menu-item{
85 | color: white;
86 | text-align: center;
87 | border: 1px solid #039be5;
88 | font-size: 2rem;
89 | cursor: pointer;
90 | height: 2rem;
91 | padding: 1rem;
92 | background: #5f7bbf;
93 | border-left: 0 solid #A8BEF3FF;
94 | transition: background 200ms ease 100ms,
95 | padding-left 100ms ease-in-out, padding-left 300ms ease-in-out,
96 | border-left 100ms ease-in-out, padding-left 300ms ease-in-out;
97 | width: 3.5rem;
98 |
99 | }
100 | .menu-item:hover{
101 | border-bottom-left-radius: 0.5rem;
102 | border-top-left-radius: 0.5rem;
103 | background: #42a5f5;
104 | padding-left: 1rem;
105 | border-left: 0.3rem solid #A8BEF3FF;
106 | }
107 | .menu-item.is-active{
108 | background: #42a5f5;
109 | }
110 |
111 |
112 | /* Network */
113 |
114 | .stat-left{
115 | display: block;
116 | float: left;
117 | width: 38%;
118 | }
119 | .stat-right{
120 | display: block;
121 | float: left;
122 | }
123 | .stat-right:before {
124 | content: '';
125 | display: inline-block;
126 | height: 100%;
127 | vertical-align: middle;
128 | margin-right: -0.25em;
129 | }
130 | .stat-right-item{
131 | margin-bottom: 1rem;
132 | width: 100%;
133 | min-width: 13rem;
134 | border: 1px solid lightgrey;
135 | padding: 0.7rem;
136 | box-shadow: 2px 2px lightgrey;
137 | }
138 | .stat-right-item span[data-content="title"]{
139 | font-weight: bold;
140 | }
141 | .stat-right-item span[data-content="status_icon"] i{
142 | float: right;
143 | }
144 |
145 | .stat-right-item span[data-sub]{
146 | display: block;
147 | }
148 | .stat-right-item span[data-sub="average_bitrate"]{
149 | font-style: italic;
150 | font-size: small;
151 | font-weight: normal;
152 | }
153 |
154 | div[data-content="video_dimensions"],
155 | div[data-content="ip_connected_to"]{
156 | font-size: smaller;
157 | }
158 |
159 | div[data-content="video_dimensions"] span[data-content="title"],
160 | div[data-content="ip_connected_to"] span[data-content="title"]{
161 | font-weight: bold;
162 | }
163 |
164 |
165 | /* Video & room components */
166 | video{
167 | background-color: black;
168 | }
169 | div#video_container{
170 | columns: 2;
171 | }
172 | #main_player {
173 | width: 80%;
174 | height: 37.5rem;
175 | }
176 | #second_player {
177 | display: none;
178 | }
179 |
180 |
181 | /* Actions */
182 | div.actions {
183 | margin-bottom: 2rem;
184 | margin-top: 2rem;
185 | }
186 | div.actions a{
187 | width: fit-content;
188 | margin-bottom: 1rem;
189 | padding: 1rem;
190 | border-radius: 0.5rem;
191 | color: white;
192 | cursor: pointer;
193 | background: #6c79b8;
194 | }
195 | div.actions a.disabled {
196 | background: #b4b9d0;
197 | cursor: not-allowed;
198 | }
199 | div.actions a.disabled:hover {
200 | background: #c5c7ce;
201 | }
202 | div.actions a:hover{
203 | background: #5F7BBF;
204 | color: #eee;
205 | }
206 |
207 | /* Results */
208 | div.test-result div a[data-action="test-runner"]{
209 | display: block;
210 | }
211 |
212 | div.test-result div.stat-right-item.test-success{
213 | border: 1px solid green;
214 | background-color: #ebf5d6;
215 | }
216 | div.test-result div.stat-right-item.test-success i{
217 | color: green;
218 | }
219 | div.test-result div.stat-right-item.test-fail{
220 | border: 1px solid orange;
221 | background-color: #fff6e9;
222 | }
223 | div.test-result div.stat-right-item.test-fail i{
224 | color: orange;
225 | }
226 | div#network_results{
227 | clear: both;
228 | }
229 |
230 | div.result-status{
231 | display: inline-block;
232 | margin-left: 1rem;
233 | }
234 | span[data-status="KO"]{
235 | color: #ff3131;
236 | }
237 | span[data-status="OK"]{
238 | color:green;
239 | }
240 |
241 | div[data-result="success"] div[data-result="data"] div div[data-result="title"]{
242 | font-weight: bold;
243 | margin-left: 1rem;
244 | margin-top: 1rem;
245 | }
246 | div#devices_results div[data-result="success"] div[data-result="data"] div div[data-result="content"],
247 | div#camera_results div[data-result="success"] div[data-result="data"],
248 | div#micro_results div[data-result="success"] div[data-result="data"]{
249 | margin-left: 2rem;
250 | font-style: italic;
251 | }
252 |
253 | a#export_results{
254 | margin-left: 1rem;
255 | }
256 |
257 |
258 |
259 |
260 | /* Run controls */
261 | div.run-controls{
262 | text-align: center;
263 | position: fixed;
264 | left: 50%;
265 | bottom: 20px;
266 | transform: translate(-50%, -50%);
267 | margin: 0 auto;
268 | }
269 | div.run-controls i{
270 | border: 1px solid #35476b;
271 | padding: 0.5rem;
272 | }
273 | div.run-controls i[data-action="start"]{
274 | border-radius: 10px 0 0 10px;
275 | }
276 | div.run-controls i[data-action="stop"]{
277 | border-radius: 0 10px 10px 0;
278 | }
279 |
280 | div[data-context="advanced_options"]{
281 | margin-top: 1.5rem;
282 | }
283 | div[data-context="advanced_options"] > *{
284 | cursor: pointer;
285 | }
286 |
287 | /* media queries */
288 | @media only screen and (max-width: 40em) {
289 | div#left-pane{
290 | min-height: 50rem;
291 | }
292 | }
293 |
294 |
295 | /*
296 | * Media queries
297 | */
298 |
299 |
300 | /* Small screens */
301 | @media screen and (max-width: 39.9375em) {
302 | /* Right pane */
303 | div#right-pane {
304 | padding-left: 0;
305 | }
306 | div#right-pane h1 {
307 | font-size: 1.4rem;
308 | margin-top: 1rem;
309 | margin-bottom: 1rem;
310 | padding-left: 1rem;
311 | }
312 | div#right-pane h2{
313 | font-size: 1.2rem;
314 | margin-left: 0;
315 | }
316 | div.progress-container{
317 | width: 95%;
318 | margin-left: 0.5rem;
319 | }
320 | div.test-result{
321 | padding-left: 1rem;
322 | }
323 |
324 |
325 | /* Responsive menu */
326 | #menu-icon-small{
327 | position: absolute;
328 | top: 0;
329 | right: 0;
330 | margin-right: 0.5rem;
331 |
332 | padding: 1rem 0.5rem 1rem 1rem;
333 | font-size: 1.5rem;
334 | }
335 | #responsive-menu-content{
336 | background-color: #5f7bbf;
337 | color: white;
338 | margin-bottom: 1rem;
339 | }
340 | #responsive-menu-content > div{
341 | padding: 1rem
342 | }
343 | #responsive-menu-content div.is-active{
344 | border-bottom-left-radius: 0.5rem;
345 | border-top-left-radius: 0.5rem;
346 | background: #42a5f5;
347 | padding-left: 1rem;
348 | border-left: 0.3rem solid #A8BEF3;
349 | }
350 |
351 | .menu-item-small{
352 | border-top: 1px solid #039be5;
353 |
354 | padding: 0.4rem;
355 | }
356 | .menu-item-small i{
357 | padding-right: 0.5rem;
358 | }
359 |
360 | .icon-opened:before{
361 | font-family: "Font Awesome 6 Free";
362 | content: "\f0c9";
363 | display: inline-block;
364 | padding-right: 3px;
365 | vertical-align: middle;
366 | font-weight: 900;
367 | font-style: normal;
368 |
369 | }
370 | .icon-closed:before{
371 | font-family: "Font Awesome 6 Free";
372 | content: "\f00d";
373 | display: inline-block;
374 | padding-right: 3px;
375 | vertical-align: middle;
376 | font-weight: 900;
377 | font-style: normal;
378 | }
379 |
380 | /* Network results */
381 |
382 | div#network_pane .stat-left{
383 | width:100%;
384 | }
385 | div#network_pane .stat-right{
386 | width: 90%;
387 | }
388 |
389 | div#video_container{
390 | columns: 1;
391 | }
392 |
393 | .hide-for-small-only {
394 | display: none !important;
395 | }
396 | }
397 | @media screen and (max-width: 0em), screen and (min-width: 40em) {
398 | .show-for-small-only {
399 | display: none !important;
400 | }
401 | }
402 |
403 |
404 | /* Medium screens */
405 | @media only screen and (min-width: 40em) and (max-width: 73.75em) {
406 | .stat-left{
407 | width: 63%;
408 | }
409 |
410 | div#video_container{
411 | columns: 1;
412 | }
413 | }
--------------------------------------------------------------------------------
/src/assets/js/app.js:
--------------------------------------------------------------------------------
1 | /**
2 | * Basic function used to show test results on UI
3 | * TODO: refactor this function by anything better
4 | *
5 | * @param result
6 | * @param id
7 | */
8 | function echo(result, id){
9 | console.log('%o', result)
10 | let res = document.createElement('div');
11 | res.append(JSON.stringify(result));
12 |
13 |
14 | let referer = document.getElementById(id).getAttribute('data-refer-to');
15 | document.getElementById(referer).append(res);
16 | }
17 |
18 |
19 | window.onload = function() {
20 |
21 | // Listen click on item (left menu)
22 | document.querySelectorAll(".menu-item, .menu-item-small").forEach(function (element){
23 | element.addEventListener("click", function() {
24 | document.querySelectorAll(".menu-item, .menu-item-small").forEach(function (element){
25 | element.classList.remove('is-active');
26 | });
27 | element.classList.add('is-active');
28 | window.JitsiTestBrowser.UI.swapPanes(this.getAttribute('data-pane'));
29 |
30 | // Hide menu for small only
31 | document.getElementById('menu-icon-small').click();
32 | });
33 | });
34 |
35 | // Listen to click on export results
36 | document.getElementById('export_results').addEventListener('click', function(){
37 | window.JitsiTestBrowser.Statistics.export();
38 | });
39 |
40 | // Listen to click on re run button
41 | document.getElementById('re_run').addEventListener('click', function(){
42 | let context = window.JitsiTestBrowser;
43 |
44 | // Reset templates
45 | for (const testCase of context.runner.testCases) {
46 | if (context.hasOwnProperty(testCase) &&
47 | context[testCase].hasOwnProperty('reset')) {
48 |
49 | context[testCase].reset()
50 |
51 | } else {
52 | context.runner.reset(testCase);
53 | }
54 | }
55 |
56 | // Clear show final results
57 | context.runner.reset('results');
58 |
59 | // Reset stats
60 | window.JitsiTestBrowser.Statistics.reset();
61 |
62 | // Reset progress bar
63 | document.querySelector(".progress").style.width = '0%'
64 |
65 | // Restart tests
66 | window.JitsiTestBrowser.runner.run();
67 | });
68 |
69 | // Listen to click on run alone test case
70 | document.querySelectorAll('[data-action="test-runner"]').forEach(function (element){
71 | element.addEventListener('click', function(){
72 | const testCase = element.getAttribute('data-test-case');
73 | window.JitsiTestBrowser.UI.blink(testCase, true);
74 |
75 | // Do nothing if disabled
76 | if (element.classList.contains('disabled')) return;
77 |
78 | // Reset UI and statistics first
79 | if (window.JitsiTestBrowser[testCase].hasOwnProperty('reset')){
80 | window.JitsiTestBrowser[testCase].reset();
81 | }else{
82 | window.JitsiTestBrowser.runner.reset();
83 | }
84 | window.JitsiTestBrowser.Statistics.reset(testCase);
85 |
86 | // Run
87 | window.JitsiTestBrowser[testCase].run()
88 | .then(function(result){
89 | window.JitsiTestBrowser.UI.showResult(testCase, result);
90 | window.JitsiTestBrowser.UI.blink(testCase, false);
91 | // Change button name
92 | element.querySelector('span[data-content="title"]').innerHTML = window.lang.get('rerun_alone_test');
93 | })
94 | .catch(function(reason){
95 | echo(reason, testCase)
96 | window.JitsiTestBrowser.UI.blink(testCase, false);
97 | });
98 | });
99 | });
100 |
101 | // Listen to click on stop on failures
102 | document.getElementById("stop_on_failures").addEventListener('click', function(element){
103 | window.JitsiTestBrowser.runner.stop_on_failures = this.checked === true;
104 | });
105 |
106 |
107 |
108 | // Listen to click on run all test
109 | document.getElementById('run_all').addEventListener('click', function(){
110 | this.setAttribute('disabled', 'disabled');
111 |
112 | window.JitsiTestBrowser.status = window.TestStatuses.PROCESSING;
113 |
114 | window.JitsiTestEvents.dispatch('run', {"status": window.TestStatuses.PROCESSING});
115 |
116 | window.JitsiTestBrowser.runner.run();
117 | });
118 |
119 | // Listen to responsive menu button click
120 | document.getElementById('menu-icon-small').addEventListener('click', function(){
121 | let iconOpened = this.querySelector('div span.icon-opened');
122 | let iconClosed = this.querySelector('div span.icon-closed');
123 |
124 | // Change icon
125 | if (this.getAttribute('data-opened') === "false"){
126 | this.setAttribute('data-opened', "true");
127 | iconOpened.classList.add('hide');
128 | iconClosed.classList.remove('hide');
129 |
130 | }else{
131 | this.setAttribute('data-opened', "false");
132 | iconOpened.classList.remove('hide');
133 | iconClosed.classList.add('hide');
134 | }
135 |
136 | // Show content
137 | document.getElementById('responsive-menu-content').classList.toggle('hide');
138 | });
139 |
140 | /**
141 | * Listen to "run" event
142 | * This function will temporarily disable run buttons for each test case
143 | */
144 | document.addEventListener('run', function(element){
145 | const status = element.status;
146 | switch (status){
147 | case window.TestStatuses.WAITING:
148 | case window.TestStatuses.ENDED:
149 | // All buttons available if not all processing
150 | if (!window.JitsiTestBrowser.runner.all_processing) {
151 | document.querySelectorAll('[data-action="test-runner"]').forEach(function (element) {
152 | element.classList.remove('disabled');
153 | element.removeAttribute('title');
154 | });
155 | }
156 | // Hide loader and show status result if needed
157 | if (element.context !== undefined){
158 | window.JitsiTestBrowser.UI.showLoader(element.context, false);
159 | window.JitsiTestBrowser.UI.showStatus(element.context, element.data.result, true);
160 | if (element.data.result === 'fail'){
161 | window.JitsiTestBrowser.TestResults.testsOnError.push(element.context);
162 | }
163 | }
164 |
165 | break;
166 | case window.TestStatuses.PROCESSING:
167 | // No buttons available
168 | document.querySelectorAll('[data-action="test-runner"]').forEach(function (element){
169 | element.classList.add('disabled');
170 | element.setAttribute('title', window.lang.get('test_running'));
171 | });
172 | // Update loader if needed
173 | if (element.component !== undefined){
174 | window.JitsiTestBrowser.UI.updateNetworkStatus(element.component, 'processing');
175 | }
176 | if (element.context !== undefined){
177 | window.JitsiTestBrowser.UI.showStatus(element.context, false, false);
178 | window.JitsiTestBrowser.UI.showLoader(element.context);
179 | }
180 | break;
181 | case window.TestStatuses.PAUSED:
182 | case window.TestStatuses.STOPPED:
183 | // TODO
184 | break;
185 |
186 | default:
187 | console.error(`Unknown status: ${status}`);
188 | }
189 | });
190 |
191 | /**
192 | * Listen to "network_stat" event
193 | * This function will temporarily disable run buttons for each test case
194 | */
195 | document.addEventListener('network_stat', function(element){
196 | if (element.data !== undefined && element.context !== undefined){
197 | switch (element.context){
198 | case 'wss':
199 | case 'tcp':
200 | case 'udp':
201 |
202 | if (element.data.status !== undefined) {
203 | window.JitsiTestBrowser.UI.updateNetworkStatus(element.context, element.data.status);
204 | }
205 | if (element.data.framesPerSecond !== undefined){
206 | // Got a framerate
207 | document.getElementById(`media_connectivity_framerate`)
208 | .querySelector('span[data-content="value"]').innerHTML = element.data.framesPerSecond;
209 |
210 | }else if (element.data.bitrate !== undefined){
211 | // Got a bitrate
212 | document.getElementById(`media_connectivity_bitrate`)
213 | .querySelector('span[data-sub="bitrate"] span[data-content="value"]').innerHTML = element.data.bitrate+' kbit/s';
214 |
215 | }else if (element.data.average_bitrate !== undefined){
216 | // Got a bitrate
217 | document.getElementById(`media_connectivity_bitrate`)
218 | .querySelector('span[data-sub="average_bitrate"] span[data-content="value"]').innerHTML = `${element.data.average_bitrate} kbit/s`;
219 |
220 | }else if (element.data.packetLost !== undefined){
221 | // Got droppedFrames
222 | document.getElementById(`media_connectivity_packetlost`)
223 | .querySelector('span[data-content="value"]').innerHTML = element.data.packetLost;
224 |
225 | }else if (element.data.jitter !== undefined){
226 | // Got droppedFrames
227 | document.getElementById(`media_connectivity_jitter`)
228 | .querySelector('span[data-content="value"]').innerHTML = element.data.jitter;
229 |
230 | }else if (element.data.ip_connected_to !== undefined){
231 | // Got droppedFrames
232 | document.querySelector(`div[data-content="ip_connected_to"]`)
233 | .querySelector('span[data-content="value"]').innerHTML = element.data.ip_connected_to;
234 | }
235 | break;
236 |
237 |
238 | case 'video_player':
239 | if (element.data.local !== undefined) {
240 | // Local video dimensions
241 | document.getElementById(`video_container`)
242 | .querySelector('section[data-content="local_stats"] div[data-content="video_dimensions"] span[data-content="value"]')
243 | .innerHTML = `${element.data.local.video_dimension.width}x${element.data.local.video_dimension.height} px`;
244 | }else{
245 | // Remote video dimensions
246 | document.getElementById(`video_container`)
247 | .querySelector('section[data-content="remote_stats"] div[data-content="video_dimensions"] span[data-content="value"]')
248 | .innerHTML = `${element.data.remote.video_dimension.width}x${element.data.remote.video_dimension.height} px`;
249 | }
250 | break;
251 | }
252 | }
253 | });
254 |
255 | };
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | Apache License
2 | Version 2.0, January 2004
3 | http://www.apache.org/licenses/
4 |
5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
6 |
7 | 1. Definitions.
8 |
9 | "License" shall mean the terms and conditions for use, reproduction,
10 | and distribution as defined by Sections 1 through 9 of this document.
11 |
12 | "Licensor" shall mean the copyright owner or entity authorized by
13 | the copyright owner that is granting the License.
14 |
15 | "Legal Entity" shall mean the union of the acting entity and all
16 | other entities that control, are controlled by, or are under common
17 | control with that entity. For the purposes of this definition,
18 | "control" means (i) the power, direct or indirect, to cause the
19 | direction or management of such entity, whether by contract or
20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the
21 | outstanding shares, or (iii) beneficial ownership of such entity.
22 |
23 | "You" (or "Your") shall mean an individual or Legal Entity
24 | exercising permissions granted by this License.
25 |
26 | "Source" form shall mean the preferred form for making modifications,
27 | including but not limited to software source code, documentation
28 | source, and configuration files.
29 |
30 | "Object" form shall mean any form resulting from mechanical
31 | transformation or translation of a Source form, including but
32 | not limited to compiled object code, generated documentation,
33 | and conversions to other media types.
34 |
35 | "Work" shall mean the work of authorship, whether in Source or
36 | Object form, made available under the License, as indicated by a
37 | copyright notice that is included in or attached to the work
38 | (an example is provided in the Appendix below).
39 |
40 | "Derivative Works" shall mean any work, whether in Source or Object
41 | form, that is based on (or derived from) the Work and for which the
42 | editorial revisions, annotations, elaborations, or other modifications
43 | represent, as a whole, an original work of authorship. For the purposes
44 | of this License, Derivative Works shall not include works that remain
45 | separable from, or merely link (or bind by name) to the interfaces of,
46 | the Work and Derivative Works thereof.
47 |
48 | "Contribution" shall mean any work of authorship, including
49 | the original version of the Work and any modifications or additions
50 | to that Work or Derivative Works thereof, that is intentionally
51 | submitted to Licensor for inclusion in the Work by the copyright owner
52 | or by an individual or Legal Entity authorized to submit on behalf of
53 | the copyright owner. For the purposes of this definition, "submitted"
54 | means any form of electronic, verbal, or written communication sent
55 | to the Licensor or its representatives, including but not limited to
56 | communication on electronic mailing lists, source code control systems,
57 | and issue tracking systems that are managed by, or on behalf of, the
58 | Licensor for the purpose of discussing and improving the Work, but
59 | excluding communication that is conspicuously marked or otherwise
60 | designated in writing by the copyright owner as "Not a Contribution."
61 |
62 | "Contributor" shall mean Licensor and any individual or Legal Entity
63 | on behalf of whom a Contribution has been received by Licensor and
64 | subsequently incorporated within the Work.
65 |
66 | 2. Grant of Copyright License. Subject to the terms and conditions of
67 | this License, each Contributor hereby grants to You a perpetual,
68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable
69 | copyright license to reproduce, prepare Derivative Works of,
70 | publicly display, publicly perform, sublicense, and distribute the
71 | Work and such Derivative Works in Source or Object form.
72 |
73 | 3. Grant of Patent License. Subject to the terms and conditions of
74 | this License, each Contributor hereby grants to You a perpetual,
75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable
76 | (except as stated in this section) patent license to make, have made,
77 | use, offer to sell, sell, import, and otherwise transfer the Work,
78 | where such license applies only to those patent claims licensable
79 | by such Contributor that are necessarily infringed by their
80 | Contribution(s) alone or by combination of their Contribution(s)
81 | with the Work to which such Contribution(s) was submitted. If You
82 | institute patent litigation against any entity (including a
83 | cross-claim or counterclaim in a lawsuit) alleging that the Work
84 | or a Contribution incorporated within the Work constitutes direct
85 | or contributory patent infringement, then any patent licenses
86 | granted to You under this License for that Work shall terminate
87 | as of the date such litigation is filed.
88 |
89 | 4. Redistribution. You may reproduce and distribute copies of the
90 | Work or Derivative Works thereof in any medium, with or without
91 | modifications, and in Source or Object form, provided that You
92 | meet the following conditions:
93 |
94 | (a) You must give any other recipients of the Work or
95 | Derivative Works a copy of this License; and
96 |
97 | (b) You must cause any modified files to carry prominent notices
98 | stating that You changed the files; and
99 |
100 | (c) You must retain, in the Source form of any Derivative Works
101 | that You distribute, all copyright, patent, trademark, and
102 | attribution notices from the Source form of the Work,
103 | excluding those notices that do not pertain to any part of
104 | the Derivative Works; and
105 |
106 | (d) If the Work includes a "NOTICE" text file as part of its
107 | distribution, then any Derivative Works that You distribute must
108 | include a readable copy of the attribution notices contained
109 | within such NOTICE file, excluding those notices that do not
110 | pertain to any part of the Derivative Works, in at least one
111 | of the following places: within a NOTICE text file distributed
112 | as part of the Derivative Works; within the Source form or
113 | documentation, if provided along with the Derivative Works; or,
114 | within a display generated by the Derivative Works, if and
115 | wherever such third-party notices normally appear. The contents
116 | of the NOTICE file are for informational purposes only and
117 | do not modify the License. You may add Your own attribution
118 | notices within Derivative Works that You distribute, alongside
119 | or as an addendum to the NOTICE text from the Work, provided
120 | that such additional attribution notices cannot be construed
121 | as modifying the License.
122 |
123 | You may add Your own copyright statement to Your modifications and
124 | may provide additional or different license terms and conditions
125 | for use, reproduction, or distribution of Your modifications, or
126 | for any such Derivative Works as a whole, provided Your use,
127 | reproduction, and distribution of the Work otherwise complies with
128 | the conditions stated in this License.
129 |
130 | 5. Submission of Contributions. Unless You explicitly state otherwise,
131 | any Contribution intentionally submitted for inclusion in the Work
132 | by You to the Licensor shall be under the terms and conditions of
133 | this License, without any additional terms or conditions.
134 | Notwithstanding the above, nothing herein shall supersede or modify
135 | the terms of any separate license agreement you may have executed
136 | with Licensor regarding such Contributions.
137 |
138 | 6. Trademarks. This License does not grant permission to use the trade
139 | names, trademarks, service marks, or product names of the Licensor,
140 | except as required for reasonable and customary use in describing the
141 | origin of the Work and reproducing the content of the NOTICE file.
142 |
143 | 7. Disclaimer of Warranty. Unless required by applicable law or
144 | agreed to in writing, Licensor provides the Work (and each
145 | Contributor provides its Contributions) on an "AS IS" BASIS,
146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
147 | implied, including, without limitation, any warranties or conditions
148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
149 | PARTICULAR PURPOSE. You are solely responsible for determining the
150 | appropriateness of using or redistributing the Work and assume any
151 | risks associated with Your exercise of permissions under this License.
152 |
153 | 8. Limitation of Liability. In no event and under no legal theory,
154 | whether in tort (including negligence), contract, or otherwise,
155 | unless required by applicable law (such as deliberate and grossly
156 | negligent acts) or agreed to in writing, shall any Contributor be
157 | liable to You for damages, including any direct, indirect, special,
158 | incidental, or consequential damages of any character arising as a
159 | result of this License or out of the use or inability to use the
160 | Work (including but not limited to damages for loss of goodwill,
161 | work stoppage, computer failure or malfunction, or any and all
162 | other commercial damages or losses), even if such Contributor
163 | has been advised of the possibility of such damages.
164 |
165 | 9. Accepting Warranty or Additional Liability. While redistributing
166 | the Work or Derivative Works thereof, You may choose to offer,
167 | and charge a fee for, acceptance of support, warranty, indemnity,
168 | or other liability obligations and/or rights consistent with this
169 | License. However, in accepting such obligations, You may act only
170 | on Your own behalf and on Your sole responsibility, not on behalf
171 | of any other Contributor, and only if You agree to indemnify,
172 | defend, and hold each Contributor harmless for any liability
173 | incurred by, or claims asserted against, such Contributor by reason
174 | of your accepting any such warranty or additional liability.
175 |
176 | END OF TERMS AND CONDITIONS
177 |
178 | APPENDIX: How to apply the Apache License to your work.
179 |
180 | To apply the Apache License to your work, attach the following
181 | boilerplate notice, with the fields enclosed by brackets "[]"
182 | replaced with your own identifying information. (Don't include
183 | the brackets!) The text should be enclosed in the appropriate
184 | comment syntax for the file format. We also recommend that a
185 | file or class name and description of purpose be included on the
186 | same "printed page" as the copyright notice for easier
187 | identification within third-party archives.
188 |
189 | Copyright [yyyy] [name of copyright owner]
190 |
191 | Licensed under the Apache License, Version 2.0 (the "License");
192 | you may not use this file except in compliance with the License.
193 | You may obtain a copy of the License at
194 |
195 | http://www.apache.org/licenses/LICENSE-2.0
196 |
197 | Unless required by applicable law or agreed to in writing, software
198 | distributed under the License is distributed on an "AS IS" BASIS,
199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
200 | See the License for the specific language governing permissions and
201 | limitations under the License.
202 |
--------------------------------------------------------------------------------
/scripts/make-release.php:
--------------------------------------------------------------------------------
1 | Mandatory options";
18 | echo "\t\n";
19 | echo "\t" . ' --release : the wanted release name' . "\n";
20 | echo "\t" . ' --debug : exec the script debugging purpose' . "\n";
21 | echo "\t" . ' -h / --help : print this help' . "\n";
22 | echo "\t" . ' -q : quiet mode' . "\n";
23 | echo "\t" . ' -v : verbose mode' . "\n";
24 | echo "\t" . ' -y : Force answer "yes" to messages prompted' . "\n";
25 | echo "\t\n";
26 | echo "\t> Optional options";
27 | echo "\t\n";
28 | echo "\t" . ' --fontawesome-kit : Font Awesome kit URL.' . "\n";
29 | echo "\t" . ' --lang : the wanted lang code. Default is "en".' . "\n";
30 | echo "\t" . ' --turn-endpoint : specify the turn endpoint to use for testing. Default is "https://rendez-vous.renater.fr/home/rest.php/TurnServer"' . "\n";
31 | echo "\t" . ' --websocket-url : specify the web socket url to use for testing. Default is "wss://rendez-vous.renater.fr/colibri-ws/echo"' . "\n";
32 | echo "\t" . ' --application-url : specify the application url to use for testing room. Default is "https://rendez-vous.renater.fr/"' . "\n";
33 |
34 | echo "\n";
35 | exit;
36 | }
37 |
38 |
39 | class MakeRelease{
40 |
41 | /**
42 | * @var string Turn endpoint to use
43 | */
44 | private static string $turnEndpoint = 'https://rendez-vous.renater.fr/home/rest.php/TurnServer';
45 |
46 | /**
47 | * @var string Websocket URL to use
48 | */
49 | private static string $websocketUrl = 'wss://rendez-vous.renater.fr/colibri-ws/echo';
50 |
51 | /**
52 | * @var string Application URL to use
53 | */
54 | private static string $applicationUrl = 'https://rendez-vous.renater.fr/';
55 |
56 | /**
57 | * @var string Application URL to use
58 | */
59 | private static string $fontawesomeKitURL = 'https://kit.fontawesome.com/0d01ffef9c.js';
60 |
61 | /**
62 | * @var string Lang to use on the generated page
63 | */
64 | private static string $langCode = 'en';
65 |
66 | /**
67 | * @var bool Quiet mod
68 | */
69 | public static bool $quiet = false;
70 |
71 | /**
72 | * @var bool Verbose mod
73 | */
74 | public static bool $verbose = false;
75 |
76 | /**
77 | * @var string $release
78 | */
79 | public static string $release = '';
80 |
81 |
82 | /**
83 | * @var bool Force yes to prompted questions
84 | */
85 | private static bool $forceYes = false;
86 |
87 |
88 | /**
89 | * EkkoLayer constructor.
90 | *
91 | * @param $options Array got from CLI
92 | */
93 | public function __construct(array $options) {
94 | // Prepare quiet & verbose mod
95 | static::$quiet = array_key_exists('q', $options);
96 | static::$forceYes = array_key_exists('y', $options);
97 | static::$verbose = !static::$quiet && array_key_exists('v', $options);
98 |
99 | if (array_key_exists('release', $options)){
100 | static::$release = $options['release'];
101 | }else{
102 | MakeRelease::error('Parameter --release not set');
103 | }
104 |
105 | if (array_key_exists('turn-endpoint', $options)){
106 | static::$turnEndpoint = $options['turn-endpoint'];
107 | }
108 | if (array_key_exists('websocket-url', $options)){
109 | static::$websocketUrl = $options['websocket-url'];
110 | }
111 | if (array_key_exists('application-url', $options)){
112 | static::$applicationUrl = $options['application-url'];
113 | }
114 | if (array_key_exists('fontawesome-kit', $options)){
115 | static::$fontawesomeKitURL = $options['fontawesome-kit'];
116 | }
117 | if (array_key_exists('lang', $options)){
118 | if (!file_exists(APP_ROOT.'src/lang/'.$options['lang'].'.php')){
119 | static::error("Lang '".$options['lang']."' not found");
120 | }else{
121 | static::$langCode = $options['lang'];
122 | }
123 | }
124 | }
125 |
126 |
127 | /**
128 | * @return void
129 | * @throws Minify\Exceptions\IOException
130 | */
131 | public function process(){
132 | $target = realpath(APP_ROOT.'/gen/').'/'.static::$release;
133 | $sources = realpath(APP_ROOT.'/src/');
134 |
135 | $continue = true;
136 |
137 | // Create target folder
138 | static::verbose("> Create target folder [".$target."] ...");
139 | if (is_dir($target)){
140 | $input = static::$forceYes ? 'y' : static::prompt("[Warning] Target folder already exists. All content will be erased. Continue? [y]");
141 | if (in_array($input, ['y', 'Y', 'yes', 'YES', ""])) {
142 | // remove old layer files
143 | if (is_dir($target)){
144 | static::deleteDir($target);
145 | static::verbose("\t=> Folder [".$target."] deleted.\n");
146 | }
147 | }else{
148 | $continue = false;
149 | static::message('Operation canceled by user.');
150 | }
151 | }
152 |
153 | if ($continue){
154 | // Create target folder
155 | mkdir($target, 0755);
156 |
157 | // Minify sources found
158 | $minified = static::minify($sources);
159 |
160 | $cssContent = '';
161 | $jsContent = '';
162 |
163 | // Copy template
164 | $targetIndex = $target.'/index.html';
165 | if (copy(APP_ROOT.'/src/index.html', $targetIndex)){
166 |
167 | // Replace in file
168 | $str = file_get_contents($targetIndex);
169 | $str = str_replace('', $jsContent, $str);
170 | $str = str_replace('', $cssContent, $str);
171 | $str = str_replace('', static::$turnEndpoint, $str);
172 | $str = str_replace('', static::$websocketUrl, $str);
173 | $str = str_replace('', static::$applicationUrl, $str);
174 | $str = str_replace('', static::$fontawesomeKitURL, $str);
175 |
176 | // Set up lang translation
177 | $str = $this->applyTranslation($str);
178 |
179 | // Put content into index target html file
180 | file_put_contents($targetIndex, $str);
181 |
182 | static::message("File $targetIndex successfully created.");
183 | }else{
184 | static::error('Cannot copy template file');
185 | }
186 | }
187 | }
188 |
189 | /**
190 | * Apply asked translation
191 | *
192 | * @param $str
193 | * @return array|mixed|string|string[]
194 | */
195 | private function applyTranslation($str){
196 | $lang = [];
197 |
198 | include APP_ROOT.'/src/lang/'.static::$langCode.'.php';
199 |
200 | foreach($lang as $id => $tr){
201 | $str = str_replace('{tr:'.$id.'}', utf8_decode($tr), $str);
202 | }
203 |
204 | return str_replace('""', utf8_decode(json_encode($lang)), $str);
205 | }
206 |
207 |
208 | /**
209 | * Minify JS & CSS files
210 | *
211 | * @param $source
212 | *
213 | * @return array
214 | *
215 | * @throws Minify\Exceptions\IOException
216 | */
217 | private static function minify($source): array {
218 | $minifierCSS = new MatthiasMullie\Minify\CSS();
219 | $minifierJS = new MatthiasMullie\Minify\JS();
220 |
221 | $files = static::getFiles($source);
222 | foreach ($files as $file) {
223 | $info = pathinfo($file);
224 |
225 | if (array_key_exists('extension', $info)) {
226 | if ($info['extension'] === 'css') {
227 | $minifierCSS->addFile($file);
228 | } else if ($info['extension'] === 'js') {
229 | $minifierJS->addFile($file);
230 | }
231 | }
232 | }
233 |
234 | return [
235 | 'js' => $minifierJS->minify(),
236 | 'css' => $minifierCSS->minify()
237 | ];
238 | }
239 |
240 | /**
241 | * Get files in dir
242 | *
243 | * @param $source
244 | *
245 | * @return array
246 | */
247 | static function getFiles($source): array {
248 | $files = array( );
249 | if (is_dir($source) & is_readable($source)) {
250 | $dir = dir($source);
251 | while (false !== ($file = $dir->read( ))) {
252 | // skip . and ..
253 | if (('.' === $file) || ('..' === $file) || ('debug' === $file)) {
254 | continue;
255 | }
256 | if (is_dir("$source/$file")) {
257 | $files = array_merge($files, static::getFiles("$source/$file"));
258 | } else {
259 | $files[] = "$source/$file";
260 | }
261 | }
262 | $dir->close( );
263 | }
264 | return $files;
265 | }
266 |
267 |
268 |
269 | /**
270 | * Prompt message and return user answer
271 | *
272 | * @param string $message
273 | * @return string
274 | */
275 | public static function prompt(string $message = ''): string {
276 | echo $message;
277 | $handle = fopen("php://stdin", 'r');
278 | $line = fgets($handle);
279 | return trim($line);
280 | }
281 |
282 |
283 | /**
284 | * Delete directory
285 | *
286 | * @param $dirPath
287 | */
288 | function deleteDir($dirPath) {
289 | if (! is_dir($dirPath)) {
290 | echo "$dirPath must be a directory";
291 | die(1);
292 | }
293 | if (substr($dirPath, strlen($dirPath) - 1, 1) != '/') {
294 | $dirPath .= '/';
295 | }
296 | $pattern = $dirPath . '{,.}[!.,!..]*';
297 | $files = glob($pattern, GLOB_MARK|GLOB_BRACE);
298 | foreach ($files as $file) {
299 | if (is_dir($file)) {
300 | static::deleteDir($file);
301 | } else {
302 | unlink($file);
303 | }
304 | }
305 | rmdir($dirPath);
306 | }
307 |
308 | /**
309 | * Show message in CLI
310 | *
311 | * @param $message
312 | */
313 | public static function message($message){
314 | if (static::$quiet) return;
315 |
316 | echo $message . "\n";
317 | }
318 |
319 |
320 | /**
321 | * Verbose message to show in CLI
322 | *
323 | * @param $message
324 | */
325 | public static function verbose($message){
326 | if (!static::$verbose) return;
327 |
328 | static::message($message);
329 | }
330 |
331 | /**
332 | * Show message then die
333 | *
334 | * @param $message string Message to show
335 | */
336 | public static function error(string $message){
337 | die('[ERROR] ' . $message . ', exiting' . "\n");
338 | }
339 | }
340 |
341 | // Make new release
342 | try{
343 | $makeRelease = new MakeRelease($options);
344 | $makeRelease->process();
345 |
346 | }catch (Exception $e){
347 | die ("[ERROR] ".$e->getMessage()."\n");
348 | }
349 |
350 |
351 |
352 |
--------------------------------------------------------------------------------
/src/assets/js/test_browser/test_cases/test_room.js:
--------------------------------------------------------------------------------
1 | /**
2 | * TestCase: test_room
3 | */
4 |
5 | if (!window.hasOwnProperty('JitsiTestBrowser'))
6 | window.JitsiTestBrowser = {};
7 |
8 |
9 | /**
10 | * Test room access case
11 | */
12 | window.JitsiTestBrowser.test_room = {
13 | /**
14 | * Use to set player lang
15 | */
16 | lang: {
17 | local: undefined,
18 | stored: undefined,
19 | },
20 |
21 | /**
22 | * Interval to wait before ensuring user connection to the room
23 | */
24 | testInterval: undefined,
25 |
26 | /**
27 | * Interval to show seconds remaining
28 | */
29 | timerInterval: undefined,
30 |
31 | /**
32 | * Test room seconds remaining
33 | */
34 | secondsRemaining: 30,
35 |
36 | /**
37 | * Room name
38 | */
39 | roomName: undefined,
40 |
41 | /**
42 | * Room token
43 | */
44 | roomToken: undefined,
45 |
46 | /**
47 | * Domain URL
48 | */
49 | domain_url: undefined,
50 |
51 | /**
52 | * Domain
53 | */
54 | domain: undefined,
55 |
56 | /**
57 | * Main API Client
58 | */
59 | mainApiClient: undefined,
60 |
61 | /**
62 | * Second API client
63 | */
64 | secondApiClient: undefined,
65 |
66 | /**
67 | * Main node player
68 | */
69 | mainNodePlayer: undefined,
70 |
71 | /**
72 | * Second node player
73 | */
74 | secondNodePlayer: undefined,
75 |
76 | /**
77 | * Network stats got between participants
78 | */
79 | network_stat: null,
80 |
81 | /**
82 | * Run test
83 | *
84 | * @return {Promise<*>}
85 | */
86 | run: function () {
87 | return new Promise(res => {
88 | console.log("> Running test_room");
89 | window.JitsiTestEvents.dispatch('run', {"status": window.TestStatuses.PROCESSING, "context": "test_room"});
90 |
91 | let context = window.JitsiTestBrowser.test_room;
92 | context.status = "pending"
93 |
94 | // Init nodes
95 | context.mainNodePlayer = document.getElementById('main_player');
96 | context.secondNodePlayer = document.getElementById('second_player');
97 |
98 | context.mainNodePlayer.classList.remove('hide');
99 | context.secondNodePlayer.classList.add('hide');
100 |
101 | /**
102 | * If there is an error processing test room, display it, close connections
103 | * if needed, then resolve with "error" status
104 | *
105 | * @param reason
106 | */
107 | let onError = function (reason) {
108 | context.onError(reason);
109 | context.closeConnections();
110 |
111 | context.mainNodePlayer.classList.add('hide');
112 | context.secondNodePlayer.classList.add('hide');
113 |
114 | window.JitsiTestBrowser.runner.resolve(res, {
115 | "result": "fail",
116 | "details": reason
117 | }, "test_room");
118 | };
119 |
120 | // init room name & room token
121 | context.getRoomName()
122 | .then(function () {
123 | context.getRoomToken()
124 | .then(function (){
125 | // Init test room
126 | context.initTestRoom().then(function () {
127 | // Connect clients
128 | context.connectClients().then(function () {
129 | // Add listeners to clients
130 | context.addListeners().then(function () {
131 | // Start interval
132 | context.testInterval = setTimeout(function () {
133 | // Test user connection to the room after 30 seconds
134 | context.testRoomConnection(function (result) {
135 | // Success, close connections, display results
136 | // and resolve with "success" status
137 | context.closeConnections();
138 | context.onSuccess(result);
139 |
140 | window.JitsiTestBrowser.runner.resolve(res, {"result": result}, "test_room");
141 | }, onError)
142 | }, 20000);
143 |
144 | // Show timer
145 | context.timerInterval = setInterval(function(){
146 | context.secondsRemaining--;
147 | if (context.secondsRemaining < 0){
148 | clearInterval(context.timerInterval);
149 | }
150 |
151 | }, 1000)
152 | }).catch(reason => {
153 | onError(reason);
154 | });
155 | }).catch(reason => {
156 | onError(reason);
157 | });
158 | }).catch(reason => {
159 | onError(reason);
160 | });
161 | }).catch(reason => {
162 | onError(reason);
163 | });
164 | }).catch(reason => {
165 | onError(reason);
166 | })
167 | })
168 |
169 | },
170 |
171 |
172 | /**
173 | * Get room name
174 | *
175 | * @returns {Promise<*>}
176 | */
177 | getRoomName: function(){
178 | return new Promise((resolve, reject) => {
179 |
180 | let appUrl = document.getElementById('main').getAttribute('data-application-url');
181 | let context = window.JitsiTestBrowser.test_room;
182 |
183 | let settings = {
184 | method: 'get',
185 | headers: new Headers({'content-type': 'text/plain'}),
186 | };
187 |
188 | // TODO: find a way to make it work without this call
189 | fetch(`${appUrl}/home/rest.php/TestBrowser/RoomName`, settings)
190 | .then(response => {
191 | response.text()
192 | .then(function (data) {
193 | context.roomName = data;
194 | resolve();
195 | })
196 | .catch(reason => {
197 | reject({"status": "fail", "error": reason.toString()});
198 | });
199 | }
200 | )
201 | .catch(reason => {
202 | reject({"status": "fail", "error": reason.toString()});
203 | });
204 | });
205 | },
206 |
207 |
208 | /**
209 | * Get room token
210 | *
211 | * @returns {Promise<*>}
212 | */
213 | getRoomToken: function() {
214 | return new Promise((resolve, reject) => {
215 | let appUrl = document.getElementById('main').getAttribute('data-application-url');
216 | let context = window.JitsiTestBrowser.test_room;
217 |
218 | let settings = {
219 | method: 'get'
220 | };
221 |
222 | // TODO: find a way to make it work without this call
223 | fetch(`${appUrl}/home/rest.php/TestBrowser/RoomToken?` + new URLSearchParams(
224 | {
225 | RoomName: context.roomName
226 | }
227 | ), settings)
228 | .then(response => {
229 | response.text()
230 | .then(function (data) {
231 | context.roomToken = decodeURIComponent(data);
232 | resolve();
233 | })
234 | .catch(reason => {
235 | reject({"status": "fail", "error": reason.toString()});
236 | });
237 | }
238 | )
239 | });
240 | },
241 |
242 | /**
243 | * Add listener for api client.
244 | *
245 | * Listeners used:
246 | * participantJoined: when a participant join the call
247 | * videoConferenceLeft: when participants left the call
248 | *
249 | * @return {Promise}
250 | */
251 | addListeners: function () {
252 | return new Promise((resolve) => {
253 | let context = window.JitsiTestBrowser.test_room;
254 |
255 | context.mainApiClient.addEventListener("networkStatUpdated", function (data) {
256 | console.log('[networkStatUpdated]');
257 | console.log(data);
258 | context.network_stat = data
259 | });
260 | context.mainApiClient.addEventListener("participantJoined", function () {
261 | let context = window.JitsiTestBrowser.test_room;
262 | context.mainApiClient.removeEventListener("participantJoined");
263 | });
264 |
265 | context.mainApiClient.addEventListener("videoConferenceLeft", function () {
266 | context.mainApiClient.removeEventListener("videoConferenceLeft");
267 |
268 | // Close connections on participant left
269 | // TODO: is it usefull anymore?
270 | context.closeConnections();
271 | });
272 |
273 | resolve();
274 | });
275 | },
276 |
277 |
278 | /**
279 | * Init test room
280 | *
281 | * @return {Promise}
282 | */
283 | initTestRoom: function () {
284 | return new Promise((resolve, reject) => {
285 | console.log('[test_room]: Init test room...');
286 |
287 | let context = window.JitsiTestBrowser.test_room;
288 |
289 | // Set lang into local storage to force translation into jitsi test room
290 | context.lang.local = document.querySelector('html').getAttribute('lang');
291 | context.lang.stored = localStorage.getItem('language');
292 | localStorage.removeItem('language');
293 | localStorage.setItem('language', context.lang.local);
294 |
295 | if (context.mainApiClient)
296 | context.mainApiClient.dispose();
297 | if (context.secondApiClient)
298 | context.secondApiClient.dispose();
299 |
300 | // Init domain
301 | context.domain = document.getElementById('main').getAttribute('data-application-url');
302 | context.domain_url = context.domain +'/home';
303 |
304 | /**
305 | * Get session ID using the Room endpoint
306 | */
307 | let appUrl = context.domain_url;
308 |
309 | let settings = {
310 | method: 'get'
311 | };
312 |
313 | // TODO: find a way to make it work without this call
314 | fetch(`${appUrl}/rest.php/Room?`+ new URLSearchParams(
315 | {
316 | roomName: context.roomName,
317 | domain: context.domain,
318 | token: context.roomToken,
319 | }
320 | ), settings)
321 | .then(response => {
322 | response.json()
323 | .then(function (data) {
324 | console.log('[test_room]: OK');
325 | if (data.status === 'success') {
326 | console.log('[test_room]: Test room initialized.');
327 | resolve();
328 | } else {
329 | reject(data);
330 | }
331 | })
332 | .catch(reason => {
333 | reject({"status": "fail", "error": reason.toString()});
334 | });
335 | }
336 | )
337 | });
338 | },
339 |
340 |
341 | /**
342 | * Connect API clients to the call
343 | *
344 | * @return {Promise}
345 | */
346 | connectClients: function () {
347 | return new Promise((resolve) => {
348 | let context = window.JitsiTestBrowser.test_room;
349 | console.log('[test_room]: Connect clients...');
350 |
351 | let mainOptions = {
352 | roomName: context.roomName,
353 | width: context.mainNodePlayer.width,
354 | height: context.mainNodePlayer.height,
355 | interfaceConfigOverwrite: {
356 | CLOSE_PAGE_GUEST_HINT: true
357 | },
358 | configOverwrite: {
359 | callStatsID: '',
360 | defaultLanguage: context.lang.local,
361 | enablePopupExternalAuth: true,
362 | startWithAudioMuted: true,
363 | startWithVideoMuted: true,
364 | p2p: {enabled: false},
365 | desktopSharingChromeDisabled: true
366 | },
367 | parentNode: context.mainNodePlayer
368 | }
369 |
370 | let secondOptions = {
371 | roomName: context.roomName,
372 | width: context.secondNodePlayer.width,
373 | height: context.secondNodePlayer.height,
374 | interfaceConfigOverwrite: {
375 | TOOLBAR_BUTTONS: ['microphone', 'camera', 'settings', 'hangup'],
376 | MAIN_TOOLBAR_BUTTONS: [],
377 | INITIAL_TOOLBAR_TIMEOUT: 0
378 | },
379 | configOverwrite: {
380 | callStatsID: '',
381 | enablePopupExternalAuth: true,
382 | p2p: {enabled: false}
383 | },
384 | parentNode: context.secondNodePlayer
385 | }
386 |
387 | // Connect main client
388 | let subDomain = context.domain.replace(/^https?:\/\//, '');
389 | context.mainApiClient = new JitsiMeetExternalAPI(subDomain, mainOptions);
390 |
391 | // Tricks to get media, connect another (same) client one second later
392 | setTimeout(function () {
393 | context.secondApiClient = new JitsiMeetExternalAPI(subDomain, secondOptions);
394 | console.log('[test_room]: Clients connected.');
395 | resolve();
396 | }, 1000);
397 |
398 |
399 | });
400 | },
401 |
402 | /**
403 | * Test user connection on the room
404 | *
405 | * @param resolve
406 | * @param reject
407 | */
408 | testRoomConnection: function (resolve, reject) {
409 | console.log('[test_room]: test room connection ...');
410 | let context = window.JitsiTestBrowser.test_room;
411 |
412 | context.mainApiClient.getNetworkStat();
413 |
414 | setTimeout(function(){
415 | if (context.network_stat === null || !context.network_stat.hasOwnProperty('stat')){
416 | reject({"status": "fail", "reason": "network_error"})
417 | }
418 |
419 | // Check if there is participants
420 | if (context.secondApiClient.getParticipantsInfo().length !== 2) {
421 | // not participant
422 | reject({"status": "fail", "reason": "no_participant"})
423 | } else {
424 | resolve("success");
425 | }
426 | }, 2000)
427 | },
428 |
429 | /**
430 | * Close API connections, clear test interval
431 | */
432 | closeConnections: function () {
433 | let context = window.JitsiTestBrowser.test_room;
434 | console.log('[test_room]: Close connections ...');
435 |
436 | // Close connections for main & second client
437 | if (context.mainApiClient) {
438 | context.mainApiClient.executeCommand('hangup');
439 | context.secondApiClient.executeCommand('hangup');
440 | context.mainApiClient.removeEventListener("videoConferenceLeft");
441 | context.mainApiClient.removeEventListener("participantJoined");
442 | setTimeout(function () {
443 | if (context.mainApiClient){
444 | context.mainApiClient.dispose();
445 | }
446 | if (context.secondApiClient) {
447 | context.secondApiClient.dispose();
448 | }
449 | context.mainApiClient = undefined;
450 | context.secondApiClient = undefined;
451 | }, 1000);
452 | localStorage.setItem('language', context.lang.stored);
453 | }
454 | // Remove timeouts
455 | if (context.testInterval) {
456 | clearInterval(context.testInterval);
457 | context.testInterval = undefined;
458 | }
459 |
460 | console.log('[test_room]: Connections closed.');
461 | },
462 |
463 |
464 | /**
465 | * Function to show test errors
466 | *
467 | * @param error
468 | */
469 | onError: function (error) {
470 | let context = window.JitsiTestBrowser.test_room;
471 |
472 | console.log('[test_room]: Error');
473 |
474 | context.statuses = "error";
475 |
476 | let details = error;
477 | if (error instanceof Error) {
478 | details = error.toString();
479 |
480 | } else if (error instanceof Object) {
481 | details = JSON.stringify(error);
482 | }
483 | console.log(details);
484 | },
485 |
486 | /**
487 | * Function to show test success
488 | *
489 | * @param result
490 | */
491 | onSuccess: function (result) {
492 | console.log('[test_room]: All test successful');
493 | console.log(result);
494 |
495 | // hide room player
496 | window.JitsiTestBrowser.test_room.mainNodePlayer.classList.add('hide');
497 | }
498 | }
--------------------------------------------------------------------------------
/src/assets/js/test_browser/utils/bowser.js:
--------------------------------------------------------------------------------
1 | /*!
2 | * Bowser - a browser detector
3 | * https://github.com/ded/bowser
4 | * MIT License | (c) Dustin Diaz 2015
5 | */
6 |
7 | !function (root, name, definition) {
8 | if (typeof module != 'undefined' && module.exports) module.exports = definition()
9 | else if (typeof define == 'function' && define.amd) define(name, definition)
10 | else root[name] = definition()
11 | }(this, 'bowser', function () {
12 | /**
13 | * See useragents.js for examples of navigator.userAgent
14 | */
15 |
16 | var t = true
17 |
18 | function detect(ua) {
19 |
20 | function getFirstMatch(regex) {
21 | var match = ua.match(regex);
22 | return (match && match.length > 1 && match[1]) || '';
23 | }
24 |
25 | function getSecondMatch(regex) {
26 | var match = ua.match(regex);
27 | return (match && match.length > 1 && match[2]) || '';
28 | }
29 |
30 | var iosdevice = getFirstMatch(/(ipod|iphone|ipad)/i).toLowerCase()
31 | , likeAndroid = /like android/i.test(ua)
32 | , android = !likeAndroid && /android/i.test(ua)
33 | , nexusMobile = /nexus\s*[0-6]\s*/i.test(ua)
34 | , nexusTablet = !nexusMobile && /nexus\s*[0-9]+/i.test(ua)
35 | , chromeos = /CrOS/.test(ua)
36 | , silk = /silk/i.test(ua)
37 | , sailfish = /sailfish/i.test(ua)
38 | , tizen = /tizen/i.test(ua)
39 | , webos = /(web|hpw)os/i.test(ua)
40 | , windowsphone = /windows phone/i.test(ua)
41 | , samsungBrowser = /SamsungBrowser/i.test(ua)
42 | , windows = !windowsphone && /windows/i.test(ua)
43 | , mac = !iosdevice && !silk && /macintosh/i.test(ua)
44 | , linux = !android && !sailfish && !tizen && !webos && /linux/i.test(ua)
45 | , edgeVersion = getSecondMatch(/edg([ea]|ios)\/(\d+(\.\d+)?)/i)
46 | , versionIdentifier = getFirstMatch(/version\/(\d+(\.\d+)?)/i)
47 | , tablet = /tablet/i.test(ua) && !/tablet pc/i.test(ua)
48 | , mobile = !tablet && /[^-]mobi/i.test(ua)
49 | , xbox = /xbox/i.test(ua)
50 | , result
51 |
52 | if (/opera/i.test(ua)) {
53 | // an old Opera
54 | result = {
55 | name: 'Opera'
56 | , opera: t
57 | , version: versionIdentifier || getFirstMatch(/(?:opera|opr|opios)[\s\/](\d+(\.\d+)?)/i)
58 | }
59 | } else if (/opr\/|opios/i.test(ua)) {
60 | // a new Opera
61 | result = {
62 | name: 'Opera'
63 | , opera: t
64 | , version: getFirstMatch(/(?:opr|opios)[\s\/](\d+(\.\d+)?)/i) || versionIdentifier
65 | }
66 | }
67 | else if (/SamsungBrowser/i.test(ua)) {
68 | result = {
69 | name: 'Samsung Internet for Android'
70 | , samsungBrowser: t
71 | , version: versionIdentifier || getFirstMatch(/(?:SamsungBrowser)[\s\/](\d+(\.\d+)?)/i)
72 | }
73 | }
74 | else if (/coast/i.test(ua)) {
75 | result = {
76 | name: 'Opera Coast'
77 | , coast: t
78 | , version: versionIdentifier || getFirstMatch(/(?:coast)[\s\/](\d+(\.\d+)?)/i)
79 | }
80 | }
81 | else if (/yabrowser/i.test(ua)) {
82 | result = {
83 | name: 'Yandex Browser'
84 | , yandexbrowser: t
85 | , version: versionIdentifier || getFirstMatch(/(?:yabrowser)[\s\/](\d+(\.\d+)?)/i)
86 | }
87 | }
88 | else if (/ucbrowser/i.test(ua)) {
89 | result = {
90 | name: 'UC Browser'
91 | , ucbrowser: t
92 | , version: getFirstMatch(/(?:ucbrowser)[\s\/](\d+(?:\.\d+)+)/i)
93 | }
94 | }
95 | else if (/mxios/i.test(ua)) {
96 | result = {
97 | name: 'Maxthon'
98 | , maxthon: t
99 | , version: getFirstMatch(/(?:mxios)[\s\/](\d+(?:\.\d+)+)/i)
100 | }
101 | }
102 | else if (/epiphany/i.test(ua)) {
103 | result = {
104 | name: 'Epiphany'
105 | , epiphany: t
106 | , version: getFirstMatch(/(?:epiphany)[\s\/](\d+(?:\.\d+)+)/i)
107 | }
108 | }
109 | else if (/puffin/i.test(ua)) {
110 | result = {
111 | name: 'Puffin'
112 | , puffin: t
113 | , version: getFirstMatch(/(?:puffin)[\s\/](\d+(?:\.\d+)?)/i)
114 | }
115 | }
116 | else if (/sleipnir/i.test(ua)) {
117 | result = {
118 | name: 'Sleipnir'
119 | , sleipnir: t
120 | , version: getFirstMatch(/(?:sleipnir)[\s\/](\d+(?:\.\d+)+)/i)
121 | }
122 | }
123 | else if (/k-meleon/i.test(ua)) {
124 | result = {
125 | name: 'K-Meleon'
126 | , kMeleon: t
127 | , version: getFirstMatch(/(?:k-meleon)[\s\/](\d+(?:\.\d+)+)/i)
128 | }
129 | }
130 | else if (windowsphone) {
131 | result = {
132 | name: 'Windows Phone'
133 | , osname: 'Windows Phone'
134 | , windowsphone: t
135 | }
136 | if (edgeVersion) {
137 | result.msedge = t
138 | result.version = edgeVersion
139 | }
140 | else {
141 | result.msie = t
142 | result.version = getFirstMatch(/iemobile\/(\d+(\.\d+)?)/i)
143 | }
144 | }
145 | else if (/msie|trident/i.test(ua)) {
146 | result = {
147 | name: 'Internet Explorer'
148 | , msie: t
149 | , version: getFirstMatch(/(?:msie |rv:)(\d+(\.\d+)?)/i)
150 | }
151 | } else if (chromeos) {
152 | result = {
153 | name: 'Chrome'
154 | , osname: 'Chrome OS'
155 | , chromeos: t
156 | , chromeBook: t
157 | , chrome: t
158 | , version: getFirstMatch(/(?:chrome|crios|crmo)\/(\d+(\.\d+)?)/i)
159 | }
160 | } else if (/edg([ea]|ios)/i.test(ua)) {
161 | result = {
162 | name: 'Microsoft Edge'
163 | , msedge: t
164 | , version: edgeVersion
165 | }
166 | }
167 | else if (/vivaldi/i.test(ua)) {
168 | result = {
169 | name: 'Vivaldi'
170 | , vivaldi: t
171 | , version: getFirstMatch(/vivaldi\/(\d+(\.\d+)?)/i) || versionIdentifier
172 | }
173 | }
174 | else if (sailfish) {
175 | result = {
176 | name: 'Sailfish'
177 | , osname: 'Sailfish OS'
178 | , sailfish: t
179 | , version: getFirstMatch(/sailfish\s?browser\/(\d+(\.\d+)?)/i)
180 | }
181 | }
182 | else if (/seamonkey\//i.test(ua)) {
183 | result = {
184 | name: 'SeaMonkey'
185 | , seamonkey: t
186 | , version: getFirstMatch(/seamonkey\/(\d+(\.\d+)?)/i)
187 | }
188 | }
189 | else if (/firefox|iceweasel|fxios/i.test(ua)) {
190 | result = {
191 | name: 'Firefox'
192 | , firefox: t
193 | , version: getFirstMatch(/(?:firefox|iceweasel|fxios)[ \/](\d+(\.\d+)?)/i)
194 | }
195 | if (/\((mobile|tablet);[^\)]*rv:[\d\.]+\)/i.test(ua)) {
196 | result.firefoxos = t
197 | result.osname = 'Firefox OS'
198 | }
199 | }
200 | else if (silk) {
201 | result = {
202 | name: 'Amazon Silk'
203 | , silk: t
204 | , version : getFirstMatch(/silk\/(\d+(\.\d+)?)/i)
205 | }
206 | }
207 | else if (/phantom/i.test(ua)) {
208 | result = {
209 | name: 'PhantomJS'
210 | , phantom: t
211 | , version: getFirstMatch(/phantomjs\/(\d+(\.\d+)?)/i)
212 | }
213 | }
214 | else if (/slimerjs/i.test(ua)) {
215 | result = {
216 | name: 'SlimerJS'
217 | , slimer: t
218 | , version: getFirstMatch(/slimerjs\/(\d+(\.\d+)?)/i)
219 | }
220 | }
221 | else if (/blackberry|\bbb\d+/i.test(ua) || /rim\stablet/i.test(ua)) {
222 | result = {
223 | name: 'BlackBerry'
224 | , osname: 'BlackBerry OS'
225 | , blackberry: t
226 | , version: versionIdentifier || getFirstMatch(/blackberry[\d]+\/(\d+(\.\d+)?)/i)
227 | }
228 | }
229 | else if (webos) {
230 | result = {
231 | name: 'WebOS'
232 | , osname: 'WebOS'
233 | , webos: t
234 | , version: versionIdentifier || getFirstMatch(/w(?:eb)?osbrowser\/(\d+(\.\d+)?)/i)
235 | };
236 | /touchpad\//i.test(ua) && (result.touchpad = t)
237 | }
238 | else if (/bada/i.test(ua)) {
239 | result = {
240 | name: 'Bada'
241 | , osname: 'Bada'
242 | , bada: t
243 | , version: getFirstMatch(/dolfin\/(\d+(\.\d+)?)/i)
244 | };
245 | }
246 | else if (tizen) {
247 | result = {
248 | name: 'Tizen'
249 | , osname: 'Tizen'
250 | , tizen: t
251 | , version: getFirstMatch(/(?:tizen\s?)?browser\/(\d+(\.\d+)?)/i) || versionIdentifier
252 | };
253 | }
254 | else if (/qupzilla/i.test(ua)) {
255 | result = {
256 | name: 'QupZilla'
257 | , qupzilla: t
258 | , version: getFirstMatch(/(?:qupzilla)[\s\/](\d+(?:\.\d+)+)/i) || versionIdentifier
259 | }
260 | }
261 | else if (/chromium/i.test(ua)) {
262 | result = {
263 | name: 'Chromium'
264 | , chromium: t
265 | , version: getFirstMatch(/(?:chromium)[\s\/](\d+(?:\.\d+)?)/i) || versionIdentifier
266 | }
267 | }
268 | else if (/chrome|crios|crmo/i.test(ua)) {
269 | result = {
270 | name: 'Chrome'
271 | , chrome: t
272 | , version: getFirstMatch(/(?:chrome|crios|crmo)\/(\d+(\.\d+)?)/i)
273 | }
274 | }
275 | else if (android) {
276 | result = {
277 | name: 'Android'
278 | , version: versionIdentifier
279 | }
280 | }
281 | else if (/safari|applewebkit/i.test(ua)) {
282 | result = {
283 | name: 'Safari'
284 | , safari: t
285 | }
286 | if (versionIdentifier) {
287 | result.version = versionIdentifier
288 | }
289 | }
290 | else if (iosdevice) {
291 | result = {
292 | name : iosdevice == 'iphone' ? 'iPhone' : iosdevice == 'ipad' ? 'iPad' : 'iPod'
293 | }
294 | // WTF: version is not part of user agent in web apps
295 | if (versionIdentifier) {
296 | result.version = versionIdentifier
297 | }
298 | }
299 | else if(/googlebot/i.test(ua)) {
300 | result = {
301 | name: 'Googlebot'
302 | , googlebot: t
303 | , version: getFirstMatch(/googlebot\/(\d+(\.\d+))/i) || versionIdentifier
304 | }
305 | }
306 | else {
307 | result = {
308 | name: getFirstMatch(/^(.*)\/(.*) /),
309 | version: getSecondMatch(/^(.*)\/(.*) /)
310 | };
311 | }
312 |
313 | // set webkit or gecko flag for browsers based on these engines
314 | if (!result.msedge && /(apple)?webkit/i.test(ua)) {
315 | if (/(apple)?webkit\/537\.36/i.test(ua)) {
316 | result.name = result.name || "Blink"
317 | result.blink = t
318 | } else {
319 | result.name = result.name || "Webkit"
320 | result.webkit = t
321 | }
322 | if (!result.version && versionIdentifier) {
323 | result.version = versionIdentifier
324 | }
325 | } else if (!result.opera && /gecko\//i.test(ua)) {
326 | result.name = result.name || "Gecko"
327 | result.gecko = t
328 | result.version = result.version || getFirstMatch(/gecko\/(\d+(\.\d+)?)/i)
329 | }
330 |
331 | // set OS flags for platforms that have multiple browsers
332 | if (!result.windowsphone && (android || result.silk)) {
333 | result.android = t
334 | result.osname = 'Android'
335 | } else if (!result.windowsphone && iosdevice) {
336 | result[iosdevice] = t
337 | result.ios = t
338 | result.osname = 'iOS'
339 | } else if (mac) {
340 | result.mac = t
341 | result.osname = 'macOS'
342 | } else if (xbox) {
343 | result.xbox = t
344 | result.osname = 'Xbox'
345 | } else if (windows) {
346 | result.windows = t
347 | result.osname = 'Windows'
348 | } else if (linux) {
349 | result.linux = t
350 | result.osname = 'Linux'
351 | }
352 |
353 | function getWindowsVersion (s) {
354 | switch (s) {
355 | case 'NT': return 'NT'
356 | case 'XP': return 'XP'
357 | case 'NT 5.0': return '2000'
358 | case 'NT 5.1': return 'XP'
359 | case 'NT 5.2': return '2003'
360 | case 'NT 6.0': return 'Vista'
361 | case 'NT 6.1': return '7'
362 | case 'NT 6.2': return '8'
363 | case 'NT 6.3': return '8.1'
364 | case 'NT 10.0': return '10'
365 | default: return undefined
366 | }
367 | }
368 |
369 | // OS version extraction
370 | var osVersion = '';
371 | if (result.windows) {
372 | osVersion = getWindowsVersion(getFirstMatch(/Windows ((NT|XP)( \d\d?.\d)?)/i))
373 | } else if (result.windowsphone) {
374 | osVersion = getFirstMatch(/windows phone (?:os)?\s?(\d+(\.\d+)*)/i);
375 | } else if (result.mac) {
376 | osVersion = getFirstMatch(/Mac OS X (\d+([_\.\s]\d+)*)/i);
377 | osVersion = osVersion.replace(/[_\s]/g, '.');
378 | } else if (iosdevice) {
379 | osVersion = getFirstMatch(/os (\d+([_\s]\d+)*) like mac os x/i);
380 | osVersion = osVersion.replace(/[_\s]/g, '.');
381 | } else if (android) {
382 | osVersion = getFirstMatch(/android[ \/-](\d+(\.\d+)*)/i);
383 | } else if (result.webos) {
384 | osVersion = getFirstMatch(/(?:web|hpw)os\/(\d+(\.\d+)*)/i);
385 | } else if (result.blackberry) {
386 | osVersion = getFirstMatch(/rim\stablet\sos\s(\d+(\.\d+)*)/i);
387 | } else if (result.bada) {
388 | osVersion = getFirstMatch(/bada\/(\d+(\.\d+)*)/i);
389 | } else if (result.tizen) {
390 | osVersion = getFirstMatch(/tizen[\/\s](\d+(\.\d+)*)/i);
391 | }
392 | if (osVersion) {
393 | result.osversion = osVersion;
394 | }
395 | else {
396 | osVersion = '';
397 | }
398 |
399 | // device type extraction
400 | var osMajorVersion = !result.windows && osVersion.split('.')[0];
401 | if (
402 | tablet
403 | || nexusTablet
404 | || iosdevice == 'ipad'
405 | || (android && (osMajorVersion == 3 || (osMajorVersion >= 4 && !mobile)))
406 | || result.silk
407 | ) {
408 | result.tablet = t
409 | } else if (
410 | mobile
411 | || iosdevice == 'iphone'
412 | || iosdevice == 'ipod'
413 | || android
414 | || nexusMobile
415 | || result.blackberry
416 | || result.webos
417 | || result.bada
418 | ) {
419 | result.mobile = t
420 | }
421 |
422 | // Graded Browser Support
423 | // http://developer.yahoo.com/yui/articles/gbs
424 | if (result.msedge ||
425 | (result.msie && result.version >= 10) ||
426 | (result.yandexbrowser && result.version >= 15) ||
427 | (result.vivaldi && result.version >= 1.0) ||
428 | (result.chrome && result.version >= 20) ||
429 | (result.samsungBrowser && result.version >= 4) ||
430 | (result.firefox && result.version >= 20.0) ||
431 | (result.safari && result.version >= 6) ||
432 | (result.opera && result.version >= 10.0) ||
433 | (result.ios && result.osversion && result.osversion.split(".")[0] >= 6) ||
434 | (result.blackberry && result.version >= 10.1)
435 | || (result.chromium && result.version >= 20)
436 | ) {
437 | result.a = t;
438 | }
439 | else if ((result.msie && result.version < 10) ||
440 | (result.chrome && result.version < 20) ||
441 | (result.firefox && result.version < 20.0) ||
442 | (result.safari && result.version < 6) ||
443 | (result.opera && result.version < 10.0) ||
444 | (result.ios && result.osversion && result.osversion.split(".")[0] < 6)
445 | || (result.chromium && result.version < 20)
446 | ) {
447 | result.c = t
448 | } else result.x = t
449 |
450 | return result
451 | }
452 |
453 | var bowser = detect(typeof navigator !== 'undefined' ? navigator.userAgent || '' : '')
454 |
455 | bowser.test = function (browserList) {
456 | for (var i = 0; i < browserList.length; ++i) {
457 | var browserItem = browserList[i];
458 | if (typeof browserItem=== 'string') {
459 | if (browserItem in bowser) {
460 | return true;
461 | }
462 | }
463 | }
464 | return false;
465 | }
466 |
467 | /**
468 | * Get version precisions count
469 | *
470 | * @example
471 | * getVersionPrecision("1.10.3") // 3
472 | *
473 | * @param {string} version
474 | * @return {number}
475 | */
476 | function getVersionPrecision(version) {
477 | return version.split(".").length;
478 | }
479 |
480 | /**
481 | * Array::map polyfill
482 | *
483 | * @param {Array} arr
484 | * @param {Function} iterator
485 | * @return {Array}
486 | */
487 | function map(arr, iterator) {
488 | var result = [], i;
489 | if (Array.prototype.map) {
490 | return Array.prototype.map.call(arr, iterator);
491 | }
492 | for (i = 0; i < arr.length; i++) {
493 | result.push(iterator(arr[i]));
494 | }
495 | return result;
496 | }
497 |
498 | /**
499 | * Calculate browser version weight
500 | *
501 | * @example
502 | * compareVersions(['1.10.2.1', '1.8.2.1.90']) // 1
503 | * compareVersions(['1.010.2.1', '1.09.2.1.90']); // 1
504 | * compareVersions(['1.10.2.1', '1.10.2.1']); // 0
505 | * compareVersions(['1.10.2.1', '1.0800.2']); // -1
506 | *
507 | * @param {Array} versions versions to compare
508 | * @return {Number} comparison result
509 | */
510 | function compareVersions(versions) {
511 | // 1) get common precision for both versions, for example for "10.0" and "9" it should be 2
512 | var precision = Math.max(getVersionPrecision(versions[0]), getVersionPrecision(versions[1]));
513 | var chunks = map(versions, function (version) {
514 | var delta = precision - getVersionPrecision(version);
515 |
516 | // 2) "9" -> "9.0" (for precision = 2)
517 | version = version + new Array(delta + 1).join(".0");
518 |
519 | // 3) "9.0" -> ["000000000"", "000000009"]
520 | return map(version.split("."), function (chunk) {
521 | return new Array(20 - chunk.length).join("0") + chunk;
522 | }).reverse();
523 | });
524 |
525 | // iterate in reverse order by reversed chunks array
526 | while (--precision >= 0) {
527 | // 4) compare: "000000009" > "000000010" = false (but "9" > "10" = true)
528 | if (chunks[0][precision] > chunks[1][precision]) {
529 | return 1;
530 | }
531 | else if (chunks[0][precision] === chunks[1][precision]) {
532 | if (precision === 0) {
533 | // all version chunks are same
534 | return 0;
535 | }
536 | }
537 | else {
538 | return -1;
539 | }
540 | }
541 | }
542 |
543 | /**
544 | * Check if browser is unsupported
545 | *
546 | * @example
547 | * bowser.isUnsupportedBrowser({
548 | * msie: "10",
549 | * firefox: "23",
550 | * chrome: "29",
551 | * safari: "5.1",
552 | * opera: "16",
553 | * phantom: "534"
554 | * });
555 | *
556 | * @param {Object} minVersions map of minimal version to browser
557 | * @param {Boolean} [strictMode = false] flag to return false if browser wasn't found in map
558 | * @param {String} [ua] user agent string
559 | * @return {Boolean}
560 | */
561 | function isUnsupportedBrowser(minVersions, strictMode, ua) {
562 | var _bowser = bowser;
563 |
564 | // make strictMode param optional with ua param usage
565 | if (typeof strictMode === 'string') {
566 | ua = strictMode;
567 | strictMode = void(0);
568 | }
569 |
570 | if (strictMode === void(0)) {
571 | strictMode = false;
572 | }
573 | if (ua) {
574 | _bowser = detect(ua);
575 | }
576 |
577 | var version = "" + _bowser.version;
578 | for (var browser in minVersions) {
579 | if (minVersions.hasOwnProperty(browser)) {
580 | if (_bowser[browser]) {
581 | if (typeof minVersions[browser] !== 'string') {
582 | throw new Error('Browser version in the minVersion map should be a string: ' + browser + ': ' + String(minVersions));
583 | }
584 |
585 | // browser version and min supported version.
586 | return compareVersions([version, minVersions[browser]]) < 0;
587 | }
588 | }
589 | }
590 |
591 | return strictMode; // not found
592 | }
593 |
594 | /**
595 | * Check if browser is supported
596 | *
597 | * @param {Object} minVersions map of minimal version to browser
598 | * @param {Boolean} [strictMode = false] flag to return false if browser wasn't found in map
599 | * @param {String} [ua] user agent string
600 | * @return {Boolean}
601 | */
602 | function check(minVersions, strictMode, ua) {
603 | return !isUnsupportedBrowser(minVersions, strictMode, ua);
604 | }
605 |
606 | bowser.isUnsupportedBrowser = isUnsupportedBrowser;
607 | bowser.compareVersions = compareVersions;
608 | bowser.check = check;
609 |
610 | /*
611 | * Set our detect method to the main bowser object so we can
612 | * reuse it to test other user agents.
613 | * This is needed to implement future tests.
614 | */
615 | bowser._detect = detect;
616 |
617 | /*
618 | * Set our detect public method to the main bowser object
619 | * This is needed to implement bowser in server side
620 | */
621 | bowser.detect = detect;
622 | return bowser
623 | });
624 |
--------------------------------------------------------------------------------
/src/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
20 |
21 |
22 |
25 |
28 |
29 |
32 |
33 |
36 |
37 |
40 |
41 |
44 |
45 |
48 |
49 |
52 |
53 |
54 |
55 |
56 |
57 |
58 |
65 |
66 |
{tr:main_title}
67 |
68 |
69 |
101 |
102 |
105 |
106 |
107 |
108 |
109 |
110 |
111 |
112 |
113 |
114 |
115 |
116 |
117 |
118 | {tr:main_title_expand}
119 |
120 |
121 |
122 |
123 | {tr:home_disclaimer}
124 |
125 |
126 |
127 | - {tr:home_browser}
128 | - {tr:home_devices}
129 | - {tr:home_camera}
130 | - {tr:home_micro}
131 | - {tr:home_network}
132 | - {tr:home_room}
133 |
134 |
135 |
136 |
147 |
148 |
149 |
150 |
151 |
152 |
153 | {tr:browser_title}
154 |
155 |
156 |
157 | OK
158 | KO
159 |
160 |
161 |
162 |
163 |
164 |
171 |
172 |
{tr:results_shown_there}
173 |
174 |
{tr:browser_test_success}
175 |
176 |
{tr:browser_test_fail}
177 |
178 |
179 |
180 |
181 |
182 |
183 | {tr:devices_title}
184 |
185 |
186 |
187 | OK
188 | KO
189 |
190 |
191 |
192 |
193 |
200 |
201 |
{tr:results_shown_there}
202 |
203 |
{tr:devices_test_success}
204 |
205 |
206 |
{tr:devices_audio_input_label}
207 |
208 |
209 |
210 |
{tr:devices_audio_output_label}
211 |
212 |
213 |
214 |
{tr:devices_video_label}
215 |
216 |
217 |
218 |
219 |
{tr:devices_test_fail}
220 |
221 |
222 |
223 |
224 |
225 |
226 | {tr:camera_title}
227 |
228 |
229 |
230 | OK
231 | KO
232 |
233 |
234 |
235 |
236 |
243 |
244 |
{tr:results_shown_there}
245 |
246 |
{tr:camera_test_success}
247 |
{tr:camera_test_success_default}
248 |
249 |
250 |
251 |
252 |
{tr:camera_test_fail}
253 |
254 |
255 |
256 |
257 |
258 |
259 | {tr:micro_title}
260 |
261 |
262 |
263 | OK
264 | KO
265 |
266 |
267 |
268 |
269 |
276 |
277 |
{tr:results_shown_there}
278 |
279 |
{tr:micro_test_success}
280 |
{tr:micro_test_success_default}
281 |
282 |
283 |
284 |
285 |
{tr:camera_test_fail}
286 |
287 |
288 |
289 |
290 |
291 |
292 |
293 |
294 | {tr:network_title}
295 |
296 |
297 |
298 | OK
299 | KO
300 |
301 |
302 |
303 |
304 |
311 |
312 |
313 |
314 | Local video
315 |
316 |
317 | Video dimensions:
318 | N/C
319 |
320 |
321 |
322 | Remote video
323 |
324 |
325 | Video dimensions:
326 | N/C
327 |
328 |
329 | Connected to:
330 | N/C
331 |
332 |
333 |
334 |
335 |
336 |
337 |
338 |
339 | {tr:websocket}
340 |
341 |
342 |
343 |
344 |
345 |
346 |
347 |
348 | {tr:udp}
349 |
350 |
351 |
352 |
353 |
354 |
355 |
356 |
357 |
358 | {tr:tcp}
359 |
360 |
361 |
362 |
363 |
364 |
365 |
366 |
367 |
368 | {tr:bitrate}
369 | 0 kbit/s
370 |
371 |
372 | {tr:average_bitrate}
373 | 0 kbit/s
374 |
375 |
376 |
377 |
378 |
379 |
380 |
381 | {tr:packetlost}
382 | 0
383 |
384 |
385 |
386 |
387 |
388 |
389 |
390 | {tr:framerate}
391 | 0
392 |
393 |
394 |
395 |
396 |
397 |
398 |
399 | {tr:droppedframes}
400 | 0
401 |
402 |
403 |
404 |
405 |
406 |
407 |
408 | {tr:jitter}
409 |
410 |
411 |
412 | 0
413 |
414 |
415 |
416 |
417 |
418 |
{tr:results_shown_there}
419 |
420 |
{tr:network_test_success}
421 |
422 |
423 |
{tr:error}
424 |
{tr:network_test_fail}
425 |
426 |
427 |
428 |
429 |
430 |
431 |
432 |
433 |
434 |
435 |
436 | {tr:room_title}
437 |
438 |
439 |
440 | OK
441 | KO
442 |
443 |
444 |
445 |
446 |
453 |
463 |
464 |
465 |
{tr:results_shown_there}
466 |
467 |
{tr:room_test_success}
468 |
469 |
470 |
{tr:error}
471 |
{tr:room_test_fail}
472 |
473 |
474 |
475 |
476 |
477 |
478 |
479 |
480 |
481 |
{tr:results}
482 |
483 |
484 |
485 |
{tr:all_results_shown_there}
486 |
487 |
{tr:global_test_success}
488 |
{tr:global_test_message}
489 |
490 |
491 |
{tr:global_test_fail}
492 |
493 |
{tr:following_test_failed}
494 |
495 | - {tr:home_devices}
496 | - {tr:home_camera}
497 | - {tr:home_micro}
498 | - {tr:home_network}
499 | - {tr:home_room}
500 |
501 |
{tr:global_more_information}
502 |
503 |
504 |
512 |
513 |
514 |
515 |
516 |
517 |
518 |
519 |
--------------------------------------------------------------------------------
/src/assets/js/test_browser/test_cases/test_network.js:
--------------------------------------------------------------------------------
1 | /**
2 | * TestCase: test_network
3 | */
4 |
5 | if (!window.hasOwnProperty('JitsiTestBrowser'))
6 | window.JitsiTestBrowser = {};
7 |
8 | if (!window.JitsiTestBrowser.hasOwnProperty('test_network'))
9 | window.JitsiTestBrowser.test_network = {};
10 |
11 |
12 | /**
13 | * Test network case
14 | */
15 | window.JitsiTestBrowser.test_network = {
16 |
17 | /**
18 | * TURN credentials
19 | */
20 | turn_credentials: [],
21 |
22 | /**
23 | * TURN servers
24 | */
25 | turn_servers: [],
26 |
27 |
28 | /**
29 | * Global local media stream object
30 | */
31 | localStream: undefined,
32 |
33 | /**
34 | * Local peer connection
35 | */
36 | localPeerConnection: undefined,
37 |
38 | /**
39 | * Remote peer connection
40 | */
41 | remotePeerConnection: undefined,
42 |
43 |
44 | /**
45 | * Local video
46 | */
47 | localVideo: undefined,
48 |
49 | /**
50 | * Remote video
51 | */
52 | remoteVideo: undefined,
53 |
54 | /**
55 | * Tests statuses
56 | */
57 | statuses: {},
58 |
59 | /**
60 | * Interval ID to stop get stats interval
61 | */
62 | intervalID: undefined,
63 |
64 | exceptions: null,
65 |
66 |
67 | /**
68 | * Variables used to get RTC stats
69 | */
70 | testing_protocol: undefined,
71 | bytesPrev: undefined,
72 | timestampPrev: undefined,
73 | stats: {
74 | tcp:{
75 | bitrate: [],
76 | packetsLost: 0,
77 | framesPerSecond: [],
78 | droppedFrames: 0,
79 | jitter: []
80 | },
81 | udp:{
82 | bitrate: [],
83 | packetsLost: 0,
84 | framesPerSecond: [],
85 | droppedFrames: 0,
86 | jitter: []
87 | },
88 | video:{
89 | local: [],
90 | remote: []
91 | }
92 | },
93 |
94 | networkEvent: new Event('network_stat'),
95 |
96 | /**
97 | * Run test
98 | *
99 | * @return {Promise}
100 | */
101 | run: function () {
102 | return new Promise(res => {
103 | console.log("> Running test_network");
104 | window.JitsiTestEvents.dispatch('run', {"status": window.TestStatuses.PROCESSING, "context": "test_network"});
105 |
106 | let context = window.JitsiTestBrowser.test_network;
107 |
108 | context.localVideo = document.querySelector('video#local_video');
109 | context.remoteVideo = document.querySelector('video#remote_video');
110 | context.exceptions = [];
111 |
112 | // Init TURN credentials
113 | context.initTURNCredentials(function(result){
114 | if (result.status === "fail"){
115 | window.JitsiTestEvents.dispatch('network_stat', {"context": "wss", "data": result});
116 | }
117 |
118 | // Run websocket test
119 | context.testWebSocket().then(function (result) {
120 |
121 | // Run UDP test
122 | context.testUDP().then(function () {
123 | // Stop getting statistics
124 | if (context.intervalID) clearInterval(context.intervalID)
125 |
126 | // Reset stats
127 | context.bytesPrev = 0;
128 | context.timestampPrev = 0;
129 |
130 | // Close connection
131 | context.hangup();
132 |
133 | // Next: test TCP protocol
134 | context.testTCP().then(function () {
135 | // Stop getting statistics
136 | if (context.intervalID) clearInterval(context.intervalID)
137 |
138 | // Reset stats
139 | context.bytesPrev = 0;
140 | context.timestampPrev = 0;
141 |
142 | // Close connection
143 | context.hangup();
144 |
145 | // Show final test results
146 | let allOK = Object.keys(context.statuses).length > 0;
147 |
148 | Object.keys(context.statuses).forEach(status => {
149 | if (context.statuses[status] === false){
150 | allOK = false;
151 | context.statuses[status] = "fail"
152 | }else{
153 | context.statuses[status] = "success";
154 | }
155 | });
156 |
157 | // Push statistics
158 | context.pushStatistics();
159 |
160 | window.JitsiTestBrowser.runner.resolve(res, {"result": allOK ? "success" : "fail"}, "test_network");
161 | })
162 | });
163 | });
164 | }, function(reason){
165 | window.JitsiTestBrowser.runner.resolve(res, {"result": "fail", "details": reason}, "test_network");
166 | });
167 |
168 | });
169 | },
170 |
171 | /**
172 | * Get network statistics
173 | */
174 | getNetworkStatistics: function () {
175 | let context = window.JitsiTestBrowser.test_network;
176 |
177 | context.intervalID = setInterval(() => {
178 | if (context.localPeerConnection && context.remotePeerConnection) {
179 | context.remotePeerConnection
180 | .getStats(null)
181 | .then(context.showRemoteStats, err => {
182 | console.log(err);
183 | throw err;
184 | });
185 | context.localPeerConnection
186 | .getStats(null)
187 | .then(context.showLocalStats, err => {
188 | console.log(err);
189 | throw err;
190 | });
191 | } else {
192 | console.log('Not connected yet');
193 | }
194 | // Collect some stats from the video tags.
195 | if (context.localVideo.videoWidth) {
196 | const width = context.localVideo.videoWidth;
197 | const height = context.localVideo.videoHeight;
198 |
199 | context.stats['video']['local'] = {video_dimension: {"width": width, "height": height}};
200 |
201 | window.JitsiTestEvents.dispatch('network_stat', {"context":"video_player", "data": {"local": {"video_dimension": {"width": width, "height": height}}}});
202 | }
203 | if (context.remoteVideo.videoWidth) {
204 | const rHeight = context.remoteVideo.videoHeight;
205 | const rWidth = context.remoteVideo.videoWidth;
206 |
207 | context.stats['video']['remote'] = {video_dimension: {"width": rWidth, "height": rHeight}};
208 |
209 | window.JitsiTestEvents.dispatch('network_stat', {"context":"video_player", "data": {"remote": {"video_dimension": {"width": rWidth, "height": rHeight}}}});
210 | }
211 | }, 1000);
212 | },
213 |
214 | /**
215 | * Init TURN credentials
216 | */
217 | initTURNCredentials: function (resolve, reject) {
218 | let context = window.JitsiTestBrowser.test_network;
219 |
220 | let turnServer = document.getElementById('main').getAttribute('data-turn-endpoint');
221 |
222 | let settings = {
223 | method: 'get',
224 | };
225 |
226 | fetch(turnServer, settings)
227 | .then(response => {
228 | response.json()
229 | .then(function (data) {
230 | context.turn_servers = {
231 | "username": data.username,
232 | "credential": data.credential,
233 | "tcp_urls": data.tcpTestUrl,
234 | "udp_urls": data.udpTestUrl,
235 | };
236 | resolve({"status": "success"});
237 | })
238 | }
239 | )
240 | .catch(reason => {
241 | resolve({"status": "fail", "details": reason.toString()});
242 | });
243 | },
244 |
245 |
246 | // Test functions
247 |
248 | /**
249 | * Test web socket
250 | *
251 | * @return {Promise}
252 | */
253 | testWebSocket: function () {
254 | return new Promise(resolve => {
255 | console.log(" >>> Test WebSocket connection");
256 |
257 | window.JitsiTestEvents.dispatch('run', {"status": window.TestStatuses.PROCESSING, "component": "wss"});
258 |
259 | let context = window.JitsiTestBrowser.test_network;
260 | context.testing_protocol = 'wss';
261 |
262 | let wssUrl = document.getElementById('main').getAttribute('data-websocket-url');
263 |
264 | let wbs = new WebSocket(wssUrl);
265 | let expected = 'this is a connection test';
266 |
267 | wbs.onopen = function () {
268 | console.log('WSS connection established');
269 | wbs.send(expected);
270 | };
271 |
272 | wbs.onmessage = function (messageEvent) {
273 | console.log(`Got data from WSS: ${messageEvent.data}`);
274 | let result;
275 |
276 | if (messageEvent.data === `echo ${expected}`) {
277 | wbs.close();
278 | context.statuses['wss'] = true;
279 | result = {"status": "success"};
280 |
281 | } else {
282 | context.statuses['wss'] = false;
283 | wbs.close();
284 | result = {"status": "fail", "details": {"protocol": "wss", "message": messageEvent.data}};
285 | context.testFail(err);
286 | resolve(err);
287 | }
288 |
289 | window.JitsiTestEvents.dispatch('network_stat', {"context":"wss", "data": result});
290 |
291 | resolve(result);
292 | };
293 |
294 | wbs.onerror = function () {
295 | context.statuses['wss'] = false;
296 | wbs.close();
297 | let err = {"status": "fail", "details": {"protocol": "wss", "details": 'cannot_connect_wss'}};
298 | context.testFail('wss', err);
299 | resolve(err);
300 | };
301 | });
302 | },
303 |
304 | /**
305 | * Test TCP
306 | *
307 | * @return {Promise}
308 | */
309 | testTCP: function () {
310 | return new Promise(resolve => {
311 | console.log(" >>> Test TCP media network");
312 |
313 | window.JitsiTestEvents.dispatch('run', {"status": window.TestStatuses.PROCESSING, "component":"tcp"});
314 |
315 | let context = window.JitsiTestBrowser.test_network;
316 | context.testing_protocol = 'tcp';
317 |
318 | // Start getting network statistics
319 | context.getNetworkStatistics();
320 |
321 | context.initiateMediaConnexion("tcp").then(function (result) {
322 | if (result instanceof Object && result.status === 'success') {
323 | let utils = new WebRTCUtils();
324 | utils.wait(5000).then(function () {
325 |
326 | window.JitsiTestEvents.dispatch('network_stat', {"context": "tcp", "data": result});
327 |
328 | resolve();
329 | });
330 | } else {
331 | context.testFail('tcp', result);
332 |
333 | window.JitsiTestEvents.dispatch('network_stat', {"context": "tcp", "data": result});
334 |
335 | resolve();
336 | }
337 | });
338 | });
339 | },
340 |
341 |
342 |
343 | /**
344 | * Test UDP
345 | *
346 | * @return {Promise}
347 | */
348 | testUDP: function () {
349 | return new Promise(resolve => {
350 | console.log(" >>> Test UDP media network");
351 |
352 | window.JitsiTestEvents.dispatch('run', {"status": window.TestStatuses.PROCESSING, "component": "udp"});
353 |
354 | let context = window.JitsiTestBrowser.test_network;
355 | context.testing_protocol = 'udp';
356 |
357 | // Start getting network statistics
358 | context.getNetworkStatistics();
359 |
360 | context.initiateMediaConnexion("udp")
361 | .then(function (result) {
362 | if (result instanceof Object && result.status === 'success') {
363 | let utils = new WebRTCUtils();
364 | utils.wait(5000).then(function () {
365 |
366 | window.JitsiTestEvents.dispatch('network_stat', {"context":"udp", "data": result});
367 |
368 | resolve({"status": "success", "details": {"protocol": "udp"}});
369 | });
370 | } else {
371 | context.testFail('udp', result);
372 |
373 | window.JitsiTestEvents.dispatch('network_stat', {"context":"udp", "data": result});
374 |
375 | resolve();
376 | }
377 | }
378 | );
379 | });
380 | },
381 |
382 |
383 | // WebRTC functions
384 |
385 | /**
386 | * Initiate a media connexion for protocol
387 | *
388 | * @param protocol
389 | */
390 | initiateMediaConnexion: function (protocol) {
391 | return new Promise(resolve => {
392 | if (protocol !== 'tcp' && protocol !== 'udp') {
393 | resolve({"status": "fail", "details": {"message": "unknown_protocol"}})
394 |
395 | } else {
396 | let context = window.JitsiTestBrowser.test_network;
397 |
398 | if (context.localStream) {
399 | context.localStream.getTracks().forEach(track => track.stop());
400 | const videoTracks = context.localStream.getVideoTracks();
401 | for (let i = 0; i !== videoTracks.length; ++i) {
402 | videoTracks[i].stop();
403 | }
404 | }
405 |
406 | // Init media constraints
407 | // Should be in config?
408 | let mediaStreamConstraints = {
409 | video: {
410 | width: {min: 300, max: 400, ideal: 320},
411 | height: {min: 150, max: 300, ideal: 240},
412 | frameRate: {min: 10, max: 60,}
413 | },
414 | audio: true
415 | };
416 | let rtcConfig = {
417 | iceTransportPolicy: 'relay'
418 | };
419 | let servers = {
420 | "username": context.turn_servers.username,
421 | "credential": context.turn_servers.credential,
422 | "urls": protocol === 'tcp' ? context.turn_servers.tcp_urls : context.turn_servers.udp_urls
423 | };
424 | rtcConfig.iceServers = [servers];
425 |
426 | // Get user media
427 | navigator.mediaDevices.getUserMedia(mediaStreamConstraints)
428 | .then(function (mediaStream) {
429 | /* use the stream */
430 | console.log('GetUserMedia succeeded');
431 | context.localStream = mediaStream;
432 | context.localVideo.srcObject = mediaStream;
433 | context.createPeerConnection(rtcConfig)
434 | context.statuses[protocol] = true;
435 | resolve({"status": "success", "details": {"message": "GetUserMedia succeeded"}})
436 | })
437 | .catch(function (err) {
438 | resolve({"status": "fail", "details": err.toString()});
439 | })
440 | }
441 | });
442 | },
443 |
444 |
445 | /**
446 | * Create peer connection
447 | *
448 | * @param rtcConfig
449 | */
450 | createPeerConnection: function (rtcConfig = null) {
451 | console.log('Call Start');
452 | let context = window.JitsiTestBrowser.test_network;
453 | try {
454 | context.localPeerConnection = new RTCPeerConnection(rtcConfig);
455 | context.remotePeerConnection = new RTCPeerConnection(rtcConfig);
456 | context.localStream.getTracks().forEach(track => context.localPeerConnection.addTrack(track, context.localStream));
457 | console.log('localPeerConnection creating offer');
458 | context.localPeerConnection.onnegotiationneeded = () => console.log('Negotiation needed - localPeerConnection');
459 | context.remotePeerConnection.onnegotiationneeded = () => console.log('Negotiation needed - remotePeerConnection');
460 |
461 | context.localPeerConnection.onicecandidate = e => {
462 | console.log('Candidate localPeerConnection');
463 | if (e.candidate && e.candidate.candidate){
464 | let clientIP = /([0-9]{1,3}(\.[0-9]{1,3}){3}|[a-f0-9]{1,4}(:[a-f0-9]{1,4}){7})/.exec(e.candidate.candidate)[1];
465 | console.log('Client IP: ', clientIP);
466 | context.stats[context.testing_protocol]['client_ip'] = clientIP;
467 | }
468 |
469 | context.remotePeerConnection
470 | .addIceCandidate(e.candidate)
471 | .then(context.onAddIceCandidateSuccess, context.onAddIceCandidateError);
472 | };
473 | context.localPeerConnection.oniceconnectionstatechange = function(){
474 | context.oniceconnectionstatechange('localPeerConnection', context.localPeerConnection)
475 | };
476 | context.remotePeerConnection.oniceconnectionstatechange = function(){
477 | context.oniceconnectionstatechange('remotePeerConnection', context.remotePeerConnection)
478 | };
479 | context.localPeerConnection.onsignalingstatechange = function(){
480 | context.onsignalingstatechange('localPeerConnection', context.localPeerConnection)
481 | };
482 | context.remotePeerConnection.onsignalingstatechange = function(){
483 | context.onsignalingstatechange('remotePeerConnection', context.remotePeerConnection)
484 | };
485 | context.remotePeerConnection.onicecandidate = e => {
486 | console.log('Candidate remotePeerConnection');
487 | context.localPeerConnection
488 | .addIceCandidate(e.candidate)
489 | .then(context.onAddIceCandidateSuccess, context.onAddIceCandidateError);
490 | };
491 | context.remotePeerConnection.ontrack = e => {
492 | if (context.remoteVideo.srcObject !== e.streams[0]) {
493 | console.log('remotePeerConnection got stream');
494 | context.remoteVideo.srcObject = e.streams[0];
495 | }
496 | };
497 | context.localPeerConnection.createOffer().then(
498 | offer => {
499 | console.log('localPeerConnection offering');
500 | console.log(`localPeerConnection offer: ${offer.sdp}`);
501 | context.localPeerConnection.setLocalDescription(offer);
502 | context.remotePeerConnection.setRemoteDescription(offer);
503 | context.remotePeerConnection.createAnswer().then(
504 | answer => {
505 | console.log('remotePeerConnection answering');
506 | console.log(`remotePeerConnection answer: ${answer.sdp}`);
507 | context.remotePeerConnection.setLocalDescription(answer);
508 | context.localPeerConnection.setRemoteDescription(answer);
509 | },
510 | err => function () {
511 | console.log(err)
512 | throw err;
513 | }
514 | );
515 | },
516 | err => function () {
517 | console.log(err)
518 | throw err;
519 | }
520 | );
521 | } catch (err) {
522 | console.log(err)
523 | throw err;
524 | }
525 | },
526 |
527 | onAddIceCandidateSuccess: function () {
528 | // TODO: show on UI?
529 | console.log('AddIceCandidate success.');
530 | },
531 |
532 | onAddIceCandidateError: function (error) {
533 | // TODO: show on UI?
534 | console.error(`Failed to add Ice Candidate: ${error.toString()}`);
535 | },
536 |
537 |
538 | showLocalStats: function (results) {
539 | // Nothing useful to show for now
540 | // Keep this function just in case
541 | },
542 |
543 | /**
544 | * Handle network stats got from remove video
545 | *
546 | * @param results
547 | */
548 | showRemoteStats: function (results) {
549 | console.log(results);
550 | let context = window.JitsiTestBrowser.test_network;
551 |
552 | // calculate video bitrate
553 | results.forEach(report => {
554 | const now = report.timestamp;
555 | window.JitsiTestEvents.networkStat.context = context.testing_protocol;
556 |
557 | let bitrate;
558 | if (report.type === 'inbound-rtp' && report.mediaType === 'video') {
559 | const bytes = report.bytesReceived;
560 | if (context.timestampPrev) {
561 | bitrate = 8 * (bytes - context.bytesPrev) / (now - context.timestampPrev);
562 | bitrate = Math.floor(bitrate);
563 | }
564 | context.bytesPrev = bytes;
565 | context.timestampPrev = now;
566 | }
567 | if (bitrate) {
568 | context.stats[context.testing_protocol].bitrate.push(bitrate);
569 |
570 | window.JitsiTestEvents.dispatch('network_stat', {"data": {"bitrate": bitrate}});
571 |
572 | if (context.stats[context.testing_protocol].bitrate.length) {
573 | let average = 0;
574 | context.stats[context.testing_protocol].bitrate.forEach(bt => {
575 | average += bt;
576 | });
577 | average = (average / context.stats[context.testing_protocol].bitrate.length).toFixed(2);
578 |
579 | window.JitsiTestEvents.dispatch('network_stat', {"data": {"average_bitrate": average}});
580 | }
581 | }
582 |
583 | // Dispatch statistics
584 | ['framesPerSecond', 'framesDropped', 'packetsLost', 'jitter'].forEach(item => {
585 | if (report[item] !== undefined) {
586 | let data = {};
587 | data[item] = report[item];
588 |
589 | context.stats[context.testing_protocol][item] = report[item];
590 |
591 | window.JitsiTestEvents.dispatch('network_stat', {data});
592 | }
593 | });
594 | });
595 |
596 | // figure out the peer's ip
597 | let activeCandidatePair = null;
598 | let remoteCandidate = null;
599 |
600 | // Search for the candidate pair, spec-way first.
601 | results.forEach(report => {
602 | if (report.type === 'transport') {
603 | activeCandidatePair = results.get(report.selectedCandidatePairId);
604 | }
605 | });
606 | // Fallback for Firefox.
607 | if (!activeCandidatePair) {
608 | results.forEach(report => {
609 | if (report.type === 'candidate-pair' && report.selected) {
610 | activeCandidatePair = report;
611 | }
612 | });
613 | }
614 | if (activeCandidatePair && activeCandidatePair.remoteCandidateId) {
615 | remoteCandidate = results.get(activeCandidatePair.remoteCandidateId);
616 | }
617 | if (remoteCandidate) {
618 | if (remoteCandidate.address && remoteCandidate.port) {
619 | context.stats[context.testing_protocol]['ip_connected_to'] = `${remoteCandidate.address}:${remoteCandidate.port}`;
620 | window.JitsiTestEvents.dispatch('network_stat', {"data": {"ip_connected_to": `${remoteCandidate.address}:${remoteCandidate.port}`}});
621 |
622 | } else if (remoteCandidate.ip && remoteCandidate.port) {
623 | context.stats[context.testing_protocol]['ip_connected_to'] = `${remoteCandidate.address}:${remoteCandidate.port}`;
624 | window.JitsiTestEvents.dispatch('network_stat', {"data": {"ip_connected_to": `${remoteCandidate.ip}:${remoteCandidate.port}`}});
625 |
626 | } else if (remoteCandidate.ipAddress && remoteCandidate.portNumber) {
627 | context.stats[context.testing_protocol]['ip_connected_to'] = `${remoteCandidate.address}:${remoteCandidate.port}`;
628 | window.JitsiTestEvents.dispatch('network_stat', {"data": {"ip_connected_to": `${remoteCandidate.ipAddress}:${remoteCandidate.portNumber}`}});
629 | }
630 | }
631 | },
632 |
633 | /**
634 | * Close the 2 active peerConnection localPeerConnection and remotePeerConnection and release media capture
635 | */
636 | hangup: function () {
637 | let context = window.JitsiTestBrowser.test_network;
638 | if (context.localPeerConnection) {
639 | console.log("localPeerConnection iceConnectionState :" + context.localPeerConnection.iceConnectionState);
640 | console.log("remotePeerConnection iceConnectionState :" + context.remotePeerConnection.iceConnectionState);
641 |
642 | context.stateEnabled = false;
643 | }
644 | if (context.localPeerConnection) {
645 | context.localPeerConnection.close();
646 | }
647 | if (context.remotePeerConnection) {
648 | context.remotePeerConnection.close();
649 | }
650 | if (context.localStream) {
651 | context.localStream.getTracks().forEach(function (track) {
652 | track.stop();
653 | });
654 | }
655 |
656 | context.localVideo.srcObject = null;
657 | context.remoteVideo.srcObject = null;
658 |
659 | context.localPeerConnection = undefined;
660 | context.remotePeerConnection = undefined;
661 | },
662 |
663 | onsignalingstatechange: function (peerConnectionName, peerConnection) {
664 | console.log(peerConnectionName + ' ICE: ' + peerConnection.iceConnectionState);
665 | },
666 |
667 | oniceconnectionstatechange: function (peerConnectionName, peerConnection) {
668 | console.log(peerConnectionName + ' ICE: ' + peerConnection.iceConnectionState);
669 | },
670 |
671 | onicecandidate: function (peerConnectionName, peerConnection, event) {
672 | if (event.candidate) {
673 | peerConnection.addIceCandidate(event.candidate);
674 | console.log(peerConnectionName + ' ICE Candidate: ' + event.candidate.candidate);
675 | }
676 | },
677 |
678 |
679 | /**
680 | * Push final statistics
681 | */
682 | pushStatistics: function(){
683 | let context = window.JitsiTestBrowser.test_network;
684 |
685 | let stats = {
686 | "wss": {
687 | "status" : context.statuses['wss'],
688 | },
689 | "tcp": {
690 | "status" : context.statuses['tcp'],
691 | "data": context.stats['tcp']
692 |
693 | },
694 | "udp": {
695 | "status" : context.statuses['udp'],
696 | "data": context.stats['udp']
697 | },
698 | "video":{
699 | "local": context.stats['video']['local'],
700 | "remote": context.stats['video']['local']
701 | }
702 | };
703 |
704 | let protocols = ['wss', 'tcp', 'udp']
705 | protocols.forEach(protocol => {
706 | if (context.exceptions.hasOwnProperty(protocol)){
707 | stats[protocol].exception = context.exceptions[protocol];
708 | }
709 | })
710 |
711 |
712 | window.JitsiTestBrowser.Statistics.addStat('test_network', stats );
713 | },
714 |
715 | /**
716 | * Default test fail handler
717 | *
718 | * @param protocol
719 | * @param result
720 | */
721 | testFail: function (protocol, result) {
722 | let context = window.JitsiTestBrowser.test_network;
723 |
724 | context.statuses[protocol] = false;
725 |
726 | let details = result;
727 | if (result instanceof Error) {
728 | details = result.toString();
729 | } else if (result instanceof Object) {
730 | details = JSON.stringify(result);
731 | }
732 |
733 | context.exceptions[protocol] = result;
734 |
735 | console.error(details);
736 |
737 | window.JitsiTestEvents.dispatch('network_stat', {"context": protocol, "data": result});
738 | },
739 |
740 |
741 | /**
742 | * Reset UI elements
743 | */
744 | reset: function (){
745 | let container = document.getElementById('network_pane');
746 |
747 | // Reset stat right items
748 | container.querySelectorAll('div.stat-right-item').forEach(function(element){
749 | element.classList.remove('test-fail', 'test-success');
750 | });
751 |
752 | // Hide status icon
753 | container.querySelectorAll('[data-content="status_icon"]').forEach(function(element){
754 | element.classList.add('hide');
755 | });
756 |
757 | // Reset stat values
758 | ['media_connectivity_packetlost', 'media_connectivity_framerate', 'media_connectivity_droppedframes', 'media_connectivity_jitter']
759 | .forEach(function (element){
760 | document.getElementById(element).querySelector(`span[data-content="value"]`).textContent = '0';
761 | });
762 | document.querySelectorAll('[data-content="video_dimensions"] span[data-content="value"] ,div[data-content="ip_connected_to"] span[data-content="value"]')
763 | .forEach(function (element){
764 | element.textContent = 'N/C'
765 | });
766 | },
767 |
768 |
769 | /**
770 | * Final resolve function
771 | *
772 | * @param res
773 | * @param data
774 | */
775 | resolve: function(res, data){
776 | window.JitsiTestEvents.dispatch('run', {"status": window.TestStatuses.ENDED, "context": "test_network"});
777 | res(data)
778 | }
779 | }
--------------------------------------------------------------------------------