diff --git a/akka-bbb-apps/src/main/scala/org/bigbluebutton/core/apps/users/SelectRandomViewerReqMsgHdlr.scala b/akka-bbb-apps/src/main/scala/org/bigbluebutton/core/apps/users/SelectRandomViewerReqMsgHdlr.scala new file mode 100644 index 0000000000000000000000000000000000000000..bc9461ebe8932283b363f61d09b24ddf67b6cbb6 --- /dev/null +++ b/akka-bbb-apps/src/main/scala/org/bigbluebutton/core/apps/users/SelectRandomViewerReqMsgHdlr.scala @@ -0,0 +1,41 @@ +package org.bigbluebutton.core.apps.users + +import org.bigbluebutton.common2.msgs._ +import org.bigbluebutton.core.running.{ LiveMeeting, OutMsgRouter } +import org.bigbluebutton.core.models.{ UserState, Users2x } +import org.bigbluebutton.core.apps.{ PermissionCheck, RightsManagementTrait } +import org.bigbluebutton.core2.MeetingStatus2x +import scala.util.Random + +trait SelectRandomViewerReqMsgHdlr extends RightsManagementTrait { + this: UsersApp => + + val outGW: OutMsgRouter + + def handleSelectRandomViewerReqMsg(msg: SelectRandomViewerReqMsg): Unit = { + log.debug("Received SelectRandomViewerReqMsg {}", SelectRandomViewerReqMsg) + + def broadcastEvent(msg: SelectRandomViewerReqMsg, selectedUser: UserState): Unit = { + val routing = Routing.addMsgToClientRouting(MessageTypes.BROADCAST_TO_MEETING, liveMeeting.props.meetingProp.intId, msg.header.userId) + val envelope = BbbCoreEnvelope(SelectRandomViewerRespMsg.NAME, routing) + val header = BbbClientMsgHeader(SelectRandomViewerRespMsg.NAME, liveMeeting.props.meetingProp.intId, msg.header.userId) + + val body = SelectRandomViewerRespMsgBody(msg.header.userId, selectedUser.intId) + val event = SelectRandomViewerRespMsg(header, body) + val msgEvent = BbbCommonEnvCoreMsg(envelope, event) + outGW.send(msgEvent) + } + + if (permissionFailed(PermissionCheck.GUEST_LEVEL, PermissionCheck.PRESENTER_LEVEL, liveMeeting.users2x, msg.header.userId)) { + val meetingId = liveMeeting.props.meetingProp.intId + val reason = "No permission to select random user." + PermissionCheck.ejectUserForFailedPermission(meetingId, msg.header.userId, reason, outGW, liveMeeting) + } else { + val users = Users2x.findViewers(liveMeeting.users2x) + val randNum = new scala.util.Random + val selectedUser = users(randNum.nextInt(users.size)); + + broadcastEvent(msg, selectedUser) + } + } +} diff --git a/akka-bbb-apps/src/main/scala/org/bigbluebutton/core/apps/users/UsersApp.scala b/akka-bbb-apps/src/main/scala/org/bigbluebutton/core/apps/users/UsersApp.scala index eec9ff6431b2dde8df320067ec6dca4f9aea0619..8f42d28960564b2562c03389eddda16c49e6e897 100755 --- a/akka-bbb-apps/src/main/scala/org/bigbluebutton/core/apps/users/UsersApp.scala +++ b/akka-bbb-apps/src/main/scala/org/bigbluebutton/core/apps/users/UsersApp.scala @@ -146,6 +146,7 @@ class UsersApp( with SendRecordingTimerInternalMsgHdlr with UpdateWebcamsOnlyForModeratorCmdMsgHdlr with GetRecordingStatusReqMsgHdlr + with SelectRandomViewerReqMsgHdlr with GetWebcamsOnlyForModeratorReqMsgHdlr with AssignPresenterReqMsgHdlr with EjectDuplicateUserReqMsgHdlr 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 3e00cca80fe660be7b8fd03bb257915af567a110..826e83cd8cdbb9e7d7eeec3956dbe1f720515e24 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 @@ -59,6 +59,10 @@ object Users2x { users.toVector.filter(u => !u.presenter) } + def findViewers(users: Users2x): Vector[UserState] = { + users.toVector.filter(u => u.role == Roles.VIEWER_ROLE) + } + def updateLastUserActivity(users: Users2x, u: UserState): UserState = { val newUserState = modify(u)(_.lastActivityTime).setTo(TimeUtil.timeNowInMs()) users.save(newUserState) @@ -241,6 +245,7 @@ class Users2x { } } } + } case class OldPresenter(userId: String, changedPresenterOn: Long) diff --git a/akka-bbb-apps/src/main/scala/org/bigbluebutton/core/pubsub/senders/ReceivedJsonMsgHandlerActor.scala b/akka-bbb-apps/src/main/scala/org/bigbluebutton/core/pubsub/senders/ReceivedJsonMsgHandlerActor.scala index 1842b9a24ed18138c39b045629f78ced7484775f..cef28ef61fa6803f8106922b3d2b411a78b80a2e 100755 --- a/akka-bbb-apps/src/main/scala/org/bigbluebutton/core/pubsub/senders/ReceivedJsonMsgHandlerActor.scala +++ b/akka-bbb-apps/src/main/scala/org/bigbluebutton/core/pubsub/senders/ReceivedJsonMsgHandlerActor.scala @@ -103,6 +103,8 @@ class ReceivedJsonMsgHandlerActor( routeGenericMsg[GetPresenterGroupReqMsg](envelope, jsonNode) case UserActivitySignCmdMsg.NAME => routeGenericMsg[UserActivitySignCmdMsg](envelope, jsonNode) + case SelectRandomViewerReqMsg.NAME => + routeGenericMsg[SelectRandomViewerReqMsg](envelope, jsonNode) // Poll case StartCustomPollReqMsg.NAME => diff --git a/akka-bbb-apps/src/main/scala/org/bigbluebutton/core/running/MeetingActor.scala b/akka-bbb-apps/src/main/scala/org/bigbluebutton/core/running/MeetingActor.scala index c0a1735a92841210e0d1f19457236444c06383ef..f2845916d2bd514745c255dba7e81c0e85cc1074 100755 --- a/akka-bbb-apps/src/main/scala/org/bigbluebutton/core/running/MeetingActor.scala +++ b/akka-bbb-apps/src/main/scala/org/bigbluebutton/core/running/MeetingActor.scala @@ -332,6 +332,7 @@ class MeetingActor( case m: UpdateWebcamsOnlyForModeratorCmdMsg => usersApp.handleUpdateWebcamsOnlyForModeratorCmdMsg(m) case m: GetRecordingStatusReqMsg => usersApp.handleGetRecordingStatusReqMsg(m) case m: ChangeUserEmojiCmdMsg => handleChangeUserEmojiCmdMsg(m) + case m: SelectRandomViewerReqMsg => usersApp.handleSelectRandomViewerReqMsg(m) // Client requested to eject user case m: EjectUserFromMeetingCmdMsg => 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/UsersMgs.scala index d658d5236580558a9890d4d7f01b0e75c3a6d3a5..3276e55389e112c4ef40fabf149dc8b8598d1c02 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/UsersMgs.scala @@ -401,3 +401,17 @@ case class UserInactivityInspectMsgBody(meetingId: String, responseDelay: Long) object UserActivitySignCmdMsg { val NAME = "UserActivitySignCmdMsg" } case class UserActivitySignCmdMsg(header: BbbClientMsgHeader, body: UserActivitySignCmdMsgBody) extends StandardMsg case class UserActivitySignCmdMsgBody(userId: String) + +/** + * Sent from client to randomly select a viewer + */ +object SelectRandomViewerReqMsg { val NAME = "SelectRandomViewerReqMsg" } +case class SelectRandomViewerReqMsg(header: BbbClientMsgHeader, body: SelectRandomViewerReqMsgBody) extends StandardMsg +case class SelectRandomViewerReqMsgBody(requestedBy: String) + +/** + * Response to request for a random viewer + */ +object SelectRandomViewerRespMsg { val NAME = "SelectRandomViewerRespMsg" } +case class SelectRandomViewerRespMsg(header: BbbClientMsgHeader, body: SelectRandomViewerRespMsgBody) extends StandardMsg +case class SelectRandomViewerRespMsgBody(requestedBy: String, selectedUserId: String) diff --git a/bigbluebutton-html5/imports/api/meetings/server/eventHandlers.js b/bigbluebutton-html5/imports/api/meetings/server/eventHandlers.js index 5d08df5ac10c0511ad7d1390f0b02aba7ee24bb7..81f0ef206a42523e373fd5b1f58f316a19b870b6 100644 --- a/bigbluebutton-html5/imports/api/meetings/server/eventHandlers.js +++ b/bigbluebutton-html5/imports/api/meetings/server/eventHandlers.js @@ -9,6 +9,7 @@ import handleRecordingStatusChange from './handlers/recordingStatusChange'; import handleRecordingTimerChange from './handlers/recordingTimerChange'; import handleTimeRemainingUpdate from './handlers/timeRemainingUpdate'; import handleChangeWebcamOnlyModerator from './handlers/webcamOnlyModerator'; +import handleSelectRandomViewer from './handlers/selectRandomViewer'; RedisPubSub.on('MeetingCreatedEvtMsg', handleMeetingCreation); RedisPubSub.on('SyncGetMeetingInfoRespMsg', handleGetAllMeetings); @@ -21,3 +22,4 @@ RedisPubSub.on('UpdateRecordingTimerEvtMsg', handleRecordingTimerChange); RedisPubSub.on('WebcamsOnlyForModeratorChangedEvtMsg', handleChangeWebcamOnlyModerator); RedisPubSub.on('GetLockSettingsRespMsg', handleMeetingLocksChange); RedisPubSub.on('MeetingTimeRemainingUpdateEvtMsg', handleTimeRemainingUpdate); +RedisPubSub.on('SelectRandomViewerRespMsg', handleSelectRandomViewer); diff --git a/bigbluebutton-html5/imports/api/meetings/server/handlers/selectRandomViewer.js b/bigbluebutton-html5/imports/api/meetings/server/handlers/selectRandomViewer.js new file mode 100644 index 0000000000000000000000000000000000000000..51c1d76f4a016ec254e9b8b2ed5506f08d204b99 --- /dev/null +++ b/bigbluebutton-html5/imports/api/meetings/server/handlers/selectRandomViewer.js @@ -0,0 +1,13 @@ +import { check } from 'meteor/check'; +import updateRandomViewer from '../modifiers/updateRandomViewer'; + +export default function randomlySelectedUser({ header, body }) { + const { selectedUserId, requestedBy } = body; + const { meetingId } = header; + + check(meetingId, String); + check(requestedBy, String); + check(selectedUserId, String); + + updateRandomViewer(meetingId, selectedUserId, requestedBy); +} diff --git a/bigbluebutton-html5/imports/api/meetings/server/methods.js b/bigbluebutton-html5/imports/api/meetings/server/methods.js index 09ac6b1e311116ecf8443bff270eb71760e6d802..960ef8082efae97b4e628dc50ec470774f70c746 100755 --- a/bigbluebutton-html5/imports/api/meetings/server/methods.js +++ b/bigbluebutton-html5/imports/api/meetings/server/methods.js @@ -4,7 +4,6 @@ import toggleRecording from './methods/toggleRecording'; import transferUser from './methods/transferUser'; import toggleLockSettings from './methods/toggleLockSettings'; import toggleWebcamsOnlyForModerator from './methods/toggleWebcamsOnlyForModerator'; -import setRandomUser from './methods/setRandomUser'; Meteor.methods({ endMeeting, @@ -12,5 +11,4 @@ Meteor.methods({ toggleLockSettings, transferUser, toggleWebcamsOnlyForModerator, - setRandomUser, }); diff --git a/bigbluebutton-html5/imports/api/meetings/server/methods/setRandomUser.js b/bigbluebutton-html5/imports/api/meetings/server/methods/setRandomUser.js deleted file mode 100644 index f810a613110c11ceeae86c99f32f13bc232d761a..0000000000000000000000000000000000000000 --- a/bigbluebutton-html5/imports/api/meetings/server/methods/setRandomUser.js +++ /dev/null @@ -1,7 +0,0 @@ -import { extractCredentials } from '/imports/api/common/server/helpers'; -import updateRandomUser from '../modifiers/updateRandomUser'; - -export default function setRandomUser(userId) { - const { meetingId, requesterUserId } = extractCredentials(this.userId); - updateRandomUser(meetingId, userId, requesterUserId); -} diff --git a/bigbluebutton-html5/imports/api/meetings/server/modifiers/updateRandomUser.js b/bigbluebutton-html5/imports/api/meetings/server/modifiers/updateRandomViewer.js similarity index 100% rename from bigbluebutton-html5/imports/api/meetings/server/modifiers/updateRandomUser.js rename to bigbluebutton-html5/imports/api/meetings/server/modifiers/updateRandomViewer.js diff --git a/bigbluebutton-html5/imports/api/users/server/methods.js b/bigbluebutton-html5/imports/api/users/server/methods.js index 98aa67982f0e58ce2ce66cfd2c52927127b70d14..9ab6ccb80d4621bf041dbed2c03d154ce3f9dc01 100644 --- a/bigbluebutton-html5/imports/api/users/server/methods.js +++ b/bigbluebutton-html5/imports/api/users/server/methods.js @@ -8,6 +8,7 @@ import toggleUserLock from './methods/toggleUserLock'; import setUserEffectiveConnectionType from './methods/setUserEffectiveConnectionType'; import userActivitySign from './methods/userActivitySign'; import userLeftMeeting from './methods/userLeftMeeting'; +import setRandomUser from './methods/setRandomUser'; Meteor.methods({ setEmojiStatus, @@ -19,4 +20,5 @@ Meteor.methods({ setUserEffectiveConnectionType, userActivitySign, userLeftMeeting, + setRandomUser, }); diff --git a/bigbluebutton-html5/imports/api/users/server/methods/setRandomUser.js b/bigbluebutton-html5/imports/api/users/server/methods/setRandomUser.js new file mode 100644 index 0000000000000000000000000000000000000000..a0da24a993a23e6d69cb11557540569beb9354f7 --- /dev/null +++ b/bigbluebutton-html5/imports/api/users/server/methods/setRandomUser.js @@ -0,0 +1,17 @@ +import { Meteor } from 'meteor/meteor'; +import RedisPubSub from '/imports/startup/server/redis'; +import { extractCredentials } from '/imports/api/common/server/helpers'; + +export default function setRandomUser() { + const REDIS_CONFIG = Meteor.settings.private.redis; + const CHANNEL = REDIS_CONFIG.channels.toAkkaApps; + const EVENT_NAME = 'SelectRandomViewerReqMsg'; + + const { meetingId, requesterUserId } = extractCredentials(this.userId); + + const payload = { + requestedBy: requesterUserId, + }; + + RedisPubSub.publishUserMessage(CHANNEL, EVENT_NAME, meetingId, requesterUserId, payload); +} diff --git a/bigbluebutton-html5/imports/ui/components/app/component.jsx b/bigbluebutton-html5/imports/ui/components/app/component.jsx index a8160d65657aa1ad90f6ecd982639cb7dfdbfddc..7221f6f6763d9e39ec0be55e5c43fec3003aae86 100755 --- a/bigbluebutton-html5/imports/ui/components/app/component.jsx +++ b/bigbluebutton-html5/imports/ui/components/app/component.jsx @@ -156,10 +156,17 @@ class App extends Component { componentDidUpdate(prevProps) { const { - meetingMuted, notify, currentUserEmoji, intl, hasPublishedPoll, randomlySelectedUser, currentUserId, mountModal, + meetingMuted, + notify, + currentUserEmoji, + intl, + hasPublishedPoll, + randomlySelectedUser, + currentUserId, + mountModal, } = this.props; - if (randomlySelectedUser === currentUserId) mountModal(<RandomUserSelectContainer isSelectedUser />); + if (randomlySelectedUser === currentUserId) mountModal(<RandomUserSelectContainer />); if (prevProps.currentUserEmoji.status !== currentUserEmoji.status) { const formattedEmojiStatus = intl.formatMessage({ id: `app.actionsBar.emojiMenu.${currentUserEmoji.status}Label` }) diff --git a/bigbluebutton-html5/imports/ui/components/modal/random-user/component.jsx b/bigbluebutton-html5/imports/ui/components/modal/random-user/component.jsx index 5b7242934fe4851bb0d14c6fc30c52249520e23a..e9aa852d4380163bce965339b1e08c61a60e5f2f 100644 --- a/bigbluebutton-html5/imports/ui/components/modal/random-user/component.jsx +++ b/bigbluebutton-html5/imports/ui/components/modal/random-user/component.jsx @@ -34,37 +34,23 @@ const propTypes = { }).isRequired, mountModal: PropTypes.func.isRequired, numAvailableViewers: PropTypes.number.isRequired, - setRandomUser: PropTypes.func.isRequired, - isSelectedUser: PropTypes.bool.isRequired, - getActiveRandomUser: PropTypes.func.isRequired, - getRandomUser: PropTypes.func.isRequired, + randomUserReq: PropTypes.func.isRequired, }; class RandomUserSelect extends Component { constructor(props) { super(props); - this.state = { - selectedUser: props.getRandomUser(), - }; - - this.findNewUser = this.findNewUser.bind(this); - } - - componentDidMount() { - const { setRandomUser, isSelectedUser } = this.props; - const { selectedUser } = this.state; - if (!isSelectedUser && selectedUser) setRandomUser(selectedUser.userId); + if (props.currentUser.presenter) { + props.randomUserReq(); + } } - findNewUser() { - const { selectedUser } = this.state; - const { getRandomUser, numAvailableViewers } = this.props; - const user = getRandomUser(); - if (user.userId === selectedUser.userId && numAvailableViewers > 1) { - return this.findNewUser(); + componentDidUpdate() { + const { selectedUser, currentUser, mountModal } = this.props; + if (selectedUser.userId !== currentUser.userId && !currentUser.presenter) { + mountModal(null); } - return user; } render() { @@ -72,25 +58,16 @@ class RandomUserSelect extends Component { intl, mountModal, numAvailableViewers, - setRandomUser, - isSelectedUser, - getActiveRandomUser, + randomUserReq, + selectedUser, + currentUser, } = this.props; - const { selectedUser } = this.state; - - let viewElement = null; - let userData = null; - let avatarColor = selectedUser ? selectedUser.color : null; - let userName = selectedUser ? selectedUser.name : null; + if (!selectedUser) return null; - if (isSelectedUser) { - userData = getActiveRandomUser(); - avatarColor = userData.color; - userName = userData.name; - } + const isSelectedUser = currentUser.userId === selectedUser.userId; - viewElement = numAvailableViewers < 1 ? ( + const viewElement = numAvailableViewers < 1 ? ( <div className={styles.modalViewContainer}> <div className={styles.modalViewTitle}> {intl.formatMessage(messages.randUserTitle)} @@ -105,11 +82,11 @@ class RandomUserSelect extends Component { : `${intl.formatMessage(messages.randUserTitle)}` } </div> - <div aria-hidden className={styles.modalAvatar} style={{ backgroundColor: `${avatarColor}` }}> - {userName.slice(0, 2)} + <div aria-hidden className={styles.modalAvatar} style={{ backgroundColor: `${selectedUser.color}` }}> + {selectedUser.name.slice(0, 2)} </div> <div className={styles.selectedUserName}> - {userName} + {selectedUser.name} </div> {!isSelectedUser && ( @@ -118,14 +95,7 @@ class RandomUserSelect extends Component { color="primary" size="md" className={styles.selectBtn} - onClick={() => { - this.setState({ - selectedUser: this.findNewUser(), - }, () => { - const { selectedUser: updatedUser } = this.state; - return setRandomUser(updatedUser.userId); - }); - }} + onClick={() => randomUserReq()} /> ) } @@ -135,10 +105,7 @@ class RandomUserSelect extends Component { return ( <Modal hideBorder - onRequestClose={() => { - if (isSelectedUser) setRandomUser(''); - mountModal(null); - }} + onRequestClose={() => { mountModal(null); }} contentLabel={intl.formatMessage(messages.ariaModalTitle)} > {viewElement} diff --git a/bigbluebutton-html5/imports/ui/components/modal/random-user/container.jsx b/bigbluebutton-html5/imports/ui/components/modal/random-user/container.jsx index c474a39508a0a57354af052c53557e825e2f325a..0ce375da6a3bad6bba8947eaed230019fb4c9f71 100644 --- a/bigbluebutton-html5/imports/ui/components/modal/random-user/container.jsx +++ b/bigbluebutton-html5/imports/ui/components/modal/random-user/container.jsx @@ -9,8 +9,8 @@ import RandomUserSelect from './component'; const RandomUserSelectContainer = props => <RandomUserSelect {...props} />; -export default withModalMounter(withTracker(({ mountModal, isSelectedUser }) => { - const randomUserPool = Users.find({ +export default withModalMounter(withTracker(({ mountModal }) => { + const viewerPool = Users.find({ meetingId: Auth.meetingID, presenter: { $ne: true }, connectionStatus: 'online', @@ -18,46 +18,39 @@ export default withModalMounter(withTracker(({ mountModal, isSelectedUser }) => }, { fields: { userId: 1, - avatar: 1, - color: 1, - name: 1, }, }).fetch(); - const getRandomUser = () => { - const { length } = randomUserPool; - const randomIndex = Math.floor(Math.random() * Math.floor(length)); - return length > 0 ? randomUserPool[randomIndex] : null; - }; + const meeting = Meetings.findOne({ meetingId: Auth.meetingID }, { + fields: { + randomlySelectedUser: 1, + }, + }); - const getActiveRandomUser = () => { - const meeting = Meetings.findOne({ meetingId: Auth.meetingID }, { - fields: { - randomlySelectedUser: 1, - }, - }); + const selectedUser = Users.findOne({ + meetingId: Auth.meetingID, + userId: meeting.randomlySelectedUser, + }, { + fields: { + userId: 1, + avatar: 1, + color: 1, + name: 1, + }, + }); - return Users.findOne({ - meetingId: Auth.meetingID, - userId: meeting.randomlySelectedUser, - }, { - fields: { - userId: 1, - avatar: 1, - color: 1, - name: 1, - }, - }); - }; + const currentUser = Users.findOne( + { userId: Auth.userID }, + { fields: { userId: 1, presenter: 1 } }, + ); - const setRandomUser = userId => makeCall('setRandomUser', userId); + const randomUserReq = () => makeCall('setRandomUser'); return ({ closeModal: () => mountModal(null), - getRandomUser, - numAvailableViewers: randomUserPool.length, - setRandomUser, - getActiveRandomUser, - isSelectedUser, + numAvailableViewers: viewerPool.length, + randomUserReq, + selectedUser, + currentUser, }); })(RandomUserSelectContainer));