refactor(guest-wait): turn guest wait page into a React component (#20344)
* refactor(guest-wait): turn guest wait page into a React component * Fix rendering when the meeting is ended * refactor(guest-wait): Backend portion for migration of `guest-wait` to Graphql * Add message timeout * Remove static guest wait page --------- Co-authored-by: Gustavo Trott <gustavo@trott.com.br>
This commit is contained in:
parent
6e3b582bdd
commit
5d3178f15d
@ -21,6 +21,12 @@ case class IsMeetingActorAliveMessage(meetingId: String) extends InMessage
|
||||
|
||||
case class MonitorNumberOfUsersInternalMsg(meetingID: String) extends InMessage
|
||||
|
||||
/**
|
||||
* Audit message sent to meeting to trigger updating clients of meeting time remaining.
|
||||
* @param meetingId
|
||||
*/
|
||||
case class MonitorGuestWaitPresenceInternalMsg(meetingId: String) extends InMessage
|
||||
|
||||
/**
|
||||
* Audit message sent to meeting to trigger updating clients of meeting time remaining.
|
||||
* @param meetingId
|
||||
|
@ -5,7 +5,6 @@ import org.bigbluebutton.core2.message.handlers.guests._
|
||||
|
||||
trait GuestsApp extends GetGuestsWaitingApprovalReqMsgHdlr
|
||||
with GuestsWaitingApprovedMsgHdlr
|
||||
with GuestWaitingLeftMsgHdlr
|
||||
with UpdatePositionInWaitingQueueReqMsgHdlr
|
||||
with SetGuestPolicyMsgHdlr
|
||||
with SetGuestLobbyMessageMsgHdlr
|
||||
|
@ -3,7 +3,7 @@ package org.bigbluebutton.core.apps.users
|
||||
import org.bigbluebutton.common2.msgs.{ BbbClientMsgHeader, BbbCommonEnvCoreMsg, BbbCoreEnvelope, MessageTypes, Routing, UserMobileFlagChangedEvtMsg, UserMobileFlagChangedEvtMsgBody }
|
||||
import org.bigbluebutton.core.api.UserEstablishedGraphqlConnectionInternalMsg
|
||||
import org.bigbluebutton.core.domain.MeetingState2x
|
||||
import org.bigbluebutton.core.models.{ UserState, Users2x }
|
||||
import org.bigbluebutton.core.models.{ RegisteredUsers, UserState, Users2x }
|
||||
import org.bigbluebutton.core.running.{ HandlerHelpers, LiveMeeting, MeetingActor, OutMsgRouter }
|
||||
|
||||
trait UserEstablishedGraphqlConnectionInternalMsgHdlr extends HandlerHelpers {
|
||||
@ -14,6 +14,13 @@ trait UserEstablishedGraphqlConnectionInternalMsgHdlr extends HandlerHelpers {
|
||||
|
||||
def handleUserEstablishedGraphqlConnectionInternalMsg(msg: UserEstablishedGraphqlConnectionInternalMsg, state: MeetingState2x): MeetingState2x = {
|
||||
log.info("Received user established a graphql connection. msg={} meetingId={}", msg, liveMeeting.props.meetingProp.intId)
|
||||
|
||||
for {
|
||||
regUser <- RegisteredUsers.findWithUserId(msg.userId, liveMeeting.registeredUsers)
|
||||
} yield {
|
||||
RegisteredUsers.updateUserConnectedToGraphql(liveMeeting.registeredUsers, regUser, graphqlConnected = true)
|
||||
}
|
||||
|
||||
Users2x.findWithIntId(liveMeeting.users2x, msg.userId) match {
|
||||
case Some(connectingUser) =>
|
||||
var userNewState = connectingUser
|
||||
|
@ -19,6 +19,12 @@ trait UserLeaveReqMsgHdlr extends HandlerHelpers {
|
||||
def handleUserClosedAllGraphqlConnectionsInternalMsg(msg: UserClosedAllGraphqlConnectionsInternalMsg, state: MeetingState2x): MeetingState2x = {
|
||||
log.info("Received user closed all graphql connections. user {} meetingId={}", msg.userId, liveMeeting.props.meetingProp.intId)
|
||||
|
||||
for {
|
||||
regUser <- RegisteredUsers.findWithUserId(msg.userId, liveMeeting.registeredUsers)
|
||||
} yield {
|
||||
RegisteredUsers.updateUserConnectedToGraphql(liveMeeting.registeredUsers, regUser, graphqlConnected = false)
|
||||
}
|
||||
|
||||
handleUserLeaveReq(msg.userId, liveMeeting.props.meetingProp.intId, loggedOut = false, state)
|
||||
}
|
||||
|
||||
|
@ -31,7 +31,7 @@ object UsersApp {
|
||||
u <- RegisteredUsers.findWithUserId(userId, liveMeeting.registeredUsers)
|
||||
} yield {
|
||||
|
||||
RegisteredUsers.eject(u.id, liveMeeting.registeredUsers, false)
|
||||
RegisteredUsers.eject(u.id, liveMeeting.registeredUsers, ban = false)
|
||||
|
||||
val event = MsgBuilder.buildGuestWaitingLeftEvtMsg(liveMeeting.props.meetingProp.intId, u.id)
|
||||
outGW.send(event)
|
||||
|
@ -143,6 +143,7 @@ object UserDAO {
|
||||
TableQuery[UserDbTableDef]
|
||||
.filter(_.meetingId === meetingId)
|
||||
.filter(_.userId === userId)
|
||||
.filter(_.loggedOut =!= true)
|
||||
.map(u => (u.loggedOut))
|
||||
.update((true))
|
||||
).onComplete {
|
||||
|
@ -26,6 +26,8 @@ object RegisteredUsers {
|
||||
System.currentTimeMillis(),
|
||||
0,
|
||||
false,
|
||||
0,
|
||||
false,
|
||||
false,
|
||||
enforceLayout,
|
||||
customParameters,
|
||||
@ -37,6 +39,10 @@ object RegisteredUsers {
|
||||
users.toVector.find(u => u.authToken == token)
|
||||
}
|
||||
|
||||
def findAll(users: RegisteredUsers): Vector[RegisteredUser] = {
|
||||
users.toVector
|
||||
}
|
||||
|
||||
def findWithUserId(id: String, users: RegisteredUsers): Option[RegisteredUser] = {
|
||||
users.toVector.find(ru => id == ru.id)
|
||||
}
|
||||
@ -123,13 +129,14 @@ object RegisteredUsers {
|
||||
u
|
||||
} else {
|
||||
users.delete(ejectedUser.id)
|
||||
// UserDAO.softDelete(ejectedUser) it's being removed in User2x already
|
||||
UserDAO.softDelete(ejectedUser.meetingId, ejectedUser.id)
|
||||
ejectedUser
|
||||
}
|
||||
}
|
||||
def eject(id: String, users: RegisteredUsers, ban: Boolean): Option[RegisteredUser] = {
|
||||
|
||||
def eject(userId: String, users: RegisteredUsers, ban: Boolean): Option[RegisteredUser] = {
|
||||
for {
|
||||
ru <- findWithUserId(id, users)
|
||||
ru <- findWithUserId(userId, users)
|
||||
} yield {
|
||||
banOrEjectUser(ru, users, ban)
|
||||
}
|
||||
@ -172,6 +179,23 @@ object RegisteredUsers {
|
||||
u
|
||||
}
|
||||
|
||||
def updateUserConnectedToGraphql(users: RegisteredUsers, user: RegisteredUser, graphqlConnected: Boolean): RegisteredUser = {
|
||||
val u = user.copy(
|
||||
graphqlConnected = graphqlConnected,
|
||||
graphqlDisconnectedOn = {
|
||||
if(graphqlConnected) {
|
||||
0
|
||||
} else if(!graphqlConnected && user.graphqlDisconnectedOn == 0) {
|
||||
System.currentTimeMillis()
|
||||
} else {
|
||||
user.graphqlDisconnectedOn
|
||||
}
|
||||
}
|
||||
)
|
||||
users.save(u)
|
||||
u
|
||||
}
|
||||
|
||||
def setUserLoggedOutFlag(users: RegisteredUsers, user: RegisteredUser): RegisteredUser = {
|
||||
val u = user.copy(loggedOut = true)
|
||||
users.save(u)
|
||||
@ -216,6 +240,8 @@ case class RegisteredUser(
|
||||
excludeFromDashboard: Boolean,
|
||||
registeredOn: Long,
|
||||
lastAuthTokenValidatedOn: Long,
|
||||
graphqlConnected: Boolean,
|
||||
graphqlDisconnectedOn: Long,
|
||||
joined: Boolean,
|
||||
banned: Boolean,
|
||||
enforceLayout: String,
|
||||
|
@ -82,8 +82,6 @@ class ReceivedJsonMsgHandlerActor(
|
||||
routeGenericMsg[GetGuestsWaitingApprovalReqMsg](envelope, jsonNode)
|
||||
case GuestsWaitingApprovedMsg.NAME =>
|
||||
routeGenericMsg[GuestsWaitingApprovedMsg](envelope, jsonNode)
|
||||
case GuestWaitingLeftMsg.NAME =>
|
||||
routeGenericMsg[GuestWaitingLeftMsg](envelope, jsonNode)
|
||||
case UpdatePositionInWaitingQueueReqMsg.NAME =>
|
||||
routeGenericMsg[UpdatePositionInWaitingQueueReqMsg](envelope, jsonNode)
|
||||
case SetGuestPolicyCmdMsg.NAME =>
|
||||
|
@ -272,6 +272,7 @@ class MeetingActor(
|
||||
//=======================================
|
||||
// internal messages
|
||||
case msg: MonitorNumberOfUsersInternalMsg => handleMonitorNumberOfUsers(msg)
|
||||
case msg: MonitorGuestWaitPresenceInternalMsg => handleMonitorGuestWaitPresenceInternalMsg(msg)
|
||||
case msg: SetPresenterInDefaultPodInternalMsg => state = presentationPodsApp.handleSetPresenterInDefaultPodInternalMsg(msg, state, liveMeeting, msgBus)
|
||||
case msg: UserClosedAllGraphqlConnectionsInternalMsg =>
|
||||
state = handleUserClosedAllGraphqlConnectionsInternalMsg(msg, state)
|
||||
@ -652,7 +653,6 @@ class MeetingActor(
|
||||
case m: GuestsWaitingApprovedMsg =>
|
||||
handleGuestsWaitingApprovedMsg(m)
|
||||
updateUserLastActivity(m.header.userId)
|
||||
case m: GuestWaitingLeftMsg => handleGuestWaitingLeftMsg(m)
|
||||
case m: GetGuestPolicyReqMsg => handleGetGuestPolicyReqMsg(m)
|
||||
case m: UpdatePositionInWaitingQueueReqMsg => handleUpdatePositionInWaitingQueueReqMsg(m)
|
||||
case m: SetPrivateGuestLobbyMessageCmdMsg =>
|
||||
@ -914,6 +914,25 @@ class MeetingActor(
|
||||
checkIfNeedToEndMeetingWhenNoModerators(liveMeeting)
|
||||
}
|
||||
|
||||
def handleMonitorGuestWaitPresenceInternalMsg(msg: MonitorGuestWaitPresenceInternalMsg) {
|
||||
if (liveMeeting.props.usersProp.waitingGuestUsersTimeout > 0) {
|
||||
for {
|
||||
regUser <- RegisteredUsers.findAll(liveMeeting.registeredUsers)
|
||||
} yield {
|
||||
if (!regUser.loggedOut
|
||||
&& regUser.guestStatus == GuestStatus.WAIT
|
||||
&& !regUser.graphqlConnected
|
||||
&& regUser.graphqlDisconnectedOn != 0) {
|
||||
val diff = System.currentTimeMillis() - regUser.graphqlDisconnectedOn
|
||||
if (diff > liveMeeting.props.usersProp.waitingGuestUsersTimeout) {
|
||||
GuestsWaiting.remove(liveMeeting.guestsWaiting, regUser.id)
|
||||
UsersApp.guestWaitingLeft(liveMeeting, regUser.id, outGW)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
def checkVoiceConfUsersStatus(): Unit = {
|
||||
val event = MsgBuilder.buildLastcheckVoiceConfUsersStatus(
|
||||
props.meetingProp.intId,
|
||||
|
@ -71,6 +71,7 @@ class MeetingActorAudit(
|
||||
|
||||
def handleMonitorNumberOfWebUsers() {
|
||||
eventBus.publish(BigBlueButtonEvent(props.meetingProp.intId, MonitorNumberOfUsersInternalMsg(props.meetingProp.intId)))
|
||||
eventBus.publish(BigBlueButtonEvent(props.meetingProp.intId, MonitorGuestWaitPresenceInternalMsg(props.meetingProp.intId)))
|
||||
|
||||
// Trigger updating users of time remaining on meeting.
|
||||
eventBus.publish(BigBlueButtonEvent(props.meetingProp.intId, SendTimeRemainingAuditInternalMsg(props.meetingProp.intId, 0)))
|
||||
|
@ -151,7 +151,6 @@ class AnalyticsActor(val includeChat: Boolean) extends Actor with ActorLogging {
|
||||
// Guest Management
|
||||
case m: GuestsWaitingApprovedMsg => logMessage(msg)
|
||||
case m: GuestsWaitingApprovedEvtMsg => logMessage(msg)
|
||||
case m: GuestWaitingLeftMsg => logMessage(msg)
|
||||
case m: GuestWaitingLeftEvtMsg => logMessage(msg)
|
||||
case m: GuestsWaitingForApprovalEvtMsg => logMessage(msg)
|
||||
case m: UpdatePositionInWaitingQueueReqMsg => logMessage(msg)
|
||||
|
@ -1,18 +0,0 @@
|
||||
package org.bigbluebutton.core2.message.handlers.guests
|
||||
|
||||
import org.bigbluebutton.common2.msgs.GuestWaitingLeftMsg
|
||||
import org.bigbluebutton.core.apps.users.UsersApp
|
||||
import org.bigbluebutton.core.models.GuestsWaiting
|
||||
import org.bigbluebutton.core.running.{ BaseMeetingActor, LiveMeeting, OutMsgRouter }
|
||||
|
||||
trait GuestWaitingLeftMsgHdlr {
|
||||
this: BaseMeetingActor =>
|
||||
|
||||
val liveMeeting: LiveMeeting
|
||||
val outGW: OutMsgRouter
|
||||
|
||||
def handleGuestWaitingLeftMsg(msg: GuestWaitingLeftMsg): Unit = {
|
||||
GuestsWaiting.remove(liveMeeting.guestsWaiting, msg.body.userId)
|
||||
UsersApp.guestWaitingLeft(liveMeeting, msg.body.userId, outGW)
|
||||
}
|
||||
}
|
@ -49,7 +49,8 @@ case class UsersProp(
|
||||
meetingLayout: String,
|
||||
allowModsToUnmuteUsers: Boolean,
|
||||
allowModsToEjectCameras: Boolean,
|
||||
authenticatedGuest: Boolean
|
||||
authenticatedGuest: Boolean,
|
||||
waitingGuestUsersTimeout: Long,
|
||||
)
|
||||
|
||||
case class MetadataProp(metadata: collection.immutable.Map[String, String])
|
||||
|
@ -63,16 +63,6 @@ case class GuestApprovedEvtMsg(
|
||||
) extends BbbCoreMsg
|
||||
case class GuestApprovedEvtMsgBody(status: String, approvedBy: String)
|
||||
|
||||
/**
|
||||
* Message from bbb-web when it detects a guest stopped polling for his status.
|
||||
*/
|
||||
object GuestWaitingLeftMsg { val NAME = "GuestWaitingLeftMsg" }
|
||||
case class GuestWaitingLeftMsg(
|
||||
header: BbbClientMsgHeader,
|
||||
body: GuestWaitingLeftMsgBody
|
||||
) extends StandardMsg
|
||||
case class GuestWaitingLeftMsgBody(userId: String)
|
||||
|
||||
/**
|
||||
* Message sent to all clients that a guest left the waiting page.
|
||||
*/
|
||||
|
@ -45,7 +45,6 @@ import org.bigbluebutton.api2.domain.UploadedTrack;
|
||||
import org.bigbluebutton.common2.redis.RedisStorageService;
|
||||
import org.bigbluebutton.presentation.PresentationUrlDownloadService;
|
||||
import org.bigbluebutton.presentation.imp.SlidesGenerationProgressNotifier;
|
||||
import org.bigbluebutton.web.services.WaitingGuestCleanupTimerTask;
|
||||
import org.bigbluebutton.web.services.UserCleanupTimerTask;
|
||||
import org.bigbluebutton.web.services.EnteredUserCleanupTimerTask;
|
||||
import org.bigbluebutton.web.services.callback.CallbackUrlService;
|
||||
@ -79,7 +78,6 @@ public class MeetingService implements MessageListener {
|
||||
|
||||
private RecordingService recordingService;
|
||||
private LearningDashboardService learningDashboardService;
|
||||
private WaitingGuestCleanupTimerTask waitingGuestCleaner;
|
||||
private UserCleanupTimerTask userCleaner;
|
||||
private EnteredUserCleanupTimerTask enteredUserCleaner;
|
||||
private StunTurnService stunTurnService;
|
||||
@ -259,31 +257,6 @@ public class MeetingService implements MessageListener {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove registered waiting guest users who left the waiting page.
|
||||
*/
|
||||
public void purgeWaitingGuestUsers() {
|
||||
for (AbstractMap.Entry<String, Meeting> entry : this.meetings.entrySet()) {
|
||||
Long now = System.currentTimeMillis();
|
||||
Meeting meeting = entry.getValue();
|
||||
ConcurrentMap<String, User> users = meeting.getUsersMap();
|
||||
for (AbstractMap.Entry<String, RegisteredUser> registeredUser : meeting.getRegisteredUsers().entrySet()) {
|
||||
String registeredUserID = registeredUser.getKey();
|
||||
RegisteredUser ru = registeredUser.getValue();
|
||||
|
||||
long elapsedTime = now - ru.getGuestWaitedOn();
|
||||
if (elapsedTime >= waitingGuestUsersTimeout && ru.getGuestStatus() == GuestPolicy.WAIT) {
|
||||
log.info("Purging user [{}]", registeredUserID);
|
||||
if (meeting.userUnregistered(registeredUserID) != null) {
|
||||
gw.guestWaitingLeft(meeting.getInternalId(), registeredUserID);
|
||||
meeting.setLeftGuestLobby(registeredUserID, true);
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private void kickOffProcessingOfRecording(Meeting m) {
|
||||
if (m.isRecord() && m.getNumUsers() == 0) {
|
||||
processRecording(m);
|
||||
@ -446,7 +419,8 @@ public class MeetingService implements MessageListener {
|
||||
m.getWebcamsOnlyForModerator(), m.getMeetingCameraCap(), m.getUserCameraCap(), m.getMaxPinnedCameras(), m.getModeratorPassword(), m.getViewerPassword(),
|
||||
m.getLearningDashboardAccessToken(), m.getCreateTime(),
|
||||
formatPrettyDate(m.getCreateTime()), m.isBreakout(), m.getSequence(), m.isFreeJoin(), m.getMetadata(),
|
||||
m.getGuestPolicy(), m.getAuthenticatedGuest(), m.getMeetingLayout(), m.getWelcomeMessageTemplate(), m.getWelcomeMessage(), m.getWelcomeMsgForModerators(),
|
||||
m.getGuestPolicy(), m.getAuthenticatedGuest(), m.getWaitingGuestUsersTimeout(), m.getMeetingLayout(),
|
||||
m.getWelcomeMessageTemplate(), m.getWelcomeMessage(), m.getWelcomeMsgForModerators(),
|
||||
m.getDialNumber(), m.getMaxUsers(), m.getMaxUserConcurrentAccesses(),
|
||||
m.getMeetingExpireIfNoUserJoinedInMinutes(), m.getMeetingExpireWhenLastUserLeftInMinutes(),
|
||||
m.getUserInactivityInspectTimerInMinutes(), m.getUserInactivityThresholdInMinutes(),
|
||||
@ -1349,7 +1323,6 @@ public class MeetingService implements MessageListener {
|
||||
|
||||
public void stop() {
|
||||
processMessage = false;
|
||||
waitingGuestCleaner.stop();
|
||||
userCleaner.stop();
|
||||
enteredUserCleaner.stop();
|
||||
}
|
||||
@ -1374,12 +1347,6 @@ public class MeetingService implements MessageListener {
|
||||
this.gw = gw;
|
||||
}
|
||||
|
||||
public void setWaitingGuestCleanupTimerTask(WaitingGuestCleanupTimerTask c) {
|
||||
waitingGuestCleaner = c;
|
||||
waitingGuestCleaner.setMeetingService(this);
|
||||
waitingGuestCleaner.start();
|
||||
}
|
||||
|
||||
public void setEnteredUserCleanupTimerTask(EnteredUserCleanupTimerTask c) {
|
||||
enteredUserCleaner = c;
|
||||
enteredUserCleaner.setMeetingService(this);
|
||||
|
@ -77,13 +77,13 @@ public class ParamsProcessorUtil {
|
||||
private String defaultHTML5ClientUrl;
|
||||
|
||||
private String graphqlWebsocketUrl;
|
||||
private String defaultGuestWaitURL;
|
||||
private Boolean allowRequestsWithoutSession = false;
|
||||
private Integer defaultHttpSessionTimeout = 14400;
|
||||
private Boolean useDefaultAvatar = false;
|
||||
private String defaultAvatarURL;
|
||||
private String defaultGuestPolicy;
|
||||
private Boolean authenticatedGuest;
|
||||
private Long waitingGuestUsersTimeout;
|
||||
private String defaultMeetingLayout;
|
||||
private int defaultMeetingDuration;
|
||||
private boolean disableRecordingDefault;
|
||||
@ -767,6 +767,7 @@ public class ParamsProcessorUtil {
|
||||
.withIsBreakout(isBreakout)
|
||||
.withGuestPolicy(guestPolicy)
|
||||
.withAuthenticatedGuest(authenticatedGuest)
|
||||
.withWaitingGuestUsersTimeout(waitingGuestUsersTimeout)
|
||||
.withAllowRequestsWithoutSession(allowRequestsWithoutSession)
|
||||
.withMeetingLayout(meetingLayout)
|
||||
.withBreakoutRoomsParams(breakoutParams)
|
||||
@ -878,10 +879,6 @@ public class ParamsProcessorUtil {
|
||||
return graphqlWebsocketUrl;
|
||||
}
|
||||
|
||||
public String getDefaultGuestWaitURL() {
|
||||
return defaultGuestWaitURL;
|
||||
}
|
||||
|
||||
public Boolean getUseDefaultLogo() {
|
||||
return useDefaultLogo;
|
||||
}
|
||||
@ -1245,10 +1242,6 @@ public class ParamsProcessorUtil {
|
||||
this.graphqlWebsocketUrl = graphqlWebsocketUrl.replace("https://","wss://");
|
||||
}
|
||||
|
||||
public void setDefaultGuestWaitURL(String url) {
|
||||
this.defaultGuestWaitURL = url;
|
||||
}
|
||||
|
||||
public void setUseDefaultLogo(Boolean value) {
|
||||
this.useDefaultLogo = value;
|
||||
}
|
||||
@ -1321,7 +1314,11 @@ public class ParamsProcessorUtil {
|
||||
this.authenticatedGuest = value;
|
||||
}
|
||||
|
||||
public void setDefaultMeetingLayout(String meetingLayout) {
|
||||
public void setWaitingGuestUsersTimeout(Long value) {
|
||||
this.waitingGuestUsersTimeout = value;
|
||||
}
|
||||
|
||||
public void setDefaultMeetingLayout(String meetingLayout) {
|
||||
this.defaultMeetingLayout = meetingLayout;
|
||||
}
|
||||
|
||||
|
@ -81,6 +81,7 @@ public class Meeting {
|
||||
private String guestLobbyMessage = "";
|
||||
private Map<String,String> usersWithGuestLobbyMessages;
|
||||
private Boolean authenticatedGuest = false;
|
||||
private long waitingGuestUsersTimeout = 30000;
|
||||
private String meetingLayout = MeetingLayout.SMART_LAYOUT;
|
||||
private boolean userHasJoined = false;
|
||||
private Map<String, String> guestUsersWithPositionInWaitingLine;
|
||||
@ -165,6 +166,7 @@ public class Meeting {
|
||||
isBreakout = builder.isBreakout;
|
||||
guestPolicy = builder.guestPolicy;
|
||||
authenticatedGuest = builder.authenticatedGuest;
|
||||
waitingGuestUsersTimeout = builder.waitingGuestUsersTimeout;
|
||||
meetingLayout = builder.meetingLayout;
|
||||
allowRequestsWithoutSession = builder.allowRequestsWithoutSession;
|
||||
breakoutRoomsParams = builder.breakoutRoomsParams;
|
||||
@ -501,6 +503,14 @@ public class Meeting {
|
||||
return authenticatedGuest;
|
||||
}
|
||||
|
||||
public void setWaitingGuestUsersTimeout(long waitingGuestUsersTimeout) {
|
||||
waitingGuestUsersTimeout = waitingGuestUsersTimeout;
|
||||
}
|
||||
|
||||
public long getWaitingGuestUsersTimeout() {
|
||||
return waitingGuestUsersTimeout;
|
||||
}
|
||||
|
||||
public void setMeetingLayout(String layout) {
|
||||
meetingLayout = layout;
|
||||
}
|
||||
@ -910,6 +920,7 @@ public class Meeting {
|
||||
private boolean isBreakout;
|
||||
private String guestPolicy;
|
||||
private Boolean authenticatedGuest;
|
||||
private long waitingGuestUsersTimeout;
|
||||
private Boolean allowRequestsWithoutSession;
|
||||
private String meetingLayout;
|
||||
private BreakoutRoomsParams breakoutRoomsParams;
|
||||
@ -1096,6 +1107,11 @@ public class Meeting {
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder withWaitingGuestUsersTimeout(long waitingGuestUsersTimeout) {
|
||||
this.waitingGuestUsersTimeout = waitingGuestUsersTimeout;
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder withAllowRequestsWithoutSession(Boolean value) {
|
||||
allowRequestsWithoutSession = value;
|
||||
return this;
|
||||
|
@ -15,19 +15,39 @@ import org.bigbluebutton.presentation.messages.IDocConversionMsg;
|
||||
|
||||
public interface IBbbWebApiGWApp {
|
||||
void send(String channel, String message);
|
||||
void createMeeting(String meetingID, String externalMeetingID,
|
||||
String parentMeetingID, String meetingName, Boolean recorded,
|
||||
String voiceBridge, Integer duration, Boolean autoStartRecording,
|
||||
void createMeeting(String meetingID,
|
||||
String externalMeetingID,
|
||||
String parentMeetingID,
|
||||
String meetingName,
|
||||
Boolean recorded,
|
||||
String voiceBridge,
|
||||
Integer duration,
|
||||
Boolean autoStartRecording,
|
||||
Boolean allowStartStopRecording,
|
||||
Boolean recordFullDurationMedia,
|
||||
Boolean webcamsOnlyForModerator,
|
||||
Integer meetingCameraCap,
|
||||
Integer userCameraCap,
|
||||
Integer maxPinnedCameras,
|
||||
String moderatorPass, String viewerPass, String learningDashboardAccessToken, Long createTime,
|
||||
String createDate, Boolean isBreakout, Integer sequence, Boolean freejoin, Map<String, String> metadata,
|
||||
String guestPolicy, Boolean authenticatedGuest, String meetingLayout, String welcomeMsgTemplate, String welcomeMsg, String welcomeMsgForModerators,
|
||||
String dialNumber, Integer maxUsers, Integer maxUserConcurrentAccesses,
|
||||
String moderatorPass,
|
||||
String viewerPass,
|
||||
String learningDashboardAccessToken,
|
||||
Long createTime,
|
||||
String createDate,
|
||||
Boolean isBreakout,
|
||||
Integer sequence,
|
||||
Boolean freejoin,
|
||||
Map<String, String> metadata,
|
||||
String guestPolicy,
|
||||
Boolean authenticatedGuest,
|
||||
Long waitingGuestUsersTimeout,
|
||||
String meetingLayout,
|
||||
String welcomeMsgTemplate,
|
||||
String welcomeMsg,
|
||||
String welcomeMsgForModerators,
|
||||
String dialNumber,
|
||||
Integer maxUsers,
|
||||
Integer maxUserConcurrentAccesses,
|
||||
Integer meetingExpireIfNoUserJoinedInMinutes,
|
||||
Integer meetingExpireWhenLastUserLeftInMinutes,
|
||||
Integer userInactivityInspectTimerInMinutes,
|
||||
@ -57,7 +77,6 @@ public interface IBbbWebApiGWApp {
|
||||
String externUserID, String authToken, String sessionToken, String avatarURL,
|
||||
Boolean guest, Boolean authed, String guestStatus, Boolean excludeFromDashboard,
|
||||
String enforceLayout, Map<String, String> customParameters);
|
||||
void guestWaitingLeft(String meetingID, String internalUserId);
|
||||
|
||||
void destroyMeeting(DestroyMeetingMessage msg);
|
||||
void endMeeting(EndMeetingMessage msg);
|
||||
|
@ -1,56 +0,0 @@
|
||||
/**
|
||||
* BigBlueButton open source conferencing system - http://www.bigbluebutton.org/
|
||||
*
|
||||
* Copyright (c) 2020 BigBlueButton Inc. and by respective authors (see below).
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify it under the
|
||||
* terms of the GNU Lesser General Public License as published by the Free Software
|
||||
* Foundation; either version 3.0 of the License, or (at your option) any later
|
||||
* version.
|
||||
*
|
||||
* BigBlueButton is distributed in the hope that it will be useful, but WITHOUT ANY
|
||||
* WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A
|
||||
* PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Lesser General Public License along
|
||||
* with BigBlueButton; if not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
|
||||
package org.bigbluebutton.web.services;
|
||||
|
||||
import java.util.concurrent.Executors;
|
||||
import java.util.concurrent.ScheduledExecutorService;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
import org.bigbluebutton.api.MeetingService;
|
||||
|
||||
public class WaitingGuestCleanupTimerTask {
|
||||
|
||||
private MeetingService service;
|
||||
private ScheduledExecutorService scheduledThreadPool = Executors.newScheduledThreadPool(1);
|
||||
private long runEvery = 15000;
|
||||
|
||||
public void setMeetingService(MeetingService svc) {
|
||||
this.service = svc;
|
||||
}
|
||||
|
||||
public void start() {
|
||||
scheduledThreadPool.scheduleWithFixedDelay(new CleanupTask(), 60000, runEvery, TimeUnit.MILLISECONDS);
|
||||
}
|
||||
|
||||
public void stop() {
|
||||
scheduledThreadPool.shutdownNow();
|
||||
}
|
||||
|
||||
public void setRunEvery(long v) {
|
||||
runEvery = v;
|
||||
}
|
||||
|
||||
private class CleanupTask implements Runnable {
|
||||
@Override
|
||||
public void run() {
|
||||
service.purgeWaitingGuestUsers();
|
||||
}
|
||||
}
|
||||
}
|
@ -130,7 +130,11 @@ class BbbWebApiGWApp(
|
||||
createTime: java.lang.Long, createDate: String, isBreakout: java.lang.Boolean,
|
||||
sequence: java.lang.Integer,
|
||||
freeJoin: java.lang.Boolean,
|
||||
metadata: java.util.Map[String, String], guestPolicy: String, authenticatedGuest: java.lang.Boolean, meetingLayout: String,
|
||||
metadata: java.util.Map[String, String],
|
||||
guestPolicy: String,
|
||||
authenticatedGuest: java.lang.Boolean,
|
||||
waitingGuestUsersTimeout: java.lang.Long,
|
||||
meetingLayout: String,
|
||||
welcomeMsgTemplate: String, welcomeMsg: String, welcomeMsgForModerators: String,
|
||||
dialNumber: String,
|
||||
maxUsers: java.lang.Integer,
|
||||
@ -215,7 +219,8 @@ class BbbWebApiGWApp(
|
||||
userCameraCap = userCameraCap.intValue(),
|
||||
guestPolicy = guestPolicy, meetingLayout = meetingLayout, allowModsToUnmuteUsers = allowModsToUnmuteUsers.booleanValue(),
|
||||
allowModsToEjectCameras = allowModsToEjectCameras.booleanValue(),
|
||||
authenticatedGuest = authenticatedGuest.booleanValue()
|
||||
authenticatedGuest = authenticatedGuest.booleanValue(),
|
||||
waitingGuestUsersTimeout = waitingGuestUsersTimeout.longValue()
|
||||
)
|
||||
val metadataProp = MetadataProp(mapAsScalaMap(metadata).toMap)
|
||||
|
||||
@ -294,11 +299,6 @@ class BbbWebApiGWApp(
|
||||
msgToAkkaAppsEventBus.publish(MsgToAkkaApps(toAkkaAppsChannel, event))
|
||||
}
|
||||
|
||||
def guestWaitingLeft(meetingId: String, intUserId: String): Unit = {
|
||||
val event = MsgBuilder.buildGuestWaitingLeftMsg(meetingId, intUserId)
|
||||
msgToAkkaAppsEventBus.publish(MsgToAkkaApps(toAkkaAppsChannel, event))
|
||||
}
|
||||
|
||||
def destroyMeeting(msg: DestroyMeetingMessage): Unit = {
|
||||
val event = MsgBuilder.buildDestroyMeetingSysCmdMsg(msg)
|
||||
msgToAkkaAppsEventBus.publish(MsgToAkkaApps(toAkkaAppsChannel, event))
|
||||
|
@ -55,15 +55,6 @@ object MsgBuilder {
|
||||
BbbCommonEnvCoreMsg(envelope, req)
|
||||
}
|
||||
|
||||
def buildGuestWaitingLeftMsg(meetingId: String, userId: String): BbbCommonEnvCoreMsg = {
|
||||
val routing = collection.immutable.HashMap("sender" -> "bbb-web")
|
||||
val envelope = BbbCoreEnvelope(GuestWaitingLeftMsg.NAME, routing)
|
||||
val header = BbbClientMsgHeader(GuestWaitingLeftMsg.NAME, meetingId, "not-used")
|
||||
val body = GuestWaitingLeftMsgBody(userId)
|
||||
val req = GuestWaitingLeftMsg(header, body)
|
||||
BbbCommonEnvCoreMsg(envelope, req)
|
||||
}
|
||||
|
||||
def buildCheckAlivePingSysMsg(system: String, bbbWebTimestamp: Long, akkaAppsTimestamp: Long): BbbCommonEnvCoreMsg = {
|
||||
val routing = collection.immutable.HashMap("sender" -> "bbb-web")
|
||||
val envelope = BbbCoreEnvelope(CheckAlivePingSysMsg.NAME, routing)
|
||||
|
@ -478,7 +478,8 @@ u."isDenied",
|
||||
COALESCE(NULLIF(u."guestLobbyMessage",''),NULLIF(mup."guestLobbyMessage",'')) AS "guestLobbyMessage"
|
||||
FROM "user" u
|
||||
JOIN "meeting_usersPolicies" mup using("meetingId")
|
||||
where u."guestStatus" = 'WAIT';
|
||||
where u."guestStatus" = 'WAIT'
|
||||
and u."loggedOut" is false;
|
||||
|
||||
--v_user_ref will be used only as foreign key (not possible to fetch this table directly through graphql)
|
||||
--it is necessary because v_user has some conditions like "lockSettings-hideUserList"
|
||||
|
@ -42,6 +42,7 @@ select_permissions:
|
||||
columns:
|
||||
- guestLobbyMessage
|
||||
- guestStatus
|
||||
- isAllowed
|
||||
- positionInWaitingQueue
|
||||
filter:
|
||||
_and:
|
||||
|
@ -10,8 +10,6 @@ import Logger from './logger';
|
||||
import setMinBrowserVersions from './minBrowserVersion';
|
||||
import { PrometheusAgent, METRIC_NAMES } from './prom-metrics/index.js'
|
||||
|
||||
let guestWaitHtml = '';
|
||||
|
||||
const DEFAULT_LANGUAGE = Meteor.settings.public.app.defaultSettings.application.fallbackLocale;
|
||||
const FALLBACK_ON_EMPTY_STRING = Meteor.settings.public.app.fallbackOnEmptyLocaleString;
|
||||
|
||||
@ -354,17 +352,3 @@ WebApp.connectHandlers.use('/feedback', (req, res) => {
|
||||
Logger.info('FEEDBACK LOG:', feedback);
|
||||
}));
|
||||
});
|
||||
|
||||
WebApp.connectHandlers.use('/guestWait', (req, res) => {
|
||||
if (!guestWaitHtml) {
|
||||
try {
|
||||
guestWaitHtml = Assets.getText('static/guest-wait/guest-wait.html');
|
||||
} catch (e) {
|
||||
Logger.warn(`Could not process guest wait html file: ${e}`);
|
||||
}
|
||||
}
|
||||
|
||||
res.setHeader('Content-Type', 'text/html');
|
||||
res.writeHead(200);
|
||||
res.end(guestWaitHtml);
|
||||
});
|
||||
|
@ -79,12 +79,12 @@ const ConnectionManager: React.FC<ConnectionManagerProps> = ({ children }): Reac
|
||||
throw new Error('Error fetching GraphQL URL: '.concat(error.message || ''));
|
||||
});
|
||||
logger.info('Fetching GraphQL URL');
|
||||
loadingContextInfo.setLoading(true, '1/4');
|
||||
loadingContextInfo.setLoading(true, '1/5');
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
logger.info('Connecting to GraphQL server');
|
||||
loadingContextInfo.setLoading(true, '2/4');
|
||||
loadingContextInfo.setLoading(true, '2/5');
|
||||
if (graphqlUrl) {
|
||||
const urlParams = new URLSearchParams(window.location.search);
|
||||
const sessionToken = urlParams.get('sessionToken');
|
||||
|
@ -0,0 +1,199 @@
|
||||
import React, {
|
||||
useCallback,
|
||||
useContext,
|
||||
useEffect,
|
||||
useRef,
|
||||
useState,
|
||||
} from 'react';
|
||||
import { defineMessages, useIntl } from 'react-intl';
|
||||
import { LoadingContext } from '../../common/loading-screen/loading-screen-HOC/component';
|
||||
import Styled from './styles';
|
||||
|
||||
const REDIRECT_TIMEOUT = 15000;
|
||||
|
||||
export const GUEST_STATUSES = {
|
||||
ALLOW: 'ALLOW',
|
||||
DENY: 'DENY',
|
||||
WAIT: 'WAIT',
|
||||
};
|
||||
|
||||
const intlMessages = defineMessages({
|
||||
windowTitle: {
|
||||
id: 'app.guest.windowTitle',
|
||||
description: 'tab title',
|
||||
},
|
||||
guestWait: {
|
||||
id: 'app.guest.guestWait',
|
||||
description: '',
|
||||
},
|
||||
noSessionToken: {
|
||||
id: 'app.guest.noSessionToken',
|
||||
description: '',
|
||||
},
|
||||
guestInvalid: {
|
||||
id: 'app.guest.guestInvalid',
|
||||
description: '',
|
||||
},
|
||||
allow: {
|
||||
id: 'app.guest.allow',
|
||||
description: '',
|
||||
},
|
||||
deny: {
|
||||
id: 'app.guest.guestDeny',
|
||||
description: '',
|
||||
},
|
||||
firstPosition: {
|
||||
id: 'app.guest.firstPositionInWaitingQueue',
|
||||
description: '',
|
||||
},
|
||||
position: {
|
||||
id: 'app.guest.positionInWaitingQueue',
|
||||
description: '',
|
||||
},
|
||||
calculating: {
|
||||
id: 'app.guest.calculating',
|
||||
description: '',
|
||||
},
|
||||
});
|
||||
|
||||
function getSearchParam(name: string) {
|
||||
const params = new URLSearchParams(window.location.search);
|
||||
|
||||
if (params && params.has(name)) {
|
||||
const param = params.get(name);
|
||||
|
||||
return param;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
interface GuestWaitProps {
|
||||
guestStatus: string | null;
|
||||
guestLobbyMessage: string | null;
|
||||
positionInWaitingQueue: number | null;
|
||||
logoutUrl: string;
|
||||
}
|
||||
|
||||
const GuestWait: React.FC<GuestWaitProps> = (props) => {
|
||||
const {
|
||||
guestLobbyMessage,
|
||||
guestStatus,
|
||||
logoutUrl,
|
||||
positionInWaitingQueue,
|
||||
} = props;
|
||||
|
||||
const intl = useIntl();
|
||||
const [animate, setAnimate] = useState(true);
|
||||
const [message, setMessage] = useState(intl.formatMessage(intlMessages.guestWait));
|
||||
const [positionMessage, setPositionMessage] = useState(intl.formatMessage(intlMessages.calculating));
|
||||
const lobbyMessageRef = useRef('');
|
||||
const positionInWaitingQueueRef = useRef('');
|
||||
const loadingContextInfo = useContext(LoadingContext);
|
||||
|
||||
const updateLobbyMessage = useCallback((message: string | null) => {
|
||||
if (!message) {
|
||||
setMessage(intl.formatMessage(intlMessages.guestWait));
|
||||
return;
|
||||
}
|
||||
if (message !== lobbyMessageRef.current) {
|
||||
lobbyMessageRef.current = message;
|
||||
if (lobbyMessageRef.current.length !== 0) {
|
||||
setMessage(lobbyMessageRef.current);
|
||||
} else {
|
||||
setMessage(intl.formatMessage(intlMessages.guestWait));
|
||||
}
|
||||
}
|
||||
}, [intl]);
|
||||
|
||||
const updatePositionInWaitingQueue = useCallback((newPositionInWaitingQueue: number) => {
|
||||
if (positionInWaitingQueueRef.current !== newPositionInWaitingQueue.toString()) {
|
||||
positionInWaitingQueueRef.current = newPositionInWaitingQueue.toString();
|
||||
if (positionInWaitingQueueRef.current === '1') {
|
||||
setPositionMessage(intl.formatMessage(intlMessages.firstPosition));
|
||||
} else {
|
||||
setPositionMessage(intl.formatMessage(intlMessages.position) + positionInWaitingQueueRef.current);
|
||||
}
|
||||
}
|
||||
}, [intl]);
|
||||
|
||||
useEffect(() => {
|
||||
document.title = intl.formatMessage(intlMessages.windowTitle);
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
const sessionToken = getSearchParam('sessionToken');
|
||||
|
||||
if (loadingContextInfo.isLoading) {
|
||||
loadingContextInfo.setLoading(false, '');
|
||||
}
|
||||
|
||||
if (!sessionToken) {
|
||||
setAnimate(false);
|
||||
setMessage(intl.formatMessage(intlMessages.noSessionToken));
|
||||
return;
|
||||
}
|
||||
|
||||
if (!guestStatus) {
|
||||
setAnimate(false);
|
||||
setPositionMessage('');
|
||||
setMessage(intl.formatMessage(intlMessages.guestInvalid));
|
||||
return;
|
||||
}
|
||||
|
||||
if (guestStatus === GUEST_STATUSES.ALLOW) {
|
||||
setPositionMessage('');
|
||||
updateLobbyMessage(intl.formatMessage(intlMessages.allow));
|
||||
setAnimate(false);
|
||||
return;
|
||||
}
|
||||
|
||||
if (guestStatus === GUEST_STATUSES.DENY) {
|
||||
setAnimate(false);
|
||||
setPositionMessage('');
|
||||
setMessage(intl.formatMessage(intlMessages.deny));
|
||||
setTimeout(() => {
|
||||
window.location.assign(logoutUrl);
|
||||
}, REDIRECT_TIMEOUT);
|
||||
return;
|
||||
}
|
||||
|
||||
// WAIT
|
||||
updateLobbyMessage(guestLobbyMessage || '');
|
||||
if (positionInWaitingQueue) {
|
||||
updatePositionInWaitingQueue(positionInWaitingQueue);
|
||||
}
|
||||
}, [
|
||||
guestLobbyMessage,
|
||||
guestStatus,
|
||||
logoutUrl,
|
||||
positionInWaitingQueue,
|
||||
intl,
|
||||
updateLobbyMessage,
|
||||
updatePositionInWaitingQueue,
|
||||
]);
|
||||
|
||||
return (
|
||||
<Styled.Container>
|
||||
<Styled.Content id="content">
|
||||
<Styled.Heading as="h2">3/5</Styled.Heading>
|
||||
<Styled.Heading id="heading">{intl.formatMessage(intlMessages.windowTitle)}</Styled.Heading>
|
||||
{animate && (
|
||||
<Styled.Spinner>
|
||||
<Styled.Bounce1 />
|
||||
<Styled.Bounce2 />
|
||||
<Styled.Bounce />
|
||||
</Styled.Spinner>
|
||||
)}
|
||||
<p aria-live="polite" data-test="guestMessage">
|
||||
{message}
|
||||
</p>
|
||||
<Styled.Position id="positionInWaitingQueue">
|
||||
<p aria-live="polite">{positionMessage}</p>
|
||||
</Styled.Position>
|
||||
</Styled.Content>
|
||||
</Styled.Container>
|
||||
);
|
||||
};
|
||||
|
||||
export default GuestWait;
|
@ -0,0 +1,71 @@
|
||||
import styled, { keyframes } from 'styled-components';
|
||||
|
||||
const Container = styled.div`
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
height: 100vh;
|
||||
margin: 0;
|
||||
`;
|
||||
|
||||
const Content = styled.div`
|
||||
text-align: center;
|
||||
color: white;
|
||||
font-weight: bold;
|
||||
font-size: 24px;
|
||||
`;
|
||||
|
||||
const Heading = styled.h1`
|
||||
font-size: 2rem;
|
||||
`;
|
||||
|
||||
const Position = styled.div`
|
||||
align-items: center;
|
||||
text-align: center;
|
||||
`;
|
||||
|
||||
const sk_bouncedelay = keyframes`
|
||||
0%,
|
||||
80%,
|
||||
100% {
|
||||
transform: scale(0);
|
||||
}
|
||||
|
||||
40% {
|
||||
transform: scale(1.0);
|
||||
}
|
||||
`;
|
||||
|
||||
const Spinner = styled.div`
|
||||
margin: 20px auto;
|
||||
font-size: 0px;
|
||||
`;
|
||||
|
||||
const Bounce = styled.div`
|
||||
width: 18px;
|
||||
height: 18px;
|
||||
margin: 0 5px;
|
||||
background-color: rgb(255, 255, 255);
|
||||
display: inline-block;
|
||||
border-radius: 100%;
|
||||
animation: ${sk_bouncedelay} calc(1.4s) infinite ease-in-out both;
|
||||
`;
|
||||
|
||||
const Bounce1 = styled(Bounce)`
|
||||
animation-delay: -0.32s;
|
||||
`;
|
||||
|
||||
const Bounce2 = styled(Bounce)`
|
||||
animation-delay: -0.16s;
|
||||
`;
|
||||
|
||||
export default {
|
||||
Container,
|
||||
Content,
|
||||
Heading,
|
||||
Position,
|
||||
Bounce,
|
||||
Bounce1,
|
||||
Bounce2,
|
||||
Spinner,
|
||||
};
|
@ -1,5 +1,5 @@
|
||||
import { useMutation, useQuery } from '@apollo/client';
|
||||
import React, { useContext, useEffect } from 'react';
|
||||
import React, { useContext, useEffect, useState } from 'react';
|
||||
import { Session } from 'meteor/session';
|
||||
import {
|
||||
getUserCurrent,
|
||||
@ -15,8 +15,10 @@ import { LoadingContext } from '../../common/loading-screen/loading-screen-HOC/c
|
||||
import useDeduplicatedSubscription from '/imports/ui/core/hooks/useDeduplicatedSubscription';
|
||||
import logger from '/imports/startup/client/logger';
|
||||
import deviceInfo from '/imports/utils/deviceInfo';
|
||||
import GuestWaitContainer, { GUEST_STATUSES } from '../guest-wait/component';
|
||||
|
||||
const connectionTimeout = 60000;
|
||||
const MESSAGE_TIMEOUT = 3000;
|
||||
|
||||
interface PresenceManagerContainerProps {
|
||||
children: React.ReactNode;
|
||||
@ -41,6 +43,9 @@ interface PresenceManagerProps extends PresenceManagerContainerProps {
|
||||
bannerText: string;
|
||||
customLogoUrl: string;
|
||||
loggedOut: boolean;
|
||||
guestStatus: string;
|
||||
guestLobbyMessage: string | null;
|
||||
positionInWaitingQueue: number | null;
|
||||
}
|
||||
|
||||
const PresenceManager: React.FC<PresenceManagerProps> = ({
|
||||
@ -63,18 +68,28 @@ const PresenceManager: React.FC<PresenceManagerProps> = ({
|
||||
bannerText,
|
||||
customLogoUrl,
|
||||
loggedOut,
|
||||
guestLobbyMessage,
|
||||
guestStatus,
|
||||
positionInWaitingQueue,
|
||||
}) => {
|
||||
const [allowToRender, setAllowToRender] = React.useState(false);
|
||||
const [dispatchUserJoin] = useMutation(userJoinMutation);
|
||||
const timeoutRef = React.useRef<ReturnType<typeof setTimeout>>();
|
||||
const loadingContextInfo = useContext(LoadingContext);
|
||||
const [isGuestAllowed, setIsGuestAllowed] = useState(guestStatus === GUEST_STATUSES.ALLOW);
|
||||
|
||||
useEffect(() => {
|
||||
timeoutRef.current = setTimeout(() => {
|
||||
loadingContextInfo.setLoading(false, '');
|
||||
throw new Error('Authentication timeout');
|
||||
}, connectionTimeout);
|
||||
const allowed = guestStatus === GUEST_STATUSES.ALLOW;
|
||||
if (allowed) {
|
||||
setTimeout(() => {
|
||||
setIsGuestAllowed(true);
|
||||
}, MESSAGE_TIMEOUT);
|
||||
} else {
|
||||
setIsGuestAllowed(false);
|
||||
}
|
||||
}, [guestStatus]);
|
||||
|
||||
useEffect(() => {
|
||||
const urlParams = new URLSearchParams(window.location.search);
|
||||
const sessionToken = urlParams.get('sessionToken') as string;
|
||||
setAuthData({
|
||||
@ -100,6 +115,15 @@ const PresenceManager: React.FC<PresenceManagerProps> = ({
|
||||
});
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
if (isGuestAllowed) {
|
||||
timeoutRef.current = setTimeout(() => {
|
||||
loadingContextInfo.setLoading(false, '');
|
||||
throw new Error('Authentication timeout');
|
||||
}, connectionTimeout);
|
||||
}
|
||||
}, [isGuestAllowed]);
|
||||
|
||||
useEffect(() => {
|
||||
if (bannerColor || bannerText) {
|
||||
Session.set('bannerText', bannerText);
|
||||
@ -108,7 +132,7 @@ const PresenceManager: React.FC<PresenceManagerProps> = ({
|
||||
}, [bannerColor, bannerText]);
|
||||
|
||||
useEffect(() => {
|
||||
if (authToken && !joined) {
|
||||
if (authToken && !joined && isGuestAllowed) {
|
||||
dispatchUserJoin({
|
||||
variables: {
|
||||
authToken,
|
||||
@ -117,7 +141,7 @@ const PresenceManager: React.FC<PresenceManagerProps> = ({
|
||||
},
|
||||
});
|
||||
}
|
||||
}, [joined, authToken]);
|
||||
}, [joined, authToken, isGuestAllowed]);
|
||||
|
||||
useEffect(() => {
|
||||
if (joined) {
|
||||
@ -149,6 +173,18 @@ const PresenceManager: React.FC<PresenceManagerProps> = ({
|
||||
)
|
||||
: null
|
||||
}
|
||||
{
|
||||
!isGuestAllowed && !(meetingEnded || joinErrorCode || ejectReasonCode || loggedOut)
|
||||
? (
|
||||
<GuestWaitContainer
|
||||
guestLobbyMessage={guestLobbyMessage}
|
||||
guestStatus={guestStatus}
|
||||
logoutUrl={logoutUrl}
|
||||
positionInWaitingQueue={positionInWaitingQueue}
|
||||
/>
|
||||
)
|
||||
: null
|
||||
}
|
||||
</>
|
||||
);
|
||||
};
|
||||
@ -182,6 +218,8 @@ const PresenceManagerContainer: React.FC<PresenceManagerContainerProps> = ({ chi
|
||||
ejectReasonCode,
|
||||
meeting,
|
||||
loggedOut,
|
||||
guestStatusDetails,
|
||||
guestStatus,
|
||||
} = data.user_current[0];
|
||||
const {
|
||||
logoutUrl,
|
||||
@ -213,6 +251,9 @@ const PresenceManagerContainer: React.FC<PresenceManagerContainerProps> = ({ chi
|
||||
bannerText={bannerText}
|
||||
loggedOut={loggedOut}
|
||||
customLogoUrl={customLogoUrl}
|
||||
guestLobbyMessage={guestStatusDetails?.guestLobbyMessage ?? null}
|
||||
positionInWaitingQueue={guestStatusDetails?.positionInWaitingQueue ?? null}
|
||||
guestStatus={guestStatus}
|
||||
>
|
||||
{children}
|
||||
</PresenceManager>
|
||||
|
@ -9,10 +9,17 @@ export interface GetUserCurrentResponse {
|
||||
joinErrorMessage: string;
|
||||
ejectReasonCode: string;
|
||||
loggedOut: boolean;
|
||||
guestStatus: string;
|
||||
guestStatusDetails: {
|
||||
guestLobbyMessage: string | null;
|
||||
positionInWaitingQueue: number;
|
||||
isAllowed: boolean;
|
||||
} | null;
|
||||
meeting: {
|
||||
ended: boolean;
|
||||
endedReasonCode: string;
|
||||
endedByUserName: string;
|
||||
logoutUrl: string;
|
||||
};
|
||||
}>;
|
||||
}
|
||||
@ -61,10 +68,17 @@ subscription getUserCurrent {
|
||||
joined
|
||||
ejectReasonCode
|
||||
loggedOut
|
||||
guestStatus
|
||||
meeting {
|
||||
ended
|
||||
endedReasonCode
|
||||
endedByUserName
|
||||
logoutUrl
|
||||
}
|
||||
guestStatusDetails {
|
||||
guestLobbyMessage
|
||||
positionInWaitingQueue
|
||||
isAllowed
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -23,7 +23,7 @@ const SettingsLoader: React.FC = () => {
|
||||
const loadingContextInfo = useContext(LoadingContext);
|
||||
useEffect(() => {
|
||||
logger.info('Fetching settings');
|
||||
loadingContextInfo.setLoading(true, '3/4');
|
||||
loadingContextInfo.setLoading(true, '4/5');
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
|
@ -1,393 +0,0 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
|
||||
<head>
|
||||
<title>BigBlueButton - Guest Lobby</title>
|
||||
<meta charset="UTF-8">
|
||||
<style>
|
||||
/* vietnamese */
|
||||
@font-face {
|
||||
font-family: 'Source Sans Pro';
|
||||
font-style: normal;
|
||||
font-weight: 300;
|
||||
src: local('Source Sans Pro Light'), local('SourceSansPro-Light'),
|
||||
url('fonts/SourceSansPro/SourceSansPro-Light.woff') format('woff');
|
||||
unicode-range: U+0102-0103, U+1EA0-1EF9, U+20AB;
|
||||
}
|
||||
/* latin-ext */
|
||||
@font-face {
|
||||
font-family: 'Source Sans Pro';
|
||||
font-style: normal;
|
||||
font-weight: 300;
|
||||
src: local('Source Sans Pro Light'), local('SourceSansPro-Light'),
|
||||
url('fonts/SourceSansPro/SourceSansPro-Light.woff') format('woff');
|
||||
unicode-range: U+0100-024F, U+1E00-1EFF, U+20A0-20AB, U+20AD-20CF, U+2C60-2C7F, U+A720-A7FF;
|
||||
}
|
||||
/* latin */
|
||||
@font-face {
|
||||
font-family: 'Source Sans Pro';
|
||||
font-style: normal;
|
||||
font-weight: 300;
|
||||
src: local('Source Sans Pro Light'), local('SourceSansPro-Light'),
|
||||
url('fonts/SourceSansPro/SourceSansPro-Light.woff') format('woff');
|
||||
unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02C6, U+02DA, U+02DC, U+2000-206F, U+2074, U+20AC, U+2212, U+2215, U+E0FF, U+EFFD, U+F000;
|
||||
}
|
||||
|
||||
:root {
|
||||
--enableAnimation: 1;
|
||||
}
|
||||
|
||||
body {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
background: #06172A;
|
||||
height: 100vh;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
#content {
|
||||
text-align: center;
|
||||
color: white;
|
||||
font-weight: bold;
|
||||
font-size: 24px;
|
||||
font-family: 'Source Sans Pro', arial, sans-serif;
|
||||
}
|
||||
|
||||
#content h1 {
|
||||
font-size: 2rem;
|
||||
}
|
||||
|
||||
.spinner {
|
||||
margin: 20px auto;
|
||||
font-size: 0px;
|
||||
}
|
||||
|
||||
.spinner .bounce1 {
|
||||
animation-delay: -0.32s;
|
||||
}
|
||||
|
||||
.spinner .bounce2 {
|
||||
animation-delay: -0.16s;
|
||||
}
|
||||
|
||||
.spinner>div {
|
||||
width: 18px;
|
||||
height: 18px;
|
||||
margin: 0 5px;
|
||||
background-color: rgb(255, 255, 255);
|
||||
display: inline-block;
|
||||
border-radius: 100%;
|
||||
animation: sk-bouncedelay calc(var(--enableAnimation) * 1.4s) infinite ease-in-out both;
|
||||
}
|
||||
|
||||
#positionInWaitingQueue {
|
||||
align-items: center;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
@-webkit-keyframes sk-bouncedelay {
|
||||
|
||||
0%,
|
||||
80%,
|
||||
100% {
|
||||
-webkit-transform: scale(0)
|
||||
}
|
||||
|
||||
40% {
|
||||
-webkit-transform: scale(1.0)
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes sk-bouncedelay {
|
||||
|
||||
0%,
|
||||
80%,
|
||||
100% {
|
||||
-webkit-transform: scale(0);
|
||||
transform: scale(0);
|
||||
}
|
||||
|
||||
40% {
|
||||
-webkit-transform: scale(1.0);
|
||||
transform: scale(1.0);
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
<script type="text/javascript">
|
||||
let messages = {};
|
||||
|
||||
function _(message) {
|
||||
return messages[message];
|
||||
}
|
||||
|
||||
const REDIRECT_TIMEOUT = 15000;
|
||||
const MESSAGE_TIMEOUT = 3000;
|
||||
|
||||
function updateMessage(message) {
|
||||
document.querySelector('#content > p').innerHTML = message;
|
||||
}
|
||||
|
||||
let lobbyMessage = '';
|
||||
function updateLobbyMessage(message) {
|
||||
if (message !== lobbyMessage) {
|
||||
lobbyMessage = message;
|
||||
if (lobbyMessage.length !== 0) {
|
||||
updateMessage(lobbyMessage);
|
||||
} else {
|
||||
updateMessage(_('app.guest.guestWait'));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function updatePositionOnPage(message, currentPosition) {
|
||||
document.querySelector('#positionInWaitingQueue > p').innerHTML = message + currentPosition;
|
||||
}
|
||||
|
||||
function stopUpdatingWaitingPosition() {
|
||||
document.querySelector('#positionInWaitingQueue > p').innerHTML = '';
|
||||
}
|
||||
|
||||
let positionInWaitingQueue = '';
|
||||
function updatePositionInWaitingQueue(newPositionInWaitingQueue) {
|
||||
if (positionInWaitingQueue !== newPositionInWaitingQueue) {
|
||||
positionInWaitingQueue = newPositionInWaitingQueue.toString();
|
||||
if (positionInWaitingQueue === '1') {
|
||||
updatePositionOnPage(_('app.guest.firstPositionInWaitingQueue'), '');
|
||||
} else {
|
||||
updatePositionOnPage(_('app.guest.positionInWaitingQueue'), positionInWaitingQueue);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function getSearchParam(name) {
|
||||
const params = new URLSearchParams(window.location.search);
|
||||
|
||||
if (params && params.has(name)) {
|
||||
const param = params.get(name);
|
||||
|
||||
return param;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
function getClientJoinUrl() {
|
||||
const joinEndpoint = '/html5client/join';
|
||||
const sessionToken = getSearchParam('sessionToken');
|
||||
const url = new URL(`${window.location.origin}${joinEndpoint}`);
|
||||
url.search = `sessionToken=${sessionToken}`;
|
||||
|
||||
return url;
|
||||
}
|
||||
|
||||
async function fetchLocalizedMessages() {
|
||||
const DEFAULT_LANGUAGE = 'en';
|
||||
const LOCALES_ENDPOINT = '/html5client/locale';
|
||||
const url = new URL(`${window.location.origin}${LOCALES_ENDPOINT}`);
|
||||
const overrideLocale = getSearchParam('locale');
|
||||
|
||||
url.search = overrideLocale
|
||||
? `locale=${overrideLocale}`
|
||||
: `locale=${navigator.language}&init=true`;
|
||||
|
||||
document.getElementsByTagName('html')[0].lang = overrideLocale || navigator.language;
|
||||
|
||||
const localesPath = 'locales';
|
||||
|
||||
fetch(url)
|
||||
.then((response) => {
|
||||
if (!response.ok) {
|
||||
return false;
|
||||
}
|
||||
return response.json();
|
||||
})
|
||||
.then(({ normalizedLocale, regionDefaultLocale }) => {
|
||||
const fetchFallbackMessages = new Promise((resolve, reject) => {
|
||||
fetch(`${localesPath}/${DEFAULT_LANGUAGE}.json`)
|
||||
.then((response) => {
|
||||
if (!response.ok) {
|
||||
return reject();
|
||||
}
|
||||
return resolve(response.json());
|
||||
});
|
||||
});
|
||||
|
||||
const fetchRegionMessages = new Promise((resolve) => {
|
||||
if (!regionDefaultLocale) {
|
||||
return resolve(false);
|
||||
}
|
||||
fetch(`${localesPath}/${regionDefaultLocale}.json`)
|
||||
.then((response) => {
|
||||
if (!response.ok) {
|
||||
return resolve(false);
|
||||
}
|
||||
return response.json()
|
||||
.then((jsonResponse) => resolve(jsonResponse))
|
||||
.catch(() => {
|
||||
resolve(false);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
const fetchSpecificMessages = new Promise((resolve) => {
|
||||
if (!normalizedLocale || normalizedLocale === DEFAULT_LANGUAGE || normalizedLocale === regionDefaultLocale) {
|
||||
return resolve(false);
|
||||
}
|
||||
fetch(`${localesPath}/${normalizedLocale}.json`)
|
||||
.then((response) => {
|
||||
if (!response.ok) {
|
||||
return resolve(false);
|
||||
}
|
||||
return response.json()
|
||||
.then((jsonResponse) => resolve(jsonResponse))
|
||||
.catch(() => {
|
||||
resolve(false);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
Promise.all([fetchFallbackMessages, fetchRegionMessages, fetchSpecificMessages])
|
||||
.then((values) => {
|
||||
let mergedMessages = Object.assign({}, values[0]);
|
||||
|
||||
if (!values[1] && !values[2]) {
|
||||
normalizedLocale = DEFAULT_LANGUAGE;
|
||||
} else {
|
||||
if (values[1]) {
|
||||
mergedMessages = Object.assign(mergedMessages, values[1]);
|
||||
}
|
||||
if (values[2]) {
|
||||
mergedMessages = Object.assign(mergedMessages, values[2]);
|
||||
}
|
||||
}
|
||||
|
||||
messages = mergedMessages;
|
||||
|
||||
window.document.title = _('app.guest.windowTitle');
|
||||
document.querySelector('#heading').innerHTML = _('app.guest.windowTitle');
|
||||
updateMessage(_('app.guest.guestWait'));
|
||||
enableAnimation();
|
||||
try {
|
||||
const sessionToken = getSearchParam('sessionToken');
|
||||
|
||||
if (!sessionToken) {
|
||||
disableAnimation();
|
||||
updateMessage(_('app.guest.noSessionToken'));
|
||||
return;
|
||||
}
|
||||
pollGuestStatus(sessionToken, 0);
|
||||
} catch (e) {
|
||||
disableAnimation();
|
||||
console.error(e);
|
||||
updateMessage(_('app.guest.errorSeeConsole'));
|
||||
}
|
||||
})
|
||||
.catch((e) => {
|
||||
console.error(e);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
function fetchUserCurrent(sessionToken) {
|
||||
const GUEST_WAIT_ENDPOINT = '/api/rest/usercurrent/';
|
||||
const url = new URL(`${window.location.origin}${GUEST_WAIT_ENDPOINT}`);
|
||||
|
||||
const headers = new Headers({
|
||||
'X-Session-Token': sessionToken,
|
||||
'Content-Type': 'application/json',
|
||||
});
|
||||
|
||||
return fetch(url, { method: 'get', headers: headers });
|
||||
};
|
||||
|
||||
function redirect(message, url) {
|
||||
disableAnimation();
|
||||
stopUpdatingWaitingPosition();
|
||||
updateMessage(message);
|
||||
setTimeout(() => {
|
||||
window.location = url;
|
||||
}, REDIRECT_TIMEOUT);
|
||||
};
|
||||
|
||||
function pollGuestStatus(token, everyMs) {
|
||||
|
||||
setTimeout(function () {
|
||||
fetchUserCurrent(token)
|
||||
.then(async (resp) => await resp.json())
|
||||
.then((data) => {
|
||||
if(data.hasOwnProperty('error') || !data.hasOwnProperty('user_current')) {
|
||||
disableAnimation();
|
||||
stopUpdatingWaitingPosition();
|
||||
updateMessage(_('app.guest.guestInvalid'));
|
||||
return;
|
||||
}
|
||||
const userData = data.user_current[0];
|
||||
|
||||
if (userData.guestStatus === 'ALLOW') {
|
||||
updateLobbyMessage(_('app.guest.allow'));
|
||||
stopUpdatingWaitingPosition();
|
||||
|
||||
// Timeout is required by accessibility to allow viewing of the message for a minimum of 3 seconds
|
||||
// before redirecting.
|
||||
setTimeout(() => {
|
||||
disableAnimation();
|
||||
window.location = getClientJoinUrl();
|
||||
}, MESSAGE_TIMEOUT);
|
||||
return;
|
||||
}
|
||||
|
||||
if (userData.guestStatus === 'DENY') {
|
||||
stopUpdatingWaitingPosition();
|
||||
return redirect(_('app.guest.guestDeny'), userData.meeting.logoutUrl);
|
||||
}
|
||||
|
||||
if (userData.guestStatus === 'WAIT') {
|
||||
//Wait message will be set by `updateLobbyMessage`
|
||||
//updateMessage(updateMessage(_('app.guest.guestWait')));
|
||||
}
|
||||
|
||||
if(userData.guestStatusDetails !== null) {
|
||||
updatePositionInWaitingQueue(userData.guestStatusDetails.positionInWaitingQueue);
|
||||
updateLobbyMessage(userData.guestStatusDetails.guestLobbyMessage || '');
|
||||
}
|
||||
|
||||
const ATTEMPT_EVERY_MS = 10 * 1000; // 10 seconds
|
||||
return pollGuestStatus(token, ATTEMPT_EVERY_MS);
|
||||
|
||||
});
|
||||
|
||||
}, everyMs);
|
||||
};
|
||||
|
||||
function enableAnimation() {
|
||||
document.documentElement.style.setProperty('--enableAnimation', 1);
|
||||
}
|
||||
|
||||
function disableAnimation() {
|
||||
document.documentElement.style.setProperty('--enableAnimation', 0);
|
||||
}
|
||||
|
||||
window.onload = function () {
|
||||
fetchLocalizedMessages();
|
||||
};
|
||||
</script>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<div id="content">
|
||||
<h1 id="heading">BigBlueButton - Guest Lobby</h1>
|
||||
<div class="spinner">
|
||||
<div class="bounce1"></div>
|
||||
<div class="bounce2"></div>
|
||||
<div class="bounce3"></div>
|
||||
</div>
|
||||
<p aria-live="polite" data-test="guestMessage">Please wait for a moderator to approve you joining the meeting.</p>
|
||||
<div id="positionInWaitingQueue">
|
||||
<p aria-live="polite">Calculating position in waiting queue</p>
|
||||
</div>
|
||||
</div>
|
||||
</body>
|
||||
|
||||
</html>
|
@ -871,6 +871,7 @@
|
||||
"app.guest.positionInWaitingQueue": "Your current position in waiting queue: ",
|
||||
"app.guest.guestInvalid": "Guest user is invalid",
|
||||
"app.guest.meetingForciblyEnded": "You cannot join a meeting that has already been forcibly ended",
|
||||
"app.guest.calculating": "Calculating position in waiting queue",
|
||||
"app.userList.guest.waitingUsers": "Waiting Users",
|
||||
"app.userList.guest.waitingUsersTitle": "User Management",
|
||||
"app.userList.guest.optionTitle": "Review Pending Users",
|
||||
|
@ -321,9 +321,6 @@ allowRequestsWithoutSession=false
|
||||
# For more info, refer to javax.servlet.http.HttpSession#setMaxInactiveInterval 's spec
|
||||
defaultHttpSessionTimeout=14400
|
||||
|
||||
# The url for where the guest will poll if approved to join or not.
|
||||
defaultGuestWaitURL=${bigbluebutton.web.serverURL}/html5client/guestWait
|
||||
|
||||
# The default avatar image to display.
|
||||
useDefaultAvatar=false
|
||||
defaultAvatarURL=${bigbluebutton.web.serverURL}/html5client/resources/images/avatar.png
|
||||
|
@ -34,7 +34,6 @@ with BigBlueButton; if not, see <http://www.gnu.org/licenses/>.
|
||||
</property>
|
||||
</bean>
|
||||
|
||||
<bean id="waitingGuestCleanupTimerTask" class="org.bigbluebutton.web.services.WaitingGuestCleanupTimerTask"/>
|
||||
<bean id="userCleanupTimerTask" class="org.bigbluebutton.web.services.UserCleanupTimerTask"/>
|
||||
<bean id="enteredUserCleanupTimerTask" class="org.bigbluebutton.web.services.EnteredUserCleanupTimerTask"/>
|
||||
|
||||
@ -51,14 +50,12 @@ with BigBlueButton; if not, see <http://www.gnu.org/licenses/>.
|
||||
<property name="presDownloadService" ref="presDownloadService"/>
|
||||
<property name="paramsProcessorUtil" ref="paramsProcessorUtil"/>
|
||||
<property name="stunTurnService" ref="stunTurnService"/>
|
||||
<property name="waitingGuestCleanupTimerTask" ref="waitingGuestCleanupTimerTask"/>
|
||||
<property name="userCleanupTimerTask" ref="userCleanupTimerTask"/>
|
||||
<property name="sessionsCleanupDelayInMinutes" value="${sessionsCleanupDelayInMinutes}"/>
|
||||
<property name="enteredUserCleanupTimerTask" ref="enteredUserCleanupTimerTask"/>
|
||||
<property name="gw" ref="bbbWebApiGWApp"/>
|
||||
<property name="callbackUrlService" ref="callbackUrlService"/>
|
||||
<property name="usersTimeout" value="${usersTimeout}"/>
|
||||
<property name="waitingGuestUsersTimeout" value="${waitingGuestUsersTimeout}"/>
|
||||
<property name="enteredUsersTimeout" value="${enteredUsersTimeout}"/>
|
||||
<property name="slidesGenerationProgressNotifier" ref="slidesGenerationProgressNotifier"/>
|
||||
</bean>
|
||||
@ -147,7 +144,6 @@ with BigBlueButton; if not, see <http://www.gnu.org/licenses/>.
|
||||
<property name="graphqlWebsocketUrl" value="${graphqlWebsocketUrl}"/>
|
||||
<property name="useDefaultLogo" value="${useDefaultLogo}"/>
|
||||
<property name="defaultLogoURL" value="${defaultLogoURL}"/>
|
||||
<property name="defaultGuestWaitURL" value="${defaultGuestWaitURL}"/>
|
||||
<property name="allowRequestsWithoutSession" value="${allowRequestsWithoutSession}"/>
|
||||
<property name="defaultHttpSessionTimeout" value="${defaultHttpSessionTimeout}"/>
|
||||
<property name="defaultMeetingDuration" value="${defaultMeetingDuration}"/>
|
||||
@ -165,6 +161,7 @@ with BigBlueButton; if not, see <http://www.gnu.org/licenses/>.
|
||||
<property name="defaultAvatarURL" value="${defaultAvatarURL}"/>
|
||||
<property name="defaultGuestPolicy" value="${defaultGuestPolicy}"/>
|
||||
<property name="authenticatedGuest" value="${authenticatedGuest}"/>
|
||||
<property name="waitingGuestUsersTimeout" value="${waitingGuestUsersTimeout}"/>
|
||||
<property name="defaultMeetingLayout" value="${defaultMeetingLayout}"/>
|
||||
<property name="meetingExpireIfNoUserJoinedInMinutes" value="${meetingExpireIfNoUserJoinedInMinutes}"/>
|
||||
<property name="meetingExpireWhenLastUserLeftInMinutes" value="${meetingExpireWhenLastUserLeftInMinutes}"/>
|
||||
|
@ -508,17 +508,7 @@ class ApiController {
|
||||
// Process if we send the user directly to the client or
|
||||
// have it wait for approval.
|
||||
String destUrl = clientURL + "?sessionToken=" + sessionToken
|
||||
if (guestStatusVal.equals(GuestPolicy.WAIT)) {
|
||||
String guestWaitUrl = paramsProcessorUtil.getDefaultGuestWaitURL();
|
||||
destUrl = guestWaitUrl + "?sessionToken=" + sessionToken
|
||||
// Check if the user has her/his default locale overridden by an userdata
|
||||
String customLocale = userCustomData.get("bbb_override_default_locale")
|
||||
if (customLocale != null) {
|
||||
destUrl += "&locale=" + customLocale
|
||||
}
|
||||
msgKey = "guestWait"
|
||||
msgValue = "Guest waiting for approval to join meeting."
|
||||
} else if (guestStatusVal.equals(GuestPolicy.DENY)) {
|
||||
if (guestStatusVal == GuestPolicy.DENY) {
|
||||
invalid("guestDeniedAccess", "You have been denied access to this meeting based on the meeting's guest policy", redirectClient, errorRedirectUrl)
|
||||
return
|
||||
}
|
||||
@ -744,115 +734,6 @@ class ApiController {
|
||||
return reqParams;
|
||||
}
|
||||
|
||||
/**********************************************
|
||||
* GUEST WAIT API
|
||||
*********************************************/
|
||||
def guestWaitHandler = {
|
||||
String API_CALL = 'guestWait'
|
||||
log.debug CONTROLLER_NAME + "#${API_CALL}"
|
||||
|
||||
String msgKey = "defaultKey"
|
||||
String msgValue = "defaultValue"
|
||||
String destURL = paramsProcessorUtil.getDefaultLogoutUrl()
|
||||
|
||||
Map.Entry<String, String> validationResponse = validateRequest(
|
||||
ValidationService.ApiCall.GUEST_WAIT,
|
||||
request
|
||||
)
|
||||
if(!(validationResponse == null)) {
|
||||
msgKey = validationResponse.getKey()
|
||||
msgValue = validationResponse.getValue()
|
||||
respondWithJSONError(msgKey, msgValue, destURL)
|
||||
return
|
||||
}
|
||||
|
||||
String sessionToken = sanitizeSessionToken(params.sessionToken)
|
||||
UserSession us = getUserSession(sessionToken)
|
||||
Meeting meeting = meetingService.getMeeting(us.meetingID)
|
||||
String status = us.guestStatus
|
||||
destURL = us.clientUrl
|
||||
String posInWaitingQueue = meeting.getWaitingPositionsInWaitingQueue(us.internalUserId)
|
||||
String lobbyMsg = meeting.getGuestLobbyMessage(us.internalUserId)
|
||||
|
||||
Boolean redirectClient = true
|
||||
if (!StringUtils.isEmpty(params.redirect)) {
|
||||
try {
|
||||
redirectClient = Boolean.parseBoolean(params.redirect)
|
||||
} catch (Exception e) {
|
||||
redirectClient = true
|
||||
}
|
||||
}
|
||||
|
||||
String guestURL = paramsProcessorUtil.getDefaultGuestWaitURL() + "?sessionToken=" + sessionToken
|
||||
|
||||
switch (status) {
|
||||
case GuestPolicy.WAIT:
|
||||
meetingService.guestIsWaiting(us.meetingID, us.internalUserId)
|
||||
destURL = guestURL
|
||||
msgKey = "guestWait"
|
||||
msgValue = "Please wait for a moderator to approve you joining the meeting."
|
||||
|
||||
// We force the response to not do a redirect. Otherwise,
|
||||
// the client would just be redirecting into this endpoint.
|
||||
redirectClient = false
|
||||
break
|
||||
case GuestPolicy.DENY:
|
||||
destURL = meeting.getLogoutUrl()
|
||||
msgKey = "guestDeny"
|
||||
msgValue = "Guest denied of joining the meeting."
|
||||
redirectClient = false
|
||||
break
|
||||
case GuestPolicy.ALLOW:
|
||||
// IF the user was allowed to join but there is no room available in
|
||||
// the meeting we must hold his approval
|
||||
if (hasReachedMaxParticipants(meeting, us)) {
|
||||
meetingService.guestIsWaiting(us.meetingID, us.internalUserId)
|
||||
destURL = guestURL
|
||||
msgKey = "seatWait"
|
||||
msgValue = "Guest waiting for a seat in the meeting."
|
||||
redirectClient = false
|
||||
status = GuestPolicy.WAIT
|
||||
}
|
||||
break
|
||||
default:
|
||||
break
|
||||
}
|
||||
|
||||
if(meeting.didGuestUserLeaveGuestLobby(us.internalUserId)){
|
||||
destURL = meeting.getLogoutUrl()
|
||||
msgKey = "guestInvalid"
|
||||
msgValue = "Invalid user"
|
||||
status = GuestPolicy.DENY
|
||||
redirectClient = false
|
||||
}
|
||||
|
||||
if (redirectClient) {
|
||||
// User may join the meeting
|
||||
redirect(url: destURL)
|
||||
} else {
|
||||
response.addHeader("Cache-Control", "no-cache")
|
||||
withFormat {
|
||||
json {
|
||||
def builder = new JsonBuilder()
|
||||
builder.response {
|
||||
returncode RESP_CODE_SUCCESS
|
||||
messageKey msgKey
|
||||
message msgValue
|
||||
meeting_id us.meetingID
|
||||
user_id us.internalUserId
|
||||
auth_token us.authToken
|
||||
session_token session[sessionToken]
|
||||
guestStatus status
|
||||
lobbyMessage lobbyMsg
|
||||
url destURL
|
||||
positionInWaitingQueue posInWaitingQueue
|
||||
}
|
||||
render(contentType: "application/json", text: builder.toPrettyString())
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/***********************************************
|
||||
* ENTER API
|
||||
***********************************************/
|
||||
|
@ -97,7 +97,6 @@ Add these options to `/etc/bigbluebutton/bbb-web.properties`:
|
||||
defaultHTML5ClientUrl=https://bbb-proxy.example.com/bbb-01/html5client/join
|
||||
presentationBaseURL=https://bbb-01.example.com/bigbluebutton/presentation
|
||||
accessControlAllowOrigin=https://bbb-proxy.example.com
|
||||
defaultGuestWaitURL=https://bbb-01.example.com/bbb-01/html5client/guestWait
|
||||
graphqlWebsocketUrl=wss://bbb-01.example.com/v1/graphql
|
||||
```
|
||||
|
||||
|
@ -1249,8 +1249,6 @@ Do the same in `/usr/share/bbb-web/WEB-INF/classes/bigbluebutton.properties` in
|
||||
|
||||
```
|
||||
defaultHTML5ClientUrl=${bigbluebutton.web.serverURL}/html5client/join
|
||||
|
||||
defaultGuestWaitURL=${bigbluebutton.web.serverURL}/html5client/guestWait
|
||||
```
|
||||
|
||||
In configuration file for the HTML5 client, located in `/usr/share/meteor/bundle/programs/server/assets/app/config/settings.yml`, change the entry for `public.app.basename`:
|
||||
|
Loading…
Reference in New Issue
Block a user