Merge branch 'develop' of https://github.com/bigbluebutton/bigbluebutton into u23-recsa
This commit is contained in:
commit
95fabfe8c2
@ -49,7 +49,6 @@ trait SystemConfiguration {
|
||||
|
||||
lazy val endMeetingWhenNoMoreAuthedUsers = Try(config.getBoolean("apps.endMeetingWhenNoMoreAuthedUsers")).getOrElse(false)
|
||||
lazy val endMeetingWhenNoMoreAuthedUsersAfterMinutes = Try(config.getInt("apps.endMeetingWhenNoMoreAuthedUsersAfterMinutes")).getOrElse(2)
|
||||
lazy val multiUserWhiteboardDefault = Try(config.getBoolean("whiteboard.multiUserDefault")).getOrElse(false)
|
||||
|
||||
// Redis server configuration
|
||||
lazy val redisHost = Try(config.getString("redis.host")).getOrElse("127.0.0.1")
|
||||
|
@ -135,10 +135,6 @@ class BigBlueButtonActor(
|
||||
|
||||
RunningMeetings.add(meetings, m)
|
||||
|
||||
// Send new 2x message
|
||||
val msgEvent = MsgBuilder.buildMeetingCreatedEvtMsg(m.props.meetingProp.intId, msg.body.props)
|
||||
m.outMsgRouter.send(msgEvent)
|
||||
|
||||
case Some(m) =>
|
||||
log.info("Meeting already created. meetingID={}", msg.body.props.meetingProp.intId)
|
||||
// do nothing
|
||||
|
@ -10,6 +10,7 @@ object ScreenshareModel {
|
||||
status.voiceConf = ""
|
||||
status.screenshareConf = ""
|
||||
status.timestamp = ""
|
||||
status.hasAudio = false
|
||||
}
|
||||
|
||||
def getScreenshareStarted(status: ScreenshareModel): Boolean = {
|
||||
@ -79,6 +80,14 @@ object ScreenshareModel {
|
||||
def getTimestamp(status: ScreenshareModel): String = {
|
||||
status.timestamp
|
||||
}
|
||||
|
||||
def setHasAudio(status: ScreenshareModel, hasAudio: Boolean): Unit = {
|
||||
status.hasAudio = hasAudio
|
||||
}
|
||||
|
||||
def getHasAudio(status: ScreenshareModel): Boolean = {
|
||||
status.hasAudio
|
||||
}
|
||||
}
|
||||
|
||||
class ScreenshareModel {
|
||||
@ -90,4 +99,5 @@ class ScreenshareModel {
|
||||
private var voiceConf: String = ""
|
||||
private var screenshareConf: String = ""
|
||||
private var timestamp: String = ""
|
||||
private var hasAudio = false
|
||||
}
|
||||
|
@ -25,7 +25,14 @@ class WhiteboardModel extends SystemConfiguration {
|
||||
}
|
||||
|
||||
private def createWhiteboard(wbId: String): Whiteboard = {
|
||||
new Whiteboard(wbId, multiUserWhiteboardDefault, System.currentTimeMillis(), 0, new HashMap[String, List[AnnotationVO]]())
|
||||
new Whiteboard(
|
||||
wbId,
|
||||
Array.empty[String],
|
||||
Array.empty[String],
|
||||
System.currentTimeMillis(),
|
||||
0,
|
||||
new HashMap[String, List[AnnotationVO]]()
|
||||
)
|
||||
}
|
||||
|
||||
private def getAnnotationsByUserId(wb: Whiteboard, id: String): List[AnnotationVO] = {
|
||||
@ -184,7 +191,7 @@ class WhiteboardModel extends SystemConfiguration {
|
||||
if (hasWhiteboard(wbId)) {
|
||||
val wb = getWhiteboard(wbId)
|
||||
|
||||
if (wb.multiUser) {
|
||||
if (wb.multiUser.contains(userId)) {
|
||||
if (wb.annotationsMap.contains(userId)) {
|
||||
val newWb = wb.copy(annotationsMap = wb.annotationsMap - userId)
|
||||
saveWhiteboard(newWb)
|
||||
@ -205,7 +212,7 @@ class WhiteboardModel extends SystemConfiguration {
|
||||
var last: Option[AnnotationVO] = None
|
||||
val wb = getWhiteboard(wbId)
|
||||
|
||||
if (wb.multiUser) {
|
||||
if (wb.multiUser.contains(userId)) {
|
||||
val usersAnnotations = getAnnotationsByUserId(wb, userId)
|
||||
|
||||
//not empty and head id equals annotation id
|
||||
@ -234,13 +241,21 @@ class WhiteboardModel extends SystemConfiguration {
|
||||
wb.copy(annotationsMap = newAnnotationsMap)
|
||||
}
|
||||
|
||||
def modifyWhiteboardAccess(wbId: String, multiUser: Boolean) {
|
||||
def modifyWhiteboardAccess(wbId: String, multiUser: Array[String]) {
|
||||
val wb = getWhiteboard(wbId)
|
||||
val newWb = wb.copy(multiUser = multiUser, changedModeOn = System.currentTimeMillis())
|
||||
val newWb = wb.copy(multiUser = multiUser, oldMultiUser = wb.multiUser, changedModeOn = System.currentTimeMillis())
|
||||
saveWhiteboard(newWb)
|
||||
}
|
||||
|
||||
def getWhiteboardAccess(wbId: String): Boolean = getWhiteboard(wbId).multiUser
|
||||
def getWhiteboardAccess(wbId: String): Array[String] = getWhiteboard(wbId).multiUser
|
||||
|
||||
def hasWhiteboardAccess(wbId: String, userId: String): Boolean = {
|
||||
val wb = getWhiteboard(wbId)
|
||||
wb.multiUser.contains(userId) || {
|
||||
val lastChange = System.currentTimeMillis() - wb.changedModeOn
|
||||
wb.oldMultiUser.contains(userId) && lastChange < 5000
|
||||
}
|
||||
}
|
||||
|
||||
def getChangedModeOn(wbId: String): Long = getWhiteboard(wbId).changedModeOn
|
||||
|
||||
|
@ -25,9 +25,10 @@ trait GetScreenshareStatusReqMsgHdlr {
|
||||
val vidWidth = ScreenshareModel.getScreenshareVideoWidth(liveMeeting.screenshareModel)
|
||||
val vidHeight = ScreenshareModel.getScreenshareVideoHeight(liveMeeting.screenshareModel)
|
||||
val timestamp = ScreenshareModel.getTimestamp(liveMeeting.screenshareModel)
|
||||
val hasAudio = ScreenshareModel.getHasAudio(liveMeeting.screenshareModel)
|
||||
|
||||
val body = ScreenshareRtmpBroadcastStartedEvtMsgBody(voiceConf, screenshareConf,
|
||||
stream, vidWidth, vidHeight, timestamp)
|
||||
stream, vidWidth, vidHeight, timestamp, hasAudio)
|
||||
val event = ScreenshareRtmpBroadcastStartedEvtMsg(header, body)
|
||||
BbbCommonEnvCoreMsg(envelope, event)
|
||||
}
|
||||
|
@ -10,7 +10,7 @@ trait ScreenshareRtmpBroadcastStartedVoiceConfEvtMsgHdlr {
|
||||
|
||||
def handle(msg: ScreenshareRtmpBroadcastStartedVoiceConfEvtMsg, liveMeeting: LiveMeeting, bus: MessageBus): Unit = {
|
||||
def broadcastEvent(voiceConf: String, screenshareConf: String, stream: String, vidWidth: Int, vidHeight: Int,
|
||||
timestamp: String): BbbCommonEnvCoreMsg = {
|
||||
timestamp: String, hasAudio: Boolean): BbbCommonEnvCoreMsg = {
|
||||
|
||||
val routing = Routing.addMsgToClientRouting(
|
||||
MessageTypes.BROADCAST_TO_MEETING,
|
||||
@ -23,7 +23,7 @@ trait ScreenshareRtmpBroadcastStartedVoiceConfEvtMsgHdlr {
|
||||
)
|
||||
|
||||
val body = ScreenshareRtmpBroadcastStartedEvtMsgBody(voiceConf, screenshareConf,
|
||||
stream, vidWidth, vidHeight, timestamp)
|
||||
stream, vidWidth, vidHeight, timestamp, hasAudio)
|
||||
val event = ScreenshareRtmpBroadcastStartedEvtMsg(header, body)
|
||||
BbbCommonEnvCoreMsg(envelope, event)
|
||||
}
|
||||
@ -41,12 +41,13 @@ trait ScreenshareRtmpBroadcastStartedVoiceConfEvtMsgHdlr {
|
||||
ScreenshareModel.setVoiceConf(liveMeeting.screenshareModel, msg.body.voiceConf)
|
||||
ScreenshareModel.setScreenshareConf(liveMeeting.screenshareModel, msg.body.screenshareConf)
|
||||
ScreenshareModel.setTimestamp(liveMeeting.screenshareModel, msg.body.timestamp)
|
||||
ScreenshareModel.setHasAudio(liveMeeting.screenshareModel, msg.body.hasAudio)
|
||||
|
||||
log.info("START broadcast ALLOWED when isBroadcastingRTMP=false")
|
||||
|
||||
// Notify viewers in the meeting that there's an rtmp stream to view
|
||||
val msgEvent = broadcastEvent(msg.body.voiceConf, msg.body.screenshareConf, msg.body.stream,
|
||||
msg.body.vidWidth, msg.body.vidHeight, msg.body.timestamp)
|
||||
msg.body.vidWidth, msg.body.vidHeight, msg.body.timestamp, msg.body.hasAudio)
|
||||
bus.outGW.send(msgEvent)
|
||||
} else {
|
||||
log.info("START broadcast NOT ALLOWED when isBroadcastingRTMP=true")
|
||||
|
@ -21,7 +21,7 @@ trait ClearWhiteboardPubMsgHdlr extends RightsManagementTrait {
|
||||
bus.outGW.send(msgEvent)
|
||||
}
|
||||
|
||||
if (filterWhiteboardMessage(msg.body.whiteboardId, liveMeeting) && permissionFailed(PermissionCheck.GUEST_LEVEL, PermissionCheck.PRESENTER_LEVEL, liveMeeting.users2x, msg.header.userId)) {
|
||||
if (filterWhiteboardMessage(msg.body.whiteboardId, msg.header.userId, liveMeeting) && permissionFailed(PermissionCheck.GUEST_LEVEL, PermissionCheck.PRESENTER_LEVEL, liveMeeting.users2x, msg.header.userId)) {
|
||||
val meetingId = liveMeeting.props.meetingProp.intId
|
||||
val reason = "No permission to clear the whiteboard."
|
||||
PermissionCheck.ejectUserForFailedPermission(meetingId, msg.header.userId, reason, bus.outGW, liveMeeting)
|
||||
|
@ -9,7 +9,7 @@ trait GetWhiteboardAnnotationsReqMsgHdlr {
|
||||
|
||||
def handle(msg: GetWhiteboardAnnotationsReqMsg, liveMeeting: LiveMeeting, bus: MessageBus): Unit = {
|
||||
|
||||
def broadcastEvent(msg: GetWhiteboardAnnotationsReqMsg, history: Array[AnnotationVO], multiUser: Boolean): Unit = {
|
||||
def broadcastEvent(msg: GetWhiteboardAnnotationsReqMsg, history: Array[AnnotationVO], multiUser: Array[String]): Unit = {
|
||||
val routing = Routing.addMsgToHtml5InstanceIdRouting(liveMeeting.props.meetingProp.intId, liveMeeting.props.systemProps.html5InstanceId.toString)
|
||||
|
||||
val envelope = BbbCoreEnvelope(GetWhiteboardAnnotationsRespMsg.NAME, routing)
|
||||
|
@ -21,7 +21,7 @@ trait ModifyWhiteboardAccessPubMsgHdlr extends RightsManagementTrait {
|
||||
bus.outGW.send(msgEvent)
|
||||
}
|
||||
|
||||
if (filterWhiteboardMessage(msg.body.whiteboardId, liveMeeting) && permissionFailed(PermissionCheck.GUEST_LEVEL, PermissionCheck.PRESENTER_LEVEL, liveMeeting.users2x, msg.header.userId)) {
|
||||
if (filterWhiteboardMessage(msg.body.whiteboardId, msg.header.userId, liveMeeting) && permissionFailed(PermissionCheck.GUEST_LEVEL, PermissionCheck.PRESENTER_LEVEL, liveMeeting.users2x, msg.header.userId)) {
|
||||
val meetingId = liveMeeting.props.meetingProp.intId
|
||||
val reason = "No permission to modify access to the whiteboard."
|
||||
PermissionCheck.ejectUserForFailedPermission(meetingId, msg.header.userId, reason, bus.outGW, liveMeeting)
|
||||
|
@ -21,7 +21,7 @@ trait SendCursorPositionPubMsgHdlr extends RightsManagementTrait {
|
||||
bus.outGW.send(msgEvent)
|
||||
}
|
||||
|
||||
if (filterWhiteboardMessage(msg.body.whiteboardId, liveMeeting) && permissionFailed(PermissionCheck.GUEST_LEVEL, PermissionCheck.PRESENTER_LEVEL, liveMeeting.users2x, msg.header.userId)) {
|
||||
if (filterWhiteboardMessage(msg.body.whiteboardId, msg.header.userId, liveMeeting) && permissionFailed(PermissionCheck.GUEST_LEVEL, PermissionCheck.PRESENTER_LEVEL, liveMeeting.users2x, msg.header.userId)) {
|
||||
val meetingId = liveMeeting.props.meetingProp.intId
|
||||
val reason = "No permission to send your cursor position."
|
||||
// Just drop messages as these might be delayed messages from multi-user whiteboard. Don't want to
|
||||
|
@ -71,7 +71,7 @@ trait SendWhiteboardAnnotationPubMsgHdlr extends RightsManagementTrait {
|
||||
WhiteboardKeyUtil.DRAW_UPDATE_STATUS == annotation.status)
|
||||
}
|
||||
|
||||
if (!excludedWbMsg(msg.body.annotation) && filterWhiteboardMessage(msg.body.annotation.wbId, liveMeeting) && permissionFailed(
|
||||
if (!excludedWbMsg(msg.body.annotation) && filterWhiteboardMessage(msg.body.annotation.wbId, msg.header.userId, liveMeeting) && permissionFailed(
|
||||
PermissionCheck.GUEST_LEVEL,
|
||||
PermissionCheck.PRESENTER_LEVEL, liveMeeting.users2x, msg.header.userId
|
||||
)) {
|
||||
|
@ -21,7 +21,7 @@ trait UndoWhiteboardPubMsgHdlr extends RightsManagementTrait {
|
||||
bus.outGW.send(msgEvent)
|
||||
}
|
||||
|
||||
if (filterWhiteboardMessage(msg.body.whiteboardId, liveMeeting) && permissionFailed(PermissionCheck.GUEST_LEVEL, PermissionCheck.PRESENTER_LEVEL, liveMeeting.users2x, msg.header.userId)) {
|
||||
if (filterWhiteboardMessage(msg.body.whiteboardId, msg.header.userId, liveMeeting) && permissionFailed(PermissionCheck.GUEST_LEVEL, PermissionCheck.PRESENTER_LEVEL, liveMeeting.users2x, msg.header.userId)) {
|
||||
val meetingId = liveMeeting.props.meetingProp.intId
|
||||
val reason = "No permission to undo an annotation."
|
||||
PermissionCheck.ejectUserForFailedPermission(meetingId, msg.header.userId, reason, bus.outGW, liveMeeting)
|
||||
|
@ -5,8 +5,16 @@ import akka.event.Logging
|
||||
import org.bigbluebutton.core.running.LiveMeeting
|
||||
import org.bigbluebutton.common2.msgs.AnnotationVO
|
||||
import org.bigbluebutton.core.apps.WhiteboardKeyUtil
|
||||
import scala.collection.immutable.{ Map, List }
|
||||
|
||||
case class Whiteboard(id: String, multiUser: Boolean, changedModeOn: Long, annotationCount: Int, annotationsMap: scala.collection.immutable.Map[String, scala.collection.immutable.List[AnnotationVO]])
|
||||
case class Whiteboard(
|
||||
id: String,
|
||||
multiUser: Array[String],
|
||||
oldMultiUser: Array[String],
|
||||
changedModeOn: Long,
|
||||
annotationCount: Int,
|
||||
annotationsMap: Map[String, List[AnnotationVO]]
|
||||
)
|
||||
|
||||
class WhiteboardApp2x(implicit val context: ActorContext)
|
||||
extends SendCursorPositionPubMsgHdlr
|
||||
@ -56,18 +64,18 @@ class WhiteboardApp2x(implicit val context: ActorContext)
|
||||
liveMeeting.wbModel.undoWhiteboard(whiteboardId, requesterId)
|
||||
}
|
||||
|
||||
def getWhiteboardAccess(whiteboardId: String, liveMeeting: LiveMeeting): Boolean = {
|
||||
def getWhiteboardAccess(whiteboardId: String, liveMeeting: LiveMeeting): Array[String] = {
|
||||
liveMeeting.wbModel.getWhiteboardAccess(whiteboardId)
|
||||
}
|
||||
|
||||
def modifyWhiteboardAccess(whiteboardId: String, multiUser: Boolean, liveMeeting: LiveMeeting) {
|
||||
def modifyWhiteboardAccess(whiteboardId: String, multiUser: Array[String], liveMeeting: LiveMeeting) {
|
||||
liveMeeting.wbModel.modifyWhiteboardAccess(whiteboardId, multiUser)
|
||||
}
|
||||
|
||||
def filterWhiteboardMessage(whiteboardId: String, liveMeeting: LiveMeeting): Boolean = {
|
||||
def filterWhiteboardMessage(whiteboardId: String, userId: String, liveMeeting: LiveMeeting): Boolean = {
|
||||
// Need to check if the wb mode change from multi-user to single-user. Give 5sec allowance to
|
||||
// allow delayed messages to be handled as clients may have been sending messages while the wb
|
||||
// mode was changed. (ralam nov 22, 2017)
|
||||
if (!liveMeeting.wbModel.getWhiteboardAccess(whiteboardId) && liveMeeting.wbModel.getChangedModeOn(whiteboardId) > 5000) true else false
|
||||
!liveMeeting.wbModel.hasWhiteboardAccess(whiteboardId, userId)
|
||||
}
|
||||
}
|
||||
|
@ -156,6 +156,10 @@ class MeetingActor(
|
||||
|
||||
var lastRttTestSentOn = System.currentTimeMillis()
|
||||
|
||||
// Send new 2x message
|
||||
val msgEvent = MsgBuilder.buildMeetingCreatedEvtMsg(liveMeeting.props.meetingProp.intId, liveMeeting.props)
|
||||
outGW.send(msgEvent)
|
||||
|
||||
// Create a default public group chat
|
||||
state = groupChatApp.handleCreateDefaultPublicGroupChat(state, liveMeeting, msgBus)
|
||||
|
||||
|
@ -95,8 +95,3 @@ recording {
|
||||
# set zero to disable chapter break
|
||||
chapterBreakLengthInMinutes = 0
|
||||
}
|
||||
|
||||
whiteboard {
|
||||
multiUserDefault = false
|
||||
}
|
||||
|
||||
|
@ -83,7 +83,7 @@ public class FreeswitchConferenceEventListener implements ConferenceEventListene
|
||||
if (((ScreenshareRTMPBroadcastEvent) event).getBroadcast()) {
|
||||
ScreenshareRTMPBroadcastEvent evt = (ScreenshareRTMPBroadcastEvent) event;
|
||||
vcs.deskShareRTMPBroadcastStarted(evt.getRoom(), evt.getBroadcastingStreamUrl(),
|
||||
evt.getVideoWidth(), evt.getVideoHeight(), evt.getTimestamp());
|
||||
evt.getVideoWidth(), evt.getVideoHeight(), evt.getTimestamp(), evt.getHasAudio());
|
||||
} else {
|
||||
ScreenshareRTMPBroadcastEvent evt = (ScreenshareRTMPBroadcastEvent) event;
|
||||
vcs.deskShareRTMPBroadcastStopped(evt.getRoom(), evt.getBroadcastingStreamUrl(),
|
||||
|
@ -55,7 +55,8 @@ public interface IVoiceConferenceService {
|
||||
String streamname,
|
||||
Integer videoWidth,
|
||||
Integer videoHeight,
|
||||
String timestamp);
|
||||
String timestamp,
|
||||
boolean hasAudio);
|
||||
|
||||
void deskShareRTMPBroadcastStopped(String room,
|
||||
String streamname,
|
||||
|
@ -25,6 +25,7 @@ public class ScreenshareRTMPBroadcastEvent extends VoiceConferenceEvent {
|
||||
private String streamUrl;
|
||||
private Integer vw;
|
||||
private Integer vh;
|
||||
private boolean hasAudio;
|
||||
|
||||
private final String SCREENSHARE_SUFFIX = "-SCREENSHARE";
|
||||
|
||||
@ -46,6 +47,10 @@ public class ScreenshareRTMPBroadcastEvent extends VoiceConferenceEvent {
|
||||
|
||||
public void setVideoHeight(Integer vh) {this.vh = vh;}
|
||||
|
||||
public void setHasAudio(boolean hasAudio) {
|
||||
this.hasAudio = hasAudio;
|
||||
}
|
||||
|
||||
public Integer getVideoHeight() {return vh;}
|
||||
|
||||
public Integer getVideoWidth() {return vw;}
|
||||
@ -65,4 +70,8 @@ public class ScreenshareRTMPBroadcastEvent extends VoiceConferenceEvent {
|
||||
public boolean getBroadcast() {
|
||||
return broadcast;
|
||||
}
|
||||
|
||||
public boolean getHasAudio() {
|
||||
return hasAudio;
|
||||
}
|
||||
}
|
||||
|
@ -237,13 +237,14 @@ class VoiceConferenceService(healthz: HealthzService,
|
||||
streamname: String,
|
||||
vw: java.lang.Integer,
|
||||
vh: java.lang.Integer,
|
||||
timestamp: String
|
||||
timestamp: String,
|
||||
hasAudio: Boolean
|
||||
) {
|
||||
|
||||
val header = BbbCoreVoiceConfHeader(ScreenshareRtmpBroadcastStartedVoiceConfEvtMsg.NAME, voiceConfId)
|
||||
val body = ScreenshareRtmpBroadcastStartedVoiceConfEvtMsgBody(voiceConf = voiceConfId, screenshareConf = voiceConfId,
|
||||
stream = streamname, vidWidth = vw.intValue(), vidHeight = vh.intValue(),
|
||||
timestamp)
|
||||
timestamp, hasAudio)
|
||||
val envelope = BbbCoreEnvelope(ScreenshareRtmpBroadcastStartedVoiceConfEvtMsg.NAME, Map("voiceConf" -> voiceConfId))
|
||||
|
||||
val msg = new ScreenshareRtmpBroadcastStartedVoiceConfEvtMsg(header, body)
|
||||
|
@ -24,7 +24,7 @@ case class ScreenshareRtmpBroadcastStartedVoiceConfEvtMsg(
|
||||
extends VoiceStandardMsg
|
||||
case class ScreenshareRtmpBroadcastStartedVoiceConfEvtMsgBody(voiceConf: String, screenshareConf: String,
|
||||
stream: String, vidWidth: Int, vidHeight: Int,
|
||||
timestamp: String)
|
||||
timestamp: String, hasAudio: Boolean)
|
||||
|
||||
/**
|
||||
* Sent to clients to notify them of an RTMP stream starting.
|
||||
@ -37,7 +37,7 @@ case class ScreenshareRtmpBroadcastStartedEvtMsg(
|
||||
extends BbbCoreMsg
|
||||
case class ScreenshareRtmpBroadcastStartedEvtMsgBody(voiceConf: String, screenshareConf: String,
|
||||
stream: String, vidWidth: Int, vidHeight: Int,
|
||||
timestamp: String)
|
||||
timestamp: String, hasAudio: Boolean)
|
||||
|
||||
/**
|
||||
* Send by FS that RTMP stream has stopped.
|
||||
|
@ -18,7 +18,7 @@ case class GetWhiteboardAnnotationsReqMsgBody(whiteboardId: String)
|
||||
|
||||
object ModifyWhiteboardAccessPubMsg { val NAME = "ModifyWhiteboardAccessPubMsg" }
|
||||
case class ModifyWhiteboardAccessPubMsg(header: BbbClientMsgHeader, body: ModifyWhiteboardAccessPubMsgBody) extends StandardMsg
|
||||
case class ModifyWhiteboardAccessPubMsgBody(whiteboardId: String, multiUser: Boolean)
|
||||
case class ModifyWhiteboardAccessPubMsgBody(whiteboardId: String, multiUser: Array[String])
|
||||
|
||||
object SendCursorPositionPubMsg { val NAME = "SendCursorPositionPubMsg" }
|
||||
case class SendCursorPositionPubMsg(header: BbbClientMsgHeader, body: SendCursorPositionPubMsgBody) extends StandardMsg
|
||||
@ -48,11 +48,11 @@ case class ClearWhiteboardEvtMsgBody(whiteboardId: String, userId: String, fullC
|
||||
|
||||
object GetWhiteboardAnnotationsRespMsg { val NAME = "GetWhiteboardAnnotationsRespMsg" }
|
||||
case class GetWhiteboardAnnotationsRespMsg(header: BbbClientMsgHeader, body: GetWhiteboardAnnotationsRespMsgBody) extends BbbCoreMsg
|
||||
case class GetWhiteboardAnnotationsRespMsgBody(whiteboardId: String, annotations: Array[AnnotationVO], multiUser: Boolean)
|
||||
case class GetWhiteboardAnnotationsRespMsgBody(whiteboardId: String, annotations: Array[AnnotationVO], multiUser: Array[String])
|
||||
|
||||
object ModifyWhiteboardAccessEvtMsg { val NAME = "ModifyWhiteboardAccessEvtMsg" }
|
||||
case class ModifyWhiteboardAccessEvtMsg(header: BbbClientMsgHeader, body: ModifyWhiteboardAccessEvtMsgBody) extends BbbCoreMsg
|
||||
case class ModifyWhiteboardAccessEvtMsgBody(whiteboardId: String, multiUser: Boolean)
|
||||
case class ModifyWhiteboardAccessEvtMsgBody(whiteboardId: String, multiUser: Array[String])
|
||||
|
||||
object SendCursorPositionEvtMsg { val NAME = "SendCursorPositionEvtMsg" }
|
||||
case class SendCursorPositionEvtMsg(header: BbbClientMsgHeader, body: SendCursorPositionEvtMsgBody) extends BbbCoreMsg
|
||||
|
@ -43,6 +43,7 @@ public class ApiParams {
|
||||
public static final String MODERATOR_ONLY_MESSAGE = "moderatorOnlyMessage";
|
||||
public static final String MODERATOR_PW = "moderatorPW";
|
||||
public static final String MUTE_ON_START = "muteOnStart";
|
||||
public static final String MEETING_KEEP_EVENTS = "meetingKeepEvents";
|
||||
public static final String ALLOW_MODS_TO_UNMUTE_USERS = "allowModsToUnmuteUsers";
|
||||
public static final String NAME = "name";
|
||||
public static final String PARENT_MEETING_ID = "parentMeetingID";
|
||||
|
@ -120,7 +120,6 @@ public class MeetingService implements MessageListener {
|
||||
private RedisStorageService storeService;
|
||||
private CallbackUrlService callbackUrlService;
|
||||
private HTML5LoadBalancingService html5LoadBalancingService;
|
||||
private boolean keepEvents;
|
||||
|
||||
private long usersTimeout;
|
||||
private long enteredUsersTimeout;
|
||||
@ -356,7 +355,7 @@ public class MeetingService implements MessageListener {
|
||||
}
|
||||
|
||||
private boolean storeEvents(Meeting m) {
|
||||
return m.isRecord() || keepEvents;
|
||||
return m.isRecord() || m.getMeetingKeepEvents();
|
||||
}
|
||||
|
||||
private void handleCreateMeeting(Meeting m) {
|
||||
@ -404,6 +403,8 @@ public class MeetingService implements MessageListener {
|
||||
logData.put("logCode", "create_meeting");
|
||||
logData.put("description", "Create meeting.");
|
||||
|
||||
logData.put("meetingKeepEvents", m.getMeetingKeepEvents());
|
||||
|
||||
Gson gson = new Gson();
|
||||
String logStr = gson.toJson(logData);
|
||||
|
||||
@ -417,7 +418,7 @@ public class MeetingService implements MessageListener {
|
||||
m.getDialNumber(), m.getMaxUsers(),
|
||||
m.getMeetingExpireIfNoUserJoinedInMinutes(), m.getmeetingExpireWhenLastUserLeftInMinutes(),
|
||||
m.getUserInactivityInspectTimerInMinutes(), m.getUserInactivityThresholdInMinutes(),
|
||||
m.getUserActivitySignResponseDelayInMinutes(), m.getMuteOnStart(), m.getAllowModsToUnmuteUsers(), keepEvents,
|
||||
m.getUserActivitySignResponseDelayInMinutes(), m.getMuteOnStart(), m.getAllowModsToUnmuteUsers(), m.getMeetingKeepEvents(),
|
||||
m.breakoutRoomsParams,
|
||||
m.lockSettingsParams, m.getHtml5InstanceId());
|
||||
}
|
||||
@ -697,7 +698,7 @@ public class MeetingService implements MessageListener {
|
||||
if (m != null) {
|
||||
m.setForciblyEnded(true);
|
||||
processRecording(m);
|
||||
if (keepEvents) {
|
||||
if (m.getMeetingKeepEvents()) {
|
||||
// The creation of the ended tag must occur after the creation of the
|
||||
// recorded tag to avoid concurrency issues at the recording scripts
|
||||
recordingService.markAsEnded(m.getInternalId());
|
||||
@ -1233,10 +1234,6 @@ public class MeetingService implements MessageListener {
|
||||
stunTurnService = s;
|
||||
}
|
||||
|
||||
public void setKeepEvents(boolean value) {
|
||||
keepEvents = value;
|
||||
}
|
||||
|
||||
public void setUsersTimeout(long value) {
|
||||
usersTimeout = value;
|
||||
}
|
||||
|
@ -87,6 +87,7 @@ public class ParamsProcessorUtil {
|
||||
private boolean webcamsOnlyForModerator;
|
||||
private boolean defaultMuteOnStart = false;
|
||||
private boolean defaultAllowModsToUnmuteUsers = false;
|
||||
private boolean defaultKeepEvents = false;
|
||||
|
||||
private boolean defaultBreakoutRoomsEnabled;
|
||||
private boolean defaultBreakoutRoomsRecord;
|
||||
@ -544,6 +545,12 @@ public class ParamsProcessorUtil {
|
||||
|
||||
meeting.setMuteOnStart(muteOnStart);
|
||||
|
||||
Boolean meetingKeepEvents = defaultKeepEvents;
|
||||
if (!StringUtils.isEmpty(params.get(ApiParams.MEETING_KEEP_EVENTS))) {
|
||||
meetingKeepEvents = Boolean.parseBoolean(params.get(ApiParams.MEETING_KEEP_EVENTS));
|
||||
}
|
||||
meeting.setMeetingKeepEvents(meetingKeepEvents);
|
||||
|
||||
Boolean allowModsToUnmuteUsers = defaultAllowModsToUnmuteUsers;
|
||||
if (!StringUtils.isEmpty(params.get(ApiParams.ALLOW_MODS_TO_UNMUTE_USERS))) {
|
||||
allowModsToUnmuteUsers = Boolean.parseBoolean(params.get(ApiParams.ALLOW_MODS_TO_UNMUTE_USERS));
|
||||
@ -1026,6 +1033,10 @@ public class ParamsProcessorUtil {
|
||||
return defaultMuteOnStart;
|
||||
}
|
||||
|
||||
public void setDefaultKeepEvents(Boolean mke) {
|
||||
defaultKeepEvents = mke;
|
||||
}
|
||||
|
||||
public void setAllowModsToUnmuteUsers(Boolean value) {
|
||||
defaultAllowModsToUnmuteUsers = value;
|
||||
}
|
||||
|
@ -84,6 +84,7 @@ public class Meeting {
|
||||
private String customCopyright = "";
|
||||
private Boolean muteOnStart = false;
|
||||
private Boolean allowModsToUnmuteUsers = false;
|
||||
private Boolean meetingKeepEvents;
|
||||
|
||||
private Integer meetingExpireIfNoUserJoinedInMinutes = 5;
|
||||
private Integer meetingExpireWhenLastUserLeftInMinutes = 1;
|
||||
@ -503,6 +504,14 @@ public class Meeting {
|
||||
return muteOnStart;
|
||||
}
|
||||
|
||||
public void setMeetingKeepEvents(Boolean mke) {
|
||||
meetingKeepEvents = mke;
|
||||
}
|
||||
|
||||
public Boolean getMeetingKeepEvents() {
|
||||
return meetingKeepEvents;
|
||||
}
|
||||
|
||||
public void setAllowModsToUnmuteUsers(Boolean value) {
|
||||
allowModsToUnmuteUsers = value;
|
||||
}
|
||||
|
@ -1,2 +1,2 @@
|
||||
BIGBLUEBUTTON_RELEASE=2.3.0-alpha8
|
||||
BIGBLUEBUTTON_RELEASE=2.3.0-beta-1
|
||||
|
||||
|
@ -23,15 +23,17 @@ else
|
||||
SERVLET_DIR=/var/lib/tomcat7/webapps/bigbluebutton
|
||||
fi
|
||||
|
||||
BBB_WEB_ETC_CONFIG=/etc/bigbluebutton/bbb-web.properties
|
||||
|
||||
PROTOCOL=http
|
||||
if [ -f $SERVLET_DIR/WEB-INF/classes/bigbluebutton.properties ]; then
|
||||
SERVER_URL=$(cat $SERVLET_DIR/WEB-INF/classes/bigbluebutton.properties | sed -n '/^bigbluebutton.web.serverURL/{s/.*\///;p}')
|
||||
if cat $SERVLET_DIR/WEB-INF/classes/bigbluebutton.properties | grep bigbluebutton.web.serverURL | grep -q https; then
|
||||
SERVER_URL=$(cat $SERVLET_DIR/WEB-INF/classes/bigbluebutton.properties $BBB_WEB_ETC_CONFIG | grep -v '#' | sed -n '/^bigbluebutton.web.serverURL/{s/.*\///;p}' | tail -n 1)
|
||||
if cat $SERVLET_DIR/WEB-INF/classes/bigbluebutton.properties $BBB_WEB_ETC_CONFIG | grep -v '#' | grep ^bigbluebutton.web.serverURL | tail -n 1 | grep -q https; then
|
||||
PROTOCOL=https
|
||||
fi
|
||||
fi
|
||||
|
||||
HOST=$(cat $SERVLET_DIR/WEB-INF/classes/bigbluebutton.properties | grep -v '#' | sed -n '/^bigbluebutton.web.serverURL/{s/.*\///;p}')
|
||||
HOST=$(cat $SERVLET_DIR/WEB-INF/classes/bigbluebutton.properties $BBB_WEB_ETC_CONFIG | grep -v '#' | sed -n '/^bigbluebutton.web.serverURL/{s/.*\///;p}' | tail -n 1)
|
||||
|
||||
HTML5_CONFIG=/usr/share/meteor/bundle/programs/server/assets/app/config/settings.yml
|
||||
BBB_WEB_CONFIG=$SERVLET_DIR/WEB-INF/classes/bigbluebutton.properties
|
||||
|
@ -82,7 +82,6 @@ with BigBlueButton; if not, see <http://www.gnu.org/licenses/>.
|
||||
</script>
|
||||
<script src="compatibility/adapter.js?v=VERSION" language="javascript"></script>
|
||||
<script src="compatibility/sip.js?v=VERSION" language="javascript"></script>
|
||||
<script src="compatibility/kurento-extension.js?v=VERSION" language="javascript"></script>
|
||||
<script src="compatibility/kurento-utils.js?v=VERSION" language="javascript"></script>
|
||||
</head>
|
||||
<body style="background-color: #06172A">
|
||||
|
@ -15,7 +15,7 @@ export default function handleWhiteboardAnnotations({ header, body }, meetingId)
|
||||
|
||||
check(annotations, Array);
|
||||
check(whiteboardId, String);
|
||||
check(multiUser, Boolean);
|
||||
check(multiUser, Array);
|
||||
|
||||
clearAnnotations(meetingId, whiteboardId);
|
||||
|
||||
|
@ -10,6 +10,8 @@ export default function clearWhiteboard(whiteboardId) {
|
||||
|
||||
const { meetingId, requesterUserId } = extractCredentials(this.userId);
|
||||
|
||||
check(meetingId, String);
|
||||
check(requesterUserId, String);
|
||||
check(whiteboardId, String);
|
||||
|
||||
const payload = {
|
||||
|
@ -1,8 +1,12 @@
|
||||
import { check } from 'meteor/check';
|
||||
import sendAnnotationHelper from './sendAnnotationHelper';
|
||||
import { extractCredentials } from '/imports/api/common/server/helpers';
|
||||
|
||||
export default function sendAnnotation(annotation) {
|
||||
const { meetingId, requesterUserId } = extractCredentials(this.userId);
|
||||
|
||||
check(meetingId, String);
|
||||
check(requesterUserId, String);
|
||||
|
||||
sendAnnotationHelper(annotation, meetingId, requesterUserId);
|
||||
}
|
||||
|
@ -1,8 +1,12 @@
|
||||
import { extractCredentials } from '/imports/api/common/server/helpers';
|
||||
import sendAnnotationHelper from './sendAnnotationHelper';
|
||||
import { check } from 'meteor/check';
|
||||
|
||||
export default function sendBulkAnnotations(payload) {
|
||||
const { meetingId, requesterUserId } = extractCredentials(this.userId);
|
||||
|
||||
check(meetingId, String);
|
||||
check(requesterUserId, String);
|
||||
|
||||
payload.forEach(annotation => sendAnnotationHelper(annotation, meetingId, requesterUserId));
|
||||
}
|
||||
|
@ -10,6 +10,8 @@ export default function undoAnnotation(whiteboardId) {
|
||||
|
||||
const { meetingId, requesterUserId } = extractCredentials(this.userId);
|
||||
|
||||
check(meetingId, String);
|
||||
check(requesterUserId, String);
|
||||
check(whiteboardId, String);
|
||||
|
||||
const payload = {
|
||||
|
@ -39,6 +39,7 @@ const WEBSOCKET_KEEP_ALIVE_DEBOUNCE = MEDIA.websocketKeepAliveDebounce || 10;
|
||||
const TRACE_SIP = MEDIA.traceSip || false;
|
||||
const AUDIO_MICROPHONE_CONSTRAINTS = Meteor.settings.public.app.defaultSettings
|
||||
.application.microphoneConstraints;
|
||||
const SDP_SEMANTICS = MEDIA.sdpSemantics;
|
||||
|
||||
const getAudioSessionNumber = () => {
|
||||
let currItem = parseInt(sessionStorage.getItem(AUDIO_SESSION_NUM_KEY), 10);
|
||||
@ -388,7 +389,7 @@ class SIPSession {
|
||||
sessionDescriptionHandlerFactoryOptions: {
|
||||
peerConnectionConfiguration: {
|
||||
iceServers,
|
||||
sdpSemantics: 'plan-b',
|
||||
sdpSemantics: SDP_SEMANTICS,
|
||||
},
|
||||
},
|
||||
displayName: callerIdName,
|
||||
|
@ -2,6 +2,7 @@ import { Meteor } from 'meteor/meteor';
|
||||
import RedisPubSub from '/imports/startup/server/redis';
|
||||
import Logger from '/imports/startup/server/logger';
|
||||
import { extractCredentials } from '/imports/api/common/server/helpers';
|
||||
import { check } from 'meteor/check';
|
||||
|
||||
export default function createBreakoutRoom(rooms, durationInMinutes, record = false) {
|
||||
const REDIS_CONFIG = Meteor.settings.private.redis;
|
||||
@ -12,6 +13,9 @@ export default function createBreakoutRoom(rooms, durationInMinutes, record = fa
|
||||
|
||||
const { meetingId, requesterUserId } = extractCredentials(this.userId);
|
||||
|
||||
check(meetingId, String);
|
||||
check(requesterUserId, String);
|
||||
|
||||
const eventName = 'CreateBreakoutRoomsCmdMsg';
|
||||
if (rooms.length > MAX_BREAKOUT_ROOMS) {
|
||||
Logger.info(`Attempt to create breakout rooms with invalid number of rooms in meeting id=${meetingId}`);
|
||||
|
@ -1,6 +1,7 @@
|
||||
import { Meteor } from 'meteor/meteor';
|
||||
import RedisPubSub from '/imports/startup/server/redis';
|
||||
import { extractCredentials } from '/imports/api/common/server/helpers';
|
||||
import { check } from 'meteor/check';
|
||||
|
||||
export default function requestJoinURL({ breakoutId, userId: userIdToInvite }) {
|
||||
const REDIS_CONFIG = Meteor.settings.private.redis;
|
||||
@ -8,6 +9,9 @@ export default function requestJoinURL({ breakoutId, userId: userIdToInvite }) {
|
||||
|
||||
const { meetingId, requesterUserId } = extractCredentials(this.userId);
|
||||
|
||||
check(meetingId, String);
|
||||
check(requesterUserId, String);
|
||||
|
||||
const userId = userIdToInvite || requesterUserId;
|
||||
const eventName = 'RequestBreakoutJoinURLReqMsg';
|
||||
|
||||
|
@ -11,12 +11,14 @@ export default function emitExternalVideoEvent(options) {
|
||||
|
||||
const { meetingId, requesterUserId } = extractCredentials(this.userId);
|
||||
|
||||
check(meetingId, String);
|
||||
check(requesterUserId, String);
|
||||
|
||||
const { status, playerStatus } = options;
|
||||
|
||||
const user = Users.findOne({ meetingId, userId: requesterUserId })
|
||||
const user = Users.findOne({ meetingId, userId: requesterUserId });
|
||||
|
||||
if (user && user.presenter) {
|
||||
|
||||
check(status, String);
|
||||
check(playerStatus, {
|
||||
rate: Match.Maybe(Number),
|
||||
@ -24,13 +26,14 @@ export default function emitExternalVideoEvent(options) {
|
||||
state: Match.Maybe(Boolean),
|
||||
});
|
||||
|
||||
let rate = playerStatus.rate || 0;
|
||||
let time = playerStatus.time || 0;
|
||||
let state = playerStatus.state || 0;
|
||||
const payload = { status, rate, time, state };
|
||||
const rate = playerStatus.rate || 0;
|
||||
const time = playerStatus.time || 0;
|
||||
const state = playerStatus.state || 0;
|
||||
const payload = {
|
||||
status, rate, time, state,
|
||||
};
|
||||
|
||||
Logger.debug(`User id=${requesterUserId} sending ${EVENT_NAME} event:${state} for meeting ${meetingId}`);
|
||||
return RedisPubSub.publishUserMessage(CHANNEL, EVENT_NAME, meetingId, requesterUserId, payload);
|
||||
|
||||
}
|
||||
}
|
||||
|
@ -10,6 +10,10 @@ const PUBLIC_CHAT_TYPE = CHAT_CONFIG.type_public;
|
||||
|
||||
export default function chatMessageBeforeJoinCounter() {
|
||||
const { meetingId, requesterUserId } = extractCredentials(this.userId);
|
||||
|
||||
check(meetingId, String);
|
||||
check(requesterUserId, String);
|
||||
|
||||
const groupChats = GroupChat.find({
|
||||
$or: [
|
||||
{ meetingId, access: PUBLIC_CHAT_TYPE },
|
||||
|
@ -1,6 +1,7 @@
|
||||
import { Meteor } from 'meteor/meteor';
|
||||
import RedisPubSub from '/imports/startup/server/redis';
|
||||
import { extractCredentials } from '/imports/api/common/server/helpers';
|
||||
import { check } from 'meteor/check';
|
||||
|
||||
export default function clearPublicChatHistory() {
|
||||
const REDIS_CONFIG = Meteor.settings.private.redis;
|
||||
@ -11,6 +12,9 @@ export default function clearPublicChatHistory() {
|
||||
|
||||
const { meetingId, requesterUserId } = extractCredentials(this.userId);
|
||||
|
||||
check(meetingId, String);
|
||||
check(requesterUserId, String);
|
||||
|
||||
const payload = {
|
||||
chatId: PUBLIC_GROUP_CHAT_ID,
|
||||
};
|
||||
|
@ -1,14 +1,18 @@
|
||||
import { Meteor } from 'meteor/meteor';
|
||||
import GroupChat from '/imports/api/group-chat';
|
||||
import { GroupChatMsg } from '/imports/api/group-chat-msg';
|
||||
import Users from '/imports/api/users';
|
||||
import { extractCredentials } from '/imports/api/common/server/helpers';
|
||||
import { check } from 'meteor/check';
|
||||
|
||||
const CHAT_CONFIG = Meteor.settings.public.chat;
|
||||
const ITENS_PER_PAGE = CHAT_CONFIG.itemsPerPage;
|
||||
|
||||
export default function fetchMessagePerPage(chatId, page) {
|
||||
const { meetingId, requesterUserId } = extractCredentials(this.userId);
|
||||
|
||||
check(meetingId, String);
|
||||
check(requesterUserId, String);
|
||||
|
||||
const User = Users.findOne({ userId: requesterUserId, meetingId });
|
||||
|
||||
const messages = GroupChatMsg.find({ chatId, meetingId, timestamp: { $lt: User.authTokenValidatedTime } },
|
||||
|
@ -34,6 +34,8 @@ export default function sendGroupChatMsg(chatId, message) {
|
||||
|
||||
const { meetingId, requesterUserId } = extractCredentials(this.userId);
|
||||
|
||||
check(meetingId, String);
|
||||
check(requesterUserId, String);
|
||||
check(message, Object);
|
||||
|
||||
const parsedMessage = parseMessage(message.message);
|
||||
|
@ -11,6 +11,8 @@ export default function startUserTyping(chatId) {
|
||||
const PUBLIC_GROUP_CHAT_ID = CHAT_CONFIG.public_group_id;
|
||||
const { meetingId, requesterUserId } = extractCredentials(this.userId);
|
||||
|
||||
check(meetingId, String);
|
||||
check(requesterUserId, String);
|
||||
check(chatId, String);
|
||||
|
||||
const payload = {
|
||||
|
@ -1,10 +1,14 @@
|
||||
import { UsersTyping } from '/imports/api/group-chat-msg';
|
||||
import stopTyping from '../modifiers/stopTyping';
|
||||
import { extractCredentials } from '/imports/api/common/server/helpers';
|
||||
import { check } from 'meteor/check';
|
||||
|
||||
export default function stopUserTyping() {
|
||||
const { meetingId, requesterUserId } = extractCredentials(this.userId);
|
||||
|
||||
check(meetingId, String);
|
||||
check(requesterUserId, String);
|
||||
|
||||
const userTyping = UsersTyping.findOne({
|
||||
meetingId,
|
||||
userId: requesterUserId,
|
||||
|
@ -11,6 +11,8 @@ export default function createGroupChat(receiver) {
|
||||
|
||||
const { meetingId, requesterUserId } = extractCredentials(this.userId);
|
||||
|
||||
check(meetingId, String);
|
||||
check(requesterUserId, String);
|
||||
check(receiver, Object);
|
||||
|
||||
const payload = {
|
||||
|
@ -1,12 +1,16 @@
|
||||
import { Meteor } from 'meteor/meteor';
|
||||
import RedisPubSub from '/imports/startup/server/redis';
|
||||
import { extractCredentials } from '/imports/api/common/server/helpers';
|
||||
import { check } from 'meteor/check';
|
||||
|
||||
export default function destroyGroupChat() {
|
||||
const REDIS_CONFIG = Meteor.settings.private.redis;
|
||||
const CHANNEL = REDIS_CONFIG.channels.toAkkaApps;
|
||||
const { meetingId, requesterUserId } = extractCredentials(this.userId);
|
||||
|
||||
check(meetingId, String);
|
||||
check(requesterUserId, String);
|
||||
|
||||
const eventName = 'DestroyGroupChatReqMsg';
|
||||
|
||||
const payload = {
|
||||
|
@ -11,6 +11,8 @@ const EVENT_NAME = 'GuestsWaitingApprovedMsg';
|
||||
export default function allowPendingUsers(guests, status) {
|
||||
const { meetingId, requesterUserId } = extractCredentials(this.userId);
|
||||
|
||||
check(meetingId, String);
|
||||
check(requesterUserId, String);
|
||||
check(guests, Array);
|
||||
const mappedGuests = guests.map(guest => ({ status, guest: guest.intId }));
|
||||
|
||||
|
@ -11,6 +11,8 @@ const EVENT_NAME = 'SetGuestPolicyCmdMsg';
|
||||
export default function changeGuestPolicy(policyRule) {
|
||||
const { meetingId, requesterUserId } = extractCredentials(this.userId);
|
||||
|
||||
check(meetingId, String);
|
||||
check(requesterUserId, String);
|
||||
check(policyRule, String);
|
||||
|
||||
const payload = {
|
||||
|
@ -10,6 +10,8 @@ export default function userChangedLocalSettings(settings) {
|
||||
if (!meetingId || !requesterUserId) return;
|
||||
|
||||
check(settings, Object);
|
||||
check(meetingId, String);
|
||||
check(requesterUserId, String);
|
||||
|
||||
const userLocalSettings = LocalSettings
|
||||
.findOne({ meetingId, userId: requesterUserId },
|
||||
|
@ -4,6 +4,7 @@ import handleGetAllMeetings from './handlers/getAllMeetings';
|
||||
import handleMeetingEnd from './handlers/meetingEnd';
|
||||
import handleMeetingDestruction from './handlers/meetingDestruction';
|
||||
import handleMeetingLocksChange from './handlers/meetingLockChange';
|
||||
import handleGuestPolicyChanged from './handlers/guestPolicyChanged';
|
||||
import handleGuestLobbyMessageChanged from './handlers/guestLobbyMessageChanged';
|
||||
import handleUserLockChange from './handlers/userLockChange';
|
||||
import handleRecordingStatusChange from './handlers/recordingStatusChange';
|
||||
@ -22,6 +23,7 @@ RedisPubSub.on('RecordingStatusChangedEvtMsg', handleRecordingStatusChange);
|
||||
RedisPubSub.on('UpdateRecordingTimerEvtMsg', handleRecordingTimerChange);
|
||||
RedisPubSub.on('WebcamsOnlyForModeratorChangedEvtMsg', handleChangeWebcamOnlyModerator);
|
||||
RedisPubSub.on('GetLockSettingsRespMsg', handleMeetingLocksChange);
|
||||
RedisPubSub.on('GuestPolicyChangedEvtMsg', handleGuestPolicyChanged);
|
||||
RedisPubSub.on('GuestLobbyMessageChangedEvtMsg', handleGuestLobbyMessageChanged);
|
||||
RedisPubSub.on('MeetingTimeRemainingUpdateEvtMsg', handleTimeRemainingUpdate);
|
||||
RedisPubSub.on('SelectRandomViewerRespMsg', handleSelectRandomViewer);
|
||||
|
@ -0,0 +1,12 @@
|
||||
import setGuestPolicy from '../modifiers/setGuestPolicy';
|
||||
import { check } from 'meteor/check';
|
||||
|
||||
export default function handleGuestPolicyChanged({ body }, meetingId) {
|
||||
const { policy } = body;
|
||||
|
||||
check(meetingId, String);
|
||||
check(policy, String);
|
||||
|
||||
|
||||
return setGuestPolicy(meetingId, policy);
|
||||
}
|
@ -1,10 +1,14 @@
|
||||
import Logger from '/imports/startup/server/logger';
|
||||
import Meetings from '/imports/api/meetings';
|
||||
import { extractCredentials } from '/imports/api/common/server/helpers';
|
||||
import { check } from 'meteor/check';
|
||||
|
||||
export default function clearRandomlySelectedUser() {
|
||||
const { meetingId, requesterUserId } = extractCredentials(this.userId);
|
||||
|
||||
check(meetingId, String);
|
||||
check(requesterUserId, String);
|
||||
|
||||
const selector = {
|
||||
meetingId,
|
||||
};
|
||||
|
@ -2,6 +2,7 @@ import { Meteor } from 'meteor/meteor';
|
||||
import RedisPubSub from '/imports/startup/server/redis';
|
||||
import Logger from '/imports/startup/server/logger';
|
||||
import { extractCredentials } from '/imports/api/common/server/helpers';
|
||||
import { check } from 'meteor/check';
|
||||
|
||||
export default function endMeeting() {
|
||||
const REDIS_CONFIG = Meteor.settings.private.redis;
|
||||
@ -9,6 +10,9 @@ export default function endMeeting() {
|
||||
const EVENT_NAME = 'LogoutAndEndMeetingCmdMsg';
|
||||
const { meetingId, requesterUserId } = extractCredentials(this.userId);
|
||||
|
||||
check(meetingId, String);
|
||||
check(requesterUserId, String);
|
||||
|
||||
const payload = {
|
||||
userId: requesterUserId,
|
||||
};
|
||||
|
@ -9,6 +9,8 @@ export default function toggleLockSettings(lockSettingsProps) {
|
||||
const EVENT_NAME = 'ChangeLockSettingsInMeetingCmdMsg';
|
||||
const { meetingId, requesterUserId } = extractCredentials(this.userId);
|
||||
|
||||
check(meetingId, String);
|
||||
check(requesterUserId, String);
|
||||
check(lockSettingsProps, {
|
||||
disableCam: Boolean,
|
||||
disableMic: Boolean,
|
||||
|
@ -4,6 +4,7 @@ import RedisPubSub from '/imports/startup/server/redis';
|
||||
import { RecordMeetings } from '/imports/api/meetings';
|
||||
import Users from '/imports/api/users';
|
||||
import { extractCredentials } from '/imports/api/common/server/helpers';
|
||||
import { check } from 'meteor/check';
|
||||
|
||||
export default function toggleRecording() {
|
||||
const REDIS_CONFIG = Meteor.settings.private.redis;
|
||||
@ -11,6 +12,10 @@ export default function toggleRecording() {
|
||||
const ROLE_MODERATOR = Meteor.settings.public.user.role_moderator;
|
||||
|
||||
const { meetingId, requesterUserId } = extractCredentials(this.userId);
|
||||
|
||||
check(meetingId, String);
|
||||
check(requesterUserId, String);
|
||||
|
||||
const EVENT_NAME = 'SetRecordingStatusCmdMsg';
|
||||
|
||||
let meetingRecorded;
|
||||
|
@ -9,6 +9,9 @@ export default function toggleWebcamsOnlyForModerator(webcamsOnlyForModerator) {
|
||||
const EVENT_NAME = 'UpdateWebcamsOnlyForModeratorCmdMsg';
|
||||
|
||||
const { meetingId, requesterUserId } = extractCredentials(this.userId);
|
||||
|
||||
check(meetingId, String);
|
||||
check(requesterUserId, String);
|
||||
check(webcamsOnlyForModerator, Boolean);
|
||||
|
||||
const payload = {
|
||||
|
@ -2,7 +2,7 @@ import { Meteor } from 'meteor/meteor';
|
||||
import RedisPubSub from '/imports/startup/server/redis';
|
||||
import Logger from '/imports/startup/server/logger';
|
||||
import { extractCredentials } from '/imports/api/common/server/helpers';
|
||||
|
||||
import { check } from 'meteor/check';
|
||||
|
||||
export default function transferUser(fromMeetingId, toMeetingId) {
|
||||
const REDIS_CONFIG = Meteor.settings.private.redis;
|
||||
@ -11,6 +11,9 @@ export default function transferUser(fromMeetingId, toMeetingId) {
|
||||
|
||||
const { meetingId, requesterUserId } = extractCredentials(this.userId);
|
||||
|
||||
check(meetingId, String);
|
||||
check(requesterUserId, String);
|
||||
|
||||
const payload = {
|
||||
fromMeetingId,
|
||||
toMeetingId,
|
||||
|
@ -0,0 +1,28 @@
|
||||
import Meetings from '/imports/api/meetings';
|
||||
import Logger from '/imports/startup/server/logger';
|
||||
import { check } from 'meteor/check';
|
||||
|
||||
export default function setGuestPolicy(meetingId, guestPolicy) {
|
||||
check(meetingId, String);
|
||||
check(guestPolicy, String);
|
||||
|
||||
const selector = {
|
||||
meetingId,
|
||||
};
|
||||
|
||||
const modifier = {
|
||||
$set: {
|
||||
'usersProp.guestPolicy': guestPolicy,
|
||||
},
|
||||
};
|
||||
|
||||
try {
|
||||
const { numberAffected } = Meetings.upsert(selector, modifier);
|
||||
|
||||
if (numberAffected) {
|
||||
Logger.verbose(`Set guest policy meetingId=${meetingId} guestPolicy=${guestPolicy}`);
|
||||
}
|
||||
} catch (err) {
|
||||
Logger.error(`Setting guest policy: ${err}`);
|
||||
}
|
||||
}
|
@ -5,6 +5,9 @@ import { extractCredentials } from '/imports/api/common/server/helpers';
|
||||
|
||||
export default function userInstabilityDetected(sender) {
|
||||
const { meetingId, requesterUserId: receiver } = extractCredentials(this.userId);
|
||||
|
||||
check(meetingId, String);
|
||||
check(receiver, String);
|
||||
check(sender, String);
|
||||
|
||||
const payload = {
|
||||
|
@ -11,12 +11,28 @@ export default function sendPollChatMsg({ body }, meetingId) {
|
||||
|
||||
const { answers, numRespondents } = poll;
|
||||
|
||||
const caseInsensitiveReducer = (acc, item) => {
|
||||
const index = acc.findIndex(ans => ans.key.toLowerCase() === item.key.toLowerCase());
|
||||
if(index !== -1) {
|
||||
if(acc[index].numVotes >= item.numVotes) acc[index].numVotes += item.numVotes;
|
||||
else {
|
||||
const tempVotes = acc[index].numVotes;
|
||||
acc[index] = item;
|
||||
acc[index].numVotes += tempVotes;
|
||||
}
|
||||
} else {
|
||||
acc.push(item);
|
||||
}
|
||||
return acc;
|
||||
};
|
||||
|
||||
let responded = 0;
|
||||
let resultString = 'bbb-published-poll-\n';
|
||||
answers.map((item) => {
|
||||
responded += item.numVotes;
|
||||
return item;
|
||||
}).map((item) => {
|
||||
}).reduce(caseInsensitiveReducer, []).map((item) => {
|
||||
item.key = item.key.split('<br/>').join('<br#>');
|
||||
const numResponded = responded === numRespondents ? numRespondents : responded;
|
||||
const pct = Math.round(item.numVotes / numResponded * 100);
|
||||
const pctFotmatted = `${Number.isNaN(pct) ? 0 : pct}%`;
|
||||
|
@ -2,6 +2,7 @@ import RedisPubSub from '/imports/startup/server/redis';
|
||||
import Polls from '/imports/api/polls';
|
||||
import Logger from '/imports/startup/server/logger';
|
||||
import { extractCredentials } from '/imports/api/common/server/helpers';
|
||||
import { check } from 'meteor/check';
|
||||
|
||||
export default function publishPoll() {
|
||||
const REDIS_CONFIG = Meteor.settings.private.redis;
|
||||
@ -9,6 +10,10 @@ export default function publishPoll() {
|
||||
const EVENT_NAME = 'ShowPollResultReqMsg';
|
||||
|
||||
const { meetingId, requesterUserId } = extractCredentials(this.userId);
|
||||
|
||||
check(meetingId, String);
|
||||
check(requesterUserId, String);
|
||||
|
||||
const poll = Polls.findOne({ meetingId }); // TODO--send pollid from client
|
||||
if (!poll) {
|
||||
Logger.error(`Attempted to publish inexisting poll for meetingId: ${meetingId}`);
|
||||
|
@ -10,6 +10,8 @@ export default function publishTypedVote(id, pollAnswer) {
|
||||
|
||||
const { meetingId, requesterUserId } = extractCredentials(this.userId);
|
||||
|
||||
check(meetingId, String);
|
||||
check(requesterUserId, String);
|
||||
check(pollAnswer, String);
|
||||
check(id, String);
|
||||
|
||||
|
@ -10,6 +10,8 @@ export default function publishVote(pollId, pollAnswerId) {
|
||||
const EVENT_NAME = 'RespondToPollReqMsg';
|
||||
const { meetingId, requesterUserId } = extractCredentials(this.userId);
|
||||
|
||||
check(meetingId, String);
|
||||
check(requesterUserId, String);
|
||||
check(pollAnswerId, Number);
|
||||
check(pollId, String);
|
||||
|
||||
|
@ -10,6 +10,8 @@ export default function startPoll(pollType, pollId, question, answers) {
|
||||
|
||||
const { meetingId, requesterUserId } = extractCredentials(this.userId);
|
||||
|
||||
check(meetingId, String);
|
||||
check(requesterUserId, String);
|
||||
check(pollId, String);
|
||||
check(pollType, String);
|
||||
|
||||
|
@ -1,8 +1,13 @@
|
||||
import RedisPubSub from '/imports/startup/server/redis';
|
||||
import { extractCredentials } from '/imports/api/common/server/helpers';
|
||||
import { check } from 'meteor/check';
|
||||
|
||||
export default function stopPoll() {
|
||||
const { meetingId, requesterUserId: requesterId } = extractCredentials(this.userId);
|
||||
|
||||
check(meetingId, String);
|
||||
check(requesterId, String);
|
||||
|
||||
const REDIS_CONFIG = Meteor.settings.private.redis;
|
||||
const CHANNEL = REDIS_CONFIG.channels.toAkkaApps;
|
||||
const EVENT_NAME = 'StopPollReqMsg';
|
||||
|
@ -8,6 +8,9 @@ export default function requestPresentationUploadToken(podId, filename) {
|
||||
const EVENT_NAME = 'PresentationUploadTokenReqMsg';
|
||||
|
||||
const { meetingId, requesterUserId } = extractCredentials(this.userId);
|
||||
|
||||
check(meetingId, String);
|
||||
check(requesterUserId, String);
|
||||
check(podId, String);
|
||||
check(filename, String);
|
||||
|
||||
|
@ -1,10 +1,14 @@
|
||||
import PresentationUploadToken from '/imports/api/presentation-upload-token';
|
||||
import Logger from '/imports/startup/server/logger';
|
||||
import { extractCredentials } from '/imports/api/common/server/helpers';
|
||||
import { check } from 'meteor/check';
|
||||
|
||||
export default function setUsedToken(authzToken) {
|
||||
const { meetingId, requesterUserId } = extractCredentials(this.userId);
|
||||
|
||||
check(meetingId, String);
|
||||
check(requesterUserId, String);
|
||||
|
||||
const payload = {
|
||||
$set: {
|
||||
used: true,
|
||||
|
@ -9,6 +9,8 @@ export default function removePresentation(presentationId, podId) {
|
||||
|
||||
const { meetingId, requesterUserId } = extractCredentials(this.userId);
|
||||
|
||||
check(meetingId, String);
|
||||
check(requesterUserId, String);
|
||||
check(presentationId, String);
|
||||
check(podId, String);
|
||||
|
||||
|
@ -8,6 +8,8 @@ export default function setPresentation(presentationId, podId) {
|
||||
const EVENT_NAME = 'SetCurrentPresentationPubMsg';
|
||||
const { meetingId, requesterUserId } = extractCredentials(this.userId);
|
||||
|
||||
check(meetingId, String);
|
||||
check(requesterUserId, String);
|
||||
check(presentationId, String);
|
||||
check(podId, String);
|
||||
|
||||
|
@ -9,6 +9,8 @@ export default function setPresentationDownloadable(presentationId, downloadable
|
||||
|
||||
const { meetingId, requesterUserId } = extractCredentials(this.userId);
|
||||
|
||||
check(meetingId, String);
|
||||
check(requesterUserId, String);
|
||||
check(downloadable, Boolean);
|
||||
check(presentationId, String);
|
||||
|
||||
|
@ -0,0 +1,61 @@
|
||||
import {
|
||||
SFU_CLIENT_SIDE_ERRORS,
|
||||
SFU_SERVER_SIDE_ERRORS
|
||||
} from '/imports/ui/services/bbb-webrtc-sfu/broker-base-errors';
|
||||
|
||||
// Mapped getDisplayMedia errors. These are bridge agnostic
|
||||
// See: https://developer.mozilla.org/en-US/docs/Web/API/MediaDevices/getDisplayMedia
|
||||
const GDM_ERRORS = {
|
||||
// Fallback error: 1130
|
||||
1130: 'GetDisplayMediaGenericError',
|
||||
1131: 'AbortError',
|
||||
1132: 'InvalidStateError',
|
||||
1133: 'OverconstrainedError',
|
||||
1134: 'TypeError',
|
||||
1135: 'NotFoundError',
|
||||
1136: 'NotAllowedError',
|
||||
1137: 'NotSupportedError',
|
||||
1138: 'NotReadableError',
|
||||
};
|
||||
|
||||
// Import as many bridge specific errors you want in this utilitary and shove
|
||||
// them into the error class slots down below.
|
||||
const CLIENT_SIDE_ERRORS = {
|
||||
1101: "SIGNALLING_TRANSPORT_DISCONNECTED",
|
||||
1102: "SIGNALLING_TRANSPORT_CONNECTION_FAILED",
|
||||
1104: "SCREENSHARE_PLAY_FAILED",
|
||||
1105: "PEER_NEGOTIATION_FAILED",
|
||||
1107: "ICE_STATE_FAILED",
|
||||
1120: "MEDIA_TIMEOUT",
|
||||
1121: "UNKNOWN_ERROR",
|
||||
};
|
||||
|
||||
const SERVER_SIDE_ERRORS = {
|
||||
...SFU_SERVER_SIDE_ERRORS,
|
||||
}
|
||||
|
||||
const AGGREGATED_ERRORS = {
|
||||
...CLIENT_SIDE_ERRORS,
|
||||
...SERVER_SIDE_ERRORS,
|
||||
...GDM_ERRORS,
|
||||
}
|
||||
|
||||
const expandErrors = () => {
|
||||
const expandedErrors = Object.keys(AGGREGATED_ERRORS).reduce((map, key) => {
|
||||
map[AGGREGATED_ERRORS[key]] = { errorCode: key, errorMessage: AGGREGATED_ERRORS[key] };
|
||||
return map;
|
||||
}, {});
|
||||
|
||||
return { ...AGGREGATED_ERRORS, ...expandedErrors };
|
||||
}
|
||||
|
||||
const SCREENSHARING_ERRORS = expandErrors();
|
||||
|
||||
export {
|
||||
GDM_ERRORS,
|
||||
BRIDGE_SERVER_SIDE_ERRORS,
|
||||
BRIDGE_CLIENT_SIDE_ERRORS,
|
||||
// All errors, [code]: [message]
|
||||
// Expanded errors. It's AGGREGATED + message: { errorCode, errorMessage }
|
||||
SCREENSHARING_ERRORS,
|
||||
}
|
@ -1,236 +1,297 @@
|
||||
import Auth from '/imports/ui/services/auth';
|
||||
import BridgeService from './service';
|
||||
import { fetchWebRTCMappedStunTurnServers, getMappedFallbackStun } from '/imports/utils/fetchStunTurnServers';
|
||||
import playAndRetry from '/imports/utils/mediaElementPlayRetry';
|
||||
import logger from '/imports/startup/client/logger';
|
||||
import BridgeService from './service';
|
||||
import ScreenshareBroker from '/imports/ui/services/bbb-webrtc-sfu/screenshare-broker';
|
||||
import { setSharingScreen, screenShareEndAlert } from '/imports/ui/components/screenshare/service';
|
||||
import { SCREENSHARING_ERRORS } from './errors';
|
||||
|
||||
const SFU_CONFIG = Meteor.settings.public.kurento;
|
||||
const SFU_URL = SFU_CONFIG.wsUrl;
|
||||
const CHROME_DEFAULT_EXTENSION_KEY = SFU_CONFIG.chromeDefaultExtensionKey;
|
||||
const CHROME_CUSTOM_EXTENSION_KEY = SFU_CONFIG.chromeExtensionKey;
|
||||
const CHROME_SCREENSHARE_SOURCES = SFU_CONFIG.screenshare.chromeScreenshareSources;
|
||||
const FIREFOX_SCREENSHARE_SOURCE = SFU_CONFIG.screenshare.firefoxScreenshareSource;
|
||||
|
||||
const BRIDGE_NAME = 'kurento'
|
||||
const SCREENSHARE_VIDEO_TAG = 'screenshareVideo';
|
||||
const SEND_ROLE = 'send';
|
||||
const RECV_ROLE = 'recv';
|
||||
|
||||
const CHROME_EXTENSION_KEY = CHROME_CUSTOM_EXTENSION_KEY === 'KEY' ? CHROME_DEFAULT_EXTENSION_KEY : CHROME_CUSTOM_EXTENSION_KEY;
|
||||
// the error-code mapping is bridge specific; that's why it's not in the errors util
|
||||
const ERROR_MAP = {
|
||||
1301: SCREENSHARING_ERRORS.SIGNALLING_TRANSPORT_DISCONNECTED,
|
||||
1302: SCREENSHARING_ERRORS.SIGNALLING_TRANSPORT_CONNECTION_FAILED,
|
||||
1305: SCREENSHARING_ERRORS.PEER_NEGOTIATION_FAILED,
|
||||
1307: SCREENSHARING_ERRORS.ICE_STATE_FAILED,
|
||||
}
|
||||
|
||||
const getUserId = () => Auth.userID;
|
||||
const mapErrorCode = (error) => {
|
||||
const { errorCode } = error;
|
||||
const mappedError = ERROR_MAP[errorCode];
|
||||
|
||||
const getMeetingId = () => Auth.meetingID;
|
||||
if (errorCode == null || mappedError == null) return error;
|
||||
error.errorCode = mappedError.errorCode;
|
||||
error.errorMessage = mappedError.errorMessage;
|
||||
error.message = mappedError.errorMessage;
|
||||
|
||||
const getUsername = () => Auth.fullname;
|
||||
|
||||
const getSessionToken = () => Auth.sessionToken;
|
||||
return error;
|
||||
}
|
||||
|
||||
export default class KurentoScreenshareBridge {
|
||||
static normalizeError(error = {}) {
|
||||
const errorMessage = error.name || error.message || error.reason || 'Unknown error';
|
||||
const errorCode = error.code || undefined;
|
||||
const errorReason = error.reason || error.id || 'Undefined reason';
|
||||
|
||||
return { errorMessage, errorCode, errorReason };
|
||||
constructor() {
|
||||
this.role;
|
||||
this.broker;
|
||||
this._gdmStream;
|
||||
this.hasAudio = false;
|
||||
this.connectionAttempts = 0;
|
||||
this.reconnecting = false;
|
||||
this.reconnectionTimeout;
|
||||
this.restartIntervalMs = BridgeService.BASE_MEDIA_TIMEOUT;
|
||||
}
|
||||
|
||||
static handlePresenterFailure(error, started = false) {
|
||||
const normalizedError = KurentoScreenshareBridge.normalizeError(error);
|
||||
if (!started) {
|
||||
logger.error({
|
||||
logCode: 'screenshare_presenter_error_failed_to_connect',
|
||||
extraInfo: { ...normalizedError },
|
||||
}, `Screenshare presenter failed when trying to start due to ${normalizedError.errorMessage}`);
|
||||
} else {
|
||||
logger.error({
|
||||
logCode: 'screenshare_presenter_error_failed_after_success',
|
||||
extraInfo: { ...normalizedError },
|
||||
}, `Screenshare presenter failed during working session due to ${normalizedError.errorMessage}`);
|
||||
get gdmStream() {
|
||||
return this._gdmStream;
|
||||
}
|
||||
|
||||
set gdmStream(stream) {
|
||||
this._gdmStream = stream;
|
||||
}
|
||||
|
||||
outboundStreamReconnect() {
|
||||
const currentRestartIntervalMs = this.restartIntervalMs;
|
||||
const stream = this.gdmStream;
|
||||
|
||||
logger.warn({
|
||||
logCode: 'screenshare_presenter_reconnect',
|
||||
extraInfo: {
|
||||
reconnecting: this.reconnecting,
|
||||
role: this.role,
|
||||
bridge: BRIDGE_NAME
|
||||
},
|
||||
}, `Screenshare presenter session is reconnecting`);
|
||||
|
||||
this.stop();
|
||||
this.restartIntervalMs = BridgeService.getNextReconnectionInterval(currentRestartIntervalMs);
|
||||
this.share(stream, this.onerror).then(() => {
|
||||
this.clearReconnectionTimeout();
|
||||
}).catch(error => {
|
||||
// Error handling is a no-op because it will be "handled" in handlePresenterFailure
|
||||
logger.debug({
|
||||
logCode: 'screenshare_reconnect_failed',
|
||||
extraInfo: {
|
||||
errorCode: error.errorCode,
|
||||
errorMessage: error.errorMessage,
|
||||
reconnecting: this.reconnecting,
|
||||
role: this.role,
|
||||
bridge: BRIDGE_NAME
|
||||
},
|
||||
}, 'Screensharing reconnect failed');
|
||||
});
|
||||
}
|
||||
|
||||
inboundStreamReconnect() {
|
||||
const currentRestartIntervalMs = this.restartIntervalMs;
|
||||
|
||||
logger.warn({
|
||||
logCode: 'screenshare_viewer_reconnect',
|
||||
extraInfo: {
|
||||
reconnecting: this.reconnecting,
|
||||
role: this.role,
|
||||
bridge: BRIDGE_NAME
|
||||
},
|
||||
}, `Screenshare viewer session is reconnecting`);
|
||||
|
||||
// Cleanly stop everything before triggering a reconnect
|
||||
this.stop();
|
||||
// Create new reconnect interval time
|
||||
this.restartIntervalMs = BridgeService.getNextReconnectionInterval(currentRestartIntervalMs);
|
||||
this.view(this.hasAudio).then(() => {
|
||||
this.clearReconnectionTimeout();
|
||||
}).catch(error => {
|
||||
// Error handling is a no-op because it will be "handled" in handleViewerFailure
|
||||
logger.debug({
|
||||
logCode: 'screenshare_reconnect_failed',
|
||||
extraInfo: {
|
||||
errorCode: error.errorCode,
|
||||
errorMessage: error.errorMessage,
|
||||
reconnecting: this.reconnecting,
|
||||
role: this.role,
|
||||
bridge: BRIDGE_NAME
|
||||
},
|
||||
}, 'Screensharing reconnect failed');
|
||||
});
|
||||
}
|
||||
|
||||
handleConnectionTimeoutExpiry() {
|
||||
this.reconnecting = true;
|
||||
|
||||
switch (this.role) {
|
||||
case RECV_ROLE:
|
||||
return this.inboundStreamReconnect();
|
||||
case SEND_ROLE:
|
||||
return this.outboundStreamReconnect();
|
||||
default:
|
||||
this.reconnecting = false;
|
||||
logger.error({
|
||||
logCode: 'screenshare_invalid_role'
|
||||
}, 'Screen sharing with invalid role, wont reconnect');
|
||||
break;
|
||||
}
|
||||
return normalizedError;
|
||||
}
|
||||
|
||||
static handleViewerFailure(error, started = false) {
|
||||
const normalizedError = KurentoScreenshareBridge.normalizeError(error);
|
||||
if (!started) {
|
||||
logger.error({
|
||||
logCode: 'screenshare_viewer_error_failed_to_connect',
|
||||
extraInfo: { ...normalizedError },
|
||||
}, `Screenshare viewer failed when trying to start due to ${normalizedError.errorMessage}`);
|
||||
} else {
|
||||
logger.error({
|
||||
logCode: 'screenshare_viewer_error_failed_after_success',
|
||||
extraInfo: { ...normalizedError },
|
||||
}, `Screenshare viewer failed during working session due to ${normalizedError.errorMessage}`);
|
||||
maxConnectionAttemptsReached () {
|
||||
return this.connectionAttempts > BridgeService.MAX_CONN_ATTEMPTS;
|
||||
}
|
||||
|
||||
scheduleReconnect () {
|
||||
if (this.reconnectionTimeout == null) {
|
||||
this.reconnectionTimeout = setTimeout(
|
||||
this.handleConnectionTimeoutExpiry.bind(this),
|
||||
this.restartIntervalMs
|
||||
);
|
||||
}
|
||||
return normalizedError;
|
||||
}
|
||||
|
||||
static playElement(screenshareMediaElement) {
|
||||
const mediaTagPlayed = () => {
|
||||
logger.info({
|
||||
logCode: 'screenshare_media_play_success',
|
||||
}, 'Screenshare media played successfully');
|
||||
clearReconnectionTimeout () {
|
||||
this.reconnecting = false;
|
||||
this.restartIntervalMs = BridgeService.BASE_MEDIA_TIMEOUT;
|
||||
|
||||
if (this.reconnectionTimeout) {
|
||||
clearTimeout(this.reconnectionTimeout);
|
||||
this.reconnectionTimeout = null;
|
||||
}
|
||||
}
|
||||
|
||||
handleViewerStart() {
|
||||
const mediaElement = document.getElementById(SCREENSHARE_VIDEO_TAG);
|
||||
|
||||
if (mediaElement && this.broker && this.broker.webRtcPeer) {
|
||||
const stream = this.broker.webRtcPeer.getRemoteStream();
|
||||
BridgeService.screenshareLoadAndPlayMediaStream(stream, mediaElement, !this.broker.hasAudio);
|
||||
}
|
||||
|
||||
this.clearReconnectionTimeout();
|
||||
}
|
||||
|
||||
handleBrokerFailure(error) {
|
||||
mapErrorCode(error);
|
||||
const { errorMessage, errorCode } = error;
|
||||
|
||||
logger.error({
|
||||
logCode: 'screenshare_broker_failure',
|
||||
extraInfo: {
|
||||
errorCode, errorMessage,
|
||||
role: this.broker.role,
|
||||
started: this.broker.started,
|
||||
reconnecting: this.reconnecting,
|
||||
bridge: BRIDGE_NAME
|
||||
},
|
||||
}, 'Screenshare broker failure');
|
||||
|
||||
// Screensharing was already successfully negotiated and error occurred during
|
||||
// during call; schedule a reconnect
|
||||
// If the session has not yet started, a reconnect should already be scheduled
|
||||
if (this.broker.started) {
|
||||
this.scheduleReconnect();
|
||||
}
|
||||
|
||||
return error;
|
||||
}
|
||||
|
||||
async view(hasAudio = false) {
|
||||
this.hasAudio = hasAudio;
|
||||
this.role = RECV_ROLE;
|
||||
const iceServers = await BridgeService.getIceServers(Auth.sessionToken);
|
||||
const options = {
|
||||
iceServers,
|
||||
userName: Auth.fullname,
|
||||
hasAudio,
|
||||
};
|
||||
|
||||
if (screenshareMediaElement.paused) {
|
||||
// Tag isn't playing yet. Play it.
|
||||
screenshareMediaElement.play()
|
||||
.then(mediaTagPlayed)
|
||||
.catch((error) => {
|
||||
// NotAllowedError equals autoplay issues, fire autoplay handling event.
|
||||
// This will be handled in the screenshare react component.
|
||||
if (error.name === 'NotAllowedError') {
|
||||
logger.error({
|
||||
logCode: 'screenshare_error_autoplay',
|
||||
extraInfo: { errorName: error.name },
|
||||
}, 'Screenshare play failed due to autoplay error');
|
||||
const tagFailedEvent = new CustomEvent('screensharePlayFailed',
|
||||
{ detail: { mediaElement: screenshareMediaElement } });
|
||||
window.dispatchEvent(tagFailedEvent);
|
||||
} else {
|
||||
// Tag failed for reasons other than autoplay. Log the error and
|
||||
// try playing again a few times until it works or fails for good
|
||||
const played = playAndRetry(screenshareMediaElement);
|
||||
if (!played) {
|
||||
logger.error({
|
||||
logCode: 'screenshare_error_media_play_failed',
|
||||
extraInfo: { errorName: error.name },
|
||||
}, `Screenshare media play failed due to ${error.name}`);
|
||||
} else {
|
||||
mediaTagPlayed();
|
||||
}
|
||||
}
|
||||
});
|
||||
} else {
|
||||
// Media tag is already playing, so log a success. This is really a
|
||||
// logging fallback for a case that shouldn't happen. But if it does
|
||||
// (ie someone re-enables the autoPlay prop in the element), then it
|
||||
// means the stream is playing properly and it'll be logged.
|
||||
mediaTagPlayed();
|
||||
}
|
||||
this.broker = new ScreenshareBroker(
|
||||
Auth.authenticateURL(SFU_URL),
|
||||
BridgeService.getConferenceBridge(),
|
||||
Auth.userID,
|
||||
Auth.meetingID,
|
||||
this.role,
|
||||
options,
|
||||
);
|
||||
|
||||
this.broker.onstart = this.handleViewerStart.bind(this);
|
||||
this.broker.onerror = this.handleBrokerFailure.bind(this);
|
||||
this.broker.onended = this.handleEnded.bind(this);
|
||||
|
||||
return this.broker.view().finally(this.scheduleReconnect.bind(this));
|
||||
}
|
||||
|
||||
static screenshareElementLoadAndPlay(stream, element, muted) {
|
||||
element.muted = muted;
|
||||
element.pause();
|
||||
element.srcObject = stream;
|
||||
KurentoScreenshareBridge.playElement(element);
|
||||
handlePresenterStart() {
|
||||
logger.info({
|
||||
logCode: 'screenshare_presenter_start_success',
|
||||
}, 'Screenshare presenter started succesfully');
|
||||
this.clearReconnectionTimeout();
|
||||
this.reconnecting = false;
|
||||
this.connectionAttempts = 0;
|
||||
}
|
||||
|
||||
kurentoViewLocalPreview() {
|
||||
const screenshareMediaElement = document.getElementById(SCREENSHARE_VIDEO_TAG);
|
||||
const { webRtcPeer } = window.kurentoManager.kurentoScreenshare;
|
||||
|
||||
if (webRtcPeer) {
|
||||
const stream = webRtcPeer.getLocalStream();
|
||||
KurentoScreenshareBridge.screenshareElementLoadAndPlay(stream, screenshareMediaElement, true);
|
||||
}
|
||||
handleEnded() {
|
||||
screenShareEndAlert();
|
||||
}
|
||||
|
||||
async kurentoViewScreen() {
|
||||
const screenshareMediaElement = document.getElementById(SCREENSHARE_VIDEO_TAG);
|
||||
let iceServers = [];
|
||||
let started = false;
|
||||
share(stream, onFailure) {
|
||||
return new Promise(async (resolve, reject) => {
|
||||
this.onerror = onFailure;
|
||||
this.connectionAttempts += 1;
|
||||
this.role = SEND_ROLE;
|
||||
this.hasAudio = BridgeService.streamHasAudioTrack(stream);
|
||||
this.gdmStream = stream;
|
||||
|
||||
try {
|
||||
iceServers = await fetchWebRTCMappedStunTurnServers(getSessionToken());
|
||||
} catch (error) {
|
||||
logger.error({
|
||||
logCode: 'screenshare_viewer_fetchstunturninfo_error',
|
||||
extraInfo: { error },
|
||||
}, 'Screenshare bridge failed to fetch STUN/TURN info, using default');
|
||||
iceServers = getMappedFallbackStun();
|
||||
} finally {
|
||||
const options = {
|
||||
wsUrl: Auth.authenticateURL(SFU_URL),
|
||||
iceServers,
|
||||
logger,
|
||||
userName: getUsername(),
|
||||
};
|
||||
const onerror = (error) => {
|
||||
const normalizedError = this.handleBrokerFailure(error);
|
||||
if (this.maxConnectionAttemptsReached()) {
|
||||
this.clearReconnectionTimeout();
|
||||
this.connectionAttempts = 0;
|
||||
onFailure(SCREENSHARING_ERRORS.MEDIA_TIMEOUT);
|
||||
|
||||
const onFail = (error) => {
|
||||
KurentoScreenshareBridge.handleViewerFailure(error, started);
|
||||
};
|
||||
|
||||
// Callback for the kurento-extension.js script. It's called when the whole
|
||||
// negotiation with SFU is successful. This will load the stream into the
|
||||
// screenshare media element and play it manually.
|
||||
const onSuccess = () => {
|
||||
started = true;
|
||||
const { webRtcPeer } = window.kurentoManager.kurentoVideo;
|
||||
if (webRtcPeer) {
|
||||
const stream = webRtcPeer.getRemoteStream();
|
||||
KurentoScreenshareBridge.screenshareElementLoadAndPlay(
|
||||
stream,
|
||||
screenshareMediaElement,
|
||||
true,
|
||||
);
|
||||
return reject(SCREENSHARING_ERRORS.MEDIA_TIMEOUT);
|
||||
}
|
||||
};
|
||||
|
||||
window.kurentoWatchVideo(
|
||||
SCREENSHARE_VIDEO_TAG,
|
||||
BridgeService.getConferenceBridge(),
|
||||
getUserId(),
|
||||
getMeetingId(),
|
||||
onFail,
|
||||
onSuccess,
|
||||
options,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
kurentoExitVideo() {
|
||||
window.kurentoExitVideo();
|
||||
}
|
||||
|
||||
async kurentoShareScreen(onFail, stream) {
|
||||
let iceServers = [];
|
||||
try {
|
||||
iceServers = await fetchWebRTCMappedStunTurnServers(getSessionToken());
|
||||
} catch (error) {
|
||||
logger.error({ logCode: 'screenshare_presenter_fetchstunturninfo_error' },
|
||||
|
||||
'Screenshare bridge failed to fetch STUN/TURN info, using default');
|
||||
iceServers = getMappedFallbackStun();
|
||||
} finally {
|
||||
const iceServers = await BridgeService.getIceServers(Auth.sessionToken);
|
||||
const options = {
|
||||
wsUrl: Auth.authenticateURL(SFU_URL),
|
||||
chromeExtension: CHROME_EXTENSION_KEY,
|
||||
chromeScreenshareSources: CHROME_SCREENSHARE_SOURCES,
|
||||
firefoxScreenshareSource: FIREFOX_SCREENSHARE_SOURCE,
|
||||
iceServers,
|
||||
logger,
|
||||
userName: getUsername(),
|
||||
userName: Auth.fullname,
|
||||
stream,
|
||||
hasAudio: this.hasAudio,
|
||||
};
|
||||
|
||||
let started = false;
|
||||
|
||||
const failureCallback = (error) => {
|
||||
const normalizedError = KurentoScreenshareBridge.handlePresenterFailure(error, started);
|
||||
onFail(normalizedError);
|
||||
};
|
||||
|
||||
const successCallback = () => {
|
||||
started = true;
|
||||
logger.info({
|
||||
logCode: 'screenshare_presenter_start_success',
|
||||
}, 'Screenshare presenter started succesfully');
|
||||
};
|
||||
|
||||
options.stream = stream || undefined;
|
||||
|
||||
window.kurentoShareScreen(
|
||||
SCREENSHARE_VIDEO_TAG,
|
||||
this.broker = new ScreenshareBroker(
|
||||
Auth.authenticateURL(SFU_URL),
|
||||
BridgeService.getConferenceBridge(),
|
||||
getUserId(),
|
||||
getMeetingId(),
|
||||
failureCallback,
|
||||
successCallback,
|
||||
Auth.userID,
|
||||
Auth.meetingID,
|
||||
this.role,
|
||||
options,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
kurentoExitScreenShare() {
|
||||
window.kurentoExitScreenShare();
|
||||
this.broker.onerror = onerror.bind(this);
|
||||
this.broker.onstreamended = this.stop.bind(this);
|
||||
this.broker.onstart = this.handlePresenterStart.bind(this);
|
||||
this.broker.onended = this.handleEnded.bind(this);
|
||||
|
||||
this.broker.share().then(() => {
|
||||
this.scheduleReconnect();
|
||||
return resolve();
|
||||
}).catch(reject);
|
||||
});
|
||||
};
|
||||
|
||||
stop() {
|
||||
if (this.broker) {
|
||||
this.broker.stop();
|
||||
// Checks if this session is a sharer and if it's not reconnecting
|
||||
// If that's the case, clear the local sharing state in screen sharing UI
|
||||
// component tracker to be extra sure we won't have any client-side state
|
||||
// inconsistency - prlanzarin
|
||||
if (this.broker.role === SEND_ROLE && !this.reconnecting) setSharingScreen(false);
|
||||
this.broker = null;
|
||||
}
|
||||
this.gdmStream = null;
|
||||
this.clearReconnectionTimeout();
|
||||
}
|
||||
}
|
||||
|
@ -1,37 +1,66 @@
|
||||
import Meetings from '/imports/api/meetings';
|
||||
import logger from '/imports/startup/client/logger';
|
||||
import { fetchWebRTCMappedStunTurnServers, getMappedFallbackStun } from '/imports/utils/fetchStunTurnServers';
|
||||
import loadAndPlayMediaStream from '/imports/ui/services/bbb-webrtc-sfu/load-play';
|
||||
import { SCREENSHARING_ERRORS } from './errors';
|
||||
|
||||
const {
|
||||
constraints: GDM_CONSTRAINTS,
|
||||
mediaTimeouts: MEDIA_TIMEOUTS,
|
||||
} = Meteor.settings.public.kurento.screenshare;
|
||||
const {
|
||||
baseTimeout: BASE_MEDIA_TIMEOUT,
|
||||
maxTimeout: MAX_MEDIA_TIMEOUT,
|
||||
maxConnectionAttempts: MAX_CONN_ATTEMPTS,
|
||||
timeoutIncreaseFactor: TIMEOUT_INCREASE_FACTOR,
|
||||
} = MEDIA_TIMEOUTS;
|
||||
|
||||
const hasDisplayMedia = (typeof navigator.getDisplayMedia === 'function'
|
||||
const HAS_DISPLAY_MEDIA = (typeof navigator.getDisplayMedia === 'function'
|
||||
|| (navigator.mediaDevices && typeof navigator.mediaDevices.getDisplayMedia === 'function'));
|
||||
|
||||
const getConferenceBridge = () => Meetings.findOne().voiceProp.voiceConf;
|
||||
|
||||
const normalizeGetDisplayMediaError = (error) => {
|
||||
return SCREENSHARING_ERRORS[error.name] || SCREENSHARING_ERRORS.GetDisplayMediaGenericError;
|
||||
};
|
||||
|
||||
const getBoundGDM = () => {
|
||||
if (typeof navigator.getDisplayMedia === 'function') {
|
||||
return navigator.getDisplayMedia.bind(navigator);
|
||||
} else if (navigator.mediaDevices && typeof navigator.mediaDevices.getDisplayMedia === 'function') {
|
||||
return navigator.mediaDevices.getDisplayMedia.bind(navigator.mediaDevices);
|
||||
}
|
||||
}
|
||||
|
||||
const getScreenStream = async () => {
|
||||
const gDMCallback = (stream) => {
|
||||
// Some older Chromium variants choke on gDM when audio: true by NOT generating
|
||||
// a promise rejection AND not generating a valid input screen stream, need to
|
||||
// work around that manually for now - prlanzarin
|
||||
if (stream == null) {
|
||||
return Promise.reject(SCREENSHARING_ERRORS.NotSupportedError);
|
||||
}
|
||||
|
||||
if (typeof stream.getVideoTracks === 'function'
|
||||
&& typeof constraints.video === 'object') {
|
||||
stream.getVideoTracks().forEach((track) => {
|
||||
if (typeof track.applyConstraints === 'function') {
|
||||
track.applyConstraints(constraints.video).catch((error) => {
|
||||
&& typeof GDM_CONSTRAINTS.video === 'object') {
|
||||
stream.getVideoTracks().forEach(track => {
|
||||
if (typeof track.applyConstraints === 'function') {
|
||||
track.applyConstraints(GDM_CONSTRAINTS.video).catch(error => {
|
||||
logger.warn({
|
||||
logCode: 'screenshare_videoconstraint_failed',
|
||||
extraInfo: { errorName: error.name, errorCode: error.code },
|
||||
},
|
||||
'Error applying screenshare video constraint');
|
||||
'Error applying screenshare video constraint');
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
if (typeof stream.getAudioTracks === 'function'
|
||||
&& typeof constraints.audio === 'object') {
|
||||
stream.getAudioTracks().forEach((track) => {
|
||||
if (typeof track.applyConstraints === 'function') {
|
||||
track.applyConstraints(constraints.audio).catch((error) => {
|
||||
&& typeof GDM_CONSTRAINTS.audio === 'object') {
|
||||
stream.getAudioTracks().forEach(track => {
|
||||
if (typeof track.applyConstraints === 'function') {
|
||||
track.applyConstraints(GDM_CONSTRAINTS.audio).catch(error => {
|
||||
logger.warn({
|
||||
logCode: 'screenshare_audioconstraint_failed',
|
||||
extraInfo: { errorName: error.name, errorCode: error.code },
|
||||
@ -44,39 +73,81 @@ const getScreenStream = async () => {
|
||||
return Promise.resolve(stream);
|
||||
};
|
||||
|
||||
const constraints = hasDisplayMedia ? GDM_CONSTRAINTS : null;
|
||||
const getDisplayMedia = getBoundGDM();
|
||||
|
||||
// getDisplayMedia isn't supported, generate no stream and let the legacy
|
||||
// constraint fetcher work its way on kurento-extension.js
|
||||
if (constraints == null) {
|
||||
return Promise.resolve();
|
||||
}
|
||||
if (typeof navigator.getDisplayMedia === 'function') {
|
||||
return navigator.getDisplayMedia(constraints)
|
||||
if (typeof getDisplayMedia === 'function') {
|
||||
return getDisplayMedia(GDM_CONSTRAINTS)
|
||||
.then(gDMCallback)
|
||||
.catch((error) => {
|
||||
.catch(error => {
|
||||
const normalizedError = normalizeGetDisplayMediaError(error);
|
||||
logger.error({
|
||||
logCode: 'screenshare_getdisplaymedia_failed',
|
||||
extraInfo: { errorName: error.name, errorCode: error.code },
|
||||
extraInfo: { errorCode: normalizedError.errorCode, errorMessage: normalizedError.errorMessage },
|
||||
}, 'getDisplayMedia call failed');
|
||||
return Promise.resolve();
|
||||
});
|
||||
} if (navigator.mediaDevices && typeof navigator.mediaDevices.getDisplayMedia === 'function') {
|
||||
return navigator.mediaDevices.getDisplayMedia(constraints)
|
||||
.then(gDMCallback)
|
||||
.catch((error) => {
|
||||
logger.error({
|
||||
logCode: 'screenshare_getdisplaymedia_failed',
|
||||
extraInfo: { errorName: error.name, errorCode: error.code },
|
||||
}, 'getDisplayMedia call failed');
|
||||
return Promise.resolve();
|
||||
return Promise.reject(normalizedError);
|
||||
});
|
||||
} else {
|
||||
// getDisplayMedia isn't supported, error its way out
|
||||
return Promise.reject(SCREENSHARING_ERRORS.NotSupportedError);
|
||||
}
|
||||
};
|
||||
|
||||
const getIceServers = (sessionToken) => {
|
||||
return fetchWebRTCMappedStunTurnServers(sessionToken).catch(error => {
|
||||
logger.error({
|
||||
logCode: 'screenshare_fetchstunturninfo_error',
|
||||
extraInfo: { error }
|
||||
}, 'Screenshare bridge failed to fetch STUN/TURN info');
|
||||
return getMappedFallbackStun();
|
||||
});
|
||||
}
|
||||
|
||||
const getNextReconnectionInterval = (oldInterval) => {
|
||||
return Math.min(
|
||||
TIMEOUT_INCREASE_FACTOR * oldInterval,
|
||||
MAX_MEDIA_TIMEOUT,
|
||||
);
|
||||
}
|
||||
|
||||
const streamHasAudioTrack = (stream) => {
|
||||
return stream
|
||||
&& typeof stream.getAudioTracks === 'function'
|
||||
&& stream.getAudioTracks().length >= 1;
|
||||
}
|
||||
|
||||
const dispatchAutoplayHandlingEvent = (mediaElement) => {
|
||||
const tagFailedEvent = new CustomEvent('screensharePlayFailed',
|
||||
{ detail: { mediaElement } });
|
||||
window.dispatchEvent(tagFailedEvent);
|
||||
}
|
||||
|
||||
const screenshareLoadAndPlayMediaStream = (stream, mediaElement, muted) => {
|
||||
return loadAndPlayMediaStream(stream, mediaElement, muted).catch(error => {
|
||||
// NotAllowedError equals autoplay issues, fire autoplay handling event.
|
||||
// This will be handled in the screenshare react component.
|
||||
if (error.name === 'NotAllowedError') {
|
||||
logger.error({
|
||||
logCode: 'screenshare_error_autoplay',
|
||||
extraInfo: { errorName: error.name },
|
||||
}, 'Screen share media play failed: autoplay error');
|
||||
dispatchAutoplayHandlingEvent(mediaElement);
|
||||
} else {
|
||||
throw {
|
||||
errorCode: SCREENSHARING_ERRORS.SCREENSHARE_PLAY_FAILED.errorCode,
|
||||
errorMessage: error.message || SCREENSHARING_ERRORS.SCREENSHARE_PLAY_FAILED.errorMessage,
|
||||
};
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
export default {
|
||||
hasDisplayMedia,
|
||||
HAS_DISPLAY_MEDIA,
|
||||
getConferenceBridge,
|
||||
getScreenStream,
|
||||
getIceServers,
|
||||
getNextReconnectionInterval,
|
||||
streamHasAudioTrack,
|
||||
screenshareLoadAndPlayMediaStream,
|
||||
BASE_MEDIA_TIMEOUT,
|
||||
MAX_CONN_ATTEMPTS,
|
||||
};
|
||||
|
@ -11,6 +11,9 @@ export default function switchSlide(slideNumber, podId) { // TODO-- send present
|
||||
const CHANNEL = REDIS_CONFIG.channels.toAkkaApps;
|
||||
const EVENT_NAME = 'SetCurrentPagePubMsg';
|
||||
const { meetingId, requesterUserId } = extractCredentials(this.userId);
|
||||
|
||||
check(meetingId, String);
|
||||
check(requesterUserId, String);
|
||||
check(slideNumber, Number);
|
||||
|
||||
const selector = {
|
||||
|
@ -3,6 +3,7 @@ import { Slides } from '/imports/api/slides';
|
||||
import { Meteor } from 'meteor/meteor';
|
||||
import RedisPubSub from '/imports/startup/server/redis';
|
||||
import { extractCredentials } from '/imports/api/common/server/helpers';
|
||||
import { check } from 'meteor/check';
|
||||
|
||||
export default function zoomSlide(slideNumber, podId, widthRatio, heightRatio, x, y) { // TODO-- send presentationId and SlideId
|
||||
const REDIS_CONFIG = Meteor.settings.private.redis;
|
||||
@ -11,6 +12,9 @@ export default function zoomSlide(slideNumber, podId, widthRatio, heightRatio, x
|
||||
const EVENT_NAME = 'ResizeAndMovePagePubMsg';
|
||||
const { meetingId, requesterUserId } = extractCredentials(this.userId);
|
||||
|
||||
check(meetingId, String);
|
||||
check(requesterUserId, String);
|
||||
|
||||
const selector = {
|
||||
meetingId,
|
||||
podId,
|
||||
|
@ -1,9 +1,14 @@
|
||||
import UserInfos from '/imports/api/users-infos';
|
||||
import Logger from '/imports/startup/server/logger';
|
||||
import { extractCredentials } from '/imports/api/common/server/helpers';
|
||||
import { check } from 'meteor/check';
|
||||
|
||||
export default function removeUserInformation() {
|
||||
const { meetingId, requesterUserId } = extractCredentials(this.userId);
|
||||
|
||||
check(meetingId, String);
|
||||
check(requesterUserId, String);
|
||||
|
||||
const selector = {
|
||||
meetingId,
|
||||
requesterUserId,
|
||||
|
@ -9,6 +9,9 @@ export default function getUserInformation(externalUserId) {
|
||||
const EVENT_NAME = 'LookUpUserReqMsg';
|
||||
|
||||
const { meetingId, requesterUserId } = extractCredentials(this.userId);
|
||||
|
||||
check(meetingId, String);
|
||||
check(requesterUserId, String);
|
||||
check(externalUserId, String);
|
||||
|
||||
const payload = {
|
||||
|
@ -85,6 +85,9 @@ export default function addUserSettings(settings) {
|
||||
|
||||
const { meetingId, requesterUserId: userId } = extractCredentials(this.userId);
|
||||
|
||||
check(meetingId, String);
|
||||
check(userId, String);
|
||||
|
||||
let parameters = {};
|
||||
|
||||
settings.forEach((el) => {
|
||||
|
@ -11,6 +11,9 @@ export default function assignPresenter(userId) { // TODO-- send username from c
|
||||
const EVENT_NAME = 'AssignPresenterReqMsg';
|
||||
|
||||
const { meetingId, requesterUserId } = extractCredentials(this.userId);
|
||||
|
||||
check(meetingId, String);
|
||||
check(requesterUserId, String);
|
||||
check(userId, String);
|
||||
|
||||
const User = Users.findOne({
|
||||
|
@ -11,6 +11,8 @@ export default function changeRole(userId, role) {
|
||||
|
||||
const { meetingId, requesterUserId } = extractCredentials(this.userId);
|
||||
|
||||
check(meetingId, String);
|
||||
check(requesterUserId, String);
|
||||
check(userId, String);
|
||||
check(role, String);
|
||||
|
||||
|
@ -12,6 +12,8 @@ export default function removeUser(userId, banUser) {
|
||||
|
||||
const { meetingId, requesterUserId: ejectedBy } = extractCredentials(this.userId);
|
||||
|
||||
check(meetingId, String);
|
||||
check(ejectedBy, String);
|
||||
check(userId, String);
|
||||
|
||||
const payload = {
|
||||
|
@ -11,6 +11,8 @@ export default function setEmojiStatus(userId, status) {
|
||||
|
||||
const { meetingId, requesterUserId } = extractCredentials(this.userId);
|
||||
|
||||
check(meetingId, String);
|
||||
check(requesterUserId, String);
|
||||
check(userId, String);
|
||||
|
||||
const payload = {
|
||||
|
@ -1,6 +1,7 @@
|
||||
import { Meteor } from 'meteor/meteor';
|
||||
import RedisPubSub from '/imports/startup/server/redis';
|
||||
import { extractCredentials } from '/imports/api/common/server/helpers';
|
||||
import { check } from 'meteor/check';
|
||||
|
||||
export default function setRandomUser() {
|
||||
const REDIS_CONFIG = Meteor.settings.private.redis;
|
||||
@ -9,6 +10,9 @@ export default function setRandomUser() {
|
||||
|
||||
const { meetingId, requesterUserId } = extractCredentials(this.userId);
|
||||
|
||||
check(meetingId, String);
|
||||
check(requesterUserId, String);
|
||||
|
||||
const payload = {
|
||||
requestedBy: requesterUserId,
|
||||
};
|
||||
|
@ -11,6 +11,8 @@ export default function setUserEffectiveConnectionType(effectiveConnectionType)
|
||||
const EVENT_NAME = 'ChangeUserEffectiveConnectionMsg';
|
||||
const { meetingId, requesterUserId } = extractCredentials(this.userId);
|
||||
|
||||
check(meetingId, String);
|
||||
check(requesterUserId, String);
|
||||
check(effectiveConnectionType, String);
|
||||
|
||||
const payload = {
|
||||
|
@ -11,6 +11,7 @@ export default function toggleUserLock(userId, lock) {
|
||||
|
||||
const { meetingId, requesterUserId: lockedBy } = extractCredentials(this.userId);
|
||||
|
||||
check(meetingId, String);
|
||||
check(lockedBy, String);
|
||||
check(userId, String);
|
||||
check(lock, Boolean);
|
||||
|
@ -3,12 +3,17 @@ import Users from '/imports/api/users';
|
||||
import RedisPubSub from '/imports/startup/server/redis';
|
||||
import Logger from '/imports/startup/server/logger';
|
||||
import { extractCredentials } from '/imports/api/common/server/helpers';
|
||||
import { check } from 'meteor/check';
|
||||
|
||||
export default function userActivitySign() {
|
||||
const REDIS_CONFIG = Meteor.settings.private.redis;
|
||||
const CHANNEL = REDIS_CONFIG.channels.toAkkaApps;
|
||||
const EVENT_NAME = 'UserActivitySignCmdMsg';
|
||||
const { meetingId, requesterUserId: userId } = extractCredentials(this.userId);
|
||||
|
||||
check(meetingId, String);
|
||||
check(userId, String);
|
||||
|
||||
const payload = {
|
||||
userId,
|
||||
};
|
||||
|
@ -2,11 +2,15 @@ import Logger from '/imports/startup/server/logger';
|
||||
import Users from '/imports/api/users';
|
||||
import { extractCredentials } from '/imports/api/common/server/helpers';
|
||||
import ClientConnections from '/imports/startup/server/ClientConnections';
|
||||
import { check } from 'meteor/check';
|
||||
|
||||
export default function userLeftMeeting() { // TODO-- spread the code to method/modifier/handler
|
||||
// so we don't update the db in a method
|
||||
const { meetingId, requesterUserId } = extractCredentials(this.userId);
|
||||
|
||||
check(meetingId, String);
|
||||
check(requesterUserId, String);
|
||||
|
||||
const selector = {
|
||||
meetingId,
|
||||
userId: requesterUserId,
|
||||
|
@ -3,6 +3,7 @@ import { Meteor } from 'meteor/meteor';
|
||||
import Logger from '/imports/startup/server/logger';
|
||||
import AuthTokenValidation, { ValidationStates } from '/imports/api/auth-token-validation';
|
||||
import { extractCredentials } from '/imports/api/common/server/helpers';
|
||||
import { check } from 'meteor/check';
|
||||
|
||||
const ROLE_MODERATOR = Meteor.settings.public.user.role_moderator;
|
||||
|
||||
@ -18,7 +19,7 @@ function currentUser() {
|
||||
const selector = {
|
||||
meetingId,
|
||||
userId: requesterUserId,
|
||||
intId: { $exists: true }
|
||||
intId: { $exists: true },
|
||||
};
|
||||
|
||||
const options = {
|
||||
@ -57,7 +58,7 @@ function users(role) {
|
||||
$or: [
|
||||
{ meetingId },
|
||||
],
|
||||
intId: { $exists: true }
|
||||
intId: { $exists: true },
|
||||
};
|
||||
|
||||
const User = Users.findOne({ userId, meetingId }, { fields: { role: 1 } });
|
||||
|
@ -10,9 +10,12 @@ export default function userShareWebcam(stream) {
|
||||
const EVENT_NAME = 'UserBroadcastCamStartMsg';
|
||||
const { meetingId, requesterUserId } = extractCredentials(this.userId);
|
||||
|
||||
check(meetingId, String);
|
||||
check(requesterUserId, String);
|
||||
check(stream, String);
|
||||
|
||||
Logger.info(`user sharing webcam: ${meetingId} ${requesterUserId}`);
|
||||
|
||||
check(stream, String);
|
||||
|
||||
// const actionName = 'joinVideo';
|
||||
/* TODO throw an error if user has no permission to share webcam
|
||||
|
@ -10,10 +10,12 @@ export default function userUnshareWebcam(stream) {
|
||||
const EVENT_NAME = 'UserBroadcastCamStopMsg';
|
||||
const { meetingId, requesterUserId } = extractCredentials(this.userId);
|
||||
|
||||
Logger.info(`user unsharing webcam: ${meetingId} ${requesterUserId}`);
|
||||
|
||||
check(meetingId, String);
|
||||
check(requesterUserId, String);
|
||||
check(stream, String);
|
||||
|
||||
Logger.info(`user unsharing webcam: ${meetingId} ${requesterUserId}`);
|
||||
|
||||
// const actionName = 'joinVideo';
|
||||
/* TODO throw an error if user has no permission to share webcam
|
||||
if (!isAllowedTo(actionName, credentials)) {
|
||||
|
@ -9,6 +9,9 @@ export default function ejectUserFromVoice(userId) {
|
||||
const EVENT_NAME = 'EjectUserFromVoiceCmdMsg';
|
||||
|
||||
const { meetingId, requesterUserId } = extractCredentials(this.userId);
|
||||
|
||||
check(meetingId, String);
|
||||
check(requesterUserId, String);
|
||||
check(userId, String);
|
||||
|
||||
const payload = {
|
||||
|
@ -2,6 +2,7 @@ import { Meteor } from 'meteor/meteor';
|
||||
import RedisPubSub from '/imports/startup/server/redis';
|
||||
import Meetings from '/imports/api/meetings';
|
||||
import { extractCredentials } from '/imports/api/common/server/helpers';
|
||||
import { check } from 'meteor/check';
|
||||
|
||||
export default function muteAllExceptPresenterToggle() {
|
||||
const REDIS_CONFIG = Meteor.settings.private.redis;
|
||||
@ -10,6 +11,9 @@ export default function muteAllExceptPresenterToggle() {
|
||||
|
||||
const { meetingId, requesterUserId } = extractCredentials(this.userId);
|
||||
|
||||
check(meetingId, String);
|
||||
check(requesterUserId, String);
|
||||
|
||||
const meeting = Meetings.findOne({ meetingId });
|
||||
const toggleMeetingMuted = !meeting.voiceProp.muteOnStart;
|
||||
|
||||
|
@ -2,6 +2,7 @@ import { Meteor } from 'meteor/meteor';
|
||||
import RedisPubSub from '/imports/startup/server/redis';
|
||||
import Meetings from '/imports/api/meetings';
|
||||
import { extractCredentials } from '/imports/api/common/server/helpers';
|
||||
import { check } from 'meteor/check';
|
||||
|
||||
export default function muteAllToggle() {
|
||||
const REDIS_CONFIG = Meteor.settings.private.redis;
|
||||
@ -10,6 +11,9 @@ export default function muteAllToggle() {
|
||||
|
||||
const { meetingId, requesterUserId } = extractCredentials(this.userId);
|
||||
|
||||
check(meetingId, String);
|
||||
check(requesterUserId, String);
|
||||
|
||||
const meeting = Meetings.findOne({ meetingId });
|
||||
const toggleMeetingMuted = !meeting.voiceProp.muteOnStart;
|
||||
|
||||
|
@ -5,6 +5,7 @@ import Users from '/imports/api/users';
|
||||
import VoiceUsers from '/imports/api/voice-users';
|
||||
import Meetings from '/imports/api/meetings';
|
||||
import Logger from '/imports/startup/server/logger';
|
||||
import { check } from 'meteor/check';
|
||||
|
||||
export default function muteToggle(uId, toggle) {
|
||||
const REDIS_CONFIG = Meteor.settings.private.redis;
|
||||
@ -12,6 +13,10 @@ export default function muteToggle(uId, toggle) {
|
||||
const EVENT_NAME = 'MuteUserCmdMsg';
|
||||
|
||||
const { meetingId, requesterUserId } = extractCredentials(this.userId);
|
||||
|
||||
check(meetingId, String);
|
||||
check(requesterUserId, String);
|
||||
|
||||
const userToMute = uId || requesterUserId;
|
||||
|
||||
const requester = Users.findOne({
|
||||
|
@ -4,7 +4,7 @@ import modifyWhiteboardAccess from '../modifiers/modifyWhiteboardAccess';
|
||||
export default function handleModifyWhiteboardAccess({ body }, meetingId) {
|
||||
const { multiUser, whiteboardId } = body;
|
||||
|
||||
check(multiUser, Boolean);
|
||||
check(multiUser, Array);
|
||||
check(whiteboardId, String);
|
||||
check(meetingId, String);
|
||||
|
||||
|
@ -0,0 +1,31 @@
|
||||
import Users from '/imports/api/users';
|
||||
import WhiteboardMultiUser from '/imports/api/whiteboard-multi-user/';
|
||||
|
||||
const getMultiUser = (meetingId, whiteboardId) => {
|
||||
const data = WhiteboardMultiUser.findOne(
|
||||
{
|
||||
meetingId,
|
||||
whiteboardId,
|
||||
}, { fields: { multiUser: 1 } },
|
||||
);
|
||||
|
||||
if (!data || !data.multiUser || !Array.isArray(data.multiUser)) return [];
|
||||
|
||||
return data.multiUser;
|
||||
};
|
||||
|
||||
const getUsers = (meetingId) => {
|
||||
const data = Users.find(
|
||||
{ meetingId },
|
||||
{ fields: { userId: 1 } },
|
||||
).fetch();
|
||||
|
||||
if (!data) return [];
|
||||
|
||||
return data.map(user => user.userId);
|
||||
};
|
||||
|
||||
export {
|
||||
getMultiUser,
|
||||
getUsers,
|
||||
};
|
@ -1,6 +1,12 @@
|
||||
import { Meteor } from 'meteor/meteor';
|
||||
import changeWhiteboardAccess from './methods/changeWhiteboardAccess';
|
||||
import addGlobalAccess from './methods/addGlobalAccess';
|
||||
import addIndividualAccess from './methods/addIndividualAccess';
|
||||
import removeGlobalAccess from './methods/removeGlobalAccess';
|
||||
import removeIndividualAccess from './methods/removeIndividualAccess';
|
||||
|
||||
Meteor.methods({
|
||||
changeWhiteboardAccess,
|
||||
addGlobalAccess,
|
||||
addIndividualAccess,
|
||||
removeGlobalAccess,
|
||||
removeIndividualAccess,
|
||||
});
|
||||
|
@ -0,0 +1,27 @@
|
||||
import RedisPubSub from '/imports/startup/server/redis';
|
||||
import { Meteor } from 'meteor/meteor';
|
||||
import { check } from 'meteor/check';
|
||||
import { getUsers } from '/imports/api/whiteboard-multi-user/server/helpers';
|
||||
import { extractCredentials } from '/imports/api/common/server/helpers';
|
||||
|
||||
export default function addGlobalAccess(whiteboardId) {
|
||||
const REDIS_CONFIG = Meteor.settings.private.redis;
|
||||
const CHANNEL = REDIS_CONFIG.channels.toAkkaApps;
|
||||
const EVENT_NAME = 'ModifyWhiteboardAccessPubMsg';
|
||||
|
||||
check(whiteboardId, String);
|
||||
|
||||
const { meetingId, requesterUserId } = extractCredentials(this.userId);
|
||||
|
||||
check(meetingId, String);
|
||||
check(requesterUserId, String);
|
||||
|
||||
const multiUser = getUsers(meetingId);
|
||||
|
||||
const payload = {
|
||||
multiUser,
|
||||
whiteboardId,
|
||||
};
|
||||
|
||||
return RedisPubSub.publishUserMessage(CHANNEL, EVENT_NAME, meetingId, requesterUserId, payload);
|
||||
}
|
@ -0,0 +1,32 @@
|
||||
import RedisPubSub from '/imports/startup/server/redis';
|
||||
import { Meteor } from 'meteor/meteor';
|
||||
import { check } from 'meteor/check';
|
||||
import { getMultiUser } from '/imports/api/whiteboard-multi-user/server/helpers';
|
||||
import { extractCredentials } from '/imports/api/common/server/helpers';
|
||||
|
||||
export default function addIndividualAccess(whiteboardId, userId) {
|
||||
const REDIS_CONFIG = Meteor.settings.private.redis;
|
||||
const CHANNEL = REDIS_CONFIG.channels.toAkkaApps;
|
||||
const EVENT_NAME = 'ModifyWhiteboardAccessPubMsg';
|
||||
|
||||
check(whiteboardId, String);
|
||||
check(userId, String);
|
||||
|
||||
const { meetingId, requesterUserId } = extractCredentials(this.userId);
|
||||
|
||||
check(meetingId, String);
|
||||
check(requesterUserId, String);
|
||||
|
||||
const multiUser = getMultiUser(meetingId, whiteboardId);
|
||||
|
||||
if (!multiUser.includes(userId)) {
|
||||
multiUser.push(userId);
|
||||
|
||||
const payload = {
|
||||
multiUser,
|
||||
whiteboardId,
|
||||
};
|
||||
|
||||
return RedisPubSub.publishUserMessage(CHANNEL, EVENT_NAME, meetingId, requesterUserId, payload);
|
||||
}
|
||||
}
|
@ -3,18 +3,20 @@ import { Meteor } from 'meteor/meteor';
|
||||
import { check } from 'meteor/check';
|
||||
import { extractCredentials } from '/imports/api/common/server/helpers';
|
||||
|
||||
export default function changeWhiteboardAccess(multiUser, whiteboardId) {
|
||||
export default function removeGlobalAccess(whiteboardId) {
|
||||
const REDIS_CONFIG = Meteor.settings.private.redis;
|
||||
const CHANNEL = REDIS_CONFIG.channels.toAkkaApps;
|
||||
const EVENT_NAME = 'ModifyWhiteboardAccessPubMsg';
|
||||
|
||||
const { meetingId, requesterUserId } = extractCredentials(this.userId);
|
||||
|
||||
check(multiUser, Boolean);
|
||||
check(whiteboardId, String);
|
||||
|
||||
const { meetingId, requesterUserId } = extractCredentials(this.userId);
|
||||
|
||||
check(meetingId, String);
|
||||
check(requesterUserId, String);
|
||||
|
||||
const payload = {
|
||||
multiUser,
|
||||
multiUser: [],
|
||||
whiteboardId,
|
||||
};
|
||||
|
@ -0,0 +1,30 @@
|
||||
import RedisPubSub from '/imports/startup/server/redis';
|
||||
import { Meteor } from 'meteor/meteor';
|
||||
import { check } from 'meteor/check';
|
||||
import { getMultiUser } from '/imports/api/whiteboard-multi-user/server/helpers';
|
||||
import { extractCredentials } from '/imports/api/common/server/helpers';
|
||||
|
||||
export default function removeIndividualAccess(whiteboardId, userId) {
|
||||
const REDIS_CONFIG = Meteor.settings.private.redis;
|
||||
const CHANNEL = REDIS_CONFIG.channels.toAkkaApps;
|
||||
const EVENT_NAME = 'ModifyWhiteboardAccessPubMsg';
|
||||
|
||||
check(whiteboardId, String);
|
||||
check(userId, String);
|
||||
|
||||
const { meetingId, requesterUserId } = extractCredentials(this.userId);
|
||||
|
||||
check(meetingId, String);
|
||||
check(requesterUserId, String);
|
||||
|
||||
const multiUser = getMultiUser(meetingId, whiteboardId);
|
||||
|
||||
if (multiUser.includes(userId)) {
|
||||
const payload = {
|
||||
multiUser: multiUser.filter(id => id !== userId),
|
||||
whiteboardId,
|
||||
};
|
||||
|
||||
return RedisPubSub.publishUserMessage(CHANNEL, EVENT_NAME, meetingId, requesterUserId, payload);
|
||||
}
|
||||
}
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user