Merge remote-tracking branch 'upstream/v3.0.x-release' into fix-input-losing-focus-tldraw

This commit is contained in:
André Möller 2024-05-09 16:56:33 -03:00
commit 41120fee80
91 changed files with 1768 additions and 2013 deletions

View File

@ -256,6 +256,9 @@ jobs:
'
- name: Install BBB
uses: nick-fields/retry@v3
env:
NODE_EXTRA_CA_CERTS: /usr/local/share/ca-certificates/bbb-dev/bbb-dev-ca.crt
ACTIONS_RUNNER_DEBUG: true
with:
timeout_minutes: 25
max_attempts: 2
@ -265,7 +268,6 @@ jobs:
cd /root/ && wget -nv https://raw.githubusercontent.com/bigbluebutton/bbb-install/v3.0.x-release/bbb-install.sh -O bbb-install.sh
cat bbb-install.sh | sed "s|> /etc/apt/sources.list.d/bigbluebutton.list||g" | bash -s -- -v jammy-30-dev -s bbb-ci.test -j -d /certs/
bbb-conf --salt bbbci
echo "NODE_EXTRA_CA_CERTS=/usr/local/share/ca-certificates/bbb-dev/bbb-dev-ca.crt" >> /usr/share/meteor/bundle/bbb-html5-with-roles.conf
sed -i "s/\"minify\": true,/\"minify\": false,/" /usr/share/etherpad-lite/settings.json
bbb-conf --restart
EOF

View File

