Merge 2.2.29 and 2.2.30 into 2.3.x

This commit is contained in:
Anton Georgiev 2020-11-24 15:13:09 +00:00
commit 3faabd1821
68 changed files with 610 additions and 198 deletions

View File

@ -357,7 +357,10 @@ object Polls {
def respondToQuestion(pollId: String, questionID: Int, responseID: Int, responder: Responder, polls: Polls) {
polls.polls.get(pollId) match {
case Some(p) => {
p.respondToQuestion(questionID, responseID, responder)
if (!p.getResponders().exists(_ == responder)) {
p.addResponder(responder)
p.respondToQuestion(questionID, responseID, responder)
}
}
case None =>
}
@ -499,6 +502,7 @@ class Poll(val id: String, val questions: Array[Question], val numRespondents: I
private var _stopped: Boolean = false
private var _showResult: Boolean = false
private var _numResponders: Int = 0
private var _responders = new ArrayBuffer[Responder]()
def showingResult() { _showResult = true }
def showResult(): Boolean = { _showResult }
@ -513,6 +517,9 @@ class Poll(val id: String, val questions: Array[Question], val numRespondents: I
_stopped = false
}
def addResponder(responder: Responder) { _responders += (responder) }
def getResponders(): ArrayBuffer[Responder] = { return _responders }
def hasResponses(): Boolean = {
questions.foreach(q => {
if (q.hasResponders) return true

View File

@ -981,6 +981,11 @@ public class MeetingService implements MessageListener {
User vuser = m.userLeft(message.userId);
} else {
user.setVoiceJoined(false);
// userLeftVoice is also used when user leaves Global (listenonly)
// audio. Also tetting listenOnly to false is not a problem,
// once user can't join both voice/mic and global/listenonly
// at the same time.
user.setListeningOnly(false);
}
}
}

View File

@ -103,12 +103,12 @@ enableUFWRules() {
enableMultipleKurentos() {
echo " - Configuring three Kurento Media Servers: one for listen only, webcam, and screeshare"
echo " - Configuring three Kurento Media Servers (listen only, webcam, and screeshare)"
# Step 1. Setup shared certificate between FreeSWITCH and Kurento
HOSTNAME=$(cat /etc/nginx/sites-available/bigbluebutton | grep -v '#' | sed -n '/server_name/{s/.*server_name[ ]*//;s/;//;p}' | cut -d' ' -f1 | head -n 1)
openssl req -x509 -new -nodes -newkey rsa:2048 -sha256 -days 3650 -subj "/C=BR/ST=Ottawa/O=BigBlueButton Inc./OU=Live/CN=$HOSTNAME" -keyout /tmp/dtls-srtp-key.pem -out /tmp/dtls-srtp-cert.pem
openssl req -x509 -new -nodes -newkey rsa:4096 -sha256 -days 3650 -subj "/C=BR/ST=Ottawa/O=BigBlueButton Inc./OU=Live/CN=$HOSTNAME" -keyout /tmp/dtls-srtp-key.pem -out /tmp/dtls-srtp-cert.pem
cat /tmp/dtls-srtp-key.pem /tmp/dtls-srtp-cert.pem > /etc/kurento/dtls-srtp.pem
cat /tmp/dtls-srtp-key.pem /tmp/dtls-srtp-cert.pem > /opt/freeswitch/etc/freeswitch/tls/dtls-srtp.pem
@ -119,50 +119,57 @@ enableMultipleKurentos() {
for i in `seq 8888 8890`; do
cat > /usr/lib/systemd/system/kurento-media-server-${i}.service << HERE
# /usr/lib/systemd/system/kurento-media-server-#{i}.service
[Unit]
Description=Kurento Media Server daemon (${i})
After=network.target
PartOf=kurento-media-server.service
After=kurento-media-server.service
# /usr/lib/systemd/system/kurento-media-server-#{i}.service
[Unit]
Description=Kurento Media Server daemon (${i})
After=network.target
PartOf=kurento-media-server.service
After=kurento-media-server.service
[Service]
UMask=0002
Environment=KURENTO_LOGS_PATH=/var/log/kurento-media-server
Environment=KURENTO_CONF_FILE=/etc/kurento/kurento-${i}.conf.json
User=kurento
Group=kurento
LimitNOFILE=1000000
ExecStartPre=-/bin/rm -f /var/kurento/.cache/gstreamer-1.5/registry.x86_64.bin
ExecStart=/usr/bin/kurento-media-server --gst-debug-level=3 --gst-debug="3,Kurento*:4,kms*:4,KurentoWebSocketTransport:5"
Type=simple
PIDFile=/var/run/kurento-media-server-${i}.pid
Restart=always
[Install]
WantedBy=kurento-media-server.service
[Service]
UMask=0002
Environment=KURENTO_LOGS_PATH=/var/log/kurento-media-server
Environment=KURENTO_CONF_FILE=/etc/kurento/kurento-${i}.conf.json
User=kurento
Group=kurento
LimitNOFILE=1000000
ExecStartPre=-/bin/rm -f /var/kurento/.cache/gstreamer-1.5/registry.x86_64.bin
ExecStart=/usr/bin/kurento-media-server --gst-debug-level=3 --gst-debug="3,Kurento*:4,kms*:4,KurentoWebSocketTransport:5"
Type=simple
PIDFile=/var/run/kurento-media-server-${i}.pid
Restart=always
[Install]
WantedBy=kurento-media-server.service
HERE
# Make a new configuration file each instance of Kurento that binds to a different port
cp /etc/kurento/kurento.conf.json /etc/kurento/kurento-${i}.conf.json
sed -i "s/8888/${i}/g" /etc/kurento/kurento-${i}.conf.json
done
# Step 3. Override the main kurento-media-server unit to start/stop the three Kurento instances
cat > /etc/systemd/system/kurento-media-server.service << HERE
[Unit]
Description=Kurento Media Server
[Unit]
Description=Kurento Media Server
[Service]
Type=oneshot
ExecStart=/bin/true
RemainAfterExit=yes
[Service]
Type=oneshot
ExecStart=/bin/true
RemainAfterExit=yes
[Install]
WantedBy=multi-user.target
[Install]
WantedBy=multi-user.target
HERE
# Step 4. Extend bbb-webrtc-sfu unit to wait for all three KMS servers to start
mkdir -p /etc/systemd/system/bbb-webrtc-sfu.service.d
cat > /etc/systemd/system/bbb-webrtc-sfu.service.d/override.conf << HERE
[Unit]
After=
After=syslog.target network.target freeswitch.service kurento-media-server-8888.service kurento-media-server-8889.service kurento-media-server-8890.service
HERE
systemctl daemon-reload
@ -172,7 +179,7 @@ HERE
done
# Step 4. Modify bbb-webrtc-sfu config to use the three Kurento servers
# Step 5. Modify bbb-webrtc-sfu config to use the three Kurento servers
KURENTO_CONFIG=/usr/local/bigbluebutton/bbb-webrtc-sfu/config/default.yml
@ -204,6 +211,8 @@ disableMultipleKurentos() {
# Remove the overrride (restoring the original kurento-media-server.service unit file)
rm -f /etc/systemd/system/kurento-media-server.service
rm -f /etc/systemd/system/bbb-webrtc-sfu.service.d/override.conf
systemctl daemon-reload
# Restore bbb-webrtc-sfu configuration to use a single instance of Kurento
@ -244,6 +253,8 @@ source /etc/bigbluebutton/bbb-conf/apply-lib.sh
#enableHTML5CameraQualityThresholds
#enableHTML5WebcamPagination
#enableMultipleKurentos
HERE
chmod +x /etc/bigbluebutton/bbb-conf/apply-config.sh
## Stop Copying HERE

View File

@ -787,6 +787,26 @@ check_configuration() {
echo "#"
fi
fi
CHECK_STUN=$(xmlstarlet sel -t -m '//X-PRE-PROCESS[@cmd="set" and starts-with(@data, "external_rtp_ip=")]' -v @data $FREESWITCH_VARS | sed 's/external_rtp_ip=stun://g')
if [ "$CHECK_STUN" == "stun.freeswitch.org" ]; then
echo
echo "# Warning: Detected FreeSWITCH is using default stun.freeswitch.org server. See"
echo "#"
echo "# http://docs.bigbluebutton.org/2.2/troubleshooting.html#freeswitch-using-default-stun-server"
echo "#"
echo
fi
if ! which ufw; then
echo
echo "# Warning: No firewall detected. Recommend using setting up a firewall for your server"
echo "#"
echo "# http://docs.bigbluebutton.org/2.2/troubleshooting.html#freeswitch-using-default-stun-server"
echo "#"
echo
fi
}
update_gstreamer() {
@ -1111,28 +1131,30 @@ check_state() {
echo "#"
fi
if [ "$PROTOCOL" == "https" ]; then
if ! cat $SIP_CONFIG | grep -v '#' | grep proxy_pass | head -n 1 | grep -q https; then
echo "# Warning: You have this server defined for https, but in"
echo "#"
echo "# $SIP_CONFIG"
echo "#"
echo "# did not find the use of https in definition for proxy_pass"
echo "#"
echo "# $(cat $SIP_CONFIG | grep -v '#' | grep proxy_pass | head -n 1)"
echo "#"
fi
if [ "$(yq r /usr/share/meteor/bundle/programs/server/assets/app/config/settings.yml public.media.sipjsHackViaWs)" != "true" ]; then
if [ "$PROTOCOL" == "https" ]; then
if ! cat $SIP_CONFIG | grep -v '#' | grep proxy_pass | head -n 1 | grep -q https; then
echo "# Warning: You have this server defined for https, but in"
echo "#"
echo "# $SIP_CONFIG"
echo "#"
echo "# did not find the use of https in definition for proxy_pass"
echo "#"
echo "# $(cat $SIP_CONFIG | grep -v '#' | grep proxy_pass | head -n 1)"
echo "#"
fi
if ! cat $SIP_CONFIG | grep -v '#' | grep proxy_pass | head -n 1 | grep -q 7443; then
echo
echo "# Warning: You have this server defined for https, but in"
echo "#"
echo "# $SIP_CONFIG"
echo "#"
echo "# did not find the use of port 7443 in definition for proxy_pass"
echo "#"
echo "# $(cat $SIP_CONFIG | grep -v '#' | grep proxy_pass | head -n 1)"
echo "#"
if ! cat $SIP_CONFIG | grep -v '#' | grep proxy_pass | head -n 1 | grep -q 7443; then
echo
echo "# Warning: You have this server defined for https, but in"
echo "#"
echo "# $SIP_CONFIG"
echo "#"
echo "# did not find the use of port 7443 in definition for proxy_pass"
echo "#"
echo "# $(cat $SIP_CONFIG | grep -v '#' | grep proxy_pass | head -n 1)"
echo "#"
fi
fi
fi
@ -1348,7 +1370,7 @@ if [ $ZIP ]; then
tar rf $TMP_LOG_FILE /var/log/mongodb > /dev/null 2>&1
tar rf $TMP_LOG_FILE /var/log/redis > /dev/null 2>&1
tar rf $TMP_LOG_FILE /var/log/nginx/error.log* > /dev/null 2>&1
tar rf $TMP_LOG_FILE /var/log/nginx/bigbluebutton.access.log* > /dev/null 2>&1
tar rf $TMP_LOG_FILE /var/log/nginx/bigbluebutton.access.log* > /dev/null 2>&1
tar rfh $TMP_LOG_FILE /opt/freeswitch/var/log/freeswitch/ > /dev/null 2>&1
if [ -f /var/log/nginx/html5-client.log ]; then
@ -1668,7 +1690,7 @@ if [ $CLEAN ]; then
rm -f /var/log/bbb-fsesl-akka/*
fi
if [ -d /var/log/bbb-apps-akka ]; then
if [ -d /var/log/bbb-apps-akka ]; then
rm -f /var/log/bbb-apps-akka/*
fi

View File

@ -116,6 +116,13 @@ need_root() {
fi
}
need_root_or_bigbluebutton() {
if [ $EUID != 0 -a "$USER" != 'bigbluebutton']; then
echo "Need to be user root or bigbluebutton to run this option"
exit 1
fi
}
print_header() {
if [ ! $HEADER ]; then
echo
@ -216,7 +223,7 @@ while [ $# -gt 0 ]; do
fi
if [ "$1" = "-delete" -o "$1" = "--delete" ]; then
need_root
need_root_or_bigbluebutton
if [ ! -z "${2}" ]; then
MEETING_ID="${2}"
shift

View File

@ -13,7 +13,7 @@ function annotations() {
const { meetingId, userId } = tokenValidation;
Logger.debug(`Publishing Annotations for ${meetingId} ${userId}`);
Logger.debug('Publishing Annotations', { meetingId, userId });
return Annotations.find({ meetingId });
}

View File

@ -23,6 +23,7 @@ const MEDIA_TAG = MEDIA.mediaTag;
const CALL_TRANSFER_TIMEOUT = MEDIA.callTransferTimeout;
const CALL_HANGUP_TIMEOUT = MEDIA.callHangupTimeout;
const CALL_HANGUP_MAX_RETRIES = MEDIA.callHangupMaximumRetries;
const SIPJS_HACK_VIA_WS = MEDIA.sipjsHackViaWs;
const IPV4_FALLBACK_DOMAIN = Meteor.settings.public.app.ipv4FallbackDomain;
const CALL_CONNECT_TIMEOUT = 20000;
const ICE_NEGOTIATION_TIMEOUT = 20000;
@ -30,6 +31,7 @@ const AUDIO_SESSION_NUM_KEY = 'AudioSessionNumber';
const USER_AGENT_RECONNECTION_ATTEMPTS = 3;
const USER_AGENT_RECONNECTION_DELAY_MS = 5000;
const USER_AGENT_CONNECTION_TIMEOUT_MS = 5000;
const ICE_GATHERING_TIMEOUT = MEDIA.iceGatheringTimeout || 5000;
const getAudioSessionNumber = () => {
let currItem = parseInt(sessionStorage.getItem(AUDIO_SESSION_NUM_KEY), 10);
@ -309,14 +311,18 @@ class SIPSession {
});
}
onBeforeUnload() {
if (this.userAgent) {
stopUserAgent() {
if (this.userAgent && (typeof this.userAgent.stop === 'function')) {
return this.userAgent.stop();
}
return Promise.resolve();
}
onBeforeUnload() {
this.userRequestedHangup = true;
return this.stopUserAgent();
}
createUserAgent(iceServers) {
return new Promise((resolve, reject) => {
if (this.userRequestedHangup === true) reject();
@ -368,11 +374,13 @@ class SIPSession {
sessionDescriptionHandlerFactoryOptions: {
peerConnectionConfiguration: {
iceServers,
sdpSemantics: 'plan-b',
},
},
displayName: callerIdName,
register: false,
userAgentString: 'BigBlueButton',
hackViaWs: SIPJS_HACK_VIA_WS,
});
const handleUserAgentConnection = () => {
@ -424,6 +432,9 @@ class SIPSession {
error = 1002;
bridgeError = 'Websocket failed to connect';
}
this.stopUserAgent();
this.callback({
status: this.baseCallStates.failed,
error,
@ -459,11 +470,13 @@ class SIPSession {
const code = getErrorCode(error);
//Websocket's 1006 is currently mapped to BBB's 1002
if (code === 1006) {
this.stopUserAgent();
this.callback({
status: this.baseCallStates.failed,
error: 1006,
error: 1002,
bridgeError: 'Websocket failed to connect',
});
return reject({
@ -481,6 +494,8 @@ class SIPSession {
resolve();
}).catch(() => {
this.stopUserAgent();
logger.info({
logCode: 'sip_js_session_ua_disconnected',
extraInfo: {
@ -519,19 +534,26 @@ class SIPSession {
this._reconnecting = true;
setTimeout(() => {
this.userAgent.reconnect().then(() => {
this._reconnecting = false;
resolve();
}).catch(() => {
logger.info({
logCode: 'sip_js_session_ua_reconnection_attempt',
extraInfo: {
callerIdName: this.user.callerIdName,
},
}, `User agent reconnection attempt ${attempts}`);
this.userAgent.reconnect().then(() => {
this._reconnecting = false;
resolve();
}).catch(() => {
setTimeout(() => {
this._reconnecting = false;
this.reconnect(++attempts).then(() => {
resolve();
}).catch((error) => {
reject(error);
});
});
}, USER_AGENT_RECONNECTION_DELAY_MS);
}, USER_AGENT_RECONNECTION_DELAY_MS);
});
});
}
@ -562,6 +584,7 @@ class SIPSession {
: audioDeviceConstraint,
video: false,
},
iceGatheringTimeout: ICE_GATHERING_TIMEOUT,
},
sessionDescriptionHandlerModifiersPostICEGathering:
[stripMDnsCandidates],
@ -720,7 +743,7 @@ class SIPSession {
callerIdName: this.user.callerIdName,
},
}, 'ICE connection closed');
}
} else return;
this.callback({
status: this.baseCallStates.failed,
@ -743,15 +766,51 @@ class SIPSession {
onconnectionstatechange: (event) => {
const peer = event.target;
logger.info({
logCode: 'sip_js_connection_state_change',
extraInfo: {
connectionStateChange: peer.connectionState,
callerIdName: this.user.callerIdName,
},
}, 'ICE connection state change - Current connection state - '
+ `${peer.connectionState}`);
switch (peer.connectionState) {
case 'failed':
// Chrome triggers 'failed' for connectionState event, only
handleIceNegotiationFailed(peer);
break;
default:
break;
}
},
oniceconnectionstatechange: (event) => {
const peer = event.target;
switch (peer.iceConnectionState) {
case 'completed':
case 'connected':
if (iceCompleted) {
logger.info({
logCode: 'sip_js_ice_connection_success_after_success',
extraInfo: {
currentState: peer.connectionState,
callerIdName: this.user.callerIdName,
},
}, 'ICE connection success, but user is already connected'
+ 'ignoring it...'
+ `${peer.iceConnectionState}`);
return;
}
logger.info({
logCode: 'sip_js_ice_connection_success',
extraInfo: {
currentState: peer.connectionState,
callerIdName: this.user.callerIdName,
},
}, 'ICE connection success. Current state - '
}, 'ICE connection success. Current ICE Connection state - '
+ `${peer.iceConnectionState}`);
clearTimeout(callTimeout);
@ -777,11 +836,11 @@ class SIPSession {
};
};
const handleSessionTerminated = (message, cause) => {
const handleSessionTerminated = (message) => {
clearTimeout(callTimeout);
clearTimeout(iceNegotiationTimeout);
if (!message && !cause && !!this.userRequestedHangup) {
if (!message && !!this.userRequestedHangup) {
return this.callback({
status: this.baseCallStates.ended,
});
@ -791,18 +850,22 @@ class SIPSession {
// any possile errors
if (!this._currentSessionState) return false;
let mappedCause;
let cause;
if (!iceCompleted) {
mappedCause = '1004';
cause = 'ICE error';
} else {
cause = 'Audio Conference Error';
mappedCause = '1005';
}
logger.error({
logCode: 'sip_js_call_terminated',
extraInfo: { cause, callerIdName: this.user.callerIdName },
}, `Audio call terminated. cause=${cause}`);
let mappedCause;
if (!iceCompleted) {
mappedCause = '1004';
} else {
mappedCause = '1005';
}
return this.callback({
status: this.baseCallStates.failed,
error: mappedCause,

View File

@ -14,9 +14,10 @@ function breakouts(role) {
return Breakouts.find({ meetingId: '' });
}
const { meetingId, userId } = tokenValidation;
Logger.debug(`Publishing Breakouts for ${meetingId} ${userId}`);
const User = Users.findOne({ userId, meetingId }, { fields: { role: 1 } });
Logger.debug('Publishing Breakouts', { meetingId, userId });
if (!!User && User.role === ROLE_MODERATOR) {
const presenterSelector = {
$or: [

View File

@ -24,7 +24,7 @@ export default function appendText(text, locale) {
}).then((response) => {
const { status } = response;
if (status === 200) {
Logger.verbose(`Appended text for padId:${padId}`);
Logger.verbose('Captions: appended text', { padId });
}
}).catch(error => Logger.error(`Could not append captions for padId=${padId}: ${error}`));
}

View File

@ -33,10 +33,10 @@ export default function addCaption(meetingId, padId, locale) {
const { insertedId } = numChanged;
if (insertedId) {
return Logger.verbose(`Added caption locale=${locale.locale} meeting=${meetingId}`);
return Logger.verbose('Captions: added locale', { locale: locale.locale, meetingId });
}
return Logger.verbose(`Upserted caption locale=${locale.locale} meeting=${meetingId}`);
return Logger.verbose('Captions: upserted locale', { locale: locale.locale, meetingId });
};
return Captions.upsert(selector, modifier, cb);

View File

@ -21,10 +21,10 @@ export default function updateOwnerId(meetingId, userId, padId) {
const cb = (err) => {
if (err) {
return Logger.error(`Updating captions pad: ${err}`);
return Logger.error('Captions: error while updating pad', { err });
}
updateOwner(meetingId, userId, padId);
return Logger.verbose(`Update captions pad=${padId} ownerId=${userId}`);
return Logger.verbose('Captions: updated caption', { padId, ownerId: userId });
};
return Captions.update(selector, modifier, { multi: true }, cb);

View File

@ -27,7 +27,7 @@ export default function updatePad(padId, data, revs) {
return Logger.error(`Updating captions pad: ${err}`);
}
editCaptions(padId, data, revs);
return Logger.verbose(`Update captions pad=${padId} revs=${revs}`);
return Logger.verbose('Captions: updated pad', { padId, revs });
};
return Captions.update(selector, modifier, { multi: true }, cb);

View File

@ -18,10 +18,10 @@ export default function updateReadOnlyPadId(padId, readOnlyPadId) {
const cb = (err) => {
if (err) {
return Logger.error(`Adding readOnlyPadId captions pad: ${err}`);
return Logger.error('Captions: error when adding readOnlyPadId', { err });
}
return Logger.verbose(`Added readOnlyPadId captions pad=${padId} readOnlyPadId=${readOnlyPadId}`);
return Logger.verbose('Captions: added readOnlyPadId', { padId, readOnlyPadId });
};
return Captions.update(selector, modifier, { multi: true }, cb);

View File

@ -12,7 +12,7 @@ function captions() {
}
const { meetingId, userId } = tokenValidation;
Logger.debug(`Publishing Captions for ${meetingId} requested by ${userId}`);
Logger.debug('Publishing Captions', { meetingId, requestedBy: userId });
return Captions.find({ meetingId });
}

View File

@ -18,7 +18,7 @@ const proccess = _.throttle(() => {
CursorStreamer(meetingId).emit('message', { meetingId, cursors });
if (streamerLog) {
Logger.debug(`CursorUpdate process for meeting ${meetingId} has finished`);
Logger.debug('CursorUpdate process has finished', { meetingId });
}
} catch (error) {
Logger.error(`Error while trying to send cursor streamer data for meeting ${meetingId}. ${error}`);

View File

@ -36,7 +36,7 @@ export default function updateCursor(meetingId, whiteboardId, userId, x = -1, y
}
if (numChanged) {
Logger.debug(`Updated cursor meeting=${meetingId}`);
Logger.debug('Updated cursor ', { meetingId });
}
};

View File

@ -11,12 +11,12 @@ export function removeCursorStreamer(meetingId) {
export function addCursorStreamer(meetingId) {
const streamer = new Meteor.Streamer(`cursor-${meetingId}`, { retransmit: false });
if (streamerLog) {
Logger.debug(`Cursor streamer created for meeting ${meetingId}`);
Logger.debug('Cursor streamer created', { meetingId });
}
streamer.allowRead(function allowRead() {
if (streamerLog) {
Logger.debug(`Cursor streamer called allowRead for user ${this.userId} in meeting ${meetingId}`);
Logger.debug('Cursor streamer called allowRead', { userId: this.userId, meetingId });
}
return this.userId && this.userId.includes(meetingId);
});

View File

@ -10,7 +10,7 @@ const allowRecentMessages = (eventName, message) => {
state,
} = message;
Logger.debug(`ExternalVideo Streamer auth allowed userId: ${userId}, meetingId: ${meetingId}, event: ${eventName}, time: ${time} rate: ${rate}, state: ${state}`);
Logger.debug('ExternalVideo Streamer auth allowed', userId, meetingId, eventName, time, rate, state);
return true;
};
@ -25,6 +25,6 @@ export default function initializeExternalVideo() {
streamer.allowEmit(allowRecentMessages);
Logger.info(`Created External Video streamer for ${streamName}`);
} else {
Logger.debug(`External Video streamer is already created for ${streamName}`);
Logger.debug('`External Video streamer is already created', { streamName });
}
}

View File

@ -44,7 +44,7 @@ export default function startTyping(meetingId, userId, chatId) {
Meteor.setTimeout(() => {
stopTyping(meetingId, userId);
}, TYPING_TIMEOUT);
return Logger.debug(`Typing indicator update for userId={${userId}} chatId={${chatId}}`);
return Logger.debug('Typing indicator update', { userId, chatId });
};
return UsersTyping.upsert(selector, mod, cb);

View File

@ -20,7 +20,7 @@ export default function stopTyping(meetingId, userId, sendMsgInitiated = false)
if (err) {
return Logger.error(`Stop user=${userId} typing indicator error: ${err}`);
}
return Logger.debug(`Stopped typing indicator for user=${userId}`);
return Logger.debug('Stopped typing indicator', { userId });
};
UsersTyping.remove(selector, cb);

View File

@ -17,7 +17,7 @@ function groupChatMsg(chatsIds) {
const CHAT_CONFIG = Meteor.settings.public.chat;
const PUBLIC_GROUP_CHAT_ID = CHAT_CONFIG.public_group_id;
Logger.debug(`Publishing group-chat-msg for ${meetingId} ${userId}`);
Logger.debug('Publishing group-chat-msg', { meetingId, userId });
return GroupChatMsg.find({
$or: [
@ -44,7 +44,7 @@ function usersTyping() {
const { meetingId, userId } = tokenValidation;
Logger.debug(`Publishing users-typing for ${meetingId} ${userId}`);
Logger.debug('Publishing users-typing', { meetingId, userId });
return UsersTyping.find({ meetingId });
}
@ -54,4 +54,4 @@ function pubishUsersTyping(...args) {
return boundUsersTyping(...args);
}
Meteor.publish('users-typing', pubishUsersTyping);
Meteor.publish('users-typing', pubishUsersTyping);

View File

@ -17,7 +17,7 @@ function groupChat() {
const CHAT_CONFIG = Meteor.settings.public.chat;
const PUBLIC_CHAT_TYPE = CHAT_CONFIG.type_public;
Logger.debug(`Publishing group-chat for ${meetingId} ${userId}`);
Logger.debug('Publishing group-chat', { meetingId, userId });
return GroupChat.find({
$or: [

View File

@ -13,7 +13,7 @@ function localSettings() {
const { meetingId, userId } = tokenValidation;
Logger.debug(`Publishing local settings for user=${userId}`);
Logger.debug('Publishing local settings', { userId });
return LocalSettings.find({ meetingId, userId });
}

View File

@ -17,8 +17,7 @@ export default function transferUser(fromMeetingId, toMeetingId) {
userId: requesterUserId,
};
Logger.verbose(`userId ${requesterUserId} was transferred from
meeting ${fromMeetingId}' to meeting '${toMeetingId}`);
Logger.verbose('User was transferred from one meting to another', { requesterUserId, fromMeetingId, toMeetingId });
return RedisPubSub.publishUserMessage(CHANNEL, EVENT_NAME, meetingId, requesterUserId, payload);
}

View File

@ -16,7 +16,7 @@ function meetings(role) {
const { meetingId, userId } = tokenValidation;
Logger.debug(`Publishing meeting =${meetingId} ${userId}`);
Logger.debug('Publishing meeting', { meetingId, userId });
const selector = {
$or: [

View File

@ -14,7 +14,7 @@ export default function userInstabilityDetected(sender) {
sender,
};
Logger.debug(`Receiver ${receiver} reported a network instability in meeting ${meetingId}`);
Logger.debug('Receiver reported a network instability', { receiver, meetingId });
return NetworkInformation.insert(payload);
}

View File

@ -18,10 +18,10 @@ export default function updateNote(noteId, revs) {
const cb = (err) => {
if (err) {
return Logger.error(`Updating note pad: ${err}`);
return Logger.error('Notes: error when updating note pad', { err });
}
return Logger.verbose(`Update note pad=${noteId} revs=${revs}`);
return Logger.verbose('Notes: update note pad', { pad: noteId, revs });
};
return Note.update(selector, modifier, { multi: true }, cb);

View File

@ -8,12 +8,22 @@ export default function publishVote(pollId, pollAnswerId) {
const REDIS_CONFIG = Meteor.settings.private.redis;
const CHANNEL = REDIS_CONFIG.channels.toAkkaApps;
const EVENT_NAME = 'RespondToPollReqMsg';
const { meetingId, requesterUserId } = extractCredentials(this.userId);
check(pollAnswerId, Number);
check(pollId, String);
const allowedToVote = Polls.findOne({ id: pollId, users: { $in: [requesterUserId] } }, {
fields: {
users: 1,
},
});
if (!allowedToVote) {
Logger.info(`Poll User={${requesterUserId}} has already voted in PollId={${pollId}}`);
return null;
}
const selector = {
users: requesterUserId,
meetingId,
@ -43,11 +53,11 @@ export default function publishVote(pollId, pollAnswerId) {
return Logger.error(`Removing responded user from Polls collection: ${err}`);
}
return Logger.info(`Removed responded user=${requesterUserId} from poll (meetingId: ${meetingId}, `
Logger.info(`Removed responded user=${requesterUserId} from poll (meetingId: ${meetingId}, `
+ `pollId: ${pollId}!)`);
return RedisPubSub.publishUserMessage(CHANNEL, EVENT_NAME, meetingId, requesterUserId, payload);
};
Polls.update(selector, modifier, cb);
return RedisPubSub.publishUserMessage(CHANNEL, EVENT_NAME, meetingId, requesterUserId, payload);
}

View File

@ -13,7 +13,7 @@ function currentPoll() {
const { meetingId, userId } = tokenValidation;
Logger.debug(`Publishing Polls for ${meetingId} ${userId}`);
Logger.debug('Publishing Polls', { meetingId, userId });
const selector = {
meetingId,
@ -40,7 +40,7 @@ function polls() {
const { meetingId, userId } = tokenValidation;
Logger.debug(`Publishing Polls for ${meetingId} ${userId}`);
Logger.debug('Publishing polls', { meetingId, userId });
const selector = {
meetingId,

View File

@ -12,7 +12,7 @@ function presentationPods() {
}
const { meetingId, userId } = tokenValidation;
Logger.debug(`Publishing PresentationPods for ${meetingId} ${userId}`);
Logger.debug('Publishing presentation-pods', { meetingId, userId });
return PresentationPods.find({ meetingId });
}

View File

@ -24,7 +24,7 @@ function presentationUploadToken(podId, filename) {
filename,
};
Logger.debug(`Publishing PresentationUploadToken for ${meetingId} ${userId}`);
Logger.debug('Publishing PresentationUploadToken', { meetingId, userId });
return PresentationUploadToken.find(selector);
}

View File

@ -83,7 +83,7 @@ export default function handlePresentationConversionUpdate({ body }, meetingId)
return Logger.info(`Updated presentation conversion status=${status} id=${presentationId} meeting=${meetingId}`);
}
return Logger.debug(`Upserted presentation conversion status=${status} id=${presentationId} meeting=${meetingId}`);
return Logger.debug('Upserted presentation conversion', { status, presentationId, meetingId });
};
return Presentations.upsert(selector, modifier, cb);

View File

@ -13,7 +13,7 @@ function presentations() {
const { meetingId, userId } = tokenValidation;
Logger.debug(`Publishing Presentations for ${meetingId} ${userId}`);
Logger.debug('Publishing Presentations', { meetingId, userId });
return Presentations.find({ meetingId });
}

View File

@ -13,7 +13,7 @@ function screenshare() {
const { meetingId, userId } = tokenValidation;
Logger.debug(`Publishing Screenshare for ${meetingId} ${userId}`);
Logger.debug('Publishing Screenshare', { meetingId, userId });
return Screenshare.find({ meetingId });
}

View File

@ -54,7 +54,7 @@ export default function resizeSlide(meetingId, slide) {
}
if (numChanged) {
return Logger.debug(`Resized slide positions id=${pageId}`);
return true;
}
return Logger.info(`No slide positions found with id=${pageId}`);

View File

@ -13,7 +13,7 @@ function slides() {
const { meetingId, userId } = tokenValidation;
Logger.debug(`Publishing Slides for ${meetingId} ${userId}`);
Logger.debug('Publishing Slides', { meetingId, userId });
return Slides.find({ meetingId });
}
@ -35,7 +35,7 @@ function slidePositions() {
const { meetingId, userId } = tokenValidation;
Logger.debug(`Publishing SlidePositions for ${meetingId} ${userId}`);
Logger.debug('Publishing SlidePositions', { meetingId, userId });
return SlidePositions.find({ meetingId });
}

View File

@ -13,7 +13,7 @@ function userInfos() {
const { meetingId, userId: requesterUserId } = tokenValidation;
Logger.debug(`Publishing UserInfos for ${meetingId} ${requesterUserId}`);
Logger.debug('Publishing UserInfos requested', { meetingId, requesterUserId });
return UserInfos.find({ meetingId, requesterUserId });
}

View File

@ -27,7 +27,7 @@ export default function addUserSetting(meetingId, userId, setting, value) {
return Logger.error(`Adding user setting to collection: ${err}`);
}
return Logger.verbose(`Upserted user setting for meetingId=${meetingId} userId=${userId} setting=${setting}`);
return Logger.verbose('Upserted user setting', { meetingId, userId, setting });
};
return UserSettings.upsert(selector, modifier, cb);

View File

@ -37,12 +37,12 @@ function userSettings() {
UserSettings.upsert(selector, doc);
});
Logger.debug(`Publishing UserSettings for ${meetingId} ${userId}`);
Logger.debug('Publishing UserSettings', { meetingId, userId });
return UserSettings.find({ meetingId, userId });
}
Logger.debug(`Publishing UserSettings for ${meetingId} ${userId}`);
Logger.debug('Publishing UserSettings', { meetingId, userId });
return UserSettings.find({ meetingId, userId });
}

View File

@ -29,8 +29,7 @@ export default function assignPresenter(userId) { // TODO-- send username from c
requesterId: requesterUserId,
};
Logger.verbose(`User '${userId}' setted as presenter by '${
requesterUserId}' from meeting '${meetingId}'`);
Logger.verbose('User set as presenter', { userId, meetingId, setBy: requesterUserId });
return RedisPubSub.publishUserMessage(CHANNEL, EVENT_NAME, meetingId, requesterUserId, payload);
}

View File

@ -20,7 +20,9 @@ export default function changeRole(userId, role) {
changedBy: requesterUserId,
};
Logger.verbose(`User '${userId}' set as '${role} role by '${requesterUserId}' from meeting '${meetingId}'`);
Logger.verbose('Changed user role', {
userId, role, changedBy: requesterUserId, meetingId,
});
return RedisPubSub.publishUserMessage(CHANNEL, EVENT_NAME, meetingId, requesterUserId, payload);
}

View File

@ -18,8 +18,9 @@ export default function setEmojiStatus(userId, status) {
userId,
};
Logger.verbose(`User '${userId}' emoji status updated to '${status}' by '${
requesterUserId}' from meeting '${meetingId}'`);
Logger.verbose('User emoji status updated', {
userId, status, requesterUserId, meetingId,
});
return RedisPubSub.publishUserMessage(CHANNEL, EVENT_NAME, meetingId, requesterUserId, payload);
}

View File

@ -21,7 +21,7 @@ export default function setUserEffectiveConnectionType(effectiveConnectionType)
setEffectiveConnectionType(meetingId, requesterUserId, effectiveConnectionType);
Logger.verbose(`User ${requesterUserId} effective connection updated to ${effectiveConnectionType}`);
Logger.verbose('Updated user effective connection', { requesterUserId, effectiveConnectionType });
return RedisPubSub.publishUserMessage(CHANNEL, EVENT_NAME, meetingId, requesterUserId, payload);
}

View File

@ -21,8 +21,9 @@ export default function toggleUserLock(userId, lock) {
lock,
};
Logger.verbose(`User ${lockedBy} updated lock status from ${userId} to ${lock}
in meeting ${meetingId}`);
Logger.verbose('Updated lock status for user', {
meetingId, userId, lock, lockedBy,
});
return RedisPubSub.publishUserMessage(CHANNEL, EVENT_NAME, meetingId, lockedBy, payload);

View File

@ -72,7 +72,7 @@ function users(role) {
},
};
Logger.debug(`Publishing Users for ${meetingId} ${userId}`);
Logger.debug('Publishing Users', { meetingId, userId });
return Users.find(selector, options);
}

View File

@ -13,7 +13,7 @@ function videoStreams() {
const { meetingId, userId } = tokenValidation;
Logger.debug(`Publishing VideoStreams for ${meetingId} ${userId}`);
Logger.debug('Publishing VideoStreams', { meetingId, userId });
const selector = {
meetingId,

View File

@ -41,7 +41,9 @@ export default function handleVoiceCallStateEvent({ body }, meetingId) {
return Logger.error(`Update voice call state=${userId}: ${err}`);
}
return Logger.debug(`Update voice call state=${userId} meeting=${meetingId} clientSession=${clientSession} callState=${callState}`);
return Logger.debug('Update voice call', {
state: userId, meetingId, clientSession, callState,
});
};
return VoiceCallState.upsert(selector, modifier, cb);

View File

@ -13,7 +13,7 @@ function voiceCallStates() {
const { meetingId, userId } = tokenValidation;
Logger.debug(`Publishing VoiceCallStates for ${meetingId} ${userId}`);
Logger.debug('Publishing Voice Call States', { meetingId, userId });
return VoiceCallStates.find({ meetingId, userId });
}

View File

@ -48,7 +48,7 @@ export default function updateVoiceUser(meetingId, voiceUser) {
return Logger.error(`Update voiceUser=${intId}: ${err}`);
}
return Logger.debug(`Update voiceUser=${intId} meeting=${meetingId}`);
return Logger.debug('Update voiceUser', { voiceUser: intId, meetingId });
};
if (!voiceUser.talking) {

View File

@ -26,7 +26,7 @@ function voiceUser() {
}
});
Logger.debug(`Publishing Voice User for ${meetingId} ${requesterUserId}`);
Logger.debug('Publishing Voice User', { meetingId, requesterUserId });
this._session.socket.on('close', _.debounce(onCloseConnection, 100));
return VoiceUsers.find({ meetingId });

View File

@ -13,7 +13,7 @@ function whiteboardMultiUser() {
const { meetingId, userId } = tokenValidation;
Logger.debug(`Publishing WhiteboardMultiUser for ${meetingId} ${userId}`);
Logger.debug('Publishing WhiteboardMultiUser', { meetingId, userId });
return WhiteboardMultiUser.find({ meetingId });
}

View File

@ -27,17 +27,12 @@ const makeEnvelope = (channel, eventName, header, body, routing) => {
return JSON.stringify(envelope);
};
const makeDebugger = enabled => (message) => {
if (!enabled) return;
Logger.debug(`REDIS: ${message}`);
};
class MeetingMessageQueue {
constructor(eventEmitter, asyncMessages = [], debug = () => { }) {
constructor(eventEmitter, asyncMessages = [], redisDebugEnabled = false) {
this.asyncMessages = asyncMessages;
this.emitter = eventEmitter;
this.queue = new PowerQueue();
this.debug = debug;
this.redisDebugEnabled = redisDebugEnabled;
this.handleTask = this.handleTask.bind(this);
this.queue.taskHandler = this.handleTask;
@ -60,11 +55,13 @@ class MeetingMessageQueue {
const callNext = () => {
if (called) return;
this.debug(`${eventName} completed ${isAsync ? 'async' : 'sync'}`);
if (this.redisDebugEnabled) {
Logger.debug(`Redis: ${eventName} completed ${isAsync ? 'async' : 'sync'}`);
}
called = true;
const queueLength = this.queue.length();
if (queueLength > 100) {
Logger.error(`prev queue size=${queueLength} `);
if (queueLength > 0) {
Logger.warn(`Redis: MeetingMessageQueue for meetingId=${meetingId} has queue size=${queueLength} `);
}
next();
};
@ -75,7 +72,9 @@ class MeetingMessageQueue {
};
try {
this.debug(`${JSON.stringify(data.parsedMessage.core)} emitted`);
if (this.redisDebugEnabled) {
Logger.debug(`Redis: ${JSON.stringify(data.parsedMessage.core)} emitted`);
}
if (isAsync) {
callNext();
@ -129,7 +128,6 @@ class RedisPubSub {
this.handleSubscribe = this.handleSubscribe.bind(this);
this.handleMessage = this.handleMessage.bind(this);
this.debug = makeDebugger(this.config.debug);
}
init() {
@ -142,12 +140,14 @@ class RedisPubSub {
this.sub.psubscribe(channel);
});
this.debug(`Subscribed to '${channelsToSubscribe}'`);
if (this.redisDebugEnabled) {
Logger.debug(`Redis: Subscribed to '${channelsToSubscribe}'`);
}
}
updateConfig(config) {
this.config = Object.assign({}, this.config, config);
this.debug = makeDebugger(this.config.debug);
this.redisDebugEnabled = this.config.debug;
}
@ -178,21 +178,23 @@ class RedisPubSub {
if (eventName === 'CheckAlivePongSysMsg') {
return;
}
this.debug(`${eventName} skipped`);
if (this.redisDebugEnabled) {
Logger.debug(`Redis: ${eventName} skipped`);
}
return;
}
const queueId = meetingId || NO_MEETING_ID;
if (eventName === 'MeetingCreatedEvtMsg'){
if (eventName === 'MeetingCreatedEvtMsg') {
const newIntId = parsedMessage.core.body.props.meetingProp.intId;
const metadata = parsedMessage.core.body.props.metadataProp.metadata;
const instanceId = parseInt(metadata['bbb-meetinginstance']) || 1;
Logger.warn("MeetingCreatedEvtMsg received with meetingInstance: " + instanceId + " -- this is instance: " + this.instanceId);
Logger.warn(`MeetingCreatedEvtMsg received with meetingInstance: ${instanceId} -- this is instance: ${this.instanceId}`);
if (instanceId === this.instanceId){
this.mettingsQueues[newIntId] = new MeetingMessageQueue(this.emitter, async, this.debug);
if (instanceId === this.instanceId) {
this.mettingsQueues[newIntId] = new MeetingMessageQueue(this.emitter, async, this.redisDebugEnabled);
} else {
// Logger.error('THIS NODEJS ' + this.instanceId + ' IS **NOT** PROCESSING EVENTS FOR THIS MEETING ' + instanceId)
}
@ -206,11 +208,9 @@ class RedisPubSub {
parsedMessage,
});
}
//else {
//Logger.info("Skipping redis message for " + queueId);
//}
// else {
// Logger.info("Skipping redis message for " + queueId);
// }
}
destroyMeetingQueue(id) {
@ -279,4 +279,3 @@ Meteor.startup(() => {
});
export default RedisPubSubSingleton;

View File

@ -183,6 +183,7 @@ class AudioModal extends Component {
componentDidUpdate(prevProps) {
const { autoplayBlocked, closeModal } = this.props;
if (autoplayBlocked !== prevProps.autoplayBlocked) {
autoplayBlocked ? this.setState({ content: 'autoplayBlocked' }) : closeModal();
}
@ -246,13 +247,14 @@ class AudioModal extends Component {
const {
joinEchoTest,
isConnecting,
} = this.props;
const {
disableActions,
} = this.state;
if (disableActions) return;
if (disableActions && isConnecting) return;
this.setState({
hasError: false,

View File

@ -120,7 +120,7 @@ export class ArcPlayer extends Component {
getHostUrl() {
const { url } = this.props;
const m = url.match(MATCH_URL);
return m && 'https://' + m[1] + '.' + m[2];
return m && 'https://' + m[1] + m[2];
}
getEmbedUrl() {

View File

@ -0,0 +1,24 @@
import React from 'react';
import ModalSimple from '/imports/ui/components/modal/simple/component';
import { defineMessages, injectIntl } from 'react-intl';
import { withModalMounter } from '/imports/ui/components/modal/service';
import FallbackView from '../fallback-view/component';
const intlMessages = defineMessages({
ariaTitle: {
id: 'app.error.fallback.modal.ariaTitle',
description: 'title announced when fallback modal is showed',
},
});
const FallbackModal = ({ error, intl, mountModal }) => (
<ModalSimple
hideBorder
onRequestClose={() => mountModal(null)}
contentLabel={intl.formatMessage(intlMessages.ariaTitle)}
>
<FallbackView {...{ error }} />
</ModalSimple>
);
export default withModalMounter(injectIntl(FallbackModal));

View File

@ -1,24 +1,40 @@
import React from 'react';
import { defineMessages, injectIntl } from 'react-intl';
import PropTypes from 'prop-types';
import Button from '/imports/ui/components/button/component';
import { styles } from './styles';
const intlMessages = defineMessages({
title: {
id: 'app.error.fallback.presentation.title',
id: 'app.error.fallback.view.title',
description: 'title for presentation when fallback is showed',
},
description: {
id: 'app.error.fallback.presentation.description',
id: 'app.error.fallback.view.description',
description: 'description for presentation when fallback is showed',
},
reloadButton: {
id: 'app.error.fallback.presentation.reloadButton',
id: 'app.error.fallback.view.reloadButton',
description: 'Button label when fallback is showed',
},
});
const FallbackPresentation = ({ error, intl }) => (
const propTypes = {
intl: PropTypes.shape({
formatMessage: PropTypes.func.isRequired,
}).isRequired,
error: PropTypes.shape({
message: PropTypes.string.isRequired,
}),
};
const defaultProps = {
error: {
message: '',
},
};
const FallbackView = ({ error, intl }) => (
<div className={styles.background}>
<h1 className={styles.codeError}>
{intl.formatMessage(intlMessages.title)}
@ -42,4 +58,7 @@ const FallbackPresentation = ({ error, intl }) => (
</div>
);
export default injectIntl(FallbackPresentation);
FallbackView.propTypes = propTypes;
FallbackView.defaultProps = defaultProps;
export default injectIntl(FallbackView);

View File

@ -26,8 +26,9 @@ const intlMessages = defineMessages({
class TalkingIndicator extends PureComponent {
handleMuteUser(id) {
const { muteUser, amIModerator } = this.props;
if (!amIModerator) return;
const { muteUser, amIModerator, isBreakoutRoom } = this.props;
// only allow moderator muting anyone in non-breakout
if (!amIModerator || isBreakoutRoom) return;
muteUser(id);
}

View File

@ -5,6 +5,7 @@ import Auth from '/imports/ui/services/auth';
import { debounce } from 'lodash';
import TalkingIndicator from './component';
import { makeCall } from '/imports/ui/services/api';
import { meetingIsBreakout } from '/imports/ui/components/app/service';
import Service from './service';
const APP_CONFIG = Meteor.settings.public.app;
@ -60,5 +61,6 @@ export default withTracker(() => {
talkers,
muteUser: id => debounce(muteUser(id), 500, { leading: true, trailing: false }),
openPanel: Session.get('openPanel'),
isBreakoutRoom: meetingIsBreakout(),
};
})(TalkingIndicatorContainer);

View File

@ -1,8 +1,13 @@
import { makeCall } from '/imports/ui/services/api';
import Polls from '/imports/api/polls';
import { debounce } from 'lodash';
const MAX_CHAR_LENGTH = 5;
const handleVote = (pollId, answerId) => {
makeCall('publishVote', pollId, answerId.id);
};
const mapPolls = () => {
const poll = Polls.findOne({});
if (!poll) {
@ -30,9 +35,7 @@ const mapPolls = () => {
},
pollExists: true,
amIRequester,
handleVote(pollId, answerId) {
makeCall('publishVote', pollId, answerId.id);
},
handleVote: debounce(handleVote, 500, { leading: true, trailing: false }),
};
};

View File

@ -2,7 +2,7 @@ import React from 'react';
import PropTypes from 'prop-types';
import { withTracker } from 'meteor/react-meteor-data';
import ErrorBoundary from '/imports/ui/components/error-boundary/component';
import FallbackPresentation from '/imports/ui/components/fallback-errors/fallback-presentation/component';
import FallbackView from '/imports/ui/components/fallback-errors/fallback-view/component';
import PresentationPodService from './service';
import PresentationPods from './component';
@ -12,7 +12,7 @@ import PresentationPods from './component';
const PresentationPodsContainer = ({ presentationPodIds, ...props }) => {
if (presentationPodIds && presentationPodIds.length > 0) {
return (
<ErrorBoundary Fallback={FallbackPresentation}>
<ErrorBoundary Fallback={FallbackView}>
<PresentationPods presentationPodIds={presentationPodIds} {...props} />
</ErrorBoundary>
);
@ -22,7 +22,7 @@ const PresentationPodsContainer = ({ presentationPodIds, ...props }) => {
};
export default withTracker(() => ({
presentationPodIds: PresentationPodService.getPresentationPodIds()
presentationPodIds: PresentationPodService.getPresentationPodIds(),
}))(PresentationPodsContainer);
PresentationPodsContainer.propTypes = {

View File

@ -715,7 +715,7 @@ class PresentationUploader extends Component {
};
const hideRemove = this.isDefault(item);
const formattedDownloadableLabel = item.isDownloadable
const formattedDownloadableLabel = !item.isDownloadable
? intl.formatMessage(intlMessages.isDownloadable)
: intl.formatMessage(intlMessages.isNotDownloadable);

View File

@ -1,13 +1,19 @@
import React from 'react';
import { Meteor } from 'meteor/meteor';
import { withTracker } from 'meteor/react-meteor-data';
import ErrorBoundary from '/imports/ui/components/error-boundary/component';
import FallbackModal from '/imports/ui/components/fallback-errors/fallback-modal/component';
import Service from './service';
import PresentationService from '../service';
import Uploader from './component';
import PresentationUploader from './component';
const PRESENTATION_CONFIG = Meteor.settings.public.presentation;
const UploaderContainer = props => <Uploader {...props} />;
const PresentationUploaderContainer = props => (
<ErrorBoundary Fallback={() => <FallbackModal />}>
<PresentationUploader {...props} />
</ErrorBoundary>
);
export default withTracker(() => {
const currentPresentations = Service.getPresentations();
@ -36,4 +42,4 @@ export default withTracker(() => {
selectedToBeNextCurrent: Session.get('selectedToBeNextCurrent') || null,
isPresenter: PresentationService.isPresenter('DEFAULT_PRESENTATION_POD'),
};
})(UploaderContainer);
})(PresentationUploaderContainer);

View File

@ -6,6 +6,7 @@ import Auth from '/imports/ui/services/auth';
import { Session } from 'meteor/session';
import logger from '/imports/startup/client/logger';
const ICE_GATHERING_CHECK_ENABLED = Meteor.settings.public.media.recvonlyIceGatheringCheck;
const getSessionToken = () => Auth.sessionToken;
export async function getIceServersList() {
@ -43,13 +44,13 @@ export function canGenerateIceCandidates() {
pc.onicegatheringstatechange = function (e) {
if (e.currentTarget.iceGatheringState === 'complete' && countIceCandidates === 0) {
logger.warn({ logCode: 'no_valid_candidate' }, 'No useful ICE candidate found. Will request gUM permission.');
reject();
reject(new Error('No valid candidate'));
}
};
setTimeout(() => {
pc.close();
if (!countIceCandidates) reject();
if (!countIceCandidates) reject(new Error('Gathering check timeout'));
}, 5000);
const p = pc.createOffer({ offerToReceiveVideo: true });
@ -66,11 +67,20 @@ export function canGenerateIceCandidates() {
* generate at least srflx candidates.
* This is a workaround due to a behaviour some browsers display (mainly Safari)
* where they won't generate srflx or relay candidates if no gUM permission is
* given. Since our media servers aren't able to make it work by prflx
* candidates, we need to do this.
* given.
*
*
* UPDATE:
* This used to be valid when Kurento wasn't treating prflx candidates properly.
* It is now, so this workaround is being revisited. I've put it under a flag
* so that we can field trial it disabled and gauge the impact of removing it.
* Hopelly we can get rid of it.
*
* prlanzarin 11-11-20
*/
export function tryGenerateIceCandidates() {
return new Promise((resolve, reject) => {
if (!ICE_GATHERING_CHECK_ENABLED) return resolve();
canGenerateIceCandidates().then(() => {
resolve();
}).catch(() => {

View File

@ -3836,6 +3836,12 @@
"minimatch": "~3.0.2"
}
},
"glur": {
"version": "1.1.2",
"resolved": "https://registry.npmjs.org/glur/-/glur-1.1.2.tgz",
"integrity": "sha1-8g6jbbEDv8KSNDkh8fkeg8NGdok=",
"dev": true
},
"good-listener": {
"version": "1.2.2",
"resolved": "https://registry.npmjs.org/good-listener/-/good-listener-1.2.2.tgz",
@ -5454,6 +5460,56 @@
}
}
},
"jest-image-snapshot": {
"version": "4.2.0",
"resolved": "https://registry.npmjs.org/jest-image-snapshot/-/jest-image-snapshot-4.2.0.tgz",
"integrity": "sha512-6aAqv2wtfOgxiJeBayBCqHo1zX+A12SUNNzo7rIxiXh6W6xYVu8QyHWkada8HeRi+QUTHddp0O0Xa6kmQr+xbQ==",
"dev": true,
"requires": {
"chalk": "^1.1.3",
"get-stdin": "^5.0.1",
"glur": "^1.1.2",
"lodash": "^4.17.4",
"mkdirp": "^0.5.1",
"pixelmatch": "^5.1.0",
"pngjs": "^3.4.0",
"rimraf": "^2.6.2",
"ssim.js": "^3.1.1"
},
"dependencies": {
"ansi-styles": {
"version": "2.2.1",
"resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-2.2.1.tgz",
"integrity": "sha1-tDLdM1i2NM914eRmQ2gkBTPB3b4=",
"dev": true
},
"chalk": {
"version": "1.1.3",
"resolved": "https://registry.npmjs.org/chalk/-/chalk-1.1.3.tgz",
"integrity": "sha1-qBFcVeSnAv5NFQq9OHKCKn4J/Jg=",
"dev": true,
"requires": {
"ansi-styles": "^2.2.1",
"escape-string-regexp": "^1.0.2",
"has-ansi": "^2.0.0",
"strip-ansi": "^3.0.0",
"supports-color": "^2.0.0"
}
},
"get-stdin": {
"version": "5.0.1",
"resolved": "https://registry.npmjs.org/get-stdin/-/get-stdin-5.0.1.tgz",
"integrity": "sha1-Ei4WFZHiH/TFJTAwVpPyDmOTo5g=",
"dev": true
},
"supports-color": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/supports-color/-/supports-color-2.0.0.tgz",
"integrity": "sha1-U10EXOa2Nj+kARcIRimZXp3zJMc=",
"dev": true
}
}
},
"jest-jasmine2": {
"version": "25.5.4",
"resolved": "https://registry.npmjs.org/jest-jasmine2/-/jest-jasmine2-25.5.4.tgz",
@ -8437,6 +8493,23 @@
"node-modules-regexp": "^1.0.0"
}
},
"pixelmatch": {
"version": "5.2.1",
"resolved": "https://registry.npmjs.org/pixelmatch/-/pixelmatch-5.2.1.tgz",
"integrity": "sha512-WjcAdYSnKrrdDdqTcVEY7aB7UhhwjYQKYhHiBXdJef0MOaQeYpUdQ+iVyBLa5YBKS8MPVPPMX7rpOByISLpeEQ==",
"dev": true,
"requires": {
"pngjs": "^4.0.1"
},
"dependencies": {
"pngjs": {
"version": "4.0.1",
"resolved": "https://registry.npmjs.org/pngjs/-/pngjs-4.0.1.tgz",
"integrity": "sha512-rf5+2/ioHeQxR6IxuYNYGFytUyG3lma/WW1nsmjeHlWwtb2aByla6dkVc8pmJ9nplzkTA0q2xx7mMWrOTqT4Gg==",
"dev": true
}
}
},
"pkg-dir": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/pkg-dir/-/pkg-dir-2.0.0.tgz",
@ -8475,6 +8548,12 @@
"integrity": "sha512-2qHaIQr2VLRFoxe2nASzsV6ef4yOOH+Fi9FBOVH6cqeSgUnoyySPZkxzLuzd+RYOQTRpROA0ztTMqxROKSb/nA==",
"dev": true
},
"pngjs": {
"version": "3.4.0",
"resolved": "https://registry.npmjs.org/pngjs/-/pngjs-3.4.0.tgz",
"integrity": "sha512-NCrCHhWmnQklfH4MtJMRjZ2a8c80qXeMlQMv2uVp9ISJMTt562SbGd6n2oq0PaPgKm7Z6pL9E2UlLIhC+SHL3w==",
"dev": true
},
"popper.js": {
"version": "1.16.1",
"resolved": "https://registry.npmjs.org/popper.js/-/popper.js-1.16.1.tgz",
@ -9995,6 +10074,12 @@
"tweetnacl": "~0.14.0"
}
},
"ssim.js": {
"version": "3.5.0",
"resolved": "https://registry.npmjs.org/ssim.js/-/ssim.js-3.5.0.tgz",
"integrity": "sha512-Aj6Jl2z6oDmgYFFbQqK7fght19bXdOxY7Tj03nF+03M9gCBAjeIiO8/PlEGMfKDwYpw4q6iBqVq2YuREorGg/g==",
"dev": true
},
"stack-trace": {
"version": "0.0.10",
"resolved": "https://registry.npmjs.org/stack-trace/-/stack-trace-0.0.10.tgz",

View File

@ -303,6 +303,7 @@ public:
stunTurnServersFetchAddress: "/bigbluebutton/api/stuns"
cacheStunTurnServers: true
fallbackStunServer: ''
recvonlyIceGatheringCheck: true
mediaTag: "#remote-media"
callTransferTimeout: 5000
callHangupTimeout: 2000
@ -328,6 +329,12 @@ public:
- danger
- critical
help: STATS_HELP_URL
#Timeout (ms) for gathering ICE candidates. When this timeout expires
#the SDP is sent to the server with the candidates the browser gathered
#so far. Increasing this value might help avoiding 1004 error when
#user activates microphone.
iceGatheringTimeout: 5000
sipjsHackViaWs: false
presentation:
allowDownloadable: true
defaultPresentationFile: default.pdf

View File

@ -3831,6 +3831,7 @@ class OutgoingRequestMessage {
fromTag: "",
forceRport: false,
hackViaTcp: false,
hackViaWs: false,
optionTags: ["outbound"],
routeSet: [],
userAgentString: "sip.js",
@ -3937,6 +3938,8 @@ class OutgoingRequestMessage {
// FIXME: Hack
if (this.options.hackViaTcp) {
transport = "TCP";
} else if (this.options.hackViaWs) {
transport = "WS";
}
let via = "SIP/2.0/" + transport;
via += " " + this.options.viaHost + ";branch=" + branch;
@ -9812,6 +9815,7 @@ class UserAgentCore {
const fromDisplayName = this.configuration.displayName;
const forceRport = this.configuration.viaForceRport;
const hackViaTcp = this.configuration.hackViaTcp;
const hackViaWs = this.configuration.hackViaWs;
const optionTags = this.configuration.supportedOptionTags.slice();
if (method === _messages__WEBPACK_IMPORTED_MODULE_0__["C"].REGISTER) {
optionTags.push("path", "gruu");
@ -9827,6 +9831,7 @@ class UserAgentCore {
forceRport,
fromDisplayName,
hackViaTcp,
hackViaWs,
optionTags,
routeSet,
userAgentString,
@ -17303,6 +17308,7 @@ class UserAgent {
hackAllowUnregisteredOptionTags: false,
hackIpInContact: false,
hackViaTcp: false,
hackViaWs: false,
hackWssInTransport: false,
logBuiltinEnabled: true,
logConfiguration: true,
@ -17657,6 +17663,7 @@ class UserAgent {
displayName: this.options.displayName,
loggerFactory: this.loggerFactory,
hackViaTcp: this.options.hackViaTcp,
hackViaWs: this.options.hackViaWs,
routeSet: this.options.preloadedRouteSet,
supportedOptionTags,
supportedOptionTagsResponse,

View File

@ -93,6 +93,11 @@ class ApiController {
log.debug CONTROLLER_NAME + "#${API_CALL}"
log.debug request.getParameterMap().toMapString()
//sanitizeInput
params.each {
key, value -> params[key] = sanitizeInput(value)
}
// BEGIN - backward compatibility
if (StringUtils.isEmpty(params.checksum)) {
invalid("checksumError", "You did not pass the checksum security check")
@ -197,6 +202,11 @@ class ApiController {
log.debug CONTROLLER_NAME + "#${API_CALL}"
ApiErrors errors = new ApiErrors()
//sanitizeInput
params.each {
key, value -> params[key] = sanitizeInput(value)
}
// BEGIN - backward compatibility
if (StringUtils.isEmpty(params.checksum)) {
invalid("checksumError", "You did not pass the checksum security check", REDIRECT_RESPONSE)
@ -266,7 +276,6 @@ class ApiController {
// Do we have a name for the user joining? If none, complain.
if (!StringUtils.isEmpty(params.fullName)) {
params.fullName = StringUtils.strip(params.fullName);
if (StringUtils.isEmpty(params.fullName)) {
errors.missingParamError("fullName");
}
@ -583,6 +592,11 @@ class ApiController {
String API_CALL = 'isMeetingRunning'
log.debug CONTROLLER_NAME + "#${API_CALL}"
//sanitizeInput
params.each {
key, value -> params[key] = sanitizeInput(value)
}
// BEGIN - backward compatibility
if (StringUtils.isEmpty(params.checksum)) {
invalid("checksumError", "You did not pass the checksum security check")
@ -659,9 +673,13 @@ class ApiController {
************************************/
def end = {
String API_CALL = "end"
log.debug CONTROLLER_NAME + "#${API_CALL}"
//sanitizeInput
params.each {
key, value -> params[key] = sanitizeInput(value)
}
// BEGIN - backward compatibility
if (StringUtils.isEmpty(params.checksum)) {
invalid("checksumError", "You did not pass the checksum security check")
@ -784,6 +802,11 @@ class ApiController {
String API_CALL = "getMeetingInfo"
log.debug CONTROLLER_NAME + "#${API_CALL}"
//sanitizeInput
params.each {
key, value -> params[key] = sanitizeInput(value)
}
// BEGIN - backward compatibility
if (StringUtils.isEmpty(params.checksum)) {
invalid("checksumError", "You did not pass the checksum security check")
@ -867,6 +890,11 @@ class ApiController {
String API_CALL = "getMeetings"
log.debug CONTROLLER_NAME + "#${API_CALL}"
//sanitizeInput
params.each {
key, value -> params[key] = sanitizeInput(value)
}
// BEGIN - backward compatibility
if (StringUtils.isEmpty(params.checksum)) {
invalid("checksumError", "You did not pass the checksum security check")
@ -925,6 +953,11 @@ class ApiController {
String API_CALL = "getSessions"
log.debug CONTROLLER_NAME + "#${API_CALL}"
//sanitizeInput
params.each {
key, value -> params[key] = sanitizeInput(value)
}
// BEGIN - backward compatibility
if (StringUtils.isEmpty(params.checksum)) {
invalid("checksumError", "You did not pass the checksum security check")
@ -1000,6 +1033,11 @@ class ApiController {
String API_CALL = "setPollXML"
log.debug CONTROLLER_NAME + "#${API_CALL}"
//sanitizeInput
params.each {
key, value -> params[key] = sanitizeInput(value)
}
if (StringUtils.isEmpty(params.checksum)) {
invalid("checksumError", "You did not pass the checksum security check")
return
@ -1086,6 +1124,11 @@ class ApiController {
String API_CALL = "setConfigXML"
log.debug CONTROLLER_NAME + "#${API_CALL}"
//sanitizeInput
params.each {
key, value -> params[key] = sanitizeInput(value)
}
if (StringUtils.isEmpty(params.checksum)) {
invalid("checksumError", "You did not pass the checksum security check")
return
@ -1165,6 +1208,11 @@ class ApiController {
String API_CALL = "getDefaultConfigXML"
ApiErrors errors = new ApiErrors();
//sanitizeInput
params.each {
key, value -> params[key] = sanitizeInput(value)
}
// BEGIN - backward compatibility
if (StringUtils.isEmpty(params.checksum)) {
invalid("checksumError", "You did not pass the checksum security check")
@ -1204,6 +1252,11 @@ class ApiController {
String API_CALL = 'configXML'
log.debug CONTROLLER_NAME + "#${API_CALL}"
//sanitizeInput
params.each {
key, value -> params[key] = sanitizeInput(value)
}
String logoutUrl = paramsProcessorUtil.getDefaultLogoutUrl()
boolean reject = false
String sessionToken = sanitizeSessionToken(params.sessionToken)
@ -1251,6 +1304,12 @@ class ApiController {
def guestWaitHandler = {
String API_CALL = 'guestWait'
log.debug CONTROLLER_NAME + "#${API_CALL}"
//sanitizeInput
params.each {
key, value -> params[key] = sanitizeInput(value)
}
ApiErrors errors = new ApiErrors()
boolean reject = false;
String sessionToken = sanitizeSessionToken(params.sessionToken)
@ -1395,13 +1454,21 @@ class ApiController {
* ENTER API
***********************************************/
def enter = {
String API_CALL = 'enter'
log.debug CONTROLLER_NAME + "#${API_CALL}"
//sanitizeInput
params.each {
key, value -> params[key] = sanitizeInput(value)
}
boolean reject = false;
String sessionToken = sanitizeSessionToken(params.sessionToken)
UserSession us = getUserSession(sessionToken);
Meeting meeting = null;
String respMessage = "Session " + sessionToken + " not found."
String respMessage = "Session not found."
if (!hasValidSession(sessionToken)) {
reject = true;
@ -1409,7 +1476,7 @@ class ApiController {
meeting = meetingService.getMeeting(us.meetingID);
if (meeting == null || meeting.isForciblyEnded()) {
reject = true
respMessage = "Meeting not found or ended for session " + sessionToken + "."
respMessage = "Meeting not found or ended for session."
} else {
if (hasReachedMaxParticipants(meeting, us)) {
reject = true;
@ -1419,7 +1486,7 @@ class ApiController {
}
}
if (us.guestStatus.equals(GuestPolicy.DENY)) {
respMessage = "User denied for user with session " + sessionToken + "."
respMessage = "User denied for user with session."
reject = true
}
}
@ -1439,6 +1506,7 @@ class ApiController {
builder.response {
returncode RESP_CODE_FAILED
message respMessage
sessionToken
logoutURL logoutUrl
}
render(contentType: "application/json", text: builder.toPrettyString())
@ -1537,6 +1605,14 @@ class ApiController {
* STUN/TURN API
***********************************************/
def stuns = {
String API_CALL = 'stuns'
log.debug CONTROLLER_NAME + "#${API_CALL}"
//sanitizeInput
params.each {
key, value -> params[key] = sanitizeInput(value)
}
boolean reject = false;
String sessionToken = sanitizeSessionToken(params.sessionToken)
@ -1608,6 +1684,13 @@ class ApiController {
* SIGNOUT API
*************************************************/
def signOut = {
String API_CALL = 'signOut'
log.debug CONTROLLER_NAME + "#${API_CALL}"
//sanitizeInput
params.each {
key, value -> params[key] = sanitizeInput(value)
}
String sessionToken = sanitizeSessionToken(params.sessionToken)
@ -1654,6 +1737,11 @@ class ApiController {
String API_CALL = "getRecordings"
log.debug CONTROLLER_NAME + "#${API_CALL}"
//sanitizeInput
params.each {
key, value -> params[key] = sanitizeInput(value)
}
// BEGIN - backward compatibility
if (StringUtils.isEmpty(params.checksum)) {
invalid("checksumError", "You did not pass the checksum security check")
@ -1728,6 +1816,11 @@ class ApiController {
String API_CALL = "publishRecordings"
log.debug CONTROLLER_NAME + "#${API_CALL}"
//sanitizeInput
params.each {
key, value -> params[key] = sanitizeInput(value)
}
// BEGIN - backward compatibility
if (StringUtils.isEmpty(params.checksum)) {
invalid("checksumError", "You did not pass the checksum security check")
@ -1809,6 +1902,11 @@ class ApiController {
String API_CALL = "deleteRecordings"
log.debug CONTROLLER_NAME + "#${API_CALL}"
//sanitizeInput
params.each {
key, value -> params[key] = sanitizeInput(value)
}
// BEGIN - backward compatibility
if (StringUtils.isEmpty(params.checksum)) {
invalid("checksumError", "You did not pass the checksum security check")
@ -1879,6 +1977,11 @@ class ApiController {
String API_CALL = "updateRecordings"
log.debug CONTROLLER_NAME + "#${API_CALL}"
//sanitizeInput
params.each {
key, value -> params[key] = sanitizeInput(value)
}
// BEGIN - backward compatibility
if (StringUtils.isEmpty(params.checksum)) {
invalid("checksumError", "You did not pass the checksum security check")
@ -1950,13 +2053,17 @@ class ApiController {
def uploadDocuments(conf) { //
log.debug("ApiController#uploadDocuments(${conf.getInternalId()})");
//sanitizeInput
params.each {
key, value -> params[key] = sanitizeInput(value)
}
String requestBody = request.inputStream == null ? null : request.inputStream.text;
requestBody = StringUtils.isEmpty(requestBody) ? null : requestBody;
if (requestBody == null) {
downloadAndProcessDocument(presentationService.defaultUploadedPresentation, conf.getInternalId(), true /* default presentation */, '');
} else {
log.debug "Request body: \n" + requestBody;
def xml = new XmlSlurper().parseText(requestBody);
xml.children().each { module ->
log.debug("module config found: [${module.@name}]");
@ -2143,6 +2250,16 @@ class ApiController {
return us
}
private def sanitizeInput (input) {
if(input == null)
return
if(!("java.lang.String".equals(input.getClass().getName())))
return input
StringUtils.strip(input.replaceAll("\\p{Cntrl}", ""));
}
def sanitizeSessionToken(param) {
if (param == null) {
log.info("sanitizeSessionToken: token is null")