Merge pull request #19104 from gustavotrott/graphql-user-join-action

graphql: New User Flow for Joining Meetings via GraphQL
This commit is contained in:
Gustavo Trott 2023-11-15 09:16:02 -03:00 committed by GitHub
commit 6dc3dbb1fe
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
12 changed files with 317 additions and 192 deletions

View File

@ -2,15 +2,14 @@ package org.bigbluebutton.core.apps.users
import org.bigbluebutton.common2.msgs.UserJoinMeetingReqMsg
import org.bigbluebutton.core.apps.breakout.BreakoutHdlrHelpers
import org.bigbluebutton.core.db.{ UserStateDAO }
import org.bigbluebutton.core.db.{ UserDAO, UserStateDAO }
import org.bigbluebutton.core.domain.MeetingState2x
import org.bigbluebutton.core.models.{ RegisteredUser, RegisteredUsers, Users2x, VoiceUsers }
import org.bigbluebutton.core.running.{ HandlerHelpers, LiveMeeting, MeetingActor, OutMsgRouter }
import org.bigbluebutton.core2.message.senders.MsgBuilder
import org.bigbluebutton.core.models._
import org.bigbluebutton.core.running._
import org.bigbluebutton.core2.message.senders._
trait UserJoinMeetingReqMsgHdlr extends HandlerHelpers {
this: MeetingActor =>
val liveMeeting: LiveMeeting
val outGW: OutMsgRouter
@ -18,64 +17,136 @@ trait UserJoinMeetingReqMsgHdlr extends HandlerHelpers {
log.info("Received user joined meeting. user {} meetingId={}", msg.body.userId, msg.header.meetingId)
Users2x.findWithIntId(liveMeeting.users2x, msg.body.userId) match {
case Some(reconnectingUser) =>
if (reconnectingUser.userLeftFlag.left) {
log.info("Resetting flag that user left meeting. user {}", msg.body.userId)
// User has reconnected. Just reset it's flag. ralam Oct 23, 2018
sendUserLeftFlagUpdatedEvtMsg(outGW, liveMeeting, msg.body.userId, false)
Users2x.resetUserLeftFlag(liveMeeting.users2x, msg.body.userId)
}
state
case None =>
// Check if maxParticipants has been reached
// User are able to reenter if he already joined previously with the same extId
val userHasJoinedAlready = RegisteredUsers.findWithUserId(msg.body.userId, liveMeeting.registeredUsers) match {
case Some(regUser: RegisteredUser) => RegisteredUsers.checkUserExtIdHasJoined(regUser.externId, liveMeeting.registeredUsers)
case None => false
}
val hasReachedMaxParticipants = liveMeeting.props.usersProp.maxUsers > 0 &&
RegisteredUsers.numUniqueJoinedUsers(liveMeeting.registeredUsers) >= liveMeeting.props.usersProp.maxUsers &&
userHasJoinedAlready == false
if (!hasReachedMaxParticipants) {
val newState = userJoinMeeting(outGW, msg.body.authToken, msg.body.clientType, liveMeeting, state)
if (liveMeeting.props.meetingProp.isBreakout) {
BreakoutHdlrHelpers.updateParentMeetingWithUsers(liveMeeting, eventBus)
}
// Warn previous users that someone connected with same Id
for {
regUser <- RegisteredUsers.getRegisteredUserWithToken(msg.body.authToken, msg.body.userId,
liveMeeting.registeredUsers)
} yield {
RegisteredUsers.findAllWithExternUserId(regUser.externId, liveMeeting.registeredUsers)
.filter(u => u.id != regUser.id)
.foreach { previousUser =>
val notifyUserEvent = MsgBuilder.buildNotifyUserInMeetingEvtMsg(
previousUser.id,
liveMeeting.props.meetingProp.intId,
"info",
"promote",
"app.mobileAppModal.userConnectedWithSameId",
"Notification to warn that user connect again from other browser/device",
Vector(regUser.name)
)
outGW.send(notifyUserEvent)
}
}
// fresh user joined (not due to reconnection). Clear (pop) the cached voice user
VoiceUsers.recoverVoiceUser(liveMeeting.voiceUsers, msg.body.userId)
UserStateDAO.updateExpired(msg.body.userId, false)
newState
} else {
log.info("Ignoring user {} attempt to join, once the meeting {} has reached max participants: {}", msg.body.userId, msg.header.meetingId, liveMeeting.props.usersProp.maxUsers)
state
}
case Some(user) => handleUserReconnecting(user, msg, state)
case None => handleUserJoining(msg, state)
}
}
}
private def handleUserJoining(msg: UserJoinMeetingReqMsg, state: MeetingState2x): MeetingState2x = {
val regUser = RegisteredUsers.getRegisteredUserWithToken(msg.body.authToken, msg.body.userId, liveMeeting.registeredUsers)
log.info(s"Number of registered users [${RegisteredUsers.numRegisteredUsers(liveMeeting.registeredUsers)}]")
regUser.fold {
handleFailedUserJoin(msg, "Invalid auth token.", EjectReasonCode.VALIDATE_TOKEN)
state
} { user =>
val validationResult = for {
_ <- checkIfUserGuestStatusIsAllowed(user)
_ <- checkIfUserIsBanned(user)
_ <- checkIfUserLoggedOut(user)
_ <- validateMaxParticipants(user)
} yield user
validationResult.fold(
reason => handleFailedUserJoin(msg, reason._1, reason._2),
validUser => handleSuccessfulUserJoin(msg, validUser)
)
}
}
private def handleSuccessfulUserJoin(msg: UserJoinMeetingReqMsg, regUser: RegisteredUser) = {
val newState = userJoinMeeting(outGW, msg.body.authToken, msg.body.clientType, liveMeeting, state)
updateParentMeetingWithNewListOfUsers()
notifyPreviousUsersWithSameExtId(regUser)
clearCachedVoiceUser(regUser)
clearExpiredUserState(regUser)
invalidateUserGraphqlConnection(regUser)
newState
}
private def handleFailedUserJoin(msg: UserJoinMeetingReqMsg, failReason: String, failReasonCode: String) = {
log.info("Ignoring user {} attempt to join in meeting {}. Reason Code: {}, Reason Message: {}", msg.body.userId, msg.header.meetingId, failReasonCode, failReason)
UserDAO.updateJoinError(msg.body.userId, failReasonCode, failReason)
state
}
private def handleUserReconnecting(user: UserState, msg: UserJoinMeetingReqMsg, state: MeetingState2x): MeetingState2x = {
if (user.userLeftFlag.left) {
resetUserLeftFlag(msg)
}
state
}
private def resetUserLeftFlag(msg: UserJoinMeetingReqMsg) = {
log.info("Resetting flag that user left meeting. user {}", msg.body.userId)
sendUserLeftFlagUpdatedEvtMsg(outGW, liveMeeting, msg.body.userId, false)
Users2x.resetUserLeftFlag(liveMeeting.users2x, msg.body.userId)
}
private def validateMaxParticipants(regUser: RegisteredUser): Either[(String, String), Unit] = {
val userHasJoinedAlready = RegisteredUsers.checkUserExtIdHasJoined(regUser.externId, liveMeeting.registeredUsers)
val maxParticipants = liveMeeting.props.usersProp.maxUsers - 1
if (maxParticipants > 0 && //0 = no limit
RegisteredUsers.numUniqueJoinedUsers(liveMeeting.registeredUsers) >= maxParticipants &&
!userHasJoinedAlready) {
Left(("The maximum number of participants allowed for this meeting has been reached.", EjectReasonCode.MAX_PARTICIPANTS))
} else {
Right(())
}
}
private def checkIfUserGuestStatusIsAllowed(user: RegisteredUser): Either[(String, String), Unit] = {
if (user.guestStatus != GuestStatus.ALLOW) {
Left(("User is not allowed to join", EjectReasonCode.PERMISSION_FAILED))
} else {
Right(())
}
}
private def checkIfUserIsBanned(user: RegisteredUser): Either[(String, String), Unit] = {
if (user.banned) {
Left(("Banned user rejoining", EjectReasonCode.BANNED_USER_REJOINING))
} else {
Right(())
}
}
private def checkIfUserLoggedOut(user: RegisteredUser): Either[(String, String), Unit] = {
if (user.loggedOut) {
Left(("User had logged out", EjectReasonCode.USER_LOGGED_OUT))
} else {
Right(())
}
}
private def updateParentMeetingWithNewListOfUsers() = {
if (liveMeeting.props.meetingProp.isBreakout) {
BreakoutHdlrHelpers.updateParentMeetingWithUsers(liveMeeting, eventBus)
}
}
private def notifyPreviousUsersWithSameExtId(regUser: RegisteredUser) = {
RegisteredUsers.findAllWithExternUserId(regUser.externId, liveMeeting.registeredUsers)
.filter(_.id != regUser.id)
.foreach { previousUser =>
sendUserConnectedNotification(previousUser, regUser, liveMeeting)
}
}
private def sendUserConnectedNotification(previousUser: RegisteredUser, newUser: RegisteredUser, liveMeeting: LiveMeeting) = {
val notifyUserEvent = MsgBuilder.buildNotifyUserInMeetingEvtMsg(
previousUser.id,
liveMeeting.props.meetingProp.intId,
"info",
"promote",
"app.mobileAppModal.userConnectedWithSameId",
"Notification to warn that user connect again from other browser/device",
Vector(newUser.name)
)
outGW.send(notifyUserEvent)
}
private def clearCachedVoiceUser(regUser: RegisteredUser) =
// fresh user joined (not due to reconnection). Clear (pop) the cached voice user
VoiceUsers.recoverVoiceUser(liveMeeting.voiceUsers, regUser.id)
private def clearExpiredUserState(regUser: RegisteredUser) =
UserStateDAO.updateExpired(regUser.id, false)
private def invalidateUserGraphqlConnection(regUser: RegisteredUser) =
Sender.sendInvalidateUserGraphqlConnectionSysMsg(liveMeeting.props.meetingProp.intId, regUser.id, regUser.sessionToken, "user_joined", outGW)
}