@ -154,7 +154,7 @@ class BigBlueButtonActor(
}
private def handleGetAllMeetingsReqMsg(msg: GetAllMeetingsReqMsg): Unit = {
RunningMeetings.meetings(meetings).filter(_.props.systemProps.html5InstanceId == msg.body.html5InstanceId).foreach(m => {
RunningMeetings.meetings(meetings).foreach(m => {
m.actorRef ! msg
})
}

View File

@ -11,7 +11,7 @@ trait SyncGetGroupChatsInfoMsgHdlr {
def handleSyncGetGroupChatsInfo(state: MeetingState2x, liveMeeting: LiveMeeting, bus: MessageBus): MeetingState2x = {
def buildSyncGetGroupChatsRespMsg(allChats: Vector[GroupChatInfo]): BbbCommonEnvCoreMsg = {
val routing = Routing.addMsgToHtml5InstanceIdRouting(liveMeeting.props.meetingProp.intId, liveMeeting.props.systemProps.html5InstanceId.toString)
val routing = Routing.addMsgToClientRouting(MessageTypes.DIRECT, liveMeeting.props.meetingProp.intId, "nodeJSapp")
val envelope = BbbCoreEnvelope(SyncGetGroupChatsRespMsg.NAME, routing)
val header = BbbClientMsgHeader(SyncGetGroupChatsRespMsg.NAME, liveMeeting.props.meetingProp.intId, "nodeJSapp")
val body = SyncGetGroupChatsRespMsgBody(allChats)
@ -21,7 +21,7 @@ trait SyncGetGroupChatsInfoMsgHdlr {
}
def buildSyncGetGroupChatMsgsRespMsg(msgs: Vector[GroupChatMsgToUser], chatId: String): BbbCommonEnvCoreMsg = {
val routing = Routing.addMsgToHtml5InstanceIdRouting(liveMeeting.props.meetingProp.intId, liveMeeting.props.systemProps.html5InstanceId.toString)
val routing = Routing.addMsgToClientRouting(MessageTypes.DIRECT, liveMeeting.props.meetingProp.intId, "nodeJSapp")
val envelope = BbbCoreEnvelope(SyncGetGroupChatMsgsRespMsg.NAME, routing)
val header = BbbClientMsgHeader(SyncGetGroupChatMsgsRespMsg.NAME, liveMeeting.props.meetingProp.intId, "nodeJSapp")
val body = SyncGetGroupChatMsgsRespMsgBody(chatId, msgs)

View File

@ -9,7 +9,7 @@ trait SyncGetMeetingInfoRespMsgHdlr {
val outGW: OutMsgRouter
def handleSyncGetMeetingInfoRespMsg(props: DefaultProps): Unit = {
val routing = Routing.addMsgToHtml5InstanceIdRouting(props.meetingProp.intId, props.systemProps.html5InstanceId.toString)
val routing = Routing.addMsgToClientRouting(MessageTypes.DIRECT, props.meetingProp.intId, "nodeJSapp")
val envelope = BbbCoreEnvelope(SyncGetMeetingInfoRespMsg.NAME, routing)
val header = BbbCoreBaseHeader(SyncGetMeetingInfoRespMsg.NAME)

View File

@ -12,7 +12,7 @@ trait SyncGetPresentationPodsMsgHdlr {
def handleSyncGetPresentationPods(state: MeetingState2x, liveMeeting: LiveMeeting, bus: MessageBus): MeetingState2x = {
def buildSyncGetPresentationPodsRespMsg(pods: Vector[PresentationPodVO]): BbbCommonEnvCoreMsg = {
val routing = Routing.addMsgToHtml5InstanceIdRouting(liveMeeting.props.meetingProp.intId, liveMeeting.props.systemProps.html5InstanceId.toString)
val routing = Routing.addMsgToClientRouting(MessageTypes.DIRECT, liveMeeting.props.meetingProp.intId, "nodeJSapp")
val envelope = BbbCoreEnvelope(SyncGetPresentationPodsRespMsg.NAME, routing)
val header = BbbClientMsgHeader(SyncGetPresentationPodsRespMsg.NAME, liveMeeting.props.meetingProp.intId, "nodeJSapp")

View File

@ -9,9 +9,10 @@ trait SyncGetScreenshareInfoRespMsgHdlr {
this: ScreenshareApp2x =>
def handleSyncGetScreenshareInfoRespMsg(liveMeeting: LiveMeeting, bus: MessageBus): Unit = {
val routing = Routing.addMsgToHtml5InstanceIdRouting(
val routing = Routing.addMsgToClientRouting(
MessageTypes.BROADCAST_TO_MEETING,
liveMeeting.props.meetingProp.intId,
liveMeeting.props.systemProps.html5InstanceId.toString
"nodeJSapp"
)
val envelope = BbbCoreEnvelope(SyncGetScreenshareInfoRespMsg.NAME, routing)
val header = BbbClientMsgHeader(

View File

@ -11,7 +11,7 @@ trait SyncGetUsersMeetingRespMsgHdlr {
val outGW: OutMsgRouter
def handleSyncGetUsersMeetingRespMsg(): Unit = {
val routing = Routing.addMsgToHtml5InstanceIdRouting(liveMeeting.props.meetingProp.intId, liveMeeting.props.systemProps.html5InstanceId.toString)
val routing = Routing.addMsgToClientRouting(MessageTypes.DIRECT, liveMeeting.props.meetingProp.intId, "nodeJSapp")
val envelope = BbbCoreEnvelope(SyncGetUsersMeetingRespMsg.NAME, routing)
val header = BbbClientMsgHeader(SyncGetUsersMeetingRespMsg.NAME, liveMeeting.props.meetingProp.intId, "nodeJSapp")

View File

@ -17,7 +17,7 @@ trait SyncGetVoiceUsersMsgHdlr {
callerNum = u.callerNum, color = u.color, muted = u.muted, talking = u.talking, listenOnly = u.listenOnly)
}
val routing = Routing.addMsgToHtml5InstanceIdRouting(liveMeeting.props.meetingProp.intId, liveMeeting.props.systemProps.html5InstanceId.toString)
val routing = Routing.addMsgToClientRouting(MessageTypes.DIRECT, liveMeeting.props.meetingProp.intId, "nodeJSapp")
val envelope = BbbCoreEnvelope(SyncGetVoiceUsersRespMsg.NAME, routing)
val header = BbbClientMsgHeader(SyncGetVoiceUsersRespMsg.NAME, liveMeeting.props.meetingProp.intId, "nodeJSapp")
val body = SyncGetVoiceUsersRespMsgBody(voiceUsers)

View File

@ -13,9 +13,10 @@ trait SyncGetWebcamInfoRespMsgHdlr {
this: WebcamApp2x =>
def handleSyncGetWebcamInfoRespMsg(liveMeeting: LiveMeeting, bus: MessageBus): Unit = {
val routing = Routing.addMsgToHtml5InstanceIdRouting(
val routing = Routing.addMsgToClientRouting(
MessageTypes.BROADCAST_TO_MEETING,
liveMeeting.props.meetingProp.intId,
liveMeeting.props.systemProps.html5InstanceId.toString
"nodeJSapp"
)
val envelope = BbbCoreEnvelope(SyncGetWebcamInfoRespMsg.NAME, routing)
val header = BbbClientMsgHeader(

View File

@ -11,7 +11,7 @@ trait ClearWhiteboardPubMsgHdlr extends RightsManagementTrait {
def handle(msg: ClearWhiteboardPubMsg, liveMeeting: LiveMeeting, bus: MessageBus): Unit = {
def broadcastEvent(msg: ClearWhiteboardPubMsg, fullClear: Boolean): Unit = {
val routing = Routing.addMsgToHtml5InstanceIdRouting(liveMeeting.props.meetingProp.intId, liveMeeting.props.systemProps.html5InstanceId.toString)
val routing = Routing.addMsgToClientRouting(MessageTypes.BROADCAST_TO_MEETING, liveMeeting.props.meetingProp.intId, msg.header.userId)
val envelope = BbbCoreEnvelope(ClearWhiteboardEvtMsg.NAME, routing)
val header = BbbClientMsgHeader(ClearWhiteboardEvtMsg.NAME, liveMeeting.props.meetingProp.intId, msg.header.userId)

View File

@ -11,7 +11,7 @@ trait DeleteWhiteboardAnnotationsPubMsgHdlr extends RightsManagementTrait {
def handle(msg: DeleteWhiteboardAnnotationsPubMsg, liveMeeting: LiveMeeting, bus: MessageBus): Unit = {
def broadcastEvent(msg: DeleteWhiteboardAnnotationsPubMsg, removedAnnotationsIds: Array[String]): Unit = {
val routing = Routing.addMsgToHtml5InstanceIdRouting(liveMeeting.props.meetingProp.intId, liveMeeting.props.systemProps.html5InstanceId.toString)
val routing = Routing.addMsgToClientRouting(MessageTypes.BROADCAST_TO_MEETING, liveMeeting.props.meetingProp.intId, msg.header.userId)
val envelope = BbbCoreEnvelope(DeleteWhiteboardAnnotationsEvtMsg.NAME, routing)
val header = BbbClientMsgHeader(DeleteWhiteboardAnnotationsEvtMsg.NAME, liveMeeting.props.meetingProp.intId, msg.header.userId)

View File

@ -10,7 +10,7 @@ trait GetWhiteboardAnnotationsReqMsgHdlr {
def handle(msg: GetWhiteboardAnnotationsReqMsg, liveMeeting: LiveMeeting, bus: MessageBus): Unit = {
def broadcastEvent(msg: GetWhiteboardAnnotationsReqMsg, history: Array[AnnotationVO], multiUser: Array[String]): Unit = {
val routing = Routing.addMsgToHtml5InstanceIdRouting(liveMeeting.props.meetingProp.intId, liveMeeting.props.systemProps.html5InstanceId.toString)
val routing = Routing.addMsgToClientRouting(MessageTypes.DIRECT, liveMeeting.props.meetingProp.intId, msg.header.userId)
val envelope = BbbCoreEnvelope(GetWhiteboardAnnotationsRespMsg.NAME, routing)
val header = BbbClientMsgHeader(GetWhiteboardAnnotationsRespMsg.NAME, liveMeeting.props.meetingProp.intId, msg.header.userId)

View File

@ -11,7 +11,7 @@ trait ModifyWhiteboardAccessPubMsgHdlr extends RightsManagementTrait {
def handle(msg: ModifyWhiteboardAccessPubMsg, liveMeeting: LiveMeeting, bus: MessageBus): Unit = {
def broadcastEvent(msg: ModifyWhiteboardAccessPubMsg): Unit = {
val routing = Routing.addMsgToHtml5InstanceIdRouting(liveMeeting.props.meetingProp.intId, liveMeeting.props.systemProps.html5InstanceId.toString)
val routing = Routing.addMsgToClientRouting(MessageTypes.BROADCAST_TO_MEETING, liveMeeting.props.meetingProp.intId, msg.header.userId)
val envelope = BbbCoreEnvelope(ModifyWhiteboardAccessEvtMsg.NAME, routing)
val header = BbbClientMsgHeader(ModifyWhiteboardAccessEvtMsg.NAME, liveMeeting.props.meetingProp.intId, msg.header.userId)

View File

@ -12,7 +12,7 @@ trait SendCursorPositionPubMsgHdlr extends RightsManagementTrait {
def handle(msg: SendCursorPositionPubMsg, liveMeeting: LiveMeeting, bus: MessageBus): Unit = {
def broadcastEvent(msg: SendCursorPositionPubMsg): Unit = {
val routing = Routing.addMsgToHtml5InstanceIdRouting(liveMeeting.props.meetingProp.intId, liveMeeting.props.systemProps.html5InstanceId.toString)
val routing = Routing.addMsgToClientRouting(MessageTypes.BROADCAST_TO_MEETING, liveMeeting.props.meetingProp.intId, msg.header.userId)
val envelope = BbbCoreEnvelope(SendCursorPositionEvtMsg.NAME, routing)
val header = BbbClientMsgHeader(SendCursorPositionEvtMsg.NAME, liveMeeting.props.meetingProp.intId, msg.header.userId)

View File

@ -10,8 +10,8 @@ trait SendWhiteboardAnnotationsPubMsgHdlr extends RightsManagementTrait {
def handle(msg: SendWhiteboardAnnotationsPubMsg, liveMeeting: LiveMeeting, bus: MessageBus): Unit = {
def broadcastEvent(msg: SendWhiteboardAnnotationsPubMsg, whiteboardId: String, annotations: Array[AnnotationVO], html5InstanceId: String): Unit = {
val routing = Routing.addMsgToHtml5InstanceIdRouting(liveMeeting.props.meetingProp.intId, html5InstanceId)
def broadcastEvent(msg: SendWhiteboardAnnotationsPubMsg, whiteboardId: String, annotations: Array[AnnotationVO]): Unit = {
val routing = Routing.addMsgToClientRouting(MessageTypes.BROADCAST_TO_MEETING, liveMeeting.props.meetingProp.intId, msg.header.userId)
val envelope = BbbCoreEnvelope(SendWhiteboardAnnotationsEvtMsg.NAME, routing)
val header = BbbClientMsgHeader(SendWhiteboardAnnotationsEvtMsg.NAME, liveMeeting.props.meetingProp.intId, msg.header.userId)
@ -58,7 +58,7 @@ trait SendWhiteboardAnnotationsPubMsgHdlr extends RightsManagementTrait {
// }
// println("============= Printed Sanitized annotations ============")
val annotations = sendWhiteboardAnnotations(msg.body.whiteboardId, msg.header.userId, msg.body.annotations, liveMeeting, isUserAmongPresenters, isUserModerator)
broadcastEvent(msg, msg.body.whiteboardId, annotations, msg.body.html5InstanceId)
broadcastEvent(msg, msg.body.whiteboardId, annotations)
} else {
//val meetingId = liveMeeting.props.meetingProp.intId
//val reason = "No permission to send a whiteboard annotation."

View File

@ -18,27 +18,18 @@ class FromAkkaAppsMsgSenderActor(msgSender: MessageSender)
case _ => log.warning("Cannot handle message ")
}
def sendToHTML5InstanceIdChannel(msg: BbbCommonEnvCoreMsg, json: String): Unit = {
msg.envelope.routing.get("html5InstanceId") match {
case Some(id) => {
msgSender.send(toHTML5RedisChannel.concat(id), json)
}
case _ => log.error("No html5InstanceId for " + msg.envelope.name)
}
}
def handleBbbCommonEnvCoreMsg(msg: BbbCommonEnvCoreMsg): Unit = {
val json = JsonUtil.toJson(msg)
msg.envelope.name match {
// HTML5 sync messages
case SyncGetPresentationPodsRespMsg.NAME => sendToHTML5InstanceIdChannel(msg, json)
case SyncGetMeetingInfoRespMsg.NAME => sendToHTML5InstanceIdChannel(msg, json)
case SyncGetUsersMeetingRespMsg.NAME => sendToHTML5InstanceIdChannel(msg, json)
case SyncGetGroupChatsRespMsg.NAME => sendToHTML5InstanceIdChannel(msg, json)
case SyncGetGroupChatMsgsRespMsg.NAME => sendToHTML5InstanceIdChannel(msg, json)
case SyncGetVoiceUsersRespMsg.NAME => sendToHTML5InstanceIdChannel(msg, json)
case SyncGetPresentationPodsRespMsg.NAME => msgSender.send(toHTML5RedisChannel, json)
case SyncGetMeetingInfoRespMsg.NAME => msgSender.send(toHTML5RedisChannel, json)
case SyncGetUsersMeetingRespMsg.NAME => msgSender.send(toHTML5RedisChannel, json)
case SyncGetGroupChatsRespMsg.NAME => msgSender.send(toHTML5RedisChannel, json)
case SyncGetGroupChatMsgsRespMsg.NAME => msgSender.send(toHTML5RedisChannel, json)
case SyncGetVoiceUsersRespMsg.NAME => msgSender.send(toHTML5RedisChannel, json)
// Sent to FreeSWITCH
case EjectAllFromVoiceConfMsg.NAME =>

View File

@ -43,7 +43,7 @@ redis {
postgres {
dataSourceClass = "org.postgresql.ds.PGSimpleDataSource"
properties = {
serverName = "localhost"
serverName = "127.0.0.1"
portNumber = "5432"
databaseName = "bbb_graphql"
user = "postgres"

View File

@ -68,7 +68,6 @@ case class LockSettingsProps(
)
case class SystemProps(
html5InstanceId: Int,
loginUrl: String,
logoutUrl: String,
customLogoURL: String,

View File

@ -4,7 +4,6 @@ object Routing {
val MSG_TYPE = "msgType"
val MEETING_ID = "meetingId"
val USER_ID = "userId"
val HTML5_INSTANCE_ID = "html5InstanceId"
def addMsgToClientRouting(msgType: String, meetingId: String, userId: String): collection.immutable.Map[String, String] = {
Map(MSG_TYPE -> msgType, MEETING_ID -> meetingId, USER_ID -> userId)
@ -13,10 +12,6 @@ object Routing {
def addMsgFromClientRouting(meetingId: String, userId: String): collection.immutable.Map[String, String] = {
Map(MEETING_ID -> meetingId, USER_ID -> userId)
}
def addMsgToHtml5InstanceIdRouting(meetingId: String, html5InstanceId: String): collection.immutable.Map[String, String] = {
Map(MEETING_ID -> meetingId, HTML5_INSTANCE_ID -> html5InstanceId)
}
}
class Routing {

View File

@ -35,7 +35,7 @@ case class GetAllMeetingsReqMsg(
header: BbbCoreBaseHeader,
body: GetAllMeetingsReqMsgBody
) extends BbbCoreMsg
case class GetAllMeetingsReqMsgBody(requesterId: String, html5InstanceId: Int)
case class GetAllMeetingsReqMsgBody(requesterId: String)
object GetRunningMeetingsReqMsg { val NAME = "GetRunningMeetingsReqMsg" }
case class GetRunningMeetingsReqMsg(

View File

@ -54,7 +54,7 @@ case class SendCursorPositionPubMsgBody(whiteboardId: String, xPercent: Double,
object SendWhiteboardAnnotationsPubMsg { val NAME = "SendWhiteboardAnnotationsPubMsg" }
case class SendWhiteboardAnnotationsPubMsg(header: BbbClientMsgHeader, body: SendWhiteboardAnnotationsPubMsgBody) extends StandardMsg
case class SendWhiteboardAnnotationsPubMsgBody(whiteboardId: String, annotations: Array[AnnotationVO], html5InstanceId: String)
case class SendWhiteboardAnnotationsPubMsgBody(whiteboardId: String, annotations: Array[AnnotationVO])
object DeleteWhiteboardAnnotationsPubMsg { val NAME = "DeleteWhiteboardAnnotationsPubMsg" }
case class DeleteWhiteboardAnnotationsPubMsg(header: BbbClientMsgHeader, body: DeleteWhiteboardAnnotationsPubMsgBody) extends StandardMsg

View File

@ -68,7 +68,6 @@ public class ApiParams {
public static final String MEETING_EXPIRE_IF_NO_USER_JOINED_IN_MINUTES = "meetingExpireIfNoUserJoinedInMinutes";
public static final String MEETING_EXPIRE_WHEN_LAST_USER_LEFT_IN_MINUTES = "meetingExpireWhenLastUserLeftInMinutes";
public static final String WELCOME = "welcome";
public static final String HTML5_INSTANCE_ID = "html5InstanceId";
public static final String ROLE = "role";
public static final String GROUPS = "groups";
public static final String DISABLED_FEATURES = "disabledFeatures";

View File

@ -1,97 +0,0 @@
/**
* BigBlueButton open source conferencing system - http://www.bigbluebutton.org/
* <p>
* Copyright (c) 2020 BigBlueButton Inc. and by respective authors (see below).
* <p>
* 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.
* <p>
* 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.
* <p>
* 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.api;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.io.BufferedReader;
import java.io.InputStreamReader;
import java.io.InputStream;
import java.io.OutputStream;
import org.apache.commons.io.IOUtils;
import java.util.ArrayList;
import java.util.List;
import org.bigbluebutton.api.util.HTML5ProcessLine;
public class HTML5LoadBalancingService {
private static Logger log = LoggerFactory.getLogger(HTML5LoadBalancingService.class);
private ArrayList<HTML5ProcessLine> list = new ArrayList<HTML5ProcessLine>();
private final int MAX_NUMBER_OF_HTML5_INSTANCES = 20;
private int lastSelectedInstanceId = 0;
public void init() {
log.info("HTML5LoadBalancingService initialised");
}
// Find nodejs processes associated with processing meeting events
// $ ps -u meteor -o pcpu,cmd= | grep NODEJS_BACKEND_INSTANCE_ID
// 1.1 /usr/lib/bbb-html5/node/bin/node --max-old-space-size=2048 --max_semi_space_size=128 main.js NODEJS_BACKEND_INSTANCE_ID=1
// 1.0 /usr/lib/bbb-html5/node/bin/node --max-old-space-size=2048 --max_semi_space_size=128 main.js NODEJS_BACKEND_INSTANCE_ID=2
public void scanHTML5processes() {
try {
this.list = new ArrayList<HTML5ProcessLine>();
Process p1 = Runtime.getRuntime().exec(new String[]{"ps", "-u", "meteor", "-o", "pcpu,cmd="});
InputStream input1 = p1.getInputStream();
Process p2 = Runtime.getRuntime().exec(new String[]{"grep", HTML5ProcessLine.BBB_HTML5_PROCESS_IDENTIFIER});
OutputStream output = p2.getOutputStream();
IOUtils.copy(input1, output);
output.close(); // signals grep to finish
List<String> result = IOUtils.readLines(p2.getInputStream());
for (String entry : result) {
HTML5ProcessLine line = new HTML5ProcessLine(entry);
list.add(line);
}
} catch (Exception e) {
e.printStackTrace();
}
}
private boolean listItemWithIdExists(int id) {
for (HTML5ProcessLine line : this.list) {
if (line.instanceId == id) {
return true;
}
}
return false;
}
public int findSuitableHTML5ProcessByRoundRobin() {
this.scanHTML5processes();
if (list.isEmpty()) {
log.warn("Did not find any instances of html5 process running");
return 1;
}
for (int i = lastSelectedInstanceId + 1; i <= MAX_NUMBER_OF_HTML5_INSTANCES + lastSelectedInstanceId; i++) {
int k = i % (MAX_NUMBER_OF_HTML5_INSTANCES + 1);
if (this.listItemWithIdExists(k)) {
this.lastSelectedInstanceId = k;
return k;
}
}
return 1;
}
}

View File

@ -452,7 +452,7 @@ public class MeetingService implements MessageListener {
m.getUserInactivityInspectTimerInMinutes(), m.getUserInactivityThresholdInMinutes(),
m.getUserActivitySignResponseDelayInMinutes(), m.getEndWhenNoModerator(), m.getEndWhenNoModeratorDelayInMinutes(),
m.getMuteOnStart(), m.getAllowModsToUnmuteUsers(), m.getAllowModsToEjectCameras(), m.getMeetingKeepEvents(),
m.breakoutRoomsParams, m.lockSettingsParams, m.getHtml5InstanceId(), m.getLoginUrl(), m.getLogoutUrl(), m.getCustomLogoURL(),
m.breakoutRoomsParams, m.lockSettingsParams, m.getLoginUrl(), m.getLogoutUrl(), m.getCustomLogoURL(),
m.getBannerText(), m.getBannerColor(), m.getGroups(), m.getDisabledFeatures(), m.getNotifyRecordingIsOn(),
m.getPresentationUploadExternalDescription(), m.getPresentationUploadExternalUrl(),
m.getOverrideClientSettings());

View File

@ -134,7 +134,6 @@ public class ParamsProcessorUtil {
private Integer maxUserConcurrentAccesses = 0;
private Boolean defaultEndWhenNoModerator = false;
private Integer defaultEndWhenNoModeratorDelayInMinutes = 1;
private Integer defaultHtml5InstanceId = 1;
private String bbbVersion = "";
private Boolean allowRevealOfBBBVersion = false;
@ -731,8 +730,6 @@ public class ParamsProcessorUtil {
String avatarURL = useDefaultAvatar ? defaultAvatarURL : "";
int html5InstanceId = processHtml5InstanceId(params.get(ApiParams.HTML5_INSTANCE_ID));
if(defaultAllowDuplicateExtUserid == false) {
log.warn("[DEPRECATION] use `maxUserConcurrentAccesses=1` instead of `allowDuplicateExtUserid=false`");
maxUserConcurrentAccesses = 1;
@ -768,7 +765,6 @@ public class ParamsProcessorUtil {
.withBreakoutRoomsParams(breakoutParams)
.withLockSettingsParams(lockSettingsParams)
.withMaxUserConcurrentAccesses(maxUserConcurrentAccesses)
.withHTML5InstanceId(html5InstanceId)
.withLearningDashboardCleanupDelayInMinutes(learningDashboardCleanupMins)
.withLearningDashboardAccessToken(learningDashboardAccessToken)
.withGroups(groups)
@ -795,7 +791,6 @@ public class ParamsProcessorUtil {
meeting.setUserInactivityInspectTimerInMinutes(userInactivityInspectTimerInMinutes);
meeting.setUserActivitySignResponseDelayInMinutes(userActivitySignResponseDelayInMinutes);
meeting.setUserInactivityThresholdInMinutes(userInactivityThresholdInMinutes);
// meeting.setHtml5InstanceId(html5InstanceId);
meeting.setEndWhenNoModerator(endWhenNoModerator);
meeting.setEndWhenNoModeratorDelayInMinutes(endWhenNoModeratorDelayInMinutes);
@ -978,17 +973,6 @@ public class ParamsProcessorUtil {
return rec;
}
public int processHtml5InstanceId(String instanceId) {
int html5InstanceId = 1;
try {
html5InstanceId = Integer.parseInt(instanceId);
} catch(Exception ex) {
html5InstanceId = defaultHtml5InstanceId;
}
return html5InstanceId;
}
public int processMaxUser(String maxUsers) {
int mUsers = -1;

View File

@ -117,8 +117,6 @@ public class Meeting {
private String meetingEndedCallbackURL = "";
private Integer html5InstanceId;
private String overrideClientSettings = "";
public Meeting(Meeting.Builder builder) {
@ -174,7 +172,6 @@ public class Meeting {
maxUserConcurrentAccesses = builder.maxUserConcurrentAccesses;
endWhenNoModerator = builder.endWhenNoModerator;
endWhenNoModeratorDelayInMinutes = builder.endWhenNoModeratorDelayInMinutes;
html5InstanceId = builder.html5InstanceId;
groups = builder.groups;
guestUsersWithPositionInWaitingLine = new HashMap<>();
userCustomData = new HashMap<>();
@ -286,10 +283,6 @@ public class Meeting {
return GuestPolicy.DENY;
}
public int getHtml5InstanceId() { return html5InstanceId; }
public void setHtml5InstanceId(int instanceId) { html5InstanceId = instanceId; }
public ArrayList<Group> getGroups() { return groups; }
public void setGroups(ArrayList<Group> groups) { this.groups = groups; }
@ -925,7 +918,6 @@ public class Meeting {
private Integer maxUserConcurrentAccesses;
private Boolean endWhenNoModerator;
private Integer endWhenNoModeratorDelayInMinutes;
private int html5InstanceId;
private ArrayList<Group> groups;
public Builder(String externalId, String internalId, long createTime) {
@ -1139,11 +1131,6 @@ public class Meeting {
return this;
}
public Builder withHTML5InstanceId(int instanceId) {
html5InstanceId = instanceId;
return this;
}
public Builder withGroups(ArrayList<Group> groups) {
this.groups = groups;
return this;

View File

@ -1,48 +0,0 @@
/**
* BigBlueButton open source conferencing system - http://www.bigbluebutton.org/
* <p>
* Copyright (c) 2020 BigBlueButton Inc. and by respective authors (see below).
* <p>
* 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.
* <p>
* 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.
* <p>
* 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.api.util;
public class HTML5ProcessLine {
public int instanceId;
public double percentageCPU;
public static final String BBB_HTML5_PROCESS_IDENTIFIER = "NODEJS_BACKEND_INSTANCE_ID";
public HTML5ProcessLine(String input) {
// $ ps -u meteor -o pcpu,cmd= | grep NODEJS_BACKEND_INSTANCE_ID
// 1.1 /usr/lib/bbb-html5/node/bin/node --max-old-space-size=2048 --max_semi_space_size=128 main.js NODEJS_BACKEND_INSTANCE_ID=1
// 1.0 /usr/lib/bbb-html5/node/bin/node --max-old-space-size=2048 --max_semi_space_size=128 main.js NODEJS_BACKEND_INSTANCE_ID=2
String[] a = input.trim().split(" ");
this.percentageCPU = Double.parseDouble(a[0]);
for (int i = 0; i < a.length; i++) {
if (a[i].toString().indexOf(BBB_HTML5_PROCESS_IDENTIFIER) > -1) {
this.instanceId = Integer.parseInt(a[i].replace(BBB_HTML5_PROCESS_IDENTIFIER + "=", ""));
}
}
}
public String toString() {
return "instanceId:" + this.instanceId + " CPU:" + this.percentageCPU;
}
}

View File

@ -41,7 +41,6 @@ public interface IBbbWebApiGWApp {
Boolean keepEvents,
BreakoutRoomsParams breakoutParams,
LockSettingsParams lockSettingsParams,
Integer html5InstanceId,
String loginUrl,
String logoutUrl,
String customLogoURL,

View File

@ -148,7 +148,6 @@ class BbbWebApiGWApp(
keepEvents: java.lang.Boolean,
breakoutParams: BreakoutRoomsParams,
lockSettingsParams: LockSettingsParams,
html5InstanceId: java.lang.Integer,
loginUrl: String,
logoutUrl: String,
customLogoURL: String,
@ -235,7 +234,6 @@ class BbbWebApiGWApp(
)
val systemProps = SystemProps(
html5InstanceId,
loginUrl match {
case url: String => url
case _ => ""

View File

@ -22,7 +22,6 @@ export default function buildRedisMessage(sessionVariables: Record<string, unkno
const body = {
whiteboardId: input.pageId,
annotations: input.annotations,
html5InstanceId: '', //TODO remove this prop from bbb-common-msg
};
return { eventName, routing, header, body };

View File

@ -9,7 +9,6 @@ export default function MeetingInfo() {
disabledFeatures
durationInSeconds
extId
html5InstanceId
isBreakout
maxPinnedCameras
meetingCameraCap

View File

@ -42,7 +42,7 @@ cd ~/src/bigbluebutton-html5/;
### Hasura Console
http://bbb27.bbbvm.imdt.com.br:8080/console
http://bbb30.bbbvm.imdt.com.br:8085/console
password: bigbluebutton
@ -53,6 +53,6 @@ npm install
npm start
```
https://bbb27.bbbvm.imdt.com.br/graphql-test
https://bbb30.bbbvm.imdt.com.br/graphql-test
- Join in a meeting, copy the param `?sessionToken=xxx` and append it to the URL above

View File

@ -19,7 +19,6 @@ create table "meeting" (
"presentationUploadExternalDescription" text,
"presentationUploadExternalUrl" varchar(500),
"learningDashboardAccessToken" varchar(100),
"html5InstanceId" varchar(100),
"loginUrl" varchar(500),
"logoutUrl" varchar(500),
"customLogoUrl" varchar(500),

View File

@ -1,7 +1,7 @@
version: 3
endpoint: http://localhost:8080
endpoint: http://127.0.0.1:8085
admin_secret: bigbluebutton
metadata_directory: metadata
actions:
kind: synchronous
handler_webhook_baseurl: http://localhost:3000
handler_webhook_baseurl: http://127.0.0.1:3000

View File

@ -79,4 +79,4 @@ hasura metadata apply --skip-update-check
echo ""
echo ""
echo "Bbb-graphql-server Installed!"
echo "http://$(hostname -f):8080/console"
echo "http://$(hostname -f):8085/console"

View File

@ -171,7 +171,6 @@ select_permissions:
- endedByUserName
- endedReasonCode
- extId
- html5InstanceId
- isBreakout
- loginUrl
- logoutUrl

View File

@ -19,7 +19,7 @@
database_url:
connection_parameters:
database: bbb_graphql
host: localhost
host: 127.0.0.1
password: bbb_graphql
port: 5432
username: postgres

View File

@ -49,7 +49,7 @@ echo "Starting Hasura"
sudo systemctl start bbb-graphql-server
#Check if Hasura is ready before applying metadata
HASURA_PORT=8080
HASURA_PORT=8085
while ! netstat -tuln | grep ":$HASURA_PORT " > /dev/null; do
echo "Waiting for Hasura's port ($HASURA_PORT) to be ready..."
sleep 1

View File

@ -1 +1 @@
BIGBLUEBUTTON_RELEASE=3.0.0-alpha.5
BIGBLUEBUTTON_RELEASE=3.0.0-alpha.6

View File

@ -380,20 +380,6 @@ display_bigbluebutton_status () {
if [ -f /usr/lib/systemd/system/bbb-html5.service ]; then
units="$units mongod bbb-html5"
source /usr/share/meteor/bundle/bbb-html5-with-roles.conf
if [ -f /etc/bigbluebutton/bbb-html5-with-roles.conf ]; then
source /etc/bigbluebutton/bbb-html5-with-roles.conf
fi
for ((i = 1 ; i <= $NUMBER_OF_BACKEND_NODEJS_PROCESSES; i++)); do
units="$units bbb-html5-backend@$i"
done
for ((i = 1; i <= $NUMBER_OF_FRONTEND_NODEJS_PROCESSES; i++)); do
units="$units bbb-html5-frontend@$i"
done
fi
if [ -f /usr/lib/systemd/system/bbb-graphql-actions.service ]; then

View File

@ -13,15 +13,18 @@ import StartupDataFetch from '/imports/ui/components/connection-manager/startup-
import GraphqlToMiniMongoAdapterManager from '/imports/ui/components/components-data/graphqlToMiniMongoAdapterManager/component';
const STARTUP_CRASH_METADATA = { logCode: 'app_startup_crash', logMessage: 'Possible startup crash' };
const APP_CRASH_METADATA = { logCode: 'app_crash', logMessage: 'Possible app crash' };
const Main: React.FC = () => {
// Meteor.disconnect();
return (
<StartupDataFetch>
<ErrorBoundary Fallback={ErrorScreen}>
<ErrorBoundary Fallback={ErrorScreen} logMetadata={STARTUP_CRASH_METADATA}>
<LoadingScreenHOC>
<IntlLoaderContainer>
{/* from there the error messages are located */}
<LocatedErrorBoundary Fallback={ErrorScreen}>
<LocatedErrorBoundary Fallback={ErrorScreen} logMetadata={APP_CRASH_METADATA}>
<ConnectionManager>
<PresenceManager>
<GraphqlToMiniMongoAdapterManager>

View File

@ -16,7 +16,7 @@ fi
sudo rm -rf "$UPPER_DESTINATION_DIR"
sudo mkdir -p "$UPPER_DESTINATION_DIR"
sudo chown -R meteor:meteor "$UPPER_DESTINATION_DIR"
sudo chown -R root:root "$UPPER_DESTINATION_DIR"
# the next 5 lines may be temporarily commented out if you are sure you are not tweaking the required node_modules after first use of the script. This will save a minute or two during the run of the script
if [ -d "node_modules" ]; then
@ -28,10 +28,9 @@ meteor npm ci --production
sudo chmod 777 /usr/share/meteor
METEOR_DISABLE_OPTIMISTIC_CACHING=1 meteor build $UPPER_DESTINATION_DIR --architecture os.linux.x86_64 --allow-superuser --directory
sudo chown -R meteor:meteor "$UPPER_DESTINATION_DIR"/
sudo chown -R root:root "$UPPER_DESTINATION_DIR"/
echo 'stage3'
cd "$DESTINATION_DIR"/programs/server/ || exit
sudo chmod -R 777 .
meteor npm i
@ -44,63 +43,15 @@ sudo cp $LOCAL_PACKAGING_DIR/mongod_start_pre.sh "$DESTINATION_DIR"/mongod_start
echo "writing $DESTINATION_DIR/mongo-ramdisk.conf"
sudo cp $LOCAL_PACKAGING_DIR/mongo-ramdisk.conf "$DESTINATION_DIR"/mongo-ramdisk.conf
echo "writing $DESTINATION_DIR/bbb-html5-with-roles.conf"
sudo tee "$DESTINATION_DIR/bbb-html5-with-roles.conf" >/dev/null <<HERE
# Default = 2; Min = 1; Max = 4
# On powerful systems with high number of meetings you can set values up to 4 to accelerate handling of events
NUMBER_OF_BACKEND_NODEJS_PROCESSES=2
# Default = 2; Min = 0; Max = 8
# If 0 is set, bbb-html5 will handle both backend and frontend roles in one process (default until Feb 2021)
# Set a number between 1 and 4 times the value of NUMBER_OF_BACKEND_NODEJS_PROCESSES where higher number helps with meetings
# stretching the recommended number of users in BigBlueButton
NUMBER_OF_FRONTEND_NODEJS_PROCESSES=2
HERE
echo "writing $DESTINATION_DIR/systemd_start.sh"
sudo cp $LOCAL_PACKAGING_DIR/systemd_start.sh "$DESTINATION_DIR"/systemd_start.sh
echo "writing $DESTINATION_DIR/systemd_start_frontend.sh"
sudo cp $LOCAL_PACKAGING_DIR/systemd_start_frontend.sh "$DESTINATION_DIR"/systemd_start_frontend.sh
sudo chown -R meteor:meteor "$UPPER_DESTINATION_DIR"/
sudo chown -R root:root "$UPPER_DESTINATION_DIR"/
sudo chmod +x "$DESTINATION_DIR"/mongod_start_pre.sh
sudo chmod +x "$DESTINATION_DIR"/systemd_start.sh
sudo chmod +x "$DESTINATION_DIR"/systemd_start_frontend.sh
sudo cp $LOCAL_PACKAGING_DIR/workers-start.sh "$DESTINATION_DIR"/workers-start.sh
sudo chmod +x "$DESTINATION_DIR"/workers-start.sh
echo "writing $SERVICE_FILES_DIR/bbb-html5-frontend@.service"
sudo cp $LOCAL_PACKAGING_DIR/bbb-html5-frontend@.service "$SERVICE_FILES_DIR"/bbb-html5-frontend@.service
echo "writing $SERVICE_FILES_DIR/bbb-html5-backend@.service"
sudo cp $LOCAL_PACKAGING_DIR/bbb-html5-backend@.service "$SERVICE_FILES_DIR"/bbb-html5-backend@.service
echo "writing $SERVICE_FILES_DIR/bbb-html5.service"
sudo cp $LOCAL_PACKAGING_DIR/bbb-html5.service "$SERVICE_FILES_DIR"/bbb-html5.service
sudo systemctl daemon-reload
echo 'before stopping bbb-html5:'
ps -ef | grep node-
sudo ss -netlp | grep -i node
echo 'before stopping bbb-html5:'
echo '_____________'
sudo systemctl stop bbb-html5
sleep 5s
echo 'after stopping bbb-html5:'
ps -ef | grep node-
sudo ss -netlp | grep -i node
echo 'after stopping bbb-html5:'
echo '_____________'
echo 'starting bbb-html5'
sudo systemctl start bbb-html5
sleep 10s
echo 'after:...'
ps -ef | grep node-
sudo ss -netlp | grep -i node
echo 'after:'
echo '_____________'
sudo systemctl restart bbb-html5

View File

@ -20,7 +20,7 @@ interface IntlAdapterProps {
const IntlAdapter: React.FC<IntlAdapterProps> = ({
children,
}) => {
const [currentLocale] = useCurrentLocale();
const [currentLocale, setCurrentLocale] = useCurrentLocale();
const intl = useIntl();
const loadingContextInfo = useContext(LoadingContext);
const sendUiDataToPlugins = () => {
@ -59,7 +59,16 @@ const IntlAdapter: React.FC<IntlAdapterProps> = ({
`${UI_DATA_LISTENER_SUBSCRIBED}-${PluginSdk.IntlLocaleUiDataNames.CURRENT_LOCALE}`,
sendUiDataToPlugins,
);
setUp();
// @ts-ignore - JS code
const { locale } = Settings.application;
if (
typeof locale === 'string'
&& locale !== currentLocale
) {
setCurrentLocale(locale);
} else {
setUp();
}
return () => {
window.removeEventListener(
`${UI_DATA_LISTENER_SUBSCRIBED}-${PluginSdk.IntlLocaleUiDataNames.CURRENT_LOCALE}`,

View File

@ -115,7 +115,6 @@ export interface Meeting {
disabledFeatures: Array<string>;
durationInSeconds: number;
extId: string;
html5InstanceId: string | null;
isBreakout: boolean;
learningDashboardAccessToken: string;
maxPinnedCameras: number;

View File

@ -38,6 +38,7 @@ const propTypes = {
isConnecting: PropTypes.bool.isRequired,
isConnected: PropTypes.bool.isRequired,
isUsingAudio: PropTypes.bool.isRequired,
isListenOnly: PropTypes.bool.isRequired,
inputDeviceId: PropTypes.string,
outputDeviceId: PropTypes.string,
formattedDialNum: PropTypes.string.isRequired,
@ -64,6 +65,7 @@ const propTypes = {
MIC_ERROR: PropTypes.number.isRequired,
NO_SSL: PropTypes.number.isRequired,
}).isRequired,
getTroubleshootingLink: PropTypes.func.isRequired,
};
const defaultProps = {
@ -144,7 +146,7 @@ const AudioModal = (props) => {
const [content, setContent] = useState(null);
const [hasError, setHasError] = useState(false);
const [disableActions, setDisableActions] = useState(false);
const [errCode, setErrCode] = useState(null);
const [errorInfo, setErrorInfo] = useState(null);
const [autoplayChecked, setAutoplayChecked] = useState(false);
const [setAway] = useMutation(SET_AWAY);
const voiceToggle = useToggleVoice();
@ -155,6 +157,7 @@ const AudioModal = (props) => {
listenOnlyMode,
audioLocked,
isUsingAudio,
isListenOnly,
autoplayBlocked,
closeModal,
isEchoTest,
@ -186,6 +189,7 @@ const AudioModal = (props) => {
isOpen,
priority,
setIsOpen,
getTroubleshootingLink,
} = props;
const prevAutoplayBlocked = usePreviousValue(autoplayBlocked);
@ -196,17 +200,24 @@ const AudioModal = (props) => {
}
}, [autoplayBlocked]);
const handleJoinMicrophoneError = (err) => {
const { type } = err;
const handleJoinAudioError = (err) => {
const { type, errCode, errMessage } = err;
switch (type) {
case 'MEDIA_ERROR':
setContent('help');
setErrCode(0);
setErrorInfo({
errCode,
errMessage,
});
setDisableActions(false);
break;
case 'CONNECTION_ERROR':
default:
setErrCode(0);
setErrorInfo({
errCode,
errMessage: type,
});
setDisableActions(false);
break;
}
@ -226,7 +237,10 @@ const AudioModal = (props) => {
if (noSSL) {
setContent('help');
setErrCode(MIC_ERROR.NO_SSL);
setErrorInfo({
errCode: MIC_ERROR.NO_SSL,
errMessage: 'NoSSL',
});
return null;
}
@ -241,7 +255,7 @@ const AudioModal = (props) => {
setContent('echoTest');
setDisableActions(true);
}).catch((err) => {
handleJoinMicrophoneError(err);
handleJoinAudioError(err);
});
};
@ -260,6 +274,7 @@ const AudioModal = (props) => {
const handleRetryGoToEchoTest = () => {
setHasError(false);
setContent(null);
setErrorInfo(null);
return handleGoToEchoTest();
};
@ -278,14 +293,14 @@ const AudioModal = (props) => {
if (disableActions && isConnecting) return null;
setDisableActions(true);
setHasError(false);
setErrorInfo(null);
return joinListenOnly().then(() => {
setDisableActions(false);
disableAwayMode();
}).catch((err) => {
if (err.type === 'MEDIA_ERROR') {
setContent('help');
}
handleJoinAudioError(err);
});
};
@ -294,11 +309,12 @@ const AudioModal = (props) => {
setHasError(false);
setDisableActions(true);
setErrorInfo(null);
joinMicrophone().then(() => {
setDisableActions(false);
}).catch((err) => {
handleJoinMicrophoneError(err);
handleJoinAudioError(err);
});
};
@ -389,9 +405,15 @@ const AudioModal = (props) => {
? handleRetryGoToEchoTest
: handleJoinLocalEcho;
const handleGUMFailure = () => {
const handleGUMFailure = (error) => {
const code = error?.name === 'NotAllowedError'
? AudioError.MIC_ERROR.NO_PERMISSION
: 0;
setContent('help');
setErrCode(0);
setErrorInfo({
errCode: code,
errMessage: error?.name || 'NotAllowedError',
});
setDisableActions(false);
};
@ -419,13 +441,16 @@ const AudioModal = (props) => {
const renderHelp = () => {
const audioErr = {
...AudioError,
code: errCode,
code: errorInfo?.errCode,
message: errorInfo?.errMessage,
};
return (
<Help
handleBack={handleGoToAudioOptions}
audioErr={audioErr}
isListenOnly={isListenOnly}
troubleshootingLink={getTroubleshootingLink(errorInfo?.errCode)}
/>
);
};

View File

@ -16,6 +16,7 @@ import {
} from './service';
import Storage from '/imports/ui/services/storage/session';
import Service from '../service';
import AudioModalService from '/imports/ui/components/audio/audio-modal/service';
const AudioModalContainer = (props) => <AudioModal {...props} />;
@ -98,5 +99,7 @@ export default lockContextContainer(withTracker(({ userLocks, setIsOpen }) => {
notify: Service.notify,
isRTL,
AudioError,
getTroubleshootingLink: AudioModalService.getTroubleshootingLink,
isListenOnly: Service.isListenOnly(),
});
})(AudioModalContainer));

View File

@ -3,6 +3,7 @@ import Storage from '/imports/ui/services/storage/session';
const CLIENT_DID_USER_SELECTED_MICROPHONE_KEY = 'clientUserSelectedMicrophone';
const CLIENT_DID_USER_SELECTED_LISTEN_ONLY_KEY = 'clientUserSelectedListenOnly';
const TROUBLESHOOTING_LINKS = Meteor.settings.public.media.audioTroubleshootingLinks;
export const setUserSelectedMicrophone = (value) => (
Storage.setItem(CLIENT_DID_USER_SELECTED_MICROPHONE_KEY, !!value)
@ -47,19 +48,15 @@ export const joinListenOnly = () => {
Storage.setItem(CLIENT_DID_USER_SELECTED_MICROPHONE_KEY, false);
Storage.setItem(CLIENT_DID_USER_SELECTED_LISTEN_ONLY_KEY, true);
const call = new Promise((resolve) => {
Service.joinListenOnly().then(() => {
// Autoplay block wasn't triggered. Close the modal. If autoplay was
// blocked, that'll be handled in the modal component when then
// prop transitions to a state where it was handled OR the user opts
// to close the modal.
if (!Service.autoplayBlocked()) {
document.dispatchEvent(new Event("CLOSE_MODAL_AUDIO"));
}
resolve();
});
});
return call.catch((error) => {
return Service.joinListenOnly().then(() => {
// Autoplay block wasn't triggered. Close the modal. If autoplay was
// blocked, that'll be handled in the modal component when then
// prop transitions to a state where it was handled OR the user opts
// to close the modal.
if (!Service.autoplayBlocked()) {
document.dispatchEvent(new Event("CLOSE_MODAL_AUDIO"));
}
}).catch((error) => {
throw error;
});
};
@ -78,6 +75,11 @@ export const closeModal = (callback) => {
callback();
};
const getTroubleshootingLink = (errorCode) => {
if (TROUBLESHOOTING_LINKS) return TROUBLESHOOTING_LINKS[errorCode] || TROUBLESHOOTING_LINKS[0];
return null;
};
export default {
joinMicrophone,
closeModal,
@ -85,4 +87,5 @@ export default {
leaveEchoTest,
didUserSelectedMicrophone,
didUserSelectedListenOnly,
getTroubleshootingLink,
};

View File

@ -1,63 +1,184 @@
import React, { Component } from 'react';
import { injectIntl, defineMessages } from 'react-intl';
import PropTypes from 'prop-types';
import Styled from './styles';
const propTypes = {
intl: PropTypes.shape({
formatMessage: PropTypes.func.isRequired,
}).isRequired,
isListenOnly: PropTypes.bool.isRequired,
audioErr: PropTypes.shape({
code: PropTypes.number,
message: PropTypes.string,
MIC_ERROR: PropTypes.shape({
NO_SSL: PropTypes.number,
MAC_OS_BLOCK: PropTypes.number,
NO_PERMISSION: PropTypes.number,
}),
}).isRequired,
handleBack: PropTypes.func.isRequired,
troubleshootingLink: PropTypes.string,
};
const defaultProps = {
troubleshootingLink: '',
};
const intlMessages = defineMessages({
descriptionHelp: {
id: 'app.audioModal.helpText',
description: 'Text decription for the audio help',
helpSubtitleMic: {
id: 'app.audioModal.helpSubtitleMic',
description: 'Text description for the audio help subtitle (microphones)',
},
backLabel: {
id: 'app.audio.backLabel',
description: 'audio settings back button label',
helpSubtitleGeneric: {
id: 'app.audioModal.helpSubtitleGeneric',
description: 'Text description for the audio help subtitle (generic)',
},
helpPermissionStep1: {
id: 'app.audioModal.helpPermissionStep1',
description: 'Text description for the audio permission help step 1',
},
helpPermissionStep2: {
id: 'app.audioModal.helpPermissionStep2',
description: 'Text description for the audio permission help step 2',
},
helpPermissionStep3: {
id: 'app.audioModal.helpPermissionStep3',
description: 'Text description for the audio permission help step 3',
},
retryLabel: {
id: 'app.audio.audioSettings.retryLabel',
description: 'audio settings retry button label',
},
noSSL: {
id: 'app.audioModal.help.noSSL',
description: 'Text decription for domain not using https',
description: 'Text description for domain not using https',
},
macNotAllowed: {
id: 'app.audioModal.help.macNotAllowed',
description: 'Text decription for mac needed to enable OS setting',
description: 'Text description for mac needed to enable OS setting',
},
helpTroubleshoot: {
id: 'app.audioModal.help.troubleshoot',
description: 'Text description for help troubleshoot',
},
unknownError: {
id: 'app.audioModal.help.unknownError',
description: 'Text description for unknown error',
},
errorCode: {
id: 'app.audioModal.help.errorCode',
description: 'Text description for error code',
},
});
class Help extends Component {
getSubtitle() {
const { intl, isListenOnly } = this.props;
return !isListenOnly
? intl.formatMessage(intlMessages.helpSubtitleMic)
: intl.formatMessage(intlMessages.helpSubtitleGeneric);
}
renderNoSSL() {
const { intl } = this.props;
return (
<Styled.Text>
{intl.formatMessage(intlMessages.noSSL)}
</Styled.Text>
);
}
renderMacNotAllowed() {
const { intl } = this.props;
return (
<Styled.Text>
{intl.formatMessage(intlMessages.macNotAllowed)}
</Styled.Text>
);
}
renderPermissionHelp() {
const { intl } = this.props;
return (
<>
<Styled.Text>
{this.getSubtitle()}
</Styled.Text>
<Styled.PermissionHelpSteps>
<li>{intl.formatMessage(intlMessages.helpPermissionStep1)}</li>
<li>{intl.formatMessage(intlMessages.helpPermissionStep2)}</li>
<li>{intl.formatMessage(intlMessages.helpPermissionStep3)}</li>
</Styled.PermissionHelpSteps>
</>
);
}
renderGenericErrorHelp() {
const { intl, audioErr } = this.props;
const { code, message } = audioErr;
return (
<>
<Styled.Text>
{this.getSubtitle()}
</Styled.Text>
<Styled.Text>
{intl.formatMessage(intlMessages.unknownError)}
</Styled.Text>
<Styled.UnknownError>
{intl.formatMessage(intlMessages.errorCode, { 0: code, 1: message || 'UnknownError' })}
</Styled.UnknownError>
</>
);
}
renderHelpMessage() {
const { audioErr } = this.props;
const { MIC_ERROR } = audioErr;
switch (audioErr.code) {
case MIC_ERROR.NO_SSL:
return this.renderNoSSL();
case MIC_ERROR.MAC_OS_BLOCK:
return this.renderMacNotAllowed();
case MIC_ERROR.NO_PERMISSION:
return this.renderPermissionHelp();
default:
return this.renderGenericErrorHelp();
}
}
render() {
const {
intl,
handleBack,
audioErr,
troubleshootingLink,
} = this.props;
const { code, MIC_ERROR } = audioErr;
let helpMessage = null;
switch (code) {
case MIC_ERROR.NO_SSL:
helpMessage = intl.formatMessage(intlMessages.noSSL);
break;
case MIC_ERROR.MAC_OS_BLOCK:
helpMessage = intl.formatMessage(intlMessages.macNotAllowed);
break;
case MIC_ERROR.NO_PERMISSION:
default:
helpMessage = intl.formatMessage(intlMessages.descriptionHelp);
break;
}
return (
<Styled.Help>
<Styled.Text>
{ helpMessage }
</Styled.Text>
{this.renderHelpMessage()}
{ troubleshootingLink && (
<Styled.Text>
<Styled.TroubleshootLink
href={troubleshootingLink}
target="_blank"
rel="noopener noreferrer"
>
{intl.formatMessage(intlMessages.helpTroubleshoot)}
</Styled.TroubleshootLink>
</Styled.Text>
)}
<Styled.EnterAudio>
<Styled.BackButton
label={intl.formatMessage(intlMessages.backLabel)}
<Styled.RetryButton
label={intl.formatMessage(intlMessages.retryLabel)}
size="md"
color="primary"
onClick={handleBack}
ghost
/>
</Styled.EnterAudio>
</Styled.Help>
@ -65,4 +186,7 @@ class Help extends Component {
}
}
Help.propTypes = propTypes;
Help.defaultProps = defaultProps;
export default injectIntl(Help);

View File

@ -1,29 +1,36 @@
import styled from 'styled-components';
import Button from '/imports/ui/components/common/button/component';
import { smallOnly } from '/imports/ui/stylesheets/styled-components/breakpoints';
import { jumboPaddingY, smPaddingY } from '/imports/ui/stylesheets/styled-components/general';
import {
fontSizeSmaller,
} from '/imports/ui/stylesheets/styled-components/typography';
import {
colorLink,
} from '/imports/ui/stylesheets/styled-components/palette';
const Help = styled.span`
display: flex;
flex-flow: column;
height: 10rem;
min-height: 10rem;
`;
const Text = styled.div`
text-align: center;
justify-content: center;
margin-top: auto;
margin-bottom: auto;
`;
const EnterAudio = styled.div`
margin-top: 1.5rem;
display: flex;
justify-content: flex-end;
margin-top: ${jumboPaddingY};
`;
const BackButton = styled(Button)`
const RetryButton = styled(Button)`
margin-right: 0.5rem;
margin-left: inherit;
border: none;
[dir="rtl"] & {
margin-right: inherit;
@ -41,9 +48,32 @@ const BackButton = styled(Button)`
}
`;
const TroubleshootLink = styled.a`
color: ${colorLink};
`;
const UnknownError = styled.label`
font-size: ${fontSizeSmaller};
justify-content: center;
text-align: center;
margin-top: ${smPaddingY};
margin-bottom: ${smPaddingY};
`;
const PermissionHelpSteps = styled.ul`
text-align: left;
justify-content: center;
li {
margin-bottom: ${smPaddingY};
}
`;
export default {
Help,
Text,
EnterAudio,
BackButton,
RetryButton,
TroubleshootLink,
UnknownError,
PermissionHelpSteps,
};

View File

@ -6,11 +6,19 @@ const propTypes = {
children: PropTypes.element.isRequired,
Fallback: PropTypes.element,
errorMessage: PropTypes.string,
logMetadata: PropTypes.shape({
logCode: PropTypes.string,
logMessage: PropTypes.string,
}),
};
const defaultProps = {
Fallback: null,
errorMessage: 'Something went wrong',
logMetadata: {
logCode: 'Error_Boundary_wrapper',
logMessage: 'generic error boundary logger',
},
};
class ErrorBoundary extends Component {
@ -30,13 +38,17 @@ class ErrorBoundary extends Component {
}
componentDidUpdate() {
const { code, error, errorInfo } = this.state;
const log = code === '403' ? 'warn' : 'error';
const { error, errorInfo } = this.state;
const { logMetadata: { logCode, logMessage } } = this.props;
if (error || errorInfo) {
logger[log]({
logCode: 'Error_Boundary_wrapper',
extraInfo: { error, errorInfo },
}, 'generic error boundary logger');
logger.error({
logCode,
extraInfo: {
errorMessage: error?.message,
errorStack: error?.stack,
errorInfo,
},
}, logMessage);
}
}

View File

@ -194,7 +194,7 @@ const UserListParticipants: React.FC<UserListParticipantsProps> = ({
const UserListParticipantsContainer: React.FC = () => {
const [offset, setOffset] = React.useState(0);
const [limit, setLimit] = React.useState(0);
const [limit, setLimit] = React.useState(50);
const {
data: meetingData,

View File

@ -233,7 +233,7 @@ export const useMyPageSize = () => {
return size ?? 0;
};
export const useIsPaginationEnabled = (paginationEnabled: boolean) => paginationEnabled && useMyPageSize() > 0;
export const useIsPaginationEnabled = (paginationEnabled: boolean) => useMyPageSize() > 0 && paginationEnabled;
export const useStreams = () => {
const videoStreams = useReactiveVar(streams);

View File

@ -224,7 +224,7 @@ const Whiteboard = React.memo(function Whiteboard(props) {
if (!isPresenterRef.current) {
if (
!hasWBAccessRef.current ||
(hasWBAccessRef.current && !tlEditorRef.current.editingShape)
(hasWBAccessRef.current && !tlEditorRef.current?.getEditingShape())
) {
event.preventDefault();
event.stopPropagation();
@ -867,41 +867,39 @@ const Whiteboard = React.memo(function Whiteboard(props) {
);
let adjustedZoom = baseZoom * (currentZoom / HUNDRED_PERCENT);
if (isPresenter) {
setTimeout(() => {
const container = document.querySelector(
'[data-test="presentationContainer"]'
const container = document.querySelector(
'[data-test="presentationContainer"]'
);
const innerWrapper = document.getElementById(
"presentationInnerWrapper"
);
const containerWidth = container ? container.offsetWidth : 0;
const innerWrapperWidth = innerWrapper
? innerWrapper.offsetWidth
: 0;
const widthGap = Math.max(containerWidth - innerWrapperWidth, 0);
const camera = tlEditorRef.current.getCamera();
let adjustedZoom;
if (widthGap > 0) {
adjustedZoom = calculateZoomWithGapValue(
currentPresentationPage.scaledWidth,
currentPresentationPage.scaledHeight,
false,
widthGap
);
const innerWrapper = document.getElementById(
"presentationInnerWrapper"
);
const containerWidth = container ? container.offsetWidth : 0;
const innerWrapperWidth = innerWrapper
? innerWrapper.offsetWidth
: 0;
const widthGap = Math.max(containerWidth - innerWrapperWidth, 0);
const camera = tlEditorRef.current.getCamera();
let adjustedZoom;
if (widthGap > 0) {
adjustedZoom = calculateZoomWithGapValue(
currentPresentationPage.scaledWidth,
currentPresentationPage.scaledHeight,
false,
widthGap
);
adjustedZoom *= currentZoom / HUNDRED_PERCENT;
} else {
adjustedZoom = baseZoom * (currentZoom / HUNDRED_PERCENT);
}
adjustedZoom *= currentZoom / HUNDRED_PERCENT;
} else {
adjustedZoom = baseZoom * (currentZoom / HUNDRED_PERCENT);
}
const zoomToApply =
widthGap > 0
? adjustedZoom
: baseZoom * (currentZoom / HUNDRED_PERCENT);
const zoomToApply =
widthGap > 0
? adjustedZoom
: baseZoom * (currentZoom / HUNDRED_PERCENT);
setCamera(zoomToApply, camera.x, camera.y);
}, 50);
setCamera(zoomToApply, camera.x, camera.y);
} else {
// Viewer logic
const effectiveZoom = calculateEffectiveZoom(
@ -1090,6 +1088,7 @@ const Whiteboard = React.memo(function Whiteboard(props) {
? presentationAreaHeight - 40
: presentationAreaHeight;
let effectiveZoom;
let baseZoom;
if (isPresenter) {
// For presenters, use the full area minus any UI components like toolbars
@ -1101,7 +1100,7 @@ const Whiteboard = React.memo(function Whiteboard(props) {
);
} else {
// For viewers
const effectiveZoom = calculateEffectiveZoom(
effectiveZoom = calculateEffectiveZoom(
initialViewBoxWidthRef.current,
currentPresentationPageRef.current.scaledViewBoxWidth
);

View File

@ -97,7 +97,6 @@ const MEETING_SUBSCRIPTION = gql`
record
sequence
}
html5InstanceId
voiceSettings {
dialNumber
muteOnStart

View File

@ -1,7 +1,9 @@
const MIC_ERROR = {
UNKNOWN: 0,
NO_SSL: 9,
MAC_OS_BLOCK: 8,
NO_PERMISSION: 7,
DEVICE_NOT_FOUND: 6,
};
export default { MIC_ERROR };

View File

@ -7,7 +7,6 @@ import SFUAudioBridge from '/imports/api/audio/client/bridge/sfu-audio-bridge';
import logger from '/imports/startup/client/logger';
import { notify } from '/imports/ui/services/notification';
import playAndRetry from '/imports/utils/mediaElementPlayRetry';
import iosWebviewAudioPolyfills from '/imports/utils/ios-webview-audio-polyfills';
import { monitorAudioConnection } from '/imports/utils/stats';
import browserInfo from '/imports/utils/browserInfo';
import getFromMeetingSettings from '/imports/ui/services/meeting-settings';
@ -23,13 +22,12 @@ import {
} from '/imports/api/audio/client/bridge/service';
import MediaStreamUtils from '/imports/utils/media-stream-utils';
import { makeVar } from '@apollo/client';
import AudioErrors from '/imports/ui/services/audio-manager/error-codes';
const STATS = window.meetingClientSettings.public.stats;
const MEDIA = window.meetingClientSettings.public.media;
const MEDIA_TAG = MEDIA.mediaTag;
const ECHO_TEST_NUMBER = MEDIA.echoTestNumber;
const MAX_LISTEN_ONLY_RETRIES = 1;
const LISTEN_ONLY_CALL_TIMEOUT_MS = MEDIA.listenOnlyCallTimeout || 25000;
const EXPERIMENTAL_USE_KMS_TRICKLE_ICE_FOR_MICROPHONE =
window.meetingClientSettings.public.app.experimentalUseKmsTrickleIceForMicrophone;
@ -351,127 +349,79 @@ class AudioManager {
}
joinAudio(callOptions, callStateCallback) {
return this.bridge.joinAudio(callOptions, callStateCallback.bind(this)).catch((error) => {
const { name } = error;
return this.bridge
.joinAudio(callOptions, callStateCallback.bind(this))
.catch((error) => {
const { name, message } = error;
const errorPayload = {
type: 'MEDIA_ERROR',
errMessage: message || 'MEDIA_ERROR',
errCode: AudioErrors.MIC_ERROR.UNKNOWN,
};
if (!name) {
throw error;
}
switch (name) {
case 'NotAllowedError':
logger.error(
{
logCode: 'audiomanager_error_getting_device',
switch (name) {
case 'NotAllowedError':
errorPayload.errCode = AudioErrors.MIC_ERROR.NO_PERMISSION;
logger.error({
logCode: 'audiomanager_error_getting_device',
extraInfo: {
errorName: error.name,
errorMessage: error.message,
},
},
`Error getting microphone - {${error.name}: ${error.message}}`,
);
break;
case 'NotFoundError':
errorPayload.errCode = AudioErrors.MIC_ERROR.DEVICE_NOT_FOUND;
logger.error({
logCode: 'audiomanager_error_device_not_found',
extraInfo: {
errorName: error.name,
errorMessage: error.message,
},
},
`Error getting microphone - {${error.name}: ${error.message}}`,
);
break;
default:
logger.error({
logCode: 'audiomanager_error_unknown',
extraInfo: {
errorName: error.name,
errorMessage: error.message,
},
},
`Error getting microphone - {${error.name}: ${error.message}}`
);
break;
case 'NotFoundError':
logger.error(
{
logCode: 'audiomanager_error_device_not_found',
extraInfo: {
errorName: error.name,
errorMessage: error.message,
},
},
`Error getting microphone - {${error.name}: ${error.message}}`
);
break;
}, `Error enabling audio - {${name}: ${message}}`);
break;
}
default:
break;
}
this.isConnecting = false;
this.isWaitingPermissions = false;
this.isConnecting = false;
this.isWaitingPermissions = false;
throw {
type: 'MEDIA_ERROR',
};
});
throw errorPayload;
});
}
async joinListenOnly(r = 0) {
async joinListenOnly() {
this.audioJoinStartTime = new Date();
this.logAudioJoinTime = false;
let retries = r;
this.isListenOnly = true;
this.isEchoTest = false;
const callOptions = {
isListenOnly: true,
extension: null,
};
// Call polyfills for webrtc client if navigator is "iOS Webview"
const userAgent = window.navigator.userAgent.toLocaleLowerCase();
if (
(userAgent.indexOf('iphone') > -1 || userAgent.indexOf('ipad') > -1) &&
userAgent.indexOf('safari') === -1
) {
iosWebviewAudioPolyfills();
}
// We need this until we upgrade to SIP 9x. See #4690
const listenOnlyCallTimeoutErr = 'SIP_CALL_TIMEOUT';
const iceGatheringTimeout = new Promise((resolve, reject) => {
setTimeout(reject, LISTEN_ONLY_CALL_TIMEOUT_MS, listenOnlyCallTimeoutErr);
});
const handleListenOnlyError = (err) => {
if (iceGatheringTimeout) {
clearTimeout(iceGatheringTimeout);
}
const errorReason =
(typeof err === 'string' ? err : undefined) || err.errorReason || err.errorMessage;
logger.error(
{
logCode: 'audiomanager_listenonly_error',
extraInfo: {
errorReason,
audioBridge: this.bridge?.bridgeName,
retries,
},
},
`Listen only error - ${errorReason} - bridge: ${this.bridge?.bridgeName}`
);
};
logger.info(
{
logCode: 'audiomanager_join_listenonly',
extraInfo: { logType: 'user_action' },
},
'user requested to connect to audio conference as listen only'
);
logger.info({
logCode: 'audiomanager_join_listenonly',
extraInfo: { logType: 'user_action' },
}, 'user requested to connect to audio conference as listen only');
window.addEventListener('audioPlayFailed', this.handlePlayElementFailed);
return this.onAudioJoining()
.then(() =>
Promise.race([
this.bridge.joinAudio(callOptions, this.callStateCallback.bind(this)),
iceGatheringTimeout,
])
)
.catch(async (err) => {
handleListenOnlyError(err);
if (retries < MAX_LISTEN_ONLY_RETRIES) {
retries += 1;
this.joinListenOnly(retries);
}
return null;
return this.onAudioJoining.bind(this)()
.then(() => {
const callOptions = {
isListenOnly: true,
extension: null,
};
return this.joinAudio(callOptions, this.callStateCallback.bind(this));
});
}

View File

@ -1,100 +0,0 @@
const iosWebviewAudioPolyfills = function () {
function shimRemoteStreamsAPI(window) {
if (!('getRemoteStreams' in window.RTCPeerConnection.prototype)) {
window.RTCPeerConnection.prototype.getRemoteStreams = function () {
return this._remoteStreams ? this._remoteStreams : [];
};
}
if (!('onaddstream' in window.RTCPeerConnection.prototype)) {
Object.defineProperty(window.RTCPeerConnection.prototype, 'onaddstream', {
get: function get() {
return this._onaddstream;
},
set: function set(f) {
const _this3 = this;
if (this._onaddstream) {
this.removeEventListener('addstream', this._onaddstream);
this.removeEventListener('track', this._onaddstreampoly);
}
this.addEventListener('addstream', this._onaddstream = f);
this.addEventListener('track', this._onaddstreampoly = function (e) {
e.streams.forEach((stream) => {
if (!_this3._remoteStreams) {
_this3._remoteStreams = [];
}
if (_this3._remoteStreams.includes(stream)) {
return;
}
_this3._remoteStreams.push(stream);
const event = new Event('addstream');
event.stream = stream;
_this3.dispatchEvent(event);
});
});
},
});
const origSetRemoteDescription = window.RTCPeerConnection.prototype.setRemoteDescription;
window.RTCPeerConnection.prototype.setRemoteDescription = function () {
const pc = this;
if (!this._onaddstreampoly) {
this.addEventListener('track', this._onaddstreampoly = function (e) {
e.streams.forEach((stream) => {
if (!pc._remoteStreams) {
pc._remoteStreams = [];
}
if (pc._remoteStreams.indexOf(stream) >= 0) {
return;
}
pc._remoteStreams.push(stream);
const event = new Event('addstream');
event.stream = stream;
pc.dispatchEvent(event);
});
});
}
return origSetRemoteDescription.apply(pc, arguments);
};
}
}
function shimCallbacksAPI(window) {
const prototype = window.RTCPeerConnection.prototype;
const createOffer = prototype.createOffer;
const setLocalDescription = prototype.setLocalDescription;
const setRemoteDescription = prototype.setRemoteDescription;
prototype.createOffer = function (successCallback, failureCallback) {
const options = arguments.length >= 2 ? arguments[2] : arguments[0];
const promise = createOffer.apply(this, [options]);
if (!failureCallback) {
return promise;
}
promise.then(successCallback, failureCallback);
return Promise.resolve();
};
prototype.setLocalDescription = function withCallback(description, successCallback, failureCallback) {
const promise = setLocalDescription.apply(this, [description]);
if (!failureCallback) {
return promise;
}
promise.then(successCallback, failureCallback);
return Promise.resolve();
};
prototype.setRemoteDescription = function withCallback(description, successCallback, failureCallback) {
const promise = setRemoteDescription.apply(this, [description]);
if (!failureCallback) {
return promise;
}
promise.then(successCallback, failureCallback);
return Promise.resolve();
};
}
shimCallbacksAPI(window);
shimRemoteStreamsAPI(window);
};
export default iosWebviewAudioPolyfills;

View File

@ -9432,4 +9432,4 @@
}
}
}
}
}

View File

@ -887,6 +887,15 @@ public:
# audio: high
# webcam: medium
# screenshare: medium
#
# audioTroubleshootingLinks: links to help users troubleshoot audio issues
# If no link is provided, the audio troubleshooting button will not be shown.
# Index is the error code:
# - 7: permission denied error code
# - 0: unknown error
#audioTroubleshootingLinks:
# 7: 'https://link.bigbluebutton.org/perm'
# 0: 'https://link.bigbluebutton.org/unk'
stats:
enabled: true
interval: 10000

View File

@ -738,8 +738,15 @@
"app.audioModal.no.arialabel": "Echo is inaudible",
"app.audioModal.echoTestTitle": "This is a private echo test. Speak a few words. Did you hear audio?",
"app.audioModal.settingsTitle": "Change your audio settings",
"app.audioModal.helpTitle": "There was an issue with your media devices",
"app.audioModal.helpText": "Did you give permission for access to your microphone? Note that a dialog should appear when you try to join audio, asking for your media device permissions, please accept that in order to join the audio conference. If that is not the case, try changing your microphone permissions in your browser's settings.",
"app.audioModal.helpTitle": "There was an issue with your audio devices",
"app.audioModal.helpSubtitleMic": "We couldn't enable your microphone",
"app.audioModal.helpSubtitleGeneric": "We're having trouble establishing an audio connection",
"app.audioModal.helpPermissionStep1": "When joining a call, accept all requests if prompted to use your microphone.",
"app.audioModal.helpPermissionStep2": "Check browser and device settings to ensure microphone access is allowed.",
"app.audioModal.helpPermissionStep3": "Refresh the page and try again.",
"app.audioModal.help.troubleshoot": "Still having issues? Click here for help.",
"app.audioModal.help.unknownError": "Review your browser and system settings. Restart your browser and try again.",
"app.audioModal.help.errorCode": "Error code: {0} - {1}",
"app.audioModal.help.noSSL": "This page is unsecured. For microphone access to be allowed the page must be served over HTTPS. Please contact the server administrator.",
"app.audioModal.help.macNotAllowed": "It looks like your Mac System Preferences are blocking access to your microphone. Open System Preferences > Security & Privacy > Privacy > Microphone, and verify that the browser you're using is checked.",
"app.audioModal.audioDialTitle": "Join using your phone",

View File

@ -374,10 +374,6 @@ exports.askModerator = 'button[data-test="askModerator"]';
exports.alwaysAccept = 'button[data-test="alwaysAccept"]';
exports.alwaysDeny = 'button[data-test="alwaysDeny"]';
exports.deniedMessageElement = 'p[class="error-message"]';
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"]';
@ -409,13 +405,6 @@ exports.hideViewersAnnotation = 'input[data-test="hideViewersAnnotation"]';
exports.hideViewersCursor = 'input[data-test="hideViewersCursor"]';
exports.whiteboardCursorIndicator = 'div[data-test="whiteboardCursorIndicator"]';
// Closed Captions
exports.writeClosedCaptions = 'li[data-test="writeClosedCaptions"]';
exports.startWritingClosedCaptions = 'button[data-test="startWritingClosedCaptions"]';
exports.startViewingClosedCaptionsBtn = 'button[data-test="startViewingClosedCaptionsBtn"]';
exports.startViewingClosedCaptions = 'button[data-test="startViewingClosedCaptions"]';
exports.liveCaptions = 'div[data-test="liveCaptions"]';
// Locales
exports.locales = ['af', 'ar', 'az', 'bg-BG', 'bn', 'ca', 'cs-CZ', 'da', 'de',
'dv', 'el-GR', 'en', 'eo', 'es', 'es-419', 'es-ES', 'es-MX', 'et', 'eu',

View File

@ -1,4 +1,4 @@
const { expect, default: test } = require('@playwright/test');
const { expect } = require('@playwright/test');
const playwright = require("playwright");
const Page = require('../core/page');
const e = require('../core/elements');
@ -207,38 +207,6 @@ class MultiUsers {
await checkTextContent(content, dataToCheck);
}
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 pinningWebcams() {
await this.modPage.shareWebcam();
await this.modPage2.shareWebcam();
@ -310,27 +278,6 @@ class MultiUsers {
await this.modPage.hasElement(e.multiUsersWhiteboardOn);
}
async writeClosedCaptions() {
await this.modPage.waitForSelector(e.whiteboard);
await this.modPage2.waitForSelector(e.whiteboard);
await this.modPage.waitAndClick(e.manageUsers);
await this.modPage.waitAndClick(e.writeClosedCaptions);
await this.modPage.waitAndClick(e.startWritingClosedCaptions);
await this.modPage.waitAndClick(e.startViewingClosedCaptionsBtn);
await this.modPage2.waitAndClick(e.startViewingClosedCaptionsBtn);
await this.modPage.waitAndClick(e.startViewingClosedCaptions);
await this.modPage2.waitAndClick(e.startViewingClosedCaptions);
const notesLocator = getNotesLocator(this.modPage);
await notesLocator.type(e.message);
await this.modPage.hasText(e.liveCaptions, e.message);
await this.modPage2.hasText(e.liveCaptions, e.message);
}
async removeUser() {
await this.modPage.waitAndClick(e.userListItem);
await this.modPage.waitAndClick(e.removeUser);
@ -370,27 +317,6 @@ class MultiUsers {
await this.modPage2.hasText(e.userBannedMessage1, /removed/);
}
}
async writeClosedCaptions() {
await this.modPage.waitForSelector(e.whiteboard);
await this.modPage2.waitForSelector(e.whiteboard);
await this.modPage.waitAndClick(e.manageUsers);
await this.modPage.waitAndClick(e.writeClosedCaptions);
await this.modPage.waitAndClick(e.startWritingClosedCaptions);
await this.modPage.waitAndClick(e.startViewingClosedCaptionsBtn);
await this.modPage2.waitAndClick(e.startViewingClosedCaptionsBtn);
await this.modPage.waitAndClick(e.startViewingClosedCaptions);
await this.modPage2.waitAndClick(e.startViewingClosedCaptions);
const notesLocator = getNotesLocator(this.modPage);
await notesLocator.type(e.message);
await this.modPage.hasText(e.liveCaptions, e.message);
await this.modPage2.hasText(e.liveCaptions, e.message);
}
}
exports.MultiUsers = MultiUsers;

View File

@ -231,13 +231,6 @@ test.describe.parallel('User', () => {
await multiusers.saveUserNames(testInfo);
});
// following test is not expected to work, the feature will be fully implemented as a plugin only
test.skip('Select random user', async ({ browser, context, page }) => {
const multiusers = new MultiUsers(browser, context);
await multiusers.initModPage(page);
await multiusers.selectRandomUser();
});
test('Mute all users', async ({ browser, context, page }) => {
const multiusers = new MultiUsers(browser, context);
await multiusers.initModPage(page, false);
@ -253,13 +246,6 @@ test.describe.parallel('User', () => {
await multiusers.initUserPage(false);
await multiusers.muteAllUsersExceptPresenter();
});
test('Write closed captions', async ({ browser, context, page }) => {
const multiusers = new MultiUsers(browser, context);
await multiusers.initModPage(page, true);
await multiusers.initModPage2(true);
await multiusers.writeClosedCaptions();
});
});
test.describe.parallel('Mobile devices', () => {

View File

@ -302,7 +302,7 @@ defaultHTML5ClientUrl=${bigbluebutton.web.serverURL}/html5client/join
# Graphql websocket url (it's necessary to change for cluster setup)
# Using `serverURL` as default, so `https` will be automatically replaced by `wss`
graphqlWebsocketUrl=${bigbluebutton.web.serverURL}/v1/graphql
graphqlWebsocketUrl=${bigbluebutton.web.serverURL}/graphql
# This parameter defines the duration (in minutes) to wait before removing user sessions after a meeting has ended.
# During this delay, users can still access information indicating that the "Meeting has ended".

View File

@ -118,8 +118,6 @@ with BigBlueButton; if not, see <http://www.gnu.org/licenses/>.
<property name="learningDashboardFilesDir" value="${learningDashboardFilesDir}"/>
</bean>
<bean id="html5LoadBalancingService" class="org.bigbluebutton.api.HTML5LoadBalancingService" init-method="init" />
<bean id="validationService" class="org.bigbluebutton.api.service.ValidationService">
<property name="securitySalt" value="${securitySalt}"/>
<property name="supportedChecksumAlgorithms" value="${supportedChecksumAlgorithms}"/>

View File

@ -62,7 +62,6 @@ class ApiController {
ParamsProcessorUtil paramsProcessorUtil
PresentationUrlDownloadService presDownloadService
StunTurnService stunTurnService
HTML5LoadBalancingService html5LoadBalancingService
ResponseBuilder responseBuilder = initResponseBuilder()
ValidationService validationService
@ -184,8 +183,6 @@ class ApiController {
// Still no unique voiceBridge found? Let createMeeting handle it.
}
params.html5InstanceId = html5LoadBalancingService.findSuitableHTML5ProcessByRoundRobin().toString()
Meeting newMeeting = paramsProcessorUtil.processCreateParams(params)
String requestBody = request.inputStream == null ? null : request.inputStream.text

View File

@ -2,7 +2,7 @@ BBB_GRAPHQL_MIDDLEWARE_LISTEN_IP=127.0.0.1
BBB_GRAPHQL_MIDDLEWARE_LISTEN_PORT=8378
BBB_GRAPHQL_MIDDLEWARE_REDIS_ADDRESS=127.0.0.1:6379
BBB_GRAPHQL_MIDDLEWARE_REDIS_PASSWORD=
BBB_GRAPHQL_MIDDLEWARE_HASURA_WS=ws://127.0.0.1:8080/v1/graphql
BBB_GRAPHQL_MIDDLEWARE_HASURA_WS=ws://127.0.0.1:8085/v1/graphql
BBB_GRAPHQL_MIDDLEWARE_MAX_CONN_PER_SECOND=10
BBB_GRAPHQL_MIDDLEWARE_AUTH_HOOK_URL=http://127.0.0.1:8090/bigbluebutton/connection/checkGraphqlAuthorization
BBB_GRAPHQL_MIDDLEWARE_GRAPHQL_ACTIONS_URL=http://127.0.0.1:8093

View File

@ -6,12 +6,12 @@ location /graphql-test {
}
# Websocket connection
location /v1/graphql {
location /graphql {
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "Upgrade";
proxy_set_header Host $host;
#proxy_pass http://127.0.0.1:8080; #Hasura
#proxy_pass http://127.0.0.1:8085; #Hasura
proxy_pass http://127.0.0.1:8378; #Graphql Middleware
}
@ -20,7 +20,7 @@ location /api/rest {
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "Upgrade";
proxy_set_header Host $host;
proxy_pass http://127.0.0.1:8080; #Hasura
proxy_pass http://127.0.0.1:8085; #Hasura
}
#Set cache system for client settings
@ -40,7 +40,7 @@ location ~ ^/api/rest/(clientStartupSettings|clientSettings)/ {
proxy_set_header x-session-token $session_token;
proxy_cache client_settings_cache;
proxy_cache_key "$request_uri|$meeting_id";
proxy_cache_key "$uri|$meeting_id";
proxy_cache_use_stale updating;
proxy_cache_valid 24h;
proxy_cache_lock on;
@ -50,5 +50,5 @@ location ~ ^/api/rest/(clientStartupSettings|clientSettings)/ {
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "Upgrade";
proxy_set_header Host $host;
proxy_pass http://127.0.0.1:8080; #Hasura
proxy_pass http://127.0.0.1:8085; #Hasura
}

View File

@ -39,13 +39,19 @@ case "$1" in
echo "Postgresql configured"
echo "Set a random password to Hasura in case its using the default 'bigbluebutton'"
HASURA_RANDOM_ADM_PASSWORD=$(openssl rand -base64 32 | sed 's/=//g' | sed 's/+//g' | sed 's/\///g')
sed -i "s/HASURA_GRAPHQL_ADMIN_SECRET=bigbluebutton/HASURA_GRAPHQL_ADMIN_SECRET=$HASURA_RANDOM_ADM_PASSWORD/g" /etc/default/bbb-graphql-server
HASURA_ADM_PASSWORD=$(grep '^HASURA_GRAPHQL_ADMIN_SECRET=' /etc/default/bbb-graphql-server | cut -d '=' -f 2)
sed -i "s/admin_secret: bigbluebutton/admin_secret: $HASURA_ADM_PASSWORD/g" /usr/share/bbb-graphql-server/config.yaml
if [ ! -f /.dockerenv ]; then
systemctl enable bbb-graphql-server.service
systemctl daemon-reload
startService bbb-graphql-server || echo "bbb-graphql-server service could not be registered or started"
#Check if Hasura is ready before applying metadata
HASURA_PORT=8080
HASURA_PORT=8085
while ! netstat -tuln | grep ":$HASURA_PORT " > /dev/null; do
echo "Waiting for Hasura's port ($HASURA_PORT) to be ready..."
sleep 1

View File

@ -1,5 +1,5 @@
HASURA_GRAPHQL_DATABASE_URL=postgres://postgres:bbb_graphql@localhost:5432/hasura_app
#HASURA_GRAPHQL_METADATA_DATABASE_URL=postgres://postgres:bbb_graphql@localhost:5432/hasura_app
HASURA_GRAPHQL_DATABASE_URL=postgres://postgres:bbb_graphql@127.0.0.1:5432/hasura_app
#HASURA_GRAPHQL_METADATA_DATABASE_URL=postgres://postgres:bbb_graphql@127.0.0.1:5432/hasura_app
#HASURA_GRAPHQL_NO_OF_RETRIES
HASURA_GRAPHQL_LOG_LEVEL=warn
HASURA_GRAPHQL_ENABLE_CONSOLE=false
@ -8,6 +8,7 @@ HASURA_GRAPHQL_LIVE_QUERIES_MULTIPLEXED_BATCH_SIZE=1000
HASURA_GRAPHQL_STREAMING_QUERIES_MULTIPLEXED_REFETCH_INTERVAL=100
HASURA_GRAPHQL_STREAMING_QUERIES_MULTIPLEXED_BATCH_SIZE=1000
#HASURA_GRAPHQL_PG_CONNECTIONS=50
HASURA_GRAPHQL_SERVER_PORT=8085
HASURA_GRAPHQL_ADMIN_SECRET=bigbluebutton
HASURA_GRAPHQL_ENABLE_TELEMETRY=false
HASURA_GRAPHQL_AUTH_HOOK=http://127.0.0.1:8090/bigbluebutton/connection/checkGraphqlAuthorization

View File

@ -1,28 +0,0 @@
[Unit]
Description=BigBlueButton HTML5 service, backend instance %i
Requires=bbb-html5.service
Before=bbb-html5.service
BindsTo=bbb-html5.service
StartLimitBurst=4
StartLimitInterval=70sec
StartLimitAction=none
[Service]
PermissionsStartOnly=true
#Type=simple
Type=idle
EnvironmentFile=/usr/share/meteor/bundle/bbb-html5-with-roles.conf
ExecStart=/usr/share/meteor/bundle/systemd_start.sh %i $BACKEND_NODEJS_ROLE
WorkingDirectory=/usr/share/meteor/bundle
StandardOutput=syslog
StandardError=syslog
TimeoutStartSec=10
Restart=on-failure
RestartSec=10
User=meteor
Group=meteor
CPUSchedulingPolicy=fifo
Nice=18
[Install]
WantedBy=bbb-html5.service

View File

@ -1,29 +0,0 @@
[Unit]
Description=BigBlueButton HTML5 service, frontend instance %i
Requires=bbb-html5.service
Before=bbb-html5.service
BindsTo=bbb-html5.service
StartLimitBurst=4
StartLimitInterval=70sec
StartLimitAction=none
[Service]
PermissionsStartOnly=true
#Type=simple
Type=idle
EnvironmentFile=/usr/share/meteor/bundle/bbb-html5-with-roles.conf
ExecStart=/usr/share/meteor/bundle/systemd_start_frontend.sh %i
WorkingDirectory=/usr/share/meteor/bundle
StandardOutput=syslog
StandardError=syslog
TimeoutStartSec=10
Restart=on-failure
RestartSec=10
User=meteor
Group=meteor
CPUSchedulingPolicy=fifo
Nice=18
[Install]
WantedBy=bbb-html5.service

View File

@ -1,13 +0,0 @@
upstream poolhtml5servers {
zone poolhtml5servers 32k;
least_conn;
server 127.0.0.1:4100 fail_timeout=5s max_fails=3;
server 127.0.0.1:4101 fail_timeout=5s max_fails=3;
server 127.0.0.1:4102 fail_timeout=5s max_fails=3;
server 127.0.0.1:4103 fail_timeout=5s max_fails=3;
server 127.0.0.1:4104 fail_timeout=5s max_fails=3;
server 127.0.0.1:4105 fail_timeout=5s max_fails=3;
server 127.0.0.1:4106 fail_timeout=5s max_fails=3;
server 127.0.0.1:4107 fail_timeout=5s max_fails=3;
}

View File

@ -1,9 +0,0 @@
# Default = 2; Min = 1; Max = 4
# On powerful systems with high number of meetings you can set values up to 4 to accelerate handling of events
NUMBER_OF_BACKEND_NODEJS_PROCESSES=2
# Default = 2; Min = 0; Max = 8
# If 0 is set, bbb-html5 will handle both backend and frontend roles in one process (default until Feb 2021)
# Set a number between 1 and 4 times the value of NUMBER_OF_BACKEND_NODEJS_PROCESSES where higher number helps with meetings
# stretching the recommended number of users in BigBlueButton
NUMBER_OF_FRONTEND_NODEJS_PROCESSES=2

View File

@ -1,4 +0,0 @@
INSTANCE_MIN=1
INSTANCE_MAX=4
DESIRED_INSTANCE_COUNT=1

View File

@ -1,6 +1,5 @@
location @html5client {
# proxy_pass http://127.0.0.1:4100; # use for development
proxy_pass http://poolhtml5servers; # use for production
proxy_pass http://127.0.0.1:4100;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "Upgrade";
@ -15,13 +14,13 @@ location /html5client/__meteor__/dynamic-import/fetch {
proxy_cache_lock on;
add_header X-Cached $upstream_cache_status;
proxy_pass http://poolhtml5servers;
proxy_pass http://127.0.0.1:4100;
proxy_http_version 1.1;
}
#location /html5client/join {
# alias /usr/share/meteor/bundle/programs/web.browser;
#}
#location /html5client/join {
# alias /usr/share/meteor/bundle/programs/web.browser;
#}
location /html5client/locales {
alias /usr/share/meteor/bundle/programs/web.browser/app/locales;

View File

@ -5,16 +5,18 @@ After=redis-server.service mongod.service disable-transparent-huge-pages.service
PartOf=bigbluebutton.target
[Service]
Type=oneshot
Type=idle
ExecStart=/usr/share/meteor/bundle/workers-start.sh
WorkingDirectory=/usr/share/meteor
# WorkingDirectory=/usr/share/meteor/bundle
RemainAfterExit=yes
# StandardOutput=syslog
# StandardError=syslog
# User=meteor
# Group=meteor
User=root
WorkingDirectory=/usr/share/meteor/bundle
StandardOutput=syslog
StandardError=syslog
TimeoutStartSec=10
Restart=on-failure
RestartSec=10
User=meteor
Group=meteor
CPUSchedulingPolicy=fifo
Nice=18
[Install]
WantedBy=multi-user.target bigbluebutton.target

View File

@ -10,5 +10,19 @@ case "$1" in
rm -r /usr/share/meteor/bundle/programs/server/node_modules
fi
# Remove remnants from old architecture prior to BBB 3.0.x-alpha.6
if [ -f /usr/lib/systemd/system/bbb-html5-backend@.service ]; then
rm /usr/lib/systemd/system/bbb-html5-backend@.service
fi
if [ -f /usr/lib/systemd/system/bbb-html5-frontend@.service ]; then
rm /usr/lib/systemd/system/bbb-html5-frontend@.service
fi
if [ -f /etc/nginx/conf.d/bbb-html5-loadbalancer.conf ]; then
rm /etc/nginx/conf.d/bbb-html5-loadbalancer.conf
fi
if [ -f /etc/bigbluebutton/bbb-html5-with-roles.conf ]; then
echo "BigBlueButton 3.0+ does not support configurations in /etc/bigbluebutton/bbb-html5-with-roles.conf"
fi
;;
esac

View File

@ -25,7 +25,6 @@ mkdir -p staging/usr/share/bigbluebutton/nginx
cp bbb-html5.nginx staging/usr/share/bigbluebutton/nginx
mkdir -p staging/etc/nginx/conf.d
cp bbb-html5-loadbalancer.conf staging/etc/nginx/conf.d
cp bbb-html5-conn-limit.conf staging/etc/nginx/conf.d
cp bbb-html5-meteor-assets-cache.conf staging/etc/nginx/conf.d
@ -78,17 +77,9 @@ if [ ! -f staging/usr/share/meteor/bundle/programs/web.browser/app/locales/index
find staging/usr/share/meteor/bundle/programs/web.browser/app/locales -maxdepth 1 -type f -name "*.json" -exec basename {} \; | awk 'BEGIN{printf "["}{printf "\"%s\", ", $0}END{print "]"}' | sed 's/, ]/]/' > staging/usr/share/meteor/bundle/programs/web.browser/app/locales/index.json
fi
cp systemd_start.sh staging/usr/share/meteor/bundle
chmod +x staging/usr/share/meteor/bundle/systemd_start.sh
cp systemd_start_frontend.sh staging/usr/share/meteor/bundle
chmod +x staging/usr/share/meteor/bundle/systemd_start_frontend.sh
cp workers-start.sh staging/usr/share/meteor/bundle
chmod +x staging/usr/share/meteor/bundle/workers-start.sh
cp bbb-html5-with-roles.conf staging/usr/share/meteor/bundle
cp mongod_start_pre.sh staging/usr/share/meteor/bundle
chmod +x staging/usr/share/meteor/bundle/mongod_start_pre.sh
@ -99,10 +90,6 @@ mkdir -p staging/usr/lib/systemd/system
cp bbb-html5.service staging/usr/lib/systemd/system
cp disable-transparent-huge-pages.service staging/usr/lib/systemd/system
cp bbb-html5-backend@.service staging/usr/lib/systemd/system
cp bbb-html5-frontend@.service staging/usr/lib/systemd/system
mkdir -p staging/usr/share
# replace v=VERSION with build number in head and css files

View File

@ -1,30 +0,0 @@
#!/bin/bash -e
#Allow to run outside of directory
cd $(dirname $0)
if [ -z $1 ]
then
INSTANCE_ID=1
else
INSTANCE_ID=$1
fi
PORT=$(echo "3999+$INSTANCE_ID" | bc)
echo "instanceId = $INSTANCE_ID and port = $PORT and role is backend (in backend file)"
export INSTANCE_ID=$INSTANCE_ID
export BBB_HTML5_ROLE=backend
# this might be already set by a systemd unit override in case this node is run
# behind a load balancer proxy node
if [[ -z $ROOT_URL ]] ; then
export ROOT_URL=http://127.0.0.1/html5client
fi
export MONGO_OPLOG_URL=mongodb://127.0.1.1/local
export MONGO_URL=mongodb://127.0.1.1/meteor
export NODE_ENV=production
export SERVER_WEBSOCKET_COMPRESSION=0
export BIND_IP=127.0.0.1
PORT=$PORT /usr/lib/bbb-html5/node/bin/node --max-old-space-size=2048 --max_semi_space_size=128 main.js NODEJS_BACKEND_INSTANCE_ID=$INSTANCE_ID

View File

@ -1,29 +0,0 @@
#!/bin/bash -e
#Allow to run outside of directory
cd $(dirname $0)
if [ -z $1 ]
then
INSTANCE_ID=1
else
INSTANCE_ID=$1
fi
PORT=$(echo "4099+$INSTANCE_ID" | bc)
echo "instanceId = $INSTANCE_ID and port = $PORT and role is frontend (in frontend file)"
export INSTANCE_ID=$INSTANCE_ID
export BBB_HTML5_ROLE=frontend
# this might be already set by a systemd unit override in case this node is run
# behind a load balancer proxy node
if [[ -z $ROOT_URL ]] ; then
export ROOT_URL=http://127.0.0.1/html5client
fi
export MONGO_OPLOG_URL=mongodb://127.0.1.1/local
export MONGO_URL=mongodb://127.0.1.1/meteor
export NODE_ENV=production
export SERVER_WEBSOCKET_COMPRESSION='{"level":5, "maxWindowBits":13, "memLevel":7, "requestMaxWindowBits":13}'
export BIND_IP=127.0.0.1
PORT=$PORT /usr/lib/bbb-html5/node/bin/node --max-old-space-size=2048 --max_semi_space_size=128 main.js

View File

@ -23,37 +23,19 @@ done;
echo "I'm the master!"
#Allow to run outside of directory
cd $(dirname $0)
# Start parallel nodejs processes for bbb-html5. Number varies on restrictions file bbb-html5-with-roles.conf
PORT=4100
source /usr/share/meteor/bundle/bbb-html5-with-roles.conf
if [ -f /etc/bigbluebutton/bbb-html5-with-roles.conf ]; then
source /etc/bigbluebutton/bbb-html5-with-roles.conf
# this might be already set by a systemd unit override in case this node is run
# behind a load balancer proxy node
if [[ -z $ROOT_URL ]] ; then
export ROOT_URL=http://127.0.0.1/html5client
fi
MIN_NUMBER_OF_BACKEND_PROCESSES=1
MAX_NUMBER_OF_BACKEND_PROCESSES=4
MIN_NUMBER_OF_FRONTEND_PROCESSES=0 # 0 means each nodejs process handles both front and backend roles
MAX_NUMBER_OF_FRONTEND_PROCESSES=8
# Start backend nodejs processes
if ((NUMBER_OF_BACKEND_NODEJS_PROCESSES >= MIN_NUMBER_OF_BACKEND_PROCESSES && NUMBER_OF_BACKEND_NODEJS_PROCESSES <= MAX_NUMBER_OF_BACKEND_PROCESSES)); then
for ((i = 1 ; i <= NUMBER_OF_BACKEND_NODEJS_PROCESSES ; i++)); do
systemctl start bbb-html5-backend@$i
done
fi
# Start frontend nodejs processes
if ((NUMBER_OF_FRONTEND_NODEJS_PROCESSES >= MIN_NUMBER_OF_FRONTEND_PROCESSES && NUMBER_OF_FRONTEND_NODEJS_PROCESSES <= MAX_NUMBER_OF_FRONTEND_PROCESSES)); then
if ((NUMBER_OF_FRONTEND_NODEJS_PROCESSES == 0)); then
echo 'Need to modify bbb-html5.nginx to ensure backend IPs are used'
fi
for ((i = 1 ; i <= NUMBER_OF_FRONTEND_NODEJS_PROCESSES ; i++)); do
systemctl start bbb-html5-frontend@$i
done
fi
export MONGO_OPLOG_URL=mongodb://127.0.1.1/local
export MONGO_URL=mongodb://127.0.1.1/meteor
export NODE_ENV=production
export SERVER_WEBSOCKET_COMPRESSION='{"level":5, "maxWindowBits":13, "memLevel":7, "requestMaxWindowBits":13}'
export BIND_IP=127.0.0.1
PORT=$PORT /usr/lib/bbb-html5/node/bin/node --max-old-space-size=2048 --max_semi_space_size=128 main.js

View File

@ -12,7 +12,16 @@ $ npm ci # install docusaurus and dependencies (based on the package-lock.json
$ npx docusaurus start # start local dev server
```
By default `docusaurus.config.js` contains instructions for the building of a few extra branches. You may want to drop all but the current branch. For example:
By default `docusaurus.config.js` contains instructions for the building of a few extra branches. However, on in a development environment you may run into the following error from docusaurus:
![Development setup for docusaurus possible error](/docusaurus_start_error_001.png)
`Error: The docs folder does not exist for version "2.6". A docs folder is expected to be found at versioned_docs/version-2.6.`
In this case you may want to drop all but the current branch. For example:
```
diff --git a/docs/docusaurus.config.js b/docs/docusaurus.config.js
@ -53,8 +62,9 @@ updates when using `npx docusaurus start`).
If you only want to build the docs you can run:
```
$ npm clear # ensure cached content is not interfering with your changes
$ npx docusaurus clear # ensure cached content is not interfering with your changes
$ npx docusaurus build
$ npm run serve
```
This command generates static content into the `build` directory

View File

@ -126,10 +126,9 @@ public:
Create (or edit if it already exists) these unit file overrides:
* `/usr/lib/systemd/system/bbb-html5-frontend@.service`
* `/usr/lib/systemd/system/bbb-html5-backend@.service`
* `/usr/lib/systemd/system/bbb-html5.service`
Each should have the following content:
It should have the following content:
```
[Service]
@ -137,14 +136,6 @@ Environment=ROOT_URL=https://127.0.0.1/bbb-01/html5client
Environment=DDP_DEFAULT_CONNECTION_URL=https://bbb-01.example.com/bbb-01/html5client
```
Change the nginx `$bbb_loadbalancer_node` variable to the name of the load
balancer node in `/usr/share/bigbluebutton/nginx/loadbalancer.nginx` to allow CORS
requests:
```
set $bbb_loadbalancer_node https://bbb-proxy.example.com;
```
Prepend the mount point of bbb-html5 in all location sections except for the
`location @html5client` section in `/usr/share/bigbluebutton/nginx/bbb-html5.nginx`:

View File

@ -25,7 +25,6 @@ Starting with BigBlueButton 2.3 many of the configuration files have local overr
| /usr/share/bbb-apps-akka/conf/application.conf | /etc/bigbluebutton/bbb-apps-akka.conf | |
| /usr/share/bbb-fsesl-akka/conf/application.conf | /etc/bigbluebutton/bbb-fsesl-akka.conf | |
| /usr/share/meteor/bundle/programs/server/assets/app/config/settings.yml | /etc/bigbluebutton/bbb-html5.yml | Arrays are merged by replacement (as of 2.4-rc-5) |
| /usr/share/meteor/bundle/bbb-html5-with-roles.conf | /etc/bigbluebutton/bbb-html5-with-roles.conf | |
| /usr/share/bbb-web/WEB-INF/classes/spring/turn-stun-servers.xml | /etc/bigbluebutton/turn-stun-servers.xml | Replaces the original file |
| /usr/local/bigbluebutton/bbb-webrtc-sfu/config/default.yml | /etc/bigbluebutton/bbb-webrtc-sfu/production.yml | Arrays are merged by replacement |
| /usr/local/bigbluebutton/bbb-pads/config/settings.json | /etc/bigbluebutton/bbb-pads.json | Arrays are merged by replacement |
@ -81,17 +80,15 @@ public:
#### Log monitoring for server logs (bbb-html5)
Since BigBlueButton 2.3 we run multiple nodejs processes in production mode, so tailing logs is slightly different from `journalctl -f bbb-html5.service` which was used in 2.2. Rather than listing all the services ( `bbb-html5-backend@1.service bbb-html5-backend@2.service bbb-html5-frontend@1.service bbb-html5-frontend@2.service bbb-html5-frontend@3.service bbb-html5-frontend@4.service ...` ) you can use the wildcard operator `*`. Notice the different process id for each bbb-html5-\* service. Also notice `systemd_start_frontend.sh` signifying a log from a frontend process vs `systemd_start.sh` - backend process.
In BigBlueButton 3.0 we modified the architecture to shift the load away from the old frontend and backend bbb-html5 pools of services. Logs for the new services can be foud via:
```
# journalctl -f -u bbb-html5-*
-- Logs begin at Mon 2021-03-15 12:13:05 UTC. --
Mar 15 15:14:18 demo2 systemd_start_frontend.sh[3881]: debug: Redis: SendCursorPositionEvtMsg completed sync
Mar 15 15:14:18 demo2 systemd_start_frontend.sh[3891]: debug: Redis: SendCursorPositionEvtMsg completed sync
Mar 15 15:14:18 demo2 systemd_start_frontend.sh[3888]: debug: Publishing Polls {"meetingId":"37d0fb4f4617b3c97948d717435f9e1cf6998477-1615821214341","userId":"w_el87iar97iwa"}
...
Mar 15 15:30:18 demo2 systemd_start.sh[3869]: debug: Redis: UpdateBreakoutUsersEvtMsg completed sync
```
`journalctl -f -u bbb-html5.service`
Akka-apps is responsible for most of the logic, so key info can be obtained via
`journalctl -f -u bbb-apps-akka.service`
`SYSTEMD_LESS=FRXMK journalctl -u bbb-graphql-middleware.service -f` can also be useful.
#### Logs sent directly from the client

View File

@ -253,10 +253,6 @@ bbb-apps-akka ——————————————————————
bbb-fsesl-akka ———————————————————————► [✔ - active]
mongod ———————————————————————————————► [✔ - active]
bbb-html5 ————————————————————————————► [✔ - active]
bbb-html5-backend@1 ——————————————————► [✔ - active]
bbb-html5-backend@2 ——————————————————► [✔ - active]
bbb-html5-frontend@1 —————————————————► [✔ - active]
bbb-html5-frontend@2 —————————————————► [✔ - active]
bbb-graphql-actions ——————————————————► [✔ - active]
bbb-graphql-middleware ———————————————► [✔ - active]
bbb-graphql-server ———————————————————► [✔ - active]

View File

@ -48,7 +48,9 @@ Administrators will appreciate that we now allow passing of custom client settin
#### Major strides in replacing Meteor
For years we have discussed internally the topic of replacing MeteorJS with other technologies in order to improve scalability, performance, etc. In the last year we have introduced several different new components which are to replace Meteor. The work is underway, it will span into BigBlueButton 3.0, 3.1, possibly 3.2 too.
These new components are: `bbb-graphql-server`, `bbb-graphql-middleware`, `bbb-graphql-actions`, database Postgres, GraphQL server Hasura. During the transition period, `bbb-html5-backend` and `bbb-html5-frontend` are still present, however, lots of what they used to do is now being carried out by the new components.
These new components are: `bbb-graphql-server`, `bbb-graphql-middleware`, `bbb-graphql-actions`, database Postgres, GraphQL server Hasura.
Note: The services `bbb-html5-backend` and `bbb-html5-frontend` have been removed and `bbb-html5` modified heavily as a result of the change in architecture.
### Experimental

View File

@ -43,8 +43,6 @@ const config = {
],
],
plugins: [require.resolve("@cmfcmf/docusaurus-search-local")],
themeConfig:
/** @type {import('@docusaurus/preset-classic').ThemeConfig} */
@ -170,6 +168,23 @@ const config = {
darkTheme: darkCodeTheme,
},
}),
themes: [
// ... Your other themes.
[
require.resolve("@easyops-cn/docusaurus-search-local"),
/** @type {import("@easyops-cn/docusaurus-search-local").PluginOptions} */
({
// ... Your options.
// `hashed` is recommended as long-term-cache of index file is possible.
hashed: true,
docsRouteBasePath: "/",
// For Docs using Chinese, The `language` is recommended to set to:
// ```
// language: ["en", "zh"],
// ```
}),
],
],
};
module.exports = config;

Binary file not shown.

After

Width:  |  Height:  |  Size: 65 KiB

2248
docs/package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@ -10,10 +10,10 @@
"serve": "npx docusaurus serve"
},
"dependencies": {
"@cmfcmf/docusaurus-search-local": "^1.1.0",
"@docusaurus/core": "3.0.1",
"@docusaurus/plugin-client-redirects": "^3.0.1",
"@docusaurus/preset-classic": "3.0.1",
"@docusaurus/core": "^3.3.2",
"@docusaurus/plugin-client-redirects": "^3.3.2",
"@docusaurus/preset-classic": "^3.3.2",
"@easyops-cn/docusaurus-search-local": "^0.40.1",
"@mdx-js/react": "^3.0.0",
"clsx": "^2.0.0",
"prism-react-renderer": "^2.3.0",
@ -21,8 +21,8 @@
"react-dom": "^18.0.0"
},
"devDependencies": {
"@docusaurus/module-type-aliases": "3.0.1",
"@docusaurus/types": "3.0.1"
"@docusaurus/module-type-aliases": "^3.3.2",
"@docusaurus/types": "^3.3.2"
},
"browserslist": {
"production": [
@ -40,8 +40,5 @@
"node": ">=18.0"
},
"overrides": {
"@cmfcmf/docusaurus-search-local": {
"@docusaurus/core": "^3.0.0-rc.0"
}
}
}