Merge pull request #14451 from ramonlsouza/24-25-feb24
chore: Merge 2.4 into 2.5
This commit is contained in:
commit
8e0fc7ddc0
@ -0,0 +1,49 @@
|
||||
package org.bigbluebutton.core.apps.users
|
||||
|
||||
import org.bigbluebutton.common2.msgs._
|
||||
import org.bigbluebutton.core.models.{
|
||||
Users2x,
|
||||
VoiceUsers,
|
||||
Webcams
|
||||
}
|
||||
import org.bigbluebutton.core.bus.MessageBus
|
||||
import org.bigbluebutton.core.running.LiveMeeting
|
||||
|
||||
trait SyncGetWebcamInfoRespMsgHdlr {
|
||||
this: UsersApp =>
|
||||
|
||||
def handleSyncGetWebcamInfoRespMsg(liveMeeting: LiveMeeting, bus: MessageBus): Unit = {
|
||||
val routing = Routing.addMsgToHtml5InstanceIdRouting(
|
||||
liveMeeting.props.meetingProp.intId,
|
||||
liveMeeting.props.systemProps.html5InstanceId.toString
|
||||
)
|
||||
val envelope = BbbCoreEnvelope(SyncGetWebcamInfoRespMsg.NAME, routing)
|
||||
val header = BbbClientMsgHeader(
|
||||
SyncGetWebcamInfoRespMsg.NAME,
|
||||
liveMeeting.props.meetingProp.intId,
|
||||
"nodeJSapp"
|
||||
)
|
||||
|
||||
val webcamsList = Webcams.findAll(liveMeeting.webcams) flatMap { webcam =>
|
||||
val stream = webcam.stream.id
|
||||
val userId = webcam.stream.userId
|
||||
val pin = Users2x.isPin(userId, liveMeeting.users2x)
|
||||
for {
|
||||
u <- Users2x.findWithIntId(liveMeeting.users2x, userId)
|
||||
} yield {
|
||||
VoiceUsers.findWIthIntId(liveMeeting.voiceUsers, userId) match {
|
||||
case Some(vu) => WebcamStreamInfo(stream, userId, u.name, pin, vu.floor, vu.lastFloorTime)
|
||||
case _ => WebcamStreamInfo(stream, userId, u.name, pin, false, "0")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
val body = SyncGetWebcamInfoRespMsgBody(
|
||||
webcamsList
|
||||
)
|
||||
val event = SyncGetWebcamInfoRespMsg(header, body)
|
||||
val msgEvent = BbbCommonEnvCoreMsg(envelope, event)
|
||||
|
||||
bus.outGW.send(msgEvent)
|
||||
}
|
||||
}
|
@ -170,6 +170,7 @@ class UsersApp(
|
||||
with EjectDuplicateUserReqMsgHdlr
|
||||
with EjectUserFromMeetingCmdMsgHdlr
|
||||
with EjectUserFromMeetingSysMsgHdlr
|
||||
with SyncGetWebcamInfoRespMsgHdlr
|
||||
with MuteUserCmdMsgHdlr {
|
||||
|
||||
val log = Logging(context.system, getClass)
|
||||
|
@ -708,6 +708,9 @@ class MeetingActor(
|
||||
|
||||
// send all screen sharing info
|
||||
screenshareApp2x.handleSyncGetScreenshareInfoRespMsg(liveMeeting, msgBus)
|
||||
|
||||
// send all webcam info
|
||||
usersApp.handleSyncGetWebcamInfoRespMsg(liveMeeting, msgBus)
|
||||
}
|
||||
|
||||
def handleGetAllMeetingsReqMsg(msg: GetAllMeetingsReqMsg): Unit = {
|
||||
|
@ -359,16 +359,14 @@ class LearningDashboardActor(
|
||||
}
|
||||
|
||||
private def handleUserRoleChangedEvtMsg(msg: UserRoleChangedEvtMsg) {
|
||||
if(msg.body.role == Roles.MODERATOR_ROLE) {
|
||||
for {
|
||||
meeting <- meetings.values.find(m => m.intId == msg.header.meetingId)
|
||||
user <- findUserByIntId(meeting, msg.body.userId)
|
||||
} yield {
|
||||
val updatedUser = user.copy(isModerator = true)
|
||||
val updatedMeeting = meeting.copy(users = meeting.users + (updatedUser.userKey -> updatedUser))
|
||||
for {
|
||||
meeting <- meetings.values.find(m => m.intId == msg.header.meetingId)
|
||||
user <- findUserByIntId(meeting, msg.body.userId)
|
||||
} yield {
|
||||
val updatedUser = user.copy(isModerator = (msg.body.role == Roles.MODERATOR_ROLE))
|
||||
val updatedMeeting = meeting.copy(users = meeting.users + (updatedUser.userKey -> updatedUser))
|
||||
|
||||
meetings += (updatedMeeting.intId -> updatedMeeting)
|
||||
}
|
||||
meetings += (updatedMeeting.intId -> updatedMeeting)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -195,3 +195,17 @@ case class CamStreamSubscribedInSfuEvtMsgBody(
|
||||
subscriberStreamId: String,
|
||||
sfuSessionId: String // Subscriber's SFU session ID
|
||||
)
|
||||
|
||||
/**
|
||||
* Sync webcam state with bbb-html5
|
||||
*/
|
||||
object SyncGetWebcamInfoRespMsg { val NAME = "SyncGetWebcamInfoRespMsg" }
|
||||
case class SyncGetWebcamInfoRespMsg(
|
||||
header: BbbClientMsgHeader,
|
||||
body: SyncGetWebcamInfoRespMsgBody
|
||||
) extends BbbCoreMsg
|
||||
|
||||
case class SyncGetWebcamInfoRespMsgBody(
|
||||
webcamListSync: Vector[WebcamStreamInfo]
|
||||
)
|
||||
case class WebcamStreamInfo(stream: String, userId: String, name: String, pin: Boolean, floor: Boolean, lastFloorTime: String)
|
||||
|
@ -105,6 +105,7 @@ public class MeetingService implements MessageListener {
|
||||
private SwfSlidesGenerationProgressNotifier notifier;
|
||||
|
||||
private long usersTimeout;
|
||||
private long waitingGuestUsersTimeout;
|
||||
private long enteredUsersTimeout;
|
||||
|
||||
private ParamsProcessorUtil paramsProcessorUtil;
|
||||
@ -139,13 +140,13 @@ public class MeetingService implements MessageListener {
|
||||
public void registerUser(String meetingID, String internalUserId,
|
||||
String fullname, String role, String externUserID,
|
||||
String authToken, String avatarURL, Boolean guest,
|
||||
Boolean authed, String guestStatus, Boolean excludeFromDashboard) {
|
||||
Boolean authed, String guestStatus, Boolean excludeFromDashboard, Boolean leftGuestLobby) {
|
||||
handle(new RegisterUser(meetingID, internalUserId, fullname, role,
|
||||
externUserID, authToken, avatarURL, guest, authed, guestStatus, excludeFromDashboard));
|
||||
externUserID, authToken, avatarURL, guest, authed, guestStatus, excludeFromDashboard, leftGuestLobby));
|
||||
|
||||
Meeting m = getMeeting(meetingID);
|
||||
if (m != null) {
|
||||
RegisteredUser ruser = new RegisteredUser(authToken, internalUserId, guestStatus, excludeFromDashboard);
|
||||
RegisteredUser ruser = new RegisteredUser(authToken, internalUserId, guestStatus, excludeFromDashboard, leftGuestLobby);
|
||||
m.userRegistered(ruser);
|
||||
}
|
||||
}
|
||||
@ -260,17 +261,16 @@ public class MeetingService implements MessageListener {
|
||||
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 >= 15000 && ru.getGuestStatus() == GuestPolicy.WAIT) {
|
||||
if (elapsedTime >= waitingGuestUsersTimeout && ru.getGuestStatus() == GuestPolicy.WAIT) {
|
||||
if (meeting.userUnregistered(registeredUserID) != null) {
|
||||
gw.guestWaitingLeft(meeting.getInternalId(), registeredUserID);
|
||||
meeting.setLeftGuestLobby(registeredUserID, true);
|
||||
};
|
||||
}
|
||||
}
|
||||
@ -1304,6 +1304,10 @@ public class MeetingService implements MessageListener {
|
||||
usersTimeout = value;
|
||||
}
|
||||
|
||||
public void setWaitingGuestUsersTimeout(long value) {
|
||||
waitingGuestUsersTimeout = value;
|
||||
}
|
||||
|
||||
public void setEnteredUsersTimeout(long value) {
|
||||
enteredUsersTimeout = value;
|
||||
}
|
||||
|
@ -188,6 +188,22 @@ public class Meeting {
|
||||
}
|
||||
}
|
||||
|
||||
public void setLeftGuestLobby(String userId, Boolean bool) {
|
||||
RegisteredUser ruser = registeredUsers.get(userId);
|
||||
if (ruser != null) {
|
||||
ruser.setLeftGuestLobby(bool);
|
||||
}
|
||||
}
|
||||
|
||||
public Boolean didGuestUserLeaveGuestLobby(String userId) {
|
||||
RegisteredUser ruser = registeredUsers.get(userId);
|
||||
|
||||
if (ruser != null) {
|
||||
return ruser.getLeftGuestLobby();
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
public void setGuestStatusWithId(String userId, String guestStatus) {
|
||||
User user = getUserById(userId);
|
||||
if (user != null) {
|
||||
|
@ -8,12 +8,14 @@ public class RegisteredUser {
|
||||
private String guestStatus;
|
||||
private Boolean excludeFromDashboard;
|
||||
private Long guestWaitedOn;
|
||||
private Boolean leftGuestLobby;
|
||||
|
||||
public RegisteredUser(String authToken, String userId, String guestStatus, Boolean excludeFromDashboard) {
|
||||
public RegisteredUser(String authToken, String userId, String guestStatus, Boolean excludeFromDashboard, Boolean leftGuestLobby) {
|
||||
this.authToken = authToken;
|
||||
this.userId = userId;
|
||||
this.guestStatus = guestStatus;
|
||||
this.excludeFromDashboard = excludeFromDashboard;
|
||||
this.leftGuestLobby = leftGuestLobby;
|
||||
|
||||
Long currentTimeMillis = System.currentTimeMillis();
|
||||
this.registeredOn = currentTimeMillis;
|
||||
@ -28,6 +30,10 @@ public class RegisteredUser {
|
||||
return guestStatus;
|
||||
}
|
||||
|
||||
public Boolean getLeftGuestLobby() {
|
||||
return leftGuestLobby;
|
||||
}
|
||||
|
||||
public void setExcludeFromDashboard(Boolean excludeFromDashboard) {
|
||||
this.excludeFromDashboard = excludeFromDashboard;
|
||||
}
|
||||
@ -40,6 +46,9 @@ public class RegisteredUser {
|
||||
this.guestWaitedOn = System.currentTimeMillis();
|
||||
}
|
||||
|
||||
public void setLeftGuestLobby(boolean bool) {
|
||||
this.leftGuestLobby = bool;
|
||||
}
|
||||
public Long getGuestWaitedOn() {
|
||||
return this.guestWaitedOn;
|
||||
}
|
||||
|
@ -45,6 +45,7 @@ public class UserSession {
|
||||
public String guestStatus = GuestPolicy.ALLOW;
|
||||
public String clientUrl = null;
|
||||
public Boolean excludeFromDashboard = false;
|
||||
public Boolean leftGuestLobby = false;
|
||||
|
||||
private AtomicInteger connections = new AtomicInteger(0);
|
||||
|
||||
|
@ -14,10 +14,11 @@ public class RegisterUser implements IMessage {
|
||||
public final Boolean authed;
|
||||
public final String guestStatus;
|
||||
public final Boolean excludeFromDashboard;
|
||||
|
||||
public final Boolean leftGuestLobby;
|
||||
|
||||
public RegisterUser(String meetingID, String internalUserId, String fullname, String role, String externUserID,
|
||||
String authToken, String avatarURL, Boolean guest,
|
||||
Boolean authed, String guestStatus, Boolean excludeFromDashboard) {
|
||||
Boolean authed, String guestStatus, Boolean excludeFromDashboard, Boolean leftGuestLobby) {
|
||||
this.meetingID = meetingID;
|
||||
this.internalUserId = internalUserId;
|
||||
this.fullname = fullname;
|
||||
@ -29,5 +30,6 @@ public class RegisterUser implements IMessage {
|
||||
this.authed = authed;
|
||||
this.guestStatus = guestStatus;
|
||||
this.excludeFromDashboard = excludeFromDashboard;
|
||||
this.leftGuestLobby = leftGuestLobby;
|
||||
}
|
||||
}
|
||||
|
@ -3,5 +3,13 @@ ENV DEBIAN_FRONTEND noninteractive
|
||||
|
||||
RUN echo "deb http://deb.debian.org/debian bullseye-backports main" >> /etc/apt/sources.list
|
||||
RUN apt update && apt -y install locales-all fontconfig libxt6 libxrender1
|
||||
RUN apt update && apt -y install -t bullseye-backports libreoffice && rm /usr/share/java/ant-apache-log4j-1.10.9.jar && rm /usr/share/maven-repo/org/apache/ant/ant-apache-log4j/1.10.9/ant-apache-log4j-1.10.9.jar
|
||||
RUN apt update && apt -y install -t \
|
||||
bullseye-backports \
|
||||
libreoffice \
|
||||
&& rm -f \
|
||||
/usr/share/java/ant-apache-log4j-1.10.9.jar \
|
||||
/usr/share/java/log4j-1.2-1.2.17.jar /usr/share/java/log4j-1.2.jar \
|
||||
/usr/share/maven-repo/log4j/log4j/1.2.17/log4j-1.2.17.jar \
|
||||
/usr/share/maven-repo/log4j/log4j/1.2.x/log4j-1.2.x.jar \
|
||||
/usr/share/maven-repo/org/apache/ant/ant-apache-log4j/1.10.9/ant-apache-log4j-1.10.9.jar
|
||||
|
||||
|
@ -105,7 +105,7 @@ enableUFWRules() {
|
||||
|
||||
|
||||
enableMultipleKurentos() {
|
||||
echo " - Configuring three Kurento Media Servers (listen only, webcam, and screeshare)"
|
||||
echo " - Configuring three Kurento Media Servers (listen only, webcam, and screenshare)"
|
||||
|
||||
# Step 1. Setup shared certificate between FreeSWITCH and Kurento
|
||||
|
||||
|
@ -115,9 +115,9 @@
|
||||
}
|
||||
|
||||
.react-toggle--focus .react-toggle-thumb {
|
||||
box-shadow: 0px 0px 2px 3px #0F70D7;
|
||||
box-shadow: 0px 0px 2px 3px var(--color-primary);
|
||||
}
|
||||
|
||||
.react-toggle:active:not(.react-toggle--disabled) .react-toggle-thumb {
|
||||
box-shadow: 0px 0px 5px 5px #0F70D7;
|
||||
box-shadow: 0px 0px 5px 5px var(--color-primary);
|
||||
}
|
||||
|
@ -3,8 +3,10 @@ import handleUserSharedHtml5Webcam from './handlers/userSharedHtml5Webcam';
|
||||
import handleUserUnsharedHtml5Webcam from './handlers/userUnsharedHtml5Webcam';
|
||||
import handleFloorChanged from './handlers/floorChanged';
|
||||
import handlePinnedChanged from './handlers/userPinChanged';
|
||||
import handleWebcamSync from './handlers/webcamSync';
|
||||
|
||||
RedisPubSub.on('UserBroadcastCamStartedEvtMsg', handleUserSharedHtml5Webcam);
|
||||
RedisPubSub.on('UserBroadcastCamStoppedEvtMsg', handleUserUnsharedHtml5Webcam);
|
||||
RedisPubSub.on('AudioFloorChangedEvtMsg', handleFloorChanged);
|
||||
RedisPubSub.on('UserPinStateChangedEvtMsg', handlePinnedChanged);
|
||||
RedisPubSub.on('SyncGetWebcamInfoRespMsg', handleWebcamSync);
|
||||
|
@ -0,0 +1,48 @@
|
||||
import { check } from 'meteor/check';
|
||||
import { addWebcamSync } from '../modifiers/sharedWebcam';
|
||||
import VideoStreams from '/imports/api/video-streams/';
|
||||
import updatedVideoStream from '../modifiers/updatedVideoStream';
|
||||
import unsharedWebcam from '../modifiers/unsharedWebcam';
|
||||
|
||||
export default function handleWebcamSync({ body }, meetingId) {
|
||||
check(meetingId, String);
|
||||
check(body, Object);
|
||||
const { webcamListSync } = body;
|
||||
check(webcamListSync, Array);
|
||||
|
||||
const streamsIds = webcamListSync.map((webcam) => webcam.stream);
|
||||
|
||||
const webcamStreamsToUpdate = VideoStreams.find({
|
||||
meetingId,
|
||||
stream: { $in: streamsIds },
|
||||
}, {
|
||||
fields: {
|
||||
stream: 1,
|
||||
},
|
||||
}).fetch()
|
||||
.map((m) => m.stream);
|
||||
|
||||
webcamListSync.forEach((webcam) => {
|
||||
if (webcamStreamsToUpdate.indexOf(webcam.stream) >= 0) {
|
||||
// stream already exist, then update
|
||||
updatedVideoStream(meetingId, webcam);
|
||||
} else {
|
||||
// stream doesn't exist yet, then add it
|
||||
addWebcamSync(meetingId, webcam);
|
||||
}
|
||||
});
|
||||
|
||||
// removing extra video streams already existing in Mongo
|
||||
const videoStreamsToRemove = VideoStreams.find({
|
||||
meetingId,
|
||||
stream: { $nin: streamsIds },
|
||||
}, {
|
||||
fields: {
|
||||
stream: 1,
|
||||
userId: 1,
|
||||
},
|
||||
}).fetch();
|
||||
|
||||
videoStreamsToRemove
|
||||
.forEach((videoStream) => unsharedWebcam(meetingId, videoStream.userId, videoStream.stream));
|
||||
}
|
@ -56,3 +56,46 @@ export default function sharedWebcam(meetingId, userId, stream) {
|
||||
Logger.error(`Error setting stream: ${err}`);
|
||||
}
|
||||
}
|
||||
|
||||
export function addWebcamSync(meetingId, videoStream) {
|
||||
check(videoStream, {
|
||||
userId: String,
|
||||
stream: String,
|
||||
name: String,
|
||||
pin: Boolean,
|
||||
floor: Boolean,
|
||||
lastFloorTime: String,
|
||||
});
|
||||
|
||||
const {
|
||||
stream, userId, name, pin, floor, lastFloorTime,
|
||||
} = videoStream;
|
||||
|
||||
const deviceId = getDeviceId(stream);
|
||||
|
||||
const selector = {
|
||||
meetingId,
|
||||
userId,
|
||||
deviceId,
|
||||
};
|
||||
|
||||
const modifier = {
|
||||
$set: {
|
||||
stream,
|
||||
name,
|
||||
lastFloorTime,
|
||||
floor,
|
||||
pin,
|
||||
},
|
||||
};
|
||||
|
||||
try {
|
||||
const { insertedId } = VideoStreams.upsert(selector, modifier);
|
||||
|
||||
if (insertedId) {
|
||||
Logger.info(`Synced stream=${stream} meeting=${meetingId}`);
|
||||
}
|
||||
} catch (err) {
|
||||
Logger.error(`Error setting sync stream: ${err}`);
|
||||
}
|
||||
}
|
||||
|
@ -0,0 +1,39 @@
|
||||
import { check } from 'meteor/check';
|
||||
import Logger from '/imports/startup/server/logger';
|
||||
import VideoStreams from '/imports/api/video-streams';
|
||||
|
||||
export default function updateVideoStream(meetingId, videoStream) {
|
||||
check(meetingId, String);
|
||||
check(videoStream, {
|
||||
userId: String,
|
||||
stream: String,
|
||||
name: String,
|
||||
pin: Boolean,
|
||||
floor: Boolean,
|
||||
lastFloorTime: String,
|
||||
});
|
||||
|
||||
const { userId, stream } = videoStream;
|
||||
|
||||
const selector = {
|
||||
meetingId,
|
||||
stream,
|
||||
userId,
|
||||
};
|
||||
|
||||
const modifier = {
|
||||
$set: Object.assign(
|
||||
...videoStream,
|
||||
),
|
||||
};
|
||||
|
||||
try {
|
||||
const numberAffected = VideoStreams.update(selector, modifier);
|
||||
|
||||
if (numberAffected) {
|
||||
Logger.debug(`Update videoStream ${stream} for user ${userId} in ${meetingId}`);
|
||||
}
|
||||
} catch (err) {
|
||||
Logger.error(`Error update videoStream ${stream} for user=${userId}: ${err}`);
|
||||
}
|
||||
}
|
@ -199,6 +199,7 @@ class ActionsDropdown extends PureComponent {
|
||||
label: intl.formatMessage(intlMessages.selectRandUserLabel),
|
||||
key: this.selectUserRandId,
|
||||
onClick: () => mountModal(<RandomUserSelectContainer isSelectedUser={false} />),
|
||||
dataTest: "selectRandomUser",
|
||||
})
|
||||
}
|
||||
|
||||
|
@ -125,7 +125,7 @@ const ChatAlert = (props) => {
|
||||
{ autoClose: 3000 },
|
||||
<div>
|
||||
<div style={{ fontWeight: 700 }}>{chatsTracker[key].lastSender}</div>
|
||||
<div>{chatsTracker[key].content}</div>
|
||||
<div dangerouslySetInnerHTML={{ __html: chatsTracker[key].content }} />
|
||||
</div>,
|
||||
true,
|
||||
);
|
||||
|
@ -73,12 +73,13 @@ const ChatAlertContainer = (props) => {
|
||||
try {
|
||||
if (c[0] === idChat || (c[0] === 'MAIN-PUBLIC-GROUP-CHAT' && idChat === 'public')) {
|
||||
chatsTracker[c[0]] = {};
|
||||
chatsTracker[c[0]].lastSender = users[Auth.meetingID][c[1]?.lastSender]?.name;
|
||||
if (c[1]?.posJoinMessages || c[1]?.messageGroups) {
|
||||
const m = Object.entries(c[1]?.posJoinMessages || c[1]?.messageGroups);
|
||||
chatsTracker[c[0]].count = m?.length;
|
||||
if (m[m.length - 1]) {
|
||||
const sameUserCount = m.filter((message) => message[1]?.sender === Auth.userID).length;
|
||||
if (m[m.length - 1] && m[m.length - 1][1]?.sender !== Auth.userID) {
|
||||
chatsTracker[c[0]].lastSender = users[Auth.meetingID][c[1]?.lastSender]?.name;
|
||||
chatsTracker[c[0]].content = m[m.length - 1][1]?.message;
|
||||
chatsTracker[c[0]].count = m?.length - sameUserCount;
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -94,7 +95,7 @@ const ChatAlertContainer = (props) => {
|
||||
if (prevTracker) {
|
||||
const keys = Object.keys(prevTracker);
|
||||
keys.forEach((key) => {
|
||||
if (chatsTracker[key]?.count > prevTracker[key]?.count) {
|
||||
if (chatsTracker[key]?.count > (prevTracker[key]?.count || 0)) {
|
||||
chatsTracker[key].shouldNotify = true;
|
||||
}
|
||||
});
|
||||
|
@ -118,6 +118,7 @@ const Chat = (props) => {
|
||||
aria-label={intl.formatMessage(intlMessages.closeChatLabel, { 0: title })}
|
||||
label={intl.formatMessage(intlMessages.closeChatLabel, { 0: title })}
|
||||
accessKey={CLOSE_CHAT_AK}
|
||||
data-test="closePrivateChat"
|
||||
/>
|
||||
)
|
||||
: (
|
||||
|
@ -1,7 +1,7 @@
|
||||
import styled from 'styled-components';
|
||||
import {
|
||||
colorWhite,
|
||||
colorGrayDark,
|
||||
colorBackground,
|
||||
colorGrayLighter,
|
||||
} from '/imports/ui/stylesheets/styled-components/palette';
|
||||
import { fontSizeBase } from '/imports/ui/stylesheets/styled-components/typography';
|
||||
@ -13,7 +13,7 @@ const Background = styled.div`
|
||||
justify-content: center;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
background-color: ${colorGrayDark};
|
||||
background-color: ${colorBackground};
|
||||
color: ${colorWhite};
|
||||
text-align: center;
|
||||
`;
|
||||
|
@ -80,7 +80,7 @@ class BBBMenu extends React.Component {
|
||||
<Styled.BBBMenuItem
|
||||
emoji={emojiSelected ? 'yes' : 'no'}
|
||||
key={label}
|
||||
data-test={dataTest || key}
|
||||
data-test={dataTest}
|
||||
disableRipple={true}
|
||||
disableGutters={true}
|
||||
disabled={disabled}
|
||||
|
@ -144,7 +144,9 @@ class RandomUserSelect extends Component {
|
||||
<Styled.ModalViewTitle>
|
||||
{intl.formatMessage(messages.randUserTitle)}
|
||||
</Styled.ModalViewTitle>
|
||||
<div>{intl.formatMessage(messages.noViewers)}</div>
|
||||
<div data-test="noViewersSelectedMessage">
|
||||
{intl.formatMessage(messages.noViewers)}
|
||||
</div>
|
||||
</Styled.ModalViewContainer>
|
||||
);
|
||||
} else { // viewers are available
|
||||
@ -165,7 +167,7 @@ class RandomUserSelect extends Component {
|
||||
<Styled.ModalAvatar aria-hidden style={{ backgroundColor: `${selectedUser.color}` }}>
|
||||
{selectedUser.name.slice(0, 2)}
|
||||
</Styled.ModalAvatar>
|
||||
<Styled.SelectedUserName>
|
||||
<Styled.SelectedUserName data-test="selectedUserName">
|
||||
{selectedUser.name}
|
||||
</Styled.SelectedUserName>
|
||||
{currentUser.presenter
|
||||
@ -176,6 +178,7 @@ class RandomUserSelect extends Component {
|
||||
color="primary"
|
||||
size="md"
|
||||
onClick={() => this.reselect()}
|
||||
data-test="selectAgainRadomUser"
|
||||
/>
|
||||
)}
|
||||
</Styled.ModalViewContainer>
|
||||
|
@ -3,7 +3,11 @@ import {
|
||||
borderRadius,
|
||||
lgPaddingX,
|
||||
} from '/imports/ui/stylesheets/styled-components/general';
|
||||
import { colorWhite, colorText } from '/imports/ui/stylesheets/styled-components/palette';
|
||||
import {
|
||||
colorWhite,
|
||||
colorText,
|
||||
colorBackground,
|
||||
} from '/imports/ui/stylesheets/styled-components/palette';
|
||||
import {
|
||||
fontSizeLarge,
|
||||
headingsFontWeight,
|
||||
@ -20,6 +24,7 @@ const Parent = styled.div`
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
background-color: ${colorBackground};
|
||||
`;
|
||||
|
||||
const Modal = styled.div`
|
||||
|
@ -98,6 +98,10 @@ const intlMessages = defineMessages({
|
||||
id: 'app.presentationUploder.rejectedError',
|
||||
description: 'some files rejected, please check the file mime types',
|
||||
},
|
||||
badConnectionError: {
|
||||
id: 'app.presentationUploder.connectionClosedError',
|
||||
description: 'message indicating that the connection was closed',
|
||||
},
|
||||
uploadProcess: {
|
||||
id: 'app.presentationUploder.upload.progress',
|
||||
description: 'message that indicates the percentage of the upload',
|
||||
@ -613,9 +617,9 @@ class PresentationUploader extends Component {
|
||||
}}
|
||||
>
|
||||
<Styled.FileLine>
|
||||
<Styled.FileIcon>
|
||||
<span>
|
||||
<Icon iconName="file" />
|
||||
</Styled.FileIcon>
|
||||
</span>
|
||||
<Styled.ToastFileName>
|
||||
<span>{item.filename}</span>
|
||||
</Styled.ToastFileName>
|
||||
@ -979,6 +983,11 @@ class PresentationUploader extends Component {
|
||||
constraint['0'] = ((item.conversion.maxFileSize) / 1000 / 1000).toFixed(2);
|
||||
}
|
||||
|
||||
if (item.upload.progress < 100) {
|
||||
const errorMessage = intlMessages.badConnectionError;
|
||||
return intl.formatMessage(errorMessage);
|
||||
}
|
||||
|
||||
const errorMessage = intlMessages[item.upload.status] || intlMessages.genericError;
|
||||
return intl.formatMessage(errorMessage, constraint);
|
||||
}
|
||||
|
@ -60,16 +60,9 @@ const UploadRow = styled.div`
|
||||
const FileLine = styled.div`
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
width: ${fileLineWidth};
|
||||
`;
|
||||
|
||||
const FileIcon = styled.span`
|
||||
width: 1%;
|
||||
align-items: center;
|
||||
padding-bottom: ${iconPaddingMd};
|
||||
& > i {
|
||||
position: relative;
|
||||
top: ${borderSizeLarge};
|
||||
}
|
||||
width: ${fileLineWidth};
|
||||
`;
|
||||
|
||||
const ToastFileName = styled.span`
|
||||
@ -77,31 +70,21 @@ const ToastFileName = styled.span`
|
||||
overflow: hidden;
|
||||
white-space: nowrap;
|
||||
height: 1.25rem !important;
|
||||
margin-left: ${lgPaddingX};
|
||||
top: ${borderSizeLarge};
|
||||
margin-left: ${mdPaddingY};
|
||||
height: 1rem;
|
||||
width: auto;
|
||||
position: relative;
|
||||
text-align: left;
|
||||
font-weight: ${headingsFontWeight};
|
||||
|
||||
[dir="rtl"] & {
|
||||
margin-right: ${lgPaddingX};
|
||||
margin-right: ${mdPaddingY};
|
||||
margin-left: 0;
|
||||
text-align: right;
|
||||
}
|
||||
`;
|
||||
|
||||
const StatusIcon = styled.span`
|
||||
margin-left: auto;
|
||||
[dir="rtl"] & {
|
||||
margin-right: auto;
|
||||
margin-left: 0;
|
||||
}
|
||||
|
||||
& > i {
|
||||
position: relative;
|
||||
top: 1px;
|
||||
height: ${statusIconSize};
|
||||
width: ${statusIconSize};
|
||||
}
|
||||
@ -162,9 +145,6 @@ const Table = styled.table`
|
||||
font-weight: bold;
|
||||
color: ${colorGrayDark};
|
||||
}
|
||||
|
||||
td {
|
||||
}
|
||||
}
|
||||
}
|
||||
`;
|
||||
@ -219,6 +199,11 @@ const InnerToast = styled(ScrollboxVertical)`
|
||||
padding-right: 1.5rem;
|
||||
box-sizing: content-box;
|
||||
background: none;
|
||||
|
||||
[dir="rtl"] & {
|
||||
padding-right: 0;
|
||||
padding-left: 1.5rem;
|
||||
}
|
||||
`;
|
||||
|
||||
const TableItemIcon = styled.td`
|
||||
@ -455,9 +440,13 @@ const ToastItemIcon = styled(Icon)`
|
||||
width: ${statusIconSize};
|
||||
height: ${statusIconSize};
|
||||
font-size: 117%;
|
||||
bottom: ${borderSize};
|
||||
left: ${statusInfoHeight};
|
||||
|
||||
[dir="rtl"] & {
|
||||
left: unset;
|
||||
right: ${statusInfoHeight};
|
||||
}
|
||||
|
||||
${({ done }) => done && `
|
||||
color: ${colorSuccess};
|
||||
`}
|
||||
@ -589,7 +578,6 @@ const ExtraHint = styled.div`
|
||||
export default {
|
||||
UploadRow,
|
||||
FileLine,
|
||||
FileIcon,
|
||||
ToastFileName,
|
||||
StatusIcon,
|
||||
StatusInfo,
|
||||
|
@ -49,7 +49,7 @@ const UserAvatar = ({
|
||||
}) => (
|
||||
<Styled.Avatar
|
||||
aria-hidden="true"
|
||||
data-test="userAvatar"
|
||||
data-test={moderator ? 'moderatorAvatar' : 'viewerAvatar'}
|
||||
moderator={moderator}
|
||||
presenter={presenter}
|
||||
whiteboardAccess={whiteboardAccess && !presenter}
|
||||
|
@ -1,6 +1,7 @@
|
||||
import React, { PureComponent } from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import { injectIntl, defineMessages } from 'react-intl';
|
||||
import TooltipContainer from '/imports/ui/components/common/tooltip/container';
|
||||
import _ from 'lodash';
|
||||
import { Session } from 'meteor/session';
|
||||
import { findDOMNode } from 'react-dom';
|
||||
@ -328,6 +329,7 @@ class UserListItem extends PureComponent {
|
||||
onClick: () => this.setState({ showNestedOptions: true }),
|
||||
icon: 'user',
|
||||
iconRight: 'right_arrow',
|
||||
dataTest: 'setStatus',
|
||||
},
|
||||
{
|
||||
allowed: showNestedOptions && isMeteorConnected && allowedToChangeStatus,
|
||||
@ -447,6 +449,7 @@ class UserListItem extends PureComponent {
|
||||
this.handleClose();
|
||||
},
|
||||
icon: 'presentation',
|
||||
dataTest: isMe(user.userId) ? 'takePresenter' : 'makePresenter',
|
||||
},
|
||||
{
|
||||
allowed: allowedToPromote && isMeteorConnected && !showNestedOptions,
|
||||
@ -457,6 +460,7 @@ class UserListItem extends PureComponent {
|
||||
this.handleClose();
|
||||
},
|
||||
icon: 'promote',
|
||||
dataTest: 'promoteToModerator',
|
||||
},
|
||||
{
|
||||
allowed: allowedToDemote && isMeteorConnected && !showNestedOptions,
|
||||
@ -467,6 +471,7 @@ class UserListItem extends PureComponent {
|
||||
this.handleClose();
|
||||
},
|
||||
icon: 'user',
|
||||
dataTest: 'demoteToViewer',
|
||||
},
|
||||
{
|
||||
allowed: allowedToChangeUserLockStatus && isMeteorConnected && !showNestedOptions,
|
||||
@ -536,6 +541,7 @@ class UserListItem extends PureComponent {
|
||||
this.handleClose();
|
||||
},
|
||||
icon: getEmojiList[s],
|
||||
dataTest: s,
|
||||
})
|
||||
});
|
||||
|
||||
@ -707,7 +713,7 @@ class UserListItem extends PureComponent {
|
||||
|
||||
const innerContents = (
|
||||
<Styled.UserItemInnerContents>
|
||||
<Styled.UserAvatar>
|
||||
<Styled.UserAvatar data-test="userAvatar">
|
||||
{this.renderUserAvatar()}
|
||||
</Styled.UserAvatar>
|
||||
{!compact
|
||||
@ -718,10 +724,12 @@ class UserListItem extends PureComponent {
|
||||
aria-expanded={isActionsOpen}
|
||||
>
|
||||
<Styled.UserNameMain>
|
||||
<span>
|
||||
{user.name}
|
||||
|
||||
</span>
|
||||
<TooltipContainer title={user.name}>
|
||||
<span>
|
||||
{user.name}
|
||||
|
||||
</span>
|
||||
</TooltipContainer>
|
||||
<i>{(isMe(user.userId)) ? `(${intl.formatMessage(messages.you)})` : ''}</i>
|
||||
</Styled.UserNameMain>
|
||||
{
|
||||
|
@ -46,7 +46,7 @@ const toolbarItemTrianglePadding = '2px';
|
||||
const toolbarMargin = '.8rem';
|
||||
|
||||
const fileLineWidth = '16.75rem';
|
||||
const iconPaddingMd = '.65rem';
|
||||
const iconPaddingMd = '.4rem';
|
||||
const statusIconSize = '16px';
|
||||
const toastMdMargin = '.5rem';
|
||||
const uploadListHeight = '30vh';
|
||||
|
@ -11,6 +11,7 @@ import {
|
||||
colorText,
|
||||
colorWhite,
|
||||
colorGrayLighter,
|
||||
colorOverlay,
|
||||
} from '/imports/ui/stylesheets/styled-components/palette';
|
||||
|
||||
const GlobalStyle = createGlobalStyle`
|
||||
@ -64,7 +65,7 @@ const GlobalStyle = createGlobalStyle`
|
||||
left: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
background-color: rgba(6, 23, 42, 0.75);
|
||||
background-color: ${colorOverlay};
|
||||
}
|
||||
|
||||
.fullscreenModalOverlay {
|
||||
|
@ -24,6 +24,8 @@ const colorMuted = 'var(--color-muted, #586571)';
|
||||
const colorMutedBackground = 'var(--color-muted-background, #F3F6F9)';
|
||||
|
||||
const colorBackground = `var(--color-background, ${colorGrayDark})`;
|
||||
const colorOverlay = `var(--color-overlay, rgba(6, 23, 42, 0.75))`;
|
||||
|
||||
const userListBg = `var(--user-list-bg, ${colorOffWhite})`;
|
||||
const userListText = `var(--user-list-text, ${colorGray})`;
|
||||
const unreadMessagesBg = `var(--unread-messages-bg, ${colorDanger})`;
|
||||
@ -91,7 +93,7 @@ const colorContentBackground = 'var(--color-content-background, #1B2A3A)';
|
||||
const dropdownBg = `var(--dropdown-bg, ${colorWhite})`;
|
||||
|
||||
const pollStatsBorderColor = 'var(--poll-stats-border-color, #D4D9DF)';
|
||||
const pollBlue = 'var(--poll-blue, #1A73D4)';
|
||||
const pollBlue = `var(--poll-blue, ${colorPrimary})`;
|
||||
|
||||
const toastDefaultColor = `var(--toast-default-color, ${colorWhite})`;
|
||||
const toastDefaultBg = `var(--toast-default-bg, ${colorGray})`;
|
||||
@ -126,6 +128,7 @@ export {
|
||||
colorSuccess,
|
||||
colorWarning,
|
||||
colorBackground,
|
||||
colorOverlay,
|
||||
userListBg,
|
||||
userListText,
|
||||
unreadMessagesBg,
|
||||
|
6
bigbluebutton-html5/jsconfig.json
Normal file
6
bigbluebutton-html5/jsconfig.json
Normal file
@ -0,0 +1,6 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"baseUrl": ".",
|
||||
"paths": { "/*": ["*"] }
|
||||
}
|
||||
}
|
@ -272,7 +272,7 @@
|
||||
return;
|
||||
}
|
||||
|
||||
pollGuestStatus(sessionToken, 0, ATTEMPT_LIMIT, ATTEMPT_EVERY_MS);
|
||||
pollGuestStatus(sessionToken, ATTEMPT_EVERY_MS);
|
||||
} catch (e) {
|
||||
disableAnimation();
|
||||
console.error(e);
|
||||
@ -302,15 +302,9 @@
|
||||
}, REDIRECT_TIMEOUT);
|
||||
};
|
||||
|
||||
function pollGuestStatus(token, attempt, limit, everyMs) {
|
||||
function pollGuestStatus(token, everyMs) {
|
||||
|
||||
setTimeout(function () {
|
||||
if (attempt >= limit) {
|
||||
disableAnimation();
|
||||
updateMessage(_('app.guest.noModeratorResponse'));
|
||||
return;
|
||||
}
|
||||
|
||||
fetchGuestWait(token)
|
||||
.then(async (resp) => await resp.json())
|
||||
.then((data) => {
|
||||
@ -342,7 +336,7 @@
|
||||
updatePositionInWaitingQueue(data.response.positionInWaitingQueue);
|
||||
updateLobbyMessage(data.response.lobbyMessage);
|
||||
|
||||
return pollGuestStatus(token, attempt + 1, limit, everyMs);
|
||||
return pollGuestStatus(token, everyMs);
|
||||
});
|
||||
}, everyMs);
|
||||
};
|
||||
|
@ -207,6 +207,7 @@
|
||||
"app.presentationUploder.fileToUpload": "To be uploaded ...",
|
||||
"app.presentationUploder.currentBadge": "Current",
|
||||
"app.presentationUploder.rejectedError": "The selected file(s) have been rejected. Please check the file type(s).",
|
||||
"app.presentationUploder.connectionClosedError": "Interrupted by poor connectivity. Please try again.",
|
||||
"app.presentationUploder.upload.progress": "Uploading ({0}%)",
|
||||
"app.presentationUploder.upload.413": "File is too large, exceeded the maximum of {0} MB",
|
||||
"app.presentationUploder.genericError": "Oops, Something went wrong ...",
|
||||
@ -607,6 +608,8 @@
|
||||
"app.guest.allow": "Guest approved and redirecting to meeting.",
|
||||
"app.guest.firstPositionInWaitingQueue": "You are the first in line!",
|
||||
"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.userList.guest.waitingUsers": "Waiting Users",
|
||||
"app.userList.guest.waitingUsersTitle": "User Management",
|
||||
"app.userList.guest.optionTitle": "Review Pending Users",
|
||||
@ -938,6 +941,17 @@
|
||||
"playback.error.wrapper.aria": "Error area",
|
||||
"playback.loader.wrapper.aria": "Loader area",
|
||||
"playback.player.wrapper.aria": "Player area",
|
||||
"playback.player.about.modal.shortcuts.title": "Shortcuts",
|
||||
"playback.player.about.modal.shortcuts.alt": "Alt",
|
||||
"playback.player.about.modal.shortcuts.shift": "Shift",
|
||||
"playback.player.about.modal.shortcuts.fullscreen": "Toggle fullscreen",
|
||||
"playback.player.about.modal.shortcuts.play": "Play/Pause",
|
||||
"playback.player.about.modal.shortcuts.section": "Toggle side section",
|
||||
"playback.player.about.modal.shortcuts.seek.backward": "Seek backwards",
|
||||
"playback.player.about.modal.shortcuts.seek.forward": "Seek forwards",
|
||||
"playback.player.about.modal.shortcuts.skip.next": "Next slide",
|
||||
"playback.player.about.modal.shortcuts.skip.previous": "Previous Slide",
|
||||
"playback.player.about.modal.shortcuts.swap": "Swap content",
|
||||
"playback.player.chat.message.poll.name": "Poll result",
|
||||
"playback.player.chat.message.poll.question": "Question",
|
||||
"playback.player.chat.message.poll.options": "Options",
|
||||
|
@ -46,4 +46,16 @@ test.describe.parallel('Chat', () => {
|
||||
await chat.init(true, true);
|
||||
await chat.emptyMessage();
|
||||
});
|
||||
|
||||
test('Close private chat', async ({ browser, context, page }) => {
|
||||
const privateChat = new PrivateChat(browser, context);
|
||||
await privateChat.initPages(page);
|
||||
await privateChat.closeChat();
|
||||
});
|
||||
|
||||
test('Private chat disabled when user leaves meeting', async ({ browser, context, page }) => {
|
||||
const privateChat = new PrivateChat(browser, context);
|
||||
await privateChat.initPages(page);
|
||||
await privateChat.chatDisabledUserLeaves();
|
||||
});
|
||||
});
|
||||
|
@ -1,8 +1,7 @@
|
||||
const { MultiUsers } = require('../user/multiusers');
|
||||
const e = require('../core/elements');
|
||||
const { sleep } = require('../core/helpers');
|
||||
const { checkElementLengthEqualTo } = require('../core/util');
|
||||
const { ELEMENT_WAIT_TIME } = require('../core/constants');
|
||||
const { expect } = require('@playwright/test');
|
||||
|
||||
class PrivateChat extends MultiUsers {
|
||||
constructor(browser, context) {
|
||||
@ -17,11 +16,7 @@ class PrivateChat extends MultiUsers {
|
||||
// modPage send message
|
||||
await this.modPage.type(e.chatBox, e.message1);
|
||||
await this.modPage.waitAndClick(e.sendButton);
|
||||
await this.userPage.page.waitForFunction(
|
||||
checkElementLengthEqualTo,
|
||||
[e.chatButton, 2],
|
||||
{ timeout: ELEMENT_WAIT_TIME },
|
||||
);
|
||||
await this.userPage.waitUntilHaveCountSelector(e.chatButton, 2);
|
||||
await this.userPage.waitAndClickElement(e.chatButton, 1);
|
||||
await this.userPage.waitForSelector(e.hidePrivateChat);
|
||||
// check sent messages
|
||||
@ -34,6 +29,33 @@ class PrivateChat extends MultiUsers {
|
||||
await this.modPage.hasText(e.privateChat, e.message2);
|
||||
await this.userPage.hasText(e.privateChat, e.message2);
|
||||
}
|
||||
|
||||
async closeChat() {
|
||||
await this.modPage.waitAndClick(e.userListItem);
|
||||
await this.modPage.waitAndClick(e.startPrivateChat);
|
||||
await this.modPage.waitUntilHaveCountSelector(e.chatButton, 2);
|
||||
const privateChatLocator = this.modPage.getLocatorByIndex(e.chatButton, -1);
|
||||
expect(privateChatLocator).toContainText(this.userPage.username);
|
||||
|
||||
await this.modPage.type(e.chatBox, e.message1);
|
||||
await this.modPage.waitAndClick(e.sendButton);
|
||||
await this.userPage.waitUntilHaveCountSelector(e.chatButton, 2);
|
||||
await this.modPage.waitAndClick(e.closePrivateChat);
|
||||
await this.modPage.wasRemoved(e.hidePrivateChat);
|
||||
await this.modPage.waitUntilHaveCountSelector(e.chatButton, 1);
|
||||
const userChatCount = await this.userPage.getSelectorCount(e.chatButton);
|
||||
expect(userChatCount).toBe(2);
|
||||
}
|
||||
|
||||
async chatDisabledUserLeaves() {
|
||||
await this.modPage.waitAndClick(e.userListItem);
|
||||
await this.modPage.waitAndClick(e.startPrivateChat);
|
||||
await this.modPage.waitForSelector(e.sendButton);
|
||||
await this.userPage.waitAndClick(e.optionsButton);
|
||||
await this.userPage.waitAndClick(e.logout);
|
||||
await this.modPage.hasElementDisabled(e.chatBox);
|
||||
await this.modPage.hasElementDisabled(e.sendButton);
|
||||
}
|
||||
}
|
||||
|
||||
exports.PrivateChat = PrivateChat;
|
||||
|
@ -59,9 +59,11 @@ exports.publicChat = 'div[data-test="publicChat"]';
|
||||
exports.privateChat = 'div[data-test="privateChat"]';
|
||||
exports.hidePublicChat = 'button[data-test="hidePublicChat"]';
|
||||
exports.hidePrivateChat = 'button[data-test="hidePrivateChat"]';
|
||||
exports.closePrivateChat = 'button[data-test="closePrivateChat"]';
|
||||
exports.typingIndicator = 'span[data-test="typingIndicator"]';
|
||||
exports.chatUserMessageText = 'p[data-test="chatUserMessageText"]';
|
||||
exports.chatClearMessageText = 'p[data-test="chatClearMessageText"]';
|
||||
exports.chatWelcomeMessageText = 'p[data-test="chatWelcomeMessageText"]';
|
||||
// Messages
|
||||
exports.message = 'Hello World!';
|
||||
exports.testMessage = 'Just a test';
|
||||
@ -187,9 +189,11 @@ exports.notesTitle = 'h2[data-test="notesTitle"]';
|
||||
|
||||
// User
|
||||
exports.userAvatar = 'div[data-test="userAvatar"]';
|
||||
exports.moderatorAvatar = 'div[data-test="moderatorAvatar"]';
|
||||
exports.viewerAvatar = 'div[data-test="viewerAvatar"]';
|
||||
exports.applauseIcon = `${this.userAvatar} > div > i[class="icon-bbb-applause"]`;
|
||||
exports.awayIcon = `${this.userAvatar} > div > i[class="icon-bbb-time"]`;
|
||||
exports.setStatus = 'li[data-test="setstatus"]';
|
||||
exports.setStatus = 'li[data-test="setStatus"]';
|
||||
exports.away = 'li[data-test="away"]';
|
||||
exports.applaud = 'li[data-test="applause"]';
|
||||
exports.userListItem = 'div[data-test="userListItem"]';
|
||||
@ -198,7 +202,7 @@ exports.multiWhiteboardTool = 'span[data-test="multiWhiteboardTool"]';
|
||||
exports.manageUsers = 'button[data-test="manageUsers"]';
|
||||
exports.presenterClassName = 'presenter--';
|
||||
exports.anyUser = 'div[data-test="userListItem"]';
|
||||
exports.userList = 'button[data-test="toggleUserList"]';
|
||||
exports.userListToggleBtn = 'button[data-test="toggleUserList"]';
|
||||
exports.mobileUser = 'span[data-test="mobileUser"]';
|
||||
exports.connectionStatusBtn = 'button[data-test="connectionStatusButton"]';
|
||||
exports.connectionStatusModal = 'div[aria-label="Connection status modal"]';
|
||||
@ -217,6 +221,14 @@ exports.joinMeetingDemoPage = 'div[class^="join-meeting"]';
|
||||
exports.askModerator = 'button[data-test="askModerator"]';
|
||||
exports.alwaysAccept = 'button[data-test="alwaysAccept"]';
|
||||
exports.alwaysDeny = 'button[data-test="alwaysDeny"]';
|
||||
exports.selectRandomUser = 'li[data-test="selectRandomUser"]';
|
||||
exports.noViewersSelectedMessage = 'div[data-test="noViewersSelectedMessage"]';
|
||||
exports.selectedUserName = 'div[data-test="selectedUserName"]';
|
||||
exports.selectAgainRadomUser = 'button[data-test="selectAgainRadomUser"]';
|
||||
exports.promoteToModerator = 'li[data-test="promoteToModerator"]';
|
||||
exports.demoteToViewer = 'li[data-test="demoteToViewer"]';
|
||||
exports.makePresenter = 'li[data-test="makePresenter"]';
|
||||
exports.takePresenter = 'li[data-test="takePresenter"]';
|
||||
// Lock Viewers
|
||||
exports.lockViewersButton = 'li[data-test="lockViewersButton"]';
|
||||
exports.unlockUserButton = 'li[data-test="unlockUserButton"]';
|
||||
|
@ -7,7 +7,7 @@ const parameters = require('./parameters');
|
||||
const helpers = require('./helpers');
|
||||
const e = require('./elements');
|
||||
const { ELEMENT_WAIT_TIME, ELEMENT_WAIT_LONGER_TIME, VIDEO_LOADING_WAIT_TIME } = require('./constants');
|
||||
const { checkElement } = require('./util');
|
||||
const { checkElement, checkElementLengthEqualTo } = require('./util');
|
||||
|
||||
class Page {
|
||||
constructor(browser, page) {
|
||||
@ -81,6 +81,10 @@ class Page {
|
||||
return this.page.locator(selector);
|
||||
}
|
||||
|
||||
getLocatorByIndex(selector, index) {
|
||||
return this.page.locator(selector).nth(index);
|
||||
}
|
||||
|
||||
async getSelectorCount(selector) {
|
||||
const locator = this.getLocator(selector);
|
||||
return locator.count();
|
||||
@ -95,6 +99,14 @@ class Page {
|
||||
await this.page.waitForSelector(selector, { timeout });
|
||||
}
|
||||
|
||||
async waitUntilHaveCountSelector(selector, count, timeout = ELEMENT_WAIT_TIME) {
|
||||
await this.page.waitForFunction(
|
||||
checkElementLengthEqualTo,
|
||||
[selector, count],
|
||||
{ timeout },
|
||||
);
|
||||
}
|
||||
|
||||
async type(selector, text) {
|
||||
const handle = this.getLocator(selector);
|
||||
await handle.focus();
|
||||
|
@ -9,7 +9,7 @@ function checkElementLengthEqualTo([element, count]) {
|
||||
}
|
||||
|
||||
function checkIncludeClass([selector, className]) {
|
||||
return document.querySelectorAll(selector)[0].className.includes(className);
|
||||
return document.querySelectorAll(`${selector} > div`)[0].className.includes(className);
|
||||
}
|
||||
|
||||
exports.checkElement = checkElement;
|
||||
|
@ -1,7 +1,5 @@
|
||||
const { expect } = require('@playwright/test');
|
||||
const e = require('../core/elements');
|
||||
const { ELEMENT_WAIT_TIME } = require('../core/constants');
|
||||
const { checkElementLengthEqualTo } = require('../core/util');
|
||||
|
||||
async function enableChatPopup(test) {
|
||||
await test.waitAndClick(e.notificationsTab);
|
||||
@ -40,11 +38,7 @@ async function privateChatMessageToast(page2) {
|
||||
await page2.waitAndClick(e.userListItem);
|
||||
await page2.waitAndClick(e.startPrivateChat);
|
||||
// wait for the private chat to be ready
|
||||
await page2.page.waitForFunction(
|
||||
checkElementLengthEqualTo,
|
||||
[e.chatButton, 2],
|
||||
{ timeout: ELEMENT_WAIT_TIME },
|
||||
);
|
||||
await page2.waitUntilHaveCountSelector(e.chatButton, 2);
|
||||
// send a private message
|
||||
await page2.type(e.chatBox, e.message1);
|
||||
await page2.waitAndClick(e.sendButton);
|
||||
|
3533
bigbluebutton-tests/playwright/package-lock.json
generated
3533
bigbluebutton-tests/playwright/package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@ -6,11 +6,11 @@
|
||||
"test:debug": "npx playwright test --debug -g"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@playwright/test": "^1.16.3",
|
||||
"axios": "^0.24.0",
|
||||
"dotenv": "^10.0.0",
|
||||
"@playwright/test": "^1.18.1",
|
||||
"axios": "^0.25.0",
|
||||
"dotenv": "^16.0.0",
|
||||
"js-yaml": "^4.1.0",
|
||||
"playwright": "^1.16.3",
|
||||
"playwright": "^1.18.1",
|
||||
"sha1": "^1.1.1"
|
||||
}
|
||||
}
|
||||
|
@ -133,17 +133,15 @@ class Polling extends MultiUsers {
|
||||
}
|
||||
|
||||
async typeOnLastChoiceInput() {
|
||||
const allInputs = this.modPage.getLocator(e.pollOptionItem);
|
||||
const lastInput = allInputs.last();
|
||||
const lastInput = this.modPage.getLocatorByIndex(e.pollOptionItem, -1);
|
||||
await lastInput.fill(this.newInputText);
|
||||
|
||||
}
|
||||
|
||||
async checkLastOptionText() {
|
||||
await this.userPage.waitForSelector(e.pollingContainer);
|
||||
const answerOptions = this.userPage.getLocator(e.pollAnswerOptionBtn);
|
||||
const lastOptionText = await answerOptions.last().textContent();
|
||||
await expect(lastOptionText).toEqual(this.newInputText);
|
||||
const lastOptionText = this.userPage.getLocatorByIndex(e.pollAnswerOptionBtn, -1);
|
||||
await expect(lastOptionText).toHaveText(this.newInputText);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -2,7 +2,7 @@ const { expect } = require('@playwright/test');
|
||||
const Page = require('../core/page');
|
||||
const e = require('../core/elements');
|
||||
const c = require('../core/constants');
|
||||
const { checkIncludeClass } = require('../core/util');
|
||||
const { checkPresenterClass } = require('../user/util');
|
||||
|
||||
class Stress {
|
||||
constructor(browser, context, page) {
|
||||
@ -18,7 +18,7 @@ class Stress {
|
||||
for (let i = 1; i <= c.JOIN_AS_MODERATOR_TEST_ROUNDS; i++) {
|
||||
await this.modPage.init(true, true, { fullName: `Moderator-${i}` });
|
||||
await this.modPage.waitForSelector(e.userAvatar);
|
||||
const hasPresenterClass = await this.modPage.page.evaluate(checkIncludeClass, [e.userAvatar, e.presenterClassName]);
|
||||
const hasPresenterClass = await checkPresenterClass(this.modPage);
|
||||
await this.modPage.waitAndClick(e.actions);
|
||||
const canStartPoll = await this.modPage.checkElement(e.polling);
|
||||
if (!hasPresenterClass || !canStartPoll) {
|
||||
|
@ -1,6 +1,7 @@
|
||||
const { MultiUsers } = require("./multiusers");
|
||||
const e = require('../core/elements');
|
||||
const { sleep } = require('../core/helpers');
|
||||
const { setGuestPolicyOption } = require("./util");
|
||||
|
||||
class GuestPolicy extends MultiUsers {
|
||||
constructor(browser, context) {
|
||||
@ -8,25 +9,22 @@ class GuestPolicy extends MultiUsers {
|
||||
}
|
||||
|
||||
async askModerator() {
|
||||
await this.modPage.waitAndClick(e.manageUsers);
|
||||
await this.modPage.waitAndClick(e.guestPolicyLabel);
|
||||
await this.modPage.waitAndClick(e.askModerator);
|
||||
await setGuestPolicyOption(this.modPage, e.askModerator);
|
||||
await sleep(500);
|
||||
await this.initUserPage(false);
|
||||
await this.modPage.hasElement(e.waitingUsersBtn);
|
||||
}
|
||||
|
||||
async alwaysAccept() {
|
||||
await this.modPage.waitAndClick(e.manageUsers);
|
||||
await this.modPage.waitAndClick(e.guestPolicyLabel);
|
||||
await this.modPage.waitAndClick(e.alwaysAccept);
|
||||
await setGuestPolicyOption(this.modPage, e.askModerator);
|
||||
await setGuestPolicyOption(this.modPage, e.alwaysAccept);
|
||||
await sleep(500);
|
||||
await this.initUserPage(false);
|
||||
await this.userPage.hasElement(e.audioModal);
|
||||
}
|
||||
|
||||
async alwaysDeny() {
|
||||
await this.modPage.waitAndClick(e.manageUsers);
|
||||
await this.modPage.waitAndClick(e.guestPolicyLabel);
|
||||
await this.modPage.waitAndClick(e.alwaysDeny);
|
||||
await setGuestPolicyOption(this.modPage, e.alwaysDeny);
|
||||
await sleep(1500);
|
||||
await this.initUserPage(false);
|
||||
await this.userPage.hasElement(e.joinMeetingDemoPage);
|
||||
|
@ -5,6 +5,7 @@ const { expect } = require("@playwright/test");
|
||||
const { ELEMENT_WAIT_LONGER_TIME, ELEMENT_WAIT_TIME } = require("../core/constants");
|
||||
const { getNotesLocator } = require("../sharednotes/util");
|
||||
const { waitAndClearNotification } = require("../notifications/util");
|
||||
const { sleep } = require("../core/helpers");
|
||||
|
||||
class LockViewers extends MultiUsers {
|
||||
constructor(browser, page) {
|
||||
@ -35,6 +36,7 @@ class LockViewers extends MultiUsers {
|
||||
await openLockViewers(this.modPage);
|
||||
await this.modPage.waitAndClickElement(e.lockSeeOtherViewersWebcam);
|
||||
await this.modPage.waitAndClick(e.applyLockSettings);
|
||||
await sleep(500);
|
||||
const videoContainersCount = [
|
||||
await this.modPage.getSelectorCount(e.webcamVideoItem),
|
||||
await this.userPage.getSelectorCount(e.webcamVideoItem),
|
||||
@ -69,9 +71,9 @@ class LockViewers extends MultiUsers {
|
||||
}
|
||||
|
||||
async lockSendPrivateChatMessages() {
|
||||
const lastUserItemLocator = this.userPage.getLocator(e.userListItem).last();
|
||||
const lastUserItemLocator = this.userPage.getLocatorByIndex(e.userListItem, -1);
|
||||
await this.userPage.clickOnLocator(lastUserItemLocator);
|
||||
const startPrivateChatButton = this.userPage.getLocator(e.startPrivateChat).last();
|
||||
const startPrivateChatButton = this.userPage.getLocatorByIndex(e.startPrivateChat, -1);
|
||||
await this.userPage.clickOnLocator(startPrivateChatButton);
|
||||
await openLockViewers(this.modPage);
|
||||
await this.modPage.waitAndClickElement(e.lockPrivateChat);
|
||||
@ -115,9 +117,9 @@ class LockViewers extends MultiUsers {
|
||||
await this.userPage.hasElementDisabled(e.joinVideo);
|
||||
await this.userPage2.hasElementDisabled(e.joinVideo);
|
||||
|
||||
const lastUserItemLocator = this.modPage.getLocator(e.userListItem).last();
|
||||
const lastUserItemLocator = this.modPage.getLocatorByIndex(e.userListItem, -1);
|
||||
await this.modPage.clickOnLocator(lastUserItemLocator);
|
||||
const unlockUserButton = this.modPage.getLocator(e.unlockUserButton).last();
|
||||
const unlockUserButton = this.modPage.getLocatorByIndex(e.unlockUserButton, -1);
|
||||
await this.modPage.clickOnLocator(unlockUserButton);
|
||||
await this.userPage.hasElementDisabled(e.joinVideo);
|
||||
await this.userPage2.hasElementEnabled(e.joinVideo);
|
||||
|
@ -7,7 +7,7 @@ class MobileDevices extends MultiUsers {
|
||||
}
|
||||
|
||||
async mobileTagName() {
|
||||
await this.modPage.waitAndClick(e.userList);
|
||||
await this.modPage.waitAndClick(e.userListToggleBtn);
|
||||
await this.modPage.waitForSelector(e.firstUser);
|
||||
await this.modPage.hasElement(e.mobileUser);
|
||||
}
|
||||
|
@ -2,6 +2,8 @@ const { expect } = require('@playwright/test');
|
||||
const Page = require('../core/page');
|
||||
const e = require('../core/elements');
|
||||
const { waitAndClearNotification } = require('../notifications/util');
|
||||
const { sleep } = require('../core/helpers');
|
||||
const { checkAvatarIcon, checkPresenterClass } = require('./util');
|
||||
|
||||
class MultiUsers {
|
||||
constructor(browser, context) {
|
||||
@ -16,14 +18,26 @@ class MultiUsers {
|
||||
|
||||
async initModPage(page, shouldCloseAudioModal = true, { fullName = 'Moderator', ...restOptions } = {}) {
|
||||
const options = {
|
||||
fullName,
|
||||
...restOptions,
|
||||
fullName,
|
||||
};
|
||||
|
||||
this.modPage = new Page(this.browser, page);
|
||||
await this.modPage.init(true, shouldCloseAudioModal, options);
|
||||
}
|
||||
|
||||
async initModPage2(shouldCloseAudioModal = true, context = this.context, { fullName = 'Moderator2', useModMeetingId = true, ...restOptions } = {}) {
|
||||
const options = {
|
||||
...restOptions,
|
||||
fullName,
|
||||
meetingId: (useModMeetingId) ? this.modPage.meetingId : undefined,
|
||||
};
|
||||
|
||||
const page = await context.newPage();
|
||||
this.modPage2 = new Page(this.browser, page);
|
||||
await this.modPage2.init(true, shouldCloseAudioModal, options);
|
||||
}
|
||||
|
||||
async initUserPage(shouldCloseAudioModal = true, context = this.context, { fullName = 'Attendee', useModMeetingId = true, ...restOptions } = {}) {
|
||||
const options = {
|
||||
...restOptions,
|
||||
@ -59,6 +73,51 @@ class MultiUsers {
|
||||
await expect(secondUserOnUserPage).toHaveCount(1);
|
||||
}
|
||||
|
||||
async makePresenter() {
|
||||
await this.modPage.waitAndClick(e.userListItem);
|
||||
await this.modPage.waitAndClick(e.makePresenter);
|
||||
|
||||
await this.userPage.hasElement(e.startScreenSharing);
|
||||
await this.userPage.hasElement(e.presentationToolbarWrapper);
|
||||
await this.userPage.hasElement(e.toolsButton);
|
||||
await this.userPage.hasElement(e.actions);
|
||||
const hasPresenterClass = await checkPresenterClass(this.userPage);
|
||||
expect(hasPresenterClass).toBeTruthy();
|
||||
}
|
||||
|
||||
async takePresenter() {
|
||||
await this.modPage2.waitAndClick(e.firstUser);
|
||||
await this.modPage2.waitAndClick(e.takePresenter);
|
||||
|
||||
await this.modPage2.hasElement(e.startScreenSharing);
|
||||
await this.modPage2.hasElement(e.toolsButton);
|
||||
await this.modPage2.hasElement(e.presentationToolbarWrapper);
|
||||
const hasPresenterClass = await checkPresenterClass(this.modPage2);
|
||||
expect(hasPresenterClass).toBeTruthy();
|
||||
await this.modPage2.waitAndClick(e.actions);
|
||||
await this.modPage2.hasElement(e.managePresentations);
|
||||
await this.modPage2.hasElement(e.polling);
|
||||
await this.modPage2.hasElement(e.shareExternalVideoBtn);
|
||||
}
|
||||
|
||||
async promoteToModerator() {
|
||||
await checkAvatarIcon(this.userPage, false);
|
||||
await this.userPage.wasRemoved(e.manageUsers);
|
||||
await this.modPage.waitAndClick(e.userListItem);
|
||||
await this.modPage.waitAndClick(e.promoteToModerator);
|
||||
await checkAvatarIcon(this.userPage);
|
||||
await this.userPage.hasElement(e.manageUsers);
|
||||
}
|
||||
|
||||
async demoteToViewer() {
|
||||
await checkAvatarIcon(this.modPage2);
|
||||
await this.modPage2.hasElement(e.manageUsers);
|
||||
await this.modPage.waitAndClick(e.userListItem);
|
||||
await this.modPage.waitAndClick(e.demoteToViewer);
|
||||
await checkAvatarIcon(this.modPage2, false);
|
||||
await this.modPage2.wasRemoved(e.manageUsers);
|
||||
}
|
||||
|
||||
async raiseHandTest() {
|
||||
await this.userPage.waitAndClick(e.raiseHandBtn);
|
||||
await this.userPage.hasElement(e.lowerHandBtn);
|
||||
@ -78,6 +137,52 @@ class MultiUsers {
|
||||
await this.userPage.hasElement(e.raiseHandBtn);
|
||||
}
|
||||
|
||||
async toggleUserList() {
|
||||
await this.modPage.hasElement(e.chatWelcomeMessageText);
|
||||
await this.modPage.hasElement(e.chatBox);
|
||||
await this.modPage.hasElement(e.chatButton);
|
||||
await this.modPage.waitAndClick(e.userListToggleBtn);
|
||||
await this.modPage.wasRemoved(e.chatWelcomeMessageText);
|
||||
await this.modPage.wasRemoved(e.chatBox);
|
||||
await this.modPage.wasRemoved(e.chatButton);
|
||||
await this.modPage.waitAndClick(e.userListToggleBtn);
|
||||
await this.modPage.wasRemoved(e.chatWelcomeMessageText);
|
||||
await this.modPage.wasRemoved(e.chatBox);
|
||||
await this.modPage.hasElement(e.chatButton);
|
||||
}
|
||||
|
||||
async selectRandomUser() {
|
||||
// check with no viewer joined
|
||||
await this.modPage.waitAndClick(e.actions);
|
||||
await this.modPage.waitAndClick(e.selectRandomUser);
|
||||
await this.modPage.hasElement(e.noViewersSelectedMessage);
|
||||
// check with only one viewer
|
||||
await this.modPage.waitAndClick(e.closeModal);
|
||||
await this.initUserPage();
|
||||
await this.modPage.waitAndClick(e.actions);
|
||||
await this.modPage.waitAndClick(e.selectRandomUser);
|
||||
await this.modPage.hasText(e.selectedUserName, this.userPage.username);
|
||||
// check with more users
|
||||
await this.modPage.waitAndClick(e.closeModal);
|
||||
await this.initUserPage2();
|
||||
await this.modPage.waitAndClick(e.actions);
|
||||
await this.modPage.waitAndClick(e.selectRandomUser);
|
||||
const nameSelected = await this.modPage.getLocator(e.selectedUserName).textContent();
|
||||
await this.userPage.hasText(e.selectedUserName, nameSelected);
|
||||
await this.userPage2.hasText(e.selectedUserName, nameSelected);
|
||||
// user close modal just for you
|
||||
await this.userPage.waitAndClick(e.closeModal);
|
||||
await this.userPage.wasRemoved(e.selectedUserName);
|
||||
await this.userPage2.hasElement(e.selectedUserName);
|
||||
await this.modPage.hasElement(e.selectedUserName);
|
||||
// moderator close modal
|
||||
await this.modPage.waitAndClick(e.selectAgainRadomUser);
|
||||
await sleep(500);
|
||||
await this.modPage.waitAndClick(e.closeModal);
|
||||
await this.userPage.wasRemoved(e.selectedUserName);
|
||||
await this.userPage2.wasRemoved(e.selectedUserName);
|
||||
}
|
||||
|
||||
async whiteboardAccess() {
|
||||
await this.modPage.waitForSelector(e.whiteboard);
|
||||
await this.modPage.waitAndClick(e.userListItem);
|
||||
|
@ -20,7 +20,7 @@ class Status extends Page {
|
||||
}
|
||||
|
||||
async mobileTagName() {
|
||||
await this.waitAndClick(e.userList);
|
||||
await this.waitAndClick(e.userListToggleBtn);
|
||||
await this.waitForSelector(e.firstUser);
|
||||
await this.hasElement(e.mobileUser);
|
||||
}
|
||||
|
@ -16,6 +16,12 @@ test.describe.parallel('User', () => {
|
||||
await multiusers.getAvatarColorAndCompareWithUserListItem();
|
||||
await multiusers.lowerHandTest();
|
||||
});
|
||||
|
||||
test('Toggle user list', async ({ browser, context, page }) => {
|
||||
const multiusers = new MultiUsers(browser, context);
|
||||
await multiusers.initModPage(page);
|
||||
await multiusers.toggleUserList();
|
||||
});
|
||||
});
|
||||
|
||||
test.describe.parallel('List', () => {
|
||||
@ -30,6 +36,32 @@ test.describe.parallel('User', () => {
|
||||
await multiusers.initPages(page);
|
||||
await multiusers.userPresence();
|
||||
});
|
||||
|
||||
test('Make presenter', async ({ browser, context, page }) => {
|
||||
const multiusers = new MultiUsers(browser, context);
|
||||
await multiusers.initPages(page);
|
||||
await multiusers.makePresenter();
|
||||
});
|
||||
|
||||
test('Take presenter', async ({ browser, context, page }) => {
|
||||
const multiusers = new MultiUsers(browser, context);
|
||||
await multiusers.initModPage(page);
|
||||
await multiusers.initModPage2();
|
||||
await multiusers.takePresenter();
|
||||
});
|
||||
|
||||
test('Promote to moderator', async ({ browser, context, page }) => {
|
||||
const multiusers = new MultiUsers(browser, context);
|
||||
await multiusers.initPages(page);
|
||||
await multiusers.promoteToModerator();
|
||||
});
|
||||
|
||||
test('Demote to viewer', async ({ browser, context, page }) => {
|
||||
const multiusers = new MultiUsers(browser, context);
|
||||
await multiusers.initModPage(page);
|
||||
await multiusers.initModPage2();
|
||||
await multiusers.demoteToViewer();
|
||||
});
|
||||
});
|
||||
|
||||
test.describe.parallel('Manage', () => {
|
||||
@ -52,6 +84,69 @@ test.describe.parallel('User', () => {
|
||||
await guestPolicy.alwaysDeny();
|
||||
});
|
||||
});
|
||||
|
||||
test.describe.parallel('Lock viewers', () => {
|
||||
test('Lock Share webcam', async ({ browser, context, page }) => {
|
||||
const lockViewers = new LockViewers(browser, context);
|
||||
await lockViewers.initPages(page);
|
||||
await lockViewers.initUserPage2();
|
||||
await lockViewers.lockShareWebcam();
|
||||
});
|
||||
|
||||
test('Lock See other viewers webcams', async ({ browser, context, page }) => {
|
||||
const lockViewers = new LockViewers(browser, context);
|
||||
await lockViewers.initPages(page);
|
||||
await lockViewers.initUserPage2();
|
||||
await lockViewers.lockSeeOtherViewersWebcams();
|
||||
});
|
||||
|
||||
test('Lock Share microphone', async ({ browser, context, page }) => {
|
||||
const lockViewers = new LockViewers(browser, context);
|
||||
await lockViewers.initPages(page);
|
||||
await lockViewers.initUserPage2();
|
||||
await lockViewers.lockShareMicrophone();
|
||||
});
|
||||
|
||||
test('Lock Send public chat messages', async ({ browser, context, page }) => {
|
||||
const lockViewers = new LockViewers(browser, context);
|
||||
await lockViewers.initPages(page);
|
||||
await lockViewers.initUserPage2();
|
||||
await lockViewers.lockSendPublicChatMessages();
|
||||
});
|
||||
|
||||
test('Lock Send private chat messages', async ({ browser, context, page }) => {
|
||||
const lockViewers = new LockViewers(browser, context);
|
||||
await lockViewers.initPages(page);
|
||||
await lockViewers.initUserPage2();
|
||||
await lockViewers.lockSendPrivateChatMessages();
|
||||
});
|
||||
|
||||
test('Lock Edit Shared Notes', async ({ browser, context, page }) => {
|
||||
const lockViewers = new LockViewers(browser, context);
|
||||
await lockViewers.initPages(page);
|
||||
await lockViewers.lockEditSharedNotes();
|
||||
});
|
||||
|
||||
test('Lock See other viewers in the Users list', async ({ browser, context, page }) => {
|
||||
const lockViewers = new LockViewers(browser, context);
|
||||
await lockViewers.initPages(page);
|
||||
await lockViewers.initUserPage2();
|
||||
await lockViewers.lockSeeOtherViewersUserList();
|
||||
});
|
||||
|
||||
test('Unlock a user', async ({ browser, context, page }) => {
|
||||
const lockViewers = new LockViewers(browser, context);
|
||||
await lockViewers.initPages(page);
|
||||
await lockViewers.initUserPage2();
|
||||
await lockViewers.unlockUser();
|
||||
});
|
||||
});
|
||||
|
||||
test('Select random user', async ({ browser, context, page }) => {
|
||||
const multiusers = new MultiUsers(browser, context);
|
||||
await multiusers.initModPage(page);
|
||||
await multiusers.selectRandomUser();
|
||||
});
|
||||
});
|
||||
|
||||
test.describe.parallel('Mobile devices', () => {
|
||||
@ -94,61 +189,4 @@ test.describe.parallel('User', () => {
|
||||
await mobileDevices.chatPanelNotAppearOnMobile();
|
||||
});
|
||||
});
|
||||
|
||||
test.describe.parallel('Lock viewers', () => {
|
||||
test('Lock Share webcam', async ({ browser, context, page }) => {
|
||||
const lockViewers = new LockViewers(browser, context);
|
||||
await lockViewers.initPages(page);
|
||||
await lockViewers.initUserPage2();
|
||||
await lockViewers.lockShareWebcam();
|
||||
});
|
||||
|
||||
test('Lock See other viewers webcams', async ({ browser, context, page }) => {
|
||||
const lockViewers = new LockViewers(browser, context);
|
||||
await lockViewers.initPages(page);
|
||||
await lockViewers.initUserPage2();
|
||||
await lockViewers.lockSeeOtherViewersWebcams();
|
||||
});
|
||||
|
||||
test('Lock Share microphone', async ({ browser, context, page }) => {
|
||||
const lockViewers = new LockViewers(browser, context);
|
||||
await lockViewers.initPages(page);
|
||||
await lockViewers.initUserPage2();
|
||||
await lockViewers.lockShareMicrophone();
|
||||
});
|
||||
|
||||
test('Lock Send public chat messages', async ({ browser, context, page }) => {
|
||||
const lockViewers = new LockViewers(browser, context);
|
||||
await lockViewers.initPages(page);
|
||||
await lockViewers.initUserPage2();
|
||||
await lockViewers.lockSendPublicChatMessages();
|
||||
});
|
||||
|
||||
test('Lock Send private chat messages', async ({ browser, context, page }) => {
|
||||
const lockViewers = new LockViewers(browser, context);
|
||||
await lockViewers.initPages(page);
|
||||
await lockViewers.initUserPage2();
|
||||
await lockViewers.lockSendPrivateChatMessages();
|
||||
});
|
||||
|
||||
test('Lock Edit Shared Notes', async ({ browser, context, page }) => {
|
||||
const lockViewers = new LockViewers(browser, context);
|
||||
await lockViewers.initPages(page);
|
||||
await lockViewers.lockEditSharedNotes();
|
||||
});
|
||||
|
||||
test('Lock See other viewers in the Users list', async ({ browser, context, page }) => {
|
||||
const lockViewers = new LockViewers(browser, context);
|
||||
await lockViewers.initPages(page);
|
||||
await lockViewers.initUserPage2();
|
||||
await lockViewers.lockSeeOtherViewersUserList();
|
||||
});
|
||||
|
||||
test('Unlock a user', async ({ browser, context, page }) => {
|
||||
const lockViewers = new LockViewers(browser, context);
|
||||
await lockViewers.initPages(page);
|
||||
await lockViewers.initUserPage2();
|
||||
await lockViewers.unlockUser();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
@ -1,4 +1,5 @@
|
||||
const e = require('../core/elements');
|
||||
const { checkIncludeClass } = require('../core/util');
|
||||
|
||||
async function setStatus(page, status) {
|
||||
await page.waitAndClick(e.firstUser);
|
||||
@ -11,5 +12,22 @@ async function openLockViewers(test) {
|
||||
await test.waitAndClick(e.lockViewersButton);
|
||||
}
|
||||
|
||||
async function setGuestPolicyOption(test, option) {
|
||||
await test.waitAndClick(e.manageUsers);
|
||||
await test.waitAndClick(e.guestPolicyLabel);
|
||||
await test.waitAndClick(option);
|
||||
}
|
||||
|
||||
async function checkAvatarIcon(test, checkModIcon = true) {
|
||||
await test.hasElement(`${e.firstUser} ${checkModIcon ? e.moderatorAvatar : e.viewerAvatar}`);
|
||||
}
|
||||
|
||||
async function checkPresenterClass(test) {
|
||||
return test.page.evaluate(checkIncludeClass, [e.userAvatar, e.presenterClassName]);
|
||||
}
|
||||
|
||||
exports.setStatus = setStatus;
|
||||
exports.openLockViewers = openLockViewers;
|
||||
exports.setGuestPolicyOption = setGuestPolicyOption;
|
||||
exports.checkAvatarIcon = checkAvatarIcon;
|
||||
exports.checkPresenterClass = checkPresenterClass;
|
||||
|
@ -274,6 +274,11 @@ defaultKeepEvents=false
|
||||
# Default 60s
|
||||
usersTimeout=60000
|
||||
|
||||
# Timeout (millis) to remove guest users that stopped fetching for her/his status
|
||||
# e.g. guest that closed the waiting page before being approved
|
||||
# Default 15s
|
||||
waitingGuestUsersTimeout=15000
|
||||
|
||||
# Timeout (millis) to remove users that called the enter API but did not join
|
||||
# e.g. user's client hanged between the enter call and join event
|
||||
# Default 45s
|
||||
|
@ -57,6 +57,7 @@ with BigBlueButton; if not, see <http://www.gnu.org/licenses/>.
|
||||
<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="swfSlidesGenerationProgressNotifier" ref="swfSlidesGenerationProgressNotifier"/>
|
||||
</bean>
|
||||
|
@ -322,6 +322,7 @@ class ApiController {
|
||||
us.guestStatus = guestStatusVal
|
||||
us.logoutUrl = meeting.getLogoutUrl()
|
||||
us.defaultLayout = meeting.getMeetingLayout()
|
||||
us.leftGuestLobby = false
|
||||
|
||||
if (!StringUtils.isEmpty(params.defaultLayout)) {
|
||||
us.defaultLayout = params.defaultLayout;
|
||||
@ -366,7 +367,8 @@ class ApiController {
|
||||
us.guest,
|
||||
us.authed,
|
||||
guestStatusVal,
|
||||
us.excludeFromDashboard
|
||||
us.excludeFromDashboard,
|
||||
us.leftGuestLobby
|
||||
)
|
||||
|
||||
session.setMaxInactiveInterval(SESSION_TIMEOUT);
|
||||
@ -733,7 +735,6 @@ class ApiController {
|
||||
request.getParameterMap(),
|
||||
request.getQueryString()
|
||||
)
|
||||
|
||||
if(!(validationResponse == null)) {
|
||||
msgKey = validationResponse.getKey()
|
||||
msgValue = validationResponse.getValue()
|
||||
@ -793,6 +794,14 @@ class ApiController {
|
||||
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)
|
||||
@ -839,7 +848,6 @@ class ApiController {
|
||||
request.getParameterMap(),
|
||||
request.getQueryString(),
|
||||
)
|
||||
|
||||
if(!(validationResponse == null)) {
|
||||
respMessage = validationResponse.getValue()
|
||||
reject = true
|
||||
|
@ -9,4 +9,8 @@ if [ ! -f /.dockerenv ]; then
|
||||
mount -t tmpfs -o size=512m tmpfs /mnt/mongo-ramdisk
|
||||
fi
|
||||
|
||||
chown -R mongodb:mongodb /mnt/mongo-ramdisk
|
||||
if id mongod &> /dev/null; then
|
||||
chown -R mongod:mongod /mnt/mongo-ramdisk
|
||||
else
|
||||
chown -R mongodb:mongodb /mnt/mongo-ramdisk
|
||||
fi
|
||||
|
@ -24,7 +24,9 @@ sudo rm -rf /usr/local/bigbluebutton/core/lib
|
||||
sudo cp -r core/lib /usr/local/bigbluebutton/core/
|
||||
sudo rm -rf /usr/local/bigbluebutton/core/scripts
|
||||
sudo cp -r core/scripts /usr/local/bigbluebutton/core/
|
||||
sudo rm -rf /var/bigbluebutton/playback/*
|
||||
sudo rm -rf /var/bigbluebutton/playback/presentation/0.81/
|
||||
sudo rm -rf /var/bigbluebutton/playback/presentation/0.9.0/
|
||||
sudo rm -rf /var/bigbluebutton/playback/presentation/2.0/
|
||||
|
||||
function deploy_format() {
|
||||
local formats=$1
|
||||
@ -95,10 +97,4 @@ if [ ! -d "$REC_STATUS_SANITY_DIR" ]; then
|
||||
sudo mkdir -p $REC_STATUS_SANITY_DIR
|
||||
fi
|
||||
|
||||
sudo mv /usr/local/bigbluebutton/core/scripts/*.nginx /etc/bigbluebutton/nginx/
|
||||
sudo service nginx reload
|
||||
sudo chown -R bigbluebutton:bigbluebutton /var/bigbluebutton/ /var/log/bigbluebutton/
|
||||
sudo chown -R red5:red5 /var/bigbluebutton/screenshare/
|
||||
|
||||
#cd /usr/local/bigbluebutton/core/
|
||||
#sudo bundle install
|
Loading…
Reference in New Issue
Block a user