View File

@ -2,6 +2,7 @@ package org.bigbluebutton.core.apps.users
import org.bigbluebutton.common2.msgs._
import org.bigbluebutton.core.bus.InternalEventBus
import org.bigbluebutton.core.db.UserDAO
import org.bigbluebutton.core.domain.MeetingState2x
import org.bigbluebutton.core.models._
import org.bigbluebutton.core.running.{ HandlerHelpers, LiveMeeting, OutMsgRouter }
@ -15,103 +16,79 @@ trait ValidateAuthTokenReqMsgHdlr extends HandlerHelpers {
val eventBus: InternalEventBus
def handleValidateAuthTokenReqMsg(msg: ValidateAuthTokenReqMsg, state: MeetingState2x): MeetingState2x = {
log.debug("RECEIVED ValidateAuthTokenReqMsg msg {}", msg)
log.debug(s"Received ValidateAuthTokenReqMsg msg $msg")
var failReason = "Invalid auth token."
var failReasonCode = EjectReasonCode.VALIDATE_TOKEN
val regUser = RegisteredUsers.getRegisteredUserWithToken(msg.body.authToken, msg.body.userId, liveMeeting.registeredUsers)
log.info(s"Number of registered users [${RegisteredUsers.numRegisteredUsers(liveMeeting.registeredUsers)}]")
log.info("Number of registered users [{}]", RegisteredUsers.numRegisteredUsers(liveMeeting.registeredUsers))
val regUser = RegisteredUsers.getRegisteredUserWithToken(msg.body.authToken, msg.body.userId,
liveMeeting.registeredUsers)
regUser match {
case Some(u) =>
// Check if maxParticipants has been reached
// User are able to reenter if he already joined previously with the same extId
val hasReachedMaxParticipants = liveMeeting.props.usersProp.maxUsers > 0 &&
RegisteredUsers.numUniqueJoinedUsers(liveMeeting.registeredUsers) >= liveMeeting.props.usersProp.maxUsers &&
RegisteredUsers.checkUserExtIdHasJoined(u.externId, liveMeeting.registeredUsers) == false
regUser.fold {
sendFailedValidateAuthTokenRespMsg(msg, "Invalid auth token.", EjectReasonCode.VALIDATE_TOKEN)
} { user =>
val validationResult = for {
_ <- checkIfUserGuestStatusIsAllowed(user)
_ <- checkIfUserIsBanned(user)
_ <- checkIfUserLoggedOut(user)
_ <- validateMaxParticipants(user)
} yield user
// Check if banned user is rejoining.
// Fail validation if ejected user is rejoining.
// ralam april 21, 2020
if (u.guestStatus == GuestStatus.ALLOW && !u.banned && !u.loggedOut && !hasReachedMaxParticipants) {
userValidated(u, state)
} else {
if (u.banned) {
failReason = "Banned user rejoining"
failReasonCode = EjectReasonCode.BANNED_USER_REJOINING
} else if (u.loggedOut) {
failReason = "User had logged out"
failReasonCode = EjectReasonCode.USER_LOGGED_OUT
} else if (hasReachedMaxParticipants) {
failReason = "The maximum number of participants allowed for this meeting has been reached."
failReasonCode = EjectReasonCode.MAX_PARTICIPANTS
}
validateTokenFailed(
outGW,
meetingId = liveMeeting.props.meetingProp.intId,
userId = msg.header.userId,
authToken = msg.body.authToken,
valid = false,
waitForApproval = false,
failReason,
failReasonCode,
state
)
}
validationResult.fold(
reason => sendFailedValidateAuthTokenRespMsg(msg, reason._1, reason._2),
validUser => sendSuccessfulValidateAuthTokenRespMsg(validUser)
)
}
case None =>
validateTokenFailed(
outGW,
meetingId = liveMeeting.props.meetingProp.intId,
userId = msg.header.userId,
authToken = msg.body.authToken,
valid = false,
waitForApproval = false,
failReason,
failReasonCode,
state
)
state
}
private def validateMaxParticipants(user: RegisteredUser): Either[(String, String), Unit] = {
if (liveMeeting.props.usersProp.maxUsers > 0 &&
RegisteredUsers.numUniqueJoinedUsers(liveMeeting.registeredUsers) >= liveMeeting.props.usersProp.maxUsers &&
RegisteredUsers.checkUserExtIdHasJoined(user.externId, liveMeeting.registeredUsers) == false) {
Left(("The maximum number of participants allowed for this meeting has been reached.", EjectReasonCode.MAX_PARTICIPANTS))
} else {
Right(())
}
}
def validateTokenFailed(
outGW: OutMsgRouter,
meetingId: String,
userId: String,
authToken: String,
valid: Boolean,
waitForApproval: Boolean,
reason: String,
reasonCode: String,
state: MeetingState2x
): MeetingState2x = {
val event = MsgBuilder.buildValidateAuthTokenRespMsg(meetingId, userId, authToken, valid, waitForApproval, 0,
0, reasonCode, reason)
outGW.send(event)
// send a system message to force disconnection
// Comment out as meteor will disconnect the client. Requested by Tiago (ralam apr 28, 2020)
//Sender.sendDisconnectClientSysMsg(meetingId, userId, SystemUser.ID, reasonCode, outGW)
state
private def checkIfUserGuestStatusIsAllowed(user: RegisteredUser): Either[(String, String), Unit] = {
if (user.guestStatus != GuestStatus.ALLOW) {
Left(("User is not allowed to join", EjectReasonCode.PERMISSION_FAILED))
} else {
Right(())
}
}
def sendValidateAuthTokenRespMsg(meetingId: String, userId: String, authToken: String,
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)
private def checkIfUserIsBanned(user: RegisteredUser): Either[(String, String), Unit] = {
if (user.banned) {
Left(("Banned user rejoining", EjectReasonCode.BANNED_USER_REJOINING))
} else {
Right(())
}
}
private def checkIfUserLoggedOut(user: RegisteredUser): Either[(String, String), Unit] = {
if (user.loggedOut) {
Left(("User had logged out", EjectReasonCode.USER_LOGGED_OUT))
} else {
Right(())
}
}
private def sendFailedValidateAuthTokenRespMsg(msg: ValidateAuthTokenReqMsg, failReason: String, failReasonCode: String) = {
UserDAO.updateJoinError(msg.body.userId, failReasonCode, failReason)
val event = MsgBuilder.buildValidateAuthTokenRespMsg(liveMeeting.props.meetingProp.intId, msg.header.userId, msg.body.authToken, false, false, 0,
0, failReasonCode, failReason)
outGW.send(event)
}
def userValidated(user: RegisteredUser, state: MeetingState2x): MeetingState2x = {
def sendSuccessfulValidateAuthTokenRespMsg(user: RegisteredUser) = {
val meetingId = liveMeeting.props.meetingProp.intId
val updatedUser = RegisteredUsers.updateUserLastAuthTokenValidated(liveMeeting.registeredUsers, user)
sendValidateAuthTokenRespMsg(meetingId, updatedUser.id, updatedUser.authToken, valid = true, waitForApproval = false, updatedUser.registeredOn, updatedUser.lastAuthTokenValidatedOn)
state
val event = MsgBuilder.buildValidateAuthTokenRespMsg(meetingId, updatedUser.id, updatedUser.authToken, true, false, updatedUser.registeredOn,
updatedUser.lastAuthTokenValidatedOn, EjectReasonCode.NOT_EJECT, "User not ejected")
outGW.send(event)
}
def sendAllUsersInMeeting(requesterId: String): Unit = {

View File

@ -77,12 +77,12 @@ object MeetingDAO {
).onComplete {
case Success(rowsAffected) => {
DatabaseConnection.logger.debug(s"$rowsAffected row(s) inserted in Meeting table!")
ChatDAO.insert(meetingProps.meetingProp.intId, GroupChatApp.createDefaultPublicGroupChat())
MeetingUsersPoliciesDAO.insert(meetingProps.meetingProp.intId, meetingProps.usersProp)
MeetingLockSettingsDAO.insert(meetingProps.meetingProp.intId, meetingProps.lockSettingsProps)
MeetingMetadataDAO.insert(meetingProps.meetingProp.intId, meetingProps.metadataProp)
MeetingRecordingPoliciesDAO.insert(meetingProps.meetingProp.intId, meetingProps.recordProp)
MeetingVoiceDAO.insert(meetingProps.meetingProp.intId, meetingProps.voiceProp)
ChatDAO.insert(meetingProps.meetingProp.intId, GroupChatApp.createDefaultPublicGroupChat())
MeetingWelcomeDAO.insert(meetingProps.meetingProp.intId, meetingProps.welcomeProp)
MeetingGroupDAO.insert(meetingProps.meetingProp.intId, meetingProps.groups)
MeetingBreakoutDAO.insert(meetingProps.meetingProp.intId, meetingProps.breakoutProps)

View File

@ -19,7 +19,7 @@ class UserConnectionStatusDbTableDef(tag: Tag) extends Table[UserConnectionStatu
val connectionAliveAt = column[Option[java.sql.Timestamp]]("connectionAliveAt")
}
object UserConnectionStatusdDAO {
object UserConnectionStatusDAO {
def insert(meetingId: String, userId: String) = {
DatabaseConnection.db.run(

View File

@ -6,29 +6,31 @@ import scala.concurrent.ExecutionContext.Implicits.global
import scala.util.{Failure, Success}
case class UserDbModel(
userId: String,
extId: String,
meetingId: String,
name: String,
role: String,
avatar: String = "",
color: String = "",
sessionToken: String = "",
authed: Boolean = false,
joined: Boolean = false,
banned: Boolean = false,
loggedOut: Boolean = false,
guest: Boolean,
guestStatus: String,
registeredOn: Long,
excludeFromDashboard: Boolean,
userId: String,
extId: String,
meetingId: String,
name: String,
role: String,
avatar: String = "",
color: String = "",
sessionToken: String = "",
authed: Boolean = false,
joined: Boolean = false,
joinErrorMessage: Option[String],
joinErrorCode: Option[String],
banned: Boolean = false,
loggedOut: Boolean = false,
guest: Boolean,
guestStatus: String,
registeredOn: Long,
excludeFromDashboard: Boolean,
)
class UserDbTableDef(tag: Tag) extends Table[UserDbModel](tag, None, "user") {
override def * = (
userId,extId,meetingId,name,role,avatar,color, sessionToken, authed,joined,banned,loggedOut,guest,guestStatus,registeredOn,excludeFromDashboard) <> (UserDbModel.tupled, UserDbModel.unapply)
userId,extId,meetingId,name,role,avatar,color, sessionToken, authed,joined,joinErrorCode, joinErrorMessage, banned,loggedOut,guest,guestStatus,registeredOn,excludeFromDashboard) <> (UserDbModel.tupled, UserDbModel.unapply)
val userId = column[String]("userId", O.PrimaryKey)
val extId = column[String]("extId")
val meetingId = column[String]("meetingId")
@ -39,6 +41,8 @@ class UserDbTableDef(tag: Tag) extends Table[UserDbModel](tag, None, "user") {
val sessionToken = column[String]("sessionToken")
val authed = column[Boolean]("authed")
val joined = column[Boolean]("joined")
val joinErrorCode = column[Option[String]]("joinErrorCode")
val joinErrorMessage = column[Option[String]]("joinErrorMessage")
val banned = column[Boolean]("banned")
val loggedOut = column[Boolean]("loggedOut")
val guest = column[Boolean]("guest")
@ -62,6 +66,8 @@ object UserDAO {
sessionToken = regUser.sessionToken,
authed = regUser.authed,
joined = regUser.joined,
joinErrorCode = None,
joinErrorMessage = None,
banned = regUser.banned,
loggedOut = regUser.loggedOut,
guest = regUser.guest,
@ -73,10 +79,10 @@ object UserDAO {
).onComplete {
case Success(rowsAffected) => {
DatabaseConnection.logger.debug(s"$rowsAffected row(s) inserted in User table!")
ChatUserDAO.insertUserPublicChat(meetingId, regUser.id)
UserConnectionStatusdDAO.insert(meetingId, regUser.id)
UserConnectionStatusDAO.insert(meetingId, regUser.id)
UserCustomParameterDAO.insert(regUser.id, regUser.customParameters)
UserClientSettingsDAO.insert(regUser.id, meetingId)
ChatUserDAO.insertUserPublicChat(meetingId, regUser.id)
}
case Failure(e) => DatabaseConnection.logger.debug(s"Error inserting user: $e")
}
@ -94,6 +100,17 @@ object UserDAO {
}
}
def updateJoinError(userId: String, joinErrorCode: String, joinErrorMessage: String) = {
DatabaseConnection.db.run(
TableQuery[UserDbTableDef]
.filter(_.userId === userId)
.map(u => (u.joined, u.joinErrorCode, u.joinErrorMessage))
.update((false, Some(joinErrorCode), Some(joinErrorMessage)))
).onComplete {
case Success(rowsAffected) => DatabaseConnection.logger.debug(s"$rowsAffected row(s) updated on user (Joined) table!")
case Failure(e) => DatabaseConnection.logger.debug(s"Error updating user (Joined): $e")
}
}
def delete(intId: String) = {

View File

@ -30,6 +30,7 @@ function App() {
const [sessionToken, setSessionToken] = useState(null);
const [userId, setUserId] = useState(null);
const [userName, setUserName] = useState(null);
const [userAuthToken, setUserAuthToken] = useState(null);
const [graphqlClient, setGraphqlClient] = useState(null);
const [enterApiResponse, setEnterApiResponse] = useState('');
@ -46,6 +47,7 @@ function App() {
if(json?.response?.internalUserID) {
setUserId(json.response.internalUserID);
setUserName(json.response.fullname);
setUserAuthToken(json.response.authToken);
}
});
}
@ -110,7 +112,7 @@ function App() {
<br />
<PluginDataChannel userId={userId} />
<br />
<MyInfo />
<MyInfo userAuthToken={userAuthToken} />
<br />
<PresPresentationUploadToken />
<br />

View File

@ -1,7 +1,7 @@
import {gql, useMutation, useQuery, useSubscription} from '@apollo/client';
import {gql, useMutation, useSubscription} from '@apollo/client';
import React from "react";
export default function MyInfo() {
export default function MyInfo({userAuthToken}) {
//where is not necessary once user can update only its own status
//Hasura accepts "now()" as value to timestamp fields
@ -20,17 +20,32 @@ export default function MyInfo() {
updateUserClientEchoTestRunningAtMeAsNow();
};
const [dispatchUserJoin] = useMutation(gql`
mutation UserJoin($authToken: String!, $clientType: String!) {
userJoin(
authToken: $authToken,
clientType: $clientType,
)
}
`);
const handleDispatchUserJoin = (authToken) => {
dispatchUserJoin({
variables: {
authToken: authToken,
clientType: 'HTML5',
},
});
};
const { loading, error, data } = useSubscription(
gql`subscription {
user_current {
userId
name
meeting {
name
}
echoTestRunningAt
isRunningEchoTest
joined
joinErrorCode
joinErrorMessage
}
}`
);
@ -45,23 +60,23 @@ export default function MyInfo() {
{/*<th>Id</th>*/}
<th>userId</th>
<th>name</th>
<th>Meeting</th>
<th>echoTestRunningAt</th>
<th>isRunningEchoTest</th>
<th>joined</th>
<th>joinErrorCode</th>
<th>joinErrorMessage</th>
</tr>
</thead>
<tbody>
{data.user_current.map((curr) => {
console.log('meeting', curr);
console.log('user_current', curr);
return (
<tr key={curr.userId}>
<td>{curr.userId}</td>
<td>{curr.name}</td>
<td>{curr.meeting.name}</td>
<td>{curr.echoTestRunningAt}
<button onClick={() => handleUpdateUserEchoTestRunningAt()}>Set running now!</button>
<td>{curr.joined ? 'Yes' : 'No'}
{curr.joined ? '' : <button onClick={() => handleDispatchUserJoin(userAuthToken)}>Join Now!</button>}
</td>
<td>{curr.isRunningEchoTest ? 'Yes' : 'No'}</td>
<td>{curr.joinErrorCode}</td>
<td>{curr.joinErrorMessage}</td>
</tr>
);
})}

View File

@ -230,6 +230,8 @@ CREATE TABLE "user" (
"sessionToken" varchar(16),
"authed" bool,
"joined" bool,
"joinErrorCode" varchar(50),
"joinErrorMessage" varchar(400),
"banned" bool,
"loggedOut" bool, -- when user clicked Leave meeting button
"guest" bool, --used for dialIn
@ -392,6 +394,8 @@ AS SELECT "user"."userId",
"user"."role",
"user"."authed",
"user"."joined",
"user"."joinErrorCode",
"user"."joinErrorMessage",
"user"."disconnected",
"user"."expired",
"user"."ejected",

View File

@ -23,3 +23,10 @@ type Mutation {
): Boolean
}
type Mutation {
userJoin(
authToken: String!
clientType: String!
): Boolean
}

View File

@ -17,6 +17,13 @@ actions:
handler: '{{HASURA_BBB_GRAPHQL_ACTIONS_ADAPTER_URL}}'
permissions:
- role: bbb_client
- name: userJoin
definition:
kind: synchronous
handler: '{{HASURA_BBB_GRAPHQL_ACTIONS_ADAPTER_URL}}'
permissions:
- role: pre_join_bbb_client
- role: bbb_client
custom_types:
enums: []
input_objects: []

View File

@ -149,6 +149,8 @@ select_permissions:
- isDialIn
- isModerator
- isRunningEchoTest
- joinErrorCode
- joinErrorMessage
- joined
- locked
- loggedOut
@ -158,16 +160,39 @@ select_permissions:
- pinned
- presenter
- raiseHand
- registeredAt
- registeredOn
- role
- speechLocale
- userId
filter:
_and:
- meetingId:
_eq: X-Hasura-MeetingId
- userId:
_eq: X-Hasura-UserId
userId:
_eq: X-Hasura-UserId
- role: pre_join_bbb_client
permission:
columns:
- authed
- banned
- color
- disconnected
- ejectReason
- ejectReasonCode
- ejected
- expired
- extId
- guest
- joinErrorCode
- joinErrorMessage
- joined
- loggedOut
- name
- registeredAt
- registeredOn
- userId
filter:
userId:
_eq: X-Hasura-UserId
comment: ""
update_permissions:
- role: bbb_client
permission:

View File

@ -81,12 +81,12 @@ class ConnectionController {
def builder = new JsonBuilder()
builder {
"response" "authorized"
"X-Hasura-Role" "bbb_client"
"X-Hasura-Locked" u.locked ? "true" : "false"
"X-Hasura-LockedInMeeting" u.locked ? userSession.meetingID : ""
"X-Hasura-LockedUserId" u.locked ? userSession.internalUserId : ""
"X-Hasura-ModeratorInMeeting" u.isModerator() ? userSession.meetingID : ""
"X-Hasura-PresenterInMeeting" u.isPresenter() ? userSession.meetingID : ""
"X-Hasura-Role" u ? "bbb_client" : "pre_join_bbb_client"
"X-Hasura-Locked" u && u.locked ? "true" : "false"
"X-Hasura-LockedInMeeting" u && u.locked ? userSession.meetingID : ""
"X-Hasura-LockedUserId" u && u.locked ? userSession.internalUserId : ""
"X-Hasura-ModeratorInMeeting" u && u.isModerator() ? userSession.meetingID : ""
"X-Hasura-PresenterInMeeting" u && u.isPresenter() ? userSession.meetingID : ""
"X-Hasura-UserId" userSession.internalUserId
"X-Hasura-MeetingId" userSession.meetingID
}