diff --git a/.github/PULL_REQUEST_TEMPLATE.md b/.github/PULL_REQUEST_TEMPLATE.md index 9547614bf4..96969e2b21 100644 --- a/.github/PULL_REQUEST_TEMPLATE.md +++ b/.github/PULL_REQUEST_TEMPLATE.md @@ -20,9 +20,11 @@ HOW TO WRITE A GOOD PULL REQUEST? ### Closes Issue(s) + +Closes # -closes #... - ### Motivation diff --git a/akka-bbb-apps/src/main/scala/org/bigbluebutton/core/apps/users/ValidateAuthTokenReqMsgHdlr.scala b/akka-bbb-apps/src/main/scala/org/bigbluebutton/core/apps/users/ValidateAuthTokenReqMsgHdlr.scala index 904c5ebaa3..9d818b367f 100755 --- a/akka-bbb-apps/src/main/scala/org/bigbluebutton/core/apps/users/ValidateAuthTokenReqMsgHdlr.scala +++ b/akka-bbb-apps/src/main/scala/org/bigbluebutton/core/apps/users/ValidateAuthTokenReqMsgHdlr.scala @@ -31,8 +31,8 @@ trait ValidateAuthTokenReqMsgHdlr extends HandlerHelpers { userValidated(u, state) } else { if (u.banned) { - failReason = "Ejected user rejoining" - failReasonCode = EjectReasonCode.EJECTED_USER_REJOINING + failReason = "Banned user rejoining" + failReasonCode = EjectReasonCode.BANNED_USER_REJOINING } else if (u.loggedOut) { failReason = "User had logged out" failReasonCode = EjectReasonCode.USER_LOGGED_OUT @@ -77,7 +77,8 @@ trait ValidateAuthTokenReqMsgHdlr extends HandlerHelpers { reasonCode: String, state: MeetingState2x ): MeetingState2x = { - val event = MsgBuilder.buildValidateAuthTokenRespMsg(meetingId, userId, authToken, valid, waitForApproval, 0, 0, Option.apply(reason)) + val event = MsgBuilder.buildValidateAuthTokenRespMsg(meetingId, userId, authToken, valid, waitForApproval, 0, + 0, reasonCode, reason) outGW.send(event) // send a system message to force disconnection @@ -88,8 +89,10 @@ trait ValidateAuthTokenReqMsgHdlr extends HandlerHelpers { } def sendValidateAuthTokenRespMsg(meetingId: String, userId: String, authToken: String, - valid: Boolean, waitForApproval: Boolean, registeredOn: Long, authTokenValidatedOn: Long, reason: Option[String] = None): Unit = { - val event = MsgBuilder.buildValidateAuthTokenRespMsg(meetingId, userId, authToken, valid, waitForApproval, registeredOn, authTokenValidatedOn, reason) + valid: Boolean, waitForApproval: Boolean, registeredOn: Long, authTokenValidatedOn: Long, + reasonCode: String = EjectReasonCode.NOT_EJECT, reason: String = "User not ejected"): Unit = { + val event = MsgBuilder.buildValidateAuthTokenRespMsg(meetingId, userId, authToken, valid, waitForApproval, registeredOn, + authTokenValidatedOn, reasonCode, reason) outGW.send(event) } diff --git a/akka-bbb-apps/src/main/scala/org/bigbluebutton/core/models/Users2x.scala b/akka-bbb-apps/src/main/scala/org/bigbluebutton/core/models/Users2x.scala index 19ca474674..88750d7dfd 100755 --- a/akka-bbb-apps/src/main/scala/org/bigbluebutton/core/models/Users2x.scala +++ b/akka-bbb-apps/src/main/scala/org/bigbluebutton/core/models/Users2x.scala @@ -300,12 +300,13 @@ object SystemUser { } object EjectReasonCode { + val NOT_EJECT = "not_eject_reason" val DUPLICATE_USER = "duplicate_user_in_meeting_eject_reason" val PERMISSION_FAILED = "not_enough_permission_eject_reason" val EJECT_USER = "user_requested_eject_reason" val SYSTEM_EJECT_USER = "system_requested_eject_reason" val VALIDATE_TOKEN = "validate_token_failed_eject_reason" val USER_INACTIVITY = "user_inactivity_eject_reason" - val EJECTED_USER_REJOINING = "ejected_user_rejoining_reason" + val BANNED_USER_REJOINING = "banned_user_rejoining_reason" val USER_LOGGED_OUT = "user_logged_out_reason" } diff --git a/akka-bbb-apps/src/main/scala/org/bigbluebutton/core2/message/senders/MsgBuilder.scala b/akka-bbb-apps/src/main/scala/org/bigbluebutton/core2/message/senders/MsgBuilder.scala index dd478beb9c..fc928d2c89 100755 --- a/akka-bbb-apps/src/main/scala/org/bigbluebutton/core2/message/senders/MsgBuilder.scala +++ b/akka-bbb-apps/src/main/scala/org/bigbluebutton/core2/message/senders/MsgBuilder.scala @@ -85,11 +85,13 @@ object MsgBuilder { } def buildValidateAuthTokenRespMsg(meetingId: String, userId: String, authToken: String, - valid: Boolean, waitForApproval: Boolean, registeredOn: Long, authTokenValidatedOn: Long, reason: Option[String]): BbbCommonEnvCoreMsg = { + valid: Boolean, waitForApproval: Boolean, registeredOn: Long, authTokenValidatedOn: Long, + reasonCode: String, reason: String): BbbCommonEnvCoreMsg = { val routing = Routing.addMsgToClientRouting(MessageTypes.DIRECT, meetingId, userId) val envelope = BbbCoreEnvelope(ValidateAuthTokenRespMsg.NAME, routing) val header = BbbClientMsgHeader(ValidateAuthTokenRespMsg.NAME, meetingId, userId) - val body = ValidateAuthTokenRespMsgBody(userId, authToken, valid, waitForApproval, registeredOn, authTokenValidatedOn, reason) + val body = ValidateAuthTokenRespMsgBody(userId, authToken, valid, waitForApproval, registeredOn, authTokenValidatedOn, + reasonCode, reason) val event = ValidateAuthTokenRespMsg(header, body) BbbCommonEnvCoreMsg(envelope, event) } diff --git a/akka-bbb-apps/src/main/scala/org/bigbluebutton/core2/message/senders/ValidateAuthTokenRespMsgSender.scala b/akka-bbb-apps/src/main/scala/org/bigbluebutton/core2/message/senders/ValidateAuthTokenRespMsgSender.scala index 2e2fb20761..51eb640da2 100755 --- a/akka-bbb-apps/src/main/scala/org/bigbluebutton/core2/message/senders/ValidateAuthTokenRespMsgSender.scala +++ b/akka-bbb-apps/src/main/scala/org/bigbluebutton/core2/message/senders/ValidateAuthTokenRespMsgSender.scala @@ -6,11 +6,11 @@ import org.bigbluebutton.core.running.OutMsgRouter object ValidateAuthTokenRespMsgSender { def send(outGW: OutMsgRouter, meetingId: String, userId: String, authToken: String, - valid: Boolean, waitForApproval: Boolean, registeredOn: Long, authTokenValidatedOn: Long, reason: Option[String]): Unit = { + valid: Boolean, waitForApproval: Boolean, registeredOn: Long, authTokenValidatedOn: Long, reasonCode: String, reason: String): Unit = { val routing = Routing.addMsgToClientRouting(MessageTypes.DIRECT, meetingId, userId) val envelope = BbbCoreEnvelope(ValidateAuthTokenRespMsg.NAME, routing) val header = BbbClientMsgHeader(ValidateAuthTokenRespMsg.NAME, meetingId, userId) - val body = ValidateAuthTokenRespMsgBody(userId, authToken, valid, waitForApproval, registeredOn, authTokenValidatedOn, reason) + val body = ValidateAuthTokenRespMsgBody(userId, authToken, valid, waitForApproval, registeredOn, authTokenValidatedOn, reasonCode, reason) val event = ValidateAuthTokenRespMsg(header, body) val msgEvent = BbbCommonEnvCoreMsg(envelope, event) outGW.send(msgEvent) diff --git a/bbb-common-message/src/main/scala/org/bigbluebutton/common2/msgs/UsersMgs.scala b/bbb-common-message/src/main/scala/org/bigbluebutton/common2/msgs/UsersMsgs.scala similarity index 97% rename from bbb-common-message/src/main/scala/org/bigbluebutton/common2/msgs/UsersMgs.scala rename to bbb-common-message/src/main/scala/org/bigbluebutton/common2/msgs/UsersMsgs.scala index 60d896f81e..e163c73be8 100755 --- a/bbb-common-message/src/main/scala/org/bigbluebutton/common2/msgs/UsersMgs.scala +++ b/bbb-common-message/src/main/scala/org/bigbluebutton/common2/msgs/UsersMsgs.scala @@ -60,7 +60,7 @@ case class ValidateAuthTokenRespMsg( body: ValidateAuthTokenRespMsgBody ) extends BbbCoreMsg case class ValidateAuthTokenRespMsgBody(userId: String, authToken: String, valid: Boolean, waitForApproval: Boolean, - registeredOn: Long, authTokenValidatedOn: Long, reason: Option[String]) + registeredOn: Long, authTokenValidatedOn: Long, reasonCode: String, reason: String) object UserLeftMeetingEvtMsg { val NAME = "UserLeftMeetingEvtMsg" diff --git a/bigbluebutton-config/bin/bbb-conf b/bigbluebutton-config/bin/bbb-conf index 4010cd3ffa..e56eb90489 100755 --- a/bigbluebutton-config/bin/bbb-conf +++ b/bigbluebutton-config/bin/bbb-conf @@ -96,7 +96,7 @@ source /etc/bigbluebutton/bigbluebutton-release # Figure out our environment (Debian vs. CentOS) # -if [ -f /etc/centos-release ]; then +if [ -f /etc/centos-release ] || [ -f /etc/system-release ]; then DISTRIB_ID=centos TOMCAT_USER=tomcat TOMCAT_DIR=/var/lib/$TOMCAT_USER @@ -822,7 +822,7 @@ check_configuration() { echo echo "# Warning: No firewall detected. Recommend using setting up a firewall for your server" echo "#" - echo "# https://docs.bigbluebutton.org/2.2/troubleshooting.html#freeswitch-using-default-stun-server" + echo "# https://docs.bigbluebutton.org/2.2/customize.html#setup-a-firewall" echo "#" echo fi diff --git a/bigbluebutton-config/cron.hourly/bbb-restart-kms b/bigbluebutton-config/cron.hourly/bbb-restart-kms index a4bec48bce..b446340b86 100644 --- a/bigbluebutton-config/cron.hourly/bbb-restart-kms +++ b/bigbluebutton-config/cron.hourly/bbb-restart-kms @@ -9,7 +9,7 @@ if [ ! -f /var/tmp/bbb-kms-last-restart.txt ]; then exit fi -users=$(mongo --quiet mongodb://127.0.1.1:27017/meteor --eval "db.users.count({connectionStatus: 'online'})") +users=$(mongo --quiet mongodb://127.0.1.1:27017/meteor --eval "db.users.count()") if [ "$users" -eq 0 ]; then diff --git a/bigbluebutton-html5/imports/api/connection-status/server/methods.js b/bigbluebutton-html5/imports/api/connection-status/server/methods.js index 14b3131b80..c5ae82db19 100644 --- a/bigbluebutton-html5/imports/api/connection-status/server/methods.js +++ b/bigbluebutton-html5/imports/api/connection-status/server/methods.js @@ -1,6 +1,8 @@ import { Meteor } from 'meteor/meteor'; import addConnectionStatus from './methods/addConnectionStatus'; +import voidConnection from './methods/voidConnection'; Meteor.methods({ addConnectionStatus, + voidConnection, }); diff --git a/bigbluebutton-html5/imports/api/connection-status/server/methods/addConnectionStatus.js b/bigbluebutton-html5/imports/api/connection-status/server/methods/addConnectionStatus.js index 9d3408555d..9e58b49f7f 100644 --- a/bigbluebutton-html5/imports/api/connection-status/server/methods/addConnectionStatus.js +++ b/bigbluebutton-html5/imports/api/connection-status/server/methods/addConnectionStatus.js @@ -7,5 +7,8 @@ export default function addConnectionStatus(level) { const { meetingId, requesterUserId } = extractCredentials(this.userId); + check(meetingId, String); + check(requesterUserId, String); + updateConnectionStatus(meetingId, requesterUserId, level); } diff --git a/bigbluebutton-html5/imports/api/connection-status/server/methods/voidConnection.js b/bigbluebutton-html5/imports/api/connection-status/server/methods/voidConnection.js new file mode 100644 index 0000000000..85297dbab7 --- /dev/null +++ b/bigbluebutton-html5/imports/api/connection-status/server/methods/voidConnection.js @@ -0,0 +1,4 @@ +// Round-trip time helper +export default function voidConnection() { + return 0; +} diff --git a/bigbluebutton-html5/imports/api/connection-status/server/modifiers/clearConnectionStatus.js b/bigbluebutton-html5/imports/api/connection-status/server/modifiers/clearConnectionStatus.js index f37d5a0a2a..74224fd9ce 100644 --- a/bigbluebutton-html5/imports/api/connection-status/server/modifiers/clearConnectionStatus.js +++ b/bigbluebutton-html5/imports/api/connection-status/server/modifiers/clearConnectionStatus.js @@ -2,13 +2,25 @@ import ConnectionStatus from '/imports/api/connection-status'; import Logger from '/imports/startup/server/logger'; export default function clearConnectionStatus(meetingId) { + const selector = {}; + if (meetingId) { - return ConnectionStatus.remove({ meetingId }, () => { - Logger.info(`Cleared ConnectionStatus (${meetingId})`); - }); + selector.meetingId = meetingId; } - return ConnectionStatus.remove({}, () => { - Logger.info('Cleared ConnectionStatus (all)'); - }); + try { + const numberAffected = ConnectionStatus.remove(selector); + + if (numberAffected) { + if (meetingId) { + Logger.info(`Removed ConnectionStatus (${meetingId})`); + } else { + Logger.info('Removed ConnectionStatus (all)'); + } + } else { + Logger.warn('Removing ConnectionStatus nonaffected'); + } + } catch (err) { + Logger.error(`Removing ConnectionStatus: ${err}`); + } } diff --git a/bigbluebutton-html5/imports/api/connection-status/server/modifiers/updateConnectionStatus.js b/bigbluebutton-html5/imports/api/connection-status/server/modifiers/updateConnectionStatus.js index bf25106caf..9a5307fc6d 100644 --- a/bigbluebutton-html5/imports/api/connection-status/server/modifiers/updateConnectionStatus.js +++ b/bigbluebutton-html5/imports/api/connection-status/server/modifiers/updateConnectionStatus.js @@ -20,18 +20,13 @@ export default function updateConnectionStatus(meetingId, userId, level) { timestamp, }; - const cb = (err, numChanged) => { - if (err) { - return Logger.error(`Updating connection status: ${err}`); + try { + const { numberAffected } = ConnectionStatus.upsert(selector, modifier); + + if (numberAffected) { + Logger.verbose(`Updated connection status userId=${userId} level=${level}`); } - - const { insertedId } = numChanged; - if (insertedId) { - return Logger.info(`Added connection status userId=${userId} level=${level}`); - } - - return Logger.verbose(`Update connection status userId=${userId} level=${level}`); - }; - - return ConnectionStatus.upsert(selector, modifier, cb); + } catch (err) { + Logger.error(`Updating connection status: ${err}`); + } } diff --git a/bigbluebutton-html5/imports/api/group-chat-msg/server/modifiers/addBulkGroupChatMsgs.js b/bigbluebutton-html5/imports/api/group-chat-msg/server/modifiers/addBulkGroupChatMsgs.js index 05662d5484..cef1990b4c 100644 --- a/bigbluebutton-html5/imports/api/group-chat-msg/server/modifiers/addBulkGroupChatMsgs.js +++ b/bigbluebutton-html5/imports/api/group-chat-msg/server/modifiers/addBulkGroupChatMsgs.js @@ -7,14 +7,22 @@ export default async function addBulkGroupChatMsgs(msgs) { if (!msgs.length) return; const mappedMsgs = msgs - .map(({ chatId, meetingId, msg }) => ({ - _id: new Mongo.ObjectID()._str, - ...msg, - meetingId, - chatId, - message: parseMessage(msg.message), - sender: msg.sender.id, - })) + .map(({ chatId, meetingId, msg }) => { + const { + sender, + color, + ...restMsg + } = msg; + + return { + _id: new Mongo.ObjectID()._str, + ...restMsg, + meetingId, + chatId, + message: parseMessage(msg.message), + sender: sender.id, + }; + }) .map(el => flat(el, { safe: true })); try { diff --git a/bigbluebutton-html5/imports/api/group-chat-msg/server/modifiers/addGroupChatMsg.js b/bigbluebutton-html5/imports/api/group-chat-msg/server/modifiers/addGroupChatMsg.js index c87e2583c2..a9e7d23f4e 100644 --- a/bigbluebutton-html5/imports/api/group-chat-msg/server/modifiers/addGroupChatMsg.js +++ b/bigbluebutton-html5/imports/api/group-chat-msg/server/modifiers/addGroupChatMsg.js @@ -28,8 +28,16 @@ export default function addGroupChatMsg(meetingId, chatId, msg) { message: String, correlationId: Match.Maybe(String), }); + + const { + color, + sender, + ...restMsg + } = msg; + const msgDocument = { - ...msg, + ...restMsg, + sender: sender.id, meetingId, chatId, message: parseMessage(msg.message), diff --git a/bigbluebutton-html5/imports/api/group-chat-msg/server/modifiers/syncMeetingChatMsgs.js b/bigbluebutton-html5/imports/api/group-chat-msg/server/modifiers/syncMeetingChatMsgs.js index a226dd9636..e1231576e6 100644 --- a/bigbluebutton-html5/imports/api/group-chat-msg/server/modifiers/syncMeetingChatMsgs.js +++ b/bigbluebutton-html5/imports/api/group-chat-msg/server/modifiers/syncMeetingChatMsgs.js @@ -16,12 +16,18 @@ export default function syncMeetingChatMsgs(meetingId, chatId, msgs) { msgs .forEach((msg) => { + const { + sender, + color, + ...restMsg + } = msg; + const msgToSync = { - ...msg, + ...restMsg, meetingId, chatId, message: parseMessage(msg.message), - sender: msg.sender.id, + sender: sender.id, }; const modifier = flat(msgToSync, { safe: true }); diff --git a/bigbluebutton-html5/imports/api/meetings/server/modifiers/meetingHasEnded.js b/bigbluebutton-html5/imports/api/meetings/server/modifiers/meetingHasEnded.js index 4448dc1908..7651b0fd5b 100755 --- a/bigbluebutton-html5/imports/api/meetings/server/modifiers/meetingHasEnded.js +++ b/bigbluebutton-html5/imports/api/meetings/server/modifiers/meetingHasEnded.js @@ -28,6 +28,8 @@ import clearRecordMeeting from './clearRecordMeeting'; import clearVoiceCallStates from '/imports/api/voice-call-states/server/modifiers/clearVoiceCallStates'; import clearVideoStreams from '/imports/api/video-streams/server/modifiers/clearVideoStreams'; import clearAuthTokenValidation from '/imports/api/auth-token-validation/server/modifiers/clearAuthTokenValidation'; +import clearUsersPersistentData from '/imports/api/users-persistent-data/server/modifiers/clearUsersPersistentData'; + import clearWhiteboardMultiUser from '/imports/api/whiteboard-multi-user/server/modifiers/clearWhiteboardMultiUser'; import Metrics from '/imports/startup/server/metrics'; @@ -62,6 +64,7 @@ export default function meetingHasEnded(meetingId) { clearAuthTokenValidation(meetingId); clearWhiteboardMultiUser(meetingId); clearScreenshare(meetingId); + clearUsersPersistentData(meetingId); BannedUsers.delete(meetingId); Metrics.removeMeeting(meetingId); diff --git a/bigbluebutton-html5/imports/api/users-persistent-data/index.js b/bigbluebutton-html5/imports/api/users-persistent-data/index.js new file mode 100644 index 0000000000..ab5a6d5337 --- /dev/null +++ b/bigbluebutton-html5/imports/api/users-persistent-data/index.js @@ -0,0 +1,9 @@ +import { Meteor } from 'meteor/meteor'; + +const UsersPersistentData = new Mongo.Collection('users-persistent-data'); + +if (Meteor.isServer) { + UsersPersistentData._ensureIndex({ meetingId: 1, userId: 1 }); +} + +export default UsersPersistentData; diff --git a/bigbluebutton-html5/imports/api/users-persistent-data/server/index.js b/bigbluebutton-html5/imports/api/users-persistent-data/server/index.js new file mode 100644 index 0000000000..f7744b2bef --- /dev/null +++ b/bigbluebutton-html5/imports/api/users-persistent-data/server/index.js @@ -0,0 +1 @@ +import './publishers'; diff --git a/bigbluebutton-html5/imports/api/users-persistent-data/server/modifiers/addUserPersistentData.js b/bigbluebutton-html5/imports/api/users-persistent-data/server/modifiers/addUserPersistentData.js new file mode 100644 index 0000000000..224fdeda73 --- /dev/null +++ b/bigbluebutton-html5/imports/api/users-persistent-data/server/modifiers/addUserPersistentData.js @@ -0,0 +1,76 @@ +import { check } from 'meteor/check'; +import UsersPersistentData from '/imports/api/users-persistent-data'; +import Logger from '/imports/startup/server/logger'; + +export default function addUserPersistentData(user) { + check(user, { + meetingId: String, + sortName: String, + color: String, + mobile: Boolean, + breakoutProps: Object, + inactivityCheck: Boolean, + responseDelay: Number, + loggedOut: Boolean, + intId: String, + extId: String, + name: String, + role: String, + guest: Boolean, + authed: Boolean, + guestStatus: String, + emoji: String, + presenter: Boolean, + locked: Boolean, + avatar: String, + clientType: String, + effectiveConnectionType: null, + }); + + + const { + intId, + extId, + meetingId, + name, + role, + token, + avatar, + guest, + color, + } = user; + + const userData = { + userId: intId, + extId, + meetingId, + name, + role, + token, + avatar, + guest, + color, + loggedOut: false, + }; + + const selector = { + userId: intId, + meetingId, + }; + + const modifier = { + $set: userData, + }; + + try { + const { insertedId } = UsersPersistentData.upsert(selector, modifier); + + if (insertedId) { + Logger.info(`Added user id=${intId} to user persistent Data: meeting=${meetingId}`); + } else { + Logger.info(`Upserted user id=${intId} to user persistent Data: meeting=${meetingId}`); + } + } catch (err) { + Logger.error(`Adding note to the collection: ${err}`); + } +} diff --git a/bigbluebutton-html5/imports/api/users-persistent-data/server/modifiers/clearUsersPersistentData.js b/bigbluebutton-html5/imports/api/users-persistent-data/server/modifiers/clearUsersPersistentData.js new file mode 100644 index 0000000000..4a5283c7e0 --- /dev/null +++ b/bigbluebutton-html5/imports/api/users-persistent-data/server/modifiers/clearUsersPersistentData.js @@ -0,0 +1,26 @@ +import Logger from '/imports/startup/server/logger'; +import UsersPersistentData from '/imports/api/users-persistent-data/index'; + +export default function clearUsersPersistentData(meetingId) { + if (meetingId) { + try { + const numberAffected = UsersPersistentData.remove({ meetingId }); + + if (numberAffected) { + Logger.info(`Cleared users persistent data (${meetingId})`); + } + } catch (err) { + Logger.error(`Error clearing users persistent data (${meetingId}). ${err}`); + } + } else { + try { + const numberAffected = UsersPersistentData.remove({}); + + if (numberAffected) { + Logger.info('Cleared users persistent data (all)'); + } + } catch (err) { + Logger.error(`Error clearing users persistent data (all). ${err}`); + } + } +} diff --git a/bigbluebutton-html5/imports/api/users-persistent-data/server/modifiers/setloggedOutStatus.js b/bigbluebutton-html5/imports/api/users-persistent-data/server/modifiers/setloggedOutStatus.js new file mode 100644 index 0000000000..7fb65739a9 --- /dev/null +++ b/bigbluebutton-html5/imports/api/users-persistent-data/server/modifiers/setloggedOutStatus.js @@ -0,0 +1,26 @@ +import { check } from 'meteor/check'; +import UsersPersistentData from '/imports/api/users-persistent-data'; +import Logger from '/imports/startup/server/logger'; + +export default function setloggedOutStatus(userId, meetingId, status = true) { + check(userId, String); + check(meetingId, String); + check(status, Boolean); + + const selector = { + userId, + meetingId, + }; + + const modifier = { + $set: { + loggedOut: status, + }, + }; + + try { + UsersPersistentData.update(selector, modifier); + } catch (err) { + Logger.error(`Adding note to the collection: ${err}`); + } +} diff --git a/bigbluebutton-html5/imports/api/users-persistent-data/server/publishers.js b/bigbluebutton-html5/imports/api/users-persistent-data/server/publishers.js new file mode 100644 index 0000000000..3a05cb4505 --- /dev/null +++ b/bigbluebutton-html5/imports/api/users-persistent-data/server/publishers.js @@ -0,0 +1,33 @@ +import UsersPersistentData from '/imports/api/users-persistent-data'; +import { Meteor } from 'meteor/meteor'; +import { extractCredentials } from '/imports/api/common/server/helpers'; +import { check } from 'meteor/check'; + +function usersPersistentData() { + if (!this.userId) { + return UsersPersistentData.find({ meetingId: '' }); + } + const { meetingId, requesterUserId } = extractCredentials(this.userId); + + check(meetingId, String); + check(requesterUserId, String); + + const selector = { + meetingId, + }; + + const options = { + fields: { + meetingId: false, + }, + }; + + return UsersPersistentData.find(selector, options); +} + +function publishUsersPersistentData(...args) { + const boundUsers = usersPersistentData.bind(this); + return boundUsers(...args); +} + +Meteor.publish('users-persistent-data', publishUsersPersistentData); diff --git a/bigbluebutton-html5/imports/api/users/server/modifiers/addUser.js b/bigbluebutton-html5/imports/api/users/server/modifiers/addUser.js index d40b5f3874..6229deeb9d 100755 --- a/bigbluebutton-html5/imports/api/users/server/modifiers/addUser.js +++ b/bigbluebutton-html5/imports/api/users/server/modifiers/addUser.js @@ -5,7 +5,7 @@ import Meetings from '/imports/api/meetings'; import VoiceUsers from '/imports/api/voice-users/'; import _ from 'lodash'; import SanitizeHTML from 'sanitize-html'; - +import addUserPsersistentData from '/imports/api/users-persistent-data/server/modifiers/addUserPersistentData'; import stringHash from 'string-hash'; import flat from 'flat'; @@ -58,26 +58,28 @@ export default function addUser(meetingId, userData) { from a list based on the userId */ const color = COLOR_LIST[stringHash(user.intId) % COLOR_LIST.length]; - const modifier = { - $set: Object.assign( - { - meetingId, - sortName: user.name.trim().toLowerCase(), - color, - mobile: false, - breakoutProps: { - isBreakoutUser: Meeting.meetingProp.isBreakout, - parentId: Meeting.breakoutProps.parentId, - }, - effectiveConnectionType: null, - inactivityCheck: false, - responseDelay: 0, - loggedOut: false, + const userInfos = Object.assign( + { + meetingId, + sortName: user.name.trim().toLowerCase(), + color, + mobile: false, + breakoutProps: { + isBreakoutUser: Meeting.meetingProp.isBreakout, + parentId: Meeting.breakoutProps.parentId, }, - flat(user), - ), - }; + effectiveConnectionType: null, + inactivityCheck: false, + responseDelay: 0, + loggedOut: false, + }, + flat(user), + ); + const modifier = { + $set: userInfos, + }; + addUserPsersistentData(userInfos); // Only add an empty VoiceUser if there isn't one already and if the user coming in isn't a // dial-in user. We want to avoid overwriting good data if (user.clientType !== 'dial-in-user' && !VoiceUsers.findOne({ meetingId, intId: userId })) { diff --git a/bigbluebutton-html5/imports/api/users/server/modifiers/removeUser.js b/bigbluebutton-html5/imports/api/users/server/modifiers/removeUser.js index 48e005873d..37af85aec7 100755 --- a/bigbluebutton-html5/imports/api/users/server/modifiers/removeUser.js +++ b/bigbluebutton-html5/imports/api/users/server/modifiers/removeUser.js @@ -2,6 +2,7 @@ import { check } from 'meteor/check'; import Users from '/imports/api/users'; import VideoStreams from '/imports/api/video-streams'; import Logger from '/imports/startup/server/logger'; +import setloggedOutStatus from '/imports/api/users-persistent-data/server/modifiers/setloggedOutStatus'; import stopWatchingExternalVideoSystemCall from '/imports/api/external-videos/server/methods/stopWatchingExternalVideoSystemCall'; import clearUserInfoForRequester from '/imports/api/users-infos/server/modifiers/clearUserInfoForRequester'; import ClientConnections from '/imports/startup/server/ClientConnections'; @@ -32,6 +33,7 @@ export default function removeUser(meetingId, userId) { }; try { + setloggedOutStatus(userId, meetingId, true); VideoStreams.remove({ meetingId, userId }); const sessionUserId = `${meetingId}-${userId}`; diff --git a/bigbluebutton-html5/imports/ui/components/app/component.jsx b/bigbluebutton-html5/imports/ui/components/app/component.jsx index dd498a7bec..890d75a857 100755 --- a/bigbluebutton-html5/imports/ui/components/app/component.jsx +++ b/bigbluebutton-html5/imports/ui/components/app/component.jsx @@ -26,6 +26,7 @@ import RandomUserSelectContainer from '/imports/ui/components/modal/random-user/ import { withDraggableContext } from '../media/webcam-draggable-overlay/context'; import { styles } from './styles'; import { makeCall } from '/imports/ui/services/api'; +import ConnectionStatusService from '/imports/ui/components/connection-status/service'; import { NAVBAR_HEIGHT } from '/imports/ui/components/layout/layout-manager'; const MOBILE_MEDIA = 'only screen and (max-width: 40em)'; @@ -161,6 +162,8 @@ class App extends Component { if (isMobileBrowser) makeCall('setMobileUser'); + ConnectionStatusService.startRoundTripTime(); + logger.info({ logCode: 'app_component_componentdidmount' }, 'Client loaded successfully'); } @@ -226,6 +229,8 @@ class App extends Component { if (navigator.connection) { navigator.connection.addEventListener('change', handleNetworkConnection, false); } + + ConnectionStatusService.stopRoundTripTime(); } handleWindowResize() { diff --git a/bigbluebutton-html5/imports/ui/components/breakout-join-confirmation/component.jsx b/bigbluebutton-html5/imports/ui/components/breakout-join-confirmation/component.jsx index 0a080fa811..21cfab534c 100755 --- a/bigbluebutton-html5/imports/ui/components/breakout-join-confirmation/component.jsx +++ b/bigbluebutton-html5/imports/ui/components/breakout-join-confirmation/component.jsx @@ -109,6 +109,7 @@ class BreakoutJoinConfirmation extends Component { }, 'joining breakout room closed audio in the main room'); } + VideoService.storeDeviceIds(); VideoService.exitVideo(); if (UserListService.amIPresenter()) screenshareHasEnded(); if (url === '') { diff --git a/bigbluebutton-html5/imports/ui/components/breakout-room/component.jsx b/bigbluebutton-html5/imports/ui/components/breakout-room/component.jsx index aad2ba1123..b4c7219fee 100644 --- a/bigbluebutton-html5/imports/ui/components/breakout-room/component.jsx +++ b/bigbluebutton-html5/imports/ui/components/breakout-room/component.jsx @@ -248,6 +248,7 @@ class BreakoutRoom extends PureComponent { logCode: 'breakoutroom_join', extraInfo: { logType: 'user_action' }, }, 'joining breakout room closed audio in the main room'); + VideoService.storeDeviceIds(); VideoService.exitVideo(); if (UserListService.amIPresenter()) screenshareHasEnded(); } diff --git a/bigbluebutton-html5/imports/ui/components/button/component.jsx b/bigbluebutton-html5/imports/ui/components/button/component.jsx index d087bb1719..a9901373ec 100755 --- a/bigbluebutton-html5/imports/ui/components/button/component.jsx +++ b/bigbluebutton-html5/imports/ui/components/button/component.jsx @@ -11,7 +11,7 @@ const SIZES = [ ]; const COLORS = [ - 'default', 'primary', 'danger', 'success', 'dark', + 'default', 'primary', 'danger', 'warning', 'success', 'dark', 'offline', ]; const propTypes = { diff --git a/bigbluebutton-html5/imports/ui/components/button/styles.scss b/bigbluebutton-html5/imports/ui/components/button/styles.scss index a3fef2b8b6..3bb841646c 100755 --- a/bigbluebutton-html5/imports/ui/components/button/styles.scss +++ b/bigbluebutton-html5/imports/ui/components/button/styles.scss @@ -18,6 +18,10 @@ --btn-success-bg: var(--color-success); --btn-success-border: var(--color-success); + --btn-warning-color: var(--color-white); + --btn-warning-bg: var(--color-warning); + --btn-warning-border: var(--color-warning); + --btn-danger-color: var(--color-white); --btn-danger-bg: var(--color-danger); --btn-danger-border: var(--color-danger); @@ -26,6 +30,10 @@ --btn-dark-bg: var(--color-gray-dark); --btn-dark-border: var(--color-danger); + --btn-offline-color: var(--color-white); + --btn-offline-bg: var(--color-offline); + --btn-offline-border: var(--color-offline); + --btn-border-size: var(--border-size); --btn-border-radius: var(--border-radius); --btn-font-weight: 600; @@ -258,6 +266,10 @@ @include button-variant(var(--btn-success-color), var(--btn-success-bg), var(--btn-success-border)); } +.warning { + @include button-variant(var(--btn-warning-color), var(--btn-warning-bg), var(--btn-warning-border)); +} + .danger { @include button-variant(var(--btn-danger-color), var(--btn-danger-bg), var(--btn-danger-border)); } @@ -266,6 +278,10 @@ @include button-variant(var(--btn-dark-color), var(--btn-dark-bg), var(--btn-dark-border)); } +.offline { + @include button-variant(var(--btn-offline-color), var(--btn-offline-bg), var(--btn-offline-border)); +} + /* Styles * ========== */ @@ -288,6 +304,10 @@ @include button-ghost-variant(var(--btn-success-bg), var(--btn-success-color)); } + &.warning { + @include button-ghost-variant(var(--btn-warning-bg), var(--btn-warning-color)); + } + &.danger { @include button-ghost-variant(var(--btn-danger-bg), var(--btn-danger-color)); } @@ -295,6 +315,10 @@ &.dark { @include button-ghost-variant(var(--btn-dark-bg), var(--btn-dark-color)); } + + &.offline { + @include button-ghost-variant(var(--btn-offline-bg), var(--btn-offline-color)); + } } .circle { diff --git a/bigbluebutton-html5/imports/ui/components/chat/chat-dropdown/component.jsx b/bigbluebutton-html5/imports/ui/components/chat/chat-dropdown/component.jsx index b3538dfd25..a673d9dac8 100755 --- a/bigbluebutton-html5/imports/ui/components/chat/chat-dropdown/component.jsx +++ b/bigbluebutton-html5/imports/ui/components/chat/chat-dropdown/component.jsx @@ -55,11 +55,11 @@ class ChatDropdown extends PureComponent { } componentDidUpdate(prevProps, prevState) { - const { timeWindowsValues } = this.props; + const { timeWindowsValues, users } = this.props; const { isSettingOpen } = this.state; if (prevState.isSettingOpen !== isSettingOpen) { this.clipboard = new Clipboard('#clipboardButton', { - text: () => ChatService.exportChat(timeWindowsValues), + text: () => ChatService.exportChat(timeWindowsValues, users), }); } } @@ -82,7 +82,7 @@ class ChatDropdown extends PureComponent { getAvailableActions() { const { - intl, isMeteorConnected, amIModerator, meetingIsBreakout, meetingName, timeWindowsValues, + intl, isMeteorConnected, amIModerator, meetingIsBreakout, meetingName, timeWindowsValues, users, } = this.props; const clearIcon = 'delete'; @@ -104,7 +104,7 @@ class ChatDropdown extends PureComponent { link.setAttribute( 'href', `data: ${mimeType} ;charset=utf-8, - ${encodeURIComponent(ChatService.exportChat(timeWindowsValues))}`, + ${encodeURIComponent(ChatService.exportChat(timeWindowsValues, users))}`, ); link.dispatchEvent(new MouseEvent('click', { bubbles: true, cancelable: true, view: window })); }} diff --git a/bigbluebutton-html5/imports/ui/components/chat/chat-dropdown/container.jsx b/bigbluebutton-html5/imports/ui/components/chat/chat-dropdown/container.jsx index f00a5d6cc1..5eae29a184 100644 --- a/bigbluebutton-html5/imports/ui/components/chat/chat-dropdown/container.jsx +++ b/bigbluebutton-html5/imports/ui/components/chat/chat-dropdown/container.jsx @@ -1,10 +1,16 @@ -import React from 'react'; +import React, { useContext } from 'react'; import { withTracker } from 'meteor/react-meteor-data'; import Auth from '/imports/ui/services/auth'; import Meetings from '/imports/api/meetings'; +import { UsersContext } from '/imports/ui/components/components-data/users-context/context'; import ChatDropdown from './component'; -const ChatDropdownContainer = ({ ...props }) => ; +const ChatDropdownContainer = ({ ...props }) => { + const usingUsersContext = useContext(UsersContext); + const { users } = usingUsersContext; + + return ; +}; export default withTracker(() => { const getMeetingName = () => { diff --git a/bigbluebutton-html5/imports/ui/components/chat/container.jsx b/bigbluebutton-html5/imports/ui/components/chat/container.jsx index 264d833065..001b37f9ac 100755 --- a/bigbluebutton-html5/imports/ui/components/chat/container.jsx +++ b/bigbluebutton-html5/imports/ui/components/chat/container.jsx @@ -167,6 +167,11 @@ const ChatContainer = (props) => { globalAppplyStateToProps = applyPropsToState; throttledFunc(); + ChatService.removePackagedClassAttribute( + ["ReactVirtualized__Grid", "ReactVirtualized__Grid__innerScrollContainer"], + "role" + ); + return ( { this.textarea = ref; return this.textarea; }} placeholder={intl.formatMessage(messages.inputPlaceholder, { 0: title })} - aria-controls={chatAreaId} aria-label={intl.formatMessage(messages.inputLabel, { 0: chatTitle })} aria-invalid={hasErrors ? 'true' : 'false'} - aria-describedby={hasErrors ? 'message-input-error' : null} autoCorrect="off" autoComplete="off" spellCheck="true" @@ -297,7 +295,7 @@ class MessageForm extends PureComponent { value={message} onChange={this.handleMessageChange} onKeyDown={this.handleMessageKeyDown} - async={true} + async />