bigbluebutton-Github/bigbluebutton-html5/imports/ui/components/connection-status/service.js

323 lines
6.9 KiB
JavaScript
Raw Normal View History

import { defineMessages } from 'react-intl';
2020-04-10 01:01:46 +08:00
import ConnectionStatus from '/imports/api/connection-status';
import Users from '/imports/api/users';
import Auth from '/imports/ui/services/auth';
import Settings from '/imports/ui/services/settings';
import Logger from '/imports/startup/client/logger';
import _ from 'lodash';
import { Session } from 'meteor/session';
import { notify } from '/imports/ui/services/notification';
2020-04-10 01:01:46 +08:00
import { makeCall } from '/imports/ui/services/api';
const STATS = Meteor.settings.public.stats;
const NOTIFICATION = STATS.notification;
const STATS_LENGTH = STATS.length;
const STATS_INTERVAL = STATS.interval;
const STATS_LOG = STATS.log;
const RTT_INTERVAL = STATS_LENGTH * STATS_INTERVAL;
// Set a bottom threshold to avoid log flooding
const RTT_LOG_THRESHOLD = STATS.rtt[STATS.level.indexOf('danger')];
2020-04-10 01:01:46 +08:00
const ROLE_MODERATOR = Meteor.settings.public.user.role_moderator;
const intlMessages = defineMessages({
saved: {
id: 'app.settings.save-notification.label',
description: 'Label shown in toast when data savings are saved',
},
notification: {
id: 'app.connection-status.notification',
description: 'Label shown in toast when connection loss is detected',
},
});
let stats = -1;
const statsDep = new Tracker.Dependency();
2020-04-28 22:20:19 +08:00
let statsTimeout = null;
const URL_REGEX = new RegExp(/^(http|https):\/\/[^ "]+$/);
const getHelp = () => {
if (URL_REGEX.test(STATS.help)) return STATS.help;
return null;
};
2020-04-28 22:20:19 +08:00
const getLevel = () => STATS.level;
const getStats = () => {
statsDep.depend();
return STATS.level[stats];
2020-04-28 22:20:19 +08:00
};
const setStats = (level = -1) => {
if (stats !== level) {
stats = level;
statsDep.changed();
2020-04-28 22:20:19 +08:00
addConnectionStatus(level);
}
};
const handleStats = (level) => {
if (level > stats) {
setStats(level);
}
};
2020-04-28 22:20:19 +08:00
const handleAudioStatsEvent = (event) => {
const { detail } = event;
if (detail) {
const { loss, jitter } = detail;
let active = false;
// From higher to lower
for (let i = STATS.level.length - 1; i >= 0; i--) {
if (loss > STATS.loss[i] || jitter > STATS.jitter[i]) {
active = true;
handleStats(i);
2020-04-28 22:20:19 +08:00
break;
}
}
if (active) startStatsTimeout();
}
};
const handleSocketStatsEvent = (event) => {
const { detail } = event;
if (detail) {
const { rtt } = detail;
let active = false;
// From higher to lower
for (let i = STATS.level.length - 1; i >= 0; i--) {
if (rtt > STATS.rtt[i]) {
active = true;
handleStats(i);
break;
}
2020-04-28 22:20:19 +08:00
}
if (active) startStatsTimeout();
2020-04-28 22:20:19 +08:00
}
};
const startStatsTimeout = () => {
if (statsTimeout !== null) clearTimeout(statsTimeout);
statsTimeout = setTimeout(() => {
setStats();
}, STATS.timeout);
};
2020-05-14 21:35:25 +08:00
const addConnectionStatus = (level) => {
if (level !== -1) makeCall('addConnectionStatus', STATS.level[level]);
};
const fetchRoundTripTime = () => {
const t0 = Date.now();
makeCall('voidConnection').then(() => {
const tf = Date.now();
const rtt = tf - t0;
if (STATS_LOG && rtt > RTT_LOG_THRESHOLD) {
Logger.info(
{
logCode: 'rtt',
extraInfo: { rtt },
},
'Calculated round-trip time in milliseconds',
);
}
const event = new CustomEvent('socketstats', { detail: { rtt } });
window.dispatchEvent(event);
});
2020-05-14 21:35:25 +08:00
};
const sortLevel = (a, b) => {
const indexOfA = STATS.level.indexOf(a.level);
const indexOfB = STATS.level.indexOf(b.level);
if (indexOfA < indexOfB) return 1;
if (indexOfA === indexOfB) return 0;
if (indexOfA > indexOfB) return -1;
};
2020-04-10 01:01:46 +08:00
const getMyConnectionStatus = () => {
const myConnectionStatus = ConnectionStatus.findOne(
{
meetingId: Auth.meetingID,
userId: Auth.userID,
},
{ fields:
{
level: 1,
timestamp: 1,
},
},
);
const me = Users.findOne(
{
meetingId: Auth.meetingID,
userId: Auth.userID,
},
{ fields:
{
avatar: 1,
color: 1,
}
},
);
if (myConnectionStatus) {
return [{
name: Auth.fullname,
avatar: me.avatar,
you: true,
moderator: false,
color: me.color,
level: myConnectionStatus.level,
timestamp: myConnectionStatus.timestamp,
}];
}
return [];
};
2020-04-10 01:01:46 +08:00
const getConnectionStatus = () => {
if (!isModerator()) return getMyConnectionStatus();
2020-04-10 01:01:46 +08:00
const connectionStatus = ConnectionStatus.find(
{ meetingId: Auth.meetingID },
).fetch().map(status => {
2020-05-14 21:35:25 +08:00
const {
userId,
level,
timestamp,
} = status;
return {
userId,
level,
timestamp,
};
2020-04-10 01:01:46 +08:00
});
return Users.find(
2020-05-14 21:35:25 +08:00
{ meetingId: Auth.meetingID },
2020-04-10 01:01:46 +08:00
{ fields:
{
userId: 1,
name: 1,
role: 1,
avatar: 1,
2020-05-15 01:33:32 +08:00
color: 1,
2020-04-10 01:01:46 +08:00
},
},
).fetch().reduce((result, user) => {
const {
userId,
name,
role,
avatar,
2020-05-15 01:33:32 +08:00
color,
2020-04-10 01:01:46 +08:00
} = user;
const status = connectionStatus.find(status => status.userId === userId);
if (status) {
result.push({
name,
avatar,
2020-04-10 01:01:46 +08:00
you: Auth.userID === userId,
moderator: role === ROLE_MODERATOR,
2020-05-15 01:33:32 +08:00
color,
2020-04-10 01:01:46 +08:00
level: status.level,
timestamp: status.timestamp,
});
}
return result;
2020-05-14 21:35:25 +08:00
}, []).sort(sortLevel);
2020-04-10 01:01:46 +08:00
};
const isEnabled = () => STATS.enabled;
let roundTripTimeInterval = null;
const startRoundTripTime = () => {
if (!isEnabled()) return;
stopRoundTripTime();
roundTripTimeInterval = setInterval(fetchRoundTripTime, RTT_INTERVAL);
};
const stopRoundTripTime = () => {
if (roundTripTimeInterval) {
clearInterval(roundTripTimeInterval);
}
}
const isModerator = () => {
const user = Users.findOne(
{
meetingId: Auth.meetingID,
userId: Auth.userID,
},
{ fields: { role: 1 }},
);
if (user && user.role === ROLE_MODERATOR) {
return true;
}
return false;
};
2020-04-28 22:20:19 +08:00
if (STATS.enabled) {
window.addEventListener('audiostats', handleAudioStatsEvent);
window.addEventListener('socketstats', handleSocketStatsEvent);
2020-04-28 22:20:19 +08:00
}
const updateDataSavingSettings = (dataSaving, intl) => {
if (!_.isEqual(Settings.dataSaving, dataSaving)) {
Settings.dataSaving = dataSaving;
Settings.save();
if (intl) notify(intl.formatMessage(intlMessages.saved), 'info', 'settings');
}
};
const getNotified = () => {
const notified = Session.get('connectionStatusNotified');
// Since notified can be undefined we need a boolean verification
return notified === true;
};
const notification = (level, intl) => {
if (!NOTIFICATION[level]) return null;
// Avoid toast spamming
const notified = getNotified();
if (notified) {
return null;
} else {
Session.set('connectionStatusNotified', true);
}
if (intl) notify(intl.formatMessage(intlMessages.notification), level, 'network');
};
2020-04-10 01:01:46 +08:00
export default {
addConnectionStatus,
getConnectionStatus,
getStats,
2020-04-28 22:20:19 +08:00
getHelp,
getLevel,
2020-04-10 01:01:46 +08:00
isEnabled,
notification,
startRoundTripTime,
stopRoundTripTime,
updateDataSavingSettings,
2020-04-10 01:01:46 +08:00
};