2021-03-26 05:22:50 +08:00
|
|
|
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';
|
2021-04-02 02:10:15 +08:00
|
|
|
import UsersPersistentData from '/imports/api/users-persistent-data';
|
2020-04-10 01:01:46 +08:00
|
|
|
import Auth from '/imports/ui/services/auth';
|
2021-03-26 05:22:50 +08:00
|
|
|
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;
|
2021-03-26 05:22:50 +08:00
|
|
|
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;
|
|
|
|
|
2021-03-26 05:22:50 +08:00
|
|
|
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;
|
|
|
|
|
2021-03-26 05:22:50 +08:00
|
|
|
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;
|
|
|
|
|
2021-03-26 05:22:50 +08:00
|
|
|
const getStats = () => {
|
|
|
|
statsDep.depend();
|
|
|
|
return STATS.level[stats];
|
2020-04-28 22:20:19 +08:00
|
|
|
};
|
|
|
|
|
2021-03-26 05:22:50 +08:00
|
|
|
const setStats = (level = -1) => {
|
|
|
|
if (stats !== level) {
|
|
|
|
stats = level;
|
|
|
|
statsDep.changed();
|
2020-04-28 22:20:19 +08:00
|
|
|
addConnectionStatus(level);
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
2021-03-26 05:22:50 +08:00
|
|
|
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;
|
2021-03-26 05:22:50 +08:00
|
|
|
handleStats(i);
|
2020-04-28 22:20:19 +08:00
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
2021-03-26 05:22:50 +08:00
|
|
|
|
|
|
|
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
|
|
|
}
|
2021-03-26 05:22:50 +08:00
|
|
|
|
|
|
|
if (active) startStatsTimeout();
|
2020-04-28 22:20:19 +08:00
|
|
|
}
|
|
|
|
};
|
|
|
|
|
2021-03-26 05:22:50 +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) => {
|
2021-03-26 05:22:50 +08:00
|
|
|
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
|
|
|
|
2021-03-26 05:22:50 +08:00
|
|
|
const getMyConnectionStatus = () => {
|
|
|
|
const myConnectionStatus = ConnectionStatus.findOne(
|
|
|
|
{
|
|
|
|
meetingId: Auth.meetingID,
|
|
|
|
userId: Auth.userID,
|
|
|
|
},
|
2021-04-02 02:10:15 +08:00
|
|
|
{
|
|
|
|
fields:
|
2021-03-26 05:22:50 +08:00
|
|
|
{
|
|
|
|
level: 1,
|
|
|
|
timestamp: 1,
|
|
|
|
},
|
|
|
|
},
|
|
|
|
);
|
|
|
|
|
|
|
|
const me = Users.findOne(
|
|
|
|
{
|
|
|
|
meetingId: Auth.meetingID,
|
|
|
|
userId: Auth.userID,
|
|
|
|
},
|
2021-04-02 02:10:15 +08:00
|
|
|
{
|
|
|
|
fields:
|
2021-03-26 05:22:50 +08:00
|
|
|
{
|
|
|
|
avatar: 1,
|
|
|
|
color: 1,
|
2021-04-02 02:10:15 +08:00
|
|
|
},
|
2021-03-26 05:22:50 +08:00
|
|
|
},
|
|
|
|
);
|
|
|
|
|
|
|
|
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 = () => {
|
2021-03-26 05:22:50 +08:00
|
|
|
if (!isModerator()) return getMyConnectionStatus();
|
|
|
|
|
2020-04-10 01:01:46 +08:00
|
|
|
const connectionStatus = ConnectionStatus.find(
|
|
|
|
{ meetingId: Auth.meetingID },
|
2021-04-02 02:10:15 +08:00
|
|
|
).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
|
|
|
});
|
|
|
|
|
2021-04-02 02:10:15 +08:00
|
|
|
return UsersPersistentData.find(
|
|
|
|
{},
|
|
|
|
{
|
|
|
|
fields:
|
2020-04-10 01:01:46 +08:00
|
|
|
{
|
|
|
|
userId: 1,
|
|
|
|
name: 1,
|
|
|
|
role: 1,
|
2020-09-15 07:13:47 +08:00
|
|
|
avatar: 1,
|
2020-05-15 01:33:32 +08:00
|
|
|
color: 1,
|
2021-04-02 02:10:15 +08:00
|
|
|
loggedOut: 1,
|
2020-04-10 01:01:46 +08:00
|
|
|
},
|
|
|
|
},
|
|
|
|
).fetch().reduce((result, user) => {
|
|
|
|
const {
|
|
|
|
userId,
|
|
|
|
name,
|
2020-05-12 21:26:08 +08:00
|
|
|
role,
|
2020-09-15 07:13:47 +08:00
|
|
|
avatar,
|
2020-05-15 01:33:32 +08:00
|
|
|
color,
|
2021-04-02 02:10:15 +08:00
|
|
|
loggedOut,
|
2020-04-10 01:01:46 +08:00
|
|
|
} = user;
|
|
|
|
|
|
|
|
const status = connectionStatus.find(status => status.userId === userId);
|
|
|
|
|
|
|
|
if (status) {
|
|
|
|
result.push({
|
|
|
|
name,
|
2020-09-15 07:13:47 +08:00
|
|
|
avatar,
|
2021-04-02 02:10:15 +08:00
|
|
|
offline: loggedOut,
|
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;
|
|
|
|
|
2021-03-26 05:22:50 +08:00
|
|
|
let roundTripTimeInterval = null;
|
|
|
|
|
|
|
|
const startRoundTripTime = () => {
|
|
|
|
if (!isEnabled()) return;
|
|
|
|
|
|
|
|
stopRoundTripTime();
|
|
|
|
|
|
|
|
roundTripTimeInterval = setInterval(fetchRoundTripTime, RTT_INTERVAL);
|
|
|
|
};
|
|
|
|
|
|
|
|
const stopRoundTripTime = () => {
|
|
|
|
if (roundTripTimeInterval) {
|
|
|
|
clearInterval(roundTripTimeInterval);
|
|
|
|
}
|
2021-04-02 02:10:15 +08:00
|
|
|
};
|
2021-03-26 05:22:50 +08:00
|
|
|
|
|
|
|
const isModerator = () => {
|
|
|
|
const user = Users.findOne(
|
|
|
|
{
|
|
|
|
meetingId: Auth.meetingID,
|
|
|
|
userId: Auth.userID,
|
|
|
|
},
|
2021-04-02 02:10:15 +08:00
|
|
|
{ fields: { role: 1 } },
|
2021-03-26 05:22:50 +08:00
|
|
|
);
|
|
|
|
|
|
|
|
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);
|
2021-03-26 05:22:50 +08:00
|
|
|
window.addEventListener('socketstats', handleSocketStatsEvent);
|
2020-04-28 22:20:19 +08:00
|
|
|
}
|
|
|
|
|
2021-03-26 05:22:50 +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;
|
|
|
|
}
|
2021-04-02 02:10:15 +08:00
|
|
|
Session.set('connectionStatusNotified', true);
|
|
|
|
|
2021-03-26 05:22:50 +08:00
|
|
|
|
|
|
|
if (intl) notify(intl.formatMessage(intlMessages.notification), level, 'network');
|
|
|
|
};
|
|
|
|
|
2020-04-10 01:01:46 +08:00
|
|
|
export default {
|
|
|
|
addConnectionStatus,
|
|
|
|
getConnectionStatus,
|
2021-03-26 05:22:50 +08:00
|
|
|
getStats,
|
2020-04-28 22:20:19 +08:00
|
|
|
getHelp,
|
|
|
|
getLevel,
|
2020-04-10 01:01:46 +08:00
|
|
|
isEnabled,
|
2021-03-26 05:22:50 +08:00
|
|
|
notification,
|
|
|
|
startRoundTripTime,
|
|
|
|
stopRoundTripTime,
|
|
|
|
updateDataSavingSettings,
|
2020-04-10 01:01:46 +08:00
|
|
|
};
|