Merge branch 'v2.5.x-release' into default-nginx-config
This commit is contained in:
commit
fd5d3cbfac
@ -1,2 +0,0 @@
|
|||||||
Dockerfile
|
|
||||||
|
|
1
akka-bbb-apps/.gitignore
vendored
1
akka-bbb-apps/.gitignore
vendored
@ -48,4 +48,5 @@ lib_managed/
|
|||||||
.cache
|
.cache
|
||||||
bin/
|
bin/
|
||||||
src/main/resources/
|
src/main/resources/
|
||||||
|
.bsp/
|
||||||
|
|
||||||
|
@ -1,24 +0,0 @@
|
|||||||
FROM bbb-common-message AS builder
|
|
||||||
|
|
||||||
ARG COMMON_VERSION=0.0.1-SNAPSHOT
|
|
||||||
|
|
||||||
COPY . /source
|
|
||||||
|
|
||||||
RUN cd /source \
|
|
||||||
&& find -name build.sbt -exec sed -i "s|\(.*org.bigbluebutton.*bbb-common-message[^\"]*\"[ ]*%[ ]*\)\"[^\"]*\"\(.*\)|\1\"$COMMON_VERSION\"\2|g" {} \; \
|
|
||||||
&& sbt compile
|
|
||||||
|
|
||||||
RUN apt-get update \
|
|
||||||
&& apt-get -y install fakeroot
|
|
||||||
|
|
||||||
RUN cd /source \
|
|
||||||
&& sbt debian:packageBin
|
|
||||||
|
|
||||||
# FROM ubuntu:16.04
|
|
||||||
FROM openjdk:8-jre-slim-stretch
|
|
||||||
|
|
||||||
COPY --from=builder /source/target/*.deb /root/
|
|
||||||
|
|
||||||
RUN dpkg -i /root/*.deb
|
|
||||||
|
|
||||||
CMD ["/usr/share/bbb-apps-akka/bin/bbb-apps-akka"]
|
|
@ -15,12 +15,13 @@ trait PresentationConversionCompletedSysPubMsgHdlr {
|
|||||||
): MeetingState2x = {
|
): MeetingState2x = {
|
||||||
|
|
||||||
val meetingId = liveMeeting.props.meetingProp.intId
|
val meetingId = liveMeeting.props.meetingProp.intId
|
||||||
|
val temporaryPresentationId = msg.body.presentation.temporaryPresentationId
|
||||||
|
|
||||||
val newState = for {
|
val newState = for {
|
||||||
pod <- PresentationPodsApp.getPresentationPod(state, msg.body.podId)
|
pod <- PresentationPodsApp.getPresentationPod(state, msg.body.podId)
|
||||||
pres <- pod.getPresentation(msg.body.presentation.id)
|
pres <- pod.getPresentation(msg.body.presentation.id)
|
||||||
} yield {
|
} yield {
|
||||||
val presVO = PresentationPodsApp.translatePresentationToPresentationVO(pres)
|
val presVO = PresentationPodsApp.translatePresentationToPresentationVO(pres, temporaryPresentationId)
|
||||||
|
|
||||||
PresentationSender.broadcastPresentationConversionCompletedEvtMsg(
|
PresentationSender.broadcastPresentationConversionCompletedEvtMsg(
|
||||||
bus,
|
bus,
|
||||||
|
@ -58,7 +58,7 @@ object PresentationPodsApp {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
PresentationVO(p.id, p.name, p.current,
|
PresentationVO(p.id, "", p.name, p.current,
|
||||||
pages.toVector, p.downloadable, p.removable)
|
pages.toVector, p.downloadable, p.removable)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -74,7 +74,7 @@ object PresentationPodsApp {
|
|||||||
state.update(podManager)
|
state.update(podManager)
|
||||||
}
|
}
|
||||||
|
|
||||||
def translatePresentationToPresentationVO(pres: PresentationInPod): PresentationVO = {
|
def translatePresentationToPresentationVO(pres: PresentationInPod, temporaryPresentationId: String): PresentationVO = {
|
||||||
val pages = pres.pages.values.map { page =>
|
val pages = pres.pages.values.map { page =>
|
||||||
PageVO(
|
PageVO(
|
||||||
id = page.id,
|
id = page.id,
|
||||||
@ -90,7 +90,7 @@ object PresentationPodsApp {
|
|||||||
heightRatio = page.heightRatio
|
heightRatio = page.heightRatio
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
PresentationVO(pres.id, pres.name, pres.current, pages.toVector, pres.downloadable, pres.removable)
|
PresentationVO(pres.id, temporaryPresentationId, pres.name, pres.current, pages.toVector, pres.downloadable, pres.removable)
|
||||||
}
|
}
|
||||||
|
|
||||||
def setCurrentPresentationInPod(state: MeetingState2x, podId: String, nextCurrentPresId: String): Option[PresentationPod] = {
|
def setCurrentPresentationInPod(state: MeetingState2x, podId: String, nextCurrentPresId: String): Option[PresentationPod] = {
|
||||||
|
@ -19,7 +19,7 @@ trait PresentationUploadTokenReqMsgHdlr extends RightsManagementTrait {
|
|||||||
val envelope = BbbCoreEnvelope(PresentationUploadTokenPassRespMsg.NAME, routing)
|
val envelope = BbbCoreEnvelope(PresentationUploadTokenPassRespMsg.NAME, routing)
|
||||||
val header = BbbClientMsgHeader(PresentationUploadTokenPassRespMsg.NAME, liveMeeting.props.meetingProp.intId, msg.header.userId)
|
val header = BbbClientMsgHeader(PresentationUploadTokenPassRespMsg.NAME, liveMeeting.props.meetingProp.intId, msg.header.userId)
|
||||||
|
|
||||||
val body = PresentationUploadTokenPassRespMsgBody(msg.body.podId, token, msg.body.filename)
|
val body = PresentationUploadTokenPassRespMsgBody(msg.body.podId, token, msg.body.filename, msg.body.tmpPresId)
|
||||||
val event = PresentationUploadTokenPassRespMsg(header, body)
|
val event = PresentationUploadTokenPassRespMsg(header, body)
|
||||||
val msgEvent = BbbCommonEnvCoreMsg(envelope, event)
|
val msgEvent = BbbCommonEnvCoreMsg(envelope, event)
|
||||||
bus.outGW.send(msgEvent)
|
bus.outGW.send(msgEvent)
|
||||||
|
@ -50,7 +50,6 @@ trait SelectRandomViewerReqMsgHdlr extends RightsManagementTrait {
|
|||||||
Users2x.setUserExempted(liveMeeting.users2x, pickedUser, true)
|
Users2x.setUserExempted(liveMeeting.users2x, pickedUser, true)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
val userIds = users.map { case (v) => v.intId }
|
val userIds = users.map { case (v) => v.intId }
|
||||||
broadcastEvent(msg, userIds, pickedUser)
|
broadcastEvent(msg, userIds, pickedUser)
|
||||||
}
|
}
|
||||||
|
@ -15,12 +15,12 @@ trait UserJoinMeetingAfterReconnectReqMsgHdlr extends HandlerHelpers with UserJo
|
|||||||
|
|
||||||
def handleUserJoinMeetingAfterReconnectReqMsg(msg: UserJoinMeetingAfterReconnectReqMsg, state: MeetingState2x): MeetingState2x = {
|
def handleUserJoinMeetingAfterReconnectReqMsg(msg: UserJoinMeetingAfterReconnectReqMsg, state: MeetingState2x): MeetingState2x = {
|
||||||
log.info("Received user joined after reconnecting. user {} meetingId={}", msg.body.userId, msg.header.meetingId)
|
log.info("Received user joined after reconnecting. user {} meetingId={}", msg.body.userId, msg.header.meetingId)
|
||||||
|
|
||||||
Users2x.findWithIntId(liveMeeting.users2x, msg.body.userId) match {
|
Users2x.findWithIntId(liveMeeting.users2x, msg.body.userId) match {
|
||||||
case Some(reconnectingUser) =>
|
case Some(reconnectingUser) =>
|
||||||
if (reconnectingUser.userLeftFlag.left) {
|
if (reconnectingUser.userLeftFlag.left) {
|
||||||
log.info("Resetting flag that user left meeting. user {}", msg.body.userId)
|
log.info("Resetting flag that user left meeting. user {}", msg.body.userId)
|
||||||
// User has reconnected. Just reset it's flag. ralam Oct 23, 2018
|
// User has reconnected. Just reset it's flag. ralam Oct 23, 2018
|
||||||
|
sendUserLeftFlagUpdatedEvtMsg(outGW, liveMeeting, msg.body.userId, false)
|
||||||
Users2x.resetUserLeftFlag(liveMeeting.users2x, msg.body.userId)
|
Users2x.resetUserLeftFlag(liveMeeting.users2x, msg.body.userId)
|
||||||
}
|
}
|
||||||
state
|
state
|
||||||
|
@ -2,9 +2,9 @@ package org.bigbluebutton.core.apps.users
|
|||||||
|
|
||||||
import org.bigbluebutton.common2.msgs.UserJoinMeetingReqMsg
|
import org.bigbluebutton.common2.msgs.UserJoinMeetingReqMsg
|
||||||
import org.bigbluebutton.core.apps.breakout.BreakoutHdlrHelpers
|
import org.bigbluebutton.core.apps.breakout.BreakoutHdlrHelpers
|
||||||
import org.bigbluebutton.core.models.{ Users2x, VoiceUsers }
|
|
||||||
import org.bigbluebutton.core.domain.MeetingState2x
|
import org.bigbluebutton.core.domain.MeetingState2x
|
||||||
import org.bigbluebutton.core.running.{ HandlerHelpers, LiveMeeting, MeetingActor, OutMsgRouter }
|
import org.bigbluebutton.core.models.{Users2x, VoiceUsers}
|
||||||
|
import org.bigbluebutton.core.running.{HandlerHelpers, LiveMeeting, MeetingActor, OutMsgRouter}
|
||||||
|
|
||||||
trait UserJoinMeetingReqMsgHdlr extends HandlerHelpers {
|
trait UserJoinMeetingReqMsgHdlr extends HandlerHelpers {
|
||||||
this: MeetingActor =>
|
this: MeetingActor =>
|
||||||
@ -20,8 +20,10 @@ trait UserJoinMeetingReqMsgHdlr extends HandlerHelpers {
|
|||||||
if (reconnectingUser.userLeftFlag.left) {
|
if (reconnectingUser.userLeftFlag.left) {
|
||||||
log.info("Resetting flag that user left meeting. user {}", msg.body.userId)
|
log.info("Resetting flag that user left meeting. user {}", msg.body.userId)
|
||||||
// User has reconnected. Just reset it's flag. ralam Oct 23, 2018
|
// User has reconnected. Just reset it's flag. ralam Oct 23, 2018
|
||||||
|
sendUserLeftFlagUpdatedEvtMsg(outGW, liveMeeting, msg.body.userId, false)
|
||||||
Users2x.resetUserLeftFlag(liveMeeting.users2x, msg.body.userId)
|
Users2x.resetUserLeftFlag(liveMeeting.users2x, msg.body.userId)
|
||||||
}
|
}
|
||||||
|
|
||||||
state
|
state
|
||||||
case None =>
|
case None =>
|
||||||
val newState = userJoinMeeting(outGW, msg.body.authToken, msg.body.clientType, liveMeeting, state)
|
val newState = userJoinMeeting(outGW, msg.body.authToken, msg.body.clientType, liveMeeting, state)
|
||||||
|
@ -3,9 +3,9 @@ package org.bigbluebutton.core.apps.users
|
|||||||
import org.bigbluebutton.common2.msgs.UserLeaveReqMsg
|
import org.bigbluebutton.common2.msgs.UserLeaveReqMsg
|
||||||
import org.bigbluebutton.core.domain.MeetingState2x
|
import org.bigbluebutton.core.domain.MeetingState2x
|
||||||
import org.bigbluebutton.core.models.{ RegisteredUsers, Users2x }
|
import org.bigbluebutton.core.models.{ RegisteredUsers, Users2x }
|
||||||
import org.bigbluebutton.core.running.{ MeetingActor, OutMsgRouter }
|
import org.bigbluebutton.core.running.{ HandlerHelpers, MeetingActor, OutMsgRouter }
|
||||||
|
|
||||||
trait UserLeaveReqMsgHdlr {
|
trait UserLeaveReqMsgHdlr extends HandlerHelpers {
|
||||||
this: MeetingActor =>
|
this: MeetingActor =>
|
||||||
|
|
||||||
val outGW: OutMsgRouter
|
val outGW: OutMsgRouter
|
||||||
@ -19,6 +19,8 @@ trait UserLeaveReqMsgHdlr {
|
|||||||
// Just flag that user has left as the user might be reconnecting.
|
// Just flag that user has left as the user might be reconnecting.
|
||||||
// An audit will remove this user if it hasn't rejoined after a certain period of time.
|
// An audit will remove this user if it hasn't rejoined after a certain period of time.
|
||||||
// ralam oct 23, 2018
|
// ralam oct 23, 2018
|
||||||
|
sendUserLeftFlagUpdatedEvtMsg(outGW, liveMeeting, msg.body.userId, true)
|
||||||
|
|
||||||
Users2x.setUserLeftFlag(liveMeeting.users2x, msg.body.userId)
|
Users2x.setUserLeftFlag(liveMeeting.users2x, msg.body.userId)
|
||||||
}
|
}
|
||||||
if (msg.body.loggedOut) {
|
if (msg.body.loggedOut) {
|
||||||
|
@ -2,6 +2,7 @@ package org.bigbluebutton.core.models
|
|||||||
|
|
||||||
import com.softwaremill.quicklens._
|
import com.softwaremill.quicklens._
|
||||||
import org.bigbluebutton.core.util.TimeUtil
|
import org.bigbluebutton.core.util.TimeUtil
|
||||||
|
import org.bigbluebutton.core2.message.senders.MsgBuilder
|
||||||
|
|
||||||
object Users2x {
|
object Users2x {
|
||||||
def findWithIntId(users: Users2x, intId: String): Option[UserState] = {
|
def findWithIntId(users: Users2x, intId: String): Option[UserState] = {
|
||||||
|
@ -0,0 +1,34 @@
|
|||||||
|
/**
|
||||||
|
* BigBlueButton open source conferencing system - http://www.bigbluebutton.org/
|
||||||
|
*
|
||||||
|
* Copyright (c) 2017 BigBlueButton Inc. and by respective authors (see below).
|
||||||
|
*
|
||||||
|
* This program is free software; you can redistribute it and/or modify it under the
|
||||||
|
* terms of the GNU Lesser General Public License as published by the Free Software
|
||||||
|
* Foundation; either version 3.0 of the License, or (at your option) any later
|
||||||
|
* version.
|
||||||
|
*
|
||||||
|
* BigBlueButton is distributed in the hope that it will be useful, but WITHOUT ANY
|
||||||
|
* WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A
|
||||||
|
* PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU Lesser General Public License along
|
||||||
|
* with BigBlueButton; if not, see <http://www.gnu.org/licenses/>.
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
|
||||||
|
package org.bigbluebutton.core.record.events
|
||||||
|
|
||||||
|
class MeetingConfigurationEvent extends StarterConfigurationEvent {
|
||||||
|
import MeetingConfigurationEvent._
|
||||||
|
|
||||||
|
setEvent("MeetingConfigurationEvent")
|
||||||
|
|
||||||
|
def setWebcamsOnlyForModerator(webcamsOnlyForModerator: Boolean) {
|
||||||
|
eventMap.put(WEBCAMS_ONLY_FOR_MODERATOR, webcamsOnlyForModerator.toString)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
object MeetingConfigurationEvent {
|
||||||
|
val WEBCAMS_ONLY_FOR_MODERATOR = "webcamsOnlyForModerator"
|
||||||
|
}
|
@ -0,0 +1,24 @@
|
|||||||
|
/**
|
||||||
|
* BigBlueButton open source conferencing system - http://www.bigbluebutton.org/
|
||||||
|
*
|
||||||
|
* Copyright (c) 2017 BigBlueButton Inc. and by respective authors (see below).
|
||||||
|
*
|
||||||
|
* This program is free software; you can redistribute it and/or modify it under the
|
||||||
|
* terms of the GNU Lesser General Public License as published by the Free Software
|
||||||
|
* Foundation; either version 3.0 of the License, or (at your option) any later
|
||||||
|
* version.
|
||||||
|
*
|
||||||
|
* BigBlueButton is distributed in the hope that it will be useful, but WITHOUT ANY
|
||||||
|
* WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A
|
||||||
|
* PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU Lesser General Public License along
|
||||||
|
* with BigBlueButton; if not, see <http://www.gnu.org/licenses/>.
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
|
||||||
|
package org.bigbluebutton.core.record.events
|
||||||
|
|
||||||
|
trait StarterConfigurationEvent extends RecordEvent {
|
||||||
|
setModule("CONFIG")
|
||||||
|
}
|
@ -25,6 +25,20 @@ trait HandlerHelpers extends SystemConfiguration {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
def sendUserLeftFlagUpdatedEvtMsg(
|
||||||
|
outGW: OutMsgRouter,
|
||||||
|
liveMeeting: LiveMeeting,
|
||||||
|
intId: String,
|
||||||
|
leftFlag: Boolean
|
||||||
|
): Unit = {
|
||||||
|
for {
|
||||||
|
u <- Users2x.findWithIntId(liveMeeting.users2x, intId)
|
||||||
|
} yield {
|
||||||
|
val userLeftFlagMeetingEvent = MsgBuilder.buildUserLeftFlagUpdatedEvtMsg(liveMeeting.props.meetingProp.intId, u.intId, leftFlag)
|
||||||
|
outGW.send(userLeftFlagMeetingEvent)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
def userJoinMeeting(outGW: OutMsgRouter, authToken: String, clientType: String,
|
def userJoinMeeting(outGW: OutMsgRouter, authToken: String, clientType: String,
|
||||||
liveMeeting: LiveMeeting, state: MeetingState2x): MeetingState2x = {
|
liveMeeting: LiveMeeting, state: MeetingState2x): MeetingState2x = {
|
||||||
|
|
||||||
|
@ -286,6 +286,16 @@ object MsgBuilder {
|
|||||||
BbbCommonEnvCoreMsg(envelope, event)
|
BbbCommonEnvCoreMsg(envelope, event)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
def buildUserLeftFlagUpdatedEvtMsg(meetingId: String, userId: String, userLeftFlag: Boolean): BbbCommonEnvCoreMsg = {
|
||||||
|
val routing = Routing.addMsgToClientRouting(MessageTypes.BROADCAST_TO_MEETING, meetingId, userId)
|
||||||
|
val envelope = BbbCoreEnvelope(UserLeftFlagUpdatedEvtMsg.NAME, routing)
|
||||||
|
val header = BbbClientMsgHeader(UserLeftFlagUpdatedEvtMsg.NAME, meetingId, userId)
|
||||||
|
val body = UserLeftFlagUpdatedEvtMsgBody(userId, userLeftFlag)
|
||||||
|
val event = UserLeftFlagUpdatedEvtMsg(header, body)
|
||||||
|
|
||||||
|
BbbCommonEnvCoreMsg(envelope, event)
|
||||||
|
}
|
||||||
|
|
||||||
def buildUserInactivityInspectMsg(meetingId: String, userId: String, responseDelay: Long): BbbCommonEnvCoreMsg = {
|
def buildUserInactivityInspectMsg(meetingId: String, userId: String, responseDelay: Long): BbbCommonEnvCoreMsg = {
|
||||||
val routing = Routing.addMsgToClientRouting(MessageTypes.DIRECT, meetingId, userId)
|
val routing = Routing.addMsgToClientRouting(MessageTypes.DIRECT, meetingId, userId)
|
||||||
val envelope = BbbCoreEnvelope(UserInactivityInspectMsg.NAME, routing)
|
val envelope = BbbCoreEnvelope(UserInactivityInspectMsg.NAME, routing)
|
||||||
|
@ -116,6 +116,7 @@ class RedisRecorderActor(
|
|||||||
case m: RecordStatusResetSysMsg => handleRecordStatusResetSysMsg(m)
|
case m: RecordStatusResetSysMsg => handleRecordStatusResetSysMsg(m)
|
||||||
case m: WebcamsOnlyForModeratorChangedEvtMsg => handleWebcamsOnlyForModeratorChangedEvtMsg(m)
|
case m: WebcamsOnlyForModeratorChangedEvtMsg => handleWebcamsOnlyForModeratorChangedEvtMsg(m)
|
||||||
case m: MeetingEndingEvtMsg => handleEndAndKickAllSysMsg(m)
|
case m: MeetingEndingEvtMsg => handleEndAndKickAllSysMsg(m)
|
||||||
|
case m: MeetingCreatedEvtMsg => handleStarterConfigurations(m)
|
||||||
|
|
||||||
// Recording
|
// Recording
|
||||||
case m: RecordingChapterBreakSysMsg => handleRecordingChapterBreakSysMsg(m)
|
case m: RecordingChapterBreakSysMsg => handleRecordingChapterBreakSysMsg(m)
|
||||||
@ -622,4 +623,10 @@ class RedisRecorderActor(
|
|||||||
log.error("recording database is not available.")
|
log.error("recording database is not available.")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private def handleStarterConfigurations(msg: MeetingCreatedEvtMsg): Unit = {
|
||||||
|
val ev = new MeetingConfigurationEvent()
|
||||||
|
ev.setWebcamsOnlyForModerator(msg.body.props.usersProp.webcamsOnlyForModerator)
|
||||||
|
record(msg.body.props.meetingProp.intId, ev.toMap().asJava)
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -1,26 +0,0 @@
|
|||||||
FROM bbb-fsesl-client AS builder
|
|
||||||
|
|
||||||
ARG COMMON_VERSION=0.0.1-SNAPSHOT
|
|
||||||
|
|
||||||
COPY . /source
|
|
||||||
|
|
||||||
RUN cd /source \
|
|
||||||
&& find -name build.sbt -exec sed -i "s|\(.*org.bigbluebutton.*bbb-common-message[^\"]*\"[ ]*%[ ]*\)\"[^\"]*\"\(.*\)|\1\"$COMMON_VERSION\"\2|g" {} \; \
|
|
||||||
&& find -name build.sbt -exec sed -i "s|\(.*org.bigbluebutton.*bbb-fsesl-client[^\"]*\"[ ]*%[ ]*\)\"[^\"]*\"\(.*\)|\1\"$COMMON_VERSION\"\2|g" {} \; \
|
|
||||||
&& sbt compile
|
|
||||||
|
|
||||||
RUN apt-get update \
|
|
||||||
&& apt-get -y install fakeroot
|
|
||||||
|
|
||||||
RUN cd /source \
|
|
||||||
&& sbt debian:packageBin
|
|
||||||
|
|
||||||
FROM openjdk:8-jre-slim-stretch
|
|
||||||
|
|
||||||
COPY --from=builder /source/target/*.deb /root/
|
|
||||||
|
|
||||||
RUN dpkg -i /root/*.deb
|
|
||||||
|
|
||||||
COPY wait-for-it.sh /usr/local/bin/
|
|
||||||
|
|
||||||
CMD ["/usr/share/bbb-fsesl-akka/bin/bbb-fsesl-akka"]
|
|
1
bbb-common-message/.gitignore
vendored
1
bbb-common-message/.gitignore
vendored
@ -53,4 +53,5 @@ akka-patterns-store/
|
|||||||
lib_managed/
|
lib_managed/
|
||||||
.cache
|
.cache
|
||||||
bin/
|
bin/
|
||||||
|
.bsp/
|
||||||
|
|
||||||
|
@ -1,13 +0,0 @@
|
|||||||
FROM sbt:0.13.8
|
|
||||||
|
|
||||||
ARG COMMON_VERSION
|
|
||||||
|
|
||||||
COPY . /bbb-common-message
|
|
||||||
|
|
||||||
RUN cd /bbb-common-message \
|
|
||||||
&& sed -i "s|\(version := \)\".*|\1\"$COMMON_VERSION\"|g" build.sbt \
|
|
||||||
&& echo 'publishTo := Some(Resolver.file("file", new File(Path.userHome.absolutePath+"/.m2/repository")))' | tee -a build.sbt \
|
|
||||||
&& sbt compile \
|
|
||||||
&& sbt publish \
|
|
||||||
&& sbt publishLocal
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
|||||||
package org.bigbluebutton.common2.domain
|
package org.bigbluebutton.common2.domain
|
||||||
|
|
||||||
case class PresentationVO(id: String, name: String, current: Boolean = false,
|
case class PresentationVO(id: String, temporaryPresentationId: String, name: String, current: Boolean = false,
|
||||||
pages: Vector[PageVO], downloadable: Boolean, removable: Boolean)
|
pages: Vector[PageVO], downloadable: Boolean, removable: Boolean)
|
||||||
|
|
||||||
case class PageVO(id: String, num: Int, thumbUri: String = "", swfUri: String,
|
case class PageVO(id: String, num: Int, thumbUri: String = "", swfUri: String,
|
||||||
|
@ -13,7 +13,7 @@ case class RemovePresentationPodPubMsgBody(podId: String)
|
|||||||
|
|
||||||
object PresentationUploadTokenReqMsg { val NAME = "PresentationUploadTokenReqMsg" }
|
object PresentationUploadTokenReqMsg { val NAME = "PresentationUploadTokenReqMsg" }
|
||||||
case class PresentationUploadTokenReqMsg(header: BbbClientMsgHeader, body: PresentationUploadTokenReqMsgBody) extends StandardMsg
|
case class PresentationUploadTokenReqMsg(header: BbbClientMsgHeader, body: PresentationUploadTokenReqMsgBody) extends StandardMsg
|
||||||
case class PresentationUploadTokenReqMsgBody(podId: String, filename: String)
|
case class PresentationUploadTokenReqMsgBody(podId: String, filename: String, tmpPresId: String)
|
||||||
|
|
||||||
object GetAllPresentationPodsReqMsg { val NAME = "GetAllPresentationPodsReqMsg" }
|
object GetAllPresentationPodsReqMsg { val NAME = "GetAllPresentationPodsReqMsg" }
|
||||||
case class GetAllPresentationPodsReqMsg(header: BbbClientMsgHeader, body: GetAllPresentationPodsReqMsgBody) extends StandardMsg
|
case class GetAllPresentationPodsReqMsg(header: BbbClientMsgHeader, body: GetAllPresentationPodsReqMsgBody) extends StandardMsg
|
||||||
@ -113,13 +113,14 @@ case class PresentationConversionRequestReceivedSysMsg(
|
|||||||
body: PresentationConversionRequestReceivedSysMsgBody
|
body: PresentationConversionRequestReceivedSysMsgBody
|
||||||
) extends StandardMsg
|
) extends StandardMsg
|
||||||
case class PresentationConversionRequestReceivedSysMsgBody(
|
case class PresentationConversionRequestReceivedSysMsgBody(
|
||||||
podId: String,
|
podId: String,
|
||||||
presentationId: String,
|
presentationId: String,
|
||||||
current: Boolean,
|
temporaryPresentationId: String,
|
||||||
presName: String,
|
current: Boolean,
|
||||||
downloadable: Boolean,
|
presName: String,
|
||||||
removable: Boolean,
|
downloadable: Boolean,
|
||||||
authzToken: String
|
removable: Boolean,
|
||||||
|
authzToken: String
|
||||||
)
|
)
|
||||||
|
|
||||||
object PresentationPageConversionStartedSysMsg { val NAME = "PresentationPageConversionStartedSysMsg" }
|
object PresentationPageConversionStartedSysMsg { val NAME = "PresentationPageConversionStartedSysMsg" }
|
||||||
@ -181,7 +182,7 @@ case class PdfConversionInvalidErrorEvtMsgBody(podId: String, messageKey: String
|
|||||||
|
|
||||||
object PresentationUploadTokenPassRespMsg { val NAME = "PresentationUploadTokenPassRespMsg" }
|
object PresentationUploadTokenPassRespMsg { val NAME = "PresentationUploadTokenPassRespMsg" }
|
||||||
case class PresentationUploadTokenPassRespMsg(header: BbbClientMsgHeader, body: PresentationUploadTokenPassRespMsgBody) extends StandardMsg
|
case class PresentationUploadTokenPassRespMsg(header: BbbClientMsgHeader, body: PresentationUploadTokenPassRespMsgBody) extends StandardMsg
|
||||||
case class PresentationUploadTokenPassRespMsgBody(podId: String, authzToken: String, filename: String)
|
case class PresentationUploadTokenPassRespMsgBody(podId: String, authzToken: String, filename: String, tmpPresId: String)
|
||||||
|
|
||||||
object PresentationUploadTokenFailRespMsg { val NAME = "PresentationUploadTokenFailRespMsg" }
|
object PresentationUploadTokenFailRespMsg { val NAME = "PresentationUploadTokenFailRespMsg" }
|
||||||
case class PresentationUploadTokenFailRespMsg(header: BbbClientMsgHeader, body: PresentationUploadTokenFailRespMsgBody) extends StandardMsg
|
case class PresentationUploadTokenFailRespMsg(header: BbbClientMsgHeader, body: PresentationUploadTokenFailRespMsgBody) extends StandardMsg
|
||||||
|
@ -70,6 +70,20 @@ case class UserLeftMeetingEvtMsg(
|
|||||||
) extends BbbCoreMsg
|
) extends BbbCoreMsg
|
||||||
case class UserLeftMeetingEvtMsgBody(intId: String, eject: Boolean, ejectedBy: String, reason: String, reasonCode: String)
|
case class UserLeftMeetingEvtMsgBody(intId: String, eject: Boolean, ejectedBy: String, reason: String, reasonCode: String)
|
||||||
|
|
||||||
|
object UserLeftFlagUpdatedEvtMsg {
|
||||||
|
val NAME = "UserLeftFlagUpdatedEvtMsg"
|
||||||
|
def apply(meetingId: String, userId: String, body: UserLeftFlagUpdatedEvtMsgBody): UserLeftFlagUpdatedEvtMsg = {
|
||||||
|
val header = BbbClientMsgHeader(UserLeftFlagUpdatedEvtMsg.NAME, meetingId, userId)
|
||||||
|
UserLeftFlagUpdatedEvtMsg(header, body)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
case class UserLeftFlagUpdatedEvtMsg(
|
||||||
|
header: BbbClientMsgHeader,
|
||||||
|
body: UserLeftFlagUpdatedEvtMsgBody
|
||||||
|
) extends BbbCoreMsg
|
||||||
|
case class UserLeftFlagUpdatedEvtMsgBody(intId: String, userLeftFlag: Boolean)
|
||||||
|
|
||||||
object UserJoinedMeetingEvtMsg {
|
object UserJoinedMeetingEvtMsg {
|
||||||
val NAME = "UserJoinedMeetingEvtMsg"
|
val NAME = "UserJoinedMeetingEvtMsg"
|
||||||
def apply(meetingId: String, userId: String, body: UserJoinedMeetingEvtMsgBody): UserJoinedMeetingEvtMsg = {
|
def apply(meetingId: String, userId: String, body: UserJoinedMeetingEvtMsgBody): UserJoinedMeetingEvtMsg = {
|
||||||
|
1
bbb-common-web/.gitignore
vendored
1
bbb-common-web/.gitignore
vendored
@ -53,4 +53,5 @@ akka-patterns-store/
|
|||||||
lib_managed/
|
lib_managed/
|
||||||
.cache
|
.cache
|
||||||
bin/
|
bin/
|
||||||
|
.bsp/
|
||||||
|
|
||||||
|
@ -1,13 +0,0 @@
|
|||||||
FROM bbb-common-message
|
|
||||||
|
|
||||||
ARG COMMON_VERSION
|
|
||||||
|
|
||||||
COPY . /bbb-common-web
|
|
||||||
|
|
||||||
RUN cd /bbb-common-web \
|
|
||||||
&& sed -i "s|\(version := \)\".*|\1\"$COMMON_VERSION\"|g" build.sbt \
|
|
||||||
&& find -name build.sbt -exec sed -i "s|\(.*org.bigbluebutton.*bbb-common-message[^\"]*\"[ ]*%[ ]*\)\"[^\"]*\"\(.*\)|\1\"$COMMON_VERSION\"\2|g" {} \; \
|
|
||||||
&& echo 'publishTo := Some(Resolver.file("file", new File(Path.userHome.absolutePath+"/.m2/repository")))' | tee -a build.sbt \
|
|
||||||
&& sbt compile \
|
|
||||||
&& sbt publish \
|
|
||||||
&& sbt publishLocal
|
|
@ -60,6 +60,7 @@ import org.bigbluebutton.api.messaging.converters.messages.DeletedRecordingMessa
|
|||||||
import org.bigbluebutton.api.messaging.messages.*;
|
import org.bigbluebutton.api.messaging.messages.*;
|
||||||
import org.bigbluebutton.api2.IBbbWebApiGWApp;
|
import org.bigbluebutton.api2.IBbbWebApiGWApp;
|
||||||
import org.bigbluebutton.api2.domain.UploadedTrack;
|
import org.bigbluebutton.api2.domain.UploadedTrack;
|
||||||
|
import org.bigbluebutton.common2.msgs.MeetingCreatedEvtMsg;
|
||||||
import org.bigbluebutton.common2.redis.RedisStorageService;
|
import org.bigbluebutton.common2.redis.RedisStorageService;
|
||||||
import org.bigbluebutton.presentation.PresentationUrlDownloadService;
|
import org.bigbluebutton.presentation.PresentationUrlDownloadService;
|
||||||
import org.bigbluebutton.presentation.imp.SwfSlidesGenerationProgressNotifier;
|
import org.bigbluebutton.presentation.imp.SwfSlidesGenerationProgressNotifier;
|
||||||
|
@ -817,11 +817,11 @@ public class ParamsProcessorUtil {
|
|||||||
return DigestUtils.sha1Hex(extMeetingId);
|
return DigestUtils.sha1Hex(extMeetingId);
|
||||||
}
|
}
|
||||||
|
|
||||||
public String processPassword(String pass) {
|
public String processPassword(String pass) {
|
||||||
return StringUtils.isEmpty(pass) ? RandomStringUtils.randomAlphanumeric(8) : pass;
|
return StringUtils.isEmpty(pass) ? RandomStringUtils.randomAlphanumeric(8) : pass;
|
||||||
}
|
}
|
||||||
|
|
||||||
public boolean hasChecksumAndQueryString(String checksum, String queryString) {
|
public boolean hasChecksumAndQueryString(String checksum, String queryString) {
|
||||||
return (! StringUtils.isEmpty(checksum) && StringUtils.isEmpty(queryString));
|
return (! StringUtils.isEmpty(checksum) && StringUtils.isEmpty(queryString));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -2,6 +2,7 @@ package org.bigbluebutton.api;
|
|||||||
|
|
||||||
import java.io.File;
|
import java.io.File;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
|
import java.util.UUID;
|
||||||
import java.util.regex.Matcher;
|
import java.util.regex.Matcher;
|
||||||
import java.util.regex.Pattern;
|
import java.util.regex.Pattern;
|
||||||
|
|
||||||
@ -45,7 +46,8 @@ public final class Util {
|
|||||||
|
|
||||||
public static String generatePresentationId(String presFilename) {
|
public static String generatePresentationId(String presFilename) {
|
||||||
long timestamp = System.currentTimeMillis();
|
long timestamp = System.currentTimeMillis();
|
||||||
return DigestUtils.sha1Hex(presFilename) + "-" + timestamp;
|
String uuid = UUID.randomUUID().toString();
|
||||||
|
return DigestUtils.sha1Hex(presFilename + uuid) + "-" + timestamp;
|
||||||
}
|
}
|
||||||
|
|
||||||
public static String createNewFilename(String presId, String fileExt) {
|
public static String createNewFilename(String presId, String fileExt) {
|
||||||
|
@ -49,8 +49,8 @@ public class Meeting {
|
|||||||
private boolean forciblyEnded = false;
|
private boolean forciblyEnded = false;
|
||||||
private String telVoice;
|
private String telVoice;
|
||||||
private String webVoice;
|
private String webVoice;
|
||||||
private String moderatorPass;
|
private String moderatorPass = "";
|
||||||
private String viewerPass;
|
private String viewerPass = "";
|
||||||
private int learningDashboardCleanupDelayInMinutes;
|
private int learningDashboardCleanupDelayInMinutes;
|
||||||
private String learningDashboardAccessToken;
|
private String learningDashboardAccessToken;
|
||||||
private ArrayList<String> disabledFeatures;
|
private ArrayList<String> disabledFeatures;
|
||||||
@ -115,9 +115,17 @@ public class Meeting {
|
|||||||
name = builder.name;
|
name = builder.name;
|
||||||
extMeetingId = builder.externalId;
|
extMeetingId = builder.externalId;
|
||||||
intMeetingId = builder.internalId;
|
intMeetingId = builder.internalId;
|
||||||
viewerPass = builder.viewerPass;
|
|
||||||
moderatorPass = builder.moderatorPass;
|
|
||||||
disabledFeatures = builder.disabledFeatures;
|
disabledFeatures = builder.disabledFeatures;
|
||||||
|
if (builder.viewerPass == null){
|
||||||
|
viewerPass = "";
|
||||||
|
} else {
|
||||||
|
viewerPass = builder.viewerPass;
|
||||||
|
}
|
||||||
|
if (builder.moderatorPass == null){
|
||||||
|
moderatorPass = "";
|
||||||
|
} else {
|
||||||
|
moderatorPass = builder.moderatorPass;
|
||||||
|
}
|
||||||
learningDashboardCleanupDelayInMinutes = builder.learningDashboardCleanupDelayInMinutes;
|
learningDashboardCleanupDelayInMinutes = builder.learningDashboardCleanupDelayInMinutes;
|
||||||
learningDashboardAccessToken = builder.learningDashboardAccessToken;
|
learningDashboardAccessToken = builder.learningDashboardAccessToken;
|
||||||
maxUsers = builder.maxUsers;
|
maxUsers = builder.maxUsers;
|
||||||
|
@ -1,22 +0,0 @@
|
|||||||
package org.bigbluebutton.api.model.constraint;
|
|
||||||
|
|
||||||
import org.bigbluebutton.api.model.validator.ModeratorPasswordValidator;
|
|
||||||
|
|
||||||
import javax.validation.Constraint;
|
|
||||||
import javax.validation.Payload;
|
|
||||||
import java.lang.annotation.Retention;
|
|
||||||
import java.lang.annotation.Target;
|
|
||||||
|
|
||||||
import static java.lang.annotation.ElementType.TYPE;
|
|
||||||
import static java.lang.annotation.RetentionPolicy.RUNTIME;
|
|
||||||
|
|
||||||
@Constraint(validatedBy = ModeratorPasswordValidator.class)
|
|
||||||
@Target(TYPE)
|
|
||||||
@Retention(RUNTIME)
|
|
||||||
public @interface ModeratorPasswordConstraint {
|
|
||||||
|
|
||||||
String key() default "invalidPassword";
|
|
||||||
String message() default "The supplied moderator password is incorrect";
|
|
||||||
Class<?>[] groups() default {};
|
|
||||||
Class<? extends Payload>[] payload() default {};
|
|
||||||
}
|
|
@ -29,7 +29,6 @@ public class EndMeeting extends RequestWithChecksum<EndMeeting.Params> {
|
|||||||
private String meetingID;
|
private String meetingID;
|
||||||
|
|
||||||
@PasswordConstraint
|
@PasswordConstraint
|
||||||
@NotEmpty(message = "You must provide the moderator password")
|
|
||||||
private String password;
|
private String password;
|
||||||
|
|
||||||
@Valid
|
@Valid
|
||||||
|
@ -38,7 +38,6 @@ public class JoinMeeting extends RequestWithChecksum<JoinMeeting.Params> {
|
|||||||
private String fullName;
|
private String fullName;
|
||||||
|
|
||||||
@PasswordConstraint
|
@PasswordConstraint
|
||||||
@NotEmpty(key = "invalidPassword", message = "You must provide either the moderator or attendee password")
|
|
||||||
private String password;
|
private String password;
|
||||||
|
|
||||||
@IsBooleanConstraint(message = "Guest must be a boolean value (true or false)")
|
@IsBooleanConstraint(message = "Guest must be a boolean value (true or false)")
|
||||||
|
@ -1,6 +1,3 @@
|
|||||||
package org.bigbluebutton.api.model.shared;
|
package org.bigbluebutton.api.model.shared;
|
||||||
|
|
||||||
import org.bigbluebutton.api.model.constraint.ModeratorPasswordConstraint;
|
|
||||||
|
|
||||||
@ModeratorPasswordConstraint
|
|
||||||
public class ModeratorPassword extends Password {}
|
public class ModeratorPassword extends Password {}
|
||||||
|
@ -7,7 +7,6 @@ public abstract class Password {
|
|||||||
@NotEmpty(message = "You must provide the meeting ID")
|
@NotEmpty(message = "You must provide the meeting ID")
|
||||||
protected String meetingID;
|
protected String meetingID;
|
||||||
|
|
||||||
@NotEmpty(message = "You must provide the password for the call")
|
|
||||||
protected String password;
|
protected String password;
|
||||||
|
|
||||||
public String getMeetingID() {
|
public String getMeetingID() {
|
||||||
|
@ -36,18 +36,10 @@ public class JoinPasswordValidator implements ConstraintValidator<JoinPasswordCo
|
|||||||
String attendeePassword = meeting.getViewerPassword();
|
String attendeePassword = meeting.getViewerPassword();
|
||||||
String providedPassword = joinPassword.getPassword();
|
String providedPassword = joinPassword.getPassword();
|
||||||
|
|
||||||
if(providedPassword == null) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
log.info("Moderator password: {}", moderatorPassword);
|
log.info("Moderator password: {}", moderatorPassword);
|
||||||
log.info("Attendee password: {}", attendeePassword);
|
log.info("Attendee password: {}", attendeePassword);
|
||||||
log.info("Provided password: {}", providedPassword);
|
log.info("Provided password: {}", providedPassword);
|
||||||
|
|
||||||
if(!providedPassword.equals(moderatorPassword) && !providedPassword.equals(attendeePassword)) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,52 +0,0 @@
|
|||||||
package org.bigbluebutton.api.model.validator;
|
|
||||||
|
|
||||||
import org.bigbluebutton.api.domain.Meeting;
|
|
||||||
import org.bigbluebutton.api.model.constraint.ModeratorPasswordConstraint;
|
|
||||||
import org.bigbluebutton.api.model.shared.ModeratorPassword;
|
|
||||||
import org.bigbluebutton.api.service.ServiceUtils;
|
|
||||||
import org.slf4j.Logger;
|
|
||||||
import org.slf4j.LoggerFactory;
|
|
||||||
|
|
||||||
import javax.validation.ConstraintValidator;
|
|
||||||
import javax.validation.ConstraintValidatorContext;
|
|
||||||
|
|
||||||
public class ModeratorPasswordValidator implements ConstraintValidator<ModeratorPasswordConstraint, ModeratorPassword> {
|
|
||||||
|
|
||||||
private static Logger log = LoggerFactory.getLogger(ModeratorPasswordValidator.class);
|
|
||||||
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void initialize(ModeratorPasswordConstraint constraintAnnotation) {}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean isValid(ModeratorPassword moderatorPassword, ConstraintValidatorContext context) {
|
|
||||||
log.info("Validating password {} for meeting with ID {}",
|
|
||||||
moderatorPassword.getPassword(), moderatorPassword.getMeetingID());
|
|
||||||
|
|
||||||
if(moderatorPassword.getMeetingID() == null) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
Meeting meeting = ServiceUtils.findMeetingFromMeetingID(moderatorPassword.getMeetingID());
|
|
||||||
|
|
||||||
if(meeting == null) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
String actualPassword = meeting.getModeratorPassword();
|
|
||||||
String providedPassword = moderatorPassword.getPassword();
|
|
||||||
|
|
||||||
if(providedPassword == null) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
log.info("Actual password: {}", actualPassword);
|
|
||||||
log.info("Provided password: {}", providedPassword);
|
|
||||||
|
|
||||||
if(!providedPassword.equals(actualPassword)) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
}
|
|
@ -19,15 +19,11 @@ public class PasswordValidator implements ConstraintValidator<PasswordConstraint
|
|||||||
@Override
|
@Override
|
||||||
public boolean isValid(String password, ConstraintValidatorContext context) {
|
public boolean isValid(String password, ConstraintValidatorContext context) {
|
||||||
log.info("Validating password [{}]", password);
|
log.info("Validating password [{}]", password);
|
||||||
|
if (password != null && !password.isEmpty()){
|
||||||
if(password == null || password.equals("")) {
|
if (password.length() < 2 || password.length() > 64) {
|
||||||
log.info("Provided password is either null or an empty string");
|
log.info("Passwords must be between 2 and 64 characters in length");
|
||||||
return true;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
if(password.length() < 2 || password.length() > 64) {
|
|
||||||
log.info("Passwords must be between 2 and 64 characters in length");
|
|
||||||
return false;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
|
@ -148,6 +148,7 @@ public class DocumentConversionServiceImp implements DocumentConversionService {
|
|||||||
pres.getPodId(),
|
pres.getPodId(),
|
||||||
pres.getMeetingId(),
|
pres.getMeetingId(),
|
||||||
pres.getId(),
|
pres.getId(),
|
||||||
|
pres.getTemporaryPresentationId(),
|
||||||
pres.getName(),
|
pres.getName(),
|
||||||
pres.getAuthzToken(),
|
pres.getAuthzToken(),
|
||||||
pres.isDownloadable(),
|
pres.isDownloadable(),
|
||||||
|
@ -26,6 +26,7 @@ public final class UploadedPresentation {
|
|||||||
private final String podId;
|
private final String podId;
|
||||||
private final String meetingId;
|
private final String meetingId;
|
||||||
private final String id;
|
private final String id;
|
||||||
|
private final String temporaryPresentationId;
|
||||||
private final String name;
|
private final String name;
|
||||||
private final boolean uploadFailed;
|
private final boolean uploadFailed;
|
||||||
private final ArrayList<String> uploadFailReason;
|
private final ArrayList<String> uploadFailReason;
|
||||||
@ -44,6 +45,7 @@ public final class UploadedPresentation {
|
|||||||
public UploadedPresentation(String podId,
|
public UploadedPresentation(String podId,
|
||||||
String meetingId,
|
String meetingId,
|
||||||
String id,
|
String id,
|
||||||
|
String temporaryPresentationId,
|
||||||
String name,
|
String name,
|
||||||
String baseUrl,
|
String baseUrl,
|
||||||
Boolean current,
|
Boolean current,
|
||||||
@ -53,6 +55,7 @@ public final class UploadedPresentation {
|
|||||||
this.podId = podId;
|
this.podId = podId;
|
||||||
this.meetingId = meetingId;
|
this.meetingId = meetingId;
|
||||||
this.id = id;
|
this.id = id;
|
||||||
|
this.temporaryPresentationId = temporaryPresentationId;
|
||||||
this.name = name;
|
this.name = name;
|
||||||
this.baseUrl = baseUrl;
|
this.baseUrl = baseUrl;
|
||||||
this.isDownloadable = false;
|
this.isDownloadable = false;
|
||||||
@ -62,6 +65,19 @@ public final class UploadedPresentation {
|
|||||||
this.uploadFailReason = uploadFailReason;
|
this.uploadFailReason = uploadFailReason;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public UploadedPresentation(String podId,
|
||||||
|
String meetingId,
|
||||||
|
String id,
|
||||||
|
String name,
|
||||||
|
String baseUrl,
|
||||||
|
Boolean current,
|
||||||
|
String authzToken,
|
||||||
|
Boolean uploadFailed,
|
||||||
|
ArrayList<String> uploadFailReason) {
|
||||||
|
this(podId, meetingId, id, "", name, baseUrl,
|
||||||
|
current, authzToken, uploadFailed, uploadFailReason);
|
||||||
|
}
|
||||||
|
|
||||||
public File getUploadedFile() {
|
public File getUploadedFile() {
|
||||||
return uploadedFile;
|
return uploadedFile;
|
||||||
}
|
}
|
||||||
@ -82,6 +98,10 @@ public final class UploadedPresentation {
|
|||||||
return id;
|
return id;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public String getTemporaryPresentationId() {
|
||||||
|
return temporaryPresentationId;
|
||||||
|
}
|
||||||
|
|
||||||
public String getName() {
|
public String getName() {
|
||||||
return name;
|
return name;
|
||||||
}
|
}
|
||||||
|
@ -85,7 +85,7 @@ public class SwfSlidesGenerationProgressNotifier {
|
|||||||
}
|
}
|
||||||
|
|
||||||
DocPageCompletedProgress progress = new DocPageCompletedProgress(pres.getPodId(), pres.getMeetingId(),
|
DocPageCompletedProgress progress = new DocPageCompletedProgress(pres.getPodId(), pres.getMeetingId(),
|
||||||
pres.getId(), pres.getId(),
|
pres.getId(), pres.getTemporaryPresentationId(), pres.getId(),
|
||||||
pres.getName(), "notUsedYet", "notUsedYet",
|
pres.getName(), "notUsedYet", "notUsedYet",
|
||||||
pres.isDownloadable(), pres.isRemovable(), ConversionMessageConstants.CONVERSION_COMPLETED_KEY,
|
pres.isDownloadable(), pres.isRemovable(), ConversionMessageConstants.CONVERSION_COMPLETED_KEY,
|
||||||
pres.getNumberOfPages(), generateBasePresUrl(pres), pres.isCurrent());
|
pres.getNumberOfPages(), generateBasePresUrl(pres), pres.isCurrent());
|
||||||
|
@ -4,6 +4,7 @@ public class DocConversionRequestReceived implements IDocConversionMsg {
|
|||||||
public final String podId;
|
public final String podId;
|
||||||
public final String meetingId;
|
public final String meetingId;
|
||||||
public final String presId;
|
public final String presId;
|
||||||
|
public final String temporaryPresentationId;
|
||||||
public final String filename;
|
public final String filename;
|
||||||
public final String authzToken;
|
public final String authzToken;
|
||||||
public final Boolean downloadable;
|
public final Boolean downloadable;
|
||||||
@ -13,6 +14,7 @@ public class DocConversionRequestReceived implements IDocConversionMsg {
|
|||||||
public DocConversionRequestReceived(String podId,
|
public DocConversionRequestReceived(String podId,
|
||||||
String meetingId,
|
String meetingId,
|
||||||
String presId,
|
String presId,
|
||||||
|
String temporaryPresentationId,
|
||||||
String filename,
|
String filename,
|
||||||
String authzToken,
|
String authzToken,
|
||||||
Boolean downloadable,
|
Boolean downloadable,
|
||||||
@ -21,6 +23,7 @@ public class DocConversionRequestReceived implements IDocConversionMsg {
|
|||||||
this.podId = podId;
|
this.podId = podId;
|
||||||
this.meetingId = meetingId;
|
this.meetingId = meetingId;
|
||||||
this.presId = presId;
|
this.presId = presId;
|
||||||
|
this.temporaryPresentationId = temporaryPresentationId;
|
||||||
this.filename = filename;
|
this.filename = filename;
|
||||||
this.authzToken = authzToken;
|
this.authzToken = authzToken;
|
||||||
this.downloadable = downloadable;
|
this.downloadable = downloadable;
|
||||||
|
@ -4,6 +4,7 @@ public class DocPageCompletedProgress implements IDocConversionMsg {
|
|||||||
public final String podId;
|
public final String podId;
|
||||||
public final String meetingId;
|
public final String meetingId;
|
||||||
public final String presId;
|
public final String presId;
|
||||||
|
public final String temporaryPresentationId;
|
||||||
public final String presInstance;
|
public final String presInstance;
|
||||||
public final String filename;
|
public final String filename;
|
||||||
public final String uploaderId;
|
public final String uploaderId;
|
||||||
@ -15,13 +16,14 @@ public class DocPageCompletedProgress implements IDocConversionMsg {
|
|||||||
public final String presBaseUrl;
|
public final String presBaseUrl;
|
||||||
public final Boolean current;
|
public final Boolean current;
|
||||||
|
|
||||||
public DocPageCompletedProgress(String podId, String meetingId, String presId, String presInstance,
|
public DocPageCompletedProgress(String podId, String meetingId, String presId, String temporaryPresentationId, String presInstance,
|
||||||
String filename, String uploaderId, String authzToken,
|
String filename, String uploaderId, String authzToken,
|
||||||
Boolean downloadable, Boolean removable, String key,
|
Boolean downloadable, Boolean removable, String key,
|
||||||
Integer numPages, String presBaseUrl, Boolean current) {
|
Integer numPages, String presBaseUrl, Boolean current) {
|
||||||
this.podId = podId;
|
this.podId = podId;
|
||||||
this.meetingId = meetingId;
|
this.meetingId = meetingId;
|
||||||
this.presId = presId;
|
this.presId = presId;
|
||||||
|
this.temporaryPresentationId = temporaryPresentationId;
|
||||||
this.presInstance = presInstance;
|
this.presInstance = presInstance;
|
||||||
this.filename = filename;
|
this.filename = filename;
|
||||||
this.uploaderId = uploaderId;
|
this.uploaderId = uploaderId;
|
||||||
|
@ -157,7 +157,7 @@ object MsgBuilder {
|
|||||||
val header = BbbClientMsgHeader(PresentationConversionCompletedSysPubMsg.NAME, msg.meetingId, msg.authzToken)
|
val header = BbbClientMsgHeader(PresentationConversionCompletedSysPubMsg.NAME, msg.meetingId, msg.authzToken)
|
||||||
|
|
||||||
val pages = generatePresentationPages(msg.presId, msg.numPages.intValue(), msg.presBaseUrl)
|
val pages = generatePresentationPages(msg.presId, msg.numPages.intValue(), msg.presBaseUrl)
|
||||||
val presentation = PresentationVO(msg.presId, msg.filename,
|
val presentation = PresentationVO(msg.presId, msg.temporaryPresentationId, msg.filename,
|
||||||
current = msg.current.booleanValue(), pages.values.toVector, msg.downloadable.booleanValue(), msg.removable.booleanValue())
|
current = msg.current.booleanValue(), pages.values.toVector, msg.downloadable.booleanValue(), msg.removable.booleanValue())
|
||||||
|
|
||||||
val body = PresentationConversionCompletedSysPubMsgBody(podId = msg.podId, messageKey = msg.key,
|
val body = PresentationConversionCompletedSysPubMsgBody(podId = msg.podId, messageKey = msg.key,
|
||||||
@ -228,6 +228,7 @@ object MsgBuilder {
|
|||||||
val body = PresentationConversionRequestReceivedSysMsgBody(
|
val body = PresentationConversionRequestReceivedSysMsgBody(
|
||||||
podId = msg.podId,
|
podId = msg.podId,
|
||||||
presentationId = msg.presId,
|
presentationId = msg.presId,
|
||||||
|
temporaryPresentationId = msg.temporaryPresentationId,
|
||||||
current = msg.current,
|
current = msg.current,
|
||||||
presName = msg.filename,
|
presName = msg.filename,
|
||||||
downloadable = msg.downloadable,
|
downloadable = msg.downloadable,
|
||||||
|
@ -1,13 +0,0 @@
|
|||||||
FROM bbb-common-message
|
|
||||||
|
|
||||||
ARG COMMON_VERSION
|
|
||||||
|
|
||||||
COPY . /bbb-fsesl-client
|
|
||||||
|
|
||||||
RUN cd /bbb-fsesl-client \
|
|
||||||
&& sed -i "s|\(version := \)\".*|\1\"$COMMON_VERSION\"|g" build.sbt \
|
|
||||||
&& find -name build.sbt -exec sed -i "s|\(.*org.bigbluebutton.*bbb-common-message[^\"]*\"[ ]*%[ ]*\)\"[^\"]*\"\(.*\)|\1\"$COMMON_VERSION\"\2|g" {} \; \
|
|
||||||
&& echo 'publishTo := Some(Resolver.file("file", new File(Path.userHome.absolutePath+"/.m2/repository")))' | tee -a build.sbt \
|
|
||||||
&& sbt compile \
|
|
||||||
&& sbt publish \
|
|
||||||
&& sbt publishLocal
|
|
1378
bbb-learning-dashboard/package-lock.json
generated
1378
bbb-learning-dashboard/package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@ -49,7 +49,7 @@ class App extends React.Component {
|
|||||||
|
|
||||||
downloadButton.setAttribute('disabled', 'true');
|
downloadButton.setAttribute('disabled', 'true');
|
||||||
downloadButton.style.cursor = 'not-allowed';
|
downloadButton.style.cursor = 'not-allowed';
|
||||||
link.setAttribute('href', `data:application/octet-stream,${encodeURIComponent(data)}`);
|
link.setAttribute('href', `data:text/csv;charset=UTF-8,${encodeURIComponent(data)}`);
|
||||||
link.setAttribute('download', filename);
|
link.setAttribute('download', filename);
|
||||||
link.style.display = 'none';
|
link.style.display = 'none';
|
||||||
document.body.appendChild(link);
|
document.body.appendChild(link);
|
||||||
@ -87,14 +87,19 @@ class App extends React.Component {
|
|||||||
const cDecoded = decodeURIComponent(document.cookie);
|
const cDecoded = decodeURIComponent(document.cookie);
|
||||||
const cArr = cDecoded.split('; ');
|
const cArr = cDecoded.split('; ');
|
||||||
cArr.forEach((val) => {
|
cArr.forEach((val) => {
|
||||||
if (val.indexOf(`${cookieName}=`) === 0) learningDashboardAccessToken = val.substring((`${cookieName}=`).length);
|
if (val.indexOf(`${cookieName}=`) === 0) {
|
||||||
|
learningDashboardAccessToken = val.substring((`${cookieName}=`).length);
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
// Extend AccessToken lifetime by 7d (in each access)
|
// Extend AccessToken lifetime by 7d (in each access)
|
||||||
if (learningDashboardAccessToken !== '') {
|
if (learningDashboardAccessToken !== '') {
|
||||||
const cookieExpiresDate = new Date();
|
const cookieExpiresDate = new Date();
|
||||||
cookieExpiresDate.setTime(cookieExpiresDate.getTime() + (3600000 * 24 * 7));
|
cookieExpiresDate.setTime(cookieExpiresDate.getTime() + (3600000 * 24 * 7));
|
||||||
document.cookie = `ld-${meetingId}=${learningDashboardAccessToken}; expires=${cookieExpiresDate.toGMTString()}; path=/;SameSite=None;Secure`;
|
const value = `ld-${meetingId}=${learningDashboardAccessToken};`;
|
||||||
|
const expire = `expires=${cookieExpiresDate.toGMTString()};`;
|
||||||
|
const args = 'path=/;SameSite=None;Secure';
|
||||||
|
document.cookie = `${value} ${expire} ${args}`;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -34,7 +34,12 @@ function Card(props) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className={`flex items-start justify-between p-3 bg-white rounded shadow border-l-4 ${cardClass}`}>
|
<div
|
||||||
|
className={
|
||||||
|
'flex items-start justify-between p-3 bg-white rounded shadow border-l-4'
|
||||||
|
+ ` ${cardClass}`
|
||||||
|
}
|
||||||
|
>
|
||||||
<div className="w-70">
|
<div className="w-70">
|
||||||
<p className="text-lg font-semibold text-gray-700">
|
<p className="text-lg font-semibold text-gray-700">
|
||||||
{ number }
|
{ number }
|
||||||
|
@ -104,6 +104,61 @@ class StatusTable extends React.Component {
|
|||||||
|
|
||||||
const isRTL = document.dir === 'rtl';
|
const isRTL = document.dir === 'rtl';
|
||||||
|
|
||||||
|
function makeLineThrough(userPeriod, period) {
|
||||||
|
const { registeredOn, leftOn } = userPeriod;
|
||||||
|
const boundaryLeft = period.start;
|
||||||
|
const boundaryRight = period.end;
|
||||||
|
const interval = period.end - period.start;
|
||||||
|
let roundedLeft = registeredOn >= boundaryLeft
|
||||||
|
&& registeredOn <= boundaryRight ? 'rounded-l' : '';
|
||||||
|
let roundedRight = leftOn >= boundaryLeft
|
||||||
|
&& leftOn <= boundaryRight ? 'rounded-r' : '';
|
||||||
|
let offsetLeft = 0;
|
||||||
|
let offsetRight = 0;
|
||||||
|
if (registeredOn >= boundaryLeft && registeredOn <= boundaryRight) {
|
||||||
|
offsetLeft = ((registeredOn - boundaryLeft) * 100) / interval;
|
||||||
|
}
|
||||||
|
if (leftOn >= boundaryLeft && leftOn <= boundaryRight) {
|
||||||
|
offsetRight = ((boundaryRight - leftOn) * 100) / interval;
|
||||||
|
}
|
||||||
|
let width = '';
|
||||||
|
if (offsetLeft === 0 && offsetRight >= 99) {
|
||||||
|
width = 'w-1.5';
|
||||||
|
}
|
||||||
|
if (offsetRight === 0 && offsetLeft >= 99) {
|
||||||
|
width = 'w-1.5';
|
||||||
|
}
|
||||||
|
if (offsetLeft && offsetRight) {
|
||||||
|
const variation = offsetLeft - offsetRight;
|
||||||
|
if (variation > -1 && variation < 1) {
|
||||||
|
width = 'w-1.5';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (isRTL) {
|
||||||
|
const aux = roundedRight;
|
||||||
|
|
||||||
|
if (roundedLeft !== '') roundedRight = 'rounded-r';
|
||||||
|
else roundedRight = '';
|
||||||
|
|
||||||
|
if (aux !== '') roundedLeft = 'rounded-l';
|
||||||
|
else roundedLeft = '';
|
||||||
|
}
|
||||||
|
const redress = '(0.375rem / 2)';
|
||||||
|
return (
|
||||||
|
<div
|
||||||
|
className={
|
||||||
|
'h-1.5 bg-gray-200 absolute inset-x-0 z-10'
|
||||||
|
+ ` ${width} ${roundedLeft} ${roundedRight}`
|
||||||
|
}
|
||||||
|
style={{
|
||||||
|
top: `calc(50% - ${redress})`,
|
||||||
|
left: `${isRTL ? offsetRight : offsetLeft}%`,
|
||||||
|
right: `${isRTL ? offsetLeft : offsetRight}%`,
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<table className="w-full">
|
<table className="w-full">
|
||||||
<thead>
|
<thead>
|
||||||
@ -134,6 +189,8 @@ class StatusTable extends React.Component {
|
|||||||
{ periods.map((period) => {
|
{ periods.map((period) => {
|
||||||
const { slide, start, end } = period;
|
const { slide, start, end } = period;
|
||||||
const padding = isRTL ? 'paddingLeft' : 'paddingRight';
|
const padding = isRTL ? 'paddingLeft' : 'paddingRight';
|
||||||
|
const URLPrefix = `/bigbluebutton/presentation/${meetingId}/${meetingId}`;
|
||||||
|
const { presentationId, pageNum } = slide || {};
|
||||||
return (
|
return (
|
||||||
<td
|
<td
|
||||||
style={{
|
style={{
|
||||||
@ -147,13 +204,13 @@ class StatusTable extends React.Component {
|
|||||||
aria-label={tsToHHmmss(start - periods[0].start)}
|
aria-label={tsToHHmmss(start - periods[0].start)}
|
||||||
>
|
>
|
||||||
<a
|
<a
|
||||||
href={`/bigbluebutton/presentation/${meetingId}/${meetingId}/${slide.presentationId}/svg/${slide.pageNum}`}
|
href={`${URLPrefix}/${presentationId}/svg/${pageNum}`}
|
||||||
className="block border-2 border-gray-300"
|
className="block border-2 border-gray-300"
|
||||||
target="_blank"
|
target="_blank"
|
||||||
rel="noreferrer"
|
rel="noreferrer"
|
||||||
>
|
>
|
||||||
<img
|
<img
|
||||||
src={`/bigbluebutton/presentation/${meetingId}/${meetingId}/${slide.presentationId}/thumbnail/${slide.pageNum}`}
|
src={`${URLPrefix}/${presentationId}/thumbnail/${pageNum}`}
|
||||||
alt={intl.formatMessage({
|
alt={intl.formatMessage({
|
||||||
id: 'app.learningDashboard.statusTimelineTable.thumbnail',
|
id: 'app.learningDashboard.statusTimelineTable.thumbnail',
|
||||||
defaultMessage: 'Presentation thumbnail',
|
defaultMessage: 'Presentation thumbnail',
|
||||||
@ -216,57 +273,9 @@ class StatusTable extends React.Component {
|
|||||||
{ (registeredOn >= boundaryLeft && registeredOn <= boundaryRight)
|
{ (registeredOn >= boundaryLeft && registeredOn <= boundaryRight)
|
||||||
|| (leftOn >= boundaryLeft && leftOn <= boundaryRight)
|
|| (leftOn >= boundaryLeft && leftOn <= boundaryRight)
|
||||||
|| (boundaryLeft > registeredOn && boundaryRight < leftOn)
|
|| (boundaryLeft > registeredOn && boundaryRight < leftOn)
|
||||||
|| (boundaryLeft >= registeredOn && leftOn === 0) ? (
|
|| (boundaryLeft >= registeredOn && leftOn === 0)
|
||||||
(function makeLineThrough() {
|
? makeLineThrough(userPeriod, period)
|
||||||
let roundedLeft = registeredOn >= boundaryLeft
|
: null }
|
||||||
&& registeredOn <= boundaryRight ? 'rounded-l' : '';
|
|
||||||
let roundedRight = leftOn >= boundaryLeft
|
|
||||||
&& leftOn <= boundaryRight ? 'rounded-r' : '';
|
|
||||||
let offsetLeft = 0;
|
|
||||||
let offsetRight = 0;
|
|
||||||
if (registeredOn >= boundaryLeft
|
|
||||||
&& registeredOn <= boundaryRight) {
|
|
||||||
offsetLeft = ((registeredOn - boundaryLeft) * 100)
|
|
||||||
/ interval;
|
|
||||||
}
|
|
||||||
if (leftOn >= boundaryLeft && leftOn <= boundaryRight) {
|
|
||||||
offsetRight = ((boundaryRight - leftOn) * 100) / interval;
|
|
||||||
}
|
|
||||||
let width = '';
|
|
||||||
if (offsetLeft === 0 && offsetRight >= 99) {
|
|
||||||
width = 'w-1.5';
|
|
||||||
}
|
|
||||||
if (offsetRight === 0 && offsetLeft >= 99) {
|
|
||||||
width = 'w-1.5';
|
|
||||||
}
|
|
||||||
if (offsetLeft && offsetRight) {
|
|
||||||
const variation = offsetLeft - offsetRight;
|
|
||||||
if (variation > -1 && variation < 1) {
|
|
||||||
width = 'w-1.5';
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (isRTL) {
|
|
||||||
const aux = roundedRight;
|
|
||||||
|
|
||||||
if (roundedLeft !== '') roundedRight = 'rounded-r';
|
|
||||||
else roundedRight = '';
|
|
||||||
|
|
||||||
if (aux !== '') roundedLeft = 'rounded-l';
|
|
||||||
else roundedLeft = '';
|
|
||||||
}
|
|
||||||
const redress = '(0.375rem / 2)';
|
|
||||||
return (
|
|
||||||
<div
|
|
||||||
className={`h-1.5 ${width} bg-gray-200 absolute inset-x-0 z-10 ${roundedLeft} ${roundedRight}`}
|
|
||||||
style={{
|
|
||||||
top: `calc(50% - ${redress})`,
|
|
||||||
left: `${isRTL ? offsetRight : offsetLeft}%`,
|
|
||||||
right: `${isRTL ? offsetLeft : offsetRight}%`,
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
);
|
|
||||||
})()
|
|
||||||
) : null }
|
|
||||||
{ userEmojisInPeriod.map((emoji) => {
|
{ userEmojisInPeriod.map((emoji) => {
|
||||||
const offset = ((emoji.sentOn - period.start) * 100)
|
const offset = ((emoji.sentOn - period.start) * 100)
|
||||||
/ (interval);
|
/ (interval);
|
||||||
@ -285,7 +294,12 @@ class StatusTable extends React.Component {
|
|||||||
defaultMessage: emojiConfigs[emoji.name].defaultMessage,
|
defaultMessage: emojiConfigs[emoji.name].defaultMessage,
|
||||||
})}
|
})}
|
||||||
>
|
>
|
||||||
<i className={`${emojiConfigs[emoji.name].icon} text-sm bbb-icon-timeline`} />
|
<i
|
||||||
|
className={
|
||||||
|
'text-sm bbb-icon-timeline'
|
||||||
|
+ ` ${emojiConfigs[emoji.name].icon}`
|
||||||
|
}
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}) }
|
}) }
|
||||||
|
@ -176,6 +176,11 @@ const UserDatailsComponent = (props) => {
|
|||||||
if (hasDraw) mostCommonAnswer = null;
|
if (hasDraw) mostCommonAnswer = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const capitalizeFirstLetter = (text) => (
|
||||||
|
String.fromCharCode(text.charCodeAt(0) - 32)
|
||||||
|
+ text.substring(1)
|
||||||
|
);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="p-6 flex flex-row justify-between items-center">
|
<div className="p-6 flex flex-row justify-between items-center">
|
||||||
<div className="min-w-[40%] text-ellipsis">{question}</div>
|
<div className="min-w-[40%] text-ellipsis">{question}</div>
|
||||||
@ -211,11 +216,11 @@ const UserDatailsComponent = (props) => {
|
|||||||
<div
|
<div
|
||||||
className="min-w-[40%] text-ellipsis text-center overflow-hidden"
|
className="min-w-[40%] text-ellipsis text-center overflow-hidden"
|
||||||
title={mostCommonAnswer
|
title={mostCommonAnswer
|
||||||
? `${String.fromCharCode(mostCommonAnswer.charCodeAt(0) - 32)}${mostCommonAnswer.substring(1)}`
|
? capitalizeFirstLetter(mostCommonAnswer)
|
||||||
: null}
|
: null}
|
||||||
>
|
>
|
||||||
{ mostCommonAnswer
|
{ mostCommonAnswer
|
||||||
? `${String.fromCharCode(mostCommonAnswer.charCodeAt(0) - 32)}${mostCommonAnswer.substring(1)}`
|
? capitalizeFirstLetter(mostCommonAnswer)
|
||||||
: intl.formatMessage({
|
: intl.formatMessage({
|
||||||
id: 'app.learningDashboard.usersTable.notAvailable',
|
id: 'app.learningDashboard.usersTable.notAvailable',
|
||||||
defaultMessage: 'N/A',
|
defaultMessage: 'N/A',
|
||||||
@ -233,7 +238,7 @@ const UserDatailsComponent = (props) => {
|
|||||||
<div className="min-w-[20%] text-ellipsis overflow-hidden">{category}</div>
|
<div className="min-w-[20%] text-ellipsis overflow-hidden">{category}</div>
|
||||||
<div className="min-w-[60%] grow text-center text-sm">
|
<div className="min-w-[60%] grow text-center text-sm">
|
||||||
<div className="mb-2">
|
<div className="mb-2">
|
||||||
{ (function () {
|
{ (function getAverage() {
|
||||||
if (average >= 0 && category === 'Talk Time') return tsToHHmmss(average);
|
if (average >= 0 && category === 'Talk Time') return tsToHHmmss(average);
|
||||||
if (average >= 0 && category !== 'Talk Time') return <FormattedNumber value={average} minimumFractionDigits="0" maximumFractionDigits="1" />;
|
if (average >= 0 && category !== 'Talk Time') return <FormattedNumber value={average} minimumFractionDigits="0" maximumFractionDigits="1" />;
|
||||||
return <FormattedMessage id="app.learningDashboard.usersTable.notAvailable" defaultMessage="N/A" />;
|
return <FormattedMessage id="app.learningDashboard.usersTable.notAvailable" defaultMessage="N/A" />;
|
||||||
|
@ -1 +1 @@
|
|||||||
git clone --branch v1.0.2 --depth 1 https://github.com/bigbluebutton/bbb-pads bbb-pads
|
git clone --branch v1.2.0 --depth 1 https://github.com/bigbluebutton/bbb-pads bbb-pads
|
||||||
|
@ -1 +1 @@
|
|||||||
git clone --branch v3.4.0 --depth 1 https://github.com/bigbluebutton/bbb-playback bbb-playback
|
git clone --branch v4.0.0 --depth 1 https://github.com/bigbluebutton/bbb-playback bbb-playback
|
||||||
|
@ -3,7 +3,7 @@
|
|||||||
<condition field="${bbb_authorized}" expression="true" break="on-false"/>
|
<condition field="${bbb_authorized}" expression="true" break="on-false"/>
|
||||||
<condition field="${sip_via_protocol}" expression="^wss?$"/>
|
<condition field="${sip_via_protocol}" expression="^wss?$"/>
|
||||||
<condition field="destination_number" expression="^(\d{5,11})$">
|
<condition field="destination_number" expression="^(\d{5,11})$">
|
||||||
<action application="jitterbuffer" data="60:120"/>
|
<action application="jitterbuffer" data="120"/>
|
||||||
<action application="answer"/>
|
<action application="answer"/>
|
||||||
<action application="conference" data="$1@cdquality"/>
|
<action application="conference" data="$1@cdquality"/>
|
||||||
</condition>
|
</condition>
|
||||||
@ -11,7 +11,7 @@
|
|||||||
<extension name="bbb_conferences">
|
<extension name="bbb_conferences">
|
||||||
<condition field="${bbb_authorized}" expression="true" break="on-false"/>
|
<condition field="${bbb_authorized}" expression="true" break="on-false"/>
|
||||||
<condition field="destination_number" expression="^(\d{5,11})$">
|
<condition field="destination_number" expression="^(\d{5,11})$">
|
||||||
<action application="jitterbuffer" data="60:120"/>
|
<action application="jitterbuffer" data="120"/>
|
||||||
<action application="answer"/>
|
<action application="answer"/>
|
||||||
<action application="conference" data="$1@cdquality"/>
|
<action application="conference" data="$1@cdquality"/>
|
||||||
</condition>
|
</condition>
|
||||||
|
@ -2,7 +2,7 @@
|
|||||||
<extension name="ECHO_TO_CONFERENCE">
|
<extension name="ECHO_TO_CONFERENCE">
|
||||||
<condition field="${bbb_from_echo}" expression="true" break="on-false"/>
|
<condition field="${bbb_from_echo}" expression="true" break="on-false"/>
|
||||||
<condition field="destination_number" expression="^(ECHO_TO_CONFERENCE)$">
|
<condition field="destination_number" expression="^(ECHO_TO_CONFERENCE)$">
|
||||||
<action application="jitterbuffer" data="60:120"/>
|
<action application="jitterbuffer" data="120"/>
|
||||||
<action application="answer"/>
|
<action application="answer"/>
|
||||||
<action application="conference" data="${vbridge}@cdquality"/>
|
<action application="conference" data="${vbridge}@cdquality"/>
|
||||||
</condition>
|
</condition>
|
||||||
|
@ -1 +1 @@
|
|||||||
git clone --branch v2.8.0-alpha.1 --depth 1 https://github.com/bigbluebutton/bbb-webrtc-sfu bbb-webrtc-sfu
|
git clone --branch v2.8.1 --depth 1 https://github.com/bigbluebutton/bbb-webrtc-sfu bbb-webrtc-sfu
|
||||||
|
@ -1 +1 @@
|
|||||||
BIGBLUEBUTTON_RELEASE=2.5.0-beta.1
|
BIGBLUEBUTTON_RELEASE=2.5.0-rc.2
|
||||||
|
@ -658,8 +658,10 @@ fi
|
|||||||
|
|
||||||
if [[ $SECRET ]]; then
|
if [[ $SECRET ]]; then
|
||||||
need_root
|
need_root
|
||||||
if get_properties_value securitySalt "$BBB_WEB_ETC_CONFIG" > /dev/null ; then
|
|
||||||
change_var_salt "$BBB_WEB_ETC_CONFIG" securitySalt "$SECRET"
|
echo "Assigning secret in $BBB_WEB_ETC_CONFIG"
|
||||||
|
if [ -f "$BBB_WEB_ETC_CONFIG" ] && grep "^securitySalt" "$BBB_WEB_ETC_CONFIG" > /dev/null ; then
|
||||||
|
change_var_value "$BBB_WEB_ETC_CONFIG" securitySalt "$SECRET"
|
||||||
else
|
else
|
||||||
echo "securitySalt=$SECRET" >> "$BBB_WEB_ETC_CONFIG"
|
echo "securitySalt=$SECRET" >> "$BBB_WEB_ETC_CONFIG"
|
||||||
fi
|
fi
|
||||||
@ -1296,19 +1298,6 @@ check_state() {
|
|||||||
done
|
done
|
||||||
fi
|
fi
|
||||||
|
|
||||||
stunServerAddress=$(cat /etc/kurento/modules/kurento/WebRtcEndpoint.conf.ini | sed -n '/^stunServerAddress/{s/.*=//;p}')
|
|
||||||
stunServerPort=$(cat /etc/kurento/modules/kurento/WebRtcEndpoint.conf.ini | sed -n '/^stunServerPort/{s/.*=//;p}')
|
|
||||||
if [ ! -z "$stunServerAddress" ]; then
|
|
||||||
if stunclient --mode full --localport 30000 $stunServerAddress $stunServerPort | grep -q "fail\|Unable\ to\ resolve"; then
|
|
||||||
echo
|
|
||||||
echo "#"
|
|
||||||
echo "# Warning: Failed to verify STUN server at $stunServerAddress:$stunServerPort with command"
|
|
||||||
echo "#"
|
|
||||||
echo "# stunclient --mode full --localport 30000 $stunServerAddress $stunServerPort"
|
|
||||||
echo "#"
|
|
||||||
fi
|
|
||||||
fi
|
|
||||||
|
|
||||||
BBB_LOG="/var/log/bigbluebutton"
|
BBB_LOG="/var/log/bigbluebutton"
|
||||||
if [ "$(stat -c "%U %G" $BBB_LOG)" != "bigbluebutton bigbluebutton" ]; then
|
if [ "$(stat -c "%U %G" $BBB_LOG)" != "bigbluebutton bigbluebutton" ]; then
|
||||||
echo
|
echo
|
||||||
@ -1476,14 +1465,6 @@ if [ $CHECK ]; then
|
|||||||
done
|
done
|
||||||
fi
|
fi
|
||||||
|
|
||||||
stunServerAddress=$(cat /etc/kurento/modules/kurento/WebRtcEndpoint.conf.ini | sed -n '/^stunServerAddress/{s/.*=//;p}')
|
|
||||||
stunServerPort=$(cat /etc/kurento/modules/kurento/WebRtcEndpoint.conf.ini | sed -n '/^stunServerPort/{s/.*=//;p}')
|
|
||||||
if [ ! -z "$stunServerAddress" ]; then
|
|
||||||
echo
|
|
||||||
echo "/etc/kurento/modules/kurento/WebRtcEndpoint.conf.ini (STUN Server)"
|
|
||||||
echo " stun: $stunServerAddress:$stunServerPort"
|
|
||||||
fi
|
|
||||||
|
|
||||||
check_state
|
check_state
|
||||||
echo
|
echo
|
||||||
|
|
||||||
@ -1633,7 +1614,12 @@ if [ -n "$HOST" ]; then
|
|||||||
if [ -f "$BBB_WEB_ETC_CONFIG" ] && grep "bigbluebutton.web.serverURL" "$BBB_WEB_ETC_CONFIG" > /dev/null ; then
|
if [ -f "$BBB_WEB_ETC_CONFIG" ] && grep "bigbluebutton.web.serverURL" "$BBB_WEB_ETC_CONFIG" > /dev/null ; then
|
||||||
change_var_value "$BBB_WEB_ETC_CONFIG" bigbluebutton.web.serverURL "$PROTOCOL://$HOST"
|
change_var_value "$BBB_WEB_ETC_CONFIG" bigbluebutton.web.serverURL "$PROTOCOL://$HOST"
|
||||||
else
|
else
|
||||||
echo "bigbluebutton.web.serverURL=$PROTOCOL://$HOST" > "$BBB_WEB_ETC_CONFIG"
|
echo "bigbluebutton.web.serverURL=$PROTOCOL://$HOST" >> "$BBB_WEB_ETC_CONFIG"
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Populate /etc/bigbluebutton/bbb-web.properites with the shared secret
|
||||||
|
if ! grep -q "^securitySalt" "$BBB_WEB_ETC_CONFIG"; then
|
||||||
|
echo "securitySalt=$(get_bbb_web_config_value securitySalt)" >> "$BBB_WEB_ETC_CONFIG"
|
||||||
fi
|
fi
|
||||||
|
|
||||||
|
|
||||||
|
@ -1,2 +0,0 @@
|
|||||||
Dockerfile
|
|
||||||
Dockerfile.dev
|
|
@ -8,10 +8,10 @@ mobile-experience@1.1.0
|
|||||||
mongo@1.14.6
|
mongo@1.14.6
|
||||||
reactive-var@1.0.11
|
reactive-var@1.0.11
|
||||||
|
|
||||||
standard-minifier-css@1.7.4
|
standard-minifier-css@1.8.1
|
||||||
standard-minifier-js@2.8.0
|
standard-minifier-js@2.8.0
|
||||||
es5-shim@4.8.0
|
es5-shim@4.8.0
|
||||||
ecmascript@0.16.1
|
ecmascript@0.16.2
|
||||||
shell-server@0.5.0
|
shell-server@0.5.0
|
||||||
|
|
||||||
static-html@1.3.2
|
static-html@1.3.2
|
||||||
|
@ -1 +1 @@
|
|||||||
METEOR@2.6.1
|
METEOR@2.7.1
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
allow-deny@1.1.1
|
allow-deny@1.1.1
|
||||||
autoupdate@1.8.0
|
autoupdate@1.8.0
|
||||||
babel-compiler@7.8.1
|
babel-compiler@7.9.0
|
||||||
babel-runtime@1.5.0
|
babel-runtime@1.5.0
|
||||||
base64@1.0.12
|
base64@1.0.12
|
||||||
binary-heap@1.0.11
|
binary-heap@1.0.11
|
||||||
@ -16,11 +16,11 @@ ddp-common@1.4.0
|
|||||||
ddp-server@2.5.0
|
ddp-server@2.5.0
|
||||||
diff-sequence@1.1.1
|
diff-sequence@1.1.1
|
||||||
dynamic-import@0.7.2
|
dynamic-import@0.7.2
|
||||||
ecmascript@0.16.1
|
ecmascript@0.16.2
|
||||||
ecmascript-runtime@0.8.0
|
ecmascript-runtime@0.8.0
|
||||||
ecmascript-runtime-client@0.12.1
|
ecmascript-runtime-client@0.12.1
|
||||||
ecmascript-runtime-server@0.11.0
|
ecmascript-runtime-server@0.11.0
|
||||||
ejson@1.1.1
|
ejson@1.1.2
|
||||||
es5-shim@4.8.0
|
es5-shim@4.8.0
|
||||||
fetch@0.1.1
|
fetch@0.1.1
|
||||||
geojson-utils@1.0.10
|
geojson-utils@1.0.10
|
||||||
@ -39,13 +39,13 @@ meteortesting:browser-tests@1.3.5
|
|||||||
meteortesting:mocha@2.0.3
|
meteortesting:mocha@2.0.3
|
||||||
meteortesting:mocha-core@8.1.2
|
meteortesting:mocha-core@8.1.2
|
||||||
minifier-css@1.6.0
|
minifier-css@1.6.0
|
||||||
minifier-js@2.7.3
|
minifier-js@2.7.4
|
||||||
minimongo@1.8.0
|
minimongo@1.8.0
|
||||||
mobile-experience@1.1.0
|
mobile-experience@1.1.0
|
||||||
mobile-status-bar@1.1.0
|
mobile-status-bar@1.1.0
|
||||||
modern-browsers@0.1.7
|
modern-browsers@0.1.7
|
||||||
modules@0.18.0
|
modules@0.18.0
|
||||||
modules-runtime@0.12.0
|
modules-runtime@0.13.0
|
||||||
mongo@1.14.6
|
mongo@1.14.6
|
||||||
mongo-decimal@0.1.2
|
mongo-decimal@0.1.2
|
||||||
mongo-dev-server@1.1.0
|
mongo-dev-server@1.1.0
|
||||||
@ -54,7 +54,7 @@ npm-mongo@4.3.1
|
|||||||
ordered-dict@1.1.0
|
ordered-dict@1.1.0
|
||||||
promise@0.12.0
|
promise@0.12.0
|
||||||
random@1.2.0
|
random@1.2.0
|
||||||
react-fast-refresh@0.2.2
|
react-fast-refresh@0.2.3
|
||||||
react-meteor-data@2.4.0
|
react-meteor-data@2.4.0
|
||||||
reactive-dict@1.3.0
|
reactive-dict@1.3.0
|
||||||
reactive-var@1.0.11
|
reactive-var@1.0.11
|
||||||
@ -66,12 +66,12 @@ session@1.2.0
|
|||||||
shell-server@0.5.0
|
shell-server@0.5.0
|
||||||
socket-stream-client@0.4.0
|
socket-stream-client@0.4.0
|
||||||
spacebars-compiler@1.3.0
|
spacebars-compiler@1.3.0
|
||||||
standard-minifier-css@1.7.4
|
standard-minifier-css@1.8.1
|
||||||
standard-minifier-js@2.8.0
|
standard-minifier-js@2.8.0
|
||||||
static-html@1.3.2
|
static-html@1.3.2
|
||||||
templating-tools@1.2.1
|
templating-tools@1.2.1
|
||||||
tracker@1.2.0
|
tracker@1.2.0
|
||||||
typescript@4.4.1
|
typescript@4.5.4
|
||||||
underscore@1.0.10
|
underscore@1.0.10
|
||||||
url@1.3.2
|
url@1.3.2
|
||||||
webapp@1.13.1
|
webapp@1.13.1
|
||||||
|
@ -1,38 +0,0 @@
|
|||||||
FROM node:8
|
|
||||||
|
|
||||||
RUN set -x \
|
|
||||||
&& curl -sL https://install.meteor.com | sed s/--progress-bar/-sL/g | /bin/sh \
|
|
||||||
&& useradd -m -G users -s /bin/bash meteor
|
|
||||||
|
|
||||||
RUN apt-get update && apt-get -y install jq
|
|
||||||
|
|
||||||
COPY . /source
|
|
||||||
|
|
||||||
RUN cd /source \
|
|
||||||
&& mv docker-entrypoint.sh /usr/local/bin/ \
|
|
||||||
&& chown -R meteor:meteor . \
|
|
||||||
&& mkdir /app \
|
|
||||||
&& chown -R meteor:meteor /app
|
|
||||||
|
|
||||||
USER meteor
|
|
||||||
|
|
||||||
RUN cd /source \
|
|
||||||
&& meteor npm install \
|
|
||||||
&& meteor build --directory /app
|
|
||||||
|
|
||||||
ENV NODE_ENV production
|
|
||||||
|
|
||||||
RUN cd /app/bundle/programs/server \
|
|
||||||
&& npm install \
|
|
||||||
&& npm cache clear --force
|
|
||||||
|
|
||||||
WORKDIR /app/bundle
|
|
||||||
|
|
||||||
ENV MONGO_URL=mongodb://mongo:27017/html5client \
|
|
||||||
PORT=3000 \
|
|
||||||
ROOT_URL=http://localhost:3000 \
|
|
||||||
METEOR_SETTINGS_MODIFIER=.
|
|
||||||
|
|
||||||
EXPOSE 3000
|
|
||||||
|
|
||||||
CMD ["docker-entrypoint.sh"]
|
|
@ -1,24 +0,0 @@
|
|||||||
FROM node:8
|
|
||||||
|
|
||||||
COPY . /source
|
|
||||||
|
|
||||||
RUN set -x \
|
|
||||||
&& curl -sL https://install.meteor.com | sed s/--progress-bar/-sL/g | /bin/sh \
|
|
||||||
&& useradd -m -G users -s /bin/bash meteor \
|
|
||||||
&& chown -R meteor:meteor /source
|
|
||||||
|
|
||||||
USER meteor
|
|
||||||
|
|
||||||
RUN cd /source \
|
|
||||||
&& meteor npm install
|
|
||||||
|
|
||||||
WORKDIR /source
|
|
||||||
|
|
||||||
ENV MONGO_URL=mongodb://mongo:27017/html5client \
|
|
||||||
PORT=3000 \
|
|
||||||
ROOT_URL=http://localhost:3000
|
|
||||||
|
|
||||||
EXPOSE 3000
|
|
||||||
|
|
||||||
CMD ["npm", "start"]
|
|
||||||
|
|
@ -1,5 +0,0 @@
|
|||||||
#!/bin/bash -e
|
|
||||||
|
|
||||||
export METEOR_SETTINGS=` jq "${METEOR_SETTINGS_MODIFIER}" ./programs/server/assets/app/config/settings.yml `
|
|
||||||
|
|
||||||
exec node main.js
|
|
@ -1,4 +1,9 @@
|
|||||||
|
import { PrometheusAgent, METRIC_NAMES } from '/imports/startup/server/prom-metrics/index.js'
|
||||||
|
|
||||||
// Round-trip time helper
|
// Round-trip time helper
|
||||||
export default function voidConnection() {
|
export default function voidConnection(previousRtt) {
|
||||||
|
if (previousRtt) {
|
||||||
|
PrometheusAgent.observe(METRIC_NAMES.METEOR_RTT, previousRtt/1000);
|
||||||
|
}
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
@ -139,10 +139,13 @@ export default function addMeeting(meeting) {
|
|||||||
const sanitizeTextInChat = original => SanitizeHTML(original, {
|
const sanitizeTextInChat = original => SanitizeHTML(original, {
|
||||||
allowedTags: ['a', 'b', 'br', 'i', 'img', 'li', 'small', 'span', 'strong', 'u', 'ul'],
|
allowedTags: ['a', 'b', 'br', 'i', 'img', 'li', 'small', 'span', 'strong', 'u', 'ul'],
|
||||||
allowedAttributes: {
|
allowedAttributes: {
|
||||||
a: ['href', 'name', 'target'],
|
a: ['href', 'target'],
|
||||||
img: ['src', 'width', 'height'],
|
img: ['src', 'width', 'height'],
|
||||||
},
|
},
|
||||||
allowedSchemes: ['https'],
|
allowedSchemes: ['https'],
|
||||||
|
allowedSchemesByTag: {
|
||||||
|
a: ['https', 'mailto', 'tel']
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
const sanitizedWelcomeText = sanitizeTextInChat(welcomeMsg);
|
const sanitizedWelcomeText = sanitizeTextInChat(welcomeMsg);
|
||||||
@ -153,14 +156,18 @@ export default function addMeeting(meeting) {
|
|||||||
|
|
||||||
const insertBlankTarget = (s, i) => `${s.substr(0, i)} target="_blank"${s.substr(i)}`;
|
const insertBlankTarget = (s, i) => `${s.substr(0, i)} target="_blank"${s.substr(i)}`;
|
||||||
const linkWithoutTarget = new RegExp('<a href="(.*?)">', 'g');
|
const linkWithoutTarget = new RegExp('<a href="(.*?)">', 'g');
|
||||||
linkWithoutTarget.test(welcomeMsg);
|
|
||||||
|
|
||||||
if (linkWithoutTarget.lastIndex > 0) {
|
do {
|
||||||
welcomeMsg = insertBlankTarget(
|
linkWithoutTarget.test(welcomeMsg);
|
||||||
welcomeMsg,
|
|
||||||
linkWithoutTarget.lastIndex - 1,
|
if (linkWithoutTarget.lastIndex > 0) {
|
||||||
);
|
welcomeMsg = insertBlankTarget(
|
||||||
}
|
welcomeMsg,
|
||||||
|
linkWithoutTarget.lastIndex - 1,
|
||||||
|
);
|
||||||
|
linkWithoutTarget.lastIndex = linkWithoutTarget.lastIndex - 1;
|
||||||
|
}
|
||||||
|
} while (linkWithoutTarget.lastIndex > 0);
|
||||||
|
|
||||||
newMeeting.welcomeProp.welcomeMsg = welcomeMsg;
|
newMeeting.welcomeProp.welcomeMsg = welcomeMsg;
|
||||||
|
|
||||||
|
@ -1,23 +1,26 @@
|
|||||||
import { check } from 'meteor/check';
|
import { check } from 'meteor/check';
|
||||||
import Logger from '/imports/startup/server/logger';
|
import Logger from '/imports/startup/server/logger';
|
||||||
import PresentationUploadToken from '/imports/api/presentation-upload-token';
|
import PresentationUploadToken from '/imports/api/presentation-upload-token';
|
||||||
|
import Presentations from '/imports/api/presentations';
|
||||||
|
|
||||||
export default function handlePresentationUploadTokenPass({ body, header }, meetingId) {
|
export default function handlePresentationUploadTokenPass({ body, header }, meetingId) {
|
||||||
check(body, Object);
|
check(body, Object);
|
||||||
|
|
||||||
const { userId } = header;
|
const { userId } = header;
|
||||||
const { podId, authzToken, filename } = body;
|
const { podId, authzToken, filename, tmpPresId } = body;
|
||||||
|
|
||||||
check(userId, String);
|
check(userId, String);
|
||||||
check(podId, String);
|
check(podId, String);
|
||||||
check(authzToken, String);
|
check(authzToken, String);
|
||||||
check(filename, String);
|
check(filename, String);
|
||||||
|
check(tmpPresId, String)
|
||||||
|
|
||||||
const selector = {
|
const selector = {
|
||||||
meetingId,
|
meetingId,
|
||||||
podId,
|
podId,
|
||||||
userId,
|
userId,
|
||||||
filename,
|
filename,
|
||||||
|
tmpPresId,
|
||||||
};
|
};
|
||||||
|
|
||||||
const modifier = {
|
const modifier = {
|
||||||
@ -26,6 +29,7 @@ export default function handlePresentationUploadTokenPass({ body, header }, meet
|
|||||||
userId,
|
userId,
|
||||||
filename,
|
filename,
|
||||||
authzToken,
|
authzToken,
|
||||||
|
tmpPresId,
|
||||||
failed: false,
|
failed: false,
|
||||||
used: false,
|
used: false,
|
||||||
};
|
};
|
||||||
|
@ -3,7 +3,7 @@ import { check } from 'meteor/check';
|
|||||||
import { extractCredentials } from '/imports/api/common/server/helpers';
|
import { extractCredentials } from '/imports/api/common/server/helpers';
|
||||||
import Logger from '/imports/startup/server/logger';
|
import Logger from '/imports/startup/server/logger';
|
||||||
|
|
||||||
export default function requestPresentationUploadToken(podId, filename) {
|
export default function requestPresentationUploadToken(podId, filename, tmpPresId) {
|
||||||
const REDIS_CONFIG = Meteor.settings.private.redis;
|
const REDIS_CONFIG = Meteor.settings.private.redis;
|
||||||
const CHANNEL = REDIS_CONFIG.channels.toAkkaApps;
|
const CHANNEL = REDIS_CONFIG.channels.toAkkaApps;
|
||||||
const EVENT_NAME = 'PresentationUploadTokenReqMsg';
|
const EVENT_NAME = 'PresentationUploadTokenReqMsg';
|
||||||
@ -15,10 +15,12 @@ export default function requestPresentationUploadToken(podId, filename) {
|
|||||||
check(requesterUserId, String);
|
check(requesterUserId, String);
|
||||||
check(podId, String);
|
check(podId, String);
|
||||||
check(filename, String);
|
check(filename, String);
|
||||||
|
check(tmpPresId, String);
|
||||||
|
|
||||||
const payload = {
|
const payload = {
|
||||||
podId,
|
podId,
|
||||||
filename,
|
filename,
|
||||||
|
tmpPresId
|
||||||
};
|
};
|
||||||
|
|
||||||
RedisPubSub.publishUserMessage(CHANNEL, EVENT_NAME, meetingId, requesterUserId, payload);
|
RedisPubSub.publishUserMessage(CHANNEL, EVENT_NAME, meetingId, requesterUserId, payload);
|
||||||
|
@ -4,7 +4,7 @@ import PresentationUploadToken from '/imports/api/presentation-upload-token';
|
|||||||
import Logger from '/imports/startup/server/logger';
|
import Logger from '/imports/startup/server/logger';
|
||||||
import AuthTokenValidation, { ValidationStates } from '/imports/api/auth-token-validation';
|
import AuthTokenValidation, { ValidationStates } from '/imports/api/auth-token-validation';
|
||||||
|
|
||||||
function presentationUploadToken(podId, filename) {
|
function presentationUploadToken(podId, filename, tmpPresId) {
|
||||||
const tokenValidation = AuthTokenValidation.findOne({ connectionId: this.connection.id });
|
const tokenValidation = AuthTokenValidation.findOne({ connectionId: this.connection.id });
|
||||||
|
|
||||||
if (!tokenValidation || tokenValidation.validationStatus !== ValidationStates.VALIDATED) {
|
if (!tokenValidation || tokenValidation.validationStatus !== ValidationStates.VALIDATED) {
|
||||||
@ -16,12 +16,14 @@ function presentationUploadToken(podId, filename) {
|
|||||||
|
|
||||||
check(podId, String);
|
check(podId, String);
|
||||||
check(filename, String);
|
check(filename, String);
|
||||||
|
check(tmpPresId, String);
|
||||||
|
|
||||||
const selector = {
|
const selector = {
|
||||||
meetingId,
|
meetingId,
|
||||||
podId,
|
podId,
|
||||||
userId,
|
userId,
|
||||||
filename,
|
filename,
|
||||||
|
tmpPresId,
|
||||||
};
|
};
|
||||||
|
|
||||||
Logger.debug('Publishing PresentationUploadToken', { meetingId, userId });
|
Logger.debug('Publishing PresentationUploadToken', { meetingId, userId });
|
||||||
|
@ -34,6 +34,7 @@ export default function addPresentation(meetingId, podId, presentation) {
|
|||||||
id: String,
|
id: String,
|
||||||
name: String,
|
name: String,
|
||||||
current: Boolean,
|
current: Boolean,
|
||||||
|
temporaryPresentationId: String,
|
||||||
pages: [
|
pages: [
|
||||||
{
|
{
|
||||||
id: String,
|
id: String,
|
||||||
|
@ -26,10 +26,10 @@ export default function addUserPersistentData(user) {
|
|||||||
locked: Boolean,
|
locked: Boolean,
|
||||||
avatar: String,
|
avatar: String,
|
||||||
clientType: String,
|
clientType: String,
|
||||||
|
left: Boolean,
|
||||||
effectiveConnectionType: null,
|
effectiveConnectionType: null,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
||||||
const {
|
const {
|
||||||
intId,
|
intId,
|
||||||
extId,
|
extId,
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
import RedisPubSub from '/imports/startup/server/redis';
|
import RedisPubSub from '/imports/startup/server/redis';
|
||||||
import handleRemoveUser from './handlers/removeUser';
|
import handleRemoveUser from './handlers/removeUser';
|
||||||
import handleUserJoined from './handlers/userJoined';
|
import handleUserJoined from './handlers/userJoined';
|
||||||
|
import handleUserLeftFlagUpdated from './handlers/userLeftFlagUpdated';
|
||||||
import handleValidateAuthToken from './handlers/validateAuthToken';
|
import handleValidateAuthToken from './handlers/validateAuthToken';
|
||||||
import handlePresenterAssigned from './handlers/presenterAssigned';
|
import handlePresenterAssigned from './handlers/presenterAssigned';
|
||||||
import handleEmojiStatus from './handlers/emojiStatus';
|
import handleEmojiStatus from './handlers/emojiStatus';
|
||||||
@ -14,5 +15,6 @@ RedisPubSub.on('UserLeftMeetingEvtMsg', handleRemoveUser);
|
|||||||
RedisPubSub.on('ValidateAuthTokenRespMsg', handleValidateAuthToken);
|
RedisPubSub.on('ValidateAuthTokenRespMsg', handleValidateAuthToken);
|
||||||
RedisPubSub.on('UserEmojiChangedEvtMsg', handleEmojiStatus);
|
RedisPubSub.on('UserEmojiChangedEvtMsg', handleEmojiStatus);
|
||||||
RedisPubSub.on('UserRoleChangedEvtMsg', handleChangeRole);
|
RedisPubSub.on('UserRoleChangedEvtMsg', handleChangeRole);
|
||||||
|
RedisPubSub.on('UserLeftFlagUpdatedEvtMsg', handleUserLeftFlagUpdated);
|
||||||
RedisPubSub.on('UserPinStateChangedEvtMsg', handleUserPinChanged);
|
RedisPubSub.on('UserPinStateChangedEvtMsg', handleUserPinChanged);
|
||||||
RedisPubSub.on('UserInactivityInspectMsg', handleUserInactivityInspect);
|
RedisPubSub.on('UserInactivityInspectMsg', handleUserInactivityInspect);
|
||||||
|
@ -0,0 +1,13 @@
|
|||||||
|
import { check } from 'meteor/check';
|
||||||
|
|
||||||
|
import userLeftFlag from '../modifiers/userLeftFlagUpdated';
|
||||||
|
|
||||||
|
export default function handleUserLeftFlag({ body }, meetingId) {
|
||||||
|
const user = body;
|
||||||
|
check(user, {
|
||||||
|
intId: String,
|
||||||
|
userLeftFlag: Boolean,
|
||||||
|
});
|
||||||
|
|
||||||
|
userLeftFlag(meetingId, user.intId, user.userLeftFlag);
|
||||||
|
}
|
@ -62,6 +62,7 @@ export default function addUser(meetingId, userData) {
|
|||||||
inactivityCheck: false,
|
inactivityCheck: false,
|
||||||
responseDelay: 0,
|
responseDelay: 0,
|
||||||
loggedOut: false,
|
loggedOut: false,
|
||||||
|
left: false,
|
||||||
...flat(user),
|
...flat(user),
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -19,6 +19,7 @@ export default function createDummyUser(meetingId, userId, authToken) {
|
|||||||
authToken,
|
authToken,
|
||||||
clientType: 'HTML5',
|
clientType: 'HTML5',
|
||||||
validated: null,
|
validated: null,
|
||||||
|
left: false,
|
||||||
};
|
};
|
||||||
|
|
||||||
try {
|
try {
|
||||||
|
@ -0,0 +1,24 @@
|
|||||||
|
import Logger from '/imports/startup/server/logger';
|
||||||
|
import Users from '/imports/api/users';
|
||||||
|
|
||||||
|
export default function userLeftFlagUpdated(meetingId, userId, left) {
|
||||||
|
const selector = {
|
||||||
|
meetingId,
|
||||||
|
userId,
|
||||||
|
};
|
||||||
|
|
||||||
|
const modifier = {
|
||||||
|
$set: {
|
||||||
|
left,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
try {
|
||||||
|
const numberAffected = Users.update(selector, modifier);
|
||||||
|
if (numberAffected) {
|
||||||
|
Logger.info(`Updated user ${userId} with left flag as ${left}`);
|
||||||
|
}
|
||||||
|
} catch (err) {
|
||||||
|
Logger.error(`Changed user role: ${err}`);
|
||||||
|
}
|
||||||
|
}
|
@ -60,6 +60,7 @@ function users() {
|
|||||||
{ meetingId },
|
{ meetingId },
|
||||||
],
|
],
|
||||||
intId: { $exists: true },
|
intId: { $exists: true },
|
||||||
|
left: false,
|
||||||
};
|
};
|
||||||
|
|
||||||
const User = Users.findOne({ userId, meetingId }, { fields: { role: 1 } });
|
const User = Users.findOne({ userId, meetingId }, { fields: { role: 1 } });
|
||||||
|
@ -4,7 +4,6 @@ import Langmap from 'langmap';
|
|||||||
import fs from 'fs';
|
import fs from 'fs';
|
||||||
import Users from '/imports/api/users';
|
import Users from '/imports/api/users';
|
||||||
import './settings';
|
import './settings';
|
||||||
import { lookup as lookupUserAgent } from 'useragent';
|
|
||||||
import { check } from 'meteor/check';
|
import { check } from 'meteor/check';
|
||||||
import Logger from './logger';
|
import Logger from './logger';
|
||||||
import Redis from './redis';
|
import Redis from './redis';
|
||||||
@ -144,7 +143,8 @@ Meteor.startup(() => {
|
|||||||
Meteor.onMessage(event => {
|
Meteor.onMessage(event => {
|
||||||
const { method } = event;
|
const { method } = event;
|
||||||
if (method) {
|
if (method) {
|
||||||
PrometheusAgent.increment(METRIC_NAMES.METEOR_METHODS, { methodName: method });
|
const methodName = method.includes('stream-cursor') ? 'stream-cursor' : method;
|
||||||
|
PrometheusAgent.increment(METRIC_NAMES.METEOR_METHODS, { methodName });
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -303,20 +303,6 @@ WebApp.connectHandlers.use('/feedback', (req, res) => {
|
|||||||
}));
|
}));
|
||||||
});
|
});
|
||||||
|
|
||||||
WebApp.connectHandlers.use('/useragent', (req, res) => {
|
|
||||||
const userAgent = req.headers['user-agent'];
|
|
||||||
let response = 'No user agent found in header';
|
|
||||||
if (userAgent) {
|
|
||||||
response = lookupUserAgent(userAgent).toString();
|
|
||||||
}
|
|
||||||
|
|
||||||
Logger.info(`The requesting user agent is ${response}`);
|
|
||||||
|
|
||||||
// res.setHeader('Content-Type', 'application/json');
|
|
||||||
res.writeHead(200);
|
|
||||||
res.end(response);
|
|
||||||
});
|
|
||||||
|
|
||||||
WebApp.connectHandlers.use('/guestWait', (req, res) => {
|
WebApp.connectHandlers.use('/guestWait', (req, res) => {
|
||||||
if (!guestWaitHtml) {
|
if (!guestWaitHtml) {
|
||||||
try {
|
try {
|
||||||
|
@ -1,5 +1,6 @@
|
|||||||
import { Meteor } from 'meteor/meteor';
|
import { Meteor } from 'meteor/meteor';
|
||||||
import { createLogger, format, transports } from 'winston';
|
import { createLogger, format, transports } from 'winston';
|
||||||
|
import WinstonPromTransport from './prom-metrics/winstonPromTransport';
|
||||||
|
|
||||||
const LOG_CONFIG = Meteor?.settings?.private?.serverLog || {};
|
const LOG_CONFIG = Meteor?.settings?.private?.serverLog || {};
|
||||||
const { level } = LOG_CONFIG;
|
const { level } = LOG_CONFIG;
|
||||||
@ -20,6 +21,10 @@ const Logger = createLogger({
|
|||||||
handleExceptions: true,
|
handleExceptions: true,
|
||||||
level,
|
level,
|
||||||
}),
|
}),
|
||||||
|
// export error logs to prometheus
|
||||||
|
new WinstonPromTransport({
|
||||||
|
level: 'error',
|
||||||
|
}),
|
||||||
],
|
],
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -1,34 +1,60 @@
|
|||||||
const {
|
const {
|
||||||
Counter,
|
Counter,
|
||||||
|
Gauge,
|
||||||
|
Histogram
|
||||||
} = require('prom-client');
|
} = require('prom-client');
|
||||||
|
|
||||||
const METRICS_PREFIX = 'html5_'
|
const METRICS_PREFIX = 'html5_'
|
||||||
const METRIC_NAMES = {
|
const METRIC_NAMES = {
|
||||||
METEOR_METHODS: 'meteorMethods',
|
METEOR_METHODS: 'meteorMethods',
|
||||||
}
|
METEOR_ERRORS_TOTAL: 'meteorErrorsTotal',
|
||||||
|
METEOR_RTT: 'meteorRtt',
|
||||||
const buildFrontendMetrics = () => {
|
REDIS_MESSAGE_QUEUE: 'redisMessageQueue',
|
||||||
return {
|
REDIS_PAYLOAD_SIZE: 'redisPayloadSize',
|
||||||
[METRIC_NAMES.METEOR_METHODS]: new Counter({
|
REDIS_PROCESSING_TIME: 'redisProcessingTime'
|
||||||
name: `${METRICS_PREFIX}meteor_methods`,
|
|
||||||
help: 'Total number of meteor methods processed in html5',
|
|
||||||
labelNames: ['methodName', 'role', 'instanceId'],
|
|
||||||
}),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const buildBackendMetrics = () => {
|
|
||||||
// TODO add relevant backend metrics
|
|
||||||
return {}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
let METRICS;
|
let METRICS;
|
||||||
const buildMetrics = () => {
|
const buildMetrics = () => {
|
||||||
if (METRICS == null) {
|
if (METRICS == null) {
|
||||||
const isFrontend = (!process.env.BBB_HTML5_ROLE || process.env.BBB_HTML5_ROLE === 'frontend');
|
METRICS = {
|
||||||
const isBackend = (!process.env.BBB_HTML5_ROLE || process.env.BBB_HTML5_ROLE === 'backend');
|
[METRIC_NAMES.METEOR_METHODS]: new Counter({
|
||||||
if (isFrontend) METRICS = buildFrontendMetrics();
|
name: `${METRICS_PREFIX}meteor_methods`,
|
||||||
if (isBackend) METRICS = { ...METRICS, ...buildBackendMetrics()}
|
help: 'Total number of meteor methods processed in html5',
|
||||||
|
labelNames: ['methodName', 'role', 'instanceId'],
|
||||||
|
}),
|
||||||
|
|
||||||
|
[METRIC_NAMES.METEOR_ERRORS_TOTAL]: new Counter({
|
||||||
|
name: `${METRICS_PREFIX}meteor_errors_total`,
|
||||||
|
help: 'Total number of errors logs in meteor',
|
||||||
|
labelNames: ['errorMessage', 'role', 'instanceId'],
|
||||||
|
}),
|
||||||
|
|
||||||
|
[METRIC_NAMES.METEOR_RTT]: new Histogram({
|
||||||
|
name: `${METRICS_PREFIX}meteor_rtt_seconds`,
|
||||||
|
help: 'Round-trip time of meteor client-server connections in seconds',
|
||||||
|
buckets: [0.05, 0.1, 0.2, 0.3, 0.4, 0.5, 0.75, 1, 1.5, 2, 2.5, 5],
|
||||||
|
labelNames: ['role', 'instanceId'],
|
||||||
|
}),
|
||||||
|
|
||||||
|
[METRIC_NAMES.REDIS_MESSAGE_QUEUE]: new Gauge({
|
||||||
|
name: `${METRICS_PREFIX}redis_message_queue`,
|
||||||
|
help: 'Message queue size in redis',
|
||||||
|
labelNames: ['meetingId', 'role', 'instanceId'],
|
||||||
|
}),
|
||||||
|
|
||||||
|
[METRIC_NAMES.REDIS_PAYLOAD_SIZE]: new Histogram({
|
||||||
|
name: `${METRICS_PREFIX}redis_payload_size`,
|
||||||
|
help: 'Redis events payload size',
|
||||||
|
labelNames: ['eventName', 'role', 'instanceId'],
|
||||||
|
}),
|
||||||
|
|
||||||
|
[METRIC_NAMES.REDIS_PROCESSING_TIME]: new Histogram({
|
||||||
|
name: `${METRICS_PREFIX}redis_processing_time`,
|
||||||
|
help: 'Redis events processing time in milliseconds',
|
||||||
|
labelNames: ['eventName', 'role', 'instanceId'],
|
||||||
|
}),
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return METRICS;
|
return METRICS;
|
||||||
|
@ -81,6 +81,16 @@ class PrometheusScrapeAgent {
|
|||||||
metric.set(labelsObject, value)
|
metric.set(labelsObject, value)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
observe(metricName, value, labelsObject) {
|
||||||
|
if (!this.started) return;
|
||||||
|
|
||||||
|
const metric = this.metrics[metricName];
|
||||||
|
if (metric) {
|
||||||
|
labelsObject = { ...labelsObject, ...this.roleAndInstanceLabels };
|
||||||
|
metric.observe(labelsObject, value)
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export default PrometheusScrapeAgent;
|
export default PrometheusScrapeAgent;
|
||||||
|
@ -0,0 +1,19 @@
|
|||||||
|
const Transport = require('winston-transport');
|
||||||
|
import { PrometheusAgent, METRIC_NAMES } from './index.js'
|
||||||
|
|
||||||
|
module.exports = class WinstonPromTransport extends Transport {
|
||||||
|
constructor(opts) {
|
||||||
|
super(opts);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
log(info, callback) {
|
||||||
|
setImmediate(() => {
|
||||||
|
this.emit('logged', info);
|
||||||
|
});
|
||||||
|
|
||||||
|
PrometheusAgent.increment(METRIC_NAMES.METEOR_ERRORS_TOTAL, { errorMessage: info.message });
|
||||||
|
|
||||||
|
callback();
|
||||||
|
}
|
||||||
|
};
|
@ -5,11 +5,13 @@ import { check } from 'meteor/check';
|
|||||||
import Logger from './logger';
|
import Logger from './logger';
|
||||||
import Metrics from './metrics';
|
import Metrics from './metrics';
|
||||||
import queue from 'queue';
|
import queue from 'queue';
|
||||||
|
import { PrometheusAgent, METRIC_NAMES } from './prom-metrics/index.js'
|
||||||
|
|
||||||
// Fake meetingId used for messages that have no meetingId
|
// Fake meetingId used for messages that have no meetingId
|
||||||
const NO_MEETING_ID = '_';
|
const NO_MEETING_ID = '_';
|
||||||
|
|
||||||
const { queueMetrics } = Meteor.settings.private.redis.metrics;
|
const { queueMetrics } = Meteor.settings.private.redis.metrics;
|
||||||
|
const { collectRedisMetrics: PROM_METRICS_ENABLED } = Meteor.settings.private.prometheus;
|
||||||
|
|
||||||
const makeEnvelope = (channel, eventName, header, body, routing) => {
|
const makeEnvelope = (channel, eventName, header, body, routing) => {
|
||||||
const envelope = {
|
const envelope = {
|
||||||
@ -78,6 +80,16 @@ class MeetingMessageQueue {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const queueLength = this.queue.length;
|
const queueLength = this.queue.length;
|
||||||
|
|
||||||
|
if (PROM_METRICS_ENABLED) {
|
||||||
|
const dataLength = JSON.stringify(data).length;
|
||||||
|
const currentTimestamp = Date.now();
|
||||||
|
const processTime = currentTimestamp - beginHandleTimestamp;
|
||||||
|
PrometheusAgent.observe(METRIC_NAMES.REDIS_PROCESSING_TIME, processTime, { eventName });
|
||||||
|
PrometheusAgent.observe(METRIC_NAMES.REDIS_PAYLOAD_SIZE, dataLength, { eventName });
|
||||||
|
meetingId && PrometheusAgent.set(METRIC_NAMES.REDIS_MESSAGE_QUEUE, queueLength, { meetingId });
|
||||||
|
}
|
||||||
|
|
||||||
if (queueLength > 100) {
|
if (queueLength > 100) {
|
||||||
Logger.warn(`Redis: MeetingMessageQueue for meetingId=${meetingId} has queue size=${queueLength} `);
|
Logger.warn(`Redis: MeetingMessageQueue for meetingId=${meetingId} has queue size=${queueLength} `);
|
||||||
}
|
}
|
||||||
|
@ -470,6 +470,7 @@ class BreakoutRoom extends PureComponent {
|
|||||||
.filter((user) => !stateUsersId.includes(user.userId))
|
.filter((user) => !stateUsersId.includes(user.userId))
|
||||||
.map((user) => ({
|
.map((user) => ({
|
||||||
userId: user.userId,
|
userId: user.userId,
|
||||||
|
extId: user.extId,
|
||||||
userName: user.name,
|
userName: user.name,
|
||||||
isModerator: user.role === ROLE_MODERATOR,
|
isModerator: user.role === ROLE_MODERATOR,
|
||||||
room: 0,
|
room: 0,
|
||||||
@ -611,7 +612,8 @@ class BreakoutRoom extends PureComponent {
|
|||||||
}
|
}
|
||||||
|
|
||||||
populateWithLastBreakouts(lastBreakouts) {
|
populateWithLastBreakouts(lastBreakouts) {
|
||||||
const { getBreakoutUserWasIn, users, intl } = this.props;
|
const { getBreakoutUserWasIn, intl } = this.props;
|
||||||
|
const { users } = this.state;
|
||||||
|
|
||||||
const changedNames = [];
|
const changedNames = [];
|
||||||
lastBreakouts.forEach((breakout) => {
|
lastBreakouts.forEach((breakout) => {
|
||||||
|
@ -44,6 +44,7 @@ import Settings from '/imports/ui/services/settings';
|
|||||||
import LayoutService from '/imports/ui/components/layout/service';
|
import LayoutService from '/imports/ui/components/layout/service';
|
||||||
import { registerTitleView } from '/imports/utils/dom-utils';
|
import { registerTitleView } from '/imports/utils/dom-utils';
|
||||||
import GlobalStyles from '/imports/ui/stylesheets/styled-components/globalStyles';
|
import GlobalStyles from '/imports/ui/stylesheets/styled-components/globalStyles';
|
||||||
|
import MediaService from '/imports/ui/components/media/service';
|
||||||
|
|
||||||
const MOBILE_MEDIA = 'only screen and (max-width: 40em)';
|
const MOBILE_MEDIA = 'only screen and (max-width: 40em)';
|
||||||
const APP_CONFIG = Meteor.settings.public.app;
|
const APP_CONFIG = Meteor.settings.public.app;
|
||||||
@ -156,6 +157,9 @@ class App extends Component {
|
|||||||
settingsLayout,
|
settingsLayout,
|
||||||
isRTL,
|
isRTL,
|
||||||
hidePresentation,
|
hidePresentation,
|
||||||
|
autoSwapLayout,
|
||||||
|
shouldShowScreenshare,
|
||||||
|
shouldShowExternalVideo,
|
||||||
} = this.props;
|
} = this.props;
|
||||||
const { browserName } = browserInfo;
|
const { browserName } = browserInfo;
|
||||||
const { osName } = deviceInfo;
|
const { osName } = deviceInfo;
|
||||||
@ -167,11 +171,18 @@ class App extends Component {
|
|||||||
value: isRTL,
|
value: isRTL,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const presentationOpen = !(autoSwapLayout || hidePresentation)
|
||||||
|
|| shouldShowExternalVideo || shouldShowScreenshare;
|
||||||
|
|
||||||
layoutContextDispatch({
|
layoutContextDispatch({
|
||||||
type: ACTIONS.SET_PRESENTATION_IS_OPEN,
|
type: ACTIONS.SET_PRESENTATION_IS_OPEN,
|
||||||
value: !hidePresentation,
|
value: presentationOpen,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
if (!presentationOpen && !MediaService.getSwapLayout()) {
|
||||||
|
MediaService.setSwapLayout(layoutContextDispatch);
|
||||||
|
}
|
||||||
|
|
||||||
Modal.setAppElement('#app');
|
Modal.setAppElement('#app');
|
||||||
|
|
||||||
const fontSize = isMobile() ? MOBILE_FONT_SIZE : DESKTOP_FONT_SIZE;
|
const fontSize = isMobile() ? MOBILE_FONT_SIZE : DESKTOP_FONT_SIZE;
|
||||||
|
@ -230,6 +230,7 @@ export default injectIntl(withModalMounter(withTracker(({ intl, baseControls })
|
|||||||
Meteor.settings.public.presentation.restoreOnUpdate,
|
Meteor.settings.public.presentation.restoreOnUpdate,
|
||||||
),
|
),
|
||||||
hidePresentation: getFromUserSettings('bbb_hide_presentation', LAYOUT_CONFIG.hidePresentation),
|
hidePresentation: getFromUserSettings('bbb_hide_presentation', LAYOUT_CONFIG.hidePresentation),
|
||||||
|
autoSwapLayout: getFromUserSettings('bbb_auto_swap_layout', LAYOUT_CONFIG.autoSwapLayout),
|
||||||
hideActionsBar: getFromUserSettings('bbb_hide_actions_bar', false),
|
hideActionsBar: getFromUserSettings('bbb_hide_actions_bar', false),
|
||||||
isModalOpen: !!getModal(),
|
isModalOpen: !!getModal(),
|
||||||
};
|
};
|
||||||
|
@ -41,7 +41,7 @@ const handleLeaveAudio = () => {
|
|||||||
Storage.setItem('getEchoTest', true);
|
Storage.setItem('getEchoTest', true);
|
||||||
}
|
}
|
||||||
|
|
||||||
Service.exitAudio();
|
Service.forceExitAudio();
|
||||||
logger.info({
|
logger.info({
|
||||||
logCode: 'audiocontrols_leave_audio',
|
logCode: 'audiocontrols_leave_audio',
|
||||||
extraInfo: { logType: 'user_action' },
|
extraInfo: { logType: 'user_action' },
|
||||||
|
@ -267,28 +267,7 @@ class AudioModal extends Component {
|
|||||||
disableActions: false,
|
disableActions: false,
|
||||||
});
|
});
|
||||||
}).catch((err) => {
|
}).catch((err) => {
|
||||||
const { type } = err;
|
this.handleJoinMicrophoneError(err);
|
||||||
switch (type) {
|
|
||||||
case 'MEDIA_ERROR':
|
|
||||||
this.setState({
|
|
||||||
content: 'help',
|
|
||||||
errCode: 0,
|
|
||||||
disableActions: false,
|
|
||||||
});
|
|
||||||
break;
|
|
||||||
case 'CONNECTION_ERROR':
|
|
||||||
this.setState({
|
|
||||||
errCode: 0,
|
|
||||||
disableActions: false,
|
|
||||||
});
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
this.setState({
|
|
||||||
errCode: 0,
|
|
||||||
disableActions: false,
|
|
||||||
});
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -342,7 +321,29 @@ class AudioModal extends Component {
|
|||||||
this.setState({
|
this.setState({
|
||||||
disableActions: false,
|
disableActions: false,
|
||||||
});
|
});
|
||||||
}).catch(this.handleGoToAudioOptions);
|
}).catch((err) => {
|
||||||
|
this.handleJoinMicrophoneError(err);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
handleJoinMicrophoneError(err) {
|
||||||
|
const { type } = err;
|
||||||
|
switch (type) {
|
||||||
|
case 'MEDIA_ERROR':
|
||||||
|
this.setState({
|
||||||
|
content: 'help',
|
||||||
|
errCode: 0,
|
||||||
|
disableActions: false,
|
||||||
|
});
|
||||||
|
break;
|
||||||
|
case 'CONNECTION_ERROR':
|
||||||
|
default:
|
||||||
|
this.setState({
|
||||||
|
errCode: 0,
|
||||||
|
disableActions: false,
|
||||||
|
});
|
||||||
|
break;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
setContent(content) {
|
setContent(content) {
|
||||||
|
@ -14,6 +14,8 @@ import { screenshareHasEnded } from '/imports/ui/components/screenshare/service'
|
|||||||
import AudioManager from '/imports/ui/services/audio-manager';
|
import AudioManager from '/imports/ui/services/audio-manager';
|
||||||
import Settings from '/imports/ui/services/settings';
|
import Settings from '/imports/ui/services/settings';
|
||||||
import BreakoutDropdown from '/imports/ui/components/breakout-room/breakout-dropdown/component';
|
import BreakoutDropdown from '/imports/ui/components/breakout-room/breakout-dropdown/component';
|
||||||
|
import Users from '/imports/api/users';
|
||||||
|
import Auth from '/imports/ui/services/auth';
|
||||||
|
|
||||||
const intlMessages = defineMessages({
|
const intlMessages = defineMessages({
|
||||||
breakoutTitle: {
|
breakoutTitle: {
|
||||||
@ -282,7 +284,8 @@ class BreakoutRoom extends PureComponent {
|
|||||||
amIPresenter,
|
amIPresenter,
|
||||||
intl,
|
intl,
|
||||||
isUserInBreakoutRoom,
|
isUserInBreakoutRoom,
|
||||||
exitAudio,
|
forceExitAudio,
|
||||||
|
rejoinAudio,
|
||||||
setBreakoutAudioTransferStatus,
|
setBreakoutAudioTransferStatus,
|
||||||
getBreakoutAudioTransferStatus,
|
getBreakoutAudioTransferStatus,
|
||||||
} = this.props;
|
} = this.props;
|
||||||
@ -349,7 +352,7 @@ class BreakoutRoom extends PureComponent {
|
|||||||
this.getBreakoutURL(breakoutId);
|
this.getBreakoutURL(breakoutId);
|
||||||
// leave main room's audio,
|
// leave main room's audio,
|
||||||
// and stops video and screenshare when joining a breakout room
|
// and stops video and screenshare when joining a breakout room
|
||||||
exitAudio();
|
forceExitAudio();
|
||||||
logger.info({
|
logger.info({
|
||||||
logCode: 'breakoutroom_join',
|
logCode: 'breakoutroom_join',
|
||||||
extraInfo: { logType: 'user_action' },
|
extraInfo: { logType: 'user_action' },
|
||||||
@ -357,6 +360,31 @@ class BreakoutRoom extends PureComponent {
|
|||||||
VideoService.storeDeviceIds();
|
VideoService.storeDeviceIds();
|
||||||
VideoService.exitVideo();
|
VideoService.exitVideo();
|
||||||
if (amIPresenter) screenshareHasEnded();
|
if (amIPresenter) screenshareHasEnded();
|
||||||
|
|
||||||
|
Tracker.autorun((c) => {
|
||||||
|
const selector = {
|
||||||
|
meetingId: breakoutId,
|
||||||
|
};
|
||||||
|
|
||||||
|
const query = Users.find(selector, {
|
||||||
|
fields: {
|
||||||
|
loggedOut: 1,
|
||||||
|
extId: 1,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
const observeLogOut = (user) => {
|
||||||
|
if (user?.loggedOut && user?.extId?.startsWith(Auth.userID)) {
|
||||||
|
rejoinAudio();
|
||||||
|
c.stop();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
query.observe({
|
||||||
|
added: observeLogOut,
|
||||||
|
changed: observeLogOut,
|
||||||
|
});
|
||||||
|
});
|
||||||
}}
|
}}
|
||||||
disabled={disable}
|
disabled={disable}
|
||||||
/>
|
/>
|
||||||
|
@ -7,6 +7,11 @@ import Service from './service';
|
|||||||
import { layoutDispatch } from '../layout/context';
|
import { layoutDispatch } from '../layout/context';
|
||||||
import Auth from '/imports/ui/services/auth';
|
import Auth from '/imports/ui/services/auth';
|
||||||
import { UsersContext } from '/imports/ui/components/components-data/users-context/context';
|
import { UsersContext } from '/imports/ui/components/components-data/users-context/context';
|
||||||
|
import {
|
||||||
|
didUserSelectedMicrophone,
|
||||||
|
didUserSelectedListenOnly,
|
||||||
|
} from '/imports/ui/components/audio/audio-modal/service';
|
||||||
|
import { makeCall } from '/imports/ui/services/api';
|
||||||
|
|
||||||
const BreakoutContainer = (props) => {
|
const BreakoutContainer = (props) => {
|
||||||
const layoutContextDispatch = layoutDispatch();
|
const layoutContextDispatch = layoutDispatch();
|
||||||
@ -45,6 +50,30 @@ export default withTracker((props) => {
|
|||||||
getBreakoutAudioTransferStatus,
|
getBreakoutAudioTransferStatus,
|
||||||
} = AudioService;
|
} = AudioService;
|
||||||
|
|
||||||
|
const logUserCouldNotRejoinAudio = () => {
|
||||||
|
logger.warn({
|
||||||
|
logCode: 'mainroom_audio_rejoin',
|
||||||
|
extraInfo: { logType: 'user_action' },
|
||||||
|
}, 'leaving breakout room couldn\'t rejoin audio in the main room');
|
||||||
|
};
|
||||||
|
|
||||||
|
const rejoinAudio = () => {
|
||||||
|
if (didUserSelectedMicrophone()) {
|
||||||
|
AudioManager.joinMicrophone().then(() => {
|
||||||
|
makeCall('toggleVoice', null, true).catch(() => {
|
||||||
|
AudioManager.forceExitAudio();
|
||||||
|
logUserCouldNotRejoinAudio();
|
||||||
|
});
|
||||||
|
}).catch(() => {
|
||||||
|
logUserCouldNotRejoinAudio();
|
||||||
|
});
|
||||||
|
} else if (didUserSelectedListenOnly()) {
|
||||||
|
AudioManager.joinListenOnly().catch(() => {
|
||||||
|
logUserCouldNotRejoinAudio();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
return {
|
return {
|
||||||
...props,
|
...props,
|
||||||
breakoutRooms,
|
breakoutRooms,
|
||||||
@ -61,7 +90,8 @@ export default withTracker((props) => {
|
|||||||
amIModerator: amIModerator(),
|
amIModerator: amIModerator(),
|
||||||
isMeteorConnected,
|
isMeteorConnected,
|
||||||
isUserInBreakoutRoom,
|
isUserInBreakoutRoom,
|
||||||
exitAudio: () => AudioManager.exitAudio(),
|
forceExitAudio: () => AudioManager.forceExitAudio(),
|
||||||
|
rejoinAudio,
|
||||||
isReconnecting,
|
isReconnecting,
|
||||||
setBreakoutAudioTransferStatus,
|
setBreakoutAudioTransferStatus,
|
||||||
getBreakoutAudioTransferStatus,
|
getBreakoutAudioTransferStatus,
|
||||||
|
@ -68,8 +68,6 @@ const ChatAlert = (props) => {
|
|||||||
unreadMessagesByChat,
|
unreadMessagesByChat,
|
||||||
intl,
|
intl,
|
||||||
layoutContextDispatch,
|
layoutContextDispatch,
|
||||||
chatsTracker,
|
|
||||||
notify,
|
|
||||||
} = props;
|
} = props;
|
||||||
|
|
||||||
const [unreadMessagesCount, setUnreadMessagesCount] = useState(0);
|
const [unreadMessagesCount, setUnreadMessagesCount] = useState(0);
|
||||||
@ -105,35 +103,6 @@ const ChatAlert = (props) => {
|
|||||||
}
|
}
|
||||||
}, [pushAlertEnabled]);
|
}, [pushAlertEnabled]);
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
const keys = Object.keys(chatsTracker);
|
|
||||||
keys.forEach((key) => {
|
|
||||||
if (chatsTracker[key]?.shouldNotify) {
|
|
||||||
if (audioAlertEnabled) {
|
|
||||||
AudioService.playAlertSound(`${Meteor.settings.public.app.cdn
|
|
||||||
+ Meteor.settings.public.app.basename
|
|
||||||
+ Meteor.settings.public.app.instanceId}`
|
|
||||||
+ '/resources/sounds/notify.mp3');
|
|
||||||
}
|
|
||||||
if (pushAlertEnabled) {
|
|
||||||
notify(
|
|
||||||
key === 'MAIN-PUBLIC-GROUP-CHAT'
|
|
||||||
? intl.formatMessage(intlMessages.publicChatMsg)
|
|
||||||
: intl.formatMessage(intlMessages.privateChatMsg),
|
|
||||||
'info',
|
|
||||||
'chat',
|
|
||||||
{ autoClose: 3000 },
|
|
||||||
<div>
|
|
||||||
<div style={{ fontWeight: 700 }}>{chatsTracker[key].lastSender}</div>
|
|
||||||
<div dangerouslySetInnerHTML={{ __html: chatsTracker[key].content }} />
|
|
||||||
</div>,
|
|
||||||
true,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}, [chatsTracker]);
|
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (pushAlertEnabled) {
|
if (pushAlertEnabled) {
|
||||||
const alertsObject = unreadMessagesByChat;
|
const alertsObject = unreadMessagesByChat;
|
||||||
|
@ -1,7 +1,5 @@
|
|||||||
import React, { useContext } from 'react';
|
import React, { useContext } from 'react';
|
||||||
import PropTypes from 'prop-types';
|
import PropTypes from 'prop-types';
|
||||||
import logger from '/imports/startup/client/logger';
|
|
||||||
import Auth from '/imports/ui/services/auth';
|
|
||||||
import ChatAlert from './component';
|
import ChatAlert from './component';
|
||||||
import { layoutSelect, layoutSelectInput, layoutDispatch } from '../../layout/context';
|
import { layoutSelect, layoutSelectInput, layoutDispatch } from '../../layout/context';
|
||||||
import { PANELS } from '../../layout/enums';
|
import { PANELS } from '../../layout/enums';
|
||||||
@ -18,15 +16,6 @@ const propTypes = {
|
|||||||
pushAlertEnabled: PropTypes.bool.isRequired,
|
pushAlertEnabled: PropTypes.bool.isRequired,
|
||||||
};
|
};
|
||||||
|
|
||||||
// custom hook for getting previous value
|
|
||||||
function usePrevious(value) {
|
|
||||||
const ref = React.useRef();
|
|
||||||
React.useEffect(() => {
|
|
||||||
ref.current = value;
|
|
||||||
});
|
|
||||||
return ref.current;
|
|
||||||
}
|
|
||||||
|
|
||||||
const ChatAlertContainer = (props) => {
|
const ChatAlertContainer = (props) => {
|
||||||
const idChatOpen = layoutSelect((i) => i.idChatOpen);
|
const idChatOpen = layoutSelect((i) => i.idChatOpen);
|
||||||
const sidebarContent = layoutSelectInput((i) => i.sidebarContent);
|
const sidebarContent = layoutSelectInput((i) => i.sidebarContent);
|
||||||
@ -65,47 +54,9 @@ const ChatAlertContainer = (props) => {
|
|||||||
})
|
})
|
||||||
: null;
|
: null;
|
||||||
|
|
||||||
const chatsTracker = {};
|
|
||||||
|
|
||||||
if (usingChatContext.chats) {
|
|
||||||
const chatsActive = Object.entries(usingChatContext.chats);
|
|
||||||
chatsActive.forEach((c) => {
|
|
||||||
try {
|
|
||||||
if (c[0] === idChat || (c[0] === 'MAIN-PUBLIC-GROUP-CHAT' && idChat === 'public')) {
|
|
||||||
chatsTracker[c[0]] = {};
|
|
||||||
if (c[1]?.posJoinMessages || c[1]?.messageGroups) {
|
|
||||||
const m = Object.entries(c[1]?.posJoinMessages || c[1]?.messageGroups);
|
|
||||||
const sameUserCount = m.filter((message) => message[1]?.sender === Auth.userID).length;
|
|
||||||
if (m[m.length - 1] && m[m.length - 1][1]?.sender !== Auth.userID) {
|
|
||||||
chatsTracker[c[0]].lastSender = users[Auth.meetingID][c[1]?.lastSender]?.name;
|
|
||||||
chatsTracker[c[0]].content = m[m.length - 1][1]?.message;
|
|
||||||
chatsTracker[c[0]].count = m?.length - sameUserCount;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} catch (e) {
|
|
||||||
logger.error({
|
|
||||||
logCode: 'chat_alert_component_error',
|
|
||||||
}, 'Error : ', e.error);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
const prevTracker = usePrevious(chatsTracker);
|
|
||||||
|
|
||||||
if (prevTracker) {
|
|
||||||
const keys = Object.keys(prevTracker);
|
|
||||||
keys.forEach((key) => {
|
|
||||||
if (chatsTracker[key]?.count > (prevTracker[key]?.count || 0)) {
|
|
||||||
chatsTracker[key].shouldNotify = true;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<ChatAlert
|
<ChatAlert
|
||||||
{...props}
|
{...props}
|
||||||
chatsTracker={chatsTracker}
|
|
||||||
layoutContextDispatch={layoutContextDispatch}
|
layoutContextDispatch={layoutContextDispatch}
|
||||||
unreadMessagesCountByChat={unreadMessagesCountByChat}
|
unreadMessagesCountByChat={unreadMessagesCountByChat}
|
||||||
unreadMessagesByChat={unreadMessagesByChat}
|
unreadMessagesByChat={unreadMessagesByChat}
|
||||||
|
@ -66,10 +66,10 @@ class TimeWindowChatItem extends PureComponent {
|
|||||||
}
|
}
|
||||||
|
|
||||||
componentDidUpdate(prevProps, prevState) {
|
componentDidUpdate(prevProps, prevState) {
|
||||||
const { height, forceCacheUpdate, systemMessage, index } = this.props;
|
const { height, forceCacheUpdate, index } = this.props;
|
||||||
const elementHeight = this.itemRef ? this.itemRef.clientHeight : null;
|
const elementHeight = this.itemRef ? this.itemRef.clientHeight : null;
|
||||||
|
|
||||||
if (systemMessage && elementHeight && height !== 'auto' && elementHeight !== height && this.state.forcedUpdateCount < 10) {
|
if (elementHeight && height !== 'auto' && elementHeight !== height && this.state.forcedUpdateCount < 10) {
|
||||||
// forceCacheUpdate() internally calls forceUpdate(), so we need a stop flag
|
// forceCacheUpdate() internally calls forceUpdate(), so we need a stop flag
|
||||||
// and cannot rely on shouldComponentUpdate() and other comparisons.
|
// and cannot rely on shouldComponentUpdate() and other comparisons.
|
||||||
forceCacheUpdate(index);
|
forceCacheUpdate(index);
|
||||||
@ -158,7 +158,10 @@ class TimeWindowChatItem extends PureComponent {
|
|||||||
const emphasizedText = messageFromModerator && CHAT_EMPHASIZE_TEXT && chatId === CHAT_PUBLIC_ID;
|
const emphasizedText = messageFromModerator && CHAT_EMPHASIZE_TEXT && chatId === CHAT_PUBLIC_ID;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Styled.Item key={`time-window-${messageKey}`}>
|
<Styled.Item
|
||||||
|
key={`time-window-${messageKey}`}
|
||||||
|
ref={element => this.itemRef = element}
|
||||||
|
>
|
||||||
<Styled.Wrapper isSystemSender={isSystemSender}>
|
<Styled.Wrapper isSystemSender={isSystemSender}>
|
||||||
<Styled.AvatarWrapper>
|
<Styled.AvatarWrapper>
|
||||||
<UserAvatar
|
<UserAvatar
|
||||||
@ -280,11 +283,7 @@ class TimeWindowChatItem extends PureComponent {
|
|||||||
return this.renderSystemMessage();
|
return this.renderSystemMessage();
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return this.renderMessageItem();
|
||||||
<Styled.Item>
|
|
||||||
{this.renderMessageItem()}
|
|
||||||
</Styled.Item>
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -101,6 +101,7 @@ const Meta = styled.div`
|
|||||||
flex: 1;
|
flex: 1;
|
||||||
flex-flow: row;
|
flex-flow: row;
|
||||||
line-height: 1.35;
|
line-height: 1.35;
|
||||||
|
align-items: baseline;
|
||||||
`;
|
`;
|
||||||
|
|
||||||
const Name = styled.div`
|
const Name = styled.div`
|
||||||
|
@ -33,6 +33,15 @@ class BBBMenu extends React.Component {
|
|||||||
this.handleClose = this.handleClose.bind(this);
|
this.handleClose = this.handleClose.bind(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
componentDidUpdate() {
|
||||||
|
const { anchorEl } = this.state;
|
||||||
|
if (this.props.open === false && anchorEl) {
|
||||||
|
this.setState({ anchorEl: null });
|
||||||
|
} else if (this.props.open === true && !anchorEl) {
|
||||||
|
this.setState({ anchorEl: this.anchorElRef });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
handleClick(event) {
|
handleClick(event) {
|
||||||
this.setState({ anchorEl: event.currentTarget });
|
this.setState({ anchorEl: event.currentTarget });
|
||||||
};
|
};
|
||||||
@ -129,6 +138,7 @@ class BBBMenu extends React.Component {
|
|||||||
this.handleClick(e);
|
this.handleClick(e);
|
||||||
}}
|
}}
|
||||||
accessKey={this.props?.accessKey}
|
accessKey={this.props?.accessKey}
|
||||||
|
ref={(ref) => this.anchorElRef = ref}
|
||||||
>
|
>
|
||||||
{trigger}
|
{trigger}
|
||||||
</div>
|
</div>
|
||||||
|
@ -71,7 +71,9 @@ class ConfirmationModal extends Component {
|
|||||||
</Styled.Title>
|
</Styled.Title>
|
||||||
</Styled.Header>
|
</Styled.Header>
|
||||||
<Styled.Description>
|
<Styled.Description>
|
||||||
<span dangerouslySetInnerHTML={{ __html: description }} />
|
<Styled.DescriptionText>
|
||||||
|
{description}
|
||||||
|
</Styled.DescriptionText>
|
||||||
{ hasCheckbox ? (
|
{ hasCheckbox ? (
|
||||||
<label htmlFor="confirmationCheckbox" key="confirmation-checkbox">
|
<label htmlFor="confirmationCheckbox" key="confirmation-checkbox">
|
||||||
<Styled.Checkbox
|
<Styled.Checkbox
|
||||||
|
@ -55,6 +55,10 @@ const Description = styled.div`
|
|||||||
margin-bottom: ${jumboPaddingY};
|
margin-bottom: ${jumboPaddingY};
|
||||||
`;
|
`;
|
||||||
|
|
||||||
|
const DescriptionText = styled.span`
|
||||||
|
white-space: pre-line;
|
||||||
|
`;
|
||||||
|
|
||||||
const Checkbox = styled.input`
|
const Checkbox = styled.input`
|
||||||
position: relative;
|
position: relative;
|
||||||
top: 0.134rem;
|
top: 0.134rem;
|
||||||
@ -85,6 +89,7 @@ export default {
|
|||||||
Header,
|
Header,
|
||||||
Title,
|
Title,
|
||||||
Description,
|
Description,
|
||||||
|
DescriptionText,
|
||||||
Checkbox,
|
Checkbox,
|
||||||
Footer,
|
Footer,
|
||||||
ConfirmationButton,
|
ConfirmationButton,
|
||||||
|
@ -69,6 +69,7 @@ class Switch extends Toggle {
|
|||||||
hasFocus={hasFocus}
|
hasFocus={hasFocus}
|
||||||
disabled={disabled}
|
disabled={disabled}
|
||||||
animations={animations}
|
animations={animations}
|
||||||
|
isRTL={document.getElementsByTagName('html')[0].dir === 'rtl'}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<Styled.ScreenreaderInput
|
<Styled.ScreenreaderInput
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
import styled from 'styled-components';
|
import styled, { css } from 'styled-components';
|
||||||
import { borderSize } from '/imports/ui/stylesheets/styled-components/general';
|
import { borderSize } from '/imports/ui/stylesheets/styled-components/general';
|
||||||
import { colorDanger, colorSuccess } from '/imports/ui/stylesheets/styled-components/palette';
|
import { colorDanger, colorSuccess } from '/imports/ui/stylesheets/styled-components/palette';
|
||||||
|
|
||||||
@ -121,7 +121,7 @@ const ToggleTrackX = styled.div`
|
|||||||
const ToggleThumb = styled.div`
|
const ToggleThumb = styled.div`
|
||||||
position: absolute;
|
position: absolute;
|
||||||
top: 1px;
|
top: 1px;
|
||||||
left: 1px;
|
left: ${({ isRTL }) => isRTL ? '2.6rem' : '1px'};
|
||||||
width: 1.35rem;
|
width: 1.35rem;
|
||||||
height: 1.35rem;
|
height: 1.35rem;
|
||||||
border-radius: 50%;
|
border-radius: 50%;
|
||||||
@ -129,16 +129,12 @@ const ToggleThumb = styled.div`
|
|||||||
box-sizing: border-box;
|
box-sizing: border-box;
|
||||||
box-shadow: 2px 0px 10px -1px rgba(0,0,0,0.4);
|
box-shadow: 2px 0px 10px -1px rgba(0,0,0,0.4);
|
||||||
|
|
||||||
[dir="rtl"] & {
|
|
||||||
left: 2.6rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
${({ animations }) => animations && `
|
${({ animations }) => animations && `
|
||||||
transition: all 0.5s cubic-bezier(0.23, 1, 0.32, 1) 0ms;
|
transition: all 0.5s cubic-bezier(0.23, 1, 0.32, 1) 0ms;
|
||||||
`}
|
`}
|
||||||
|
|
||||||
${({ checked }) => checked && `
|
${({ checked }) => checked && css`
|
||||||
left: 2.1rem;
|
left: ${({ isRTL }) => isRTL ? '1px' : '2.1rem' };
|
||||||
box-shadow: -2px 0px 10px -1px rgba(0,0,0,0.4);
|
box-shadow: -2px 0px 10px -1px rgba(0,0,0,0.4);
|
||||||
`}
|
`}
|
||||||
|
|
||||||
|
@ -29,6 +29,7 @@ const intlMessages = defineMessages({
|
|||||||
});
|
});
|
||||||
|
|
||||||
let stats = -1;
|
let stats = -1;
|
||||||
|
let lastRtt = null;
|
||||||
const statsDep = new Tracker.Dependency();
|
const statsDep = new Tracker.Dependency();
|
||||||
|
|
||||||
let statsTimeout = null;
|
let statsTimeout = null;
|
||||||
@ -111,11 +112,12 @@ const addConnectionStatus = (level, type, value) => {
|
|||||||
|
|
||||||
const fetchRoundTripTime = () => {
|
const fetchRoundTripTime = () => {
|
||||||
const t0 = Date.now();
|
const t0 = Date.now();
|
||||||
makeCall('voidConnection').then(() => {
|
makeCall('voidConnection', lastRtt).then(() => {
|
||||||
const tf = Date.now();
|
const tf = Date.now();
|
||||||
const rtt = tf - t0;
|
const rtt = tf - t0;
|
||||||
const event = new CustomEvent('socketstats', { detail: { rtt } });
|
const event = new CustomEvent('socketstats', { detail: { rtt } });
|
||||||
window.dispatchEvent(event);
|
window.dispatchEvent(event);
|
||||||
|
lastRtt = rtt;
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -46,7 +46,8 @@ class EndMeetingComponent extends PureComponent {
|
|||||||
: intl.formatMessage(intlMessages.endMeetingNoUserDescription);
|
: intl.formatMessage(intlMessages.endMeetingNoUserDescription);
|
||||||
|
|
||||||
if (warnAboutUnsavedContentOnMeetingEnd) {
|
if (warnAboutUnsavedContentOnMeetingEnd) {
|
||||||
description += `<p>${intl.formatMessage(intlMessages.contentWarning)}</p>`;
|
// the double breakline it to put one empty line between the descriptions
|
||||||
|
description += `\n\n${intl.formatMessage(intlMessages.contentWarning)}`;
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
@ -13,6 +13,7 @@ import deviceInfo from '/imports/utils/deviceInfo';
|
|||||||
|
|
||||||
import logger from '/imports/startup/client/logger';
|
import logger from '/imports/startup/client/logger';
|
||||||
|
|
||||||
|
import Subtitles from './subtitles/component';
|
||||||
import VolumeSlider from './volume-slider/component';
|
import VolumeSlider from './volume-slider/component';
|
||||||
import ReloadButton from '/imports/ui/components/reload-button/component';
|
import ReloadButton from '/imports/ui/components/reload-button/component';
|
||||||
import FullscreenButtonContainer from '/imports/ui/components/common/fullscreen-button/container';
|
import FullscreenButtonContainer from '/imports/ui/components/common/fullscreen-button/container';
|
||||||
@ -34,6 +35,12 @@ const intlMessages = defineMessages({
|
|||||||
fullscreenLabel: {
|
fullscreenLabel: {
|
||||||
id: 'app.externalVideo.fullscreenLabel',
|
id: 'app.externalVideo.fullscreenLabel',
|
||||||
},
|
},
|
||||||
|
subtitlesOn: {
|
||||||
|
id: 'app.externalVideo.subtitlesOn',
|
||||||
|
},
|
||||||
|
subtitlesOff: {
|
||||||
|
id: 'app.externalVideo.subtitlesOff',
|
||||||
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
const SYNC_INTERVAL_SECONDS = 5;
|
const SYNC_INTERVAL_SECONDS = 5;
|
||||||
@ -69,18 +76,22 @@ class VideoPlayer extends Component {
|
|||||||
this.throttleTimeout = null;
|
this.throttleTimeout = null;
|
||||||
|
|
||||||
this.state = {
|
this.state = {
|
||||||
|
subtitlesOn: false,
|
||||||
muted: false,
|
muted: false,
|
||||||
playing: false,
|
playing: false,
|
||||||
autoPlayBlocked: false,
|
autoPlayBlocked: false,
|
||||||
volume: 1,
|
volume: 1,
|
||||||
playbackRate: 1,
|
playbackRate: 1,
|
||||||
key: 0,
|
key: 0,
|
||||||
|
played:0,
|
||||||
|
loaded:0,
|
||||||
};
|
};
|
||||||
|
|
||||||
this.hideVolume = {
|
this.hideVolume = {
|
||||||
Vimeo: true,
|
Vimeo: true,
|
||||||
Facebook: true,
|
Facebook: true,
|
||||||
ArcPlayer: true,
|
ArcPlayer: true,
|
||||||
|
//YouTube: true,
|
||||||
};
|
};
|
||||||
|
|
||||||
this.opts = {
|
this.opts = {
|
||||||
@ -113,6 +124,7 @@ class VideoPlayer extends Component {
|
|||||||
rel: 0,
|
rel: 0,
|
||||||
ecver: 2,
|
ecver: 2,
|
||||||
controls: isPresenter ? 1 : 0,
|
controls: isPresenter ? 1 : 0,
|
||||||
|
cc_lang_pref: document.getElementsByTagName('html')[0].lang.substring(0, 2),
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
peertube: {
|
peertube: {
|
||||||
@ -145,6 +157,7 @@ class VideoPlayer extends Component {
|
|||||||
this.getMuted = this.getMuted.bind(this);
|
this.getMuted = this.getMuted.bind(this);
|
||||||
this.setPlaybackRate = this.setPlaybackRate.bind(this);
|
this.setPlaybackRate = this.setPlaybackRate.bind(this);
|
||||||
this.onBeforeUnload = this.onBeforeUnload.bind(this);
|
this.onBeforeUnload = this.onBeforeUnload.bind(this);
|
||||||
|
this.toggleSubtitle = this.toggleSubtitle.bind(this);
|
||||||
|
|
||||||
this.mobileHoverSetTimeout = null;
|
this.mobileHoverSetTimeout = null;
|
||||||
}
|
}
|
||||||
@ -234,6 +247,20 @@ class VideoPlayer extends Component {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
toggleSubtitle() {
|
||||||
|
this.setState((state) => {
|
||||||
|
return { subtitlesOn: !state.subtitlesOn };
|
||||||
|
}, () => {
|
||||||
|
const { subtitlesOn } = this.state;
|
||||||
|
const { isPresenter } = this.props;
|
||||||
|
if (!isPresenter && subtitlesOn) {
|
||||||
|
this?.player?.getInternalPlayer()?.setOption('captions', 'reload', true);
|
||||||
|
} else {
|
||||||
|
this?.player?.getInternalPlayer()?.unloadModule('captions');
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
handleOnReady() {
|
handleOnReady() {
|
||||||
const { hasPlayedBefore, playerIsReady } = this;
|
const { hasPlayedBefore, playerIsReady } = this;
|
||||||
|
|
||||||
@ -301,12 +328,16 @@ class VideoPlayer extends Component {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
handleOnProgress() {
|
handleOnProgress(data) {
|
||||||
const { mutedByEchoTest } = this.state;
|
const { mutedByEchoTest } = this.state;
|
||||||
|
|
||||||
const volume = this.getCurrentVolume();
|
const volume = this.getCurrentVolume();
|
||||||
const muted = this.getMuted();
|
const muted = this.getMuted();
|
||||||
|
|
||||||
|
const { played, loaded } = data;
|
||||||
|
|
||||||
|
this.setState({played, loaded});
|
||||||
|
|
||||||
if (!mutedByEchoTest) {
|
if (!mutedByEchoTest) {
|
||||||
this.setState({ volume, muted });
|
this.setState({ volume, muted });
|
||||||
}
|
}
|
||||||
@ -380,10 +411,10 @@ class VideoPlayer extends Component {
|
|||||||
}
|
}
|
||||||
|
|
||||||
getMuted() {
|
getMuted() {
|
||||||
const { mutedByEchoTest } = this.state;
|
const { mutedByEchoTest, muted } = this.state;
|
||||||
const intPlayer = this.player && this.player.getInternalPlayer();
|
const intPlayer = this.player && this.player.getInternalPlayer();
|
||||||
|
|
||||||
return intPlayer && intPlayer.isMuted && intPlayer.isMuted() && !mutedByEchoTest;
|
return (intPlayer && intPlayer.isMuted && intPlayer.isMuted?.() && !mutedByEchoTest) || muted;
|
||||||
}
|
}
|
||||||
|
|
||||||
autoPlayBlockDetected() {
|
autoPlayBlockDetected() {
|
||||||
@ -549,7 +580,7 @@ class VideoPlayer extends Component {
|
|||||||
|
|
||||||
const {
|
const {
|
||||||
playing, playbackRate, mutedByEchoTest, autoPlayBlocked,
|
playing, playbackRate, mutedByEchoTest, autoPlayBlocked,
|
||||||
volume, muted, key, showHoverToolBar,
|
volume, muted, key, showHoverToolBar, played, loaded, subtitlesOn
|
||||||
} = this.state;
|
} = this.state;
|
||||||
|
|
||||||
// This looks weird, but I need to get this nested player
|
// This looks weird, but I need to get this nested player
|
||||||
@ -578,6 +609,7 @@ class VideoPlayer extends Component {
|
|||||||
width,
|
width,
|
||||||
pointerEvents: isResizing ? 'none' : 'inherit',
|
pointerEvents: isResizing ? 'none' : 'inherit',
|
||||||
display: isMinimized && 'none',
|
display: isMinimized && 'none',
|
||||||
|
background: 'var(--color-black)',
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<Styled.VideoPlayerWrapper
|
<Styled.VideoPlayerWrapper
|
||||||
@ -617,10 +649,7 @@ class VideoPlayer extends Component {
|
|||||||
!isPresenter
|
!isPresenter
|
||||||
? [
|
? [
|
||||||
(
|
(
|
||||||
<Styled.HoverToolbar
|
<Styled.HoverToolbar key="hover-toolbar-external-video">
|
||||||
toolbarStyle={toolbarStyle}
|
|
||||||
key="hover-toolbar-external-video"
|
|
||||||
>
|
|
||||||
<VolumeSlider
|
<VolumeSlider
|
||||||
hideVolume={this.hideVolume[playerName]}
|
hideVolume={this.hideVolume[playerName]}
|
||||||
volume={volume}
|
volume={volume}
|
||||||
@ -628,12 +657,32 @@ class VideoPlayer extends Component {
|
|||||||
onMuted={this.handleOnMuted}
|
onMuted={this.handleOnMuted}
|
||||||
onVolumeChanged={this.handleVolumeChanged}
|
onVolumeChanged={this.handleVolumeChanged}
|
||||||
/>
|
/>
|
||||||
|
<Styled.ButtonsWrapper>
|
||||||
<ReloadButton
|
<ReloadButton
|
||||||
handleReload={this.handleReload}
|
handleReload={this.handleReload}
|
||||||
label={intl.formatMessage(intlMessages.refreshLabel)}
|
label={intl.formatMessage(intlMessages.refreshLabel)}
|
||||||
/>
|
/>
|
||||||
|
{playerName === 'YouTube' && (
|
||||||
|
<Subtitles
|
||||||
|
toggleSubtitle={this.toggleSubtitle}
|
||||||
|
label={subtitlesOn
|
||||||
|
? intl.formatMessage(intlMessages.subtitlesOn)
|
||||||
|
: intl.formatMessage(intlMessages.subtitlesOff)
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
</Styled.ButtonsWrapper>
|
||||||
{this.renderFullscreenButton()}
|
{this.renderFullscreenButton()}
|
||||||
|
|
||||||
|
<Styled.ProgressBar>
|
||||||
|
<Styled.Loaded
|
||||||
|
style={{ width: loaded * 100 + '%' }}
|
||||||
|
>
|
||||||
|
<Styled.Played
|
||||||
|
style={{ width: played * 100 / loaded + '%'}}
|
||||||
|
/>
|
||||||
|
</Styled.Loaded>
|
||||||
|
</Styled.ProgressBar>
|
||||||
</Styled.HoverToolbar>
|
</Styled.HoverToolbar>
|
||||||
),
|
),
|
||||||
(deviceInfo.isMobile && playing) && (
|
(deviceInfo.isMobile && playing) && (
|
||||||
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user