diff --git a/.travis.yml b/.travis.yml index 12a35f6bf6bfcdd646c520ddae4dad3d1d90b545..1747ee5f9d674f4a1c608276a8a98ca13a4f3ad1 100644 --- a/.travis.yml +++ b/.travis.yml @@ -2,9 +2,24 @@ dist: trusty language: node_js +env: + - JOB_TYPE=linter + - JOB_TYPE=acceptance_tests + node_js: - "8" +jobs: + allow_failures: + - env: JOB_TYPE=linter + include: + - stage: "Linter" + name: "ESLint" + env: JOB_TYPE=linter + - stage: "Tests" + name: "Acceptance Tests" + env: JOB_TYPE=acceptance_tests + if: type = pull_request env: @@ -12,7 +27,7 @@ env: - BBB_SERVER_URL=http://localhost/bigbluebutton/api script: - - travis_wait bash ./build_script.sh + - travis_wait 30 bash ./build_script.sh $JOB_TYPE after_script: - docker stop $docker diff --git a/akka-bbb-apps/build.sbt b/akka-bbb-apps/build.sbt index af6c93edc018133d6157c388642194f9447837e8..74b92478f2713fae9e63d34bfb5877c9dcf19307 100755 --- a/akka-bbb-apps/build.sbt +++ b/akka-bbb-apps/build.sbt @@ -1,33 +1,36 @@ -enablePlugins(JavaServerAppPackaging) - -name := "bbb-apps-akka" - -organization := "org.bigbluebutton" - -version := "0.0.2" - -scalaVersion := "2.12.6" +import org.bigbluebutton.build._ -scalacOptions ++= Seq( - "-unchecked", - "-deprecation", - "-Xlint", - "-Ywarn-dead-code", - "-language:_", - "-target:jvm-1.8", - "-encoding", "UTF-8" -) +import scalariform.formatter.preferences._ +import com.typesafe.sbt.SbtScalariform +import com.typesafe.sbt.SbtScalariform.ScalariformKeys +import NativePackagerHelper._ +import com.typesafe.sbt.SbtNativePackager.autoImport._ -resolvers ++= Seq( - "spray repo" at "http://repo.spray.io/", - "rediscala" at "http://dl.bintray.com/etaty/maven", - "blindside-repos" at "http://blindside.googlecode.com/svn/repository/" +enablePlugins(JavaServerAppPackaging) +enablePlugins(UniversalPlugin) +enablePlugins(DebianPlugin) + +version := "0.0.4" + +val compileSettings = Seq( + organization := "org.bigbluebutton", + + scalacOptions ++= List( + "-unchecked", + "-deprecation", + "-Xlint", + "-Ywarn-dead-code", + "-language:_", + "-target:jvm-1.8", + "-encoding", "UTF-8" + ), + javacOptions ++= List( + "-Xlint:unchecked", + "-Xlint:deprecation" + ) ) -resolvers += Resolver.sonatypeRepo("releases") -resolvers += Resolver.typesafeRepo("releases") - -publishTo := Some(Resolver.file("file", new File(Path.userHome.absolutePath+"/dev/repo/maven-repo/releases" )) ) +publishTo := Some(Resolver.file("file", new File(Path.userHome.absolutePath + "/dev/repo/maven-repo/releases"))) // We want to have our jar files in lib_managed dir. // This way we'll have the right path when we import @@ -38,75 +41,16 @@ testOptions in Test += Tests.Argument(TestFrameworks.Specs2, "html", "console", testOptions in Test += Tests.Argument(TestFrameworks.ScalaTest, "-h", "target/scalatest-reports") -val akkaVersion = "2.5.14" -val scalaTestVersion = "3.0.5" - -libraryDependencies ++= { - Seq( - "ch.qos.logback" % "logback-classic" % "1.2.3" % "runtime", - "junit" % "junit" % "4.11", - "commons-codec" % "commons-codec" % "1.11", - "org.apache.commons" % "commons-lang3" % "3.7" - ) -} - -libraryDependencies += "org.bigbluebutton" % "bbb-common-message_2.12" % "0.0.19-SNAPSHOT" - -// https://mvnrepository.com/artifact/org.scala-lang/scala-library -libraryDependencies += "org.scala-lang" % "scala-library" % scalaVersion.value -// https://mvnrepository.com/artifact/org.scala-lang/scala-compiler -libraryDependencies += "org.scala-lang" % "scala-compiler" % scalaVersion.value - -// https://mvnrepository.com/artifact/com.typesafe.akka/akka-actor_2.12 -libraryDependencies += "com.typesafe.akka" % "akka-actor_2.12" % akkaVersion - -// https://mvnrepository.com/artifact/com.typesafe.akka/akka-slf4j_2.12 -libraryDependencies += "com.typesafe.akka" % "akka-slf4j_2.12" % akkaVersion - -// https://mvnrepository.com/artifact/com.github.etaty/rediscala_2.12 -libraryDependencies += "com.github.etaty" % "rediscala_2.12" % "1.8.0" - -libraryDependencies += "com.softwaremill.quicklens" %% "quicklens" % "1.4.11" -libraryDependencies += "com.google.code.gson" % "gson" % "2.8.5" -libraryDependencies += "joda-time" % "joda-time" % "2.10" -libraryDependencies += "io.spray" % "spray-json_2.12" % "1.3.4" -libraryDependencies += "org.parboiled" % "parboiled-scala_2.12" % "1.1.8" +Seq(Revolver.settings: _*) +lazy val bbbAppsAkka = (project in file(".")).settings(name := "bbb-apps-akka", libraryDependencies ++= Dependencies.runtime).settings(compileSettings) -// https://mvnrepository.com/artifact/com.fasterxml.jackson.module/jackson-module-scala_2.12 -libraryDependencies += "com.fasterxml.jackson.module" % "jackson-module-scala_2.12" % "2.9.6" +scalariformAutoformat := false - -// For generating test reports -libraryDependencies += "org.pegdown" % "pegdown" % "1.6.0" % "test" -// https://mvnrepository.com/artifact/com.typesafe.akka/akka-testkit_2.12 -libraryDependencies += "com.typesafe.akka" % "akka-testkit_2.12" % akkaVersion % "test" - -// https://mvnrepository.com/artifact/org.scalactic/scalactic_2.12 -libraryDependencies += "org.scalactic" % "scalactic_2.12" % "3.0.3" % "test" - -// https://mvnrepository.com/artifact/org.scalatest/scalatest_2.12 -libraryDependencies += "org.scalatest" % "scalatest_2.12" % scalaTestVersion % "test" - -libraryDependencies += "org.mockito" % "mockito-core" % "2.21.0" % "test" - - - - -import com.typesafe.sbt.SbtScalariform - -import scalariform.formatter.preferences._ -import com.typesafe.sbt.SbtScalariform.ScalariformKeys - -SbtScalariform.defaultScalariformSettings - -ScalariformKeys.preferences := ScalariformKeys.preferences.value +scalariformPreferences := scalariformPreferences.value .setPreference(AlignSingleLineCaseStatements, true) - .setPreference(DoubleIndentClassDeclaration, true) + .setPreference(DoubleIndentConstructorArguments, true) .setPreference(AlignParameters, true) - - - //----------- // Packaging // @@ -127,19 +71,11 @@ val user = "bigbluebutton" val group = "bigbluebutton" // user which will execute the application -daemonUser in Linux := user +daemonUser in Linux := user // group which will execute the application -daemonGroup in Linux := group - -mappings in Universal <+= (packageBin in Compile, sourceDirectory ) map { (_, src) => - // Move the application.conf so the user can override settings here - val appConf = src / "main" / "resources" / "application.conf" - appConf -> "conf/application.conf" -} - -mappings in Universal <+= (packageBin in Compile, sourceDirectory ) map { (_, src) => - // Move logback.xml so the user can override settings here - val logConf = src / "main" / "resources" / "logback.xml" - logConf -> "conf/logback.xml" -} +daemonGroup in Linux := group + +javaOptions in Universal ++= Seq("-J-Xms130m", "-J-Xmx256m", "-Dconfig.file=conf/application.conf", "-Dlogback.configurationFile=conf/logback.xml") + +debianPackageDependencies in Debian ++= Seq("java8-runtime-headless", "bash") diff --git a/akka-bbb-apps/project/Build.scala b/akka-bbb-apps/project/Build.scala deleted file mode 100755 index e69de29bb2d1d6434b8b29ae775ad8c2e48c5391..0000000000000000000000000000000000000000 diff --git a/akka-bbb-apps/project/Dependencies.scala b/akka-bbb-apps/project/Dependencies.scala new file mode 100644 index 0000000000000000000000000000000000000000..ad3746784188fe410839c58e63e295d72a2b3fa4 --- /dev/null +++ b/akka-bbb-apps/project/Dependencies.scala @@ -0,0 +1,84 @@ +package org.bigbluebutton.build + +import sbt._ +import Keys._ + +object Dependencies { + + object Versions { + // Scala + val scala = "2.12.7" + val junit = "4.12" + val junitInterface = "0.11" + val scalactic = "3.0.3" + + // Libraries + val akkaVersion = "2.5.17" + val gson = "2.8.5" + val jackson = "2.9.7" + val logback = "1.2.3" + val quicklens = "1.4.11" + val spray = "1.3.4" + + // Apache Commons + val lang = "3.8.1" + val codec = "1.11" + + // BigBlueButton + val bbbCommons = "0.0.20-SNAPSHOT" + + // Test + val scalaTest = "3.0.5" + val mockito = "2.23.0" + val akkaTestKit = "2.5.18" + } + + object Compile { + val scalaLibrary = "org.scala-lang" % "scala-library" % Versions.scala + val scalaCompiler = "org.scala-lang" % "scala-compiler" % Versions.scala + + val akkaActor = "com.typesafe.akka" % "akka-actor_2.12" % Versions.akkaVersion + val akkaSl4fj = "com.typesafe.akka" % "akka-slf4j_2.12" % Versions.akkaVersion + + val googleGson = "com.google.code.gson" % "gson" % Versions.gson + val jacksonModule = "com.fasterxml.jackson.module" %% "jackson-module-scala" % Versions.jackson + val quicklens = "com.softwaremill.quicklens" %% "quicklens" % Versions.quicklens + val logback = "ch.qos.logback" % "logback-classic" % Versions.logback + val commonsCodec = "commons-codec" % "commons-codec" % Versions.codec + val sprayJson = "io.spray" % "spray-json_2.12" % Versions.spray + + val apacheLang = "org.apache.commons" % "commons-lang3" % Versions.lang + + val bbbCommons = "org.bigbluebutton" % "bbb-common-message_2.12" % Versions.bbbCommons excludeAll ( + ExclusionRule(organization = "org.red5")) + } + + object Test { + val scalaTest = "org.scalatest" %% "scalatest" % Versions.scalaTest % "test" + val junit = "junit" % "junit" % Versions.junit % "test" + val mockitoCore = "org.mockito" % "mockito-core" % Versions.mockito % "test" + val scalactic = "org.scalactic" % "scalactic_2.12" % Versions.scalactic % "test" + val akkaTestKit = "com.typesafe.akka" %% "akka-testkit" % Versions.akkaTestKit % "test" + } + + val testing = Seq( + Test.scalaTest, + Test.junit, + Test.mockitoCore, + Test.scalactic, + Test.akkaTestKit) + + val runtime = Seq( + Compile.scalaLibrary, + Compile.scalaCompiler, + Compile.akkaActor, + Compile.akkaSl4fj, + Compile.googleGson, + Compile.jacksonModule, + Compile.quicklens, + Compile.logback, + Compile.commonsCodec, + Compile.sprayJson, + Compile.apacheLang, + Compile.bbbCommons) ++ testing +} \ No newline at end of file diff --git a/akka-bbb-apps/project/build.properties b/akka-bbb-apps/project/build.properties index a6e117b61042ee81c62ba3a0fc5210d9502944df..2e6e3d24608ee15e892ed3b16d84224f7667e808 100755 --- a/akka-bbb-apps/project/build.properties +++ b/akka-bbb-apps/project/build.properties @@ -1 +1 @@ -sbt.version=0.13.8 +sbt.version=1.2.6 \ No newline at end of file diff --git a/akka-bbb-apps/project/plugins.sbt b/akka-bbb-apps/project/plugins.sbt index ec155bbffce66550d65b058d63ef45fea0fa8b56..bc8c448553a2010f0c76ea69ccb7917f51ffef8a 100755 --- a/akka-bbb-apps/project/plugins.sbt +++ b/akka-bbb-apps/project/plugins.sbt @@ -1,11 +1,11 @@ addSbtPlugin("io.spray" % "sbt-revolver" % "0.9.1") -addSbtPlugin("org.scalariform" % "sbt-scalariform" % "1.8.2") +addSbtPlugin("com.typesafe.sbteclipse" % "sbteclipse-plugin" % "5.2.4") -addSbtPlugin("com.typesafe.sbteclipse" % "sbteclipse-plugin" % "2.2.0") +addSbtPlugin("org.scalariform" % "sbt-scalariform" % "1.8.2") -addSbtPlugin("com.typesafe.sbt" % "sbt-native-packager" % "1.3.6") +addSbtPlugin("com.typesafe.sbt" % "sbt-native-packager" % "1.3.12") -addSbtPlugin("net.vonbuchholtz" % "sbt-dependency-check" % "0.2.7") +addSbtPlugin("net.vonbuchholtz" % "sbt-dependency-check" % "0.2.8") addSbtPlugin("org.scalastyle" %% "scalastyle-sbt-plugin" % "1.0.0") diff --git a/akka-bbb-apps/run.sh b/akka-bbb-apps/run.sh index 4015c4f1373a8caa1271bb9fa175c5940624bf89..3e24e4cf0a4fe68b386aff857a8c6cf2d051f5fd 100755 --- a/akka-bbb-apps/run.sh +++ b/akka-bbb-apps/run.sh @@ -1,3 +1,6 @@ -sbt clean -sbt run +#!/usr/bin/env bash +sbt clean stage +sudo service bbb-apps-akka stop +cd target/universal/stage +./bin/bbb-apps-akka diff --git a/akka-bbb-apps/src/main/resources/README b/akka-bbb-apps/src/main/resources/README deleted file mode 100644 index e69de29bb2d1d6434b8b29ae775ad8c2e48c5391..0000000000000000000000000000000000000000 diff --git a/akka-bbb-apps/src/main/scala/org/bigbluebutton/Boot.scala b/akka-bbb-apps/src/main/scala/org/bigbluebutton/Boot.scala index 74a852b9a5ce018e90f1a4db00c0762c53475526..82b54478421679653cf4b56766a8146660b531dc 100755 --- a/akka-bbb-apps/src/main/scala/org/bigbluebutton/Boot.scala +++ b/akka-bbb-apps/src/main/scala/org/bigbluebutton/Boot.scala @@ -1,12 +1,18 @@ package org.bigbluebutton -import akka.event.Logging -import akka.actor.ActorSystem -import org.bigbluebutton.endpoint.redis.{ AppsRedisSubscriberActor, KeepAliveRedisPublisher, RedisPublisher, RedisRecorderActor } +import org.bigbluebutton.common2.redis.RedisPublisher import org.bigbluebutton.core._ import org.bigbluebutton.core.bus._ import org.bigbluebutton.core.pubsub.senders.ReceivedJsonMsgHandlerActor -import org.bigbluebutton.core2.{ AnalyticsActor, FromAkkaAppsMsgSenderActor } +import org.bigbluebutton.core2.AnalyticsActor +import org.bigbluebutton.core2.FromAkkaAppsMsgSenderActor +import org.bigbluebutton.endpoint.redis.AppsRedisSubscriberActor +import org.bigbluebutton.endpoint.redis.RedisRecorderActor + +import akka.actor.ActorSystem +import akka.event.Logging +import org.bigbluebutton.common2.redis.MessageSender +import org.bigbluebutton.common2.bus.IncomingJsonMessageBus object Boot extends App with SystemConfiguration { @@ -22,7 +28,7 @@ object Boot extends App with SystemConfiguration { val outGW = new OutMessageGatewayImp(outBus2) - val redisPublisher = new RedisPublisher(system) + val redisPublisher = new RedisPublisher(system, "BbbAppsAkkaPub") val msgSender = new MessageSender(redisPublisher) val redisRecorderActor = system.actorOf(RedisRecorderActor.props(system), "redisRecorderActor") @@ -46,8 +52,5 @@ object Boot extends App with SystemConfiguration { val redisMessageHandlerActor = system.actorOf(ReceivedJsonMsgHandlerActor.props(bbbMsgBus, incomingJsonMessageBus)) incomingJsonMessageBus.subscribe(redisMessageHandlerActor, toAkkaAppsJsonChannel) - val redisSubscriberActor = system.actorOf(AppsRedisSubscriberActor.props(incomingJsonMessageBus), "redis-subscriber") - - val keepAliveRedisPublisher = new KeepAliveRedisPublisher(system, redisPublisher) - + val redisSubscriberActor = system.actorOf(AppsRedisSubscriberActor.props(system, incomingJsonMessageBus), "redis-subscriber") } diff --git a/akka-bbb-apps/src/main/scala/org/bigbluebutton/SystemConfiguration.scala b/akka-bbb-apps/src/main/scala/org/bigbluebutton/SystemConfiguration.scala index d2986d262a88d84363f7870e563f11d981312a6f..743a4894c4acec763614b2f9d50ca560e59ba24b 100755 --- a/akka-bbb-apps/src/main/scala/org/bigbluebutton/SystemConfiguration.scala +++ b/akka-bbb-apps/src/main/scala/org/bigbluebutton/SystemConfiguration.scala @@ -1,15 +1,10 @@ package org.bigbluebutton -import com.typesafe.config.ConfigFactory import scala.util.Try -trait SystemConfiguration { +import org.bigbluebutton.common2.redis.RedisConfiguration - val config = ConfigFactory.load() - - lazy val redisHost = Try(config.getString("redis.host")).getOrElse("127.0.0.1") - lazy val redisPort = Try(config.getInt("redis.port")).getOrElse(6379) - lazy val redisPassword = Try(config.getString("redis.password")).getOrElse("") +trait SystemConfiguration extends RedisConfiguration { lazy val bbbWebHost = Try(config.getString("services.bbbWebHost")).getOrElse("localhost") lazy val bbbWebPort = Try(config.getInt("services.bbbWebPort")).getOrElse(8888) @@ -31,8 +26,6 @@ trait SystemConfiguration { lazy val outBbbMsgMsgChannel = Try(config.getString("eventBus.outBbbMsgMsgChannel")).getOrElse("OutBbbMsgChannel") lazy val recordServiceMessageChannel = Try(config.getString("eventBus.recordServiceMessageChannel")).getOrElse("RecordServiceMessageChannel") - lazy val toAkkaAppsRedisChannel = Try(config.getString("redis.toAkkaAppsRedisChannel")).getOrElse("to-akka-apps-redis-channel") - lazy val fromAkkaAppsRedisChannel = Try(config.getString("redis.fromAkkaAppsRedisChannel")).getOrElse("from-akka-apps-redis-channel") lazy val toHTML5RedisChannel = Try(config.getString("redis.toHTML5RedisChannel")).getOrElse("to-html5-redis-channel") lazy val fromAkkaAppsChannel = Try(config.getString("eventBus.fromAkkaAppsChannel")).getOrElse("from-akka-apps-channel") lazy val toAkkaAppsChannel = Try(config.getString("eventBus.toAkkaAppsChannel")).getOrElse("to-akka-apps-channel") @@ -41,21 +34,9 @@ trait SystemConfiguration { lazy val toAkkaAppsJsonChannel = Try(config.getString("eventBus.toAkkaAppsChannel")).getOrElse("to-akka-apps-json-channel") lazy val fromAkkaAppsJsonChannel = Try(config.getString("eventBus.fromAkkaAppsChannel")).getOrElse("from-akka-apps-json-channel") - lazy val toVoiceConfRedisChannel = Try(config.getString("redis.toVoiceConfRedisChannel")).getOrElse("to-voice-conf-redis-channel") - lazy val fromVoiceConfRedisChannel = Try(config.getString("redis.fromVoiceConfRedisChannel")).getOrElse("from-voice-conf-redis-channel") - - lazy val fromAkkaAppsWbRedisChannel = Try(config.getString("redis.fromAkkaAppsWbRedisChannel")).getOrElse("from-akka-apps-wb-redis-channel") - lazy val fromAkkaAppsChatRedisChannel = Try(config.getString("redis.fromAkkaAppsChatRedisChannel")).getOrElse("from-akka-apps-chat-redis-channel") - lazy val fromAkkaAppsPresRedisChannel = Try(config.getString("redis.fromAkkaAppsPresRedisChannel")).getOrElse("from-akka-apps-pres-redis-channel") - lazy val maxNumberOfNotes = Try(config.getInt("sharedNotes.maxNumberOfNotes")).getOrElse(3) lazy val maxNumberOfUndos = Try(config.getInt("sharedNotes.maxNumberOfUndos")).getOrElse(30) - lazy val httpInterface = Try(config.getString("http.interface")).getOrElse("") - lazy val httpPort = Try(config.getInt("http.port")).getOrElse(9090) - lazy val telizeHost = Try(config.getString("services.telizeHost")).getOrElse("") - lazy val telizePort = Try(config.getInt("services.telizePort")).getOrElse(80) - lazy val applyPermissionCheck = Try(config.getBoolean("apps.checkPermissions")).getOrElse(false) lazy val voiceConfRecordPath = Try(config.getString("voiceConf.recordPath")).getOrElse("/var/freeswitch/meetings") diff --git a/akka-bbb-apps/src/main/scala/org/bigbluebutton/core/MessageSender.scala b/akka-bbb-apps/src/main/scala/org/bigbluebutton/core/MessageSender.scala deleted file mode 100755 index 250b1b3fba8c2b5f3a122f5cd54c168b7fdf30c4..0000000000000000000000000000000000000000 --- a/akka-bbb-apps/src/main/scala/org/bigbluebutton/core/MessageSender.scala +++ /dev/null @@ -1,10 +0,0 @@ -package org.bigbluebutton.core - -import org.bigbluebutton.endpoint.redis.RedisPublisher - -class MessageSender(publisher: RedisPublisher) { - - def send(channel: String, data: String) { - publisher.publish(channel, data) - } -} \ No newline at end of file diff --git a/akka-bbb-apps/src/main/scala/org/bigbluebutton/core/apps/users/RegisterUserReqMsgHdlr.scala b/akka-bbb-apps/src/main/scala/org/bigbluebutton/core/apps/users/RegisterUserReqMsgHdlr.scala index b9ed6467939bf9dfa7d6b605361704b0524cdf01..1659cea34f42f3158217f41a95c6488a2661e88c 100755 --- a/akka-bbb-apps/src/main/scala/org/bigbluebutton/core/apps/users/RegisterUserReqMsgHdlr.scala +++ b/akka-bbb-apps/src/main/scala/org/bigbluebutton/core/apps/users/RegisterUserReqMsgHdlr.scala @@ -22,7 +22,6 @@ trait RegisterUserReqMsgHdlr { BbbCommonEnvCoreMsg(envelope, event) } - val guestPolicy = liveMeeting.guestsWaiting.getGuestPolicy().policy val guestStatus = msg.body.guestStatus val regUser = RegisteredUsers.create(msg.body.intUserId, msg.body.extUserId, diff --git a/akka-bbb-apps/src/main/scala/org/bigbluebutton/core/domain/MeetingInactivityTracker.scala b/akka-bbb-apps/src/main/scala/org/bigbluebutton/core/domain/MeetingInactivityTracker.scala index 96fa0e49bdc80d6cf25e2ac6db6bfe4adcf6882a..1a59927ac8fabdd2d3218b586c20c4862b26824c 100755 --- a/akka-bbb-apps/src/main/scala/org/bigbluebutton/core/domain/MeetingInactivityTracker.scala +++ b/akka-bbb-apps/src/main/scala/org/bigbluebutton/core/domain/MeetingInactivityTracker.scala @@ -60,7 +60,10 @@ case class MeetingExpiryTracker( val expire = for { lastUserLeftOn <- lastUserLeftOnInMs } yield { - timestampInMs - lastUserLeftOn > meetingExpireWhenLastUserLeftInMs + // Check if we need to end meeting right away when the last user left + // ralam Nov 16, 2018 + if (meetingExpireWhenLastUserLeftInMs == 0) true + else timestampInMs - lastUserLeftOn > meetingExpireWhenLastUserLeftInMs } expire.getOrElse(false) diff --git a/akka-bbb-apps/src/main/scala/org/bigbluebutton/core/models/Users2x.scala b/akka-bbb-apps/src/main/scala/org/bigbluebutton/core/models/Users2x.scala index 9fe5d4e652e3b1c7e2ded35b231442b769ffa03f..a5fd753f5988dd0a13a843332261dc9d98d5f931 100755 --- a/akka-bbb-apps/src/main/scala/org/bigbluebutton/core/models/Users2x.scala +++ b/akka-bbb-apps/src/main/scala/org/bigbluebutton/core/models/Users2x.scala @@ -39,9 +39,16 @@ object Users2x { } } - def findAllExpiredUserLeftFlags(users: Users2x): Vector[UserState] = { - users.toVector filter (u => u.userLeftFlag.left && u.userLeftFlag.leftOn != 0 && - System.currentTimeMillis() - u.userLeftFlag.leftOn > 10000) + def findAllExpiredUserLeftFlags(users: Users2x, meetingExpireWhenLastUserLeftInMs: Long): Vector[UserState] = { + if (meetingExpireWhenLastUserLeftInMs > 0) { + users.toVector filter (u => u.userLeftFlag.left && u.userLeftFlag.leftOn != 0 && + System.currentTimeMillis() - u.userLeftFlag.leftOn > 1000) + } else { + // When meetingExpireWhenLastUserLeftInMs is set zero we need to + // remove user right away to end the meeting as soon as possible. + // ralam Nov 16, 2018 + users.toVector filter (u => u.userLeftFlag.left && u.userLeftFlag.leftOn != 0) + } } def numUsers(users: Users2x): Int = { @@ -252,11 +259,10 @@ case class UserState( locked: Boolean, presenter: Boolean, avatar: String, - roleChangedOn: Long = System.currentTimeMillis(), - lastActivityTime: Long = TimeUtil.timeNowInMs(), + roleChangedOn: Long = System.currentTimeMillis(), + lastActivityTime: Long = TimeUtil.timeNowInMs(), clientType: String, - userLeftFlag: UserLeftFlag) - + userLeftFlag: UserLeftFlag) case class UserIdAndName(id: String, name: String) diff --git a/akka-bbb-apps/src/main/scala/org/bigbluebutton/core/pubsub/senders/ReceivedJsonMsgHandlerActor.scala b/akka-bbb-apps/src/main/scala/org/bigbluebutton/core/pubsub/senders/ReceivedJsonMsgHandlerActor.scala index 7d762dbb11a7ba9e0a4d168621abb52967782224..639adf2ca10bf647b747d88e3ce40b85b2161391 100755 --- a/akka-bbb-apps/src/main/scala/org/bigbluebutton/core/pubsub/senders/ReceivedJsonMsgHandlerActor.scala +++ b/akka-bbb-apps/src/main/scala/org/bigbluebutton/core/pubsub/senders/ReceivedJsonMsgHandlerActor.scala @@ -6,7 +6,9 @@ import com.fasterxml.jackson.databind.JsonNode import org.bigbluebutton.common2.msgs._ import org.bigbluebutton.core.bus._ import org.bigbluebutton.core2.ReceivedMessageRouter -import scala.reflect.runtime.universe._ +import scala.reflect.runtime.universe._ +import org.bigbluebutton.common2.bus.ReceivedJsonMessage +import org.bigbluebutton.common2.bus.IncomingJsonMessageBus object ReceivedJsonMsgHandlerActor { def props(eventBus: BbbMsgRouterEventBus, incomingJsonMessageBus: IncomingJsonMessageBus): Props = diff --git a/akka-bbb-apps/src/main/scala/org/bigbluebutton/core/record/events/RecordEvent.scala b/akka-bbb-apps/src/main/scala/org/bigbluebutton/core/record/events/RecordEvent.scala index 3dfb958767ff633bdacc8d637a4f6d5bd743af33..cc18aec6b2d2f62dc28331fb8f88290ff4813588 100755 --- a/akka-bbb-apps/src/main/scala/org/bigbluebutton/core/record/events/RecordEvent.scala +++ b/akka-bbb-apps/src/main/scala/org/bigbluebutton/core/record/events/RecordEvent.scala @@ -21,7 +21,9 @@ package org.bigbluebutton.core.record.events import java.text.SimpleDateFormat +import scala.collection.Map import scala.collection.mutable.HashMap + import org.bigbluebutton.core.api.TimestampGenerator trait RecordEvent { @@ -70,6 +72,7 @@ trait RecordEvent { eventMap.put(EVENT, event) } + // @fixme : not used anymore /** * Convert the event into a Map to be recorded. * @return @@ -77,6 +80,7 @@ trait RecordEvent { final def toMap(): Map[String, String] = { eventMap.toMap } + } object RecordEvent extends RecordEvent { diff --git a/akka-bbb-apps/src/main/scala/org/bigbluebutton/core/running/MeetingActor.scala b/akka-bbb-apps/src/main/scala/org/bigbluebutton/core/running/MeetingActor.scala index 61c607cd42f2fbddf748ab269d303c85f39d4fbb..9beb7a71b01ae94b61fca7b2097ef93fef66a7fd 100755 --- a/akka-bbb-apps/src/main/scala/org/bigbluebutton/core/running/MeetingActor.scala +++ b/akka-bbb-apps/src/main/scala/org/bigbluebutton/core/running/MeetingActor.scala @@ -53,34 +53,34 @@ class MeetingActor( val eventBus: InternalEventBus, val outGW: OutMsgRouter, val liveMeeting: LiveMeeting) - extends BaseMeetingActor - with SystemConfiguration - with GuestsApp - with LayoutApp2x - with VoiceApp2x - with BreakoutApp2x - with UsersApp2x - - with UserBroadcastCamStartMsgHdlr - with UserJoinMeetingReqMsgHdlr - with UserJoinMeetingAfterReconnectReqMsgHdlr - with UserBroadcastCamStopMsgHdlr - with UserConnectedToGlobalAudioMsgHdlr - with UserDisconnectedFromGlobalAudioMsgHdlr - with MuteAllExceptPresentersCmdMsgHdlr - with MuteMeetingCmdMsgHdlr - with IsMeetingMutedReqMsgHdlr - - with EjectUserFromVoiceCmdMsgHdlr - with EndMeetingSysCmdMsgHdlr - with DestroyMeetingSysCmdMsgHdlr - with SendTimeRemainingUpdateHdlr - with SendBreakoutTimeRemainingMsgHdlr - with ChangeLockSettingsInMeetingCmdMsgHdlr - with SyncGetMeetingInfoRespMsgHdlr - with ClientToServerLatencyTracerMsgHdlr - with ValidateConnAuthTokenSysMsgHdlr - with UserActivitySignCmdMsgHdlr { + extends BaseMeetingActor + with SystemConfiguration + with GuestsApp + with LayoutApp2x + with VoiceApp2x + with BreakoutApp2x + with UsersApp2x + + with UserBroadcastCamStartMsgHdlr + with UserJoinMeetingReqMsgHdlr + with UserJoinMeetingAfterReconnectReqMsgHdlr + with UserBroadcastCamStopMsgHdlr + with UserConnectedToGlobalAudioMsgHdlr + with UserDisconnectedFromGlobalAudioMsgHdlr + with MuteAllExceptPresentersCmdMsgHdlr + with MuteMeetingCmdMsgHdlr + with IsMeetingMutedReqMsgHdlr + + with EjectUserFromVoiceCmdMsgHdlr + with EndMeetingSysCmdMsgHdlr + with DestroyMeetingSysCmdMsgHdlr + with SendTimeRemainingUpdateHdlr + with SendBreakoutTimeRemainingMsgHdlr + with ChangeLockSettingsInMeetingCmdMsgHdlr + with SyncGetMeetingInfoRespMsgHdlr + with ClientToServerLatencyTracerMsgHdlr + with ValidateConnAuthTokenSysMsgHdlr + with UserActivitySignCmdMsgHdlr { override val supervisorStrategy = OneForOneStrategy(maxNrOfRetries = 10, withinTimeRange = 1 minute) { case e: Exception => { @@ -179,6 +179,7 @@ class MeetingActor( def receive = { //============================= + // 2x messages case msg: BbbCommonEnvCoreMsg => handleBbbCommonEnvCoreMsg(msg) @@ -240,6 +241,19 @@ class MeetingActor( } private def handleBbbCommonEnvCoreMsg(msg: BbbCommonEnvCoreMsg): Unit = { + msg.core match { + case m: ClientToServerLatencyTracerMsg => handleMessageThatDoesNotAffectsInactivity(msg) + case _ => handleMessageThatAffectsInactivity(msg) + } + } + + private def handleMessageThatDoesNotAffectsInactivity(msg: BbbCommonEnvCoreMsg): Unit = { + msg.core match { + case m: ClientToServerLatencyTracerMsg => handleClientToServerLatencyTracerMsg(m) + } + } + + private def handleMessageThatAffectsInactivity(msg: BbbCommonEnvCoreMsg): Unit = { msg.core match { case m: EndMeetingSysCmdMsg => handleEndMeeting(m, state) @@ -287,8 +301,6 @@ class MeetingActor( case m: SendWhiteboardAnnotationPubMsg => wbApp.handle(m, liveMeeting, msgBus) case m: GetWhiteboardAnnotationsReqMsg => wbApp.handle(m, liveMeeting, msgBus) - case m: ClientToServerLatencyTracerMsg => handleClientToServerLatencyTracerMsg(m) - // Poll case m: StartPollReqMsg => pollApp.handle(m, state, liveMeeting, msgBus) // passing state but not modifying it @@ -561,7 +573,7 @@ class MeetingActor( } def removeUsersWithExpiredUserLeftFlag(liveMeeting: LiveMeeting, state: MeetingState2x): MeetingState2x = { - val leftUsers = Users2x.findAllExpiredUserLeftFlags(liveMeeting.users2x) + val leftUsers = Users2x.findAllExpiredUserLeftFlags(liveMeeting.users2x, expiryTracker.meetingExpireWhenLastUserLeftInMs) leftUsers foreach { leftUser => for { u <- Users2x.remove(liveMeeting.users2x, leftUser.intId) @@ -592,7 +604,10 @@ class MeetingActor( updateParentMeetingWithUsers() } - if (Users2x.numUsers(liveMeeting.users2x) == 0) { + if (state.expiryTracker.userHasJoined && + Users2x.numUsers(liveMeeting.users2x) == 0 + && !state.expiryTracker.lastUserLeftOnInMs.isDefined) { + log.info("Setting meeting no more users. meetingId=" + props.meetingProp.intId) val tracker = state.expiryTracker.setLastUserLeftOn(TimeUtil.timeNowInMs()) state.update(tracker) } else { diff --git a/akka-bbb-apps/src/main/scala/org/bigbluebutton/core2/FromAkkaAppsMsgSenderActor.scala b/akka-bbb-apps/src/main/scala/org/bigbluebutton/core2/FromAkkaAppsMsgSenderActor.scala index 2dcaaf0f1f44a96019243bbd968727a8ecc684ff..0061b110d6c0e6437a8ae2a1f9386594b08935ce 100755 --- a/akka-bbb-apps/src/main/scala/org/bigbluebutton/core2/FromAkkaAppsMsgSenderActor.scala +++ b/akka-bbb-apps/src/main/scala/org/bigbluebutton/core2/FromAkkaAppsMsgSenderActor.scala @@ -3,8 +3,8 @@ package org.bigbluebutton.core2 import akka.actor.{ Actor, ActorLogging, Props } import org.bigbluebutton.SystemConfiguration import org.bigbluebutton.common2.msgs._ -import org.bigbluebutton.common2.util.JsonUtil -import org.bigbluebutton.core.MessageSender +import org.bigbluebutton.common2.util.JsonUtil +import org.bigbluebutton.common2.redis.MessageSender object FromAkkaAppsMsgSenderActor { def props(msgSender: MessageSender): Props = Props(classOf[FromAkkaAppsMsgSenderActor], msgSender) diff --git a/akka-bbb-apps/src/main/scala/org/bigbluebutton/core2/testdata/FakeTestData.scala b/akka-bbb-apps/src/main/scala/org/bigbluebutton/core2/testdata/FakeTestData.scala index b9af74ca01a4c1a115199704238433354964a188..e0966b6bb968052d59e28a0a3ce138bb28e944f4 100755 --- a/akka-bbb-apps/src/main/scala/org/bigbluebutton/core2/testdata/FakeTestData.scala +++ b/akka-bbb-apps/src/main/scala/org/bigbluebutton/core2/testdata/FakeTestData.scala @@ -69,8 +69,7 @@ trait FakeTestData { UserState(intId = regUser.id, extId = regUser.externId, name = regUser.name, role = regUser.role, guest = regUser.guest, authed = regUser.authed, guestStatus = regUser.guestStatus, emoji = "none", locked = false, presenter = false, avatar = regUser.avatarURL, clientType = "unknown", - userLeftFlag = UserLeftFlag(false, 0) - ) + userLeftFlag = UserLeftFlag(false, 0)) } - + } diff --git a/akka-bbb-apps/src/main/scala/org/bigbluebutton/endpoint/redis/AkkaAppsRedisSubscriberActor.scala b/akka-bbb-apps/src/main/scala/org/bigbluebutton/endpoint/redis/AkkaAppsRedisSubscriberActor.scala new file mode 100755 index 0000000000000000000000000000000000000000..8e8ba2ebc6cdceb55bc6e8df8cb003267dd2bbb5 --- /dev/null +++ b/akka-bbb-apps/src/main/scala/org/bigbluebutton/endpoint/redis/AkkaAppsRedisSubscriberActor.scala @@ -0,0 +1,32 @@ +package org.bigbluebutton.endpoint.redis + +import org.bigbluebutton.SystemConfiguration +import org.bigbluebutton.common2.bus.IncomingJsonMessageBus +import org.bigbluebutton.common2.redis.{ RedisSubscriber, RedisSubscriberProvider } + +import akka.actor.ActorSystem +import akka.actor.Props + +object AppsRedisSubscriberActor extends RedisSubscriber { + + val channels = Seq(toAkkaAppsRedisChannel, fromVoiceConfRedisChannel) + val patterns = Seq("bigbluebutton:to-bbb-apps:*", "bigbluebutton:from-voice-conf:*", "bigbluebutton:from-bbb-transcode:*") + + def props(system: ActorSystem, jsonMsgBus: IncomingJsonMessageBus): Props = + Props( + classOf[AppsRedisSubscriberActor], + system, jsonMsgBus, + redisHost, redisPort, + channels, patterns).withDispatcher("akka.redis-subscriber-worker-dispatcher") +} + +class AppsRedisSubscriberActor( + system: ActorSystem, + jsonMsgBus: IncomingJsonMessageBus, + redisHost: String, redisPort: Int, + channels: Seq[String] = Nil, patterns: Seq[String] = Nil) + extends RedisSubscriberProvider(system, "BbbAppsAkkaSub", channels, patterns, jsonMsgBus) with SystemConfiguration { + + addListener(toAkkaAppsJsonChannel) + subscribe() +} diff --git a/akka-bbb-apps/src/main/scala/org/bigbluebutton/endpoint/redis/AppsRedisSubscriberActor.scala b/akka-bbb-apps/src/main/scala/org/bigbluebutton/endpoint/redis/AppsRedisSubscriberActor.scala deleted file mode 100755 index 5a5a1424f59eaee4f6c91c2714e74bead3251766..0000000000000000000000000000000000000000 --- a/akka-bbb-apps/src/main/scala/org/bigbluebutton/endpoint/redis/AppsRedisSubscriberActor.scala +++ /dev/null @@ -1,64 +0,0 @@ -package org.bigbluebutton.endpoint.redis - -import akka.actor.Props -import akka.actor.OneForOneStrategy -import akka.actor.SupervisorStrategy.Resume -import java.io.{ PrintWriter, StringWriter } -import java.net.InetSocketAddress - -import redis.actors.RedisSubscriberActor -import redis.api.pubsub.{ Message, PMessage } - -import scala.concurrent.duration._ -import org.bigbluebutton.SystemConfiguration -import org.bigbluebutton.core.bus.{ IncomingJsonMessage, IncomingJsonMessageBus, ReceivedJsonMessage } -import redis.api.servers.ClientSetname - -object AppsRedisSubscriberActor extends SystemConfiguration { - - val TO_AKKA_APPS = "bbb:to-akka-apps" - val channels = Seq(toAkkaAppsRedisChannel, fromVoiceConfRedisChannel) - val patterns = Seq("bigbluebutton:to-bbb-apps:*", "bigbluebutton:from-voice-conf:*", "bigbluebutton:from-bbb-transcode:*") - - def props(jsonMsgBus: IncomingJsonMessageBus): Props = - Props(classOf[AppsRedisSubscriberActor], jsonMsgBus, - redisHost, redisPort, - channels, patterns).withDispatcher("akka.rediscala-subscriber-worker-dispatcher") -} - -class AppsRedisSubscriberActor(jsonMsgBus: IncomingJsonMessageBus, redisHost: String, - redisPort: Int, - channels: Seq[String] = Nil, patterns: Seq[String] = Nil) - extends RedisSubscriberActor( - new InetSocketAddress(redisHost, redisPort), - channels, patterns, onConnectStatus = connected => { println(s"connected: $connected") }) with SystemConfiguration { - - override val supervisorStrategy = OneForOneStrategy(maxNrOfRetries = 10, withinTimeRange = 1 minute) { - case e: Exception => { - val sw: StringWriter = new StringWriter() - sw.write("An exception has been thrown on AppsRedisSubscriberActor, exception message [" + e.getMessage() + "] (full stacktrace below)\n") - e.printStackTrace(new PrintWriter(sw)) - log.error(sw.toString()) - Resume - } - } - - // Set the name of this client to be able to distinguish when doing - // CLIENT LIST on redis-cli - write(ClientSetname("BbbAppsAkkaSub").encodedRequest) - - def onMessage(message: Message) { - if (message.channel == toAkkaAppsRedisChannel || message.channel == fromVoiceConfRedisChannel) { - val receivedJsonMessage = new ReceivedJsonMessage(message.channel, message.data.utf8String) - //log.debug(s"RECEIVED:\n [${receivedJsonMessage.channel}] \n ${receivedJsonMessage.data} \n") - jsonMsgBus.publish(IncomingJsonMessage(toAkkaAppsJsonChannel, receivedJsonMessage)) - } - } - - def onPMessage(pmessage: PMessage) { - - // We don't use PSubscribe anymore, but an implementation of the method is required - //log.error("Should not be receiving a PMessage. It triggered on a match of pattern: " + pmessage.patternMatched) - //log.error(pmessage.data.utf8String) - } -} diff --git a/akka-bbb-apps/src/main/scala/org/bigbluebutton/endpoint/redis/KeepAliveRedisPublisher.scala b/akka-bbb-apps/src/main/scala/org/bigbluebutton/endpoint/redis/KeepAliveRedisPublisher.scala deleted file mode 100755 index 74aa6e4b7885620de866627bba075e45e28dba75..0000000000000000000000000000000000000000 --- a/akka-bbb-apps/src/main/scala/org/bigbluebutton/endpoint/redis/KeepAliveRedisPublisher.scala +++ /dev/null @@ -1,16 +0,0 @@ -package org.bigbluebutton.endpoint.redis - -import scala.concurrent.duration._ -import scala.concurrent.ExecutionContext.Implicits.global -import akka.actor.ActorSystem -import org.bigbluebutton.SystemConfiguration - -class KeepAliveRedisPublisher(val system: ActorSystem, sender: RedisPublisher) extends SystemConfiguration { - - val startedOn = System.currentTimeMillis() - - system.scheduler.schedule(2 seconds, 5 seconds) { - //val msg = new BbbAppsIsAliveMessage(startedOn, System.currentTimeMillis()) - // sender.publish("bigbluebutton:from-bbb-apps:keepalive", msg.toJson()) - } -} diff --git a/akka-bbb-apps/src/main/scala/org/bigbluebutton/endpoint/redis/RedisPublisher.scala b/akka-bbb-apps/src/main/scala/org/bigbluebutton/endpoint/redis/RedisPublisher.scala deleted file mode 100755 index 6af8a64ebb05093023cc7e90a0f445cce3170b8b..0000000000000000000000000000000000000000 --- a/akka-bbb-apps/src/main/scala/org/bigbluebutton/endpoint/redis/RedisPublisher.scala +++ /dev/null @@ -1,21 +0,0 @@ -package org.bigbluebutton.endpoint.redis - -import redis.RedisClient -import akka.actor.ActorSystem -import org.bigbluebutton.SystemConfiguration -import akka.util.ByteString - -class RedisPublisher(val system: ActorSystem) extends SystemConfiguration { - - val redis = RedisClient(redisHost, redisPort)(system) - - // Set the name of this client to be able to distinguish when doing - // CLIENT LIST on redis-cli - redis.clientSetname("BbbAppsAkkaPub") - - def publish(channel: String, data: String) { - //println("PUBLISH TO [" + channel + "]: \n [" + data + "]") - redis.publish(channel, ByteString(data)) - } - -} diff --git a/akka-bbb-apps/src/main/scala/org/bigbluebutton/endpoint/redis/RedisRecorderActor.scala b/akka-bbb-apps/src/main/scala/org/bigbluebutton/endpoint/redis/RedisRecorderActor.scala index 7a01783d5b6448ad53fd98eb53a235f2829d284b..c3c68f726ad22021d7a1c68fc02aac5fb46cd217 100755 --- a/akka-bbb-apps/src/main/scala/org/bigbluebutton/endpoint/redis/RedisRecorderActor.scala +++ b/akka-bbb-apps/src/main/scala/org/bigbluebutton/endpoint/redis/RedisRecorderActor.scala @@ -1,40 +1,30 @@ package org.bigbluebutton.endpoint.redis -import akka.actor.{ Actor, ActorLogging, ActorSystem, Props } -import org.bigbluebutton.SystemConfiguration -import redis.RedisClient -import scala.concurrent.ExecutionContext.Implicits.global import scala.collection.immutable.StringOps +import scala.collection.JavaConverters._ + +import org.bigbluebutton.SystemConfiguration import org.bigbluebutton.common2.msgs._ -import org.bigbluebutton.core.record.events._ +import org.bigbluebutton.common2.redis.RedisStorageProvider import org.bigbluebutton.core.apps.groupchats.GroupChatApp +import org.bigbluebutton.core.record.events._ + +import akka.actor.Actor +import akka.actor.ActorLogging +import akka.actor.ActorSystem +import akka.actor.Props object RedisRecorderActor { def props(system: ActorSystem): Props = Props(classOf[RedisRecorderActor], system) } -class RedisRecorderActor(val system: ActorSystem) - extends SystemConfiguration - with Actor with ActorLogging { - val redis = RedisClient(redisHost, redisPort)(system) - - // Set the name of this client to be able to distinguish when doing - // CLIENT LIST on redis-cli - redis.clientSetname("BbbAppsAkkaRecorder") - - val COLON = ":" - - private def record(session: String, message: collection.immutable.Map[String, String]): Unit = { - for { - msgid <- redis.incr("global:nextRecordedMsgId") - key = "recording" + COLON + session + COLON + msgid - _ <- redis.hmset(key.mkString, message) - _ <- redis.expire(key.mkString, keysExpiresInSec) - key2 = "meeting" + COLON + session + COLON + "recordings" - _ <- redis.rpush(key2.mkString, msgid.toString) - result <- redis.expire(key2.mkString, keysExpiresInSec) - } yield result +class RedisRecorderActor(system: ActorSystem) + extends RedisStorageProvider(system, "BbbAppsAkkaRecorder") + with SystemConfiguration + with Actor with ActorLogging { + private def record(session: String, message: java.util.Map[java.lang.String, java.lang.String]): Unit = { + redis.recordAndExpire(session, message) } def receive = { @@ -121,7 +111,7 @@ class RedisRecorderActor(val system: ActorSystem) ev.setMessage(msg.body.msg.message) ev.setColor(msg.body.msg.color) - record(msg.header.meetingId, ev.toMap) + record(msg.header.meetingId, ev.toMap.asJava) } } @@ -129,7 +119,7 @@ class RedisRecorderActor(val system: ActorSystem) val ev = new ClearPublicChatRecordEvent() ev.setMeetingId(msg.header.meetingId) - record(msg.header.meetingId, ev.toMap) + record(msg.header.meetingId, ev.toMap.asJava) } private def handlePresentationConversionCompletedEvtMsg(msg: PresentationConversionCompletedEvtMsg) { @@ -139,7 +129,7 @@ class RedisRecorderActor(val system: ActorSystem) ev.setPresentationName(msg.body.presentation.id) ev.setOriginalFilename(msg.body.presentation.name) - record(msg.header.meetingId, ev.toMap) + record(msg.header.meetingId, ev.toMap.asJava) if (msg.body.presentation.current) { recordSharePresentationEvent(msg.header.meetingId, msg.body.podId, msg.body.presentation.id) @@ -154,7 +144,7 @@ class RedisRecorderActor(val system: ActorSystem) ev.setSlide(getPageNum(msg.body.pageId)) ev.setId(msg.body.pageId) - record(msg.header.meetingId, ev.toMap) + record(msg.header.meetingId, ev.toMap.asJava) } private def handleResizeAndMovePageEvtMsg(msg: ResizeAndMovePageEvtMsg) { @@ -168,7 +158,7 @@ class RedisRecorderActor(val system: ActorSystem) ev.setWidthRatio(msg.body.widthRatio) ev.setHeightRatio(msg.body.heightRatio) - record(msg.header.meetingId, ev.toMap) + record(msg.header.meetingId, ev.toMap.asJava) } private def handleRemovePresentationEvtMsg(msg: RemovePresentationEvtMsg) { @@ -177,7 +167,7 @@ class RedisRecorderActor(val system: ActorSystem) ev.setPodId(msg.body.podId) ev.setPresentationName(msg.body.presentationId) - record(msg.header.meetingId, ev.toMap) + record(msg.header.meetingId, ev.toMap.asJava) } private def handleSetPresentationDownloadableEvtMsg(msg: SetPresentationDownloadableEvtMsg) { @@ -187,7 +177,7 @@ class RedisRecorderActor(val system: ActorSystem) ev.setPresentationName(msg.body.presentationId) ev.setDownloadable(msg.body.downloadable) - record(msg.header.meetingId, ev.toMap) + record(msg.header.meetingId, ev.toMap.asJava) } private def handleSetCurrentPresentationEvtMsg(msg: SetCurrentPresentationEvtMsg) { @@ -200,7 +190,7 @@ class RedisRecorderActor(val system: ActorSystem) ev.setPodId(msg.body.podId) ev.setCurrentPresenter(msg.body.currentPresenterId) - record(msg.header.meetingId, ev.toMap) + record(msg.header.meetingId, ev.toMap.asJava) } private def handleRemovePresentationPodEvtMsg(msg: RemovePresentationPodEvtMsg) { @@ -208,7 +198,7 @@ class RedisRecorderActor(val system: ActorSystem) ev.setMeetingId(msg.header.meetingId) ev.setPodId(msg.body.podId) - record(msg.header.meetingId, ev.toMap) + record(msg.header.meetingId, ev.toMap.asJava) } private def handleSetPresenterInPodRespMsg(msg: SetPresenterInPodRespMsg) { @@ -217,7 +207,7 @@ class RedisRecorderActor(val system: ActorSystem) ev.setPodId(msg.body.podId) ev.setNextPresenterId(msg.body.nextPresenterId) - record(msg.header.meetingId, ev.toMap) + record(msg.header.meetingId, ev.toMap.asJava) } private def recordSharePresentationEvent(meetingId: String, podId: String, presentationId: String) { @@ -227,7 +217,7 @@ class RedisRecorderActor(val system: ActorSystem) ev.setPresentationName(presentationId) ev.setShare(true) - record(meetingId, ev.toMap) + record(meetingId, ev.toMap.asJava) } private def getPageNum(id: String): Integer = { @@ -266,7 +256,7 @@ class RedisRecorderActor(val system: ActorSystem) ev.setPosition(annotation.position) ev.addAnnotation(annotation.annotationInfo) - record(msg.header.meetingId, ev.toMap) + record(msg.header.meetingId, ev.toMap.asJava) } private def handleSendCursorPositionEvtMsg(msg: SendCursorPositionEvtMsg) { @@ -279,7 +269,7 @@ class RedisRecorderActor(val system: ActorSystem) ev.setXPercent(msg.body.xPercent) ev.setYPercent(msg.body.yPercent) - record(msg.header.meetingId, ev.toMap) + record(msg.header.meetingId, ev.toMap.asJava) } private def handleClearWhiteboardEvtMsg(msg: ClearWhiteboardEvtMsg) { @@ -291,7 +281,7 @@ class RedisRecorderActor(val system: ActorSystem) ev.setUserId(msg.body.userId) ev.setFullClear(msg.body.fullClear) - record(msg.header.meetingId, ev.toMap) + record(msg.header.meetingId, ev.toMap.asJava) } private def handleUndoWhiteboardEvtMsg(msg: UndoWhiteboardEvtMsg) { @@ -302,7 +292,7 @@ class RedisRecorderActor(val system: ActorSystem) ev.setWhiteboardId(msg.body.whiteboardId) ev.setUserId(msg.body.userId) ev.setShapeId(msg.body.annotationId) - record(msg.header.meetingId, ev.toMap) + record(msg.header.meetingId, ev.toMap.asJava) } private def handleUserJoinedMeetingEvtMsg(msg: UserJoinedMeetingEvtMsg): Unit = { @@ -313,7 +303,7 @@ class RedisRecorderActor(val system: ActorSystem) ev.setName(msg.body.name) ev.setRole(msg.body.role) - record(msg.header.meetingId, ev.toMap) + record(msg.header.meetingId, ev.toMap.asJava) } private def handleUserLeftMeetingEvtMsg(msg: UserLeftMeetingEvtMsg): Unit = { @@ -321,7 +311,7 @@ class RedisRecorderActor(val system: ActorSystem) ev.setMeetingId(msg.header.meetingId) ev.setUserId(msg.body.intId) - record(msg.header.meetingId, ev.toMap) + record(msg.header.meetingId, ev.toMap.asJava) } private def handlePresenterAssignedEvtMsg(msg: PresenterAssignedEvtMsg): Unit = { @@ -331,7 +321,7 @@ class RedisRecorderActor(val system: ActorSystem) ev.setName(msg.body.presenterName) ev.setAssignedBy(msg.body.assignedBy) - record(msg.header.meetingId, ev.toMap) + record(msg.header.meetingId, ev.toMap.asJava) } private def handleUserEmojiChangedEvtMsg(msg: UserEmojiChangedEvtMsg) { handleUserStatusChange(msg.header.meetingId, msg.body.userId, "emojiStatus", msg.body.emoji) @@ -352,7 +342,7 @@ class RedisRecorderActor(val system: ActorSystem) ev.setStatus(statusName) ev.setValue(statusValue) - record(meetingId, ev.toMap) + record(meetingId, ev.toMap.asJava) } private def handleUserJoinedVoiceConfToClientEvtMsg(msg: UserJoinedVoiceConfToClientEvtMsg) { @@ -365,7 +355,7 @@ class RedisRecorderActor(val system: ActorSystem) ev.setMuted(msg.body.muted) ev.setTalking(msg.body.talking) - record(msg.header.meetingId, ev.toMap) + record(msg.header.meetingId, ev.toMap.asJava) } private def handleUserLeftVoiceConfToClientEvtMsg(msg: UserLeftVoiceConfToClientEvtMsg) { @@ -374,7 +364,7 @@ class RedisRecorderActor(val system: ActorSystem) ev.setBridge(msg.body.voiceConf) ev.setParticipant(msg.body.intId) - record(msg.header.meetingId, ev.toMap) + record(msg.header.meetingId, ev.toMap.asJava) } private def handleUserMutedVoiceEvtMsg(msg: UserMutedVoiceEvtMsg) { @@ -384,7 +374,7 @@ class RedisRecorderActor(val system: ActorSystem) ev.setParticipant(msg.body.intId) ev.setMuted(msg.body.muted) - record(msg.header.meetingId, ev.toMap) + record(msg.header.meetingId, ev.toMap.asJava) } private def handleUserTalkingVoiceEvtMsg(msg: UserTalkingVoiceEvtMsg) { @@ -394,7 +384,7 @@ class RedisRecorderActor(val system: ActorSystem) ev.setParticipant(msg.body.intId) ev.setTalking(msg.body.talking) - record(msg.header.meetingId, ev.toMap) + record(msg.header.meetingId, ev.toMap.asJava) } private def handleVoiceRecordingStartedEvtMsg(msg: VoiceRecordingStartedEvtMsg) { @@ -404,7 +394,7 @@ class RedisRecorderActor(val system: ActorSystem) ev.setRecordingTimestamp(msg.body.timestamp) ev.setFilename(msg.body.stream) - record(msg.header.meetingId, ev.toMap) + record(msg.header.meetingId, ev.toMap.asJava) } private def handleVoiceRecordingStoppedEvtMsg(msg: VoiceRecordingStoppedEvtMsg) { @@ -414,7 +404,7 @@ class RedisRecorderActor(val system: ActorSystem) ev.setRecordingTimestamp(msg.body.timestamp) ev.setFilename(msg.body.stream) - record(msg.header.meetingId, ev.toMap) + record(msg.header.meetingId, ev.toMap.asJava) } private def handleEditCaptionHistoryEvtMsg(msg: EditCaptionHistoryEvtMsg) { @@ -426,7 +416,7 @@ class RedisRecorderActor(val system: ActorSystem) ev.setLocaleCode(msg.body.localeCode) ev.setText(msg.body.text) - record(msg.header.meetingId, ev.toMap) + record(msg.header.meetingId, ev.toMap.asJava) } private def handleScreenshareRtmpBroadcastStartedEvtMsg(msg: ScreenshareRtmpBroadcastStartedEvtMsg) { @@ -434,7 +424,7 @@ class RedisRecorderActor(val system: ActorSystem) ev.setMeetingId(msg.header.meetingId) ev.setStreamPath(msg.body.stream) - record(msg.header.meetingId, ev.toMap) + record(msg.header.meetingId, ev.toMap.asJava) } private def handleScreenshareRtmpBroadcastStoppedEvtMsg(msg: ScreenshareRtmpBroadcastStoppedEvtMsg) { @@ -442,7 +432,7 @@ class RedisRecorderActor(val system: ActorSystem) ev.setMeetingId(msg.header.meetingId) ev.setStreamPath(msg.body.stream) - record(msg.header.meetingId, ev.toMap) + record(msg.header.meetingId, ev.toMap.asJava) } /* @@ -462,7 +452,7 @@ class RedisRecorderActor(val system: ActorSystem) ev.setUserId(msg.body.setBy) ev.setRecordingStatus(msg.body.recording) - record(msg.header.meetingId, ev.toMap) + record(msg.header.meetingId, ev.toMap.asJava) } private def handleRecordStatusResetSysMsg(msg: RecordStatusResetSysMsg) { @@ -471,7 +461,7 @@ class RedisRecorderActor(val system: ActorSystem) ev.setUserId(msg.body.setBy) ev.setRecordingStatus(msg.body.recording) - record(msg.header.meetingId, ev.toMap) + record(msg.header.meetingId, ev.toMap.asJava) } private def handleWebcamsOnlyForModeratorChangedEvtMsg(msg: WebcamsOnlyForModeratorChangedEvtMsg) { @@ -480,14 +470,14 @@ class RedisRecorderActor(val system: ActorSystem) ev.setUserId(msg.body.setBy) ev.setWebcamsOnlyForModerator(msg.body.webcamsOnlyForModerator) - record(msg.header.meetingId, ev.toMap) + record(msg.header.meetingId, ev.toMap.asJava) } private def handleEndAndKickAllSysMsg(msg: EndAndKickAllSysMsg): Unit = { val ev = new EndAndKickAllRecordEvent() ev.setMeetingId(msg.header.meetingId) - record(msg.header.meetingId, ev.toMap) + record(msg.header.meetingId, ev.toMap.asJava) } private def handleRecordingChapterBreakSysMsg(msg: RecordingChapterBreakSysMsg): Unit = { @@ -495,7 +485,7 @@ class RedisRecorderActor(val system: ActorSystem) ev.setMeetingId(msg.header.meetingId) ev.setChapterBreakTimestamp(msg.body.timestamp) - record(msg.header.meetingId, ev.toMap) + record(msg.header.meetingId, ev.toMap.asJava) } private def handlePollStartedEvtMsg(msg: PollStartedEvtMsg): Unit = { @@ -503,7 +493,7 @@ class RedisRecorderActor(val system: ActorSystem) ev.setPollId(msg.body.pollId) ev.setAnswers(msg.body.poll.answers) - record(msg.header.meetingId, ev.toMap) + record(msg.header.meetingId, ev.toMap.asJava) } private def handleUserRespondedToPollRecordMsg(msg: UserRespondedToPollRecordMsg): Unit = { @@ -512,7 +502,7 @@ class RedisRecorderActor(val system: ActorSystem) ev.setUserId(msg.header.userId) ev.setAnswerId(msg.body.answerId) - record(msg.header.meetingId, ev.toMap) + record(msg.header.meetingId, ev.toMap.asJava) } private def handlePollStoppedEvtMsg(msg: PollStoppedEvtMsg): Unit = { @@ -527,6 +517,6 @@ class RedisRecorderActor(val system: ActorSystem) val ev = new PollStoppedRecordEvent() ev.setPollId(pollId) - record(meetingId, ev.toMap) + record(meetingId, ev.toMap.asJava) } } diff --git a/akka-bbb-apps/src/test/scala/org/bigbluebutton/core/AppsTestFixtures.scala b/akka-bbb-apps/src/test/scala/org/bigbluebutton/core/AppsTestFixtures.scala index fb2b3f5e7694a60e7140ea4158684e103b4a882b..3e57891347a7b653e8fe4392aea6854d7065ddc1 100755 --- a/akka-bbb-apps/src/test/scala/org/bigbluebutton/core/AppsTestFixtures.scala +++ b/akka-bbb-apps/src/test/scala/org/bigbluebutton/core/AppsTestFixtures.scala @@ -15,10 +15,16 @@ trait AppsTestFixtures { val meetingName = "test meeting" val record = false val voiceConfId = "85115" + val muteOnStart = true val deskshareConfId = "85115-DESKSHARE" val durationInMinutes = 10 val maxInactivityTimeoutMinutes = 120 - val warnMinutesBeforeMax = 5 + val warnMinutesBeforeMax = 30 + val meetingExpireIfNoUserJoinedInMinutes = 5 + val meetingExpireWhenLastUserLeftInMinutes = 10 + val userInactivityInspectTimerInMinutes = 60 + val userInactivityThresholdInMinutes = 10 + val userActivitySignResponseDelayInMinutes = 5 val autoStartRecording = false val allowStartStopRecording = false val webcamsOnlyForModerator = false; @@ -38,24 +44,19 @@ trait AppsTestFixtures { val red5DeskShareAppTestFixtures = "red5App" val metadata: collection.immutable.Map[String, String] = Map("foo" -> "bar", "bar" -> "baz", "baz" -> "foo") val screenshareProps = ScreenshareProps("TODO", "TODO", "TODO") - val breakoutProps = BreakoutProps(parentMeetingId, sequence, Vector()) + val breakoutProps = BreakoutProps(parentId = parentMeetingId, sequence = sequence, freeJoin = false, breakoutRooms = Vector()) val meetingProp = MeetingProp(name = meetingName, extId = externalMeetingId, intId = meetingId, isBreakout = isBreakout.booleanValue()) - val durationProps = DurationProps( - duration = durationInMinutes, - createdTime = createTime, createdDate = createDate, - maxInactivityTimeoutMinutes = maxInactivityTimeoutMinutes, - warnMinutesBeforeMax = warnMinutesBeforeMax, - meetingExpireIfNoUserJoinedInMinutes = 5, - meetingExpireWhenLastUserLeftInMinutes = 1 - ) + val durationProps = DurationProps(duration = durationInMinutes, createdTime = createTime, createdDate = createDate, maxInactivityTimeoutMinutes = maxInactivityTimeoutMinutes, warnMinutesBeforeMax = warnMinutesBeforeMax, + meetingExpireIfNoUserJoinedInMinutes = meetingExpireIfNoUserJoinedInMinutes, meetingExpireWhenLastUserLeftInMinutes = meetingExpireWhenLastUserLeftInMinutes, + userInactivityInspectTimerInMinutes = userInactivityInspectTimerInMinutes, userInactivityThresholdInMinutes = userInactivityInspectTimerInMinutes, userActivitySignResponseDelayInMinutes = userActivitySignResponseDelayInMinutes) val password = PasswordProp(moderatorPass = moderatorPassword, viewerPass = viewerPassword) val recordProp = RecordProp(record = record, autoStartRecording = autoStartRecording, allowStartStopRecording = allowStartStopRecording) val welcomeProp = WelcomeProp(welcomeMsgTemplate = welcomeMsgTemplate, welcomeMsg = welcomeMsg, modOnlyMessage = modOnlyMessage) - val voiceProp = VoiceProp(telVoice = voiceConfId, voiceConf = voiceConfId, dialNumber = dialNumber) + val voiceProp = VoiceProp(telVoice = voiceConfId, voiceConf = voiceConfId, dialNumber = dialNumber, muteOnStart = muteOnStart) val usersProp = UsersProp(maxUsers = maxUsers, webcamsOnlyForModerator = webcamsOnlyForModerator, guestPolicy = guestPolicy) val metadataProp = new MetadataProp(metadata) @@ -84,7 +85,6 @@ trait AppsTestFixtures { val layouts = new Layouts() val wbModel = new WhiteboardModel() val presModel = new PresentationModel() - val breakoutRooms = new BreakoutRooms() val captionModel = new CaptionModel() val notesModel = new SharedNotesModel() val registeredUsers = new RegisteredUsers diff --git a/akka-bbb-apps/src/test/scala/org/bigbluebutton/core/domain/MeetingInactivityTrackerTests.scala b/akka-bbb-apps/src/test/scala/org/bigbluebutton/core/domain/MeetingInactivityTrackerTests.scala index 6a09dc346bd76bba763afeea65f961be87b4daed..95f8ab07d9f674a95c649813b873fe8e5c11c6da 100755 --- a/akka-bbb-apps/src/test/scala/org/bigbluebutton/core/domain/MeetingInactivityTrackerTests.scala +++ b/akka-bbb-apps/src/test/scala/org/bigbluebutton/core/domain/MeetingInactivityTrackerTests.scala @@ -1,7 +1,6 @@ package org.bigbluebutton.core.domain import org.bigbluebutton.core.UnitSpec -import org.bigbluebutton.core.running.MeetingExpiryTrackerHelper import org.bigbluebutton.core.util.TimeUtil class MeetingInactivityTrackerTests extends UnitSpec { diff --git a/akka-bbb-apps/src/test/scala/org/bigbluebutton/core/models/GroupsChatTests.scala b/akka-bbb-apps/src/test/scala/org/bigbluebutton/core/models/GroupsChatTests.scala index ca600fa0a2c3bff3f0db4fb9d6a95797f61ae74d..60246a571a7341bd1abca9ce3c484d690e92168a 100755 --- a/akka-bbb-apps/src/test/scala/org/bigbluebutton/core/models/GroupsChatTests.scala +++ b/akka-bbb-apps/src/test/scala/org/bigbluebutton/core/models/GroupsChatTests.scala @@ -2,7 +2,6 @@ package org.bigbluebutton.core.models import org.bigbluebutton.common2.msgs.{ GroupChatAccess, GroupChatUser } import org.bigbluebutton.core.UnitSpec -import org.bigbluebutton.core.domain.BbbSystemConst class GroupsChatTests extends UnitSpec { @@ -10,7 +9,7 @@ class GroupsChatTests extends UnitSpec { val gcId = "gc-id" val chatName = "Public" val userId = "uid-1" - val createBy = GroupChatUser(BbbSystemConst.SYSTEM_USER, BbbSystemConst.SYSTEM_USER) + val createBy = GroupChatUser("groupId", "groupname") val gc = GroupChatFactory.create(gcId, chatName, GroupChatAccess.PUBLIC, createBy, Vector.empty, Vector.empty) val user = GroupChatUser(userId, "User 1") val gc2 = gc.add(user) @@ -25,18 +24,16 @@ class GroupsChatTests extends UnitSpec { } "A GroupChat" should "be able to add, update, and remove msg" in { - val createBy = GroupChatUser(BbbSystemConst.SYSTEM_USER, BbbSystemConst.SYSTEM_USER) + val createBy = GroupChatUser("groupId", "groupname") val gcId = "gc-id" val chatName = "Public" - val userId = "uid-1" val gc = GroupChatFactory.create(gcId, chatName, GroupChatAccess.PUBLIC, createBy, Vector.empty, Vector.empty) val msgId1 = "msgid-1" val ts = System.currentTimeMillis() val hello = "Hello World!" val msg1 = GroupChatMessage(id = msgId1, timestamp = ts, correlationId = "cordId1", createdOn = ts, - updatedOn = ts, sender = createBy, - font = "arial", size = 12, color = "red", message = hello) + updatedOn = ts, sender = createBy, color = "red", message = hello) val gc2 = gc.add(msg1) assert(gc2.msgs.size == 1) @@ -45,8 +42,7 @@ class GroupsChatTests extends UnitSpec { val foo = "Foo bar" val ts2 = System.currentTimeMillis() val msg2 = GroupChatMessage(id = msgId2, timestamp = ts2, correlationId = "cordId2", createdOn = ts2, - updatedOn = ts2, sender = createBy, - font = "arial", size = 12, color = "red", message = foo) + updatedOn = ts2, sender = createBy, color = "red", message = foo) val gc3 = gc2.add(msg2) assert(gc3.msgs.size == 2) @@ -55,8 +51,7 @@ class GroupsChatTests extends UnitSpec { val msgId3 = "msgid-3" val ts3 = System.currentTimeMillis() val msg3 = GroupChatMessage(id = msgId3, timestamp = ts3, correlationId = "cordId3", createdOn = ts3, - updatedOn = ts3, sender = createBy, - font = "arial", size = 12, color = "red", message = baz) + updatedOn = ts3, sender = createBy, color = "red", message = baz) val gc4 = gc3.update(msg3) gc4.findMsgWithId(msgId3) match { diff --git a/akka-bbb-apps/src/test/scala/org/bigbluebutton/core/pubsub/sender/ReceivedJsonMsgHandlerTraitTests.scala b/akka-bbb-apps/src/test/scala/org/bigbluebutton/core/pubsub/sender/ReceivedJsonMsgHandlerTraitTests.scala index 8de72a97a547dd9e08f8c75d76089b406df4574e..329383ad1512275c9afab734496cd35dfee05273 100755 --- a/akka-bbb-apps/src/test/scala/org/bigbluebutton/core/pubsub/sender/ReceivedJsonMsgHandlerTraitTests.scala +++ b/akka-bbb-apps/src/test/scala/org/bigbluebutton/core/pubsub/sender/ReceivedJsonMsgHandlerTraitTests.scala @@ -3,13 +3,13 @@ package org.bigbluebutton.core.pubsub.sender import org.bigbluebutton.SystemConfiguration import org.bigbluebutton.common2.msgs._ import org.bigbluebutton.core.{ AppsTestFixtures, UnitSpec } -import org.bigbluebutton.core.bus.{ BbbMsgEvent, BbbMsgRouterEventBus, ReceivedJsonMessage } +import org.bigbluebutton.core.bus.{ BbbMsgEvent, BbbMsgRouterEventBus } import org.bigbluebutton.core2.ReceivedMessageRouter import org.mockito.Mockito._ import org.scalatest.mockito.MockitoSugar class ReceivedJsonMsgHandlerTraitTests extends UnitSpec - with AppsTestFixtures with MockitoSugar with SystemConfiguration { + with AppsTestFixtures with MockitoSugar with SystemConfiguration { class MessageRouter(val eventBus: BbbMsgRouterEventBus) extends ReceivedMessageRouter { diff --git a/akka-bbb-apps/src/test/scala/org/bigbluebutton/core2/testdata/TestDataGen.scala b/akka-bbb-apps/src/test/scala/org/bigbluebutton/core2/testdata/TestDataGen.scala index d4789a43739f5a05ca2d49e1d5eb65c9a844e564..907d352379aec626bc70dbb139878d14d3d7b412 100755 --- a/akka-bbb-apps/src/test/scala/org/bigbluebutton/core2/testdata/TestDataGen.scala +++ b/akka-bbb-apps/src/test/scala/org/bigbluebutton/core2/testdata/TestDataGen.scala @@ -46,9 +46,9 @@ object TestDataGen { def createUserFor(liveMeeting: LiveMeeting, regUser: RegisteredUser, presenter: Boolean): UserState = { val u = UserState(intId = regUser.id, extId = regUser.externId, name = regUser.name, role = regUser.role, guest = regUser.guest, authed = regUser.authed, guestStatus = regUser.guestStatus, - emoji = "none", locked = false, presenter, avatar = regUser.avatarURL) + emoji = "none", locked = false, presenter = false, avatar = regUser.avatarURL, clientType = "unknown", + userLeftFlag = UserLeftFlag(false, 0)) Users2x.add(liveMeeting.users2x, u) - u } } diff --git a/akka-bbb-apps/src/main/resources/application.conf b/akka-bbb-apps/src/universal/conf/application.conf similarity index 91% rename from akka-bbb-apps/src/main/resources/application.conf rename to akka-bbb-apps/src/universal/conf/application.conf index e6904f7e9d99b9aae44fb0428072f15ebef56a5c..591f22a88f5ef2613020407b8dc6f493bb6cf046 100755 --- a/akka-bbb-apps/src/main/resources/application.conf +++ b/akka-bbb-apps/src/universal/conf/application.conf @@ -10,7 +10,7 @@ akka { loggers = ["akka.event.slf4j.Slf4jLogger"] loglevel = "DEBUG" - rediscala-publish-worker-dispatcher { + redis-publish-worker-dispatcher { mailbox-type = "akka.dispatch.SingleConsumerOnlyUnboundedMailbox" # Throughput defines the maximum number of messages to be # processed per actor before the thread jumps to the next actor. @@ -18,7 +18,7 @@ akka { throughput = 512 } - rediscala-subscriber-worker-dispatcher { + redis-subscriber-worker-dispatcher { mailbox-type = "akka.dispatch.SingleConsumerOnlyUnboundedMailbox" # Throughput defines the maximum number of messages to be # processed per actor before the thread jumps to the next actor. diff --git a/akka-bbb-apps/src/universal/conf/application.ini b/akka-bbb-apps/src/universal/conf/application.ini deleted file mode 100644 index fd2def1422e02c232613e5ac925ebd482d0e4508..0000000000000000000000000000000000000000 --- a/akka-bbb-apps/src/universal/conf/application.ini +++ /dev/null @@ -1,42 +0,0 @@ -# ################################# -# ##### Default configuration ##### -# ################################# - -# Available replacements -# ------------------------------------------------ -# ${{author}} debian author -# ${{descr}} debian package description -# ${{exec}} startup script name -# ${{chdir}} app directory -# ${{retries}} retries for startup -# ${{retryTimeout}} retry timeout -# ${{app_name}} normalized app name -# ${{daemon_user}} daemon user -# ------------------------------------------------- -# DEPRECATED, use -J-Xmx1024m instead -# -mem 1024 - -# Setting -X directly (-J is stripped) -# -J-X -# -J-Xmx1024 - -# Add additional jvm parameters -# -Dkey=val - -# For play applications you may set -# -Dpidfile.path=/var/run/${{app_name}}/play.pid - -# Turn on JVM debugging, open at the given port -# -jvm-debug <port> - -# Don't run the java version check -# -no-version-check - --J-Xms130m --J-Xmx256m - -# With universal:packageBin: -# - setup with a configuration tool after unzip -# - use the path to the application.ini file -# -Dconfig.file=${{path_to}}/conf/application.conf --Dconfig.file=/usr/share/bbb-apps-akka/conf/application.conf diff --git a/akka-bbb-apps/src/main/resources/logback.xml b/akka-bbb-apps/src/universal/conf/logback.xml similarity index 93% rename from akka-bbb-apps/src/main/resources/logback.xml rename to akka-bbb-apps/src/universal/conf/logback.xml index 9c708ee57417ccbcd1512e675e9ba8b774d002ad..6f600cf75ea09a9480faec3b76a6c918d34f8968 100755 --- a/akka-bbb-apps/src/main/resources/logback.xml +++ b/akka-bbb-apps/src/universal/conf/logback.xml @@ -20,6 +20,7 @@ <logger name="akka" level="INFO" /> <logger name="org.bigbluebutton" level="DEBUG" /> + <logger name="io.lettuce" level="INFO" /> <root level="DEBUG"> <appender-ref ref="STDOUT"/> diff --git a/akka-bbb-fsesl/build.sbt b/akka-bbb-fsesl/build.sbt index 319345d682c45b7255b457ba2a3adafae985b767..98a18e948b17fb1d28e7325049e72816aa04b775 100755 --- a/akka-bbb-fsesl/build.sbt +++ b/akka-bbb-fsesl/build.sbt @@ -1,26 +1,35 @@ -enablePlugins(JavaServerAppPackaging) - -name := "bbb-fsesl-akka" +import org.bigbluebutton.build._ -organization := "org.bigbluebutton" +import scalariform.formatter.preferences._ +import com.typesafe.sbt.SbtScalariform +import com.typesafe.sbt.SbtScalariform.ScalariformKeys -version := "0.0.1" +import com.typesafe.sbt.SbtNativePackager.autoImport._ -scalaVersion := "2.12.6" +enablePlugins(JavaServerAppPackaging) -scalacOptions ++= Seq( - "-unchecked", - "-deprecation", - "-Xlint", - "-Ywarn-dead-code", - "-language:_", - "-target:jvm-1.8", - "-encoding", "UTF-8" +version := "0.0.2" + +val compileSettings = Seq( + organization := "org.bigbluebutton", + + scalacOptions ++= List( + "-unchecked", + "-deprecation", + "-Xlint", + "-Ywarn-dead-code", + "-language:_", + "-target:jvm-1.8", + "-encoding", "UTF-8" + ), + javacOptions ++= List( + "-Xlint:unchecked", + "-Xlint:deprecation" + ) ) resolvers ++= Seq( "spray repo" at "http://repo.spray.io/", - "rediscala" at "http://dl.bintray.com/etaty/maven", "blindside-repos" at "http://blindside.googlecode.com/svn/repository/" ) @@ -37,63 +46,14 @@ testOptions in Test += Tests.Argument(TestFrameworks.Specs2, "html", "console", testOptions in Test += Tests.Argument(TestFrameworks.ScalaTest, "-h", "target/scalatest-reports") -val akkaVersion = "2.5.14" -val scalaTestV = "2.2.6" - - -libraryDependencies ++= { - Seq( - "ch.qos.logback" % "logback-classic" % "1.2.3" % "runtime", - "junit" % "junit" % "4.11", - "commons-codec" % "commons-codec" % "1.11", - "joda-time" % "joda-time" % "2.10", - "org.apache.commons" % "commons-lang3" % "3.7" - - )} - -libraryDependencies += "org.bigbluebutton" % "bbb-common-message_2.12" % "0.0.19-SNAPSHOT" - -libraryDependencies += "org.bigbluebutton" % "bbb-fsesl-client" % "0.0.6" - -// https://mvnrepository.com/artifact/org.scala-lang/scala-library -libraryDependencies += "org.scala-lang" % "scala-library" % scalaVersion.value -// https://mvnrepository.com/artifact/org.scala-lang/scala-compiler -libraryDependencies += "org.scala-lang" % "scala-compiler" % scalaVersion.value - -// https://mvnrepository.com/artifact/com.typesafe.akka/akka-actor_2.12 -libraryDependencies += "com.typesafe.akka" % "akka-actor_2.12" % akkaVersion - -// https://mvnrepository.com/artifact/com.typesafe.akka/akka-slf4j_2.12 -libraryDependencies += "com.typesafe.akka" % "akka-slf4j_2.12" % akkaVersion - -// https://mvnrepository.com/artifact/com.github.etaty/rediscala_2.12 -libraryDependencies += "com.github.etaty" % "rediscala_2.12" % "1.8.0" - -// For generating test reports -libraryDependencies += "org.pegdown" % "pegdown" % "1.6.0" % "test" -// https://mvnrepository.com/artifact/com.typesafe.akka/akka-testkit_2.12 -libraryDependencies += "com.typesafe.akka" % "akka-testkit_2.12" % "2.5.14" % "test" - -// https://mvnrepository.com/artifact/org.scalactic/scalactic_2.12 -libraryDependencies += "org.scalactic" % "scalactic_2.12" % "3.0.5" % "test" - -// https://mvnrepository.com/artifact/org.scalatest/scalatest_2.12 -libraryDependencies += "org.scalatest" % "scalatest_2.12" % "3.0.5" % "test" - -libraryDependencies += "org.mockito" % "mockito-core" % "2.21.0" % "test" - -seq(Revolver.settings: _*) - -import com.typesafe.sbt.SbtScalariform - -import scalariform.formatter.preferences._ -import com.typesafe.sbt.SbtScalariform.ScalariformKeys +Seq(Revolver.settings: _*) +lazy val bbbFseslAkka = (project in file(".")).settings(name := "bbb-fsesl-akka", libraryDependencies ++= Dependencies.runtime).settings(compileSettings) -SbtScalariform.defaultScalariformSettings +scalariformAutoformat := false -ScalariformKeys.preferences := ScalariformKeys.preferences.value +scalariformPreferences := scalariformPreferences.value .setPreference(AlignSingleLineCaseStatements, true) - .setPreference(DoubleIndentClassDeclaration, true) + .setPreference(DoubleIndentConstructorArguments, true) .setPreference(AlignParameters, true) //----------- @@ -116,21 +76,9 @@ val user = "bigbluebutton" val group = "bigbluebutton" // user which will execute the application -daemonUser in Linux := user +daemonUser in Linux := user // group which will execute the application -daemonGroup in Linux := group - -mappings in Universal <+= (packageBin in Compile, sourceDirectory ) map { (_, src) => - // Move the application.conf so the user can override settings here - val appConf = src / "main" / "resources" / "application.conf" - appConf -> "conf/application.conf" -} - -mappings in Universal <+= (packageBin in Compile, sourceDirectory ) map { (_, src) => - // Move logback.xml so the user can override settings here - val logConf = src / "main" / "resources" / "logback.xml" - logConf -> "conf/logback.xml" -} +daemonGroup in Linux := group debianPackageDependencies in Debian ++= Seq("java8-runtime-headless", "bash") diff --git a/akka-bbb-fsesl/project/Build.scala b/akka-bbb-fsesl/project/Build.scala deleted file mode 100755 index e69de29bb2d1d6434b8b29ae775ad8c2e48c5391..0000000000000000000000000000000000000000 diff --git a/akka-bbb-fsesl/project/Dependencies.scala b/akka-bbb-fsesl/project/Dependencies.scala new file mode 100644 index 0000000000000000000000000000000000000000..15c2ace50af2b60f09633b4f9464ff30ffb1fd1b --- /dev/null +++ b/akka-bbb-fsesl/project/Dependencies.scala @@ -0,0 +1,72 @@ +package org.bigbluebutton.build + +import sbt._ +import Keys._ + +object Dependencies { + + object Versions { + // Scala + val scala = "2.12.7" + val junitInterface = "0.11" + val scalactic = "3.0.3" + + // Libraries + val akkaVersion = "2.5.17" + val logback = "1.2.3" + + // Apache Commons + val lang = "3.8.1" + val codec = "1.11" + + // BigBlueButton + val bbbCommons = "0.0.20-SNAPSHOT" + val bbbFsesl = "0.0.7-SNAPSHOT" + + // Test + val scalaTest = "3.0.5" + val akkaTestKit = "2.5.18" + val junit = "4.12" + } + + object Compile { + val scalaLibrary = "org.scala-lang" % "scala-library" % Versions.scala + val scalaCompiler = "org.scala-lang" % "scala-compiler" % Versions.scala + + val akkaActor = "com.typesafe.akka" % "akka-actor_2.12" % Versions.akkaVersion + val akkaSl4fj = "com.typesafe.akka" % "akka-slf4j_2.12" % Versions.akkaVersion + + val logback = "ch.qos.logback" % "logback-classic" % Versions.logback + val commonsCodec = "commons-codec" % "commons-codec" % Versions.codec + + val apacheLang = "org.apache.commons" % "commons-lang3" % Versions.lang + + val bbbCommons = "org.bigbluebutton" % "bbb-common-message_2.12" % Versions.bbbCommons excludeAll ( + ExclusionRule(organization = "org.red5")) + val bbbFseslClient = "org.bigbluebutton" % "bbb-fsesl-client" % Versions.bbbFsesl + } + + object Test { + val scalaTest = "org.scalatest" %% "scalatest" % Versions.scalaTest % "test" + val junit = "junit" % "junit" % Versions.junit % "test" + val scalactic = "org.scalactic" % "scalactic_2.12" % Versions.scalactic % "test" + val akkaTestKit = "com.typesafe.akka" %% "akka-testkit" % Versions.akkaTestKit % "test" + } + + val testing = Seq( + Test.scalaTest, + Test.junit, + Test.scalactic, + Test.akkaTestKit) + + val runtime = Seq( + Compile.scalaLibrary, + Compile.scalaCompiler, + Compile.akkaActor, + Compile.akkaSl4fj, + Compile.logback, + Compile.commonsCodec, + Compile.apacheLang, + Compile.bbbCommons, + Compile.bbbFseslClient) ++ testing +} diff --git a/akka-bbb-fsesl/project/build.properties b/akka-bbb-fsesl/project/build.properties index a6e117b61042ee81c62ba3a0fc5210d9502944df..2e6e3d24608ee15e892ed3b16d84224f7667e808 100755 --- a/akka-bbb-fsesl/project/build.properties +++ b/akka-bbb-fsesl/project/build.properties @@ -1 +1 @@ -sbt.version=0.13.8 +sbt.version=1.2.6 \ No newline at end of file diff --git a/akka-bbb-fsesl/project/plugins.sbt b/akka-bbb-fsesl/project/plugins.sbt index 56e1e39f39dc0e78ca98f35d7c98f4c833509120..bc8c448553a2010f0c76ea69ccb7917f51ffef8a 100755 --- a/akka-bbb-fsesl/project/plugins.sbt +++ b/akka-bbb-fsesl/project/plugins.sbt @@ -1,11 +1,11 @@ addSbtPlugin("io.spray" % "sbt-revolver" % "0.9.1") -addSbtPlugin("org.scalariform" % "sbt-scalariform" % "1.8.2") - addSbtPlugin("com.typesafe.sbteclipse" % "sbteclipse-plugin" % "5.2.4") -addSbtPlugin("com.typesafe.sbt" % "sbt-native-packager" % "1.3.6") +addSbtPlugin("org.scalariform" % "sbt-scalariform" % "1.8.2") + +addSbtPlugin("com.typesafe.sbt" % "sbt-native-packager" % "1.3.12") -addSbtPlugin("net.vonbuchholtz" % "sbt-dependency-check" % "0.2.7") +addSbtPlugin("net.vonbuchholtz" % "sbt-dependency-check" % "0.2.8") addSbtPlugin("org.scalastyle" %% "scalastyle-sbt-plugin" % "1.0.0") diff --git a/akka-bbb-fsesl/run.sh b/akka-bbb-fsesl/run.sh index 4015c4f1373a8caa1271bb9fa175c5940624bf89..97d3f3534508e5c4b3e816a8481bb4ae16b80644 100755 --- a/akka-bbb-fsesl/run.sh +++ b/akka-bbb-fsesl/run.sh @@ -1,3 +1 @@ -sbt clean -sbt run - +sbt clean run \ No newline at end of file diff --git a/akka-bbb-fsesl/src/main/resources/README b/akka-bbb-fsesl/src/main/resources/README deleted file mode 100644 index e69de29bb2d1d6434b8b29ae775ad8c2e48c5391..0000000000000000000000000000000000000000 diff --git a/akka-bbb-fsesl/src/main/resources/application.conf b/akka-bbb-fsesl/src/main/resources/application.conf index 37ac46cfee4c9c4a83c09df12747a5bbfeda6ac0..ac1acc3b350de7a5059eccf75d229db145a5a592 100755 --- a/akka-bbb-fsesl/src/main/resources/application.conf +++ b/akka-bbb-fsesl/src/main/resources/application.conf @@ -1,37 +1,37 @@ -akka { - actor { - debug { - receive = on - } - } - loggers = ["akka.event.slf4j.Slf4jLogger"] - loglevel = "DEBUG" - stdout-loglevel = "DEBUG" - - rediscala-subscriber-worker-dispatcher { - mailbox-type = "akka.dispatch.SingleConsumerOnlyUnboundedMailbox" - # Throughput defines the maximum number of messages to be - # processed per actor before the thread jumps to the next actor. - # Set to 1 for as fair as possible. - throughput = 512 - } -} - - -freeswitch { - esl { - host="127.0.0.1" - port=8021 - password="ClueCon" - } - conf { - profile="cdquality" - } -} - -redis { - host="127.0.0.1" - port=6379 - password="" -} - +akka { + actor { + debug { + receive = on + } + } + loggers = ["akka.event.slf4j.Slf4jLogger"] + loglevel = "DEBUG" + stdout-loglevel = "DEBUG" + + redis-subscriber-worker-dispatcher { + mailbox-type = "akka.dispatch.SingleConsumerOnlyUnboundedMailbox" + # Throughput defines the maximum number of messages to be + # processed per actor before the thread jumps to the next actor. + # Set to 1 for as fair as possible. + throughput = 512 + } +} + + +freeswitch { + esl { + host="127.0.0.1" + port=8021 + password="ClueCon" + } + conf { + profile="cdquality" + } +} + +redis { + host="127.0.0.1" + port=6379 + password="" +} + diff --git a/akka-bbb-fsesl/src/main/resources/logback.xml b/akka-bbb-fsesl/src/main/resources/logback.xml index 8d82842c8e363216e6ba4bd9fef04b323999b0a6..e749065dae82aa407ddb622470491f0705eb29a2 100755 --- a/akka-bbb-fsesl/src/main/resources/logback.xml +++ b/akka-bbb-fsesl/src/main/resources/logback.xml @@ -21,6 +21,7 @@ <logger name="akka" level="INFO" /> <logger name="org.bigbluebutton" level="DEBUG" /> <logger name="org.freeswitch.esl" level="WARN" /> + <logger name="io.lettuce" level="INFO" /> <root level="DEBUG"> <appender-ref ref="STDOUT"/> diff --git a/akka-bbb-fsesl/src/main/scala/org/bigbluebutton/Boot.scala b/akka-bbb-fsesl/src/main/scala/org/bigbluebutton/Boot.scala index 1ab8664c2b09a3489ca5c8aa60322a67ca47f57e..aad91e4ddd34681322d68a2a89cf912849dbfb55 100755 --- a/akka-bbb-fsesl/src/main/scala/org/bigbluebutton/Boot.scala +++ b/akka-bbb-fsesl/src/main/scala/org/bigbluebutton/Boot.scala @@ -1,19 +1,20 @@ package org.bigbluebutton -import akka.actor.{ ActorSystem } - -import org.bigbluebutton.endpoint.redis.{ AppsRedisSubscriberActor, RedisPublisher } +import org.bigbluebutton.common2.bus.IncomingJsonMessageBus +import org.bigbluebutton.common2.redis.RedisPublisher +import org.bigbluebutton.endpoint.redis.FSESLRedisSubscriberActor import org.bigbluebutton.freeswitch.{ RxJsonMsgHdlrActor, VoiceConferenceService } -import org.bigbluebutton.freeswitch.bus.InsonMsgBus import org.bigbluebutton.freeswitch.voice.FreeswitchConferenceEventListener import org.bigbluebutton.freeswitch.voice.freeswitch.{ ConnectionManager, ESLEventListener, FreeswitchApplication } import org.freeswitch.esl.client.manager.DefaultManagerConnection +import akka.actor.ActorSystem + object Boot extends App with SystemConfiguration { implicit val system = ActorSystem("bigbluebutton-fsesl-system") - val redisPublisher = new RedisPublisher(system) + val redisPublisher = new RedisPublisher(system, "BbbFsEslAkkaPub") val eslConnection = new DefaultManagerConnection(eslHost, eslPort, eslPassword) @@ -30,10 +31,9 @@ object Boot extends App with SystemConfiguration { val fsApplication = new FreeswitchApplication(connManager, fsProfile) fsApplication.start() - val inJsonMsgBus = new InsonMsgBus + val inJsonMsgBus = new IncomingJsonMessageBus val redisMessageHandlerActor = system.actorOf(RxJsonMsgHdlrActor.props(fsApplication)) inJsonMsgBus.subscribe(redisMessageHandlerActor, toFsAppsJsonChannel) - val redisSubscriberActor = system.actorOf(AppsRedisSubscriberActor.props(system, inJsonMsgBus), "redis-subscriber") - + val redisSubscriberActor = system.actorOf(FSESLRedisSubscriberActor.props(system, inJsonMsgBus), "redis-subscriber") } diff --git a/akka-bbb-fsesl/src/main/scala/org/bigbluebutton/SystemConfiguration.scala b/akka-bbb-fsesl/src/main/scala/org/bigbluebutton/SystemConfiguration.scala index c9c776a410d714d2e28627a173e97a075500050e..188d202835c68438d44ba8fc7f26308fb929d5d2 100755 --- a/akka-bbb-fsesl/src/main/scala/org/bigbluebutton/SystemConfiguration.scala +++ b/akka-bbb-fsesl/src/main/scala/org/bigbluebutton/SystemConfiguration.scala @@ -1,23 +1,15 @@ package org.bigbluebutton -import com.typesafe.config.ConfigFactory import scala.util.Try -trait SystemConfiguration { - - val config = ConfigFactory.load() +import org.bigbluebutton.common2.redis.RedisConfiguration +trait SystemConfiguration extends RedisConfiguration { lazy val eslHost = Try(config.getString("freeswitch.esl.host")).getOrElse("127.0.0.1") lazy val eslPort = Try(config.getInt("freeswitch.esl.port")).getOrElse(8021) lazy val eslPassword = Try(config.getString("freeswitch.esl.password")).getOrElse("ClueCon") lazy val fsProfile = Try(config.getString("freeswitch.conf.profile")).getOrElse("cdquality") - lazy val redisHost = Try(config.getString("redis.host")).getOrElse("127.0.0.1") - lazy val redisPort = Try(config.getInt("redis.port")).getOrElse(6379) - lazy val redisPassword = Try(config.getString("redis.password")).getOrElse("") - - lazy val toVoiceConfRedisChannel = Try(config.getString("redis.toVoiceConfRedisChannel")).getOrElse("to-voice-conf-redis-channel") - lazy val fromVoiceConfRedisChannel = Try(config.getString("redis.fromVoiceConfRedisChannel")).getOrElse("from-voice-conf-redis-channel") lazy val toFsAppsJsonChannel = Try(config.getString("eventBus.toFsAppsChannel")).getOrElse("to-fs-apps-json-channel") lazy val fromFsAppsJsonChannel = Try(config.getString("eventBus.fromFsAppsChannel")).getOrElse("from-fs-apps-json-channel") } diff --git a/akka-bbb-fsesl/src/main/scala/org/bigbluebutton/endpoint/redis/AppsRedisSubscriberActor.scala b/akka-bbb-fsesl/src/main/scala/org/bigbluebutton/endpoint/redis/AppsRedisSubscriberActor.scala deleted file mode 100755 index 4f969be0b6465347544e038f95917fa3c8a1794e..0000000000000000000000000000000000000000 --- a/akka-bbb-fsesl/src/main/scala/org/bigbluebutton/endpoint/redis/AppsRedisSubscriberActor.scala +++ /dev/null @@ -1,82 +0,0 @@ -package org.bigbluebutton.endpoint.redis - -import java.io.PrintWriter -import java.io.StringWriter -import java.net.InetSocketAddress - -import scala.concurrent.ExecutionContext.Implicits.global -import scala.concurrent.duration.DurationInt -import org.bigbluebutton.SystemConfiguration -import akka.actor.ActorSystem -import akka.actor.OneForOneStrategy -import akka.actor.Props -import akka.actor.SupervisorStrategy.Resume -import org.bigbluebutton.freeswitch.bus.{ InJsonMsg, InsonMsgBus, ReceivedJsonMsg } -import redis.actors.RedisSubscriberActor -import redis.api.pubsub.Message -import redis.api.pubsub.PMessage -import redis.api.servers.ClientSetname - -object AppsRedisSubscriberActor extends SystemConfiguration { - - val channels = Seq(toVoiceConfRedisChannel) - val patterns = Seq("bigbluebutton:to-voice-conf:*", "bigbluebutton:from-bbb-apps:*") - - def props(system: ActorSystem, inJsonMgBus: InsonMsgBus): Props = - Props(classOf[AppsRedisSubscriberActor], system, inJsonMgBus, - redisHost, redisPort, - channels, patterns).withDispatcher("akka.rediscala-subscriber-worker-dispatcher") -} - -class AppsRedisSubscriberActor( - val system: ActorSystem, - inJsonMgBus: InsonMsgBus, redisHost: String, - redisPort: Int, - channels: Seq[String] = Nil, patterns: Seq[String] = Nil) - extends RedisSubscriberActor( - new InetSocketAddress(redisHost, redisPort), - channels, patterns, onConnectStatus = connected => { println(s"connected: $connected") }) with SystemConfiguration { - - override val supervisorStrategy = OneForOneStrategy(maxNrOfRetries = 10, withinTimeRange = 1 minute) { - case e: Exception => { - val sw: StringWriter = new StringWriter() - sw.write("An exception has been thrown on AppsRedisSubscriberActor, exception message [" + e.getMessage() + "] (full stacktrace below)\n") - e.printStackTrace(new PrintWriter(sw)) - log.error(sw.toString()) - Resume - } - } - - // val decoder = new FromJsonDecoder() - - var lastPongReceivedOn = 0L - system.scheduler.schedule(10 seconds, 10 seconds)(checkPongMessage()) - - // Set the name of this client to be able to distinguish when doing - // CLIENT LIST on redis-cli - write(ClientSetname("BbbFsEslAkkaSub").encodedRequest) - - def checkPongMessage() { - val now = System.currentTimeMillis() - - if (lastPongReceivedOn != 0 && (now - lastPongReceivedOn > 30000)) { - log.error("FSESL pubsub error!"); - } - } - - def onMessage(message: Message) { - if (message.channel == toVoiceConfRedisChannel) { - val receivedJsonMessage = new ReceivedJsonMsg(message.channel, message.data.utf8String) - log.debug(s"RECEIVED:\n [${receivedJsonMessage.channel}] \n ${receivedJsonMessage.data} \n") - inJsonMgBus.publish(InJsonMsg(toFsAppsJsonChannel, receivedJsonMessage)) - } - } - - def onPMessage(pmessage: PMessage) { - // log.debug(s"pattern message received: $pmessage") - } - - def handleMessage(msg: String) { - log.warning("**** TODO: Handle pubsub messages. ****") - } -} diff --git a/akka-bbb-fsesl/src/main/scala/org/bigbluebutton/endpoint/redis/FSESLRedisSubscriberActor.scala b/akka-bbb-fsesl/src/main/scala/org/bigbluebutton/endpoint/redis/FSESLRedisSubscriberActor.scala new file mode 100755 index 0000000000000000000000000000000000000000..016bc9890b42407769b824287b1705f415063b9e --- /dev/null +++ b/akka-bbb-fsesl/src/main/scala/org/bigbluebutton/endpoint/redis/FSESLRedisSubscriberActor.scala @@ -0,0 +1,46 @@ +package org.bigbluebutton.endpoint.redis + +import scala.concurrent.ExecutionContext.Implicits.global +import scala.concurrent.duration.DurationInt + +import org.bigbluebutton.SystemConfiguration +import org.bigbluebutton.common2.bus.IncomingJsonMessageBus +import org.bigbluebutton.common2.redis.{ RedisSubscriber, RedisSubscriberProvider } + +import akka.actor.ActorSystem +import akka.actor.Props + +object FSESLRedisSubscriberActor extends RedisSubscriber { + + val channels = Seq(toVoiceConfRedisChannel) + val patterns = Seq("bigbluebutton:to-voice-conf:*", "bigbluebutton:from-bbb-apps:*") + + def props(system: ActorSystem, inJsonMgBus: IncomingJsonMessageBus): Props = + Props( + classOf[FSESLRedisSubscriberActor], + system, inJsonMgBus, + redisHost, redisPort, + channels, patterns).withDispatcher("akka.redis-subscriber-worker-dispatcher") +} + +class FSESLRedisSubscriberActor( + system: ActorSystem, + inJsonMgBus: IncomingJsonMessageBus, + redisHost: String, redisPort: Int, + channels: Seq[String] = Nil, patterns: Seq[String] = Nil) + extends RedisSubscriberProvider(system, "BbbFsEslAkkaSub", channels, patterns, inJsonMgBus) with SystemConfiguration { + + var lastPongReceivedOn = 0L + system.scheduler.schedule(10 seconds, 10 seconds)(checkPongMessage()) + + def checkPongMessage() { + val now = System.currentTimeMillis() + + if (lastPongReceivedOn != 0 && (now - lastPongReceivedOn > 30000)) { + log.error("FSESL pubsub error!"); + } + } + + addListener(toFsAppsJsonChannel) + subscribe() +} \ No newline at end of file diff --git a/akka-bbb-fsesl/src/main/scala/org/bigbluebutton/endpoint/redis/RedisPublisher.scala b/akka-bbb-fsesl/src/main/scala/org/bigbluebutton/endpoint/redis/RedisPublisher.scala deleted file mode 100755 index 11acf3d06d30cd2574e3e38a26befc09fae7166d..0000000000000000000000000000000000000000 --- a/akka-bbb-fsesl/src/main/scala/org/bigbluebutton/endpoint/redis/RedisPublisher.scala +++ /dev/null @@ -1,20 +0,0 @@ -package org.bigbluebutton.endpoint.redis - -import redis.RedisClient -import akka.actor.ActorSystem -import org.bigbluebutton.SystemConfiguration - -class RedisPublisher(val system: ActorSystem) extends SystemConfiguration { - - val redis = RedisClient(redisHost, redisPort)(system) - - // Set the name of this client to be able to distinguish when doing - // CLIENT LIST on redis-cli - redis.clientSetname("BbbFsEslAkkaPub") - - def publish(channel: String, data: String) { - //println("PUBLISH TO [" + channel + "]: \n [" + data + "]") - redis.publish(channel, data) - } - -} diff --git a/akka-bbb-fsesl/src/main/scala/org/bigbluebutton/freeswitch/RxJsonMsgHdlrActor.scala b/akka-bbb-fsesl/src/main/scala/org/bigbluebutton/freeswitch/RxJsonMsgHdlrActor.scala index 378210c50ca7307ba0927d0d84fc0f18997471ee..dc839a9f3162c165c0ec3521fd1fa0a931147fd3 100755 --- a/akka-bbb-fsesl/src/main/scala/org/bigbluebutton/freeswitch/RxJsonMsgHdlrActor.scala +++ b/akka-bbb-fsesl/src/main/scala/org/bigbluebutton/freeswitch/RxJsonMsgHdlrActor.scala @@ -1,12 +1,16 @@ package org.bigbluebutton.freeswitch -import akka.actor.{ Actor, ActorLogging, Props } -import com.fasterxml.jackson.databind.JsonNode import org.bigbluebutton.SystemConfiguration +import org.bigbluebutton.common2.bus.ReceivedJsonMessage import org.bigbluebutton.common2.msgs._ -import org.bigbluebutton.freeswitch.bus.ReceivedJsonMsg import org.bigbluebutton.freeswitch.voice.freeswitch.FreeswitchApplication +import com.fasterxml.jackson.databind.JsonNode + +import akka.actor.Actor +import akka.actor.ActorLogging +import akka.actor.Props + object RxJsonMsgHdlrActor { def props(fsApp: FreeswitchApplication): Props = Props(classOf[RxJsonMsgHdlrActor], fsApp) @@ -15,13 +19,13 @@ object RxJsonMsgHdlrActor { class RxJsonMsgHdlrActor(val fsApp: FreeswitchApplication) extends Actor with ActorLogging with SystemConfiguration with RxJsonMsgDeserializer { def receive = { - case msg: ReceivedJsonMsg => + case msg: ReceivedJsonMessage => log.debug("handling {} - {}", msg.channel, msg.data) handleReceivedJsonMessage(msg) case _ => // do nothing } - def handleReceivedJsonMessage(msg: ReceivedJsonMsg): Unit = { + def handleReceivedJsonMessage(msg: ReceivedJsonMessage): Unit = { for { envJsonNode <- JsonDeserializer.toBbbCommonEnvJsNodeMsg(msg.data) } yield handle(envJsonNode.envelope, envJsonNode.core) diff --git a/akka-bbb-fsesl/src/main/scala/org/bigbluebutton/freeswitch/VoiceConferenceService.scala b/akka-bbb-fsesl/src/main/scala/org/bigbluebutton/freeswitch/VoiceConferenceService.scala index f82d08080087570a5b1102f2e972443904c10163..a72302e7f3fdbbd6093f8d7c206668e7fe251aca 100755 --- a/akka-bbb-fsesl/src/main/scala/org/bigbluebutton/freeswitch/VoiceConferenceService.scala +++ b/akka-bbb-fsesl/src/main/scala/org/bigbluebutton/freeswitch/VoiceConferenceService.scala @@ -2,9 +2,9 @@ package org.bigbluebutton.freeswitch import org.bigbluebutton.SystemConfiguration import org.bigbluebutton.freeswitch.voice.IVoiceConferenceService -import org.bigbluebutton.endpoint.redis.RedisPublisher import org.bigbluebutton.common2.msgs._ -import org.bigbluebutton.common2.util.JsonUtil +import org.bigbluebutton.common2.util.JsonUtil +import org.bigbluebutton.common2.redis.RedisPublisher class VoiceConferenceService(sender: RedisPublisher) extends IVoiceConferenceService with SystemConfiguration { diff --git a/akka-bbb-fsesl/src/main/scala/org/bigbluebutton/freeswitch/bus/InJsonMsgBus.scala b/akka-bbb-fsesl/src/main/scala/org/bigbluebutton/freeswitch/bus/InJsonMsgBus.scala deleted file mode 100755 index 6f7bb865ea8c1002d7f6ff16655a3c2eb016568e..0000000000000000000000000000000000000000 --- a/akka-bbb-fsesl/src/main/scala/org/bigbluebutton/freeswitch/bus/InJsonMsgBus.scala +++ /dev/null @@ -1,31 +0,0 @@ -package org.bigbluebutton.freeswitch.bus - -import akka.actor.ActorRef -import akka.event.{ EventBus, LookupClassification } - -case class ReceivedJsonMsg(channel: String, data: String) -case class InJsonMsg(val topic: String, val payload: ReceivedJsonMsg) - -class InsonMsgBus extends EventBus with LookupClassification { - type Event = InJsonMsg - type Classifier = String - type Subscriber = ActorRef - - // is used for extracting the classifier from the incoming events - override protected def classify(event: Event): Classifier = event.topic - - // will be invoked for each event for all subscribers which registered themselves - // for the event’s classifier - override protected def publish(event: Event, subscriber: Subscriber): Unit = { - subscriber ! event.payload - } - - // must define a full order over the subscribers, expressed as expected from - // `java.lang.Comparable.compare` - override protected def compareSubscribers(a: Subscriber, b: Subscriber): Int = - a.compareTo(b) - - // determines the initial size of the index data structure - // used internally (i.e. the expected number of different classifiers) - override protected def mapSize: Int = 128 -} diff --git a/akka-bbb-fsesl/src/universal/conf/application.conf b/akka-bbb-fsesl/src/universal/conf/application.conf new file mode 100755 index 0000000000000000000000000000000000000000..ac1acc3b350de7a5059eccf75d229db145a5a592 --- /dev/null +++ b/akka-bbb-fsesl/src/universal/conf/application.conf @@ -0,0 +1,37 @@ +akka { + actor { + debug { + receive = on + } + } + loggers = ["akka.event.slf4j.Slf4jLogger"] + loglevel = "DEBUG" + stdout-loglevel = "DEBUG" + + redis-subscriber-worker-dispatcher { + mailbox-type = "akka.dispatch.SingleConsumerOnlyUnboundedMailbox" + # Throughput defines the maximum number of messages to be + # processed per actor before the thread jumps to the next actor. + # Set to 1 for as fair as possible. + throughput = 512 + } +} + + +freeswitch { + esl { + host="127.0.0.1" + port=8021 + password="ClueCon" + } + conf { + profile="cdquality" + } +} + +redis { + host="127.0.0.1" + port=6379 + password="" +} + diff --git a/akka-bbb-fsesl/src/universal/conf/logback.xml b/akka-bbb-fsesl/src/universal/conf/logback.xml new file mode 100755 index 0000000000000000000000000000000000000000..e749065dae82aa407ddb622470491f0705eb29a2 --- /dev/null +++ b/akka-bbb-fsesl/src/universal/conf/logback.xml @@ -0,0 +1,30 @@ +<?xml version="1.0" encoding="UTF-8"?> +<configuration> +<appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender"> + <layout class="ch.qos.logback.classic.PatternLayout"> + <Pattern>%d{"yyyy-MM-dd'T'HH:mm:ss.SSSXXX"} %-5level %logger{35} - %msg%n</Pattern> + </layout> +</appender> + + <appender name="FILE" class="ch.qos.logback.core.rolling.RollingFileAppender"> + <File>logs/bbb-fsesl-akka.log</File> + <rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy"> + <FileNamePattern>logs/bbb-fsesl-akka.%d{yyyy-MM-dd}.log</FileNamePattern> + <!-- keep 14 days worth of history --> + <MaxHistory>14</MaxHistory> + </rollingPolicy> + <layout class="ch.qos.logback.classic.PatternLayout"> + <Pattern>%d{"yyyy-MM-dd'T'HH:mm:ss.SSSXXX"} %-5level %logger{35} - %msg%n</Pattern> + </layout> + </appender> + + <logger name="akka" level="INFO" /> + <logger name="org.bigbluebutton" level="DEBUG" /> + <logger name="org.freeswitch.esl" level="WARN" /> + <logger name="io.lettuce" level="INFO" /> + + <root level="DEBUG"> + <appender-ref ref="STDOUT"/> + <appender-ref ref="FILE" /> + </root> +</configuration> diff --git a/bbb-api-demo/src/main/webapp/demo_iframe.jsp b/bbb-api-demo/src/main/webapp/demo_iframe.jsp new file mode 100644 index 0000000000000000000000000000000000000000..55ac7eed7499513ae92d97a555a208d95310aeaf --- /dev/null +++ b/bbb-api-demo/src/main/webapp/demo_iframe.jsp @@ -0,0 +1,243 @@ +<!-- + +BigBlueButton - http://www.bigbluebutton.org + +Copyright (c) 2008-2018 by respective authors (see below). All rights reserved. + +BigBlueButton is free software; you can redistribute it and/or modify it under the +terms of the GNU Lesser General Public License as published by the Free Software +Foundation; either version 3 of the License, or (at your option) any later +version. + +BigBlueButton is distributed in the hope that it will be useful, but WITHOUT ANY +WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A +PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. + +You should have received a copy of the GNU Lesser General Public License along +with BigBlueButton; if not, If not, see <http://www.gnu.org/licenses/>. + +Authors: James Jung + Anton Georgiev + +--> +<%@ page language="java" contentType="text/html; charset=UTF-8" + pageEncoding="UTF-8"%> +<% + request.setCharacterEncoding("UTF-8"); + response.setCharacterEncoding("UTF-8"); +%> +<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd"> +<html> +<head> + <meta http-equiv="Content-Type" content="text/html; charset=UTF-8"> + <title>Join Meeting via HTML5 Client (API)</title> + <style> + #controls { + width:50%; + height:200px; + float:left; + } + #client { + width:100%; + height:700px; + float:left; + } + #client-content { + width:100%; + height:100%; + } + </style> +</head> + +<body> + +<p>You must have the BigBlueButton HTML5 client installed to use this API demo.</p> + +<%@ include file="bbb_api.jsp"%> + +<% +if (request.getParameterMap().isEmpty()) { + // + // Assume we want to create a meeting + // + %> +<%@ include file="demo_header.jsp"%> + +<h2>Join Meeting via HTML5 Client (API)</h2> + +<FORM NAME="form1" METHOD="GET"> +<table cellpadding="5" cellspacing="5" style="width: 400px; "> + <tbody> + <tr> + <td> </td> + <td style="text-align: right; ">Full Name:</td> + <td style="width: 5px; "> </td> + <td style="text-align: left "><input type="text" autofocus required name="username" /></td> + </tr> + + <tr> + <td> </td> + <td style="text-align: right; ">Meeting Name:</td> + <td style="width: 5px; "> </td> + <td style="text-align: left "><input type="text" required name="meetingname" value="Demo Meeting" /></td> + <tr> + + <tr> + <td> </td> + <td style="text-align: right; ">Moderator Role:</td> + <td style="width: 5px; "> </td> + <td style="text-align: left "><input type=checkbox name=isModerator value="true" checked></td> + <tr> + + <tr> + <td> </td> + <td> </td> + <td> </td> + <td><input type="submit" value="Join" /></td> + <tr> + </tbody> +</table> +<INPUT TYPE=hidden NAME=action VALUE="create"> +</FORM> + + +<% +} else if (request.getParameter("action").equals("create")) { + + String username = request.getParameter("username"); + + // set defaults and overwrite them if custom values exist + String meetingname = "Demo Meeting"; + if (request.getParameter("meetingname") != null) { + meetingname = request.getParameter("meetingname"); + } + + Boolean isModerator = new Boolean(false); + Boolean isHTML5 = new Boolean(true); + Boolean isRecorded = new Boolean(true); + if (request.getParameter("isModerator") != null) { + isModerator = Boolean.parseBoolean(request.getParameter("isModerator")); + } + + String joinURL = getJoinURLExtended(username, meetingname, isRecorded.toString(), null, null, null, isHTML5.toString(), isModerator.toString()); + + if (joinURL.startsWith("http://") || joinURL.startsWith("https://")) { +%> + +<script language="javascript" type="text/javascript"> + +const recButton = document.createElement('button'); +recButton.id = 'recButton'; +const muteButton = document.createElement('button'); +muteButton.id = 'muteButton'; + +function getInitialState() { + document.getElementById('client-content').contentWindow.postMessage('c_recording_status', '*'); + document.getElementById('client-content').contentWindow.postMessage('get_audio_joined_status', '*'); +} + +function handleMessage(e) { + switch (e) { + case 'readyToConnect': { + // get initial state + getInitialState(); break; } + case 'recordingStarted': { + recButton.innerHTML = 'Stop Recording'; + break; + } + case 'recordingStopped': { + recButton.innerHTML = 'Start Recording'; + break; + } + case 'selfMuted': { + muteButton.innerHTML = 'Unmute me'; + break; + } + case 'selfUnmuted': { + muteButton.innerHTML = 'Mute me'; + break; + } + case 'notInAudio': { + muteButton.innerHTML = 'Not in audio'; + document.getElementById('muteButton').disabled = true; + break; + } + case 'joinedAudio': { + muteButton.innerHTML = ''; + document.getElementById('muteButton').disabled = false; + document.getElementById('client-content').contentWindow.postMessage('c_mute_status', '*'); + break; + } + default: console.log('neither', { e }); + } +} + +// EventListener(Getting message from iframe) +window.addEventListener('message', function(e) { + handleMessage(e.data.response); +}); + +// Clean up the body node before loading controls and the client +document.body.innerHTML = ''; + +// Node for the Client +const client = document.createElement('div'); +client.setAttribute('id', 'client'); + +const clientContent = document.createElement('iframe'); +clientContent.setAttribute('id', 'client-content'); +clientContent.setAttribute('src','<%=joinURL%>'); + +// // in case your iframe is on a different domain MYDOMAIN.com +// clientContent.setAttribute('src','https://MYDOMAIN.com/demo/demoHTML5.jsp'); + +// to enable microphone or camera use allow your iframe domain explicitly +// clientContent.setAttribute('allow','microphone https://MYDOMAIN.com; camera https://MYDOMAIN.com'); + +client.appendChild(clientContent); + +// Node for the Controls +const controls = document.createElement('div'); +controls.setAttribute('id', 'controls'); +controls.setAttribute('align', 'middle'); +controls.setAttribute('float', 'left'); + +// ****************** Controls *****************************/ +function recToggle(){ + document.getElementById("client-content").contentWindow.postMessage('c_record', '*'); +} + +function muteToggle(){ + document.getElementById("client-content").contentWindow.postMessage('c_mute', '*'); +} + +// Node for the control which controls recording functionality of the html5Client +recButton.setAttribute('onClick', 'recToggle();'); +controls.appendChild(recButton); + +muteButton.setAttribute('onClick', 'muteToggle();'); +controls.appendChild(muteButton); + +// Append the nodes of contents to the body node +document.body.appendChild(controls); +document.body.appendChild(client); + +</script> +<% + } else { +%> + +Error: getJoinURL() failed +<p/> +<%=joinURL %> + +<% + } +} +%> + +<%@ include file="demo_footer.jsp"%> + +</body> +</html> + diff --git a/bbb-apps-common/build.sbt b/bbb-apps-common/build.sbt index 693e0e360cde0a9ce16593b4d235f9a6e0d9eb10..9a31293e2a7201ccbe2e0c514da9c4b7a4ac3c33 100755 --- a/bbb-apps-common/build.sbt +++ b/bbb-apps-common/build.sbt @@ -1,20 +1,23 @@ - -name := "bbb-apps-common" - -organization := "org.bigbluebutton" - -version := "0.0.3-SNAPSHOT" - -scalaVersion := "2.12.6" - -scalacOptions ++= Seq( - "-unchecked", - "-deprecation", - "-Xlint", - "-Ywarn-dead-code", - "-language:_", - "-target:jvm-1.8", - "-encoding", "UTF-8" +import org.bigbluebutton.build._ + +version := "0.0.4-SNAPSHOT" + +val compileSettings = Seq( + organization := "org.bigbluebutton", + + scalacOptions ++= List( + "-unchecked", + "-deprecation", + "-Xlint", + "-Ywarn-dead-code", + "-language:_", + "-target:jvm-1.8", + "-encoding", "UTF-8" + ), + javacOptions ++= List( + "-Xlint:unchecked", + "-Xlint:deprecation" + ) ) // We want to have our jar files in lib_managed dir. @@ -22,57 +25,8 @@ scalacOptions ++= Seq( // into eclipse. retrieveManaged := true -testOptions in Test += Tests.Argument(TestFrameworks.Specs2, "html", "console", "junitxml") - -testOptions in Test += Tests.Argument(TestFrameworks.ScalaTest, "-h", "target/scalatest-reports") - -val akkaVersion = "2.5.14" -val scalaTestV = "2.2.6" - -// https://mvnrepository.com/artifact/org.scala-lang/scala-library -libraryDependencies += "org.scala-lang" % "scala-library" % scalaVersion.value -// https://mvnrepository.com/artifact/org.scala-lang/scala-compiler -libraryDependencies += "org.scala-lang" % "scala-compiler" % scalaVersion.value - -// https://mvnrepository.com/artifact/com.typesafe.akka/akka-actor_2.12 -libraryDependencies += "com.typesafe.akka" % "akka-actor_2.12" % "2.5.1" -// https://mvnrepository.com/artifact/com.typesafe.akka/akka-slf4j_2.12 -libraryDependencies += "com.typesafe.akka" % "akka-slf4j_2.12" % "2.5.1" - -// https://mvnrepository.com/artifact/com.github.etaty/rediscala_2.12 -libraryDependencies += "com.github.etaty" % "rediscala_2.12" % "1.8.0" - -libraryDependencies += "com.softwaremill.quicklens" %% "quicklens" % "1.4.11" - -libraryDependencies += "org.bigbluebutton" % "bbb-common-message_2.12" % "0.0.19-SNAPSHOT" - -libraryDependencies += "com.google.code.gson" % "gson" % "2.8.5" -libraryDependencies += "redis.clients" % "jedis" % "2.9.0" - -// https://mvnrepository.com/artifact/org.apache.commons/commons-lang3 -libraryDependencies += "org.apache.commons" % "commons-lang3" % "3.7" -libraryDependencies += "commons-io" % "commons-io" % "2.4" -libraryDependencies += "org.apache.commons" % "commons-pool2" % "2.6.0" -libraryDependencies += "org.slf4j" % "slf4j-api" % "1.7.23" % "provided" - - -libraryDependencies += "junit" % "junit" % "4.12" % "test" -libraryDependencies += "com.novocode" % "junit-interface" % "0.11" % "test" - -// For generating test reports -libraryDependencies += "org.pegdown" % "pegdown" % "1.6.0" % "test" -// https://mvnrepository.com/artifact/com.typesafe.akka/akka-testkit_2.12 -libraryDependencies += "com.typesafe.akka" % "akka-testkit_2.12" % "2.5.1" % "test" - -// https://mvnrepository.com/artifact/org.scalactic/scalactic_2.12 -libraryDependencies += "org.scalactic" % "scalactic_2.12" % "3.0.3" % "test" - -// https://mvnrepository.com/artifact/org.scalatest/scalatest_2.12 -libraryDependencies += "org.scalatest" % "scalatest_2.12" % "3.0.3" % "test" - -libraryDependencies += "org.mockito" % "mockito-core" % "2.7.22" % "test" - -seq(Revolver.settings: _*) +Seq(Revolver.settings: _*) +lazy val appsCommons = (project in file(".")).settings(name := "bbb-apps-common", libraryDependencies ++= Dependencies.runtime).settings(compileSettings) //----------- // Packaging @@ -133,5 +87,3 @@ pomExtra := ( licenses := Seq("LGPL-3.0" -> url("http://opensource.org/licenses/LGPL-3.0")) homepage := Some(url("http://www.bigbluebutton.org")) - - diff --git a/bbb-apps-common/deploy.sh b/bbb-apps-common/deploy.sh index ccba754eaaf8182e665e94d1210571e23ebfb7b4..f829bc13dc598da480ee4e512a87c9436925a359 100755 --- a/bbb-apps-common/deploy.sh +++ b/bbb-apps-common/deploy.sh @@ -1,2 +1 @@ -sbt clean -sbt publish publishLocal +sbt clean publish publishLocal diff --git a/bbb-apps-common/project/Dependencies.scala b/bbb-apps-common/project/Dependencies.scala new file mode 100644 index 0000000000000000000000000000000000000000..86f51a25ef7c86fa3e7f0a0f0a11c094d9bbf46d --- /dev/null +++ b/bbb-apps-common/project/Dependencies.scala @@ -0,0 +1,57 @@ +package org.bigbluebutton.build + +import sbt._ +import Keys._ + +object Dependencies { + + object Versions { + // Scala + val scala = "2.12.7" + + // Libraries + val akkaVersion = "2.5.17" + val gson = "2.8.5" + val sl4j = "1.7.25" + val quicklens = "1.4.11" + + // Apache Commons + val lang = "3.8.1" + val io = "2.6" + val pool = "2.6.0" + + // BigBlueButton + val bbbCommons = "0.0.20-SNAPSHOT" + } + + object Compile { + val scalaLibrary = "org.scala-lang" % "scala-library" % Versions.scala + val scalaCompiler = "org.scala-lang" % "scala-compiler" % Versions.scala + + val akkaActor = "com.typesafe.akka" % "akka-actor_2.12" % Versions.akkaVersion + val akkaSl4fj = "com.typesafe.akka" % "akka-slf4j_2.12" % Versions.akkaVersion + + val googleGson = "com.google.code.gson" % "gson" % Versions.gson + val quicklens = "com.softwaremill.quicklens" %% "quicklens" % Versions.quicklens + val sl4jApi = "org.slf4j" % "slf4j-api" % Versions.sl4j % "provided" + + val apacheLang = "org.apache.commons" % "commons-lang3" % Versions.lang + val apacheIo = "commons-io" % "commons-io" % Versions.io + val apachePool2 = "org.apache.commons" % "commons-pool2" % Versions.pool + + val bbbCommons = "org.bigbluebutton" % "bbb-common-message_2.12" % Versions.bbbCommons + } + + val runtime = Seq( + Compile.scalaLibrary, + Compile.scalaCompiler, + Compile.akkaActor, + Compile.akkaSl4fj, + Compile.googleGson, + Compile.quicklens, + Compile.sl4jApi, + Compile.apacheLang, + Compile.apacheIo, + Compile.apachePool2, + Compile.bbbCommons) +} diff --git a/bbb-apps-common/project/build.properties b/bbb-apps-common/project/build.properties index a6e117b61042ee81c62ba3a0fc5210d9502944df..2e6e3d24608ee15e892ed3b16d84224f7667e808 100755 --- a/bbb-apps-common/project/build.properties +++ b/bbb-apps-common/project/build.properties @@ -1 +1 @@ -sbt.version=0.13.8 +sbt.version=1.2.6 \ No newline at end of file diff --git a/bbb-apps-common/project/plugins.sbt b/bbb-apps-common/project/plugins.sbt index 1a99bbcaf45749dbd833d7f6141744e2062bb181..3559bf68d62ef19f25fa810533bbe596eb022d02 100755 --- a/bbb-apps-common/project/plugins.sbt +++ b/bbb-apps-common/project/plugins.sbt @@ -1,9 +1,9 @@ -addSbtPlugin("io.spray" % "sbt-revolver" % "0.7.2") +addSbtPlugin("io.spray" % "sbt-revolver" % "0.9.1") -addSbtPlugin("com.typesafe.sbteclipse" % "sbteclipse-plugin" % "2.2.0") +addSbtPlugin("com.typesafe.sbteclipse" % "sbteclipse-plugin" % "5.2.4") -addSbtPlugin("com.jsuereth" % "sbt-pgp" % "1.0.0") +addSbtPlugin("com.jsuereth" % "sbt-pgp" % "1.1.1") -addSbtPlugin("net.vonbuchholtz" % "sbt-dependency-check" % "0.2.7") +addSbtPlugin("net.vonbuchholtz" % "sbt-dependency-check" % "0.2.8") addSbtPlugin("org.scalastyle" %% "scalastyle-sbt-plugin" % "1.0.0") diff --git a/bbb-apps-common/src/main/scala/org/bigbluebutton/client/ClientGWApplication.scala b/bbb-apps-common/src/main/scala/org/bigbluebutton/client/ClientGWApplication.scala index 9f0bf267da240b1da0d28ae1f8231cfed4f00891..bad84e615018bd9a435aad7f8f63a3f95855270d 100644 --- a/bbb-apps-common/src/main/scala/org/bigbluebutton/client/ClientGWApplication.scala +++ b/bbb-apps-common/src/main/scala/org/bigbluebutton/client/ClientGWApplication.scala @@ -3,12 +3,16 @@ package org.bigbluebutton.client import akka.actor.ActorSystem import akka.event.Logging import org.bigbluebutton.client.bus._ -import org.bigbluebutton.client.endpoint.redis.{AppsRedisSubscriberActor, MessageSender, RedisPublisher} +import org.bigbluebutton.client.endpoint.redis.Red5AppsRedisSubscriberActor import org.bigbluebutton.client.meeting.MeetingManagerActor +import org.bigbluebutton.common2.redis.RedisPublisher import scala.concurrent.duration._ +import org.bigbluebutton.common2.redis.MessageSender +import org.bigbluebutton.api2.bus.MsgFromAkkaAppsEventBus +import org.bigbluebutton.common2.bus.JsonMsgFromAkkaAppsBus -class ClientGWApplication(val msgToClientGW: MsgToClientGW) extends SystemConfiguration{ +class ClientGWApplication(val msgToClientGW: MsgToClientGW) extends SystemConfiguration { implicit val system = ActorSystem("bbb-apps-common") implicit val timeout = akka.util.Timeout(3 seconds) @@ -20,7 +24,7 @@ class ClientGWApplication(val msgToClientGW: MsgToClientGW) extends SystemConfig private val msgToRedisEventBus = new MsgToRedisEventBus private val msgToClientEventBus = new MsgToClientEventBus - private val redisPublisher = new RedisPublisher(system) + private val redisPublisher = new RedisPublisher(system, "Red5AppsPub") private val msgSender: MessageSender = new MessageSender(redisPublisher) private val meetingManagerActorRef = system.actorOf( @@ -41,19 +45,17 @@ class ClientGWApplication(val msgToClientGW: MsgToClientGW) extends SystemConfig msgToClientEventBus.subscribe(msgToClientJsonActor, toClientChannel) - private val appsRedisSubscriberActor = system.actorOf( - AppsRedisSubscriberActor.props(receivedJsonMsgBus), "appsRedisSubscriberActor") + private val appsRedisSubscriberActor = system.actorOf(Red5AppsRedisSubscriberActor.props(system, receivedJsonMsgBus), "appsRedisSubscriberActor") private val receivedJsonMsgHdlrActor = system.actorOf( ReceivedJsonMsgHdlrActor.props(msgFromAkkaAppsEventBus), "receivedJsonMsgHdlrActor") receivedJsonMsgBus.subscribe(receivedJsonMsgHdlrActor, fromAkkaAppsJsonChannel) - /** - * - * External Interface for Gateway - */ + * + * External Interface for Gateway + */ def connect(connInfo: ConnInfo): Unit = { //log.debug("**** ClientGWApplication connect " + connInfo) diff --git a/bbb-apps-common/src/main/scala/org/bigbluebutton/client/MsgToRedisActor.scala b/bbb-apps-common/src/main/scala/org/bigbluebutton/client/MsgToRedisActor.scala index 94b37ae92489caf176caa2a5b3191dbaeb4d3fd4..5a3ce496ff2c1827b741c8106adf66693af2f2c1 100644 --- a/bbb-apps-common/src/main/scala/org/bigbluebutton/client/MsgToRedisActor.scala +++ b/bbb-apps-common/src/main/scala/org/bigbluebutton/client/MsgToRedisActor.scala @@ -1,10 +1,10 @@ package org.bigbluebutton.client -import akka.actor.{Actor, ActorLogging, Props} +import akka.actor.{ Actor, ActorLogging, Props } import org.bigbluebutton.common2.msgs.BbbCommonEnvJsNodeMsg -import org.bigbluebutton.common2.util.JsonUtil -import org.bigbluebutton.client.endpoint.redis.MessageSender +import org.bigbluebutton.common2.util.JsonUtil import org.bigbluebutton.common2.msgs.LookUpUserReqMsg +import org.bigbluebutton.common2.redis.MessageSender object MsgToRedisActor { def props(msgSender: MessageSender): Props = @@ -20,11 +20,10 @@ class MsgToRedisActor(msgSender: MessageSender) def handle(msg: BbbCommonEnvJsNodeMsg): Unit = { val json = JsonUtil.toJson(msg) - + msg.envelope.name match { case LookUpUserReqMsg.NAME => msgSender.send(toThirdPartyRedisChannel, json) - case _ => msgSender.send(toAkkaAppsRedisChannel, json) + case _ => msgSender.send(toAkkaAppsRedisChannel, json) } } - } diff --git a/bbb-apps-common/src/main/scala/org/bigbluebutton/client/ReceivedJsonMsgHdlrActor.scala b/bbb-apps-common/src/main/scala/org/bigbluebutton/client/ReceivedJsonMsgHdlrActor.scala index d18cb333ed7e2cbd1066db5c11e61bf3e50160e8..ea6c3a3b660418de17a5c7969212f142d6f9e95e 100755 --- a/bbb-apps-common/src/main/scala/org/bigbluebutton/client/ReceivedJsonMsgHdlrActor.scala +++ b/bbb-apps-common/src/main/scala/org/bigbluebutton/client/ReceivedJsonMsgHdlrActor.scala @@ -1,11 +1,13 @@ package org.bigbluebutton.client import akka.actor.{Actor, ActorLogging, Props} -import org.bigbluebutton.client.bus.{JsonMsgFromAkkaApps, MsgFromAkkaApps, MsgFromAkkaAppsEventBus} import org.bigbluebutton.common2.msgs.BbbCommonEnvJsNodeMsg import org.bigbluebutton.common2.util.JsonUtil -import scala.util.{Failure, Success} +import scala.util.{Failure, Success} +import org.bigbluebutton.common2.bus.JsonMsgFromAkkaApps +import org.bigbluebutton.api2.bus.MsgFromAkkaAppsEventBus +import org.bigbluebutton.api2.bus.MsgFromAkkaApps object ReceivedJsonMsgHdlrActor { diff --git a/bbb-apps-common/src/main/scala/org/bigbluebutton/client/ReceivedMessageRouter.scala b/bbb-apps-common/src/main/scala/org/bigbluebutton/client/ReceivedMessageRouter.scala index 701be0b1654e89eb0a0d7b7cad51305b7b72be7d..232720efbf4c61140f6ec0d5ddfe963e189cfd27 100755 --- a/bbb-apps-common/src/main/scala/org/bigbluebutton/client/ReceivedMessageRouter.scala +++ b/bbb-apps-common/src/main/scala/org/bigbluebutton/client/ReceivedMessageRouter.scala @@ -1,6 +1,6 @@ -package org.bigbluebutton.client - -import org.bigbluebutton.client.bus.{MsgFromAkkaApps, MsgFromAkkaAppsEventBus} +package org.bigbluebutton.client + +import org.bigbluebutton.api2.bus.{ MsgFromAkkaApps, MsgFromAkkaAppsEventBus } trait ReceivedMessageRouter { val msgFromAkkaAppsEventBus: MsgFromAkkaAppsEventBus diff --git a/bbb-apps-common/src/main/scala/org/bigbluebutton/client/SystemConfiguration.scala b/bbb-apps-common/src/main/scala/org/bigbluebutton/client/SystemConfiguration.scala index 26e7528bbbb99e0b0424c9ca0e269c0cde477089..0f9d34cfadcaa854d871330a1434928744281ebd 100644 --- a/bbb-apps-common/src/main/scala/org/bigbluebutton/client/SystemConfiguration.scala +++ b/bbb-apps-common/src/main/scala/org/bigbluebutton/client/SystemConfiguration.scala @@ -1,17 +1,10 @@ package org.bigbluebutton.client import scala.util.Try -import com.typesafe.config.ConfigFactory -trait SystemConfiguration { - val config = ConfigFactory.load() +import org.bigbluebutton.common2.redis.RedisConfiguration - lazy val redisHost = Try(config.getString("redis.host")).getOrElse("127.0.0.1") - lazy val redisPort = Try(config.getInt("redis.port")).getOrElse(6379) - lazy val redisPassword = Try(config.getString("redis.password")).getOrElse("") - - lazy val toAkkaAppsRedisChannel = Try(config.getString("redis.toAkkaAppsRedisChannel")).getOrElse("to-akka-apps-redis-channel") - lazy val fromAkkaAppsRedisChannel = Try(config.getString("redis.fromAkkaAppsRedisChannel")).getOrElse("from-akka-apps-redis-channel") +trait SystemConfiguration extends RedisConfiguration { lazy val toThirdPartyRedisChannel = Try(config.getString("redis.toThirdPartyRedisChannel")).getOrElse("to-third-party-redis-channel") lazy val fromThirdPartyRedisChannel = Try(config.getString("redis.fromThirdPartyRedisChannel")).getOrElse("from-third-party-redis-channel") lazy val fromAkkaAppsChannel = Try(config.getString("eventBus.fromAkkaAppsChannel")).getOrElse("from-akka-apps-channel") @@ -19,8 +12,4 @@ trait SystemConfiguration { lazy val fromClientChannel = Try(config.getString("eventBus.fromClientChannel")).getOrElse("from-client-channel") lazy val toClientChannel = Try(config.getString("eventBus.toClientChannel")).getOrElse("to-client-channel") lazy val fromAkkaAppsJsonChannel = Try(config.getString("eventBus.fromAkkaAppsChannel")).getOrElse("from-akka-apps-json-channel") - - lazy val fromAkkaAppsWbRedisChannel = Try(config.getString("redis.fromAkkaAppsWbRedisChannel")).getOrElse("from-akka-apps-wb-redis-channel") - lazy val fromAkkaAppsChatRedisChannel = Try(config.getString("redis.fromAkkaAppsChatRedisChannel")).getOrElse("from-akka-apps-chat-redis-channel") - lazy val fromAkkaAppsPresRedisChannel = Try(config.getString("redis.fromAkkaAppsPresRedisChannel")).getOrElse("from-akka-apps-pres-redis-channel") } diff --git a/bbb-apps-common/src/main/scala/org/bigbluebutton/client/bus/JsonMsgFromAkkaAppsBus.scala b/bbb-apps-common/src/main/scala/org/bigbluebutton/client/bus/JsonMsgFromAkkaAppsBus.scala deleted file mode 100755 index ad28aa0618eb0ce5298feb8be9383ff2850d4c5c..0000000000000000000000000000000000000000 --- a/bbb-apps-common/src/main/scala/org/bigbluebutton/client/bus/JsonMsgFromAkkaAppsBus.scala +++ /dev/null @@ -1,32 +0,0 @@ -package org.bigbluebutton.client.bus - -import akka.actor.ActorRef -import akka.event.{EventBus, LookupClassification} - -case class JsonMsgFromAkkaApps(name: String, data: String) -case class JsonMsgFromAkkaAppsEvent(val topic: String, val payload: JsonMsgFromAkkaApps) - -class JsonMsgFromAkkaAppsBus extends EventBus with LookupClassification { - type Event = JsonMsgFromAkkaAppsEvent - type Classifier = String - type Subscriber = ActorRef - - // is used for extracting the classifier from the incoming events - override protected def classify(event: Event): Classifier = event.topic - - // will be invoked for each event for all subscribers which registered themselves - // for the event’s classifier - override protected def publish(event: Event, subscriber: Subscriber): Unit = { - subscriber ! event.payload - } - - // must define a full order over the subscribers, expressed as expected from - // `java.lang.Comparable.compare` - override protected def compareSubscribers(a: Subscriber, b: Subscriber): Int = - a.compareTo(b) - - // determines the initial size of the index data structure - // used internally (i.e. the expected number of different classifiers) - override protected def mapSize: Int = 128 - -} diff --git a/bbb-apps-common/src/main/scala/org/bigbluebutton/client/bus/MsgFromAkkaAppsEventBus.scala b/bbb-apps-common/src/main/scala/org/bigbluebutton/client/bus/MsgFromAkkaAppsEventBus.scala deleted file mode 100755 index 20dc5055368e2084c2f8c3dc50e6fcf88f870135..0000000000000000000000000000000000000000 --- a/bbb-apps-common/src/main/scala/org/bigbluebutton/client/bus/MsgFromAkkaAppsEventBus.scala +++ /dev/null @@ -1,31 +0,0 @@ -package org.bigbluebutton.client.bus - -import akka.actor.ActorRef -import akka.event.{EventBus, LookupClassification} -import org.bigbluebutton.common2.msgs.{ BbbCommonEnvJsNodeMsg} - -case class MsgFromAkkaApps(val topic: String, val payload: BbbCommonEnvJsNodeMsg) - -class MsgFromAkkaAppsEventBus extends EventBus with LookupClassification { - type Event = MsgFromAkkaApps - type Classifier = String - type Subscriber = ActorRef - - // is used for extracting the classifier from the incoming events - override protected def classify(event: Event): Classifier = event.topic - - // will be invoked for each event for all subscribers which registered themselves - // for the event’s classifier - override protected def publish(event: Event, subscriber: Subscriber): Unit = { - subscriber ! event.payload - } - - // must define a full order over the subscribers, expressed as expected from - // `java.lang.Comparable.compare` - override protected def compareSubscribers(a: Subscriber, b: Subscriber): Int = - a.compareTo(b) - - // determines the initial size of the index data structure - // used internally (i.e. the expected number of different classifiers) - override protected def mapSize: Int = 128 -} diff --git a/bbb-apps-common/src/main/scala/org/bigbluebutton/client/endpoint/redis/AppsRedisSubscriberActor.scala b/bbb-apps-common/src/main/scala/org/bigbluebutton/client/endpoint/redis/AppsRedisSubscriberActor.scala deleted file mode 100644 index 2d3a30fe97fac898b5d91ae743c4c096d5c67658..0000000000000000000000000000000000000000 --- a/bbb-apps-common/src/main/scala/org/bigbluebutton/client/endpoint/redis/AppsRedisSubscriberActor.scala +++ /dev/null @@ -1,62 +0,0 @@ -package org.bigbluebutton.client.endpoint.redis - -import akka.actor.{ActorLogging, OneForOneStrategy, Props} -import akka.actor.SupervisorStrategy.Resume -import java.io.{PrintWriter, StringWriter} -import java.net.InetSocketAddress - -import redis.actors.RedisSubscriberActor -import redis.api.pubsub.{Message, PMessage} - -import scala.concurrent.duration._ -import org.bigbluebutton.client._ -import org.bigbluebutton.client.bus.{JsonMsgFromAkkaApps, JsonMsgFromAkkaAppsBus, JsonMsgFromAkkaAppsEvent} -import redis.api.servers.ClientSetname - -object AppsRedisSubscriberActor extends SystemConfiguration { - - val channels = Seq(fromAkkaAppsRedisChannel, fromAkkaAppsWbRedisChannel, fromAkkaAppsChatRedisChannel, fromAkkaAppsPresRedisChannel, fromThirdPartyRedisChannel) - val patterns = Seq("bigbluebutton:from-bbb-apps:*") - - def props(jsonMsgBus: JsonMsgFromAkkaAppsBus): Props = - Props(classOf[AppsRedisSubscriberActor], jsonMsgBus, - redisHost, redisPort, - channels, patterns).withDispatcher("akka.rediscala-subscriber-worker-dispatcher") -} - -class AppsRedisSubscriberActor(jsonMsgBus: JsonMsgFromAkkaAppsBus, redisHost: String, - redisPort: Int, - channels: Seq[String] = Nil, patterns: Seq[String] = Nil) - extends RedisSubscriberActor(new InetSocketAddress(redisHost, redisPort), - channels, patterns, onConnectStatus = connected => { println(s"connected: $connected") }) - with SystemConfiguration with ActorLogging { - - override val supervisorStrategy = OneForOneStrategy(maxNrOfRetries = 10, withinTimeRange = 1 minute) { - case e: Exception => { - val sw: StringWriter = new StringWriter() - sw.write("An exception has been thrown on AppsRedisSubscriberActor, exception message [" + e.getMessage() + "] (full stacktrace below)\n") - e.printStackTrace(new PrintWriter(sw)) - log.error(sw.toString()) - Resume - } - } - - - // Set the name of this client to be able to distinguish when doing - // CLIENT LIST on redis-cli - write(ClientSetname("Red5AppsSub").encodedRequest) - - def onMessage(message: Message) { - if (channels.contains(message.channel)) { - //log.debug(s"RECEIVED:\n ${message.data.utf8String} \n") - val receivedJsonMessage = new JsonMsgFromAkkaApps(message.channel, message.data.utf8String) - jsonMsgBus.publish(JsonMsgFromAkkaAppsEvent(fromAkkaAppsJsonChannel, receivedJsonMessage)) - } - - } - - def onPMessage(pmessage: PMessage) { - // We don't use PSubscribe anymore, but an implementation of the method is required - log.error("Should not be receiving a PMessage. It triggered on a match of pattern: " + pmessage.patternMatched) - } -} diff --git a/bbb-apps-common/src/main/scala/org/bigbluebutton/client/endpoint/redis/KeepAliveRedisPublisher.scala b/bbb-apps-common/src/main/scala/org/bigbluebutton/client/endpoint/redis/KeepAliveRedisPublisher.scala deleted file mode 100755 index 131a2d46ceec85cc9d5f870ca32f381c70f797d9..0000000000000000000000000000000000000000 --- a/bbb-apps-common/src/main/scala/org/bigbluebutton/client/endpoint/redis/KeepAliveRedisPublisher.scala +++ /dev/null @@ -1,17 +0,0 @@ -package org.bigbluebutton.client.endpoint.redis - -import scala.concurrent.duration._ -import scala.concurrent.ExecutionContext.Implicits.global -import akka.actor.ActorSystem -import org.bigbluebutton.client.SystemConfiguration -//import org.bigbluebutton.common.messages.BbbAppsIsAliveMessage - -class KeepAliveRedisPublisher(val system: ActorSystem, sender: RedisPublisher) extends SystemConfiguration { - - val startedOn = System.currentTimeMillis() - - system.scheduler.schedule(2 seconds, 5 seconds) { -// val msg = new BbbAppsIsAliveMessage(startedOn, System.currentTimeMillis()) -// sender.publish("bigbluebutton:from-bbb-apps:keepalive", msg.toJson()) - } -} diff --git a/bbb-apps-common/src/main/scala/org/bigbluebutton/client/endpoint/redis/MessageSender.scala b/bbb-apps-common/src/main/scala/org/bigbluebutton/client/endpoint/redis/MessageSender.scala deleted file mode 100755 index 191af624c8c0e67aa8be845784a1ca9942ed3255..0000000000000000000000000000000000000000 --- a/bbb-apps-common/src/main/scala/org/bigbluebutton/client/endpoint/redis/MessageSender.scala +++ /dev/null @@ -1,8 +0,0 @@ -package org.bigbluebutton.client.endpoint.redis - -class MessageSender(publisher: RedisPublisher) { - - def send(channel: String, data: String) { - publisher.publish(channel, data) - } -} diff --git a/bbb-apps-common/src/main/scala/org/bigbluebutton/client/endpoint/redis/Red5AppsRedisSubscriberActor.scala b/bbb-apps-common/src/main/scala/org/bigbluebutton/client/endpoint/redis/Red5AppsRedisSubscriberActor.scala new file mode 100644 index 0000000000000000000000000000000000000000..0281a091f34ae86e23e4494eef8682934775e904 --- /dev/null +++ b/bbb-apps-common/src/main/scala/org/bigbluebutton/client/endpoint/redis/Red5AppsRedisSubscriberActor.scala @@ -0,0 +1,50 @@ +package org.bigbluebutton.client.endpoint.redis + +import org.bigbluebutton.common2.redis.RedisSubscriberProvider +import io.lettuce.core.pubsub.RedisPubSubListener +import org.bigbluebutton.common2.bus.JsonMsgFromAkkaApps +import org.bigbluebutton.common2.redis.RedisConfiguration +import org.bigbluebutton.client.SystemConfiguration +import akka.actor.ActorSystem +import org.bigbluebutton.common2.redis.RedisSubscriber +import org.bigbluebutton.common2.bus.JsonMsgFromAkkaAppsBus +import akka.actor.Props +import org.bigbluebutton.common2.bus.JsonMsgFromAkkaAppsEvent + +object Red5AppsRedisSubscriberActor extends RedisSubscriber with RedisConfiguration with SystemConfiguration { + + val channels = Seq(fromAkkaAppsRedisChannel, fromAkkaAppsWbRedisChannel, fromAkkaAppsChatRedisChannel, fromAkkaAppsPresRedisChannel, fromThirdPartyRedisChannel) + val patterns = Seq("bigbluebutton:from-bbb-apps:*") + + def props(system: ActorSystem, jsonMsgBus: JsonMsgFromAkkaAppsBus): Props = + Props( + classOf[Red5AppsRedisSubscriberActor], + system, jsonMsgBus, + redisHost, redisPort, + channels, patterns).withDispatcher("akka.redis-subscriber-worker-dispatcher") +} + +class Red5AppsRedisSubscriberActor(system: ActorSystem, jsonMsgBus: JsonMsgFromAkkaAppsBus, + redisHost: String, redisPort: Int, + channels: Seq[String] = Nil, patterns: Seq[String] = Nil) + extends RedisSubscriberProvider(system, "Red5AppsSub", channels, patterns, null) with SystemConfiguration { + + override def addListener(appChannel: String) { + connection.addListener(new RedisPubSubListener[String, String] { + def message(channel: String, message: String): Unit = { + if (channels.contains(channel)) { + val receivedJsonMessage = new JsonMsgFromAkkaApps(channel, message) + jsonMsgBus.publish(JsonMsgFromAkkaAppsEvent(fromAkkaAppsJsonChannel, receivedJsonMessage)) + } + } + def message(pattern: String, channel: String, message: String): Unit = { log.info("Subscribed to channel {} with pattern {}", channel, pattern) } + def psubscribed(pattern: String, count: Long): Unit = { log.info("Subscribed to pattern {}", pattern) } + def punsubscribed(pattern: String, count: Long): Unit = { log.info("Unsubscribed from pattern {}", pattern) } + def subscribed(channel: String, count: Long): Unit = { log.info("Subscribed to pattern {}", channel) } + def unsubscribed(channel: String, count: Long): Unit = { log.info("Unsubscribed from channel {}", channel) } + }) + } + + addListener(null) + subscribe() +} diff --git a/bbb-apps-common/src/main/scala/org/bigbluebutton/client/endpoint/redis/RedisPublisher.scala b/bbb-apps-common/src/main/scala/org/bigbluebutton/client/endpoint/redis/RedisPublisher.scala deleted file mode 100755 index 379b15f5dec53f025e454613d7c8b2a08bdcf042..0000000000000000000000000000000000000000 --- a/bbb-apps-common/src/main/scala/org/bigbluebutton/client/endpoint/redis/RedisPublisher.scala +++ /dev/null @@ -1,24 +0,0 @@ -package org.bigbluebutton.client.endpoint.redis - -import redis.RedisClient -import akka.actor.ActorSystem -import akka.event.Logging -import org.bigbluebutton.client.SystemConfiguration -import akka.util.ByteString - -class RedisPublisher(val system: ActorSystem) extends SystemConfiguration { - - val redis = RedisClient(redisHost, redisPort)(system) - - val log = Logging(system, getClass) - - // Set the name of this client to be able to distinguish when doing - // CLIENT LIST on redis-cli - redis.clientSetname("Red5AppsPub") - - def publish(channel: String, data: String) { - //log.debug("PUBLISH TO [" + channel + "]: \n [" + data + "]") - redis.publish(channel, ByteString(data)) - } - -} diff --git a/bbb-common-message/build.sbt b/bbb-common-message/build.sbt index 29cf68bec63494dc7c7a58eb54b78c7748c6a525..054aa4acd34f7ae83ac5ea420818bfcf71ade0fd 100755 --- a/bbb-common-message/build.sbt +++ b/bbb-common-message/build.sbt @@ -1,19 +1,23 @@ -name := "bbb-common-message" - -organization := "org.bigbluebutton" - -version := "0.0.19-SNAPSHOT" - -scalaVersion := "2.12.6" - -scalacOptions ++= Seq( - "-unchecked", - "-deprecation", - "-Xlint", - "-Ywarn-dead-code", - "-language:_", - "-target:jvm-1.8", - "-encoding", "UTF-8" +import org.bigbluebutton.build._ + +version := "0.0.20-SNAPSHOT" + +val compileSettings = Seq( + organization := "org.bigbluebutton", + + scalacOptions ++= List( + "-unchecked", + "-deprecation", + "-Xlint", + "-Ywarn-dead-code", + "-language:_", + "-target:jvm-1.8", + "-encoding", "UTF-8" + ), + javacOptions ++= List( + "-Xlint:unchecked", + "-Xlint:deprecation" + ) ) resolvers += Resolver.sonatypeRepo("releases") @@ -26,32 +30,8 @@ retrieveManaged := true testOptions in Test += Tests.Argument(TestFrameworks.Specs2, "html", "console", "junitxml") testOptions in Test += Tests.Argument(TestFrameworks.ScalaTest, "-h", "target/scalatest-reports") -libraryDependencies ++= { - Seq( - "com.google.code.gson" % "gson" % "2.5" - )} - -// https://mvnrepository.com/artifact/org.scala-lang/scala-library -libraryDependencies += "org.scala-lang" % "scala-library" % scalaVersion.value -// https://mvnrepository.com/artifact/org.scala-lang/scala-compiler -libraryDependencies += "org.scala-lang" % "scala-compiler" % scalaVersion.value - -libraryDependencies += "junit" % "junit" % "4.12" % "test" -libraryDependencies += "com.novocode" % "junit-interface" % "0.11" % "test" - -// https://mvnrepository.com/artifact/org.scalactic/scalactic_2.12 -libraryDependencies += "org.scalactic" % "scalactic_2.12" % "3.0.3" % "test" - -// For generating test reports -libraryDependencies += "org.pegdown" % "pegdown" % "1.6.0" % "test" - -// https://mvnrepository.com/artifact/org.scalatest/scalatest_2.12 -libraryDependencies += "org.scalatest" % "scalatest_2.12" % "3.0.3" % "test" - -// https://mvnrepository.com/artifact/com.fasterxml.jackson.module/jackson-module-scala_2.12 -libraryDependencies += "com.fasterxml.jackson.module" % "jackson-module-scala_2.12" % "2.9.6" - -seq(Revolver.settings: _*) +Seq(Revolver.settings: _*) +lazy val commonMessage = (project in file(".")).settings(name := "bbb-common-message", libraryDependencies ++= Dependencies.runtime).settings(compileSettings) //----------- // Packaging @@ -71,12 +51,12 @@ seq(Revolver.settings: _*) // This forbids including Scala related libraries into the dependency //autoScalaLibrary := false -/*************************** -* When developing, change the version above to x.x.x-SNAPSHOT then use the file resolver to -* publish to the local maven repo using "sbt publish" -*/ +/** ************************* + * When developing, change the version above to x.x.x-SNAPSHOT then use the file resolver to + * publish to the local maven repo using "sbt publish" + */ // Uncomment this to publish to local maven repo while commenting out the nexus repo -publishTo := Some(Resolver.file("file", new File(Path.userHome.absolutePath+"/.m2/repository"))) +publishTo := Some(Resolver.file("file", new File(Path.userHome.absolutePath + "/.m2/repository"))) // Comment this out when publishing to local maven repo using SNAPSHOT version. @@ -101,15 +81,14 @@ pomExtra := ( <url>git@github.com:bigbluebutton/bigbluebutton.git</url> <connection>scm:git:git@github.com:bigbluebutton/bigbluebutton.git</connection> </scm> - <developers> - <developer> - <id>ritzalam</id> - <name>Richard Alam</name> - <url>http://www.bigbluebutton.org</url> - </developer> - </developers>) - + <developers> + <developer> + <id>ritzalam</id> + <name>Richard Alam</name> + <url>http://www.bigbluebutton.org</url> + </developer> + </developers>) + licenses := Seq("LGPL-3.0" -> url("http://opensource.org/licenses/LGPL-3.0")) homepage := Some(url("http://www.bigbluebutton.org")) - diff --git a/bbb-common-message/deploy.sh b/bbb-common-message/deploy.sh index ccba754eaaf8182e665e94d1210571e23ebfb7b4..f829bc13dc598da480ee4e512a87c9436925a359 100755 --- a/bbb-common-message/deploy.sh +++ b/bbb-common-message/deploy.sh @@ -1,2 +1 @@ -sbt clean -sbt publish publishLocal +sbt clean publish publishLocal diff --git a/bbb-common-message/project/Build.scala b/bbb-common-message/project/Build.scala deleted file mode 100755 index e69de29bb2d1d6434b8b29ae775ad8c2e48c5391..0000000000000000000000000000000000000000 diff --git a/bbb-common-message/project/Dependencies.scala b/bbb-common-message/project/Dependencies.scala new file mode 100644 index 0000000000000000000000000000000000000000..814cbc71cf62d6d8799f9db91fc582f086c8eb03 --- /dev/null +++ b/bbb-common-message/project/Dependencies.scala @@ -0,0 +1,68 @@ +package org.bigbluebutton.build + +import sbt._ +import Keys._ + +object Dependencies { + + object Versions { + // Scala + val scala = "2.12.7" + val junit = "4.12" + val junitInterface = "0.11" + val scalactic = "3.0.3" + + // Libraries + val akkaVersion = "2.5.17" + val gson = "2.8.5" + val jackson = "2.9.7" + val sl4j = "1.7.25" + val red5 = "1.0.10-M9" + val pool = "2.6.0" + + // Redis + val lettuce = "5.1.3.RELEASE" + + // Test + val scalaTest = "3.0.5" + } + + object Compile { + val scalaLibrary = "org.scala-lang" % "scala-library" % Versions.scala + val scalaCompiler = "org.scala-lang" % "scala-compiler" % Versions.scala + + val akkaActor = "com.typesafe.akka" % "akka-actor_2.12" % Versions.akkaVersion + + val googleGson = "com.google.code.gson" % "gson" % Versions.gson + val jacksonModule = "com.fasterxml.jackson.module" %% "jackson-module-scala" % Versions.jackson + val sl4jApi = "org.slf4j" % "slf4j-api" % Versions.sl4j % "runtime" + val red5 = "org.red5" % "red5-server-common" % Versions.red5 + val apachePool2 = "org.apache.commons" % "commons-pool2" % Versions.pool + + val lettuceCore = "io.lettuce" % "lettuce-core" % Versions.lettuce + } + + object Test { + val scalaTest = "org.scalatest" %% "scalatest" % Versions.scalaTest % "test" + val junit = "junit" % "junit" % Versions.junit % "test" + val junitInteface = "com.novocode" % "junit-interface" % Versions.junitInterface % "test" + val scalactic = "org.scalactic" % "scalactic_2.12" % Versions.scalactic % "test" + } + + val testing = Seq( + Test.scalaTest, + Test.junit, + Test.junitInteface, + Test.scalactic) + + val runtime = Seq( + Compile.scalaLibrary, + Compile.scalaCompiler, + Compile.akkaActor, + Compile.googleGson, + Compile.jacksonModule, + Compile.sl4jApi, + Compile.red5, + Compile.apachePool2, + Compile.lettuceCore) ++ testing +} diff --git a/bbb-common-message/project/build.properties b/bbb-common-message/project/build.properties index a6e117b61042ee81c62ba3a0fc5210d9502944df..2e6e3d24608ee15e892ed3b16d84224f7667e808 100755 --- a/bbb-common-message/project/build.properties +++ b/bbb-common-message/project/build.properties @@ -1 +1 @@ -sbt.version=0.13.8 +sbt.version=1.2.6 \ No newline at end of file diff --git a/bbb-common-message/project/plugins.sbt b/bbb-common-message/project/plugins.sbt index 5ab7b095f69a2b3d8a3bf5350fa7e7e1a64f8f2f..3559bf68d62ef19f25fa810533bbe596eb022d02 100755 --- a/bbb-common-message/project/plugins.sbt +++ b/bbb-common-message/project/plugins.sbt @@ -2,8 +2,8 @@ addSbtPlugin("io.spray" % "sbt-revolver" % "0.9.1") addSbtPlugin("com.typesafe.sbteclipse" % "sbteclipse-plugin" % "5.2.4") -addSbtPlugin("com.jsuereth" % "sbt-pgp" % "1.0.0") +addSbtPlugin("com.jsuereth" % "sbt-pgp" % "1.1.1") -addSbtPlugin("net.vonbuchholtz" % "sbt-dependency-check" % "0.2.7") +addSbtPlugin("net.vonbuchholtz" % "sbt-dependency-check" % "0.2.8") addSbtPlugin("org.scalastyle" %% "scalastyle-sbt-plugin" % "1.0.0") diff --git a/bbb-screenshare/app/src/main/java/org/bigbluebutton/app/screenshare/EventRecordingService.java b/bbb-common-message/src/main/java/org/bigbluebutton/common2/redis/Keys.java old mode 100755 new mode 100644 similarity index 50% rename from bbb-screenshare/app/src/main/java/org/bigbluebutton/app/screenshare/EventRecordingService.java rename to bbb-common-message/src/main/java/org/bigbluebutton/common2/redis/Keys.java index d534387e2e99c3a2a0c0b38bdb7511a69bf0a0d3..96509383d5f8e564b9f480baa862bc2277a6b0c0 --- a/bbb-screenshare/app/src/main/java/org/bigbluebutton/app/screenshare/EventRecordingService.java +++ b/bbb-common-message/src/main/java/org/bigbluebutton/common2/redis/Keys.java @@ -1,7 +1,7 @@ /** * BigBlueButton open source conferencing system - http://www.bigbluebutton.org/ * -* Copyright (c) 2012 BigBlueButton Inc. and by respective authors (see below). +* Copyright (c) 2018 BigBlueButton Inc. and by respective authors (see below). * * This program is free software; you can redistribute it and/or modify it under the * terms of the GNU Lesser General Public License as published by the Free Software @@ -16,28 +16,13 @@ * with BigBlueButton; if not, see <http://www.gnu.org/licenses/>. * */ -package org.bigbluebutton.app.screenshare; +package org.bigbluebutton.common2.redis; -import java.util.Map; - -import redis.clients.jedis.Jedis; - -public class EventRecordingService { - private static final String COLON = ":"; - - private final String host; - private final int port; - - public EventRecordingService(String host, int port) { - this.host = host; - this.port = port; - } - - public void record(String meetingId, Map<String, String> event) { - Jedis jedis = new Jedis(host, port); - Long msgid = jedis.incr("global:nextRecordedMsgId"); - jedis.hmset("recording:" + meetingId + COLON + msgid, event); - jedis.rpush("meeting:" + meetingId + COLON + "recordings", msgid.toString()); - } +public final class Keys { + public static final String MEETING = "meeting-"; + public static final String MEETINGS = "meetings"; + public static final String MEETING_INFO = "meeting:info:"; + public static final String BREAKOUT_MEETING = "meeting:breakout:"; + public static final String BREAKOUT_ROOMS = "meeting:breakout:rooms:"; } diff --git a/bbb-common-message/src/main/java/org/bigbluebutton/common2/redis/RedisAwareCommunicator.java b/bbb-common-message/src/main/java/org/bigbluebutton/common2/redis/RedisAwareCommunicator.java new file mode 100644 index 0000000000000000000000000000000000000000..23dc26d26f04721273a3a8f552001d87dfd0ed8a --- /dev/null +++ b/bbb-common-message/src/main/java/org/bigbluebutton/common2/redis/RedisAwareCommunicator.java @@ -0,0 +1,98 @@ +/** +* BigBlueButton open source conferencing system - http://www.bigbluebutton.org/ +* +* Copyright (c) 2018 BigBlueButton Inc. and by respective authors (see below). +* +* This program is free software; you can redistribute it and/or modify it under the +* terms of the GNU Lesser General Public License as published by the Free Software +* Foundation; either version 3.0 of the License, or (at your option) any later +* version. +* +* BigBlueButton is distributed in the hope that it will be useful, but WITHOUT ANY +* WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A +* PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. +* +* You should have received a copy of the GNU Lesser General Public License along +* with BigBlueButton; if not, see <http://www.gnu.org/licenses/>. +* +*/ + +package org.bigbluebutton.common2.redis; + +import org.apache.commons.pool2.impl.GenericObjectPoolConfig; +import org.slf4j.Logger; + +import io.lettuce.core.RedisClient; +import io.lettuce.core.event.Event; +import io.lettuce.core.event.EventBus; +import io.lettuce.core.event.connection.ConnectedEvent; +import io.lettuce.core.event.connection.ConnectionActivatedEvent; +import io.lettuce.core.event.connection.ConnectionDeactivatedEvent; +import io.lettuce.core.event.connection.DisconnectedEvent; +import reactor.core.Disposable; + +public abstract class RedisAwareCommunicator { + + protected RedisClient redisClient; + + protected Disposable eventBusSubscription; + + protected EventBus eventBus; + + protected String host; + protected String password; + protected int port; + protected String clientName; + protected int expireKey; + + public abstract void start(); + + public abstract void stop(); + + public void setPassword(String password) { + this.password = password; + } + + protected void connectionStatusHandler(Event event, Logger log) { + if (event instanceof ConnectedEvent) { + log.info("Connected to redis"); + } else if (event instanceof ConnectionActivatedEvent) { + log.info("Connected to redis activated"); + } else if (event instanceof DisconnectedEvent) { + log.info("Disconnected from redis"); + } else if (event instanceof ConnectionDeactivatedEvent) { + log.info("Connected to redis deactivated"); + } + } + + public void setClientName(String clientName) { + this.clientName = clientName; + } + + public void setHost(String host) { + this.host = host; + } + + public void setPort(int port) { + this.port = port; + } + + public void setExpireKey(int expireKey) { + this.expireKey = expireKey; + } + + protected GenericObjectPoolConfig createPoolingConfig() { + GenericObjectPoolConfig config = new GenericObjectPoolConfig(); + config.setMaxTotal(32); + config.setMaxIdle(8); + config.setMinIdle(1); + config.setTestOnBorrow(true); + config.setTestOnReturn(true); + config.setTestWhileIdle(true); + config.setNumTestsPerEvictionRun(12); + config.setMaxWaitMillis(5000); + config.setTimeBetweenEvictionRunsMillis(60000); + config.setBlockWhenExhausted(true); + return config; + } +} diff --git a/bbb-common-message/src/main/java/org/bigbluebutton/common2/redis/RedisStorageService.java b/bbb-common-message/src/main/java/org/bigbluebutton/common2/redis/RedisStorageService.java new file mode 100644 index 0000000000000000000000000000000000000000..19f844bc159ef7979c4dbcf1b9f546d2067adb0e --- /dev/null +++ b/bbb-common-message/src/main/java/org/bigbluebutton/common2/redis/RedisStorageService.java @@ -0,0 +1,116 @@ +/** +* BigBlueButton open source conferencing system - http://www.bigbluebutton.org/ +* +* Copyright (c) 2018 BigBlueButton Inc. and by respective authors (see below). +* +* This program is free software; you can redistribute it and/or modify it under the +* terms of the GNU Lesser General Public License as published by the Free Software +* Foundation; either version 3.0 of the License, or (at your option) any later +* version. +* +* BigBlueButton is distributed in the hope that it will be useful, but WITHOUT ANY +* WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A +* PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. +* +* You should have received a copy of the GNU Lesser General Public License along +* with BigBlueButton; if not, see <http://www.gnu.org/licenses/>. +* +*/ + +package org.bigbluebutton.common2.redis; + +import java.util.Map; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import io.lettuce.core.ClientOptions; +import io.lettuce.core.RedisClient; +import io.lettuce.core.RedisURI; +import io.lettuce.core.api.StatefulRedisConnection; +import io.lettuce.core.api.sync.RedisCommands; + +public class RedisStorageService extends RedisAwareCommunicator { + + private static Logger log = LoggerFactory.getLogger(RedisStorageService.class); + + StatefulRedisConnection<String, String> connection; + + public void start() { + log.info("Starting RedisStorageService with client name: {}", clientName); + RedisURI redisUri = RedisURI.Builder.redis(this.host, this.port).withClientName(this.clientName) + .withPassword(this.password).build(); + + redisClient = RedisClient.create(redisUri); + redisClient.setOptions(ClientOptions.builder().autoReconnect(true).build()); + eventBus = redisClient.getResources().eventBus(); + eventBusSubscription = eventBus.get().subscribe(e -> connectionStatusHandler(e, log)); + + connection = redisClient.connect(); + } + + public void stop() { + eventBusSubscription.dispose(); + connection.close(); + redisClient.shutdown(); + log.info("RedisStorageService Stopped"); + } + + public void recordMeetingInfo(String meetingId, Map<String, String> info) { + log.debug("Storing meeting {} metadata {}", meetingId, info); + recordMeeting(Keys.MEETING_INFO + meetingId, info); + } + + public void recordBreakoutInfo(String meetingId, Map<String, String> breakoutInfo) { + log.debug("Saving breakout metadata in {}", meetingId); + recordMeeting(Keys.BREAKOUT_MEETING + meetingId, breakoutInfo); + } + + public void addBreakoutRoom(String parentId, String breakoutId) { + log.debug("Saving breakout room for meeting {}", parentId); + RedisCommands<String, String> commands = connection.sync(); + commands.sadd(Keys.BREAKOUT_ROOMS + parentId, breakoutId); + } + + public void record(String meetingId, Map<String, String> event) { + log.debug("Recording meeting event {} inside a transaction", meetingId); + RedisCommands<String, String> commands = connection.sync(); + Long msgid = commands.incr("global:nextRecordedMsgId"); + commands.hmset("recording:" + meetingId + ":" + msgid, event); + commands.rpush("meeting:" + meetingId + ":" + "recordings", Long.toString(msgid)); + } + + // @fixme: not used anywhere + public void removeMeeting(String meetingId) { + log.debug("Removing meeting meeting {} inside a transaction", meetingId); + RedisCommands<String, String> commands = connection.sync(); + commands.del(Keys.MEETING + meetingId); + commands.srem(Keys.MEETINGS + meetingId); + } + + public void recordAndExpire(String meetingId, Map<String, String> event) { + log.debug("Recording meeting event {} inside a transaction", meetingId); + RedisCommands<String, String> commands = connection.sync(); + + Long msgid = commands.incr("global:nextRecordedMsgId"); + String key = "recording:" + meetingId + ":" + msgid; + commands.hmset(key, event); + /** + * We set the key to expire after 14 days as we are still recording the + * event into redis even if the meeting is not recorded. (ralam sept 23, + * 2015) + */ + commands.expire(key, expireKey); + key = "meeting:" + meetingId + ":recordings"; + commands.rpush(key, Long.toString(msgid)); + commands.expire(key, expireKey); + } + + private String recordMeeting(String key, Map<String, String> info) { + log.debug("Storing metadata {}", info); + String result = ""; + RedisCommands<String, String> commands = connection.sync(); + result = commands.hmset(key, info); + return result; + } +} \ No newline at end of file diff --git a/bbb-video/src/main/java/org/bigbluebutton/red5/pubsub/MessageDistributor.java b/bbb-common-message/src/main/java/org/bigbluebutton/common2/redis/pubsub/MessageDistributor.java similarity index 89% rename from bbb-video/src/main/java/org/bigbluebutton/red5/pubsub/MessageDistributor.java rename to bbb-common-message/src/main/java/org/bigbluebutton/common2/redis/pubsub/MessageDistributor.java index b51ed206c8f6ea6b5da5e3681709dbfe103f6f3b..89c8e9e46587090caa945a3a28fd8cb8529add65 100755 --- a/bbb-video/src/main/java/org/bigbluebutton/red5/pubsub/MessageDistributor.java +++ b/bbb-common-message/src/main/java/org/bigbluebutton/common2/redis/pubsub/MessageDistributor.java @@ -1,4 +1,4 @@ -package org.bigbluebutton.red5.pubsub; +package org.bigbluebutton.common2.redis.pubsub; import java.util.Set; @@ -22,4 +22,4 @@ public class MessageDistributor { listener.handleMessage(pattern, channel, message); } } -} \ No newline at end of file +} diff --git a/bbb-voice/src/main/java/org/bigbluebutton/voiceconf/messaging/MessageHandler.java b/bbb-common-message/src/main/java/org/bigbluebutton/common2/redis/pubsub/MessageHandler.java similarity index 85% rename from bbb-voice/src/main/java/org/bigbluebutton/voiceconf/messaging/MessageHandler.java rename to bbb-common-message/src/main/java/org/bigbluebutton/common2/redis/pubsub/MessageHandler.java index b94db88559d8e7d351690cd88b72ad4ba228099a..95bbde9e690becf55776d73e61a8ddd548e32365 100755 --- a/bbb-voice/src/main/java/org/bigbluebutton/voiceconf/messaging/MessageHandler.java +++ b/bbb-common-message/src/main/java/org/bigbluebutton/common2/redis/pubsub/MessageHandler.java @@ -16,8 +16,8 @@ * with BigBlueButton; if not, see <http://www.gnu.org/licenses/>. * */ -package org.bigbluebutton.voiceconf.messaging; +package org.bigbluebutton.common2.redis.pubsub; public interface MessageHandler { - void handleMessage(String pattern, String channel, String message); + void handleMessage(String pattern, String channel, String message); } diff --git a/bbb-common-message/src/main/java/org/bigbluebutton/common2/redis/pubsub/MessageReceiver.java b/bbb-common-message/src/main/java/org/bigbluebutton/common2/redis/pubsub/MessageReceiver.java new file mode 100755 index 0000000000000000000000000000000000000000..ade4b5d648f11a4da9ba29adbb72de43d921edce --- /dev/null +++ b/bbb-common-message/src/main/java/org/bigbluebutton/common2/redis/pubsub/MessageReceiver.java @@ -0,0 +1,121 @@ +package org.bigbluebutton.common2.redis.pubsub; + +import java.util.concurrent.Executor; +import java.util.concurrent.Executors; + +import org.apache.commons.pool2.impl.GenericObjectPool; +import org.bigbluebutton.common2.redis.RedisAwareCommunicator; +import org.red5.logging.Red5LoggerFactory; +import org.slf4j.Logger; + +import io.lettuce.core.ClientOptions; +import io.lettuce.core.RedisClient; +import io.lettuce.core.RedisFuture; +import io.lettuce.core.RedisURI; +import io.lettuce.core.pubsub.RedisPubSubListener; +import io.lettuce.core.pubsub.StatefulRedisPubSubConnection; +import io.lettuce.core.pubsub.api.async.RedisPubSubAsyncCommands; +import io.lettuce.core.support.ConnectionPoolSupport; + +public class MessageReceiver extends RedisAwareCommunicator { + private static Logger log = Red5LoggerFactory.getLogger(MessageReceiver.class, "video"); + + private ReceivedMessageHandler handler; + + GenericObjectPool<StatefulRedisPubSubConnection<String, String>> connectionPool; + + private final Executor runExec = Executors.newSingleThreadExecutor(); + + private volatile boolean receiveMessage = false; + + private final String FROM_BBB_APPS_PATTERN = "from-akka-apps-redis-channel"; + + public void start() { + log.info("Ready to receive messages from Redis pubsub."); + receiveMessage = true; + + RedisURI redisUri = RedisURI.Builder.redis(this.host, this.port).withClientName(this.clientName).build(); + if (!this.password.isEmpty()) { + redisUri.setPassword(this.password); + } + + redisClient = RedisClient.create(redisUri); + redisClient.setOptions(ClientOptions.builder().autoReconnect(true).build()); + eventBus = redisClient.getResources().eventBus(); + eventBusSubscription = eventBus.get().subscribe(e -> connectionStatusHandler(e, log)); + + connectionPool = ConnectionPoolSupport.createGenericObjectPool(() -> redisClient.connectPubSub(), + createPoolingConfig()); + + Runnable messageReceiver = new Runnable() { + public void run() { + if (receiveMessage) { + try (StatefulRedisPubSubConnection<String, String> connection = connectionPool.borrowObject()) { + if (receiveMessage) { + connection.addListener(new MessageListener()); + + RedisPubSubAsyncCommands<String, String> async = connection.async(); + RedisFuture<Void> future = async.subscribe(FROM_BBB_APPS_PATTERN); + } + } catch (Exception e) { + log.error("Error resubscribing to channels: ", e); + } + } + } + }; + + runExec.execute(messageReceiver); + } + + public void stop() { + receiveMessage = false; + connectionPool.close(); + redisClient.shutdown(); + log.info("MessageReceiver Stopped"); + } + + public void setMessageHandler(ReceivedMessageHandler handler) { + this.handler = handler; + } + + private class MessageListener implements RedisPubSubListener<String, String> { + + @Override + public void message(String channel, String message) { + handler.handleMessage("", channel, message); + } + + @Override + public void message(String pattern, String channel, String message) { + log.debug("RECEIVED onPMessage" + channel + " message=\n" + message); + Runnable task = new Runnable() { + public void run() { + handler.handleMessage(pattern, channel, message); + } + }; + + runExec.execute(task); + } + + @Override + public void subscribed(String channel, long count) { + log.debug("Subscribed to the channel: " + channel); + } + + @Override + public void psubscribed(String pattern, long count) { + log.debug("Subscribed to the pattern: " + pattern); + } + + @Override + public void unsubscribed(String channel, long count) { + log.debug("Unsubscribed from the channel: " + channel); + } + + @Override + public void punsubscribed(String pattern, long count) { + log.debug("Unsubscribed from the pattern: " + pattern); + } + } + +} diff --git a/bbb-common-message/src/main/java/org/bigbluebutton/common2/redis/pubsub/MessageSender.java b/bbb-common-message/src/main/java/org/bigbluebutton/common2/redis/pubsub/MessageSender.java new file mode 100755 index 0000000000000000000000000000000000000000..8209f5bf76934d9959a385861ef773f184845bd4 --- /dev/null +++ b/bbb-common-message/src/main/java/org/bigbluebutton/common2/redis/pubsub/MessageSender.java @@ -0,0 +1,94 @@ +package org.bigbluebutton.common2.redis.pubsub; + +import java.util.concurrent.BlockingQueue; +import java.util.concurrent.Executor; +import java.util.concurrent.Executors; +import java.util.concurrent.LinkedBlockingQueue; + +import org.apache.commons.pool2.impl.GenericObjectPool; +import org.bigbluebutton.common2.redis.RedisAwareCommunicator; +import org.red5.logging.Red5LoggerFactory; +import org.slf4j.Logger; + +import io.lettuce.core.ClientOptions; +import io.lettuce.core.RedisClient; +import io.lettuce.core.RedisFuture; +import io.lettuce.core.RedisURI; +import io.lettuce.core.api.async.RedisAsyncCommands; +import io.lettuce.core.pubsub.StatefulRedisPubSubConnection; +import io.lettuce.core.support.ConnectionPoolSupport; + +public class MessageSender extends RedisAwareCommunicator { + private static Logger log = Red5LoggerFactory.getLogger(MessageSender.class, "bigbluebutton"); + + GenericObjectPool<StatefulRedisPubSubConnection<String, String>> connectionPool; + + private volatile boolean sendMessage = false; + + private final Executor msgSenderExec = Executors.newSingleThreadExecutor(); + private final Executor runExec = Executors.newSingleThreadExecutor(); + private BlockingQueue<MessageToSend> messages = new LinkedBlockingQueue<MessageToSend>(); + + public void stop() { + sendMessage = false; + connectionPool.close(); + redisClient.shutdown(); + } + + public void start() { + RedisURI redisUri = RedisURI.Builder.redis(this.host, this.port).withClientName(this.clientName).build(); + if (!this.password.isEmpty()) { + redisUri.setPassword(this.password); + } + + redisClient = RedisClient.create(redisUri); + redisClient.setOptions(ClientOptions.builder().autoReconnect(true).build()); + eventBus = redisClient.getResources().eventBus(); + eventBusSubscription = eventBus.get().subscribe(e -> connectionStatusHandler(e, log)); + + connectionPool = ConnectionPoolSupport.createGenericObjectPool(() -> redisClient.connectPubSub(), + createPoolingConfig()); + + log.info("Redis org.bigbluebutton.red5.pubsub.message publisher starting!"); + try { + sendMessage = true; + + Runnable messageSender = new Runnable() { + public void run() { + while (sendMessage) { + try { + MessageToSend msg = messages.take(); + publish(msg.getChannel(), msg.getMessage()); + } catch (InterruptedException e) { + log.warn("Failed to get org.bigbluebutton.common2.redis.pubsub from queue."); + } + } + } + }; + msgSenderExec.execute(messageSender); + } catch (Exception e) { + log.error("Error subscribing to channels: " + e.getMessage()); + } + + } + + public void send(String channel, String message) { + MessageToSend msg = new MessageToSend(channel, message); + messages.add(msg); + } + + private void publish(final String channel, final String message) { + Runnable task = new Runnable() { + public void run() { + try (StatefulRedisPubSubConnection<String, String> connection = connectionPool.borrowObject()) { + RedisAsyncCommands<String, String> async = connection.async(); + RedisFuture<Long> future = async.publish(channel, message); + } catch (Exception e) { + log.warn("Cannot publish the org.bigbluebutton.red5.pubsub.message to redis", e); + } + } + }; + + runExec.execute(task); + } +} diff --git a/bbb-common-message/src/main/java/org/bigbluebutton/common2/redis/pubsub/MessageToSend.java b/bbb-common-message/src/main/java/org/bigbluebutton/common2/redis/pubsub/MessageToSend.java new file mode 100755 index 0000000000000000000000000000000000000000..dba49444b87ae7ac2d2e9040ff7d44a6ffd81bd9 --- /dev/null +++ b/bbb-common-message/src/main/java/org/bigbluebutton/common2/redis/pubsub/MessageToSend.java @@ -0,0 +1,19 @@ +package org.bigbluebutton.common2.redis.pubsub; + +public class MessageToSend { + private final String channel; + private final String message; + + public MessageToSend(String channel, String message) { + this.channel = channel; + this.message = message; + } + + public String getChannel() { + return channel; + } + + public String getMessage() { + return message; + } +} diff --git a/bbb-video/src/main/java/org/bigbluebutton/red5/pubsub/ReceivedMessage.java b/bbb-common-message/src/main/java/org/bigbluebutton/common2/redis/pubsub/ReceivedMessage.java similarity index 87% rename from bbb-video/src/main/java/org/bigbluebutton/red5/pubsub/ReceivedMessage.java rename to bbb-common-message/src/main/java/org/bigbluebutton/common2/redis/pubsub/ReceivedMessage.java index 41c775f739fc95f8e0c827ff1aa4a2adf6976ee5..01f893f309ef0ae497cdcc54a6808e0f252c72a2 100755 --- a/bbb-video/src/main/java/org/bigbluebutton/red5/pubsub/ReceivedMessage.java +++ b/bbb-common-message/src/main/java/org/bigbluebutton/common2/redis/pubsub/ReceivedMessage.java @@ -1,4 +1,4 @@ -package org.bigbluebutton.red5.pubsub; +package org.bigbluebutton.common2.redis.pubsub; public class ReceivedMessage { private final String pattern; diff --git a/bbb-video/src/main/java/org/bigbluebutton/red5/pubsub/ReceivedMessageHandler.java b/bbb-common-message/src/main/java/org/bigbluebutton/common2/redis/pubsub/ReceivedMessageHandler.java similarity index 90% rename from bbb-video/src/main/java/org/bigbluebutton/red5/pubsub/ReceivedMessageHandler.java rename to bbb-common-message/src/main/java/org/bigbluebutton/common2/redis/pubsub/ReceivedMessageHandler.java index ddafee0b6e8b2897c8da27a344c24e7ad63cd8fe..ed296626114b185eaf805ef21a73e665416778f3 100755 --- a/bbb-video/src/main/java/org/bigbluebutton/red5/pubsub/ReceivedMessageHandler.java +++ b/bbb-common-message/src/main/java/org/bigbluebutton/common2/redis/pubsub/ReceivedMessageHandler.java @@ -1,15 +1,16 @@ -package org.bigbluebutton.red5.pubsub; +package org.bigbluebutton.common2.redis.pubsub; - -import org.red5.logging.Red5LoggerFactory; -import org.slf4j.Logger; import java.util.concurrent.BlockingQueue; import java.util.concurrent.Executor; import java.util.concurrent.Executors; import java.util.concurrent.LinkedBlockingQueue; +import org.red5.logging.Red5LoggerFactory; +import org.slf4j.Logger; + public class ReceivedMessageHandler { - private static Logger log = Red5LoggerFactory.getLogger(ReceivedMessageHandler.class, "video"); + private static Logger log = Red5LoggerFactory + .getLogger(ReceivedMessageHandler.class/* , "video" */); private BlockingQueue<ReceivedMessage> receivedMessages = new LinkedBlockingQueue<ReceivedMessage>(); diff --git a/akka-bbb-apps/src/main/scala/org/bigbluebutton/core/bus/IncomingJsonMessageBus.scala b/bbb-common-message/src/main/scala/org/bigbluebutton/common2/bus/IncomingJsonMessageBus.scala old mode 100755 new mode 100644 similarity index 94% rename from akka-bbb-apps/src/main/scala/org/bigbluebutton/core/bus/IncomingJsonMessageBus.scala rename to bbb-common-message/src/main/scala/org/bigbluebutton/common2/bus/IncomingJsonMessageBus.scala index f2e9aa281de9441af7bf0fe3fe1c7e63f91fa59d..96dbff59b4312665dcd909d73d3e3a3d1b102731 --- a/akka-bbb-apps/src/main/scala/org/bigbluebutton/core/bus/IncomingJsonMessageBus.scala +++ b/bbb-common-message/src/main/scala/org/bigbluebutton/common2/bus/IncomingJsonMessageBus.scala @@ -1,4 +1,4 @@ -package org.bigbluebutton.core.bus +package org.bigbluebutton.common2.bus import akka.actor.ActorRef import akka.event.{ EventBus, LookupClassification } diff --git a/bbb-common-web/src/main/scala/org/bigbluebutton/api2/bus/JsonMsgFromAkkaAppsBus.scala b/bbb-common-message/src/main/scala/org/bigbluebutton/common2/bus/JsonMsgFromAkkaAppsBus.scala old mode 100755 new mode 100644 similarity index 88% rename from bbb-common-web/src/main/scala/org/bigbluebutton/api2/bus/JsonMsgFromAkkaAppsBus.scala rename to bbb-common-message/src/main/scala/org/bigbluebutton/common2/bus/JsonMsgFromAkkaAppsBus.scala index 5d26e2c23bb7a1c4f27a150d64a8a1fe4f4a0907..871f6f76f9948b4fc7b7fd0abbdf3cbb9bdd6c6b --- a/bbb-common-web/src/main/scala/org/bigbluebutton/api2/bus/JsonMsgFromAkkaAppsBus.scala +++ b/bbb-common-message/src/main/scala/org/bigbluebutton/common2/bus/JsonMsgFromAkkaAppsBus.scala @@ -1,7 +1,7 @@ -package org.bigbluebutton.api2.bus +package org.bigbluebutton.common2.bus import akka.actor.ActorRef -import akka.event.{EventBus, LookupClassification} +import akka.event.{ EventBus, LookupClassification } case class JsonMsgFromAkkaApps(name: String, data: String) case class JsonMsgFromAkkaAppsEvent(val topic: String, val payload: JsonMsgFromAkkaApps) @@ -23,7 +23,7 @@ class JsonMsgFromAkkaAppsBus extends EventBus with LookupClassification { // must define a full order over the subscribers, expressed as expected from // `java.lang.Comparable.compare` override protected def compareSubscribers(a: Subscriber, b: Subscriber): Int = - a.compareTo(b) + a.compareTo(b) // determines the initial size of the index data structure // used internally (i.e. the expected number of different classifiers) diff --git a/bbb-common-web/src/main/scala/org/bigbluebutton/api2/bus/MsgFromAkkaAppsEventBus.scala b/bbb-common-message/src/main/scala/org/bigbluebutton/common2/bus/MsgFromAkkaAppsEventBus.scala old mode 100755 new mode 100644 similarity index 91% rename from bbb-common-web/src/main/scala/org/bigbluebutton/api2/bus/MsgFromAkkaAppsEventBus.scala rename to bbb-common-message/src/main/scala/org/bigbluebutton/common2/bus/MsgFromAkkaAppsEventBus.scala index e9f25b91a5f9e2f8f224bed7e482334204ea806a..097df4a29c433f8e7b8e3dc1702696f1b8a2ad12 --- a/bbb-common-web/src/main/scala/org/bigbluebutton/api2/bus/MsgFromAkkaAppsEventBus.scala +++ b/bbb-common-message/src/main/scala/org/bigbluebutton/common2/bus/MsgFromAkkaAppsEventBus.scala @@ -2,9 +2,9 @@ package org.bigbluebutton.api2.bus import akka.actor.ActorRef import akka.event.{EventBus, LookupClassification} -import org.bigbluebutton.common2.msgs.{BbbCommonEnvCoreMsg} +import org.bigbluebutton.common2.msgs.BbbCommonMsg -case class MsgFromAkkaApps(val topic: String, val payload: BbbCommonEnvCoreMsg) +case class MsgFromAkkaApps(val topic: String, val payload: BbbCommonMsg) class MsgFromAkkaAppsEventBus extends EventBus with LookupClassification { type Event = MsgFromAkkaApps diff --git a/bbb-common-web/src/main/scala/org/bigbluebutton/api2/bus/OldMessageEventBus.scala b/bbb-common-message/src/main/scala/org/bigbluebutton/common2/bus/OldMessageEventBus.scala similarity index 86% rename from bbb-common-web/src/main/scala/org/bigbluebutton/api2/bus/OldMessageEventBus.scala rename to bbb-common-message/src/main/scala/org/bigbluebutton/common2/bus/OldMessageEventBus.scala index cccd288cdcbf317ad7ea7da62761dcbdfcb4e9f6..9b7c6bdd0b248a80efe49dc062501f0d472338dc 100755 --- a/bbb-common-web/src/main/scala/org/bigbluebutton/api2/bus/OldMessageEventBus.scala +++ b/bbb-common-message/src/main/scala/org/bigbluebutton/common2/bus/OldMessageEventBus.scala @@ -1,7 +1,8 @@ -package org.bigbluebutton.api2.bus +package org.bigbluebutton.common2.bus import akka.actor.ActorRef import akka.event.{EventBus, LookupClassification} +import akka.actor.actorRef2Scala case class OldReceivedJsonMessage(pattern: String, channel: String, msg: String) case class OldIncomingJsonMessage(val topic: String, val payload: OldReceivedJsonMessage) @@ -11,7 +12,7 @@ class OldMessageEventBus extends EventBus with LookupClassification { type Classifier = String type Subscriber = ActorRef - // is used for extracting the classifier from the incoming events + // is used for extracting te classifier from the incoming events override protected def classify(event: Event): Classifier = event.topic // will be invoked for each event for all subscribers which registered themselves diff --git a/bbb-common-message/src/main/scala/org/bigbluebutton/common2/msgs/SystemMsgs.scala b/bbb-common-message/src/main/scala/org/bigbluebutton/common2/msgs/SystemMsgs.scala index 02fa08817044243ee197b0231ca3cd8b15614d14..08e8c38735cf9a9d1f36ddb575129f30b2de0d6b 100755 --- a/bbb-common-message/src/main/scala/org/bigbluebutton/common2/msgs/SystemMsgs.scala +++ b/bbb-common-message/src/main/scala/org/bigbluebutton/common2/msgs/SystemMsgs.scala @@ -151,3 +151,15 @@ case class ValidateConnAuthTokenSysRespMsg(header: BbbCoreHeaderWithMeetingId, body: ValidateConnAuthTokenSysRespMsgBody) extends BbbCoreMsg case class ValidateConnAuthTokenSysRespMsgBody(meetingId: String, userId: String, connId: String, authzed: Boolean, app: String) + +object PublishedRecordingSysMsg { val NAME = "PublishedRecordingSysMsg" } +case class PublishedRecordingSysMsg(header: BbbCoreBaseHeader, body: PublishedRecordingSysMsgBody) extends BbbCoreMsg +case class PublishedRecordingSysMsgBody(recordId: String) + +object UnpublishedRecordingSysMsg { val NAME = "UnpublishedRecordingSysMsg" } +case class UnpublishedRecordingSysMsg(header: BbbCoreBaseHeader, body: UnpublishedRecordingSysMsgBody) extends BbbCoreMsg +case class UnpublishedRecordingSysMsgBody(recordId: String) + +object DeletedRecordingSysMsg { val NAME = "DeletedRecordingSysMsg" } +case class DeletedRecordingSysMsg(header: BbbCoreBaseHeader, body: DeletedRecordingSysMsgBody) extends BbbCoreMsg +case class DeletedRecordingSysMsgBody(recordId: String) diff --git a/bbb-common-web/src/main/scala/org/bigbluebutton/api2/endpoint/redis/MessageSender.scala b/bbb-common-message/src/main/scala/org/bigbluebutton/common2/redis/MessageSender.scala similarity index 71% rename from bbb-common-web/src/main/scala/org/bigbluebutton/api2/endpoint/redis/MessageSender.scala rename to bbb-common-message/src/main/scala/org/bigbluebutton/common2/redis/MessageSender.scala index be38ef420165f015adbeb0a6675f4f213dacd304..2426a353e6d687ebfe073acc28337f48b961444a 100755 --- a/bbb-common-web/src/main/scala/org/bigbluebutton/api2/endpoint/redis/MessageSender.scala +++ b/bbb-common-message/src/main/scala/org/bigbluebutton/common2/redis/MessageSender.scala @@ -1,4 +1,4 @@ -package org.bigbluebutton.api2.endpoint.redis +package org.bigbluebutton.common2.redis class MessageSender(publisher: RedisPublisher) { diff --git a/bbb-common-message/src/main/scala/org/bigbluebutton/common2/redis/RedisClientProvider.scala b/bbb-common-message/src/main/scala/org/bigbluebutton/common2/redis/RedisClientProvider.scala new file mode 100644 index 0000000000000000000000000000000000000000..afeba57bf44c5604908cbc22711342ee580fa1a0 --- /dev/null +++ b/bbb-common-message/src/main/scala/org/bigbluebutton/common2/redis/RedisClientProvider.scala @@ -0,0 +1,15 @@ +package org.bigbluebutton.common2.redis + +import akka.actor.ActorSystem +import io.lettuce.core.ClientOptions +import io.lettuce.core.RedisClient +import io.lettuce.core.RedisURI + +abstract class RedisClientProvider(val system: ActorSystem, val clientName: String) extends RedisConfiguration { + // Set the name of this client to be able to distinguish when doing + // CLIENT LIST on redis-cli + val redisUri = RedisURI.Builder.redis(redisHost, redisPort).withClientName(clientName).withPassword(redisPassword).build() + + var redis = RedisClient.create(redisUri) + redis.setOptions(ClientOptions.builder().autoReconnect(true).build()) +} diff --git a/bbb-common-message/src/main/scala/org/bigbluebutton/common2/redis/RedisConfiguration.scala b/bbb-common-message/src/main/scala/org/bigbluebutton/common2/redis/RedisConfiguration.scala new file mode 100644 index 0000000000000000000000000000000000000000..643e847a395fb9a48cf628e8121beedd298ccbf1 --- /dev/null +++ b/bbb-common-message/src/main/scala/org/bigbluebutton/common2/redis/RedisConfiguration.scala @@ -0,0 +1,27 @@ +package org.bigbluebutton.common2.redis + +import scala.util.Try +import com.typesafe.config.ConfigFactory + +trait RedisConfiguration { + val config = ConfigFactory.load() + + // Redis server configuration + lazy val redisHost = Try(config.getString("redis.host")).getOrElse("127.0.0.1") + lazy val redisPort = Try(config.getInt("redis.port")).getOrElse(6379) + lazy val redisPassword = Try(config.getString("redis.password")).getOrElse("") + lazy val redisExpireKey = Try(config.getInt("redis.keyExpiry")).getOrElse(1209600) + + // Redis channels + lazy val toAkkaAppsRedisChannel = Try(config.getString("redis.toAkkaAppsRedisChannel")).getOrElse("to-akka-apps-redis-channel") + lazy val fromAkkaAppsRedisChannel = Try(config.getString("redis.fromAkkaAppsRedisChannel")).getOrElse("from-akka-apps-redis-channel") + + lazy val toVoiceConfRedisChannel = Try(config.getString("redis.toVoiceConfRedisChannel")).getOrElse("to-voice-conf-redis-channel") + lazy val fromVoiceConfRedisChannel = Try(config.getString("redis.fromVoiceConfRedisChannel")).getOrElse("from-voice-conf-redis-channel") + + lazy val fromAkkaAppsWbRedisChannel = Try(config.getString("redis.fromAkkaAppsWbRedisChannel")).getOrElse("from-akka-apps-wb-redis-channel") + lazy val fromAkkaAppsChatRedisChannel = Try(config.getString("redis.fromAkkaAppsChatRedisChannel")).getOrElse("from-akka-apps-chat-redis-channel") + lazy val fromAkkaAppsPresRedisChannel = Try(config.getString("redis.fromAkkaAppsPresRedisChannel")).getOrElse("from-akka-apps-pres-redis-channel") + + lazy val fromBbbWebRedisChannel = Try(config.getString("redis.fromBbbWebRedisChannel")).getOrElse("from-bbb-web-redis-channel") +} diff --git a/bbb-common-message/src/main/scala/org/bigbluebutton/common2/redis/RedisConnectionHandler.scala b/bbb-common-message/src/main/scala/org/bigbluebutton/common2/redis/RedisConnectionHandler.scala new file mode 100644 index 0000000000000000000000000000000000000000..c30003c95da1becaaff67d45c705d67bcf09a650 --- /dev/null +++ b/bbb-common-message/src/main/scala/org/bigbluebutton/common2/redis/RedisConnectionHandler.scala @@ -0,0 +1,29 @@ +package org.bigbluebutton.common2.redis + +import io.lettuce.core.RedisClient +import io.lettuce.core.event.Event +import io.lettuce.core.event.EventBus +import io.lettuce.core.event.connection.{ ConnectionDeactivatedEvent, ConnectionActivatedEvent, ConnectedEvent, DisconnectedEvent } +import reactor.core.Disposable +import akka.event.LoggingAdapter + +trait RedisConnectionHandler { + + def subscribeToEventBus(redis: RedisClient, log: LoggingAdapter) { + val eventBus: EventBus = redis.getResources().eventBus(); + // @todo : unsubscribe when connection is closed + val eventBusSubscription: Disposable = eventBus.get().subscribe(e => connectionStatusHandler(e, log)) + } + + def connectionStatusHandler(event: Event, log: LoggingAdapter) { + if (event.isInstanceOf[ConnectedEvent]) { + log.info("Connected to redis"); + } else if (event.isInstanceOf[ConnectionActivatedEvent]) { + log.info("Connection to redis activated"); + } else if (event.isInstanceOf[DisconnectedEvent]) { + log.info("Disconnected from redis"); + } else if (event.isInstanceOf[ConnectionDeactivatedEvent]) { + log.info("Connection to redis deactivated"); + } + } +} diff --git a/bbb-common-message/src/main/scala/org/bigbluebutton/common2/redis/RedisPublisher.scala b/bbb-common-message/src/main/scala/org/bigbluebutton/common2/redis/RedisPublisher.scala new file mode 100755 index 0000000000000000000000000000000000000000..47c5af6f10dcba75a018bcc1658bfebd12219fb2 --- /dev/null +++ b/bbb-common-message/src/main/scala/org/bigbluebutton/common2/redis/RedisPublisher.scala @@ -0,0 +1,21 @@ +package org.bigbluebutton.common2.redis + +import akka.actor.ActorSystem +import akka.event.Logging + +class RedisPublisher(system: ActorSystem, clientName: String) extends RedisClientProvider(system, clientName) with RedisConnectionHandler { + + val log = Logging(system, getClass) + + subscribeToEventBus(redis, log) + + val connection = redis.connectPubSub() + + redis.connect() + + def publish(channel: String, data: String) { + val async = connection.async(); + async.publish(channel, data); + } + +} diff --git a/bbb-common-message/src/main/scala/org/bigbluebutton/common2/redis/RedisStorageProvider.scala b/bbb-common-message/src/main/scala/org/bigbluebutton/common2/redis/RedisStorageProvider.scala new file mode 100644 index 0000000000000000000000000000000000000000..71fc9df41a4c2d2f755c794a949d42db41b46203 --- /dev/null +++ b/bbb-common-message/src/main/scala/org/bigbluebutton/common2/redis/RedisStorageProvider.scala @@ -0,0 +1,13 @@ +package org.bigbluebutton.common2.redis + +import akka.actor.ActorSystem + +abstract class RedisStorageProvider(system: ActorSystem, clientName: String) extends RedisConfiguration { + var redis = new RedisStorageService() + redis.setHost(redisHost) + redis.setPort(redisPort) + redis.setPassword(redisPassword) + redis.setExpireKey(redisExpireKey) + redis.setClientName(clientName) + redis.start(); +} diff --git a/bbb-common-message/src/main/scala/org/bigbluebutton/common2/redis/RedisSubscriber.scala b/bbb-common-message/src/main/scala/org/bigbluebutton/common2/redis/RedisSubscriber.scala new file mode 100644 index 0000000000000000000000000000000000000000..5c65a338577604ac1c9b342740bc99f7641b8591 --- /dev/null +++ b/bbb-common-message/src/main/scala/org/bigbluebutton/common2/redis/RedisSubscriber.scala @@ -0,0 +1,6 @@ +package org.bigbluebutton.common2.redis + +trait RedisSubscriber extends RedisConfiguration { + val channels: Seq[String] + val patterns: Seq[String] +} diff --git a/bbb-common-message/src/main/scala/org/bigbluebutton/common2/redis/RedisSubscriberProvider.scala b/bbb-common-message/src/main/scala/org/bigbluebutton/common2/redis/RedisSubscriberProvider.scala new file mode 100644 index 0000000000000000000000000000000000000000..e091e9ad25919454973ae8f4bf450c712d89eccc --- /dev/null +++ b/bbb-common-message/src/main/scala/org/bigbluebutton/common2/redis/RedisSubscriberProvider.scala @@ -0,0 +1,62 @@ +package org.bigbluebutton.common2.redis + +import akka.actor.ActorSystem +import org.bigbluebutton.common2.bus.ReceivedJsonMessage +import org.bigbluebutton.common2.bus.IncomingJsonMessage +import io.lettuce.core.pubsub.RedisPubSubListener +import org.bigbluebutton.common2.bus.IncomingJsonMessageBus +import akka.actor.ActorLogging +import akka.actor.Actor + +import akka.actor.ActorSystem +import akka.actor.OneForOneStrategy +import akka.actor.SupervisorStrategy.Resume +import java.io.StringWriter +import scala.concurrent.duration._ +import java.io.PrintWriter + +abstract class RedisSubscriberProvider(system: ActorSystem, clientName: String, + channels: Seq[String], patterns: Seq[String], + jsonMsgBus: IncomingJsonMessageBus) + extends RedisClientProvider(system, clientName) with RedisConnectionHandler with Actor with ActorLogging { + + subscribeToEventBus(redis, log) + + var connection = redis.connectPubSub() + + def addListener(appChannel: String) { + connection.addListener(new RedisPubSubListener[String, String] { + def message(channel: String, message: String): Unit = { + if (channels.contains(channel)) { + val receivedJsonMessage = new ReceivedJsonMessage(channel, message) + jsonMsgBus.publish(IncomingJsonMessage(appChannel, receivedJsonMessage)) + } + } + def message(pattern: String, channel: String, message: String): Unit = { log.info("Subscribed to channel {} with pattern {}", channel, pattern) } + def psubscribed(pattern: String, count: Long): Unit = { log.info("Subscribed to pattern {}", pattern) } + def punsubscribed(pattern: String, count: Long): Unit = { log.info("Unsubscribed from pattern {}", pattern) } + def subscribed(channel: String, count: Long): Unit = { log.info("Subscribed to pattern {}", channel) } + def unsubscribed(channel: String, count: Long): Unit = { log.info("Unsubscribed from channel {}", channel) } + }) + } + + def subscribe() { + val async = connection.async() + for (channel <- channels) async.subscribe(channel) + for (pattern <- patterns) async.psubscribe(pattern) + } + + override val supervisorStrategy = OneForOneStrategy(maxNrOfRetries = 10, withinTimeRange = 1 minute) { + case e: Exception => { + val sw: StringWriter = new StringWriter() + sw.write("An exception has been thrown on " + getClass + ", exception message [" + e.getMessage + "] (full stacktrace below)\n") + e.printStackTrace(new PrintWriter(sw)) + log.error(sw.toString()) + Resume + } + } + + def receive = { + case _ => // do nothing + } +} diff --git a/bbb-common-message/src/test/scala/org/bigbluebutton/common2/TestFixtures.scala b/bbb-common-message/src/test/scala/org/bigbluebutton/common2/TestFixtures.scala index d2a42e7dffe3c62f49da15b23c8f54b205409559..ccfa4e929a14eaa1bd2f6100e3872be1433271a8 100755 --- a/bbb-common-message/src/test/scala/org/bigbluebutton/common2/TestFixtures.scala +++ b/bbb-common-message/src/test/scala/org/bigbluebutton/common2/TestFixtures.scala @@ -2,7 +2,6 @@ package org.bigbluebutton.common2 import org.bigbluebutton.common2.domain._ - trait TestFixtures { val meetingId = "testMeetingId" val externalMeetingId = "testExternalMeetingId" @@ -12,6 +11,15 @@ trait TestFixtures { val record = false val voiceConfId = "85115" val durationInMinutes = 10 + + val maxInactivityTimeoutMinutes = 120 + val warnMinutesBeforeMax = 30 + val meetingExpireIfNoUserJoinedInMinutes = 5 + val meetingExpireWhenLastUserLeftInMinutes = 10 + val userInactivityInspectTimerInMinutes = 60 + val userInactivityThresholdInMinutes = 10 + val userActivitySignResponseDelayInMinutes = 5 + val autoStartRecording = false val allowStartStopRecording = false val webcamsOnlyForModerator = false @@ -25,19 +33,23 @@ trait TestFixtures { val modOnlyMessage = "Moderator only message" val dialNumber = "613-555-1234" val maxUsers = 25 + val muteOnStart = false val guestPolicy = "ALWAYS_ASK" val metadata: collection.immutable.Map[String, String] = Map("foo" -> "bar", "bar" -> "baz", "baz" -> "foo") val meetingProp = MeetingProp(name = meetingName, extId = externalMeetingId, intId = meetingId, isBreakout = isBreakout.booleanValue()) - val breakoutProps = BreakoutProps(parentId = parentMeetingId, sequence = sequence, breakoutRooms = Vector()) - val durationProps = DurationProps(duration = durationInMinutes, createdTime = createTime, createdDate = createDate) + val breakoutProps = BreakoutProps(parentId = parentMeetingId, sequence = sequence, freeJoin = false, breakoutRooms = Vector()) + + val durationProps = DurationProps(duration = durationInMinutes, createdTime = createTime, createdDate = createDate, maxInactivityTimeoutMinutes = maxInactivityTimeoutMinutes, warnMinutesBeforeMax = warnMinutesBeforeMax, + meetingExpireIfNoUserJoinedInMinutes = meetingExpireIfNoUserJoinedInMinutes, meetingExpireWhenLastUserLeftInMinutes = meetingExpireWhenLastUserLeftInMinutes, + userInactivityInspectTimerInMinutes = userInactivityInspectTimerInMinutes, userInactivityThresholdInMinutes = userInactivityInspectTimerInMinutes, userActivitySignResponseDelayInMinutes = userActivitySignResponseDelayInMinutes) val password = PasswordProp(moderatorPass = moderatorPassword, viewerPass = viewerPassword) val recordProp = RecordProp(record = record, autoStartRecording = autoStartRecording, allowStartStopRecording = allowStartStopRecording) val welcomeProp = WelcomeProp(welcomeMsgTemplate = welcomeMsgTemplate, welcomeMsg = welcomeMsg, modOnlyMessage = modOnlyMessage) - val voiceProp = VoiceProp(telVoice = voiceConfId, voiceConf = voiceConfId, dialNumber = dialNumber) + val voiceProp = VoiceProp(telVoice = voiceConfId, voiceConf = voiceConfId, dialNumber = dialNumber, muteOnStart = muteOnStart) val usersProp = UsersProp(maxUsers = maxUsers, webcamsOnlyForModerator = webcamsOnlyForModerator, guestPolicy = guestPolicy) val metadataProp = new MetadataProp(metadata) diff --git a/bbb-common-message/src/test/scala/org/bigbluebutton/common2/messages/DeserializerTests.scala b/bbb-common-message/src/test/scala/org/bigbluebutton/common2/messages/DeserializerTests.scala index b3050e3bf264eaee2c50e9779e6c603ba8d3e66d..6eccd3d56cef3de2b3ba6b859085eb7520f941f6 100755 --- a/bbb-common-message/src/test/scala/org/bigbluebutton/common2/messages/DeserializerTests.scala +++ b/bbb-common-message/src/test/scala/org/bigbluebutton/common2/messages/DeserializerTests.scala @@ -1,12 +1,11 @@ package org.bigbluebutton.common2.messages import com.fasterxml.jackson.databind.JsonNode -import org.bigbluebutton.common2.messages.MessageBody.CreateMeetingReqMsgBody +import org.bigbluebutton.common2.msgs._ import org.bigbluebutton.common2.util.JsonUtil -import org.bigbluebutton.common2.{TestFixtures, UnitSpec2} - -import scala.util.{Failure, Success} +import org.bigbluebutton.common2.{ TestFixtures, UnitSpec2 } +import scala.util.{ Failure, Success } class DeserializerTests extends UnitSpec2 with TestFixtures { @@ -28,7 +27,7 @@ class DeserializerTests extends UnitSpec2 with TestFixtures { println(map) map match { case Success(envJsNodeMsg) => assert(envJsNodeMsg.core.isInstanceOf[JsonNode]) - case Failure(ex) => fail("Failed to decode json message " + ex) + case Failure(ex) => fail("Failed to decode json message " + ex) } } @@ -46,11 +45,12 @@ class DeserializerTests extends UnitSpec2 with TestFixtures { println(map) map match { - case Success(envJsNodeMsg) => assert(envJsNodeMsg.core.isInstanceOf[JsonNode]) - val createMeetingReqMsg = Deserializer.toCreateMeetingReqMsg(envJsNodeMsg.envelope, envJsNodeMsg.core) - createMeetingReqMsg match { + case Success(envJsNodeMsg) => + assert(envJsNodeMsg.core.isInstanceOf[JsonNode]) + val (msg, exception) = Deserializer.toBbbCommonMsg[CreateMeetingReqMsg](envJsNodeMsg.core) + msg match { case Some(cmrq) => assert(cmrq.isInstanceOf[CreateMeetingReqMsg]) - case None => fail("Failed to decode CreateMeetingReqMsg") + case None => fail("Failed to decode CreateMeetingReqMsg") } case Failure(ex) => fail("Failed to decode json message " + ex) } @@ -71,11 +71,12 @@ class DeserializerTests extends UnitSpec2 with TestFixtures { println(map) map match { - case Success(envJsNodeMsg) => assert(envJsNodeMsg.core.isInstanceOf[JsonNode]) + case Success(envJsNodeMsg) => + assert(envJsNodeMsg.core.isInstanceOf[JsonNode]) val (msg, exception) = Deserializer.toBbbCommonMsg[CreateMeetingReqMsg](envJsNodeMsg.core) msg match { case Some(cmrq) => assert(cmrq.isInstanceOf[CreateMeetingReqMsg]) - case None => fail("Should have successfully decoded CreateMeetingReqMsg ") + case None => fail("Should have successfully decoded CreateMeetingReqMsg ") } case Failure(ex) => fail("Failed to decode json message " + ex) } @@ -103,7 +104,7 @@ class DeserializerTests extends UnitSpec2 with TestFixtures { val (result, error) = Deserializer.toBbbCoreMessageFromClient(jsonMsg) result match { case Some(msg) => assert(msg.header.name == "foo") - case None => fail("Should have deserialized message but failed with error: " + error) + case None => fail("Should have deserialized message but failed with error: " + error) } } } diff --git a/bbb-common-message/src/test/scala/org/bigbluebutton/common2/util/JsonUtilTest.scala b/bbb-common-message/src/test/scala/org/bigbluebutton/common2/util/JsonUtilTest.scala index f701a432de2a92dc520fe585b4b17ec5b00d13ae..7d65d24d2b53db9acc5d623cb40ec2640a6676e1 100755 --- a/bbb-common-message/src/test/scala/org/bigbluebutton/common2/util/JsonUtilTest.scala +++ b/bbb-common-message/src/test/scala/org/bigbluebutton/common2/util/JsonUtilTest.scala @@ -1,14 +1,12 @@ package org.bigbluebutton.common2.util -import org.bigbluebutton.common2.{TestFixtures, UnitSpec2} -import org.bigbluebutton.common2.messages._ +import org.bigbluebutton.common2.{ TestFixtures, UnitSpec2 } +import org.bigbluebutton.common2.msgs._ import scala.collection.immutable.List import com.fasterxml.jackson.databind.JsonNode -import org.bigbluebutton.common2.messages.MessageBody.ValidateAuthTokenReqMsgBody - -import scala.util.{Failure, Success} +import scala.util.{ Failure, Success } case class Person(name: String, age: Int) case class Group(name: String, persons: Seq[Person], leader: Person) @@ -26,7 +24,7 @@ class JsonUtilTest extends UnitSpec2 with TestFixtures { // map: Map[String,Seq[Int]] = Map(a -> List(1, 2), b -> List(3, 4, 5), c -> List()) println(map) map match { - case Success(a) => assert(a.values.size == 3) + case Success(a) => assert(a.values.size == 3) case Failure(ex) => fail("Failed to decode json message") } } @@ -38,7 +36,7 @@ class JsonUtilTest extends UnitSpec2 with TestFixtures { val jeroen = Person("Jeroen", 26) val martin = Person("Martin", 54) - val originalGroup = Group("Scala ppl", Seq(jeroen,martin), martin) + val originalGroup = Group("Scala ppl", Seq(jeroen, martin), martin) // originalGroup: Group = Group(Scala ppl,List(Person(Jeroen,26), Person(Martin,54)),Person(Martin,54)) println(originalGroup) @@ -52,7 +50,7 @@ class JsonUtilTest extends UnitSpec2 with TestFixtures { } "JsonUtil" should "unmarshall a ValidateAuthTokenReq" in { - val header: BbbCoreHeaderWithMeetingId = new BbbCoreHeaderWithMeetingId("foo", "mId") + val header: BbbClientMsgHeader = new BbbClientMsgHeader("foo", "mId", "uId") val body: ValidateAuthTokenReqMsgBody = new ValidateAuthTokenReqMsgBody(userId = "uId", authToken = "myToken") val msg: ValidateAuthTokenReqMsg = new ValidateAuthTokenReqMsg(header, body) val json = JsonUtil.toJson(msg) diff --git a/bbb-common-web/build.sbt b/bbb-common-web/build.sbt index 11ff1b07e84f78a5e330ea98ddf8763bd2cb50aa..0519059e3804bb645328d651ae678ce0ef83224d 100755 --- a/bbb-common-web/build.sbt +++ b/bbb-common-web/build.sbt @@ -1,19 +1,23 @@ -name := "bbb-common-web" - -organization := "org.bigbluebutton" - -version := "0.0.2-SNAPSHOT" - -scalaVersion := "2.12.6" - -scalacOptions ++= Seq( - "-unchecked", - "-deprecation", - "-Xlint", - "-Ywarn-dead-code", - "-language:_", - "-target:jvm-1.8", - "-encoding", "UTF-8" +import org.bigbluebutton.build._ + +version := "0.0.3-SNAPSHOT" + +val compileSettings = Seq( + organization := "org.bigbluebutton", + + scalacOptions ++= List( + "-unchecked", + "-deprecation", + "-Xlint", + "-Ywarn-dead-code", + "-language:_", + "-target:jvm-1.8", + "-encoding", "UTF-8" + ), + javacOptions ++= List( + "-Xlint:unchecked", + "-Xlint:deprecation" + ) ) // We want to have our jar files in lib_managed dir. @@ -25,82 +29,8 @@ testOptions in Test += Tests.Argument(TestFrameworks.Specs2, "html", "console", testOptions in Test += Tests.Argument(TestFrameworks.ScalaTest, "-h", "target/scalatest-reports") -val akkaVersion = "2.5.14" - -// https://mvnrepository.com/artifact/org.scala-lang/scala-library -libraryDependencies += "org.scala-lang" % "scala-library" % scalaVersion.value -// https://mvnrepository.com/artifact/org.scala-lang/scala-compiler -libraryDependencies += "org.scala-lang" % "scala-compiler" % scalaVersion.value - -// https://mvnrepository.com/artifact/com.typesafe.akka/akka-actor_2.12 -libraryDependencies += "com.typesafe.akka" % "akka-actor_2.12" % akkaVersion -// https://mvnrepository.com/artifact/com.typesafe.akka/akka-slf4j_2.12 -libraryDependencies += "com.typesafe.akka" % "akka-slf4j_2.12" % akkaVersion - -// https://mvnrepository.com/artifact/com.github.etaty/rediscala_2.12 -libraryDependencies += "com.github.etaty" % "rediscala_2.12" % "1.8.0" - -libraryDependencies += "com.softwaremill.quicklens" %% "quicklens" % "1.4.11" - -libraryDependencies += "org.bigbluebutton" % "bbb-common-message_2.12" % "0.0.19-SNAPSHOT" -// https://mvnrepository.com/artifact/com.fasterxml.jackson.module/jackson-module-scala_2.12 -libraryDependencies += "com.fasterxml.jackson.module" % "jackson-module-scala_2.12" % "2.9.6" - -libraryDependencies += "redis.clients" % "jedis" % "2.9.0" -libraryDependencies += "com.google.code.gson" % "gson" % "2.8.5" - -// https://mvnrepository.com/artifact/org.apache.commons/commons-lang3 -libraryDependencies += "org.apache.commons" % "commons-lang3" % "3.7" -libraryDependencies += "commons-io" % "commons-io" % "2.6" -libraryDependencies += "org.apache.commons" % "commons-pool2" % "2.6.0" -libraryDependencies += "com.zaxxer" % "nuprocess" % "1.2.4" - -// https://mvnrepository.com/artifact/org.jodconverter/jodconverter-core -libraryDependencies += "org.jodconverter" % "jodconverter-local" % "4.2.0" - -// https://mvnrepository.com/artifact/org.libreoffice/unoil -libraryDependencies += "org.libreoffice" % "unoil" % "5.4.2" - -// https://mvnrepository.com/artifact/org.libreoffice/ridl -libraryDependencies += "org.libreoffice" % "ridl" % "5.4.2" - -// https://mvnrepository.com/artifact/org.libreoffice/juh -libraryDependencies += "org.libreoffice" % "juh" % "5.4.2" - -// https://mvnrepository.com/artifact/org.libreoffice/jurt -libraryDependencies += "org.libreoffice" % "jurt" % "5.4.2" - - -libraryDependencies += "org.apache.poi" % "poi-ooxml" % "3.17" - -libraryDependencies += "org.slf4j" % "slf4j-api" % "1.7.25" - -// https://mvnrepository.com/artifact/org.apache.httpcomponents/httpclient -libraryDependencies += "org.apache.httpcomponents" % "httpclient" % "4.5.6" -// https://mvnrepository.com/artifact/org.apache.httpcomponents/httpasyncclient -libraryDependencies += "org.apache.httpcomponents" % "httpasyncclient" % "4.1.4" - -libraryDependencies += "org.freemarker" % "freemarker" % "2.3.28" -libraryDependencies += "com.fasterxml.jackson.dataformat" % "jackson-dataformat-xml" % "2.9.6" -// https://mvnrepository.com/artifact/org.codehaus.woodstox/woodstox-core-asl -libraryDependencies += "org.codehaus.woodstox" % "woodstox-core-asl" % "4.4.1" - -libraryDependencies += "org.pegdown" % "pegdown" % "1.4.0" % "test" -libraryDependencies += "junit" % "junit" % "4.12" % "test" -libraryDependencies += "com.novocode" % "junit-interface" % "0.11" % "test" -// https://mvnrepository.com/artifact/org.mockito/mockito-core -libraryDependencies += "org.mockito" % "mockito-core" % "2.7.12" % "test" -libraryDependencies += "org.scalactic" %% "scalactic" % "3.0.1" % "test" -libraryDependencies += "org.scalatest" %% "scalatest" % "3.0.1" % "test" - -// https://mvnrepository.com/artifact/com.typesafe.akka/akka-testkit_2.12 -libraryDependencies += "com.typesafe.akka" % "akka-testkit_2.12" % akkaVersion % "test" - -// https://mvnrepository.com/artifact/org.scala-lang.modules/scala-xml_2.12 -libraryDependencies += "org.scala-lang.modules" % "scala-xml_2.12" % "1.1.0" - - -seq(Revolver.settings: _*) +Seq(Revolver.settings: _*) +lazy val commonWeb = (project in file(".")).settings(name := "bbb-common-web", libraryDependencies ++= Dependencies.runtime).settings(compileSettings) //----------- // Packaging @@ -120,12 +50,12 @@ crossPaths := false // This forbids including Scala related libraries into the dependency autoScalaLibrary := false -/*************************** -* When developing, change the version above to x.x.x-SNAPSHOT then use the file resolver to -* publish to the local maven repo using "sbt publish" -*/ +/** ************************* + * When developing, change the version above to x.x.x-SNAPSHOT then use the file resolver to + * publish to the local maven repo using "sbt publish" + */ // Uncomment this to publish to local maven repo while commenting out the nexus repo -publishTo := Some(Resolver.file("file", new File(Path.userHome.absolutePath+"/.m2/repository"))) +publishTo := Some(Resolver.file("file", new File(Path.userHome.absolutePath + "/.m2/repository"))) // Comment this out when publishing to local maven repo using SNAPSHOT version. @@ -150,16 +80,14 @@ pomExtra := ( <url>git@github.com:bigbluebutton/bigbluebutton.git</url> <connection>scm:git:git@github.com:bigbluebutton/bigbluebutton.git</connection> </scm> - <developers> - <developer> - <id>ritzalam</id> - <name>Richard Alam</name> - <url>http://www.bigbluebutton.org</url> - </developer> - </developers>) - + <developers> + <developer> + <id>ritzalam</id> + <name>Richard Alam</name> + <url>http://www.bigbluebutton.org</url> + </developer> + </developers>) + licenses := Seq("LGPL-3.0" -> url("http://opensource.org/licenses/LGPL-3.0")) homepage := Some(url("http://www.bigbluebutton.org")) - - diff --git a/bbb-common-web/deploy.sh b/bbb-common-web/deploy.sh index 948f5634178c0655544d5ab9148e6df8cc177ffc..f829bc13dc598da480ee4e512a87c9436925a359 100755 --- a/bbb-common-web/deploy.sh +++ b/bbb-common-web/deploy.sh @@ -1,3 +1 @@ -sbt clean -sbt publish publishLocal - +sbt clean publish publishLocal diff --git a/bbb-common-web/project/Build.scala b/bbb-common-web/project/Build.scala deleted file mode 100755 index e69de29bb2d1d6434b8b29ae775ad8c2e48c5391..0000000000000000000000000000000000000000 diff --git a/bbb-common-web/project/Dependencies.scala b/bbb-common-web/project/Dependencies.scala new file mode 100644 index 0000000000000000000000000000000000000000..b26f15643f90080a33b05edcdea27201728f0ebc --- /dev/null +++ b/bbb-common-web/project/Dependencies.scala @@ -0,0 +1,104 @@ +package org.bigbluebutton.build + +import sbt._ +import Keys._ + +object Dependencies { + + object Versions { + // Scala + val scala = "2.12.7" + val junit = "4.12" + val junitInterface = "0.11" + val scalactic = "3.0.3" + + // Libraries + val akkaVersion = "2.5.17" + val gson = "2.8.5" + val jackson = "2.9.7" + val freemaker = "2.3.28" + val apacheHttp = "4.5.6" + val apacheHttpAsync = "4.1.4" + + // Office and document conversion + val apacheOffice = "4.0.0" + val jodConverter = "4.2.1" + val apachePoi = "3.17" + val nuProcess = "1.2.4" + val libreOffice = "5.4.2" + + // Apache Commons + val lang = "3.8.1" + val io = "2.6" + val pool = "2.6.0" + + // BigBlueButton + val bbbCommons = "0.0.20-SNAPSHOT" + + // Test + val scalaTest = "3.0.5" + } + + object Compile { + val scalaLibrary = "org.scala-lang" % "scala-library" % Versions.scala + val scalaCompiler = "org.scala-lang" % "scala-compiler" % Versions.scala + + val akkaActor = "com.typesafe.akka" % "akka-actor_2.12" % Versions.akkaVersion + val akkaSl4fj = "com.typesafe.akka" % "akka-slf4j_2.12" % Versions.akkaVersion % "runtime" + + val googleGson = "com.google.code.gson" % "gson" % Versions.gson + val jacksonModule = "com.fasterxml.jackson.module" %% "jackson-module-scala" % Versions.jackson + val jacksonXml = "com.fasterxml.jackson.dataformat" % "jackson-dataformat-xml" % Versions.jackson + val freeMaker = "org.freemarker" % "freemarker" % Versions.freemaker + val apacheHttp = "org.apache.httpcomponents" % "httpclient" % Versions.apacheHttp + val apacheHttpAsync = "org.apache.httpcomponents" % "httpasyncclient" % Versions.apacheHttpAsync + + val poiXml = "org.apache.poi" % "poi-ooxml" % Versions.apachePoi + val jodConverter = "org.jodconverter" % "jodconverter-local" % Versions.jodConverter + val nuProcess = "com.zaxxer" % "nuprocess" % Versions.nuProcess + + val officeUnoil = "org.libreoffice" % "unoil" % Versions.libreOffice + val officeRidl = "org.libreoffice" % "ridl" % Versions.libreOffice + val officeJuh = "org.libreoffice" % "juh" % Versions.libreOffice + val officejurt = "org.libreoffice" % "jurt" % Versions.libreOffice + + val apacheLang = "org.apache.commons" % "commons-lang3" % Versions.lang + val apacheIo = "commons-io" % "commons-io" % Versions.io + val apachePool2 = "org.apache.commons" % "commons-pool2" % Versions.pool + + val bbbCommons = "org.bigbluebutton" % "bbb-common-message_2.12" % Versions.bbbCommons excludeAll ( + ExclusionRule(organization = "org.red5")) + } + + object Test { + val scalaTest = "org.scalatest" %% "scalatest" % Versions.scalaTest % "test" + val junit = "junit" % "junit" % Versions.junit % "test" + val junitInteface = "com.novocode" % "junit-interface" % Versions.junitInterface % "test" + val scalactic = "org.scalactic" % "scalactic_2.12" % Versions.scalactic % "test" + } + + val testing = Seq( + Test.scalaTest, + Test.junit, + Test.junitInteface, + Test.scalactic) + + val runtime = Seq( + Compile.scalaLibrary, + Compile.scalaCompiler, + Compile.akkaActor, + Compile.akkaSl4fj, + Compile.googleGson, + Compile.jacksonModule, + Compile.jacksonXml, + Compile.freeMaker, + Compile.apacheHttp, + Compile.apacheHttpAsync, + Compile.poiXml, + Compile.jodConverter, + Compile.nuProcess, + Compile.apacheLang, + Compile.apacheIo, + Compile.apachePool2, + Compile.bbbCommons) ++ testing +} diff --git a/bbb-common-web/project/build.properties b/bbb-common-web/project/build.properties index a6e117b61042ee81c62ba3a0fc5210d9502944df..2e6e3d24608ee15e892ed3b16d84224f7667e808 100755 --- a/bbb-common-web/project/build.properties +++ b/bbb-common-web/project/build.properties @@ -1 +1 @@ -sbt.version=0.13.8 +sbt.version=1.2.6 \ No newline at end of file diff --git a/bbb-common-web/project/plugins.sbt b/bbb-common-web/project/plugins.sbt index b91f89e4a637ce583af1b0e09615d2f9044e033b..4eb70b26c5aeae53a81b4c122607bda9733ec175 100755 --- a/bbb-common-web/project/plugins.sbt +++ b/bbb-common-web/project/plugins.sbt @@ -2,9 +2,7 @@ addSbtPlugin("io.spray" % "sbt-revolver" % "0.9.1") addSbtPlugin("com.typesafe.sbteclipse" % "sbteclipse-plugin" % "5.2.4") -addSbtPlugin("com.jsuereth" % "sbt-pgp" % "1.0.0") - -addSbtPlugin("com.artima.supersafe" % "sbtplugin" % "1.1.7") +addSbtPlugin("com.jsuereth" % "sbt-pgp" % "1.1.1") addSbtPlugin("net.vonbuchholtz" % "sbt-dependency-check" % "0.2.7") diff --git a/bbb-common-web/src/main/java/org/bigbluebutton/api/MeetingService.java b/bbb-common-web/src/main/java/org/bigbluebutton/api/MeetingService.java index c203b1e8e2dfd7a050b5c97d2d29302a9808b86f..0865d49710673d57f12eb10b30963f1380f6adc3 100755 --- a/bbb-common-web/src/main/java/org/bigbluebutton/api/MeetingService.java +++ b/bbb-common-web/src/main/java/org/bigbluebutton/api/MeetingService.java @@ -48,9 +48,11 @@ import org.bigbluebutton.api.domain.RegisteredUser; import org.bigbluebutton.api.domain.User; import org.bigbluebutton.api.domain.UserSession; import org.bigbluebutton.api.messaging.MessageListener; -import org.bigbluebutton.api.messaging.RedisStorageService; import org.bigbluebutton.api.messaging.converters.messages.DestroyMeetingMessage; import org.bigbluebutton.api.messaging.converters.messages.EndMeetingMessage; +import org.bigbluebutton.api.messaging.converters.messages.PublishedRecordingMessage; +import org.bigbluebutton.api.messaging.converters.messages.UnpublishedRecordingMessage; +import org.bigbluebutton.api.messaging.converters.messages.DeletedRecordingMessage; import org.bigbluebutton.api.messaging.messages.CreateBreakoutRoom; import org.bigbluebutton.api.messaging.messages.CreateMeeting; import org.bigbluebutton.api.messaging.messages.EndMeeting; @@ -77,6 +79,7 @@ import org.bigbluebutton.api.messaging.messages.UserStatusChanged; import org.bigbluebutton.api.messaging.messages.UserUnsharedWebcam; import org.bigbluebutton.api2.IBbbWebApiGWApp; import org.bigbluebutton.api2.domain.UploadedTrack; +import org.bigbluebutton.common2.redis.RedisStorageService; import org.bigbluebutton.presentation.PresentationUrlDownloadService; import org.bigbluebutton.web.services.RegisteredUserCleanupTimerTask; import org.bigbluebutton.web.services.callback.CallbackUrlService; @@ -197,19 +200,7 @@ public class MeetingService implements MessageListener { private void kickOffProcessingOfRecording(Meeting m) { if (m.isRecord() && m.getNumUsers() == 0) { - Map<String, Object> logData = new HashMap<>(); - logData.put("meetingId", m.getInternalId()); - logData.put("externalMeetingId", m.getExternalId()); - logData.put("name", m.getName()); - logData.put("event", "kick_off_ingest_and_processing"); - logData.put("description", "Start processing of recording."); - - Gson gson = new Gson(); - String logStr = gson.toJson(logData); - - log.info("Initiate recording processing: data={}", logStr); - - processRecording(m.getInternalId()); + processRecording(m); } } @@ -429,16 +420,22 @@ public class MeetingService implements MessageListener { public void setPublishRecording(List<String> idList, boolean publish) { for (String id : idList) { if (publish) { - recordingService.changeState(id, Recording.STATE_PUBLISHED); + if (recordingService.changeState(id, Recording.STATE_PUBLISHED)) { + gw.publishedRecording(new PublishedRecordingMessage(id)); + } } else { - recordingService.changeState(id, Recording.STATE_UNPUBLISHED); + if (recordingService.changeState(id, Recording.STATE_UNPUBLISHED)) { + gw.unpublishedRecording(new UnpublishedRecordingMessage(id)); + } } } } public void deleteRecordings(List<String> idList) { for (String id : idList) { - recordingService.changeState(id, Recording.STATE_DELETED); + if (recordingService.changeState(id, Recording.STATE_DELETED)) { + gw.deletedRecording(new DeletedRecordingMessage(id)); + } } } @@ -446,10 +443,21 @@ public class MeetingService implements MessageListener { recordingService.updateMetaParams(idList, metaParams); } + public void processRecording(Meeting m) { + if (m.isRecord()) { + Map<String, Object> logData = new HashMap<String, Object>(); + logData.put("meetingId", m.getInternalId()); + logData.put("externalMeetingId", m.getExternalId()); + logData.put("name", m.getName()); + logData.put("event", "kick_off_ingest_and_processing"); + logData.put("description", "Start processing of recording."); + Gson gson = new Gson(); + String logStr = gson.toJson(logData); - public void processRecording(String meetingId) { - recordingService.startIngestAndProcessing(meetingId); + log.info("Initiate recording processing: data={}", logStr); + recordingService.startIngestAndProcessing(m.getInternalId()); + } } public void endMeeting(String meetingId) { @@ -513,7 +521,7 @@ public class MeetingService implements MessageListener { Meeting m = getMeeting(message.meetingId); if (m != null) { m.setForciblyEnded(true); - processRecording(m.getInternalId()); + processRecording(m); destroyMeeting(m.getInternalId()); meetings.remove(m.getInternalId()); removeUserSessions(m.getInternalId()); diff --git a/bbb-common-web/src/main/java/org/bigbluebutton/api/RecordingService.java b/bbb-common-web/src/main/java/org/bigbluebutton/api/RecordingService.java index 57d5f2761a19a155fe7c4c7d1c65ed826145e224..0a18d237c6d2f8aa1e75b4ee093f61a7c498cc9d 100755 --- a/bbb-common-web/src/main/java/org/bigbluebutton/api/RecordingService.java +++ b/bbb-common-web/src/main/java/org/bigbluebutton/api/RecordingService.java @@ -398,46 +398,52 @@ public class RecordingService { return r; } - public void changeState(String recordingId, String state) { + public boolean changeState(String recordingId, String state) { + boolean succeeded = false; if (state.equals(Recording.STATE_PUBLISHED)) { // It can only be published if it is unpublished - changeState(unpublishedDir, recordingId, state); + succeeded |= changeState(unpublishedDir, recordingId, state); } else if (state.equals(Recording.STATE_UNPUBLISHED)) { // It can only be unpublished if it is published - changeState(publishedDir, recordingId, state); + succeeded |= changeState(publishedDir, recordingId, state); } else if (state.equals(Recording.STATE_DELETED)) { // It can be deleted from any state - changeState(publishedDir, recordingId, state); - changeState(unpublishedDir, recordingId, state); + succeeded |= changeState(publishedDir, recordingId, state); + succeeded |= changeState(unpublishedDir, recordingId, state); } + return succeeded; } - private void changeState(String path, String recordingId, String state) { + private boolean changeState(String path, String recordingId, String state) { + boolean exists = false; + boolean succeeded = true; String[] format = getPlaybackFormats(path); for (String aFormat : format) { List<File> recordings = getDirectories(path + File.separatorChar + aFormat); for (File recording : recordings) { if (recording.getName().equalsIgnoreCase(recordingId)) { + exists = true; File dest; if (state.equals(Recording.STATE_PUBLISHED)) { dest = new File(publishedDir + File.separatorChar + aFormat); - publishRecording(dest, recordingId, recording, aFormat); + succeeded &= publishRecording(dest, recordingId, recording, aFormat); } else if (state.equals(Recording.STATE_UNPUBLISHED)) { dest = new File(unpublishedDir + File.separatorChar + aFormat); - unpublishRecording(dest, recordingId, recording, aFormat); + succeeded &= unpublishRecording(dest, recordingId, recording, aFormat); } else if (state.equals(Recording.STATE_DELETED)) { dest = new File(deletedDir + File.separatorChar + aFormat); - deleteRecording(dest, recordingId, recording, aFormat); + succeeded &= deleteRecording(dest, recordingId, recording, aFormat); } else { log.debug(String.format("State: %s, is not supported", state)); - return; + return false; } } } } + return exists && succeeded; } - public void publishRecording(File destDir, String recordingId, File recordingDir, String format) { + public boolean publishRecording(File destDir, String recordingId, File recordingDir, String format) { File metadataXml = recordingServiceHelper.getMetadataXmlLocation(recordingDir.getPath()); RecordingMetadata r = recordingServiceHelper.getRecordingMetadata(metadataXml); if (r != null) { @@ -453,14 +459,15 @@ public class RecordingService { destDir.getAbsolutePath() + File.separatorChar + recordingId); // Process the changes by saving the recording into metadata.xml - recordingServiceHelper.saveRecordingMetadata(medataXmlFile, r); + return recordingServiceHelper.saveRecordingMetadata(medataXmlFile, r); } catch (IOException e) { log.error("Failed to publish recording : " + recordingId, e); } } + return false; } - public void unpublishRecording(File destDir, String recordingId, File recordingDir, String format) { + public boolean unpublishRecording(File destDir, String recordingId, File recordingDir, String format) { File metadataXml = recordingServiceHelper.getMetadataXmlLocation(recordingDir.getPath()); RecordingMetadata r = recordingServiceHelper.getRecordingMetadata(metadataXml); @@ -476,14 +483,15 @@ public class RecordingService { destDir.getAbsolutePath() + File.separatorChar + recordingId); // Process the changes by saving the recording into metadata.xml - recordingServiceHelper.saveRecordingMetadata(medataXmlFile, r); + return recordingServiceHelper.saveRecordingMetadata(medataXmlFile, r); } catch (IOException e) { log.error("Failed to unpublish recording : " + recordingId, e); } } + return false; } - public void deleteRecording(File destDir, String recordingId, File recordingDir, String format) { + public boolean deleteRecording(File destDir, String recordingId, File recordingDir, String format) { File metadataXml = recordingServiceHelper.getMetadataXmlLocation(recordingDir.getPath()); RecordingMetadata r = recordingServiceHelper.getRecordingMetadata(metadataXml); @@ -499,11 +507,12 @@ public class RecordingService { destDir.getAbsolutePath() + File.separatorChar + recordingId); // Process the changes by saving the recording into metadata.xml - recordingServiceHelper.saveRecordingMetadata(medataXmlFile, r); + return recordingServiceHelper.saveRecordingMetadata(medataXmlFile, r); } catch (IOException e) { log.error("Failed to delete recording : " + recordingId, e); } } + return false; } diff --git a/bbb-common-web/src/main/java/org/bigbluebutton/api/messaging/MessageDistributor.java b/bbb-common-web/src/main/java/org/bigbluebutton/api/messaging/MessageDistributor.java index d6f2837b40f3c7a4bc0df3744d340a689531b39a..007d16d51d374da794faf6dcc5027571841e3ea3 100755 --- a/bbb-common-web/src/main/java/org/bigbluebutton/api/messaging/MessageDistributor.java +++ b/bbb-common-web/src/main/java/org/bigbluebutton/api/messaging/MessageDistributor.java @@ -5,23 +5,23 @@ import java.util.Set; import org.bigbluebutton.api.messaging.messages.IMessage; public class MessageDistributor { - private ReceivedMessageHandler handler; - private Set<MessageListener> listeners; + private ReceivedMessageHandler handler; + private Set<MessageListener> listeners; - public void setMessageListeners(Set<MessageListener> listeners) { - this.listeners = listeners; - } + public void setMessageListeners(Set<MessageListener> listeners) { + this.listeners = listeners; + } - public void setMessageHandler(ReceivedMessageHandler handler) { - this.handler = handler; - if (handler != null) { - handler.setMessageDistributor(this); + public void setMessageHandler(ReceivedMessageHandler handler) { + this.handler = handler; + if (handler != null) { + handler.setMessageDistributor(this); + } } - } - public void notifyListeners(IMessage message) { - for (MessageListener listener : listeners) { - listener.handle(message); + public void notifyListeners(IMessage message) { + for (MessageListener listener : listeners) { + listener.handle(message); + } } - } } diff --git a/bbb-common-web/src/main/java/org/bigbluebutton/api/messaging/ReceivedMessageHandler.java b/bbb-common-web/src/main/java/org/bigbluebutton/api/messaging/ReceivedMessageHandler.java index 38690c4b1e1810135b329130eb6b436bfd8fd3c0..9a1171de745952d9d915efabcd3490a3328a8ab6 100755 --- a/bbb-common-web/src/main/java/org/bigbluebutton/api/messaging/ReceivedMessageHandler.java +++ b/bbb-common-web/src/main/java/org/bigbluebutton/api/messaging/ReceivedMessageHandler.java @@ -11,65 +11,65 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; public class ReceivedMessageHandler implements IReceivedOldMessageHandler { - private static Logger log = LoggerFactory.getLogger(ReceivedMessageHandler.class); + private static Logger log = LoggerFactory.getLogger(ReceivedMessageHandler.class); - private BlockingQueue<ReceivedMessage> receivedMessages = new LinkedBlockingQueue<>(); + private BlockingQueue<ReceivedMessage> receivedMessages = new LinkedBlockingQueue<>(); - private volatile boolean processMessage = false; + private volatile boolean processMessage = false; - private final Executor msgProcessorExec = Executors.newSingleThreadExecutor(); - private final Executor runExec = Executors.newSingleThreadExecutor(); + private final Executor msgProcessorExec = Executors.newSingleThreadExecutor(); + private final Executor runExec = Executors.newSingleThreadExecutor(); - private MessageDistributor outGW; + private MessageDistributor outGW; - public void stop() { - processMessage = false; - } + public void stop() { + processMessage = false; + } - public void start() { - log.info("Ready to handle messages from Redis pubsub!"); + public void start() { + log.info("Ready to handle messages from Redis pubsub!"); + + try { + processMessage = true; + + Runnable messageProcessor = new Runnable() { + public void run() { + while (processMessage) { + try { + ReceivedMessage msg = receivedMessages.take(); + processMessage(msg); + } catch (InterruptedException e) { + log.warn("Error while taking received message from queue."); + } + } + } + }; + msgProcessorExec.execute(messageProcessor); + } catch (Exception e) { + log.error("Error subscribing to channels: {}", e); + } + } - try { - processMessage = true; + private void notifyListeners(IMessage message) { + outGW.notifyListeners(message); + } - Runnable messageProcessor = new Runnable() { - public void run() { - while (processMessage) { - try { - ReceivedMessage msg = receivedMessages.take(); - processMessage(msg); - } catch (InterruptedException e) { - log.warn("Error while taking received message from queue."); + private void processMessage(final ReceivedMessage msg) { + Runnable task = new Runnable() { + public void run() { + notifyListeners(msg.getMessage()); } - } - } - }; - msgProcessorExec.execute(messageProcessor); - } catch (Exception e) { - log.error("Error subscribing to channels: {}", e); + }; + + runExec.execute(task); + } + + public void handleMessage(IMessage message) { + ReceivedMessage rm = new ReceivedMessage(message); + receivedMessages.add(rm); + } + + public void setMessageDistributor(MessageDistributor outGW) { + this.outGW = outGW; } - } - - private void notifyListeners(IMessage message) { - outGW.notifyListeners(message); - } - - private void processMessage(final ReceivedMessage msg) { - Runnable task = new Runnable() { - public void run() { - notifyListeners(msg.getMessage()); - } - }; - - runExec.execute(task); - } - - public void handleMessage(IMessage message) { - ReceivedMessage rm = new ReceivedMessage(message); - receivedMessages.add(rm); - } - - public void setMessageDistributor(MessageDistributor outGW) { - this.outGW = outGW; - } } diff --git a/bbb-common-web/src/main/java/org/bigbluebutton/api/messaging/RedisStorageService.java b/bbb-common-web/src/main/java/org/bigbluebutton/api/messaging/RedisStorageService.java deleted file mode 100755 index 180c95e5ce1517133df3bf5fb72d55aa967a6680..0000000000000000000000000000000000000000 --- a/bbb-common-web/src/main/java/org/bigbluebutton/api/messaging/RedisStorageService.java +++ /dev/null @@ -1,91 +0,0 @@ -package org.bigbluebutton.api.messaging; - -import java.util.Map; - -import org.apache.commons.pool2.impl.GenericObjectPoolConfig; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -import redis.clients.jedis.Jedis; -import redis.clients.jedis.JedisPool; -import redis.clients.jedis.Protocol; - -public class RedisStorageService { - private static Logger log = LoggerFactory.getLogger(RedisStorageService.class); - - private JedisPool redisPool; - private String host; - private int port; - - public void stop() { - - } - - public void start() { - // Set the name of this client to be able to distinguish when doing - // CLIENT LIST on redis-cli - redisPool = new JedisPool(new GenericObjectPoolConfig<Object>(), host, port, Protocol.DEFAULT_TIMEOUT, null, - Protocol.DEFAULT_DATABASE, "BbbRed5AppsPub"); - } - - public void recordMeetingInfo(String meetingId, Map<String, String> info) { - Jedis jedis = redisPool.getResource(); - try { - if (log.isDebugEnabled()) { - for (Map.Entry<String,String> entry : info.entrySet()) { - log.debug("Storing metadata {} = {}", entry.getKey(), entry.getValue()); - } - } - - log.debug("Saving metadata in {}", meetingId); - jedis.hmset("meeting:info:" + meetingId, info); - } catch (Exception e) { - log.warn("Cannot record the info meeting: {}", meetingId, e); - } finally { - jedis.close(); - } - } - - public void recordBreakoutInfo(String meetingId, Map<String, String> breakoutInfo) { - Jedis jedis = redisPool.getResource(); - try { - log.debug("Saving breakout metadata in {}", meetingId); - jedis.hmset("meeting:breakout:" + meetingId, breakoutInfo); - } catch (Exception e) { - log.warn("Cannot record the info meeting: {}", meetingId, e); - } finally { - jedis.close(); - } - } - - public void addBreakoutRoom(String parentId, String breakoutId) { - Jedis jedis = redisPool.getResource(); - try { - - log.debug("Saving breakout room for meeting {}", parentId); - jedis.sadd("meeting:breakout:rooms:" + parentId, breakoutId); - } catch (Exception e) { - log.warn("Cannot record the info meeting:" + parentId, e); - } finally { - jedis.close(); - } - } - - public void removeMeeting(String meetingId) { - Jedis jedis = redisPool.getResource(); - try { - jedis.del("meeting-" + meetingId); - jedis.srem("meetings", meetingId); - } finally { - jedis.close(); - } - } - - public void setHost(String host) { - this.host = host; - } - - public void setPort(int port) { - this.port = port; - } -} diff --git a/bbb-common-web/src/main/java/org/bigbluebutton/api/messaging/converters/messages/DeleteRecordingMessage.java b/bbb-common-web/src/main/java/org/bigbluebutton/api/messaging/converters/messages/DeleteRecordingMessage.java deleted file mode 100755 index dc79d97e64b6c49f5fa1bf0d862e2d09a01261d3..0000000000000000000000000000000000000000 --- a/bbb-common-web/src/main/java/org/bigbluebutton/api/messaging/converters/messages/DeleteRecordingMessage.java +++ /dev/null @@ -1,18 +0,0 @@ -package org.bigbluebutton.api.messaging.converters.messages; - -public class DeleteRecordingMessage { - public static final String DELETE_RECORDING = "deleted"; - public static final String VERSION = "0.0.1"; - - public final String recordId; - public final String meetingId; - public final String externalMeetingId; - public final String format; - - public DeleteRecordingMessage(String recordId, String meetingId, String externalMeetingId, String format) { - this.recordId = recordId; - this.meetingId = meetingId; - this.externalMeetingId = externalMeetingId; - this.format = format; - } -} diff --git a/bbb-common-web/src/main/java/org/bigbluebutton/api/messaging/converters/messages/DeletedRecordingMessage.java b/bbb-common-web/src/main/java/org/bigbluebutton/api/messaging/converters/messages/DeletedRecordingMessage.java new file mode 100755 index 0000000000000000000000000000000000000000..91b8318706ef00e45f1efcec608666216dd8ff19 --- /dev/null +++ b/bbb-common-web/src/main/java/org/bigbluebutton/api/messaging/converters/messages/DeletedRecordingMessage.java @@ -0,0 +1,12 @@ +package org.bigbluebutton.api.messaging.converters.messages; + +public class DeletedRecordingMessage { + public static final String DELETED_RECORDING_EVENT = "deleted_recording_event"; + public static final String VERSION = "0.0.1"; + + public final String recordId; + + public DeletedRecordingMessage(String recordId) { + this.recordId = recordId; + } +} diff --git a/bbb-common-web/src/main/java/org/bigbluebutton/api/messaging/converters/messages/PublishRecordingMessage.java b/bbb-common-web/src/main/java/org/bigbluebutton/api/messaging/converters/messages/PublishRecordingMessage.java deleted file mode 100755 index 509679ea7ef30cbb9b0cf02600a4b6180e4e79dd..0000000000000000000000000000000000000000 --- a/bbb-common-web/src/main/java/org/bigbluebutton/api/messaging/converters/messages/PublishRecordingMessage.java +++ /dev/null @@ -1,18 +0,0 @@ -package org.bigbluebutton.api.messaging.converters.messages; - -public class PublishRecordingMessage { - public static final String PUBLISH_RECORDING = "published"; - public static final String VERSION = "0.0.1"; - - public final String recordId; - public final String meetingId; - public final String externalMeetingId; - public final String format; - - public PublishRecordingMessage(String recordId, String meetingId, String externalMeetingId, String format) { - this.recordId = recordId; - this.meetingId = meetingId; - this.externalMeetingId = externalMeetingId; - this.format = format; - } -} diff --git a/bbb-common-web/src/main/java/org/bigbluebutton/api/messaging/converters/messages/PublishedRecordingMessage.java b/bbb-common-web/src/main/java/org/bigbluebutton/api/messaging/converters/messages/PublishedRecordingMessage.java new file mode 100755 index 0000000000000000000000000000000000000000..d85f55f2affba9bf48663f27984889ba53ac3dcb --- /dev/null +++ b/bbb-common-web/src/main/java/org/bigbluebutton/api/messaging/converters/messages/PublishedRecordingMessage.java @@ -0,0 +1,12 @@ +package org.bigbluebutton.api.messaging.converters.messages; + +public class PublishedRecordingMessage { + public static final String PUBLISHED_RECORDING_EVENT = "published_recording_event"; + public static final String VERSION = "0.0.1"; + + public final String recordId; + + public PublishedRecordingMessage(String recordId) { + this.recordId = recordId; + } +} diff --git a/bbb-common-web/src/main/java/org/bigbluebutton/api/messaging/converters/messages/UnpublishRecordingMessage.java b/bbb-common-web/src/main/java/org/bigbluebutton/api/messaging/converters/messages/UnpublishRecordingMessage.java deleted file mode 100755 index 822104657d6ed02d5e8f542bccc315fc0349967b..0000000000000000000000000000000000000000 --- a/bbb-common-web/src/main/java/org/bigbluebutton/api/messaging/converters/messages/UnpublishRecordingMessage.java +++ /dev/null @@ -1,18 +0,0 @@ -package org.bigbluebutton.api.messaging.converters.messages; - -public class UnpublishRecordingMessage { - public static final String UNPUBLISH_RECORDING = "unpublished"; - public static final String VERSION = "0.0.1"; - - public final String recordId; - public final String meetingId; - public final String externalMeetingId; - public final String format; - - public UnpublishRecordingMessage(String recordId, String meetingId, String externalMeetingId, String format) { - this.recordId = recordId; - this.meetingId = meetingId; - this.externalMeetingId = externalMeetingId; - this.format = format; - } -} diff --git a/bbb-common-web/src/main/java/org/bigbluebutton/api/messaging/converters/messages/UnpublishedRecordingMessage.java b/bbb-common-web/src/main/java/org/bigbluebutton/api/messaging/converters/messages/UnpublishedRecordingMessage.java new file mode 100755 index 0000000000000000000000000000000000000000..f02807eef1886fe290ba0c85ba7ccc79d281e00c --- /dev/null +++ b/bbb-common-web/src/main/java/org/bigbluebutton/api/messaging/converters/messages/UnpublishedRecordingMessage.java @@ -0,0 +1,12 @@ +package org.bigbluebutton.api.messaging.converters.messages; + +public class UnpublishedRecordingMessage { + public static final String UNPUBLISHED_RECORDING_EVENT = "unpublished_recording_event"; + public static final String VERSION = "0.0.1"; + + public final String recordId; + + public UnpublishedRecordingMessage(String recordId) { + this.recordId = recordId; + } +} diff --git a/bbb-common-web/src/main/java/org/bigbluebutton/api/util/RecordingMetadataReaderHelper.java b/bbb-common-web/src/main/java/org/bigbluebutton/api/util/RecordingMetadataReaderHelper.java index e979af4e5959b374bc8aa73cab2d27e69b23202f..6c7f045e43f3d063af610925f1f4b8c20026bca7 100755 --- a/bbb-common-web/src/main/java/org/bigbluebutton/api/util/RecordingMetadataReaderHelper.java +++ b/bbb-common-web/src/main/java/org/bigbluebutton/api/util/RecordingMetadataReaderHelper.java @@ -44,8 +44,8 @@ public class RecordingMetadataReaderHelper { return new File(destDir + File.separatorChar + "metadata.xml"); } - public void saveRecordingMetadata(File metadataXml, RecordingMetadata recordingMetadata) { - recordingServiceGW.saveRecordingMetadata(metadataXml, recordingMetadata); + public boolean saveRecordingMetadata(File metadataXml, RecordingMetadata recordingMetadata) { + return recordingServiceGW.saveRecordingMetadata(metadataXml, recordingMetadata); } public void setRecordingServiceGW(RecordingServiceGW recordingServiceGW) { diff --git a/bbb-common-web/src/main/java/org/bigbluebutton/api2/IBbbWebApiGWApp.java b/bbb-common-web/src/main/java/org/bigbluebutton/api2/IBbbWebApiGWApp.java index 0fbd8f10d703cd33dcbf123dc98d41b7ce759c5d..cced3e2f34cfb9d4ebfadb793eedc2191a59b84f 100755 --- a/bbb-common-web/src/main/java/org/bigbluebutton/api2/IBbbWebApiGWApp.java +++ b/bbb-common-web/src/main/java/org/bigbluebutton/api2/IBbbWebApiGWApp.java @@ -2,11 +2,11 @@ package org.bigbluebutton.api2; import java.util.Map; -import org.bigbluebutton.api.messaging.converters.messages.DeleteRecordingMessage; import org.bigbluebutton.api.messaging.converters.messages.DestroyMeetingMessage; import org.bigbluebutton.api.messaging.converters.messages.EndMeetingMessage; -import org.bigbluebutton.api.messaging.converters.messages.PublishRecordingMessage; -import org.bigbluebutton.api.messaging.converters.messages.UnpublishRecordingMessage; +import org.bigbluebutton.api.messaging.converters.messages.PublishedRecordingMessage; +import org.bigbluebutton.api.messaging.converters.messages.UnpublishedRecordingMessage; +import org.bigbluebutton.api.messaging.converters.messages.DeletedRecordingMessage; import org.bigbluebutton.presentation.messages.IDocConversionMsg; public interface IBbbWebApiGWApp { @@ -36,8 +36,8 @@ public interface IBbbWebApiGWApp { void destroyMeeting(DestroyMeetingMessage msg); void endMeeting(EndMeetingMessage msg); void sendKeepAlive(String system, Long timestamp); - void publishRecording(PublishRecordingMessage msg); - void unpublishRecording(UnpublishRecordingMessage msg); - void deleteRecording(DeleteRecordingMessage msg); + void publishedRecording(PublishedRecordingMessage msg); + void unpublishedRecording(UnpublishedRecordingMessage msg); + void deletedRecording(DeletedRecordingMessage msg); void sendDocConversionMsg(IDocConversionMsg msg); } diff --git a/bbb-common-web/src/main/java/org/bigbluebutton/api2/RecordingServiceGW.java b/bbb-common-web/src/main/java/org/bigbluebutton/api2/RecordingServiceGW.java index d23d4f2ec9b380fc7724b7c02e7f2055f32f0ef7..987d9f96b59a20cc3d1801e0cddc3cea864b8c05 100755 --- a/bbb-common-web/src/main/java/org/bigbluebutton/api2/RecordingServiceGW.java +++ b/bbb-common-web/src/main/java/org/bigbluebutton/api2/RecordingServiceGW.java @@ -12,7 +12,7 @@ public interface RecordingServiceGW { String getRecordings2x(ArrayList<RecordingMetadata> recs); Option<RecordingMetadata> getRecordingMetadata(File xml); - void saveRecordingMetadata(File xml, RecordingMetadata metadata); + boolean saveRecordingMetadata(File xml, RecordingMetadata metadata); String getRecordingTextTracks(String recordId, String captionsDir); String putRecordingTextTrack(UploadedTrack track); } diff --git a/bbb-common-web/src/main/java/org/bigbluebutton/presentation/imp/PdfToSwfSlidesGenerationService.java b/bbb-common-web/src/main/java/org/bigbluebutton/presentation/imp/PdfToSwfSlidesGenerationService.java index b8207611a15b1cb29fd6361ee8226bdba5afc129..ff6621bc1378efc9738112ac15b663cc7087e34c 100755 --- a/bbb-common-web/src/main/java/org/bigbluebutton/presentation/imp/PdfToSwfSlidesGenerationService.java +++ b/bbb-common-web/src/main/java/org/bigbluebutton/presentation/imp/PdfToSwfSlidesGenerationService.java @@ -54,14 +54,12 @@ public class PdfToSwfSlidesGenerationService { private TextFileCreator textFileCreator; private SvgImageCreator svgImageCreator; - private long MAX_CONVERSION_TIME = 5 * 60 * 1000L; + private long MAX_CONVERSION_TIME = 5 * 60 * 1000L * 1000L * 1000L; private String BLANK_SLIDE; private int MAX_SWF_FILE_SIZE; private boolean svgImagesRequired; private boolean generatePngs; - private static final long CONVERSION_TIMEOUT = 20000000000L; // 20s - public PdfToSwfSlidesGenerationService(int numConversionThreads) { executor = Executors.newFixedThreadPool(numConversionThreads); } @@ -194,7 +192,7 @@ public class PdfToSwfSlidesGenerationService { }; Future<PdfToSwfSlide> f = executor.submit(c); - long endNanos = System.nanoTime() + CONVERSION_TIMEOUT; + long endNanos = System.nanoTime() + MAX_CONVERSION_TIME; try { // Only wait for the remaining time budget long timeLeft = endNanos - System.nanoTime(); @@ -346,7 +344,7 @@ public class PdfToSwfSlidesGenerationService { } public void setMaxConversionTime(int minutes) { - MAX_CONVERSION_TIME = minutes * 60 * 1000L; + MAX_CONVERSION_TIME = minutes * 60 * 1000L * 1000L * 1000L; } public void setSwfSlidesGenerationProgressNotifier( diff --git a/bbb-common-web/src/main/java/org/bigbluebutton/web/services/RedisStorageService.java b/bbb-common-web/src/main/java/org/bigbluebutton/web/services/RedisStorageService.java deleted file mode 100755 index 3a45aedfd144ec9a6803672d81432e1c21420f7d..0000000000000000000000000000000000000000 --- a/bbb-common-web/src/main/java/org/bigbluebutton/web/services/RedisStorageService.java +++ /dev/null @@ -1,92 +0,0 @@ -/** -* BigBlueButton open source conferencing system - http://www.bigbluebutton.org/ -* -* Copyright (c) 2012 BigBlueButton Inc. and by respective authors (see below). -* -* This program is free software; you can redistribute it and/or modify it under the -* terms of the GNU Lesser General Public License as published by the Free Software -* Foundation; either version 3.0 of the License, or (at your option) any later -* version. -* -* BigBlueButton is distributed in the hope that it will be useful, but WITHOUT ANY -* WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A -* PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. -* -* You should have received a copy of the GNU Lesser General Public License along -* with BigBlueButton; if not, see <http://www.gnu.org/licenses/>. -* -*/ - -package org.bigbluebutton.web.services; - -import java.util.HashMap; -import java.util.Map; - -import org.bigbluebutton.api.domain.Poll; - -import redis.clients.jedis.Jedis; -import redis.clients.jedis.JedisPool; - -public class RedisStorageService implements IStorageService{ - JedisPool jedisPool; - - private static final String SEPARATOR = ":"; - private static final String ID_SEED = "nextID"; - - /* Meeting Patterns */ - private static final String MEETING = "meeting"; - private static final String POLL = "poll"; - private static final String POLL_ANSWER = "answer"; - private static final String POLL_RESULTS = "results"; - - /* -meeting:<id>:poll:list [1,2,3] <-- list -meeting:<id>:poll:<pollid> title, date <-- hash -meeting:<id>:poll:<pollid>:answer:list [1,2,3] <-- list -meeting:<id>:poll:<pollid>:answer:<answerid> answertext <-- key/value - -meeting:<id>:poll:<pollid>:answer:<answerid>:results [<userid>|1] <-- Set - */ - - public String generatePollID(String meetingID){ - Jedis jedis = (Jedis) jedisPool.getResource(); - String pattern = getPollRedisPattern(meetingID); - String pollID = Long.toString(jedis.incr(pattern + SEPARATOR + ID_SEED)); - jedisPool.returnResource(jedis); - return pollID; - } - - public String generatePollAnswerID(String meetingID){ - Jedis jedis = jedisPool.getResource(); - String pattern = getPollRedisPattern(meetingID); - String pollID = Long.toString(jedis.incr(pattern + SEPARATOR + POLL_ANSWER + SEPARATOR + ID_SEED)); - jedisPool.returnResource(jedis); - return pollID; - } - - public void storePoll(Poll p){ - Jedis jedis = jedisPool.getResource(); - String pattern = getPollRedisPattern(p.getMeetingID()); - - HashMap<String,String> pollMap = p.toMap(); - jedis.hmset(pattern + SEPARATOR + p.getPollID(), pollMap); - jedisPool.returnResource(jedis); - } - - public void storePollAnswers(String meetingID, String pollID, Map<String,String> answers){ - Jedis jedis = jedisPool.getResource(); - String pattern = getPollRedisPattern(meetingID); - - //HashMap<String,String> pollMap = p.toMap(); - //jedis.hmset(pattern + SEPARATOR + p.getPollID + SEPARATOR + POLL_ANSWER + SEPARATOR + ID_SEED, pollMap); - //jedisPool.returnResource(jedis); - } - - private String getPollRedisPattern(String meetingID){ - return MEETING + SEPARATOR + meetingID + SEPARATOR + POLL; - } - - public void setJedisPool(JedisPool jedisPool){ - this.jedisPool = jedisPool; - } -} \ No newline at end of file diff --git a/bbb-common-web/src/main/scala/org/bigbluebutton/api2/BbbWebApiGWApp.scala b/bbb-common-web/src/main/scala/org/bigbluebutton/api2/BbbWebApiGWApp.scala index 053c62ad567117e2120f8eb817a85ae55db7b081..dfce728857ffa53fe4112226e6c6c10f38abaee0 100755 --- a/bbb-common-web/src/main/scala/org/bigbluebutton/api2/BbbWebApiGWApp.scala +++ b/bbb-common-web/src/main/scala/org/bigbluebutton/api2/BbbWebApiGWApp.scala @@ -5,12 +5,15 @@ import akka.actor.ActorSystem import akka.event.Logging import org.bigbluebutton.api.messaging.converters.messages._ import org.bigbluebutton.api2.bus._ -import org.bigbluebutton.api2.endpoint.redis.{ AppsRedisSubscriberActor, MessageSender, RedisPublisher } +import org.bigbluebutton.api2.endpoint.redis.{ WebRedisSubscriberActor } +import org.bigbluebutton.common2.redis.MessageSender import org.bigbluebutton.api2.meeting.{ OldMeetingMsgHdlrActor, RegisterUser } import org.bigbluebutton.common2.domain._ +import org.bigbluebutton.common2.util.JsonUtil import org.bigbluebutton.presentation.messages._ - import scala.concurrent.duration._ +import org.bigbluebutton.common2.redis._ +import org.bigbluebutton.common2.bus._ class BbbWebApiGWApp( val oldMessageReceivedGW: OldMessageReceivedGW, @@ -24,10 +27,8 @@ class BbbWebApiGWApp( val log = Logging(system, getClass) - log.debug("*********** meetingManagerChannel = " + meetingManagerChannel) - private val jsonMsgToAkkaAppsBus = new JsonMsgToAkkaAppsBus - private val redisPublisher = new RedisPublisher(system) + private val redisPublisher = new RedisPublisher(system, "BbbWebPub") private val msgSender: MessageSender = new MessageSender(redisPublisher) private val messageSenderActorRef = system.actorOf(MessageSenderActor.props(msgSender), "messageSenderActor") @@ -54,8 +55,7 @@ class BbbWebApiGWApp( msgToAkkaAppsEventBus.subscribe(msgToAkkaAppsToJsonActor, toAkkaAppsChannel) - private val appsRedisSubscriberActor = system.actorOf( - AppsRedisSubscriberActor.props(receivedJsonMsgBus, oldMessageEventBus), "appsRedisSubscriberActor") + private val appsRedisSubscriberActor = system.actorOf(WebRedisSubscriberActor.props(system, receivedJsonMsgBus, oldMessageEventBus), "appsRedisSubscriberActor") private val receivedJsonMsgHdlrActor = system.actorOf( ReceivedJsonMsgHdlrActor.props(msgFromAkkaAppsEventBus), "receivedJsonMsgHdlrActor") @@ -166,19 +166,24 @@ class BbbWebApiGWApp( def sendKeepAlive(system: String, timestamp: java.lang.Long): Unit = { val event = MsgBuilder.buildCheckAlivePingSysMsg(system, timestamp.longValue()) msgToAkkaAppsEventBus.publish(MsgToAkkaApps(toAkkaAppsChannel, event)) - } - def publishRecording(msg: PublishRecordingMessage): Unit = { - + def publishedRecording(msg: PublishedRecordingMessage): Unit = { + val event = MsgBuilder.buildPublishedRecordingSysMsg(msg) + // Probably violating something here, but a new event bus looks just too much for this + msgSender.send(fromBbbWebRedisChannel, JsonUtil.toJson(event)) } - def unpublishRecording(msg: UnpublishRecordingMessage): Unit = { - + def unpublishedRecording(msg: UnpublishedRecordingMessage): Unit = { + val event = MsgBuilder.buildUnpublishedRecordingSysMsg(msg) + // Probably violating something here, but a new event bus looks just too much for this + msgSender.send(fromBbbWebRedisChannel, JsonUtil.toJson(event)) } - def deleteRecording(msg: DeleteRecordingMessage): Unit = { - + def deletedRecording(msg: DeletedRecordingMessage): Unit = { + val event = MsgBuilder.buildDeletedRecordingSysMsg(msg) + // Probably violating something here, but a new event bus looks just too much for this + msgSender.send(fromBbbWebRedisChannel, JsonUtil.toJson(event)) } def sendDocConversionMsg(msg: IDocConversionMsg): Unit = { diff --git a/bbb-common-web/src/main/scala/org/bigbluebutton/api2/MsgBuilder.scala b/bbb-common-web/src/main/scala/org/bigbluebutton/api2/MsgBuilder.scala index e71f7ff1301cc1bcdd65536536bf1b8b3fda99ef..ba5bcd29bc864cbdb7862c51fe74b00354af95e9 100755 --- a/bbb-common-web/src/main/scala/org/bigbluebutton/api2/MsgBuilder.scala +++ b/bbb-common-web/src/main/scala/org/bigbluebutton/api2/MsgBuilder.scala @@ -1,6 +1,6 @@ package org.bigbluebutton.api2 -import org.bigbluebutton.api.messaging.converters.messages.{DestroyMeetingMessage, EndMeetingMessage} +import org.bigbluebutton.api.messaging.converters.messages._ import org.bigbluebutton.api2.meeting.RegisterUser import org.bigbluebutton.common2.domain.{DefaultProps, PageVO, PresentationVO} import org.bigbluebutton.common2.msgs._ @@ -143,4 +143,31 @@ object MsgBuilder { val req = PresentationPageCountErrorSysPubMsg(header, body) BbbCommonEnvCoreMsg(envelope, req) } + + def buildPublishedRecordingSysMsg(msg: PublishedRecordingMessage): BbbCommonEnvCoreMsg = { + val routing = collection.immutable.HashMap("sender" -> "bbb-web") + val envelope = BbbCoreEnvelope(PublishedRecordingSysMsg.NAME, routing) + val header = BbbCoreBaseHeader(PublishedRecordingSysMsg.NAME) + val body = PublishedRecordingSysMsgBody(msg.recordId) + val req = PublishedRecordingSysMsg(header, body) + BbbCommonEnvCoreMsg(envelope, req) + } + + def buildUnpublishedRecordingSysMsg(msg: UnpublishedRecordingMessage): BbbCommonEnvCoreMsg = { + val routing = collection.immutable.HashMap("sender" -> "bbb-web") + val envelope = BbbCoreEnvelope(UnpublishedRecordingSysMsg.NAME, routing) + val header = BbbCoreBaseHeader(UnpublishedRecordingSysMsg.NAME) + val body = UnpublishedRecordingSysMsgBody(msg.recordId) + val req = UnpublishedRecordingSysMsg(header, body) + BbbCommonEnvCoreMsg(envelope, req) + } + + def buildDeletedRecordingSysMsg(msg: DeletedRecordingMessage): BbbCommonEnvCoreMsg = { + val routing = collection.immutable.HashMap("sender" -> "bbb-web") + val envelope = BbbCoreEnvelope(DeletedRecordingSysMsg.NAME, routing) + val header = BbbCoreBaseHeader(DeletedRecordingSysMsg.NAME) + val body = DeletedRecordingSysMsgBody(msg.recordId) + val req = DeletedRecordingSysMsg(header, body) + BbbCommonEnvCoreMsg(envelope, req) + } } diff --git a/bbb-common-web/src/main/scala/org/bigbluebutton/api2/SystemConfiguration.scala b/bbb-common-web/src/main/scala/org/bigbluebutton/api2/SystemConfiguration.scala index 0eb67da98170ac2b5304922e745ffcd835351719..71d5d402c9dbc462ff2206677a22e5912f4ed5eb 100755 --- a/bbb-common-web/src/main/scala/org/bigbluebutton/api2/SystemConfiguration.scala +++ b/bbb-common-web/src/main/scala/org/bigbluebutton/api2/SystemConfiguration.scala @@ -3,17 +3,11 @@ package org.bigbluebutton.api2 import com.typesafe.config.ConfigFactory import scala.util.Try +import org.bigbluebutton.common2.redis.RedisConfiguration -trait SystemConfiguration { - val config = ConfigFactory.load("bbb-web") +trait SystemConfiguration extends RedisConfiguration { + override val config = ConfigFactory.load("bbb-web") - lazy val redisHost = Try(config.getString("redis.host")).getOrElse("127.0.0.1") - lazy val redisPort = Try(config.getInt("redis.port")).getOrElse(6379) - lazy val redisPassword = Try(config.getString("redis.password")).getOrElse("") - - lazy val toAkkaAppsRedisChannel = Try(config.getString("redis.toAkkaAppsRedisChannel")).getOrElse("to-akka-apps-redis-channel") - lazy val fromAkkaAppsRedisChannel = Try(config.getString("redis.fromAkkaAppsRedisChannel")).getOrElse("from-akka-apps-redis-channel") - lazy val meetingManagerChannel = Try(config.getString("eventBus.meetingManagerChannel")).getOrElse("FOOOOOOOOO") lazy val fromAkkaAppsChannel = Try(config.getString("eventBus.fromAkkaAppsChannel")).getOrElse("from-akka-apps-channel") lazy val toAkkaAppsChannel = Try(config.getString("eventBus.toAkkaAppsChannel")).getOrElse("to-akka-apps-channel") lazy val fromClientChannel = Try(config.getString("eventBus.fromClientChannel")).getOrElse("from-client-channel") diff --git a/bbb-common-web/src/main/scala/org/bigbluebutton/api2/bus/MessageSenderActor.scala b/bbb-common-web/src/main/scala/org/bigbluebutton/api2/bus/MessageSenderActor.scala index f4dc981cc96c2d79547f5d62693aa8da83c33558..e21612025c28d43587c0f715fe81455e67ffd03b 100755 --- a/bbb-common-web/src/main/scala/org/bigbluebutton/api2/bus/MessageSenderActor.scala +++ b/bbb-common-web/src/main/scala/org/bigbluebutton/api2/bus/MessageSenderActor.scala @@ -4,7 +4,7 @@ import java.io.{PrintWriter, StringWriter} import akka.actor.SupervisorStrategy.Resume import akka.actor.{Actor, ActorLogging, OneForOneStrategy, Props} -import org.bigbluebutton.api2.endpoint.redis.MessageSender +import org.bigbluebutton.common2.redis.MessageSender import scala.concurrent.duration._ object MessageSenderActor { diff --git a/bbb-common-web/src/main/scala/org/bigbluebutton/api2/bus/OldMessageJsonReceiverActor.scala b/bbb-common-web/src/main/scala/org/bigbluebutton/api2/bus/OldMessageJsonReceiverActor.scala index f996ad30ef5c87f4b417d238639ffc3facd34f76..ab5af661bf3f1fec0935d364ce2e9c027538813d 100755 --- a/bbb-common-web/src/main/scala/org/bigbluebutton/api2/bus/OldMessageJsonReceiverActor.scala +++ b/bbb-common-web/src/main/scala/org/bigbluebutton/api2/bus/OldMessageJsonReceiverActor.scala @@ -1,6 +1,7 @@ package org.bigbluebutton.api2.bus -import akka.actor.{Actor, ActorLogging, Props} +import akka.actor.{Actor, ActorLogging, Props} +import org.bigbluebutton.common2.bus.OldReceivedJsonMessage object OldMessageJsonReceiverActor{ def props(gw: OldMessageReceivedGW): Props = Props(classOf[OldMessageJsonReceiverActor], gw) diff --git a/bbb-common-web/src/main/scala/org/bigbluebutton/api2/bus/ReceivedJsonMsgHdlrActor.scala b/bbb-common-web/src/main/scala/org/bigbluebutton/api2/bus/ReceivedJsonMsgHdlrActor.scala index e63f2d454eecd42ea026bab87933fa10d02dde58..174e4951dfd5fad4a2a6c1dbd980207d78999111 100755 --- a/bbb-common-web/src/main/scala/org/bigbluebutton/api2/bus/ReceivedJsonMsgHdlrActor.scala +++ b/bbb-common-web/src/main/scala/org/bigbluebutton/api2/bus/ReceivedJsonMsgHdlrActor.scala @@ -1,6 +1,7 @@ package org.bigbluebutton.api2.bus import org.bigbluebutton.api2.SystemConfiguration +import org.bigbluebutton.common2.bus._ import org.bigbluebutton.common2.msgs._ import com.fasterxml.jackson.databind.JsonNode import akka.actor.Actor diff --git a/bbb-common-web/src/main/scala/org/bigbluebutton/api2/domain/Recording.scala b/bbb-common-web/src/main/scala/org/bigbluebutton/api2/domain/Recording.scala index ef3ad2841b4339afe9ab223a5a09a27acdf9181a..1584b300207bef03c2de675264cf8726bc972436 100755 --- a/bbb-common-web/src/main/scala/org/bigbluebutton/api2/domain/Recording.scala +++ b/bbb-common-web/src/main/scala/org/bigbluebutton/api2/domain/Recording.scala @@ -243,7 +243,7 @@ case class RecMeta(id: String, meetingId: String, internalMeetingId: Option[ Str val startTimeElem = <startTime>{startTime}</startTime> val endTimeElem = <endTime>{endTime}</endTime> val participantsElem = <participants>{participants}</participants> - + val rawSizeElem = <rawSize>{rawSize}</rawSize> val buffer = new scala.xml.NodeBuffer buffer += recordIdElem @@ -256,6 +256,7 @@ case class RecMeta(id: String, meetingId: String, internalMeetingId: Option[ Str buffer += startTimeElem buffer += endTimeElem buffer += participantsElem + buffer += rawSizeElem meta foreach (m => buffer += metaToElem(m)) breakout foreach (b => buffer += b.toXml()) @@ -285,6 +286,7 @@ case class RecMeta(id: String, meetingId: String, internalMeetingId: Option[ Str buffer += startTimeElem buffer += endTimeElem buffer += participantsElem + buffer += rawSizeElem meeting foreach { m => buffer += m.toMetadataXml() @@ -320,8 +322,6 @@ case class RecMeta(id: String, meetingId: String, internalMeetingId: Option[ Str dataMetrics foreach(p => buffer += p.toMetadataXml()) - buffer += rawSizeElem - <recording>{buffer}</recording> } } @@ -346,11 +346,13 @@ case class RecMetaPlayback(format: String, link: String, processingTime: Int, val urlElem = <url>{link}</url> val processTimeElem = <processingTime>{processingTime}</processingTime> val lengthElem = <length>{duration / 60000}</length> + val sizeElem = <size>{size}</size> buffer += formatElem buffer += urlElem buffer += processTimeElem buffer += lengthElem + buffer += sizeElem extensions foreach {ext => ext.head.child foreach {child => @@ -369,12 +371,13 @@ case class RecMetaPlayback(format: String, link: String, processingTime: Int, val urlElem = <link>{link}</link> val processTimeElem = <processing_time>{processingTime}</processing_time> val lengthElem = <duration>{duration}</duration> + val sizeElem = <size>{size}</size> buffer += formatElem buffer += urlElem buffer += processTimeElem buffer += lengthElem - + buffer += sizeElem extensions foreach {ext => buffer += ext.head @@ -390,11 +393,13 @@ case class RecMetaPlayback(format: String, link: String, processingTime: Int, val urlElem = <url>{link}</url> val processTimeElem = <processingTime>{processingTime}</processingTime> val lengthElem = <length>{duration / 60000}</length> + val sizeElem = <size>{size}</size> buffer += formatElem buffer += urlElem buffer += processTimeElem buffer += lengthElem + buffer += sizeElem extensions foreach {ext => ext.head.child foreach {child => @@ -530,6 +535,7 @@ case class RecMetaResponse( val startTimeElem = <startTime>{startTime}</startTime> val endTimeElem = <endTime>{endTime}</endTime> val participantsElem = <participants>{participants}</participants> + val rawSizeElem = <rawSize>{rawSize}</rawSize> val buffer = new scala.xml.NodeBuffer buffer += recordIdElem @@ -542,6 +548,7 @@ case class RecMetaResponse( buffer += startTimeElem buffer += endTimeElem buffer += participantsElem + buffer += rawSizeElem meta foreach (m => buffer += metaToElem(m)) breakout foreach (b => buffer += b.toXml()) @@ -551,7 +558,13 @@ case class RecMetaResponse( // Iterate over all formats before include the playback tag val formats = new scala.xml.NodeBuffer - playbacks foreach(p => formats += p.toFormatXml()) + var size = 0L + playbacks foreach(p => { + size += p.size + formats += p.toFormatXml() + }) + val sizeElem = <size>{size}</size> + buffer += sizeElem val playbackElem = <playback>{formats}</playback> buffer += playbackElem diff --git a/bbb-common-web/src/main/scala/org/bigbluebutton/api2/endpoint/redis/AppsRedisSubscriberActor.scala b/bbb-common-web/src/main/scala/org/bigbluebutton/api2/endpoint/redis/AppsRedisSubscriberActor.scala deleted file mode 100755 index 3b566c6f2f90eedc8969cb7b3346aef349c69a02..0000000000000000000000000000000000000000 --- a/bbb-common-web/src/main/scala/org/bigbluebutton/api2/endpoint/redis/AppsRedisSubscriberActor.scala +++ /dev/null @@ -1,63 +0,0 @@ -package org.bigbluebutton.api2.endpoint.redis - -import java.io.{PrintWriter, StringWriter} -import java.net.InetSocketAddress - -import akka.actor.SupervisorStrategy.Resume -import akka.actor.{OneForOneStrategy, Props} -import redis.api.servers.ClientSetname -import redis.actors.RedisSubscriberActor -import redis.api.pubsub.{Message, PMessage} -import scala.concurrent.duration._ - -import org.bigbluebutton.api2.SystemConfiguration -import org.bigbluebutton.api2.bus._ - -object AppsRedisSubscriberActor extends SystemConfiguration { - - val channels = Seq(fromAkkaAppsRedisChannel) - val patterns = Seq("bigbluebutton:from-bbb-apps:*") - - def props(jsonMsgBus: JsonMsgFromAkkaAppsBus, oldMessageEventBus: OldMessageEventBus): Props = - Props(classOf[AppsRedisSubscriberActor], jsonMsgBus, oldMessageEventBus, - redisHost, redisPort, - channels, patterns).withDispatcher("akka.rediscala-subscriber-worker-dispatcher") -} - -class AppsRedisSubscriberActor(jsonMsgBus: JsonMsgFromAkkaAppsBus, oldMessageEventBus: OldMessageEventBus, redisHost: String, - redisPort: Int, - channels: Seq[String] = Nil, patterns: Seq[String] = Nil) - extends RedisSubscriberActor(new InetSocketAddress(redisHost, redisPort), - channels, patterns, onConnectStatus = connected => { println(s"connected: $connected") }) with SystemConfiguration { - - override val supervisorStrategy = OneForOneStrategy(maxNrOfRetries = 10, withinTimeRange = 1 minute) { - case e: Exception => { - val sw: StringWriter = new StringWriter() - sw.write("An exception has been thrown on AppsRedisSubscriberActor, exception message [" + e.getMessage() + "] (full stacktrace below)\n") - e.printStackTrace(new PrintWriter(sw)) - log.error(sw.toString()) - Resume - } - } - - - // Set the name of this client to be able to distinguish when doing - // CLIENT LIST on redis-cli - write(ClientSetname("Red5AppsSub").encodedRequest) - - def onMessage(message: Message) { - //log.error(s"SHOULD NOT BE RECEIVING: $message") - if (message.channel == fromAkkaAppsRedisChannel) { - val receivedJsonMessage = new JsonMsgFromAkkaApps(message.channel, message.data.utf8String) - jsonMsgBus.publish(JsonMsgFromAkkaAppsEvent(fromAkkaAppsJsonChannel, receivedJsonMessage)) - } - } - - def onPMessage(pmessage: PMessage) { - log.debug(s"RECEIVED:\n ${pmessage.data.utf8String} \n") - val receivedJsonMessage = new OldReceivedJsonMessage(pmessage.patternMatched, - pmessage.channel, pmessage.data.utf8String) - - oldMessageEventBus.publish(OldIncomingJsonMessage(fromAkkaAppsOldJsonChannel, receivedJsonMessage)) - } -} diff --git a/bbb-common-web/src/main/scala/org/bigbluebutton/api2/endpoint/redis/RedisDataStorageActor.scala b/bbb-common-web/src/main/scala/org/bigbluebutton/api2/endpoint/redis/RedisDataStorageActor.scala index 8fb2731d4204bd661b5c1762549593439adf1066..e4d6628018a0a84ee2c596c3b12221c4b1393c86 100755 --- a/bbb-common-web/src/main/scala/org/bigbluebutton/api2/endpoint/redis/RedisDataStorageActor.scala +++ b/bbb-common-web/src/main/scala/org/bigbluebutton/api2/endpoint/redis/RedisDataStorageActor.scala @@ -1,51 +1,48 @@ package org.bigbluebutton.api2.endpoint.redis -import akka.actor.{Actor, ActorLogging, ActorSystem, Props} import org.bigbluebutton.api2.SystemConfiguration -import redis.RedisClient +import org.bigbluebutton.common2.redis.RedisStorageProvider +import akka.actor.Actor +import akka.actor.ActorLogging +import akka.actor.ActorSystem +import akka.actor.Props case class RecordMeetingInfoMsg(meetingId: String, info: collection.immutable.Map[String, String]) case class RecordBreakoutInfoMsg(meetingId: String, info: collection.immutable.Map[String, String]) case class AddBreakoutRoomMsg(parentId: String, breakoutId: String) case class RemoveMeetingMsg(meetingId: String) - object RedisDataStorageActor { def props(system: ActorSystem): Props = Props(classOf[RedisDataStorageActor], system) } -class RedisDataStorageActor(val system: ActorSystem) extends Actor with ActorLogging with SystemConfiguration { - - val redis = RedisClient(redisHost, redisPort)(system) - - // Set the name of this client to be able to distinguish when doing - // CLIENT LIST on redis-cli - redis.clientSetname("BbbWebStore") +class RedisDataStorageActor(val system: ActorSystem) + extends RedisStorageProvider(system, "BbbWebStore") + with SystemConfiguration + with Actor with ActorLogging { def receive = { - case msg: RecordMeetingInfoMsg => handleRecordMeetingInfoMsg(msg) + case msg: RecordMeetingInfoMsg => handleRecordMeetingInfoMsg(msg) case msg: RecordBreakoutInfoMsg => handleRecordBreakoutInfoMsg(msg) - case msg: AddBreakoutRoomMsg => handleAddBreakoutRoomMsg(msg) - case msg: RemoveMeetingMsg => handleRemoveMeetingMsg(msg) + case msg: AddBreakoutRoomMsg => handleAddBreakoutRoomMsg(msg) + case msg: RemoveMeetingMsg => handleRemoveMeetingMsg(msg) } - def handleRecordMeetingInfoMsg(msg: RecordMeetingInfoMsg): Unit = { - redis.hmset("meeting:info:" + msg.meetingId, msg.info) + redis.recordMeetingInfo(msg.meetingId, msg.info.asInstanceOf[java.util.Map[java.lang.String, java.lang.String]]); } def handleRecordBreakoutInfoMsg(msg: RecordBreakoutInfoMsg): Unit = { - redis.hmset("meeting:breakout:" + msg.meetingId, msg.info) + redis.recordBreakoutInfo(msg.meetingId, msg.info.asInstanceOf[java.util.Map[java.lang.String, java.lang.String]]) } def handleAddBreakoutRoomMsg(msg: AddBreakoutRoomMsg): Unit = { - redis.sadd("meeting:breakout:rooms:" + msg.parentId, msg.breakoutId) + redis.addBreakoutRoom(msg.parentId, msg.breakoutId) } def handleRemoveMeetingMsg(msg: RemoveMeetingMsg): Unit = { - redis.del("meeting-" + msg.meetingId) - redis.srem("meetings", msg.meetingId) + redis.removeMeeting(msg.meetingId) } } diff --git a/bbb-common-web/src/main/scala/org/bigbluebutton/api2/endpoint/redis/RedisPublisher.scala b/bbb-common-web/src/main/scala/org/bigbluebutton/api2/endpoint/redis/RedisPublisher.scala deleted file mode 100755 index 23e47ddd06d00f8173032f08c5d585c354129f5f..0000000000000000000000000000000000000000 --- a/bbb-common-web/src/main/scala/org/bigbluebutton/api2/endpoint/redis/RedisPublisher.scala +++ /dev/null @@ -1,24 +0,0 @@ -package org.bigbluebutton.api2.endpoint.redis - -import akka.actor.ActorSystem -import akka.event.Logging -import akka.util.ByteString -import org.bigbluebutton.api2.SystemConfiguration -import redis.RedisClient - -class RedisPublisher(val system: ActorSystem) extends SystemConfiguration { - - val redis = RedisClient(redisHost, redisPort)(system) - - val log = Logging(system, getClass) - - // Set the name of this client to be able to distinguish when doing - // CLIENT LIST on redis-cli - redis.clientSetname("BbbWebPub") - - def publish(channel: String, data: String) { - //log.debug("PUBLISH TO \n[" + channel + "]: \n " + data + "\n") - redis.publish(channel, ByteString(data)) - } - -} diff --git a/bbb-common-web/src/main/scala/org/bigbluebutton/api2/endpoint/redis/WebRedisSubscriberActor.scala b/bbb-common-web/src/main/scala/org/bigbluebutton/api2/endpoint/redis/WebRedisSubscriberActor.scala new file mode 100755 index 0000000000000000000000000000000000000000..bcb2e4b3d26ebbe6d7d9b7573849162364e3f35b --- /dev/null +++ b/bbb-common-web/src/main/scala/org/bigbluebutton/api2/endpoint/redis/WebRedisSubscriberActor.scala @@ -0,0 +1,53 @@ +package org.bigbluebutton.api2.endpoint.redis + +import org.bigbluebutton.api2.SystemConfiguration +import org.bigbluebutton.common2.bus._ +import org.bigbluebutton.common2.redis.{ RedisConfiguration, RedisSubscriber, RedisSubscriberProvider } + +import akka.actor.ActorSystem +import akka.actor.Props +import io.lettuce.core.pubsub.RedisPubSubListener + +object WebRedisSubscriberActor extends RedisSubscriber with RedisConfiguration { + + val channels = Seq(fromAkkaAppsRedisChannel) + val patterns = Seq("bigbluebutton:from-bbb-apps:*") + + def props(system: ActorSystem, jsonMsgBus: JsonMsgFromAkkaAppsBus, oldMessageEventBus: OldMessageEventBus): Props = + Props( + classOf[WebRedisSubscriberActor], + system, jsonMsgBus, oldMessageEventBus, + redisHost, redisPort, + channels, patterns).withDispatcher("akka.redis-subscriber-worker-dispatcher") +} + +class WebRedisSubscriberActor( + system: ActorSystem, + jsonMsgBus: JsonMsgFromAkkaAppsBus, oldMessageEventBus: OldMessageEventBus, redisHost: String, + redisPort: Int, + channels: Seq[String] = Nil, patterns: Seq[String] = Nil) + extends RedisSubscriberProvider(system, "BbbWebSub", channels, patterns, null) with SystemConfiguration { + + override def addListener(appChannel: String) { + connection.addListener(new RedisPubSubListener[String, String] { + def message(channel: String, message: String): Unit = { + if (channels.contains(channel)) { + val receivedJsonMessage = new JsonMsgFromAkkaApps(channel, message) + jsonMsgBus.publish(JsonMsgFromAkkaAppsEvent(fromAkkaAppsJsonChannel, receivedJsonMessage)) + } + } + def message(pattern: String, channel: String, message: String): Unit = { + log.debug(s"RECEIVED:\n ${message} \n") + val receivedJsonMessage = new OldReceivedJsonMessage(pattern, channel, message) + oldMessageEventBus.publish(OldIncomingJsonMessage(fromAkkaAppsOldJsonChannel, receivedJsonMessage)) + } + def psubscribed(pattern: String, count: Long): Unit = { log.info("Subscribed to pattern {}", pattern) } + def punsubscribed(pattern: String, count: Long): Unit = { log.info("Unsubscribed from pattern {}", pattern) } + def subscribed(channel: String, count: Long): Unit = { log.info("Subscribed to pattern {}", channel) } + def unsubscribed(channel: String, count: Long): Unit = { log.info("Unsubscribed from channel {}", channel) } + }) + } + + addListener(fromAkkaAppsJsonChannel) + subscribe() +} diff --git a/bbb-common-web/src/main/scala/org/bigbluebutton/api2/util/RecMetaXmlHelper.scala b/bbb-common-web/src/main/scala/org/bigbluebutton/api2/util/RecMetaXmlHelper.scala index e57516da9293fcb05f81a933154189efa5faeb8f..e81e0c743cc78f94fb082ce485834e9ca4e1dbe7 100755 --- a/bbb-common-web/src/main/scala/org/bigbluebutton/api2/util/RecMetaXmlHelper.scala +++ b/bbb-common-web/src/main/scala/org/bigbluebutton/api2/util/RecMetaXmlHelper.scala @@ -42,7 +42,8 @@ class RecMetaXmlHelper extends RecordingServiceGW with LogHelper { } } - def saveRecordingMetadata(xml: File, metadata: RecordingMetadata): Unit = { + def saveRecordingMetadata(xml: File, metadata: RecordingMetadata): Boolean = { + var result = false try { val Encoding = StandardCharsets.UTF_8.name() val pp = new PrettyPrinter(80, 2) @@ -52,6 +53,7 @@ class RecMetaXmlHelper extends RecordingServiceGW with LogHelper { try { writer.write("<?xml version='1.0' encoding='" + Encoding + "'?>\n") writer.write(pp.format(metadata.getRecMeta.toMetadataXml())) + result = true } catch { case ex: Exception => logger.info("Exception while saving {}", xml.getAbsolutePath) @@ -66,6 +68,7 @@ class RecMetaXmlHelper extends RecordingServiceGW with LogHelper { logger.info("Exception while saving {}", xml.getAbsolutePath) logger.info("Exception details: {}", ex.fillInStackTrace()) } + result } def getRecordingMetadata(xml: File): Option[RecordingMetadata] = { diff --git a/bbb-common-web/src/test/scala/org/bigbluebutton/api/util/ParamsUtilTest.scala b/bbb-common-web/src/test/scala/org/bigbluebutton/api/util/ParamsUtilTest.scala index 6cf5716b067425c1a5a4e5372b58049bc24d2632..460701c67a52074344736bacfea2d03adfbb3066 100755 --- a/bbb-common-web/src/test/scala/org/bigbluebutton/api/util/ParamsUtilTest.scala +++ b/bbb-common-web/src/test/scala/org/bigbluebutton/api/util/ParamsUtilTest.scala @@ -1,23 +1,21 @@ -package org.bigbluebutton.api.util - -import org.scalatest._ - -class ParamsUtilTest extends UnitSpec { - - it should "strip out control chars from text" in { - val text = "a\u0000b\u0007c\u008fd" - val cleaned = ParamsUtil.stripControlChars(text) - assert("abcd" == cleaned) - } - - it should "complain about invalid chars in meetingId" in { - val meetingId = "Demo , Meeting" - assert(ParamsUtil.isValidMeetingId(meetingId) == false) - } - - it should "accept valid chars in meetingId" in { - val meetingId = "Demo Meeting - 123" - assert(ParamsUtil.isValidMeetingId(meetingId) == true) - } - -} +package org.bigbluebutton.api.util + +class ParamsUtilTest extends UnitSpec { + + it should "strip out control chars from text" in { + val text = "a\u0000b\u0007c\u008fd" + val cleaned = ParamsUtil.stripControlChars(text) + assert("abcd" == cleaned) + } + + it should "complain about invalid chars in meetingId" in { + val meetingId = "Demo , Meeting" + assert(ParamsUtil.isValidMeetingId(meetingId) == false) + } + + it should "accept valid chars in meetingId" in { + val meetingId = "Demo Meeting - 123" + assert(ParamsUtil.isValidMeetingId(meetingId) == true) + } + +} diff --git a/bbb-common-web/src/test/scala/org/bigbluebutton/api/util/UnitSpec.scala b/bbb-common-web/src/test/scala/org/bigbluebutton/api/util/UnitSpec.scala index fef2d5a231b50c2ea1597f488d4533cd972b040c..6c5694d0c06fd995a03851556746f84cefeeabc4 100755 --- a/bbb-common-web/src/test/scala/org/bigbluebutton/api/util/UnitSpec.scala +++ b/bbb-common-web/src/test/scala/org/bigbluebutton/api/util/UnitSpec.scala @@ -1,8 +1,7 @@ -package org.bigbluebutton.api.util - -import org.scalatest.FlatSpec -import org.scalatest.BeforeAndAfterAll -import org.scalatest.WordSpec -import org.scalatest.Matchers - -abstract class UnitSpec extends FlatSpec with Matchers with BeforeAndAfterAll +package org.bigbluebutton.api.util + +import org.scalatest.FlatSpec +import org.scalatest.BeforeAndAfterAll +import org.scalatest.Matchers + +abstract class UnitSpec extends FlatSpec with Matchers with BeforeAndAfterAll diff --git a/bbb-fsesl-client/build.gradle b/bbb-fsesl-client/build.gradle deleted file mode 100755 index ad7449677b1d7cfcb3a84f1b5ad1a2b3524472c1..0000000000000000000000000000000000000000 --- a/bbb-fsesl-client/build.gradle +++ /dev/null @@ -1,72 +0,0 @@ -apply plugin: 'java' -apply plugin: 'eclipse' - -version = '0.9.0' -jar.enabled = true - -def appName = 'fs-esl-client' - -archivesBaseName = appName - -task resolveDeps(type: Copy) { - into('lib') - from configurations.default - from configurations.default.allArtifacts.file -} - - -artifacts { - archives jar -} - -repositories { - add(new org.apache.ivy.plugins.resolver.ChainResolver()) { - name = 'remote' - returnFirst = true - add(new org.apache.ivy.plugins.resolver.URLResolver()) { - name = "googlecode" - addArtifactPattern "http://red5.googlecode.com/svn/repository/[artifact](-[revision]).[ext]" - addArtifactPattern "http://red5.googlecode.com/svn/repository/[organisation]/[artifact](-[revision]).[ext]" - } - add(new org.apache.ivy.plugins.resolver.URLResolver()) { - name = "blindside-repos" - addArtifactPattern "http://blindside.googlecode.com/svn/repository/[artifact](-[revision]).[ext]" - addArtifactPattern "http://blindside.googlecode.com/svn/repository/[organisation]/[artifact](-[revision]).[ext]" - } - add(new org.apache.ivy.plugins.resolver.URLResolver()) { - name = "maven2-central" - m2compatible = true - addArtifactPattern "http://repo1.maven.org/maven2/[organisation]/[module]/[revision]/[artifact](-[revision]).[ext]" - addArtifactPattern "http://repo1.maven.org/maven2/[organisation]/[artifact]/[revision]/[artifact](-[revision]).[ext]" - } - add(new org.apache.ivy.plugins.resolver.URLResolver()) { - name = "netty-dependency" - m2compatible = true - addArtifactPattern "http://repository.jboss.org/nexus/content/groups/public-jboss/[organisation]/[module]/[revision]/[artifact](-[revision]).[ext]" - addArtifactPattern "http://repo1.maven.org/maven2/[organisation]/[artifact]/[revision]/[artifact](-[revision]).[ext]" - } - } - flatDir name: 'fileRepo', dirs: "/home/firstuser/dev/repo" -} - -dependencies { - // Logging - compile 'ch.qos.logback:logback-core:1.2.3@jar' - compile 'ch.qos.logback:logback-classic:1.2.3@jar' - compile 'org.slf4j:log4j-over-slf4j:1.7.25@jar' - compile 'org.slf4j:jcl-over-slf4j:1.7.25@jar' - compile 'org.slf4j:jul-to-slf4j:1.7.25@jar' - compile 'org.slf4j:slf4j-api:1.7.25@jar' - - testRuntime 'junit:junit:4.8.1.@jar' - compile 'org.jboss.netty:netty:3.2.10.Final@jar' -} - - -uploadArchives { - uploadDescriptor = false - repositories { - add project.repositories.fileRepo - } -} - diff --git a/bbb-fsesl-client/build.sbt b/bbb-fsesl-client/build.sbt index 6cc930898e0964d5da423f80eab0781b35b9b812..4399efc2a4eb945ecde60540463bfe99b225e001 100755 --- a/bbb-fsesl-client/build.sbt +++ b/bbb-fsesl-client/build.sbt @@ -1,10 +1,26 @@ -name := "bbb-fsesl-client" +import org.bigbluebutton.build._ description := "BigBlueButton custom FS-ESL client built on top of FS-ESL Java library." -organization := "org.bigbluebutton" - -version := "0.0.6" +version := "0.0.7-SNAPSHOT" + +val compileSettings = Seq( + organization := "org.bigbluebutton", + + scalacOptions ++= List( + "-unchecked", + "-deprecation", + "-Xlint", + "-Ywarn-dead-code", + "-language:_", + "-target:jvm-1.8", + "-encoding", "UTF-8" + ), + javacOptions ++= List( + "-Xlint:unchecked", + "-Xlint:deprecation" + ) +) // We want to have our jar files in lib_managed dir. // This way we'll have the right path when we import @@ -15,14 +31,8 @@ testOptions in Test += Tests.Argument(TestFrameworks.Specs2, "html", "console", testOptions in Test += Tests.Argument(TestFrameworks.ScalaTest, "-h", "target/scalatest-reports") -libraryDependencies ++= { - Seq( - "org.jboss.netty" % "netty" % "3.2.10.Final", - "junit" % "junit" % "4.12", - "ch.qos.logback" % "logback-classic" % "1.2.3" - )} - -seq(Revolver.settings: _*) +Seq(Revolver.settings: _*) +lazy val bbbFSESLClient = (project in file(".")).settings(name := "bbb-fsesl-client", libraryDependencies ++= Dependencies.runtime).settings(compileSettings) //----------- // Packaging @@ -42,7 +52,7 @@ crossPaths := false // This forbids including Scala related libraries into the dependency autoScalaLibrary := false -publishTo := Some(Resolver.file("file", new File(Path.userHome.absolutePath+"/.m2/repository"))) +publishTo := Some(Resolver.file("file", new File(Path.userHome.absolutePath + "/.m2/repository"))) //publishTo := { // val nexus = "https://oss.sonatype.org/" @@ -60,10 +70,10 @@ publishArtifact in Test := false // http://www.scala-sbt.org/release/docs/Artifacts.html // disable publishing the main API jar -publishArtifact in (Compile, packageDoc) := false +publishArtifact in(Compile, packageDoc) := false // disable publishing the main sources jar -publishArtifact in (Compile, packageSrc) := false +publishArtifact in(Compile, packageSrc) := false pomIncludeRepository := { _ => false } @@ -72,14 +82,14 @@ pomExtra := ( <url>git@github.com:bigbluebutton/bigbluebutton.git</url> <connection>scm:git:git@github.com:bigbluebutton/bigbluebutton.git</connection> </scm> - <developers> - <developer> - <id>ritzalam</id> - <name>Richard Alam</name> - <url>http://www.bigbluebutton.org</url> - </developer> - </developers>) - + <developers> + <developer> + <id>ritzalam</id> + <name>Richard Alam</name> + <url>http://www.bigbluebutton.org</url> + </developer> + </developers>) + licenses := Seq("Apache License, Version 2.0" -> url("http://opensource.org/licenses/Apache-2.0")) homepage := Some(url("http://www.bigbluebutton.org")) diff --git a/bbb-fsesl-client/deploy.sh b/bbb-fsesl-client/deploy.sh index a5f14b0d75d69db2a5f3bfb38226a0f1f643c13d..fdfce32665453b90bcea6d522bcaf673fb56f93b 100644 --- a/bbb-fsesl-client/deploy.sh +++ b/bbb-fsesl-client/deploy.sh @@ -1,2 +1 @@ -sbt clean -sbt publish publishLocal +sbt clean publish publishLocal diff --git a/bbb-fsesl-client/project/Build.scala b/bbb-fsesl-client/project/Build.scala deleted file mode 100755 index e69de29bb2d1d6434b8b29ae775ad8c2e48c5391..0000000000000000000000000000000000000000 diff --git a/bbb-fsesl-client/project/Dependencies.scala b/bbb-fsesl-client/project/Dependencies.scala new file mode 100644 index 0000000000000000000000000000000000000000..9da6506153827201f161ef66183863558c91b4f2 --- /dev/null +++ b/bbb-fsesl-client/project/Dependencies.scala @@ -0,0 +1,40 @@ +package org.bigbluebutton.build + +import sbt._ +import Keys._ + +object Dependencies { + + object Versions { + // Scala + val scala = "2.12.7" + + // Libraries + val netty = "3.2.10.Final" + val logback = "1.2.3" + + // Test + val junit = "4.12" + } + + object Compile { + val scalaLibrary = "org.scala-lang" % "scala-library" % Versions.scala + val scalaCompiler = "org.scala-lang" % "scala-compiler" % Versions.scala + + val netty = "org.jboss.netty" % "netty" % Versions.netty + val logback = "ch.qos.logback" % "logback-classic" % Versions.logback + } + + object Test { + val junit = "junit" % "junit" % Versions.junit % "test" + } + + val testing = Seq( + Test.junit) + + val runtime = Seq( + Compile.scalaLibrary, + Compile.scalaCompiler, + Compile.netty, + Compile.logback) ++ testing +} diff --git a/bbb-fsesl-client/project/build.properties b/bbb-fsesl-client/project/build.properties index a6e117b61042ee81c62ba3a0fc5210d9502944df..2e6e3d24608ee15e892ed3b16d84224f7667e808 100755 --- a/bbb-fsesl-client/project/build.properties +++ b/bbb-fsesl-client/project/build.properties @@ -1 +1 @@ -sbt.version=0.13.8 +sbt.version=1.2.6 \ No newline at end of file diff --git a/bbb-fsesl-client/project/plugins.sbt b/bbb-fsesl-client/project/plugins.sbt index 5ab7b095f69a2b3d8a3bf5350fa7e7e1a64f8f2f..3559bf68d62ef19f25fa810533bbe596eb022d02 100755 --- a/bbb-fsesl-client/project/plugins.sbt +++ b/bbb-fsesl-client/project/plugins.sbt @@ -2,8 +2,8 @@ addSbtPlugin("io.spray" % "sbt-revolver" % "0.9.1") addSbtPlugin("com.typesafe.sbteclipse" % "sbteclipse-plugin" % "5.2.4") -addSbtPlugin("com.jsuereth" % "sbt-pgp" % "1.0.0") +addSbtPlugin("com.jsuereth" % "sbt-pgp" % "1.1.1") -addSbtPlugin("net.vonbuchholtz" % "sbt-dependency-check" % "0.2.7") +addSbtPlugin("net.vonbuchholtz" % "sbt-dependency-check" % "0.2.8") addSbtPlugin("org.scalastyle" %% "scalastyle-sbt-plugin" % "1.0.0") diff --git a/bbb-fsesl-client/src/test/java/org/freeswitch/esl/client/inbound/ClientTest.java b/bbb-fsesl-client/src/test/java/org/freeswitch/esl/client/inbound/ClientTest.java index e3577e52fd6de92f692f7531c168fecbaf4ab252..fb15b27bd2cd7c0b16219291cc0451d43e58ed01 100644 --- a/bbb-fsesl-client/src/test/java/org/freeswitch/esl/client/inbound/ClientTest.java +++ b/bbb-fsesl-client/src/test/java/org/freeswitch/esl/client/inbound/ClientTest.java @@ -17,12 +17,11 @@ package org.freeswitch.esl.client.inbound; import java.util.Map.Entry; -import org.freeswitch.esl.client.IEslEventListener; -import org.freeswitch.esl.client.inbound.Client; -import org.freeswitch.esl.client.inbound.InboundConnectionFailure; +import org.freeswitch.esl.client.example.EslEventListener; import org.freeswitch.esl.client.transport.event.EslEvent; -import org.freeswitch.esl.client.transport.message.EslMessage; import org.freeswitch.esl.client.transport.message.EslHeaders.Name; +import org.freeswitch.esl.client.transport.message.EslMessage; +import org.jboss.netty.channel.ExceptionEvent; import org.junit.Test; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -40,7 +39,7 @@ public class ClientTest { Client client = new Client(); - client.addEventListener( new IEslEventListener() + client.addEventListener( new EslEventListener() { public void eventReceived( EslEvent event ) { diff --git a/bbb-screenshare/app/build.sbt b/bbb-screenshare/app/build.sbt index dc47de3dedc1ee69a30ebd348a40ddcf5f1ebd44..a49598ac7d645e2454021d987f743758ef29001e 100755 --- a/bbb-screenshare/app/build.sbt +++ b/bbb-screenshare/app/build.sbt @@ -1,28 +1,30 @@ +import org.bigbluebutton.build._ //enablePlugins(JavaServerAppPackaging) enablePlugins(JettyPlugin) -name := "bbb-screenshare-akka" - -organization := "org.bigbluebutton" - -version := "0.0.2" - -scalaVersion := "2.12.6" - -scalacOptions ++= Seq( - "-unchecked", - "-deprecation", - "-Xlint", - "-Ywarn-dead-code", - "-language:_", - "-target:jvm-1.8", - "-encoding", "UTF-8" +version := "0.0.3" + +val compileSettings = Seq( + organization := "org.bigbluebutton", + + scalacOptions ++= List( + "-unchecked", + "-deprecation", + "-Xlint", + "-Ywarn-dead-code", + "-language:_", + "-target:jvm-1.8", + "-encoding", "UTF-8" + ), + javacOptions ++= List( + "-Xlint:unchecked", + "-Xlint:deprecation" + ) ) resolvers ++= Seq( "spray repo" at "http://repo.spray.io/", - "rediscala" at "http://dl.bintray.com/etaty/maven", "blindside-repos" at "http://blindside.googlecode.com/svn/repository/" ) @@ -39,45 +41,8 @@ testOptions in Test += Tests.Argument(TestFrameworks.Specs2, "html", "console", testOptions in Test += Tests.Argument(TestFrameworks.ScalaTest, "-h", "target/scalatest-reports") -val akkaVersion = "2.5.14" -val scalaTestV = "2.2.6" - -libraryDependencies ++= { - val springVersion = "4.3.12.RELEASE" - Seq( - "com.typesafe.akka" %% "akka-actor" % akkaVersion, - "com.typesafe.akka" %% "akka-testkit" % akkaVersion % "test", - "com.typesafe.akka" %% "akka-slf4j" % akkaVersion, - "com.typesafe" % "config" % "1.3.0", - "ch.qos.logback" % "logback-classic" % "1.2.3" % "runtime", - "commons-codec" % "commons-codec" % "1.11", - "redis.clients" % "jedis" % "2.9.0", - "org.apache.commons" % "commons-pool2" % "2.6.0", - "org.red5" % "red5-server" % "1.0.10-M5", - "com.google.code.gson" % "gson" % "2.8.5", - "org.springframework" % "spring-web" % springVersion, - "org.springframework" % "spring-beans" % springVersion, - "org.springframework" % "spring-context" % springVersion, - "org.springframework" % "spring-core" % springVersion, - "org.springframework" % "spring-webmvc" % springVersion, - "org.springframework" % "spring-aop" % springVersion, - "javax.servlet" % "servlet-api" % "2.5" - )} - -// https://mvnrepository.com/artifact/org.scala-lang/scala-library -libraryDependencies += "org.scala-lang" % "scala-library" % scalaVersion.value -libraryDependencies += "org.scala-lang" % "scala-reflect" % scalaVersion.value - -libraryDependencies += "org.bigbluebutton" % "bbb-common-message_2.12" % "0.0.19-SNAPSHOT" -// https://mvnrepository.com/artifact/com.github.etaty/rediscala_2.12 -libraryDependencies += "com.github.etaty" % "rediscala_2.12" % "1.8.0" -// https://mvnrepository.com/artifact/com.fasterxml.jackson.module/jackson-module-scala_2.12 -libraryDependencies += "com.fasterxml.jackson.module" % "jackson-module-scala_2.12" % "2.9.6" - -//seq(Revolver.settings: _*) -// -//scalariformSettings - +Seq(Revolver.settings: _*) +lazy val bbbScreenshareAkka = (project in file(".")).settings(name := "bbb-screenshare-akka", libraryDependencies ++= Dependencies.runtime).settings(compileSettings) //----------- // Packaging diff --git a/bbb-screenshare/app/deploy.sh b/bbb-screenshare/app/deploy.sh index a21a310dbb12dcf7258fdfd49cb43ee187c31545..69d99a0f08fa0b716c734b4a525efc203e0c6922 100755 --- a/bbb-screenshare/app/deploy.sh +++ b/bbb-screenshare/app/deploy.sh @@ -1,52 +1,45 @@ #!/bin/bash # deploying 'screenshare' to /usr/share/red5/webapps -sbt clean -sbt compile -sbt package +sbt clean compile package + if [[ -d /usr/share/red5/webapps/screenshare ]]; then sudo rm -r /usr/share/red5/webapps/screenshare fi sudo cp -r target/webapp/ /usr/share/red5/webapps/screenshare - sudo rm -rf /usr/share/red5/webapps/screenshare/WEB-INF/lib/* -sudo cp ~/dev/bigbluebutton/bbb-screenshare/app/target/webapp/WEB-INF/lib/bbb-screenshare-akka_2.12-0.0.2.jar \ - ~/dev/bigbluebutton/bbb-screenshare/app/target/webapp/WEB-INF/lib/scala-library-* \ - ~/dev/bigbluebutton/bbb-screenshare/app/target/webapp/WEB-INF/lib/scala-reflect-* \ - ~/dev/bigbluebutton/bbb-screenshare/app/target/webapp/WEB-INF/lib/jackson-* \ - ~/dev/bigbluebutton/bbb-screenshare/app/target/webapp/WEB-INF/lib/paranamer-2.8.jar \ - ~/dev/bigbluebutton/bbb-screenshare/app/target/webapp/WEB-INF/lib/akka-* \ - ~/dev/bigbluebutton/bbb-screenshare/app/target/webapp/WEB-INF/lib/config-1.3.3.jar \ - ~/dev/bigbluebutton/bbb-screenshare/app/target/webapp/WEB-INF/lib/gson-2.8.5.jar \ - ~/dev/bigbluebutton/bbb-screenshare/app/target/webapp/WEB-INF/lib/jedis-2.9.0.jar \ - ~/dev/bigbluebutton/bbb-screenshare/app/target/webapp/WEB-INF/lib/commons-pool2-2.6.0.jar \ - ~/dev/bigbluebutton/bbb-screenshare/app/target/webapp/WEB-INF/lib/spring-webmvc-4.3.12.RELEASE.jar \ - ~/dev/bigbluebutton/bbb-screenshare/app/target/webapp/WEB-INF/lib/rediscala_2.12-1.8.0.jar \ - ~/dev/bigbluebutton/bbb-screenshare/app/target/webapp/WEB-INF/lib/bbb-common-message_2.12-0.0.19-SNAPSHOT.jar \ +sudo cp target/webapp/WEB-INF/lib/bbb-screenshare-akka_2.12-0.0.3.jar \ + target/webapp/WEB-INF/lib/scala-library.jar \ + target/webapp/WEB-INF/lib/scala-reflect.jar \ + target/webapp/WEB-INF/lib/jackson-* \ + target/webapp/WEB-INF/lib/paranamer-2.8.jar \ + target/webapp/WEB-INF/lib/akka-* \ + target/webapp/WEB-INF/lib/config-1.3.3.jar \ + target/webapp/WEB-INF/lib/gson-2.8.5.jar \ + target/webapp/WEB-INF/lib/commons-pool2-2.6.0.jar \ + target/webapp/WEB-INF/lib/spring-webmvc-4.3.12.RELEASE.jar \ + target/webapp/WEB-INF/lib/bbb-common-message_2.12-0.0.20-SNAPSHOT.jar \ + target/webapp/WEB-INF/lib/lettuce-core-5.1.3.RELEASE.jar \ + target/webapp/WEB-INF/lib/netty-* \ + target/webapp/WEB-INF/lib/reactor-core-3.2.3.RELEASE.jar \ + target/webapp/WEB-INF/lib/reactive-streams-1.0.2.jar \ /usr/share/red5/webapps/screenshare/WEB-INF/lib/ - #sudo mkdir /usr/share/red5/webapps/screenshare/WEB-INF/classes #cd /usr/share/red5/webapps/screenshare/WEB-INF/classes/ -#sudo jar -xf ../lib/bbb-screenshare-akka_2.12-0.0.2.jar -#sudo rm /usr/share/red5/webapps/screenshare/WEB-INF/lib/bbb-screenshare-akka_2.12-0.0.2.jar +#sudo jar -xf .lib/bbb-screenshare-akka_2.12-0.0.3.jar +#sudo rm /usr/share/red5/webapps/screenshare/WEB-INF/lib/bbb-screenshare-akka_2.12-0.0.3.jar -cd /usr/share/red5/webapps/screenshare -sudo mkdir lib -cd lib -sudo cp -r ~/dev/bigbluebutton/bbb-screenshare/app/jws/lib/* . -cd .. -sudo cp ~/dev/bigbluebutton/bbb-screenshare/app/jws/screenshare.jnlp . -sudo cp ~/dev/bigbluebutton/bbb-screenshare/app/jws/screenshare.jnlp.h264 . +sudo mkdir -p /usr/share/red5/webapps/screenshare/lib +sudo cp -r jws/lib/* /usr/share/red5/webapps/screenshare/lib +sudo cp jws/screenshare.jnlp /usr/share/red5/webapps/screenshare +sudo cp jws/screenshare.jnlp.h264 /usr/share/red5/webapps/screenshare sudo chmod -R 777 /usr/share/red5/webapps/screenshare sudo chown -R red5:red5 /usr/share/red5/webapps/screenshare -# TODO change the owner username to 'firstuser' - # // Dev only #sudo service red5 restart #sudo service tomcat7 restart #sudo service bbb-apps-akka restart - diff --git a/bbb-screenshare/app/project/Dependencies.scala b/bbb-screenshare/app/project/Dependencies.scala new file mode 100644 index 0000000000000000000000000000000000000000..28749e54ecb99990585a7eebc96d5c8c97bf8f15 --- /dev/null +++ b/bbb-screenshare/app/project/Dependencies.scala @@ -0,0 +1,105 @@ +package org.bigbluebutton.build + +import sbt._ +import Keys._ + +object Dependencies { + + object Versions { + // Scala + val scala = "2.12.7" + val junitInterface = "0.11" + val scalactic = "3.0.3" + + // Libraries + val akkaVersion = "2.5.17" + val gson = "2.8.5" + val jackson = "2.9.7" + val logback = "1.2.3" + val springVersion = "4.3.12.RELEASE" + val red5 = "1.0.10-M9" + val servlet = "2.5" + val ffmpeg = "4.0.2-1.4.3" + val openCv = "1.4.3" + + // Apache Commons + val lang = "3.7" + val codec = "1.11" + val pool2 = "2.6.0" + + // Redis + val lettuce = "5.1.3.RELEASE" + + // BigBlueButton + val bbbCommons = "0.0.20-SNAPSHOT" + + // Test + val scalaTest = "3.0.5" + val akkaTestKit = "2.5.18" + } + + object Compile { + val scalaLibrary = "org.scala-lang" % "scala-library" % Versions.scala + val scalaReflect = "org.scala-lang" % "scala-reflect" % Versions.scala + + val akkaActor = "com.typesafe.akka" % "akka-actor_2.12" % Versions.akkaVersion + val akkaSl4fj = "com.typesafe.akka" % "akka-slf4j_2.12" % Versions.akkaVersion + + val googleGson = "com.google.code.gson" % "gson" % Versions.gson + val jacksonModule = "com.fasterxml.jackson.module" %% "jackson-module-scala" % Versions.jackson + val logback = "ch.qos.logback" % "logback-classic" % Versions.logback % "runtime" + val red5Server = "org.red5" % "red5-server" % Versions.red5 + val javaServlet = "javax.servlet" % "servlet-api" % Versions.servlet + val ffmpeg = "org.bytedeco.javacpp-presets" % "ffmpeg" % Versions.ffmpeg + val openCv = "org.bytedeco" % "javacv" % Versions.openCv + + val springWeb = "org.springframework" % "spring-web" % Versions.springVersion + val springBeans = "org.springframework" % "spring-beans" % Versions.springVersion + val springContext = "org.springframework" % "spring-context" % Versions.springVersion + val springCore = "org.springframework" % "spring-core" % Versions.springVersion + val springWebmvc = "org.springframework" % "spring-webmvc" % Versions.springVersion + val springAop = "org.springframework" % "spring-aop" % Versions.springVersion + + val commonsCodec = "commons-codec" % "commons-codec" % Versions.codec + val apacheLang = "org.apache.commons" % "commons-lang3" % Versions.lang + val apachePool2 = "org.apache.commons" % "commons-pool2" % Versions.pool2 + + val lettuceCore = "io.lettuce" % "lettuce-core" % Versions.lettuce + + val bbbCommons = "org.bigbluebutton" % "bbb-common-message_2.12" % Versions.bbbCommons + } + + object Test { + val scalaTest = "org.scalatest" %% "scalatest" % Versions.scalaTest % "test" + val scalactic = "org.scalactic" % "scalactic_2.12" % Versions.scalactic % "test" + val akkaTestKit = "com.typesafe.akka" %% "akka-testkit" % Versions.akkaTestKit % "test" + } + + val testing = Seq( + Test.scalaTest, + Test.scalactic, + Test.akkaTestKit) + + val runtime = Seq( + Compile.scalaLibrary, + Compile.scalaReflect, + Compile.akkaActor, + Compile.akkaSl4fj, + Compile.googleGson, + Compile.jacksonModule, + Compile.red5Server, + Compile.javaServlet, + Compile.ffmpeg, + Compile.openCv, + Compile.logback, + Compile.springWeb, + Compile.springBeans, + Compile.springContext, + Compile.springWebmvc, + Compile.springAop, + Compile.commonsCodec, + Compile.apacheLang, + Compile.apachePool2, + Compile.lettuceCore, + Compile.bbbCommons) ++ testing +} diff --git a/bbb-screenshare/app/project/build.properties b/bbb-screenshare/app/project/build.properties new file mode 100644 index 0000000000000000000000000000000000000000..7c58a83abffb36afce7051243bff3a50e3fa3dab --- /dev/null +++ b/bbb-screenshare/app/project/build.properties @@ -0,0 +1 @@ +sbt.version=1.2.6 diff --git a/bbb-screenshare/app/project/plugins.sbt b/bbb-screenshare/app/project/plugins.sbt index 1c5c911f1db806157b151442a8ed6230cbd9afe9..0a2cbd1dd31631fd8a7a856591f2deeaf85a7840 100644 --- a/bbb-screenshare/app/project/plugins.sbt +++ b/bbb-screenshare/app/project/plugins.sbt @@ -1,15 +1,13 @@ addSbtPlugin("io.spray" % "sbt-revolver" % "0.9.1") -addSbtPlugin("org.scalariform" % "sbt-scalariform" % "1.8.2") - addSbtPlugin("com.typesafe.sbteclipse" % "sbteclipse-plugin" % "5.2.4") -addSbtPlugin("com.typesafe.sbt" % "sbt-native-packager" % "1.3.6") - -addSbtPlugin("com.earldouglas" % "xsbt-web-plugin" % "2.1.0") +addSbtPlugin("org.scalariform" % "sbt-scalariform" % "1.8.2") -//addSbtPlugin("org.xerial.sbt" % "sbt-pack" % "0.7.9") +addSbtPlugin("com.typesafe.sbt" % "sbt-native-packager" % "1.3.12") -addSbtPlugin("net.vonbuchholtz" % "sbt-dependency-check" % "0.2.7") +addSbtPlugin("net.vonbuchholtz" % "sbt-dependency-check" % "0.2.8") addSbtPlugin("org.scalastyle" %% "scalastyle-sbt-plugin" % "1.0.0") + +addSbtPlugin("com.earldouglas" % "xsbt-web-plugin" % "4.0.2") diff --git a/bbb-screenshare/app/src/main/java/org/bigbluebutton/app/screenshare/ScreenshareStreamListener.java b/bbb-screenshare/app/src/main/java/org/bigbluebutton/app/screenshare/ScreenshareStreamListener.java index 1e90ff0111d506485d1bf5cf12daa99acbbcf73a..7cbecf986d010a8f1a957462773b2ea41640f055 100755 --- a/bbb-screenshare/app/src/main/java/org/bigbluebutton/app/screenshare/ScreenshareStreamListener.java +++ b/bbb-screenshare/app/src/main/java/org/bigbluebutton/app/screenshare/ScreenshareStreamListener.java @@ -23,6 +23,7 @@ import java.util.Map; import java.util.concurrent.TimeUnit; import org.apache.mina.core.buffer.IoBuffer; +import org.bigbluebutton.common2.redis.RedisStorageService; import org.red5.server.api.IConnection; import org.red5.server.api.Red5; import org.red5.server.api.stream.IBroadcastStream; @@ -46,11 +47,11 @@ import org.red5.server.net.rtmp.event.VideoData; * */ public class ScreenshareStreamListener implements IStreamListener { - private EventRecordingService recordingService; + private RedisStorageService recordingService; private volatile boolean firstPacketReceived = false; private String recordingDir; - public ScreenshareStreamListener(EventRecordingService s, String recordingDir) { + public ScreenshareStreamListener(RedisStorageService s, String recordingDir) { this.recordingService = s; this.recordingDir = recordingDir; } diff --git a/bbb-screenshare/app/src/main/java/org/bigbluebutton/app/screenshare/VideoStreamListener.java b/bbb-screenshare/app/src/main/java/org/bigbluebutton/app/screenshare/VideoStreamListener.java index 41468e7ed845331d4706df0ab30a711a6d70f35a..bf91a9770f607ffc91bb3974ec24d4ac6bf37cb1 100755 --- a/bbb-screenshare/app/src/main/java/org/bigbluebutton/app/screenshare/VideoStreamListener.java +++ b/bbb-screenshare/app/src/main/java/org/bigbluebutton/app/screenshare/VideoStreamListener.java @@ -17,24 +17,24 @@ */ package org.bigbluebutton.app.screenshare; +import java.text.SimpleDateFormat; import java.util.HashMap; import java.util.Map; import java.util.concurrent.TimeUnit; import org.apache.mina.core.buffer.IoBuffer; +import org.bigbluebutton.common2.redis.RedisStorageService; +import org.red5.logging.Red5LoggerFactory; import org.red5.server.api.scheduling.IScheduledJob; import org.red5.server.api.scheduling.ISchedulingService; -import org.red5.server.api.scope.IScope; import org.red5.server.api.stream.IBroadcastStream; import org.red5.server.api.stream.IStreamListener; import org.red5.server.api.stream.IStreamPacket; import org.red5.server.net.rtmp.event.VideoData; import org.red5.server.scheduling.QuartzSchedulingService; import org.slf4j.Logger; -import org.red5.logging.Red5LoggerFactory; import com.google.gson.Gson; -import java.text.SimpleDateFormat; /** * Class to listen for the first video packet of the webcam. @@ -54,7 +54,7 @@ import java.text.SimpleDateFormat; public class VideoStreamListener implements IStreamListener { private static final Logger log = Red5LoggerFactory.getLogger(VideoStreamListener.class, "screenshare"); - private EventRecordingService recordingService; + private RedisStorageService redisStorageService; private volatile boolean firstPacketReceived = false; // Maximum time between video packets @@ -96,14 +96,14 @@ public class VideoStreamListener implements IStreamListener { public VideoStreamListener(String meetingId, String streamId, Boolean record, String recordingDir, int packetTimeout, QuartzSchedulingService scheduler, - EventRecordingService recordingService) { + RedisStorageService recordingService) { this.meetingId = meetingId; this.streamId = streamId; this.record = record; this.videoTimeout = packetTimeout; this.recordingDir = recordingDir; this.scheduler = scheduler; - this.recordingService = recordingService; + this.redisStorageService = recordingService; // start the worker to monitor if we are still receiving video packets timeoutJobName = scheduler.addScheduledJob(videoTimeout, new TimeoutJob()); @@ -166,7 +166,7 @@ public class VideoStreamListener implements IStreamListener { event.put(DATE, sdf.format(recordingStartTime)); event.put("eventName", "DeskshareStartedEvent"); - recordingService.record(meetingId, event); + redisStorageService.record(meetingId, event); Gson gson = new Gson(); @@ -233,7 +233,7 @@ public class VideoStreamListener implements IStreamListener { SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSSX"); event.put(DATE, sdf.format(now)); event.put("eventName", "DeskshareStoppedEvent"); - recordingService.record(meetingId, event); + redisStorageService.record(meetingId, event); Gson gson = new Gson(); String logStr = gson.toJson(event); diff --git a/bbb-screenshare/app/src/main/java/org/bigbluebutton/app/screenshare/messaging/redis/MessageSender.java b/bbb-screenshare/app/src/main/java/org/bigbluebutton/app/screenshare/messaging/redis/MessageSender.java deleted file mode 100755 index 182ab1dac605fa0b6cdc2e7216ab476de0afecdc..0000000000000000000000000000000000000000 --- a/bbb-screenshare/app/src/main/java/org/bigbluebutton/app/screenshare/messaging/redis/MessageSender.java +++ /dev/null @@ -1,102 +0,0 @@ -package org.bigbluebutton.app.screenshare.messaging.redis; - -import java.util.concurrent.BlockingQueue; -import java.util.concurrent.Executor; -import java.util.concurrent.Executors; -import java.util.concurrent.LinkedBlockingQueue; - -import org.apache.commons.pool2.impl.GenericObjectPoolConfig; -import org.red5.logging.Red5LoggerFactory; -import org.slf4j.Logger; - -import redis.clients.jedis.Jedis; -import redis.clients.jedis.JedisPool; -import redis.clients.jedis.Protocol; - -public class MessageSender { - private static Logger log = Red5LoggerFactory.getLogger(MessageSender.class, "bigbluebutton"); - - private volatile boolean sendMessage = false; - private final Executor msgSenderExec = Executors.newSingleThreadExecutor(); - private final Executor runExec = Executors.newSingleThreadExecutor(); - private BlockingQueue<MessageToSend> messages = new LinkedBlockingQueue<MessageToSend>(); - - private JedisPool redisPool; - private String host; - private int port; - - public void stop() { - sendMessage = false; - redisPool.destroy(); - } - - public void start() { - GenericObjectPoolConfig config = new GenericObjectPoolConfig(); - config.setMaxTotal(32); - config.setMaxIdle(8); - config.setMinIdle(1); - config.setTestOnBorrow(true); - config.setTestOnReturn(true); - config.setTestWhileIdle(true); - config.setNumTestsPerEvictionRun(12); - config.setMaxWaitMillis(5000); - config.setTimeBetweenEvictionRunsMillis(60000); - config.setBlockWhenExhausted(true); - - // Set the name of this client to be able to distinguish when doing - // CLIENT LIST on redis-cli - redisPool = new JedisPool(config, host, port, Protocol.DEFAULT_TIMEOUT, null, - Protocol.DEFAULT_DATABASE, "BbbRed5AppsPub"); - - log.info("Redis message publisher starting!"); - try { - sendMessage = true; - - Runnable messageSender = new Runnable() { - public void run() { - while (sendMessage) { - try { - MessageToSend msg = messages.take(); - publish(msg.getChannel(), msg.getMessage()); - } catch (InterruptedException e) { - log.warn("Failed to get message from queue."); - } - } - } - }; - msgSenderExec.execute(messageSender); - } catch (Exception e) { - log.error("Error subscribing to channels: " + e.getMessage()); - } - } - - public void send(String channel, String message) { - MessageToSend msg = new MessageToSend(channel, message); - messages.add(msg); - } - - private void publish(final String channel, final String message) { - Runnable task = new Runnable() { - public void run() { - Jedis jedis = redisPool.getResource(); - try { - jedis.publish(channel, message); - } catch(Exception e){ - log.warn("Cannot publish the message to redis", e); - } finally { - jedis.close(); - } - } - }; - - runExec.execute(task); - } - - public void setHost(String host){ - this.host = host; - } - - public void setPort(int port) { - this.port = port; - } -} diff --git a/bbb-screenshare/app/src/main/java/org/bigbluebutton/app/screenshare/red5/Red5AppAdapter.java b/bbb-screenshare/app/src/main/java/org/bigbluebutton/app/screenshare/red5/Red5AppAdapter.java index 432f4049a08a30376e42869588daf798d68a6749..142b9a2a50c8f7d2bb93572521a0b0e61ab6cb69 100755 --- a/bbb-screenshare/app/src/main/java/org/bigbluebutton/app/screenshare/red5/Red5AppAdapter.java +++ b/bbb-screenshare/app/src/main/java/org/bigbluebutton/app/screenshare/red5/Red5AppAdapter.java @@ -24,24 +24,22 @@ import java.util.concurrent.TimeUnit; import java.util.regex.Matcher; import java.util.regex.Pattern; +import org.bigbluebutton.app.screenshare.IScreenShareApplication; +import org.bigbluebutton.app.screenshare.MeetingManager; +import org.bigbluebutton.app.screenshare.VideoStream; +import org.bigbluebutton.app.screenshare.VideoStreamListener; +import org.bigbluebutton.common2.redis.RedisStorageService; import org.red5.logging.Red5LoggerFactory; import org.red5.server.adapter.MultiThreadedApplicationAdapter; import org.red5.server.api.IConnection; import org.red5.server.api.Red5; import org.red5.server.api.scope.IScope; import org.red5.server.api.stream.IBroadcastStream; -import org.red5.server.api.stream.IServerStream; -import org.red5.server.api.stream.IStreamListener; +import org.red5.server.scheduling.QuartzSchedulingService; import org.red5.server.stream.ClientBroadcastStream; import org.slf4j.Logger; -import org.red5.server.scheduling.QuartzSchedulingService; + import com.google.gson.Gson; -import org.bigbluebutton.app.screenshare.MeetingManager; -import org.bigbluebutton.app.screenshare.VideoStreamListener; -import org.bigbluebutton.app.screenshare.VideoStream; -import org.bigbluebutton.app.screenshare.EventRecordingService; -import org.bigbluebutton.app.screenshare.IScreenShareApplication; -import org.bigbluebutton.app.screenshare.ScreenshareStreamListener; public class Red5AppAdapter extends MultiThreadedApplicationAdapter { private static Logger log = Red5LoggerFactory.getLogger(Red5AppAdapter.class, "screenshare"); @@ -49,7 +47,7 @@ public class Red5AppAdapter extends MultiThreadedApplicationAdapter { // Scheduler private QuartzSchedulingService scheduler; - private EventRecordingService recordingService; + private RedisStorageService redisStorageService; private IScreenShareApplication app; private String streamBaseUrl; private ConnectionInvokerService sender; @@ -196,7 +194,7 @@ public class Red5AppAdapter extends MultiThreadedApplicationAdapter { log.info(logStr2); VideoStreamListener listener = new VideoStreamListener(meetingId, streamId, - recordVideoStream, recordingDirectory, packetTimeout, scheduler, recordingService); + recordVideoStream, recordingDirectory, packetTimeout, scheduler, redisStorageService); ClientBroadcastStream cstream = (ClientBroadcastStream) this.getBroadcastStream(conn.getScope(), stream.getPublishedName()); stream.addStreamListener(listener); VideoStream vstream = new VideoStream(stream, listener, cstream); @@ -255,8 +253,8 @@ public class Red5AppAdapter extends MultiThreadedApplicationAdapter { this.meetingManager = meetingManager; } - public void setEventRecordingService(EventRecordingService s) { - recordingService = s; + public void setRedisStorageService(RedisStorageService s) { + redisStorageService = s; } public void setStreamBaseUrl(String baseUrl) { diff --git a/bbb-screenshare/app/src/main/java/org/bigbluebutton/app/screenshare/red5/Red5AppService.java b/bbb-screenshare/app/src/main/java/org/bigbluebutton/app/screenshare/red5/Red5AppService.java index da895535568acdabfc6d19509c72c25b3318e650..1e459fafa28e53ad935c41966c408f2adb361f0a 100755 --- a/bbb-screenshare/app/src/main/java/org/bigbluebutton/app/screenshare/red5/Red5AppService.java +++ b/bbb-screenshare/app/src/main/java/org/bigbluebutton/app/screenshare/red5/Red5AppService.java @@ -3,19 +3,18 @@ package org.bigbluebutton.app.screenshare.red5; import java.util.HashMap; import java.util.Map; import java.util.Set; -import java.util.concurrent.TimeUnit; -import org.bigbluebutton.app.screenshare.messaging.redis.MessageSender; + import org.red5.logging.Red5LoggerFactory; import org.red5.server.api.IConnection; import org.red5.server.api.Red5; import org.slf4j.Logger; + import com.google.gson.Gson; public class Red5AppService { private static Logger log = Red5LoggerFactory.getLogger(Red5AppService.class, "screenshare"); private Red5AppHandler handler; - private MessageSender red5RedisSender; /** * Called from the client to pass us the userId. @@ -180,15 +179,7 @@ public class Red5AppService { handler.screenShareClientPongMessage(meetingId, userId, streamId, timestamp.longValue()); } - private Long genTimestamp() { - return TimeUnit.NANOSECONDS.toMillis(System.nanoTime()); - } - public void setAppHandler(Red5AppHandler handler) { this.handler = handler; } - - public void setRed5RedisSender(MessageSender red5RedisSender) { - this.red5RedisSender = red5RedisSender; - } } diff --git a/bbb-screenshare/app/src/main/java/org/bigbluebutton/app/screenshare/server/recorder/EventRecorder.java b/bbb-screenshare/app/src/main/java/org/bigbluebutton/app/screenshare/server/recorder/EventRecorder.java deleted file mode 100755 index ec4452c66ea6600f878cfd91355bd3283aed6aa2..0000000000000000000000000000000000000000 --- a/bbb-screenshare/app/src/main/java/org/bigbluebutton/app/screenshare/server/recorder/EventRecorder.java +++ /dev/null @@ -1,59 +0,0 @@ -/** -* BigBlueButton open source conferencing system - http://www.bigbluebutton.org/ -* -* Copyright (c) 2012 BigBlueButton Inc. and by respective authors (see below). -* -* This program is free software; you can redistribute it and/or modify it under the -* terms of the GNU Lesser General Public License as published by the Free Software -* Foundation; either version 3.0 of the License, or (at your option) any later -* version. -* -* BigBlueButton is distributed in the hope that it will be useful, but WITHOUT ANY -* WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A -* PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. -* -* You should have received a copy of the GNU Lesser General Public License along -* with BigBlueButton; if not, see <http://www.gnu.org/licenses/>. -* -*/ -package org.bigbluebutton.app.screenshare.server.recorder; - -import java.util.concurrent.TimeUnit; - -import org.bigbluebutton.app.screenshare.server.recorder.event.AbstractDeskshareRecordEvent; -import org.bigbluebutton.app.screenshare.server.recorder.event.RecordEvent; -import org.bigbluebutton.app.screenshare.server.recorder.event.RecordStartedEvent; -import org.bigbluebutton.app.screenshare.server.recorder.event.RecordStoppedEvent; - -import redis.clients.jedis.Jedis; - -public class EventRecorder implements RecordStatusListener { - private static final String COLON=":"; - private String host; - private int port; - - public EventRecorder(String host, int port){ - this.host = host; - this.port = port; - } - - private Long genTimestamp() { - return TimeUnit.NANOSECONDS.toMillis(System.nanoTime()); - } - - private void record(String session, RecordEvent message) { - Jedis jedis = new Jedis(host, port); - Long msgid = jedis.incr("global:nextRecordedMsgId"); - jedis.hmset("recording" + COLON + session + COLON + msgid, message.toMap()); - jedis.rpush("meeting" + COLON + session + COLON + "recordings", msgid.toString()); - } - - @Override - public void notify(RecordEvent event) { - if ((event instanceof RecordStoppedEvent) || (event instanceof RecordStartedEvent)) { - event.setTimestamp(genTimestamp()); - event.setMeetingId(((AbstractDeskshareRecordEvent)event).getSession()); - record(((AbstractDeskshareRecordEvent)event).getSession(), event); - } - } -} diff --git a/bbb-screenshare/app/src/main/java/org/bigbluebutton/app/screenshare/store/redis/RedisDataStore.java b/bbb-screenshare/app/src/main/java/org/bigbluebutton/app/screenshare/store/redis/RedisDataStore.java deleted file mode 100755 index e1900231febfe63631debd14a2c6a61702f4e220..0000000000000000000000000000000000000000 --- a/bbb-screenshare/app/src/main/java/org/bigbluebutton/app/screenshare/store/redis/RedisDataStore.java +++ /dev/null @@ -1,79 +0,0 @@ -package org.bigbluebutton.app.screenshare.store.redis; - -import java.util.concurrent.BlockingQueue; -import java.util.concurrent.Executor; -import java.util.concurrent.Executors; -import java.util.concurrent.LinkedBlockingQueue; -import org.red5.logging.Red5LoggerFactory; -import org.slf4j.Logger; -import redis.clients.jedis.Jedis; -import redis.clients.jedis.JedisPool; - -public class RedisDataStore { - private static Logger log = Red5LoggerFactory.getLogger(RedisDataStore.class, "screenshare"); - - private JedisPool redisPool; - private volatile boolean sendMessage = false; - private int maxThreshold = 1024; - private final Executor msgSenderExec = Executors.newSingleThreadExecutor(); - private BlockingQueue<IScreenShareData> dataToStore = new LinkedBlockingQueue<IScreenShareData>(); - private final Executor runExec = Executors.newSingleThreadExecutor(); - - public void stop() { - sendMessage = false; - } - - public void start() { - try { - sendMessage = true; - - Runnable messageSender = new Runnable() { - public void run() { - while (sendMessage) { - try { - IScreenShareData data = dataToStore.take(); - storeData(data); - } catch (InterruptedException e) { - log.warn("Failed to get data from queue."); - } - } - } - }; - msgSenderExec.execute(messageSender); - } catch (Exception e) { - log.error("Error storing data into redis: " + e.getMessage()); - } - } - - public void store(IScreenShareData data) { - if (dataToStore.size() > maxThreshold) { - log.warn("Queued number of data [{}] is greater than threshold [{}]", dataToStore.size(), maxThreshold); - } - dataToStore.add(data); - } - - private void storeData(IScreenShareData data) { - Runnable task = new Runnable() { - public void run() { - Jedis jedis = redisPool.getResource(); - try { - // jedis.publish(channel, message); - } catch(Exception e){ - log.warn("Cannot publish the message to redis", e); - } finally { - redisPool.returnResource(jedis); - } - } - }; - - runExec.execute(task); - } - - public void setRedisPool(JedisPool redisPool){ - this.redisPool = redisPool; - } - - public void setMaxThreshold(int threshold) { - maxThreshold = threshold; - } -} diff --git a/bbb-screenshare/app/src/main/scala/org/bigbluebutton/app/screenshare/ScreenShareApplication.scala b/bbb-screenshare/app/src/main/scala/org/bigbluebutton/app/screenshare/ScreenShareApplication.scala index cb3082f1a6e119ab56337eab61cfdacc2035430a..d1c42cdd8c86e4d22ef02f7add2d8e3f287a8b0b 100755 --- a/bbb-screenshare/app/src/main/scala/org/bigbluebutton/app/screenshare/ScreenShareApplication.scala +++ b/bbb-screenshare/app/src/main/scala/org/bigbluebutton/app/screenshare/ScreenShareApplication.scala @@ -26,10 +26,11 @@ import org.bigbluebutton.app.screenshare.server.sessions.ScreenshareManager import org.bigbluebutton.app.screenshare.server.sessions.messages._ import org.bigbluebutton.app.screenshare.server.util.LogHelper import akka.actor.ActorSystem -import org.bigbluebutton.app.screenshare.redis.{ AppsRedisSubscriberActor, IncomingJsonMessageBus, ReceivedJsonMsgHandlerActor } +import org.bigbluebutton.app.screenshare.redis.{ ScreenshareRedisSubscriberActor, ReceivedJsonMsgHandlerActor } import scala.concurrent.{ Await, Future, TimeoutException } import scala.concurrent.duration._ +import org.bigbluebutton.common2.bus.IncomingJsonMessageBus class ScreenShareApplication(val bus: IEventsMessageBus, val jnlpFile: String, val streamBaseUrl: String) extends IScreenShareApplication @@ -46,7 +47,7 @@ class ScreenShareApplication(val bus: IEventsMessageBus, val jnlpFile: String, //logger.debug("*********** meetingManagerChannel = " + meetingManagerChannel) val incomingJsonMessageBus = new IncomingJsonMessageBus - val redisSubscriberActor = system.actorOf(AppsRedisSubscriberActor.props(incomingJsonMessageBus), "redis-subscriber") + val redisSubscriberActor = system.actorOf(ScreenshareRedisSubscriberActor.props(system, incomingJsonMessageBus), "redis-subscriber") val screenShareManager = system.actorOf(ScreenshareManager.props(system, bus), "screenshare-manager") diff --git a/bbb-screenshare/app/src/main/scala/org/bigbluebutton/app/screenshare/SystemConfiguration.scala b/bbb-screenshare/app/src/main/scala/org/bigbluebutton/app/screenshare/SystemConfiguration.scala index c25b684048db28d44b04cc90e5eef302c56ccb50..a838726e128301e5f91b955e81e123f832ac8523 100755 --- a/bbb-screenshare/app/src/main/scala/org/bigbluebutton/app/screenshare/SystemConfiguration.scala +++ b/bbb-screenshare/app/src/main/scala/org/bigbluebutton/app/screenshare/SystemConfiguration.scala @@ -2,18 +2,11 @@ package org.bigbluebutton.app.screenshare import scala.util.Try import com.typesafe.config.ConfigFactory +import org.bigbluebutton.common2.redis.RedisConfiguration -trait SystemConfiguration { - - //val config = ConfigFactory.load("screenshare-app") - val config = ConfigFactory.load() - - lazy val redisHost = Try(config.getString("redis.host")).getOrElse("127.0.0.1") - lazy val redisPort = Try(config.getInt("redis.port")).getOrElse(6379) - lazy val redisPassword = Try(config.getString("redis.password")).getOrElse("") +trait SystemConfiguration extends RedisConfiguration { lazy val meetingManagerChannel = Try(config.getString("eventBus.meetingManagerChannel")).getOrElse("NOT FROM APP CONF") lazy val toScreenshareAppsJsonChannel = Try(config.getString("eventBus.toAkkaAppsChannel")).getOrElse("to-screenshare-apps-json-channel") - lazy val fromAkkaAppsRedisChannel = Try(config.getString("redis.fromAkkaAppsRedisChannel")).getOrElse("from-akka-apps-redis-channel") } diff --git a/bbb-screenshare/app/src/main/scala/org/bigbluebutton/app/screenshare/redis/AppsRedisSubscriberActor.scala b/bbb-screenshare/app/src/main/scala/org/bigbluebutton/app/screenshare/redis/AppsRedisSubscriberActor.scala deleted file mode 100755 index 36bbc477fc0d0201f1a875f03bdb1dbad7599324..0000000000000000000000000000000000000000 --- a/bbb-screenshare/app/src/main/scala/org/bigbluebutton/app/screenshare/redis/AppsRedisSubscriberActor.scala +++ /dev/null @@ -1,59 +0,0 @@ -package org.bigbluebutton.app.screenshare.redis - -import java.io.{ PrintWriter, StringWriter } -import java.net.InetSocketAddress - -import akka.actor.{ OneForOneStrategy, Props } -import akka.actor.SupervisorStrategy.Resume -import org.bigbluebutton.app.screenshare.SystemConfiguration -import redis.actors.RedisSubscriberActor -import redis.api.servers.ClientSetname -import redis.actors.RedisSubscriberActor -import redis.api.pubsub.{ Message, PMessage } -import scala.concurrent.duration._ - -object AppsRedisSubscriberActor extends SystemConfiguration { - - val channels = Seq(fromAkkaAppsRedisChannel) - val patterns = Seq("bigbluebutton:to-bbb-apps:*", "bigbluebutton:from-voice-conf:*") - - def props(jsonMsgBus: IncomingJsonMessageBus): Props = - Props(classOf[AppsRedisSubscriberActor], jsonMsgBus, - redisHost, redisPort, - channels, patterns).withDispatcher("akka.rediscala-subscriber-worker-dispatcher") -} - -class AppsRedisSubscriberActor(jsonMsgBus: IncomingJsonMessageBus, redisHost: String, - redisPort: Int, - channels: Seq[String] = Nil, patterns: Seq[String] = Nil) - extends RedisSubscriberActor( - new InetSocketAddress(redisHost, redisPort), - channels, patterns, onConnectStatus = connected => { println(s"connected: $connected") }) with SystemConfiguration { - - override val supervisorStrategy = OneForOneStrategy(maxNrOfRetries = 10, withinTimeRange = 1 minute) { - case e: Exception => { - val sw: StringWriter = new StringWriter() - sw.write("An exception has been thrown on AppsRedisSubscriberActor, exception message [" + e.getMessage() + "] (full stacktrace below)\n") - e.printStackTrace(new PrintWriter(sw)) - log.error(sw.toString()) - Resume - } - } - - // Set the name of this client to be able to distinguish when doing - // CLIENT LIST on redis-cli - write(ClientSetname("BbbScreenshareAkkaSub").encodedRequest) - - def onMessage(message: Message) { - //log.error(s"SHOULD NOT BE RECEIVING: $message") - if (message.channel == fromAkkaAppsRedisChannel) { - val receivedJsonMessage = new ReceivedJsonMessage(message.channel, message.data.utf8String) - //log.debug(s"RECEIVED:\n [${receivedJsonMessage.channel}] \n ${receivedJsonMessage.data} \n") - jsonMsgBus.publish(IncomingJsonMessage(toScreenshareAppsJsonChannel, receivedJsonMessage)) - } - } - - def onPMessage(pmessage: PMessage) { - //log.debug(s"RECEIVED:\n ${pmessage.data.utf8String} \n") - } -} diff --git a/bbb-screenshare/app/src/main/scala/org/bigbluebutton/app/screenshare/redis/IncomingJsonMessageBus.scala b/bbb-screenshare/app/src/main/scala/org/bigbluebutton/app/screenshare/redis/IncomingJsonMessageBus.scala deleted file mode 100755 index 532b120a86c332aaec568ed93dd0941bc21f5fcf..0000000000000000000000000000000000000000 --- a/bbb-screenshare/app/src/main/scala/org/bigbluebutton/app/screenshare/redis/IncomingJsonMessageBus.scala +++ /dev/null @@ -1,31 +0,0 @@ -package org.bigbluebutton.app.screenshare.redis - -import akka.actor.ActorRef -import akka.event.{ EventBus, LookupClassification } - -case class ReceivedJsonMessage(channel: String, data: String) -case class IncomingJsonMessage(val topic: String, val payload: ReceivedJsonMessage) - -class IncomingJsonMessageBus extends EventBus with LookupClassification { - type Event = IncomingJsonMessage - type Classifier = String - type Subscriber = ActorRef - - // is used for extracting the classifier from the incoming events - override protected def classify(event: Event): Classifier = event.topic - - // will be invoked for each event for all subscribers which registered themselves - // for the event’s classifier - override protected def publish(event: Event, subscriber: Subscriber): Unit = { - subscriber ! event.payload - } - - // must define a full order over the subscribers, expressed as expected from - // `java.lang.Comparable.compare` - override protected def compareSubscribers(a: Subscriber, b: Subscriber): Int = - a.compareTo(b) - - // determines the initial size of the index data structure - // used internally (i.e. the expected number of different classifiers) - override protected def mapSize: Int = 128 -} diff --git a/bbb-screenshare/app/src/main/scala/org/bigbluebutton/app/screenshare/redis/ReceivedJsonMsgHandlerActor.scala b/bbb-screenshare/app/src/main/scala/org/bigbluebutton/app/screenshare/redis/ReceivedJsonMsgHandlerActor.scala index 36d638988c3a31b63c4149b6f53b8cd2b6db2af0..ca57f9d59956fbd81963126d41fb0903f7637b59 100755 --- a/bbb-screenshare/app/src/main/scala/org/bigbluebutton/app/screenshare/redis/ReceivedJsonMsgHandlerActor.scala +++ b/bbb-screenshare/app/src/main/scala/org/bigbluebutton/app/screenshare/redis/ReceivedJsonMsgHandlerActor.scala @@ -5,7 +5,8 @@ import org.bigbluebutton.app.screenshare.server.sessions.messages.{ MeetingCreat import akka.actor.{ Actor, ActorLogging, ActorRef, Props } import scala.reflect.runtime.universe._ -import org.bigbluebutton.common2.msgs._ +import org.bigbluebutton.common2.msgs._ +import org.bigbluebutton.common2.bus.ReceivedJsonMessage object ReceivedJsonMsgHandlerActor { def props(screenshareManager: ActorRef): Props = diff --git a/bbb-screenshare/app/src/main/scala/org/bigbluebutton/app/screenshare/redis/ScreenshareRedisSubscriberActor.scala b/bbb-screenshare/app/src/main/scala/org/bigbluebutton/app/screenshare/redis/ScreenshareRedisSubscriberActor.scala new file mode 100755 index 0000000000000000000000000000000000000000..8c4875497e4d2065644e23aa1469ceca4a1d959d --- /dev/null +++ b/bbb-screenshare/app/src/main/scala/org/bigbluebutton/app/screenshare/redis/ScreenshareRedisSubscriberActor.scala @@ -0,0 +1,32 @@ +package org.bigbluebutton.app.screenshare.redis + +import org.bigbluebutton.app.screenshare.SystemConfiguration +import org.bigbluebutton.common2.bus.IncomingJsonMessageBus +import org.bigbluebutton.common2.redis.RedisSubscriberProvider + +import akka.actor.ActorSystem +import akka.actor.Props + +object ScreenshareRedisSubscriberActor extends SystemConfiguration { + + val channels = Seq(fromAkkaAppsRedisChannel) + val patterns = Seq("bigbluebutton:to-bbb-apps:*", "bigbluebutton:from-voice-conf:*") + + def props(system: ActorSystem, jsonMsgBus: IncomingJsonMessageBus): Props = + Props( + classOf[ScreenshareRedisSubscriberActor], + system, jsonMsgBus, + redisHost, redisPort, + channels, patterns).withDispatcher("akka.redis-subscriber-worker-dispatcher") +} + +class ScreenshareRedisSubscriberActor( + system: ActorSystem, + jsonMsgBus: IncomingJsonMessageBus, + redisHost: String, redisPort: Int, + channels: Seq[String] = Nil, patterns: Seq[String] = Nil) + extends RedisSubscriberProvider(system, "BbbScreenshareAkkaSub", channels, patterns, jsonMsgBus) with SystemConfiguration { + + addListener(toScreenshareAppsJsonChannel) + subscribe() +} diff --git a/bbb-screenshare/app/src/main/scala/org/bigbluebutton/app/screenshare/server/util/LogHelper.scala b/bbb-screenshare/app/src/main/scala/org/bigbluebutton/app/screenshare/server/util/LogHelper.scala index dbe83dc52d9a768277f146622f565f816d6b2033..2ed8ee87d1a79cd87144b8b0c09b2e4bc9d4c960 100755 --- a/bbb-screenshare/app/src/main/scala/org/bigbluebutton/app/screenshare/server/util/LogHelper.scala +++ b/bbb-screenshare/app/src/main/scala/org/bigbluebutton/app/screenshare/server/util/LogHelper.scala @@ -1,6 +1,5 @@ package org.bigbluebutton.app.screenshare.server.util -import org.slf4j.Logger import org.red5.logging.Red5LoggerFactory /** diff --git a/bbb-screenshare/app/src/main/webapp/WEB-INF/bbb-red5-redis-pubsub.xml b/bbb-screenshare/app/src/main/webapp/WEB-INF/bbb-red5-redis-pubsub.xml deleted file mode 100755 index d056fd28aa14a8c318a9a45c428f294edc6cadee..0000000000000000000000000000000000000000 --- a/bbb-screenshare/app/src/main/webapp/WEB-INF/bbb-red5-redis-pubsub.xml +++ /dev/null @@ -1,37 +0,0 @@ -<?xml version="1.0" encoding="UTF-8"?> -<!-- - -BigBlueButton open source conferencing system - http://www.bigbluebutton.org/ - -Copyright (c) 2014 BigBlueButton Inc. and by respective authors (see below). - -This program is free software; you can redistribute it and/or modify it under the -terms of the GNU Lesser General Public License as published by the Free Software -Foundation; either version 3.0 of the License, or (at your option) any later -version. - -BigBlueButton is distributed in the hope that it will be useful, but WITHOUT ANY -WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A -PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. - -You should have received a copy of the GNU Lesser General Public License along -with BigBlueButton; if not, see <http://www.gnu.org/licenses/>. - ---> -<beans xmlns="http://www.springframework.org/schema/beans" - xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" - xmlns:util="http://www.springframework.org/schema/util" - xsi:schemaLocation="http://www.springframework.org/schema/beans - http://www.springframework.org/schema/beans/spring-beans-2.5.xsd - http://www.springframework.org/schema/util - http://www.springframework.org/schema/util/spring-util-2.0.xsd - "> - - <bean id="red5RedisSender" - class="org.bigbluebutton.app.screenshare.messaging.redis.MessageSender" - init-method="start" destroy-method="stop"> - <property name="host" value="${redis.host}" /> - <property name="port" value="${redis.port}" /> - </bean> - -</beans> diff --git a/bbb-screenshare/app/src/main/webapp/WEB-INF/bbb-redis-pool.xml b/bbb-screenshare/app/src/main/webapp/WEB-INF/bbb-redis-messaging.xml similarity index 78% rename from bbb-screenshare/app/src/main/webapp/WEB-INF/bbb-redis-pool.xml rename to bbb-screenshare/app/src/main/webapp/WEB-INF/bbb-redis-messaging.xml index 5aa3cc5f9aefa4a4b8af09abc545e26a898800db..49057aa845d009416b86f9763d1602060c0ae688 100755 --- a/bbb-screenshare/app/src/main/webapp/WEB-INF/bbb-redis-pool.xml +++ b/bbb-screenshare/app/src/main/webapp/WEB-INF/bbb-redis-messaging.xml @@ -27,10 +27,12 @@ with BigBlueButton; if not, see <http://www.gnu.org/licenses/>. http://www.springframework.org/schema/util/spring-util-2.0.xsd "> -<!-- - <bean id="redisPool" class="redis.clients.jedis.JedisPool"> - <constructor-arg index="0" value="${redis.host}"/> - <constructor-arg index="1" value="${redis.port}"/> + <bean id="redisStorageService" + class="org.bigbluebutton.common2.redis.RedisStorageService" + init-method="start" destroy-method="stop"> + <property name="host" value="${redis.host}" /> + <property name="port" value="${redis.port}" /> + <property name="password" value="${redis.password:}" /> + <property name="clientName" value="BbbScreenshare" /> </bean> ---> </beans> diff --git a/bbb-screenshare/app/src/main/webapp/WEB-INF/classes/application.conf b/bbb-screenshare/app/src/main/webapp/WEB-INF/classes/application.conf index 74183500ea75522d1927e5742c4f93d2722b1269..3b72838eb4bbe89bb4f2c7d26663b8c90cbd2a2b 100755 --- a/bbb-screenshare/app/src/main/webapp/WEB-INF/classes/application.conf +++ b/bbb-screenshare/app/src/main/webapp/WEB-INF/classes/application.conf @@ -10,7 +10,7 @@ akka { loggers = ["akka.event.slf4j.Slf4jLogger"] loglevel = "DEBUG" - rediscala-publish-worker-dispatcher { + redis-publish-worker-dispatcher { mailbox-type = "akka.dispatch.SingleConsumerOnlyUnboundedMailbox" # Throughput defines the maximum number of messages to be # processed per actor before the thread jumps to the next actor. @@ -18,7 +18,7 @@ akka { throughput = 512 } - rediscala-subscriber-worker-dispatcher { + redis-subscriber-worker-dispatcher { mailbox-type = "akka.dispatch.SingleConsumerOnlyUnboundedMailbox" # Throughput defines the maximum number of messages to be # processed per actor before the thread jumps to the next actor. diff --git a/bbb-screenshare/app/src/main/webapp/WEB-INF/classes/screenshare-app.conf b/bbb-screenshare/app/src/main/webapp/WEB-INF/classes/screenshare-app.conf index 7a46eb9c45206cb1b304a93596212cf8b9595647..32a843bfaf817b1ee334bcdbe3ec5fba9b793ff5 100755 --- a/bbb-screenshare/app/src/main/webapp/WEB-INF/classes/screenshare-app.conf +++ b/bbb-screenshare/app/src/main/webapp/WEB-INF/classes/screenshare-app.conf @@ -10,7 +10,7 @@ akka { loggers = ["akka.event.slf4j.Slf4jLoggerDDD"] loglevel = "DEBUG" - rediscala-publish-worker-dispatcher { + redis-publish-worker-dispatcher { mailbox-type = "akka.dispatch.SingleConsumerOnlyUnboundedMailbox" # Throughput defines the maximum number of messages to be # processed per actor before the thread jumps to the next actor. @@ -18,7 +18,7 @@ akka { throughput = 512 } - rediscala-subscriber-worker-dispatcher { + redis-subscriber-worker-dispatcher { mailbox-type = "akka.dispatch.SingleConsumerOnlyUnboundedMailbox" # Throughput defines the maximum number of messages to be # processed per actor before the thread jumps to the next actor. diff --git a/bbb-screenshare/app/src/main/webapp/WEB-INF/red5-web.xml b/bbb-screenshare/app/src/main/webapp/WEB-INF/red5-web.xml index 1fd0b7bfc69867548f365a3042d4383967e17aa1..91e61cb370d46ab8fc3a57cefcea8e19ecf720a0 100755 --- a/bbb-screenshare/app/src/main/webapp/WEB-INF/red5-web.xml +++ b/bbb-screenshare/app/src/main/webapp/WEB-INF/red5-web.xml @@ -35,6 +35,18 @@ with BigBlueButton; if not, see <http://www.gnu.org/licenses/>. </property> </bean> + <bean id="screenShareApplication" class="org.bigbluebutton.app.screenshare.ScreenShareApplication"> + <constructor-arg index="0" ref="messageBus"/> + <constructor-arg index="1" value="${jnlpFile}"/> + <constructor-arg index="2" value="${streamBaseUrl}"/> + </bean> + + <bean id="connectionInvokerService" class="org.bigbluebutton.app.screenshare.red5.ConnectionInvokerService" + init-method="start" destroy-method="stop"> + </bean> + + <bean id="meetingManager" class="org.bigbluebutton.app.screenshare.MeetingManager"/> + <bean id="web.context" class="org.red5.server.Context" autowire="byType"/> <bean id="web.scope" class="org.red5.server.scope.WebScope" init-method="register"> @@ -48,36 +60,27 @@ with BigBlueButton; if not, see <http://www.gnu.org/licenses/>. <bean id="web.handler" class="org.bigbluebutton.app.screenshare.red5.Red5AppAdapter"> <property name="streamBaseUrl" value="${streamBaseUrl}"/> - <property name="eventRecordingService" ref="eventRecordingService"/> + <property name="redisStorageService" ref="redisStorageService"/> <property name="recordingDirectory" value="${recordingDirectory}"/> <property name="application" ref="screenShareApplication"/> <property name="messageSender" ref="connectionInvokerService"/> <property name="meetingManager" ref="meetingManager"/> </bean> - <bean id="meetingManager" class="org.bigbluebutton.app.screenshare.MeetingManager"/> - - <bean id="screenshare.service" class="org.bigbluebutton.app.screenshare.red5.Red5AppService"> - <property name="appHandler" ref="red5AppHandler"/> - <property name="red5RedisSender" ref="red5RedisSender"/> - </bean> - <bean id="red5AppHandler" class="org.bigbluebutton.app.screenshare.red5.Red5AppHandler"> <property name="application" ref="screenShareApplication"/> <property name="messageSender" ref="connectionInvokerService"/> </bean> + + <bean id="screenshare.service" class="org.bigbluebutton.app.screenshare.red5.Red5AppService"> + <property name="appHandler" ref="red5AppHandler"/> + </bean> <!-- The IoHandler implementation --> <bean id="screenCaptureHandler" class="org.bigbluebutton.app.screenshare.server.socket.BlockStreamEventMessageHandler"> <property name="application" ref="screenShareApplication"/> </bean> - <bean id="screenShareApplication" class="org.bigbluebutton.app.screenshare.ScreenShareApplication"> - <constructor-arg index="0" ref="messageBus"/> - <constructor-arg index="1" value="${jnlpFile}"/> - <constructor-arg index="2" value="${streamBaseUrl}"/> - </bean> - <bean id="eventListenerImp" class="org.bigbluebutton.app.screenshare.red5.EventListenerImp"> <property name="messageSender" ref="connectionInvokerService"/> <property name="meetingManager" ref="meetingManager"/> @@ -100,21 +103,6 @@ with BigBlueButton; if not, see <http://www.gnu.org/licenses/>. </property> </bean> - <bean id="connectionInvokerService" class="org.bigbluebutton.app.screenshare.red5.ConnectionInvokerService" - init-method="start" destroy-method="stop"> - </bean> - - <bean id="eventRecordingService" class="org.bigbluebutton.app.screenshare.EventRecordingService"> - <constructor-arg index="0" value="${redis.host}"/> - <constructor-arg index="1" value="${redis.port}"/> - </bean> - - <bean id="redisRecorder" class="org.bigbluebutton.app.screenshare.server.recorder.EventRecorder"> - <constructor-arg index="0" value="${redis.host}"/> - <constructor-arg index="1" value="${redis.port}"/> - </bean> - + <import resource="bbb-redis-messaging.xml"/> - <import resource="bbb-redis-pool.xml"/> - <import resource="bbb-red5-redis-pubsub.xml"/> </beans> diff --git a/bbb-screenshare/app/src/main/webapp/WEB-INF/screenshare.properties b/bbb-screenshare/app/src/main/webapp/WEB-INF/screenshare.properties index 55bd99c50f97b91fe009d1446db69a6948d04348..808616484c689c8d140c98c061b29fc0dd0b591a 100755 --- a/bbb-screenshare/app/src/main/webapp/WEB-INF/screenshare.properties +++ b/bbb-screenshare/app/src/main/webapp/WEB-INF/screenshare.properties @@ -1,30 +1,29 @@ -# -# NOTE: default properties. -# -# NOTE!!!! NOTE!!!! NOTE!!!! NOTE!!!! NOTE!!!! -# When making changes that you don't want checked-in, do -# git update-index --assume-unchanged <file> -# -# To have git track the changes again -# git update-index --no-assume-unchanged <file> -# - -recordingDirectory=/usr/share/red5/webapps/screenshare/streams - -redis.host=127.0.0.1 -redis.port=6379 - - -streamBaseUrl=rtmp://192.168.23.22/screenshare -jnlpUrl=http://192.168.23.22/screenshare -jnlpFile=http://192.168.23.22/screenshare/screenshare.jnlp -useH264=false - -# NOTES: -# 1. GOP (group of pictures) is calculated as frameRate * keyFrameInterval -# 2. intra-refresh=1 doesn't work in Chrome. Late comers can't view the stream as -# the user missed the key frame -# 3. keyFrameInterval is in seconds -# 4. Make sure you encode & into & as it will break the JNLP XML -#codecOptions=crf=36&preset=veryfast&tune=animation,zerolatency&frameRate=12.0&keyFrameInterval=6 -codecOptions=crf=38&preset=veryfast&tune=zerolatency&frameRate=5.0&keyFrameInterval=5 +# +# NOTE: default properties. +# +# NOTE!!!! NOTE!!!! NOTE!!!! NOTE!!!! NOTE!!!! +# When making changes that you don't want checked-in, do +# git update-index --assume-unchanged <file> +# +# To have git track the changes again +# git update-index --no-assume-unchanged <file> +# + +recordingDirectory=/usr/share/red5/webapps/screenshare/streams + +redis.host=127.0.0.1 +redis.port=6379 + +streamBaseUrl=rtmp://192.168.23.22/screenshare +jnlpUrl=http://192.168.23.22/screenshare +jnlpFile=http://192.168.23.22/screenshare/screenshare.jnlp +useH264=false + +# NOTES: +# 1. GOP (group of pictures) is calculated as frameRate * keyFrameInterval +# 2. intra-refresh=1 doesn't work in Chrome. Late comers can't view the stream as +# the user missed the key frame +# 3. keyFrameInterval is in seconds +# 4. Make sure you encode & into & as it will break the JNLP XML +#codecOptions=crf=36&preset=veryfast&tune=animation,zerolatency&frameRate=12.0&keyFrameInterval=6 +codecOptions=crf=38&preset=veryfast&tune=zerolatency&frameRate=5.0&keyFrameInterval=5 diff --git a/bbb-screenshare/app/src/test/java/org/bigbluebutton/deskshare/server/recorder/FileRecorderTest.java b/bbb-screenshare/app/src/test/java/org/bigbluebutton/deskshare/server/recorder/FileRecorderTest.java deleted file mode 100755 index f5491111f761d49201584918ff900cdd51a8c3d3..0000000000000000000000000000000000000000 --- a/bbb-screenshare/app/src/test/java/org/bigbluebutton/deskshare/server/recorder/FileRecorderTest.java +++ /dev/null @@ -1,12 +0,0 @@ -package org.bigbluebutton.deskshare.server.recorder; - -import org.testng.Assert; -import org.testng.annotations.Test; - -public class FileRecorderTest { - - @Test - public void testHello() { - Assert.assertEquals(true, true); - } -} diff --git a/bbb-screenshare/jws/native-libs/ffmpeg-linux-x86/h264/sign-jar.sh b/bbb-screenshare/jws/native-libs/ffmpeg-linux-x86/h264/sign-jar.sh index 01e78770a36a2cd553bea92b1646eb6e25e27319..09225a39145e51797093fae83685c958b7b4a4bd 100755 --- a/bbb-screenshare/jws/native-libs/ffmpeg-linux-x86/h264/sign-jar.sh +++ b/bbb-screenshare/jws/native-libs/ffmpeg-linux-x86/h264/sign-jar.sh @@ -1,18 +1,16 @@ -FFMPEG=ffmpeg-3.0.2-1.2-linux-x86-h264.jar -mkdir workdir -cp $FFMPEG workdir/ffmpeg-linux-x86.jar -rm -rf src -mkdir -p src/main/resources -mkdir -p src/main/java -cd workdir -jar xvf ffmpeg-linux-x86.jar -cp org/bytedeco/javacpp/linux-x86/*.so* ../src/main/resources -cd .. -gradle jar -cp build/libs/ffmpeg-linux-x86-0.0.1.jar ../../unsigned-jars/ffmpeg-linux-x86-h264-unsigned.jar -ant sign-jar -cp build/libs/ffmpeg-linux-x86-0.0.1.jar ../../../../app/jws/lib/ffmpeg-linux-x86-h264.jar -rm -rf workdir -rm -rf src - - +FFMPEG=ffmpeg-3.0.2-1.2-linux-x86-h264.jar +mkdir workdir +cp $FFMPEG workdir/ffmpeg-linux-x86.jar +rm -rf src +mkdir -p src/main/resources +mkdir -p src/main/java +cd workdir +jar xvf ffmpeg-linux-x86.jar +cp org/bytedeco/javacpp/linux-x86/*.so* ../src/main/resources +cd .. +gradle jar +cp build/libs/ffmpeg-linux-x86-0.0.1.jar ../../unsigned-jars/ffmpeg-linux-x86-h264-unsigned.jar +ant sign-jar +cp build/libs/ffmpeg-linux-x86-0.0.1.jar ../../../../app/jws/lib/ffmpeg-linux-x86-h264.jar +rm -rf workdir +rm -rf src diff --git a/bbb-screenshare/jws/native-libs/ffmpeg-linux-x86/svc2/sign-jar.sh b/bbb-screenshare/jws/native-libs/ffmpeg-linux-x86/svc2/sign-jar.sh index e82feeb760ee234e2a44d540c57802b734831493..ae658006f6ca72a6223abd5b90346d192c1c03e7 100755 --- a/bbb-screenshare/jws/native-libs/ffmpeg-linux-x86/svc2/sign-jar.sh +++ b/bbb-screenshare/jws/native-libs/ffmpeg-linux-x86/svc2/sign-jar.sh @@ -1,18 +1,16 @@ -FFMPEG=ffmpeg-3.0.2-1.2-linux-x86-svc2.jar -mkdir workdir -cp $FFMPEG workdir/ffmpeg-linux-x86.jar -rm -rf src -mkdir -p src/main/resources -mkdir -p src/main/java -cd workdir -jar xvf ffmpeg-linux-x86.jar -cp org/bytedeco/javacpp/linux-x86/*.so* ../src/main/resources -cd .. -gradle jar -cp build/libs/ffmpeg-linux-x86-0.0.1.jar ../../unsigned-jars/ffmpeg-linux-x86-svc2-unsigned.jar -ant sign-jar -cp build/libs/ffmpeg-linux-x86-0.0.1.jar ../../../../app/jws/lib/ffmpeg-linux-x86-svc2.jar -rm -rf workdir -rm -rf src - - +FFMPEG=ffmpeg-3.0.2-1.2-linux-x86-svc2.jar +mkdir workdir +cp $FFMPEG workdir/ffmpeg-linux-x86.jar +rm -rf src +mkdir -p src/main/resources +mkdir -p src/main/java +cd workdir +jar xvf ffmpeg-linux-x86.jar +cp org/bytedeco/javacpp/linux-x86/*.so* ../src/main/resources +cd .. +gradle jar +cp build/libs/ffmpeg-linux-x86-0.0.1.jar ../../unsigned-jars/ffmpeg-linux-x86-svc2-unsigned.jar +ant sign-jar +cp build/libs/ffmpeg-linux-x86-0.0.1.jar ../../../../app/jws/lib/ffmpeg-linux-x86-svc2.jar +rm -rf workdir +rm -rf src diff --git a/bbb-screenshare/jws/native-libs/ffmpeg-linux-x86_64/h264/sign-jar.sh b/bbb-screenshare/jws/native-libs/ffmpeg-linux-x86_64/h264/sign-jar.sh index 2ffaf065ce97591bfc898cbd2a6cadefa6d6b715..d9c1ba9c026dbb71e1bd3ed53c167783280f669b 100755 --- a/bbb-screenshare/jws/native-libs/ffmpeg-linux-x86_64/h264/sign-jar.sh +++ b/bbb-screenshare/jws/native-libs/ffmpeg-linux-x86_64/h264/sign-jar.sh @@ -1,17 +1,16 @@ -FFMPEG=ffmpeg-3.0.2-1.2-linux-x86_64-h264.jar -mkdir workdir -cp $FFMPEG workdir/ffmpeg-linux-x86_64.jar -rm -rf src -mkdir -p src/main/resources -mkdir -p src/main/java -cd workdir -jar xvf ffmpeg-linux-x86_64.jar -cp org/bytedeco/javacpp/linux-x86_64/*.so* ../src/main/resources -cd .. -gradle jar -cp build/libs/ffmpeg-linux-x86_64-0.0.1.jar ../../unsigned-jars/ffmpeg-linux-x86_64-h264-unsigned.jar -ant sign-jar -cp build/libs/ffmpeg-linux-x86_64-0.0.1.jar ../../../../app/jws/lib/ffmpeg-linux-x86_64-h264.jar -rm -rf workdir -rm -rf src - +FFMPEG=ffmpeg-3.0.2-1.2-linux-x86_64-h264.jar +mkdir workdir +cp $FFMPEG workdir/ffmpeg-linux-x86_64.jar +rm -rf src +mkdir -p src/main/resources +mkdir -p src/main/java +cd workdir +jar xvf ffmpeg-linux-x86_64.jar +cp org/bytedeco/javacpp/linux-x86_64/*.so* ../src/main/resources +cd .. +gradle jar +cp build/libs/ffmpeg-linux-x86_64-0.0.1.jar ../../unsigned-jars/ffmpeg-linux-x86_64-h264-unsigned.jar +ant sign-jar +cp build/libs/ffmpeg-linux-x86_64-0.0.1.jar ../../../../app/jws/lib/ffmpeg-linux-x86_64-h264.jar +rm -rf workdir +rm -rf src diff --git a/bbb-screenshare/jws/native-libs/ffmpeg-linux-x86_64/svc2/sign-jar.sh b/bbb-screenshare/jws/native-libs/ffmpeg-linux-x86_64/svc2/sign-jar.sh index 781bc3e084016be9de8a136cc2ad0806c86e83d9..72f4872670e8eb4171f6ff2919c98d2469535729 100755 --- a/bbb-screenshare/jws/native-libs/ffmpeg-linux-x86_64/svc2/sign-jar.sh +++ b/bbb-screenshare/jws/native-libs/ffmpeg-linux-x86_64/svc2/sign-jar.sh @@ -1,17 +1,16 @@ -FFMPEG=ffmpeg-3.0.2-1.2-linux-x86_64-svc2.jar -mkdir workdir -cp $FFMPEG workdir/ffmpeg-linux-x86_64.jar -rm -rf src -mkdir -p src/main/resources -mkdir -p src/main/java -cd workdir -jar xvf ffmpeg-linux-x86_64.jar -cp org/bytedeco/javacpp/linux-x86_64/*.so* ../src/main/resources -cd .. -gradle jar -cp build/libs/ffmpeg-linux-x86_64-0.0.1.jar ../../unsigned-jars/ffmpeg-linux-x86_64-svc2-unsigned.jar -ant sign-jar -cp build/libs/ffmpeg-linux-x86_64-0.0.1.jar ../../../../app/jws/lib/ffmpeg-linux-x86_64-svc2.jar -rm -rf workdir -rm -rf src - +FFMPEG=ffmpeg-3.0.2-1.2-linux-x86_64-svc2.jar +mkdir workdir +cp $FFMPEG workdir/ffmpeg-linux-x86_64.jar +rm -rf src +mkdir -p src/main/resources +mkdir -p src/main/java +cd workdir +jar xvf ffmpeg-linux-x86_64.jar +cp org/bytedeco/javacpp/linux-x86_64/*.so* ../src/main/resources +cd .. +gradle jar +cp build/libs/ffmpeg-linux-x86_64-0.0.1.jar ../../unsigned-jars/ffmpeg-linux-x86_64-svc2-unsigned.jar +ant sign-jar +cp build/libs/ffmpeg-linux-x86_64-0.0.1.jar ../../../../app/jws/lib/ffmpeg-linux-x86_64-svc2.jar +rm -rf workdir +rm -rf src diff --git a/bbb-screenshare/jws/native-libs/ffmpeg-macosx-x86_64/h264/sign-jar.sh b/bbb-screenshare/jws/native-libs/ffmpeg-macosx-x86_64/h264/sign-jar.sh index 61c91813178c545c9770e7e7aa91596c78e22cdb..d8255408d3d9025dd48518325b9cf59797cd872e 100755 --- a/bbb-screenshare/jws/native-libs/ffmpeg-macosx-x86_64/h264/sign-jar.sh +++ b/bbb-screenshare/jws/native-libs/ffmpeg-macosx-x86_64/h264/sign-jar.sh @@ -1,16 +1,15 @@ -FFMPEG=ffmpeg-3.0.2-1.2-macosx-x86_64-h264.jar -mkdir workdir -cp $FFMPEG workdir/ffmpeg-macosx-x86_64.jar -rm -rf src -mkdir -p src/main/resources -cd workdir -jar xvf ffmpeg-macosx-x86_64.jar -cp org/bytedeco/javacpp/macosx-x86_64/* ../src/main/resources -cd .. -rm -rf workdir -gradle jar -cp build/libs/ffmpeg-macosx-x86_64-0.0.1.jar ../../unsigned-jars/ffmpeg-macosx-x86_64-h264-unsigned.jar -ant sign-jar -cp build/libs/ffmpeg-macosx-x86_64-0.0.1.jar ../../../../app/jws/lib/ffmpeg-macosx-x86_64-h264.jar -rm -rf src - +FFMPEG=ffmpeg-3.0.2-1.2-macosx-x86_64-h264.jar +mkdir workdir +cp $FFMPEG workdir/ffmpeg-macosx-x86_64.jar +rm -rf src +mkdir -p src/main/resources +cd workdir +jar xvf ffmpeg-macosx-x86_64.jar +cp org/bytedeco/javacpp/macosx-x86_64/* ../src/main/resources +cd .. +rm -rf workdir +gradle jar +cp build/libs/ffmpeg-macosx-x86_64-0.0.1.jar ../../unsigned-jars/ffmpeg-macosx-x86_64-h264-unsigned.jar +ant sign-jar +cp build/libs/ffmpeg-macosx-x86_64-0.0.1.jar ../../../../app/jws/lib/ffmpeg-macosx-x86_64-h264.jar +rm -rf src diff --git a/bbb-screenshare/jws/native-libs/ffmpeg-macosx-x86_64/svc2/sign-jar.sh b/bbb-screenshare/jws/native-libs/ffmpeg-macosx-x86_64/svc2/sign-jar.sh index 159d670c4b56cf9386e2b59600b9011f25ec7f1b..1639a806d0892dd8ca0350a592fd1ebaceccf7cc 100755 --- a/bbb-screenshare/jws/native-libs/ffmpeg-macosx-x86_64/svc2/sign-jar.sh +++ b/bbb-screenshare/jws/native-libs/ffmpeg-macosx-x86_64/svc2/sign-jar.sh @@ -1,16 +1,15 @@ -FFMPEG=ffmpeg-3.0.2-1.2-macosx-x86_64-svc2.jar -mkdir workdir -cp $FFMPEG workdir/ffmpeg-macosx-x86_64.jar -rm -rf src -mkdir -p src/main/resources -cd workdir -jar xvf ffmpeg-macosx-x86_64.jar -cp org/bytedeco/javacpp/macosx-x86_64/* ../src/main/resources -cd .. -rm -rf workdir -gradle jar -cp build/libs/ffmpeg-macosx-x86_64-0.0.1.jar ../../unsigned-jars/ffmpeg-macosx-x86_64-svc2-unsigned.jar -ant sign-jar -cp build/libs/ffmpeg-macosx-x86_64-0.0.1.jar ../../../../app/jws/lib/ffmpeg-macosx-x86_64-svc2.jar -rm -rf src - +FFMPEG=ffmpeg-3.0.2-1.2-macosx-x86_64-svc2.jar +mkdir workdir +cp $FFMPEG workdir/ffmpeg-macosx-x86_64.jar +rm -rf src +mkdir -p src/main/resources +cd workdir +jar xvf ffmpeg-macosx-x86_64.jar +cp org/bytedeco/javacpp/macosx-x86_64/* ../src/main/resources +cd .. +rm -rf workdir +gradle jar +cp build/libs/ffmpeg-macosx-x86_64-0.0.1.jar ../../unsigned-jars/ffmpeg-macosx-x86_64-svc2-unsigned.jar +ant sign-jar +cp build/libs/ffmpeg-macosx-x86_64-0.0.1.jar ../../../../app/jws/lib/ffmpeg-macosx-x86_64-svc2.jar +rm -rf src diff --git a/bbb-screenshare/jws/native-libs/ffmpeg-windows-x86/h264/sign-jar.sh b/bbb-screenshare/jws/native-libs/ffmpeg-windows-x86/h264/sign-jar.sh index 1fbfa0ec41526a3a280e8e6fad206b611f733a8f..4f7c36064ba816126685d5410e81bf7b1fbc599b 100755 --- a/bbb-screenshare/jws/native-libs/ffmpeg-windows-x86/h264/sign-jar.sh +++ b/bbb-screenshare/jws/native-libs/ffmpeg-windows-x86/h264/sign-jar.sh @@ -1,17 +1,16 @@ -FFMPEG=ffmpeg-3.0.2-1.2-windows-x86-h264.jar -mkdir workdir -cp $FFMPEG workdir/ffmpeg-windows-x86.jar -rm -rf src -mkdir -p src/main/resources -mkdir -p src/main/java -cd workdir -jar xvf ffmpeg-windows-x86.jar -cp org/bytedeco/javacpp/windows-x86/*.dll ../src/main/resources -cd .. -rm -rf workdir -gradle jar -cp build/libs/ffmpeg-windows-x86-0.0.1.jar ../../unsigned-jars/ffmpeg-win-x86-h264-unsigned.jar -ant sign-jar -cp build/libs/ffmpeg-windows-x86-0.0.1.jar ../../../../app/jws/lib/ffmpeg-win-x86-h264.jar -rm -rf src - +FFMPEG=ffmpeg-3.0.2-1.2-windows-x86-h264.jar +mkdir workdir +cp $FFMPEG workdir/ffmpeg-windows-x86.jar +rm -rf src +mkdir -p src/main/resources +mkdir -p src/main/java +cd workdir +jar xvf ffmpeg-windows-x86.jar +cp org/bytedeco/javacpp/windows-x86/*.dll ../src/main/resources +cd .. +rm -rf workdir +gradle jar +cp build/libs/ffmpeg-windows-x86-0.0.1.jar ../../unsigned-jars/ffmpeg-win-x86-h264-unsigned.jar +ant sign-jar +cp build/libs/ffmpeg-windows-x86-0.0.1.jar ../../../../app/jws/lib/ffmpeg-win-x86-h264.jar +rm -rf src diff --git a/bbb-screenshare/jws/native-libs/ffmpeg-windows-x86/svc2/sign-jar.sh b/bbb-screenshare/jws/native-libs/ffmpeg-windows-x86/svc2/sign-jar.sh index 1e8a5d3afb9c94885b7053e981e22560c2e9b39e..320f34680b98c219c449c9eacd21a611ca31ae06 100755 --- a/bbb-screenshare/jws/native-libs/ffmpeg-windows-x86/svc2/sign-jar.sh +++ b/bbb-screenshare/jws/native-libs/ffmpeg-windows-x86/svc2/sign-jar.sh @@ -1,17 +1,16 @@ -FFMPEG=ffmpeg-3.0.2-1.2-windows-x86-svc2.jar -mkdir workdir -cp $FFMPEG workdir/ffmpeg-windows-x86.jar -rm -rf src -mkdir -p src/main/resources -mkdir -p src/main/java -cd workdir -jar xvf ffmpeg-windows-x86.jar -cp org/bytedeco/javacpp/windows-x86/*.dll ../src/main/resources -cd .. -rm -rf workdir -gradle jar -cp build/libs/ffmpeg-windows-x86-0.0.1.jar ../../unsigned-jars/ffmpeg-win-x86-svc2-unsigned.jar -ant sign-jar -cp build/libs/ffmpeg-windows-x86-0.0.1.jar ../../../../app/jws/lib/ffmpeg-win-x86-svc2.jar -rm -rf src - +FFMPEG=ffmpeg-3.0.2-1.2-windows-x86-svc2.jar +mkdir workdir +cp $FFMPEG workdir/ffmpeg-windows-x86.jar +rm -rf src +mkdir -p src/main/resources +mkdir -p src/main/java +cd workdir +jar xvf ffmpeg-windows-x86.jar +cp org/bytedeco/javacpp/windows-x86/*.dll ../src/main/resources +cd .. +rm -rf workdir +gradle jar +cp build/libs/ffmpeg-windows-x86-0.0.1.jar ../../unsigned-jars/ffmpeg-win-x86-svc2-unsigned.jar +ant sign-jar +cp build/libs/ffmpeg-windows-x86-0.0.1.jar ../../../../app/jws/lib/ffmpeg-win-x86-svc2.jar +rm -rf src diff --git a/bbb-screenshare/jws/native-libs/ffmpeg-windows-x86_64/h264/sign-jar.sh b/bbb-screenshare/jws/native-libs/ffmpeg-windows-x86_64/h264/sign-jar.sh index 94ed509cc73fc2cfc9d6bce5a50e71b46ddbf683..7ace75cc22b9316e8bea64d1aa17e372858be86d 100755 --- a/bbb-screenshare/jws/native-libs/ffmpeg-windows-x86_64/h264/sign-jar.sh +++ b/bbb-screenshare/jws/native-libs/ffmpeg-windows-x86_64/h264/sign-jar.sh @@ -1,17 +1,16 @@ -FFMPEG=ffmpeg-3.0.2-1.2-windows-x86_64-h264.jar -mkdir workdir -cp $FFMPEG workdir/ffmpeg-windows-x86_64.jar -rm -rf src -mkdir -p src/main/resources -mkdir -p src/main/java -cd workdir -jar xvf ffmpeg-windows-x86_64.jar -cp org/bytedeco/javacpp/windows-x86_64/*.dll ../src/main/resources -cd .. -rm -rf workdir -gradle jar -cp build/libs/ffmpeg-windows-x86_64-0.0.1.jar ../../unsigned-jars/ffmpeg-win-x86_64-h264-unsigned.jar -ant sign-jar -cp build/libs/ffmpeg-windows-x86_64-0.0.1.jar ../../../../app/jws/lib/ffmpeg-win-x86_64-h264.jar -rm -rf src - +FFMPEG=ffmpeg-3.0.2-1.2-windows-x86_64-h264.jar +mkdir workdir +cp $FFMPEG workdir/ffmpeg-windows-x86_64.jar +rm -rf src +mkdir -p src/main/resources +mkdir -p src/main/java +cd workdir +jar xvf ffmpeg-windows-x86_64.jar +cp org/bytedeco/javacpp/windows-x86_64/*.dll ../src/main/resources +cd .. +rm -rf workdir +gradle jar +cp build/libs/ffmpeg-windows-x86_64-0.0.1.jar ../../unsigned-jars/ffmpeg-win-x86_64-h264-unsigned.jar +ant sign-jar +cp build/libs/ffmpeg-windows-x86_64-0.0.1.jar ../../../../app/jws/lib/ffmpeg-win-x86_64-h264.jar +rm -rf src diff --git a/bbb-screenshare/jws/native-libs/ffmpeg-windows-x86_64/svc2/sign-jar.sh b/bbb-screenshare/jws/native-libs/ffmpeg-windows-x86_64/svc2/sign-jar.sh index 4b8c8c1023acdb7649988c76470fbb13ebac00ee..0c67733b6a4defa15d824876a17133ffbe6e9962 100755 --- a/bbb-screenshare/jws/native-libs/ffmpeg-windows-x86_64/svc2/sign-jar.sh +++ b/bbb-screenshare/jws/native-libs/ffmpeg-windows-x86_64/svc2/sign-jar.sh @@ -1,17 +1,16 @@ -FFMPEG=ffmpeg-3.0.2-1.2-windows-x86_64-svc2.jar -mkdir workdir -cp $FFMPEG workdir/ffmpeg-windows-x86_64.jar -rm -rf src -mkdir -p src/main/resources -mkdir -p src/main/java -cd workdir -jar xvf ffmpeg-windows-x86_64.jar -cp org/bytedeco/javacpp/windows-x86_64/*.dll ../src/main/resources -cd .. -rm -rf workdir -gradle jar -cp build/libs/ffmpeg-windows-x86_64-0.0.1.jar ../../unsigned-jars/ffmpeg-win-x86_64-svc2-unsigned.jar -ant sign-jar -cp build/libs/ffmpeg-windows-x86_64-0.0.1.jar ../../../../app/jws/lib/ffmpeg-win-x86_64-svc2.jar -rm -rf src - +FFMPEG=ffmpeg-3.0.2-1.2-windows-x86_64-svc2.jar +mkdir workdir +cp $FFMPEG workdir/ffmpeg-windows-x86_64.jar +rm -rf src +mkdir -p src/main/resources +mkdir -p src/main/java +cd workdir +jar xvf ffmpeg-windows-x86_64.jar +cp org/bytedeco/javacpp/windows-x86_64/*.dll ../src/main/resources +cd .. +rm -rf workdir +gradle jar +cp build/libs/ffmpeg-windows-x86_64-0.0.1.jar ../../unsigned-jars/ffmpeg-win-x86_64-svc2-unsigned.jar +ant sign-jar +cp build/libs/ffmpeg-windows-x86_64-0.0.1.jar ../../../../app/jws/lib/ffmpeg-win-x86_64-svc2.jar +rm -rf src diff --git a/bbb-screenshare/jws/native-libs/unsigned-jars/sign-ffmpeg.sh b/bbb-screenshare/jws/native-libs/unsigned-jars/sign-ffmpeg.sh index 6cb98b0ea9930b6e4a4863e4c46fa0dc3d435a05..0ea0ec71b4c7e1e6115076ad991b3bfc52b603d0 100755 --- a/bbb-screenshare/jws/native-libs/unsigned-jars/sign-ffmpeg.sh +++ b/bbb-screenshare/jws/native-libs/unsigned-jars/sign-ffmpeg.sh @@ -1,16 +1,15 @@ -FFMPEG=ffmpeg-3.0.2-1.2.jar -if [ -d "workdir" ]; then - rm -rf workdir -fi -mkdir workdir -cp $FFMPEG workdir -cd workdir -jar xf $FFMPEG -rm $FFMPEG -rm -rf META-INF -jar cf ffmpeg.jar * -cd .. -ant sign-ffmpeg-jar -cp workdir/ffmpeg.jar ../../../app/jws/lib/ -rm -rf workdir - +FFMPEG=ffmpeg-3.0.2-1.2.jar +if [ -d "workdir" ]; then + rm -rf workdir +fi +mkdir workdir +cp $FFMPEG workdir +cd workdir +jar xf $FFMPEG +rm $FFMPEG +rm -rf META-INF +jar cf ffmpeg.jar * +cd .. +ant sign-ffmpeg-jar +cp workdir/ffmpeg.jar ../../../app/jws/lib/ +rm -rf workdir diff --git a/bbb-screenshare/jws/webstart/build.sh b/bbb-screenshare/jws/webstart/build.sh index 7074eba05053fb7e50983b4a92274174da04f6fd..fec75ea03c9b57d17e66cce5ad66a63e48f080a3 100755 --- a/bbb-screenshare/jws/webstart/build.sh +++ b/bbb-screenshare/jws/webstart/build.sh @@ -1,11 +1,10 @@ -if [ -d "lib" ]; then - rm -rf lib -fi -mkdir lib -cp ../../app/jws/lib/ffmpeg.jar lib -gradle clean -gradle jar -ant sign-jar -cp build/libs/javacv-screenshare-0.0.1.jar ../../app/jws/lib/ -rm -rf lib - +if [ -d "lib" ]; then + rm -rf lib +fi +mkdir lib +cp ../../app/jws/lib/ffmpeg.jar lib +gradle clean +gradle jar +ant sign-jar +cp build/libs/javacv-screenshare-0.0.1.jar ../../app/jws/lib/ +rm -rf lib diff --git a/bbb-screenshare/jws/webstart/deploy.sh b/bbb-screenshare/jws/webstart/deploy.sh index d8322cc8028df17d13f92a3e5de60fec086d9173..e49c761f5592b2ee5439484e40c99141912801c7 100755 --- a/bbb-screenshare/jws/webstart/deploy.sh +++ b/bbb-screenshare/jws/webstart/deploy.sh @@ -1,2 +1 @@ -cp build/libs/javacv-screenshare-0.0.1.jar /usr/share/red5/webapps/screenshare/lib - +cp build/libs/javacv-screenshare-0.0.1.jar /usr/share/red5/webapps/screenshare/lib diff --git a/bbb-video/build.gradle b/bbb-video/build.gradle index 88d9a056cc948c10ab31da12063df7dea0fb82cc..78aa01e7b88cc37f9c7818b8189c5d7c80687e31 100755 --- a/bbb-video/build.gradle +++ b/bbb-video/build.gradle @@ -17,24 +17,32 @@ repositories { mavenLocal() } +configurations { + runtime.exclude group: "org.slf4j", module: "slf4j-api" + runtime.exclude group: "org.red5", module: "red5-server" + runtime.exclude group: "org.red5", module: "red5-server-common" + runtime.exclude group: "org.red5", module: "red5-io" +} + dependencies { // Servlet providedCompile 'javax.servlet:servlet-api:2.5@jar' // Mina - providedCompile 'org.apache.mina:mina-core:2.0.17@jar' - providedCompile 'org.apache.mina:mina-integration-beans:2.0.17@jar' - providedCompile 'org.apache.mina:mina-integration-jmx:2.0.17@jar' + providedCompile 'org.apache.mina:mina-core:2.0.19@jar' + providedCompile 'org.apache.mina:mina-integration-beans:2.0.19@jar' + providedCompile 'org.apache.mina:mina-integration-jmx:2.0.19@jar' // Spring providedCompile 'org.springframework:spring-web:4.3.12.RELEASE@jar' - providedCompile 'org.springframework:spring-beans:4.3.12.RELEASE@jar' + providedCompile 'org.springframework:spring-beans:4.3.12.RELEASE@jar' providedCompile 'org.springframework:spring-context:4.3.12.RELEASE@jar' providedCompile 'org.springframework:spring-core:4.3.12.RELEASE@jar' - providedCompile 'org.red5:red5-server:1.0.10-M5@jar' - providedCompile 'org.red5:red5-server-common:1.0.10-M5@jar' - providedCompile 'org.red5:red5-io:1.0.10-M5@jar' + // Red5 + providedCompile 'org.red5:red5-server:1.0.10-M9@jar' + providedCompile 'org.red5:red5-server-common:1.0.10-M9@jar' + providedCompile 'org.red5:red5-io:1.0.10-M9@jar' // Logging providedCompile 'ch.qos.logback:logback-core:1.2.3@jar' @@ -49,23 +57,12 @@ dependencies { providedCompile 'org.springframework:spring-aop:4.3.12.RELEASE@jar' compile 'aopalliance:aopalliance:1.0@jar' - // Testing - compile 'org.easymock:easymock:2.4@jar' - - // Testing - testRuntime 'org.easymock:easymock:2.4@jar' - //redis - compile 'redis.clients:jedis:2.9.0' - compile 'org.apache.commons:commons-pool2:2.3' - compile 'com.google.code.gson:gson:2.5' - - compile 'org.apache.commons:commons-lang3:3.7' - compile 'org.bigbluebutton:bbb-common-message_2.12:0.0.19-SNAPSHOT' -} + compile 'org.apache.commons:commons-pool2:2.6.0' + compile 'com.google.code.gson:gson:2.8.5' -test { - useTestNG() + providedCompile 'org.apache.commons:commons-lang3:3.7' + compile 'org.bigbluebutton:bbb-common-message_2.12:0.0.20-SNAPSHOT' } war.doLast { @@ -76,6 +73,7 @@ war.doLast { task deploy() << { def red5AppsDir = '/usr/share/red5/webapps' def videoDir = new File("${red5AppsDir}/video") + println "Deleting $videoDir" if (videoDir.exists()) ant.delete(dir: videoDir) ant.mkdir(dir: videoDir) ant.copy(todir: videoDir) { diff --git a/bbb-video/deploy.sh b/bbb-video/deploy.sh index ff71c3ac7c685a5db422d9161f25a82b6c186300..b7cab36a4bc4e2ff6e2b72d8bb9bf36079ce3149 100755 --- a/bbb-video/deploy.sh +++ b/bbb-video/deploy.sh @@ -1,11 +1,10 @@ #!/bin/bash # deploying 'bigbluebutton-apps' to /usr/share/red5/webapps -sudo chown -R red5.red5 /usr/share/red5/webapps +sudo chmod -R 777 /usr/share/red5/webapps/* gradle clean gradle resolveDeps gradle war deploy -sudo chown -R red5.red5 /usr/share/red5/webapps - +sudo chown -R red5:red5 /usr/share/red5/webapps diff --git a/bbb-video/src/main/java/org/bigbluebutton/app/video/EventRecordingService.java b/bbb-video/src/main/java/org/bigbluebutton/app/video/EventRecordingService.java deleted file mode 100755 index 4bdec5c1df4e6b7f547df283519762cddc6c5006..0000000000000000000000000000000000000000 --- a/bbb-video/src/main/java/org/bigbluebutton/app/video/EventRecordingService.java +++ /dev/null @@ -1,78 +0,0 @@ -/** -* BigBlueButton open source conferencing system - http://www.bigbluebutton.org/ -* -* Copyright (c) 2012 BigBlueButton Inc. and by respective authors (see below). -* -* This program is free software; you can redistribute it and/or modify it under the -* terms of the GNU Lesser General Public License as published by the Free Software -* Foundation; either version 3.0 of the License, or (at your option) any later -* version. -* -* BigBlueButton is distributed in the hope that it will be useful, but WITHOUT ANY -* WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A -* PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. -* -* You should have received a copy of the GNU Lesser General Public License along -* with BigBlueButton; if not, see <http://www.gnu.org/licenses/>. -* -*/ -package org.bigbluebutton.app.video; - -import java.util.Map; -import org.apache.commons.pool2.impl.GenericObjectPoolConfig; -import org.red5.logging.Red5LoggerFactory; -import org.slf4j.Logger; -import redis.clients.jedis.Jedis; -import redis.clients.jedis.JedisPool; -import redis.clients.jedis.Protocol; - -public class EventRecordingService { - private static Logger log = Red5LoggerFactory.getLogger(EventRecordingService.class, "video"); - - private static final String COLON = ":"; - - private JedisPool redisPool; - private final String host; - private final int port; - private final int keyExpiry; - - public EventRecordingService(String host, int port, int keyExpiry) { - this.host = host; - this.port = port; - this.keyExpiry = keyExpiry; - } - - public void record(String meetingId, Map<String, String> event) { - Jedis jedis = redisPool.getResource(); - try { - Long msgid = jedis.incr("global:nextRecordedMsgId"); - String key = "recording:" + meetingId + COLON + msgid; - jedis.hmset(key, event); - /** - * We set the key to expire after 14 days as we are still - * recording the event into redis even if the meeting is not - * recorded. (ralam sept 23, 2015) - */ - jedis.expire(key, keyExpiry); - key = "meeting:" + meetingId + COLON + "recordings"; - jedis.rpush(key, msgid.toString()); - jedis.expire(key, keyExpiry); - } catch (Exception e) { - log.warn("Cannot record the info meeting:" + meetingId, e); - } finally { - jedis.close(); - } - - } - - public void stop() { - - } - - public void start() { - // Set the name of this client to be able to distinguish when doing - // CLIENT LIST on redis-cli - redisPool = new JedisPool(new GenericObjectPoolConfig(), host, port, Protocol.DEFAULT_TIMEOUT, null, - Protocol.DEFAULT_DATABASE, "BbbRed5AppsPub"); - } -} diff --git a/bbb-video/src/main/java/org/bigbluebutton/app/video/VideoApplication.java b/bbb-video/src/main/java/org/bigbluebutton/app/video/VideoApplication.java index 3b48025cc4ff4462867d0dae92ed5d4b14c6d1b0..054b5ca6f0fc5b8b9f0146f5e8f5d45da2a33c8b 100755 --- a/bbb-video/src/main/java/org/bigbluebutton/app/video/VideoApplication.java +++ b/bbb-video/src/main/java/org/bigbluebutton/app/video/VideoApplication.java @@ -21,11 +21,16 @@ import java.util.Collection; import java.util.HashMap; import java.util.Map; import java.util.Set; -import java.util.concurrent.TimeUnit; import java.util.concurrent.Executors; import java.util.concurrent.ScheduledExecutorService; +import java.util.concurrent.TimeUnit; +import java.util.regex.Matcher; +import java.util.regex.Pattern; +import java.util.regex.PatternSyntaxException; + import org.bigbluebutton.app.video.converter.H263Converter; import org.bigbluebutton.app.video.converter.VideoRotator; +import org.bigbluebutton.common2.redis.RedisStorageService; import org.bigbluebutton.red5.pubsub.MessagePublisher; import org.red5.logging.Red5LoggerFactory; import org.red5.server.adapter.MultiThreadedApplicationAdapter; @@ -39,11 +44,9 @@ import org.red5.server.api.stream.ISubscriberStream; import org.red5.server.scheduling.QuartzSchedulingService; import org.red5.server.stream.ClientBroadcastStream; import org.slf4j.Logger; -import com.google.gson.Gson; import org.springframework.util.StringUtils; -import java.util.regex.Matcher; -import java.util.regex.Pattern; -import java.util.regex.PatternSyntaxException; + +import com.google.gson.Gson; public class VideoApplication extends MultiThreadedApplicationAdapter { @@ -53,7 +56,7 @@ public class VideoApplication extends MultiThreadedApplicationAdapter { private QuartzSchedulingService scheduler; private MessagePublisher publisher; - private EventRecordingService recordingService; + private RedisStorageService recordingService; private final Map<String, IStreamListener> streamListeners = new HashMap<String, IStreamListener>(); private int packetTimeout = 10000; @@ -518,7 +521,7 @@ public class VideoApplication extends MultiThreadedApplicationAdapter { event.put("stream", stream.getPublishedName()); event.put("duration", new Long(publishDuration).toString()); event.put("eventName", "StopWebcamShareEvent"); - recordingService.record(scopeName, event); + recordingService.recordAndExpire(scopeName, event); } } @@ -526,7 +529,7 @@ public class VideoApplication extends MultiThreadedApplicationAdapter { this.packetTimeout = timeout; } - public void setEventRecordingService(EventRecordingService s) { + public void setEventRecordingService(RedisStorageService s) { recordingService = s; } diff --git a/bbb-video/src/main/java/org/bigbluebutton/app/video/VideoStreamListener.java b/bbb-video/src/main/java/org/bigbluebutton/app/video/VideoStreamListener.java index d1cb002298338ea060787a12cfc30b0e8f5e0728..f235e0574780141a6a14f82ef028fedd7f7de873 100755 --- a/bbb-video/src/main/java/org/bigbluebutton/app/video/VideoStreamListener.java +++ b/bbb-video/src/main/java/org/bigbluebutton/app/video/VideoStreamListener.java @@ -18,24 +18,24 @@ package org.bigbluebutton.app.video; +import java.text.SimpleDateFormat; import java.util.HashMap; import java.util.Map; import java.util.concurrent.TimeUnit; import org.apache.mina.core.buffer.IoBuffer; +import org.bigbluebutton.common2.redis.RedisStorageService; +import org.red5.logging.Red5LoggerFactory; import org.red5.server.api.scheduling.IScheduledJob; import org.red5.server.api.scheduling.ISchedulingService; -import org.red5.server.api.scope.IScope; import org.red5.server.api.stream.IBroadcastStream; import org.red5.server.api.stream.IStreamListener; import org.red5.server.api.stream.IStreamPacket; import org.red5.server.net.rtmp.event.VideoData; import org.red5.server.scheduling.QuartzSchedulingService; import org.slf4j.Logger; -import org.red5.logging.Red5LoggerFactory; import com.google.gson.Gson; -import java.text.SimpleDateFormat; /** * Class to listen for the first video packet of the webcam. @@ -55,7 +55,7 @@ import java.text.SimpleDateFormat; public class VideoStreamListener implements IStreamListener { private static final Logger log = Red5LoggerFactory.getLogger(VideoStreamListener.class, "video"); - private EventRecordingService recordingService; + private RedisStorageService recordingService; private volatile boolean firstPacketReceived = false; // Maximum time between video packets @@ -98,7 +98,7 @@ public class VideoStreamListener implements IStreamListener { public VideoStreamListener(String meetingId, String streamId, Boolean record, String userId, int packetTimeout, QuartzSchedulingService scheduler, - EventRecordingService recordingService) { + RedisStorageService recordingService) { this.meetingId = meetingId; this.streamId = streamId; this.record = record; @@ -160,7 +160,7 @@ public class VideoStreamListener implements IStreamListener { event.put(DATE, sdf.format(recordingStartTime)); event.put("eventName", "StartWebcamShareEvent"); - recordingService.record(meetingId, event); + recordingService.recordAndExpire(meetingId, event); Gson gson = new Gson(); String logStr = gson.toJson(event); @@ -229,7 +229,7 @@ public class VideoStreamListener implements IStreamListener { event.put(DATE, sdf.format(now)); event.put("eventName", "StopWebcamShareEvent"); - recordingService.record(meetingId, event); + recordingService.recordAndExpire(meetingId, event); Gson gson = new Gson(); String logStr = gson.toJson(event); diff --git a/bbb-video/src/main/java/org/bigbluebutton/red5/pubsub/MeetingMessageHandler.java b/bbb-video/src/main/java/org/bigbluebutton/red5/pubsub/MeetingMessageHandler.java index 14d8b61a8e3f40a1bbe8f1ae69056864cf6740b2..13b183125a22ae66a56f21f4ffe334fd3af045f0 100755 --- a/bbb-video/src/main/java/org/bigbluebutton/red5/pubsub/MeetingMessageHandler.java +++ b/bbb-video/src/main/java/org/bigbluebutton/red5/pubsub/MeetingMessageHandler.java @@ -1,19 +1,20 @@ package org.bigbluebutton.red5.pubsub; -import com.google.gson.Gson; -import com.google.gson.JsonObject; -import com.google.gson.JsonParser; import org.bigbluebutton.app.video.ConnectionInvokerService; import org.bigbluebutton.app.video.MeetingManager; +import org.bigbluebutton.common2.redis.pubsub.MessageHandler; import org.bigbluebutton.red5.pubsub.message.RecordChapterBreakMessage; import org.bigbluebutton.red5.pubsub.message.ValidateConnTokenRespMsg; import org.red5.logging.Red5LoggerFactory; import org.slf4j.Logger; +import com.google.gson.Gson; +import com.google.gson.JsonObject; +import com.google.gson.JsonParser; + public class MeetingMessageHandler implements MessageHandler { private static Logger log = Red5LoggerFactory.getLogger(MeetingMessageHandler.class, "video"); - private final String HEADER = "header"; private final String NAME = "name"; private final String BODY = "body"; @@ -61,8 +62,7 @@ public class MeetingMessageHandler implements MessageHandler { String logStr = gson.toJson(body); log.debug("HANDLE: {}", logStr); - if (body.has(MEETING_ID) && body.has(USERID) - && body.has(AUTHZED) && body.has(CONN) && body.has(APP)) { + if (body.has(MEETING_ID) && body.has(USERID) && body.has(AUTHZED) && body.has(CONN) && body.has(APP)) { String meetingId = body.get(MEETING_ID).getAsString(); String userId = body.get(USERID).getAsString(); diff --git a/bbb-video/src/main/java/org/bigbluebutton/red5/pubsub/MessageHandler.java b/bbb-video/src/main/java/org/bigbluebutton/red5/pubsub/MessageHandler.java deleted file mode 100755 index 8f197312c290d0ef21e0baed37478d9d7bfb94e3..0000000000000000000000000000000000000000 --- a/bbb-video/src/main/java/org/bigbluebutton/red5/pubsub/MessageHandler.java +++ /dev/null @@ -1,5 +0,0 @@ -package org.bigbluebutton.red5.pubsub; - -public interface MessageHandler { - void handleMessage(String pattern, String channel, String message); -} diff --git a/bbb-video/src/main/java/org/bigbluebutton/red5/pubsub/MessagePublisher.java b/bbb-video/src/main/java/org/bigbluebutton/red5/pubsub/MessagePublisher.java index 0326ff3651835846f109ccab38c23b7978cd580b..f2619b6341e2e52da76d55349c42c1548915a677 100755 --- a/bbb-video/src/main/java/org/bigbluebutton/red5/pubsub/MessagePublisher.java +++ b/bbb-video/src/main/java/org/bigbluebutton/red5/pubsub/MessagePublisher.java @@ -1,11 +1,21 @@ package org.bigbluebutton.red5.pubsub; -import com.google.gson.Gson; -import org.bigbluebutton.common2.msgs.*; import java.util.HashMap; import java.util.Map; +import org.bigbluebutton.common2.msgs.BbbClientMsgHeader; +import org.bigbluebutton.common2.msgs.BbbCoreBaseHeader; +import org.bigbluebutton.common2.msgs.UserBroadcastCamStartMsg; +import org.bigbluebutton.common2.msgs.UserBroadcastCamStartMsgBody; +import org.bigbluebutton.common2.msgs.UserBroadcastCamStopMsg; +import org.bigbluebutton.common2.msgs.UserBroadcastCamStopMsgBody; +import org.bigbluebutton.common2.msgs.ValidateConnAuthTokenSysMsg; +import org.bigbluebutton.common2.msgs.ValidateConnAuthTokenSysMsgBody; +import org.bigbluebutton.common2.redis.pubsub.MessageSender; + +import com.google.gson.Gson; + public class MessagePublisher { private MessageSender sender; diff --git a/bbb-video/src/main/java/org/bigbluebutton/red5/pubsub/MessageReceiver.java b/bbb-video/src/main/java/org/bigbluebutton/red5/pubsub/MessageReceiver.java deleted file mode 100755 index fc215cdba5811e3f5ea061a18268c1bca4d6e821..0000000000000000000000000000000000000000 --- a/bbb-video/src/main/java/org/bigbluebutton/red5/pubsub/MessageReceiver.java +++ /dev/null @@ -1,123 +0,0 @@ -package org.bigbluebutton.red5.pubsub; - -import java.util.concurrent.Executor; -import java.util.concurrent.Executors; -import org.red5.logging.Red5LoggerFactory; -import org.slf4j.Logger; -import redis.clients.jedis.Jedis; -import redis.clients.jedis.JedisPubSub; -import redis.clients.jedis.exceptions.JedisConnectionException; - -public class MessageReceiver { - private static Logger log = Red5LoggerFactory.getLogger(MessageReceiver.class, "video"); - - private ReceivedMessageHandler handler; - - private Jedis jedis; - private volatile boolean receiveMessage = false; - - private final Executor msgReceiverExec = Executors.newSingleThreadExecutor(); - private final Executor runExec = Executors.newSingleThreadExecutor(); - - private final String FROM_BBB_APPS_PATTERN = "from-akka-apps-redis-channel"; - - private String host; - private int port; - - public void stop() { - receiveMessage = false; - } - - public void start() { - log.info("Ready to receive messages from Redis pubsub."); - try { - receiveMessage = true; - jedis = new Jedis(host, port); - // Set the name of this client to be able to distinguish when doing - // CLIENT LIST on redis-cli - jedis.clientSetname("BbbRed5VideoSub"); - - Runnable messageReceiver = new Runnable() { - public void run() { - if (receiveMessage) { - try { - jedis.subscribe(new PubSubListener(), FROM_BBB_APPS_PATTERN); - } catch(JedisConnectionException ex) { - log.warn("Exception on Jedis connection. Resubscribing to pubsub."); - start(); - } catch (Exception e) { - log.error("Error resubscribing to channels: " + e.getMessage()); - } - } - } - }; - msgReceiverExec.execute(messageReceiver); - } catch (Exception e) { - log.error("Error subscribing to channels: " + e.getMessage()); - } - } - - public void setHost(String host){ - this.host = host; - } - - public void setPort(int port) { - this.port = port; - } - - public void setMessageHandler(ReceivedMessageHandler handler) { - this.handler = handler; - } - - private class PubSubListener extends JedisPubSub { - - public PubSubListener() { - super(); - } - - @Override - public void onMessage(String channel, String message) { - // Not used. - Runnable task = new Runnable() { - public void run() { - handler.handleMessage("", channel, message); - } - }; - - runExec.execute(task); - } - - @Override - public void onPMessage(final String pattern, final String channel, final String message) { - System.out.println("RECEIVED onPMessage" + channel + " message=\n" + message); - Runnable task = new Runnable() { - public void run() { - handler.handleMessage(pattern, channel, message); - } - }; - - runExec.execute(task); - } - - @Override - public void onPSubscribe(String pattern, int subscribedChannels) { - log.debug("Subscribed to the pattern: " + pattern); - } - - @Override - public void onPUnsubscribe(String pattern, int subscribedChannels) { - // Not used. - } - - @Override - public void onSubscribe(String channel, int subscribedChannels) { - // Not used. - } - - @Override - public void onUnsubscribe(String channel, int subscribedChannels) { - // Not used. - } - } -} - diff --git a/bbb-video/src/main/java/org/bigbluebutton/red5/pubsub/MessageSender.java b/bbb-video/src/main/java/org/bigbluebutton/red5/pubsub/MessageSender.java deleted file mode 100755 index 0b732de4e99d98c2a1d6dda54777fa585f1a6eed..0000000000000000000000000000000000000000 --- a/bbb-video/src/main/java/org/bigbluebutton/red5/pubsub/MessageSender.java +++ /dev/null @@ -1,103 +0,0 @@ -package org.bigbluebutton.red5.pubsub; - -import org.apache.commons.pool2.impl.GenericObjectPoolConfig; -import org.red5.logging.Red5LoggerFactory; -import org.slf4j.Logger; -import redis.clients.jedis.Jedis; -import redis.clients.jedis.JedisPool; -import redis.clients.jedis.Protocol; -import java.util.concurrent.BlockingQueue; -import java.util.concurrent.Executor; -import java.util.concurrent.Executors; -import java.util.concurrent.LinkedBlockingQueue; - -public class MessageSender { - private static Logger log = Red5LoggerFactory.getLogger(MessageSender.class, "bigbluebutton"); - - private JedisPool redisPool; - private volatile boolean sendMessage = false; - - private final Executor msgSenderExec = Executors.newSingleThreadExecutor(); - private final Executor runExec = Executors.newSingleThreadExecutor(); - private BlockingQueue<MessageToSend> messages = new LinkedBlockingQueue<MessageToSend>(); - private String host; - private int port; - - public void stop() { - sendMessage = false; - redisPool.destroy(); - } - - public void start() { - - GenericObjectPoolConfig config = new GenericObjectPoolConfig(); - config.setMaxTotal(32); - config.setMaxIdle(8); - config.setMinIdle(1); - config.setTestOnBorrow(true); - config.setTestOnReturn(true); - config.setTestWhileIdle(true); - config.setNumTestsPerEvictionRun(12); - config.setMaxWaitMillis(5000); - config.setTimeBetweenEvictionRunsMillis(60000); - config.setBlockWhenExhausted(true); - - // Set the name of this client to be able to distinguish when doing - // CLIENT LIST on redis-cli - redisPool = new JedisPool(config, host, port, Protocol.DEFAULT_TIMEOUT, null, - Protocol.DEFAULT_DATABASE, "BbbRed5VideoPub"); - - log.info("Redis message publisher starting!"); - - try { - sendMessage = true; - - Runnable messageSender = new Runnable() { - public void run() { - while (sendMessage) { - try { - MessageToSend msg = messages.take(); - publish(msg.getChannel(), msg.getMessage()); - } catch (InterruptedException e) { - log.warn("Failed to get org.bigbluebutton.red5.pubsub.message from queue."); - } - } - } - }; - msgSenderExec.execute(messageSender); - } catch (Exception e) { - log.error("Error subscribing to channels: " + e.getMessage()); - } - } - - public void send(String channel, String message) { - MessageToSend msg = new MessageToSend(channel, message); - messages.add(msg); - } - - private void publish(final String channel, final String message) { - Runnable task = new Runnable() { - public void run() { - Jedis jedis = redisPool.getResource(); - try { - jedis.publish(channel, message); - } catch(Exception e){ - log.warn("Cannot publish the org.bigbluebutton.red5.pubsub.message to redis", e); - } finally { - redisPool.returnResource(jedis); - } - } - }; - - runExec.execute(task); - } - - - public void setHost(String host){ - this.host = host; - } - - public void setPort(int port) { - this.port = port; - } -} diff --git a/bbb-video/src/main/java/org/bigbluebutton/red5/pubsub/MessageToSend.java b/bbb-video/src/main/java/org/bigbluebutton/red5/pubsub/MessageToSend.java deleted file mode 100755 index 3ae3874039b4637af954bf7d5d176bc80d268405..0000000000000000000000000000000000000000 --- a/bbb-video/src/main/java/org/bigbluebutton/red5/pubsub/MessageToSend.java +++ /dev/null @@ -1,19 +0,0 @@ -package org.bigbluebutton.red5.pubsub; - -public class MessageToSend { - private final String channel; - private final String message; - - public MessageToSend(String channel, String message) { - this.channel = channel; - this.message = message; - } - - public String getChannel() { - return channel; - } - - public String getMessage() { - return message; - } -} diff --git a/bbb-video/src/main/webapp/WEB-INF/bbb-redis-messaging.xml b/bbb-video/src/main/webapp/WEB-INF/bbb-redis-messaging.xml new file mode 100755 index 0000000000000000000000000000000000000000..918167fe436ec37521319e311f84e2f639da9775 --- /dev/null +++ b/bbb-video/src/main/webapp/WEB-INF/bbb-redis-messaging.xml @@ -0,0 +1,79 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + +BigBlueButton open source conferencing system - http://www.bigbluebutton.org/ + +Copyright (c) 2012 BigBlueButton Inc. and by respective authors (see below). + +This program is free software; you can redistribute it and/or modify it under the +terms of the GNU Lesser General Public License as published by the Free Software +Foundation; either version 3.0 of the License, or (at your option) any later +version. + +BigBlueButton is distributed in the hope that it will be useful, but WITHOUT ANY +WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A +PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. + +You should have received a copy of the GNU Lesser General Public License along +with BigBlueButton; if not, see <http://www.gnu.org/licenses/>. + +--> +<beans xmlns="http://www.springframework.org/schema/beans" + xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xmlns:util="http://www.springframework.org/schema/util" + xsi:schemaLocation="http://www.springframework.org/schema/beans + http://www.springframework.org/schema/beans/spring-beans-2.5.xsd + http://www.springframework.org/schema/util + http://www.springframework.org/schema/util/spring-util-2.0.xsd + "> + + <bean id="meetingMessageHandler" class="org.bigbluebutton.red5.pubsub.MeetingMessageHandler"> + <property name="meetingManager" ref="meetingManager"/> + <property name="connInvokerService" ref="connInvokerService"/> + </bean> + + <bean id="receivedMessageHandler" class="org.bigbluebutton.common2.redis.pubsub.ReceivedMessageHandler" + init-method="start" destroy-method="stop"> + </bean> + + <bean id="redisStorageService" + class="org.bigbluebutton.common2.redis.RedisStorageService" + init-method="start" destroy-method="stop"> + <property name="host" value="${redis.host}" /> + <property name="port" value="${redis.port}" /> + <property name="password" value="${redis.password:}" /> + <property name="expireKey" value="${redis.keyExpiry}" /> + <property name="clientName" value="BbbRed5VideoStore" /> + </bean> + + <bean id="messageReceiver" class="org.bigbluebutton.common2.redis.pubsub.MessageReceiver" + init-method="start" destroy-method="stop"> + <property name="host" value="${redis.host}" /> + <property name="port" value="${redis.port}" /> + <property name="password" value="${redis.password:}" /> + <property name="clientName" value="BbbRed5VideoReceiver" /> + <property name="messageHandler" ref="receivedMessageHandler"/> + </bean> + + <bean id="redisSender" class="org.bigbluebutton.common2.redis.pubsub.MessageSender" + init-method="start" destroy-method="stop"> + <property name="host" value="${redis.host}" /> + <property name="port" value="${redis.port}" /> + <property name="password" value="${redis.password:}" /> + <property name="clientName" value="BbbRed5VideoSender" /> + </bean> + + <bean id="redisPublisher" class="org.bigbluebutton.red5.pubsub.MessagePublisher"> + <property name="messageSender" ref="redisSender"/> + </bean> + + <bean id="messageDistributor" class="org.bigbluebutton.common2.redis.pubsub.MessageDistributor"> + <property name="messageHandler" ref="receivedMessageHandler"/> + <property name="messageListeners"> + <set> + <ref bean="meetingMessageHandler" /> + </set> + </property> + </bean> + +</beans> diff --git a/bbb-video/src/main/webapp/WEB-INF/bbb-redis-pool.xml b/bbb-video/src/main/webapp/WEB-INF/bbb-redis-pool.xml deleted file mode 100755 index 2d3cfd9098a5141b93f859828d18f65c5a5348b7..0000000000000000000000000000000000000000 --- a/bbb-video/src/main/webapp/WEB-INF/bbb-redis-pool.xml +++ /dev/null @@ -1,31 +0,0 @@ -<?xml version="1.0" encoding="UTF-8"?> -<!-- - -BigBlueButton open source conferencing system - http://www.bigbluebutton.org/ - -Copyright (c) 2012 BigBlueButton Inc. and by respective authors (see below). - -This program is free software; you can redistribute it and/or modify it under the -terms of the GNU Lesser General Public License as published by the Free Software -Foundation; either version 3.0 of the License, or (at your option) any later -version. - -BigBlueButton is distributed in the hope that it will be useful, but WITHOUT ANY -WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A -PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. - -You should have received a copy of the GNU Lesser General Public License along -with BigBlueButton; if not, see <http://www.gnu.org/licenses/>. - ---> -<beans xmlns="http://www.springframework.org/schema/beans" - xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" - xmlns:util="http://www.springframework.org/schema/util" - xsi:schemaLocation="http://www.springframework.org/schema/beans - http://www.springframework.org/schema/beans/spring-beans-2.5.xsd - http://www.springframework.org/schema/util - http://www.springframework.org/schema/util/spring-util-2.0.xsd - "> - - -</beans> diff --git a/bbb-video/src/main/webapp/WEB-INF/bigbluebutton-video.properties b/bbb-video/src/main/webapp/WEB-INF/bigbluebutton-video.properties index 5f8030de0c73cfb61b1eb9fa6816b9cec420445a..c8b4ea2405ecdd8772b5af5a7ee2aebdbe15a2f5 100755 --- a/bbb-video/src/main/webapp/WEB-INF/bigbluebutton-video.properties +++ b/bbb-video/src/main/webapp/WEB-INF/bigbluebutton-video.properties @@ -1,4 +1,5 @@ -redis.host=127.0.0.1 -redis.port=6379 -# recording keys should expire in 14 days -redis.keyExpiry=1209600 +redis.host=127.0.0.1 +redis.port=6379 +redis.password= +# recording keys should expire in 14 days +redis.keyExpiry=1209600 diff --git a/bbb-video/src/main/webapp/WEB-INF/red5-web.xml b/bbb-video/src/main/webapp/WEB-INF/red5-web.xml index e4c7b3adc83ee1d7e20c2a2f0ca62ddb48976a4f..fc80adb5503af93a9b1ff65584cf5f5928bc6df5 100755 --- a/bbb-video/src/main/webapp/WEB-INF/red5-web.xml +++ b/bbb-video/src/main/webapp/WEB-INF/red5-web.xml @@ -21,8 +21,10 @@ with BigBlueButton; if not, see <http://www.gnu.org/licenses/>. <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:lang="http://www.springframework.org/schema/lang" - xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-2.0.xsd - http://www.springframework.org/schema/lang http://www.springframework.org/schema/lang/spring-lang-2.0.xsd"> + xsi:schemaLocation="http://www.springframework.org/schema/beans + http://www.springframework.org/schema/beans/spring-beans-2.0.xsd + http://www.springframework.org/schema/lang + http://www.springframework.org/schema/lang/spring-lang-2.0.xsd"> <bean id="placeholderConfig" class="org.springframework.beans.factory.config.PropertyPlaceholderConfigurer"> <property name="locations"> @@ -32,6 +34,11 @@ with BigBlueButton; if not, see <http://www.gnu.org/licenses/>. </list> </property> </bean> + + <bean id="meetingManager" class="org.bigbluebutton.app.video.MeetingManager"/> + + <bean id="connInvokerService" class="org.bigbluebutton.app.video.ConnectionInvokerService" + init-method="start" destroy-method="stop"/> <bean id="web.context" class="org.red5.server.Context" autowire="byType" /> @@ -44,59 +51,15 @@ with BigBlueButton; if not, see <http://www.gnu.org/licenses/>. <property name="contextPath" value="${webapp.contextPath}" /> <property name="virtualHosts" value="${webapp.virtualHosts}" /> </bean> - + <bean id="web.handler" class="org.bigbluebutton.app.video.VideoApplication"> <property name="packetTimeout" value="10000"/> - <property name="eventRecordingService" ref="redisRecorder"/> + <property name="eventRecordingService" ref="redisStorageService"/> <property name="messagePublisher" ref="redisPublisher"/> <property name="meetingManager" ref="meetingManager"/> <property name="connInvokerService" ref="connInvokerService"/> </bean> + + <import resource="bbb-redis-messaging.xml"/> - <bean id="connInvokerService" class="org.bigbluebutton.app.video.ConnectionInvokerService" - init-method="start" destroy-method="stop"/> - - <bean id="meetingManager" class="org.bigbluebutton.app.video.MeetingManager"/> - - <bean id="redisPublisher" class="org.bigbluebutton.red5.pubsub.MessagePublisher"> - <property name="messageSender" ref="redisSender"/> - </bean> - - <bean id="redisRecorder" class="org.bigbluebutton.app.video.EventRecordingService" - init-method="start" destroy-method="stop"> - <constructor-arg index="0" value="${redis.host}"/> - <constructor-arg index="1" value="${redis.port}"/> - <constructor-arg index="2" value="${redis.keyExpiry}"/> - </bean> - - <bean id="messageReceiver" class="org.bigbluebutton.red5.pubsub.MessageReceiver" - init-method="start" destroy-method="stop"> - <property name="host" value="${redis.host}"/> - <property name="port" value="${redis.port}"/> - <property name="messageHandler" ref="receivedMessageHandler"/> - </bean> - - <bean id="messageDistributor" class="org.bigbluebutton.red5.pubsub.MessageDistributor"> - <property name="messageHandler" ref="receivedMessageHandler"/> - <property name="messageListeners"> - <set> - <ref bean="meetingMessageHandler" /> - </set> - </property> - </bean> - - <bean id="meetingMessageHandler" class="org.bigbluebutton.red5.pubsub.MeetingMessageHandler"> - <property name="meetingManager" ref="meetingManager"/> - <property name="connInvokerService" ref="connInvokerService"/> - </bean> - - <bean id="receivedMessageHandler" class="org.bigbluebutton.red5.pubsub.ReceivedMessageHandler" - init-method="start" destroy-method="stop"> - </bean> - - <bean id="redisSender" class="org.bigbluebutton.red5.pubsub.MessageSender" - init-method="start" destroy-method="stop"> - <property name="host" value="${redis.host}"/> - <property name="port" value="${redis.port}"/> - </bean> </beans> diff --git a/bbb-video/src/main/webapp/WEB-INF/web.xml b/bbb-video/src/main/webapp/WEB-INF/web.xml index af135b12d6da7c523b765161022e49ef73f4d134..24e0b09f9ff3286ff18d193120143b2d298121b3 100755 --- a/bbb-video/src/main/webapp/WEB-INF/web.xml +++ b/bbb-video/src/main/webapp/WEB-INF/web.xml @@ -29,7 +29,6 @@ with BigBlueButton; if not, see <http://www.gnu.org/licenses/>. <param-name>webAppRootKey</param-name> <param-value>/video</param-value> </context-param> - <listener> <listener-class>org.red5.logging.ContextLoggingListener</listener-class> diff --git a/bbb-voice/build.gradle b/bbb-voice/build.gradle index e364181721d6b9c86df523f55c5b1fc51c0ed0d5..4e46e8040b25face30c3de8fea561c0e2b1b7f71 100755 --- a/bbb-voice/build.gradle +++ b/bbb-voice/build.gradle @@ -17,26 +17,33 @@ repositories { mavenLocal() } +configurations { + runtime.exclude group: "org.slf4j", module: "slf4j-api" + runtime.exclude group: "org.red5", module: "red5-server" + runtime.exclude group: "org.red5", module: "red5-server-common" + runtime.exclude group: "org.red5", module: "red5-io" +} -dependencies { +dependencies { // Servlet providedCompile 'javax.servlet:servlet-api:2.5@jar' // Mina - providedCompile 'org.apache.mina:mina-core:2.0.17@jar' - providedCompile 'org.apache.mina:mina-integration-beans:2.0.17@jar' - providedCompile 'org.apache.mina:mina-integration-jmx:2.0.17@jar' - - // Spring + providedCompile 'org.apache.mina:mina-core:2.0.19@jar' + providedCompile 'org.apache.mina:mina-integration-beans:2.0.19@jar' + providedCompile 'org.apache.mina:mina-integration-jmx:2.0.19@jar' + + // Spring providedCompile 'org.springframework:spring-web:4.3.12.RELEASE@jar' - providedCompile 'org.springframework:spring-beans:4.3.12.RELEASE@jar' + providedCompile 'org.springframework:spring-beans:4.3.12.RELEASE@jar' providedCompile 'org.springframework:spring-context:4.3.12.RELEASE@jar' providedCompile 'org.springframework:spring-core:4.3.12.RELEASE@jar' - providedCompile 'org.red5:red5-server:1.0.10-M5@jar' - providedCompile 'org.red5:red5-server-common:1.0.10-M5@jar' - providedCompile 'org.red5:red5-io:1.0.10-M5@jar' - + // Red5 + providedCompile 'org.red5:red5-server:1.0.10-M9@jar' + providedCompile 'org.red5:red5-server-common:1.0.10-M9@jar' + providedCompile 'org.red5:red5-io:1.0.10-M9@jar' + // Logging providedCompile 'ch.qos.logback:logback-core:1.2.3@jar' providedCompile 'ch.qos.logback:logback-classic:1.2.3@jar' @@ -49,28 +56,19 @@ dependencies { // Otherwise we get exception on aop utils class not found. providedCompile 'org.springframework:spring-aop:4.3.12.RELEASE@jar' compile 'aopalliance:aopalliance:1.0@jar' - + //redis - compile 'redis.clients:jedis:2.9.0' compile 'org.apache.commons:commons-pool2:2.6.0' compile 'com.google.code.gson:gson:2.8.5' - compile 'org.apache.commons:commons-lang3:3.7' - compile 'org.bigbluebutton:bbb-common-message_2.12:0.0.19-SNAPSHOT' - - // Testing - testRuntime 'org.easymock:easymock:3.6@jar' -} - -test { - useTestNG() + providedCompile 'org.apache.commons:commons-lang3:3.7' + compile 'org.bigbluebutton:bbb-common-message_2.12:0.0.20-SNAPSHOT' } war.doLast { ant.unzip(src: war.archivePath, dest: "$buildDir/sip") } - task deploy() << { def red5AppsDir = '/usr/share/red5/webapps' def sipDir = new File("${red5AppsDir}/sip") @@ -80,5 +78,4 @@ task deploy() << { ant.copy(todir: sipDir) { fileset(dir: "$buildDir/sip") } -} - +} diff --git a/bbb-voice/deploy.sh b/bbb-voice/deploy.sh index ebd457e9196547175c1de294c08b6bc833e310c8..b7cab36a4bc4e2ff6e2b72d8bb9bf36079ce3149 100755 --- a/bbb-voice/deploy.sh +++ b/bbb-voice/deploy.sh @@ -1,13 +1,10 @@ #!/bin/bash # deploying 'bigbluebutton-apps' to /usr/share/red5/webapps -sudo chmod -R 777 /usr/share/red5/webapps -sudo chown -R red5.red5 /usr/share/red5/webapps +sudo chmod -R 777 /usr/share/red5/webapps/* gradle clean gradle resolveDeps gradle war deploy -sudo chown -R red5.red5 /usr/share/red5/webapps -sudo chmod -R 777 /usr/share/red5/webapps - +sudo chown -R red5:red5 /usr/share/red5/webapps diff --git a/bbb-voice/src/main/java/org/bigbluebutton/voiceconf/messaging/MeetingMessageHandler.java b/bbb-voice/src/main/java/org/bigbluebutton/voiceconf/messaging/MeetingMessageHandler.java index 04623357859b93ae1dcb41d49aff0bc66994cbe4..847c837c186a5a48f5639e15a17448169e8134f5 100755 --- a/bbb-voice/src/main/java/org/bigbluebutton/voiceconf/messaging/MeetingMessageHandler.java +++ b/bbb-voice/src/main/java/org/bigbluebutton/voiceconf/messaging/MeetingMessageHandler.java @@ -3,6 +3,8 @@ package org.bigbluebutton.voiceconf.messaging; import com.google.gson.Gson; import com.google.gson.JsonObject; import com.google.gson.JsonParser; + +import org.bigbluebutton.common2.redis.pubsub.MessageHandler; import org.bigbluebutton.voiceconf.messaging.messages.ValidateConnTokenRespMsg; import org.bigbluebutton.voiceconf.red5.ConnectionInvokerService; import org.red5.logging.Red5LoggerFactory; diff --git a/bbb-voice/src/main/java/org/bigbluebutton/voiceconf/messaging/MessageDistributor.java b/bbb-voice/src/main/java/org/bigbluebutton/voiceconf/messaging/MessageDistributor.java deleted file mode 100755 index 595bf905cbc5bc84f3f03640c9bf26e6f2e0eda0..0000000000000000000000000000000000000000 --- a/bbb-voice/src/main/java/org/bigbluebutton/voiceconf/messaging/MessageDistributor.java +++ /dev/null @@ -1,25 +0,0 @@ -package org.bigbluebutton.voiceconf.messaging; - -import java.util.Set; - -public class MessageDistributor { - private ReceivedMessageHandler handler; - private Set<MessageHandler> listeners; - - public void setMessageListeners(Set<MessageHandler> listeners) { - this.listeners = listeners; - } - - public void setMessageHandler(ReceivedMessageHandler handler) { - this.handler = handler; - if (handler != null) { - handler.setMessageDistributor(this); - } - } - - public void notifyListeners(String pattern, String channel, String message) { - for (MessageHandler listener : listeners) { - listener.handleMessage(pattern, channel, message); - } - } -} diff --git a/bbb-voice/src/main/java/org/bigbluebutton/voiceconf/messaging/MessageReceiver.java b/bbb-voice/src/main/java/org/bigbluebutton/voiceconf/messaging/MessageReceiver.java deleted file mode 100755 index 9033abe06a9a511d90f42520cf8c5daae97b5df6..0000000000000000000000000000000000000000 --- a/bbb-voice/src/main/java/org/bigbluebutton/voiceconf/messaging/MessageReceiver.java +++ /dev/null @@ -1,123 +0,0 @@ -package org.bigbluebutton.voiceconf.messaging; - -import java.util.concurrent.Executor; -import java.util.concurrent.Executors; -import org.red5.logging.Red5LoggerFactory; -import org.slf4j.Logger; -import redis.clients.jedis.Jedis; -import redis.clients.jedis.JedisPool; -import redis.clients.jedis.JedisPubSub; -import redis.clients.jedis.exceptions.JedisConnectionException; - -public class MessageReceiver { - private static Logger log = Red5LoggerFactory.getLogger(MessageReceiver.class, "bigbluebutton"); - - private ReceivedMessageHandler handler; - - private Jedis jedis; - private volatile boolean receiveMessage = false; - - private final Executor msgReceiverExec = Executors.newSingleThreadExecutor(); - private final Executor runExec = Executors.newSingleThreadExecutor(); - - private final String FROM_BBB_APPS_PATTERN = "from-akka-apps-redis-channel"; - - private String host; - private int port; - - public void stop() { - receiveMessage = false; - } - - public void start() { - log.info("Ready to receive messages from Redis pubsub."); - try { - receiveMessage = true; - jedis = new Jedis(host, port); - // Set the name of this client to be able to distinguish when doing - // CLIENT LIST on redis-cli - jedis.clientSetname("BbbRed5VoiceSub"); - - Runnable messageReceiver = new Runnable() { - public void run() { - if (receiveMessage) { - try { - jedis.subscribe(new PubSubListener(), FROM_BBB_APPS_PATTERN); - } catch(JedisConnectionException ex) { - log.warn("Exception on Jedis connection. Resubscribing to pubsub."); - start(); - } catch (Exception e) { - log.error("Error resubscribing to channels: " + e.getMessage()); - } - } - } - }; - msgReceiverExec.execute(messageReceiver); - } catch (Exception e) { - log.error("Error subscribing to channels: " + e.getMessage()); - } - } - - public void setHost(String host){ - this.host = host; - } - - public void setPort(int port) { - this.port = port; - } - - public void setMessageHandler(ReceivedMessageHandler handler) { - this.handler = handler; - } - - private class PubSubListener extends JedisPubSub { - - public PubSubListener() { - super(); - } - - @Override - public void onMessage(String channel, String message) { - // Not used. - Runnable task = new Runnable() { - public void run() { - handler.handleMessage("", channel, message); - } - }; - - runExec.execute(task); - } - - @Override - public void onPMessage(final String pattern, final String channel, final String message) { - System.out.println("RECEIVED onPMessage" + channel + "\n" + message); - Runnable task = new Runnable() { - public void run() { - handler.handleMessage(pattern, channel, message); - } - }; - - runExec.execute(task); - } - - @Override - public void onPSubscribe(String pattern, int subscribedChannels) { - log.debug("Subscribed to the pattern: " + pattern); - } - - @Override - public void onPUnsubscribe(String pattern, int subscribedChannels) { - // Not used. - } - - @Override - public void onSubscribe(String channel, int subscribedChannels) { - // Not used. - } - - @Override - public void onUnsubscribe(String channel, int subscribedChannels) { - // Not used. - } - } -} diff --git a/bbb-voice/src/main/java/org/bigbluebutton/voiceconf/messaging/MessageSender.java b/bbb-voice/src/main/java/org/bigbluebutton/voiceconf/messaging/MessageSender.java deleted file mode 100755 index 67869c13367f6892a4a7b3e0cf82fcd393844227..0000000000000000000000000000000000000000 --- a/bbb-voice/src/main/java/org/bigbluebutton/voiceconf/messaging/MessageSender.java +++ /dev/null @@ -1,102 +0,0 @@ -package org.bigbluebutton.voiceconf.messaging; - -import java.util.concurrent.BlockingQueue; -import java.util.concurrent.Executor; -import java.util.concurrent.Executors; -import java.util.concurrent.LinkedBlockingQueue; -import org.red5.logging.Red5LoggerFactory; -import org.slf4j.Logger; -import redis.clients.jedis.Jedis; -import redis.clients.jedis.JedisPool; -import redis.clients.jedis.Protocol; -import org.apache.commons.pool2.impl.GenericObjectPoolConfig; - -public class MessageSender { - private static Logger log = Red5LoggerFactory.getLogger(MessageSender.class, "bigbluebutton"); - - private JedisPool redisPool; - private volatile boolean sendMessage = false; - - private final Executor msgSenderExec = Executors.newSingleThreadExecutor(); - private final Executor runExec = Executors.newSingleThreadExecutor(); - private BlockingQueue<MessageToSend> messages = new LinkedBlockingQueue<MessageToSend>(); - private String host; - private int port; - - public void stop() { - sendMessage = false; - redisPool.destroy(); - } - - public void start() { - - GenericObjectPoolConfig config = new GenericObjectPoolConfig(); - config.setMaxTotal(32); - config.setMaxIdle(8); - config.setMinIdle(1); - config.setTestOnBorrow(true); - config.setTestOnReturn(true); - config.setTestWhileIdle(true); - config.setNumTestsPerEvictionRun(12); - config.setMaxWaitMillis(5000); - config.setTimeBetweenEvictionRunsMillis(60000); - config.setBlockWhenExhausted(true); - - // Set the name of this client to be able to distinguish when doing - // CLIENT LIST on redis-cli - redisPool = new JedisPool(config, host, port, Protocol.DEFAULT_TIMEOUT, null, - Protocol.DEFAULT_DATABASE, "BbbRed5VoicePub"); - - log.info("Redis org.bigbluebutton.red5.pubsub.message publisher starting!"); - try { - sendMessage = true; - - Runnable messageSender = new Runnable() { - public void run() { - while (sendMessage) { - try { - MessageToSend msg = messages.take(); - publish(msg.getChannel(), msg.getMessage()); - } catch (InterruptedException e) { - log.warn("Failed to get org.bigbluebutton.red5.pubsub.message from queue."); - } - } - } - }; - msgSenderExec.execute(messageSender); - } catch (Exception e) { - log.error("Error subscribing to channels: " + e.getMessage()); - } - } - - public void send(String channel, String message) { - MessageToSend msg = new MessageToSend(channel, message); - messages.add(msg); - } - - private void publish(final String channel, final String message) { - Runnable task = new Runnable() { - public void run() { - Jedis jedis = redisPool.getResource(); - try { - jedis.publish(channel, message); - } catch(Exception e){ - log.warn("Cannot publish the org.bigbluebutton.red5.pubsub.message to redis", e); - } finally { - redisPool.returnResource(jedis); - } - } - }; - - runExec.execute(task); - } - - - public void setHost(String host){ - this.host = host; - } - - public void setPort(int port) { - this.port = port; - } -} diff --git a/bbb-voice/src/main/java/org/bigbluebutton/voiceconf/messaging/MessageToSend.java b/bbb-voice/src/main/java/org/bigbluebutton/voiceconf/messaging/MessageToSend.java deleted file mode 100755 index a72c9c6efe90da652577f9c7de285302abbbe3af..0000000000000000000000000000000000000000 --- a/bbb-voice/src/main/java/org/bigbluebutton/voiceconf/messaging/MessageToSend.java +++ /dev/null @@ -1,19 +0,0 @@ -package org.bigbluebutton.voiceconf.messaging; - -public class MessageToSend { - private final String channel; - private final String message; - - public MessageToSend(String channel, String message) { - this.channel = channel; - this.message = message; - } - - public String getChannel() { - return channel; - } - - public String getMessage() { - return message; - } -} diff --git a/bbb-voice/src/main/java/org/bigbluebutton/voiceconf/messaging/ReceivedMessage.java b/bbb-voice/src/main/java/org/bigbluebutton/voiceconf/messaging/ReceivedMessage.java deleted file mode 100755 index 82d14824b11570bb36446b5d29be9977f27ae72f..0000000000000000000000000000000000000000 --- a/bbb-voice/src/main/java/org/bigbluebutton/voiceconf/messaging/ReceivedMessage.java +++ /dev/null @@ -1,28 +0,0 @@ -package org.bigbluebutton.voiceconf.messaging; - -public class ReceivedMessage { - - private final String pattern; - private final String channel; - private final String message; - - public ReceivedMessage(String pattern, String channel, String message) { - this.pattern = pattern; - this.channel = channel; - this.message = message; - } - - public String getPattern() { - return pattern; - } - - public String getChannel() { - return channel; - } - - public String getMessage() { - return message; - } - - -} diff --git a/bbb-voice/src/main/java/org/bigbluebutton/voiceconf/messaging/ReceivedMessageHandler.java b/bbb-voice/src/main/java/org/bigbluebutton/voiceconf/messaging/ReceivedMessageHandler.java deleted file mode 100755 index 596710e10cd61f3cfa0284ef3a85a83b3d6fc7e7..0000000000000000000000000000000000000000 --- a/bbb-voice/src/main/java/org/bigbluebutton/voiceconf/messaging/ReceivedMessageHandler.java +++ /dev/null @@ -1,69 +0,0 @@ -package org.bigbluebutton.voiceconf.messaging; - -import java.util.concurrent.BlockingQueue; -import java.util.concurrent.Executor; -import java.util.concurrent.Executors; -import java.util.concurrent.LinkedBlockingQueue; - -import org.red5.logging.Red5LoggerFactory; -import org.slf4j.Logger; - -public class ReceivedMessageHandler { - private static Logger log = Red5LoggerFactory.getLogger(ReceivedMessageHandler.class, "bigbluebutton"); - - private BlockingQueue<ReceivedMessage> receivedMessages = new LinkedBlockingQueue<ReceivedMessage>(); - - private volatile boolean processMessage = false; - - private final Executor msgProcessorExec = Executors.newSingleThreadExecutor(); - - - private MessageDistributor handler; - - public void stop() { - processMessage = false; - } - - public void start() { - log.info("Ready to handle messages from Redis pubsub!"); - - try { - processMessage = true; - - Runnable messageProcessor = new Runnable() { - public void run() { - while (processMessage) { - try { - ReceivedMessage msg = receivedMessages.take(); - processMessage(msg); - } catch (InterruptedException e) { - log.warn("Error while taking received message from queue."); - } - } - } - }; - msgProcessorExec.execute(messageProcessor); - } catch (Exception e) { - log.error("Error subscribing to channels: " + e.getMessage()); - } - } - - private void processMessage(ReceivedMessage msg) { - if (handler != null) { - log.debug("Let's process this message: " + msg.getMessage()); - - handler.notifyListeners(msg.getPattern(), msg.getChannel(), msg.getMessage()); - } else { - log.warn("No listeners interested in messages from Redis!"); - } - } - - public void handleMessage(String pattern, String channel, String message) { - ReceivedMessage rm = new ReceivedMessage(pattern, channel, message); - receivedMessages.add(rm); - } - - public void setMessageDistributor(MessageDistributor h) { - this.handler = h; - } -} diff --git a/bbb-voice/src/main/java/org/bigbluebutton/voiceconf/messaging/RedisMessagingService.java b/bbb-voice/src/main/java/org/bigbluebutton/voiceconf/messaging/RedisMessagingService.java index 11da04a9f7c525a296437691cacb8284b2a82f91..853f5fd4fefc59230ae20d9d00151b3dd210f381 100755 --- a/bbb-voice/src/main/java/org/bigbluebutton/voiceconf/messaging/RedisMessagingService.java +++ b/bbb-voice/src/main/java/org/bigbluebutton/voiceconf/messaging/RedisMessagingService.java @@ -5,6 +5,8 @@ import java.util.Map; import java.util.regex.Matcher; import java.util.regex.Pattern; import org.bigbluebutton.common2.msgs.*; +import org.bigbluebutton.common2.redis.pubsub.MessageSender; + import com.google.gson.Gson; import org.bigbluebutton.voiceconf.messaging.messages.UserConnectedToGlobalAudio; import org.bigbluebutton.voiceconf.messaging.messages.UserDisconnectedFromGlobalAudio; diff --git a/bbb-voice/src/main/java/org/bigbluebutton/voiceconf/red5/media/SipToFlashAudioStream.java b/bbb-voice/src/main/java/org/bigbluebutton/voiceconf/red5/media/SipToFlashAudioStream.java index b81cdd554ab63b8384a45994d09b36d6edc0356d..51b77259fb3571d468a49190c72fa62bbba33a56 100644 --- a/bbb-voice/src/main/java/org/bigbluebutton/voiceconf/red5/media/SipToFlashAudioStream.java +++ b/bbb-voice/src/main/java/org/bigbluebutton/voiceconf/red5/media/SipToFlashAudioStream.java @@ -19,16 +19,16 @@ package org.bigbluebutton.voiceconf.red5.media; import java.net.DatagramSocket; + import org.apache.mina.core.buffer.IoBuffer; import org.bigbluebutton.voiceconf.red5.media.transcoder.SipToFlashTranscoder; import org.bigbluebutton.voiceconf.red5.media.transcoder.TranscodedAudioDataListener; import org.red5.logging.Red5LoggerFactory; import org.red5.server.api.IContext; import org.red5.server.api.scope.IScope; -import org.red5.server.net.rtmp.event.AudioData; +import org.red5.server.net.rtmp.event.AudioData; import org.red5.server.net.rtmp.event.Notify; import org.red5.server.net.rtmp.message.Constants; -import org.red5.server.scope.Scope; import org.red5.server.stream.IProviderService; import org.slf4j.Logger; diff --git a/bbb-voice/src/main/java/org/red5/app/sip/stream/ListenStream.java b/bbb-voice/src/main/java/org/red5/app/sip/stream/ListenStream.java index e0c4543b911c92fb53ae5e5fd505bf0c120d4fa0..ab41d32d89850cc2861b721fee91411bccd30f7b 100755 --- a/bbb-voice/src/main/java/org/red5/app/sip/stream/ListenStream.java +++ b/bbb-voice/src/main/java/org/red5/app/sip/stream/ListenStream.java @@ -5,13 +5,13 @@ import org.red5.app.sip.trancoders.TranscodedAudioDataListener; import org.red5.logging.Red5LoggerFactory; import org.red5.server.api.IContext; import org.red5.server.api.scope.IScope; -import org.red5.server.net.rtmp.event.AudioData; +import org.red5.server.net.rtmp.event.AudioData; import org.red5.server.scope.Scope; import org.red5.server.stream.IProviderService; import org.slf4j.Logger; public class ListenStream implements TranscodedAudioDataListener { - final private Logger log = Red5LoggerFactory.getLogger(ListenStream.class, "sip"); + private final Logger log = Red5LoggerFactory.getLogger(ListenStream.class, "sip"); private AudioStream broadcastStream; private IScope scope; diff --git a/bbb-voice/src/main/webapp/WEB-INF/bbb-redis-messaging.xml b/bbb-voice/src/main/webapp/WEB-INF/bbb-redis-messaging.xml index fe19d06cc39a355141fc0d48cf604f7b9f8913bb..8da566658471941dd04c309f0028c4ba32835f4b 100755 --- a/bbb-voice/src/main/webapp/WEB-INF/bbb-redis-messaging.xml +++ b/bbb-voice/src/main/webapp/WEB-INF/bbb-redis-messaging.xml @@ -24,42 +24,45 @@ with BigBlueButton; if not, see <http://www.gnu.org/licenses/>. xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-2.5.xsd http://www.springframework.org/schema/util - http://www.springframework.org/schema/util/spring-util-2.0.xsd - "> + http://www.springframework.org/schema/util/spring-util-2.0.xsd"> - <bean id="messagingService" class="org.bigbluebutton.voiceconf.messaging.RedisMessagingService"> - <property name="redisMessageSender"> <ref bean="redisMessageSender"/></property> + <bean id="redisMessageDistributor" class="org.bigbluebutton.common2.redis.pubsub.MessageDistributor"> + <property name="messageHandler"> <ref local="redisMessageHandler"/> </property> + <property name="messageListeners"> + <set> + <ref bean="meetingMessageHandler" /> + </set> + </property> + </bean> + + <bean id="redisMessageHandler" class="org.bigbluebutton.common2.redis.pubsub.ReceivedMessageHandler" + init-method="start" destroy-method="stop"> + <property name="messageDistributor"><ref bean="redisMessageDistributor" /></property> </bean> - <bean id="redisMessageSender" class="org.bigbluebutton.voiceconf.messaging.MessageSender" + <bean id="redisMessageSender" class="org.bigbluebutton.common2.redis.pubsub.MessageSender" init-method="start" destroy-method="stop"> <property name="host" value="${redis.host}"/> <property name="port" value="${redis.port}"/> + <property name="clientName" value="BbbRed5VoiceSender" /> + <property name="password" value="${redis.password:}" /> + </bean> + + <bean id="messagingService" class="org.bigbluebutton.voiceconf.messaging.RedisMessagingService"> + <property name="redisMessageSender"> <ref bean="redisMessageSender"/></property> </bean> <bean id="meetingMessageHandler" class="org.bigbluebutton.voiceconf.messaging.MeetingMessageHandler"> <property name="connInvokerService" ref="connInvokerService"/> </bean> - <bean id="redisMessageReceiver" class="org.bigbluebutton.voiceconf.messaging.MessageReceiver" + <bean id="redisMessageReceiver" class="org.bigbluebutton.common2.redis.pubsub.MessageReceiver" init-method="start" destroy-method="stop"> <property name="host" value="${redis.host}"/> <property name="port" value="${redis.port}"/> + <property name="password" value="${redis.password:}" /> + <property name="clientName" value="BbbRed5VideoReceiver" /> <property name="messageHandler"> <ref local="redisMessageHandler"/> </property> </bean> - <bean id="redisMessageHandler" class="org.bigbluebutton.voiceconf.messaging.ReceivedMessageHandler" - init-method="start" destroy-method="stop"> - <property name="messageDistributor"><ref bean="redisMessageDistributor" /></property> - </bean> - - <bean id="redisMessageDistributor" class="org.bigbluebutton.voiceconf.messaging.MessageDistributor"> - <property name="messageHandler"> <ref local="redisMessageHandler"/> </property> - <property name="messageListeners"> - <set> - <ref bean="meetingMessageHandler" /> - </set> - </property> - </bean> - </beans> diff --git a/bbb-voice/src/main/webapp/WEB-INF/bigbluebutton-sip.properties b/bbb-voice/src/main/webapp/WEB-INF/bigbluebutton-sip.properties index 370990a1859ac2180df786079036a1d379702eaf..3dc265ff1bea57288d330def75ba2f334fd931fd 100755 --- a/bbb-voice/src/main/webapp/WEB-INF/bigbluebutton-sip.properties +++ b/bbb-voice/src/main/webapp/WEB-INF/bigbluebutton-sip.properties @@ -7,7 +7,6 @@ bbb.sip.app.port=5070 sip.server.username=bbbuser sip.server.password=secret - # The ip and port of the FreeSWITCH server freeswitch.ip=192.168.23.53 freeswitch.port=5060 @@ -19,8 +18,7 @@ stopAudioPort=16383 redis.host=127.0.0.1 redis.port=6379 -redis.pass= - +redis.password= # If you want mjsip stack (red5/log/*access*.log) to minimize the amount of logs it # generates, set this to a lower value (e.g. 3). diff --git a/bbb-voice/src/main/webapp/WEB-INF/red5-web.properties b/bbb-voice/src/main/webapp/WEB-INF/red5-web.properties index 1e17dff094bfb42a2837817c705fdfe500fbb960..5d646d514744049abf439aca1a0e28163e07dc78 100644 --- a/bbb-voice/src/main/webapp/WEB-INF/red5-web.properties +++ b/bbb-voice/src/main/webapp/WEB-INF/red5-web.properties @@ -1,2 +1,2 @@ -webapp.contextPath=/sip -webapp.virtualHosts=*, localhost, localhost:8088, 127.0.0.1:8088 +webapp.contextPath=/sip +webapp.virtualHosts=*, localhost, localhost:8088, 127.0.0.1:8088 diff --git a/bbb-voice/src/main/webapp/WEB-INF/red5-web.xml b/bbb-voice/src/main/webapp/WEB-INF/red5-web.xml index 12f3bb8eebe8e7b6c2f0778a9aabf5983d045e08..610836054c018196d4416822c6ab1049a4cd1df7 100755 --- a/bbb-voice/src/main/webapp/WEB-INF/red5-web.xml +++ b/bbb-voice/src/main/webapp/WEB-INF/red5-web.xml @@ -1,5 +1,4 @@ <?xml version="1.0" encoding="UTF-8"?> - <!-- BigBlueButton open source conferencing system - http://www.bigbluebutton.org/ @@ -36,6 +35,12 @@ with BigBlueButton; if not, see <http://www.gnu.org/licenses/>. </property> </bean> + <import resource="bbb-redis-messaging.xml"/> + + <bean id="clientConnectionManager" class="org.bigbluebutton.voiceconf.red5.ClientConnectionManager"/> + + <bean id="connInvokerService" class="org.bigbluebutton.voiceconf.red5.ConnectionInvokerService" + init-method="start" destroy-method="stop"/> <bean id="web.context" class="org.red5.server.Context" autowire="byType" /> @@ -49,6 +54,16 @@ with BigBlueButton; if not, see <http://www.gnu.org/licenses/>. <property name="contextPath" value="${webapp.contextPath}" /> <property name="virtualHosts" value="${webapp.virtualHosts}" /> </bean> + + <bean id="sipPeerManager" class="org.bigbluebutton.voiceconf.sip.SipPeerManager"> + <property name="sipStackDebugLevel" value="${sipStackDebugLevel}"/> + <property name="sipRemotePort" value="${freeswitch.port}"/> + <property name="messagingService" ref="messagingService"/> + </bean> + + <bean id="voiceconf.service" class="org.bigbluebutton.voiceconf.red5.Service"> + <property name="sipPeerManager" ref="sipPeerManager"/> + </bean> <bean id="web.handler" class="org.bigbluebutton.voiceconf.red5.Application"> <property name="sipClientRtpIp" value="${bbb.sip.app.ip}" /> @@ -64,22 +79,4 @@ with BigBlueButton; if not, see <http://www.gnu.org/licenses/>. <property name="messagingService" ref="messagingService"/> </bean> - <bean id="voiceconf.service" class="org.bigbluebutton.voiceconf.red5.Service"> - <property name="sipPeerManager" ref="sipPeerManager"/> - </bean> - - <bean id="connInvokerService" class="org.bigbluebutton.voiceconf.red5.ConnectionInvokerService" - init-method="start" destroy-method="stop"/> - - <bean id="sipPeerManager" class="org.bigbluebutton.voiceconf.sip.SipPeerManager"> - <property name="sipStackDebugLevel" value="${sipStackDebugLevel}"/> - <property name="sipRemotePort" value="${freeswitch.port}"/> - <property name="messagingService" ref="messagingService"/> - - </bean> - - <bean id="clientConnectionManager" class="org.bigbluebutton.voiceconf.red5.ClientConnectionManager"/> - - <import resource="bbb-redis-messaging.xml"/> - </beans> diff --git a/bbb-webhooks/.dockerignore b/bbb-webhooks/.dockerignore new file mode 100644 index 0000000000000000000000000000000000000000..c2658d7d1b31848c3b71960543cb0368e56cd4c7 --- /dev/null +++ b/bbb-webhooks/.dockerignore @@ -0,0 +1 @@ +node_modules/ diff --git a/bbb-webhooks/config.js b/bbb-webhooks/config.js index cfc7ccda07e0a8f5f33c3f695da7c6e9d274d9b9..4c06a78fbbb92fb68eec04b05ada2438249bd25a 100644 --- a/bbb-webhooks/config.js +++ b/bbb-webhooks/config.js @@ -21,7 +21,10 @@ if (!config.hooks.channels) { config.hooks.channels = { mainChannel: 'from-akka-apps-redis-channel', rapChannel: 'bigbluebutton:from-rap', - chatChannel: 'from-akka-apps-chat-redis-channel' + chatChannel: 'from-akka-apps-chat-redis-channel', + compMeetingChannel: 'bigbluebutton:from-bbb-apps:meeting', + compUserChannel: 'bigbluebutton:from-bbb-apps:users', + compChatChannel: 'bigbluebutton:from-bbb-apps:chat' } } // IP where permanent hook will post data (more than 1 URL means more than 1 permanent hook) diff --git a/bbb-webhooks/messageMapping.js b/bbb-webhooks/messageMapping.js index 7ea2062c8a022894279ad9f4f15c20f82a4aac7e..3501d19fcada1e3fb69cd2333b18fb4a78291e5b 100644 --- a/bbb-webhooks/messageMapping.js +++ b/bbb-webhooks/messageMapping.js @@ -11,6 +11,9 @@ module.exports = class MessageMapping { this.userEvents = ["UserJoinedMeetingEvtMsg","UserLeftMeetingEvtMsg","UserJoinedVoiceConfToClientEvtMsg","UserLeftVoiceConfToClientEvtMsg","PresenterAssignedEvtMsg", "PresenterUnassignedEvtMsg", "UserBroadcastCamStartedEvtMsg", "UserBroadcastCamStoppedEvtMsg", "UserEmojiChangedEvtMsg"]; this.chatEvents = ["SendPublicMessageEvtMsg","SendPrivateMessageEvtMsg"]; this.rapEvents = ["archive_started","archive_ended","sanity_started","sanity_ended","post_archive_started","post_archive_ended","process_started","process_ended","post_process_started","post_process_ended","publish_started","publish_ended","post_publish_started","post_publish_ended"]; + + this.compMeetingEvents = ["meeting_created_message","meeting_destroyed_event"]; + this.compUserEvents = ["user_joined_message","user_left_message","user_listening_only","user_joined_voice_message","user_left_voice_message","user_shared_webcam_message","user_unshared_webcam_message","user_status_changed_message"]; } // Map internal message based on it's type @@ -23,6 +26,10 @@ module.exports = class MessageMapping { this.chatTemplate(messageObj); } else if (this.mappedEvent(messageObj,this.rapEvents)) { this.rapTemplate(messageObj); + } else if (this.mappedEvent(messageObj,this.compMeetingEvents)) { + this.compMeetingTemplate(messageObj); + } else if (this.mappedEvent(messageObj,this.compUserEvents)) { + this.compUserTemplate(messageObj); } } @@ -79,6 +86,46 @@ module.exports = class MessageMapping { Logger.info("[MessageMapping] Mapped message:", this.mappedMessage); } + compMeetingTemplate(messageObj) { + const props = messageObj.payload; + const meetingId = props.meeting_id; + this.mappedObject.data = { + "type": "event", + "id": this.mapInternalMessage(messageObj), + "attributes":{ + "meeting":{ + "internal-meeting-id": meetingId, + "external-meeting-id": IDMapping.getExternalMeetingID(meetingId) + } + }, + "event":{ + "ts": Date.now() + } + }; + if (messageObj.header.name === "meeting_created_message") { + this.mappedObject.data.attributes = { + "meeting":{ + "internal-meeting-id": meetingId, + "external-meeting-id": props.external_meeting_id, + "name": props.name, + "is-breakout": props.is_breakout, + "duration": props.duration, + "create-time": props.create_time, + "create-date": props.create_date, + "moderator-pass": props.moderator_pass, + "viewer-pass": props.viewer_pass, + "record": props.recorded, + "voice-conf": props.voice_conf, + "dial-number": props.dial_number, + "max-users": props.max_users, + "metadata": props.metadata + } + }; + } + this.mappedMessage = JSON.stringify(this.mappedObject); + Logger.info("[MessageMapping] Mapped message:", this.mappedMessage); + } + // Map internal to external message for user information userTemplate(messageObj) { const msgBody = messageObj.core.body; @@ -111,6 +158,68 @@ module.exports = class MessageMapping { Logger.info("[MessageMapping] Mapped message:", this.mappedMessage); } + // Map internal to external message for user information + compUserTemplate(messageObj) { + const msgBody = messageObj.payload; + const msgHeader = messageObj.header; + + let user; + if (msgHeader.name === "user_joined_message") { + user = { + "internal-user-id": msgBody.user.userid, + "external-user-id": msgBody.user.extern_userid, + "sharing-mic": msgBody.user.voiceUser.joined, + "name": msgBody.user.name, + "role": msgBody.user.role, + "presenter": msgBody.user.presenter, + "stream": msgBody.user.webcam_stream, + "listening-only": msgBody.user.listenOnly + } + } + else { + user = UserMapping.getUser(msgBody.userid) || { "internal-user-id": msgBody.userid || msgBody.user.userid }; + if (msgHeader.name === "user_status_changed_message") { + if (msgBody.status === "presenter") { + user["presenter"] = msgBody.value; + } + } + else if (msgHeader.name === "user_listening_only") { + user["listening-only"] = msgBody.listen_only; + } + else if (msgHeader.name === "user_joined_voice_message" || msgHeader.name === "user_left_voice_message") { + user["sharing-mic"] = msgBody.user.voiceUser.joined; + } + else if (msgHeader.name === "user_shared_webcam_message") { + user["stream"].push(msgBody.stream); + } + else if (msgHeader.name === "user_unshared_webcam_message") { + let streams = user["stream"]; + let index = streams.indexOf(msgBody.stream); + if (index != -1) { + streams.splice(index,1); + } + user["stream"] = streams; + } + } + + this.mappedObject.data = { + "type": "event", + "id": this.mapInternalMessage(messageObj), + "attributes":{ + "meeting":{ + "internal-meeting-id": msgBody.meeting_id, + "external-meeting-id": IDMapping.getExternalMeetingID(msgBody.meeting_id) + }, + "user": user + }, + "event":{ + "ts": Date.now() + } + }; + this.mappedMessage = JSON.stringify(this.mappedObject); + Logger.info("[MessageMapping] Mapped message:", this.mappedMessage); + } + // Map internal to external message for chat information chatTemplate(messageObj) { const message = messageObj.core.body.message; @@ -150,12 +259,13 @@ module.exports = class MessageMapping { const data = messageObj.payload; this.mappedObject.data = { "type": "event", - "id": this.mapInternalMessage(messageObj.header.name), + "id": this.mapInternalMessage(messageObj), "attributes": { "meeting": { "internal-meeting-id": data.meeting_id, - "external-meeting-id": data.external_meeting_id + "external-meeting-id": data.external_meeting_id || IDMapping.getExternalMeetingID(data.meeting_id) }, + "record-id": data.record_id, "success": data.success, "step-time": data.step_time }, @@ -164,14 +274,18 @@ module.exports = class MessageMapping { } }; - if (this.mappedObject.data["id"] == "rap-publish-ended") { - this.mappedObject.data["attributes"]["recording"] = { + if (data.workflow) { + this.mappedObject.data.attributes.workflow = data.workflow; + } + + if (this.mappedObject.data.id === "rap-publish-ended") { + this.mappedObject.data.attributes.recording = { "name": data.metadata.meetingName, - "isBreakout": data.metadata.isBreakout, - "startTime": data.startTime, - "endTime": data.endTime, + "is-breakout": data.metadata.isBreakout, + "start-time": data.startTime, + "end-time": data.endTime, "size": data.playback.size, - "rawSize": data.rawSize, + "raw-size": data.rawSize, "metadata": data.metadata, "playback": data.playback, "download": data.download @@ -183,13 +297,14 @@ module.exports = class MessageMapping { mapInternalMessage(message) { + let name; if (message.envelope) { - message = message.envelope.name + name = message.envelope.name } else if (message.header) { - message = message.header.name + name = message.header.name } - const mappedMsg = (() => { switch (message) { + const mappedMsg = (() => { switch (name) { case "MeetingCreatedEvtMsg": return "meeting-created"; case "MeetingDestroyedEvtMsg": return "meeting-ended"; case "RecordingStatusChangedEvtMsg": return "meeting-recording-changed"; @@ -221,6 +336,19 @@ module.exports = class MessageMapping { case "publish_ended": return "rap-publish-ended"; case "post_publish_started": return "rap-post-publish-started"; case "post_publish_ended": return "rap-post-publish-ended"; + case "meeting_created_message": return "meeting-created"; + case "meeting_destroyed_event": return "meeting-ended"; + case "user_joined_message": return "user-joined"; + case "user_left_message": return "user-left"; + case "user_listening_only": return (message.payload.listen_only ? "user-audio-listen-only-enabled" : "user-audio-listen-only-disabled"); + case "user_joined_voice_message": return "user-audio-voice-enabled"; + case "user_left_voice_message": return "user-audio-voice-disabled"; + case "user_shared_webcam_message": return "user-cam-broadcast-start"; + case "video_stream_unpublished": return "user-cam-broadcast-end"; + case "user_status_changed_message": + if (message.payload.status === "presenter") { + return (message.payload.value === "true" ? "user-presenter-assigned" : "user-presenter-unassigned" ); + } } })(); return mappedMsg; } diff --git a/bbb-webhooks/userMapping.js b/bbb-webhooks/userMapping.js index 185e2fc9a4a5d247cde2859f5c7a3021dedf681c..bf11b4a031d83db9ea4927926c0c38f573ec1ea2 100644 --- a/bbb-webhooks/userMapping.js +++ b/bbb-webhooks/userMapping.js @@ -32,16 +32,18 @@ module.exports = class UserMapping { this.externalUserID = null; this.internalUserID = null; this.meetingId = null; + this.user = null; this.redisClient = config.redis.client; } save(callback) { + db[this.internalUserID] = this; + this.redisClient.hmset(config.redis.keys.userMap(this.id), this.toRedis(), (error, reply) => { if (error != null) { Logger.error("[UserMapping] error saving mapping to redis:", error, reply); } this.redisClient.sadd(config.redis.keys.userMaps, this.id, (error, reply) => { if (error != null) { Logger.error("[UserMapping] error saving mapping ID to the list of mappings:", error, reply); } - db[this.internalUserID] = this; (typeof callback === 'function' ? callback(error, db[this.internalUserID]) : undefined); }); }); @@ -68,7 +70,8 @@ module.exports = class UserMapping { "id": this.id, "internalUserID": this.internalUserID, "externalUserID": this.externalUserID, - "meetingId": this.meetingId + "meetingId": this.meetingId, + "user": this.user }; return r; } @@ -78,18 +81,20 @@ module.exports = class UserMapping { this.externalUserID = redisData.externalUserID; this.internalUserID = redisData.internalUserID; this.meetingId = redisData.meetingId; + this.user = redisData.user; } print() { return JSON.stringify(this.toRedis()); } - static addMapping(internalUserID, externalUserID, meetingId, callback) { + static addOrUpdateMapping(internalUserID, externalUserID, meetingId, user, callback) { let mapping = new UserMapping(); mapping.id = nextID++; mapping.internalUserID = internalUserID; mapping.externalUserID = externalUserID; mapping.meetingId = meetingId; + mapping.user = user; mapping.save(function(error, result) { Logger.info(`[UserMapping] added user mapping to the list ${internalUserID}:`, mapping.print()); (typeof callback === 'function' ? callback(error, result) : undefined); @@ -131,6 +136,12 @@ module.exports = class UserMapping { })(); } + static getUser(internalUserID) { + if (db[internalUserID]){ + return db[internalUserID].user; + } + } + static getExternalUserID(internalUserID) { if (db[internalUserID]){ return db[internalUserID].externalUserID; diff --git a/bbb-webhooks/web_hooks.js b/bbb-webhooks/web_hooks.js index c00a2cf0b14c5eb2af40d16eeeb399fdd089c266..787027787a3f13d8b7c1a3dfb471d3fcacd4d604 100644 --- a/bbb-webhooks/web_hooks.js +++ b/bbb-webhooks/web_hooks.js @@ -54,7 +54,7 @@ module.exports = class WebHooks { }); break; case "user-joined": - UserMapping.addMapping(message.data.attributes.user["internal-user-id"],message.data.attributes.user["external-user-id"], intId, () => { + UserMapping.addOrUpdateMapping(message.data.attributes.user["internal-user-id"],message.data.attributes.user["external-user-id"], intId, message.data.attributes.user, () => { processMessage(); }); break; diff --git a/bigbluebutton-apps/build.gradle b/bigbluebutton-apps/build.gradle index 85dc33cbaf3f8d581abaa6ec6da1cfb687110869..0433b262ca0540b0f0155674fb743dfbe3c9be60 100755 --- a/bigbluebutton-apps/build.gradle +++ b/bigbluebutton-apps/build.gradle @@ -21,25 +21,33 @@ repositories { mavenLocal() } +configurations { + runtime.exclude group: "org.slf4j", module: "slf4j-api" + runtime.exclude group: "org.red5", module: "red5-server" + runtime.exclude group: "org.red5", module: "red5-server-common" + runtime.exclude group: "org.red5", module: "red5-io" +} + dependencies { // Servlet providedCompile 'javax.servlet:servlet-api:2.5@jar' // Mina - providedCompile 'org.apache.mina:mina-core:2.0.17@jar' - providedCompile 'org.apache.mina:mina-integration-beans:2.0.17@jar' - providedCompile 'org.apache.mina:mina-integration-jmx:2.0.17@jar' + providedCompile 'org.apache.mina:mina-core:2.0.19@jar' + providedCompile 'org.apache.mina:mina-integration-beans:2.0.19@jar' + providedCompile 'org.apache.mina:mina-integration-jmx:2.0.19@jar' // Spring providedCompile 'org.springframework:spring-web:4.3.12.RELEASE@jar' providedCompile 'org.springframework:spring-beans:4.3.12.RELEASE@jar' providedCompile 'org.springframework:spring-context:4.3.12.RELEASE@jar' - providedCompile 'org.springframework:spring-core:4.3.13.RELEASE@jar' + providedCompile 'org.springframework:spring-core:4.3.12.RELEASE@jar' // Red5 - providedCompile 'org.red5:red5-server:1.0.10-M5@jar' - providedCompile 'org.red5:red5-server-common:1.0.10-M5@jar' - + providedCompile 'org.red5:red5-server:1.0.10-M9@jar' + providedCompile 'org.red5:red5-server-common:1.0.10-M9@jar' + providedCompile 'org.red5:red5-io:1.0.10-M9@jar' + // Logging providedCompile 'ch.qos.logback:logback-core:1.2.3@jar' providedCompile 'ch.qos.logback:logback-classic:1.2.3@jar' @@ -57,14 +65,13 @@ dependencies { compile 'org.easymock:easymock:3.6@jar' //redis - compile "redis.clients:jedis:2.9.0" compile 'org.apache.commons:commons-pool2:2.6.0' - compile 'com.google.code.gson:gson:2.8.5' - providedCompile 'org.apache.commons:commons-lang3:3.7' + compile 'com.google.code.gson:gson:2.8.5' + providedCompile 'org.apache.commons:commons-lang3:3.7' - compile 'org.bigbluebutton:bbb-common-message_2.12:0.0.19-SNAPSHOT' - compile 'org.bigbluebutton:bbb-apps-common_2.12:0.0.3-SNAPSHOT' + compile 'org.bigbluebutton:bbb-common-message_2.12:0.0.20-SNAPSHOT' + compile 'org.bigbluebutton:bbb-apps-common_2.12:0.0.4-SNAPSHOT' } test { diff --git a/bigbluebutton-apps/deploy.sh b/bigbluebutton-apps/deploy.sh index b002c6f858b83aa29961ec5a1b9193b600bbbade..b7cab36a4bc4e2ff6e2b72d8bb9bf36079ce3149 100755 --- a/bigbluebutton-apps/deploy.sh +++ b/bigbluebutton-apps/deploy.sh @@ -1,18 +1,10 @@ #!/bin/bash # deploying 'bigbluebutton-apps' to /usr/share/red5/webapps -sudo chown -R red5.red5 /usr/share/red5/webapps +sudo chmod -R 777 /usr/share/red5/webapps/* gradle clean gradle resolveDeps gradle war deploy -sudo chown -R red5.red5 /usr/share/red5/webapps - -# Remove slf4j jar as it conflicts with logging with red5 -FILE=/usr/share/red5/webapps/bigbluebutton/WEB-INF/lib/slf4j-api-1.7.25.jar -if [ -f $FILE ] -then - sudo rm $FILE -fi - +sudo chown -R red5:red5 /usr/share/red5/webapps diff --git a/bigbluebutton-apps/gradle.properties b/bigbluebutton-apps/gradle.properties deleted file mode 100644 index a0cdfb5b63d4667c2cdcd67631f5017fd006f34e..0000000000000000000000000000000000000000 --- a/bigbluebutton-apps/gradle.properties +++ /dev/null @@ -1,16 +0,0 @@ -## Dependecies Version - -# Logging -log4jVersion = 1.2.17 -slf4jVersion = 1.6.6 - -# Common libraries -springVersion = 3.1.4.RELEASE -springRedisVersion = 1.1.0.RELEASE -jacksonVersion = 1.8.3 - -# Testing -junitVersion = 4.8.1 -mockitoVersion = 1.8.5 - - diff --git a/bigbluebutton-apps/src/main/webapp/WEB-INF/bbb-redis-pool.xml b/bigbluebutton-apps/src/main/webapp/WEB-INF/bbb-redis-pool.xml deleted file mode 100755 index b72ba93cd2670e60586dec23400a791ac34031de..0000000000000000000000000000000000000000 --- a/bigbluebutton-apps/src/main/webapp/WEB-INF/bbb-redis-pool.xml +++ /dev/null @@ -1,35 +0,0 @@ -<?xml version="1.0" encoding="UTF-8"?> -<!-- - -BigBlueButton open source conferencing system - http://www.bigbluebutton.org/ - -Copyright (c) 2012 BigBlueButton Inc. and by respective authors (see below). - -This program is free software; you can redistribute it and/or modify it under the -terms of the GNU Lesser General Public License as published by the Free Software -Foundation; either version 3.0 of the License, or (at your option) any later -version. - -BigBlueButton is distributed in the hope that it will be useful, but WITHOUT ANY -WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A -PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. - -You should have received a copy of the GNU Lesser General Public License along -with BigBlueButton; if not, see <http://www.gnu.org/licenses/>. - ---> -<beans xmlns="http://www.springframework.org/schema/beans" - xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" - xmlns:util="http://www.springframework.org/schema/util" - xsi:schemaLocation="http://www.springframework.org/schema/beans - http://www.springframework.org/schema/beans/spring-beans-2.5.xsd - http://www.springframework.org/schema/util - http://www.springframework.org/schema/util/spring-util-2.0.xsd - "> - <!-- - <bean id="redisPool" class="redis.clients.jedis.JedisPool"> - <constructor-arg index="0" value="${redis.host}"/> - <constructor-arg index="1" value="${redis.port}"/> - </bean> - --> -</beans> diff --git a/bigbluebutton-apps/src/main/webapp/WEB-INF/classes/application.conf b/bigbluebutton-apps/src/main/webapp/WEB-INF/classes/application.conf index 74183500ea75522d1927e5742c4f93d2722b1269..3b72838eb4bbe89bb4f2c7d26663b8c90cbd2a2b 100755 --- a/bigbluebutton-apps/src/main/webapp/WEB-INF/classes/application.conf +++ b/bigbluebutton-apps/src/main/webapp/WEB-INF/classes/application.conf @@ -10,7 +10,7 @@ akka { loggers = ["akka.event.slf4j.Slf4jLogger"] loglevel = "DEBUG" - rediscala-publish-worker-dispatcher { + redis-publish-worker-dispatcher { mailbox-type = "akka.dispatch.SingleConsumerOnlyUnboundedMailbox" # Throughput defines the maximum number of messages to be # processed per actor before the thread jumps to the next actor. @@ -18,7 +18,7 @@ akka { throughput = 512 } - rediscala-subscriber-worker-dispatcher { + redis-subscriber-worker-dispatcher { mailbox-type = "akka.dispatch.SingleConsumerOnlyUnboundedMailbox" # Throughput defines the maximum number of messages to be # processed per actor before the thread jumps to the next actor. diff --git a/bigbluebutton-apps/src/main/webapp/WEB-INF/red5-web.xml b/bigbluebutton-apps/src/main/webapp/WEB-INF/red5-web.xml index 8891ce3b5af9e5a0f53a1552ac80e00323fb2f8d..2023fa50935836d8464329a04c76eee9125187e1 100755 --- a/bigbluebutton-apps/src/main/webapp/WEB-INF/red5-web.xml +++ b/bigbluebutton-apps/src/main/webapp/WEB-INF/red5-web.xml @@ -36,7 +36,18 @@ with BigBlueButton; if not, see <http://www.gnu.org/licenses/>. </property> </bean> + <bean id="msgToClientGW" class="org.bigbluebutton.client.MsgToClientGW" > + <constructor-arg index="0" ref="connInvokerService"/> + </bean> + + <bean id="clientGWApp" class="org.bigbluebutton.client.ClientGWApplication" destroy-method="shutdown"> + <constructor-arg index="0" ref="msgToClientGW"/> + </bean> + <bean id="web.context" class="org.red5.server.Context" autowire="byType" /> + + <bean id="connInvokerService" class="org.bigbluebutton.red5.client.messaging.ConnectionInvokerService" + init-method="start" destroy-method="stop"/> <bean id="web.scope" class="org.red5.server.scope.WebScope" init-method="register"> @@ -47,6 +58,10 @@ with BigBlueButton; if not, see <http://www.gnu.org/licenses/>. <property name="contextPath" value="${webapp.contextPath}" /> <property name="virtualHosts" value="${webapp.virtualHosts}" /> </bean> + + <bean id="clientInGW" class="org.bigbluebutton.client.ClientInGW" > + <constructor-arg index="0" ref="clientGWApp"/> + </bean> <bean id="web.handler" class="org.bigbluebutton.red5.BigBlueButtonApplication"> <property name="connInvokerService"><ref bean="connInvokerService"/></property> @@ -54,25 +69,8 @@ with BigBlueButton; if not, see <http://www.gnu.org/licenses/>. <property name="maxMessageLength" value="${maxMessageLength}"/> </bean> - <bean id="clientInGW" class="org.bigbluebutton.client.ClientInGW" > - <constructor-arg index="0" ref="clientGWApp"/> - </bean> - - <bean id="msgToClientGW" class="org.bigbluebutton.client.MsgToClientGW" > - <constructor-arg index="0" ref="connInvokerService"/> - </bean> - - <bean id="clientGWApp" class="org.bigbluebutton.client.ClientGWApplication" destroy-method="shutdown"> - <constructor-arg index="0" ref="msgToClientGW"/> - </bean> - - <bean id="connInvokerService" class="org.bigbluebutton.red5.client.messaging.ConnectionInvokerService" - init-method="start" destroy-method="stop"/> - <bean id="bbbAppsIsAliveMonitorService" class="org.bigbluebutton.red5.monitoring.BbbAppsIsAliveMonitorService" init-method="start" destroy-method="stop"> <property name="connectionInvokerService"> <ref bean="connInvokerService"/></property> </bean> - - <import resource="bbb-redis-pool.xml"/> </beans> diff --git a/bigbluebutton-apps/src/main/webapp/WEB-INF/spring/applicationContext-redis.xml b/bigbluebutton-apps/src/main/webapp/WEB-INF/spring/applicationContext-redis.xml deleted file mode 100755 index ebc657b8c66a89465b13937687f52376c5cc3243..0000000000000000000000000000000000000000 --- a/bigbluebutton-apps/src/main/webapp/WEB-INF/spring/applicationContext-redis.xml +++ /dev/null @@ -1,18 +0,0 @@ -<?xml version="1.0" encoding="UTF-8"?> -<beans xmlns="http://www.springframework.org/schema/beans" - xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" - xmlns:p="http://www.springframework.org/schema/p" - xmlns:context="http://www.springframework.org/schema/context" - xsi:schemaLocation=" - http://www.springframework.org/schema/beans - http://www.springframework.org/schema/beans/spring-beans.xsd - http://www.springframework.org/schema/context - http://www.springframework.org/schema/context/spring-context.xsd"> - - <bean id="connectionFactory" class="org.springframework.data.redis.connection.jedis.JedisConnectionFactory" - p:host-name="${redis.host}" p:port="${redis.port}" p:password="${redis.pass}"/> - - <bean id="redisTemplate" class="org.springframework.data.redis.core.StringRedisTemplate" - p:connection-factory-ref="connectionFactory"/> - -</beans> diff --git a/bigbluebutton-client/resources/config.xml.template b/bigbluebutton-client/resources/config.xml.template index f878f6bf49fc9cdb2b744f128df893b7a9806815..763979403d6a8f57b44e4ab52eb262355619f6f5 100644 --- a/bigbluebutton-client/resources/config.xml.template +++ b/bigbluebutton-client/resources/config.xml.template @@ -30,7 +30,7 @@ uri="rtmp://HOST/bigbluebutton" dependsOn="UsersModule" privateEnabled="true" - groupEnabled="true" + groupEnabled="false" fontSize="14" baseTabIndex="801" colorPickerIsVisible="false" @@ -116,9 +116,10 @@ showPresentWindow="true" showWindowControls="true" openExternalFileUploadDialog="false" + multiPods="false" baseTabIndex="501" maxFileSize="30" - enableDownload="true" + enableDownload="false" disableFirefoxF60Upload="true" /> diff --git a/bigbluebutton-client/src/org/bigbluebutton/modules/chat/model/ChatOptions.as b/bigbluebutton-client/src/org/bigbluebutton/modules/chat/model/ChatOptions.as index 29fad3c8ec8cd166720cbea3ba35cca25db63898..7f9f48bf4aa30974b5d6e71182938a7eecc48b52 100755 --- a/bigbluebutton-client/src/org/bigbluebutton/modules/chat/model/ChatOptions.as +++ b/bigbluebutton-client/src/org/bigbluebutton/modules/chat/model/ChatOptions.as @@ -26,7 +26,7 @@ package org.bigbluebutton.modules.chat.model { public var privateEnabled:Boolean = true; [Bindable] - public var groupEnabled:Boolean = true; + public var groupEnabled:Boolean = false; [Bindable] public var fontSize:String = "14"; diff --git a/bigbluebutton-client/src/org/bigbluebutton/modules/present/model/PresentOptions.as b/bigbluebutton-client/src/org/bigbluebutton/modules/present/model/PresentOptions.as index d0474e088f24dd8f0bbb93580d9ebb8c010769e0..e9f6f29255ac775d7fe6643b68a18a482200a5d6 100755 --- a/bigbluebutton-client/src/org/bigbluebutton/modules/present/model/PresentOptions.as +++ b/bigbluebutton-client/src/org/bigbluebutton/modules/present/model/PresentOptions.as @@ -37,7 +37,10 @@ package org.bigbluebutton.modules.present.model { public var openExternalFileUploadDialog:Boolean = false; [Bindable] - public var enableDownload:Boolean = true; + public var multiPods:Boolean = false; + + [Bindable] + public var enableDownload:Boolean = false; [Bindable] public var disableFirefoxF60Upload:Boolean = true; diff --git a/bigbluebutton-client/src/org/bigbluebutton/modules/present/ui/views/PresentationWindow.mxml b/bigbluebutton-client/src/org/bigbluebutton/modules/present/ui/views/PresentationWindow.mxml index 2d52fc5437cc936f26be7debdb4898bc618b63ef..c7bae41b02eb024b00da1e1517421883add3f5d0 100755 --- a/bigbluebutton-client/src/org/bigbluebutton/modules/present/ui/views/PresentationWindow.mxml +++ b/bigbluebutton-client/src/org/bigbluebutton/modules/present/ui/views/PresentationWindow.mxml @@ -1191,8 +1191,8 @@ with BigBlueButton; if not, see <http://www.gnu.org/licenses/>. includeInLayout="{quickPollBtn.visible}" /> <views:UnselectableItemsComboBox id="presentationPodControls" - visible="{podDropdownVisible}" - includeInLayout="{podDropdownVisible}" + visible="{presentOptions.multiPods && podDropdownVisible}" + includeInLayout="{presentationPodControls.visible}" selectableFunction="podsSelectableFunction" dataProvider = "{listOfPodControls}" height="30" selectedIndex="-1" diff --git a/bigbluebutton-config/bigbluebutton-release b/bigbluebutton-config/bigbluebutton-release index 3e3fed860f984d91395ad78a951be478a636713a..beffee177ee32c109e5ff426c38b0f2c2a0524d7 100644 --- a/bigbluebutton-config/bigbluebutton-release +++ b/bigbluebutton-config/bigbluebutton-release @@ -1,2 +1 @@ BIGBLUEBUTTON_RELEASE=2.2.0-dev - diff --git a/bigbluebutton-config/bin/bbb-conf b/bigbluebutton-config/bin/bbb-conf index b322cf6485169921821618cf4ec4978dff631577..e1e54e0c6f258be489be95c1e54a228f784b8763 100755 --- a/bigbluebutton-config/bin/bbb-conf +++ b/bigbluebutton-config/bin/bbb-conf @@ -56,16 +56,26 @@ # 2016-08-15 GTR Archive more logs with zip option and show more applications with status # 2016-10-17 GTR Added redis to checked server components & added ownership check for video and freeswitch recording directories # 2017-04-08 FFD Cleanup for 1.1-beta +# 2018-11-22 MNE Dynamically detect if sudo is needed +# 2018-12-09 GTR More logs cleanup #set -x #set -e PATH=$PATH:/sbin +if [ "$(id -u)" != "0" ]; then + if [ -x "$(which sudo)" ]; then + SUDO="$(which sudo)" + else + echo "bbb-conf must be ran as root!" && exit 1 + fi +fi + source /etc/bigbluebutton/bigbluebutton-release # -# Figure out our environment +# Figure out our environment (Debian vs. CentOS) # RED5_DIR=/usr/share/red5 @@ -136,7 +146,7 @@ fi # if [ -f $SERVLET_DIR/bigbluebutton/WEB-INF/classes/bigbluebutton.properties ]; then if cat $SERVLET_DIR/bigbluebutton/WEB-INF/classes/bigbluebutton.properties | grep -q ^bigbluebutton.web.logoutURL=$; then - sudo sed -i s/^bigbluebutton.web.logoutURL=$/bigbluebutton.web.logoutURL=default/g $SERVLET_DIR/webapps/bigbluebutton/WEB-INF/classes/bigbluebutton.properties + $SUDO sed -i s/^bigbluebutton.web.logoutURL=$/bigbluebutton.web.logoutURL=default/g $SERVLET_DIR/webapps/bigbluebutton/WEB-INF/classes/bigbluebutton.properties fi fi @@ -487,19 +497,25 @@ start_bigbluebutton () { display_bigbluebutton_status () { if command -v systemctl >/dev/null; then - units="red5 $TOMCAT_SERVICE nginx freeswitch $REDIS_SERVICE bbb-apps-akka bbb-transcode-akka bbb-fsesl-akka" + units="red5 $TOMCAT_SERVICE nginx freeswitch $REDIS_SERVICE bbb-apps-akka bbb-transcode-akka bbb-fsesl-akka" - if [ -f /usr/lib/systemd/system/bbb-html5.service ]; then - units="$units mongod bbb-html5 bbb-webrtc-sfu kurento-media-server" - fi + if [ -f /usr/lib/systemd/system/bbb-html5.service ]; then + units="$units mongod bbb-html5 bbb-webrtc-sfu kurento-media-server" + fi - for unit in $units; do - echo "$unit: $(systemctl is-active $unit)" - done + line='——————————————————————►' + for unit in $units; do + status=$(systemctl is-active "$unit") + if [ "$status" = "active" ]; then + printf "%s %s [✔ - $status]\n" $unit "${line:${#unit}}" + else + printf "%s %s [✘ - $status]\n" $unit "${line:${#unit}}" + fi + done else - /etc/init.d/nginx status - /etc/init.d/$RED5 status - /etc/init.d/${SERVLET_CONTAINER} status + /etc/init.d/nginx status + /etc/init.d/$RED5 status + /etc/init.d/${SERVLET_CONTAINER} status fi } @@ -530,7 +546,7 @@ enable_webrtc(){ echo echo "WebRTC audio enabled. To apply settings to your server, do" echo - echo " sudo bbb-conf --clean" + echo " $SUDO bbb-conf --clean" echo } @@ -554,7 +570,7 @@ disable_webrtc(){ echo echo "WebRTC audio disabled. To apply settings to your server, do" echo - echo " sudo bbb-conf --clean" + echo " $SUDO bbb-conf --clean" echo } @@ -730,7 +746,7 @@ if [ $SECRET ]; then echo "Changed BigBlueButton's shared secret to $SECRET" echo echo "You must restart BigBlueButton for the changes to take effect" - echo " sudo bbb-conf --restart" + echo " $SUDO bbb-conf --restart" echo fi @@ -745,7 +761,7 @@ if [ $SAMBA ]; then # Instal Samba # if ! dpkg-query -s samba > /dev/null 2>&1; then - sudo apt-get install -y --force-yes samba ant + $SUDO apt-get install -y --force-yes samba ant fi # @@ -764,9 +780,9 @@ if [ $SAMBA ]; then directory mask = 0775 guest ok = yes force user = $USER -" | sudo tee -a /etc/samba/smb.conf > /dev/null 2>&1 +" | $SUDO tee -a /etc/samba/smb.conf > /dev/null 2>&1 - sudo /etc/init.d/smbd restart + $SUDO /etc/init.d/smbd restart echo " You can now access your development folder through: @@ -1220,10 +1236,10 @@ check_state() { echo "# Warning: The installed ffmpeg version '${FFMPEG_VERSION}' is not recommended." echo "# Recommend you update to the 4.0.x version of ffmpeg. To upgrade, do the following" echo "#" - echo "# sudo apt-get install software-properties-common" - echo "# sudo add-apt-repository ppa:jonathonf/ffmpeg-4" - echo "# sudo apt-get update" - echo "# sudo apt-get dist-upgrade" + echo "# $SUDO apt-get install software-properties-common" + echo "# $SUDO add-apt-repository ppa:jonathonf/ffmpeg-4" + echo "# $SUDO apt-get update" + echo "# $SUDO apt-get dist-upgrade" echo "#" echo ;; @@ -1238,7 +1254,7 @@ check_state() { echo "# Warning: The voice application may not have registered with the sip server." echo "# Try running: " echo "#" - echo "# sudo bbb-conf --clean" + echo "# $SUDO bbb-conf --clean" echo "#" echo fi @@ -1383,7 +1399,7 @@ check_state() { if test ${SERVLET_DIR}/lti.war -nt ${SERVLET_DIR}/lti; then echo "# Error: The updated lti.war did not deploy. To manually deploy:" echo "#" - echo "# sudo touch ${SERVLET_DIR}/lti.war" + echo "# $SUDO touch ${SERVLET_DIR}/lti.war" echo "#" echo fi @@ -1393,7 +1409,7 @@ check_state() { if test ${SERVLET_DIR}/demo.war -nt ${SERVLET_DIR}/demo; then echo "# Error: The updated demo.war did not deploy. To manually deploy:" echo "#" - echo "# sudo touch ${SERVLET_DIR}/demo.war" + echo "# $SUDO touch ${SERVLET_DIR}/demo.war" echo "#" echo fi @@ -1415,7 +1431,7 @@ check_state() { echo "# to create/manage meetings and recordings. They are for testing purposes only." echo "# If you are running a production system, remove them by running:" echo "#" - echo "# sudo apt-get purge bbb-demo" + echo "# $SUDO apt-get purge bbb-demo" echo fi @@ -1492,7 +1508,7 @@ check_state() { echo "# Error: Detected that /usr/share/bbb-apps-akka/conf/application.conf has the default" echo "# configuration values. To update, run" echo "#" - echo "# sudo bbb-conf --setip $BBB_WEB_IP" + echo "# $SUDO bbb-conf --setip $BBB_WEB_IP" echo "#" fi @@ -1555,10 +1571,10 @@ if [ $CHECK ]; then echo " useWebrtcIfAvailable: $WEBRTC_ENABLED_CLIENT" if grep -q ws-binding $FREESWITCH_EXTERNAL; then - WEBRTC_SOCKET=$(sudo cat $FREESWITCH_EXTERNAL | sed -n '/ws-binding/{s/.*value="//;s/".*//;p}') + WEBRTC_SOCKET=$($SUDO cat $FREESWITCH_EXTERNAL | sed -n '/ws-binding/{s/.*value="//;s/".*//;p}') fi if grep -q wss-binding $FREESWITCH_EXTERNAL; then - WEBRTC_SOCKET=$(sudo cat $FREESWITCH_EXTERNAL | sed -n '/wss-binding/{s/.*value="//;s/".*//;p}') + WEBRTC_SOCKET=$($SUDO cat $FREESWITCH_EXTERNAL | sed -n '/wss-binding/{s/.*value="//;s/".*//;p}') fi echo echo "$FREESWITCH_EXTERNAL (FreeSWITCH)" @@ -1723,7 +1739,7 @@ if [ $DEBUG ]; then rm -rf /tmp/t - sudo grep --directories=skip Exception $SERVLET_LOGS/* | grep -v CacheExceptionHandlerFactory > /tmp/t + $SUDO grep --directories=skip Exception $SERVLET_LOGS/* | grep -v CacheExceptionHandlerFactory > /tmp/t if [ -s /tmp/t ]; then echo " -- Exceptions found in $SERVLET_LOGS/ -- " cat /tmp/t @@ -1742,7 +1758,7 @@ if [ $DEBUG ]; then if [ $DISTRIB_ID == "Ubuntu" ]; then rm -rf /tmp/t - sudo grep --directories=skip -i exception /var/log/syslog > /tmp/t + $SUDO grep --directories=skip -i exception /var/log/syslog > /tmp/t if [ -s /tmp/t ]; then echo " -- Errors found in /var/log/syslog -- " cat /tmp/t @@ -1752,7 +1768,7 @@ if [ $DEBUG ]; then rm -rf /tmp/t if [ -d /var/log/bigbluebutton ]; then - sudo grep --directories=skip ERROR /var/log/bigbluebutton/* > /tmp/t + $SUDO grep --directories=skip ERROR /var/log/bigbluebutton/* > /tmp/t if [ -s /tmp/t ]; then echo " -- Errors found in /var/log/bigbluebutton -- " cat /tmp/t @@ -1762,7 +1778,7 @@ if [ $DEBUG ]; then rm -rf /tmp/t if [ -d /var/log/bigbluebutton ]; then - sudo grep --directories=skip -i exception /var/log/bigbluebutton/* > /tmp/t + $SUDO grep --directories=skip -i exception /var/log/bigbluebutton/* > /tmp/t if [ -s /tmp/t ]; then echo " -- Exceptions found in /var/log/bigbluebutton -- " cat /tmp/t @@ -1789,13 +1805,13 @@ if [ -n "$HOST" ]; then # Just use the IP for port test in /var/www/bigbluebutton/client/conf/config.xml # echo "Assigning $HOST for testing for firewall in /var/www/bigbluebutton/client/conf/config.xml" - sudo sed -i "s/porttest host=\(\"[^\"]*\"\)/porttest host=\"$PROTOCOL_RTMP:\/\/$HOST\"/g" /var/www/bigbluebutton/client/conf/config.xml + $SUDO sed -i "s/porttest host=\(\"[^\"]*\"\)/porttest host=\"$PROTOCOL_RTMP:\/\/$HOST\"/g" /var/www/bigbluebutton/client/conf/config.xml echo "Assigning $HOST for rtmp:// in /var/www/bigbluebutton/client/conf/config.xml" - sudo sed -i "s/rtmp[s]*:\/\/\([^\"\/]*\)\([\"\/]\)/$PROTOCOL_RTMP:\/\/$HOST\2/g" /var/www/bigbluebutton/client/conf/config.xml + $SUDO sed -i "s/rtmp[s]*:\/\/\([^\"\/]*\)\([\"\/]\)/$PROTOCOL_RTMP:\/\/$HOST\2/g" /var/www/bigbluebutton/client/conf/config.xml echo "Assigning $HOST for servername in /etc/nginx/sites-available/bigbluebutton" - sudo sed -i "s/server_name .*/server_name $HOST;/g" /etc/nginx/sites-available/bigbluebutton + $SUDO sed -i "s/server_name .*/server_name $HOST;/g" /etc/nginx/sites-available/bigbluebutton # # Update configuration for BigBlueButton client (and preserve hostname for chromeExtensionLink if exists) @@ -1805,26 +1821,26 @@ if [ -n "$HOST" ]; then chromeExtensionLinkURL=$(cat /var/www/bigbluebutton/client/conf/config.xml | sed -n '/chromeExtensionLink/{s/.*https*:\/\///;s/\/.*//;p}') echo "Assigning $HOST for http[s]:// in /var/www/bigbluebutton/client/conf/config.xml" - sudo sed -i "s/http[s]*:\/\/\([^\"\/]*\)\([\"\/]\)/$PROTOCOL_HTTP:\/\/$HOST\2/g" \ + $SUDO sed -i "s/http[s]*:\/\/\([^\"\/]*\)\([\"\/]\)/$PROTOCOL_HTTP:\/\/$HOST\2/g" \ /var/www/bigbluebutton/client/conf/config.xml if ! echo "$chromeExtensionLinkURL" | grep -q '""'; then - sudo sed -i "s/chromeExtensionLink=\"https:\/\/[^\/]*/chromeExtensionLink=\"https:\/\/$chromeExtensionLinkURL/g" \ + $SUDO sed -i "s/chromeExtensionLink=\"https:\/\/[^\/]*/chromeExtensionLink=\"https:\/\/$chromeExtensionLinkURL/g" \ /var/www/bigbluebutton/client/conf/config.xml fi echo "Assigning $HOST for publishURI in /var/www/bigbluebutton/client/conf/config.xml" - sudo sed -i "s/publishURI=\"[^\"]*\"/publishURI=\"$HOST\"/" /var/www/bigbluebutton/client/conf/config.xml + $SUDO sed -i "s/publishURI=\"[^\"]*\"/publishURI=\"$HOST\"/" /var/www/bigbluebutton/client/conf/config.xml # # Update configuration for BigBlueButton web app # echo "Assigning $HOST for web application URL in ${SERVLET_DIR}/bigbluebutton/WEB-INF/classes/bigbluebutton.properties" - sudo sed -i "s/bigbluebutton.web.serverURL=http[s]*:\/\/.*/bigbluebutton.web.serverURL=$PROTOCOL_HTTP:\/\/$HOST/g" \ + $SUDO sed -i "s/bigbluebutton.web.serverURL=http[s]*:\/\/.*/bigbluebutton.web.serverURL=$PROTOCOL_HTTP:\/\/$HOST/g" \ ${SERVLET_DIR}/bigbluebutton/WEB-INF/classes/bigbluebutton.properties - sudo sed -i "s/screenshareRtmpServer=.*/screenshareRtmpServer=$HOST/g" \ + $SUDO sed -i "s/screenshareRtmpServer=.*/screenshareRtmpServer=$HOST/g" \ ${SERVLET_DIR}/bigbluebutton/WEB-INF/classes/bigbluebutton.properties change_var_value /usr/share/red5/webapps/screenshare/WEB-INF/screenshare.properties streamBaseUrl rtmp://$HOST/screenshare @@ -1832,7 +1848,7 @@ if [ -n "$HOST" ]; then change_var_value /usr/share/red5/webapps/screenshare/WEB-INF/screenshare.properties jnlpFile $PROTOCOL_HTTP://$HOST/screenshare/screenshare.jnlp if ! grep -q server_names_hash_bucket_size /etc/nginx/nginx.conf; then - sudo sed -i "s/gzip on;/gzip on;\n server_names_hash_bucket_size 64;/g" /etc/nginx/nginx.conf + $SUDO sed -i "s/gzip on;/gzip on;\n server_names_hash_bucket_size 64;/g" /etc/nginx/nginx.conf fi # @@ -1857,7 +1873,7 @@ if [ -n "$HOST" ]; then if [ -f ${SERVLET_DIR}/demo/bbb_api_conf.jsp ]; then echo "Assigning $HOST for api demos in ${SERVLET_DIR}/demo/bbb_api_conf.jsp" - sudo sed -i "s/BigBlueButtonURL = \"http[s]*:\/\/\([^\"\/]*\)\([\"\/]\)/BigBlueButtonURL = \"$PROTOCOL_HTTP:\/\/$HOST\2/g" \ + $SUDO sed -i "s/BigBlueButtonURL = \"http[s]*:\/\/\([^\"\/]*\)\([\"\/]\)/BigBlueButtonURL = \"$PROTOCOL_HTTP:\/\/$HOST\2/g" \ ${SERVLET_DIR}/demo/bbb_api_conf.jsp fi @@ -1973,8 +1989,20 @@ if [ $CLEAN ]; then # rm -f /var/log/bigbluebutton/bbb-rap-worker.log* rm -f /var/log/bigbluebutton/archive.log* - if [ -d /var/log/bigbluebutton/slides ]; then - rm -f /var/log/bigbluebutton/slides/* + if [ -d /var/log/bigbluebutton/html5 ]; then + rm -f /var/log/bigbluebutton/html5/* + fi + + if [ -d /var/log/bigbluebutton/podcast ]; then + rm -f /var/log/bigbluebutton/podcast/* + fi + + if [ -d /var/log/bigbluebutton/presentation ]; then + rm -f /var/log/bigbluebutton/presentation/* + fi + + if [ -d /var/log/bigbluebutton/screenshare ]; then + rm -f /var/log/bigbluebutton/screenshare/* fi @@ -2009,6 +2037,18 @@ if [ $CLEAN ]; then if [ -d /var/log/bbb-webrtc-sfu ]; then rm -f /var/log/bbb-webrtc-sfu/* fi + + if [ -d /var/log/redis ]; then + rm -f /var/log/redis/* + fi + + if [ -d /var/log/mongodb ]; then + rm -f /var/log/mongodb/* + fi + + if [ -d /var/log/kurento-media-server ]; then + rm -f /var/log/kurento-media-server/* + fi start_bigbluebutton check_state diff --git a/bigbluebutton-html5/.meteor/.finished-upgraders b/bigbluebutton-html5/.meteor/.finished-upgraders index 910574ce2df99023d64240d2865fcd83e3e400e4..4538749ab812db7df6cff962d3b43de5108a1ebb 100644 --- a/bigbluebutton-html5/.meteor/.finished-upgraders +++ b/bigbluebutton-html5/.meteor/.finished-upgraders @@ -15,3 +15,4 @@ notices-for-facebook-graph-api-2 1.4.1-add-shell-server-package 1.4.3-split-account-service-packages 1.5-add-dynamic-import-package +1.7-split-underscore-from-meteor-base diff --git a/bigbluebutton-html5/.meteor/packages b/bigbluebutton-html5/.meteor/packages index a0f2677c4c0edd35479d857cb77bcf15a282cd30..f23ddee2e79c68345ddeb08c1aa84fa7ae92fdfd 100644 --- a/bigbluebutton-html5/.meteor/packages +++ b/bigbluebutton-html5/.meteor/packages @@ -3,24 +3,28 @@ # 'meteor add' and 'meteor remove' will edit this file for you, # but you can also edit it by hand. -standard-app-packages@1.0.9 -arunoda:npm@0.2.6 -amplify -blaze@2.1.8 -francocatena:status -mizzao:timesync -clinical:nightwatch -cfs:power-queue -cfs:reactive-list -cfs:micro-queue -reactive-var@1.0.11 -ecmascript@0.9.0 +meteor-base +mobile-experience +mongo +reactive-var + +standard-minifier-css +standard-minifier-js +es5-shim +ecmascript +shell-server + +static-html react-meteor-data -standard-minifier-css@1.3.5 -standard-minifier-js@2.2.0 -nathantreid:css-modules -shell-server@0.3.0 -http@1.3.0 -dynamic-import@0.2.0 -rocketchat:streamer +http session +tracker +check +jquery + +nathantreid:css-modules@=3.1.4 +rocketchat:streamer +cfs:power-queue +cfs:micro-queue +cfs:reactive-list +stevezhu:lodash diff --git a/bigbluebutton-html5/.meteor/release b/bigbluebutton-html5/.meteor/release index 56a7a07fee76d4cf2cf564cfb7a2c134ea8f74cc..2299ae70d95537b4a520d309fe34c4324b1d3eb5 100644 --- a/bigbluebutton-html5/.meteor/release +++ b/bigbluebutton-html5/.meteor/release @@ -1 +1 @@ -METEOR@1.6.0.1 +METEOR@1.8.0.1 diff --git a/bigbluebutton-html5/.meteor/versions b/bigbluebutton-html5/.meteor/versions index 71c85b26db682da4c483714055a63bff219324d4..5b0d886693cfe6a7b19cf02aeee58470d43729fb 100644 --- a/bigbluebutton-html5/.meteor/versions +++ b/bigbluebutton-html5/.meteor/versions @@ -1,94 +1,82 @@ -aldeed:simple-schema@1.5.3 allow-deny@1.1.0 -amplify@1.0.0 -arunoda:npm@0.2.6 -autoupdate@1.3.12 -babel-compiler@6.24.7 -babel-runtime@1.1.1 -base64@1.0.10 -binary-heap@1.0.10 -blaze@2.3.2 +autoupdate@1.5.0 +babel-compiler@7.2.3 +babel-runtime@1.3.0 +base64@1.0.11 +binary-heap@1.0.11 blaze-tools@1.0.10 -boilerplate-generator@1.3.1 -caching-compiler@1.1.9 -caching-html-compiler@1.1.2 -callback-hook@1.0.10 -cfs:http-methods@0.0.32 +boilerplate-generator@1.6.0 +caching-compiler@1.2.1 +caching-html-compiler@1.1.3 +callback-hook@1.1.0 cfs:micro-queue@0.0.6 cfs:power-queue@0.9.11 cfs:reactive-list@0.0.9 cfs:reactive-property@0.0.4 -check@1.2.5 -clinical:nightwatch@2.0.1 -coffeescript@1.12.7_3 -coffeescript-compiler@1.12.7_3 +check@1.3.1 ddp@1.4.0 -ddp-client@2.2.0 -ddp-common@1.3.0 -ddp-server@2.1.1 +ddp-client@2.3.3 +ddp-common@1.4.0 +ddp-server@2.2.0 deps@1.0.12 -diff-sequence@1.0.7 -dynamic-import@0.2.1 -ecmascript@0.9.0 -ecmascript-runtime@0.5.0 -ecmascript-runtime-client@0.5.0 -ecmascript-runtime-server@0.5.0 +diff-sequence@1.1.1 +dynamic-import@0.5.0 +ecmascript@0.12.3 +ecmascript-runtime@0.7.0 +ecmascript-runtime-client@0.8.0 +ecmascript-runtime-server@0.7.1 ejson@1.1.0 -fastclick@1.0.13 -francocatena:status@1.5.3 +es5-shim@4.8.0 +fetch@0.1.0 geojson-utils@1.0.10 +hot-code-push@1.0.4 html-tools@1.0.11 htmljs@1.0.11 -http@1.3.0 -id-map@1.0.9 -jquery@1.11.10 +http@1.4.2 +id-map@1.1.0 +inter-process-messaging@0.1.0 +jquery@1.11.11 launch-screen@1.1.1 livedata@1.0.18 -logging@1.1.19 -mdg:validation-error@0.5.1 -meteor@1.8.2 -meteor-platform@1.2.6 -meteorspark:util@0.2.0 -minifier-css@1.2.16 -minifier-js@2.2.2 -minimongo@1.4.3 -mizzao:timesync@0.5.0 +logging@1.1.20 +meteor@1.9.2 +meteor-base@1.4.0 +minifier-css@1.4.1 +minifier-js@2.4.0 +minimongo@1.4.5 +mobile-experience@1.0.5 mobile-status-bar@1.0.14 -modules@0.11.2 -modules-runtime@0.9.1 -mongo@1.3.1 +modern-browsers@0.1.3 +modules@0.13.0 +modules-runtime@0.10.3 +mongo@1.6.0 +mongo-decimal@0.1.0 mongo-dev-server@1.1.0 -mongo-id@1.0.6 -nathantreid:css-modules@2.8.0 -npm-mongo@2.2.33 -observe-sequence@1.0.16 -ordered-dict@1.0.9 -promise@0.10.0 -raix:eventemitter@0.1.3 -random@1.0.10 -react-meteor-data@0.2.15 -reactive-dict@1.2.0 +mongo-id@1.0.7 +nathantreid:css-modules@3.1.4 +npm-mongo@3.1.1 +ordered-dict@1.1.0 +promise@0.11.1 +random@1.1.0 +react-meteor-data@0.2.16 +reactive-dict@1.2.1 reactive-var@1.0.11 -reload@1.1.11 -retry@1.0.9 -rocketchat:streamer@0.6.2 -routepolicy@1.0.12 -session@1.1.7 -shell-server@0.3.1 -spacebars@1.0.15 +reload@1.2.0 +retry@1.1.0 +rocketchat:streamer@1.0.1 +routepolicy@1.1.0 +session@1.2.0 +shell-server@0.4.0 +socket-stream-client@0.2.2 spacebars-compiler@1.1.3 -standard-app-packages@1.0.9 -standard-minifier-css@1.3.5 -standard-minifier-js@2.2.3 -tap:i18n@1.8.2 -templating@1.3.2 -templating-compiler@1.3.3 -templating-runtime@1.3.2 +standard-minifier-css@1.5.2 +standard-minifier-js@2.4.0 +static-html@1.2.2 +stevezhu:lodash@4.17.2 templating-tools@1.1.2 -tmeasday:check-npm-versions@0.3.1 -tracker@1.1.3 -ui@1.0.13 +tmeasday:check-npm-versions@0.3.2 +tracker@1.2.0 underscore@1.0.10 -url@1.1.0 -webapp@1.4.0 +url@1.2.0 +webapp@1.7.1 webapp-hashing@1.0.9 diff --git a/bigbluebutton-html5/client/main.jsx b/bigbluebutton-html5/client/main.jsx index c7715123a1807c91bbd5f3d4b1a1298067039157..7eea3c5c4441c8fdbd5b9795ece2fcebecd39f63 100755 --- a/bigbluebutton-html5/client/main.jsx +++ b/bigbluebutton-html5/client/main.jsx @@ -1,16 +1,13 @@ /* eslint no-unused-vars: 0 */ import React from 'react'; import { Meteor } from 'meteor/meteor'; -import { Session } from 'meteor/session'; import { render } from 'react-dom'; import logger from '/imports/startup/client/logger'; -import { joinRouteHandler, authenticatedRouteHandler } from '/imports/startup/client/auth'; import Base from '/imports/startup/client/base'; -import LoadingScreen from '/imports/ui/components/loading-screen/component'; +import JoinHandler from '/imports/ui/components/join-handler/component'; +import AuthenticatedHandler from '/imports/ui/components/authenticated-handler/component'; Meteor.startup(() => { - render(<LoadingScreen />, document.getElementById('app')); - // Logs all uncaught exceptions to the client logger window.addEventListener('error', (e) => { const { stack } = e.error; @@ -26,20 +23,12 @@ Meteor.startup(() => { }); // TODO make this a Promise - joinRouteHandler((value, error) => { - if (error) { - logger.error(`User faced [${value}] on main.joinRouteHandler. Error was:`, JSON.stringify(error)); - } else { - logger.info(`User successfully went through main.joinRouteHandler with [${value}].`); - } - authenticatedRouteHandler(() => { - // set defaults - Session.set('isChatOpen', false); - Session.set('idChatOpen', ''); - Session.set('isMeetingEnded', false); - Session.set('isPollOpen', false); - Session.get('breakoutRoomIsOpen', false); - render(<Base />, document.getElementById('app')); - }); - }); + render( + <JoinHandler > + <AuthenticatedHandler> + <Base /> + </AuthenticatedHandler> + </JoinHandler>, + document.getElementById('app'), + ); }); diff --git a/bigbluebutton-html5/imports/api/audio/client/bridge/sip.js b/bigbluebutton-html5/imports/api/audio/client/bridge/sip.js index ba2635714d3204d5e19f25a7486cc38d447032fc..2961cfd2cbcd912db87c658e6bb4acf8c85fc3b9 100755 --- a/bigbluebutton-html5/imports/api/audio/client/bridge/sip.js +++ b/bigbluebutton-html5/imports/api/audio/client/bridge/sip.js @@ -12,6 +12,7 @@ const CALL_TRANSFER_TIMEOUT = MEDIA.callTransferTimeout; const CALL_HANGUP_TIMEOUT = MEDIA.callHangupTimeout; const CALL_HANGUP_MAX_RETRIES = MEDIA.callHangupMaximumRetries; const CONNECTION_TERMINATED_EVENTS = ['iceConnectionFailed', 'iceConnectionClosed']; +const CALL_CONNECT_NOTIFICATION_TIMEOUT = 500; export default class SIPBridge extends BaseAudioBridge { constructor(userData) { @@ -275,9 +276,15 @@ export default class SIPBridge extends BaseAudioBridge { const connectionCompletedEvents = ['iceConnectionCompleted', 'iceConnectionConnected']; const handleConnectionCompleted = () => { connectionCompletedEvents.forEach(e => mediaHandler.off(e, handleConnectionCompleted)); - this.callback({ status: this.baseCallStates.started }); - this.connectionCompleted = true; - resolve(); + // We have to delay notifying that the call is connected because it is sometimes not + // actually ready and if the user says "Yes they can hear themselves" too quickly the + // B-leg transfer will fail + const that = this; + setTimeout(() => { + that.callback({ status: that.baseCallStates.started }); + that.connectionCompleted = true; + resolve(); + }, CALL_CONNECT_NOTIFICATION_TIMEOUT); }; connectionCompletedEvents.forEach(e => mediaHandler.on(e, handleConnectionCompleted)); diff --git a/bigbluebutton-html5/imports/api/meetings/server/methods.js b/bigbluebutton-html5/imports/api/meetings/server/methods.js old mode 100644 new mode 100755 index 74401c1b1544f0637d7245e0574139d6ccdf3165..960ef8082efae97b4e628dc50ec470774f70c746 --- a/bigbluebutton-html5/imports/api/meetings/server/methods.js +++ b/bigbluebutton-html5/imports/api/meetings/server/methods.js @@ -2,9 +2,13 @@ import { Meteor } from 'meteor/meteor'; import endMeeting from './methods/endMeeting'; import toggleRecording from './methods/toggleRecording'; import transferUser from './methods/transferUser'; +import toggleLockSettings from './methods/toggleLockSettings'; +import toggleWebcamsOnlyForModerator from './methods/toggleWebcamsOnlyForModerator'; Meteor.methods({ endMeeting, toggleRecording, + toggleLockSettings, transferUser, + toggleWebcamsOnlyForModerator, }); diff --git a/bigbluebutton-html5/imports/api/meetings/server/methods/toggleLockSettings.js b/bigbluebutton-html5/imports/api/meetings/server/methods/toggleLockSettings.js new file mode 100755 index 0000000000000000000000000000000000000000..ee59cf102e9ed1b70e138abff731bbe21d08edce --- /dev/null +++ b/bigbluebutton-html5/imports/api/meetings/server/methods/toggleLockSettings.js @@ -0,0 +1,37 @@ +import { Meteor } from 'meteor/meteor'; +import { check } from 'meteor/check'; +import RedisPubSub from '/imports/startup/server/redis'; + +export default function toggleLockSettings(credentials, meeting) { + const REDIS_CONFIG = Meteor.settings.private.redis; + const CHANNEL = REDIS_CONFIG.channels.toAkkaApps; + const EVENT_NAME = 'ChangeLockSettingsInMeetingCmdMsg'; + + const { meetingId, requesterUserId } = credentials; + + check(meetingId, String); + check(requesterUserId, String); + check(meeting.lockSettingsProp, { + disableCam: Boolean, + disableMic: Boolean, + disablePrivChat: Boolean, + disablePubChat: Boolean, + lockedLayout: Boolean, + lockOnJoin: Boolean, + lockOnJoinConfigurable: Boolean, + setBy: String, + }); + + const payload = { + disableCam: meeting.lockSettingsProp.disableCam, + disableMic: meeting.lockSettingsProp.disableMic, + disablePrivChat: meeting.lockSettingsProp.disablePrivChat, + disablePubChat: meeting.lockSettingsProp.disablePubChat, + lockedLayout: meeting.lockSettingsProp.lockedLayout, + lockOnJoin: meeting.lockSettingsProp.lockOnJoin, + lockOnJoinConfigurable: meeting.lockSettingsProp.lockOnJoinConfigurable, + setBy: requesterUserId, + }; + + RedisPubSub.publishUserMessage(CHANNEL, EVENT_NAME, meetingId, requesterUserId, payload); +} diff --git a/bigbluebutton-html5/imports/api/meetings/server/methods/toggleWebcamsOnlyForModerator.js b/bigbluebutton-html5/imports/api/meetings/server/methods/toggleWebcamsOnlyForModerator.js new file mode 100755 index 0000000000000000000000000000000000000000..75f052de76503bc36967c966e157d4cbcb6a063d --- /dev/null +++ b/bigbluebutton-html5/imports/api/meetings/server/methods/toggleWebcamsOnlyForModerator.js @@ -0,0 +1,22 @@ +import { Meteor } from 'meteor/meteor'; +import { check } from 'meteor/check'; +import RedisPubSub from '/imports/startup/server/redis'; + +export default function toggleWebcamsOnlyForModerator(credentials, meeting) { + const REDIS_CONFIG = Meteor.settings.private.redis; + const CHANNEL = REDIS_CONFIG.channels.toAkkaApps; + const EVENT_NAME = 'UpdateWebcamsOnlyForModeratorCmdMsg'; + + const { meetingId, requesterUserId } = credentials; + + check(meetingId, String); + check(requesterUserId, String); + check(meeting.usersProp.webcamsOnlyForModerator, Boolean); + + const payload = { + webcamsOnlyForModerator: meeting.usersProp.webcamsOnlyForModerator, + setBy: requesterUserId, + }; + + RedisPubSub.publishUserMessage(CHANNEL, EVENT_NAME, meetingId, requesterUserId, payload); +} diff --git a/bigbluebutton-html5/imports/api/meetings/server/modifiers/addMeeting.js b/bigbluebutton-html5/imports/api/meetings/server/modifiers/addMeeting.js index a8023da3626343ddc0c50c913caba9062494b8e0..f8011fd0d32225429338b6252eefd78af4307bc8 100644 --- a/bigbluebutton-html5/imports/api/meetings/server/modifiers/addMeeting.js +++ b/bigbluebutton-html5/imports/api/meetings/server/modifiers/addMeeting.js @@ -69,10 +69,22 @@ export default function addMeeting(meeting) { meetingId, }; + const lockSettingsProp = { + disableCam: false, + disableMic: false, + disablePrivChat: false, + disablePubChat: false, + lockOnJoin: true, + lockOnJoinConfigurable: false, + lockedLayout: false, + setBy: 'temp', + }; + const modifier = { $set: Object.assign( { meetingId }, flat(meeting, { safe: true }), + { lockSettingsProp }, ), }; diff --git a/bigbluebutton-html5/imports/api/presentation-pods/server/index.js b/bigbluebutton-html5/imports/api/presentation-pods/server/index.js index bcb5f35cc4d7ce08d18f527b3ecf3246bfc9b3ab..92451ac76bf27410726e8f3cd2eebac46cd7b83e 100755 --- a/bigbluebutton-html5/imports/api/presentation-pods/server/index.js +++ b/bigbluebutton-html5/imports/api/presentation-pods/server/index.js @@ -1,3 +1,3 @@ import './eventHandlers'; -// import './methods'; +import './methods'; import './publishers'; diff --git a/bigbluebutton-html5/imports/api/presentation-pods/server/methods.js b/bigbluebutton-html5/imports/api/presentation-pods/server/methods.js new file mode 100644 index 0000000000000000000000000000000000000000..968c9d28c6e08b13f4dfe5bedd3984f7485c6257 --- /dev/null +++ b/bigbluebutton-html5/imports/api/presentation-pods/server/methods.js @@ -0,0 +1,6 @@ +import { Meteor } from 'meteor/meteor'; +import setPresenterInPodReqMsg from './methods/setPresenterInPodReqMsg'; + +Meteor.methods({ + setPresenterInPodReqMsg, +}); diff --git a/bigbluebutton-html5/imports/api/presentation-pods/server/methods/setPresenterInPodReqMsg.js b/bigbluebutton-html5/imports/api/presentation-pods/server/methods/setPresenterInPodReqMsg.js new file mode 100644 index 0000000000000000000000000000000000000000..8679aa62cf75357d4aa4ad916a512edcbbecac81 --- /dev/null +++ b/bigbluebutton-html5/imports/api/presentation-pods/server/methods/setPresenterInPodReqMsg.js @@ -0,0 +1,22 @@ +import { Meteor } from 'meteor/meteor'; +import { check } from 'meteor/check'; +import RedisPubSub from '/imports/startup/server/redis'; + +export default function setPresenterInPodReqMsg(credentials) { + const REDIS_CONFIG = Meteor.settings.private.redis; + const CHANNEL = REDIS_CONFIG.channels.toAkkaApps; + const EVENT_NAME = 'SetPresenterInPodReqMsg'; + + const { meetingId, requesterUserId, presenterId } = credentials; + + check(meetingId, String); + check(requesterUserId, String); + check(presenterId, String); + + const payload = { + podId: 'DEFAULT_PRESENTATION_POD', + nextPresenterId: presenterId, + }; + + RedisPubSub.publishUserMessage(CHANNEL, EVENT_NAME, meetingId, requesterUserId, payload); +} diff --git a/bigbluebutton-html5/imports/api/slides/server/methods/zoomSlide.js b/bigbluebutton-html5/imports/api/slides/server/methods/zoomSlide.js index c546a88af87a3abf577c76a23a9ee7111ab48091..54826f9056a8038b3b10eccfaa1cfb3ec4678090 100644 --- a/bigbluebutton-html5/imports/api/slides/server/methods/zoomSlide.js +++ b/bigbluebutton-html5/imports/api/slides/server/methods/zoomSlide.js @@ -4,7 +4,7 @@ import { Meteor } from 'meteor/meteor'; import { check } from 'meteor/check'; import RedisPubSub from '/imports/startup/server/redis'; -export default function switchSlide(credentials, slideNumber, podId, widthRatio, heightRatio, x, y) { +export default function zoomSlide(credentials, slideNumber, podId, widthRatio, heightRatio, x, y) { const REDIS_CONFIG = Meteor.settings.private.redis; const CHANNEL = REDIS_CONFIG.channels.toAkkaApps; diff --git a/bigbluebutton-html5/imports/api/users-settings/server/methods/addUserSettings.js b/bigbluebutton-html5/imports/api/users-settings/server/methods/addUserSettings.js index 97c79f89db793c1c1b78a35fd43f485f740916c6..4310a9fe0b0ab6c10a15f3b056b6879ed20f5e4d 100644 --- a/bigbluebutton-html5/imports/api/users-settings/server/methods/addUserSettings.js +++ b/bigbluebutton-html5/imports/api/users-settings/server/methods/addUserSettings.js @@ -38,6 +38,9 @@ export default function addUserSettings(credentials, meetingId, userId, settings // LAYOUT 'autoSwapLayout', 'hidePresentation', + // OUTSIDE COMMANDS + 'outsideToggleSelfVoice', + 'outsideToggleRecording', ]; if (!handledHTML5Parameters.includes(key)) { return acc; diff --git a/bigbluebutton-html5/imports/api/users/server/handlers/presenterAssigned.js b/bigbluebutton-html5/imports/api/users/server/handlers/presenterAssigned.js index 105772c23263026b5baed5bd935bf5d74512e3f9..2c36b274a01bef4b04729e60028cf15c78076446 100644 --- a/bigbluebutton-html5/imports/api/users/server/handlers/presenterAssigned.js +++ b/bigbluebutton-html5/imports/api/users/server/handlers/presenterAssigned.js @@ -1,5 +1,7 @@ import Users from '/imports/api/users'; +import PresentationPods from '/imports/api/presentation-pods'; import changeRole from '/imports/api/users/server/modifiers/changeRole'; +import setPresenterInPodReqMsg from '/imports/api/presentation-pods/server/methods/setPresenterInPodReqMsg'; export default function handlePresenterAssigned({ body }, meetingId) { const USER_CONFIG = Meteor.settings.public.user; @@ -18,7 +20,32 @@ export default function handlePresenterAssigned({ body }, meetingId) { const prevPresenter = Users.findOne(selector); // no previous presenters + // The below code is responsible for set Meeting presenter to be default pod presenter as well. + // It's been handled here because right now akka-apps don't handle all cases scenarios. if (!prevPresenter) { + const setPresenterPayload = { + meetingId, + requesterUserId: assignedBy, + presenterId, + }; + + const defaultPodSelector = { + meetingId, + podId: 'DEFAULT_PRESENTATION_POD', + }; + const currentDefaultPodPresenter = PresentationPods.findOne(defaultPodSelector); + const { currentPresenterId } = currentDefaultPodPresenter; + + if (currentPresenterId === '') { + return setPresenterInPodReqMsg(setPresenterPayload); + } + + const oldPresenter = Users.findOne({ meetingId, userId: currentPresenterId, connectionStatus: 'offline' }); + + if (oldPresenter) { + return setPresenterInPodReqMsg(setPresenterPayload); + } + return true; } diff --git a/bigbluebutton-html5/imports/api/voice-users/server/eventHandlers.js b/bigbluebutton-html5/imports/api/voice-users/server/eventHandlers.js index 10481cda4f1914179945930e6cc9c04e7c330178..6fc5a6dcdefc83becd0c09ed16454aad2f99ec59 100755 --- a/bigbluebutton-html5/imports/api/voice-users/server/eventHandlers.js +++ b/bigbluebutton-html5/imports/api/voice-users/server/eventHandlers.js @@ -6,6 +6,7 @@ import handleTalkingVoiceUser from './handlers/talkingVoiceUser'; import handleMutedVoiceUser from './handlers/mutedVoiceUser'; import handleGetVoiceUsers from './handlers/getVoiceUsers'; import handleVoiceUsers from './handlers/voiceUsers'; +import handleMeetingMuted from './handlers/meetingMuted'; RedisPubSub.on('UserLeftVoiceConfToClientEvtMsg', handleLeftVoiceUser); RedisPubSub.on('UserJoinedVoiceConfToClientEvtMsg', handleJoinVoiceUser); @@ -13,3 +14,4 @@ RedisPubSub.on('UserTalkingVoiceEvtMsg', handleTalkingVoiceUser); RedisPubSub.on('UserMutedVoiceEvtMsg', handleMutedVoiceUser); RedisPubSub.on('GetVoiceUsersMeetingRespMsg', processForHTML5ServerOnly(handleGetVoiceUsers)); RedisPubSub.on('SyncGetVoiceUsersRespMsg', handleVoiceUsers); +RedisPubSub.on('MeetingMutedEvtMsg', handleMeetingMuted); diff --git a/bigbluebutton-html5/imports/api/voice-users/server/handlers/meetingMuted.js b/bigbluebutton-html5/imports/api/voice-users/server/handlers/meetingMuted.js new file mode 100755 index 0000000000000000000000000000000000000000..9d14a1dc8bc2ae6e9722f5d4a6a2dfcefb5be146 --- /dev/null +++ b/bigbluebutton-html5/imports/api/voice-users/server/handlers/meetingMuted.js @@ -0,0 +1,5 @@ +import changeMuteMeeting from '../modifiers/changeMuteMeeting'; + +export default function handleMeetingMuted({ body }, meetingId) { + return changeMuteMeeting(meetingId, body); +} diff --git a/bigbluebutton-html5/imports/api/voice-users/server/methods.js b/bigbluebutton-html5/imports/api/voice-users/server/methods.js old mode 100644 new mode 100755 index f423373b82f11ecb054b7222d7fbd849db5f0bc4..975770e43fdadd32ae7ffd64dc5a8c3143162ebe --- a/bigbluebutton-html5/imports/api/voice-users/server/methods.js +++ b/bigbluebutton-html5/imports/api/voice-users/server/methods.js @@ -1,11 +1,15 @@ import { Meteor } from 'meteor/meteor'; import listenOnlyToggle from './methods/listenOnlyToggle'; import muteToggle from './methods/muteToggle'; +import muteAllToggle from './methods/muteAllToggle'; +import muteAllExceptPresenterToggle from './methods/muteAllExceptPresenterToggle'; import ejectUserFromVoice from './methods/ejectUserFromVoice'; Meteor.methods({ listenOnlyToggle, toggleSelfVoice: (credentials) => { muteToggle(credentials, credentials.requesterUserId); }, toggleVoice: muteToggle, + muteAllUsers: muteAllToggle, + muteAllExceptPresenter: muteAllExceptPresenterToggle, ejectUserFromVoice, }); diff --git a/bigbluebutton-html5/imports/api/voice-users/server/methods/muteAllExceptPresenterToggle.js b/bigbluebutton-html5/imports/api/voice-users/server/methods/muteAllExceptPresenterToggle.js new file mode 100755 index 0000000000000000000000000000000000000000..db73c7df61fd4c4dc6631c2fdd22a0bbcd7cdb78 --- /dev/null +++ b/bigbluebutton-html5/imports/api/voice-users/server/methods/muteAllExceptPresenterToggle.js @@ -0,0 +1,24 @@ +import { Meteor } from 'meteor/meteor'; +import { check } from 'meteor/check'; +import RedisPubSub from '/imports/startup/server/redis'; +import Meetings from '/imports/api/meetings'; + +export default function muteAllExceptPresenterToggle(credentials) { + const REDIS_CONFIG = Meteor.settings.private.redis; + const CHANNEL = REDIS_CONFIG.channels.toAkkaApps; + const EVENT_NAME = 'MuteAllExceptPresentersCmdMsg'; + + const { meetingId, requesterUserId } = credentials; + + check(meetingId, String); + check(requesterUserId, String); + const meeting = Meetings.findOne({ meetingId }); + const toggleMeetingMuted = !meeting.voiceProp.muteOnStart; + + const payload = { + mutedBy: requesterUserId, + mute: toggleMeetingMuted, + }; + + RedisPubSub.publishUserMessage(CHANNEL, EVENT_NAME, meetingId, requesterUserId, payload); +} diff --git a/bigbluebutton-html5/imports/api/voice-users/server/methods/muteAllToggle.js b/bigbluebutton-html5/imports/api/voice-users/server/methods/muteAllToggle.js new file mode 100755 index 0000000000000000000000000000000000000000..2d142dabef3b20539ceb683c762abc28fbe3867d --- /dev/null +++ b/bigbluebutton-html5/imports/api/voice-users/server/methods/muteAllToggle.js @@ -0,0 +1,24 @@ +import { Meteor } from 'meteor/meteor'; +import { check } from 'meteor/check'; +import RedisPubSub from '/imports/startup/server/redis'; +import Meetings from '/imports/api/meetings'; + +export default function muteAllToggle(credentials) { + const REDIS_CONFIG = Meteor.settings.private.redis; + const CHANNEL = REDIS_CONFIG.channels.toAkkaApps; + const EVENT_NAME = 'MuteMeetingCmdMsg'; + + const { meetingId, requesterUserId } = credentials; + + check(meetingId, String); + check(requesterUserId, String); + const meeting = Meetings.findOne({ meetingId }); + const toggleMeetingMuted = !meeting.voiceProp.muteOnStart; + + const payload = { + mutedBy: requesterUserId, + mute: toggleMeetingMuted, + }; + + RedisPubSub.publishUserMessage(CHANNEL, EVENT_NAME, meetingId, requesterUserId, payload); +} diff --git a/bigbluebutton-html5/imports/api/voice-users/server/methods/muteToggle.js b/bigbluebutton-html5/imports/api/voice-users/server/methods/muteToggle.js index a5377f177326825bc6c7c38fa038575aeebc7936..be28a30199fa02f69b55a5534a9ca607f1a542f8 100644 --- a/bigbluebutton-html5/imports/api/voice-users/server/methods/muteToggle.js +++ b/bigbluebutton-html5/imports/api/voice-users/server/methods/muteToggle.js @@ -8,10 +8,6 @@ export default function muteToggle(credentials, userId) { const REDIS_CONFIG = Meteor.settings.private.redis; const CHANNEL = REDIS_CONFIG.channels.toAkkaApps; const EVENT_NAME = 'MuteUserCmdMsg'; - const APP_CONFIG = Meteor.settings.public.app; - const ALLOW_MODERATOR_TO_UNMUTE_AUDIO = APP_CONFIG.allowModeratorToUnmuteAudio; - const USER_CONFIG = Meteor.settings.public.user; - const ROLE_MODERATOR = USER_CONFIG.role_moderator; const { meetingId, requesterUserId } = credentials; @@ -32,15 +28,6 @@ export default function muteToggle(credentials, userId) { const { listenOnly, muted } = voiceUser; if (listenOnly) return; - const isModerator = requester.roles.includes(ROLE_MODERATOR.toLowerCase()); - const isNotHimself = requesterUserId !== userId; - - // the ability for a moderator to unmute other users is configurable (on/off) - if (!ALLOW_MODERATOR_TO_UNMUTE_AUDIO && - isModerator && - muted && - isNotHimself) return; - const payload = { userId, mutedBy: requesterUserId, diff --git a/bigbluebutton-html5/imports/api/voice-users/server/modifiers/changeMuteMeeting.js b/bigbluebutton-html5/imports/api/voice-users/server/modifiers/changeMuteMeeting.js new file mode 100755 index 0000000000000000000000000000000000000000..63995e242945ce46c92b2f2706e8c5274dc1dea2 --- /dev/null +++ b/bigbluebutton-html5/imports/api/voice-users/server/modifiers/changeMuteMeeting.js @@ -0,0 +1,35 @@ +import Meetings from '/imports/api/meetings'; +import { check } from 'meteor/check'; +import Logger from '/imports/startup/server/logger'; + +export default function changeMuteMeeting(meetingId, payload) { + check(meetingId, String); + check(payload, { + muted: Boolean, + mutedBy: String, + }); + + const selector = { + meetingId, + }; + + const modifier = { + $set: { + 'voiceProp.muteOnStart': payload.muted, + }, + }; + + const cb = (err, numChanged) => { + if (err) { + Logger.error(`Changing meeting mute status meeting={${meetingId}} ${err}`); + return; + } + + if (numChanged) { + Logger.info(`Changed meeting mute status meeting=${meetingId}`); + } + }; + + + return Meetings.upsert(selector, modifier, cb); +} diff --git a/bigbluebutton-html5/imports/startup/client/auth.js b/bigbluebutton-html5/imports/startup/client/auth.js deleted file mode 100755 index 6f50e762c62d406821a8d7f65d603220c1b58bef..0000000000000000000000000000000000000000 --- a/bigbluebutton-html5/imports/startup/client/auth.js +++ /dev/null @@ -1,134 +0,0 @@ -import Auth from '/imports/ui/services/auth'; -import { setCustomLogoUrl } from '/imports/ui/components/user-list/service'; -import { log, makeCall } from '/imports/ui/services/api'; -import deviceInfo from '/imports/utils/deviceInfo'; -import logger from '/imports/startup/client/logger'; -import { Session } from 'meteor/session'; - -// disconnected and trying to open a new connection -const STATUS_CONNECTING = 'connecting'; - -const setError = (errorCode) => { - Session.set('hasError', true); - Session.set('codeError', errorCode); -}; - -export function joinRouteHandler(callback) { - const urlParams = new URLSearchParams(window.location.search); - const sessionToken = urlParams.get('sessionToken'); - - if (!sessionToken) { - setError('404'); - callback('failed - no sessionToken', urlParams); - } - - // Old credentials stored in memory were being used when joining a new meeting - Auth.clearCredentials(); - - // use enter api to get params for the client - const url = `/bigbluebutton/api/enter?sessionToken=${sessionToken}`; - - fetch(url, { credentials: 'same-origin' }) - .then(response => response.json()) - .then(({ response }) => { - const { - returncode, meetingID, internalUserID, authToken, logoutUrl, customLogoURL, - externUserID, fullname, confname, customdata, - } = response; - - if (returncode === 'FAILED') { - setError('404'); - callback('failed during enter API call', response); - } else { - setCustomLogoUrl(customLogoURL); - - if (customdata.length) { - makeCall('addUserSettings', meetingID, internalUserID, customdata); - } - - Auth.set( - meetingID, internalUserID, authToken, logoutUrl, - sessionToken, fullname, externUserID, confname, - ); - - Session.set('isUserListOpen', deviceInfo.type().isPhone); - const userInfo = window.navigator; - - // Browser information is sent once on startup - // Sent here instead of Meteor.startup, as the - // user might not be validated by then, thus user's data - // would not be sent with this information - const clientInfo = { - language: userInfo.language, - userAgent: userInfo.userAgent, - screenSize: { width: window.screen.width, height: window.screen.height }, - windowSize: { width: window.innerWidth, height: window.innerHeight }, - bbbVersion: Meteor.settings.public.app.bbbServerVersion, - location: window.location.href, - }; - - logger.info(clientInfo); - - callback('all is good', null); - } - }); -} - -export function logoutRouteHandler() { - Auth.logout() - .then((logoutURL = window.location.origin) => { - const protocolPattern = /^((http|https):\/\/)/; - - window.location.href = - protocolPattern.test(logoutURL) ? - logoutURL : - `http://${logoutURL}`; - }); -} - -/** - * Check if should revalidate the auth - * @param {Object} status - * @param {String} lastStatus - */ -export function shouldAuthenticate(status, lastStatus) { - return lastStatus != null && lastStatus === STATUS_CONNECTING && status.connected; -} - -/** - * Check if the isn't the first connection try, preventing to authenticate on login. - * @param {Object} status - * @param {string} lastStatus - */ -export function updateStatus(status, lastStatus) { - return status.retryCount > 0 && lastStatus !== STATUS_CONNECTING ? status.status : lastStatus; -} - -function _addReconnectObservable() { - let lastStatus = null; - - Tracker.autorun(() => { - lastStatus = updateStatus(Meteor.status(), lastStatus); - - if (shouldAuthenticate(Meteor.status(), lastStatus)) { - Auth.authenticate(true); - lastStatus = Meteor.status().status; - } - }); -} - -export function authenticatedRouteHandler(callback) { - if (Auth.loggedIn) { - callback(); - } - - _addReconnectObservable(); - - Auth.authenticate() - .then(callback) - .catch((reason) => { - log('error', reason); - setError(reason.error); - callback(); - }); -} diff --git a/bigbluebutton-html5/imports/startup/client/base.jsx b/bigbluebutton-html5/imports/startup/client/base.jsx index 182386c1e6340e3a549542c6213e4d32c4408e71..4428471e9d3bafb1363607e4d010cd155f0ae3c4 100755 --- a/bigbluebutton-html5/imports/startup/client/base.jsx +++ b/bigbluebutton-html5/imports/startup/client/base.jsx @@ -83,7 +83,7 @@ class Base extends Component { // this.props.annotationsHandler.stop(); if (subscriptionsReady) { - logger.info('Client loaded successfully'); + logger.info('Subscriptions are ready'); } return (<AppContainer {...this.props} baseControls={stateControls} />); @@ -131,8 +131,7 @@ const BaseContainer = withTracker(() => { }, }; - const subscriptionsHandlers = SUBSCRIPTIONS_NAME.map(name => - Meteor.subscribe(name, credentials, subscriptionErrorHandler)); + const subscriptionsHandlers = SUBSCRIPTIONS_NAME.map(name => Meteor.subscribe(name, credentials, subscriptionErrorHandler)); const chats = GroupChat.find({ $or: [ diff --git a/bigbluebutton-html5/imports/startup/client/intl.jsx b/bigbluebutton-html5/imports/startup/client/intl.jsx index 3602f33a625a184b04068b8f11fbb8c8d49c2d0f..9ce3417c36a7704b78f501fd5b67e1007e13ce72 100644 --- a/bigbluebutton-html5/imports/startup/client/intl.jsx +++ b/bigbluebutton-html5/imports/startup/client/intl.jsx @@ -27,6 +27,7 @@ class IntlStartup extends Component { this.fetchLocalizedMessages = this.fetchLocalizedMessages.bind(this); } + componentWillMount() { this.fetchLocalizedMessages(this.props.locale); } @@ -69,7 +70,7 @@ class IntlStartup extends Component { render() { return this.state.fetching ? <LoadingScreen /> : ( - <IntlProvider locale={this.state.normalizedLocale} messages={this.state.messages}> + <IntlProvider locale={DEFAULT_LANGUAGE} messages={this.state.messages}> {this.props.children} </IntlProvider> ); diff --git a/bigbluebutton-html5/imports/startup/server/logger.js b/bigbluebutton-html5/imports/startup/server/logger.js old mode 100755 new mode 100644 index e9eacdd43fe8fdf5ad7697befcedc016ad245d80..a394ae512f7451b60debca41b6e0aa2e2dfba33b --- a/bigbluebutton-html5/imports/startup/server/logger.js +++ b/bigbluebutton-html5/imports/startup/server/logger.js @@ -1,19 +1,12 @@ import { Meteor } from 'meteor/meteor'; -import Winston from 'winston'; +import { createLogger, format, transports } from 'winston'; -const Logger = new Winston.Logger(); - -Logger.configure({ - levels: { - error: 0, warn: 1, info: 2, verbose: 3, debug: 4, - }, - colors: { - error: 'red', - warn: 'yellow', - info: 'green', - verbose: 'cyan', - debug: 'magenta', - }, +const Logger = createLogger({ + format: format.combine( + format.colorize({ level: true }), + format.splat(), + format.simple(), + ), }); Meteor.startup(() => { @@ -21,7 +14,7 @@ Meteor.startup(() => { const { level } = LOG_CONFIG; // console logging - Logger.add(Winston.transports.Console, { + Logger.add(new transports.Console(), { prettyPrint: false, humanReadableUnhandledException: true, colorize: true, diff --git a/bigbluebutton-html5/imports/ui/components/actions-bar/actions-dropdown/component.jsx b/bigbluebutton-html5/imports/ui/components/actions-bar/actions-dropdown/component.jsx index da2a4b24a63da9233bd5b8f8bc30c058e9be4ca1..0676065f445719c327c30932904a8d029895d2da 100755 --- a/bigbluebutton-html5/imports/ui/components/actions-bar/actions-dropdown/component.jsx +++ b/bigbluebutton-html5/imports/ui/components/actions-bar/actions-dropdown/component.jsx @@ -1,3 +1,4 @@ +import _ from 'lodash'; import React, { Component } from 'react'; import PropTypes from 'prop-types'; import { defineMessages, injectIntl, intlShape } from 'react-intl'; @@ -9,9 +10,11 @@ import DropdownList from '/imports/ui/components/dropdown/list/component'; import DropdownListItem from '/imports/ui/components/dropdown/list/item/component'; import PresentationUploaderContainer from '/imports/ui/components/presentation/presentation-uploader/container'; import { withModalMounter } from '/imports/ui/components/modal/service'; +import getFromUserSettings from '/imports/ui/services/users-settings'; import withShortcutHelper from '/imports/ui/components/shortcut-help/service'; import BreakoutRoom from '../create-breakout-room/component'; import { styles } from '../styles'; +import ActionBarService from '../service'; const propTypes = { isUserPresenter: PropTypes.bool.isRequired, @@ -83,12 +86,19 @@ class ActionsDropdown extends Component { componentWillMount() { this.presentationItemId = _.uniqueId('action-item-'); - this.videoItemId = _.uniqueId('action-item-'); this.recordId = _.uniqueId('action-item-'); this.pollId = _.uniqueId('action-item-'); this.createBreakoutRoomId = _.uniqueId('action-item-'); } + componentDidMount() { + if (Meteor.settings.public.allowOutsideCommands.toggleRecording + || getFromUserSettings('outsideToggleRecording', false)) { + ActionBarService.connectRecordingObserver(); + window.addEventListener('message', ActionBarService.processOutsideToggleRecording); + } + } + componentWillUpdate(nextProps) { const { isUserPresenter: isPresenter } = nextProps; const { isUserPresenter: wasPresenter, mountModal } = this.props; @@ -112,43 +122,52 @@ class ActionsDropdown extends Component { } = this.props; return _.compact([ - (isUserPresenter ? - <DropdownListItem - icon="user" - label={intl.formatMessage(intlMessages.pollBtnLabel)} - description={intl.formatMessage(intlMessages.pollBtnDesc)} - key={this.pollId} - onClick={() => togglePollMenu()} - /> + (isUserPresenter + ? ( + <DropdownListItem + icon="user" + label={intl.formatMessage(intlMessages.pollBtnLabel)} + description={intl.formatMessage(intlMessages.pollBtnDesc)} + key={this.pollId} + onClick={() => togglePollMenu()} + /> + ) : null), - (isUserPresenter ? - <DropdownListItem - icon="presentation" - label={intl.formatMessage(intlMessages.presentationLabel)} - description={intl.formatMessage(intlMessages.presentationDesc)} - key={this.presentationItemId} - onClick={this.handlePresentationClick} - /> + (isUserPresenter + ? ( + <DropdownListItem + data-test="uploadPresentation" + icon="presentation" + label={intl.formatMessage(intlMessages.presentationLabel)} + description={intl.formatMessage(intlMessages.presentationDesc)} + key={this.presentationItemId} + onClick={this.handlePresentationClick} + /> + ) : null), - (record && isUserModerator && allowStartStopRecording ? - <DropdownListItem - icon="record" - label={intl.formatMessage(isRecording ? - intlMessages.stopRecording : intlMessages.startRecording)} - description={intl.formatMessage(isRecording ? - intlMessages.stopRecording : intlMessages.startRecording)} - key={this.recordId} - onClick={toggleRecording} - /> + (record && isUserModerator && allowStartStopRecording + ? ( + <DropdownListItem + icon="record" + label={intl.formatMessage(isRecording + ? intlMessages.stopRecording : intlMessages.startRecording)} + description={intl.formatMessage(isRecording + ? intlMessages.stopRecording : intlMessages.startRecording)} + key={this.recordId} + onClick={toggleRecording} + /> + ) : null), - (isUserModerator && !meetingIsBreakout && !hasBreakoutRoom ? - <DropdownListItem - icon="rooms" - label={intl.formatMessage(intlMessages.createBreakoutRoom)} - description={intl.formatMessage(intlMessages.createBreakoutRoomDesc)} - key={this.createBreakoutRoomId} - onClick={this.handleCreateBreakoutRoomClick} - /> + (isUserModerator && !meetingIsBreakout && !hasBreakoutRoom + ? ( + <DropdownListItem + icon="rooms" + label={intl.formatMessage(intlMessages.createBreakoutRoom)} + description={intl.formatMessage(intlMessages.createBreakoutRoomDesc)} + key={this.createBreakoutRoomId} + onClick={this.handleCreateBreakoutRoomClick} + /> + ) : null), ]); } @@ -156,6 +175,7 @@ class ActionsDropdown extends Component { handlePresentationClick() { this.props.mountModal(<PresentationUploaderContainer />); } + handleCreateBreakoutRoomClick() { const { createBreakoutRoom, @@ -185,7 +205,7 @@ class ActionsDropdown extends Component { if ((!isUserPresenter && !isUserModerator) || availableActions.length === 0) return null; return ( - <Dropdown ref={(ref) => { this._dropdown = ref; }} > + <Dropdown ref={(ref) => { this._dropdown = ref; }}> <DropdownTrigger tabIndex={0} accessKey={OPEN_ACTIONS_AK}> <Button hideLabel diff --git a/bigbluebutton-html5/imports/ui/components/actions-bar/container.jsx b/bigbluebutton-html5/imports/ui/components/actions-bar/container.jsx index 787849ba8c1b75a2183e60034b52df7dabd91fda..ea5d7d6897023a9d1bfaa90b16e24c1a75615ec8 100644 --- a/bigbluebutton-html5/imports/ui/components/actions-bar/container.jsx +++ b/bigbluebutton-html5/imports/ui/components/actions-bar/container.jsx @@ -2,6 +2,8 @@ import React from 'react'; import { withTracker } from 'meteor/react-meteor-data'; import { Session } from 'meteor/session'; import getFromUserSettings from '/imports/ui/services/users-settings'; +import Meetings from '/imports/api/meetings'; +import Auth from '/imports/ui/services/auth'; import ActionsBar from './component'; import Service from './service'; import VideoService from '../video-provider/service'; @@ -9,7 +11,7 @@ import { shareScreen, unshareScreen, isVideoBroadcasting } from '../screenshare/ const ActionsBarContainer = props => <ActionsBar {...props} />; -export default withTracker(({ }) => { +export default withTracker(() => { const togglePollMenu = () => { const showPoll = Session.equals('isPollOpen', false) || !Session.get('isPollOpen'); @@ -27,6 +29,18 @@ export default withTracker(({ }) => { return showPoll ? show() : hide(); }; + Meetings.find({ meetingId: Auth.meetingID }).observeChanges({ + changed: (id, fields) => { + if (fields.recordProp && fields.recordProp.recording) { + this.window.parent.postMessage({ response: 'recordingStarted' }, '*'); + } + + if (fields.recordProp && !fields.recordProp.recording) { + this.window.parent.postMessage({ response: 'recordingStopped' }, '*'); + } + }, + }); + return { isUserPresenter: Service.isUserPresenter(), isUserModerator: Service.isUserModerator(), diff --git a/bigbluebutton-html5/imports/ui/components/actions-bar/create-breakout-room/component.jsx b/bigbluebutton-html5/imports/ui/components/actions-bar/create-breakout-room/component.jsx index 1abd9e197c88b96ac48f990e740bc1de4fdfee57..c46eb0dda1446a9618dfa3848f02410eb2c4f8b9 100644 --- a/bigbluebutton-html5/imports/ui/components/actions-bar/create-breakout-room/component.jsx +++ b/bigbluebutton-html5/imports/ui/components/actions-bar/create-breakout-room/component.jsx @@ -1,10 +1,15 @@ import React, { Component } from 'react'; -import Modal from '/imports/ui/components/modal/fullscreen/component'; import { defineMessages, injectIntl } from 'react-intl'; import _ from 'lodash'; import cx from 'classnames'; +<<<<<<< HEAD import browser from 'browser-detect'; import Button from '/imports/ui/components/button/component'; +======= +import { Session } from 'meteor/session'; +import Modal from '/imports/ui/components/modal/fullscreen/component'; +import { withModalMounter } from '/imports/ui/components/modal/service'; +>>>>>>> upstream/master import HoldButton from '/imports/ui/components/presentation/presentation-toolbar/zoom-tool/holdButton/component'; import SortList from './sort-user-list/component'; import { styles } from './styles'; @@ -23,6 +28,10 @@ const intlMessages = defineMessages({ id: 'app.createBreakoutRoom.confirm', description: 'confirm button label', }, + dismissLabel: { + id: 'app.presentationUploder.dismissLabel', + description: 'used in the button that close modal', + }, numberOfRooms: { id: 'app.createBreakoutRoom.numberOfRooms', description: 'number of rooms label', @@ -47,6 +56,10 @@ const intlMessages = defineMessages({ id: 'app.createBreakoutRoom.room', description: 'Room label', }, + leastOneWarnBreakout: { + id: 'app.createBreakoutRoom.leastOneWarnBreakout', + description: 'warn message label', + }, notAssigned: { id: 'app.createBreakoutRoom.notAssigned', description: 'Not assigned label', @@ -87,11 +100,15 @@ class BreakoutRoom extends Component { this.renderRoomsGrid = this.renderRoomsGrid.bind(this); this.renderBreakoutForm = this.renderBreakoutForm.bind(this); this.renderFreeJoinCheck = this.renderFreeJoinCheck.bind(this); +<<<<<<< HEAD this.renderRoomSortList = this.renderRoomSortList.bind(this); this.renderDesktop = this.renderDesktop.bind(this); this.renderMobile = this.renderMobile.bind(this); this.renderButtonSetLevel = this.renderButtonSetLevel.bind(this); this.renderSelectUserScreen = this.renderSelectUserScreen.bind(this); +======= + this.handleDismiss = this.handleDismiss.bind(this); +>>>>>>> upstream/master this.state = { numberOfRooms: MIN_BREAKOUT_ROOMS, @@ -99,8 +116,13 @@ class BreakoutRoom extends Component { users: [], durationTime: 1, freeJoin: false, +<<<<<<< HEAD formFillLevel: 1, roomSelected: 0, +======= + preventClosing: true, + valid: true, +>>>>>>> upstream/master }; } @@ -122,6 +144,11 @@ class BreakoutRoom extends Component { intl, } = this.props; + if (this.state.users.length === this.getUserByRoom(0).length) { + this.setState({ valid: false }); + return; + } + this.setState({ preventClosing: false }); const { numberOfRooms, durationTime } = this.state; const rooms = _.range(1, numberOfRooms + 1).map(value => ({ users: this.getUserByRoom(value).map(u => u.userId), @@ -134,6 +161,7 @@ class BreakoutRoom extends Component { })); createBreakoutRoom(rooms, durationTime, this.state.freeJoin); + Session.set('isUserListOpen', true); } setRoomUsers() { @@ -157,6 +185,18 @@ class BreakoutRoom extends Component { return this.state.users.filter(user => user.room === room); } + handleDismiss() { + const { mountModal } = this.props; + + return new Promise((resolve) => { + mountModal(null); + + this.setState({ + preventClosing: false, + }, resolve); + }); + } + resetUserWhenRoomsChange(rooms) { const { users } = this.state; const filtredUsers = users.filter(u => u.room > rooms); @@ -203,7 +243,7 @@ class BreakoutRoom extends Component { return ( <div className={styles.boxContainer}> - <label htmlFor="BreakoutRoom"> + <label htmlFor="BreakoutRoom" className={!this.state.valid ? styles.changeToWarn : null}> <p className={styles.freeJoinLabel} > @@ -212,11 +252,14 @@ class BreakoutRoom extends Component { <div className={styles.breakoutBox} onDrop={drop(0)} onDragOver={allowDrop} > {this.renderUserItemByRoom(0)} </div> + <span className={this.state.valid ? styles.dontShow : styles.leastOneWarn} > + {intl.formatMessage(intlMessages.leastOneWarnBreakout)} + </span> </label> { _.range(1, this.state.numberOfRooms + 1).map(value => ( - <label htmlFor="BreakoutRoom"> + <label htmlFor="BreakoutRoom" key={`room-${value}`}> <p className={styles.freeJoinLabel} > @@ -320,6 +363,10 @@ class BreakoutRoom extends Component { const dragStart = (ev) => { ev.dataTransfer.setData('text', ev.target.id); this.setState({ seletedId: ev.target.id }); + + if (!this.state.valid) { + this.setState({ valid: true }); + } }; @@ -331,6 +378,7 @@ class BreakoutRoom extends Component { .map(user => ( <p id={user.userId} + key={user.userId} className={cx( styles.roomUserItem, this.state.seletedId === user.userId ? styles.selectedItem : null, @@ -425,6 +473,11 @@ class BreakoutRoom extends Component { callback: this.onCreateBreakouts, } } + dismiss={{ + callback: this.handleDismiss, + label: intl.formatMessage(intlMessages.dismissLabel), + }} + preventClosing={this.state.preventClosing} > <div className={styles.content}> <p className={styles.subTitle}> @@ -438,4 +491,4 @@ class BreakoutRoom extends Component { } } -export default injectIntl(BreakoutRoom); +export default withModalMounter(injectIntl(BreakoutRoom)); diff --git a/bigbluebutton-html5/imports/ui/components/actions-bar/create-breakout-room/styles.scss b/bigbluebutton-html5/imports/ui/components/actions-bar/create-breakout-room/styles.scss index da72c0b6bb54bc81ed33c7cbb00f20743d306144..328fb12ee87b2f6a64c1aa0b6eed44bf97816077 100644 --- a/bigbluebutton-html5/imports/ui/components/actions-bar/create-breakout-room/styles.scss +++ b/bigbluebutton-html5/imports/ui/components/actions-bar/create-breakout-room/styles.scss @@ -98,14 +98,25 @@ input[type="number"]::-webkit-outer-spin-button, input[type="number"]::-webkit-i display: grid; grid-template-columns: 1fr 1fr 1fr; grid-template-rows: 33% 33% 33%; - grid-gap: 1rem; + grid-gap: 1.5rem 1rem; +} + +.changeToWarn { + position: relative; + & > .breakoutBox { + border-color: var(--color-danger) !important; + } + + & > .freeJoinLabel { + color: var(--color-danger); + } } .breakoutBox { @include scrollbox-vertical(); width: 100%; height: 80%; - min-height: 5rem; + min-height: 4rem; max-height: 8rem; border: 1px solid var(--color-gray-lighter); border-radius: var(--border-radius); @@ -116,6 +127,15 @@ input[type="number"]::-webkit-outer-spin-button, input[type="number"]::-webkit-i font-weight: bolder; } +.leastOneWarn { + margin: .25rem; + position: absolute; + font-size: var(--font-size-small); + color: var(--color-danger); + font-weight: 200; + white-space: nowrap; +} + .roomUserItem { width: 11rem; margin: 0; @@ -134,6 +154,7 @@ input[type="number"]::-webkit-outer-spin-button, input[type="number"]::-webkit-i color: var(--color-white) } +<<<<<<< HEAD /* mobile */ .listContainer { @@ -253,4 +274,8 @@ input[type="number"]::-webkit-outer-spin-button, input[type="number"]::-webkit-i & input[type="checkbox"]:checked + label:after { opacity: 1; } +======= +.dontShow { + display: none; +>>>>>>> upstream/master } \ No newline at end of file diff --git a/bigbluebutton-html5/imports/ui/components/actions-bar/desktop-share/component.jsx b/bigbluebutton-html5/imports/ui/components/actions-bar/desktop-share/component.jsx index 4eda188074daf3e13072d23567358e262d838ed4..1a2ce6c29e26c8117a76fd09ba073bdadc06c6d1 100755 --- a/bigbluebutton-html5/imports/ui/components/actions-bar/desktop-share/component.jsx +++ b/bigbluebutton-html5/imports/ui/components/actions-bar/desktop-share/component.jsx @@ -5,6 +5,7 @@ import browser from 'browser-detect'; import Button from '/imports/ui/components/button/component'; import logger from '/imports/startup/client/logger'; import { notify } from '/imports/ui/services/notification'; +import cx from 'classnames'; import { styles } from '../styles'; const propTypes = { @@ -68,14 +69,14 @@ const DesktopShare = ({ }; return (screenSharingCheck && !isMobileBrowser && isUserPresenter ? <Button - className={styles.button} - icon={isVideoBroadcasting ? 'desktop_off' : 'desktop'} + className={cx(styles.button, isVideoBroadcasting || styles.btn)} + icon={isVideoBroadcasting ? 'desktop' : 'desktop_off'} label={intl.formatMessage(isVideoBroadcasting ? intlMessages.stopDesktopShareLabel : intlMessages.desktopShareLabel)} description={intl.formatMessage(isVideoBroadcasting ? intlMessages.stopDesktopShareDesc : intlMessages.desktopShareDesc)} - color={isVideoBroadcasting ? 'danger' : 'primary'} - ghost={false} + color={isVideoBroadcasting ? 'primary' : 'default'} + ghost={!isVideoBroadcasting} hideLabel circle size="lg" diff --git a/bigbluebutton-html5/imports/ui/components/actions-bar/service.js b/bigbluebutton-html5/imports/ui/components/actions-bar/service.js index 69738f6dc3183e7ddec98afaf2f21eb720d8d5d0..b62899a38ec3fea7dd2ffffb387c6f06027d0729 100644 --- a/bigbluebutton-html5/imports/ui/components/actions-bar/service.js +++ b/bigbluebutton-html5/imports/ui/components/actions-bar/service.js @@ -4,7 +4,32 @@ import { makeCall } from '/imports/ui/services/api'; import Meetings from '/imports/api/meetings'; import Breakouts from '/imports/api/breakouts'; +const processOutsideToggleRecording = (e) => { + switch (e.data) { + case 'c_record': { + makeCall('toggleRecording'); + break; + } + case 'c_recording_status': { + const recordingState = Meetings.findOne({ meetingId: Auth.meetingID }).recordProp.recording; + const recordingMessage = recordingState ? 'recordingStarted' : 'recordingStopped'; + this.window.parent.postMessage({ response: recordingMessage }, '*'); + break; + } + default: { + // console.log(e.data); + } + } +}; + +const connectRecordingObserver = () => { + // notify on load complete + this.window.parent.postMessage({ response: 'readyToConnect' }, '*'); +}; + + export default { + connectRecordingObserver: () => connectRecordingObserver(), isUserPresenter: () => Users.findOne({ userId: Auth.userID }).presenter, isUserModerator: () => Users.findOne({ userId: Auth.userID }).moderator, recordSettingsList: () => Meetings.findOne({ meetingId: Auth.meetingID }).recordProp, @@ -13,5 +38,6 @@ export default { users: () => Users.find({ connectionStatus: 'online' }).fetch(), hasBreakoutRoom: () => Breakouts.find({ parentMeetingId: Auth.meetingID }).fetch().length > 0, toggleRecording: () => makeCall('toggleRecording'), + processOutsideToggleRecording: arg => processOutsideToggleRecording(arg), createBreakoutRoom: (numberOfRooms, durationInMinutes, freeJoin = true, record = false) => makeCall('createBreakoutRoom', numberOfRooms, durationInMinutes, freeJoin, record), }; diff --git a/bigbluebutton-html5/imports/ui/components/actions-bar/styles.scss b/bigbluebutton-html5/imports/ui/components/actions-bar/styles.scss index feaefe627cf7449eef9f16c906398b2be9dba544..7619bf7adca27ee4fba95d6505aed73b602ed7be 100644 --- a/bigbluebutton-html5/imports/ui/components/actions-bar/styles.scss +++ b/bigbluebutton-html5/imports/ui/components/actions-bar/styles.scss @@ -44,3 +44,17 @@ box-shadow: 0 2px 5px 0 rgb(0, 0, 0); } } + +.btn { + span { + border: none; + box-shadow: none; + background-color: transparent !important; + color: var(--color-white) !important; + border: 1.5px solid var(--color-white) !important; + } + + span:hover { + border: 1.5px solid rgba(255,255,255, .5) !important; + } +} \ No newline at end of file diff --git a/bigbluebutton-html5/imports/ui/components/app/component.jsx b/bigbluebutton-html5/imports/ui/components/app/component.jsx index 865c0278339cdf30936bc7ab442068cf668c3b89..6a1fb08bfb6a87821561bccd81e37800477e43e6 100755 --- a/bigbluebutton-html5/imports/ui/components/app/component.jsx +++ b/bigbluebutton-html5/imports/ui/components/app/component.jsx @@ -8,6 +8,8 @@ import Resizable from 're-resizable'; import browser from 'browser-detect'; import BreakoutRoomContainer from '/imports/ui/components/breakout-room/container'; import PollingContainer from '/imports/ui/components/polling/container'; +import PollContainer from '/imports/ui/components/poll/container'; +import logger from '/imports/startup/client/logger'; import ToastContainer from '../toast/container'; import ModalContainer from '../modal/container'; import NotificationsBarContainer from '../notifications-bar/container'; @@ -16,7 +18,7 @@ import ChatAlertContainer from '../chat/alert/container'; import { styles } from './styles'; import UserListContainer from '../user-list/container'; import ChatContainer from '../chat/container'; -import PollContainer from '/imports/ui/components/poll/container'; + const MOBILE_MEDIA = 'only screen and (max-width: 40em)'; const USERLIST_COMPACT_WIDTH = 50; @@ -94,6 +96,8 @@ class App extends Component { this.handleWindowResize(); window.addEventListener('resize', this.handleWindowResize, false); + + logger.info('Client loaded successfully'); } componentWillUnmount() { @@ -224,7 +228,6 @@ class App extends Component { minWidth={USERLIST_MIN_WIDTH_PX} maxWidth={USERLIST_MAX_WIDTH_PX} ref={(node) => { this.resizableUserList = node; }} - className={styles.resizableUserList} enable={resizableEnableOptions} onResize={(e, direction, ref) => { const { compactUserList } = this.state; @@ -280,7 +283,6 @@ class App extends Component { minWidth={CHAT_MIN_WIDTH} maxWidth={CHAT_MAX_WIDTH} ref={(node) => { this.resizableChat = node; }} - className={styles.resizableChat} enable={resizableEnableOptions} > {this.renderChat()} @@ -327,7 +329,7 @@ class App extends Component { render() { const { - userListIsOpen, customStyle, customStyleUrl, + userListIsOpen, customStyle, customStyleUrl, micsLocked, } = this.props; const { enableResize } = this.state; @@ -349,11 +351,11 @@ class App extends Component { </section> <PollingContainer /> <ModalContainer /> - <AudioContainer /> + {micsLocked ? null : <AudioContainer />} <ToastContainer /> <ChatAlertContainer /> - { customStyleUrl ? <link rel="stylesheet" type="text/css" href={customStyleUrl} /> : null } - { customStyle ? <link rel="stylesheet" type="text/css" href={`data:text/css;charset=UTF-8,${encodeURIComponent(customStyle)}`} /> : null } + {customStyleUrl ? <link rel="stylesheet" type="text/css" href={customStyleUrl} /> : null} + {customStyle ? <link rel="stylesheet" type="text/css" href={`data:text/css;charset=UTF-8,${encodeURIComponent(customStyle)}`} /> : null} </main> ); } diff --git a/bigbluebutton-html5/imports/ui/components/app/container.jsx b/bigbluebutton-html5/imports/ui/components/app/container.jsx index 600aee257851fedf1a23fe30c9df4aa3645db74a..3e1af53e04e10874a23bc3e7ddb816e23d1a16c5 100755 --- a/bigbluebutton-html5/imports/ui/components/app/container.jsx +++ b/bigbluebutton-html5/imports/ui/components/app/container.jsx @@ -4,9 +4,9 @@ import { defineMessages, injectIntl } from 'react-intl'; import PropTypes from 'prop-types'; import Auth from '/imports/ui/services/auth'; import Users from '/imports/api/users'; +import mapUser from '/imports/ui/services/user/mapUser'; import Breakouts from '/imports/api/breakouts'; import Meetings from '/imports/api/meetings'; -import logger from '/imports/startup/client/logger'; import ClosedCaptionsContainer from '/imports/ui/components/closed-captions/container'; import getFromUserSettings from '/imports/ui/services/users-settings'; @@ -70,14 +70,14 @@ const AppContainer = (props) => { export default injectIntl(withModalMounter(withTracker(({ intl, baseControls }) => { const currentUser = Users.findOne({ userId: Auth.userID }); + const currentUserIsLocked = mapUser(currentUser).isLocked; + const meeting = Meetings.findOne({ meetingId: Auth.meetingID }); const isMeetingBreakout = meetingIsBreakout(); if (!currentUser.approved) { baseControls.updateLoadingState(intl.formatMessage(intlMessages.waitingApprovalMessage)); } - logger.info('User joined meeting and subscribed to data successfully'); - // Check if user is removed out of the session Users.find({ userId: Auth.userID }).observeChanges({ changed(id, fields) { @@ -117,6 +117,7 @@ export default injectIntl(withModalMounter(withTracker(({ intl, baseControls }) pollIsOpen: Session.get('isPollOpen') && Session.get('isUserListOpen'), customStyle: getFromUserSettings('customStyle', false), customStyleUrl: getFromUserSettings('customStyleUrl', false), + micsLocked: (currentUserIsLocked && meeting.lockSettingsProp.disableMic), }; })(AppContainer))); diff --git a/bigbluebutton-html5/imports/ui/components/app/styles.scss b/bigbluebutton-html5/imports/ui/components/app/styles.scss index 688df986f27676e65bde70b8838a0d20adbd46d9..089e4bf729c01531f965f267fb9607819ef62868 100755 --- a/bigbluebutton-html5/imports/ui/components/app/styles.scss +++ b/bigbluebutton-html5/imports/ui/components/app/styles.scss @@ -1,4 +1,7 @@ @import "/imports/ui/stylesheets/variables/_all"; +@import "/imports/ui/stylesheets/variables/general"; +@import "/imports/ui/stylesheets/variables/palette"; +@import "/imports/ui/stylesheets/variables/typography"; :root { --navbar-height: 63px; // TODO: Change to NavBar real height diff --git a/bigbluebutton-html5/imports/ui/components/audio/audio-controls/component.jsx b/bigbluebutton-html5/imports/ui/components/audio/audio-controls/component.jsx index 0b4cc607394acb006ee40f8b8d511e2c96982ea0..dc2bc885d7ae4102c909ddcc3080612abba08e16 100644 --- a/bigbluebutton-html5/imports/ui/components/audio/audio-controls/component.jsx +++ b/bigbluebutton-html5/imports/ui/components/audio/audio-controls/component.jsx @@ -1,8 +1,9 @@ -import React from 'react'; +import React, { Component } from 'react'; import PropTypes from 'prop-types'; import cx from 'classnames'; import { defineMessages, intlShape, injectIntl } from 'react-intl'; import Button from '/imports/ui/components/button/component'; +import getFromUserSettings from '/imports/ui/services/users-settings'; import withShortcutHelper from '/imports/ui/components/shortcut-help/service'; import { styles } from './styles'; @@ -26,6 +27,7 @@ const intlMessages = defineMessages({ }); const propTypes = { + processToggleMuteFromOutside: PropTypes.func.isRequired, handleToggleMuteMicrophone: PropTypes.func.isRequired, handleJoinAudio: PropTypes.func.isRequired, handleLeaveAudio: PropTypes.func.isRequired, @@ -42,47 +44,66 @@ const defaultProps = { unmute: false, }; -const AudioControls = ({ - handleToggleMuteMicrophone, - handleJoinAudio, - handleLeaveAudio, - mute, - unmute, - disable, - glow, - join, - intl, - shortcuts, -}) => ( - <span className={styles.container}> - {mute ? - <Button - className={glow ? cx(styles.button, styles.glow) : styles.button} - onClick={handleToggleMuteMicrophone} - disabled={disable} - hideLabel - label={unmute ? intl.formatMessage(intlMessages.unmuteAudio) : intl.formatMessage(intlMessages.muteAudio)} - aria-label={unmute ? intl.formatMessage(intlMessages.unmuteAudio) : intl.formatMessage(intlMessages.muteAudio)} - color="primary" - icon={unmute ? 'mute' : 'unmute'} - size="lg" - circle - accessKey={shortcuts.toggleMute} - /> : null} - <Button - className={styles.button} - onClick={join ? handleLeaveAudio : handleJoinAudio} - disabled={disable} - hideLabel - aria-label={join ? intl.formatMessage(intlMessages.leaveAudio) : intl.formatMessage(intlMessages.joinAudio)} - label={join ? intl.formatMessage(intlMessages.leaveAudio) : intl.formatMessage(intlMessages.joinAudio)} - color={join ? 'danger' : 'primary'} - icon={join ? 'audio_off' : 'audio_on'} - size="lg" - circle - accessKey={join ? shortcuts.leaveAudio : shortcuts.joinAudio} - /> - </span>); +class AudioControls extends Component { + componentDidMount() { + const { processToggleMuteFromOutside } = this.props; + if (Meteor.settings.public.allowOutsideCommands.toggleSelfVoice || + getFromUserSettings('outsideToggleSelfVoice', false)) { + window.addEventListener('message', processToggleMuteFromOutside); + } + } + + render() { + const { + handleToggleMuteMicrophone, + handleJoinAudio, + handleLeaveAudio, + mute, + unmute, + disable, + glow, + join, + intl, + shortcuts, + } = this.props; + + return ( + <span className={styles.container}> + {mute ? + <Button + className={glow ? cx(styles.button, styles.glow) : cx(styles.button, !unmute || styles.ghostButton)} + onClick={handleToggleMuteMicrophone} + disabled={disable} + hideLabel + label={unmute ? intl.formatMessage(intlMessages.unmuteAudio) : + intl.formatMessage(intlMessages.muteAudio)} + aria-label={unmute ? intl.formatMessage(intlMessages.unmuteAudio) : + intl.formatMessage(intlMessages.muteAudio)} + color={!unmute ? 'primary' : 'default'} + ghost={unmute} + icon={unmute ? 'mute' : 'unmute'} + size="lg" + circle + accessKey={shortcuts.toggleMute} + /> : null} + <Button + className={cx(styles.button, join || styles.ghostButton)} + onClick={join ? handleLeaveAudio : handleJoinAudio} + disabled={disable} + hideLabel + aria-label={join ? intl.formatMessage(intlMessages.leaveAudio) : + intl.formatMessage(intlMessages.joinAudio)} + label={join ? intl.formatMessage(intlMessages.leaveAudio) : + intl.formatMessage(intlMessages.joinAudio)} + color={join ? 'primary' : 'default'} + icon={join ? 'audio_on' : 'audio_off'} + size="lg" + circle + accessKey={join ? shortcuts.leaveAudio : shortcuts.joinAudio} + /> + </span>); + } +} AudioControls.propTypes = propTypes; AudioControls.defaultProps = defaultProps; diff --git a/bigbluebutton-html5/imports/ui/components/audio/audio-controls/container.jsx b/bigbluebutton-html5/imports/ui/components/audio/audio-controls/container.jsx index 51578831f3ca51845c74adb06735c635abaa86ca..11cf83edb83fdbec7114f39fc599dbf03d27d205 100644 --- a/bigbluebutton-html5/imports/ui/components/audio/audio-controls/container.jsx +++ b/bigbluebutton-html5/imports/ui/components/audio/audio-controls/container.jsx @@ -1,20 +1,55 @@ import React from 'react'; import { withTracker } from 'meteor/react-meteor-data'; import { withModalMounter } from '/imports/ui/components/modal/service'; +import AudioManager from '/imports/ui/services/audio-manager'; +import { makeCall } from '/imports/ui/services/api'; +import Users from '/imports/api/users/'; +import Meetings from '/imports/api/meetings'; +import mapUser from '/imports/ui/services/user/mapUser'; +import Auth from '/imports/ui/services/auth'; import AudioControls from './component'; import AudioModalContainer from '../audio-modal/container'; import Service from '../service'; const AudioControlsContainer = props => <AudioControls {...props} />; -export default withModalMounter(withTracker(({ mountModal }) => - ({ - mute: Service.isConnected() && !Service.isListenOnly() && !Service.isEchoTest(), - unmute: Service.isConnected() && !Service.isListenOnly() && Service.isMuted(), - join: Service.isConnected() && !Service.isEchoTest(), - disable: Service.isConnecting() || Service.isHangingUp(), - glow: Service.isTalking() && !Service.isMuted(), - handleToggleMuteMicrophone: () => Service.toggleMuteMicrophone(), - handleJoinAudio: () => mountModal(<AudioModalContainer />), - handleLeaveAudio: () => Service.exitAudio(), - }))(AudioControlsContainer)); +const processToggleMuteFromOutside = (e) => { + switch (e.data) { + case 'c_mute': { + makeCall('toggleSelfVoice'); + break; + } + case 'get_audio_joined_status': { + const audioJoinedState = AudioManager.isConnected ? 'joinedAudio' : 'notInAudio'; + this.window.parent.postMessage({ response: audioJoinedState }, '*'); + break; + } + case 'c_mute_status': { + const muteState = AudioManager.isMuted ? 'selfMuted' : 'selfUnmuted'; + this.window.parent.postMessage({ response: muteState }, '*'); + break; + } + default: { + // console.log(e.data); + } + } +}; + +export default withModalMounter(withTracker(({ mountModal }) => ({ + processToggleMuteFromOutside: arg => processToggleMuteFromOutside(arg), + mute: Service.isConnected() && !Service.isListenOnly() && !Service.isEchoTest(), + unmute: Service.isConnected() && !Service.isListenOnly() && Service.isMuted(), + join: Service.isConnected() && !Service.isEchoTest(), + disable: Service.isConnecting() || Service.isHangingUp(), + glow: Service.isTalking() && !Service.isMuted(), + handleToggleMuteMicrophone: () => Service.toggleMuteMicrophone(), + handleJoinAudio: () => { + const meeting = Meetings.findOne({ meetingId: Auth.meetingID }); + const currentUser = Users.findOne({ userId: Auth.userID }); + const currentUserIsLocked = mapUser(currentUser).isLocked; + const micsLocked = (currentUserIsLocked && meeting.lockSettingsProp.disableMic); + + return micsLocked ? Service.joinListenOnly() : mountModal(<AudioModalContainer />); + }, + handleLeaveAudio: () => Service.exitAudio(), +}))(AudioControlsContainer)); diff --git a/bigbluebutton-html5/imports/ui/components/audio/audio-controls/styles.scss b/bigbluebutton-html5/imports/ui/components/audio/audio-controls/styles.scss index 58f47fdddcd95011c414c35f974446c8ae538b35..c1bfcdeedb5c354f0dd8f2939cf1cc885ff784a6 100755 --- a/bigbluebutton-html5/imports/ui/components/audio/audio-controls/styles.scss +++ b/bigbluebutton-html5/imports/ui/components/audio/audio-controls/styles.scss @@ -1,4 +1,5 @@ @import "/imports/ui/stylesheets/variables/_all"; +@import "/imports/ui/components/actions-bar/styles.scss"; .container { display: flex; @@ -47,3 +48,7 @@ box-shadow: 0 0 0 0 transparent; } } + +.ghostButton { + @extend .btn; +} diff --git a/bigbluebutton-html5/imports/ui/components/audio/container.jsx b/bigbluebutton-html5/imports/ui/components/audio/container.jsx index 112476a3d7f4f097ed50b5a9186ed5d74c43be75..a7a75ebd2e271dd5e58d74e983637ec839ea9d8d 100755 --- a/bigbluebutton-html5/imports/ui/components/audio/container.jsx +++ b/bigbluebutton-html5/imports/ui/components/audio/container.jsx @@ -6,6 +6,7 @@ import _ from 'lodash'; import Breakouts from '/imports/api/breakouts'; import { notify } from '/imports/ui/services/notification'; import getFromUserSettings from '/imports/ui/services/users-settings'; +import VideoPreviewContainer from '/imports/ui/components/video-preview/container'; import Service from './service'; import AudioModalContainer from './audio-modal/container'; @@ -77,12 +78,16 @@ let didMountAutoJoin = false; export default withModalMounter(injectIntl(withTracker(({ mountModal, intl }) => { const APP_CONFIG = Meteor.settings.public.app; + const KURENTO_CONFIG = Meteor.settings.public.kurento; const autoJoin = getFromUserSettings('autoJoin', APP_CONFIG.autoJoin); const openAudioModal = mountModal.bind( null, <AudioModalContainer />, ); + const openVideoPreviewModal = () => new Promise((resolve) => { + mountModal(<VideoPreviewContainer resolve={resolve} />); + }); if (Service.audioLocked() && Service.isConnected() && !Service.isListenOnly()) { Service.exitAudio(); notify(intl.formatMessage(intlMessages.reconectingAsListener), 'info', 'audio_on'); @@ -130,8 +135,15 @@ export default withModalMounter(injectIntl(withTracker(({ mountModal, intl }) => Service.init(messages); Service.changeOutputDevice(document.querySelector('#remote-media').sinkId); if (!autoJoin || didMountAutoJoin) return; - openAudioModal(); - didMountAutoJoin = true; + + const enableVideo = getFromUserSettings('enableVideo', KURENTO_CONFIG.enableVideo); + const autoShareWebcam = getFromUserSettings('autoShareWebcam', KURENTO_CONFIG.autoShareWebcam); + if (enableVideo && autoShareWebcam) { + openVideoPreviewModal().then(() => { openAudioModal(); didMountAutoJoin = true; }); + } else { + openAudioModal(); + didMountAutoJoin = true; + } }, }; })(AudioContainer))); diff --git a/bigbluebutton-html5/imports/ui/components/audio/device-selector/component.jsx b/bigbluebutton-html5/imports/ui/components/audio/device-selector/component.jsx index 5e32ca79f6b1e8da44eca5e2aab665a1eb9dcfb7..8e8fafd0c96cf3106964fe802768ee78c038e159 100644 --- a/bigbluebutton-html5/imports/ui/components/audio/device-selector/component.jsx +++ b/bigbluebutton-html5/imports/ui/components/audio/device-selector/component.jsx @@ -2,13 +2,13 @@ import React, { Component } from 'react'; import _ from 'lodash'; import PropTypes from 'prop-types'; import cx from 'classnames'; +import logger from '/imports/startup/client/logger'; import { styles } from '../audio-modal/styles'; const propTypes = { kind: PropTypes.oneOf(['audioinput', 'audiooutput', 'videoinput']), onChange: PropTypes.func.isRequired, value: PropTypes.string, - handleDeviceChange: PropTypes.func, className: PropTypes.string, }; @@ -16,7 +16,6 @@ const defaultProps = { kind: 'audioinput', value: undefined, className: null, - handleDeviceChange: null, }; class DeviceSelector extends Component { @@ -35,7 +34,7 @@ class DeviceSelector extends Component { componentDidMount() { const handleEnumerateDevicesSuccess = (deviceInfos) => { const devices = deviceInfos.filter(d => d.kind === this.props.kind); - + logger.info(`Success on enumerateDevices() for ${this.props.kind}: ${JSON.stringify(devices)}`); this.setState({ devices, options: devices.map((d, i) => ({ @@ -48,11 +47,14 @@ class DeviceSelector extends Component { navigator.mediaDevices .enumerateDevices() - .then(handleEnumerateDevicesSuccess); + .then(handleEnumerateDevicesSuccess) + .catch((err) => { + logger.error(`Error on enumerateDevices(): ${JSON.stringify(err)}`); + }); } handleSelectChange(event) { - const value = event.target.value; + const { value } = event.target; const { onChange } = this.props; this.setState({ value }, () => { const selectedDevice = this.state.devices.find(d => d.deviceId === value); @@ -61,7 +63,9 @@ class DeviceSelector extends Component { } render() { - const { kind, handleDeviceChange, className, ...props } = this.props; + const { + kind, className, ...props + } = this.props; const { options, value } = this.state; return ( diff --git a/bigbluebutton-html5/imports/ui/components/authenticated-handler/component.jsx b/bigbluebutton-html5/imports/ui/components/authenticated-handler/component.jsx new file mode 100644 index 0000000000000000000000000000000000000000..d1ae21cd5479ffc2566c4e2665643761833c98aa --- /dev/null +++ b/bigbluebutton-html5/imports/ui/components/authenticated-handler/component.jsx @@ -0,0 +1,97 @@ +import React, { Component } from 'react'; +import { Session } from 'meteor/session'; +import { log } from '/imports/ui/services/api'; +import Auth from '/imports/ui/services/auth'; +import LoadingScreen from '/imports/ui/components/loading-screen/component'; + +const STATUS_CONNECTING = 'connecting'; + +class AuthenticatedHandler extends Component { + static setError(codeError) { + Session.set('hasError', true); + if (codeError) Session.set('codeError', codeError); + } + + static shouldAuthenticate(status, lastStatus) { + return lastStatus != null && lastStatus === STATUS_CONNECTING && status.connected; + } + + static updateStatus(status, lastStatus) { + return status.retryCount > 0 && lastStatus !== STATUS_CONNECTING ? status.status : lastStatus; + } + + static addReconnectObservable() { + let lastStatus = null; + + Tracker.autorun(() => { + lastStatus = AuthenticatedHandler.updateStatus(Meteor.status(), lastStatus); + + if (AuthenticatedHandler.shouldAuthenticate(Meteor.status(), lastStatus)) { + Auth.authenticate(true); + lastStatus = Meteor.status().status; + } + }); + } + + static async authenticatedRouteHandler(callback) { + if (Auth.loggedIn) { + callback(); + } + + AuthenticatedHandler.addReconnectObservable(); + + const setReason = (reason) => { + log('error', reason); + AuthenticatedHandler.setError(reason.error); + callback(); + }; + + try { + const getAuthenticate = await Auth.authenticate(); + callback(getAuthenticate); + } catch (error) { + setReason(error); + } + } + + constructor(props) { + super(props); + this.changeState = this.changeState.bind(this); + this.state = { + authenticated: false, + }; + } + + componentDidMount() { + AuthenticatedHandler.authenticatedRouteHandler((value, error) => { + if (error) AuthenticatedHandler.setError(error); + this.changeState(true); + }); + } + + changeState(state) { + this.setState({ authenticated: state }); + } + + render() { + const { + children, + } = this.props; + const { + authenticated, + } = this.state; + + Session.set('isChatOpen', false); + Session.set('idChatOpen', ''); + Session.set('isMeetingEnded', false); + Session.set('isPollOpen', false); + Session.set('breakoutRoomIsOpen', false); + + return authenticated + ? children + : (<LoadingScreen />); + } +} + + +export default AuthenticatedHandler; diff --git a/bigbluebutton-html5/imports/ui/components/button/component.jsx b/bigbluebutton-html5/imports/ui/components/button/component.jsx index 4ca5fc9843646774e166c1c383a6607ddba5e02e..5f1a5b62747ca72ea77dbb569ad222cf0e1b8d1c 100644 --- a/bigbluebutton-html5/imports/ui/components/button/component.jsx +++ b/bigbluebutton-html5/imports/ui/components/button/component.jsx @@ -91,11 +91,9 @@ export default class Button extends BaseButton { const { size, color, - disabled, ghost, circle, block, - iconRight, } = this.props; const propClassNames = {}; @@ -106,8 +104,6 @@ export default class Button extends BaseButton { propClassNames[styles.ghost] = ghost; propClassNames[styles.circle] = circle; propClassNames[styles.block] = block; - propClassNames[styles.iconRight] = iconRight; - propClassNames[styles.disabled] = disabled; return propClassNames; } @@ -118,11 +114,12 @@ export default class Button extends BaseButton { hideLabel, label, 'aria-label': ariaLabel, + 'aria-expanded': ariaExpanded, } = this.props; const renderFuncName = circle ? 'renderCircle' : 'renderDefault'; - if (hideLabel) { + if (hideLabel && !ariaExpanded) { const tooltipLabel = label || ariaLabel; return ( @@ -206,7 +203,7 @@ export default class Button extends BaseButton { if (iconName) { return (<Icon className={styles.icon} iconName={iconName} />); - } else if (customIcon) { + } if (customIcon) { return customIcon; } diff --git a/bigbluebutton-html5/imports/ui/components/button/styles.scss b/bigbluebutton-html5/imports/ui/components/button/styles.scss index a048ad3466e68b4d4f1c9b428fbf1855620bb9fb..70a37f4150d7571a8d176bc1796aff102c23d25a 100644 --- a/bigbluebutton-html5/imports/ui/components/button/styles.scss +++ b/bigbluebutton-html5/imports/ui/components/button/styles.scss @@ -143,7 +143,8 @@ margin: 0 !important; padding: 0 !important; overflow: hidden; - display: block; + display: none !important; + } .icon { diff --git a/bigbluebutton-html5/imports/ui/components/chat/chat-dropdown/component.jsx b/bigbluebutton-html5/imports/ui/components/chat/chat-dropdown/component.jsx index f7af8612f7ca31d90de37da83e057d4bbd471343..48433e1782a5cb6c800d9e63c335245b65f567cc 100644 --- a/bigbluebutton-html5/imports/ui/components/chat/chat-dropdown/component.jsx +++ b/bigbluebutton-html5/imports/ui/components/chat/chat-dropdown/component.jsx @@ -11,7 +11,7 @@ import DropdownListItem from '/imports/ui/components/dropdown/list/item/componen import Auth from '/imports/ui/services/auth'; import Button from '/imports/ui/components/button/component'; -import ChatService from './../service'; +import ChatService from '../service'; import { styles } from './styles'; const intlMessages = defineMessages({ @@ -83,6 +83,7 @@ class ChatDropdown extends Component { return _.compact([ <DropdownListItem + data-test="chatSave" icon={saveIcon} label={intl.formatMessage(intlMessages.save)} key={this.actionsKey[0]} @@ -100,6 +101,7 @@ class ChatDropdown extends Component { }} />, <DropdownListItem + data-test="chatCopy" icon={copyIcon} id="clipboardButton" label={intl.formatMessage(intlMessages.copy)} @@ -107,6 +109,7 @@ class ChatDropdown extends Component { />, user.isModerator ? ( <DropdownListItem + data-test="chatClear" icon={clearIcon} label={intl.formatMessage(intlMessages.clear)} key={this.actionsKey[2]} diff --git a/bigbluebutton-html5/imports/ui/components/chat/message-list/component.jsx b/bigbluebutton-html5/imports/ui/components/chat/message-list/component.jsx index 04dff92add6f08f587cc18068197663acbf3fef2..4bdea69c8078f2fe39122f75387f4e0ff9ea391b 100644 --- a/bigbluebutton-html5/imports/ui/components/chat/message-list/component.jsx +++ b/bigbluebutton-html5/imports/ui/components/chat/message-list/component.jsx @@ -89,9 +89,9 @@ class MessageList extends Component { // Compare with <1 to account for the chance scrollArea.scrollTop is a float // value in some browsers. - this.shouldScrollBottom = position === scrollArea.scrollHeight || - (scrollArea.scrollHeight - position < 1) || - nextProps.scrollPosition === null; + this.shouldScrollBottom = position === scrollArea.scrollHeight + || (scrollArea.scrollHeight - position < 1) + || nextProps.scrollPosition === null; } componentDidUpdate(prevProps) { @@ -183,7 +183,6 @@ class MessageList extends Component { {messages.map(message => ( <MessageListItem handleReadMessage={handleReadMessage} - className={styles.messageListItem} key={message.id} messages={message.content} user={message.sender} diff --git a/bigbluebutton-html5/imports/ui/components/chat/message-list/styles.scss b/bigbluebutton-html5/imports/ui/components/chat/message-list/styles.scss index f03f66821be1f4be4231dc438fc869e23889a09a..e51ec5d7a3f2d91fd1f962bf33e001980324e915 100644 --- a/bigbluebutton-html5/imports/ui/components/chat/message-list/styles.scss +++ b/bigbluebutton-html5/imports/ui/components/chat/message-list/styles.scss @@ -11,7 +11,7 @@ overflow-y: auto; padding-left: var(--md-padding-x); margin-left: calc(-1 * var(--md-padding-x)); - padding-right: var(--md-padding-x); + padding-right: var(--sm-padding-y); margin-right: calc(-1 * var(--md-padding-x)); padding-bottom: var(--md-padding-x); margin-bottom: calc(-1 * var(--md-padding-x)); @@ -22,7 +22,7 @@ flex-flow: column; flex-grow: 1; flex-shrink: 1; - margin-right: calc(-1 * var(--md-padding-x)); + margin-right: auto; padding-right: var(--md-padding-x); padding-top: 0; &:after { diff --git a/bigbluebutton-html5/imports/ui/components/checkbox/component.jsx b/bigbluebutton-html5/imports/ui/components/checkbox/component.jsx index c290664a4350031dab77ad6576b0a1055c3aa48f..c2a1084ca9fd6f119f67e82b2c322f43af23b575 100644 --- a/bigbluebutton-html5/imports/ui/components/checkbox/component.jsx +++ b/bigbluebutton-html5/imports/ui/components/checkbox/component.jsx @@ -47,7 +47,7 @@ export default class Checkbox extends Component { return ( <div className={cx({ [styles.disabled]: !!disabled, - }, styles.container, className)} + }, className)} > <input type="checkbox" @@ -59,9 +59,9 @@ export default class Checkbox extends Component { disabled={disabled} /> <div role="presentation" onClick={this.handleChange}> - { checked ? - <Icon iconName="check" className={cx(styles.icon, styles.checked)} /> : - <Icon iconName="circle" className={styles.icon} /> + { checked + ? <Icon iconName="check" className={cx(styles.icon, styles.checked)} /> + : <Icon iconName="circle" className={styles.icon} /> } </div> <div id={ariaLabelledBy} hidden>{ariaLabel}</div> diff --git a/bigbluebutton-html5/imports/ui/components/closed-captions/service.js b/bigbluebutton-html5/imports/ui/components/closed-captions/service.js index 7a4c9fe37ab7b3f7a799d10b601eb8bb3fa3e088..da2e0e10fc1d2a79b821cb80bd1aa5c927ce9f26 100644 --- a/bigbluebutton-html5/imports/ui/components/closed-captions/service.js +++ b/bigbluebutton-html5/imports/ui/components/closed-captions/service.js @@ -1,6 +1,7 @@ import Captions from '/imports/api/captions'; import Auth from '/imports/ui/services/auth'; import Settings from '/imports/ui/services/settings'; +import _ from 'lodash'; const getCCData = () => { const meetingID = Auth.meetingID; diff --git a/bigbluebutton-html5/imports/ui/components/dropdown/styles.scss b/bigbluebutton-html5/imports/ui/components/dropdown/styles.scss index 952aba87eab005b01fbc1d790902ab70d06aff32..d622ad91cb164489467d6c59f053cfa44025c9ef 100755 --- a/bigbluebutton-html5/imports/ui/components/dropdown/styles.scss +++ b/bigbluebutton-html5/imports/ui/components/dropdown/styles.scss @@ -205,7 +205,7 @@ &:after, &:before { - left: calc(var(--dropdown-caret-width) / 2); + left: var(--dropdown-caret-width); } } diff --git a/bigbluebutton-html5/imports/ui/components/error-screen/component.jsx b/bigbluebutton-html5/imports/ui/components/error-screen/component.jsx index 800d6409e03e0c8c38cb70244c0725cd48cc92bb..53c405e37f03e61f6904917edfd67f127578eb63 100644 --- a/bigbluebutton-html5/imports/ui/components/error-screen/component.jsx +++ b/bigbluebutton-html5/imports/ui/components/error-screen/component.jsx @@ -3,6 +3,7 @@ import PropTypes from 'prop-types'; import { defineMessages, injectIntl } from 'react-intl'; import { Meteor } from 'meteor/meteor'; import Button from '/imports/ui/components/button/component'; +import logoutRouteHandler from '/imports/utils/logoutRouteHandler'; import { styles } from './styles'; const intlMessages = defineMessages({ @@ -54,19 +55,19 @@ class ErrorScreen extends React.PureComponent { return ( <div className={styles.background}> - <h1 className={styles.code}> + <h1> {code} </h1> <h1 className={styles.message}> {formatedMessage} </h1> - <div className={styles.content}> + <div> {children} </div> - <div className={styles.content}> + <div> <Button size="sm" - onClick={() => Session.set('isMeetingEnded', true)} + onClick={logoutRouteHandler} label={intl.formatMessage(intlMessages.leave)} /> </div> diff --git a/bigbluebutton-html5/imports/ui/components/join-handler/component.jsx b/bigbluebutton-html5/imports/ui/components/join-handler/component.jsx new file mode 100644 index 0000000000000000000000000000000000000000..353f27bd8b673295c971c266da8d511297009967 --- /dev/null +++ b/bigbluebutton-html5/imports/ui/components/join-handler/component.jsx @@ -0,0 +1,130 @@ +import React, { Component } from 'react'; +import { Session } from 'meteor/session'; +import Auth from '/imports/ui/services/auth'; +import { setCustomLogoUrl } from '/imports/ui/components/user-list/service'; +import { makeCall } from '/imports/ui/services/api'; +import deviceInfo from '/imports/utils/deviceInfo'; +import logger from '/imports/startup/client/logger'; +import LoadingScreen from '/imports/ui/components/loading-screen/component'; + + +class JoinHandler extends Component { + static setError(codeError) { + Session.set('hasError', true); + if (codeError) Session.set('codeError', codeError); + } + + constructor(props) { + super(props); + this.fetchToken = this.fetchToken.bind(this); + this.changeToJoin = this.changeToJoin.bind(this); + + this.state = { + joined: false, + }; + } + + componentDidMount() { + this.fetchToken(); + } + + changeToJoin(bool) { + this.setState({ joined: bool }); + } + + async fetchToken() { + const urlParams = new URLSearchParams(window.location.search); + const sessionToken = urlParams.get('sessionToken'); + if (!sessionToken) { + JoinHandler.setError('404'); + } + + // Old credentials stored in memory were being used when joining a new meeting + Auth.clearCredentials(); + const logUserInfo = () => { + const userInfo = window.navigator; + + // Browser information is sent once on startup + // Sent here instead of Meteor.startup, as the + // user might not be validated by then, thus user's data + // would not be sent with this information + const clientInfo = { + language: userInfo.language, + userAgent: userInfo.userAgent, + screenSize: { width: window.screen.width, height: window.screen.height }, + windowSize: { width: window.innerWidth, height: window.innerHeight }, + bbbVersion: Meteor.settings.public.app.bbbServerVersion, + location: window.location.href, + }; + + logger.info(clientInfo); + }; + + const setAuth = (resp) => { + const { + meetingID, internalUserID, authToken, logoutUrl, + fullname, externUserID, confname, + } = resp; + return new Promise((resolve) => { + Auth.set( + meetingID, internalUserID, authToken, logoutUrl, + sessionToken, fullname, externUserID, confname, + ); + resolve(resp); + }); + }; + + const setLogoutURL = (url) => { + Auth.logoutURL = url; + return true; + }; + + const setLogoURL = (resp) => { + setCustomLogoUrl(resp.customLogoURL); + return resp; + }; + + const setCustomData = (resp) => { + const { + meetingID, internalUserID, customdata, + } = resp; + + + return new Promise((resolve) => { + if (customdata.length) { + makeCall('addUserSettings', meetingID, internalUserID, customdata).then(r => resolve(r)); + } + resolve(true); + }); + }; + // use enter api to get params for the client + const url = `/bigbluebutton/api/enter?sessionToken=${sessionToken}`; + + const fetchContent = await fetch(url, { credentials: 'same-origin' }); + const parseToJson = await fetchContent.json(); + const { response } = parseToJson; + setLogoutURL(response); + if (response.returncode !== 'FAILED') { + await setAuth(response); + await setCustomData(response); + setLogoURL(response); + logUserInfo(); + Session.set('isUserListOpen', deviceInfo.type().isPhone); + logger.info(`User successfully went through main.joinRouteHandler with [${JSON.stringify(response)}].`); + } else { + const e = new Error('Session not found'); + logger.error(`User faced [${e}] on main.joinRouteHandler. Error was:`, JSON.stringify(response)); + } + this.changeToJoin(true); + } + + render() { + const { children } = this.props; + const { joined } = this.state; + return joined + ? children + : (<LoadingScreen />); + } +} + +export default JoinHandler; diff --git a/bigbluebutton-html5/imports/ui/components/loading-screen/component.jsx b/bigbluebutton-html5/imports/ui/components/loading-screen/component.jsx index dcb56c35ccc03f59a35e9298dcc47d013d78a8f4..c54e954a3943eb4a45b281f19da6f36a15751bc6 100644 --- a/bigbluebutton-html5/imports/ui/components/loading-screen/component.jsx +++ b/bigbluebutton-html5/imports/ui/components/loading-screen/component.jsx @@ -6,7 +6,7 @@ const LoadingScreen = ({ children }) => ( <div className={styles.spinner}> <div className={styles.bounce1} /> <div className={styles.bounce2} /> - <div className={styles.bounce3} /> + <div /> </div> <div className={styles.message}> {children} diff --git a/bigbluebutton-html5/imports/ui/components/lock-viewers/component.jsx b/bigbluebutton-html5/imports/ui/components/lock-viewers/component.jsx new file mode 100755 index 0000000000000000000000000000000000000000..7c89ec1f1bbd035f3a1316caded8e3081dc9efb3 --- /dev/null +++ b/bigbluebutton-html5/imports/ui/components/lock-viewers/component.jsx @@ -0,0 +1,251 @@ +import React, { Component } from 'react'; +import { defineMessages, injectIntl } from 'react-intl'; +import Button from '/imports/ui/components/button/component'; +import Toggle from '/imports/ui/components/switch/component'; +import cx from 'classnames'; +import ModalBase from '/imports/ui/components/modal/base/component'; +import { styles } from './styles'; + +const intlMessages = defineMessages({ + lockViewersTitle: { + id: 'app.lock-viewers.title', + description: 'lock-viewers title', + }, + closeLabel: { + id: 'app.shortcut-help.closeLabel', + description: 'label for close button', + }, + closeDesc: { + id: 'app.shortcut-help.closeDesc', + description: 'description for close button', + }, + lockViewersDescription: { + id: 'app.lock-viewers.description', + description: 'description for lock viewers feature', + }, + featuresLable: { + id: 'app.lock-viewers.featuresLable', + description: 'features label', + }, + lockStatusLabel: { + id: 'app.lock-viewers.lockStatusLabel', + description: 'description for close button', + }, + webcamLabel: { + id: 'app.lock-viewers.webcamLabel', + description: 'description for close button', + }, + otherViewersWebcamLabel: { + id: 'app.lock-viewers.otherViewersWebcamLabel', + description: 'description for close button', + }, + microphoneLable: { + id: 'app.lock-viewers.microphoneLable', + description: 'description for close button', + }, + publicChatLabel: { + id: 'app.lock-viewers.PublicChatLabel', + description: 'description for close button', + }, + privateChatLable: { + id: 'app.lock-viewers.PrivateChatLable', + description: 'description for close button', + }, + layoutLable: { + id: 'app.lock-viewers.Layout', + description: 'description for close button', + }, +}); + +class LockViewersComponent extends Component { + constructor(props) { + super(props); + const { + closeModal, + toggleLockSettings, + toggleWebcamsOnlyForModerator, + } = props; + + this.closeModal = closeModal; + this.toggleLockSettings = toggleLockSettings; + this.toggleWebcamsOnlyForModerator = toggleWebcamsOnlyForModerator; + } + + render() { + const { intl, meeting } = this.props; + + return ( + <ModalBase + overlayClassName={styles.overlay} + className={styles.modal} + onRequestClose={this.closeModal} + > + + <div className={styles.container}> + <div className={styles.header}> + <div className={styles.title}>{intl.formatMessage(intlMessages.lockViewersTitle)}</div> + <Button + data-test="modalBaseCloseButton" + className={styles.closeBtn} + label={intl.formatMessage(intlMessages.closeLabel)} + icon="close" + size="md" + hideLabel + onClick={this.closeModal} + /> + </div> + <div className={styles.description}> + {`${intl.formatMessage(intlMessages.lockViewersDescription)}`} + </div> + + <div className={styles.form}> + <header className={styles.subHeader}> + <div className={styles.bold}>{intl.formatMessage(intlMessages.featuresLable)}</div> + <div className={styles.bold}>{intl.formatMessage(intlMessages.lockStatusLabel)}</div> + </header> + <div className={styles.row}> + <div className={styles.col} aria-hidden="true"> + <div className={styles.formElement}> + <div className={styles.label}> + {intl.formatMessage(intlMessages.webcamLabel)} + </div> + </div> + </div> + <div className={styles.col}> + <div className={cx(styles.formElement, styles.pullContentRight)}> + <Toggle + icons={false} + defaultChecked={meeting.lockSettingsProp.disableCam} + onChange={() => { + meeting.lockSettingsProp.disableCam = + !meeting.lockSettingsProp.disableCam; + this.toggleLockSettings(meeting); + }} + ariaLabel={intl.formatMessage(intlMessages.webcamLabel)} + /> + </div> + </div> + </div> + <div className={styles.row}> + <div className={styles.col} aria-hidden="true"> + <div className={styles.formElement}> + <div className={styles.label}> + {intl.formatMessage(intlMessages.otherViewersWebcamLabel)} + </div> + </div> + </div> + <div className={styles.col}> + <div className={cx(styles.formElement, styles.pullContentRight)}> + <Toggle + icons={false} + defaultChecked={meeting.usersProp.webcamsOnlyForModerator} + onChange={() => { + meeting.usersProp.webcamsOnlyForModerator = + !meeting.usersProp.webcamsOnlyForModerator; + this.toggleWebcamsOnlyForModerator(meeting); + }} + ariaLabel={intl.formatMessage(intlMessages.otherViewersWebcamLabel)} + /> + </div> + </div> + </div> + <div className={styles.row}> + <div className={styles.col} aria-hidden="true"> + <div className={styles.formElement}> + <div className={styles.label}> + {intl.formatMessage(intlMessages.microphoneLable)} + </div> + </div> + </div> + <div className={styles.col}> + <div className={cx(styles.formElement, styles.pullContentRight)}> + <Toggle + icons={false} + defaultChecked={meeting.lockSettingsProp.disableMic} + onChange={() => { + meeting.lockSettingsProp.disableMic = + !meeting.lockSettingsProp.disableMic; + this.toggleLockSettings(meeting); + }} + ariaLabel={intl.formatMessage(intlMessages.microphoneLable)} + /> + </div> + </div> + </div> + <div className={styles.row}> + <div className={styles.col} aria-hidden="true"> + <div className={styles.formElement}> + <div className={styles.label}> + {intl.formatMessage(intlMessages.publicChatLabel)} + </div> + </div> + </div> + <div className={styles.col}> + <div className={cx(styles.formElement, styles.pullContentRight)}> + <Toggle + icons={false} + defaultChecked={meeting.lockSettingsProp.disablePubChat} + onChange={() => { + meeting.lockSettingsProp.disablePubChat = + !meeting.lockSettingsProp.disablePubChat; + this.toggleLockSettings(meeting); + }} + ariaLabel={intl.formatMessage(intlMessages.publicChatLabel)} + /> + </div> + </div> + </div> + <div className={styles.row}> + <div className={styles.col} aria-hidden="true"> + <div className={styles.formElement}> + <div className={styles.label}> + {intl.formatMessage(intlMessages.privateChatLable)} + </div> + </div> + </div> + <div className={styles.col}> + <div className={cx(styles.formElement, styles.pullContentRight)}> + <Toggle + icons={false} + defaultChecked={meeting.lockSettingsProp.disablePrivChat} + onChange={() => { + meeting.lockSettingsProp.disablePrivChat = + !meeting.lockSettingsProp.disablePrivChat; + this.toggleLockSettings(meeting); + }} + ariaLabel={intl.formatMessage(intlMessages.privateChatLable)} + /> + </div> + </div> + </div> + <div className={styles.row}> + <div className={styles.col} aria-hidden="true"> + <div className={styles.formElement}> + <div className={styles.label}> + {intl.formatMessage(intlMessages.layoutLable)} + </div> + </div> + </div> + <div className={styles.col}> + <div className={cx(styles.formElement, styles.pullContentRight)}> + <Toggle + icons={false} + defaultChecked={meeting.lockSettingsProp.lockedLayout} + onChange={() => { + meeting.lockSettingsProp.lockedLayout = + !meeting.lockSettingsProp.lockedLayout; + this.toggleLockSettings(meeting); + }} + ariaLabel={intl.formatMessage(intlMessages.layoutLable)} + /> + </div> + </div> + </div> + </div> + </div> + </ModalBase> + ); + } +} + +export default injectIntl(LockViewersComponent); diff --git a/bigbluebutton-html5/imports/ui/components/lock-viewers/container.jsx b/bigbluebutton-html5/imports/ui/components/lock-viewers/container.jsx new file mode 100755 index 0000000000000000000000000000000000000000..2ce4edcc89e00bac16de77a4dbc5c48e4f60b8ea --- /dev/null +++ b/bigbluebutton-html5/imports/ui/components/lock-viewers/container.jsx @@ -0,0 +1,24 @@ +import React from 'react'; +import { withTracker } from 'meteor/react-meteor-data'; +import { withModalMounter } from '/imports/ui/components/modal/service'; +import { makeCall } from '/imports/ui/services/api'; +import Meetings from '/imports/api/meetings'; +import Auth from '/imports/ui/services/auth'; +import LockViewersComponent from './component'; + +const LockViewersContainer = props => <LockViewersComponent {...props} />; + +export default withModalMounter(withTracker(({ mountModal }) => ({ + closeModal() { + mountModal(null); + }, + + toggleLockSettings(meeting) { + makeCall('toggleLockSettings', meeting); + }, + + toggleWebcamsOnlyForModerator(meeting) { + makeCall('toggleWebcamsOnlyForModerator', meeting); + }, + meeting: (Meetings.findOne({ meetingId: Auth.meetingID })), +}))(LockViewersContainer)); diff --git a/bigbluebutton-html5/imports/ui/components/lock-viewers/styles.scss b/bigbluebutton-html5/imports/ui/components/lock-viewers/styles.scss new file mode 100755 index 0000000000000000000000000000000000000000..078b36e6daa2ef8be0ca1eaad6c887953eda040e --- /dev/null +++ b/bigbluebutton-html5/imports/ui/components/lock-viewers/styles.scss @@ -0,0 +1,118 @@ +@import '/imports/ui/stylesheets/mixins/focus'; +@import '/imports/ui/stylesheets/variables/_all'; +@import "/imports/ui/components/modal/simple/styles"; + +:root { + --modal-margin: 3rem; + --title-position-left: 2.2rem; + --closeBtn-position-left: 2.5rem; +} + +.title { + position: relative; + left: var(--title-position-left); + color: var(--color-gray-dark); + font-weight: bold; + font-size: var(--font-size-large); + text-align: center; +} + +.form { + display: flex; + flex-flow: column; +} + +.container { + margin: var(--modal-margin); + margin-bottom: var(--lg-padding-x); +} + +.subHeader { + display: flex; + flex-flow: row; + flex-grow: 1; + justify-content: space-between; + color: var(--color-gray-label); + font-size: var(--font-size-base); + margin-bottom: var(--title-position-left); +} + +.modal { + @extend .modal; + padding: var(--jumbo-padding-y); +} + +.overlay { + @extend .overlay; +} + +.description { + text-align: center; + color: var(--color-gray); + margin-bottom: var(--jumbo-padding-y) +} + +.row { + display: flex; + flex-flow: row; + flex-grow: 1; + justify-content: space-between; + margin-bottom: var(--md-padding-x); +} + +.col { + display: flex; + flex-grow: 1; + flex-basis: 0; + margin-right: var(--md-padding-x); +} + +.label { + color: var(--color-gray-label); + font-size: var(--font-size-small); + margin-bottom: var(--lg-padding-y); +} + +.formElement { + position: relative; + display: flex; + flex-flow: column; + flex-grow: 1; +} + +.pullContentRight { + display: flex; + justify-content: flex-end; + flex-flow: row; +} + +.bold { + font-weight: bold; +} + +.closeBtn { + position: relative; + background-color: var(--color-white); + left: var(--closeBtn-position-left); + bottom: var(--closeBtn-position-left); + + i { + color: var(--color-gray-light); + } + + &:focus, + &:hover { + background-color: var(--color-gray-lighter); + i { + color: var(--color-gray); + } + } +} + +.header { + margin: 0; + padding: 0; + border: none; + line-height: var(--title-position-left); + margin-bottom: var(--lg-padding-y); +} diff --git a/bigbluebutton-html5/imports/ui/components/media/component.jsx b/bigbluebutton-html5/imports/ui/components/media/component.jsx index c011bfe293071798ccd5c8046a7f4eb73d4cf723..2e5faf840e1ca19d4ebca0807b8cffd7397c2b07 100644 --- a/bigbluebutton-html5/imports/ui/components/media/component.jsx +++ b/bigbluebutton-html5/imports/ui/components/media/component.jsx @@ -29,7 +29,6 @@ export default class Media extends Component { const contentClassName = cx({ [styles.content]: true, - [styles.hasOverlay]: !hideOverlay, }); const overlayClassName = cx({ diff --git a/bigbluebutton-html5/imports/ui/components/media/container.jsx b/bigbluebutton-html5/imports/ui/components/media/container.jsx index 75723126c6ce67fb6ebcb1bae2e7325d895bcb5d..142205160b34943774484d73368cedd70dab90a3 100755 --- a/bigbluebutton-html5/imports/ui/components/media/container.jsx +++ b/bigbluebutton-html5/imports/ui/components/media/container.jsx @@ -6,6 +6,8 @@ import PropTypes from 'prop-types'; import { notify } from '/imports/ui/services/notification'; import VideoService from '/imports/ui/components/video-provider/service'; import getFromUserSettings from '/imports/ui/services/users-settings'; +import { withModalMounter } from '/imports/ui/components/modal/service'; +import VideoPreviewContainer from '/imports/ui/components/video-preview/container'; import Media from './component'; import MediaService, { getSwapLayout } from './service'; import PresentationPodsContainer from '../presentation-pod/container'; @@ -45,10 +47,6 @@ const intlMessages = defineMessages({ class MediaContainer extends Component { componentWillMount() { - const { willMount } = this.props; - if (willMount) { - willMount(); - } document.addEventListener('installChromeExtension', this.installChromeExtension.bind(this)); document.addEventListener('safariScreenshareNotSupported', this.safariScreenshareNotSupported.bind(this)); } @@ -101,7 +99,7 @@ class MediaContainer extends Component { } } -export default withTracker(() => { +export default withModalMounter(withTracker(({ mountModal }) => { const { dataSaving } = Settings; const { viewParticipantsWebcams, viewScreenshare } = dataSaving; @@ -134,13 +132,6 @@ export default withTracker(() => { data.hideOverlay = hidePresentation; } - const enableVideo = getFromUserSettings('enableVideo', KURENTO_CONFIG.enableVideo); - const autoShareWebcam = getFromUserSettings('autoShareWebcam', KURENTO_CONFIG.autoShareWebcam); - - if (enableVideo && autoShareWebcam) { - data.willMount = VideoService.joinVideo; - } - MediaContainer.propTypes = propTypes; return data; -})(injectIntl(MediaContainer)); +})(injectIntl(MediaContainer))); diff --git a/bigbluebutton-html5/imports/ui/components/meeting-ended/component.jsx b/bigbluebutton-html5/imports/ui/components/meeting-ended/component.jsx index 7ab4b952328325d1a92ec4649778395156f9af32..f16754c6eef0b596671584e4396dee786b82c752 100755 --- a/bigbluebutton-html5/imports/ui/components/meeting-ended/component.jsx +++ b/bigbluebutton-html5/imports/ui/components/meeting-ended/component.jsx @@ -5,7 +5,7 @@ import { Meteor } from 'meteor/meteor'; import Auth from '/imports/ui/services/auth'; import Button from '/imports/ui/components/button/component'; import getFromUserSettings from '/imports/ui/services/users-settings'; -import { logoutRouteHandler } from '/imports/startup/client/auth'; +import logoutRouteHandler from '/imports/utils/logoutRouteHandler'; import Rating from './rating/component'; import { styles } from './styles'; @@ -78,7 +78,6 @@ class MeetingEnded extends React.PureComponent { const comment = textarea.value; return comment; } - constructor(props) { super(props); this.state = { @@ -126,7 +125,10 @@ class MeetingEnded extends React.PureComponent { }; fetch(url, options) - .finally(() => { + .then(() => { + logoutRouteHandler(); + }) + .catch(() => { logoutRouteHandler(); }); } diff --git a/bigbluebutton-html5/imports/ui/components/modal/service.js b/bigbluebutton-html5/imports/ui/components/modal/service.js index d920ec8390784fe883de0e191093e5ec710b2e25..f8427302455b59b4968f1362838555045ef6c13a 100644 --- a/bigbluebutton-html5/imports/ui/components/modal/service.js +++ b/bigbluebutton-html5/imports/ui/components/modal/service.js @@ -1,5 +1,5 @@ import { Tracker } from 'meteor/tracker'; -import React, { Component } from 'react'; +import React, { PureComponent } from 'react'; const currentModal = { component: null, @@ -19,7 +19,7 @@ export const getModal = () => { }; export const withModalMounter = ComponentToWrap => - class ModalMounterWrapper extends Component { + class ModalMounterWrapper extends PureComponent { static mount(modalComponent) { showModal(null); // defer the execution to a subsequent event loop diff --git a/bigbluebutton-html5/imports/ui/components/nav-bar/component.jsx b/bigbluebutton-html5/imports/ui/components/nav-bar/component.jsx index 8f68bd48c05a4d37998855374d88eef9409f083f..38625dc6e115e05c4ca3feaef2f09ef2c7bd6e20 100755 --- a/bigbluebutton-html5/imports/ui/components/nav-bar/component.jsx +++ b/bigbluebutton-html5/imports/ui/components/nav-bar/component.jsx @@ -1,4 +1,4 @@ -import React, { Component } from 'react'; +import React, { PureComponent } from 'react'; import PropTypes from 'prop-types'; import _ from 'lodash'; import cx from 'classnames'; @@ -59,16 +59,14 @@ const defaultProps = { shortcuts: '', }; -const openBreakoutJoinConfirmation = (breakout, breakoutName, mountModal) => - mountModal(<BreakoutJoinConfirmation - breakout={breakout} - breakoutName={breakoutName} - />); +const openBreakoutJoinConfirmation = (breakout, breakoutName, mountModal) => mountModal(<BreakoutJoinConfirmation + breakout={breakout} + breakoutName={breakoutName} +/>); -const closeBreakoutJoinConfirmation = mountModal => - mountModal(null); +const closeBreakoutJoinConfirmation = mountModal => mountModal(null); -class NavBar extends Component { +class NavBar extends PureComponent { constructor(props) { super(props); @@ -145,7 +143,9 @@ class NavBar extends Component { <Dropdown isOpen={this.state.isActionsOpen}> <DropdownTrigger> <h1 className={cx(styles.presentationTitle, styles.dropdownBreakout)}> - {presentationTitle} <Icon iconName="down-arrow" /> + {presentationTitle} + {' '} + <Icon iconName="down-arrow" /> </h1> </DropdownTrigger> <DropdownContent @@ -168,7 +168,6 @@ class NavBar extends Component { return ( <DropdownListItem - className={styles.actionsHeader} key={_.uniqueId('action-header')} label={breakoutName} onClick={openBreakoutJoinConfirmation.bind(this, breakout, breakoutName, mountModal)} @@ -213,9 +212,9 @@ class NavBar extends Component { </div> <div className={styles.center}> {this.renderPresentationTitle()} - {beingRecorded.record ? - <span className={styles.presentationTitleSeparator}>|</span> - : null} + {beingRecorded.record + ? <span className={styles.presentationTitleSeparator}>|</span> + : null} <RecordingIndicator {...beingRecorded} title={intl.formatMessage(intlMessages[recordingMessage])} diff --git a/bigbluebutton-html5/imports/ui/components/nav-bar/settings-dropdown/component.jsx b/bigbluebutton-html5/imports/ui/components/nav-bar/settings-dropdown/component.jsx index 108b08ce51ebd79e96d65133a33e36c2deef8d87..bc3a659924e371a0a1b6573e98d835404dd34431 100755 --- a/bigbluebutton-html5/imports/ui/components/nav-bar/settings-dropdown/component.jsx +++ b/bigbluebutton-html5/imports/ui/components/nav-bar/settings-dropdown/component.jsx @@ -1,6 +1,5 @@ -import React, { Component } from 'react'; +import React, { PureComponent } from 'react'; import { defineMessages, injectIntl } from 'react-intl'; -import cx from 'classnames'; import _ from 'lodash'; import { withModalMounter } from '/imports/ui/components/modal/service'; import LogoutConfirmationContainer from '/imports/ui/components/logout-confirmation/container'; @@ -81,7 +80,7 @@ const intlMessages = defineMessages({ }, }); -class SettingsDropdown extends Component { +class SettingsDropdown extends PureComponent { constructor(props) { super(props); @@ -98,7 +97,7 @@ class SettingsDropdown extends Component { const { fullscreenLabel, fullscreenDesc, fullscreenIcon } = this.checkFullscreen(this.props); const { showHelpButton: helpButton } = Meteor.settings.public.app; - this.menuItems =_.compact( [(<DropdownListItem + this.menuItems = _.compact([(<DropdownListItem key={_.uniqueId('list-item-')} icon={fullscreenIcon} label={fullscreenLabel} @@ -142,14 +141,12 @@ class SettingsDropdown extends Component { description={intl.formatMessage(intlMessages.leaveSessionDesc)} onClick={() => mountModal(<LogoutConfirmationContainer />)} />), - ]) + ]); // Removes fullscreen button if not on Android if (!isAndroid) { this.menuItems.shift(); } - - } componentWillReceiveProps(nextProps) { @@ -193,13 +190,15 @@ class SettingsDropdown extends Component { alterMenu(props) { const { fullscreenLabel, fullscreenDesc, fullscreenIcon } = this.checkFullscreen(props); - const newFullScreenButton = (<DropdownListItem - key={_.uniqueId('list-item-')} - icon={fullscreenIcon} - label={fullscreenLabel} - description={fullscreenDesc} - onClick={this.props.handleToggleFullscreen} - />); + const newFullScreenButton = ( + <DropdownListItem + key={_.uniqueId('list-item-')} + icon={fullscreenIcon} + label={fullscreenLabel} + description={fullscreenDesc} + onClick={this.props.handleToggleFullscreen} + /> + ); this.menuItems = this.menuItems.slice(1); this.menuItems.unshift(newFullScreenButton); } @@ -224,7 +223,7 @@ class SettingsDropdown extends Component { ghost circle hideLabel - className={cx(styles.btn, styles.btnSettings)} + className={styles.btn} // FIXME: Without onClick react proptypes keep warning // even after the DropdownTrigger inject an onClick handler diff --git a/bigbluebutton-html5/imports/ui/components/nav-bar/settings-dropdown/container.jsx b/bigbluebutton-html5/imports/ui/components/nav-bar/settings-dropdown/container.jsx index 6a13b39a6e9d209806b96997dfa2f84dd4bff391..9036ae76a533e4c06d5c3ff5a22c88b60996b203 100755 --- a/bigbluebutton-html5/imports/ui/components/nav-bar/settings-dropdown/container.jsx +++ b/bigbluebutton-html5/imports/ui/components/nav-bar/settings-dropdown/container.jsx @@ -1,9 +1,9 @@ -import React, { Component } from 'react'; +import React, { PureComponent } from 'react'; import browser from 'browser-detect'; import SettingsDropdown from './component'; import { toggleFullScreen } from './service'; -export default class SettingsDropdownContainer extends Component { +export default class SettingsDropdownContainer extends PureComponent { constructor(props) { super(props); @@ -51,7 +51,7 @@ export default class SettingsDropdownContainer extends Component { render() { const handleToggleFullscreen = toggleFullScreen; - const isFullScreen = this.state.isFullScreen; + const { isFullScreen } = this.state; const result = browser(); const isAndroid = (result && result.os) ? result.os.includes('Android') : false; diff --git a/bigbluebutton-html5/imports/ui/components/presentation/component.jsx b/bigbluebutton-html5/imports/ui/components/presentation/component.jsx index 38fa1e20a18c248266c70077cc244f21cd1cb0fc..889ba8f50d13bf20fe40ac0e8f0ffc3fb916c72a 100644 --- a/bigbluebutton-html5/imports/ui/components/presentation/component.jsx +++ b/bigbluebutton-html5/imports/ui/components/presentation/component.jsx @@ -3,6 +3,7 @@ import PropTypes from 'prop-types'; import { TransitionGroup, CSSTransition } from 'react-transition-group'; import WhiteboardOverlayContainer from '/imports/ui/components/whiteboard/whiteboard-overlay/container'; import WhiteboardToolbarContainer from '/imports/ui/components/whiteboard/whiteboard-toolbar/container'; +import { HUNDRED_PERCENT, MAX_PERCENT } from '/imports/utils/slideCalcUtils'; import CursorWrapperContainer from './cursor/cursor-wrapper-container/container'; import AnnotationGroupContainer from '../whiteboard/annotation-group/container'; import PresentationToolbarContainer from './presentation-toolbar/container'; @@ -10,9 +11,6 @@ import PresentationOverlayContainer from './presentation-overlay/container'; import Slide from './slide/component'; import { styles } from './styles.scss'; -const HUNDRED_PERCENT = 100; -const MAX_PERCENT = 400; - export default class PresentationArea extends Component { constructor() { super(); @@ -137,6 +135,7 @@ export default class PresentationArea extends Component { height: adjustedHeight, }; } + zoomChanger(zoom) { let newZoom = zoom; const isDifferent = newZoom !== this.state.zoom; @@ -148,6 +147,7 @@ export default class PresentationArea extends Component { } if (isDifferent) this.setState({ zoom: newZoom }); } + pointUpdate(pointX, pointY) { this.setState({ delta: { @@ -156,11 +156,13 @@ export default class PresentationArea extends Component { }, }); } + touchUpdate(bool) { this.setState({ touchZoom: bool, }); } + fitToWidthHandler() { this.setState({ fitToWidth: !this.state.fitToWidth, @@ -170,8 +172,8 @@ export default class PresentationArea extends Component { // renders the whole presentation area renderPresentationArea() { // sometimes tomcat publishes the slide url, but the actual file is not accessible (why?) - if (!this.props.currentSlide || - !this.props.currentSlide.calculatedData) { + if (!this.props.currentSlide + || !this.props.currentSlide.calculatedData) { return null; } // to control the size of the svg wrapper manually @@ -215,6 +217,7 @@ export default class PresentationArea extends Component { timeout={{ enter: 400 }} > <svg + data-test="whiteboard" width={width} height={height} ref={(ref) => { if (ref != null) { this.svggroup = ref; } }} @@ -329,8 +332,8 @@ export default class PresentationArea extends Component { } renderWhiteboardToolbar() { - if (!this.props.currentSlide || - !this.props.currentSlide.calculatedData) { + if (!this.props.currentSlide + || !this.props.currentSlide.calculatedData) { return null; } @@ -354,11 +357,11 @@ export default class PresentationArea extends Component { ref={(ref) => { this.refWhiteboardArea = ref; }} className={styles.whiteboardSizeAvailable} /> - {this.state.showSlide ? - this.renderPresentationArea() + {this.state.showSlide + ? this.renderPresentationArea() : null } - {this.props.userIsPresenter || this.props.multiUser ? - this.renderWhiteboardToolbar() + {this.props.userIsPresenter || this.props.multiUser + ? this.renderWhiteboardToolbar() : null } </div> {this.renderPresentationToolbar()} diff --git a/bigbluebutton-html5/imports/ui/components/presentation/presentation-overlay/component.jsx b/bigbluebutton-html5/imports/ui/components/presentation/presentation-overlay/component.jsx index 0b78549aad24a7f7d0375fc51a5d75662b5f1c75..492d446c53251e1fbb6f77391470174d2fdffa30 100755 --- a/bigbluebutton-html5/imports/ui/components/presentation/presentation-overlay/component.jsx +++ b/bigbluebutton-html5/imports/ui/components/presentation/presentation-overlay/component.jsx @@ -1,13 +1,11 @@ import React, { Component } from 'react'; import PropTypes from 'prop-types'; -import SlideCalcUtil from '/imports/utils/slideCalcUtils'; +import SlideCalcUtil, { HUNDRED_PERCENT, MAX_PERCENT, MYSTERY_NUM, STEP } from '/imports/utils/slideCalcUtils'; // After lots of trial and error on why synching doesn't work properly, I found I had to // multiply the coordinates by 2. There's something I don't understand probably on the // canvas coordinate system. (ralam feb 22, 2012) -const MYSTERY_NUM = 2; + const CURSOR_INTERVAL = 16; -const HUNDRED_PERCENT = 100; -const MAX_PERCENT = 400; export default class PresentationOverlay extends Component { constructor(props) { @@ -338,10 +336,10 @@ export default class PresentationOverlay extends Component { let newZoom = zoom; if (e.deltaY < 0) { - newZoom += 5; + newZoom += STEP; } if (e.deltaY > 0) { - newZoom -= 5; + newZoom -= STEP; } if (newZoom <= HUNDRED_PERCENT) { newZoom = HUNDRED_PERCENT; diff --git a/bigbluebutton-html5/imports/ui/components/presentation/presentation-toolbar/component.jsx b/bigbluebutton-html5/imports/ui/components/presentation/presentation-toolbar/component.jsx index e0eae079190897c60771a179262b83ff847ca075..123b2fda29413bd9f3caf5bb893863c9c2c1ce19 100644 --- a/bigbluebutton-html5/imports/ui/components/presentation/presentation-toolbar/component.jsx +++ b/bigbluebutton-html5/imports/ui/components/presentation/presentation-toolbar/component.jsx @@ -4,12 +4,10 @@ import { defineMessages, injectIntl, FormattedMessage } from 'react-intl'; import browser from 'browser-detect'; import injectWbResizeEvent from '/imports/ui/components/presentation/resize-wrapper/component'; import Button from '/imports/ui/components/button/component'; +import { HUNDRED_PERCENT, MAX_PERCENT, STEP } from '/imports/utils/slideCalcUtils'; import { styles } from './styles.scss'; import ZoomTool from './zoom-tool/component'; -const STEP = 5; -const HUNDRED_PERCENT = 100; -const MAX_PERCENT = 400; const intlMessages = defineMessages({ previousSlideLabel: { @@ -242,8 +240,8 @@ class PresentationToolbar extends Component { } { !isMobileBrowser ? - <span className={styles.zoomWrapper}> - <ZoomTool + <span className={styles.zoomWrapper}> + <ZoomTool value={zoom} change={this.change} minBound={HUNDRED_PERCENT} diff --git a/bigbluebutton-html5/imports/ui/components/presentation/presentation-toolbar/service.js b/bigbluebutton-html5/imports/ui/components/presentation/presentation-toolbar/service.js index a9c98a1c1ea011ea430ce08fc74f1b263a258208..6fade29e30d47e6014e8df0011bd18ff4a4d3852 100644 --- a/bigbluebutton-html5/imports/ui/components/presentation/presentation-toolbar/service.js +++ b/bigbluebutton-html5/imports/ui/components/presentation/presentation-toolbar/service.js @@ -6,15 +6,6 @@ import { makeCall } from '/imports/ui/services/api'; const getSlideData = (podId, presentationId) => { // Get meetingId and userId const meetingId = Auth.meetingID; - const userId = Auth.userID; - - // fetching the presentation pod in order to see who owns it - const selector = { - meetingId, - podId, - }; - const pod = PresentationPods.findOne(selector); - const userIsPresenter = pod.currentPresenterId === userId; // Get total number of slides in this presentation const numberOfSlides = Slides.find({ diff --git a/bigbluebutton-html5/imports/ui/components/presentation/presentation-toolbar/zoom-tool/component.jsx b/bigbluebutton-html5/imports/ui/components/presentation/presentation-toolbar/zoom-tool/component.jsx index 4a936a15e58aeebf9b88e248cfec0d2d87d08b8e..bccaba920aa0ad1ebe350db6f5c177d57dba5a85 100644 --- a/bigbluebutton-html5/imports/ui/components/presentation/presentation-toolbar/zoom-tool/component.jsx +++ b/bigbluebutton-html5/imports/ui/components/presentation/presentation-toolbar/zoom-tool/component.jsx @@ -12,42 +12,39 @@ export default class ZoomTool extends Component { static renderAriaLabelsDescs() { return ( <div hidden key="hidden-div"> - {/* Zoom in button aria */} <div id="zoomInLabel"> <FormattedMessage id="app.presentation.presentationToolbar.zoomInLabel" - description="Aria label for when switching to previous slide" - defaultMessage="Previous slide" + description="Aria label for increment zoom level" + defaultMessage="Increment zoom" /> </div> <div id="zoomInDesc"> <FormattedMessage id="app.presentation.presentationToolbar.zoomInDesc" - description="Aria description for when switching to previous slide" - defaultMessage="Change the presentation to the previous slide" + description="Aria description for increment zoom level" + defaultMessage="Increment zoom" /> </div> - {/* Zoom out button aria */} <div id="zoomOutLabel"> <FormattedMessage id="app.presentation.presentationToolbar.zoomOutLabel" - description="Aria label for when switching to next slide" - defaultMessage="Next slide" + description="Aria label for decrement zoom level" + defaultMessage="Decrement zoom" /> </div> <div id="zoomOutDesc"> <FormattedMessage id="app.presentation.presentationToolbar.zoomOutDesc" - description="Aria description for when switching to next slide" - defaultMessage="Change the presentation to the next slide" + description="Aria description for decrement zoom level" + defaultMessage="Decrement zoom" /> </div> - {/* Zoom indicator aria */} <div id="zoomIndicator"> <FormattedMessage id="app.presentation.presentationToolbar.zoomIndicator" - description="Aria label for when switching to a specific slide" - defaultMessage="Skip slide" + description="Aria label for current zoom level" + defaultMessage="Current zoom level" /> </div> </div> @@ -160,8 +157,8 @@ export default class ZoomTool extends Component { > <Button key="zoom-tool-1" - aria-labelledby="zoomInLabel" - aria-describedby="zoomInDesc" + aria-labelledby="zoomOutLabel" + aria-describedby="zoomOutDesc" role="button" label="-" icon="minus" @@ -175,7 +172,7 @@ export default class ZoomTool extends Component { ( <span key="zoom-tool-2" - aria-labelledby="prevSlideLabel" + aria-labelledby="zoomIndicator" aria-describedby={this.state.value} className={styles.zoomPercentageDisplay} > @@ -191,8 +188,8 @@ export default class ZoomTool extends Component { > <Button key="zoom-tool-3" - aria-labelledby="zoomOutLabel" - aria-describedby="zoomOutDesc" + aria-labelledby="zoomInLabel" + aria-describedby="zoomInDesc" role="button" label="+" icon="plus" diff --git a/bigbluebutton-html5/imports/ui/components/presentation/presentation-toolbar/zoom-tool/holdButton/component.jsx b/bigbluebutton-html5/imports/ui/components/presentation/presentation-toolbar/zoom-tool/holdButton/component.jsx index dea39ebaaa3c481f4117eb0006b8716eee9b994a..7619456b140bc597ceff786ec093e0f1bb357079 100644 --- a/bigbluebutton-html5/imports/ui/components/presentation/presentation-toolbar/zoom-tool/holdButton/component.jsx +++ b/bigbluebutton-html5/imports/ui/components/presentation/presentation-toolbar/zoom-tool/holdButton/component.jsx @@ -80,15 +80,15 @@ class HoldDownButton extends Component { render() { const { - key, + uniqueKey, className, children, } = this.props; - + return ( <span role="button" - key={key} + key={uniqueKey} onClick={this.onClick} onMouseDown={this.mouseDownHandler} onMouseUp={this.mouseUpHandler} @@ -107,12 +107,12 @@ const defaultProps = { exec: () => {}, minBound: null, maxBound: Infinity, - key: _.uniqueId('holdButton-'), + uniqueKey: _.uniqueId('holdButton-'), value: 0, }; const propTypes = { - key: PropTypes.string, + uniqueKey: PropTypes.string, exec: PropTypes.func.isRequired, minBound: PropTypes.number, maxBound: PropTypes.number, diff --git a/bigbluebutton-html5/imports/ui/components/presentation/presentation-uploader/component.jsx b/bigbluebutton-html5/imports/ui/components/presentation/presentation-uploader/component.jsx index 01309c44d15bdf3adb442d8fbe9f5f2b950fedb5..cd768057c610b66f7944445d5fed5837bdda578a 100755 --- a/bigbluebutton-html5/imports/ui/components/presentation/presentation-uploader/component.jsx +++ b/bigbluebutton-html5/imports/ui/components/presentation/presentation-uploader/component.jsx @@ -145,12 +145,11 @@ class PresentationUploader extends Component { return fileIndex === -1 ? false : { presentations: update(presentations, { [fileIndex]: { - $apply: file => - update(file, { - [key]: { - [operation]: value, - }, - }), + $apply: file => update(file, { + [key]: { + [operation]: value, + }, + }), }, }), }; @@ -383,8 +382,7 @@ class PresentationUploader extends Component { }); } - const conversionStatusMessage = - intlMessages[item.conversion.status] || intlMessages.genericConversionStatus; + const conversionStatusMessage = intlMessages[item.conversion.status] || intlMessages.genericConversionStatus; return intl.formatMessage(conversionStatusMessage); } @@ -419,13 +417,15 @@ class PresentationUploader extends Component { <Icon iconName="file" /> </td> { - isActualCurrent ? - <th className={styles.tableItemCurrent}> - <span className={styles.currentLabel}> - {this.props.intl.formatMessage(intlMessages.current)} - </span> - </th> - : null + isActualCurrent + ? ( + <th className={styles.tableItemCurrent}> + <span className={styles.currentLabel}> + {this.props.intl.formatMessage(intlMessages.current)} + </span> + </th> + ) + : null } <th className={styles.tableItemName} colSpan={!isActualCurrent ? 2 : 0}> <span>{item.filename}</span> @@ -479,12 +479,13 @@ class PresentationUploader extends Component { accept={fileValidMimeTypes.join()} minSize={fileSizeMin} maxSize={fileSizeMax} - disablePreview + disablepreview="true" onDrop={this.handleFiledrop} > <Icon className={styles.dropzoneIcon} iconName="upload" /> <p className={styles.dropzoneMessage}> - {intl.formatMessage(intlMessages.dropzoneLabel)} + {intl.formatMessage(intlMessages.dropzoneLabel)} + <span className={styles.dropzoneLink}> {intl.formatMessage(intlMessages.browseFilesLabel)} </span> diff --git a/bigbluebutton-html5/imports/ui/components/settings/submenus/application/component.jsx b/bigbluebutton-html5/imports/ui/components/settings/submenus/application/component.jsx index cbd75e98268a1b093451823559d97b2102a84837..9605cf4f5d7d01bcc12ccf60049366c34af42944 100644 --- a/bigbluebutton-html5/imports/ui/components/settings/submenus/application/component.jsx +++ b/bigbluebutton-html5/imports/ui/components/settings/submenus/application/component.jsx @@ -122,8 +122,8 @@ class ApplicationMenu extends BaseMenu { const { isLargestFontSize, isSmallestFontSize } = this.state; return ( - <div className={styles.tabContent}> - <div className={styles.header}> + <div> + <div> <h3 className={styles.title}> {intl.formatMessage(intlMessages.applicationSectionTitle)} </h3> @@ -223,24 +223,24 @@ class ApplicationMenu extends BaseMenu { <div className={styles.pullContentRight}> <div className={styles.col}> <Button - onClick={() => this.handleIncreaseFontSize()} + onClick={() => this.handleDecreaseFontSize()} color="primary" - icon="add" + icon="substract" circle hideLabel - label={intl.formatMessage(intlMessages.increaseFontBtnLabel)} - disabled={isLargestFontSize} + label={intl.formatMessage(intlMessages.decreaseFontBtnLabel)} + disabled={isSmallestFontSize} /> </div> <div className={styles.col}> <Button - onClick={() => this.handleDecreaseFontSize()} + onClick={() => this.handleIncreaseFontSize()} color="primary" - icon="substract" + icon="add" circle hideLabel - label={intl.formatMessage(intlMessages.decreaseFontBtnLabel)} - disabled={isSmallestFontSize} + label={intl.formatMessage(intlMessages.increaseFontBtnLabel)} + disabled={isLargestFontSize} /> </div> </div> diff --git a/bigbluebutton-html5/imports/ui/components/settings/submenus/closed-captions/component.jsx b/bigbluebutton-html5/imports/ui/components/settings/submenus/closed-captions/component.jsx index faaec2072d02fbe0a445c42ca06831edeee63474..12f4f34be11a35682c57dba80f741c3160fb8a82 100644 --- a/bigbluebutton-html5/imports/ui/components/settings/submenus/closed-captions/component.jsx +++ b/bigbluebutton-html5/imports/ui/components/settings/submenus/closed-captions/component.jsx @@ -1,11 +1,11 @@ import React from 'react'; -import { styles } from '../styles'; import cx from 'classnames'; -import BaseMenu from '../base/component'; import Toggle from '/imports/ui/components/switch/component'; import Checkbox from '/imports/ui/components/checkbox/component'; import { GithubPicker } from 'react-color'; import { defineMessages, injectIntl } from 'react-intl'; +import BaseMenu from '../base/component'; +import { styles } from '../styles'; // an array of font-families const FONT_FAMILIES = ['Arial', 'Calibri', 'Time New Roman', 'Sans-serif']; @@ -131,8 +131,8 @@ class ClosedCaptionsMenu extends BaseMenu { } = this.props; return ( - <div className={styles.tabContent}> - <div className={styles.header}> + <div> + <div> <h3 className={styles.title}>{intl.formatMessage(intlMessages.closedCaptionsLabel)}</h3> </div> <div className={styles.form}> @@ -145,7 +145,7 @@ class ClosedCaptionsMenu extends BaseMenu { </div> </div> <div className={styles.col}> - <div className={cx(styles.formElement, styles.pullContentRight)} > + <div className={cx(styles.formElement, styles.pullContentRight)}> <Toggle icons={false} defaultChecked={this.state.settings.enabled} @@ -156,223 +156,234 @@ class ClosedCaptionsMenu extends BaseMenu { </div> </div> </div> - { this.state.settings.enabled ? - <div> - { isModerator ? + { this.state.settings.enabled + ? ( + <div> + { isModerator + ? ( + <div className={cx(styles.row, styles.spacedLeft)}> + <div className={styles.col}> + <div className={styles.formElement}> + <label className={styles.label}> + {intl.formatMessage(intlMessages.takeOwnershipLabel)} + </label> + </div> + </div> + <div className={styles.col}> + <div className={cx(styles.formElement, styles.pullContentRight)}> + <Checkbox + onChange={() => this.handleToggle('takeOwnership')} + checked={this.state.settings.takeOwnership} + ariaLabelledBy="takeOwnership" + ariaLabel={intl.formatMessage(intlMessages.takeOwnershipLabel)} + /> + </div> + </div> + </div> + ) + : null } <div className={cx(styles.row, styles.spacedLeft)}> <div className={styles.col}> <div className={styles.formElement}> <label className={styles.label}> - {intl.formatMessage(intlMessages.takeOwnershipLabel)} + {intl.formatMessage(intlMessages.languageLabel)} </label> </div> </div> <div className={styles.col}> - <div className={cx(styles.formElement, styles.pullContentRight)}> - <Checkbox - onChange={() => this.handleToggle('takeOwnership')} - checked={this.state.settings.takeOwnership} - ariaLabelledBy="takeOwnership" - ariaLabel={intl.formatMessage(intlMessages.takeOwnershipLabel)} - /> - </div> - </div> - </div> - : null } - <div className={cx(styles.row, styles.spacedLeft)}> - <div className={styles.col}> - <div className={styles.formElement}> - <label className={styles.label}> - {intl.formatMessage(intlMessages.languageLabel)} + <label + className={cx(styles.formElement, styles.pullContentRight)} + aria-label={intl.formatMessage(intlMessages.languageLabel)} + > + <select + defaultValue={locales ? locales.indexOf(this.state.settings.locale) : -1} + className={styles.select} + onChange={this.handleSelectChange.bind(this, 'locale', this.props.locales)} + > + <option> + { this.props.locales + && this.props.locales.length + ? intl.formatMessage(intlMessages.localeOptionLabel) + : intl.formatMessage(intlMessages.noLocaleOptionLabel) } + </option> + {this.props.locales ? this.props.locales.map((locale, index) => ( + <option key={index} value={index}> + {locale} + </option> + )) : null } + </select> </label> </div> </div> - <div className={styles.col}> - <label - className={cx(styles.formElement, styles.pullContentRight)} - aria-label={intl.formatMessage(intlMessages.languageLabel)} - > - <select - defaultValue={locales ? locales.indexOf(this.state.settings.locale) : -1} - className={styles.select} - onChange={this.handleSelectChange.bind(this, 'locale', this.props.locales)} - > - <option> - { this.props.locales && - this.props.locales.length ? - intl.formatMessage(intlMessages.localeOptionLabel) : - intl.formatMessage(intlMessages.noLocaleOptionLabel) } - </option> - {this.props.locales ? this.props.locales.map((locale, index) => - (<option key={index} value={index}> - {locale} - </option>)) : null } - </select> - </label> - </div> - </div> - <div className={cx(styles.row, styles.spacedLeft)}> - <div className={styles.col}> - <div className={styles.formElement}> - <label className={styles.label}> - {intl.formatMessage(intlMessages.fontFamilyLabel)} - </label> + <div className={cx(styles.row, styles.spacedLeft)}> + <div className={styles.col}> + <div className={styles.formElement}> + <label className={styles.label}> + {intl.formatMessage(intlMessages.fontFamilyLabel)} + </label> + </div> </div> - </div> - <div className={styles.col}> - <label - className={cx(styles.formElement, styles.pullContentRight)} - aria-label={intl.formatMessage(intlMessages.fontFamilyLabel)} - > - <select - defaultValue={FONT_FAMILIES.indexOf(this.state.settings.fontFamily)} - onChange={this.handleSelectChange.bind(this, 'fontFamily', FONT_FAMILIES)} - className={styles.select} + <div className={styles.col}> + <label + className={cx(styles.formElement, styles.pullContentRight)} + aria-label={intl.formatMessage(intlMessages.fontFamilyLabel)} > - <option value="-1" disabled> - {intl.formatMessage(intlMessages.fontFamilyOptionLabel)} - </option> - { - FONT_FAMILIES.map((family, index) => - (<option key={index} value={index}> + <select + defaultValue={FONT_FAMILIES.indexOf(this.state.settings.fontFamily)} + onChange={this.handleSelectChange.bind(this, 'fontFamily', FONT_FAMILIES)} + className={styles.select} + > + <option value="-1" disabled> + {intl.formatMessage(intlMessages.fontFamilyOptionLabel)} + </option> + { + FONT_FAMILIES.map((family, index) => ( + <option key={index} value={index}> {family} - </option>)) + </option> + )) } - </select> - </label> - </div> - </div> - - <div className={cx(styles.row, styles.spacedLeft)}> - <div className={styles.col}> - <div className={styles.formElement}> - <label className={styles.label}> - {intl.formatMessage(intlMessages.fontSizeLabel)} + </select> </label> </div> </div> - <div className={styles.col}> - <label - className={cx(styles.formElement, styles.pullContentRight)} - aria-label={intl.formatMessage(intlMessages.fontSizeLabel)} - > - <select - defaultValue={FONT_SIZES.indexOf(this.state.settings.fontSize)} - onChange={this.handleSelectChange.bind(this, 'fontSize', FONT_SIZES)} - className={styles.select} + + <div className={cx(styles.row, styles.spacedLeft)}> + <div className={styles.col}> + <div className={styles.formElement}> + <label className={styles.label}> + {intl.formatMessage(intlMessages.fontSizeLabel)} + </label> + </div> + </div> + <div className={styles.col}> + <label + className={cx(styles.formElement, styles.pullContentRight)} + aria-label={intl.formatMessage(intlMessages.fontSizeLabel)} > - <option value="-1" disabled> - {intl.formatMessage(intlMessages.fontSizeOptionLabel)} - </option> - { - FONT_SIZES.map((size, index) => - (<option key={index} value={index}> + <select + defaultValue={FONT_SIZES.indexOf(this.state.settings.fontSize)} + onChange={this.handleSelectChange.bind(this, 'fontSize', FONT_SIZES)} + className={styles.select} + > + <option value="-1" disabled> + {intl.formatMessage(intlMessages.fontSizeOptionLabel)} + </option> + { + FONT_SIZES.map((size, index) => ( + <option key={index} value={index}> {size} - </option>)) + </option> + )) } - </select> - </label> - </div> - </div> - - <div className={cx(styles.row, styles.spacedLeft)}> - <div className={styles.col}> - <div className={styles.formElement}> - <label className={styles.label}> - {intl.formatMessage(intlMessages.backgroundColorLabel)} + </select> </label> </div> </div> - <div className={styles.col}> - <div - className={cx(styles.formElement, styles.pullContentRight)} - aria-label={intl.formatMessage(intlMessages.backgroundColorLabel)} - > + + <div className={cx(styles.row, styles.spacedLeft)}> + <div className={styles.col}> + <div className={styles.formElement}> + <label className={styles.label}> + {intl.formatMessage(intlMessages.backgroundColorLabel)} + </label> + </div> + </div> + <div className={styles.col}> <div - tabIndex="0" - className={styles.swatch} - onClick={ - this.handleColorPickerClick.bind(this, 'displayBackgroundColorPicker') - } + className={cx(styles.formElement, styles.pullContentRight)} + aria-label={intl.formatMessage(intlMessages.backgroundColorLabel)} > <div - className={styles.swatchInner} - style={{ background: this.state.settings.backgroundColor }} - /> - </div> - { this.state.displayBackgroundColorPicker ? - <div className={styles.colorPickerPopover}> + tabIndex="0" + className={styles.swatch} + onClick={ + this.handleColorPickerClick.bind(this, 'displayBackgroundColorPicker') + } + > <div - className={styles.colorPickerOverlay} - onClick={this.handleCloseColorPicker.bind(this)} - /> - <GithubPicker - onChange={this.handleColorChange.bind(this, 'backgroundColor')} - color={this.state.settings.backgroundColor} - colors={COLORS} - width="140px" - triangle="top-right" + className={styles.swatchInner} + style={{ background: this.state.settings.backgroundColor }} /> </div> - : null } + { this.state.displayBackgroundColorPicker + ? ( + <div className={styles.colorPickerPopover}> + <div + className={styles.colorPickerOverlay} + onClick={this.handleCloseColorPicker.bind(this)} + /> + <GithubPicker + onChange={this.handleColorChange.bind(this, 'backgroundColor')} + color={this.state.settings.backgroundColor} + colors={COLORS} + width="140px" + triangle="top-right" + /> + </div> + ) + : null } + </div> </div> </div> - </div> - <div className={cx(styles.row, styles.spacedLeft)}> - <div className={styles.col}> - <div className={styles.formElement}> - <label className={styles.label}> - {intl.formatMessage(intlMessages.fontColorLabel)} - </label> + <div className={cx(styles.row, styles.spacedLeft)}> + <div className={styles.col}> + <div className={styles.formElement}> + <label className={styles.label}> + {intl.formatMessage(intlMessages.fontColorLabel)} + </label> + </div> </div> - </div> - <div className={styles.col}> - <div - className={cx(styles.formElement, styles.pullContentRight)} - aria-label={intl.formatMessage(intlMessages.fontColorLabel)} - > + <div className={styles.col}> <div - tabIndex="0" - className={styles.swatch} - onClick={this.handleColorPickerClick.bind(this, 'displayFontColorPicker')} + className={cx(styles.formElement, styles.pullContentRight)} + aria-label={intl.formatMessage(intlMessages.fontColorLabel)} > <div - className={styles.swatchInner} - style={{ background: this.state.settings.fontColor }} - /> - </div> - { this.state.displayFontColorPicker ? - <div className={styles.colorPickerPopover}> + tabIndex="0" + className={styles.swatch} + onClick={this.handleColorPickerClick.bind(this, 'displayFontColorPicker')} + > <div - className={styles.colorPickerOverlay} - onClick={this.handleCloseColorPicker.bind(this)} - /> - <GithubPicker - onChange={this.handleColorChange.bind(this, 'fontColor')} - color={this.state.settings.fontColor} - colors={COLORS} - width="140px" - triangle="top-right" + className={styles.swatchInner} + style={{ background: this.state.settings.fontColor }} /> </div> - : null } + { this.state.displayFontColorPicker + ? ( + <div className={styles.colorPickerPopover}> + <div + className={styles.colorPickerOverlay} + onClick={this.handleCloseColorPicker.bind(this)} + /> + <GithubPicker + onChange={this.handleColorChange.bind(this, 'fontColor')} + color={this.state.settings.fontColor} + colors={COLORS} + width="140px" + triangle="top-right" + /> + </div> + ) + : null } + </div> </div> </div> - </div> - <div - className={cx(styles.ccPreviewBox, styles.spacedLeft)} - role="presentation" - style={{ background: this.state.settings.backgroundColor }} - > - <span style={this.getPreviewStyle()}> + <div + className={cx(styles.ccPreviewBox, styles.spacedLeft)} + role="presentation" + style={{ background: this.state.settings.backgroundColor }} + > + <span style={this.getPreviewStyle()}> Etiam porta sem malesuada magna mollis euis-mod. Donec ullamcorper nulla non metus auctor fringilla. - </span> + </span> + </div> </div> - </div> - : null } + ) + : null } </div> </div> ); diff --git a/bigbluebutton-html5/imports/ui/components/settings/submenus/data-saving/component.jsx b/bigbluebutton-html5/imports/ui/components/settings/submenus/data-saving/component.jsx index ef1de1585e81a1c4921fcb5850c1a1f015fa4768..a7b1446c1eff566db4e20de8bf4c4aedd0ac414a 100644 --- a/bigbluebutton-html5/imports/ui/components/settings/submenus/data-saving/component.jsx +++ b/bigbluebutton-html5/imports/ui/components/settings/submenus/data-saving/component.jsx @@ -33,14 +33,15 @@ class DataSaving extends BaseMenu { settings: props.settings, }; } + render() { const { intl } = this.props; const { viewParticipantsWebcams, viewScreenshare } = this.state.settings; return ( - <div className={styles.tabContent}> - <div className={styles.header}> + <div> + <div> <h3 className={styles.title}>{intl.formatMessage(intlMessages.dataSavingLabel)}</h3> <p className={styles.subtitle}>{intl.formatMessage(intlMessages.dataSavingDesc)}</p> </div> diff --git a/bigbluebutton-html5/imports/ui/components/tooltip/component.jsx b/bigbluebutton-html5/imports/ui/components/tooltip/component.jsx index 2b20c0193d069e15624def5f95a2dec199fdc9e1..981fde80ad20205d98f4544069290d52f27acc68 100755 --- a/bigbluebutton-html5/imports/ui/components/tooltip/component.jsx +++ b/bigbluebutton-html5/imports/ui/components/tooltip/component.jsx @@ -18,12 +18,12 @@ const defaultProps = { }; class Tooltip extends Component { - static wait(show, event) { + static wait(tip, event) { const tooltipTarget = event.target; const expandedEl = tooltipTarget.parentElement.querySelector('[aria-expanded="true"]'); const isTarget = expandedEl === tooltipTarget; if (expandedEl && !isTarget) return; - show(); + tip.show(); } constructor(props) { @@ -34,17 +34,18 @@ class Tooltip extends Component { this.onHide = this.onHide.bind(this); this.handleEscapeHide = this.handleEscapeHide.bind(this); this.delay = [150, 50]; - this.dynamicTitle = true; } componentDidMount() { const { position, + title, } = this.props; const options = { - position, - dynamicTitle: this.dynamicTitle, + placement: position, + performance: true, + content: title, delay: this.delay, onShow: this.onShow, onHide: this.onHide, @@ -53,6 +54,7 @@ class Tooltip extends Component { }; this.tooltip = Tippy(`#${this.tippySelectorId}`, options); } + onShow() { document.addEventListener('keyup', this.handleEscapeHide); } diff --git a/bigbluebutton-html5/imports/ui/components/user-list/chat-list-item/chat-icon/component.jsx b/bigbluebutton-html5/imports/ui/components/user-list/chat-list-item/chat-icon/component.jsx index 0dc1867befbd1b47d59493a1106ca2472edabdb5..0fcfa8908cd6ac4eab4c3061ee2c670e786cc25e 100644 --- a/bigbluebutton-html5/imports/ui/components/user-list/chat-list-item/chat-icon/component.jsx +++ b/bigbluebutton-html5/imports/ui/components/user-list/chat-list-item/chat-icon/component.jsx @@ -12,7 +12,7 @@ const defaultProps = { const ChatIcon = props => ( <div className={styles.chatThumbnail}> - <Icon iconName={props.icon} className={styles.actionIcon} /> + <Icon iconName={props.icon} /> </div> ); diff --git a/bigbluebutton-html5/imports/ui/components/user-list/chat-list-item/component.jsx b/bigbluebutton-html5/imports/ui/components/user-list/chat-list-item/component.jsx index 59231a64096da978fe216bf09cf215535b0e1288..b4cb9c1a48c6d9404a01bd66c1d4cf2ae2b283e6 100644 --- a/bigbluebutton-html5/imports/ui/components/user-list/chat-list-item/component.jsx +++ b/bigbluebutton-html5/imports/ui/components/user-list/chat-list-item/component.jsx @@ -67,6 +67,7 @@ const ChatListItem = (props) => { return ( <div + data-test="chatButton" role="button" className={cx(styles.chatListItem, linkClasses)} aria-expanded={isCurrentChat} @@ -87,26 +88,31 @@ const ChatListItem = (props) => { <div className={styles.chatListItemLink}> <div className={styles.chatIcon}> - {chat.icon ? - <ChatIcon icon={chat.icon} /> - : - <ChatAvatar - isModerator={chat.isModerator} - color={chat.color} - name={chat.name.toLowerCase().slice(0, 2)} - />} + {chat.icon + ? <ChatIcon icon={chat.icon} /> + : ( + <ChatAvatar + isModerator={chat.isModerator} + color={chat.color} + name={chat.name.toLowerCase().slice(0, 2)} + /> + )} </div> <div className={styles.chatName}> - {!compact ? - <span className={styles.chatNameMain}> - {isPublicChat(chat) ? - intl.formatMessage(intlMessages.titlePublic) : chat.name} - </span> : null} + {!compact + ? ( + <span className={styles.chatNameMain}> + {isPublicChat(chat) + ? intl.formatMessage(intlMessages.titlePublic) : chat.name} + </span> + ) : null} </div> - {(chat.unreadCounter > 0) ? - <ChatUnreadCounter - counter={chat.unreadCounter} - /> + {(chat.unreadCounter > 0) + ? ( + <ChatUnreadCounter + counter={chat.unreadCounter} + /> + ) : null} </div> </div> diff --git a/bigbluebutton-html5/imports/ui/components/user-list/component.jsx b/bigbluebutton-html5/imports/ui/components/user-list/component.jsx index 2ca157964d55e562df398aa040c3b2cfe5b81430..8b6c91d3956da8cae04e7840a65f0f58a8845e2c 100755 --- a/bigbluebutton-html5/imports/ui/components/user-list/component.jsx +++ b/bigbluebutton-html5/imports/ui/components/user-list/component.jsx @@ -1,4 +1,4 @@ -import React, { Component } from 'react'; +import React, { PureComponent } from 'react'; import { injectIntl } from 'react-intl'; import PropTypes from 'prop-types'; import injectWbResizeEvent from '/imports/ui/components/presentation/resize-wrapper/component'; @@ -8,13 +8,14 @@ import UserContentContainer from './user-list-content/container'; const propTypes = { openChats: PropTypes.arrayOf(String).isRequired, - users: PropTypes.arrayOf(Object).isRequired, compact: PropTypes.bool, intl: PropTypes.shape({ formatMessage: PropTypes.func.isRequired, }).isRequired, currentUser: PropTypes.shape({}).isRequired, - meeting: PropTypes.shape({}), + CustomLogoUrl: PropTypes.string.isRequired, + handleEmojiChange: PropTypes.func.isRequired, + getUsersId: PropTypes.func.isRequired, isBreakoutRoom: PropTypes.bool, getAvailableActions: PropTypes.func.isRequired, normalizeEmojiName: PropTypes.func.isRequired, @@ -24,6 +25,8 @@ const propTypes = { assignPresenter: PropTypes.func.isRequired, removeUser: PropTypes.func.isRequired, toggleVoice: PropTypes.func.isRequired, + muteAllUsers: PropTypes.func.isRequired, + muteAllExceptPresenter: PropTypes.func.isRequired, changeRole: PropTypes.func.isRequired, roving: PropTypes.func.isRequired, getGroupChatPrivate: PropTypes.func.isRequired, @@ -33,17 +36,13 @@ const propTypes = { const defaultProps = { compact: false, isBreakoutRoom: false, - // This one is kinda tricky, meteor takes sometime to fetch the data and passing down - // So the first time its create, the meeting comes as null, sending an error to the client. - meeting: {}, }; -class UserList extends Component { +class UserList extends PureComponent { render() { const { intl, openChats, - users, compact, currentUser, isBreakoutRoom, @@ -51,8 +50,9 @@ class UserList extends Component { assignPresenter, removeUser, toggleVoice, + muteAllUsers, + muteAllExceptPresenter, changeRole, - meeting, getAvailableActions, normalizeEmojiName, isMeetingLocked, @@ -65,6 +65,7 @@ class UserList extends Component { getEmoji, showBranding, hasBreakoutRoom, + getUsersId, } = this.props; return ( @@ -79,7 +80,6 @@ class UserList extends Component { {...{ intl, openChats, - users, compact, currentUser, isBreakoutRoom, @@ -87,8 +87,9 @@ class UserList extends Component { assignPresenter, removeUser, toggleVoice, + muteAllUsers, + muteAllExceptPresenter, changeRole, - meeting, getAvailableActions, normalizeEmojiName, isMeetingLocked, @@ -99,6 +100,7 @@ class UserList extends Component { getEmojiList, getEmoji, hasBreakoutRoom, + getUsersId, } } />} diff --git a/bigbluebutton-html5/imports/ui/components/user-list/container.jsx b/bigbluebutton-html5/imports/ui/components/user-list/container.jsx index e69c8fb4db700e369d2dd3a36286654350cced5b..8599e4c68d082e6751bfb0d8ae677903bf2c5bb9 100755 --- a/bigbluebutton-html5/imports/ui/components/user-list/container.jsx +++ b/bigbluebutton-html5/imports/ui/components/user-list/container.jsx @@ -2,16 +2,14 @@ import React from 'react'; import PropTypes from 'prop-types'; import { withTracker } from 'meteor/react-meteor-data'; import { meetingIsBreakout } from '/imports/ui/components/app/service'; -import Meetings from '/imports/api/meetings'; import getFromUserSettings from '/imports/ui/services/users-settings'; import Service from './service'; import UserList from './component'; const propTypes = { openChats: PropTypes.arrayOf(String).isRequired, - users: PropTypes.arrayOf(Object).isRequired, currentUser: PropTypes.shape({}).isRequired, - meeting: PropTypes.shape({}).isRequired, + getUsersId: PropTypes.func.isRequired, isBreakoutRoom: PropTypes.bool.isRequired, getAvailableActions: PropTypes.func.isRequired, normalizeEmojiName: PropTypes.func.isRequired, @@ -21,6 +19,8 @@ const propTypes = { assignPresenter: PropTypes.func.isRequired, removeUser: PropTypes.func.isRequired, toggleVoice: PropTypes.func.isRequired, + muteAllUsers: PropTypes.func.isRequired, + muteAllExceptPresenter: PropTypes.func.isRequired, changeRole: PropTypes.func.isRequired, roving: PropTypes.func.isRequired, getGroupChatPrivate: PropTypes.func.isRequired, @@ -31,9 +31,8 @@ const UserListContainer = props => <UserList {...props} />; UserListContainer.propTypes = propTypes; export default withTracker(({ chatID, compact }) => ({ - users: Service.getUsers(), - meeting: Meetings.findOne({}), hasBreakoutRoom: Service.hasBreakoutRoom(), + getUsersId: Service.getUsersId, currentUser: Service.getCurrentUser(), openChats: Service.getOpenChats(chatID), isBreakoutRoom: meetingIsBreakout(), @@ -45,6 +44,8 @@ export default withTracker(({ chatID, compact }) => ({ assignPresenter: Service.assignPresenter, removeUser: Service.removeUser, toggleVoice: Service.toggleVoice, + muteAllUsers: Service.muteAllUsers, + muteAllExceptPresenter: Service.muteAllExceptPresenter, changeRole: Service.changeRole, roving: Service.roving, CustomLogoUrl: Service.getCustomLogoUrl(), diff --git a/bigbluebutton-html5/imports/ui/components/user-list/service.js b/bigbluebutton-html5/imports/ui/components/user-list/service.js index 28e8c2543b4f281b3d5f2561dcc0bfca7ddc41be..dbae3223c6cf128e43ac7f5119f273706d1541d9 100755 --- a/bigbluebutton-html5/imports/ui/components/user-list/service.js +++ b/bigbluebutton-html5/imports/ui/components/user-list/service.js @@ -12,9 +12,6 @@ import { makeCall } from '/imports/ui/services/api'; import _ from 'lodash'; import KEY_CODES from '/imports/utils/keyCodes'; -const APP_CONFIG = Meteor.settings.public.app; -const ALLOW_MODERATOR_TO_UNMUTE_AUDIO = APP_CONFIG.allowModeratorToUnmuteAudio; - const CHAT_CONFIG = Meteor.settings.public.chat; const PUBLIC_GROUP_CHAT_ID = CHAT_CONFIG.public_group_id; @@ -191,6 +188,8 @@ const getUsers = () => { .sort(sortUsers); }; +const getUsersId = () => getUsers().map(u => u.id); + const hasBreakoutRoom = () => Breakouts.find({ parentMeetingId: Auth.meetingID }).count() > 0; const getOpenChats = (chatID) => { @@ -277,7 +276,7 @@ const getAvailableActions = (currentUser, user, isBreakoutRoom) => { && user.isVoiceUser && !user.isListenOnly && user.isMuted - && (ALLOW_MODERATOR_TO_UNMUTE_AUDIO || user.isCurrent); + && user.isCurrent; const allowedToResetStatus = hasAuthority && user.emoji.status !== EMOJI_STATUSES.none @@ -364,6 +363,10 @@ const removeUser = (userId) => { const toggleVoice = (userId) => { userId === Auth.userID ? makeCall('toggleSelfVoice') : makeCall('toggleVoice', userId); }; +const muteAllUsers = (userId) => { makeCall('muteAllUsers', userId); }; + +const muteAllExceptPresenter = (userId) => { makeCall('muteAllExceptPresenter', userId); }; + const changeRole = (userId, role) => { makeCall('changeRole', userId, role); }; const roving = (event, itemCount, changeState) => { @@ -438,8 +441,11 @@ export default { assignPresenter, removeUser, toggleVoice, + muteAllUsers, + muteAllExceptPresenter, changeRole, getUsers, + getUsersId, getOpenChats, getCurrentUser, getAvailableActions, diff --git a/bigbluebutton-html5/imports/ui/components/user-list/styles.scss b/bigbluebutton-html5/imports/ui/components/user-list/styles.scss index d8de801266d015d2bfbf6c4b70f90fdc9399518c..50e54ffaf1e03fc3259b88ea863cba5b177194ad 100755 --- a/bigbluebutton-html5/imports/ui/components/user-list/styles.scss +++ b/bigbluebutton-html5/imports/ui/components/user-list/styles.scss @@ -121,6 +121,11 @@ color: var(--color-gray-light); } +.userListColumn { + @extend %flex-column; + min-height: 0; +} + .enter, .appear { opacity: 0.01; diff --git a/bigbluebutton-html5/imports/ui/components/user-list/user-list-content/breakout-room/component.jsx b/bigbluebutton-html5/imports/ui/components/user-list/user-list-content/breakout-room/component.jsx index b0df572d51606acd8334bad0fa48b803adb27d11..5638e6ce9d2fb05eb41a9c1dca4a13e163d305ce 100644 --- a/bigbluebutton-html5/imports/ui/components/user-list/user-list-content/breakout-room/component.jsx +++ b/bigbluebutton-html5/imports/ui/components/user-list/user-list-content/breakout-room/component.jsx @@ -1,5 +1,4 @@ -import React, { Component } from 'react'; -import PropTypes from 'prop-types'; +import React from 'react'; import { defineMessages, injectIntl } from 'react-intl'; import { Session } from 'meteor/session'; import Icon from '/imports/ui/components/icon/component'; diff --git a/bigbluebutton-html5/imports/ui/components/user-list/user-list-content/component.jsx b/bigbluebutton-html5/imports/ui/components/user-list/user-list-content/component.jsx index e6f58256aea8e253a9820b5e94458183f5d075f4..17188a23ad545946db55e7b762205b9c85fdf38b 100644 --- a/bigbluebutton-html5/imports/ui/components/user-list/user-list-content/component.jsx +++ b/bigbluebutton-html5/imports/ui/components/user-list/user-list-content/component.jsx @@ -1,20 +1,18 @@ -import React from 'react'; +import React, { PureComponent } from 'react'; import PropTypes from 'prop-types'; import { styles } from './styles'; -import UserParticipants from './user-participants/component'; +import UserParticipantsContainer from './user-participants/container'; import UserMessages from './user-messages/component'; import UserPolls from './user-polls/component'; import BreakoutRoomItem from './breakout-room/component'; const propTypes = { openChats: PropTypes.arrayOf(String).isRequired, - users: PropTypes.arrayOf(Object).isRequired, compact: PropTypes.bool, intl: PropTypes.shape({ formatMessage: PropTypes.func.isRequired, }).isRequired, currentUser: PropTypes.shape({}).isRequired, - meeting: PropTypes.shape({}), isBreakoutRoom: PropTypes.bool, getAvailableActions: PropTypes.func.isRequired, normalizeEmojiName: PropTypes.func.isRequired, @@ -24,32 +22,35 @@ const propTypes = { assignPresenter: PropTypes.func.isRequired, removeUser: PropTypes.func.isRequired, toggleVoice: PropTypes.func.isRequired, + muteAllUsers: PropTypes.func.isRequired, + muteAllExceptPresenter: PropTypes.func.isRequired, changeRole: PropTypes.func.isRequired, roving: PropTypes.func.isRequired, getGroupChatPrivate: PropTypes.func.isRequired, + handleEmojiChange: PropTypes.func.isRequired, + getUsersId: PropTypes.func.isRequired, + pollIsOpen: PropTypes.bool.isRequired, + forcePollOpen: PropTypes.bool.isRequired, }; const defaultProps = { compact: false, isBreakoutRoom: false, - // This one is kinda tricky, meteor takes sometime to fetch the data and passing down - // So the first time its create, the meeting comes as null, sending an error to the client. - meeting: {}, }; -class UserContent extends React.Component { +class UserContent extends PureComponent { render() { const { - users, compact, intl, currentUser, - meeting, isBreakoutRoom, setEmojiStatus, assignPresenter, removeUser, toggleVoice, + muteAllUsers, + muteAllExceptPresenter, changeRole, getAvailableActions, normalizeEmojiName, @@ -64,6 +65,7 @@ class UserContent extends React.Component { pollIsOpen, forcePollOpen, hasBreakoutRoom, + getUsersId, } = this.props; return ( @@ -89,18 +91,18 @@ class UserContent extends React.Component { }} /> <BreakoutRoomItem isPresenter={currentUser.isPresenter} hasBreakoutRoom={hasBreakoutRoom} /> - <UserParticipants + <UserParticipantsContainer {...{ - users, compact, intl, currentUser, - meeting, isBreakoutRoom, setEmojiStatus, assignPresenter, removeUser, toggleVoice, + muteAllUsers, + muteAllExceptPresenter, changeRole, getAvailableActions, normalizeEmojiName, @@ -110,6 +112,7 @@ class UserContent extends React.Component { getEmojiList, getEmoji, getGroupChatPrivate, + getUsersId, }} /> </div> diff --git a/bigbluebutton-html5/imports/ui/components/user-list/user-list-content/container.jsx b/bigbluebutton-html5/imports/ui/components/user-list/user-list-content/container.jsx index f9daa37e1f0ee3e31ca5e865d0da36af9dad3211..2e85bee5452db9b5b6746acc350c3eaedb38e848 100644 --- a/bigbluebutton-html5/imports/ui/components/user-list/user-list-content/container.jsx +++ b/bigbluebutton-html5/imports/ui/components/user-list/user-list-content/container.jsx @@ -3,9 +3,9 @@ import { withTracker } from 'meteor/react-meteor-data'; import { Session } from 'meteor/session'; import UserContent from './component'; -const UserContentContainer = ({ ...props }) => <UserContent {...props} />; +const UserContentContainer = props => <UserContent {...props} />; -export default withTracker(({ }) => ({ +export default withTracker(() => ({ pollIsOpen: Session.equals('isPollOpen', true), forcePollOpen: Session.equals('forcePollOpen', true), }))(UserContentContainer); diff --git a/bigbluebutton-html5/imports/ui/components/user-list/user-list-content/styles.scss b/bigbluebutton-html5/imports/ui/components/user-list/user-list-content/styles.scss index 6ab7b7e20fe87ce91b77de1c08f0f4b40b084027..6c671c9f3a88438f609c0b7b07d562423b6f3442 100755 --- a/bigbluebutton-html5/imports/ui/components/user-list/user-list-content/styles.scss +++ b/bigbluebutton-html5/imports/ui/components/user-list/user-list-content/styles.scss @@ -12,7 +12,8 @@ .container{ display: flex; align-items: center; - margin-bottom: .3rem; + margin-bottom: var(--lg-padding-y); + margin-top: var(--sm-padding-x); } .scrollableList { diff --git a/bigbluebutton-html5/imports/ui/components/user-list/user-list-content/user-messages/component.jsx b/bigbluebutton-html5/imports/ui/components/user-list/user-list-content/user-messages/component.jsx index ca2b28c5a42a10f28fa48003d7750a937186a38d..cfb12a669f8bc5de943898e80ccea13649d3ec04 100644 --- a/bigbluebutton-html5/imports/ui/components/user-list/user-list-content/user-messages/component.jsx +++ b/bigbluebutton-html5/imports/ui/components/user-list/user-list-content/user-messages/component.jsx @@ -1,4 +1,4 @@ -import React, { Component } from 'react'; +import React, { PureComponent } from 'react'; import { TransitionGroup, CSSTransition } from 'react-transition-group'; import PropTypes from 'prop-types'; import { defineMessages } from 'react-intl'; @@ -38,7 +38,7 @@ const intlMessages = defineMessages({ }, }); -class UserMessages extends Component { +class UserMessages extends PureComponent { constructor() { super(); diff --git a/bigbluebutton-html5/imports/ui/components/user-list/user-list-content/user-participants/component.jsx b/bigbluebutton-html5/imports/ui/components/user-list/user-list-content/user-participants/component.jsx index 87418fff550fc9b968b7a81f9d71834281184a7f..a5b0c31f514c8495e368bf6462b4f7d3666325a8 100755 --- a/bigbluebutton-html5/imports/ui/components/user-list/user-list-content/user-participants/component.jsx +++ b/bigbluebutton-html5/imports/ui/components/user-list/user-list-content/user-participants/component.jsx @@ -4,22 +4,28 @@ import { defineMessages } from 'react-intl'; import PropTypes from 'prop-types'; import cx from 'classnames'; import { styles } from '/imports/ui/components/user-list/user-list-content/styles'; -import UserListItem from './user-list-item/component'; +import _ from 'lodash'; +import UserListItemContainer from './user-list-item/container'; import UserOptionsContainer from './user-options/container'; const propTypes = { - users: PropTypes.arrayOf(Object).isRequired, compact: PropTypes.bool, intl: PropTypes.shape({ formatMessage: PropTypes.func.isRequired, }).isRequired, currentUser: PropTypes.shape({}).isRequired, - meeting: PropTypes.shape({}), + meeting: PropTypes.shape({}).isRequired, + users: PropTypes.arrayOf(PropTypes.string).isRequired, + getGroupChatPrivate: PropTypes.func.isRequired, + handleEmojiChange: PropTypes.func.isRequired, + getUsersId: PropTypes.func.isRequired, isBreakoutRoom: PropTypes.bool, setEmojiStatus: PropTypes.func.isRequired, assignPresenter: PropTypes.func.isRequired, removeUser: PropTypes.func.isRequired, toggleVoice: PropTypes.func.isRequired, + muteAllUsers: PropTypes.func.isRequired, + muteAllExceptPresenter: PropTypes.func.isRequired, changeRole: PropTypes.func.isRequired, getAvailableActions: PropTypes.func.isRequired, normalizeEmojiName: PropTypes.func.isRequired, @@ -30,9 +36,6 @@ const propTypes = { const defaultProps = { compact: false, isBreakoutRoom: false, - // This one is kinda tricky, meteor takes sometime to fetch the data and passing down - // So the first time its create, the meeting comes as null, sending an error to the client. - meeting: {}, }; const listTransition = { @@ -81,6 +84,12 @@ class UserParticipants extends Component { } } + shouldComponentUpdate(nextProps, nextState) { + const isPropsEqual = _.isEqual(this.props, nextProps); + const isStateEqual = _.isEqual(this.state, nextState); + return !isPropsEqual || !isStateEqual; + } + componentDidUpdate(prevProps, prevState) { if (this.state.index === -1) { return; @@ -104,57 +113,60 @@ class UserParticipants extends Component { getAvailableActions, normalizeEmojiName, isMeetingLocked, - users, changeRole, assignPresenter, setEmojiStatus, removeUser, toggleVoice, - getGroupChatPrivate, // // TODO check if this is used - handleEmojiChange, // // TODO add to props validation + getGroupChatPrivate, + handleEmojiChange, getEmojiList, getEmoji, + users, } = this.props; let index = -1; - return users.map(user => ( - <CSSTransition - classNames={listTransition} - appear - enter - exit - timeout={0} - component="div" - className={cx(styles.participantsList)} - key={user.id} - > - <div ref={(node) => { this.userRefs[index += 1] = node; }}> - <UserListItem - {...{ - user, - currentUser, - compact, - isBreakoutRoom, - meeting, - getAvailableActions, - normalizeEmojiName, - isMeetingLocked, - handleEmojiChange, - getEmojiList, - getEmoji, - setEmojiStatus, - assignPresenter, - removeUser, - toggleVoice, - changeRole, - getGroupChatPrivate, - }} - getScrollContainerRef={this.getScrollContainerRef} - /> - </div> - </CSSTransition> - )); + const { meetingId } = meeting; + + return users.map(u => + ( + <CSSTransition + classNames={listTransition} + appear + enter + exit + timeout={0} + component="div" + className={cx(styles.participantsList)} + key={u} + > + <div ref={(node) => { this.userRefs[index += 1] = node; }}> + <UserListItemContainer + {...{ + currentUser, + compact, + isBreakoutRoom, + meetingId, + getAvailableActions, + normalizeEmojiName, + isMeetingLocked, + handleEmojiChange, + getEmojiList, + getEmoji, + setEmojiStatus, + assignPresenter, + removeUser, + toggleVoice, + changeRole, + getGroupChatPrivate, + }} + userId={u} + getScrollContainerRef={this.getScrollContainerRef} + /> + </div> + </CSSTransition> + )); } focusUserItem(index) { @@ -168,10 +180,12 @@ class UserParticipants extends Component { } render() { - const { intl, users, compact } = this.props; + const { + intl, users, compact, setEmojiStatus, muteAllUsers, meeting, muteAllExceptPresenter, + } = this.props; return ( - <div> + <div className={styles.userListColumn}> { !compact ? <div className={styles.container}> @@ -180,7 +194,14 @@ class UserParticipants extends Component { ({users.length}) </h2> - <UserOptionsContainer /> + <UserOptionsContainer {...{ + users, + muteAllUsers, + muteAllExceptPresenter, + setEmojiStatus, + meeting, + }} + /> </div> : <hr className={styles.separator} /> } diff --git a/bigbluebutton-html5/imports/ui/components/user-list/user-list-content/user-participants/container.jsx b/bigbluebutton-html5/imports/ui/components/user-list/user-list-content/user-participants/container.jsx new file mode 100644 index 0000000000000000000000000000000000000000..b5ef848a0d23e708ebcd76bfe65add95a2980a35 --- /dev/null +++ b/bigbluebutton-html5/imports/ui/components/user-list/user-list-content/user-participants/container.jsx @@ -0,0 +1,11 @@ +import React from 'react'; +import { withTracker } from 'meteor/react-meteor-data'; +import Meetings from '/imports/api/meetings'; +import UserParticipants from './component'; + +const UserParticipantsContainer = ({ ...props }) => <UserParticipants {...props} />; + +export default withTracker(({ getUsersId }) => ({ + meeting: Meetings.findOne({}), + users: getUsersId(), +}))(UserParticipantsContainer); diff --git a/bigbluebutton-html5/imports/ui/components/user-list/user-list-content/user-participants/user-list-item/component.jsx b/bigbluebutton-html5/imports/ui/components/user-list/user-list-content/user-participants/user-list-item/component.jsx index 54d5fa0d7b24d8bdb4f50fe855d5dfd3e4d7990c..1bafe9249b99d8ba95574c8ce1bb576e4c6cf63d 100644 --- a/bigbluebutton-html5/imports/ui/components/user-list/user-list-content/user-participants/user-list-item/component.jsx +++ b/bigbluebutton-html5/imports/ui/components/user-list/user-list-content/user-participants/user-list-item/component.jsx @@ -1,17 +1,10 @@ -import React, { Component } from 'react'; +import React, { PureComponent } from 'react'; import PropTypes from 'prop-types'; import { injectIntl } from 'react-intl'; +import _ from 'lodash'; import UserDropdown from './user-dropdown/component'; const propTypes = { - user: PropTypes.shape({ - name: PropTypes.string.isRequired, - isPresenter: PropTypes.bool.isRequired, - isVoiceUser: PropTypes.bool.isRequired, - isModerator: PropTypes.bool.isRequired, - image: PropTypes.string, - }).isRequired, - currentUser: PropTypes.shape({ id: PropTypes.string.isRequired, }).isRequired, @@ -22,7 +15,6 @@ const propTypes = { }).isRequired, isBreakoutRoom: PropTypes.bool, getAvailableActions: PropTypes.func.isRequired, - meeting: PropTypes.shape({}).isRequired, isMeetingLocked: PropTypes.func.isRequired, normalizeEmojiName: PropTypes.func.isRequired, getScrollContainerRef: PropTypes.func.isRequired, @@ -32,9 +24,10 @@ const defaultProps = { isBreakoutRoom: false, }; -class UserListItem extends Component { +class UserListItem extends PureComponent { render() { const { + user, assignPresenter, compact, currentUser, @@ -48,12 +41,11 @@ class UserListItem extends Component { intl, isBreakoutRoom, isMeetingLocked, - meeting, + meetingId, normalizeEmojiName, removeUser, setEmojiStatus, toggleVoice, - user, } = this.props; const contents = (<UserDropdown @@ -71,7 +63,7 @@ class UserListItem extends Component { intl, isBreakoutRoom, isMeetingLocked, - meeting, + meetingId, normalizeEmojiName, removeUser, setEmojiStatus, diff --git a/bigbluebutton-html5/imports/ui/components/user-list/user-list-content/user-participants/user-list-item/container.jsx b/bigbluebutton-html5/imports/ui/components/user-list/user-list-content/user-participants/user-list-item/container.jsx new file mode 100644 index 0000000000000000000000000000000000000000..e27d54154b162b2d11c859833d53dbcdde184cf6 --- /dev/null +++ b/bigbluebutton-html5/imports/ui/components/user-list/user-list-content/user-participants/user-list-item/container.jsx @@ -0,0 +1,11 @@ +import React from 'react'; +import { withTracker } from 'meteor/react-meteor-data'; +import Users from '/imports/api/users'; +import mapUser from '/imports/ui/services/user/mapUser'; +import UserListItem from './component'; + +const UserListItemContainer = props => <UserListItem {...props} />; + +export default withTracker(({ userId }) => ({ + user: mapUser(Users.findOne({ userId })), +}))(UserListItemContainer); diff --git a/bigbluebutton-html5/imports/ui/components/user-list/user-list-content/user-participants/user-list-item/user-dropdown/component.jsx b/bigbluebutton-html5/imports/ui/components/user-list/user-list-content/user-participants/user-list-item/user-dropdown/component.jsx index ccb9aa475ba187dbb71c652cc522616dfd268f49..7fbcb87020aae5906e12d3eb94e1c2c1fcbc2032 100755 --- a/bigbluebutton-html5/imports/ui/components/user-list/user-list-content/user-participants/user-list-item/user-dropdown/component.jsx +++ b/bigbluebutton-html5/imports/ui/components/user-list/user-list-content/user-participants/user-list-item/user-dropdown/component.jsx @@ -1,4 +1,4 @@ -import React, { Component } from 'react'; +import React, { PureComponent } from 'react'; import { defineMessages } from 'react-intl'; import PropTypes from 'prop-types'; import { findDOMNode } from 'react-dom'; @@ -13,8 +13,8 @@ import DropdownListSeparator from '/imports/ui/components/dropdown/list/separato import _ from 'lodash'; import { Session } from 'meteor/session'; import { styles } from './styles'; -import UserName from './../user-name/component'; -import UserIcons from './../user-icons/component'; +import UserName from '../user-name/component'; +import UserIcons from '../user-icons/component'; const messages = defineMessages({ presenter: { @@ -90,12 +90,11 @@ const propTypes = { formatMessage: PropTypes.func.isRequired, }).isRequired, normalizeEmojiName: PropTypes.func.isRequired, - meeting: PropTypes.shape({}).isRequired, isMeetingLocked: PropTypes.func.isRequired, getScrollContainerRef: PropTypes.func.isRequired, }; -class UserDropdown extends Component { +class UserDropdown extends PureComponent { /** * Return true if the content fit on the screen, false otherwise. * @@ -151,6 +150,7 @@ class UserDropdown extends Component { iconRight, }} className={key === this.props.getEmoji ? styles.emojiSelected : null} + data-test={key} /> ); } @@ -371,15 +371,13 @@ class UserDropdown extends Component { dropdownVisible: true, }; - const isDropdownVisible = - UserDropdown.checkIfDropdownIsVisible( - dropdownContent.offsetTop, - dropdownContent.offsetHeight, - ); + const isDropdownVisible = UserDropdown.checkIfDropdownIsVisible( + dropdownContent.offsetTop, + dropdownContent.offsetHeight, + ); if (!isDropdownVisible) { - const offsetPageTop = - ((dropdownTrigger.offsetTop + dropdownTrigger.offsetHeight) - scrollContainer.scrollTop); + const offsetPageTop = ((dropdownTrigger.offsetTop + dropdownTrigger.offsetHeight) - scrollContainer.scrollTop); nextState.dropdownOffset = window.innerHeight - offsetPageTop; nextState.dropdownDirection = 'bottom'; @@ -409,9 +407,9 @@ class UserDropdown extends Component { const { clientType } = user; const isVoiceOnly = clientType === 'dial-in-user'; - const iconUser = user.emoji.status !== 'none' ? - (<Icon iconName={normalizeEmojiName(user.emoji.status)} />) : - user.name.toLowerCase().slice(0, 2); + const iconUser = user.emoji.status !== 'none' + ? (<Icon iconName={normalizeEmojiName(user.emoji.status)} />) + : user.name.toLowerCase().slice(0, 2); const iconVoiceOnlyUser = (<Icon iconName="speak_louder" />); @@ -436,7 +434,7 @@ class UserDropdown extends Component { user, intl, isMeetingLocked, - meeting, + meetingId, } = this.props; const { @@ -450,7 +448,6 @@ class UserDropdown extends Component { const userItemContentsStyle = {}; - userItemContentsStyle[styles.userItemContentsCompact] = compact; userItemContentsStyle[styles.dropdown] = true; userItemContentsStyle[styles.userListItem] = !this.state.isActionsOpen; userItemContentsStyle[styles.usertListItemWithMenu] = this.state.isActionsOpen; @@ -473,6 +470,7 @@ class UserDropdown extends Component { const contents = ( <div + data-test={user.isCurrent ? 'userListItemCurrent' : null} className={!actions.length ? styles.userListItem : null} > <div className={styles.userItemContents}> @@ -484,7 +482,7 @@ class UserDropdown extends Component { user, compact, intl, - meeting, + meetingId, isMeetingLocked, userAriaLabel, isActionsOpen, diff --git a/bigbluebutton-html5/imports/ui/components/user-list/user-list-content/user-participants/user-list-item/user-name/component.jsx b/bigbluebutton-html5/imports/ui/components/user-list/user-list-content/user-participants/user-list-item/user-name/component.jsx index 231171c78e895fc8730fd2db8b1e7c113285e82e..d3c7e26181ae2c17ea2df0ebd3b6561a17c94ec7 100644 --- a/bigbluebutton-html5/imports/ui/components/user-list/user-list-content/user-participants/user-list-item/user-name/component.jsx +++ b/bigbluebutton-html5/imports/ui/components/user-list/user-list-content/user-participants/user-list-item/user-name/component.jsx @@ -43,9 +43,10 @@ const propTypes = { intl: PropTypes.shape({ formatMessage: PropTypes.func.isRequired, }).isRequired, - meeting: PropTypes.shape({}).isRequired, isMeetingLocked: PropTypes.func.isRequired, userAriaLabel: PropTypes.string.isRequired, + meetingId: PropTypes.string.isRequired, + isActionsOpen: PropTypes.bool.isRequired, }; const UserName = (props) => { @@ -54,7 +55,7 @@ const UserName = (props) => { intl, compact, isMeetingLocked, - meeting, + meetingId, userAriaLabel, isActionsOpen, } = props; @@ -69,7 +70,7 @@ const UserName = (props) => { return null; } - if (isMeetingLocked(meeting.meetingId) && user.isLocked) { + if (isMeetingLocked(meetingId) && user.isLocked) { userNameSub.push(<span> <Icon iconName="lock" /> {intl.formatMessage(messages.locked)} @@ -81,7 +82,12 @@ const UserName = (props) => { } return ( - <div className={styles.userName} role="button" aria-label={userAriaLabel} aria-expanded={isActionsOpen}> + <div + className={styles.userName} + role="button" + aria-label={userAriaLabel} + aria-expanded={isActionsOpen} + > <span className={styles.userNameMain}> {user.name} <i>{(user.isCurrent) ? `(${intl.formatMessage(messages.you)})` : ''}</i> </span> @@ -90,7 +96,7 @@ const UserName = (props) => { <span className={styles.userNameSub}> {userNameSub.reduce((prev, curr) => [prev, ' | ', curr])} </span> - : null + : null } </div> ); diff --git a/bigbluebutton-html5/imports/ui/components/user-list/user-list-content/user-participants/user-options/component.jsx b/bigbluebutton-html5/imports/ui/components/user-list/user-list-content/user-participants/user-options/component.jsx index ae6ce9632f067f3dc48c675da82049eff6d6e8e4..6e26614b16501532a602db95d0fe9cc8b4a738fe 100755 --- a/bigbluebutton-html5/imports/ui/components/user-list/user-list-content/user-participants/user-options/component.jsx +++ b/bigbluebutton-html5/imports/ui/components/user-list/user-list-content/user-participants/user-options/component.jsx @@ -1,4 +1,5 @@ -import React, { Component } from 'react'; +import React, { PureComponent } from 'react'; +import PropTypes from 'prop-types'; import { defineMessages, injectIntl } from 'react-intl'; import _ from 'lodash'; import { withModalMounter } from '/imports/ui/components/modal/service'; @@ -8,8 +9,19 @@ import DropdownTrigger from '/imports/ui/components/dropdown/trigger/component'; import DropdownContent from '/imports/ui/components/dropdown/content/component'; import DropdownList from '/imports/ui/components/dropdown/list/component'; import DropdownListItem from '/imports/ui/components/dropdown/list/item/component'; +import LockViewersContainer from '/imports/ui/components/lock-viewers/container'; import { styles } from './styles'; +const propTypes = { + intl: PropTypes.shape({ + formatMessage: PropTypes.func.isRequired, + }).isRequired, + isMeetingMuted: PropTypes.bool.isRequired, + toggleMuteAllUsers: PropTypes.func.isRequired, + toggleMuteAllUsersExceptPresenter: PropTypes.func.isRequired, + toggleStatus: PropTypes.func.isRequired, +}; + const intlMessages = defineMessages({ optionsLabel: { id: 'app.userList.userOptions.manageUsersLabel', @@ -31,6 +43,14 @@ const intlMessages = defineMessages({ id: 'app.userList.userOptions.muteAllDesc', description: 'Mute all description', }, + unmuteAllLabel: { + id: 'app.userList.userOptions.unmuteAllLabel', + description: 'Unmute all label', + }, + unmuteAllDesc: { + id: 'app.userList.userOptions.unmuteAllDesc', + description: 'Unmute all desc', + }, lockViewersLabel: { id: 'app.userList.userOptions.lockViewersLabel', description: 'Lock viewers label', @@ -49,7 +69,7 @@ const intlMessages = defineMessages({ }, }); -class UserOptions extends Component { +class UserOptions extends PureComponent { constructor(props) { super(props); @@ -59,10 +79,11 @@ class UserOptions extends Component { this.onActionsShow = this.onActionsShow.bind(this); this.onActionsHide = this.onActionsHide.bind(this); + this.alterMenu = this.alterMenu.bind(this); } componentWillMount() { - const { intl } = this.props; + const { intl, isMeetingMuted, mountModal } = this.props; this.menuItems = _.compact([ (<DropdownListItem @@ -91,9 +112,19 @@ class UserOptions extends Component { icon="lock" label={intl.formatMessage(intlMessages.lockViewersLabel)} description={intl.formatMessage(intlMessages.lockViewersDesc)} - onClick={this.props.toggleLockView} + onClick={() => mountModal(<LockViewersContainer />)} />), ]); + + if (isMeetingMuted) { + this.alterMenu(); + } + } + + componentDidUpdate(prevProps) { + if (prevProps.isMeetingMuted !== this.props.isMeetingMuted) { + this.alterMenu(); + } } onActionsShow() { @@ -108,6 +139,37 @@ class UserOptions extends Component { }); } + alterMenu() { + const { intl, isMeetingMuted } = this.props; + + if (isMeetingMuted) { + const menuButton = (<DropdownListItem + key={_.uniqueId('list-item-')} + icon="unmute" + label={intl.formatMessage(intlMessages.unmuteAllLabel)} + description={intl.formatMessage(intlMessages.unmuteAllDesc)} + onClick={this.props.toggleMuteAllUsers} + />); + this.menuItems.splice(1, 2, menuButton); + } else { + const muteMeetingButtons = [(<DropdownListItem + key={_.uniqueId('list-item-')} + icon="mute" + label={intl.formatMessage(intlMessages.muteAllLabel)} + description={intl.formatMessage(intlMessages.muteAllDesc)} + onClick={this.props.toggleMuteAllUsers} + />), (<DropdownListItem + key={_.uniqueId('list-item-')} + icon="mute" + label={intl.formatMessage(intlMessages.muteAllExceptPresenterLabel)} + description={intl.formatMessage(intlMessages.muteAllExceptPresenterDesc)} + onClick={this.props.toggleMuteAllUsersExceptPresenter} + />)]; + + this.menuItems.splice(1, 1, muteMeetingButtons[0], muteMeetingButtons[1]); + } + } + render() { const { intl } = this.props; @@ -124,11 +186,11 @@ class UserOptions extends Component { <Button label={intl.formatMessage(intlMessages.optionsLabel)} icon="settings" - circle ghost color="primary" hideLabel className={styles.optionsButton} + size="sm" onClick={() => null} /> </DropdownTrigger> @@ -147,4 +209,5 @@ class UserOptions extends Component { } } +UserOptions.propTypes = propTypes; export default withModalMounter(injectIntl(UserOptions)); diff --git a/bigbluebutton-html5/imports/ui/components/user-list/user-list-content/user-participants/user-options/container.jsx b/bigbluebutton-html5/imports/ui/components/user-list/user-list-content/user-participants/user-options/container.jsx index 199e859178beb5612cbeb2efa7363bf32b7289a6..70cffbac78e99a60a3659300add2dc7af1c1444a 100755 --- a/bigbluebutton-html5/imports/ui/components/user-list/user-list-content/user-participants/user-options/container.jsx +++ b/bigbluebutton-html5/imports/ui/components/user-list/user-list-content/user-participants/user-options/container.jsx @@ -1,48 +1,75 @@ -import React, { Component } from 'react'; -import logger from '/imports/startup/client/logger'; +import React, { PureComponent } from 'react'; +import PropTypes from 'prop-types'; import Auth from '/imports/ui/services/auth'; import mapUser from '/imports/ui/services/user/mapUser'; import Users from '/imports/api/users/'; import UserOptions from './component'; -export default class UserOptionsContainer extends Component { + +const propTypes = { + users: PropTypes.arrayOf(Object).isRequired, + muteAllUsers: PropTypes.func.isRequired, + muteAllExceptPresenter: PropTypes.func.isRequired, + setEmojiStatus: PropTypes.func.isRequired, + meeting: PropTypes.shape({}).isRequired, +}; + +export default class UserOptionsContainer extends PureComponent { constructor(props) { super(props); - this.muteAllUsers = this.muteAllUsers.bind(this); + const { meeting } = this.props; + + this.state = { + meetingMuted: meeting.voiceProp.muteOnStart, + }; + + this.muteMeeting = this.muteMeeting.bind(this); this.muteAllUsersExceptPresenter = this.muteAllUsersExceptPresenter.bind(this); - this.handleLockView = this.handleLockView.bind(this); this.handleClearStatus = this.handleClearStatus.bind(this); } - muteAllUsers() { - logger.info('muteAllUsers function'); + muteMeeting() { + const { muteAllUsers } = this.props; + const currentUser = Users.findOne({ userId: Auth.userID }); + + muteAllUsers(currentUser.userId); } muteAllUsersExceptPresenter() { - logger.info('muteAllUsersExceptPresenter function'); - } + const { muteAllExceptPresenter } = this.props; + const currentUser = Users.findOne({ userId: Auth.userID }); - handleLockView() { - logger.info('handleLockView function'); + muteAllExceptPresenter(currentUser.userId); } handleClearStatus() { - logger.info('handleClearStatus function'); + const { users, setEmojiStatus } = this.props; + + users.forEach((user) => { + if (user.emoji.status !== 'none') { + setEmojiStatus(user.id, 'none'); + } + }); } render() { const currentUser = Users.findOne({ userId: Auth.userID }); const currentUserIsModerator = mapUser(currentUser).isModerator; + const { meeting } = this.props; + + this.state.meetingMuted = meeting.voiceProp.muteOnStart; return ( currentUserIsModerator ? <UserOptions - toggleMuteAllUsers={this.muteAllUsers} + toggleMuteAllUsers={this.muteMeeting} toggleMuteAllUsersExceptPresenter={this.muteAllUsersExceptPresenter} - toggleLockView={this.handleLockView} toggleStatus={this.handleClearStatus} + isMeetingMuted={this.state.meetingMuted} /> : null ); } } + +UserOptionsContainer.propTypes = propTypes; diff --git a/bigbluebutton-html5/imports/ui/components/user-list/user-list-content/user-participants/user-options/styles.scss b/bigbluebutton-html5/imports/ui/components/user-list/user-list-content/user-participants/user-options/styles.scss index 0b2e0230ef2eb8ffd18020e00768a742e429b223..5a2e29c997c49b8a5074ddb67dcff5a05b7b626f 100755 --- a/bigbluebutton-html5/imports/ui/components/user-list/user-list-content/user-participants/user-options/styles.scss +++ b/bigbluebutton-html5/imports/ui/components/user-list/user-list-content/user-participants/user-options/styles.scss @@ -1,35 +1,41 @@ -@import "/imports/ui/stylesheets/variables/_all"; - -.dropdown { - position: static; -} - -.dropdownContent { - top: 4.8rem; - margin-right: -0.2rem; -} - -.optionsButton{ - display: block; - margin-right: var(--sm-padding-y); - - span:first-child { - width: 1.56rem; - height: 1.56rem; - } - - i { - top: -.38em; - left: -.44em; - color: var(--color-gray-dark) !important; - background-color: transparent !important; - } - - &:hover, - &:focus { - > span:first-child { - background-color: transparent !important; - } - } - -} +@import "/imports/ui/stylesheets/variables/_all"; + +:root { + --user-manage-menu-top : -0.5rem; + --user-manage-menu-right : -0.75rem; + --user-manage-menu-width : 10rem; +} + +.dropdown { + position: absolute; + right: 0; +} + +.dropdownContent { + top: var(--user-manage-menu-top); + right: var(--user-manage-menu-right); + width: var(--user-manage-menu-width); + + @include mq($small-only) { + width: 100vw; + } +} + +.optionsButton{ + border-radius: 50%; + display: block; + padding: 0; + + i { + width: auto; + font-size: var(--font-size-base) !important; + color: var(--color-gray-dark) !important; + background-color: transparent !important; + } + + &:hover, + &:focus { + background-color: var(--color-off-white) !important; + } +} + diff --git a/bigbluebutton-html5/imports/ui/components/user-list/user-list-content/user-polls/component.jsx b/bigbluebutton-html5/imports/ui/components/user-list/user-list-content/user-polls/component.jsx index 1d9098daea4f4812309daf89f8acf633d16eefd2..f0776f82536b937f6f9ffec1c3ef4d8856b4c6c0 100644 --- a/bigbluebutton-html5/imports/ui/components/user-list/user-list-content/user-polls/component.jsx +++ b/bigbluebutton-html5/imports/ui/components/user-list/user-list-content/user-polls/component.jsx @@ -1,4 +1,4 @@ -import React, { Component } from 'react'; +import React, { PureComponent } from 'react'; import _ from 'lodash'; import { defineMessages, injectIntl } from 'react-intl'; import Icon from '/imports/ui/components/icon/component'; @@ -12,11 +12,7 @@ const intlMessages = defineMessages({ }, }); -class UserPolls extends Component { - constructor(props) { - super(props); - } - +class UserPolls extends PureComponent { render() { const { intl, diff --git a/bigbluebutton-html5/imports/ui/components/video-preview/component.jsx b/bigbluebutton-html5/imports/ui/components/video-preview/component.jsx new file mode 100644 index 0000000000000000000000000000000000000000..afcbab35d91f317622f5274a71b8f4005ad75540 --- /dev/null +++ b/bigbluebutton-html5/imports/ui/components/video-preview/component.jsx @@ -0,0 +1,261 @@ +import PropTypes from 'prop-types'; +import React, { Component } from 'react'; +import { defineMessages, injectIntl, intlShape } from 'react-intl'; +import Button from '/imports/ui/components/button/component'; +import ModalBase from '/imports/ui/components/modal/base/component'; +import { notify } from '/imports/ui/services/notification'; +import logger from '/imports/startup/client/logger'; +import { styles } from './styles'; + +const VIDEO_CONSTRAINTS = Meteor.settings.public.kurento.cameraConstraints; + +const propTypes = { + intl: intlShape.isRequired, + closeModal: PropTypes.func.isRequired, + startSharing: PropTypes.func.isRequired, + changeWebcam: PropTypes.func.isRequired, +}; + +const intlMessages = defineMessages({ + webcamSettingsTitle: { + id: 'app.videoPreview.webcamSettingsTitle', + description: 'Title for the video preview modal', + }, + closeLabel: { + id: 'app.videoPreview.closeLabel', + description: 'Close button label', + }, + webcamPreviewLabel: { + id: 'app.videoPreview.webcamPreviewLabel', + description: 'Webcam preview label', + }, + cameraLabel: { + id: 'app.videoPreview.cameraLabel', + description: 'Camera dropdown label', + }, + cancelLabel: { + id: 'app.videoPreview.cancelLabel', + description: 'Cancel button label', + }, + startSharingLabel: { + id: 'app.videoPreview.startSharingLabel', + description: 'Start Sharing button label', + }, + webcamOptionLabel: { + id: 'app.videoPreview.webcamOptionLabel', + description: 'Default webcam option label', + }, + webcamNotFoundLabel: { + id: 'app.videoPreview.webcamNotFoundLabel', + description: 'Webcam not found label', + }, + sharingError: { + id: 'app.video.sharingError', + description: 'Error on sharing webcam', + }, +}); + +class VideoPreview extends Component { + constructor(props) { + super(props); + + const { + closeModal, + startSharing, + changeWebcam, + webcamDeviceId, + } = props; + + this.handleJoinVideo = this.handleJoinVideo.bind(this); + this.handleProceed = this.handleProceed.bind(this); + this.handleStartSharing = this.handleStartSharing.bind(this); + this.closeModal = closeModal; + this.startSharing = startSharing; + this.changeWebcam = changeWebcam; + this.webcamDeviceId = webcamDeviceId; + + this.deviceStream = null; + + this.state = { + webcamDeviceId: this.webcamDeviceId, + availableWebcams: null, + isStartSharingDisabled: false, + }; + } + + stopTracks() { + if (this.deviceStream) { + this.deviceStream.getTracks().forEach((track) => { + track.stop(); + }); + } + } + + handleSelectWebcam(event) { + const { + intl, + } = this.props; + + const webcamValue = event.target.value; + this.setState({ webcamDeviceId: webcamValue }); + this.changeWebcam(webcamValue); + VIDEO_CONSTRAINTS.deviceId = webcamValue ? { exact: webcamValue } : undefined; + const constraints = { + video: VIDEO_CONSTRAINTS, + }; + this.stopTracks(); + navigator.mediaDevices.getUserMedia(constraints).then((stream) => { + this.video.srcObject = stream; + this.deviceStream = stream; + }).catch((error) => { + notify(intl.formatMessage(intlMessages.sharingError), 'error', 'video'); + logger.error(error); + }); + } + + handleStartSharing() { + const { resolve } = this.props; + this.stopTracks(); + this.startSharing(); + if (resolve) resolve(); + } + + handleProceed() { + const { resolve } = this.props; + this.closeModal(); + if (resolve) resolve(); + } + + componentDidMount() { + const { webcamDeviceId } = this.props; + const constraints = { + video: VIDEO_CONSTRAINTS, + }; + navigator.mediaDevices.getUserMedia(constraints).then((stream) => { + this.video.srcObject = stream; + this.deviceStream = stream; + navigator.mediaDevices.enumerateDevices().then((devices) => { + let isInitialDeviceSet = false; + const webcams = []; + if (webcamDeviceId) { + this.changeWebcam(webcamDeviceId); + this.setState({ webcamDeviceId }); + isInitialDeviceSet = true; + } + devices.forEach((device) => { + if (device.kind === 'videoinput') { + webcams.push(device); + if (!isInitialDeviceSet) { + this.changeWebcam(device.deviceId); + this.setState({ webcamDeviceId: device.deviceId }); + isInitialDeviceSet = true; + } + } + }); + if (webcams.length > 0) { + this.setState({ availableWebcams: webcams }); + } + }); + }).catch(() => { + this.setState({ isStartSharingDisabled: true }); + }); + } + + handleJoinVideo() { + const { + joinVideo, + } = this.props; + + joinVideo(); + } + + render() { + const { + intl, + } = this.props; + + return ( + <span> + <ModalBase + overlayClassName={styles.overlay} + className={styles.modal} + onRequestClose={this.handleProceed} + > + <header + className={styles.header} + > + <Button + className={styles.closeBtn} + label={intl.formatMessage(intlMessages.closeLabel)} + icon="close" + size="md" + hideLabel + onClick={this.handleProceed} + /> + </header> + <h3 className={styles.title}> + {intl.formatMessage(intlMessages.webcamSettingsTitle)} + </h3> + <div className={styles.content}> + <div className={styles.col}> + <video + id="preview" + className={styles.preview} + ref={(ref) => { this.video = ref; }} + autoPlay + playsInline + /> + </div> + <div className={styles.options}> + <label className={styles.label}> + {intl.formatMessage(intlMessages.cameraLabel)} + </label> + {this.state.availableWebcams && this.state.availableWebcams.length > 0 ? ( + <select + value={this.state.webcamDeviceId} + className={styles.select} + onChange={this.handleSelectWebcam.bind(this)} + > + <option disabled> + {intl.formatMessage(intlMessages.webcamOptionLabel)} + </option> + {this.state.availableWebcams.map((webcam, index) => ( + <option key={index} value={webcam.deviceId}> + {webcam.label} + </option> + ))} + </select> + ) : + <select + className={styles.select} + > + <option disabled> + {intl.formatMessage(intlMessages.webcamNotFoundLabel)} + </option> + </select>} + </div> + </div> + <div className={styles.footer}> + <div className={styles.actions}> + <Button + label={intl.formatMessage(intlMessages.cancelLabel)} + onClick={this.handleProceed} + /> + <Button + color="primary" + label={intl.formatMessage(intlMessages.startSharingLabel)} + onClick={() => this.handleStartSharing()} + disabled={this.state.isStartSharingDisabled} + /> + </div> + </div> + </ModalBase> + </span> + ); + } +} + +VideoPreview.propTypes = propTypes; + +export default injectIntl(VideoPreview); + diff --git a/bigbluebutton-html5/imports/ui/components/video-preview/container.jsx b/bigbluebutton-html5/imports/ui/components/video-preview/container.jsx new file mode 100644 index 0000000000000000000000000000000000000000..88b2f54d558cb6ddab1ba796fa2ec67f0c5e80bc --- /dev/null +++ b/bigbluebutton-html5/imports/ui/components/video-preview/container.jsx @@ -0,0 +1,21 @@ +import React from 'react'; +import { withModalMounter } from '/imports/ui/components/modal/service'; +import { withTracker } from 'meteor/react-meteor-data'; +import Service from './service'; +import VideoPreview from './component'; +import VideoService from '../video-provider/service'; + +const VideoPreviewContainer = props => <VideoPreview {...props} />; + +export default withModalMounter(withTracker(({ mountModal }) => ({ + closeModal: () => { + mountModal(null); + }, + startSharing: () => { + mountModal(null); + VideoService.joinVideo(); + }, + changeWebcam: deviceId => Service.changeWebcam(deviceId), + webcamDeviceId: Service.webcamDeviceId(), +}))(VideoPreviewContainer)); + diff --git a/bigbluebutton-html5/imports/ui/components/video-preview/service.js b/bigbluebutton-html5/imports/ui/components/video-preview/service.js new file mode 100644 index 0000000000000000000000000000000000000000..c1f0e2208a54d40078072d8c709ba56088f4f474 --- /dev/null +++ b/bigbluebutton-html5/imports/ui/components/video-preview/service.js @@ -0,0 +1,7 @@ +export default { + changeWebcam: (deviceId) => { + Session.set('WebcamDeviceId', deviceId); + }, + webcamDeviceId: () => Session.get('WebcamDeviceId'), +}; + diff --git a/bigbluebutton-html5/imports/ui/components/video-preview/styles.scss b/bigbluebutton-html5/imports/ui/components/video-preview/styles.scss new file mode 100644 index 0000000000000000000000000000000000000000..b21e8649b2ef12679e1dcd8159a7d8afb27ece00 --- /dev/null +++ b/bigbluebutton-html5/imports/ui/components/video-preview/styles.scss @@ -0,0 +1,79 @@ +@import "/imports/ui/stylesheets/variables/_all"; +@import '/imports/ui/stylesheets/mixins/focus'; +@import "/imports/ui/components/modal/simple/styles"; + +.actions { + margin-left: auto; +} + +.closeBtn { + i { + color: var(--color-gray-light); + } + margin-left: auto; +} + +.col { + display: flex; + + width: 30%; + height: 100%; + + margin-right: 1.5rem; +} + +.content { + display: flex; + flex: 3; +} + +.footer { + display: flex; +} + +.header { + display: flex; + border: none; +} + +.label { + font-size: 0.85rem; + font-weight: bold; + color: var(--color-gray-label); +} + +.modal { + padding: 1rem; + @extend .modal; +} + +.overlay { + @extend .overlay; +} + +.preview { + border-radius: 5px; + width: 100%; + height: 8rem; +} + +.row { + display: flex; +} + +.select { + margin-top: 0.4rem; + width: 100%; + height: 1.6rem; + + color: var(--color-gray-label); + border-radius: 0.3rem; + + background-color: var(--color-white); +} + +.title { + font-size: 1.4rem; + text-align: center; +} + diff --git a/bigbluebutton-html5/imports/ui/components/video-provider/component.jsx b/bigbluebutton-html5/imports/ui/components/video-provider/component.jsx index e5eeca50b08363a1caf8afe18e58e63d74a6c3e5..e00a6df51d591da9e62bf1f62fceec1466ae22e8 100755 --- a/bigbluebutton-html5/imports/ui/components/video-provider/component.jsx +++ b/bigbluebutton-html5/imports/ui/components/video-provider/component.jsx @@ -5,6 +5,7 @@ import VisibilityEvent from '/imports/utils/visibilityEvent'; import { fetchWebRTCMappedStunTurnServers } from '/imports/utils/fetchStunTurnServers'; import ReconnectingWebSocket from 'reconnecting-websocket'; import logger from '/imports/startup/client/logger'; +import { Session } from 'meteor/session'; import VideoService from './service'; import VideoList from './video-list/component'; @@ -177,6 +178,7 @@ class VideoProvider extends Component { document.addEventListener('joinVideo', this.shareWebcam); // TODO find a better way to do this document.addEventListener('exitVideo', this.unshareWebcam); this.ws.addEventListener('message', this.onWsMessage); + window.addEventListener('beforeunload', this.unshareWebcam); this.visibility.onVisible(this.unpauseViewers); this.visibility.onHidden(this.pauseViewers); @@ -352,7 +354,7 @@ class VideoProvider extends Component { // in this case, 'closed' state is not caused by an error; // we stop listening to prevent this from being treated as an error - if (this.webRtcPeers[id]) { + if (this.webRtcPeers[id] && this.webRtcPeers[id].peerConnection) { this.webRtcPeers[id].peerConnection.oniceconnectionstatechange = null; } @@ -378,7 +380,9 @@ class VideoProvider extends Component { const webRtcPeer = this.webRtcPeers[id]; if (webRtcPeer) { this.logger('info', 'Stopping WebRTC peer', { cameraId: id }); - webRtcPeer.dispose(); + if (typeof webRtcPeer.dispose === 'function') { + webRtcPeer.dispose(); + } delete this.webRtcPeers[id]; } else { this.logger('warn', 'No WebRTC peer to stop (not an error)', { cameraId: id }); @@ -386,14 +390,24 @@ class VideoProvider extends Component { } async createWebRTCPeer(id, shareWebcam) { - const { meetingId, sessionToken } = this.props; + const { meetingId, sessionToken, voiceBridge } = this.props; let iceServers = []; + // Check if the peer is already being processed + if (this.webRtcPeers[id]) { + return; + } + + this.webRtcPeers[id] = {}; + try { iceServers = await fetchWebRTCMappedStunTurnServers(sessionToken); } catch (error) { this.logger('error', 'Video provider failed to fetch ice servers, using default'); } finally { + if (Session.get('WebcamDeviceId')) { + VIDEO_CONSTRAINTS.deviceId = { exact: Session.get('WebcamDeviceId') }; + } const options = { mediaConstraints: { audio: false, @@ -443,6 +457,7 @@ class VideoProvider extends Component { sdpOffer: offerSdp, cameraId: id, meetingId, + voiceBridge, }; this.sendMessage(message); @@ -451,8 +466,10 @@ class VideoProvider extends Component { peer.didSDPAnswered = true; }); }); - this.webRtcPeers[id].peerConnection.oniceconnectionstatechange = - this._getOnIceConnectionStateChangeCallback(id); + if (this.webRtcPeers[id].peerConnection) { + this.webRtcPeers[id].peerConnection.oniceconnectionstatechange = + this._getOnIceConnectionStateChangeCallback(id); + } } } @@ -472,7 +489,7 @@ class VideoProvider extends Component { // Increment reconnect interval this.restartTimer[id] = Math.min(2 * this.restartTimer[id], MAX_CAMERA_SHARE_FAILED_WAIT_TIME); - this.logger('info', `Reconnecting peer ${id} with timer`, this.restartTimer) + this.logger('info', `Reconnecting peer ${id} with timer`, this.restartTimer); } }; } @@ -541,7 +558,6 @@ class VideoProvider extends Component { return (event) => { const connectionState = peer.peerConnection.iceConnectionState; if (connectionState === 'failed' || connectionState === 'closed') { - // prevent the same error from being detected multiple times peer.peerConnection.oniceconnectionstatechange = null; diff --git a/bigbluebutton-html5/imports/ui/components/video-provider/container.jsx b/bigbluebutton-html5/imports/ui/components/video-provider/container.jsx index 12bab47d132c9406c24351b9571084d172acd7a9..eb6bbd51d1bc99f4e58ee856baa8451bd36b963e 100755 --- a/bigbluebutton-html5/imports/ui/components/video-provider/container.jsx +++ b/bigbluebutton-html5/imports/ui/components/video-provider/container.jsx @@ -14,4 +14,5 @@ export default withTracker(() => ({ sessionToken: VideoService.sessionToken(), userName: VideoService.userName(), enableVideoStats: getFromUserSettings('enableVideoStats', Meteor.settings.public.kurento.enableVideoStats), + voiceBridge: VideoService.voiceBridge(), }))(VideoProviderContainer); diff --git a/bigbluebutton-html5/imports/ui/components/video-provider/service.js b/bigbluebutton-html5/imports/ui/components/video-provider/service.js index 5c672116d78bf63326d99ea03bc6e6e5b77f7067..9e3f0f33b74d1af92b06baac615844311098204d 100755 --- a/bigbluebutton-html5/imports/ui/components/video-provider/service.js +++ b/bigbluebutton-html5/imports/ui/components/video-provider/service.js @@ -105,7 +105,7 @@ class VideoService { } isLocked() { - const m = Meetings.findOne({ meetingId: Auth.meetingID }); + const m = Meetings.findOne({ meetingId: Auth.meetingID }) || {}; return m.lockSettingsProp ? m.lockSettingsProp.disableCam : false; } @@ -126,6 +126,11 @@ class VideoService { return Auth.sessionToken; } + voiceBridge() { + const voiceBridge = Meetings.findOne({ meetingId: Auth.meetingID }).voiceProp.voiceConf; + return voiceBridge; + } + isConnected() { return this.isConnected; } @@ -157,4 +162,5 @@ export default { meetingId: () => videoService.meetingId(), getAllUsersVideo: () => videoService.getAllUsersVideo(), sessionToken: () => videoService.sessionToken(), + voiceBridge: () => videoService.voiceBridge(), }; diff --git a/bigbluebutton-html5/imports/ui/components/video-provider/video-list/styles.scss b/bigbluebutton-html5/imports/ui/components/video-provider/video-list/styles.scss index 06a6d28322e7779556cecf07597941a2b8a22ff2..3e42276c40369c70bd33e55e85b12719008b06e3 100755 --- a/bigbluebutton-html5/imports/ui/components/video-provider/video-list/styles.scss +++ b/bigbluebutton-html5/imports/ui/components/video-provider/video-list/styles.scss @@ -70,7 +70,7 @@ position: relative; height: 100%; width: 100%; - object-fit: cover; + object-fit: contain; border-radius: 5px; } diff --git a/bigbluebutton-html5/imports/ui/components/video-provider/video-menu/component.jsx b/bigbluebutton-html5/imports/ui/components/video-provider/video-menu/component.jsx index 80c48a5029c5564f40102c7455a0525d8d915c7e..88ed1f14a20b9de2aa2fec811ccc76b07f1eaa65 100755 --- a/bigbluebutton-html5/imports/ui/components/video-provider/video-menu/component.jsx +++ b/bigbluebutton-html5/imports/ui/components/video-provider/video-menu/component.jsx @@ -1,6 +1,7 @@ import React from 'react'; import PropTypes from 'prop-types'; import _ from 'lodash'; +import cx from 'classnames'; import Button from '/imports/ui/components/button/component'; import { defineMessages, injectIntl, intlShape } from 'react-intl'; import Dropdown from '/imports/ui/components/dropdown/component'; @@ -64,12 +65,13 @@ const JoinVideoOptions = ({ intl.formatMessage(intlMessages.videoMenuDisabled) : intl.formatMessage(intlMessages.videoMenu) } - className={styles.button} + className={cx(styles.button, isSharingVideo || styles.ghostButton)} onClick={() => null} hideLabel aria-label={intl.formatMessage(intlMessages.videoMenuDesc)} - color={isSharingVideo ? 'danger' : 'primary'} - icon={isSharingVideo ? 'video_off' : 'video'} + color={isSharingVideo ? 'primary' : 'default'} + icon={isSharingVideo ? 'video' : 'video_off'} + ghost={!isSharingVideo} size="lg" circle disabled={!videoShareAllowed} diff --git a/bigbluebutton-html5/imports/ui/components/video-provider/video-menu/container.jsx b/bigbluebutton-html5/imports/ui/components/video-provider/video-menu/container.jsx index 2994d93fae8b3b5e897b1e3916aade215182767b..8e453a95ec9d0916cfb65b827475d652a04ca48c 100755 --- a/bigbluebutton-html5/imports/ui/components/video-provider/video-menu/container.jsx +++ b/bigbluebutton-html5/imports/ui/components/video-provider/video-menu/container.jsx @@ -1,6 +1,8 @@ import React from 'react'; import { withTracker } from 'meteor/react-meteor-data'; import { defineMessages, injectIntl } from 'react-intl'; +import { withModalMounter } from '/imports/ui/components/modal/service'; +import VideoPreviewContainer from '/imports/ui/components/video-preview/container'; import JoinVideoOptions from './component'; import VideoMenuService from './service'; @@ -33,6 +35,7 @@ const JoinVideoOptionsContainer = (props) => { swapLayoutAllowed, baseName, intl, + mountModal, ...restProps } = props; const videoItems = [ @@ -49,7 +52,7 @@ const JoinVideoOptionsContainer = (props) => { description: intl.formatMessage(intlMessages[isSharingVideo ? 'leaveVideo' : 'joinVideo']), label: intl.formatMessage(intlMessages[isSharingVideo ? 'leaveVideo' : 'joinVideo']), disabled: isDisabled && !isSharingVideo, - click: isSharingVideo ? handleCloseVideo : handleJoinVideo, + click: isSharingVideo ? handleCloseVideo : () => { mountModal(<VideoPreviewContainer />); }, id: isSharingVideo ? 'leave-video-button' : 'join-video-button', }, ]; @@ -57,11 +60,11 @@ const JoinVideoOptionsContainer = (props) => { return <JoinVideoOptions {...{ videoItems, isSharingVideo, ...restProps }} />; }; -export default injectIntl(withTracker(() => ({ +export default withModalMounter(injectIntl(withTracker(() => ({ baseName: VideoMenuService.baseName, isSharingVideo: VideoMenuService.isSharingVideo(), isDisabled: VideoMenuService.isDisabled(), videoShareAllowed: VideoMenuService.videoShareAllowed(), toggleSwapLayout: VideoMenuService.toggleSwapLayout, swapLayoutAllowed: VideoMenuService.swapLayoutAllowed(), -}))(JoinVideoOptionsContainer)); +}))(JoinVideoOptionsContainer))); diff --git a/bigbluebutton-html5/imports/ui/components/video-provider/video-menu/styles.scss b/bigbluebutton-html5/imports/ui/components/video-provider/video-menu/styles.scss index f8051d532d6b67debf1442d338b8cbf997b8771c..9276e6006bc18a9bc7a958069db43fe3ab313605 100755 --- a/bigbluebutton-html5/imports/ui/components/video-provider/video-menu/styles.scss +++ b/bigbluebutton-html5/imports/ui/components/video-provider/video-menu/styles.scss @@ -1,4 +1,5 @@ @import "/imports/ui/stylesheets/variables/_all"; +@import "/imports/ui/components/actions-bar/styles.scss"; .button { &:focus { @@ -10,6 +11,10 @@ } } +.ghostButton { + @extend .btn; +} + .imageSize { height: 4rem; @include mq($small-only) { diff --git a/bigbluebutton-html5/imports/ui/components/whiteboard/annotation-factory/component.jsx b/bigbluebutton-html5/imports/ui/components/whiteboard/annotation-factory/component.jsx index d41fc85bf98fde286c604a6b3059f98dbdf3bf73..a32564ac3d571eb1149ea1c55964a619058ac0c4 100755 --- a/bigbluebutton-html5/imports/ui/components/whiteboard/annotation-factory/component.jsx +++ b/bigbluebutton-html5/imports/ui/components/whiteboard/annotation-factory/component.jsx @@ -68,11 +68,12 @@ export default class AnnotationFactory extends Component { render() { const { annotationsInfo } = this.props; + return ( <g> - {annotationsInfo ? - annotationsInfo.map(annotationInfo => this.renderAnnotation(annotationInfo)) - : null } + {annotationsInfo + ? annotationsInfo.map(annotationInfo => this.renderAnnotation(annotationInfo)) + : null } </g> ); } diff --git a/bigbluebutton-html5/imports/ui/components/whiteboard/whiteboard-toolbar/component.jsx b/bigbluebutton-html5/imports/ui/components/whiteboard/whiteboard-toolbar/component.jsx index e4129f3810e8dbbc98b8f2b82683e2d8d3b10f42..a34fca67d22aa53e10fac398ed5e1be7f75bbb10 100755 --- a/bigbluebutton-html5/imports/ui/components/whiteboard/whiteboard-toolbar/component.jsx +++ b/bigbluebutton-html5/imports/ui/components/whiteboard/whiteboard-toolbar/component.jsx @@ -213,8 +213,8 @@ class WhiteboardToolbar extends Component { } else if (this.state.thicknessSelected.value !== prevState.thicknessSelected.value) { this.thicknessListIconRadius.beginElement(); // 3rd case - } else if (this.state.annotationSelected.value !== 'text' && - prevState.annotationSelected.value === 'text') { + } else if (this.state.annotationSelected.value !== 'text' + && prevState.annotationSelected.value === 'text') { this.thicknessListIconRadius.beginElement(); this.thicknessListIconColor.beginElement(); } @@ -339,17 +339,19 @@ class WhiteboardToolbar extends Component { onBlur={this.closeSubMenu} className={cx(styles.toolbarButton, this.state.currentSubmenuOpen === 'annotationList' ? styles.toolbarActive : null)} > - {this.state.currentSubmenuOpen === 'annotationList' && annotations.length > 1 ? - <ToolbarSubmenu - type="annotations" - customIcon={false} - label="Annotations" - onItemClick={this.handleAnnotationChange} - objectsToRender={annotations} - objectSelected={this.state.annotationSelected} - handleMouseEnter={this.handleMouseEnter} - handleMouseLeave={this.handleMouseLeave} - /> + {this.state.currentSubmenuOpen === 'annotationList' && annotations.length > 1 + ? ( + <ToolbarSubmenu + type="annotations" + customIcon={false} + label="Annotations" + onItemClick={this.handleAnnotationChange} + objectsToRender={annotations} + objectSelected={this.state.annotationSelected} + handleMouseEnter={this.handleMouseEnter} + handleMouseLeave={this.handleMouseLeave} + /> + ) : null} </ToolbarMenuItem> ); @@ -367,17 +369,19 @@ class WhiteboardToolbar extends Component { onBlur={this.closeSubMenu} className={cx(styles.toolbarButton, this.state.currentSubmenuOpen === 'fontSizeList' ? styles.toolbarActive : null)} > - {this.state.currentSubmenuOpen === 'fontSizeList' ? - <ToolbarSubmenu - type="font-size" - customIcon - label="Font Size" - onItemClick={this.handleFontSizeChange} - objectsToRender={this.props.fontSizes} - objectSelected={this.state.fontSizeSelected} - handleMouseEnter={this.handleMouseEnter} - handleMouseLeave={this.handleMouseLeave} - /> + {this.state.currentSubmenuOpen === 'fontSizeList' + ? ( + <ToolbarSubmenu + type="font-size" + customIcon + label="Font Size" + onItemClick={this.handleFontSizeChange} + objectsToRender={this.props.fontSizes} + objectSelected={this.state.fontSizeSelected} + handleMouseEnter={this.handleMouseEnter} + handleMouseLeave={this.handleMouseLeave} + /> + ) : null} </ToolbarMenuItem> ); @@ -405,8 +409,8 @@ class WhiteboardToolbar extends Component { return ( <ToolbarMenuItem disabled={isDisabled} - label={isDisabled ? - intl.formatMessage(intlMessages.toolbarLineThicknessDisabled) + label={isDisabled + ? intl.formatMessage(intlMessages.toolbarLineThicknessDisabled) : intl.formatMessage(intlMessages.toolbarLineThickness)} onItemClick={this.displaySubMenu} objectToReturn="thicknessList" @@ -414,17 +418,19 @@ class WhiteboardToolbar extends Component { className={cx(styles.toolbarButton, this.state.currentSubmenuOpen === 'thicknessList' ? styles.toolbarActive : null)} customIcon={this.renderThicknessItemIcon()} > - {this.state.currentSubmenuOpen === 'thicknessList' ? - <ToolbarSubmenu - type="thickness" - customIcon - label="Thickness" - onItemClick={this.handleThicknessChange} - objectsToRender={this.props.thicknessRadiuses} - objectSelected={this.state.thicknessSelected} - handleMouseEnter={this.handleMouseEnter} - handleMouseLeave={this.handleMouseLeave} - /> + {this.state.currentSubmenuOpen === 'thicknessList' + ? ( + <ToolbarSubmenu + type="thickness" + customIcon + label="Thickness" + onItemClick={this.handleThicknessChange} + objectsToRender={this.props.thicknessRadiuses} + objectSelected={this.state.thicknessSelected} + handleMouseEnter={this.handleMouseEnter} + handleMouseLeave={this.handleMouseLeave} + /> + ) : null} </ToolbarMenuItem> ); @@ -485,8 +491,8 @@ class WhiteboardToolbar extends Component { return ( <ToolbarMenuItem disabled={isDisabled} - label={isDisabled ? - intl.formatMessage(intlMessages.toolbarLineColorDisabled) + label={isDisabled + ? intl.formatMessage(intlMessages.toolbarLineColorDisabled) : intl.formatMessage(intlMessages.toolbarLineColor)} onItemClick={this.displaySubMenu} objectToReturn="colorList" @@ -494,17 +500,19 @@ class WhiteboardToolbar extends Component { className={cx(styles.toolbarButton, this.state.currentSubmenuOpen === 'colorList' ? styles.toolbarActive : null)} customIcon={this.renderColorItemIcon()} > - {this.state.currentSubmenuOpen === 'colorList' ? - <ToolbarSubmenu - type="color" - customIcon - label="Color" - onItemClick={this.handleColorChange} - objectsToRender={this.props.colors} - objectSelected={this.state.colorSelected} - handleMouseEnter={this.handleMouseEnter} - handleMouseLeave={this.handleMouseLeave} - /> + {this.state.currentSubmenuOpen === 'colorList' + ? ( + <ToolbarSubmenu + type="color" + customIcon + label="Color" + onItemClick={this.handleColorChange} + objectsToRender={this.props.colors} + objectSelected={this.state.colorSelected} + handleMouseEnter={this.handleMouseEnter} + handleMouseLeave={this.handleMouseLeave} + /> + ) : null} </ToolbarMenuItem> ); @@ -551,7 +559,7 @@ class WhiteboardToolbar extends Component { label={intl.formatMessage(intlMessages.toolbarUndoAnnotation)} icon="undo" onItemClick={this.handleUndo} - className={cx(styles.toolbarButton, styles.notActive)} + className={styles.toolbarButton} /> ); } @@ -562,9 +570,9 @@ class WhiteboardToolbar extends Component { return ( <ToolbarMenuItem label={intl.formatMessage(intlMessages.toolbarClearAnnotations)} - icon="circle_close" + icon="delete" onItemClick={this.handleClearAll} - className={cx(styles.toolbarButton, styles.notActive)} + className={styles.toolbarButton} /> ); } @@ -574,13 +582,13 @@ class WhiteboardToolbar extends Component { return ( <ToolbarMenuItem - label={multiUser ? - intl.formatMessage(intlMessages.toolbarMultiUserOff) - : intl.formatMessage(intlMessages.toolbarMultiUserOn) + label={multiUser + ? intl.formatMessage(intlMessages.toolbarMultiUserOff) + : intl.formatMessage(intlMessages.toolbarMultiUserOn) } icon={multiUser ? 'multi_whiteboard' : 'whiteboard'} onItemClick={this.handleSwitchWhiteboardMode} - className={cx(styles.toolbarButton, styles.notActive)} + className={styles.toolbarButton} /> ); } diff --git a/bigbluebutton-html5/imports/ui/components/whiteboard/whiteboard-toolbar/toolbar-submenu/component.jsx b/bigbluebutton-html5/imports/ui/components/whiteboard/whiteboard-toolbar/toolbar-submenu/component.jsx index 138079f833cd5e57082d9f2f014a98c53480c88f..7639507a73d884e7e95024620bfc11cf42b21dce 100755 --- a/bigbluebutton-html5/imports/ui/components/whiteboard/whiteboard-toolbar/toolbar-submenu/component.jsx +++ b/bigbluebutton-html5/imports/ui/components/whiteboard/whiteboard-toolbar/toolbar-submenu/component.jsx @@ -93,13 +93,13 @@ class ToolbarSubmenu extends Component { <rect x="20%" y="20%" width="60%" height="60%" fill={obj.value} /> </svg> ); - } else if (type === 'thickness') { + } if (type === 'thickness') { return ( <svg className={styles.customSvgIcon}> <circle cx="50%" cy="50%" r={obj.value} /> </svg> ); - } else if (type === 'font-size') { + } if (type === 'font-size') { return ( <p className={styles.textThickness} style={{ fontSize: obj.value }}> Aa @@ -111,18 +111,19 @@ class ToolbarSubmenu extends Component { } static getWrapperClassNames(type) { - if (type === 'color') { - return cx(styles.colorList, styles.toolbarList); - } else if (type === 'thickness') { - return cx(styles.thicknessList, styles.toolbarList); - } else if (type === 'font-size') { + if (type === 'font-size') { return cx(styles.fontSizeList, styles.toolbarList); - } else if (type === 'annotations') { - return cx(styles.annotationList, styles.toolbarList); + } if ( + type === 'annotations' + || type === 'thickness' + || type === 'color' + ) { + return styles.toolbarList; } return null; } + constructor() { super(); @@ -183,21 +184,20 @@ class ToolbarSubmenu extends Component { onMouseLeave={this.handleMouseLeave} className={ToolbarSubmenu.getWrapperClassNames(type)} > - {objectsToRender ? objectsToRender.map(obj => - ( - <ToolbarSubmenuItem - label={this.formatSubmenuLabel(type, obj)} - icon={!customIcon ? obj.icon : null} - customIcon={customIcon ? ToolbarSubmenu.getCustomIcon(type, obj) : null} - onItemClick={this.onItemClick} - objectToReturn={obj} - className={cx( - styles.toolbarListButton, - objectSelected.value === obj.value ? styles.selectedListButton : '', - )} - key={obj.value} - /> - )) : null} + {objectsToRender ? objectsToRender.map(obj => ( + <ToolbarSubmenuItem + label={this.formatSubmenuLabel(type, obj)} + icon={!customIcon ? obj.icon : null} + customIcon={customIcon ? ToolbarSubmenu.getCustomIcon(type, obj) : null} + onItemClick={this.onItemClick} + objectToReturn={obj} + className={cx( + styles.toolbarListButton, + objectSelected.value === obj.value ? styles.selectedListButton : '', + )} + key={obj.value} + /> + )) : null} </div> ); } diff --git a/bigbluebutton-html5/imports/ui/services/audio-manager/index.js b/bigbluebutton-html5/imports/ui/services/audio-manager/index.js index 5bf04b42ef9e002aacd7bcd42c163ae6e015c300..58e2eed1c883d1c1bb09906a359eac9f5d34147d 100755 --- a/bigbluebutton-html5/imports/ui/services/audio-manager/index.js +++ b/bigbluebutton-html5/imports/ui/services/audio-manager/index.js @@ -136,7 +136,7 @@ class AudioManager { this.isEchoTest = false; const { name } = browser(); // The kurento bridge isn't a full audio bridge yet, so we have to differ it - const bridge = USE_KURENTO ? this.listenOnlyBridge : this.bridge; + const bridge = this.useKurento ? this.listenOnlyBridge : this.bridge; const callOptions = { isListenOnly: true, @@ -146,7 +146,7 @@ class AudioManager { // Webkit ICE restrictions demand a capture device permission to release // host candidates - if (name == 'safari') { + if (name === 'safari') { await this.askDevicesPermissions(); } @@ -162,12 +162,11 @@ class AudioManager { } logger.error('Listen only error:', err, 'on try', retries); - const error = { + throw { type: 'MEDIA_ERROR', message: this.messages.error.MEDIA_ERROR, - } - throw error; - } + }; + }; return this.onAudioJoining() .then(() => Promise.race([ @@ -181,14 +180,14 @@ class AudioManager { // Exit previous SFU session and clean audio tag state window.kurentoExitAudio(); this.useKurento = false; - let audio = document.querySelector(MEDIA_TAG); + const audio = document.querySelector(MEDIA_TAG); audio.muted = false; } try { await this.joinListenOnly(++retries); - } catch (err) { - return handleListenOnlyError(err); + } catch (error) { + return handleListenOnlyError(error); } } else { handleListenOnlyError(err); @@ -207,7 +206,7 @@ class AudioManager { exitAudio() { if (!this.isConnected) return Promise.resolve(); - const bridge = (this.useKurento && this.isListenOnly) ? this.listenOnlyBridge : this.bridge; + const bridge = (this.useKurento && this.isListenOnly) ? this.listenOnlyBridge : this.bridge; this.isHangingUp = true; this.isEchoTest = false; @@ -235,6 +234,8 @@ class AudioManager { changed: (id, fields) => { if (fields.muted !== undefined && fields.muted !== this.isMuted) { this.isMuted = fields.muted; + const muteState = this.isMuted ? 'selfMuted' : 'selfUnmuted'; + window.parent.postMessage({ response: muteState }, '*'); } if (fields.talking !== undefined && fields.talking !== this.isTalking) { @@ -249,6 +250,7 @@ class AudioManager { } if (!this.isEchoTest) { + window.parent.postMessage({ response: 'joinedAudio' }, '*'); this.notify(this.messages.info.JOINED_AUDIO); } } @@ -273,6 +275,7 @@ class AudioManager { if (!this.error && !this.isEchoTest) { this.notify(this.messages.info.LEFT_AUDIO); } + window.parent.postMessage({ response: 'notInAudio' }, '*'); } callStateCallback(response) { @@ -313,17 +316,14 @@ class AudioManager { new window.AudioContext() : new window.webkitAudioContext(); - // Create a placeholder buffer to upstart audio context - const pBuffer = this.listenOnlyAudioContext.createBuffer(2, this.listenOnlyAudioContext.sampleRate * 3, this.listenOnlyAudioContext.sampleRate); - - var dest = this.listenOnlyAudioContext.createMediaStreamDestination(); + const dest = this.listenOnlyAudioContext.createMediaStreamDestination(); - let audio = document.querySelector(MEDIA_TAG); + const audio = document.querySelector(MEDIA_TAG); // Play bogus silent audio to try to circumvent autoplay policy on Safari - audio.src = 'resources/sounds/silence.mp3' + audio.src = 'resources/sounds/silence.mp3'; - audio.play().catch(e => { + audio.play().catch((e) => { logger.warn('Error on playing test audio:', e); }); diff --git a/bigbluebutton-html5/imports/ui/services/auth/index.js b/bigbluebutton-html5/imports/ui/services/auth/index.js index d950defbd6f4164b26aaad269262c0a201f73f77..95b8fa9b80d4bb55e0a36a204bc9697aa730ef11 100755 --- a/bigbluebutton-html5/imports/ui/services/auth/index.js +++ b/bigbluebutton-html5/imports/ui/services/auth/index.js @@ -4,6 +4,7 @@ import { Tracker } from 'meteor/tracker'; import Storage from '/imports/ui/services/storage/session'; import Users from '/imports/api/users'; +import logger from '/imports/startup/client/logger'; import { makeCall } from '/imports/ui/services/api'; const CONNECTION_TIMEOUT = Meteor.settings.public.app.connectionTimeout; @@ -137,7 +138,16 @@ class Auth { }; } - set(meetingId, requesterUserId, requesterToken, logoutURL, sessionToken, fullname, externUserID, confname) { + set( + meetingId, + requesterUserId, + requesterToken, + logoutURL, + sessionToken, + fullname, + externUserID, + confname, + ) { this.meetingID = meetingId; this.userID = requesterUserId; this.token = requesterToken; @@ -207,7 +217,11 @@ class Auth { const User = Users.findOne(selector); // Skip in case the user is not in the collection yet or is a dummy user - if (!User || !('intId' in User)) return; + if (!User || !('intId' in User)) { + logger.info('re-send validateAuthToken for delayed authentication'); + makeCall('validateAuthToken'); + return; + } if (User.ejected) { reject({ @@ -221,7 +235,7 @@ class Auth { computation.stop(); clearTimeout(validationTimeout); // setTimeout to prevent race-conditions with subscription - setTimeout(resolve, 100); + setTimeout(() => resolve(true), 100); } }); diff --git a/bigbluebutton-html5/imports/ui/services/unread-messages/index.js b/bigbluebutton-html5/imports/ui/services/unread-messages/index.js index 54bf967721cd737c00e6f96c83e4b11c3557a779..70d5c1d05267ed013a1f4b22e912d8ddbd24ff2c 100755 --- a/bigbluebutton-html5/imports/ui/services/unread-messages/index.js +++ b/bigbluebutton-html5/imports/ui/services/unread-messages/index.js @@ -40,10 +40,12 @@ class UnreadMessagesTracker { sender: { $ne: Auth.userID }, }; if (chatID === PUBLIC_GROUP_CHAT_ID) { - filter.chatId = { $not: { $ne: chatID } }; + filter.chatId = { $eq: chatID }; } else { const privateChat = GroupChat.findOne({ users: { $all: [chatID, Auth.userID] } }); + filter.chatId = { $ne: PUBLIC_GROUP_CHAT_ID }; + if (privateChat) { filter.chatId = privateChat.chatId; } diff --git a/bigbluebutton-html5/imports/ui/stylesheets/variables/_all.scss b/bigbluebutton-html5/imports/ui/stylesheets/variables/_all.scss index 7813d0ab943174acc90567ecb4dbbdb6182bf62e..1fa95597203199f7c2b497a8a997c9364a470bb6 100644 --- a/bigbluebutton-html5/imports/ui/stylesheets/variables/_all.scss +++ b/bigbluebutton-html5/imports/ui/stylesheets/variables/_all.scss @@ -1,4 +1,9 @@ -@import "./general"; @import "./breakpoints"; -@import "./palette"; -@import "./typography"; + +%text-elipsis { + min-width: 0; + display: inline-block; + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; +} diff --git a/bigbluebutton-html5/imports/ui/stylesheets/variables/typography.scss b/bigbluebutton-html5/imports/ui/stylesheets/variables/typography.scss index 0d55a6a3d83016c25ee8b76aa9c98ff3ff4df889..965e62997fa2a6f706672062fc9e3efaf776a1f9 100644 --- a/bigbluebutton-html5/imports/ui/stylesheets/variables/typography.scss +++ b/bigbluebutton-html5/imports/ui/stylesheets/variables/typography.scss @@ -22,11 +22,4 @@ * Placeholders * =============== */ - -%text-elipsis { - min-width: 0; - display: inline-block; - white-space: nowrap; - overflow: hidden; - text-overflow: ellipsis; -} + \ No newline at end of file diff --git a/bigbluebutton-html5/imports/utils/logoutRouteHandler.js b/bigbluebutton-html5/imports/utils/logoutRouteHandler.js new file mode 100644 index 0000000000000000000000000000000000000000..c0febc21367d1bad7ef5475b4d9df34a3526c949 --- /dev/null +++ b/bigbluebutton-html5/imports/utils/logoutRouteHandler.js @@ -0,0 +1,15 @@ +import Auth from '/imports/ui/services/auth'; + +const logoutRouteHandler = () => { + Auth.logout() + .then((logoutURL = window.location.origin) => { + const protocolPattern = /^((http|https):\/\/)/; + + window.location.href = + protocolPattern.test(logoutURL) ? + logoutURL : + `http://${logoutURL}`; + }); +}; + +export default logoutRouteHandler; diff --git a/bigbluebutton-html5/imports/utils/slideCalcUtils.js b/bigbluebutton-html5/imports/utils/slideCalcUtils.js index b136f8c1dbda839878e5e09c20377574754a83c8..153e55f498b69aad09229bf73a4d8ce8bb2a8280 100644 --- a/bigbluebutton-html5/imports/utils/slideCalcUtils.js +++ b/bigbluebutton-html5/imports/utils/slideCalcUtils.js @@ -1,5 +1,8 @@ -const HUNDRED_PERCENT = 100; -const MYSTERY_NUM = 2; +export const HUNDRED_PERCENT = 100; +export const MAX_PERCENT = 400; +export const MYSTERY_NUM = 2; +export const STEP = 25; + export default class SlideCalcUtil { // After lots of trial and error on why synching doesn't work properly, I found I had to // multiply the coordinates by 2. There's something I don't understand probably on the diff --git a/bigbluebutton-html5/package-lock.json b/bigbluebutton-html5/package-lock.json old mode 100755 new mode 100644 index 7b6939da5fc45d887f0f37b812eff814b4442f5c..9b7031b8ba096adc57047233a3f2a70da6174be9 --- a/bigbluebutton-html5/package-lock.json +++ b/bigbluebutton-html5/package-lock.json @@ -3,10 +3,88 @@ "requires": true, "lockfileVersion": 1, "dependencies": { + "@babel/code-frame": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.0.0.tgz", + "integrity": "sha512-OfC2uemaknXr87bdLUkWog7nYuliM9Ij5HUcajsVcMCpQrcLmtxRbVFTIqmcSkSeYRBFBRxs2FiUqFJDLdiebA==", + "dev": true, + "requires": { + "@babel/highlight": "^7.0.0" + } + }, + "@babel/highlight": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.0.0.tgz", + "integrity": "sha512-UFMC4ZeFC48Tpvj7C8UgLvtkaUuovQX+5xNWrsIoMG8o2z+XFKjKaN9iVmS84dPwVN00W4wPmqvYoZF3EGAsfw==", + "dev": true, + "requires": { + "chalk": "^2.0.0", + "esutils": "^2.0.2", + "js-tokens": "^4.0.0" + }, + "dependencies": { + "ansi-styles": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", + "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", + "dev": true, + "requires": { + "color-convert": "^1.9.0" + } + }, + "chalk": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.1.tgz", + "integrity": "sha512-ObN6h1v2fTJSmUXoS3nMQ92LbDK9be4TV+6G+omQlGJFdcUX5heKi1LZ1YnRMIgwTLEj3E24bT6tYni50rlCfQ==", + "dev": true, + "requires": { + "ansi-styles": "^3.2.1", + "escape-string-regexp": "^1.0.5", + "supports-color": "^5.3.0" + } + }, + "has-flag": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", + "integrity": "sha1-tdRU3CGZriJWmfNGfloH87lVuv0=", + "dev": true + }, + "js-tokens": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", + "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==", + "dev": true + }, + "supports-color": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", + "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", + "dev": true, + "requires": { + "has-flag": "^3.0.0" + } + } + } + }, + "@babel/runtime": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.2.0.tgz", + "integrity": "sha512-oouEibCbHMVdZSDlJBO6bZmID/zA/G/Qx3H1d3rSNPTD+L8UNKvCat7aKWSJ74zYbm5zWGh0GQN0hKj8zYFTCg==", + "requires": { + "regenerator-runtime": "^0.12.0" + }, + "dependencies": { + "regenerator-runtime": { + "version": "0.12.1", + "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.12.1.tgz", + "integrity": "sha512-odxIc1/vDlo4iZcfXqRYFj0vpXFNoGdKMAUieAlFYO6m/nl5e9KR/beGf41z4a1FI+aQgtjhuaSlDxQ0hmkrHg==" + } + } + }, "@browser-bunyan/console-formatted-stream": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/@browser-bunyan/console-formatted-stream/-/console-formatted-stream-1.3.0.tgz", - "integrity": "sha1-PcBZqlwbKnofJuJwbiveuaCbvlc=", + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/@browser-bunyan/console-formatted-stream/-/console-formatted-stream-1.5.0.tgz", + "integrity": "sha512-W6XF3tMyzA4sE/fnL2J9cpcD2G9xhlrB2yBA7xMmRkKIKb/n/QIpC7hDLbNZQXq1/fuTP0X3R+e3cFGK6z4b1g==", "requires": { "@browser-bunyan/levels": "^1.3.0" } @@ -33,9 +111,86 @@ "integrity": "sha1-oFIwOuXRofm2Pus6lElaL0KfSDE=" }, "@browser-bunyan/server-stream": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/@browser-bunyan/server-stream/-/server-stream-1.3.0.tgz", - "integrity": "sha1-U7MlP6T8WA6GrZoWNqA6ISo0W1Y=" + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/@browser-bunyan/server-stream/-/server-stream-1.5.0.tgz", + "integrity": "sha512-u/ixWjnIESdF1LOtn/yh4Fe7wTAoGmhcdBk3cb4bmI+s1Pr9f5bUoHD8wbQlbok2sQw5TpQbPPh6r5IdfJFbcQ==" + }, + "@iamstarkov/listr-update-renderer": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/@iamstarkov/listr-update-renderer/-/listr-update-renderer-0.4.1.tgz", + "integrity": "sha512-IJyxQWsYDEkf8C8QthBn5N8tIUR9V9je6j3sMIpAkonaadjbvxmRC6RAhpa3RKxndhNnU2M6iNbtJwd7usQYIA==", + "dev": true, + "requires": { + "chalk": "^1.1.3", + "cli-truncate": "^0.2.1", + "elegant-spinner": "^1.0.1", + "figures": "^1.7.0", + "indent-string": "^3.0.0", + "log-symbols": "^1.0.2", + "log-update": "^2.3.0", + "strip-ansi": "^3.0.1" + }, + "dependencies": { + "ansi-styles": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-2.2.1.tgz", + "integrity": "sha1-tDLdM1i2NM914eRmQ2gkBTPB3b4=", + "dev": true + }, + "chalk": { + "version": "1.1.3", + "resolved": "http://registry.npmjs.org/chalk/-/chalk-1.1.3.tgz", + "integrity": "sha1-qBFcVeSnAv5NFQq9OHKCKn4J/Jg=", + "dev": true, + "requires": { + "ansi-styles": "^2.2.1", + "escape-string-regexp": "^1.0.2", + "has-ansi": "^2.0.0", + "strip-ansi": "^3.0.0", + "supports-color": "^2.0.0" + } + }, + "figures": { + "version": "1.7.0", + "resolved": "https://registry.npmjs.org/figures/-/figures-1.7.0.tgz", + "integrity": "sha1-y+Hjr/zxzUS4DK3+0o3Hk6lwHS4=", + "dev": true, + "requires": { + "escape-string-regexp": "^1.0.5", + "object-assign": "^4.1.0" + } + }, + "indent-string": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/indent-string/-/indent-string-3.2.0.tgz", + "integrity": "sha1-Sl/W0nzDMvN+VBmlBNu4NxBckok=", + "dev": true + }, + "log-symbols": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/log-symbols/-/log-symbols-1.0.2.tgz", + "integrity": "sha1-N2/3tY6jCGoPCfrMdGF+ylAeGhg=", + "dev": true, + "requires": { + "chalk": "^1.0.0" + } + }, + "supports-color": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-2.0.0.tgz", + "integrity": "sha1-U10EXOa2Nj+kARcIRimZXp3zJMc=", + "dev": true + } + } + }, + "@samverschueren/stream-to-observable": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/@samverschueren/stream-to-observable/-/stream-to-observable-0.3.0.tgz", + "integrity": "sha512-MI4Xx6LHs4Webyvi6EbspgyAb4D2Q2VtnCQ1blOJcoLS6mVa8lNN2rkIy1CVxfTUpoyIbCTkXES1rLXztFD1lg==", + "dev": true, + "requires": { + "any-observable": "^0.3.0" + } }, "abbrev": { "version": "1.1.1", @@ -43,60 +198,43 @@ "integrity": "sha512-nne9/IiQ/hzIhY6pdDnbBtz7DjPTKrY00P/zvPSm5pOFkl6xuGrGnXn/VtTNNfNtAfZ9/1RtehkszU9qcTii0Q==" }, "acorn": { - "version": "5.3.0", - "resolved": "https://registry.npmjs.org/acorn/-/acorn-5.3.0.tgz", - "integrity": "sha512-Yej+zOJ1Dm/IMZzzj78OntP/r3zHEaKcyNoU2lAaxPtrseM6rF0xwqoz5Q5ysAiED9hTjI2hgtvLXitlCN1/Ug==", + "version": "6.0.4", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-6.0.4.tgz", + "integrity": "sha512-VY4i5EKSKkofY2I+6QLTbTTN/UvEQPCo6eiwzzSaSWfpaDhOmStMCMod6wmuPciNq+XS0faCglFu2lHZpdHUtg==", "dev": true }, "acorn-jsx": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-3.0.1.tgz", - "integrity": "sha1-r9+UiPsezvyDSPb7IvRk4ypYs2s=", - "dev": true, - "requires": { - "acorn": "^3.0.4" - }, - "dependencies": { - "acorn": { - "version": "3.3.0", - "resolved": "https://registry.npmjs.org/acorn/-/acorn-3.3.0.tgz", - "integrity": "sha1-ReN/s56No/JbruP/U2niu18iAXo=", - "dev": true - } - } + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.0.1.tgz", + "integrity": "sha512-HJ7CfNHrfJLlNTzIEUTj43LNWGkqpRLxm3YjAlcD0ACydk9XynzYsCBHxut+iqt+1aBXkx9UP/w/ZqMr13XIzg==", + "dev": true }, "adm-zip": { - "version": "0.4.11", - "resolved": "https://registry.npmjs.org/adm-zip/-/adm-zip-0.4.11.tgz", - "integrity": "sha512-L8vcjDTCOIJk7wFvmlEUN7AsSb8T+2JrdP7KINBjzr24TJ5Mwj590sLu3BC7zNZowvJWa/JtPmD8eJCzdtDWjA==", + "version": "0.4.13", + "resolved": "https://registry.npmjs.org/adm-zip/-/adm-zip-0.4.13.tgz", + "integrity": "sha512-fERNJX8sOXfel6qCBCMPvZLzENBEhZTzKqg6vrOW5pvoEaQuJhRU4ndTAh6lHOxn1I6jnz2NHra56ZODM751uw==", "dev": true }, "ajv": { - "version": "5.5.2", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-5.5.2.tgz", - "integrity": "sha1-c7Xuyj+rZT49P5Qis0GtQiBdyWU=", + "version": "6.6.1", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.6.1.tgz", + "integrity": "sha512-ZoJjft5B+EJBjUyu9C9Hc0OZyPZSSlOF+plzouTrg6UlA8f+e/n8NIgBFG/9tppJtpPWfthHakK7juJdNDODww==", "requires": { - "co": "^4.6.0", - "fast-deep-equal": "^1.0.0", + "fast-deep-equal": "^2.0.1", "fast-json-stable-stringify": "^2.0.0", - "json-schema-traverse": "^0.3.0" + "json-schema-traverse": "^0.4.1", + "uri-js": "^4.2.2" } }, - "ajv-keywords": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/ajv-keywords/-/ajv-keywords-2.1.1.tgz", - "integrity": "sha1-YXmX/F9gV2iUxDX5QNgZ4TW4B2I=", - "dev": true - }, "amdefine": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/amdefine/-/amdefine-1.0.1.tgz", "integrity": "sha1-SlKCrBZHKek2Gbz9OtFR+BfOkfU=" }, "ansi-escapes": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-3.0.0.tgz", - "integrity": "sha512-O/klc27mWNUigtv0F8NJWbLF00OcegQalkqKURWdosW08YZKi4m6CnSUSvIZG1otNJbTWhN01Hhz389DW7mvDQ==", + "version": "3.1.0", + "resolved": "http://registry.npmjs.org/ansi-escapes/-/ansi-escapes-3.1.0.tgz", + "integrity": "sha512-UgAb8H9D41AQnu/PbWlCofQVcnV4Gs2bBJi9eZPxfU/hgglFh3SMDMENRIqdr7H6XFnXdoknctFByVsCOotTVw==", "dev": true }, "ansi-regex": { @@ -105,26 +243,45 @@ "integrity": "sha1-w7M6te42DYbg5ijwRorn7yfWVN8=" }, "ansi-styles": { - "version": "2.2.1", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-2.2.1.tgz", - "integrity": "sha1-tDLdM1i2NM914eRmQ2gkBTPB3b4=" + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", + "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", + "requires": { + "color-convert": "^1.9.0" + } + }, + "any-observable": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/any-observable/-/any-observable-0.3.0.tgz", + "integrity": "sha512-/FQM1EDkTsf63Ub2C6O7GuYFDsSXUwsaZDurV0np41ocwq0jthUAYCmhBX9f+KwlaCgIuWyr/4WlUQUBfKfZog==", + "dev": true }, "any-promise": { "version": "1.3.0", "resolved": "https://registry.npmjs.org/any-promise/-/any-promise-1.3.0.tgz", "integrity": "sha1-q8av7tzqUugJzcA3au0845Y10X8=" }, - "app-root-path": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/app-root-path/-/app-root-path-2.0.1.tgz", - "integrity": "sha1-zWLc+OT9WkF+/GZNLlsQZTxlG0Y=", - "dev": true - }, "aproba": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/aproba/-/aproba-1.2.0.tgz", "integrity": "sha512-Y9J6ZjXtoYh8RnXVCMOU/ttDmk1aBjunq9vO0ta5x85WDQiQfUF9sIPBITdbiiIVcBo03Hi3jMxigBtsddlXRw==" }, + "archiver": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/archiver/-/archiver-2.1.1.tgz", + "integrity": "sha1-/2YrSnggFJSj7lRNOjP+dJZQnrw=", + "dev": true, + "requires": { + "archiver-utils": "^1.3.0", + "async": "^2.0.0", + "buffer-crc32": "^0.2.1", + "glob": "^7.0.0", + "lodash": "^4.8.0", + "readable-stream": "^2.0.0", + "tar-stream": "^1.5.0", + "zip-stream": "^1.2.0" + } + }, "archiver-utils": { "version": "1.3.0", "resolved": "https://registry.npmjs.org/archiver-utils/-/archiver-utils-1.3.0.tgz", @@ -137,17 +294,6 @@ "lodash": "^4.8.0", "normalize-path": "^2.0.0", "readable-stream": "^2.0.0" - }, - "dependencies": { - "normalize-path": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-2.1.1.tgz", - "integrity": "sha1-GrKLVW4Zg2Oowab35vogE3/mrtk=", - "dev": true, - "requires": { - "remove-trailing-separator": "^1.0.1" - } - } } }, "are-we-there-yet": { @@ -160,23 +306,36 @@ } }, "argparse": { - "version": "1.0.9", - "resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.9.tgz", - "integrity": "sha1-c9g7wmP4bpf4zE9rrhsOkKfSLIY=", + "version": "1.0.10", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz", + "integrity": "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==", "dev": true, "requires": { "sprintf-js": "~1.0.2" } }, "aria-query": { - "version": "0.7.0", - "resolved": "https://registry.npmjs.org/aria-query/-/aria-query-0.7.0.tgz", - "integrity": "sha512-/r2lHl09V3o74+2MLKEdewoj37YZqiQZnfen1O4iNlrOjUgeKuu1U2yF3iKh6HJxqF+OXkLMfQv65Z/cvxD6vA==", + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/aria-query/-/aria-query-3.0.0.tgz", + "integrity": "sha1-ZbP8wcoRVajJrmTW7uKX8V1RM8w=", "dev": true, "requires": { - "ast-types-flow": "0.0.7" + "ast-types-flow": "0.0.7", + "commander": "^2.11.0" } }, + "arr-flatten": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/arr-flatten/-/arr-flatten-1.1.0.tgz", + "integrity": "sha512-L3hKV5R/p5o81R7O02IGnwpDmkp6E982XhtbuwSe3O4qOtMMMtodicASA1Cny2U+aCXcNpml+m4dPsvsJ3jatg==", + "dev": true + }, + "arr-union": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/arr-union/-/arr-union-3.1.0.tgz", + "integrity": "sha1-45sJrqne+Gao8gbiiK9jkZuuOcQ=", + "dev": true + }, "array-find-index": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/array-find-index/-/array-find-index-1.0.2.tgz", @@ -249,16 +408,31 @@ "integrity": "sha512-jgsaNduz+ndvGyFt3uSuWqvy4lCnIJiovtouQN5JZHOKCS2QuhEdbcQHFhVksz2N2U9hXJo8odG7ETyWlEeuDw==", "dev": true }, + "assign-symbols": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/assign-symbols/-/assign-symbols-1.0.0.tgz", + "integrity": "sha1-WWZ/QfrdTyDMvCu5a41Pf3jsA2c=", + "dev": true + }, "ast-types-flow": { "version": "0.0.7", "resolved": "https://registry.npmjs.org/ast-types-flow/-/ast-types-flow-0.0.7.tgz", "integrity": "sha1-9wtzXGvKGlycItmCw+Oef+ujva0=", "dev": true }, - "async": { + "astral-regex": { "version": "1.0.0", - "resolved": "https://registry.npmjs.org/async/-/async-1.0.0.tgz", - "integrity": "sha1-+PwEyjoTeErenhZBr5hXjPvWR6k=" + "resolved": "https://registry.npmjs.org/astral-regex/-/astral-regex-1.0.0.tgz", + "integrity": "sha512-+Ryf6g3BKoRc7jfp7ad8tM4TtMiaWvbF/1/sQcZPkkS7ag3D5nMBCe2UfOTONtAkaG0tO0ij3C5Lwmf1EiyjHg==", + "dev": true + }, + "async": { + "version": "2.6.1", + "resolved": "https://registry.npmjs.org/async/-/async-2.6.1.tgz", + "integrity": "sha512-fNEiL2+AZt6AlAw/29Cr0UDe4sRAHCpEHh54WMz+Bb7QfNcFw4h3loofyJpLeQs4Yx7yuqu/2dLgM5hKOs6HlQ==", + "requires": { + "lodash": "^4.17.10" + } }, "async-foreach": { "version": "0.1.3", @@ -285,22 +459,34 @@ } }, "autoprefixer": { - "version": "7.1.6", - "resolved": "https://registry.npmjs.org/autoprefixer/-/autoprefixer-7.1.6.tgz", - "integrity": "sha512-C9yv/UF3X+eJTi/zvfxuyfxmLibYrntpF3qoJYrMeQwgUJOZrZvpJiMG2FMQ3qnhWtF/be4pYONBBw95ZGe3vA==", + "version": "9.3.1", + "resolved": "https://registry.npmjs.org/autoprefixer/-/autoprefixer-9.3.1.tgz", + "integrity": "sha512-DY9gOh8z3tnCbJ13JIWaeQsoYncTGdsrgCceBaQSIL4nvdrLxgbRSBPevg2XbX7u4QCSfLheSJEEIUUSlkbx6Q==", "requires": { - "browserslist": "^2.5.1", - "caniuse-lite": "^1.0.30000748", + "browserslist": "^4.3.3", + "caniuse-lite": "^1.0.30000898", "normalize-range": "^0.1.2", "num2fraction": "^1.2.2", - "postcss": "^6.0.13", - "postcss-value-parser": "^3.2.3" + "postcss": "^7.0.5", + "postcss-value-parser": "^3.3.1" + }, + "dependencies": { + "postcss": { + "version": "7.0.6", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-7.0.6.tgz", + "integrity": "sha512-Nq/rNjnHFcKgCDDZYO0lNsl6YWe6U7tTy+ESN+PnLxebL8uBtYX59HZqvrj7YLK5UCyll2hqDsJOo3ndzEW8Ug==", + "requires": { + "chalk": "^2.4.1", + "source-map": "^0.6.1", + "supports-color": "^5.5.0" + } + } } }, "autosize": { - "version": "3.0.21", - "resolved": "https://registry.npmjs.org/autosize/-/autosize-3.0.21.tgz", - "integrity": "sha1-8YL0DRd1fZeKE5pMnKQMTA5EhgM=" + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/autosize/-/autosize-4.0.2.tgz", + "integrity": "sha512-jnSyH2d+qdfPGpWlcuhGiHmqBJ6g3X+8T+iRwFrHPLVcdoGJE/x6Qicm6aDHfTsbgZKxyV8UU/YB2p4cjKDRRA==" }, "aws-sign2": { "version": "0.7.0", @@ -313,25 +499,14 @@ "integrity": "sha512-ReZxvNHIOv88FlT7rxcXIIC0fPt4KZqZbOlivyWtXLt8ESx84zd3kMC6iK5jVeS2qt+g7ftS7ye4fi06X5rtRQ==" }, "axobject-query": { - "version": "0.1.0", - "resolved": "https://registry.npmjs.org/axobject-query/-/axobject-query-0.1.0.tgz", - "integrity": "sha1-YvWdvFnJ+SQnWco0mWDnov48NsA=", + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/axobject-query/-/axobject-query-2.0.2.tgz", + "integrity": "sha512-MCeek8ZH7hKyO1rWUbKNQBbl4l2eY0ntk7OGi+q0RlafrCnfPxC06WZA+uebCfmYp4mNU9jRBP1AhGyf8+W3ww==", "dev": true, "requires": { "ast-types-flow": "0.0.7" } }, - "babel-code-frame": { - "version": "6.26.0", - "resolved": "https://registry.npmjs.org/babel-code-frame/-/babel-code-frame-6.26.0.tgz", - "integrity": "sha1-Y/1D99weO7fONZR9uP42mj9Yx0s=", - "dev": true, - "requires": { - "chalk": "^1.1.3", - "esutils": "^2.0.2", - "js-tokens": "^3.0.2" - } - }, "babel-plugin-react-remove-properties": { "version": "0.2.5", "resolved": "https://registry.npmjs.org/babel-plugin-react-remove-properties/-/babel-plugin-react-remove-properties-0.2.5.tgz", @@ -344,6 +519,13 @@ "requires": { "core-js": "^2.4.0", "regenerator-runtime": "^0.11.0" + }, + "dependencies": { + "core-js": { + "version": "2.5.7", + "resolved": "https://registry.npmjs.org/core-js/-/core-js-2.5.7.tgz", + "integrity": "sha512-RszJCAxg/PP6uzXVXL6BsxSXx/B05oJAQ2vkJRjyjrEcNVycaqOmNb5OTxZPE3xa5gwZduqza6L9JOCenh/Ecw==" + } } }, "balanced-match": { @@ -351,28 +533,101 @@ "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.0.tgz", "integrity": "sha1-ibTRmasr7kneFk6gK4nORi1xt2c=" }, + "base": { + "version": "0.11.2", + "resolved": "https://registry.npmjs.org/base/-/base-0.11.2.tgz", + "integrity": "sha512-5T6P4xPgpp0YDFvSWwEZ4NoE3aM4QBQXDzmVbraCkFj8zHM+mba8SyqB5DbZWyR7mYHo6Y7BdQo3MoA4m0TeQg==", + "dev": true, + "requires": { + "cache-base": "^1.0.1", + "class-utils": "^0.3.5", + "component-emitter": "^1.2.1", + "define-property": "^1.0.0", + "isobject": "^3.0.1", + "mixin-deep": "^1.2.0", + "pascalcase": "^0.1.1" + }, + "dependencies": { + "define-property": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/define-property/-/define-property-1.0.0.tgz", + "integrity": "sha1-dp66rz9KY6rTr56NMEybvnm/sOY=", + "dev": true, + "requires": { + "is-descriptor": "^1.0.0" + } + }, + "is-accessor-descriptor": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-1.0.0.tgz", + "integrity": "sha512-m5hnHTkcVsPfqx3AKlyttIPb7J+XykHvJP2B9bZDjlhLIoEq4XoK64Vg7boZlVWYK6LUY94dYPEE7Lh0ZkZKcQ==", + "dev": true, + "requires": { + "kind-of": "^6.0.0" + } + }, + "is-data-descriptor": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-1.0.0.tgz", + "integrity": "sha512-jbRXy1FmtAoCjQkVmIVYwuuqDFUbaOeDjmed1tOGPrsMhtJA4rD9tkgA0F1qJ3gRFRXcHYVkdeaP50Q5rE/jLQ==", + "dev": true, + "requires": { + "kind-of": "^6.0.0" + } + }, + "is-descriptor": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-1.0.2.tgz", + "integrity": "sha512-2eis5WqQGV7peooDyLmNEPUrps9+SXX5c9pL3xEB+4e9HnGuDa7mB7kHxHw4CbqS9k1T2hOH3miL8n8WtiYVtg==", + "dev": true, + "requires": { + "is-accessor-descriptor": "^1.0.0", + "is-data-descriptor": "^1.0.0", + "kind-of": "^6.0.2" + } + }, + "isobject": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/isobject/-/isobject-3.0.1.tgz", + "integrity": "sha1-TkMekrEalzFjaqH5yNHMvP2reN8=", + "dev": true + }, + "kind-of": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.2.tgz", + "integrity": "sha512-s5kLOcnH0XqDO+FvuaLX8DDjZ18CGFk7VygH40QoKPUQhW4e2rvM0rwUq0t8IQDOwYSeLK01U90OjzBTme2QqA==", + "dev": true + } + } + }, + "base64-js": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.3.0.tgz", + "integrity": "sha512-ccav/yGvoa80BQDljCxsmmQ3Xvx60/UpBIij5QN21W3wBi/hhIC9OoO+KLpu9IJTS9j4DRVJ3aDDF9cMSoa2lw==", + "dev": true + }, "bcrypt-pbkdf": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/bcrypt-pbkdf/-/bcrypt-pbkdf-1.0.2.tgz", "integrity": "sha1-pDAdOJtqQ/m2f/PKEaP2Y342Dp4=", - "optional": true, "requires": { "tweetnacl": "^0.14.3" } }, "bignumber.js": { "version": "2.4.0", - "resolved": "https://registry.npmjs.org/bignumber.js/-/bignumber.js-2.4.0.tgz", + "resolved": "http://registry.npmjs.org/bignumber.js/-/bignumber.js-2.4.0.tgz", "integrity": "sha1-g4qZLan51zfg9LLbC+YrsJ3Qxeg=", "dev": true }, "bl": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/bl/-/bl-1.2.1.tgz", - "integrity": "sha1-ysMo977kVzDUBLaSID/LWQ4XLV4=", + "version": "1.2.2", + "resolved": "http://registry.npmjs.org/bl/-/bl-1.2.2.tgz", + "integrity": "sha512-e8tQYnZodmebYDWGH7KMRvtzKXaJHx3BbilrgZCfvyLUYdKpK1t5PSPmpkny/SgiTSCnjfLW7v5rlONXVFkQEA==", "dev": true, "requires": { - "readable-stream": "^2.0.5" + "readable-stream": "^2.3.5", + "safe-buffer": "^5.1.1" } }, "block-stream": { @@ -399,11 +654,11 @@ } }, "browser-bunyan": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/browser-bunyan/-/browser-bunyan-1.4.0.tgz", - "integrity": "sha512-fED2PWhdLmS2zvc+oFI+2asiV7vcqw/B4QSTKzC15pIAnk5dk7Fv2zAxOYdMnbY9rcke6FH3a/FLxSSd2ZOo9g==", + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/browser-bunyan/-/browser-bunyan-1.5.0.tgz", + "integrity": "sha512-X/RLA/bJAVj6jp6UeAvvukKAKR+ALiP3aKK8aJ5z5CEZ3z0sFEhZXlhjS0kqsd7flsKSKkHKVBim9eIhHELvmA==", "requires": { - "@browser-bunyan/console-formatted-stream": "^1.3.0", + "@browser-bunyan/console-formatted-stream": "^1.5.0", "@browser-bunyan/console-plain-stream": "^1.4.0", "@browser-bunyan/console-raw-stream": "^1.3.0", "@browser-bunyan/levels": "^1.3.0" @@ -425,14 +680,41 @@ } }, "browserslist": { - "version": "2.11.3", - "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-2.11.3.tgz", - "integrity": "sha512-yWu5cXT7Av6mVwzWc8lMsJMHWn4xyjSuGYi4IozbVTLUOEYPSagUB8kiMDUHA1fS3zjr8nkxkn9jdvug4BBRmA==", + "version": "4.3.5", + "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.3.5.tgz", + "integrity": "sha512-z9ZhGc3d9e/sJ9dIx5NFXkKoaiQTnrvrMsN3R1fGb1tkWWNSz12UewJn9TNxGo1l7J23h0MRaPmk7jfeTZYs1w==", "requires": { - "caniuse-lite": "^1.0.30000792", - "electron-to-chromium": "^1.3.30" + "caniuse-lite": "^1.0.30000912", + "electron-to-chromium": "^1.3.86", + "node-releases": "^1.0.5" } }, + "buffer": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/buffer/-/buffer-5.2.1.tgz", + "integrity": "sha512-c+Ko0loDaFfuPWiL02ls9Xd3GO3cPVmUobQ6t3rXNUk304u6hGq+8N/kFi+QEIKhzK3uwolVhLzszmfLmMLnqg==", + "dev": true, + "requires": { + "base64-js": "^1.0.2", + "ieee754": "^1.1.4" + } + }, + "buffer-alloc": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/buffer-alloc/-/buffer-alloc-1.2.0.tgz", + "integrity": "sha512-CFsHQgjtW1UChdXgbyJGtnm+O/uLQeZdtbDo8mfUgYXCHSM1wgrVxXm6bSyrUuErEb+4sYVGCzASBRot7zyrow==", + "dev": true, + "requires": { + "buffer-alloc-unsafe": "^1.1.0", + "buffer-fill": "^1.0.0" + } + }, + "buffer-alloc-unsafe": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/buffer-alloc-unsafe/-/buffer-alloc-unsafe-1.1.0.tgz", + "integrity": "sha512-TEM2iMIEQdJ2yjPJoSIsldnleVaAk1oW3DBVUykyOLsEsFmEc9kn+SFFPz+gl54KQNxlDnAwCXosOS9Okx2xAg==", + "dev": true + }, "buffer-crc32": { "version": "0.2.13", "resolved": "https://registry.npmjs.org/buffer-crc32/-/buffer-crc32-0.2.13.tgz", @@ -445,11 +727,59 @@ "integrity": "sha1-kbx0sR6kBbyRa8aqkI+q+ltKrEs=", "dev": true }, + "buffer-fill": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/buffer-fill/-/buffer-fill-1.0.0.tgz", + "integrity": "sha1-+PeLdniYiO858gXNY39o5wISKyw=", + "dev": true + }, "builtin-modules": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/builtin-modules/-/builtin-modules-1.1.1.tgz", "integrity": "sha1-Jw8HbFpywC9bZaR9+Uxf46J4iS8=" }, + "cache-base": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/cache-base/-/cache-base-1.0.1.tgz", + "integrity": "sha512-AKcdTnFSWATd5/GCPRxr2ChwIJ85CeyrEyjRHlKxQ56d4XJMGym0uAiKn0xbLOGOl3+yRpOTi484dVCEc5AUzQ==", + "dev": true, + "requires": { + "collection-visit": "^1.0.0", + "component-emitter": "^1.2.1", + "get-value": "^2.0.6", + "has-value": "^1.0.0", + "isobject": "^3.0.1", + "set-value": "^2.0.0", + "to-object-path": "^0.3.0", + "union-value": "^1.0.0", + "unset-value": "^1.0.0" + }, + "dependencies": { + "isobject": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/isobject/-/isobject-3.0.1.tgz", + "integrity": "sha1-TkMekrEalzFjaqH5yNHMvP2reN8=", + "dev": true + } + } + }, + "caller-callsite": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/caller-callsite/-/caller-callsite-2.0.0.tgz", + "integrity": "sha1-hH4PzgoiN1CpoCfFSzNzGtMVQTQ=", + "dev": true, + "requires": { + "callsites": "^2.0.0" + }, + "dependencies": { + "callsites": { + "version": "2.0.0", + "resolved": "http://registry.npmjs.org/callsites/-/callsites-2.0.0.tgz", + "integrity": "sha1-BuuE8A7qQT2oav/vrL/7Ngk7PFA=", + "dev": true + } + } + }, "caller-path": { "version": "0.1.0", "resolved": "https://registry.npmjs.org/caller-path/-/caller-path-0.1.0.tgz", @@ -461,7 +791,7 @@ }, "callsites": { "version": "0.2.0", - "resolved": "https://registry.npmjs.org/callsites/-/callsites-0.2.0.tgz", + "resolved": "http://registry.npmjs.org/callsites/-/callsites-0.2.0.tgz", "integrity": "sha1-r6uWJikQp/M8GaV3WCXGnzTjUMo=", "dev": true }, @@ -472,7 +802,7 @@ }, "camelcase-keys": { "version": "2.1.0", - "resolved": "https://registry.npmjs.org/camelcase-keys/-/camelcase-keys-2.1.0.tgz", + "resolved": "http://registry.npmjs.org/camelcase-keys/-/camelcase-keys-2.1.0.tgz", "integrity": "sha1-MIvur/3ygRkFHvodkyITyRuPkuc=", "requires": { "camelcase": "^2.0.0", @@ -480,9 +810,9 @@ } }, "caniuse-lite": { - "version": "1.0.30000792", - "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30000792.tgz", - "integrity": "sha1-0M6pgfgRjzlhRxr7tDyaHlu/AzI=" + "version": "1.0.30000918", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30000918.tgz", + "integrity": "sha512-CAZ9QXGViBvhHnmIHhsTPSWFBujDaelKnUj7wwImbyQRxmXynYqKGi3UaZTSz9MoVh+1EVxOS/DFIkrJYgR3aw==" }, "caseless": { "version": "0.12.0", @@ -490,40 +820,33 @@ "integrity": "sha1-G2gcIf+EAzyCZUMJBolCDRhxUdw=" }, "chai": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chai/-/chai-4.1.2.tgz", - "integrity": "sha1-D2RYS6ZC8PKs4oBiefTwbKI61zw=", + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/chai/-/chai-4.2.0.tgz", + "integrity": "sha512-XQU3bhBukrOsQCuwZndwGcCVQHyZi53fQ6Ys1Fym7E4olpIqqZZhhoFJoaKVvV17lWQoXYwgWN2nF5crA8J2jw==", "dev": true, "requires": { - "assertion-error": "^1.0.1", - "check-error": "^1.0.1", - "deep-eql": "^3.0.0", + "assertion-error": "^1.1.0", + "check-error": "^1.0.2", + "deep-eql": "^3.0.1", "get-func-name": "^2.0.0", - "pathval": "^1.0.0", - "type-detect": "^4.0.0" + "pathval": "^1.1.0", + "type-detect": "^4.0.5" } }, - "chain-function": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/chain-function/-/chain-function-1.0.0.tgz", - "integrity": "sha1-DUqzfn4Y6tC9xHuSB2QRjOWHM9w=" - }, "chalk": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-1.1.3.tgz", - "integrity": "sha1-qBFcVeSnAv5NFQq9OHKCKn4J/Jg=", + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.1.tgz", + "integrity": "sha512-ObN6h1v2fTJSmUXoS3nMQ92LbDK9be4TV+6G+omQlGJFdcUX5heKi1LZ1YnRMIgwTLEj3E24bT6tYni50rlCfQ==", "requires": { - "ansi-styles": "^2.2.1", - "escape-string-regexp": "^1.0.2", - "has-ansi": "^2.0.0", - "strip-ansi": "^3.0.0", - "supports-color": "^2.0.0" + "ansi-styles": "^3.2.1", + "escape-string-regexp": "^1.0.5", + "supports-color": "^5.3.0" } }, "chardet": { - "version": "0.4.2", - "resolved": "https://registry.npmjs.org/chardet/-/chardet-0.4.2.tgz", - "integrity": "sha1-tUc7M9yXxCTl2Y3IfVXU2KKci/I=", + "version": "0.7.0", + "resolved": "https://registry.npmjs.org/chardet/-/chardet-0.7.0.tgz", + "integrity": "sha512-mT8iDcrh03qDGRRmoA2hmBJnxpllMR+0/0qlzjqZES6NdiWDcZkCNAk4rPFZ9Q85r27unkiNNg8ZOiwZXBHwcA==", "dev": true }, "check-error": { @@ -533,9 +856,9 @@ "dev": true }, "ci-info": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/ci-info/-/ci-info-1.1.2.tgz", - "integrity": "sha512-uTGIPNx/nSpBdsF6xnseRXLLtfr9VLqkz8ZqHXr3Y7b6SftyRxBGjwMtJj1OhNbmlc1wZzLNAlAcvyIiE8a6ZA==", + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/ci-info/-/ci-info-1.6.0.tgz", + "integrity": "sha512-vsGdkwSCDpWmP80ncATX7iea5DWQemg1UgCW5J8tqjU3lYw4FBYuj89J0CTVomA7BEfvSZd84GmHko+MxFQU2A==", "dev": true }, "circular-json": { @@ -544,6 +867,35 @@ "integrity": "sha512-UZK3NBx2Mca+b5LsG7bY183pHWt5Y1xts4P3Pz7ENTwGVnJOUWbRb3ocjvX7hx9tq/yTAdclXm9sZ38gNuem4A==", "dev": true }, + "class-utils": { + "version": "0.3.6", + "resolved": "https://registry.npmjs.org/class-utils/-/class-utils-0.3.6.tgz", + "integrity": "sha512-qOhPa/Fj7s6TY8H8esGu5QNpMMQxz79h+urzrNYN6mn+9BnxlDGf5QZ+XeCDsxSjPqsSR56XOZOJmpeurnLMeg==", + "dev": true, + "requires": { + "arr-union": "^3.1.0", + "define-property": "^0.2.5", + "isobject": "^3.0.0", + "static-extend": "^0.1.1" + }, + "dependencies": { + "define-property": { + "version": "0.2.5", + "resolved": "https://registry.npmjs.org/define-property/-/define-property-0.2.5.tgz", + "integrity": "sha1-w1se+RjsPJkPmlvFe+BKrOxcgRY=", + "dev": true, + "requires": { + "is-descriptor": "^0.1.0" + } + }, + "isobject": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/isobject/-/isobject-3.0.1.tgz", + "integrity": "sha1-TkMekrEalzFjaqH5yNHMvP2reN8=", + "dev": true + } + } + }, "classnames": { "version": "2.2.6", "resolved": "https://registry.npmjs.org/classnames/-/classnames-2.2.6.tgz", @@ -558,12 +910,6 @@ "restore-cursor": "^2.0.0" } }, - "cli-spinners": { - "version": "0.1.2", - "resolved": "https://registry.npmjs.org/cli-spinners/-/cli-spinners-0.1.2.tgz", - "integrity": "sha1-u3ZNiOGF+54eaiofGXcjGPYF4xw=", - "dev": true - }, "cli-truncate": { "version": "0.2.1", "resolved": "https://registry.npmjs.org/cli-truncate/-/cli-truncate-0.2.1.tgz", @@ -576,7 +922,7 @@ "dependencies": { "slice-ansi": { "version": "0.0.4", - "resolved": "https://registry.npmjs.org/slice-ansi/-/slice-ansi-0.0.4.tgz", + "resolved": "http://registry.npmjs.org/slice-ansi/-/slice-ansi-0.0.4.tgz", "integrity": "sha1-7b+JA/ZvfOL46v1s7tZeJkyDGzU=", "dev": true } @@ -589,9 +935,9 @@ "dev": true }, "clipboard": { - "version": "1.7.1", - "resolved": "https://registry.npmjs.org/clipboard/-/clipboard-1.7.1.tgz", - "integrity": "sha1-Ng1taUbpmnof7zleQrqStem1oWs=", + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/clipboard/-/clipboard-2.0.4.tgz", + "integrity": "sha512-Vw26VSLRpJfBofiVaFb/I8PVfdI1OxKcYShe6fm0sP/DtmiWQNCjhM/okTvdCo0G+lMMm1rMYbk4IK4x1X+kgQ==", "requires": { "good-listener": "^1.2.2", "select": "^1.1.2", @@ -608,16 +954,30 @@ "wrap-ansi": "^2.0.0" } }, - "co": { - "version": "4.6.0", - "resolved": "https://registry.npmjs.org/co/-/co-4.6.0.tgz", - "integrity": "sha1-bqa989hTrlTMuOR7+gvz+QMfsYQ=" - }, "code-point-at": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/code-point-at/-/code-point-at-1.1.0.tgz", "integrity": "sha1-DQcLTQQ6W+ozovGkDi7bPZpMz3c=" }, + "collection-visit": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/collection-visit/-/collection-visit-1.0.0.tgz", + "integrity": "sha1-S8A3PBZLwykbTTaMgpzxqApZ3KA=", + "dev": true, + "requires": { + "map-visit": "^1.0.0", + "object-visit": "^1.0.0" + } + }, + "color": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/color/-/color-3.0.0.tgz", + "integrity": "sha512-jCpd5+s0s0t7p3pHQKpnJ0TpQKKdleP71LWcA0aqiljpiuAkOSUFN/dyH8ZwF0hRmFlrIuRhufds1QyEP9EB+w==", + "requires": { + "color-convert": "^1.9.1", + "color-string": "^1.5.2" + } + }, "color-convert": { "version": "1.9.1", "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.1.tgz", @@ -631,23 +991,52 @@ "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", "integrity": "sha1-p9BVi9icQveV3UIyj3QIMcpTvCU=" }, + "color-string": { + "version": "1.5.3", + "resolved": "https://registry.npmjs.org/color-string/-/color-string-1.5.3.tgz", + "integrity": "sha512-dC2C5qeWoYkxki5UAXapdjqO672AM4vZuPGRQfO8b5HKuKGBbKWpITyDYN7TOFKvRW7kOgAn3746clDBMDJyQw==", + "requires": { + "color-name": "^1.0.0", + "simple-swizzle": "^0.2.2" + } + }, + "colornames": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/colornames/-/colornames-1.1.1.tgz", + "integrity": "sha1-+IiQMGhcfE/54qVZ9Qd+t2qBb5Y=" + }, "colors": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/colors/-/colors-1.0.3.tgz", - "integrity": "sha1-BDP0TYCWgP3rYO0mDxsMJi6CpAs=" + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/colors/-/colors-1.3.3.tgz", + "integrity": "sha512-mmGt/1pZqYRjMxB1axhTo16/snVZ5krrKkcmMeVKxzECMMXoCgnvTPp10QgHfcbQZw8Dq2jMNG6je4JlWU0gWg==" + }, + "colorspace": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/colorspace/-/colorspace-1.1.1.tgz", + "integrity": "sha512-pI3btWyiuz7Ken0BWh9Elzsmv2bM9AhA7psXib4anUXy/orfZ/E0MbQwhSOG/9L8hLlalqrU0UhOuqxW1YjmVw==", + "requires": { + "color": "3.0.x", + "text-hex": "1.0.x" + } }, "combined-stream": { - "version": "1.0.6", - "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.6.tgz", - "integrity": "sha1-cj599ugBrFYTETp+RFqbactjKBg=", + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.7.tgz", + "integrity": "sha512-brWl9y6vOB1xYPZcpZde3N9zDByXTosAeMDo4p1wzo6UMOX4vumB+TP1RZ76sfE6Md68Q0NJSrE/gbezd4Ul+w==", "requires": { "delayed-stream": "~1.0.0" } }, "commander": { - "version": "2.13.0", - "resolved": "https://registry.npmjs.org/commander/-/commander-2.13.0.tgz", - "integrity": "sha512-MVuS359B+YzaWqjCL/c+22gfryv+mCBPHAv3zyVI2GN8EY6IRP8VwtasXn8jyyhvvq84R4ImN1OKRtcbIasjYA==", + "version": "2.19.0", + "resolved": "https://registry.npmjs.org/commander/-/commander-2.19.0.tgz", + "integrity": "sha512-6tvAOO+D6OENvRAh524Dh9jcfKTYDQAqvqezbCW82xj5X0pSrcpxtvRKHLG0yBY6SD7PSDrJaj+0AiOcKVd1Xg==", + "dev": true + }, + "component-emitter": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/component-emitter/-/component-emitter-1.2.1.tgz", + "integrity": "sha1-E3kY1teCg/ffemt8WmPhQOaUJeY=", "dev": true }, "compress-commons": { @@ -660,17 +1049,6 @@ "crc32-stream": "^2.0.0", "normalize-path": "^2.0.0", "readable-stream": "^2.0.0" - }, - "dependencies": { - "normalize-path": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-2.1.1.tgz", - "integrity": "sha1-GrKLVW4Zg2Oowab35vogE3/mrtk=", - "dev": true, - "requires": { - "remove-trailing-separator": "^1.0.1" - } - } } }, "computed-style": { @@ -683,17 +1061,6 @@ "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", "integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=" }, - "concat-stream": { - "version": "1.6.0", - "resolved": "https://registry.npmjs.org/concat-stream/-/concat-stream-1.6.0.tgz", - "integrity": "sha1-CqxmL9Ur54lk1VMvaUeE5wEQrPc=", - "dev": true, - "requires": { - "inherits": "^2.0.3", - "readable-stream": "^2.2.2", - "typedarray": "^0.0.6" - } - }, "console-control-strings": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/console-control-strings/-/console-control-strings-1.1.0.tgz", @@ -705,10 +1072,16 @@ "integrity": "sha1-/ozxhP9mcLa67wGp1IYaXL7EEgo=", "dev": true }, + "copy-descriptor": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/copy-descriptor/-/copy-descriptor-0.1.1.tgz", + "integrity": "sha1-Z29us8OZl8LuGsOpJP1hJHSPV40=", + "dev": true + }, "core-js": { - "version": "2.5.7", - "resolved": "https://registry.npmjs.org/core-js/-/core-js-2.5.7.tgz", - "integrity": "sha512-RszJCAxg/PP6uzXVXL6BsxSXx/B05oJAQ2vkJRjyjrEcNVycaqOmNb5OTxZPE3xa5gwZduqza6L9JOCenh/Ecw==" + "version": "2.6.0", + "resolved": "https://registry.npmjs.org/core-js/-/core-js-2.6.0.tgz", + "integrity": "sha512-kLRC6ncVpuEW/1kwrOXYX6KQASCVtrh1gQr/UiaVgFlf9WE5Vp+lNe5+h3LuMr5PAucWnnEXwH0nQHRH/gpGtw==" }, "core-util-is": { "version": "1.0.2", @@ -716,34 +1089,37 @@ "integrity": "sha1-tf1UIgqivFq1eqtxQMlAdUUDwac=" }, "cosmiconfig": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/cosmiconfig/-/cosmiconfig-1.1.0.tgz", - "integrity": "sha1-DeoPmATv37kp+7GxiOJVU+oFPTc=", + "version": "5.0.7", + "resolved": "https://registry.npmjs.org/cosmiconfig/-/cosmiconfig-5.0.7.tgz", + "integrity": "sha512-PcLqxTKiDmNT6pSpy4N6KtuPwb53W+2tzNvwOZw0WH9N6O0vLIBq0x8aj8Oj75ere4YcGi48bDFCL+3fRJdlNA==", "dev": true, "requires": { - "graceful-fs": "^4.1.2", - "js-yaml": "^3.4.3", - "minimist": "^1.2.0", - "object-assign": "^4.0.1", - "os-homedir": "^1.0.1", - "parse-json": "^2.2.0", - "pinkie-promise": "^2.0.0", - "require-from-string": "^1.1.0" + "import-fresh": "^2.0.0", + "is-directory": "^0.3.1", + "js-yaml": "^3.9.0", + "parse-json": "^4.0.0" }, "dependencies": { - "minimist": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.0.tgz", - "integrity": "sha1-o1AIsg9BOD7sH7kU9M1d95omQoQ=", - "dev": true + "parse-json": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-4.0.0.tgz", + "integrity": "sha1-vjX1Qlvh9/bHRxhPmKeIy5lHfuA=", + "dev": true, + "requires": { + "error-ex": "^1.3.1", + "json-parse-better-errors": "^1.0.1" + } } } }, "crc": { - "version": "3.5.0", - "resolved": "https://registry.npmjs.org/crc/-/crc-3.5.0.tgz", - "integrity": "sha1-mLi6fUiWZbo5efWbITgTdBAaGWQ=", - "dev": true + "version": "3.8.0", + "resolved": "https://registry.npmjs.org/crc/-/crc-3.8.0.tgz", + "integrity": "sha512-iX3mfgcTMIq3ZKLIsVFAbv7+Mc10kxabAGQb8HvjA1o3T1PIYprbakQ65d3I+2HGHt6nSKkM9PYjgoJO2KcFBQ==", + "dev": true, + "requires": { + "buffer": "^5.1.0" + } }, "crc32-stream": { "version": "2.0.0", @@ -774,14 +1150,6 @@ "source-map": "^0.6.1", "source-map-resolve": "^0.5.2", "urix": "^0.1.0" - }, - "dependencies": { - "source-map": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", - "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", - "dev": true - } } }, "css-parse": { @@ -829,11 +1197,6 @@ "resolved": "https://registry.npmjs.org/custom-event-polyfill/-/custom-event-polyfill-0.3.0.tgz", "integrity": "sha1-mYB4Ob5i7bRGtkWDLg2A6tb6GIg=" }, - "cycle": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/cycle/-/cycle-1.0.3.tgz", - "integrity": "sha1-IegLK+hYD5i0aPN5QwZisEbDStI=" - }, "damerau-levenshtein": { "version": "1.0.4", "resolved": "https://registry.npmjs.org/damerau-levenshtein/-/damerau-levenshtein-1.0.4.tgz", @@ -849,14 +1212,14 @@ } }, "date-fns": { - "version": "1.29.0", - "resolved": "https://registry.npmjs.org/date-fns/-/date-fns-1.29.0.tgz", - "integrity": "sha512-lbTXWZ6M20cWH8N9S6afb0SBm6tMk+uUg6z3MqHPKE9atmsY3kJkTm8vKe93izJ2B2+q5MV990sM2CHgtAZaOw==", + "version": "1.30.1", + "resolved": "https://registry.npmjs.org/date-fns/-/date-fns-1.30.1.tgz", + "integrity": "sha512-hBSVCvSmWC+QypYObzwGOd9wqdDpOt+0wl0KbU+R+uuZBS1jN8VsD1ss3irQDknRj5NvxiTF6oj/nDRnN/UQNw==", "dev": true }, "date-format": { "version": "0.0.2", - "resolved": "https://registry.npmjs.org/date-format/-/date-format-0.0.2.tgz", + "resolved": "http://registry.npmjs.org/date-format/-/date-format-0.0.2.tgz", "integrity": "sha1-+v1Ej3IRXvHitzkVWukvK+bCjdE=", "dev": true }, @@ -879,13 +1242,11 @@ "integrity": "sha1-6zkTMzRYd1y4TNGh+uBiEGu4dUU=", "dev": true }, - "decompress-response": { - "version": "3.3.0", - "resolved": "https://registry.npmjs.org/decompress-response/-/decompress-response-3.3.0.tgz", - "integrity": "sha1-gKTdMjdIOEv6JICDYirt7Jgq3/M=", - "requires": { - "mimic-response": "^1.0.0" - } + "dedent": { + "version": "0.7.0", + "resolved": "https://registry.npmjs.org/dedent/-/dedent-0.7.0.tgz", + "integrity": "sha1-JJXduvbrh0q7Dhvp3yLS5aVEMmw=", + "dev": true }, "deep-eql": { "version": "3.0.1", @@ -903,33 +1264,92 @@ "dev": true }, "deepmerge": { - "version": "1.5.2", - "resolved": "https://registry.npmjs.org/deepmerge/-/deepmerge-1.5.2.tgz", - "integrity": "sha512-95k0GDqvBjZavkuvzx/YqVLv/6YYa17fz6ILMSf7neqQITCPbnfEnQvEgMPNjH4kgobe7+WIL0yJEHku+H3qtQ==" + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/deepmerge/-/deepmerge-2.2.1.tgz", + "integrity": "sha512-R9hc1Xa/NOBi9WRVUWg19rl1UB7Tt4kuPd+thNJgFZoxXsTz7ncaPaeIm+40oSGuP33DfMb4sZt1QIGiJzC4EA==" }, "define-properties": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/define-properties/-/define-properties-1.1.2.tgz", - "integrity": "sha1-g6c/L+pWmJj7c3GTyPhzyvbUXJQ=", + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/define-properties/-/define-properties-1.1.3.tgz", + "integrity": "sha512-3MqfYKj2lLzdMSf8ZIZE/V+Zuy+BgD6f164e8K2w7dgnpKArBDerGYpM46IYYcjnkdPNMjPk9A6VFB8+3SKlXQ==", "dev": true, "requires": { - "foreach": "^2.0.5", - "object-keys": "^1.0.8" + "object-keys": "^1.0.12" + } + }, + "define-property": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/define-property/-/define-property-2.0.2.tgz", + "integrity": "sha512-jwK2UV4cnPpbcG7+VRARKTZPUWowwXA8bzH5NP6ud0oeAxyYPuGZUAC7hMugpCdz4BeSZl2Dl9k66CHJ/46ZYQ==", + "dev": true, + "requires": { + "is-descriptor": "^1.0.2", + "isobject": "^3.0.1" + }, + "dependencies": { + "is-accessor-descriptor": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-1.0.0.tgz", + "integrity": "sha512-m5hnHTkcVsPfqx3AKlyttIPb7J+XykHvJP2B9bZDjlhLIoEq4XoK64Vg7boZlVWYK6LUY94dYPEE7Lh0ZkZKcQ==", + "dev": true, + "requires": { + "kind-of": "^6.0.0" + } + }, + "is-data-descriptor": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-1.0.0.tgz", + "integrity": "sha512-jbRXy1FmtAoCjQkVmIVYwuuqDFUbaOeDjmed1tOGPrsMhtJA4rD9tkgA0F1qJ3gRFRXcHYVkdeaP50Q5rE/jLQ==", + "dev": true, + "requires": { + "kind-of": "^6.0.0" + } + }, + "is-descriptor": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-1.0.2.tgz", + "integrity": "sha512-2eis5WqQGV7peooDyLmNEPUrps9+SXX5c9pL3xEB+4e9HnGuDa7mB7kHxHw4CbqS9k1T2hOH3miL8n8WtiYVtg==", + "dev": true, + "requires": { + "is-accessor-descriptor": "^1.0.0", + "is-data-descriptor": "^1.0.0", + "kind-of": "^6.0.2" + } + }, + "isobject": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/isobject/-/isobject-3.0.1.tgz", + "integrity": "sha1-TkMekrEalzFjaqH5yNHMvP2reN8=", + "dev": true + }, + "kind-of": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.2.tgz", + "integrity": "sha512-s5kLOcnH0XqDO+FvuaLX8DDjZ18CGFk7VygH40QoKPUQhW4e2rvM0rwUq0t8IQDOwYSeLK01U90OjzBTme2QqA==", + "dev": true + } } }, "del": { - "version": "2.2.2", - "resolved": "https://registry.npmjs.org/del/-/del-2.2.2.tgz", - "integrity": "sha1-wSyYHQZ4RshLyvhiz/kw2Qf/0ag=", + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/del/-/del-3.0.0.tgz", + "integrity": "sha1-U+z2mf/LyzljdpGrE7rxYIGXZuU=", "dev": true, "requires": { - "globby": "^5.0.0", + "globby": "^6.1.0", "is-path-cwd": "^1.0.0", "is-path-in-cwd": "^1.0.0", - "object-assign": "^4.0.1", - "pify": "^2.0.0", - "pinkie-promise": "^2.0.0", + "p-map": "^1.1.1", + "pify": "^3.0.0", "rimraf": "^2.2.8" + }, + "dependencies": { + "pify": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/pify/-/pify-3.0.0.tgz", + "integrity": "sha1-5aSs0sEB/fPZpNB/DbxNtJ3SgXY=", + "dev": true + } } }, "delayed-stream": { @@ -947,6 +1367,21 @@ "resolved": "https://registry.npmjs.org/delegates/-/delegates-1.0.0.tgz", "integrity": "sha1-hMbhWbgZBP3KWaDvRM2HDTElD5o=" }, + "detect-libc": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-1.0.3.tgz", + "integrity": "sha1-+hN8S9aY7fVc1c0CrFWfkaTEups=" + }, + "diagnostics": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/diagnostics/-/diagnostics-1.1.1.tgz", + "integrity": "sha512-8wn1PmdunLJ9Tqbx+Fx/ZEuHfJf4NKSN2ZBj7SJC/OWRWha843+WsTjqMe1B5E3p28jqBlp+mJ2fPVxPyNgYKQ==", + "requires": { + "colorspace": "1.1.x", + "enabled": "1.0.x", + "kuler": "1.0.x" + } + }, "doctrine": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-2.1.0.tgz", @@ -957,9 +1392,12 @@ } }, "dom-helpers": { - "version": "3.3.1", - "resolved": "https://registry.npmjs.org/dom-helpers/-/dom-helpers-3.3.1.tgz", - "integrity": "sha512-2Sm+JaYn74OiTM2wHvxJOo3roiq/h25Yi69Fqk269cNUwIXsCvATB6CRSFC9Am/20G2b28hGv/+7NiWydIrPvg==" + "version": "3.4.0", + "resolved": "https://registry.npmjs.org/dom-helpers/-/dom-helpers-3.4.0.tgz", + "integrity": "sha512-LnuPJ+dwqKDIyotW1VzmOZ5TONUN7CwkCR5hrgawTUbkBGYdeoNLZo6nNfGkCrjtE1nXXaj7iMMpDa8/d9WoIA==", + "requires": { + "@babel/runtime": "^7.1.2" + } }, "dom-walk": { "version": "0.1.1", @@ -967,36 +1405,38 @@ "integrity": "sha1-ZyIm3HTI95mtNTB9+TaroRrNYBg=", "dev": true }, + "dot-prop": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/dot-prop/-/dot-prop-4.2.0.tgz", + "integrity": "sha512-tUMXrxlExSW6U2EXiiKGSBVdYgtV8qlHL+C10TsW4PURY/ic+eaysnSkwB4kA/mBlCyy/IKDJ+Lc3wbWeaXtuQ==", + "requires": { + "is-obj": "^1.0.0" + } + }, "double-ended-queue": { "version": "2.1.0-0", "resolved": "https://registry.npmjs.org/double-ended-queue/-/double-ended-queue-2.1.0-0.tgz", "integrity": "sha1-ED01J/0xUo9AGIEwyEHv3XgmTlw=" }, - "duplexer3": { - "version": "0.1.4", - "resolved": "https://registry.npmjs.org/duplexer3/-/duplexer3-0.1.4.tgz", - "integrity": "sha1-7gHdHKwO08vH/b6jfcCo8c4ALOI=" - }, "ecc-jsbn": { "version": "0.1.2", "resolved": "https://registry.npmjs.org/ecc-jsbn/-/ecc-jsbn-0.1.2.tgz", "integrity": "sha1-OoOpBOVDUyh4dMVkt1SThoSamMk=", - "optional": true, "requires": { "jsbn": "~0.1.0", "safer-buffer": "^2.1.0" } }, "ejs": { - "version": "2.5.7", - "resolved": "https://registry.npmjs.org/ejs/-/ejs-2.5.7.tgz", - "integrity": "sha1-zIcsFoiArjxxiXYv1f/ACJbJUYo=", + "version": "2.5.9", + "resolved": "https://registry.npmjs.org/ejs/-/ejs-2.5.9.tgz", + "integrity": "sha512-GJCAeDBKfREgkBtgrYSf9hQy9kTb3helv0zGdzqhM7iAkW8FA/ZF97VQDbwFiwIT8MQLLOe5VlPZOEvZAqtUAQ==", "dev": true }, "electron-to-chromium": { - "version": "1.3.31", - "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.3.31.tgz", - "integrity": "sha512-XE4CLbswkZgZFn34cKFy1xaX+F5LHxeDLjY1+rsK9asDzknhbrd9g/n/01/acbU25KTsUSiLKwvlLyA+6XLUOA==" + "version": "1.3.90", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.3.90.tgz", + "integrity": "sha512-IjJZKRhFbWSOX1w0sdIXgp4CMRguu6UYcTckyFF/Gjtemsu/25eZ+RXwFlV+UWcIueHyQA1UnRJxocTpH5NdGA==" }, "elegant-spinner": { "version": "1.0.1", @@ -1010,6 +1450,14 @@ "integrity": "sha512-PAHp6TxrCy7MGMFidro8uikr+zlJJKJ/Q6mm2ExZ7HwkyR9lSVFfE3kt36qcwa24BQL7y0G9axycGjK1A/0uNQ==", "dev": true }, + "enabled": { + "version": "1.0.2", + "resolved": "http://registry.npmjs.org/enabled/-/enabled-1.0.2.tgz", + "integrity": "sha1-ll9lE9LC0cX0ZStkouM5ZGf8L5M=", + "requires": { + "env-variable": "0.0.x" + } + }, "encoding": { "version": "0.1.12", "resolved": "https://registry.npmjs.org/encoding/-/encoding-0.1.12.tgz", @@ -1027,6 +1475,11 @@ "once": "^1.4.0" } }, + "env-variable": { + "version": "0.0.5", + "resolved": "https://registry.npmjs.org/env-variable/-/env-variable-0.0.5.tgz", + "integrity": "sha512-zoB603vQReOFvTg5xMl9I1P2PnHsHQQKTEowsKKD7nseUfJq6UWzK+4YtlWUO1nhiQUxe6XMkk+JleSZD1NZFA==" + }, "error-ex": { "version": "1.3.2", "resolved": "https://registry.npmjs.org/error-ex/-/error-ex-1.3.2.tgz", @@ -1036,9 +1489,9 @@ } }, "es-abstract": { - "version": "1.10.0", - "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.10.0.tgz", - "integrity": "sha512-/uh/DhdqIOSkAWifU+8nG78vlQxdLckUdI/sPgy0VhuXi2qJ7T8czBmqIYtLQVpCIFYafChnsRsB5pyb1JdmCQ==", + "version": "1.12.0", + "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.12.0.tgz", + "integrity": "sha512-C8Fx/0jFmV5IPoMOFPA9P9G5NtqW+4cOPit3MIuvR2t7Ag2K15EJTpxnHAYTzL+aYQJIESYeXZmDBfOBE1HcpA==", "dev": true, "requires": { "es-to-primitive": "^1.1.1", @@ -1049,19 +1502,19 @@ } }, "es-to-primitive": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/es-to-primitive/-/es-to-primitive-1.1.1.tgz", - "integrity": "sha1-RTVSSKiJeQNLZ5Lhm7gfK3l13Q0=", + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/es-to-primitive/-/es-to-primitive-1.2.0.tgz", + "integrity": "sha512-qZryBOJjV//LaxLTV6UC//WewneB3LcXOL9NP++ozKVXsIIIpm/2c13UDiD9Jp2eThsecw9m3jPqDwTyobcdbg==", "dev": true, "requires": { - "is-callable": "^1.1.1", + "is-callable": "^1.1.4", "is-date-object": "^1.0.1", - "is-symbol": "^1.0.1" + "is-symbol": "^1.0.2" } }, "es6-promise": { "version": "3.3.1", - "resolved": "https://registry.npmjs.org/es6-promise/-/es6-promise-3.3.1.tgz", + "resolved": "http://registry.npmjs.org/es6-promise/-/es6-promise-3.3.1.tgz", "integrity": "sha1-oIzd6EzNvzTQJ6FFG8kdS80ophM=", "dev": true }, @@ -1071,48 +1524,49 @@ "integrity": "sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ=" }, "eslint": { - "version": "4.9.0", - "resolved": "https://registry.npmjs.org/eslint/-/eslint-4.9.0.tgz", - "integrity": "sha1-doedJ0BoJhsZH+Dy9Wx0wvQgjos=", + "version": "5.8.0", + "resolved": "https://registry.npmjs.org/eslint/-/eslint-5.8.0.tgz", + "integrity": "sha512-Zok6Bru3y2JprqTNm14mgQ15YQu/SMDkWdnmHfFg770DIUlmMFd/gqqzCHekxzjHZJxXv3tmTpH0C1icaYJsRQ==", "dev": true, "requires": { - "ajv": "^5.2.0", - "babel-code-frame": "^6.22.0", + "@babel/code-frame": "^7.0.0", + "ajv": "^6.5.3", "chalk": "^2.1.0", - "concat-stream": "^1.6.0", - "cross-spawn": "^5.1.0", - "debug": "^3.0.1", - "doctrine": "^2.0.0", - "eslint-scope": "^3.7.1", - "espree": "^3.5.1", - "esquery": "^1.0.0", - "estraverse": "^4.2.0", + "cross-spawn": "^6.0.5", + "debug": "^4.0.1", + "doctrine": "^2.1.0", + "eslint-scope": "^4.0.0", + "eslint-utils": "^1.3.1", + "eslint-visitor-keys": "^1.0.0", + "espree": "^4.0.0", + "esquery": "^1.0.1", "esutils": "^2.0.2", "file-entry-cache": "^2.0.0", "functional-red-black-tree": "^1.0.1", "glob": "^7.1.2", - "globals": "^9.17.0", - "ignore": "^3.3.3", + "globals": "^11.7.0", + "ignore": "^4.0.6", "imurmurhash": "^0.1.4", - "inquirer": "^3.0.6", - "is-resolvable": "^1.0.0", - "js-yaml": "^3.9.1", - "json-stable-stringify": "^1.0.1", + "inquirer": "^6.1.0", + "is-resolvable": "^1.1.0", + "js-yaml": "^3.12.0", + "json-stable-stringify-without-jsonify": "^1.0.1", "levn": "^0.3.0", - "lodash": "^4.17.4", - "minimatch": "^3.0.2", + "lodash": "^4.17.5", + "minimatch": "^3.0.4", "mkdirp": "^0.5.1", "natural-compare": "^1.4.0", "optionator": "^0.8.2", "path-is-inside": "^1.0.2", "pluralize": "^7.0.0", "progress": "^2.0.0", + "regexpp": "^2.0.1", "require-uncached": "^1.0.3", - "semver": "^5.3.0", + "semver": "^5.5.1", "strip-ansi": "^4.0.0", - "strip-json-comments": "~2.0.1", - "table": "^4.0.1", - "text-table": "~0.2.0" + "strip-json-comments": "^2.0.1", + "table": "^5.0.2", + "text-table": "^0.2.0" }, "dependencies": { "ansi-regex": { @@ -1121,46 +1575,34 @@ "integrity": "sha1-7QMXwyIGT3lGbAKWa922Bas32Zg=", "dev": true }, - "ansi-styles": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.0.tgz", - "integrity": "sha512-NnSOmMEYtVR2JVMIGTzynRkkaxtiq1xnFBcdQD/DnNCYPoEPsVJhM98BDyaoNOQIi7p4okdi3E27eN7GQbsUug==", - "dev": true, - "requires": { - "color-convert": "^1.9.0" - } - }, - "chalk": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.3.0.tgz", - "integrity": "sha512-Az5zJR2CBujap2rqXGaJKaPHyJ0IrUimvYNX+ncCy8PJP4ltOGTrHUIo097ZaL2zMeKYpiCdqDvS6zdrTFok3Q==", - "dev": true, - "requires": { - "ansi-styles": "^3.1.0", - "escape-string-regexp": "^1.0.5", - "supports-color": "^4.0.0" - } - }, "cross-spawn": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-5.1.0.tgz", - "integrity": "sha1-6L0O/uWPz/b4+UUQoKVUu/ojVEk=", + "version": "6.0.5", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-6.0.5.tgz", + "integrity": "sha512-eTVLrBSt7fjbDygz805pMnstIs2VTBNkRm0qxZd+M7A5XDdxVRWO5MxGBXZhjY4cqLYLdtrGqRf8mBPmzwSpWQ==", "dev": true, "requires": { - "lru-cache": "^4.0.1", + "nice-try": "^1.0.4", + "path-key": "^2.0.1", + "semver": "^5.5.0", "shebang-command": "^1.2.0", "which": "^1.2.9" } }, "debug": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/debug/-/debug-3.1.0.tgz", - "integrity": "sha512-OX8XqP7/1a9cqkxYw2yXss15f26NKWBpDXQd0/uK/KPqdQhxbPa994hnzjcE2VqQpDslf55723cKPUOGSmMY3g==", + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.1.0.tgz", + "integrity": "sha512-heNPJUJIqC+xB6ayLAMHaIrmN9HKa7aQO8MGqKpvCA+uJYVcvR6l5kgdrhRuwPFHU7P5/A1w0BjByPHwpfTDKg==", "dev": true, "requires": { - "ms": "2.0.0" + "ms": "^2.1.1" } }, + "ms": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.1.tgz", + "integrity": "sha512-tgp+dl5cGk28utYktBsrFqA7HKgrhgPsg6Z/EfhWI4gl1Hwq8B/GmY/0oXZ6nF8hDVesS/FpnYaD/kOWhYQvyg==", + "dev": true + }, "strip-ansi": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-4.0.0.tgz", @@ -1169,34 +1611,29 @@ "requires": { "ansi-regex": "^3.0.0" } - }, - "supports-color": { - "version": "4.5.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-4.5.0.tgz", - "integrity": "sha1-vnoN5ITexcXN34s9WRJQRJEvY1s=", - "dev": true, - "requires": { - "has-flag": "^2.0.0" - } } } }, "eslint-config-airbnb": { - "version": "16.1.0", - "resolved": "https://registry.npmjs.org/eslint-config-airbnb/-/eslint-config-airbnb-16.1.0.tgz", - "integrity": "sha512-zLyOhVWhzB/jwbz7IPSbkUuj7X2ox4PHXTcZkEmDqTvd0baJmJyuxlFPDlZOE/Y5bC+HQRaEkT3FoHo9wIdRiw==", + "version": "17.1.0", + "resolved": "https://registry.npmjs.org/eslint-config-airbnb/-/eslint-config-airbnb-17.1.0.tgz", + "integrity": "sha512-R9jw28hFfEQnpPau01NO5K/JWMGLi6aymiF6RsnMURjTk+MqZKllCqGK/0tOvHkPi/NWSSOU2Ced/GX++YxLnw==", "dev": true, "requires": { - "eslint-config-airbnb-base": "^12.1.0" + "eslint-config-airbnb-base": "^13.1.0", + "object.assign": "^4.1.0", + "object.entries": "^1.0.4" } }, "eslint-config-airbnb-base": { - "version": "12.1.0", - "resolved": "https://registry.npmjs.org/eslint-config-airbnb-base/-/eslint-config-airbnb-base-12.1.0.tgz", - "integrity": "sha512-/vjm0Px5ZCpmJqnjIzcFb9TKZrKWz0gnuG/7Gfkt0Db1ELJR51xkZth+t14rYdqWgX836XbuxtArbIHlVhbLBA==", + "version": "13.1.0", + "resolved": "https://registry.npmjs.org/eslint-config-airbnb-base/-/eslint-config-airbnb-base-13.1.0.tgz", + "integrity": "sha512-XWwQtf3U3zIoKO1BbHh6aUhJZQweOwSt4c2JrPDg9FP3Ltv3+YfEv7jIDB8275tVnO/qOHbfuYg3kzw6Je7uWw==", "dev": true, "requires": { - "eslint-restricted-globals": "^0.1.1" + "eslint-restricted-globals": "^0.1.1", + "object.assign": "^4.1.0", + "object.entries": "^1.0.4" } }, "eslint-import-resolver-node": { @@ -1210,9 +1647,9 @@ } }, "eslint-module-utils": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/eslint-module-utils/-/eslint-module-utils-2.1.1.tgz", - "integrity": "sha512-jDI/X5l/6D1rRD/3T43q8Qgbls2nq5km5KSqiwlyUbGo5+04fXhMKdCPhjwbqAa6HXWaMxj8Q4hQDIh7IadJQw==", + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/eslint-module-utils/-/eslint-module-utils-2.2.0.tgz", + "integrity": "sha1-snA2LNiLGkitMIl2zn+lTphBF0Y=", "dev": true, "requires": { "debug": "^2.6.8", @@ -1220,26 +1657,26 @@ } }, "eslint-plugin-import": { - "version": "2.8.0", - "resolved": "https://registry.npmjs.org/eslint-plugin-import/-/eslint-plugin-import-2.8.0.tgz", - "integrity": "sha512-Rf7dfKJxZ16QuTgVv1OYNxkZcsu/hULFnC+e+w0Gzi6jMC3guQoWQgxYxc54IDRinlb6/0v5z/PxxIKmVctN+g==", + "version": "2.14.0", + "resolved": "https://registry.npmjs.org/eslint-plugin-import/-/eslint-plugin-import-2.14.0.tgz", + "integrity": "sha512-FpuRtniD/AY6sXByma2Wr0TXvXJ4nA/2/04VPlfpmUDPOpOY264x+ILiwnrk/k4RINgDAyFZByxqPUbSQ5YE7g==", "dev": true, "requires": { - "builtin-modules": "^1.1.1", "contains-path": "^0.1.0", "debug": "^2.6.8", "doctrine": "1.5.0", "eslint-import-resolver-node": "^0.3.1", - "eslint-module-utils": "^2.1.1", + "eslint-module-utils": "^2.2.0", "has": "^1.0.1", - "lodash.cond": "^4.3.0", + "lodash": "^4.17.4", "minimatch": "^3.0.3", - "read-pkg-up": "^2.0.0" + "read-pkg-up": "^2.0.0", + "resolve": "^1.6.0" }, "dependencies": { "doctrine": { "version": "1.5.0", - "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-1.5.0.tgz", + "resolved": "http://registry.npmjs.org/doctrine/-/doctrine-1.5.0.tgz", "integrity": "sha1-N53Ocw9hZvds76TmcHoVmwLFpvo=", "dev": true, "requires": { @@ -1258,7 +1695,7 @@ }, "load-json-file": { "version": "2.0.0", - "resolved": "https://registry.npmjs.org/load-json-file/-/load-json-file-2.0.0.tgz", + "resolved": "http://registry.npmjs.org/load-json-file/-/load-json-file-2.0.0.tgz", "integrity": "sha1-eUfkIUmvgNaWy/eXvKq8/h/inKg=", "dev": true, "requires": { @@ -1307,30 +1744,32 @@ } }, "eslint-plugin-jsx-a11y": { - "version": "6.0.3", - "resolved": "https://registry.npmjs.org/eslint-plugin-jsx-a11y/-/eslint-plugin-jsx-a11y-6.0.3.tgz", - "integrity": "sha1-VFg9GuRCSDFi4EDhPMMYZUZRAOU=", + "version": "6.1.2", + "resolved": "https://registry.npmjs.org/eslint-plugin-jsx-a11y/-/eslint-plugin-jsx-a11y-6.1.2.tgz", + "integrity": "sha512-7gSSmwb3A+fQwtw0arguwMdOdzmKUgnUcbSNlo+GjKLAQFuC2EZxWqG9XHRI8VscBJD5a8raz3RuxQNFW+XJbw==", "dev": true, "requires": { - "aria-query": "^0.7.0", + "aria-query": "^3.0.0", "array-includes": "^3.0.3", - "ast-types-flow": "0.0.7", - "axobject-query": "^0.1.0", - "damerau-levenshtein": "^1.0.0", - "emoji-regex": "^6.1.0", - "jsx-ast-utils": "^2.0.0" + "ast-types-flow": "^0.0.7", + "axobject-query": "^2.0.1", + "damerau-levenshtein": "^1.0.4", + "emoji-regex": "^6.5.1", + "has": "^1.0.3", + "jsx-ast-utils": "^2.0.1" } }, "eslint-plugin-react": { - "version": "7.4.0", - "resolved": "https://registry.npmjs.org/eslint-plugin-react/-/eslint-plugin-react-7.4.0.tgz", - "integrity": "sha512-tvjU9u3VqmW2vVuYnE8Qptq+6ji4JltjOjJ9u7VAOxVYkUkyBZWRvNYKbDv5fN+L6wiA+4we9+qQahZ0m63XEA==", + "version": "7.11.1", + "resolved": "https://registry.npmjs.org/eslint-plugin-react/-/eslint-plugin-react-7.11.1.tgz", + "integrity": "sha512-cVVyMadRyW7qsIUh3FHp3u6QHNhOgVrLQYdQEB1bPWBsgbNCHdFAeNMquBMCcZJu59eNthX053L70l7gRt4SCw==", "dev": true, "requires": { - "doctrine": "^2.0.0", - "has": "^1.0.1", - "jsx-ast-utils": "^2.0.0", - "prop-types": "^15.5.10" + "array-includes": "^3.0.3", + "doctrine": "^2.1.0", + "has": "^1.0.3", + "jsx-ast-utils": "^2.0.1", + "prop-types": "^15.6.2" } }, "eslint-restricted-globals": { @@ -1340,48 +1779,60 @@ "dev": true }, "eslint-scope": { - "version": "3.7.1", - "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-3.7.1.tgz", - "integrity": "sha1-PWPD7f2gLgbgGkUq2IyqzHzctug=", + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-4.0.0.tgz", + "integrity": "sha512-1G6UTDi7Jc1ELFwnR58HV4fK9OQK4S6N985f166xqXxpjU6plxFISJa2Ba9KCQuFa8RCnj/lSFJbHo7UFDBnUA==", "dev": true, "requires": { "esrecurse": "^4.1.0", "estraverse": "^4.1.1" } }, + "eslint-utils": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/eslint-utils/-/eslint-utils-1.3.1.tgz", + "integrity": "sha512-Z7YjnIldX+2XMcjr7ZkgEsOj/bREONV60qYeB/bjMAqqqZ4zxKyWX+BOUkdmRmA9riiIPVvo5x86m5elviOk0Q==", + "dev": true + }, + "eslint-visitor-keys": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-1.0.0.tgz", + "integrity": "sha512-qzm/XxIbxm/FHyH341ZrbnMUpe+5Bocte9xkmFMzPMjRaZMcXww+MpBptFvtU+79L362nqiLhekCxCxDPaUMBQ==", + "dev": true + }, "espree": { - "version": "3.5.2", - "resolved": "https://registry.npmjs.org/espree/-/espree-3.5.2.tgz", - "integrity": "sha512-sadKeYwaR/aJ3stC2CdvgXu1T16TdYN+qwCpcWbMnGJ8s0zNWemzrvb2GbD4OhmJ/fwpJjudThAlLobGbWZbCQ==", + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/espree/-/espree-4.1.0.tgz", + "integrity": "sha512-I5BycZW6FCVIub93TeVY1s7vjhP9CY6cXCznIRfiig7nRviKZYdRnj/sHEWC6A7WE9RDWOFq9+7OsWSYz8qv2w==", "dev": true, "requires": { - "acorn": "^5.2.1", - "acorn-jsx": "^3.0.0" + "acorn": "^6.0.2", + "acorn-jsx": "^5.0.0", + "eslint-visitor-keys": "^1.0.0" } }, "esprima": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.0.tgz", - "integrity": "sha512-oftTcaMu/EGrEIu904mWteKIv8vMuOgGYo7EhVJJN00R/EED9DCua/xxHRdYnKtcECzVg7xOWhflvJMnqcFZjw==", + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.1.tgz", + "integrity": "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==", "dev": true }, "esquery": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.0.0.tgz", - "integrity": "sha1-z7qLV9f7qT8XKYqKAGoEzaE9gPo=", + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.0.1.tgz", + "integrity": "sha512-SmiyZ5zIWH9VM+SRUReLS5Q8a7GxtRdxEBVZpm98rJM7Sb+A9DVCndXfkeFUd3byderg+EbDkfnevfCwynWaNA==", "dev": true, "requires": { "estraverse": "^4.0.0" } }, "esrecurse": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/esrecurse/-/esrecurse-4.2.0.tgz", - "integrity": "sha1-+pVo2Y04I/mkHZHpAtyrnqblsWM=", + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/esrecurse/-/esrecurse-4.2.1.tgz", + "integrity": "sha512-64RBB++fIOAXPw3P9cy89qfMlvZEXZkqqJkjqqXIvzP5ezRZjW+lPWjw35UX/3EhUPFYbg5ER4JYgDw4007/DQ==", "dev": true, "requires": { - "estraverse": "^4.1.0", - "object-assign": "^4.0.1" + "estraverse": "^4.1.0" } }, "estraverse": { @@ -1397,18 +1848,18 @@ "dev": true }, "eventemitter2": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/eventemitter2/-/eventemitter2-4.1.2.tgz", - "integrity": "sha1-DhqEd6+CGm7zmVsxG/dMI6UkfxU=" + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/eventemitter2/-/eventemitter2-5.0.1.tgz", + "integrity": "sha1-YZegldX7a1folC9v1+qtY6CclFI=" }, "execa": { - "version": "0.8.0", - "resolved": "https://registry.npmjs.org/execa/-/execa-0.8.0.tgz", - "integrity": "sha1-2NdrvBtVIX7RkP1t1J08d07PyNo=", + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/execa/-/execa-1.0.0.tgz", + "integrity": "sha512-adbxcyWV46qiHyvSp50TKt05tB4tK3HcmF7/nxfAdhnox83seTDbwnaqKO4sXRy7roHAIFqJP/Rw/AuEbX61LA==", "dev": true, "requires": { - "cross-spawn": "^5.0.1", - "get-stream": "^3.0.0", + "cross-spawn": "^6.0.0", + "get-stream": "^4.0.0", "is-stream": "^1.1.0", "npm-run-path": "^2.0.0", "p-finally": "^1.0.0", @@ -1417,12 +1868,14 @@ }, "dependencies": { "cross-spawn": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-5.1.0.tgz", - "integrity": "sha1-6L0O/uWPz/b4+UUQoKVUu/ojVEk=", + "version": "6.0.5", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-6.0.5.tgz", + "integrity": "sha512-eTVLrBSt7fjbDygz805pMnstIs2VTBNkRm0qxZd+M7A5XDdxVRWO5MxGBXZhjY4cqLYLdtrGqRf8mBPmzwSpWQ==", "dev": true, "requires": { - "lru-cache": "^4.0.1", + "nice-try": "^1.0.4", + "path-key": "^2.0.1", + "semver": "^5.5.0", "shebang-command": "^1.2.0", "which": "^1.2.9" } @@ -1440,31 +1893,40 @@ "integrity": "sha1-WKnS1ywCwfbwKg70qRZicrd2CSI=", "dev": true }, - "exit": { - "version": "0.1.2", - "resolved": "https://registry.npmjs.org/exit/-/exit-0.1.2.tgz", - "integrity": "sha1-BjJjj42HfMghB9MKD/8aF8uhzQw=", - "dev": true - }, - "exit-hook": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/exit-hook/-/exit-hook-1.1.1.tgz", - "integrity": "sha1-8FyiM7SMBdVP/wd2XfhQfpXAL/g=", - "dev": true - }, "extend": { "version": "3.0.2", "resolved": "https://registry.npmjs.org/extend/-/extend-3.0.2.tgz", "integrity": "sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g==" }, + "extend-shallow": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-3.0.2.tgz", + "integrity": "sha1-Jqcarwc7OfshJxcnRhMcJwQCjbg=", + "dev": true, + "requires": { + "assign-symbols": "^1.0.0", + "is-extendable": "^1.0.1" + }, + "dependencies": { + "is-extendable": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/is-extendable/-/is-extendable-1.0.1.tgz", + "integrity": "sha512-arnXMxT1hhoKo9k1LZdmlNyJdDDfy2v0fXjFlmok4+i8ul/6WlbVge9bhM74OpNPQPMGUToDtz+KXa1PneJxOA==", + "dev": true, + "requires": { + "is-plain-object": "^2.0.4" + } + } + } + }, "external-editor": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/external-editor/-/external-editor-2.1.0.tgz", - "integrity": "sha512-E44iT5QVOUJBKij4IIV3uvxuNlbKS38Tw1HiupxEIHPv9qtC2PrDYohbXV5U+1jnfIXttny8gUhj+oZvflFlzA==", + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/external-editor/-/external-editor-3.0.3.tgz", + "integrity": "sha512-bn71H9+qWoOQKyZDo25mOMVpSmXROAsTJVVVYzrrtol3d4y+AsKjf4Iwl2Q+IuT0kFSQ1qo166UuIwqYq7mGnA==", "dev": true, "requires": { - "chardet": "^0.4.0", - "iconv-lite": "^0.4.17", + "chardet": "^0.7.0", + "iconv-lite": "^0.4.24", "tmp": "^0.0.33" } }, @@ -1473,15 +1935,10 @@ "resolved": "https://registry.npmjs.org/extsprintf/-/extsprintf-1.3.0.tgz", "integrity": "sha1-lpGEQOMEGnpBT4xS48V06zw+HgU=" }, - "eyes": { - "version": "0.1.8", - "resolved": "https://registry.npmjs.org/eyes/-/eyes-0.1.8.tgz", - "integrity": "sha1-Ys8SAjTGg3hdkCNIqADvPgzCC8A=" - }, "fast-deep-equal": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-1.1.0.tgz", - "integrity": "sha1-wFNHeBfIa1HaqFPIHgWbcz0CNhQ=" + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-2.0.1.tgz", + "integrity": "sha1-ewUhjd+WZ79/Nwv3/bLLFf3Qqkk=" }, "fast-json-stable-stringify": { "version": "2.0.0", @@ -1494,6 +1951,11 @@ "integrity": "sha1-PYpcZog6FqMMqGQ+hR8Zuqd5eRc=", "dev": true }, + "fast-safe-stringify": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/fast-safe-stringify/-/fast-safe-stringify-2.0.6.tgz", + "integrity": "sha512-q8BZ89jjc+mz08rSxROs8VsrBBcn1SIw1kq9NjolL509tkABRk9io01RAjSaEv1Xb2uFLt8VtRiZbGp5H8iDtg==" + }, "fastparse": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/fastparse/-/fastparse-1.1.1.tgz", @@ -1501,9 +1963,9 @@ "dev": true }, "fbjs": { - "version": "0.8.16", - "resolved": "https://registry.npmjs.org/fbjs/-/fbjs-0.8.16.tgz", - "integrity": "sha1-XmdDL1UNxBtXK/VYR7ispk5TN9s=", + "version": "0.8.17", + "resolved": "https://registry.npmjs.org/fbjs/-/fbjs-0.8.17.tgz", + "integrity": "sha1-xNWY6taUkRJlPWWIsBpc3Nn5D90=", "requires": { "core-js": "^1.0.0", "isomorphic-fetch": "^2.1.1", @@ -1511,21 +1973,28 @@ "object-assign": "^4.1.0", "promise": "^7.1.1", "setimmediate": "^1.0.5", - "ua-parser-js": "^0.7.9" + "ua-parser-js": "^0.7.18" }, "dependencies": { "core-js": { "version": "1.2.7", - "resolved": "https://registry.npmjs.org/core-js/-/core-js-1.2.7.tgz", + "resolved": "http://registry.npmjs.org/core-js/-/core-js-1.2.7.tgz", "integrity": "sha1-ZSKUwUZR2yj6k70tX/KYOk8IxjY=" } } }, + "fecha": { + "version": "2.3.3", + "resolved": "http://registry.npmjs.org/fecha/-/fecha-2.3.3.tgz", + "integrity": "sha512-lUGBnIamTAwk4znq5BcqsDaxSmZ9nDVJaij6NvRt/Tg4R69gERA+otPKbS86ROw9nxVMw2/mp1fnaiWqbs6Sdg==" + }, "fibers": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/fibers/-/fibers-2.0.0.tgz", - "integrity": "sha512-sLxo4rZVk7xLgAjb/6zEzHJfSALx6u6coN1z61XCOF7i6CyTdJawF4+RdpjCSeS8AP66eR2InScbYAz9RAVOgA==", - "dev": true + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/fibers/-/fibers-3.1.1.tgz", + "integrity": "sha512-dl3Ukt08rHVQfY8xGD0ODwyjwrRALtaghuqGH2jByYX1wpY+nAnRQjJ6Dbqq0DnVgNVQ9yibObzbF4IlPyiwPw==", + "requires": { + "detect-libc": "^1.0.3" + } }, "figures": { "version": "2.0.0", @@ -1548,10 +2017,16 @@ }, "file-type": { "version": "3.9.0", - "resolved": "https://registry.npmjs.org/file-type/-/file-type-3.9.0.tgz", + "resolved": "http://registry.npmjs.org/file-type/-/file-type-3.9.0.tgz", "integrity": "sha1-JXoHg4TR24CHvESdEH1SpSZyuek=", "dev": true }, + "find-parent-dir": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/find-parent-dir/-/find-parent-dir-0.3.0.tgz", + "integrity": "sha1-M8RLQpqysvBkYpnF+fcY83b/jVQ=", + "dev": true + }, "find-up": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/find-up/-/find-up-1.1.2.tgz", @@ -1562,43 +2037,38 @@ } }, "flat": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/flat/-/flat-4.0.0.tgz", - "integrity": "sha512-ji/WMv2jdsE+LaznpkIF9Haax0sdpTBozrz/Dtg4qSRMfbs8oVg4ypJunIRYPiMLvH/ed6OflXbnbTIKJhtgeg==", + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/flat/-/flat-4.1.0.tgz", + "integrity": "sha512-Px/TiLIznH7gEDlPXcUD4KnBusa6kR6ayRUVcnEAbreRIuhkqow/mun59BuRXwoYk7ZQOLW1ZM05ilIvK38hFw==", "requires": { - "is-buffer": "~1.1.5" + "is-buffer": "~2.0.3" } }, "flat-cache": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-1.3.0.tgz", - "integrity": "sha1-0wMLMrOBVPTjt+nHCfSQ9++XxIE=", + "version": "1.3.4", + "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-1.3.4.tgz", + "integrity": "sha512-VwyB3Lkgacfik2vhqR4uv2rvebqmDvFu4jlN/C1RzWoJEo8I7z4Q404oiqYCkq41mni8EzQnm95emU9seckwtg==", "dev": true, "requires": { "circular-json": "^0.3.1", - "del": "^2.0.2", "graceful-fs": "^4.1.2", + "rimraf": "~2.6.2", "write": "^0.2.1" } }, - "flatten": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/flatten/-/flatten-1.0.2.tgz", - "integrity": "sha1-2uRqnXj74lKSJYzB54CkHZXAN4I=" - }, "for-each": { - "version": "0.3.2", - "resolved": "https://registry.npmjs.org/for-each/-/for-each-0.3.2.tgz", - "integrity": "sha1-LEBFC5NI6X8oEyJZO6lnBLmr1NQ=", + "version": "0.3.3", + "resolved": "https://registry.npmjs.org/for-each/-/for-each-0.3.3.tgz", + "integrity": "sha512-jqYfLp7mo9vIyQf8ykW2v7A+2N4QjeCeI5+Dz9XraiO1ign81wjiH7Fb9vSOWvQfNtmSa4H2RoQTrrXivdUZmw==", "dev": true, "requires": { - "is-function": "~1.0.0" + "is-callable": "^1.1.3" } }, - "foreach": { - "version": "2.0.5", - "resolved": "https://registry.npmjs.org/foreach/-/foreach-2.0.5.tgz", - "integrity": "sha1-C+4AUBiusmDQo6865ljdATbsG5k=", + "for-in": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/for-in/-/for-in-1.0.2.tgz", + "integrity": "sha1-gQaNKVqBQuwKxybG4iAMMPttXoA=", "dev": true }, "forever-agent": { @@ -1607,15 +2077,30 @@ "integrity": "sha1-+8cfDEGt6zf5bFd60e1C2P2sypE=" }, "form-data": { - "version": "2.3.2", - "resolved": "https://registry.npmjs.org/form-data/-/form-data-2.3.2.tgz", - "integrity": "sha1-SXBJi+YEwgwAXU9cI67NIda0kJk=", + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/form-data/-/form-data-2.3.3.tgz", + "integrity": "sha512-1lLKB2Mu3aGP1Q/2eCOx0fNbRMe7XdwktwOruhfqqd0rIJWwN4Dh+E3hrPSlDCXnSR7UtZ1N38rVXm+6+MEhJQ==", "requires": { "asynckit": "^0.4.0", - "combined-stream": "1.0.6", + "combined-stream": "^1.0.6", "mime-types": "^2.1.12" } }, + "fragment-cache": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/fragment-cache/-/fragment-cache-0.2.1.tgz", + "integrity": "sha1-QpD60n8T6Jvn8zeZxrxaCr//DRk=", + "dev": true, + "requires": { + "map-cache": "^0.2.2" + } + }, + "fs-constants": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/fs-constants/-/fs-constants-1.0.0.tgz", + "integrity": "sha512-y6OAwoSIf7FyjMIv94u+b5rdheZEjzR63GTyZJm5qh4Bi+2YgwLCcI/fPFZkL5PSixOt6ZNKm+w+Hfp/Bciwow==", + "dev": true + }, "fs-extra": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-3.0.1.tgz", @@ -1655,6 +2140,17 @@ "integrity": "sha1-GwqzvVU7Kg1jmdKcDj6gslIHgyc=", "dev": true }, + "g-status": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/g-status/-/g-status-2.0.2.tgz", + "integrity": "sha512-kQoE9qH+T1AHKgSSD0Hkv98bobE90ILQcXAF4wvGgsr7uFqNvwmh8j+Lq3l0RVt3E3HjSbv2B9biEGcEtpHLCA==", + "dev": true, + "requires": { + "arrify": "^1.0.1", + "matcher": "^1.0.0", + "simple-git": "^1.85.0" + } + }, "gauge": { "version": "2.7.4", "resolved": "https://registry.npmjs.org/gauge/-/gauge-2.7.4.tgz", @@ -1690,9 +2186,9 @@ "dev": true }, "get-own-enumerable-property-symbols": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/get-own-enumerable-property-symbols/-/get-own-enumerable-property-symbols-2.0.1.tgz", - "integrity": "sha512-TtY/sbOemiMKPRUDDanGCSgBYe7Mf0vbRsWnBZ+9yghpZ1MvcpSpuZFjHdEeY/LZjZy0vdLjS77L6HosisFiug==", + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/get-own-enumerable-property-symbols/-/get-own-enumerable-property-symbols-3.0.0.tgz", + "integrity": "sha512-CIJYJC4GGF06TakLg8z4GQKvDsx9EMspVxOYih7LerEL/WosUnFIww45CGfxfeKHqlg3twgUrYRT1O3WQqjGCg==", "dev": true }, "get-stdin": { @@ -1701,9 +2197,19 @@ "integrity": "sha1-uWjGsKBDhDJJAui/Gl3zJXmkUP4=" }, "get-stream": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-3.0.0.tgz", - "integrity": "sha1-jpQ9E1jcN1VQVOy+LtsFqhdO3hQ=" + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-4.1.0.tgz", + "integrity": "sha512-GMat4EJ5161kIy2HevLlr4luNjBgvmj413KaQA7jt4V8B4RDsfpHk7WQ9GVqfYyyx8OS/L66Kox+rJRNklLK7w==", + "dev": true, + "requires": { + "pump": "^3.0.0" + } + }, + "get-value": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/get-value/-/get-value-2.0.6.tgz", + "integrity": "sha1-3BXKHGcjh8p2vTesCjlbogQqLCg=", + "dev": true }, "getpass": { "version": "0.1.7", @@ -1737,19 +2243,18 @@ } }, "globals": { - "version": "9.18.0", - "resolved": "https://registry.npmjs.org/globals/-/globals-9.18.0.tgz", - "integrity": "sha512-S0nG3CLEQiY/ILxqtztTWH/3iRRdyBLw6KMDxnKMchrtbj2OFmehVh0WUCfW3DUrIgx/qFrJPICrq4Z4sTR9UQ==", + "version": "11.9.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-11.9.0.tgz", + "integrity": "sha512-5cJVtyXWH8PiJPVLZzzoIizXx944O4OmRro5MWKx5fT4MgcN7OfaMutPeaTdJCCURwbWdhhcCWcKIffPnmTzBg==", "dev": true }, "globby": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/globby/-/globby-5.0.0.tgz", - "integrity": "sha1-69hGZ8oNuzMLmbz8aOrCvFQ3Dg0=", + "version": "6.1.0", + "resolved": "http://registry.npmjs.org/globby/-/globby-6.1.0.tgz", + "integrity": "sha1-9abXDoOV4hyFj7BInWTfAkJNUGw=", "dev": true, "requires": { "array-union": "^1.0.1", - "arrify": "^1.0.0", "glob": "^7.0.3", "object-assign": "^4.0.1", "pify": "^2.0.0", @@ -1789,13 +2294,19 @@ } }, "debug": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/debug/-/debug-3.1.0.tgz", - "integrity": "sha512-OX8XqP7/1a9cqkxYw2yXss15f26NKWBpDXQd0/uK/KPqdQhxbPa994hnzjcE2VqQpDslf55723cKPUOGSmMY3g==", + "version": "3.2.6", + "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.6.tgz", + "integrity": "sha512-mel+jf7nrtEl5Pn1Qx46zARXKDpBbvzezse7p7LqINmdoIk8PYP5SySaxEmYv6TZ0JyEKA1hsCId6DIhgITtWQ==", "dev": true, "requires": { - "ms": "2.0.0" + "ms": "^2.1.1" } + }, + "ms": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.1.tgz", + "integrity": "sha512-tgp+dl5cGk28utYktBsrFqA7HKgrhgPsg6Z/EfhWI4gl1Hwq8B/GmY/0oXZ6nF8hDVesS/FpnYaD/kOWhYQvyg==", + "dev": true } } }, @@ -1807,31 +2318,16 @@ "delegate": "^3.1.2" } }, - "got": { - "version": "7.1.0", - "resolved": "https://registry.npmjs.org/got/-/got-7.1.0.tgz", - "integrity": "sha512-Y5WMo7xKKq1muPsxD+KmrR8DH5auG7fBdDVueZwETwV6VytKyU9OX/ddpq2/1hp1vIPvVb4T81dKQz3BivkNLw==", - "requires": { - "decompress-response": "^3.2.0", - "duplexer3": "^0.1.4", - "get-stream": "^3.0.0", - "is-plain-obj": "^1.1.0", - "is-retry-allowed": "^1.0.0", - "is-stream": "^1.0.0", - "isurl": "^1.0.0-alpha5", - "lowercase-keys": "^1.0.0", - "p-cancelable": "^0.3.0", - "p-timeout": "^1.1.1", - "safe-buffer": "^5.0.1", - "timed-out": "^4.0.0", - "url-parse-lax": "^1.0.0", - "url-to-options": "^1.0.1" - } - }, "graceful-fs": { - "version": "4.1.11", - "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.1.11.tgz", - "integrity": "sha1-Dovf5NHduIVNZOBOp8AOKgJuVlg=" + "version": "4.1.15", + "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.1.15.tgz", + "integrity": "sha512-6uHUhOPEBgQ24HM+r6b/QwWfZq+yiFcipKFrOFiBEnWdy5sdzYoi+pJeQaPI5qOLRFqWmAXUPQNsielzdLoecA==" + }, + "grapheme-splitter": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/grapheme-splitter/-/grapheme-splitter-1.0.4.tgz", + "integrity": "sha512-bzh50DW9kTPM00T8y4o8vQg89Di9oLJVLW/KaOGIXJWP/iqCN6WKYkbNOF04vFLJhwcpYUh9ydh/+5vpOqV4YQ==", + "dev": true }, "har-schema": { "version": "2.0.0", @@ -1839,21 +2335,21 @@ "integrity": "sha1-qUwiJOvKwEeCoNkDVSHyRzW37JI=" }, "har-validator": { - "version": "5.0.3", - "resolved": "https://registry.npmjs.org/har-validator/-/har-validator-5.0.3.tgz", - "integrity": "sha1-ukAsJmGU8VlW7xXg/PJCmT9qff0=", + "version": "5.1.3", + "resolved": "https://registry.npmjs.org/har-validator/-/har-validator-5.1.3.tgz", + "integrity": "sha512-sNvOCzEQNr/qrvJgc3UG/kD4QtlHycrzwS+6mfTrrSq97BvaYcPZZI1ZSqGSPR73Cxn4LKTD4PttRwfU7jWq5g==", "requires": { - "ajv": "^5.1.0", + "ajv": "^6.5.5", "har-schema": "^2.0.0" } }, "has": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/has/-/has-1.0.1.tgz", - "integrity": "sha1-hGFzP1OLCDfJNh45qauelwTcLyg=", + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/has/-/has-1.0.3.tgz", + "integrity": "sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw==", "dev": true, "requires": { - "function-bind": "^1.0.2" + "function-bind": "^1.1.1" } }, "has-ansi": { @@ -1867,12 +2363,8 @@ "has-flag": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-2.0.0.tgz", - "integrity": "sha1-6CB68cx7MNRGzHC3NLXovhj4jVE=" - }, - "has-symbol-support-x": { - "version": "1.4.1", - "resolved": "https://registry.npmjs.org/has-symbol-support-x/-/has-symbol-support-x-1.4.1.tgz", - "integrity": "sha512-JkaetveU7hFbqnAC1EV1sF4rlojU2D4Usc5CmS69l6NfmPDnpnFUegzFg33eDkkpNCxZ0mQp65HwUDrNFS/8MA==" + "integrity": "sha1-6CB68cx7MNRGzHC3NLXovhj4jVE=", + "dev": true }, "has-symbols": { "version": "1.0.0", @@ -1880,30 +2372,104 @@ "integrity": "sha1-uhqPGvKg/DllD1yFA2dwQSIGO0Q=", "dev": true }, - "has-to-string-tag-x": { - "version": "1.4.1", - "resolved": "https://registry.npmjs.org/has-to-string-tag-x/-/has-to-string-tag-x-1.4.1.tgz", - "integrity": "sha512-vdbKfmw+3LoOYVr+mtxHaX5a96+0f3DljYd8JOqvOLsf5mw2Otda2qCDT9qRqLAhrjyQ0h7ual5nOiASpsGNFw==", - "requires": { - "has-symbol-support-x": "^1.4.1" - } - }, "has-unicode": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/has-unicode/-/has-unicode-2.0.1.tgz", "integrity": "sha1-4Ob+aijPUROIVeCG0Wkedx3iqLk=" }, + "has-value": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/has-value/-/has-value-1.0.0.tgz", + "integrity": "sha1-GLKB2lhbHFxR3vJMkw7SmgvmsXc=", + "dev": true, + "requires": { + "get-value": "^2.0.6", + "has-values": "^1.0.0", + "isobject": "^3.0.0" + }, + "dependencies": { + "isobject": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/isobject/-/isobject-3.0.1.tgz", + "integrity": "sha1-TkMekrEalzFjaqH5yNHMvP2reN8=", + "dev": true + } + } + }, + "has-values": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/has-values/-/has-values-1.0.0.tgz", + "integrity": "sha1-lbC2P+whRmGab+V/51Yo1aOe/k8=", + "dev": true, + "requires": { + "is-number": "^3.0.0", + "kind-of": "^4.0.0" + }, + "dependencies": { + "is-number": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-number/-/is-number-3.0.0.tgz", + "integrity": "sha1-JP1iAaR4LPUFYcgQJ2r8fRLXEZU=", + "dev": true, + "requires": { + "kind-of": "^3.0.2" + }, + "dependencies": { + "kind-of": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", + "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", + "dev": true, + "requires": { + "is-buffer": "^1.1.5" + }, + "dependencies": { + "is-buffer": { + "version": "1.1.6", + "resolved": "https://registry.npmjs.org/is-buffer/-/is-buffer-1.1.6.tgz", + "integrity": "sha512-NcdALwpXkTm5Zvvbk7owOUSvVvBKDgKP5/ewfXEznmQFfs4ZRmanOeKBTjRVjka3QFoN6XJ+9F3USqfHqTaU5w==", + "dev": true + } + } + } + } + }, + "kind-of": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-4.0.0.tgz", + "integrity": "sha1-IIE989cSkosgc3hpGkUGb65y3Vc=", + "dev": true, + "requires": { + "is-buffer": "^1.1.5" + }, + "dependencies": { + "is-buffer": { + "version": "1.1.6", + "resolved": "https://registry.npmjs.org/is-buffer/-/is-buffer-1.1.6.tgz", + "integrity": "sha512-NcdALwpXkTm5Zvvbk7owOUSvVvBKDgKP5/ewfXEznmQFfs4ZRmanOeKBTjRVjka3QFoN6XJ+9F3USqfHqTaU5w==", + "dev": true + } + } + } + } + }, "history": { - "version": "3.3.0", - "resolved": "https://registry.npmjs.org/history/-/history-3.3.0.tgz", - "integrity": "sha1-/O3M6PEpdTcVRdc1RhAzV5ptrpw=", + "version": "4.7.2", + "resolved": "https://registry.npmjs.org/history/-/history-4.7.2.tgz", + "integrity": "sha512-1zkBRWW6XweO0NBcjiphtVJVsIQ+SXF29z9DVkceeaSLVMFXHool+fdCZD4spDCfZJCILPILc3bm7Bc+HRi0nA==", "requires": { "invariant": "^2.2.1", "loose-envify": "^1.2.0", - "query-string": "^4.2.2", + "resolve-pathname": "^2.2.0", + "value-equal": "^0.4.0", "warning": "^3.0.0" } }, + "hoist-non-react-statics": { + "version": "2.5.5", + "resolved": "https://registry.npmjs.org/hoist-non-react-statics/-/hoist-non-react-statics-2.5.5.tgz", + "integrity": "sha512-rqcy4pJo55FTTLWt+bU8ukscqHeE/e9KWvsOW2b/a3afxQZhwkQdT1rPPCJ0rYXdj4vNcasY8zHTH+jF/qStxw==" + }, "hosted-git-info": { "version": "2.7.1", "resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-2.7.1.tgz", @@ -1920,34 +2486,135 @@ } }, "humanize-duration": { - "version": "3.12.1", - "resolved": "https://registry.npmjs.org/humanize-duration/-/humanize-duration-3.12.1.tgz", - "integrity": "sha512-Eu68Xnq5C38391em1zfVy8tiapQrOvTNTlWpax9smHMlEEUcudXrdMfXMoMRyZx4uODowYgi1AYiMzUXEbG+sA==", + "version": "3.15.3", + "resolved": "https://registry.npmjs.org/humanize-duration/-/humanize-duration-3.15.3.tgz", + "integrity": "sha512-BMz6w8p3NVa6QP9wDtqUkXfwgBqDaZ5z/np0EYdoWrLqL849Onp6JWMXMhbHtuvO9jUThLN5H1ThRQ8dUWnYkA==", "dev": true }, "husky": { - "version": "0.14.3", - "resolved": "https://registry.npmjs.org/husky/-/husky-0.14.3.tgz", - "integrity": "sha512-e21wivqHpstpoiWA/Yi8eFti8E+sQDSS53cpJsPptPs295QTOQR0ZwnHo2TXy1XOpZFD9rPOd3NpmqTK6uMLJA==", - "dev": true, - "requires": { - "is-ci": "^1.0.10", - "normalize-path": "^1.0.0", - "strip-indent": "^2.0.0" + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/husky/-/husky-1.2.0.tgz", + "integrity": "sha512-/ib3+iycykXC0tYIxsyqierikVa9DA2DrT32UEirqNEFVqOj1bFMTgP3jAz8HM7FgC/C8pc/BTUa9MV2GEkZaA==", + "dev": true, + "requires": { + "cosmiconfig": "^5.0.6", + "execa": "^1.0.0", + "find-up": "^3.0.0", + "get-stdin": "^6.0.0", + "is-ci": "^1.2.1", + "pkg-dir": "^3.0.0", + "please-upgrade-node": "^3.1.1", + "read-pkg": "^4.0.1", + "run-node": "^1.0.0", + "slash": "^2.0.0" }, "dependencies": { - "strip-indent": { + "find-up": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-3.0.0.tgz", + "integrity": "sha512-1yD6RmLI1XBfxugvORwlck6f75tYL+iR0jqwsOrOxMZyGYqUuDhJ0l4AXdO1iX/FTs9cBAMEk1gWSEx1kSbylg==", + "dev": true, + "requires": { + "locate-path": "^3.0.0" + } + }, + "get-stdin": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/get-stdin/-/get-stdin-6.0.0.tgz", + "integrity": "sha512-jp4tHawyV7+fkkSKyvjuLZswblUtz+SQKzSWnBbii16BuZksJlU1wuBYXY75r+duh/llF1ur6oNwi+2ZzjKZ7g==", + "dev": true + }, + "locate-path": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-3.0.0.tgz", + "integrity": "sha512-7AO748wWnIhNqAuaty2ZWHkQHRSNfPVIsPIfwEOWO22AmaoVrWavlOcMR5nzTLNYvp36X220/maaRsrec1G65A==", + "dev": true, + "requires": { + "p-locate": "^3.0.0", + "path-exists": "^3.0.0" + } + }, + "p-limit": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.0.0.tgz", + "integrity": "sha512-fl5s52lI5ahKCernzzIyAP0QAZbGIovtVHGwpcu1Jr/EpzLVDI2myISHwGqK7m8uQFugVWSrbxH7XnhGtvEc+A==", + "dev": true, + "requires": { + "p-try": "^2.0.0" + } + }, + "p-locate": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-3.0.0.tgz", + "integrity": "sha512-x+12w/To+4GFfgJhBEpiDcLozRJGegY+Ei7/z0tSLkMmxGZNybVMSfWj9aJn8Z5Fc7dBUNJOOVgPv2H7IwulSQ==", + "dev": true, + "requires": { + "p-limit": "^2.0.0" + } + }, + "p-try": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/p-try/-/p-try-2.0.0.tgz", + "integrity": "sha512-hMp0onDKIajHfIkdRk3P4CdCmErkYAxxDtP3Wx/4nZ3aGlau2VKh3mZpcuFkH27WQkL/3WBCPOktzA9ZOAnMQQ==", + "dev": true + }, + "parse-json": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-4.0.0.tgz", + "integrity": "sha1-vjX1Qlvh9/bHRxhPmKeIy5lHfuA=", + "dev": true, + "requires": { + "error-ex": "^1.3.1", + "json-parse-better-errors": "^1.0.1" + } + }, + "path-exists": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-3.0.0.tgz", + "integrity": "sha1-zg6+ql94yxiSXqfYENe1mwEP1RU=", + "dev": true + }, + "pify": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/pify/-/pify-3.0.0.tgz", + "integrity": "sha1-5aSs0sEB/fPZpNB/DbxNtJ3SgXY=", + "dev": true + }, + "pkg-dir": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/pkg-dir/-/pkg-dir-3.0.0.tgz", + "integrity": "sha512-/E57AYkoeQ25qkxMj5PBOVgF8Kiu/h7cYS30Z5+R7WaiCCBfLq58ZI/dSeaEKb9WVJV5n/03QwrN3IeWIFllvw==", + "dev": true, + "requires": { + "find-up": "^3.0.0" + } + }, + "read-pkg": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/read-pkg/-/read-pkg-4.0.1.tgz", + "integrity": "sha1-ljYlN48+HE1IyFhytabsfV0JMjc=", + "dev": true, + "requires": { + "normalize-package-data": "^2.3.2", + "parse-json": "^4.0.0", + "pify": "^3.0.0" + } + }, + "slash": { "version": "2.0.0", - "resolved": "https://registry.npmjs.org/strip-indent/-/strip-indent-2.0.0.tgz", - "integrity": "sha1-XvjbKV0B5u1sv3qrlpmNeCJSe2g=", + "resolved": "https://registry.npmjs.org/slash/-/slash-2.0.0.tgz", + "integrity": "sha512-ZYKh3Wh2z1PpEXWr0MpSBZ0V6mZHAQfYevttO11c51CaWjGTaadiKZ+wVt1PbMlDV5qhMFslpZCemhwOK7C89A==", "dev": true } } }, "iconv-lite": { - "version": "0.4.19", - "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.19.tgz", - "integrity": "sha512-oTZqweIP51xaGPI4uPa56/Pri/480R+mo7SeU+YETByQNhDG55ycFyNLIgta9vXhILrxXDmF7ZGhqZIcuN0gJQ==" + "version": "0.4.24", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz", + "integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==", + "requires": { + "safer-buffer": ">= 2.1.2 < 3" + } }, "icss-replace-symbols": { "version": "1.1.0", @@ -1955,10 +2622,16 @@ "integrity": "sha1-Bupvg2ead0njhs/h/oEq5dsiPe0=", "dev": true }, + "ieee754": { + "version": "1.1.12", + "resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.1.12.tgz", + "integrity": "sha512-GguP+DRY+pJ3soyIiGPTvdiVXjZ+DbXOxGpXn3eMvNW4x4irjqXm4wHKscC+TfxSJ0yw/S1F24tqdMNsMZTiLA==", + "dev": true + }, "ignore": { - "version": "3.3.7", - "resolved": "https://registry.npmjs.org/ignore/-/ignore-3.3.7.tgz", - "integrity": "sha512-YGG3ejvBNHRqu0559EOxxNFihD0AjpvHlC/pdGKd3X3ofe+CoJkYazwNJYTNebqpPKN+VVQbh4ZFn1DivMNuHA==", + "version": "4.0.6", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-4.0.6.tgz", + "integrity": "sha512-cyFDKrqc/YdcWFniJhzI42+AzS+gNwmUzOSFcRCQYwySuBBBy/KjuxWLZ/FHEH6Moq1NizMOBWyTcv8O4OZIMg==", "dev": true }, "image-size": { @@ -1968,13 +2641,40 @@ "dev": true }, "immutability-helper": { - "version": "2.4.0", - "resolved": "https://registry.npmjs.org/immutability-helper/-/immutability-helper-2.4.0.tgz", - "integrity": "sha512-rW/L/56ZMo9NStMK85kFrUFFGy4NeJbCdhfrDHIZrFfxYtuwuxD+dT3mWMcdmrNO61hllc60AeGglCRhfZ1dZw==", + "version": "2.8.1", + "resolved": "https://registry.npmjs.org/immutability-helper/-/immutability-helper-2.8.1.tgz", + "integrity": "sha512-8AVB5EUpRBUdXqfe4cFsFECsOIZ9hX/Arl8B8S9/tmwpYv3UWvOsXUPOjkuZIMaVxfSWkxCzkng1rjmEoSWrxQ==", "requires": { "invariant": "^2.2.0" } }, + "import-fresh": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-2.0.0.tgz", + "integrity": "sha1-2BNVwVYS04bGH53dOSLUMEgipUY=", + "dev": true, + "requires": { + "caller-path": "^2.0.0", + "resolve-from": "^3.0.0" + }, + "dependencies": { + "caller-path": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/caller-path/-/caller-path-2.0.0.tgz", + "integrity": "sha1-Ro+DBE42mrIBD6xfBs7uFbsssfQ=", + "dev": true, + "requires": { + "caller-callsite": "^2.0.0" + } + }, + "resolve-from": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-3.0.0.tgz", + "integrity": "sha1-six699nWiBvItuZTM17rywoYh0g=", + "dev": true + } + } + }, "imurmurhash": { "version": "0.1.4", "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz", @@ -2020,24 +2720,23 @@ "dev": true }, "inquirer": { - "version": "3.3.0", - "resolved": "https://registry.npmjs.org/inquirer/-/inquirer-3.3.0.tgz", - "integrity": "sha512-h+xtnyk4EwKvFWHrUYsWErEVR+igKtLdchu+o0Z1RL7VU/jVMFbYir2bp6bAj8efFNxWqHX0dIss6fJQ+/+qeQ==", + "version": "6.2.1", + "resolved": "https://registry.npmjs.org/inquirer/-/inquirer-6.2.1.tgz", + "integrity": "sha512-088kl3DRT2dLU5riVMKKr1DlImd6X7smDhpXUCkJDCKvTEJeRiXh0G132HG9u5a+6Ylw9plFRY7RuTnwohYSpg==", "dev": true, "requires": { "ansi-escapes": "^3.0.0", "chalk": "^2.0.0", "cli-cursor": "^2.1.0", "cli-width": "^2.0.0", - "external-editor": "^2.0.4", + "external-editor": "^3.0.0", "figures": "^2.0.0", - "lodash": "^4.3.0", + "lodash": "^4.17.10", "mute-stream": "0.0.7", "run-async": "^2.2.0", - "rx-lite": "^4.0.8", - "rx-lite-aggregates": "^4.0.8", + "rxjs": "^6.1.0", "string-width": "^2.1.0", - "strip-ansi": "^4.0.0", + "strip-ansi": "^5.0.0", "through": "^2.3.6" }, "dependencies": { @@ -2047,26 +2746,6 @@ "integrity": "sha1-7QMXwyIGT3lGbAKWa922Bas32Zg=", "dev": true }, - "ansi-styles": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.0.tgz", - "integrity": "sha512-NnSOmMEYtVR2JVMIGTzynRkkaxtiq1xnFBcdQD/DnNCYPoEPsVJhM98BDyaoNOQIi7p4okdi3E27eN7GQbsUug==", - "dev": true, - "requires": { - "color-convert": "^1.9.0" - } - }, - "chalk": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.3.0.tgz", - "integrity": "sha512-Az5zJR2CBujap2rqXGaJKaPHyJ0IrUimvYNX+ncCy8PJP4ltOGTrHUIo097ZaL2zMeKYpiCdqDvS6zdrTFok3Q==", - "dev": true, - "requires": { - "ansi-styles": "^3.1.0", - "escape-string-regexp": "^1.0.5", - "supports-color": "^4.0.0" - } - }, "is-fullwidth-code-point": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-2.0.0.tgz", @@ -2081,24 +2760,34 @@ "requires": { "is-fullwidth-code-point": "^2.0.0", "strip-ansi": "^4.0.0" + }, + "dependencies": { + "strip-ansi": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-4.0.0.tgz", + "integrity": "sha1-qEeQIusaw2iocTibY1JixQXuNo8=", + "dev": true, + "requires": { + "ansi-regex": "^3.0.0" + } + } } }, "strip-ansi": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-4.0.0.tgz", - "integrity": "sha1-qEeQIusaw2iocTibY1JixQXuNo8=", - "dev": true, - "requires": { - "ansi-regex": "^3.0.0" - } - }, - "supports-color": { - "version": "4.5.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-4.5.0.tgz", - "integrity": "sha1-vnoN5ITexcXN34s9WRJQRJEvY1s=", + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-5.0.0.tgz", + "integrity": "sha512-Uu7gQyZI7J7gn5qLn1Np3G9vcYGTVqB+lFTytnDJv83dd8T22aGH451P3jueT2/QemInJDfxHB5Tde5OzgG1Ow==", "dev": true, "requires": { - "has-flag": "^2.0.0" + "ansi-regex": "^4.0.0" + }, + "dependencies": { + "ansi-regex": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-4.0.0.tgz", + "integrity": "sha512-iB5Dda8t/UqpPI/IjsejXu5jOGDrzn41wJyljwPH65VCIbk6+1BzFIMJGFwTNrYXT1CrD+B4l19U7awiQ8rk7w==", + "dev": true + } } } } @@ -2130,9 +2819,9 @@ } }, "invariant": { - "version": "2.2.2", - "resolved": "https://registry.npmjs.org/invariant/-/invariant-2.2.2.tgz", - "integrity": "sha1-nh9WrArNtr8wMwbzOL47IErmA2A=", + "version": "2.2.4", + "resolved": "https://registry.npmjs.org/invariant/-/invariant-2.2.4.tgz", + "integrity": "sha512-phJfQVBuaJM5raOpJjSfkiD6BpbCE4Ns//LaXl6wGYtUBY83nWS6Rf9tXm2e8VaK60JEjYldbPif/A2B1C2gNA==", "requires": { "loose-envify": "^1.0.0" } @@ -2148,37 +2837,55 @@ "integrity": "sha1-3FiQdvZZ9BnCIgOaMzFvHHOH7/0=", "dev": true }, + "is-accessor-descriptor": { + "version": "0.1.6", + "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-0.1.6.tgz", + "integrity": "sha1-qeEss66Nh2cn7u84Q/igiXtcmNY=", + "dev": true, + "requires": { + "kind-of": "^3.0.2" + } + }, "is-arrayish": { "version": "0.2.1", "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.2.1.tgz", "integrity": "sha1-d8mYQFJ6qOyxqLppe4BkWnqSap0=" }, "is-buffer": { - "version": "1.1.6", - "resolved": "https://registry.npmjs.org/is-buffer/-/is-buffer-1.1.6.tgz", - "integrity": "sha512-NcdALwpXkTm5Zvvbk7owOUSvVvBKDgKP5/ewfXEznmQFfs4ZRmanOeKBTjRVjka3QFoN6XJ+9F3USqfHqTaU5w==" + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/is-buffer/-/is-buffer-2.0.3.tgz", + "integrity": "sha512-U15Q7MXTuZlrbymiz95PJpZxu8IlipAp4dtS3wOdgPXx3mqBnslrWU14kxfHB+Py/+2PVKSr37dMAgM2A4uArw==" }, "is-builtin-module": { "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-builtin-module/-/is-builtin-module-1.0.0.tgz", + "resolved": "http://registry.npmjs.org/is-builtin-module/-/is-builtin-module-1.0.0.tgz", "integrity": "sha1-VAVy0096wxGfj3bDDLwbHgN6/74=", "requires": { "builtin-modules": "^1.0.0" } }, "is-callable": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/is-callable/-/is-callable-1.1.3.tgz", - "integrity": "sha1-hut1OSgF3cM69xySoO7fdO52BLI=", + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/is-callable/-/is-callable-1.1.4.tgz", + "integrity": "sha512-r5p9sxJjYnArLjObpjA4xu5EKI3CuKHkJXMhT7kwbpUyIFD1n5PMAsoPvWnvtZiNz7LjkYDRZhd7FlI0eMijEA==", "dev": true }, "is-ci": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/is-ci/-/is-ci-1.1.0.tgz", - "integrity": "sha512-c7TnwxLePuqIlxHgr7xtxzycJPegNHFuIrBkwbf8hc58//+Op1CqFkyS+xnIMkwn9UsJIwc174BIjkyBmSpjKg==", + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/is-ci/-/is-ci-1.2.1.tgz", + "integrity": "sha512-s6tfsaQaQi3JNciBH6shVqEDvhGut0SUXr31ag8Pd8BBbVVlcGfWhpPmEOoM6RJ5TFhbypvf5yyRw/VXW1IiWg==", "dev": true, "requires": { - "ci-info": "^1.0.0" + "ci-info": "^1.5.0" + } + }, + "is-data-descriptor": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-0.1.4.tgz", + "integrity": "sha1-C17mSDiOLIYCgueT8YVv7D8wG1Y=", + "dev": true, + "requires": { + "kind-of": "^3.0.2" } }, "is-date-object": { @@ -2187,6 +2894,37 @@ "integrity": "sha1-mqIOtq7rv/d/vTPnTKAbM1gdOhY=", "dev": true }, + "is-descriptor": { + "version": "0.1.6", + "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-0.1.6.tgz", + "integrity": "sha512-avDYr0SB3DwO9zsMov0gKCESFYqCnE4hq/4z3TdUlukEy5t9C0YRq7HLrsN52NAcqXKaepeCD0n+B0arnVG3Hg==", + "dev": true, + "requires": { + "is-accessor-descriptor": "^0.1.6", + "is-data-descriptor": "^0.1.4", + "kind-of": "^5.0.0" + }, + "dependencies": { + "kind-of": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-5.1.0.tgz", + "integrity": "sha512-NGEErnH6F2vUuXDh+OlbcKW7/wOcfdRHaZ7VWtqCztfHri/++YKmP51OdWeGPuqCOba6kk2OTe5d02VmTB80Pw==", + "dev": true + } + } + }, + "is-directory": { + "version": "0.3.1", + "resolved": "https://registry.npmjs.org/is-directory/-/is-directory-0.3.1.tgz", + "integrity": "sha1-YTObbyR1/Hcv2cnYP1yFddwVSuE=", + "dev": true + }, + "is-extendable": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/is-extendable/-/is-extendable-0.1.1.tgz", + "integrity": "sha1-YrEQ4omkcUGOPsNqYX1HLjAd/Ik=", + "dev": true + }, "is-extglob": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", @@ -2227,13 +2965,16 @@ "is-obj": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/is-obj/-/is-obj-1.0.1.tgz", - "integrity": "sha1-PkcprB9f3gJc19g6iW2rn09n2w8=", - "dev": true + "integrity": "sha1-PkcprB9f3gJc19g6iW2rn09n2w8=" }, - "is-object": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/is-object/-/is-object-1.0.1.tgz", - "integrity": "sha1-iVJojF7C/9awPsyF52ngKQMINHA=" + "is-observable": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/is-observable/-/is-observable-1.1.0.tgz", + "integrity": "sha512-NqCa4Sa2d+u7BWc6CukaObG3Fh+CU9bvixbpcXYhy2VvYS7vVGIdAgnIS5Ks3A/cqk4rebLJ9s8zBstT2aKnIA==", + "dev": true, + "requires": { + "symbol-observable": "^1.1.0" + } }, "is-path-cwd": { "version": "1.0.0", @@ -2242,9 +2983,9 @@ "dev": true }, "is-path-in-cwd": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-path-in-cwd/-/is-path-in-cwd-1.0.0.tgz", - "integrity": "sha1-ZHdYK4IU1gI0YJRWcAO+ip6sBNw=", + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/is-path-in-cwd/-/is-path-in-cwd-1.0.1.tgz", + "integrity": "sha512-FjV1RTW48E7CWM7eE/J2NJvAEEVektecDBVBE5Hh3nM1Jd0kvhHtX68Pr3xsDf857xt3Y4AkwVULK1Vku62aaQ==", "dev": true, "requires": { "is-path-inside": "^1.0.0" @@ -2259,10 +3000,22 @@ "path-is-inside": "^1.0.1" } }, - "is-plain-obj": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/is-plain-obj/-/is-plain-obj-1.1.0.tgz", - "integrity": "sha1-caUMhCnfync8kqOQpKA7OfzVHT4=" + "is-plain-object": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/is-plain-object/-/is-plain-object-2.0.4.tgz", + "integrity": "sha512-h5PpgXkWitc38BBMYawTYMWJHFZJVnBquFE57xFpjB8pJFiF6gZ+bU+WyI/yqXiFR5mdLsgYNaPe8uao6Uv9Og==", + "dev": true, + "requires": { + "isobject": "^3.0.1" + }, + "dependencies": { + "isobject": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/isobject/-/isobject-3.0.1.tgz", + "integrity": "sha1-TkMekrEalzFjaqH5yNHMvP2reN8=", + "dev": true + } + } }, "is-promise": { "version": "2.1.0", @@ -2291,21 +3044,19 @@ "integrity": "sha512-qgDYXFSR5WvEfuS5dMj6oTMEbrrSaM0CrFk2Yiq/gXnBvD9pMa2jGXxyhGLfvhZpuMZe18CJpFxAt3CRs42NMg==", "dev": true }, - "is-retry-allowed": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/is-retry-allowed/-/is-retry-allowed-1.1.0.tgz", - "integrity": "sha1-EaBgVotnM5REAz0BJaYaINVk+zQ=" - }, "is-stream": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-1.1.0.tgz", "integrity": "sha1-EtSj3U5o4Lec6428hBc66A2RykQ=" }, "is-symbol": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/is-symbol/-/is-symbol-1.0.1.tgz", - "integrity": "sha1-PMWfAAJRlLarLjjbrmaJJWtmBXI=", - "dev": true + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/is-symbol/-/is-symbol-1.0.2.tgz", + "integrity": "sha512-HS8bZ9ox60yCJLH9snBpIwv9pYUAkcuLhSA1oero1UB5y9aiQpRA8y2ex945AOtCZL1lJDeIk3G5LthswI46Lw==", + "dev": true, + "requires": { + "has-symbols": "^1.0.0" + } }, "is-typedarray": { "version": "1.0.0", @@ -2317,6 +3068,12 @@ "resolved": "https://registry.npmjs.org/is-utf8/-/is-utf8-0.2.1.tgz", "integrity": "sha1-Sw2hRCEE0bM2NA6AeX6GXPOffXI=" }, + "is-windows": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/is-windows/-/is-windows-1.0.2.tgz", + "integrity": "sha512-eXK1UInq2bPmjyX6e3VHIzMLobc4J94i4AWn+Hpq3OU5KkrRC96OAcR3PRJ/pGu6m8TRnBHP9dkXQVsT/COVIA==", + "dev": true + }, "isarray": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", @@ -2341,79 +3098,22 @@ "resolved": "https://registry.npmjs.org/isstream/-/isstream-0.1.2.tgz", "integrity": "sha1-R+Y/evVa+m+S4VAOaQ64uFKcCZo=" }, - "isurl": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/isurl/-/isurl-1.0.0.tgz", - "integrity": "sha512-1P/yWsxPlDtn7QeRD+ULKQPaIaN6yF368GZ2vDfv0AL0NwpStafjWCDDdn0k8wgFMWpVAqG7oJhxHnlud42i9w==", - "requires": { - "has-to-string-tag-x": "^1.2.0", - "is-object": "^1.0.1" - } - }, - "jasmine": { - "version": "2.9.0", - "resolved": "https://registry.npmjs.org/jasmine/-/jasmine-2.9.0.tgz", - "integrity": "sha1-dlcfklyHg0CefGFTVy5aY0HPk+s=", - "dev": true, - "requires": { - "exit": "^0.1.2", - "glob": "^7.0.6", - "jasmine-core": "~2.9.0" - } - }, - "jasmine-core": { - "version": "2.9.1", - "resolved": "https://registry.npmjs.org/jasmine-core/-/jasmine-core-2.9.1.tgz", - "integrity": "sha1-trvB2OZSUNVvWIhGFwXr7uuI8i8=", - "dev": true - }, "jest-get-type": { - "version": "21.2.0", - "resolved": "https://registry.npmjs.org/jest-get-type/-/jest-get-type-21.2.0.tgz", - "integrity": "sha512-y2fFw3C+D0yjNSDp7ab1kcd6NUYfy3waPTlD8yWkAtiocJdBRQqNoRqVfMNxgj+IjT0V5cBIHJO0z9vuSSZ43Q==", + "version": "22.4.3", + "resolved": "http://registry.npmjs.org/jest-get-type/-/jest-get-type-22.4.3.tgz", + "integrity": "sha512-/jsz0Y+V29w1chdXVygEKSz2nBoHoYqNShPe+QgxSNjAuP1i8+k4LbQNrfoliKej0P45sivkSCh7yiD6ubHS3w==", "dev": true }, "jest-validate": { - "version": "21.2.1", - "resolved": "https://registry.npmjs.org/jest-validate/-/jest-validate-21.2.1.tgz", - "integrity": "sha512-k4HLI1rZQjlU+EC682RlQ6oZvLrE5SCh3brseQc24vbZTxzT/k/3urar5QMCVgjadmSO7lECeGdc6YxnM3yEGg==", + "version": "23.6.0", + "resolved": "https://registry.npmjs.org/jest-validate/-/jest-validate-23.6.0.tgz", + "integrity": "sha512-OFKapYxe72yz7agrDAWi8v2WL8GIfVqcbKRCLbRG9PAxtzF9b1SEDdTpytNDN12z2fJynoBwpMpvj2R39plI2A==", "dev": true, "requires": { "chalk": "^2.0.1", - "jest-get-type": "^21.2.0", + "jest-get-type": "^22.1.0", "leven": "^2.1.0", - "pretty-format": "^21.2.1" - }, - "dependencies": { - "ansi-styles": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.0.tgz", - "integrity": "sha512-NnSOmMEYtVR2JVMIGTzynRkkaxtiq1xnFBcdQD/DnNCYPoEPsVJhM98BDyaoNOQIi7p4okdi3E27eN7GQbsUug==", - "dev": true, - "requires": { - "color-convert": "^1.9.0" - } - }, - "chalk": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.3.0.tgz", - "integrity": "sha512-Az5zJR2CBujap2rqXGaJKaPHyJ0IrUimvYNX+ncCy8PJP4ltOGTrHUIo097ZaL2zMeKYpiCdqDvS6zdrTFok3Q==", - "dev": true, - "requires": { - "ansi-styles": "^3.1.0", - "escape-string-regexp": "^1.0.5", - "supports-color": "^4.0.0" - } - }, - "supports-color": { - "version": "4.5.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-4.5.0.tgz", - "integrity": "sha1-vnoN5ITexcXN34s9WRJQRJEvY1s=", - "dev": true, - "requires": { - "has-flag": "^2.0.0" - } - } + "pretty-format": "^23.6.0" } }, "jimp": { @@ -2441,9 +3141,9 @@ }, "dependencies": { "pngjs": { - "version": "3.3.1", - "resolved": "https://registry.npmjs.org/pngjs/-/pngjs-3.3.1.tgz", - "integrity": "sha512-ggXCTsqHRIsGMkHlCEhbHhUmNTA2r1lpkE0NL4Q9S8spkXbm4vE9TVmPso2AGYn90Gltdz8W5CyzhcIGg2Gejg==", + "version": "3.3.3", + "resolved": "https://registry.npmjs.org/pngjs/-/pngjs-3.3.3.tgz", + "integrity": "sha512-1n3Z4p3IOxArEs1VRXnZ/RXdfEniAUS9jb68g58FIXMNkPJeZd+Qh4Uq7/e0LVxAQGos1eIUrqrt4FpjdnEd+Q==", "dev": true } } @@ -2465,9 +3165,9 @@ "integrity": "sha1-mGbfOVECEw449/mWvOtlRDIJwls=" }, "js-yaml": { - "version": "3.10.0", - "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.10.0.tgz", - "integrity": "sha512-O2v52ffjLa9VeM43J4XocZE//WT9N0IiwDa3KSHH7Tu8CtH+1qM8SIZvnsTh6v+4yFy5KUY3BHUVwjpfAWsjIA==", + "version": "3.12.0", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.12.0.tgz", + "integrity": "sha512-PIt2cnwmPfL4hKNwqeiuz4bKfnzHTBv6HyVgjahA6mPLwPDzjDWrplJBMjHUFxku/N3FlmrbyPclad+I+4mJ3A==", "dev": true, "requires": { "argparse": "^1.0.7", @@ -2477,8 +3177,7 @@ "jsbn": { "version": "0.1.1", "resolved": "https://registry.npmjs.org/jsbn/-/jsbn-0.1.1.tgz", - "integrity": "sha1-peZUwuWi3rXyAdls77yoDA7y9RM=", - "optional": true + "integrity": "sha1-peZUwuWi3rXyAdls77yoDA7y9RM=" }, "jsesc": { "version": "0.5.0", @@ -2486,24 +3185,27 @@ "integrity": "sha1-597mbjXW/Bb3EP6R1c9p9w8IkR0=", "dev": true }, + "json-parse-better-errors": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/json-parse-better-errors/-/json-parse-better-errors-1.0.2.tgz", + "integrity": "sha512-mrqyZKfX5EhL7hvqcV6WG1yYjnjeuYDzDhhcAAUrq8Po85NBQBJP+ZDUT75qZQ98IkUoBqdkExkukOU7Ts2wrw==", + "dev": true + }, "json-schema": { "version": "0.2.3", "resolved": "https://registry.npmjs.org/json-schema/-/json-schema-0.2.3.tgz", "integrity": "sha1-tIDIkuWaLwWVTOcnvT8qTogvnhM=" }, "json-schema-traverse": { - "version": "0.3.1", - "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.3.1.tgz", - "integrity": "sha1-NJptRMU6Ud6JtAgFxdXlm0F9M0A=" + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", + "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==" }, - "json-stable-stringify": { + "json-stable-stringify-without-jsonify": { "version": "1.0.1", - "resolved": "https://registry.npmjs.org/json-stable-stringify/-/json-stable-stringify-1.0.1.tgz", - "integrity": "sha1-mnWdOcXy/1A/1TAGRu1EX4jE+a8=", - "dev": true, - "requires": { - "jsonify": "~0.0.0" - } + "resolved": "https://registry.npmjs.org/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz", + "integrity": "sha1-nbe1lJatPzz+8wp1FC0tkwrXJlE=", + "dev": true }, "json-stringify-safe": { "version": "5.0.1", @@ -2519,12 +3221,6 @@ "graceful-fs": "^4.1.6" } }, - "jsonify": { - "version": "0.0.0", - "resolved": "https://registry.npmjs.org/jsonify/-/jsonify-0.0.0.tgz", - "integrity": "sha1-LHS27kHZPKUbe1qu6PUDYx0lKnM=", - "dev": true - }, "jsprim": { "version": "1.4.1", "resolved": "https://registry.npmjs.org/jsprim/-/jsprim-1.4.1.tgz", @@ -2545,6 +3241,43 @@ "array-includes": "^3.0.3" } }, + "junit-report-builder": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/junit-report-builder/-/junit-report-builder-1.3.1.tgz", + "integrity": "sha512-KTueBpPsmjfiyrAxxhKlEMwXb3aRmDHG5tRYwtRF3ujLQ7/e/5MH3b2p9ND2P84rU8z5dQq40vWJv6TtEdS16Q==", + "dev": true, + "requires": { + "date-format": "0.0.2", + "lodash": "^4.17.10", + "mkdirp": "^0.5.0", + "xmlbuilder": "^10.0.0" + } + }, + "kind-of": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", + "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", + "dev": true, + "requires": { + "is-buffer": "^1.1.5" + }, + "dependencies": { + "is-buffer": { + "version": "1.1.6", + "resolved": "https://registry.npmjs.org/is-buffer/-/is-buffer-1.1.6.tgz", + "integrity": "sha512-NcdALwpXkTm5Zvvbk7owOUSvVvBKDgKP5/ewfXEznmQFfs4ZRmanOeKBTjRVjka3QFoN6XJ+9F3USqfHqTaU5w==", + "dev": true + } + } + }, + "kuler": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/kuler/-/kuler-1.0.1.tgz", + "integrity": "sha512-J9nVUucG1p/skKul6DU3PUZrhs0LPulNaeUOox0IyXDi8S4CztTHs1gQphhuZmzXG7VOQSf6NJfKuzteQLv9gQ==", + "requires": { + "colornames": "^1.1.1" + } + }, "langmap": { "version": "0.0.16", "resolved": "https://registry.npmjs.org/langmap/-/langmap-0.0.16.tgz", @@ -2592,101 +3325,399 @@ } }, "lint-staged": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/lint-staged/-/lint-staged-4.3.0.tgz", - "integrity": "sha512-C/Zxslg0VRbsxwmCu977iIs+QyrmW2cyRCPUV5NDFYOH/jtRFHH8ch7ua2fH0voI/nVC3Tpg7DykfgMZySliKw==", - "dev": true, - "requires": { - "app-root-path": "^2.0.0", - "chalk": "^2.1.0", - "commander": "^2.11.0", - "cosmiconfig": "^1.1.0", - "execa": "^0.8.0", + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/lint-staged/-/lint-staged-8.1.0.tgz", + "integrity": "sha512-yfSkyJy7EuVsaoxtUSEhrD81spdJOe/gMTGea3XaV7HyoRhTb9Gdlp6/JppRZERvKSEYXP9bjcmq6CA5oL2lYQ==", + "dev": true, + "requires": { + "@iamstarkov/listr-update-renderer": "0.4.1", + "chalk": "^2.3.1", + "commander": "^2.14.1", + "cosmiconfig": "5.0.6", + "debug": "^3.1.0", + "dedent": "^0.7.0", + "del": "^3.0.0", + "execa": "^1.0.0", + "find-parent-dir": "^0.3.0", + "g-status": "^2.0.2", "is-glob": "^4.0.0", - "jest-validate": "^21.1.0", - "listr": "^0.12.0", - "lodash": "^4.17.4", - "log-symbols": "^2.0.0", - "minimatch": "^3.0.0", + "is-windows": "^1.0.2", + "jest-validate": "^23.5.0", + "listr": "^0.14.2", + "lodash": "^4.17.5", + "log-symbols": "^2.2.0", + "micromatch": "^3.1.8", "npm-which": "^3.0.1", "p-map": "^1.1.1", - "staged-git-files": "0.0.4", - "stringify-object": "^3.2.0" + "path-is-inside": "^1.0.2", + "pify": "^3.0.0", + "please-upgrade-node": "^3.0.2", + "staged-git-files": "1.1.2", + "string-argv": "^0.0.2", + "stringify-object": "^3.2.2" }, "dependencies": { - "ansi-styles": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.0.tgz", - "integrity": "sha512-NnSOmMEYtVR2JVMIGTzynRkkaxtiq1xnFBcdQD/DnNCYPoEPsVJhM98BDyaoNOQIi7p4okdi3E27eN7GQbsUug==", + "arr-diff": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/arr-diff/-/arr-diff-4.0.0.tgz", + "integrity": "sha1-1kYQdP6/7HHn4VI1dhoyml3HxSA=", + "dev": true + }, + "array-unique": { + "version": "0.3.2", + "resolved": "https://registry.npmjs.org/array-unique/-/array-unique-0.3.2.tgz", + "integrity": "sha1-qJS3XUvE9s1nnvMkSp/Y9Gri1Cg=", + "dev": true + }, + "braces": { + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/braces/-/braces-2.3.2.tgz", + "integrity": "sha512-aNdbnj9P8PjdXU4ybaWLK2IF3jc/EoDYbC7AazW6to3TRsfXxscC9UXOB5iDiEQrkyIbWp2SLQda4+QAa7nc3w==", + "dev": true, + "requires": { + "arr-flatten": "^1.1.0", + "array-unique": "^0.3.2", + "extend-shallow": "^2.0.1", + "fill-range": "^4.0.0", + "isobject": "^3.0.1", + "repeat-element": "^1.1.2", + "snapdragon": "^0.8.1", + "snapdragon-node": "^2.0.1", + "split-string": "^3.0.2", + "to-regex": "^3.0.1" + }, + "dependencies": { + "extend-shallow": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", + "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", + "dev": true, + "requires": { + "is-extendable": "^0.1.0" + } + } + } + }, + "cosmiconfig": { + "version": "5.0.6", + "resolved": "https://registry.npmjs.org/cosmiconfig/-/cosmiconfig-5.0.6.tgz", + "integrity": "sha512-6DWfizHriCrFWURP1/qyhsiFvYdlJzbCzmtFWh744+KyWsJo5+kPzUZZaMRSSItoYc0pxFX7gEO7ZC1/gN/7AQ==", + "dev": true, + "requires": { + "is-directory": "^0.3.1", + "js-yaml": "^3.9.0", + "parse-json": "^4.0.0" + } + }, + "debug": { + "version": "3.2.6", + "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.6.tgz", + "integrity": "sha512-mel+jf7nrtEl5Pn1Qx46zARXKDpBbvzezse7p7LqINmdoIk8PYP5SySaxEmYv6TZ0JyEKA1hsCId6DIhgITtWQ==", + "dev": true, + "requires": { + "ms": "^2.1.1" + } + }, + "expand-brackets": { + "version": "2.1.4", + "resolved": "https://registry.npmjs.org/expand-brackets/-/expand-brackets-2.1.4.tgz", + "integrity": "sha1-t3c14xXOMPa27/D4OwQVGiJEliI=", + "dev": true, + "requires": { + "debug": "^2.3.3", + "define-property": "^0.2.5", + "extend-shallow": "^2.0.1", + "posix-character-classes": "^0.1.0", + "regex-not": "^1.0.0", + "snapdragon": "^0.8.1", + "to-regex": "^3.0.1" + }, + "dependencies": { + "debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "dev": true, + "requires": { + "ms": "2.0.0" + } + }, + "define-property": { + "version": "0.2.5", + "resolved": "https://registry.npmjs.org/define-property/-/define-property-0.2.5.tgz", + "integrity": "sha1-w1se+RjsPJkPmlvFe+BKrOxcgRY=", + "dev": true, + "requires": { + "is-descriptor": "^0.1.0" + } + }, + "extend-shallow": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", + "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", + "dev": true, + "requires": { + "is-extendable": "^0.1.0" + } + }, + "is-accessor-descriptor": { + "version": "0.1.6", + "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-0.1.6.tgz", + "integrity": "sha1-qeEss66Nh2cn7u84Q/igiXtcmNY=", + "dev": true, + "requires": { + "kind-of": "^3.0.2" + }, + "dependencies": { + "kind-of": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", + "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", + "dev": true, + "requires": { + "is-buffer": "^1.1.5" + } + } + } + }, + "is-data-descriptor": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-0.1.4.tgz", + "integrity": "sha1-C17mSDiOLIYCgueT8YVv7D8wG1Y=", + "dev": true, + "requires": { + "kind-of": "^3.0.2" + }, + "dependencies": { + "kind-of": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", + "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", + "dev": true, + "requires": { + "is-buffer": "^1.1.5" + } + } + } + }, + "is-descriptor": { + "version": "0.1.6", + "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-0.1.6.tgz", + "integrity": "sha512-avDYr0SB3DwO9zsMov0gKCESFYqCnE4hq/4z3TdUlukEy5t9C0YRq7HLrsN52NAcqXKaepeCD0n+B0arnVG3Hg==", + "dev": true, + "requires": { + "is-accessor-descriptor": "^0.1.6", + "is-data-descriptor": "^0.1.4", + "kind-of": "^5.0.0" + } + }, + "kind-of": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-5.1.0.tgz", + "integrity": "sha512-NGEErnH6F2vUuXDh+OlbcKW7/wOcfdRHaZ7VWtqCztfHri/++YKmP51OdWeGPuqCOba6kk2OTe5d02VmTB80Pw==", + "dev": true + }, + "ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=", + "dev": true + } + } + }, + "extglob": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/extglob/-/extglob-2.0.4.tgz", + "integrity": "sha512-Nmb6QXkELsuBr24CJSkilo6UHHgbekK5UiZgfE6UHD3Eb27YC6oD+bhcT+tJ6cl8dmsgdQxnWlcry8ksBIBLpw==", + "dev": true, + "requires": { + "array-unique": "^0.3.2", + "define-property": "^1.0.0", + "expand-brackets": "^2.1.4", + "extend-shallow": "^2.0.1", + "fragment-cache": "^0.2.1", + "regex-not": "^1.0.0", + "snapdragon": "^0.8.1", + "to-regex": "^3.0.1" + }, + "dependencies": { + "define-property": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/define-property/-/define-property-1.0.0.tgz", + "integrity": "sha1-dp66rz9KY6rTr56NMEybvnm/sOY=", + "dev": true, + "requires": { + "is-descriptor": "^1.0.0" + } + }, + "extend-shallow": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", + "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", + "dev": true, + "requires": { + "is-extendable": "^0.1.0" + } + } + } + }, + "fill-range": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-4.0.0.tgz", + "integrity": "sha1-1USBHUKPmOsGpj3EAtJAPDKMOPc=", "dev": true, "requires": { - "color-convert": "^1.9.0" + "extend-shallow": "^2.0.1", + "is-number": "^3.0.0", + "repeat-string": "^1.6.1", + "to-regex-range": "^2.1.0" + }, + "dependencies": { + "extend-shallow": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", + "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", + "dev": true, + "requires": { + "is-extendable": "^0.1.0" + } + } } }, - "chalk": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.3.0.tgz", - "integrity": "sha512-Az5zJR2CBujap2rqXGaJKaPHyJ0IrUimvYNX+ncCy8PJP4ltOGTrHUIo097ZaL2zMeKYpiCdqDvS6zdrTFok3Q==", + "is-accessor-descriptor": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-1.0.0.tgz", + "integrity": "sha512-m5hnHTkcVsPfqx3AKlyttIPb7J+XykHvJP2B9bZDjlhLIoEq4XoK64Vg7boZlVWYK6LUY94dYPEE7Lh0ZkZKcQ==", "dev": true, "requires": { - "ansi-styles": "^3.1.0", - "escape-string-regexp": "^1.0.5", - "supports-color": "^4.0.0" + "kind-of": "^6.0.0" } }, - "supports-color": { - "version": "4.5.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-4.5.0.tgz", - "integrity": "sha1-vnoN5ITexcXN34s9WRJQRJEvY1s=", + "is-buffer": { + "version": "1.1.6", + "resolved": "https://registry.npmjs.org/is-buffer/-/is-buffer-1.1.6.tgz", + "integrity": "sha512-NcdALwpXkTm5Zvvbk7owOUSvVvBKDgKP5/ewfXEznmQFfs4ZRmanOeKBTjRVjka3QFoN6XJ+9F3USqfHqTaU5w==", + "dev": true + }, + "is-data-descriptor": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-1.0.0.tgz", + "integrity": "sha512-jbRXy1FmtAoCjQkVmIVYwuuqDFUbaOeDjmed1tOGPrsMhtJA4rD9tkgA0F1qJ3gRFRXcHYVkdeaP50Q5rE/jLQ==", "dev": true, "requires": { - "has-flag": "^2.0.0" + "kind-of": "^6.0.0" + } + }, + "is-descriptor": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-1.0.2.tgz", + "integrity": "sha512-2eis5WqQGV7peooDyLmNEPUrps9+SXX5c9pL3xEB+4e9HnGuDa7mB7kHxHw4CbqS9k1T2hOH3miL8n8WtiYVtg==", + "dev": true, + "requires": { + "is-accessor-descriptor": "^1.0.0", + "is-data-descriptor": "^1.0.0", + "kind-of": "^6.0.2" + } + }, + "is-number": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-number/-/is-number-3.0.0.tgz", + "integrity": "sha1-JP1iAaR4LPUFYcgQJ2r8fRLXEZU=", + "dev": true, + "requires": { + "kind-of": "^3.0.2" + }, + "dependencies": { + "kind-of": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", + "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", + "dev": true, + "requires": { + "is-buffer": "^1.1.5" + } + } + } + }, + "isobject": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/isobject/-/isobject-3.0.1.tgz", + "integrity": "sha1-TkMekrEalzFjaqH5yNHMvP2reN8=", + "dev": true + }, + "kind-of": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.2.tgz", + "integrity": "sha512-s5kLOcnH0XqDO+FvuaLX8DDjZ18CGFk7VygH40QoKPUQhW4e2rvM0rwUq0t8IQDOwYSeLK01U90OjzBTme2QqA==", + "dev": true + }, + "micromatch": { + "version": "3.1.10", + "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-3.1.10.tgz", + "integrity": "sha512-MWikgl9n9M3w+bpsY3He8L+w9eF9338xRl8IAO5viDizwSzziFEyUzo2xrrloB64ADbTf8uA8vRqqttDTOmccg==", + "dev": true, + "requires": { + "arr-diff": "^4.0.0", + "array-unique": "^0.3.2", + "braces": "^2.3.1", + "define-property": "^2.0.2", + "extend-shallow": "^3.0.2", + "extglob": "^2.0.4", + "fragment-cache": "^0.2.1", + "kind-of": "^6.0.2", + "nanomatch": "^1.2.9", + "object.pick": "^1.3.0", + "regex-not": "^1.0.0", + "snapdragon": "^0.8.1", + "to-regex": "^3.0.2" + } + }, + "ms": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.1.tgz", + "integrity": "sha512-tgp+dl5cGk28utYktBsrFqA7HKgrhgPsg6Z/EfhWI4gl1Hwq8B/GmY/0oXZ6nF8hDVesS/FpnYaD/kOWhYQvyg==", + "dev": true + }, + "parse-json": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-4.0.0.tgz", + "integrity": "sha1-vjX1Qlvh9/bHRxhPmKeIy5lHfuA=", + "dev": true, + "requires": { + "error-ex": "^1.3.1", + "json-parse-better-errors": "^1.0.1" } + }, + "pify": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/pify/-/pify-3.0.0.tgz", + "integrity": "sha1-5aSs0sEB/fPZpNB/DbxNtJ3SgXY=", + "dev": true } } }, "listr": { - "version": "0.12.0", - "resolved": "https://registry.npmjs.org/listr/-/listr-0.12.0.tgz", - "integrity": "sha1-a84sD1YD+klYDqF81qAMwOX6RRo=", + "version": "0.14.3", + "resolved": "https://registry.npmjs.org/listr/-/listr-0.14.3.tgz", + "integrity": "sha512-RmAl7su35BFd/xoMamRjpIE4j3v+L28o8CT5YhAXQJm1fD+1l9ngXY8JAQRJ+tFK2i5njvi0iRUKV09vPwA0iA==", "dev": true, "requires": { - "chalk": "^1.1.3", - "cli-truncate": "^0.2.1", - "figures": "^1.7.0", - "indent-string": "^2.1.0", + "@samverschueren/stream-to-observable": "^0.3.0", + "is-observable": "^1.1.0", "is-promise": "^2.1.0", "is-stream": "^1.1.0", "listr-silent-renderer": "^1.1.1", - "listr-update-renderer": "^0.2.0", - "listr-verbose-renderer": "^0.4.0", - "log-symbols": "^1.0.2", - "log-update": "^1.0.2", - "ora": "^0.2.3", - "p-map": "^1.1.1", - "rxjs": "^5.0.0-beta.11", - "stream-to-observable": "^0.1.0", - "strip-ansi": "^3.0.1" + "listr-update-renderer": "^0.5.0", + "listr-verbose-renderer": "^0.5.0", + "p-map": "^2.0.0", + "rxjs": "^6.3.3" }, "dependencies": { - "figures": { - "version": "1.7.0", - "resolved": "https://registry.npmjs.org/figures/-/figures-1.7.0.tgz", - "integrity": "sha1-y+Hjr/zxzUS4DK3+0o3Hk6lwHS4=", - "dev": true, - "requires": { - "escape-string-regexp": "^1.0.5", - "object-assign": "^4.1.0" - } - }, - "log-symbols": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/log-symbols/-/log-symbols-1.0.2.tgz", - "integrity": "sha1-N2/3tY6jCGoPCfrMdGF+ylAeGhg=", - "dev": true, - "requires": { - "chalk": "^1.0.0" - } + "p-map": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/p-map/-/p-map-2.0.0.tgz", + "integrity": "sha512-GO107XdrSUmtHxVoi60qc9tUl/KkNKm+X2CF4P9amalpGxv5YqVPJNfSb0wcA+syCopkZvYYIzW8OVTQW59x/w==", + "dev": true } } }, @@ -2697,9 +3728,9 @@ "dev": true }, "listr-update-renderer": { - "version": "0.2.0", - "resolved": "https://registry.npmjs.org/listr-update-renderer/-/listr-update-renderer-0.2.0.tgz", - "integrity": "sha1-yoDhd5tOcCZoB+ju0a1qvjmFUPk=", + "version": "0.5.0", + "resolved": "https://registry.npmjs.org/listr-update-renderer/-/listr-update-renderer-0.5.0.tgz", + "integrity": "sha512-tKRsZpKz8GSGqoI/+caPmfrypiaq+OQCbd+CovEC24uk1h952lVj5sC7SqyFUm+OaJ5HN/a1YLt5cit2FMNsFA==", "dev": true, "requires": { "chalk": "^1.1.3", @@ -2708,10 +3739,29 @@ "figures": "^1.7.0", "indent-string": "^3.0.0", "log-symbols": "^1.0.2", - "log-update": "^1.0.2", + "log-update": "^2.3.0", "strip-ansi": "^3.0.1" }, "dependencies": { + "ansi-styles": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-2.2.1.tgz", + "integrity": "sha1-tDLdM1i2NM914eRmQ2gkBTPB3b4=", + "dev": true + }, + "chalk": { + "version": "1.1.3", + "resolved": "http://registry.npmjs.org/chalk/-/chalk-1.1.3.tgz", + "integrity": "sha1-qBFcVeSnAv5NFQq9OHKCKn4J/Jg=", + "dev": true, + "requires": { + "ansi-styles": "^2.2.1", + "escape-string-regexp": "^1.0.2", + "has-ansi": "^2.0.0", + "strip-ansi": "^3.0.0", + "supports-color": "^2.0.0" + } + }, "figures": { "version": "1.7.0", "resolved": "https://registry.npmjs.org/figures/-/figures-1.7.0.tgz", @@ -2736,76 +3786,46 @@ "requires": { "chalk": "^1.0.0" } + }, + "supports-color": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-2.0.0.tgz", + "integrity": "sha1-U10EXOa2Nj+kARcIRimZXp3zJMc=", + "dev": true } } }, "listr-verbose-renderer": { - "version": "0.4.1", - "resolved": "https://registry.npmjs.org/listr-verbose-renderer/-/listr-verbose-renderer-0.4.1.tgz", - "integrity": "sha1-ggb0z21S3cWCfl/RSYng6WWTOjU=", + "version": "0.5.0", + "resolved": "https://registry.npmjs.org/listr-verbose-renderer/-/listr-verbose-renderer-0.5.0.tgz", + "integrity": "sha512-04PDPqSlsqIOaaaGZ+41vq5FejI9auqTInicFRndCBgE3bXG8D6W1I+mWhk+1nqbHmyhla/6BUrd5OSiHwKRXw==", "dev": true, "requires": { - "chalk": "^1.1.3", - "cli-cursor": "^1.0.2", + "chalk": "^2.4.1", + "cli-cursor": "^2.1.0", "date-fns": "^1.27.2", - "figures": "^1.7.0" - }, - "dependencies": { - "cli-cursor": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/cli-cursor/-/cli-cursor-1.0.2.tgz", - "integrity": "sha1-ZNo/fValRBLll5S9Ytw1KV6PKYc=", - "dev": true, - "requires": { - "restore-cursor": "^1.0.1" - } - }, - "figures": { - "version": "1.7.0", - "resolved": "https://registry.npmjs.org/figures/-/figures-1.7.0.tgz", - "integrity": "sha1-y+Hjr/zxzUS4DK3+0o3Hk6lwHS4=", - "dev": true, - "requires": { - "escape-string-regexp": "^1.0.5", - "object-assign": "^4.1.0" - } - }, - "onetime": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/onetime/-/onetime-1.1.0.tgz", - "integrity": "sha1-ofeDj4MUxRbwXs78vEzP4EtO14k=", - "dev": true - }, - "restore-cursor": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/restore-cursor/-/restore-cursor-1.0.1.tgz", - "integrity": "sha1-NGYfRohjJ/7SmRR5FSJS35LapUE=", - "dev": true, - "requires": { - "exit-hook": "^1.0.0", - "onetime": "^1.0.0" - } - } + "figures": "^2.0.0" } }, "load-bmfont": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/load-bmfont/-/load-bmfont-1.3.0.tgz", - "integrity": "sha1-u358cQ3mvK/LE8s7jIHgwBMey8k=", + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/load-bmfont/-/load-bmfont-1.4.0.tgz", + "integrity": "sha512-kT63aTAlNhZARowaNYcY29Fn/QYkc52M3l6V1ifRcPewg2lvUZDAj7R6dXjOL9D0sict76op3T5+odumDSF81g==", "dev": true, "requires": { "buffer-equal": "0.0.1", "mime": "^1.3.4", "parse-bmfont-ascii": "^1.0.3", "parse-bmfont-binary": "^1.0.5", - "parse-bmfont-xml": "^1.1.0", + "parse-bmfont-xml": "^1.1.4", + "phin": "^2.9.1", "xhr": "^2.0.1", "xtend": "^4.0.0" } }, "load-json-file": { "version": "1.1.0", - "resolved": "https://registry.npmjs.org/load-json-file/-/load-json-file-1.1.0.tgz", + "resolved": "http://registry.npmjs.org/load-json-file/-/load-json-file-1.1.0.tgz", "integrity": "sha1-lWkFcI1YtLq0wiYbBPWfMcmTdMA=", "requires": { "graceful-fs": "^4.1.2", @@ -2834,9 +3854,9 @@ } }, "lodash": { - "version": "4.17.10", - "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.10.tgz", - "integrity": "sha512-UejweD1pDoXu+AD825lWwp4ZGtSwgnpZxb3JDViD7StjQz+Nb/6l093lx4OQ0foGWNRoc19mWy7BzL+UAK2iVg==" + "version": "4.17.11", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.11.tgz", + "integrity": "sha512-cQKh8igo5QUhZ7lg38DYWAxMvjSAKG0A8wGSVimP07SIUEK2UO+arSRKbRZWtelMtN5V0Hkwh5ryOto/SshYIg==" }, "lodash.assign": { "version": "4.2.0", @@ -2848,12 +3868,6 @@ "resolved": "https://registry.npmjs.org/lodash.clonedeep/-/lodash.clonedeep-4.5.0.tgz", "integrity": "sha1-4j8/nE+Pvd6HJSnBBxhXoIblzO8=" }, - "lodash.cond": { - "version": "4.5.2", - "resolved": "https://registry.npmjs.org/lodash.cond/-/lodash.cond-4.5.2.tgz", - "integrity": "sha1-9HGh2khr5g9quVXRcRVSPdHSVdU=", - "dev": true - }, "lodash.get": { "version": "4.4.2", "resolved": "https://registry.npmjs.org/lodash.get/-/lodash.get-4.4.2.tgz", @@ -2872,79 +3886,78 @@ "dev": true, "requires": { "chalk": "^2.0.1" + } + }, + "log-update": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/log-update/-/log-update-2.3.0.tgz", + "integrity": "sha1-iDKP19HOeTiykoN0bwsbwSayRwg=", + "dev": true, + "requires": { + "ansi-escapes": "^3.0.0", + "cli-cursor": "^2.0.0", + "wrap-ansi": "^3.0.1" }, "dependencies": { - "ansi-styles": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.0.tgz", - "integrity": "sha512-NnSOmMEYtVR2JVMIGTzynRkkaxtiq1xnFBcdQD/DnNCYPoEPsVJhM98BDyaoNOQIi7p4okdi3E27eN7GQbsUug==", + "ansi-regex": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-3.0.0.tgz", + "integrity": "sha1-7QMXwyIGT3lGbAKWa922Bas32Zg=", + "dev": true + }, + "is-fullwidth-code-point": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-2.0.0.tgz", + "integrity": "sha1-o7MKXE8ZkYMWeqq5O+764937ZU8=", + "dev": true + }, + "string-width": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-2.1.1.tgz", + "integrity": "sha512-nOqH59deCq9SRHlxq1Aw85Jnt4w6KvLKqWVik6oA9ZklXLNIOlqg4F2yrT1MVaTjAqvVwdfeZ7w7aCvJD7ugkw==", "dev": true, "requires": { - "color-convert": "^1.9.0" + "is-fullwidth-code-point": "^2.0.0", + "strip-ansi": "^4.0.0" } }, - "chalk": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.3.0.tgz", - "integrity": "sha512-Az5zJR2CBujap2rqXGaJKaPHyJ0IrUimvYNX+ncCy8PJP4ltOGTrHUIo097ZaL2zMeKYpiCdqDvS6zdrTFok3Q==", + "strip-ansi": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-4.0.0.tgz", + "integrity": "sha1-qEeQIusaw2iocTibY1JixQXuNo8=", "dev": true, "requires": { - "ansi-styles": "^3.1.0", - "escape-string-regexp": "^1.0.5", - "supports-color": "^4.0.0" + "ansi-regex": "^3.0.0" } }, - "supports-color": { - "version": "4.5.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-4.5.0.tgz", - "integrity": "sha1-vnoN5ITexcXN34s9WRJQRJEvY1s=", + "wrap-ansi": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-3.0.1.tgz", + "integrity": "sha1-KIoE2H7aXChuBg3+jxNc6NAH+Lo=", "dev": true, "requires": { - "has-flag": "^2.0.0" + "string-width": "^2.1.1", + "strip-ansi": "^4.0.0" } } } }, - "log-update": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/log-update/-/log-update-1.0.2.tgz", - "integrity": "sha1-GZKfZMQJPS0ucHWh2tivWcKWuNE=", - "dev": true, - "requires": { - "ansi-escapes": "^1.0.0", - "cli-cursor": "^1.0.2" + "logform": { + "version": "1.10.0", + "resolved": "https://registry.npmjs.org/logform/-/logform-1.10.0.tgz", + "integrity": "sha512-em5ojIhU18fIMOw/333mD+ZLE2fis0EzXl1ZwHx4iQzmpQi6odNiY/t+ITNr33JZhT9/KEaH+UPIipr6a9EjWg==", + "requires": { + "colors": "^1.2.1", + "fast-safe-stringify": "^2.0.4", + "fecha": "^2.3.3", + "ms": "^2.1.1", + "triple-beam": "^1.2.0" }, "dependencies": { - "ansi-escapes": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-1.4.0.tgz", - "integrity": "sha1-06ioOzGapneTZisT52HHkRQiMG4=", - "dev": true - }, - "cli-cursor": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/cli-cursor/-/cli-cursor-1.0.2.tgz", - "integrity": "sha1-ZNo/fValRBLll5S9Ytw1KV6PKYc=", - "dev": true, - "requires": { - "restore-cursor": "^1.0.1" - } - }, - "onetime": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/onetime/-/onetime-1.1.0.tgz", - "integrity": "sha1-ofeDj4MUxRbwXs78vEzP4EtO14k=", - "dev": true - }, - "restore-cursor": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/restore-cursor/-/restore-cursor-1.0.1.tgz", - "integrity": "sha1-NGYfRohjJ/7SmRR5FSJS35LapUE=", - "dev": true, - "requires": { - "exit-hook": "^1.0.0", - "onetime": "^1.0.0" - } + "ms": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.1.tgz", + "integrity": "sha512-tgp+dl5cGk28utYktBsrFqA7HKgrhgPsg6Z/EfhWI4gl1Hwq8B/GmY/0oXZ6nF8hDVesS/FpnYaD/kOWhYQvyg==" } } }, @@ -2965,15 +3978,10 @@ "signal-exit": "^3.0.0" } }, - "lowercase-keys": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/lowercase-keys/-/lowercase-keys-1.0.0.tgz", - "integrity": "sha1-TjNms55/VFfjXxMkvfb4jQv8cwY=" - }, "lru-cache": { - "version": "4.1.3", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-4.1.3.tgz", - "integrity": "sha512-fFEhvcgzuIoJVUF8fYr5KR0YqxD238zgObTps31YdADwPPAp82a4M8TrckkWyx7ekNlf9aBcVn81cFwwXngrJA==", + "version": "4.1.5", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-4.1.5.tgz", + "integrity": "sha512-sWZlbEP2OsHNkXrMl5GYk/jKk70MBng6UU4YI/qGDYbgf6YbP4EvmqISbXCoJiRKs+1bSpFHVgQxvJ17F2li5g==", "requires": { "pseudomap": "^1.0.2", "yallist": "^2.1.2" @@ -2987,11 +3995,35 @@ "custom-event-polyfill": "~0.3" } }, + "map-cache": { + "version": "0.2.2", + "resolved": "https://registry.npmjs.org/map-cache/-/map-cache-0.2.2.tgz", + "integrity": "sha1-wyq9C9ZSXZsFFkW7TyasXcmKDb8=", + "dev": true + }, "map-obj": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/map-obj/-/map-obj-1.0.1.tgz", "integrity": "sha1-2TPOuSBdgr3PSIb2dCvcK03qFG0=" }, + "map-visit": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/map-visit/-/map-visit-1.0.0.tgz", + "integrity": "sha1-7Nyo8TFE5mDxtb1B8S80edmN+48=", + "dev": true, + "requires": { + "object-visit": "^1.0.0" + } + }, + "matcher": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/matcher/-/matcher-1.1.1.tgz", + "integrity": "sha512-+BmqxWIubKTRKNWx/ahnCkk3mG8m7OturVlqq6HiojGJTd5hVYbgZm6WzcYPCoB+KBT4Vd6R7WSRG2OADNaCjg==", + "dev": true, + "requires": { + "escape-string-regexp": "^1.0.4" + } + }, "material-colors": { "version": "1.2.5", "resolved": "https://registry.npmjs.org/material-colors/-/material-colors-1.2.5.tgz", @@ -2999,7 +4031,7 @@ }, "meow": { "version": "3.7.0", - "resolved": "https://registry.npmjs.org/meow/-/meow-3.7.0.tgz", + "resolved": "http://registry.npmjs.org/meow/-/meow-3.7.0.tgz", "integrity": "sha1-cstmi0JSKCkKu/qFaJJYcwioAfs=", "requires": { "camelcase-keys": "^2.0.0", @@ -3015,9 +4047,9 @@ } }, "meteor-node-stubs": { - "version": "0.3.3", - "resolved": "https://registry.npmjs.org/meteor-node-stubs/-/meteor-node-stubs-0.3.3.tgz", - "integrity": "sha512-TI1aQRK0vqs94OCkUMkmf5lXNWfIsjSaEDP1inUuwRGt9w8/S2V+HdRikz9r1k/gew+7NcJieaqHsHX7pSTEgA==", + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/meteor-node-stubs/-/meteor-node-stubs-0.4.1.tgz", + "integrity": "sha512-UO2OStvLOKoApmOdIP5eCqoLaa/ritMXRg4ffJVdkNLEsczzPvTjgC0Mxk4cM4R8MZkwll90FYgjDf5qUTJdMA==", "requires": { "assert": "^1.4.1", "browserify-zlib": "^0.1.4", @@ -3033,7 +4065,7 @@ "process": "^0.11.9", "punycode": "^1.4.1", "querystring-es3": "^0.2.1", - "readable-stream": "git+https://github.com/meteor/readable-stream.git#c688cdd193549919b840e8d72a86682d91961e12", + "readable-stream": "^2.3.6", "stream-browserify": "^2.0.1", "stream-http": "^2.8.0", "string_decoder": "^1.1.0", @@ -3046,8 +4078,7 @@ "dependencies": { "asn1.js": { "version": "4.10.1", - "resolved": "https://registry.npmjs.org/asn1.js/-/asn1.js-4.10.1.tgz", - "integrity": "sha512-p32cOF5q0Zqs9uBiONKYLm6BClCoBCM5O9JfeUSlnQLBTxYdTK+pW+nXflm8UkKd2UYlEbYz5qEi0JuZR9ckSw==", + "bundled": true, "requires": { "bn.js": "^4.0.0", "inherits": "^2.0.1", @@ -3056,45 +4087,26 @@ }, "assert": { "version": "1.4.1", - "resolved": "https://registry.npmjs.org/assert/-/assert-1.4.1.tgz", - "integrity": "sha1-mZEtWRg2tab1s0XA8H7vwI/GXZE=", + "bundled": true, "requires": { "util": "0.10.3" } }, - "balanced-match": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.0.tgz", - "integrity": "sha1-ibTRmasr7kneFk6gK4nORi1xt2c=" - }, "base64-js": { - "version": "1.2.3", - "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.2.3.tgz", - "integrity": "sha512-MsAhsUW1GxCdgYSO6tAfZrNapmUKk7mWx/k5mFY/A1gBtkaCaNapTg+FExCw1r9yeaZhqx/xPg43xgTFH6KL5w==" + "version": "1.3.0", + "bundled": true }, "bn.js": { "version": "4.11.8", - "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-4.11.8.tgz", - "integrity": "sha512-ItfYfPLkWHUjckQCk8xC+LwxgK8NYcXywGigJgSwOP8Y2iyWT4f2vsZnoOXTTbo+o5yXmIUJ4gn5538SO5S3gA==" - }, - "brace-expansion": { - "version": "1.1.11", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", - "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", - "requires": { - "balanced-match": "^1.0.0", - "concat-map": "0.0.1" - } + "bundled": true }, "brorand": { "version": "1.1.0", - "resolved": "https://registry.npmjs.org/brorand/-/brorand-1.1.0.tgz", - "integrity": "sha1-EsJe/kCkXjwyPrhnWgoM5XsiNx8=" + "bundled": true }, "browserify-aes": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/browserify-aes/-/browserify-aes-1.1.1.tgz", - "integrity": "sha512-UGnTYAnB2a3YuYKIRy1/4FB2HdM866E0qC46JXvVTYKlBlZlnvfpSfY6OKfXZAkv70eJ2a1SqzpAo5CRhZGDFg==", + "version": "1.2.0", + "bundled": true, "requires": { "buffer-xor": "^1.0.3", "cipher-base": "^1.0.0", @@ -3105,9 +4117,8 @@ } }, "browserify-cipher": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/browserify-cipher/-/browserify-cipher-1.0.0.tgz", - "integrity": "sha1-mYgkSHS/XtTijalWZtzWasj8Njo=", + "version": "1.0.1", + "bundled": true, "requires": { "browserify-aes": "^1.0.4", "browserify-des": "^1.0.0", @@ -3115,9 +4126,8 @@ } }, "browserify-des": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/browserify-des/-/browserify-des-1.0.0.tgz", - "integrity": "sha1-2qJ3cXRwki7S/hhZQRihdUOXId0=", + "version": "1.0.1", + "bundled": true, "requires": { "cipher-base": "^1.0.1", "des.js": "^1.0.0", @@ -3126,8 +4136,7 @@ }, "browserify-rsa": { "version": "4.0.1", - "resolved": "https://registry.npmjs.org/browserify-rsa/-/browserify-rsa-4.0.1.tgz", - "integrity": "sha1-IeCr+vbyApzy+vsTNWenAdQTVSQ=", + "bundled": true, "requires": { "bn.js": "^4.1.0", "randombytes": "^2.0.1" @@ -3135,8 +4144,7 @@ }, "browserify-sign": { "version": "4.0.4", - "resolved": "https://registry.npmjs.org/browserify-sign/-/browserify-sign-4.0.4.tgz", - "integrity": "sha1-qk62jl17ZYuqa/alfmMMvXqT0pg=", + "bundled": true, "requires": { "bn.js": "^4.1.1", "browserify-rsa": "^4.0.0", @@ -3149,16 +4157,14 @@ }, "browserify-zlib": { "version": "0.1.4", - "resolved": "https://registry.npmjs.org/browserify-zlib/-/browserify-zlib-0.1.4.tgz", - "integrity": "sha1-uzX4pRn2AOD6a4SFJByXnQFB+y0=", + "bundled": true, "requires": { "pako": "~0.2.0" } }, "buffer": { "version": "4.9.1", - "resolved": "https://registry.npmjs.org/buffer/-/buffer-4.9.1.tgz", - "integrity": "sha1-bRu2AbB6TvztlwlBMgkwJ8lbwpg=", + "bundled": true, "requires": { "base64-js": "^1.0.2", "ieee754": "^1.1.4", @@ -3167,65 +4173,57 @@ }, "buffer-xor": { "version": "1.0.3", - "resolved": "https://registry.npmjs.org/buffer-xor/-/buffer-xor-1.0.3.tgz", - "integrity": "sha1-JuYe0UIvtw3ULm42cp7VHYVf6Nk=" + "bundled": true }, "builtin-status-codes": { "version": "3.0.0", - "resolved": "https://registry.npmjs.org/builtin-status-codes/-/builtin-status-codes-3.0.0.tgz", - "integrity": "sha1-hZgoeOIbmOHGZCXgPQF0eI9Wnug=" + "bundled": true }, "cipher-base": { "version": "1.0.4", - "resolved": "https://registry.npmjs.org/cipher-base/-/cipher-base-1.0.4.tgz", - "integrity": "sha512-Kkht5ye6ZGmwv40uUDZztayT2ThLQGfnj/T71N/XzeZeo3nf8foyW7zGTsPYkEya3m5f3cAypH+qe7YOrM1U2Q==", + "bundled": true, "requires": { "inherits": "^2.0.1", "safe-buffer": "^5.0.1" } }, - "concat-map": { - "version": "0.0.1", - "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", - "integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=" - }, "console-browserify": { "version": "1.1.0", - "resolved": "https://registry.npmjs.org/console-browserify/-/console-browserify-1.1.0.tgz", - "integrity": "sha1-8CQcRXMKn8YyOyBtvzjtx0HQuxA=", + "bundled": true, "requires": { "date-now": "^0.1.4" } }, "constants-browserify": { "version": "1.0.0", - "resolved": "https://registry.npmjs.org/constants-browserify/-/constants-browserify-1.0.0.tgz", - "integrity": "sha1-wguW2MYXdIqvHBYCF2DNJ/y4y3U=" + "bundled": true + }, + "core-util-is": { + "version": "1.0.2", + "bundled": true }, "create-ecdh": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/create-ecdh/-/create-ecdh-4.0.0.tgz", - "integrity": "sha1-iIxyNZbN92EvZJgjPuvXo1MBc30=", + "version": "4.0.3", + "bundled": true, "requires": { "bn.js": "^4.1.0", "elliptic": "^6.0.0" } }, "create-hash": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/create-hash/-/create-hash-1.1.3.tgz", - "integrity": "sha1-YGBCrIuSYnUPSDyt2rD1gZFy2P0=", + "version": "1.2.0", + "bundled": true, "requires": { "cipher-base": "^1.0.1", "inherits": "^2.0.1", - "ripemd160": "^2.0.0", + "md5.js": "^1.3.4", + "ripemd160": "^2.0.1", "sha.js": "^2.4.0" } }, "create-hmac": { - "version": "1.1.6", - "resolved": "https://registry.npmjs.org/create-hmac/-/create-hmac-1.1.6.tgz", - "integrity": "sha1-rLniIaThe9sHbpBlfEK5PjcmzwY=", + "version": "1.1.7", + "bundled": true, "requires": { "cipher-base": "^1.0.3", "create-hash": "^1.1.0", @@ -3237,8 +4235,7 @@ }, "crypto-browserify": { "version": "3.12.0", - "resolved": "https://registry.npmjs.org/crypto-browserify/-/crypto-browserify-3.12.0.tgz", - "integrity": "sha512-fz4spIh+znjO2VjL+IdhEpRJ3YN6sMzITSBijk6FK2UvTqruSQW+/cCZTSNsMiZNvUeq0CqurF+dAbyiGOY6Wg==", + "bundled": true, "requires": { "browserify-cipher": "^1.0.0", "browserify-sign": "^4.0.0", @@ -3255,22 +4252,19 @@ }, "date-now": { "version": "0.1.4", - "resolved": "https://registry.npmjs.org/date-now/-/date-now-0.1.4.tgz", - "integrity": "sha1-6vQ5/U1ISK105cx9vvIAZyueNFs=" + "bundled": true }, "des.js": { "version": "1.0.0", - "resolved": "https://registry.npmjs.org/des.js/-/des.js-1.0.0.tgz", - "integrity": "sha1-wHTS4qpqipoH29YfmhXCzYPsjsw=", + "bundled": true, "requires": { "inherits": "^2.0.1", "minimalistic-assert": "^1.0.0" } }, "diffie-hellman": { - "version": "5.0.2", - "resolved": "https://registry.npmjs.org/diffie-hellman/-/diffie-hellman-5.0.2.tgz", - "integrity": "sha1-tYNXOScM/ias9jIJn97SoH8gnl4=", + "version": "5.0.3", + "bundled": true, "requires": { "bn.js": "^4.1.0", "miller-rabin": "^4.0.0", @@ -3279,13 +4273,11 @@ }, "domain-browser": { "version": "1.2.0", - "resolved": "https://registry.npmjs.org/domain-browser/-/domain-browser-1.2.0.tgz", - "integrity": "sha512-jnjyiM6eRyZl2H+W8Q/zLMA481hzi0eszAaBUzIVnmYVDBbnLxVNnfu1HgEBvCbL+71FrxMl3E6lpKH7Ge3OXA==" + "bundled": true }, "elliptic": { "version": "6.4.0", - "resolved": "https://registry.npmjs.org/elliptic/-/elliptic-6.4.0.tgz", - "integrity": "sha1-ysmvh2LIWDYYcAPI3+GT5eLq5d8=", + "bundled": true, "requires": { "bn.js": "^4.4.0", "brorand": "^1.0.1", @@ -3298,48 +4290,27 @@ }, "events": { "version": "1.1.1", - "resolved": "https://registry.npmjs.org/events/-/events-1.1.1.tgz", - "integrity": "sha1-nr23Y1rQmccNzEwqH1AEKI6L2SQ=" + "bundled": true }, "evp_bytestokey": { "version": "1.0.3", - "resolved": "https://registry.npmjs.org/evp_bytestokey/-/evp_bytestokey-1.0.3.tgz", - "integrity": "sha512-/f2Go4TognH/KvCISP7OUsHn85hT9nUkxxA9BEWxFn+Oj9o8ZNLm/40hdlgSLyuOimsrTKLUMEorQexp/aPQeA==", + "bundled": true, "requires": { "md5.js": "^1.3.4", "safe-buffer": "^5.1.1" } }, - "fs.realpath": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", - "integrity": "sha1-FQStJSMVjKpA20onh8sBQRmU6k8=" - }, - "glob": { - "version": "7.1.2", - "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.2.tgz", - "integrity": "sha512-MJTUg1kjuLeQCJ+ccE4Vpa6kKVXkPYJ2mOCQyUuKLcLQsdrMCpBPUi8qVE6+YuaJkozeA9NusTAw3hLr8Xe5EQ==", - "requires": { - "fs.realpath": "^1.0.0", - "inflight": "^1.0.4", - "inherits": "2", - "minimatch": "^3.0.4", - "once": "^1.3.0", - "path-is-absolute": "^1.0.0" - } - }, "hash-base": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/hash-base/-/hash-base-2.0.2.tgz", - "integrity": "sha1-ZuodhW206KVHDK32/OI65SRO8uE=", + "version": "3.0.4", + "bundled": true, "requires": { - "inherits": "^2.0.1" + "inherits": "^2.0.1", + "safe-buffer": "^5.0.1" } }, "hash.js": { "version": "1.1.3", - "resolved": "https://registry.npmjs.org/hash.js/-/hash.js-1.1.3.tgz", - "integrity": "sha512-/UETyP0W22QILqS+6HowevwhEFJ3MBJnwTf75Qob9Wz9t0DPuisL8kW8YZMK62dHAKE1c1p+gY1TtOLY+USEHA==", + "bundled": true, "requires": { "inherits": "^2.0.3", "minimalistic-assert": "^1.0.0" @@ -3347,15 +4318,13 @@ "dependencies": { "inherits": { "version": "2.0.3", - "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz", - "integrity": "sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4=" + "bundled": true } } }, "hmac-drbg": { "version": "1.0.1", - "resolved": "https://registry.npmjs.org/hmac-drbg/-/hmac-drbg-1.0.1.tgz", - "integrity": "sha1-0nRXAQJabHdabFRXk+1QL8DGSaE=", + "bundled": true, "requires": { "hash.js": "^1.0.3", "minimalistic-assert": "^1.0.0", @@ -3364,107 +4333,59 @@ }, "https-browserify": { "version": "0.0.1", - "resolved": "https://registry.npmjs.org/https-browserify/-/https-browserify-0.0.1.tgz", - "integrity": "sha1-P5E2XKvmC3ftDruiS0VOPgnZWoI=" + "bundled": true }, "ieee754": { - "version": "1.1.8", - "resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.1.8.tgz", - "integrity": "sha1-vjPUCsEO8ZJnAfbwii2G+/0a0+Q=" + "version": "1.1.11", + "bundled": true }, "indexof": { "version": "0.0.1", - "resolved": "https://registry.npmjs.org/indexof/-/indexof-0.0.1.tgz", - "integrity": "sha1-gtwzbSMrkGIXnQWrMpOmYFn9Q10=" - }, - "inflight": { - "version": "1.0.6", - "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", - "integrity": "sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk=", - "requires": { - "once": "^1.3.0", - "wrappy": "1" - } + "bundled": true }, "inherits": { "version": "2.0.1", - "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.1.tgz", - "integrity": "sha1-sX0I0ya0Qj5Wjv9xn5GwscvfafE=" + "bundled": true }, "isarray": { "version": "1.0.0", - "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", - "integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=" + "bundled": true }, "md5.js": { "version": "1.3.4", - "resolved": "https://registry.npmjs.org/md5.js/-/md5.js-1.3.4.tgz", - "integrity": "sha1-6b296UogpawYsENA/Fdk1bCdkB0=", + "bundled": true, "requires": { "hash-base": "^3.0.0", "inherits": "^2.0.1" - }, - "dependencies": { - "hash-base": { - "version": "3.0.4", - "resolved": "https://registry.npmjs.org/hash-base/-/hash-base-3.0.4.tgz", - "integrity": "sha1-X8hoaEfs1zSZQDMZprCj8/auSRg=", - "requires": { - "inherits": "^2.0.1", - "safe-buffer": "^5.0.1" - } - } } }, "miller-rabin": { "version": "4.0.1", - "resolved": "https://registry.npmjs.org/miller-rabin/-/miller-rabin-4.0.1.tgz", - "integrity": "sha512-115fLhvZVqWwHPbClyntxEVfVDfl9DLLTuJvq3g2O/Oxi8AiNouAHvDSzHS0viUJc+V5vm3eq91Xwqn9dp4jRA==", + "bundled": true, "requires": { "bn.js": "^4.0.0", "brorand": "^1.0.1" } }, "minimalistic-assert": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/minimalistic-assert/-/minimalistic-assert-1.0.0.tgz", - "integrity": "sha1-cCvi3aazf0g2vLP121ZkG2Sh09M=" + "version": "1.0.1", + "bundled": true }, "minimalistic-crypto-utils": { "version": "1.0.1", - "resolved": "https://registry.npmjs.org/minimalistic-crypto-utils/-/minimalistic-crypto-utils-1.0.1.tgz", - "integrity": "sha1-9sAMHAsIIkblxNmd+4x8CDsrWCo=" - }, - "minimatch": { - "version": "3.0.4", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz", - "integrity": "sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==", - "requires": { - "brace-expansion": "^1.1.7" - } - }, - "once": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", - "integrity": "sha1-WDsap3WWHUsROsF9nFC6753Xa9E=", - "requires": { - "wrappy": "1" - } + "bundled": true }, "os-browserify": { "version": "0.2.1", - "resolved": "https://registry.npmjs.org/os-browserify/-/os-browserify-0.2.1.tgz", - "integrity": "sha1-Y/xMzuXS13Y9Jrv4YBB45sLgBE8=" + "bundled": true }, "pako": { "version": "0.2.9", - "resolved": "https://registry.npmjs.org/pako/-/pako-0.2.9.tgz", - "integrity": "sha1-8/dSL073gjSNqBYbrZ7P1Rv4OnU=" + "bundled": true }, "parse-asn1": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/parse-asn1/-/parse-asn1-5.1.0.tgz", - "integrity": "sha1-N8T5t+06tlx0gXtfJICTf7+XxxI=", + "version": "5.1.1", + "bundled": true, "requires": { "asn1.js": "^4.0.0", "browserify-aes": "^1.0.0", @@ -3475,18 +4396,11 @@ }, "path-browserify": { "version": "0.0.0", - "resolved": "https://registry.npmjs.org/path-browserify/-/path-browserify-0.0.0.tgz", - "integrity": "sha1-oLhwcpquIUAFt9UDLsLLuw+0RRo=" - }, - "path-is-absolute": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", - "integrity": "sha1-F0uSaHNVNP+8es5r9TpanhtcX18=" + "bundled": true }, "pbkdf2": { - "version": "3.0.14", - "resolved": "https://registry.npmjs.org/pbkdf2/-/pbkdf2-3.0.14.tgz", - "integrity": "sha512-gjsZW9O34fm0R7PaLHRJmLLVfSoesxztjPjE9o6R+qtVJij90ltg1joIovN9GKrRW3t1PzhDDG3UMEMFfZ+1wA==", + "version": "3.0.16", + "bundled": true, "requires": { "create-hash": "^1.1.2", "create-hmac": "^1.1.4", @@ -3497,18 +4411,15 @@ }, "process": { "version": "0.11.10", - "resolved": "https://registry.npmjs.org/process/-/process-0.11.10.tgz", - "integrity": "sha1-czIwDoQBYb2j5podHZGn1LwW8YI=" + "bundled": true }, "process-nextick-args": { "version": "2.0.0", - "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.0.tgz", - "integrity": "sha512-MtEC1TqN0EU5nephaJ4rAtThHtC86dNN9qCuEhtshvpVBkAW5ZO7BASN9REnF9eoXGcRub+pFuKEpOHE+HbEMw==" + "bundled": true }, "public-encrypt": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/public-encrypt/-/public-encrypt-4.0.0.tgz", - "integrity": "sha1-OfaZ86RlYN1eusvKaTyvfGXBjMY=", + "version": "4.0.2", + "bundled": true, "requires": { "bn.js": "^4.1.0", "browserify-rsa": "^4.0.0", @@ -3519,81 +4430,65 @@ }, "punycode": { "version": "1.4.1", - "resolved": "https://registry.npmjs.org/punycode/-/punycode-1.4.1.tgz", - "integrity": "sha1-wNWmOycYgArY4esPpSachN1BhF4=" + "bundled": true }, "querystring": { "version": "0.2.0", - "resolved": "https://registry.npmjs.org/querystring/-/querystring-0.2.0.tgz", - "integrity": "sha1-sgmEkgO7Jd+CDadW50cAWHhSFiA=" + "bundled": true }, "querystring-es3": { "version": "0.2.1", - "resolved": "https://registry.npmjs.org/querystring-es3/-/querystring-es3-0.2.1.tgz", - "integrity": "sha1-nsYfeQSYdXB9aUFFlv2Qek1xHnM=" + "bundled": true }, "randombytes": { "version": "2.0.6", - "resolved": "https://registry.npmjs.org/randombytes/-/randombytes-2.0.6.tgz", - "integrity": "sha512-CIQ5OFxf4Jou6uOKe9t1AOgqpeU5fd70A8NPdHSGeYXqXsPe6peOwI0cUl88RWZ6sP1vPMV3avd/R6cZ5/sP1A==", + "bundled": true, "requires": { "safe-buffer": "^5.1.0" } }, "randomfill": { "version": "1.0.4", - "resolved": "https://registry.npmjs.org/randomfill/-/randomfill-1.0.4.tgz", - "integrity": "sha512-87lcbR8+MhcWcUiQ+9e+Rwx8MyR2P7qnt15ynUlbm3TU/fjbgz4GsvfSUDTemtCCtVCqb4ZcEFlyPNTh9bBTLw==", + "bundled": true, "requires": { "randombytes": "^2.0.5", "safe-buffer": "^5.1.0" } }, "readable-stream": { - "version": "git+https://github.com/meteor/readable-stream.git#c688cdd193549919b840e8d72a86682d91961e12", - "from": "readable-stream@git+https://github.com/meteor/readable-stream.git#c688cdd193549919b840e8d72a86682d91961e12", + "version": "2.3.6", + "bundled": true, "requires": { + "core-util-is": "~1.0.0", "inherits": "~2.0.3", "isarray": "~1.0.0", "process-nextick-args": "~2.0.0", "safe-buffer": "~5.1.1", - "string_decoder": "~1.1.0", + "string_decoder": "~1.1.1", "util-deprecate": "~1.0.1" }, "dependencies": { "inherits": { "version": "2.0.3", - "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz", - "integrity": "sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4=" + "bundled": true } } }, - "rimraf": { - "version": "2.6.2", - "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.6.2.tgz", - "integrity": "sha512-lreewLK/BlghmxtfH36YYVg1i8IAce4TI7oao75I1g245+6BctqTVQiBP3YUJ9C6DQOXJmkYR9X9fCLtCOJc5w==", - "requires": { - "glob": "^7.0.5" - } - }, "ripemd160": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/ripemd160/-/ripemd160-2.0.1.tgz", - "integrity": "sha1-D0WEKVxTo2KK9+bXmsohzlfRxuc=", + "version": "2.0.2", + "bundled": true, "requires": { - "hash-base": "^2.0.0", + "hash-base": "^3.0.0", "inherits": "^2.0.1" } }, "safe-buffer": { - "version": "5.1.1", - "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.1.tgz", - "integrity": "sha512-kKvNJn6Mm93gAczWVJg7wH+wGYWNrDHdWvpUmHyEsgCtIwwo3bqPtV4tR5tuPaUhTOo/kvhVwd8XwwOllGYkbg==" + "version": "5.1.2", + "bundled": true }, "sha.js": { - "version": "2.4.10", - "resolved": "https://registry.npmjs.org/sha.js/-/sha.js-2.4.10.tgz", - "integrity": "sha512-vnwmrFDlOExK4Nm16J2KMWHLrp14lBrjxMxBJpu++EnsuBmpiYaM/MEs46Vxxm/4FvdP5yTwuCTO9it5FSjrqA==", + "version": "2.4.11", + "bundled": true, "requires": { "inherits": "^2.0.1", "safe-buffer": "^5.0.1" @@ -3601,117 +4496,48 @@ }, "stream-browserify": { "version": "2.0.1", - "resolved": "https://registry.npmjs.org/stream-browserify/-/stream-browserify-2.0.1.tgz", - "integrity": "sha1-ZiZu5fm9uZQKTkUUyvtDu3Hlyds=", + "bundled": true, "requires": { "inherits": "~2.0.1", "readable-stream": "^2.0.2" - }, - "dependencies": { - "readable-stream": { - "version": "2.3.6", - "resolved": "http://registry.npmjs.org/readable-stream/-/readable-stream-2.3.6.tgz", - "integrity": "sha512-tQtKA9WIAhBF3+VLAseyMqZeBjW0AHJoxOtYqSUZNJxauErmLbVm2FW1y+J/YA9dUrAC39ITejlZWhVIwawkKw==", - "requires": { - "core-util-is": "~1.0.0", - "inherits": "~2.0.3", - "isarray": "~1.0.0", - "process-nextick-args": "~2.0.0", - "safe-buffer": "~5.1.1", - "string_decoder": "~1.1.1", - "util-deprecate": "~1.0.1" - }, - "dependencies": { - "inherits": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz", - "integrity": "sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4=" - } - } - }, - "string_decoder": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", - "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", - "requires": { - "safe-buffer": "~5.1.0" - } - } } }, "stream-http": { - "version": "2.8.0", - "resolved": "https://registry.npmjs.org/stream-http/-/stream-http-2.8.0.tgz", - "integrity": "sha512-sZOFxI/5xw058XIRHl4dU3dZ+TTOIGJR78Dvo0oEAejIt4ou27k+3ne1zYmCV+v7UucbxIFQuOgnkTVHh8YPnw==", + "version": "2.8.1", + "bundled": true, "requires": { "builtin-status-codes": "^3.0.0", "inherits": "^2.0.1", "readable-stream": "^2.3.3", "to-arraybuffer": "^1.0.0", "xtend": "^4.0.0" - }, - "dependencies": { - "readable-stream": { - "version": "2.3.6", - "resolved": "http://registry.npmjs.org/readable-stream/-/readable-stream-2.3.6.tgz", - "integrity": "sha512-tQtKA9WIAhBF3+VLAseyMqZeBjW0AHJoxOtYqSUZNJxauErmLbVm2FW1y+J/YA9dUrAC39ITejlZWhVIwawkKw==", - "requires": { - "core-util-is": "~1.0.0", - "inherits": "~2.0.3", - "isarray": "~1.0.0", - "process-nextick-args": "~2.0.0", - "safe-buffer": "~5.1.1", - "string_decoder": "~1.1.1", - "util-deprecate": "~1.0.1" - }, - "dependencies": { - "inherits": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz", - "integrity": "sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4=" - } - } - }, - "string_decoder": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", - "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", - "requires": { - "safe-buffer": "~5.1.0" - } - } } }, "string_decoder": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.0.tgz", - "integrity": "sha512-8zQpRF6juocE69ae7CSPmYEGJe4VCXwP6S6dxUWI7i53Gwv54/ec41fiUA+X7BPGGv7fRSQJjBQVa0gomGaOgg==", + "version": "1.1.1", + "bundled": true, "requires": { "safe-buffer": "~5.1.0" } }, "timers-browserify": { "version": "1.4.2", - "resolved": "https://registry.npmjs.org/timers-browserify/-/timers-browserify-1.4.2.tgz", - "integrity": "sha1-ycWLV1voQHN1y14kYtrO50NZ9B0=", + "bundled": true, "requires": { "process": "~0.11.0" } }, "to-arraybuffer": { "version": "1.0.1", - "resolved": "https://registry.npmjs.org/to-arraybuffer/-/to-arraybuffer-1.0.1.tgz", - "integrity": "sha1-fSKbH8xjfkZsoIEYCDanqr/4P0M=" + "bundled": true }, "tty-browserify": { "version": "0.0.0", - "resolved": "https://registry.npmjs.org/tty-browserify/-/tty-browserify-0.0.0.tgz", - "integrity": "sha1-oVe6QC2iTpv5V/mqadUk7tQpAaY=" + "bundled": true }, "url": { "version": "0.11.0", - "resolved": "https://registry.npmjs.org/url/-/url-0.11.0.tgz", - "integrity": "sha1-ODjpfPxgUh63PFJajlW/3Z4uKPE=", + "bundled": true, "requires": { "punycode": "1.3.2", "querystring": "0.2.0" @@ -3719,41 +4545,31 @@ "dependencies": { "punycode": { "version": "1.3.2", - "resolved": "https://registry.npmjs.org/punycode/-/punycode-1.3.2.tgz", - "integrity": "sha1-llOgNvt8HuQjQvIyXM7v6jkmxI0=" + "bundled": true } } }, "util": { "version": "0.10.3", - "resolved": "https://registry.npmjs.org/util/-/util-0.10.3.tgz", - "integrity": "sha1-evsa/lCAUkZInj23/g7TeTNqwPk=", + "bundled": true, "requires": { "inherits": "2.0.1" } }, "util-deprecate": { "version": "1.0.2", - "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", - "integrity": "sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8=" + "bundled": true }, "vm-browserify": { "version": "0.0.4", - "resolved": "https://registry.npmjs.org/vm-browserify/-/vm-browserify-0.0.4.tgz", - "integrity": "sha1-XX6kW7755Kb/ZflUOOCofDV9WnM=", + "bundled": true, "requires": { "indexof": "0.0.1" } }, - "wrappy": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", - "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=" - }, "xtend": { "version": "4.0.1", - "resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.1.tgz", - "integrity": "sha1-pcbVMr5lbiPbgg77lDofBJmNY68=" + "bundled": true } } }, @@ -3764,29 +4580,24 @@ "dev": true }, "mime-db": { - "version": "1.36.0", - "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.36.0.tgz", - "integrity": "sha512-L+xvyD9MkoYMXb1jAmzI/lWYAxAMCPvIBSWur0PZ5nOf5euahRLVqH//FKW9mWp2lkqUgYiXPgkzfMUFi4zVDw==" + "version": "1.37.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.37.0.tgz", + "integrity": "sha512-R3C4db6bgQhlIhPU48fUtdVmKnflq+hRdad7IyKhtFj06VPNVdk2RhiYL3UjQIlso8L+YxAtFkobT0VK+S/ybg==" }, "mime-types": { - "version": "2.1.20", - "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.20.tgz", - "integrity": "sha512-HrkrPaP9vGuWbLK1B1FfgAkbqNjIuy4eHlIYnFi7kamZyLLrGlo2mpcx0bBmNpKqBtYtAfGbodDddIgddSJC2A==", + "version": "2.1.21", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.21.tgz", + "integrity": "sha512-3iL6DbwpyLzjR3xHSFNFeb9Nz/M8WDkX33t1GFQnFOllWk8pOrh/LSrB5OXlnlW5P9LH73X6loW/eogc+F5lJg==", "requires": { - "mime-db": "~1.36.0" + "mime-db": "~1.37.0" } }, "mimic-fn": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-1.1.0.tgz", - "integrity": "sha1-5md4PZLonb00KBi1IwudYqZyrRg=", + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-1.2.0.tgz", + "integrity": "sha512-jf84uxzwiuiIVKiOLpfYk7N46TSy8ubTonmneY9vrpHNAnp0QBt2BxWV9dO3/j+BoVAb+a5G6YDPW3M5HOdMWQ==", "dev": true }, - "mimic-response": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/mimic-response/-/mimic-response-1.0.0.tgz", - "integrity": "sha1-3z02Uqc/3ta5sLJBRub9BSNTRY4=" - }, "min-document": { "version": "2.19.0", "resolved": "https://registry.npmjs.org/min-document/-/min-document-2.19.0.tgz", @@ -3809,9 +4620,30 @@ "resolved": "http://registry.npmjs.org/minimist/-/minimist-1.2.0.tgz", "integrity": "sha1-o1AIsg9BOD7sH7kU9M1d95omQoQ=" }, + "mixin-deep": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/mixin-deep/-/mixin-deep-1.3.1.tgz", + "integrity": "sha512-8ZItLHeEgaqEvd5lYBXfm4EZSFCX29Jb9K+lAHhDKzReKBQKj3R+7NOF6tjqYi9t4oI8VUfaWITJQm86wnXGNQ==", + "dev": true, + "requires": { + "for-in": "^1.0.2", + "is-extendable": "^1.0.1" + }, + "dependencies": { + "is-extendable": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/is-extendable/-/is-extendable-1.0.1.tgz", + "integrity": "sha512-arnXMxT1hhoKo9k1LZdmlNyJdDDfy2v0fXjFlmok4+i8ul/6WlbVge9bhM74OpNPQPMGUToDtz+KXa1PneJxOA==", + "dev": true, + "requires": { + "is-plain-object": "^2.0.4" + } + } + } + }, "mkdirp": { "version": "0.5.1", - "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.1.tgz", + "resolved": "http://registry.npmjs.org/mkdirp/-/mkdirp-0.5.1.tgz", "integrity": "sha1-MAV0OOrGz3+MR2fzhkjWaX11yQM=", "requires": { "minimist": "0.0.8" @@ -3836,9 +4668,48 @@ "dev": true }, "nan": { - "version": "2.11.0", - "resolved": "https://registry.npmjs.org/nan/-/nan-2.11.0.tgz", - "integrity": "sha512-F4miItu2rGnV2ySkXOQoA8FKz/SR2Q2sWP0sbTxNxz/tuokeC8WxOhPMcwi0qIyGtVn/rrSeLbvVkznqCdwYnw==" + "version": "2.11.1", + "resolved": "https://registry.npmjs.org/nan/-/nan-2.11.1.tgz", + "integrity": "sha512-iji6k87OSXa0CcrLl9z+ZiYSuR2o+c0bGuNmXdrhTQTakxytAFsC56SArGYoiHlJlFoHSnvmhpceZJaXkVuOtA==" + }, + "nanomatch": { + "version": "1.2.13", + "resolved": "https://registry.npmjs.org/nanomatch/-/nanomatch-1.2.13.tgz", + "integrity": "sha512-fpoe2T0RbHwBTBUOftAfBPaDEi06ufaUai0mE6Yn1kacc3SnTErfb/h+X94VXzI64rKFHYImXSvdwGGCmwOqCA==", + "dev": true, + "requires": { + "arr-diff": "^4.0.0", + "array-unique": "^0.3.2", + "define-property": "^2.0.2", + "extend-shallow": "^3.0.2", + "fragment-cache": "^0.2.1", + "is-windows": "^1.0.2", + "kind-of": "^6.0.2", + "object.pick": "^1.3.0", + "regex-not": "^1.0.0", + "snapdragon": "^0.8.1", + "to-regex": "^3.0.1" + }, + "dependencies": { + "arr-diff": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/arr-diff/-/arr-diff-4.0.0.tgz", + "integrity": "sha1-1kYQdP6/7HHn4VI1dhoyml3HxSA=", + "dev": true + }, + "array-unique": { + "version": "0.3.2", + "resolved": "https://registry.npmjs.org/array-unique/-/array-unique-0.3.2.tgz", + "integrity": "sha1-qJS3XUvE9s1nnvMkSp/Y9Gri1Cg=", + "dev": true + }, + "kind-of": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.2.tgz", + "integrity": "sha512-s5kLOcnH0XqDO+FvuaLX8DDjZ18CGFk7VygH40QoKPUQhW4e2rvM0rwUq0t8IQDOwYSeLK01U90OjzBTme2QqA==", + "dev": true + } + } }, "natural-compare": { "version": "1.4.0", @@ -3848,9 +4719,15 @@ }, "next-tick": { "version": "1.0.0", - "resolved": "https://registry.npmjs.org/next-tick/-/next-tick-1.0.0.tgz", + "resolved": "http://registry.npmjs.org/next-tick/-/next-tick-1.0.0.tgz", "integrity": "sha1-yobR/ogoFpsBICCOPchCS524NCw=" }, + "nice-try": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/nice-try/-/nice-try-1.0.5.tgz", + "integrity": "sha512-1nh45deeb5olNY7eX82BkPO7SSxR5SSYJiPTrTdFUVYwAl8CKMA5N9PjTYkHiRjisVcxcQ1HXdLhx2qxxJzLNQ==", + "dev": true + }, "node-fetch": { "version": "1.7.3", "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-1.7.3.tgz", @@ -3881,11 +4758,19 @@ "dependencies": { "semver": { "version": "5.3.0", - "resolved": "https://registry.npmjs.org/semver/-/semver-5.3.0.tgz", + "resolved": "http://registry.npmjs.org/semver/-/semver-5.3.0.tgz", "integrity": "sha1-myzl094C0XxgEq0yaqa00M9U+U8=" } } }, + "node-releases": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-1.1.1.tgz", + "integrity": "sha512-2UXrBr6gvaebo5TNF84C66qyJJ6r0kxBObgZIDX3D3/mt1ADKiHux3NJPWisq0wxvJJdkjECH+9IIKYViKj71Q==", + "requires": { + "semver": "^5.3.0" + } + }, "node-resemble-js": { "version": "0.0.5", "resolved": "https://registry.npmjs.org/node-resemble-js/-/node-resemble-js-0.0.5.tgz", @@ -3896,9 +4781,9 @@ } }, "node-sass": { - "version": "4.9.3", - "resolved": "https://registry.npmjs.org/node-sass/-/node-sass-4.9.3.tgz", - "integrity": "sha512-XzXyGjO+84wxyH7fV6IwBOTrEBe2f0a6SBze9QWWYR/cL74AcQUks2AsqcCZenl/Fp/JVbuEaLpgrLtocwBUww==", + "version": "4.11.0", + "resolved": "https://registry.npmjs.org/node-sass/-/node-sass-4.11.0.tgz", + "integrity": "sha512-bHUdHTphgQJZaF1LASx0kAviPH7sGlcyNhWade4eVIpFp6tsn7SV8xNMTbsQFpEV9VXpnwTTnNYlfsZXgGgmkA==", "requires": { "async-foreach": "^0.1.3", "chalk": "^1.1.1", @@ -3915,16 +4800,40 @@ "nan": "^2.10.0", "node-gyp": "^3.8.0", "npmlog": "^4.0.0", - "request": "2.87.0", + "request": "^2.88.0", "sass-graph": "^2.2.4", "stdout-stream": "^1.4.0", "true-case-path": "^1.0.2" + }, + "dependencies": { + "ansi-styles": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-2.2.1.tgz", + "integrity": "sha1-tDLdM1i2NM914eRmQ2gkBTPB3b4=" + }, + "chalk": { + "version": "1.1.3", + "resolved": "http://registry.npmjs.org/chalk/-/chalk-1.1.3.tgz", + "integrity": "sha1-qBFcVeSnAv5NFQq9OHKCKn4J/Jg=", + "requires": { + "ansi-styles": "^2.2.1", + "escape-string-regexp": "^1.0.2", + "has-ansi": "^2.0.0", + "strip-ansi": "^3.0.0", + "supports-color": "^2.0.0" + } + }, + "supports-color": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-2.0.0.tgz", + "integrity": "sha1-U10EXOa2Nj+kARcIRimZXp3zJMc=" + } } }, "nodeclient-spectre": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/nodeclient-spectre/-/nodeclient-spectre-1.0.3.tgz", - "integrity": "sha512-Y1oMvLmR+0P/v2pXG99RKwnC3nb4fl0vmMtbPrZkRB+FE1ADZhfZpHVbBwIcQdsnJTYOpVa7y+hIFtNSv7PYNw==", + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/nodeclient-spectre/-/nodeclient-spectre-1.0.5.tgz", + "integrity": "sha512-bTVRrNgR8iIJrlj1c51JdRrp7eO5KIQzXjl0IuQPUI9THIOcsAwtO16kdr8r+Sp/ycXaFocyR3WfFNj7H8AocA==", "dev": true, "requires": { "request": "^2.83.0", @@ -3951,10 +4860,13 @@ } }, "normalize-path": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-1.0.0.tgz", - "integrity": "sha1-MtDkcvkf80VwHBWoMRAY07CpA3k=", - "dev": true + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-2.1.1.tgz", + "integrity": "sha1-GrKLVW4Zg2Oowab35vogE3/mrtk=", + "dev": true, + "requires": { + "remove-trailing-separator": "^1.0.1" + } }, "normalize-range": { "version": "0.1.2", @@ -3963,7 +4875,7 @@ }, "npm-install-package": { "version": "2.1.0", - "resolved": "https://registry.npmjs.org/npm-install-package/-/npm-install-package-2.1.0.tgz", + "resolved": "http://registry.npmjs.org/npm-install-package/-/npm-install-package-2.1.0.tgz", "integrity": "sha1-1+/jz816sAYUuJbqUxGdyaslkSU=", "dev": true }, @@ -4018,21 +4930,60 @@ "integrity": "sha1-CXtgK1NCKlIsGvuHkDGDNpQaAR0=" }, "oauth-sign": { - "version": "0.8.2", - "resolved": "https://registry.npmjs.org/oauth-sign/-/oauth-sign-0.8.2.tgz", - "integrity": "sha1-Rqarfwrq2N6unsBWV4C31O/rnUM=" + "version": "0.9.0", + "resolved": "https://registry.npmjs.org/oauth-sign/-/oauth-sign-0.9.0.tgz", + "integrity": "sha512-fexhUFFPTGV8ybAtSIGbV6gOkSv8UtRbDBnAyLQw4QPKkgNlsH2ByPGtMUqdWkos6YCRmAqViwgZrJc/mRDzZQ==" }, "object-assign": { "version": "4.1.1", "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", "integrity": "sha1-IQmtx5ZYh8/AXLvUQsrIv7s2CGM=" }, + "object-copy": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/object-copy/-/object-copy-0.1.0.tgz", + "integrity": "sha1-fn2Fi3gb18mRpBupde04EnVOmYw=", + "dev": true, + "requires": { + "copy-descriptor": "^0.1.0", + "define-property": "^0.2.5", + "kind-of": "^3.0.3" + }, + "dependencies": { + "define-property": { + "version": "0.2.5", + "resolved": "https://registry.npmjs.org/define-property/-/define-property-0.2.5.tgz", + "integrity": "sha1-w1se+RjsPJkPmlvFe+BKrOxcgRY=", + "dev": true, + "requires": { + "is-descriptor": "^0.1.0" + } + } + } + }, "object-keys": { - "version": "1.0.11", - "resolved": "https://registry.npmjs.org/object-keys/-/object-keys-1.0.11.tgz", - "integrity": "sha1-xUYBd4rVYPEULODgG8yotW0TQm0=", + "version": "1.0.12", + "resolved": "https://registry.npmjs.org/object-keys/-/object-keys-1.0.12.tgz", + "integrity": "sha512-FTMyFUm2wBcGHnH2eXmz7tC6IwlqQZ6mVZ+6dm6vZ4IQIHjs6FdNsQBuKGPuUUUY6NfJw2PshC08Tn6LzLDOag==", "dev": true }, + "object-visit": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/object-visit/-/object-visit-1.0.1.tgz", + "integrity": "sha1-95xEk68MU3e1n+OdOV5BBC3QRbs=", + "dev": true, + "requires": { + "isobject": "^3.0.0" + }, + "dependencies": { + "isobject": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/isobject/-/isobject-3.0.1.tgz", + "integrity": "sha1-TkMekrEalzFjaqH5yNHMvP2reN8=", + "dev": true + } + } + }, "object.assign": { "version": "4.1.0", "resolved": "https://registry.npmjs.org/object.assign/-/object.assign-4.1.0.tgz", @@ -4045,6 +4996,35 @@ "object-keys": "^1.0.11" } }, + "object.entries": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/object.entries/-/object.entries-1.0.4.tgz", + "integrity": "sha1-G/mk3SKI9bM/Opk9JXZh8F0WGl8=", + "dev": true, + "requires": { + "define-properties": "^1.1.2", + "es-abstract": "^1.6.1", + "function-bind": "^1.1.0", + "has": "^1.0.1" + } + }, + "object.pick": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/object.pick/-/object.pick-1.3.0.tgz", + "integrity": "sha1-h6EKxMFpS9Lhy/U1kaZhQftd10c=", + "dev": true, + "requires": { + "isobject": "^3.0.1" + }, + "dependencies": { + "isobject": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/isobject/-/isobject-3.0.1.tgz", + "integrity": "sha1-TkMekrEalzFjaqH5yNHMvP2reN8=", + "dev": true + } + } + }, "once": { "version": "1.4.0", "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", @@ -4053,6 +5033,11 @@ "wrappy": "1" } }, + "one-time": { + "version": "0.0.4", + "resolved": "https://registry.npmjs.org/one-time/-/one-time-0.0.4.tgz", + "integrity": "sha1-+M33eISCb+Tf+T46nMN7HkSAdC4=" + }, "onetime": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/onetime/-/onetime-2.0.1.tgz", @@ -4100,53 +5085,14 @@ "wordwrap": "~1.0.0" } }, - "ora": { - "version": "0.2.3", - "resolved": "https://registry.npmjs.org/ora/-/ora-0.2.3.tgz", - "integrity": "sha1-N1J9Igrc1Tw5tzVx11QVbV22V6Q=", - "dev": true, - "requires": { - "chalk": "^1.1.1", - "cli-cursor": "^1.0.2", - "cli-spinners": "^0.1.2", - "object-assign": "^4.0.1" - }, - "dependencies": { - "cli-cursor": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/cli-cursor/-/cli-cursor-1.0.2.tgz", - "integrity": "sha1-ZNo/fValRBLll5S9Ytw1KV6PKYc=", - "dev": true, - "requires": { - "restore-cursor": "^1.0.1" - } - }, - "onetime": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/onetime/-/onetime-1.1.0.tgz", - "integrity": "sha1-ofeDj4MUxRbwXs78vEzP4EtO14k=", - "dev": true - }, - "restore-cursor": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/restore-cursor/-/restore-cursor-1.0.1.tgz", - "integrity": "sha1-NGYfRohjJ/7SmRR5FSJS35LapUE=", - "dev": true, - "requires": { - "exit-hook": "^1.0.0", - "onetime": "^1.0.0" - } - } - } - }, "os-homedir": { "version": "1.0.2", - "resolved": "https://registry.npmjs.org/os-homedir/-/os-homedir-1.0.2.tgz", + "resolved": "http://registry.npmjs.org/os-homedir/-/os-homedir-1.0.2.tgz", "integrity": "sha1-/7xJiDNuDoM94MFox+8VISGqf7M=" }, "os-locale": { "version": "1.4.0", - "resolved": "https://registry.npmjs.org/os-locale/-/os-locale-1.4.0.tgz", + "resolved": "http://registry.npmjs.org/os-locale/-/os-locale-1.4.0.tgz", "integrity": "sha1-IPnxeuKe00XoveWDsT0gCYA8FNk=", "requires": { "lcid": "^1.0.0" @@ -4154,7 +5100,7 @@ }, "os-tmpdir": { "version": "1.0.2", - "resolved": "https://registry.npmjs.org/os-tmpdir/-/os-tmpdir-1.0.2.tgz", + "resolved": "http://registry.npmjs.org/os-tmpdir/-/os-tmpdir-1.0.2.tgz", "integrity": "sha1-u+Z0BseaqFxc/sdm/lc0VV36EnQ=" }, "osenv": { @@ -4166,20 +5112,16 @@ "os-tmpdir": "^1.0.0" } }, - "p-cancelable": { - "version": "0.3.0", - "resolved": "https://registry.npmjs.org/p-cancelable/-/p-cancelable-0.3.0.tgz", - "integrity": "sha512-RVbZPLso8+jFeq1MfNvgXtCRED2raz/dKpacfTNxsx6pLEpEomM7gah6VeHSYV3+vo0OAi4MkArtQcWWXuQoyw==" - }, "p-finally": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/p-finally/-/p-finally-1.0.0.tgz", - "integrity": "sha1-P7z7FbiZpEEjs0ttzBi3JDNqLK4=" + "integrity": "sha1-P7z7FbiZpEEjs0ttzBi3JDNqLK4=", + "dev": true }, "p-limit": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-1.2.0.tgz", - "integrity": "sha512-Y/OtIaXtUPr4/YpMv1pCL5L5ed0rumAaAeBSj12F+bSlMdys7i8oQF/GUJmfpTS/QoaRrS/k6pma29haJpsMng==", + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-1.3.0.tgz", + "integrity": "sha512-vvcXsLAJ9Dr5rQOPk7toZQZJApBl2K4J6dANSsEuh6QI41JYcsS/qhTGa9ErIUUgK3WNQoJYvylxvjqmiqEA9Q==", "dev": true, "requires": { "p-try": "^1.0.0" @@ -4200,14 +5142,6 @@ "integrity": "sha512-r6zKACMNhjPJMTl8KcFH4li//gkrXWfbD6feV8l6doRHlzljFWGJ2AP6iKaCJXyZmAUMOPtvbW7EXkbWO/pLEA==", "dev": true }, - "p-timeout": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/p-timeout/-/p-timeout-1.2.1.tgz", - "integrity": "sha1-XrOzU7f86Z8QGhA4iAuwVOu+o4Y=", - "requires": { - "p-finally": "^1.0.0" - } - }, "p-try": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/p-try/-/p-try-1.0.0.tgz", @@ -4227,9 +5161,9 @@ "dev": true }, "parse-bmfont-xml": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/parse-bmfont-xml/-/parse-bmfont-xml-1.1.3.tgz", - "integrity": "sha1-1rZqNxr9OcUAfZ8O6yYqTyzOe3w=", + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/parse-bmfont-xml/-/parse-bmfont-xml-1.1.4.tgz", + "integrity": "sha512-bjnliEOmGv3y1aMEfREMBJ9tfL3WR0i0CKPj61DnSLaoxWR3nLrsQrEbCId/8rF4NyRF0cCqisSVXyQYWM+mCQ==", "dev": true, "requires": { "xml-parse-from-string": "^1.0.0", @@ -4254,6 +5188,12 @@ "error-ex": "^1.2.0" } }, + "pascalcase": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/pascalcase/-/pascalcase-0.1.1.tgz", + "integrity": "sha1-s2PlXoAGym/iF4TS2yK9FdeRfxQ=", + "dev": true + }, "path-exists": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-2.1.0.tgz", @@ -4264,7 +5204,7 @@ }, "path-is-absolute": { "version": "1.0.1", - "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", + "resolved": "http://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", "integrity": "sha1-F0uSaHNVNP+8es5r9TpanhtcX18=" }, "path-is-inside": { @@ -4280,9 +5220,9 @@ "dev": true }, "path-parse": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.5.tgz", - "integrity": "sha1-PBrfhx6pzWyUMbbqK9dKD/BVxME=", + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.6.tgz", + "integrity": "sha512-GSmOT2EbHrINBf9SR7CDELwlJ8AENk3Qn7OikK4nFYAu3Ote2+JYNVvkpAEQm3/TLNEJFD/xZJjzyxg3KBWOzw==", "dev": true }, "path-type": { @@ -4306,9 +5246,15 @@ "resolved": "https://registry.npmjs.org/performance-now/-/performance-now-2.1.0.tgz", "integrity": "sha1-Ywn04OX6kT7BxpMHrjZLSzd8nns=" }, + "phin": { + "version": "2.9.3", + "resolved": "https://registry.npmjs.org/phin/-/phin-2.9.3.tgz", + "integrity": "sha512-CzFr90qM24ju5f88quFC/6qohjC144rehe5n6DH900lgXmUe86+xCKc10ev56gRKC4/BkHUoG4uSiQgBiIXwDA==", + "dev": true + }, "pify": { "version": "2.3.0", - "resolved": "https://registry.npmjs.org/pify/-/pify-2.3.0.tgz", + "resolved": "http://registry.npmjs.org/pify/-/pify-2.3.0.tgz", "integrity": "sha1-7RQaasBDqEnqWISY59yosVMw6Qw=" }, "pinkie": { @@ -4334,9 +5280,9 @@ }, "dependencies": { "pngjs": { - "version": "3.3.1", - "resolved": "https://registry.npmjs.org/pngjs/-/pngjs-3.3.1.tgz", - "integrity": "sha512-ggXCTsqHRIsGMkHlCEhbHhUmNTA2r1lpkE0NL4Q9S8spkXbm4vE9TVmPso2AGYn90Gltdz8W5CyzhcIGg2Gejg==", + "version": "3.3.3", + "resolved": "https://registry.npmjs.org/pngjs/-/pngjs-3.3.3.tgz", + "integrity": "sha512-1n3Z4p3IOxArEs1VRXnZ/RXdfEniAUS9jb68g58FIXMNkPJeZd+Qh4Uq7/e0LVxAQGos1eIUrqrt4FpjdnEd+Q==", "dev": true } } @@ -4356,6 +5302,15 @@ "integrity": "sha512-TuvHS8AOIZNAlE77WUDiR4rySV/VMptyMfcfeoMgs4P8apaZM3JrnbzBiixKUv+XR6i+BXrQh8WAnjaSPFO65Q==", "dev": true }, + "please-upgrade-node": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/please-upgrade-node/-/please-upgrade-node-3.1.1.tgz", + "integrity": "sha512-KY1uHnQ2NlQHqIJQpnh/i54rKkuxCEBx+voJIS/Mvb+L2iYd2NMotwduhKTMjfC1uKoX3VXOxLjIYG66dfJTVQ==", + "dev": true, + "requires": { + "semver-compare": "^1.0.0" + } + }, "pluralize": { "version": "7.0.0", "resolved": "https://registry.npmjs.org/pluralize/-/pluralize-7.0.0.tgz", @@ -4369,14 +5324,21 @@ "dev": true }, "popper.js": { - "version": "1.12.9", - "resolved": "https://registry.npmjs.org/popper.js/-/popper.js-1.12.9.tgz", - "integrity": "sha1-DfvC3/lsRRuzMu3Pz6r1ZtMx1bM=" + "version": "1.14.6", + "resolved": "https://registry.npmjs.org/popper.js/-/popper.js-1.14.6.tgz", + "integrity": "sha512-AGwHGQBKumlk/MDfrSOf0JHhJCImdDMcGNoqKmKkU+68GFazv3CQ6q9r7Ja1sKDZmYWTckY/uLyEznheTDycnA==" + }, + "posix-character-classes": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/posix-character-classes/-/posix-character-classes-0.1.1.tgz", + "integrity": "sha1-AerA/jta9xoqbAL+q7jB/vfgDqs=", + "dev": true }, "postcss": { "version": "6.0.16", "resolved": "https://registry.npmjs.org/postcss/-/postcss-6.0.16.tgz", "integrity": "sha512-m758RWPmSjFH/2MyyG3UOW1fgYbR9rtdzz5UNJnlm7OLtu4B2h9C6gi+bE4qFKghsBRFfZT8NzoQBs6JhLotoA==", + "dev": true, "requires": { "chalk": "^2.3.0", "source-map": "^0.6.1", @@ -4387,6 +5349,7 @@ "version": "3.2.0", "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.0.tgz", "integrity": "sha512-NnSOmMEYtVR2JVMIGTzynRkkaxtiq1xnFBcdQD/DnNCYPoEPsVJhM98BDyaoNOQIi7p4okdi3E27eN7GQbsUug==", + "dev": true, "requires": { "color-convert": "^1.9.0" } @@ -4395,6 +5358,7 @@ "version": "2.3.0", "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.3.0.tgz", "integrity": "sha512-Az5zJR2CBujap2rqXGaJKaPHyJ0IrUimvYNX+ncCy8PJP4ltOGTrHUIo097ZaL2zMeKYpiCdqDvS6zdrTFok3Q==", + "dev": true, "requires": { "ansi-styles": "^3.1.0", "escape-string-regexp": "^1.0.5", @@ -4405,6 +5369,7 @@ "version": "4.5.0", "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-4.5.0.tgz", "integrity": "sha1-vnoN5ITexcXN34s9WRJQRJEvY1s=", + "dev": true, "requires": { "has-flag": "^2.0.0" } @@ -4414,12 +5379,14 @@ "source-map": { "version": "0.6.1", "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", - "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==" + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "dev": true }, "supports-color": { "version": "5.1.0", "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.1.0.tgz", "integrity": "sha512-Ry0AwkoKjDpVKK4sV4h6o3UJmNRbjYm2uXhwfj3J56lMVdvnUNqzQVRztOOMGQ++w1K/TjNDFvpJk0F/LoeBCQ==", + "dev": true, "requires": { "has-flag": "^2.0.0" } @@ -4427,12 +5394,25 @@ } }, "postcss-modules-extract-imports": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/postcss-modules-extract-imports/-/postcss-modules-extract-imports-1.1.0.tgz", - "integrity": "sha1-thTJcgvmgW6u41+zpfqh26agXds=", + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/postcss-modules-extract-imports/-/postcss-modules-extract-imports-2.0.0.tgz", + "integrity": "sha512-LaYLDNS4SG8Q5WAWqIJgdHPJrDDr/Lv775rMBFUbgjTz6j34lUznACHcdRWroPvXANP2Vj7yNK57vp9eFqzLWQ==", "dev": true, "requires": { - "postcss": "^6.0.1" + "postcss": "^7.0.5" + }, + "dependencies": { + "postcss": { + "version": "7.0.6", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-7.0.6.tgz", + "integrity": "sha512-Nq/rNjnHFcKgCDDZYO0lNsl6YWe6U7tTy+ESN+PnLxebL8uBtYX59HZqvrj7YLK5UCyll2hqDsJOo3ndzEW8Ug==", + "dev": true, + "requires": { + "chalk": "^2.4.1", + "source-map": "^0.6.1", + "supports-color": "^5.5.0" + } + } } }, "postcss-modules-local-by-default": { @@ -4466,28 +5446,40 @@ } }, "postcss-nested": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/postcss-nested/-/postcss-nested-2.1.2.tgz", - "integrity": "sha512-CU7KjbFOZSNrbFwrl8+KJHTj29GjCEhL86kCKyvf+k633fc+FQA6IuhGyPze5e+a4O5d2fP7hDlMOlVDXia1Xg==", + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/postcss-nested/-/postcss-nested-4.1.0.tgz", + "integrity": "sha512-owY13v4s3WWTUjsT1H1Cgpa4veHjcBJ/FqbgORe1dJIKpggbFoh6ww+zUP0nzrvy7fXGihcuFhJQj3eXtaWXsw==", "requires": { - "postcss": "^6.0.9", - "postcss-selector-parser": "^2.2.3" + "postcss": "^7.0.2", + "postcss-selector-parser": "^3.1.1" + }, + "dependencies": { + "postcss": { + "version": "7.0.6", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-7.0.6.tgz", + "integrity": "sha512-Nq/rNjnHFcKgCDDZYO0lNsl6YWe6U7tTy+ESN+PnLxebL8uBtYX59HZqvrj7YLK5UCyll2hqDsJOo3ndzEW8Ug==", + "requires": { + "chalk": "^2.4.1", + "source-map": "^0.6.1", + "supports-color": "^5.5.0" + } + } } }, "postcss-selector-parser": { - "version": "2.2.3", - "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-2.2.3.tgz", - "integrity": "sha1-+UN3iGBsPJrO4W/+jYsWKX8nu5A=", + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-3.1.1.tgz", + "integrity": "sha1-T4dfSvsMllc9XPTXQBGu4lCn6GU=", "requires": { - "flatten": "^1.0.2", + "dot-prop": "^4.1.1", "indexes-of": "^1.0.1", "uniq": "^1.0.1" } }, "postcss-value-parser": { - "version": "3.3.0", - "resolved": "https://registry.npmjs.org/postcss-value-parser/-/postcss-value-parser-3.3.0.tgz", - "integrity": "sha1-h/OPnxj3dKSrTIojL1xc6IcqnRU=" + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/postcss-value-parser/-/postcss-value-parser-3.3.1.tgz", + "integrity": "sha512-pISE66AbVkp4fDQ7VHBwRNXzAAKJjw4Vw7nWI/+Q3vuly7SNfgYXvm6i5IgFylHGK5sP/xHAbB7N49OS4gWNyQ==" }, "prelude-ls": { "version": "1.1.2", @@ -4495,15 +5487,10 @@ "integrity": "sha1-IZMqVJ9eUv/ZqCf1cOBL5iqX2lQ=", "dev": true }, - "prepend-http": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/prepend-http/-/prepend-http-1.0.4.tgz", - "integrity": "sha1-1PRWKwzjaW5BrFLQ4ALlemNdxtw=" - }, "pretty-format": { - "version": "21.2.1", - "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-21.2.1.tgz", - "integrity": "sha512-ZdWPGYAnYfcVP8yKA3zFjCn8s4/17TeYH28MXuC8vTp0o21eXjbFGcOAXZEaDaOFJjc3h2qa7HQNHNshhvoh2A==", + "version": "23.6.0", + "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-23.6.0.tgz", + "integrity": "sha512-zf9NV1NSlDLDjycnwm6hpFATCGl/K1lt0R/GdkAK2O5LN/rwJoB+Mh93gGJjut4YbmecbfgLWVGSTCr0Ewvvbw==", "dev": true, "requires": { "ansi-regex": "^3.0.0", @@ -4515,28 +5502,19 @@ "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-3.0.0.tgz", "integrity": "sha1-7QMXwyIGT3lGbAKWa922Bas32Zg=", "dev": true - }, - "ansi-styles": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.0.tgz", - "integrity": "sha512-NnSOmMEYtVR2JVMIGTzynRkkaxtiq1xnFBcdQD/DnNCYPoEPsVJhM98BDyaoNOQIi7p4okdi3E27eN7GQbsUug==", - "dev": true, - "requires": { - "color-convert": "^1.9.0" - } } } }, "probe-image-size": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/probe-image-size/-/probe-image-size-3.1.0.tgz", - "integrity": "sha1-50e+maDQqOUFiqcihUwkCSuS3WY=", + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/probe-image-size/-/probe-image-size-4.0.0.tgz", + "integrity": "sha512-nm7RvWUxps+2+jZKNLkd04mNapXNariS6G5WIEVzvAqjx7EUuKcY1Dp3e6oUK7GLwzJ+3gbSbPLFAASHFQrPcQ==", "requires": { "any-promise": "^1.3.0", - "deepmerge": "^1.3.0", - "got": "^7.0.0", + "deepmerge": "^2.0.1", "inherits": "^2.0.3", "next-tick": "^1.0.0", + "request": "^2.83.0", "stream-parser": "~0.3.1" } }, @@ -4552,9 +5530,9 @@ "integrity": "sha512-MtEC1TqN0EU5nephaJ4rAtThHtC86dNN9qCuEhtshvpVBkAW5ZO7BASN9REnF9eoXGcRub+pFuKEpOHE+HbEMw==" }, "progress": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/progress/-/progress-2.0.0.tgz", - "integrity": "sha1-ihvjZr+Pwj2yvSPxDG/pILQ4nR8=", + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/progress/-/progress-2.0.3.tgz", + "integrity": "sha512-7PiHtLll5LdnKIMw100I+8xJXR5gW2QwWYkT6iJva0bXitZKa/XMrSbdmg3r2Xnaidz9Qumd0VPaMrZlF9V9sA==", "dev": true }, "promise": { @@ -4579,10 +5557,25 @@ "resolved": "https://registry.npmjs.org/pseudomap/-/pseudomap-1.0.2.tgz", "integrity": "sha1-8FKijacOYYkX7wqKw0wa5aaChrM=" }, + "psl": { + "version": "1.1.31", + "resolved": "https://registry.npmjs.org/psl/-/psl-1.1.31.tgz", + "integrity": "sha512-/6pt4+C+T+wZUieKR620OpzN/LlnNKuWjy1iFLQ/UG35JqHlR/89MP1d96dUfkf6Dne3TuLQzOYEYshJ+Hx8mw==" + }, + "pump": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/pump/-/pump-3.0.0.tgz", + "integrity": "sha512-LwZy+p3SFs1Pytd/jYct4wpv49HiYCqd9Rlc5ZVdk0V+8Yzv6jR5Blk3TRmPL1ft69TxP0IMZGJ+WPFU2BFhww==", + "dev": true, + "requires": { + "end-of-stream": "^1.1.0", + "once": "^1.3.1" + } + }, "punycode": { - "version": "1.4.1", - "resolved": "https://registry.npmjs.org/punycode/-/punycode-1.4.1.tgz", - "integrity": "sha1-wNWmOycYgArY4esPpSachN1BhF4=" + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.1.1.tgz", + "integrity": "sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A==" }, "q": { "version": "1.5.1", @@ -4591,18 +5584,9 @@ "dev": true }, "qs": { - "version": "6.5.2", - "resolved": "https://registry.npmjs.org/qs/-/qs-6.5.2.tgz", - "integrity": "sha512-N5ZAX4/LxJmF+7wN74pUD6qAh9/wnvdQcjq9TZjevvXzSUo7bfmw91saqMjzGS2xq91/odN2dW/WOl7qQHNDGA==" - }, - "query-string": { - "version": "4.3.4", - "resolved": "https://registry.npmjs.org/query-string/-/query-string-4.3.4.tgz", - "integrity": "sha1-u7aTucqRXCMlFbIosaArYJBD2+s=", - "requires": { - "object-assign": "^4.1.0", - "strict-uri-encode": "^1.0.0" - } + "version": "6.5.2", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.5.2.tgz", + "integrity": "sha512-N5ZAX4/LxJmF+7wN74pUD6qAh9/wnvdQcjq9TZjevvXzSUo7bfmw91saqMjzGS2xq91/odN2dW/WOl7qQHNDGA==" }, "querystring": { "version": "0.2.0", @@ -4611,27 +5595,27 @@ "dev": true }, "re-resizable": { - "version": "4.8.1", - "resolved": "https://registry.npmjs.org/re-resizable/-/re-resizable-4.8.1.tgz", - "integrity": "sha512-73HInnxHyMh/BnJMAZUtErIWX6reY4TU9+AHC4U/VLd9s2h1SHT2VoONO8jj9pv0+e4SVhiDzV+rxOPT/BWn6Q==" + "version": "4.10.0", + "resolved": "https://registry.npmjs.org/re-resizable/-/re-resizable-4.10.0.tgz", + "integrity": "sha512-g5Q5IswKX7LM+MtYFnuzaQrTEGr/kpserqGV8V6HYkjwbV60XnJv00VlKugLHEwlQ5pgrV08spm0TjyyYVbWmQ==" }, "react": { - "version": "16.0.0", - "resolved": "https://registry.npmjs.org/react/-/react-16.0.0.tgz", - "integrity": "sha1-zn348ZQbA28Cssyp29DLHw6FXi0=", + "version": "16.6.3", + "resolved": "https://registry.npmjs.org/react/-/react-16.6.3.tgz", + "integrity": "sha512-zCvmH2vbEolgKxtqXL2wmGCUxUyNheYn/C+PD1YAjfxHC54+MhdruyhO7QieQrYsYeTxrn93PM2y0jRH1zEExw==", "requires": { - "fbjs": "^0.8.16", "loose-envify": "^1.1.0", "object-assign": "^4.1.1", - "prop-types": "^15.6.0" + "prop-types": "^15.6.2", + "scheduler": "^0.11.2" } }, "react-autosize-textarea": { - "version": "0.4.9", - "resolved": "https://registry.npmjs.org/react-autosize-textarea/-/react-autosize-textarea-0.4.9.tgz", - "integrity": "sha1-jVXIX0xmWm1jWehK8oYQnFBKsps=", + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/react-autosize-textarea/-/react-autosize-textarea-5.0.1.tgz", + "integrity": "sha512-VHYFObl0XtI4xy2pEMqKDIFCOs1e4JEaDaq2WgwZAZdXrahspnzJAX+Zy4V9eKZIhY+lUKA2MlVZxtqexx/7YA==", "requires": { - "autosize": "^3.0.15", + "autosize": "^4.0.2", "line-height": "^0.3.1", "prop-types": "^15.5.6" } @@ -4646,46 +5630,61 @@ "prop-types": "^15.5.10", "reactcss": "^1.2.0", "tinycolor2": "^1.4.1" + }, + "dependencies": { + "lodash": { + "version": "4.17.10", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.10.tgz", + "integrity": "sha512-UejweD1pDoXu+AD825lWwp4ZGtSwgnpZxb3JDViD7StjQz+Nb/6l093lx4OQ0foGWNRoc19mWy7BzL+UAK2iVg==" + } } }, "react-dom": { - "version": "16.0.1", - "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-16.0.1.tgz", - "integrity": "sha512-gGJNmuS0VpkJsNStpzplcgc4iuHJ2X8rjiiaY/5YfHrsAd2cw1JkMXD6Z1kBOed8rDUNrRYrnDYptnhFghFFhA==", + "version": "16.6.3", + "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-16.6.3.tgz", + "integrity": "sha512-8ugJWRCWLGXy+7PmNh8WJz3g1TaTUt1XyoIcFN+x0Zbkoz+KKdUyx1AQLYJdbFXjuF41Nmjn5+j//rxvhFjgSQ==", "requires": { - "fbjs": "^0.8.16", "loose-envify": "^1.1.0", "object-assign": "^4.1.1", - "prop-types": "^15.6.0" + "prop-types": "^15.6.2", + "scheduler": "^0.11.2" } }, "react-dropzone": { - "version": "4.2.13", - "resolved": "https://registry.npmjs.org/react-dropzone/-/react-dropzone-4.2.13.tgz", - "integrity": "sha512-kqpt0Up4GZZFoz4WvBTVzMmVDUZFoGRKDXeyV+baWXZx8Gt0CZmOtV7BSMF1JaBihx6mwy+rfYVNnOKB2hrY9Q==", + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/react-dropzone/-/react-dropzone-7.0.1.tgz", + "integrity": "sha512-J4rbzhFZPVW7k7K9CVb0OcwSOJGLWa0y+0rvtB4rBLVkvq0agH/o3kPJ0DCkd6ZVzL2K1NFqIOvtQkwQKpmJBA==", "requires": { - "attr-accept": "^1.0.3", - "prop-types": "^15.5.7" + "attr-accept": "^1.1.3", + "prop-types": "^15.6.2" } }, "react-intl": { - "version": "2.4.0", - "resolved": "https://registry.npmjs.org/react-intl/-/react-intl-2.4.0.tgz", - "integrity": "sha1-ZsFNyd+ac7L7v71gIXJugKYT6xU=", + "version": "2.7.2", + "resolved": "https://registry.npmjs.org/react-intl/-/react-intl-2.7.2.tgz", + "integrity": "sha512-3dcNGLqEw2FKkX+1L2WYLgjP0MVJkvWuVd1uLcnwifIQe8JQvnd9Bss4hb4Gvg/YhBIRcs4LM6C2bAgyklucjw==", "requires": { + "hoist-non-react-statics": "^2.5.5", "intl-format-cache": "^2.0.5", "intl-messageformat": "^2.1.0", - "intl-relativeformat": "^2.0.0", + "intl-relativeformat": "^2.1.0", "invariant": "^2.1.1" } }, - "react-modal": { + "react-lifecycles-compat": { "version": "3.0.4", - "resolved": "https://registry.npmjs.org/react-modal/-/react-modal-3.0.4.tgz", - "integrity": "sha512-IvRZxJkXiDqEIl4cxCccCzP37z+YOSN+yNOkYH99Ime+n9nPSowgxkX0KfHzR2ezP72LSS3Uln54JDZXUJmLdA==", + "resolved": "https://registry.npmjs.org/react-lifecycles-compat/-/react-lifecycles-compat-3.0.4.tgz", + "integrity": "sha512-fBASbA6LnOU9dOU2eW7aQ8xmYBSXUIWr+UmF9b1efZBazGNO+rcXT/icdKnYm2pTwcRylVUYwW7H1PHfLekVzA==" + }, + "react-modal": { + "version": "3.6.1", + "resolved": "https://registry.npmjs.org/react-modal/-/react-modal-3.6.1.tgz", + "integrity": "sha512-vAhnawahH1fz8A5x/X/1X20KHMe6Q0mkfU5BKPgKSVPYhMhsxtRbNHSitsoJ7/oP27xZo3naZZlwYuuzuSO1xw==", "requires": { "exenv": "^1.2.0", - "prop-types": "^15.5.10" + "prop-types": "^15.5.10", + "react-lifecycles-compat": "^3.0.0", + "warning": "^3.0.0" } }, "react-render-in-browser": { @@ -4696,24 +5695,49 @@ "prop-types": "^15.6.0", "react": "^16.0.0", "react-dom": "^16.0.0" + }, + "dependencies": { + "react": { + "version": "16.0.0", + "resolved": "https://registry.npmjs.org/react/-/react-16.0.0.tgz", + "integrity": "sha1-zn348ZQbA28Cssyp29DLHw6FXi0=", + "requires": { + "fbjs": "^0.8.16", + "loose-envify": "^1.1.0", + "object-assign": "^4.1.1", + "prop-types": "^15.6.0" + } + }, + "react-dom": { + "version": "16.0.1", + "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-16.0.1.tgz", + "integrity": "sha512-gGJNmuS0VpkJsNStpzplcgc4iuHJ2X8rjiiaY/5YfHrsAd2cw1JkMXD6Z1kBOed8rDUNrRYrnDYptnhFghFFhA==", + "requires": { + "fbjs": "^0.8.16", + "loose-envify": "^1.1.0", + "object-assign": "^4.1.1", + "prop-types": "^15.6.0" + } + } } }, "react-tabs": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/react-tabs/-/react-tabs-2.1.1.tgz", - "integrity": "sha512-55jl6lsYmPTQarnjgrBU68WZlNtVSngpRxOc4iXm+Te27F9ixUr/IBTbhlhDCMiFJreP+cqu1OaMdNGY2Hg10A==", + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/react-tabs/-/react-tabs-2.3.0.tgz", + "integrity": "sha512-pYaefgVy76/36AMEP+B8YuVVzDHa3C5UFZ3REU78zolk0qMxEhKvUFofvDCXyLZwf0RZjxIfiwok1BEb18nHyA==", "requires": { "classnames": "^2.2.0", "prop-types": "^15.5.0" } }, "react-toastify": { - "version": "2.1.7", - "resolved": "https://registry.npmjs.org/react-toastify/-/react-toastify-2.1.7.tgz", - "integrity": "sha512-iMS5wXiTDKXcWTIF055BmsSwJINcxs9+CUGeEPMSllU0I00IKfV2inb3xhRxrB7d+4QvPqWZAtDPTk6nz6o1nA==", + "version": "4.4.3", + "resolved": "https://registry.npmjs.org/react-toastify/-/react-toastify-4.4.3.tgz", + "integrity": "sha512-MHv9eAkb02xLKemOtS1mnv+jq+2iThYG8pWJxRCj5s/wgr5lU69uVLh/bTy7lU/HJq0uJEcdwfuIeHr4KGuoUQ==", "requires": { + "classnames": "^2.2.6", "prop-types": "^15.6.0", - "react-transition-group": "^2.2.1" + "react-transition-group": "^2.4.0" } }, "react-toggle": { @@ -4725,16 +5749,24 @@ } }, "react-transition-group": { - "version": "2.2.1", - "resolved": "https://registry.npmjs.org/react-transition-group/-/react-transition-group-2.2.1.tgz", - "integrity": "sha512-q54UBM22bs/CekG8r3+vi9TugSqh0t7qcEVycaRc9M0p0aCEu+h6rp/RFiW7fHfgd1IKpd9oILFTl5QK+FpiPA==", - "requires": { - "chain-function": "^1.0.0", - "classnames": "^2.2.5", - "dom-helpers": "^3.2.0", - "loose-envify": "^1.3.1", - "prop-types": "^15.5.8", - "warning": "^3.0.0" + "version": "2.5.1", + "resolved": "https://registry.npmjs.org/react-transition-group/-/react-transition-group-2.5.1.tgz", + "integrity": "sha512-8x/CxUL9SjYFmUdzsBPTgtKeCxt7QArjNSte0wwiLtF/Ix/o1nWNJooNy5o9XbHIKS31pz7J5VF2l41TwlvbHQ==", + "requires": { + "dom-helpers": "^3.3.1", + "loose-envify": "^1.4.0", + "prop-types": "^15.6.2", + "react-lifecycles-compat": "^3.0.4" + }, + "dependencies": { + "loose-envify": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/loose-envify/-/loose-envify-1.4.0.tgz", + "integrity": "sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==", + "requires": { + "js-tokens": "^3.0.0 || ^4.0.0" + } + } } }, "reactcss": { @@ -4743,6 +5775,13 @@ "integrity": "sha512-KiwVUcFu1RErkI97ywr8nvx8dNOpT03rbnma0SSalTYjkrPYaEajR4a/MRt6DZ46K6arDRbWMNHF+xH7G7n/8A==", "requires": { "lodash": "^4.0.1" + }, + "dependencies": { + "lodash": { + "version": "4.17.10", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.10.tgz", + "integrity": "sha512-UejweD1pDoXu+AD825lWwp4ZGtSwgnpZxb3JDViD7StjQz+Nb/6l093lx4OQ0foGWNRoc19mWy7BzL+UAK2iVg==" + } } }, "read-chunk": { @@ -4772,7 +5811,7 @@ }, "readable-stream": { "version": "2.3.6", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.6.tgz", + "resolved": "http://registry.npmjs.org/readable-stream/-/readable-stream-2.3.6.tgz", "integrity": "sha512-tQtKA9WIAhBF3+VLAseyMqZeBjW0AHJoxOtYqSUZNJxauErmLbVm2FW1y+J/YA9dUrAC39ITejlZWhVIwawkKw==", "requires": { "core-util-is": "~1.0.0", @@ -4785,9 +5824,9 @@ } }, "reconnecting-websocket": { - "version": "3.2.2", - "resolved": "https://registry.npmjs.org/reconnecting-websocket/-/reconnecting-websocket-3.2.2.tgz", - "integrity": "sha512-SWSfoXiaHVOqXuPWFgGWeUxKnb5HIY7I/Fh5C/hy4wUOgeOh7YIMXEiv5/eHBlNs4tNzCrO5YDR9AH62NWle0Q==" + "version": "4.1.10", + "resolved": "https://registry.npmjs.org/reconnecting-websocket/-/reconnecting-websocket-4.1.10.tgz", + "integrity": "sha512-x6vgqa8q9KRuJyFkOvBKH1TfyTmN5OWQUf1MIsblfOSY29VE+iI9cKqmIuPvg9TO/hUBxdmUIVutDkcdbqFwZg==" }, "redent": { "version": "1.0.0", @@ -4829,6 +5868,22 @@ "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.11.1.tgz", "integrity": "sha512-MguG95oij0fC3QV3URf4V2SDYGJhJnJGqvIIgdECeODCT98wSWDAJ94SSuVpYQUoTcGUIL6L4yNB7j1DFFHSBg==" }, + "regex-not": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/regex-not/-/regex-not-1.0.2.tgz", + "integrity": "sha512-J6SDjUgDxQj5NusnOtdFxDwN/+HWykR8GELwctJ7mdqhcyy1xEc4SRFHUXvxTp661YaVKAjfRLZ9cCqS6tn32A==", + "dev": true, + "requires": { + "extend-shallow": "^3.0.2", + "safe-regex": "^1.1.0" + } + }, + "regexpp": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/regexpp/-/regexpp-2.0.1.tgz", + "integrity": "sha512-lv0M6+TkDVniA3aD1Eg0DVpfU/booSu7Eev3TDO/mZKHBfVjgCGTV4t4buppESEYDtkArYFOxTJWv6S5C+iaNw==", + "dev": true + }, "regexpu-core": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/regexpu-core/-/regexpu-core-1.0.0.tgz", @@ -4861,6 +5916,18 @@ "integrity": "sha1-wkvOKig62tW8P1jg1IJJuSN52O8=", "dev": true }, + "repeat-element": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/repeat-element/-/repeat-element-1.1.3.tgz", + "integrity": "sha512-ahGq0ZnV5m5XtZLMb+vP76kcAM5nkLqk0lpqAuojSKGgQtn4eRi4ZZGm2olo2zKFH+sMsWaqOCW1dqAnOru72g==", + "dev": true + }, + "repeat-string": { + "version": "1.6.1", + "resolved": "https://registry.npmjs.org/repeat-string/-/repeat-string-1.6.1.tgz", + "integrity": "sha1-jcrkcOHIirwtYA//Sndihtp15jc=", + "dev": true + }, "repeating": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/repeating/-/repeating-2.0.1.tgz", @@ -4870,30 +5937,30 @@ } }, "request": { - "version": "2.87.0", - "resolved": "https://registry.npmjs.org/request/-/request-2.87.0.tgz", - "integrity": "sha512-fcogkm7Az5bsS6Sl0sibkbhcKsnyon/jV1kF3ajGmF0c8HrttdKTPRT9hieOaQHA5HEq6r8OyWOo/o781C1tNw==", + "version": "2.88.0", + "resolved": "https://registry.npmjs.org/request/-/request-2.88.0.tgz", + "integrity": "sha512-NAqBSrijGLZdM0WZNsInLJpkJokL72XYjUpnB0iwsRgxh7dB6COrHnTBNwN0E+lHDAJzu7kLAkDeY08z2/A0hg==", "requires": { "aws-sign2": "~0.7.0", - "aws4": "^1.6.0", + "aws4": "^1.8.0", "caseless": "~0.12.0", - "combined-stream": "~1.0.5", - "extend": "~3.0.1", + "combined-stream": "~1.0.6", + "extend": "~3.0.2", "forever-agent": "~0.6.1", - "form-data": "~2.3.1", - "har-validator": "~5.0.3", + "form-data": "~2.3.2", + "har-validator": "~5.1.0", "http-signature": "~1.2.0", "is-typedarray": "~1.0.0", "isstream": "~0.1.2", "json-stringify-safe": "~5.0.1", - "mime-types": "~2.1.17", - "oauth-sign": "~0.8.2", + "mime-types": "~2.1.19", + "oauth-sign": "~0.9.0", "performance-now": "^2.1.0", - "qs": "~6.5.1", - "safe-buffer": "^5.1.1", - "tough-cookie": "~2.3.3", + "qs": "~6.5.2", + "safe-buffer": "^5.1.2", + "tough-cookie": "~2.4.3", "tunnel-agent": "^0.6.0", - "uuid": "^3.1.0" + "uuid": "^3.3.2" } }, "request-promise-core": { @@ -4903,6 +5970,14 @@ "dev": true, "requires": { "lodash": "^4.13.1" + }, + "dependencies": { + "lodash": { + "version": "4.17.10", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.10.tgz", + "integrity": "sha512-UejweD1pDoXu+AD825lWwp4ZGtSwgnpZxb3JDViD7StjQz+Nb/6l093lx4OQ0foGWNRoc19mWy7BzL+UAK2iVg==", + "dev": true + } } }, "request-promise-native": { @@ -4914,6 +5989,23 @@ "request-promise-core": "1.1.1", "stealthy-require": "^1.1.0", "tough-cookie": ">=2.3.3" + }, + "dependencies": { + "punycode": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/punycode/-/punycode-1.4.1.tgz", + "integrity": "sha1-wNWmOycYgArY4esPpSachN1BhF4=", + "dev": true + }, + "tough-cookie": { + "version": "2.3.4", + "resolved": "http://registry.npmjs.org/tough-cookie/-/tough-cookie-2.3.4.tgz", + "integrity": "sha512-TZ6TTfI5NtZnuyy/Kecv+CnoROnyXn2DN97LontgQpCwsX2XyLYCC0ENhYkehSOwAp8rTQKc/NUIF7BkQ5rKLA==", + "dev": true, + "requires": { + "punycode": "^1.4.1" + } + } } }, "require-directory": { @@ -4921,12 +6013,6 @@ "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz", "integrity": "sha1-jGStX9MNqxyXbiNE/+f3kqam30I=" }, - "require-from-string": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/require-from-string/-/require-from-string-1.2.1.tgz", - "integrity": "sha1-UpyczvJzgK3+yaL5ZbZJu+5jZBg=", - "dev": true - }, "require-main-filename": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/require-main-filename/-/require-main-filename-1.0.1.tgz", @@ -4934,7 +6020,7 @@ }, "require-uncached": { "version": "1.0.3", - "resolved": "https://registry.npmjs.org/require-uncached/-/require-uncached-1.0.3.tgz", + "resolved": "http://registry.npmjs.org/require-uncached/-/require-uncached-1.0.3.tgz", "integrity": "sha1-Tg1W1slmL9MeQwEcS5WqSZVUIdM=", "dev": true, "requires": { @@ -4943,9 +6029,9 @@ } }, "resolve": { - "version": "1.5.0", - "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.5.0.tgz", - "integrity": "sha512-hgoSGrc3pjzAPHNBg+KnFcK2HwlHTs/YrAGUr6qgTVUZmXv1UEXXl0bZNBKMA9fud6lRYFdPGz0xXxycPzmmiw==", + "version": "1.8.1", + "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.8.1.tgz", + "integrity": "sha512-AicPrAC7Qu1JxPCZ9ZgCZlY35QgFnNqc+0LtbRNxnVw4TXvjQ72wnuL9JQcEBgXkI9JM8MsT9kaQoHcpCRJOYA==", "dev": true, "requires": { "path-parse": "^1.0.5" @@ -4957,6 +6043,11 @@ "integrity": "sha1-Jsv+k10a7uq7Kbw/5a6wHpPUQiY=", "dev": true }, + "resolve-pathname": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/resolve-pathname/-/resolve-pathname-2.2.0.tgz", + "integrity": "sha512-bAFz9ld18RzJfddgrO2e/0S2O81710++chRMUxHjXOYKF6jTAMrUNZrEZ1PvV0zlhfjidm08iRPdTLPno1FuRg==" + }, "resolve-url": { "version": "0.2.1", "resolved": "https://registry.npmjs.org/resolve-url/-/resolve-url-0.2.1.tgz", @@ -4973,6 +6064,18 @@ "signal-exit": "^3.0.2" } }, + "ret": { + "version": "0.1.15", + "resolved": "https://registry.npmjs.org/ret/-/ret-0.1.15.tgz", + "integrity": "sha512-TTlYpa+OL+vMMNG24xSlQGEJ3B/RzEfUlLct7b5G/ytav+wPrplCpVMFuwzXbkecJrb6IYo1iFb0S9v37754mg==", + "dev": true + }, + "rgb2hex": { + "version": "0.1.9", + "resolved": "https://registry.npmjs.org/rgb2hex/-/rgb2hex-0.1.9.tgz", + "integrity": "sha512-32iuQzhOjyT+cv9aAFRBJ19JgHwzQwbjUhH3Fj2sWW2EEGAW8fpFrDFP5ndoKDxJaLO06x1hE3kyuIFrUQtybQ==", + "dev": true + }, "rimraf": { "version": "2.6.2", "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.6.2.tgz", @@ -4990,6 +6093,12 @@ "is-promise": "^2.1.0" } }, + "run-node": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/run-node/-/run-node-1.0.0.tgz", + "integrity": "sha512-kc120TBlQ3mih1LSzdAJXo4xn/GWS2ec0l3S+syHDXP9uRr0JAT8Qd3mdMuyjqCzeZktgP3try92cEgf9Nks8A==", + "dev": true + }, "rx-lite": { "version": "4.0.8", "resolved": "https://registry.npmjs.org/rx-lite/-/rx-lite-4.0.8.tgz", @@ -5006,18 +6115,27 @@ } }, "rxjs": { - "version": "5.5.6", - "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-5.5.6.tgz", - "integrity": "sha512-v4Q5HDC0FHAQ7zcBX7T2IL6O5ltl1a2GX4ENjPXg6SjDY69Cmx9v4113C99a4wGF16ClPv5Z8mghuYorVkg/kg==", + "version": "6.3.3", + "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-6.3.3.tgz", + "integrity": "sha512-JTWmoY9tWCs7zvIk/CvRjhjGaOd+OVBM987mxFo+OW66cGpdKjZcpmc74ES1sB//7Kl/PAe8+wEakuhG4pcgOw==", "dev": true, "requires": { - "symbol-observable": "1.0.1" + "tslib": "^1.9.0" } }, "safe-buffer": { - "version": "5.1.1", - "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.1.tgz", - "integrity": "sha512-kKvNJn6Mm93gAczWVJg7wH+wGYWNrDHdWvpUmHyEsgCtIwwo3bqPtV4tR5tuPaUhTOo/kvhVwd8XwwOllGYkbg==" + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", + "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==" + }, + "safe-regex": { + "version": "1.1.0", + "resolved": "http://registry.npmjs.org/safe-regex/-/safe-regex-1.1.0.tgz", + "integrity": "sha1-QKNmnzsHfR6UPURinhV91IAjvy4=", + "dev": true, + "requires": { + "ret": "~0.1.10" + } }, "safer-buffer": { "version": "2.1.2", @@ -5041,6 +6159,15 @@ "integrity": "sha512-NqVDv9TpANUjFm0N8uM5GxL36UgKi9/atZw+x7YFnQ8ckwFGKrl4xX4yWtrey3UJm5nP1kUbnYgLopqWNSRhWw==", "dev": true }, + "scheduler": { + "version": "0.11.3", + "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.11.3.tgz", + "integrity": "sha512-i9X9VRRVZDd3xZw10NY5Z2cVMbdYg6gqFecfj79USv1CFN+YrJ3gIPRKf1qlY+Sxly4djoKdfx1T+m9dnRB8kQ==", + "requires": { + "loose-envify": "^1.1.0", + "object-assign": "^4.1.1" + } + }, "scss-tokenizer": { "version": "0.2.3", "resolved": "https://registry.npmjs.org/scss-tokenizer/-/scss-tokenizer-0.2.3.tgz", @@ -5048,6 +6175,16 @@ "requires": { "js-base64": "^2.1.8", "source-map": "^0.4.2" + }, + "dependencies": { + "source-map": { + "version": "0.4.4", + "resolved": "http://registry.npmjs.org/source-map/-/source-map-0.4.4.tgz", + "integrity": "sha1-66T12pwNyZneaAMti092FzZSA2s=", + "requires": { + "amdefine": ">=0.0.4" + } + } } }, "select": { @@ -5056,15 +6193,44 @@ "integrity": "sha1-DnNQrN7ICxEIUoeG7B1EGNEbOW0=" }, "semver": { - "version": "5.5.1", - "resolved": "https://registry.npmjs.org/semver/-/semver-5.5.1.tgz", - "integrity": "sha512-PqpAxfrEhlSUWge8dwIp4tZnQ25DIOthpiaHNIthsjEFQD6EvqUKUDM7L8O2rShkFccYo1VjJR0coWfNkCubRw==" + "version": "5.6.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-5.6.0.tgz", + "integrity": "sha512-RS9R6R35NYgQn++fkDWaOmqGoj4Ek9gGs+DPxNUZKuwE183xjJroKvyo1IzVFeXvUrvmALy6FWD5xrdJT25gMg==" + }, + "semver-compare": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/semver-compare/-/semver-compare-1.0.0.tgz", + "integrity": "sha1-De4hahyUGrN+nvsXiPavxf9VN/w=", + "dev": true }, "set-blocking": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/set-blocking/-/set-blocking-2.0.0.tgz", "integrity": "sha1-BF+XgtARrppoA93TgrJDkrPYkPc=" }, + "set-value": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/set-value/-/set-value-2.0.0.tgz", + "integrity": "sha512-hw0yxk9GT/Hr5yJEYnHNKYXkIA8mVJgd9ditYZCe16ZczcaELYYcfvaXesNACk2O8O0nTiPQcQhGUQj8JLzeeg==", + "dev": true, + "requires": { + "extend-shallow": "^2.0.1", + "is-extendable": "^0.1.1", + "is-plain-object": "^2.0.3", + "split-string": "^3.0.1" + }, + "dependencies": { + "extend-shallow": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", + "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", + "dev": true, + "requires": { + "is-extendable": "^0.1.0" + } + } + } + }, "setimmediate": { "version": "1.0.5", "resolved": "https://registry.npmjs.org/setimmediate/-/setimmediate-1.0.5.tgz", @@ -5090,12 +6256,55 @@ "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.2.tgz", "integrity": "sha1-tf3AjxKH6hF4Yo5BXiUTK3NkbG0=" }, + "simple-git": { + "version": "1.107.0", + "resolved": "https://registry.npmjs.org/simple-git/-/simple-git-1.107.0.tgz", + "integrity": "sha512-t4OK1JRlp4ayKRfcW6owrWcRVLyHRUlhGd0uN6ZZTqfDq8a5XpcUdOKiGRNobHEuMtNqzp0vcJNvhYWwh5PsQA==", + "dev": true, + "requires": { + "debug": "^4.0.1" + }, + "dependencies": { + "debug": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.1.0.tgz", + "integrity": "sha512-heNPJUJIqC+xB6ayLAMHaIrmN9HKa7aQO8MGqKpvCA+uJYVcvR6l5kgdrhRuwPFHU7P5/A1w0BjByPHwpfTDKg==", + "dev": true, + "requires": { + "ms": "^2.1.1" + } + }, + "ms": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.1.tgz", + "integrity": "sha512-tgp+dl5cGk28utYktBsrFqA7HKgrhgPsg6Z/EfhWI4gl1Hwq8B/GmY/0oXZ6nF8hDVesS/FpnYaD/kOWhYQvyg==", + "dev": true + } + } + }, + "simple-swizzle": { + "version": "0.2.2", + "resolved": "https://registry.npmjs.org/simple-swizzle/-/simple-swizzle-0.2.2.tgz", + "integrity": "sha1-pNprY1/8zMoz9w0Xy5JZLeleVXo=", + "requires": { + "is-arrayish": "^0.3.1" + }, + "dependencies": { + "is-arrayish": { + "version": "0.3.2", + "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.3.2.tgz", + "integrity": "sha512-eVRqCvVlZbuw3GrM63ovNSNAeA1K16kaR/LRY/92w0zxQ5/1YzwblUX652i4Xs9RwAGjW9d9y6X88t8OaAJfWQ==" + } + } + }, "slice-ansi": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/slice-ansi/-/slice-ansi-1.0.0.tgz", - "integrity": "sha512-POqxBK6Lb3q6s047D/XsDVNPnF9Dl8JSaqe9h9lURl0OdNqy/ujDrOiIHtsqXMGbWWTIomRzAMaTyawAU//Reg==", + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/slice-ansi/-/slice-ansi-2.0.0.tgz", + "integrity": "sha512-4j2WTWjp3GsZ+AOagyzVbzp4vWGtZ0hEZ/gDY/uTvm6MTxUfTUIsnMIFb1bn8o0RuXiqUw15H1bue8f22Vw2oQ==", "dev": true, "requires": { + "ansi-styles": "^3.2.0", + "astral-regex": "^1.0.0", "is-fullwidth-code-point": "^2.0.0" }, "dependencies": { @@ -5107,14 +6316,125 @@ } } }, - "source-map": { - "version": "0.4.4", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.4.4.tgz", - "integrity": "sha1-66T12pwNyZneaAMti092FzZSA2s=", + "snapdragon": { + "version": "0.8.2", + "resolved": "https://registry.npmjs.org/snapdragon/-/snapdragon-0.8.2.tgz", + "integrity": "sha512-FtyOnWN/wCHTVXOMwvSv26d+ko5vWlIDD6zoUJ7LW8vh+ZBC8QdljveRP+crNrtBwioEUWy/4dMtbBjA4ioNlg==", + "dev": true, + "requires": { + "base": "^0.11.1", + "debug": "^2.2.0", + "define-property": "^0.2.5", + "extend-shallow": "^2.0.1", + "map-cache": "^0.2.2", + "source-map": "^0.5.6", + "source-map-resolve": "^0.5.0", + "use": "^3.1.0" + }, + "dependencies": { + "define-property": { + "version": "0.2.5", + "resolved": "https://registry.npmjs.org/define-property/-/define-property-0.2.5.tgz", + "integrity": "sha1-w1se+RjsPJkPmlvFe+BKrOxcgRY=", + "dev": true, + "requires": { + "is-descriptor": "^0.1.0" + } + }, + "extend-shallow": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", + "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", + "dev": true, + "requires": { + "is-extendable": "^0.1.0" + } + }, + "source-map": { + "version": "0.5.7", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz", + "integrity": "sha1-igOdLRAh0i0eoUyA2OpGi6LvP8w=", + "dev": true + } + } + }, + "snapdragon-node": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/snapdragon-node/-/snapdragon-node-2.1.1.tgz", + "integrity": "sha512-O27l4xaMYt/RSQ5TR3vpWCAB5Kb/czIcqUFOM/C4fYcLnbZUc1PkjTAMjof2pBWaSTwOUd6qUHcFGVGj7aIwnw==", + "dev": true, + "requires": { + "define-property": "^1.0.0", + "isobject": "^3.0.0", + "snapdragon-util": "^3.0.1" + }, + "dependencies": { + "define-property": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/define-property/-/define-property-1.0.0.tgz", + "integrity": "sha1-dp66rz9KY6rTr56NMEybvnm/sOY=", + "dev": true, + "requires": { + "is-descriptor": "^1.0.0" + } + }, + "is-accessor-descriptor": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-1.0.0.tgz", + "integrity": "sha512-m5hnHTkcVsPfqx3AKlyttIPb7J+XykHvJP2B9bZDjlhLIoEq4XoK64Vg7boZlVWYK6LUY94dYPEE7Lh0ZkZKcQ==", + "dev": true, + "requires": { + "kind-of": "^6.0.0" + } + }, + "is-data-descriptor": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-1.0.0.tgz", + "integrity": "sha512-jbRXy1FmtAoCjQkVmIVYwuuqDFUbaOeDjmed1tOGPrsMhtJA4rD9tkgA0F1qJ3gRFRXcHYVkdeaP50Q5rE/jLQ==", + "dev": true, + "requires": { + "kind-of": "^6.0.0" + } + }, + "is-descriptor": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-1.0.2.tgz", + "integrity": "sha512-2eis5WqQGV7peooDyLmNEPUrps9+SXX5c9pL3xEB+4e9HnGuDa7mB7kHxHw4CbqS9k1T2hOH3miL8n8WtiYVtg==", + "dev": true, + "requires": { + "is-accessor-descriptor": "^1.0.0", + "is-data-descriptor": "^1.0.0", + "kind-of": "^6.0.2" + } + }, + "isobject": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/isobject/-/isobject-3.0.1.tgz", + "integrity": "sha1-TkMekrEalzFjaqH5yNHMvP2reN8=", + "dev": true + }, + "kind-of": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.2.tgz", + "integrity": "sha512-s5kLOcnH0XqDO+FvuaLX8DDjZ18CGFk7VygH40QoKPUQhW4e2rvM0rwUq0t8IQDOwYSeLK01U90OjzBTme2QqA==", + "dev": true + } + } + }, + "snapdragon-util": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/snapdragon-util/-/snapdragon-util-3.0.1.tgz", + "integrity": "sha512-mbKkMdQKsjX4BAL4bRYTj21edOf8cN7XHdYUJEe+Zn99hVEYcMvKPct1IqNe7+AZPirn8BCDOQBHQZknqmKlZQ==", + "dev": true, "requires": { - "amdefine": ">=0.0.4" + "kind-of": "^3.2.0" } }, + "source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==" + }, "source-map-resolve": { "version": "0.5.2", "resolved": "https://registry.npmjs.org/source-map-resolve/-/source-map-resolve-0.5.2.tgz", @@ -5135,18 +6455,18 @@ "dev": true }, "spdx-correct": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/spdx-correct/-/spdx-correct-3.0.0.tgz", - "integrity": "sha512-N19o9z5cEyc8yQQPukRCZ9EUmb4HUpnrmaL/fxS2pBo2jbfcFRVuFZ/oFC+vZz0MNNk0h80iMn5/S6qGZOL5+g==", + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/spdx-correct/-/spdx-correct-3.1.0.tgz", + "integrity": "sha512-lr2EZCctC2BNR7j7WzJ2FpDznxky1sjfxvvYEyzxNyb6lZXHODmEoJeFu4JupYlkfha1KZpJyoqiJ7pgA1qq8Q==", "requires": { "spdx-expression-parse": "^3.0.0", "spdx-license-ids": "^3.0.0" } }, "spdx-exceptions": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/spdx-exceptions/-/spdx-exceptions-2.1.0.tgz", - "integrity": "sha512-4K1NsmrlCU1JJgUrtgEeTVyfx8VaYea9J9LvARxhbHtVtohPs/gFGG5yy49beySjlIMhhXZ4QqujIZEfS4l6Cg==" + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/spdx-exceptions/-/spdx-exceptions-2.2.0.tgz", + "integrity": "sha512-2XQACfElKi9SlVb1CYadKDXvoajPgBVPn/gOQLrTvHdElaVhr7ZEbqJaRnJLVNeaI4cMEAgVCeBMKF6MWRDCRA==" }, "spdx-expression-parse": { "version": "3.0.0", @@ -5158,20 +6478,29 @@ } }, "spdx-license-ids": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/spdx-license-ids/-/spdx-license-ids-3.0.1.tgz", - "integrity": "sha512-TfOfPcYGBB5sDuPn3deByxPhmfegAhpDYKSOXZQN81Oyrrif8ZCodOLzK3AesELnCx03kikhyDwh0pfvvQvF8w==" + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/spdx-license-ids/-/spdx-license-ids-3.0.2.tgz", + "integrity": "sha512-qky9CVt0lVIECkEsYbNILVnPvycuEBkXoMFLRWsREkomQLevYhtRKC+R91a5TOAQ3bCMjikRwhyaRqj1VYatYg==" + }, + "split-string": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/split-string/-/split-string-3.1.0.tgz", + "integrity": "sha512-NzNVhJDYpwceVVii8/Hu6DKfD2G+NrQHlS/V/qgv763EYudVwEcMQNxd2lh+0VrUByXN/oJkl5grOhYWvQUYiw==", + "dev": true, + "requires": { + "extend-shallow": "^3.0.0" + } }, "sprintf-js": { "version": "1.0.3", - "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz", + "resolved": "http://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz", "integrity": "sha1-BOaSb2YolTVPPdAVIDYzuFcpfiw=", "dev": true }, "sshpk": { - "version": "1.14.2", - "resolved": "https://registry.npmjs.org/sshpk/-/sshpk-1.14.2.tgz", - "integrity": "sha1-xvxhZIo9nE52T9P8306hBeSSupg=", + "version": "1.15.2", + "resolved": "https://registry.npmjs.org/sshpk/-/sshpk-1.15.2.tgz", + "integrity": "sha512-Ra/OXQtuh0/enyl4ETZAfTaeksa6BXks5ZcjpSUNrjBr0DvrJKX+1fsKDPpT9TBXgHAFsa4510aNVgI8g/+SzA==", "requires": { "asn1": "~0.2.3", "assert-plus": "^1.0.0", @@ -5190,11 +6519,32 @@ "integrity": "sha1-VHxws0fo0ytOEI6hoqFZ5f3eGcA=" }, "staged-git-files": { - "version": "0.0.4", - "resolved": "https://registry.npmjs.org/staged-git-files/-/staged-git-files-0.0.4.tgz", - "integrity": "sha1-15fhtVHKemOd7AI33G60u5vhfTU=", + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/staged-git-files/-/staged-git-files-1.1.2.tgz", + "integrity": "sha512-0Eyrk6uXW6tg9PYkhi/V/J4zHp33aNyi2hOCmhFLqLTIhbgqWn5jlSzI+IU0VqrZq6+DbHcabQl/WP6P3BG0QA==", "dev": true }, + "static-extend": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/static-extend/-/static-extend-0.1.2.tgz", + "integrity": "sha1-YICcOcv/VTNyJv1eC1IPNB8ftcY=", + "dev": true, + "requires": { + "define-property": "^0.2.5", + "object-copy": "^0.1.0" + }, + "dependencies": { + "define-property": { + "version": "0.2.5", + "resolved": "https://registry.npmjs.org/define-property/-/define-property-0.2.5.tgz", + "integrity": "sha1-w1se+RjsPJkPmlvFe+BKrOxcgRY=", + "dev": true, + "requires": { + "is-descriptor": "^0.1.0" + } + } + } + }, "stdout-stream": { "version": "1.4.1", "resolved": "https://registry.npmjs.org/stdout-stream/-/stdout-stream-1.4.1.tgz", @@ -5232,17 +6582,12 @@ "stream-to": "~0.2.0" } }, - "stream-to-observable": { - "version": "0.1.0", - "resolved": "https://registry.npmjs.org/stream-to-observable/-/stream-to-observable-0.1.0.tgz", - "integrity": "sha1-Rb8dny19wJvtgfHDB8Qw5ouEz/4=", + "string-argv": { + "version": "0.0.2", + "resolved": "https://registry.npmjs.org/string-argv/-/string-argv-0.0.2.tgz", + "integrity": "sha1-2sMECGkMIfPDYwo/86BYd73L1zY=", "dev": true }, - "strict-uri-encode": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/strict-uri-encode/-/strict-uri-encode-1.1.0.tgz", - "integrity": "sha1-J5siXfHVgrH1TmWt3UNS4Y+qBxM=" - }, "string-hash": { "version": "1.1.3", "resolved": "https://registry.npmjs.org/string-hash/-/string-hash-1.1.3.tgz", @@ -5260,26 +6605,26 @@ }, "string_decoder": { "version": "1.1.1", - "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", + "resolved": "http://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", "requires": { "safe-buffer": "~5.1.0" } }, "stringify-object": { - "version": "3.2.1", - "resolved": "https://registry.npmjs.org/stringify-object/-/stringify-object-3.2.1.tgz", - "integrity": "sha512-jPcQYw/52HUPP8uOE4kkjxl5bB9LfHkKCTptIk3qw7ozP5XMIMlHMLjt00GGSwW6DJAf/njY5EU6Vpwl4LlBKQ==", + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/stringify-object/-/stringify-object-3.3.0.tgz", + "integrity": "sha512-rHqiFh1elqCQ9WPLIC8I0Q/g/wj5J1eMkyoiD6eoQApWHP0FtlK7rqnhmabL5VUY9JQCcqwwvlOaSuutekgyrw==", "dev": true, "requires": { - "get-own-enumerable-property-symbols": "^2.0.1", + "get-own-enumerable-property-symbols": "^3.0.0", "is-obj": "^1.0.1", "is-regexp": "^1.0.0" } }, "strip-ansi": { "version": "3.0.1", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-3.0.1.tgz", + "resolved": "http://registry.npmjs.org/strip-ansi/-/strip-ansi-3.0.1.tgz", "integrity": "sha1-ajhfuIU9lS1f8F0Oiq+UJ43GPc8=", "requires": { "ansi-regex": "^2.0.0" @@ -5295,7 +6640,7 @@ }, "strip-eof": { "version": "1.0.0", - "resolved": "https://registry.npmjs.org/strip-eof/-/strip-eof-1.0.0.tgz", + "resolved": "http://registry.npmjs.org/strip-eof/-/strip-eof-1.0.0.tgz", "integrity": "sha1-u0P/VZim6wXYm1n80SnJgzE2Br8=", "dev": true }, @@ -5314,55 +6659,43 @@ "dev": true }, "supports-color": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-2.0.0.tgz", - "integrity": "sha1-U10EXOa2Nj+kARcIRimZXp3zJMc=" + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", + "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", + "requires": { + "has-flag": "^3.0.0" + }, + "dependencies": { + "has-flag": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", + "integrity": "sha1-tdRU3CGZriJWmfNGfloH87lVuv0=" + } + } }, "symbol-observable": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/symbol-observable/-/symbol-observable-1.0.1.tgz", - "integrity": "sha1-g0D8RwLDEi310iKI+IKD9RPT/dQ=", + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/symbol-observable/-/symbol-observable-1.2.0.tgz", + "integrity": "sha512-e900nM8RRtGhlV36KGEU9k65K3mPb1WV70OdjfxlG2EAuM1noi/E/BaW/uMhL7bPEssK8QV57vN3esixjUvcXQ==", "dev": true }, "table": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/table/-/table-4.0.2.tgz", - "integrity": "sha512-UUkEAPdSGxtRpiV9ozJ5cMTtYiqz7Ni1OGqLXRCynrvzdtR1p+cfOWe2RJLwvUG8hNanaSRjecIqwOjqeatDsA==", + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/table/-/table-5.1.1.tgz", + "integrity": "sha512-NUjapYb/qd4PeFW03HnAuOJ7OMcBkJlqeClWxeNlQ0lXGSb52oZXGzkO0/I0ARegQ2eUT1g2VDJH0eUxDRcHmw==", "dev": true, "requires": { - "ajv": "^5.2.3", - "ajv-keywords": "^2.1.0", - "chalk": "^2.1.0", - "lodash": "^4.17.4", - "slice-ansi": "1.0.0", + "ajv": "^6.6.1", + "lodash": "^4.17.11", + "slice-ansi": "2.0.0", "string-width": "^2.1.1" }, "dependencies": { "ansi-regex": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-3.0.0.tgz", - "integrity": "sha1-7QMXwyIGT3lGbAKWa922Bas32Zg=", - "dev": true - }, - "ansi-styles": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.0.tgz", - "integrity": "sha512-NnSOmMEYtVR2JVMIGTzynRkkaxtiq1xnFBcdQD/DnNCYPoEPsVJhM98BDyaoNOQIi7p4okdi3E27eN7GQbsUug==", - "dev": true, - "requires": { - "color-convert": "^1.9.0" - } - }, - "chalk": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.3.0.tgz", - "integrity": "sha512-Az5zJR2CBujap2rqXGaJKaPHyJ0IrUimvYNX+ncCy8PJP4ltOGTrHUIo097ZaL2zMeKYpiCdqDvS6zdrTFok3Q==", - "dev": true, - "requires": { - "ansi-styles": "^3.1.0", - "escape-string-regexp": "^1.0.5", - "supports-color": "^4.0.0" - } + "integrity": "sha1-7QMXwyIGT3lGbAKWa922Bas32Zg=", + "dev": true }, "is-fullwidth-code-point": { "version": "2.0.0", @@ -5388,21 +6721,12 @@ "requires": { "ansi-regex": "^3.0.0" } - }, - "supports-color": { - "version": "4.5.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-4.5.0.tgz", - "integrity": "sha1-vnoN5ITexcXN34s9WRJQRJEvY1s=", - "dev": true, - "requires": { - "has-flag": "^2.0.0" - } } } }, "tar": { "version": "2.2.1", - "resolved": "https://registry.npmjs.org/tar/-/tar-2.2.1.tgz", + "resolved": "http://registry.npmjs.org/tar/-/tar-2.2.1.tgz", "integrity": "sha1-jk0qJWwOIYXGsYrWlK7JaLg8sdE=", "requires": { "block-stream": "*", @@ -5411,17 +6735,25 @@ } }, "tar-stream": { - "version": "1.5.5", - "resolved": "https://registry.npmjs.org/tar-stream/-/tar-stream-1.5.5.tgz", - "integrity": "sha512-mQdgLPc/Vjfr3VWqWbfxW8yQNiJCbAZ+Gf6GDu1Cy0bdb33ofyiNGBtAY96jHFhDuivCwgW1H9DgTON+INiXgg==", + "version": "1.6.2", + "resolved": "https://registry.npmjs.org/tar-stream/-/tar-stream-1.6.2.tgz", + "integrity": "sha512-rzS0heiNf8Xn7/mpdSVVSMAWAoy9bfb1WOTYC78Z0UQKeKa/CWS8FOq0lKGNa8DWKAn9gxjCvMLYc5PGXYlK2A==", "dev": true, "requires": { "bl": "^1.0.0", + "buffer-alloc": "^1.2.0", "end-of-stream": "^1.0.0", - "readable-stream": "^2.0.0", + "fs-constants": "^1.0.0", + "readable-stream": "^2.3.0", + "to-buffer": "^1.1.1", "xtend": "^4.0.0" } }, + "text-hex": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/text-hex/-/text-hex-1.0.0.tgz", + "integrity": "sha512-uuVGNWzgJ4yhRaNSiubPY7OjISw4sw4E5Uv0wbjp+OzcbmVU/rsT8ujgcXJhn9ypzsgr5vlzpPqP+MBBKcGvbg==" + }, "text-table": { "version": "0.2.0", "resolved": "https://registry.npmjs.org/text-table/-/text-table-0.2.0.tgz", @@ -5430,15 +6762,10 @@ }, "through": { "version": "2.3.8", - "resolved": "https://registry.npmjs.org/through/-/through-2.3.8.tgz", + "resolved": "http://registry.npmjs.org/through/-/through-2.3.8.tgz", "integrity": "sha1-DdTJ/6q8NXlgsbckEV1+Doai4fU=", "dev": true }, - "timed-out": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/timed-out/-/timed-out-4.0.1.tgz", - "integrity": "sha1-8y6srFoXW+ol1/q1Zas+2HQe9W8=" - }, "tiny-emitter": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/tiny-emitter/-/tiny-emitter-2.0.2.tgz", @@ -5450,11 +6777,11 @@ "integrity": "sha1-9PrTM0R7wLB9TcjpIJ2POaisd+g=" }, "tippy.js": { - "version": "2.0.9", - "resolved": "https://registry.npmjs.org/tippy.js/-/tippy.js-2.0.9.tgz", - "integrity": "sha512-HgsZTP+unnDMEL7hkbAb9Iv11JLjd8SI38a9oR/1GpAHwhQ3ndNkdSHW5QziS5D4/GrapfbehPUZrCvBGGKY7Q==", + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/tippy.js/-/tippy.js-3.3.0.tgz", + "integrity": "sha512-2gIQg57EFSCBqE97NZbakSkGBJF0GzdOhx/lneGQGMzJiJyvbpyKgNy4l4qofq0nEbXACl7C/jW/ErsdQa21aQ==", "requires": { - "popper.js": "^1.12.9" + "popper.js": "^1.14.6" } }, "tmp": { @@ -5466,12 +6793,68 @@ "os-tmpdir": "~1.0.2" } }, + "to-buffer": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/to-buffer/-/to-buffer-1.1.1.tgz", + "integrity": "sha512-lx9B5iv7msuFYE3dytT+KE5tap+rNYw+K4jVkb9R/asAb+pbBSM17jtunHplhBe6RRJdZx3Pn2Jph24O32mOVg==", + "dev": true + }, + "to-object-path": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/to-object-path/-/to-object-path-0.3.0.tgz", + "integrity": "sha1-KXWIt7Dn4KwI4E5nL4XB9JmeF68=", + "dev": true, + "requires": { + "kind-of": "^3.0.2" + } + }, + "to-regex": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/to-regex/-/to-regex-3.0.2.tgz", + "integrity": "sha512-FWtleNAtZ/Ki2qtqej2CXTOayOH9bHDQF+Q48VpWyDXjbYxA4Yz8iDB31zXOBUlOHHKidDbqGVrTUvQMPmBGBw==", + "dev": true, + "requires": { + "define-property": "^2.0.2", + "extend-shallow": "^3.0.2", + "regex-not": "^1.0.2", + "safe-regex": "^1.1.0" + } + }, + "to-regex-range": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-2.1.1.tgz", + "integrity": "sha1-fIDBe53+vlmeJzZ+DU3VWQFB2zg=", + "dev": true, + "requires": { + "is-number": "^3.0.0", + "repeat-string": "^1.6.1" + }, + "dependencies": { + "is-number": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-number/-/is-number-3.0.0.tgz", + "integrity": "sha1-JP1iAaR4LPUFYcgQJ2r8fRLXEZU=", + "dev": true, + "requires": { + "kind-of": "^3.0.2" + } + } + } + }, "tough-cookie": { - "version": "2.3.4", - "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-2.3.4.tgz", - "integrity": "sha512-TZ6TTfI5NtZnuyy/Kecv+CnoROnyXn2DN97LontgQpCwsX2XyLYCC0ENhYkehSOwAp8rTQKc/NUIF7BkQ5rKLA==", + "version": "2.4.3", + "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-2.4.3.tgz", + "integrity": "sha512-Q5srk/4vDM54WJsJio3XNn6K2sCG+CQ8G5Wz6bZhRZoAe/+TxjWB/GlFAnYEbkYVlON9FMk/fE3h2RLpPXo4lQ==", "requires": { + "psl": "^1.1.24", "punycode": "^1.4.1" + }, + "dependencies": { + "punycode": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/punycode/-/punycode-1.4.1.tgz", + "integrity": "sha1-wNWmOycYgArY4esPpSachN1BhF4=" + } } }, "trim": { @@ -5485,6 +6868,11 @@ "resolved": "https://registry.npmjs.org/trim-newlines/-/trim-newlines-1.0.0.tgz", "integrity": "sha1-WIeWa7WCpFA6QetST301ARgVphM=" }, + "triple-beam": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/triple-beam/-/triple-beam-1.3.0.tgz", + "integrity": "sha512-XrHUvV5HpdLmIj4uVMxHggLbFSZYIn7HEWsqePZcI50pco+MPqJ50wMGY794X7AOOhxOBAjbkqfAbEe/QMp2Lw==" + }, "true-case-path": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/true-case-path/-/true-case-path-1.0.3.tgz", @@ -5493,6 +6881,12 @@ "glob": "^7.1.2" } }, + "tslib": { + "version": "1.9.3", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.9.3.tgz", + "integrity": "sha512-4krF8scpejhaOgqzBEcGM7yDIEfi0/8+8zDRZhNZZ2kjmHJ4hv3zCbQWxoJGz1iw5U0Jl0nma13xzHXcncMavQ==", + "dev": true + }, "tunnel-agent": { "version": "0.6.0", "resolved": "https://registry.npmjs.org/tunnel-agent/-/tunnel-agent-0.6.0.tgz", @@ -5504,8 +6898,7 @@ "tweetnacl": { "version": "0.14.5", "resolved": "https://registry.npmjs.org/tweetnacl/-/tweetnacl-0.14.5.tgz", - "integrity": "sha1-WuaBd/GS1EViadEIr6k/+HQ/T2Q=", - "optional": true + "integrity": "sha1-WuaBd/GS1EViadEIr6k/+HQ/T2Q=" }, "type-check": { "version": "0.3.2", @@ -5517,21 +6910,50 @@ } }, "type-detect": { - "version": "4.0.7", - "resolved": "https://registry.npmjs.org/type-detect/-/type-detect-4.0.7.tgz", - "integrity": "sha512-4Rh17pAMVdMWzktddFhISRnUnFIStObtUMNGzDwlA6w/77bmGv3aBbRdCmQR6IjzfkTo9otnW+2K/cDRhKSxDA==", - "dev": true - }, - "typedarray": { - "version": "0.0.6", - "resolved": "https://registry.npmjs.org/typedarray/-/typedarray-0.0.6.tgz", - "integrity": "sha1-hnrHTjhkGHsdPUfZlqeOxciDB3c=", + "version": "4.0.8", + "resolved": "https://registry.npmjs.org/type-detect/-/type-detect-4.0.8.tgz", + "integrity": "sha512-0fr/mIH1dlO+x7TlcMy+bIDqKPsw/70tVyeHW787goQjhmqaZe10uwLujubK9q9Lg6Fiho1KUKDYz0Z7k7g5/g==", "dev": true }, "ua-parser-js": { - "version": "0.7.17", - "resolved": "https://registry.npmjs.org/ua-parser-js/-/ua-parser-js-0.7.17.tgz", - "integrity": "sha512-uRdSdu1oA1rncCQL7sCj8vSyZkgtL7faaw9Tc9rZ3mGgraQ7+Pdx7w5mnOSF3gw9ZNG6oc+KXfkon3bKuROm0g==" + "version": "0.7.19", + "resolved": "https://registry.npmjs.org/ua-parser-js/-/ua-parser-js-0.7.19.tgz", + "integrity": "sha512-T3PVJ6uz8i0HzPxOF9SWzWAlfN/DavlpQqepn22xgve/5QecC+XMCAtmUNnY7C9StehaV6exjUCI801lOI7QlQ==" + }, + "union-value": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/union-value/-/union-value-1.0.0.tgz", + "integrity": "sha1-XHHDTLW61dzr4+oM0IIHulqhrqQ=", + "dev": true, + "requires": { + "arr-union": "^3.1.0", + "get-value": "^2.0.6", + "is-extendable": "^0.1.1", + "set-value": "^0.4.3" + }, + "dependencies": { + "extend-shallow": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", + "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", + "dev": true, + "requires": { + "is-extendable": "^0.1.0" + } + }, + "set-value": { + "version": "0.4.3", + "resolved": "https://registry.npmjs.org/set-value/-/set-value-0.4.3.tgz", + "integrity": "sha1-fbCPnT0i3H945Trzw79GZuzfzPE=", + "dev": true, + "requires": { + "extend-shallow": "^2.0.1", + "is-extendable": "^0.1.1", + "is-plain-object": "^2.0.1", + "to-object-path": "^0.3.0" + } + } + } }, "uniq": { "version": "1.0.1", @@ -5539,11 +6961,65 @@ "integrity": "sha1-sxxa6CVIRKOoKBVBzisEuGWnNP8=" }, "universalify": { - "version": "0.1.1", - "resolved": "https://registry.npmjs.org/universalify/-/universalify-0.1.1.tgz", - "integrity": "sha1-+nG63UQ3r0wUiEHjs7Fl+enlkLc=", + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/universalify/-/universalify-0.1.2.tgz", + "integrity": "sha512-rBJeI5CXAlmy1pV+617WB9J63U6XcazHHF2f2dbJix4XzpUF0RS3Zbj0FGIOCAva5P/d/GBOYaACQ1w+0azUkg==", "dev": true }, + "unset-value": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/unset-value/-/unset-value-1.0.0.tgz", + "integrity": "sha1-g3aHP30jNRef+x5vw6jtDfyKtVk=", + "dev": true, + "requires": { + "has-value": "^0.3.1", + "isobject": "^3.0.0" + }, + "dependencies": { + "has-value": { + "version": "0.3.1", + "resolved": "https://registry.npmjs.org/has-value/-/has-value-0.3.1.tgz", + "integrity": "sha1-ex9YutpiyoJ+wKIHgCVlSEWZXh8=", + "dev": true, + "requires": { + "get-value": "^2.0.3", + "has-values": "^0.1.4", + "isobject": "^2.0.0" + }, + "dependencies": { + "isobject": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/isobject/-/isobject-2.1.0.tgz", + "integrity": "sha1-8GVWEJaj8dou9GJy+BXIQNh+DIk=", + "dev": true, + "requires": { + "isarray": "1.0.0" + } + } + } + }, + "has-values": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/has-values/-/has-values-0.1.4.tgz", + "integrity": "sha1-bWHeldkd/Km5oCCJrThL/49it3E=", + "dev": true + }, + "isobject": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/isobject/-/isobject-3.0.1.tgz", + "integrity": "sha1-TkMekrEalzFjaqH5yNHMvP2reN8=", + "dev": true + } + } + }, + "uri-js": { + "version": "4.2.2", + "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.2.2.tgz", + "integrity": "sha512-KY9Frmirql91X2Qgjry0Wd4Y+YTdrdZheS8TFwvkbLWf/G5KNJDCh6pKL5OZctEW4+0Baa5idK2ZQuELRwPznQ==", + "requires": { + "punycode": "^2.1.0" + } + }, "urix": { "version": "0.1.0", "resolved": "https://registry.npmjs.org/urix/-/urix-0.1.0.tgz", @@ -5568,14 +7044,6 @@ } } }, - "url-parse-lax": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/url-parse-lax/-/url-parse-lax-1.0.0.tgz", - "integrity": "sha1-evjzA2Rem9eaJy56FKxovAYJ2nM=", - "requires": { - "prepend-http": "^1.0.1" - } - }, "url-regex": { "version": "3.2.0", "resolved": "https://registry.npmjs.org/url-regex/-/url-regex-3.2.0.tgz", @@ -5585,10 +7053,11 @@ "ip-regex": "^1.0.1" } }, - "url-to-options": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/url-to-options/-/url-to-options-1.0.1.tgz", - "integrity": "sha1-FQWgOiiaSMvXpDTvuu7FBV9WM6k=" + "use": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/use/-/use-3.1.1.tgz", + "integrity": "sha512-cwESVXlO3url9YWlFW/TA9cshCEhtu7IKJ/p5soJ/gGpj7vbvFrAY/eIioQ6Dw23KjZhYgiIo8HOs1nQ2vr/oQ==", + "dev": true }, "util-deprecate": { "version": "1.0.2", @@ -5609,6 +7078,11 @@ "spdx-expression-parse": "^3.0.0" } }, + "value-equal": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/value-equal/-/value-equal-0.4.0.tgz", + "integrity": "sha512-x+cYdNnaA3CxvMaTX0INdTCN8m8aF2uY9BvEqmxuYp8bL09cs/kWVQPVGcA35fMktdOsP69IgU7wFj/61dJHEw==" + }, "verror": { "version": "1.10.0", "resolved": "https://registry.npmjs.org/verror/-/verror-1.10.0.tgz", @@ -5628,40 +7102,11 @@ } }, "wdio-dot-reporter": { - "version": "0.0.9", - "resolved": "https://registry.npmjs.org/wdio-dot-reporter/-/wdio-dot-reporter-0.0.9.tgz", - "integrity": "sha1-kpsq2v1J1rBTT9oGjocxm0fjj+U=", + "version": "0.0.10", + "resolved": "https://registry.npmjs.org/wdio-dot-reporter/-/wdio-dot-reporter-0.0.10.tgz", + "integrity": "sha512-A0TCk2JdZEn3M1DSG9YYbNRcGdx/YRw19lTiRpgwzH4qqWkO/oRDZRmi3Snn4L2j54KKTfPalBhlOtc8fojVgg==", "dev": true }, - "wdio-jasmine-framework": { - "version": "0.3.2", - "resolved": "https://registry.npmjs.org/wdio-jasmine-framework/-/wdio-jasmine-framework-0.3.2.tgz", - "integrity": "sha1-l4QSFCEqm1f3DZB3Tw2bR759+YE=", - "dev": true, - "requires": { - "babel-runtime": "6.25.0", - "jasmine": "^2.5.3", - "wdio-sync": "0.7.0" - }, - "dependencies": { - "babel-runtime": { - "version": "6.25.0", - "resolved": "https://registry.npmjs.org/babel-runtime/-/babel-runtime-6.25.0.tgz", - "integrity": "sha1-M7mOql1IK7AajRqmtDetKwGuxBw=", - "dev": true, - "requires": { - "core-js": "^2.4.0", - "regenerator-runtime": "^0.10.0" - } - }, - "regenerator-runtime": { - "version": "0.10.5", - "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.10.5.tgz", - "integrity": "sha1-M2w+/BIgrc7dosn6tntaeVWjNlg=", - "dev": true - } - } - }, "wdio-junit-reporter": { "version": "0.4.4", "resolved": "https://registry.npmjs.org/wdio-junit-reporter/-/wdio-junit-reporter-0.4.4.tgz", @@ -5671,26 +7116,6 @@ "junit-report-builder": "~1.3.0", "lodash.get": "^4.4.2", "mkdirp": "~0.5.1" - }, - "dependencies": { - "junit-report-builder": { - "version": "1.3.1", - "resolved": "https://registry.npmjs.org/junit-report-builder/-/junit-report-builder-1.3.1.tgz", - "integrity": "sha512-KTueBpPsmjfiyrAxxhKlEMwXb3aRmDHG5tRYwtRF3ujLQ7/e/5MH3b2p9ND2P84rU8z5dQq40vWJv6TtEdS16Q==", - "dev": true, - "requires": { - "date-format": "0.0.2", - "lodash": "^4.17.10", - "mkdirp": "^0.5.0", - "xmlbuilder": "^10.0.0" - } - }, - "xmlbuilder": { - "version": "10.0.0", - "resolved": "https://registry.npmjs.org/xmlbuilder/-/xmlbuilder-10.0.0.tgz", - "integrity": "sha512-7RWHlmF1yU/E++BZkRQTEv8ZFAhZ+YHINUAxiZ5LQTKRQq//igpiY8rh7dJqPzgb/IzeC5jH9P7OaCERfM9DwA==", - "dev": true - } } }, "wdio-screenshot": { @@ -5711,74 +7136,14 @@ } }, "wdio-spec-reporter": { - "version": "0.1.3", - "resolved": "https://registry.npmjs.org/wdio-spec-reporter/-/wdio-spec-reporter-0.1.3.tgz", - "integrity": "sha512-zPLAmaq255UVcN3Z9AKuuY4prwwpkiQ3j0Ne0zJWl9/lZamfKN0k3DQslO0M/YCN8jPfHfaFJvP/VHas7iPdkQ==", + "version": "0.1.5", + "resolved": "https://registry.npmjs.org/wdio-spec-reporter/-/wdio-spec-reporter-0.1.5.tgz", + "integrity": "sha512-MqvgTow8hFwhFT47q67JwyJyeynKodGRQCxF7ijKPGfsaG1NLssbXYc0JhiL7SiAyxnQxII0UxzTCd3I6sEdkg==", "dev": true, "requires": { "babel-runtime": "~6.26.0", "chalk": "^2.3.0", - "humanize-duration": "~3.12.0" - }, - "dependencies": { - "ansi-styles": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.0.tgz", - "integrity": "sha512-NnSOmMEYtVR2JVMIGTzynRkkaxtiq1xnFBcdQD/DnNCYPoEPsVJhM98BDyaoNOQIi7p4okdi3E27eN7GQbsUug==", - "dev": true, - "requires": { - "color-convert": "^1.9.0" - } - }, - "chalk": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.3.0.tgz", - "integrity": "sha512-Az5zJR2CBujap2rqXGaJKaPHyJ0IrUimvYNX+ncCy8PJP4ltOGTrHUIo097ZaL2zMeKYpiCdqDvS6zdrTFok3Q==", - "dev": true, - "requires": { - "ansi-styles": "^3.1.0", - "escape-string-regexp": "^1.0.5", - "supports-color": "^4.0.0" - } - }, - "supports-color": { - "version": "4.5.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-4.5.0.tgz", - "integrity": "sha1-vnoN5ITexcXN34s9WRJQRJEvY1s=", - "dev": true, - "requires": { - "has-flag": "^2.0.0" - } - } - } - }, - "wdio-sync": { - "version": "0.7.0", - "resolved": "https://registry.npmjs.org/wdio-sync/-/wdio-sync-0.7.0.tgz", - "integrity": "sha1-L7B9JQEh3IH1Y1MWVCw8SaEiAWg=", - "dev": true, - "requires": { - "babel-runtime": "6.25.0", - "fibers": "~2.0.0", - "object.assign": "^4.0.3" - }, - "dependencies": { - "babel-runtime": { - "version": "6.25.0", - "resolved": "https://registry.npmjs.org/babel-runtime/-/babel-runtime-6.25.0.tgz", - "integrity": "sha1-M7mOql1IK7AajRqmtDetKwGuxBw=", - "dev": true, - "requires": { - "core-js": "^2.4.0", - "regenerator-runtime": "^0.10.0" - } - }, - "regenerator-runtime": { - "version": "0.10.5", - "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.10.5.tgz", - "integrity": "sha1-M2w+/BIgrc7dosn6tntaeVWjNlg=", - "dev": true - } + "humanize-duration": "~3.15.0" } }, "wdio-visual-regression-service": { @@ -5798,36 +7163,84 @@ } }, "webdriver-manager": { - "version": "12.0.6", - "resolved": "https://registry.npmjs.org/webdriver-manager/-/webdriver-manager-12.0.6.tgz", - "integrity": "sha1-PfGkgZdwELTL+MnYXHpXeCjA5ws=", + "version": "12.1.0", + "resolved": "https://registry.npmjs.org/webdriver-manager/-/webdriver-manager-12.1.0.tgz", + "integrity": "sha512-oEc5fmkpz6Yh6udhwir5m0eN5mgRPq9P/NU5YWuT3Up5slt6Zz+znhLU7q4+8rwCZz/Qq3Fgpr/4oao7NPCm2A==", "dev": true, "requires": { - "adm-zip": "^0.4.7", + "adm-zip": "^0.4.9", "chalk": "^1.1.1", "del": "^2.2.0", "glob": "^7.0.3", "ini": "^1.3.4", "minimist": "^1.2.0", "q": "^1.4.1", - "request": "^2.78.0", + "request": "^2.87.0", "rimraf": "^2.5.2", "semver": "^5.3.0", "xml2js": "^0.4.17" }, "dependencies": { - "minimist": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.0.tgz", - "integrity": "sha1-o1AIsg9BOD7sH7kU9M1d95omQoQ=", + "ansi-styles": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-2.2.1.tgz", + "integrity": "sha1-tDLdM1i2NM914eRmQ2gkBTPB3b4=", + "dev": true + }, + "chalk": { + "version": "1.1.3", + "resolved": "http://registry.npmjs.org/chalk/-/chalk-1.1.3.tgz", + "integrity": "sha1-qBFcVeSnAv5NFQq9OHKCKn4J/Jg=", + "dev": true, + "requires": { + "ansi-styles": "^2.2.1", + "escape-string-regexp": "^1.0.2", + "has-ansi": "^2.0.0", + "strip-ansi": "^3.0.0", + "supports-color": "^2.0.0" + } + }, + "del": { + "version": "2.2.2", + "resolved": "https://registry.npmjs.org/del/-/del-2.2.2.tgz", + "integrity": "sha1-wSyYHQZ4RshLyvhiz/kw2Qf/0ag=", + "dev": true, + "requires": { + "globby": "^5.0.0", + "is-path-cwd": "^1.0.0", + "is-path-in-cwd": "^1.0.0", + "object-assign": "^4.0.1", + "pify": "^2.0.0", + "pinkie-promise": "^2.0.0", + "rimraf": "^2.2.8" + } + }, + "globby": { + "version": "5.0.0", + "resolved": "http://registry.npmjs.org/globby/-/globby-5.0.0.tgz", + "integrity": "sha1-69hGZ8oNuzMLmbz8aOrCvFQ3Dg0=", + "dev": true, + "requires": { + "array-union": "^1.0.1", + "arrify": "^1.0.0", + "glob": "^7.0.3", + "object-assign": "^4.0.1", + "pify": "^2.0.0", + "pinkie-promise": "^2.0.0" + } + }, + "supports-color": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-2.0.0.tgz", + "integrity": "sha1-U10EXOa2Nj+kARcIRimZXp3zJMc=", "dev": true } } }, "webdriverio": { - "version": "4.13.1", - "resolved": "https://registry.npmjs.org/webdriverio/-/webdriverio-4.13.1.tgz", - "integrity": "sha1-Yk70ylafPJpejpsRMCtEMe2h+4o=", + "version": "4.14.1", + "resolved": "https://registry.npmjs.org/webdriverio/-/webdriverio-4.14.1.tgz", + "integrity": "sha512-Gjb5ft6JtO7WdoZifedeM6U941UZi03IlG0t3Xq9M9SxSm6FuyqMEmNZ4HI3UcBRkSbWxdOWGAvpFShYxVr7iA==", "dev": true, "requires": { "archiver": "~2.1.0", @@ -5838,6 +7251,7 @@ "ejs": "~2.5.6", "gaze": "~1.1.2", "glob": "~7.1.1", + "grapheme-splitter": "^1.0.2", "inquirer": "~3.3.0", "json-stringify-safe": "~5.0.1", "mkdirp": "~0.5.1", @@ -5845,7 +7259,7 @@ "optimist": "~0.6.1", "q": "~1.5.0", "request": "^2.83.0", - "rgb2hex": "~0.1.4", + "rgb2hex": "^0.1.9", "safe-buffer": "~5.1.1", "supports-color": "~5.0.0", "url": "~0.11.0", @@ -5853,42 +7267,81 @@ "wgxpath": "~1.0.0" }, "dependencies": { - "archiver": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/archiver/-/archiver-2.1.1.tgz", - "integrity": "sha1-/2YrSnggFJSj7lRNOjP+dJZQnrw=", + "ansi-regex": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-3.0.0.tgz", + "integrity": "sha1-7QMXwyIGT3lGbAKWa922Bas32Zg=", + "dev": true + }, + "chardet": { + "version": "0.4.2", + "resolved": "https://registry.npmjs.org/chardet/-/chardet-0.4.2.tgz", + "integrity": "sha1-tUc7M9yXxCTl2Y3IfVXU2KKci/I=", + "dev": true + }, + "deepmerge": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/deepmerge/-/deepmerge-2.0.1.tgz", + "integrity": "sha512-VIPwiMJqJ13ZQfaCsIFnp5Me9tnjURiaIFxfz7EH0Ci0dTSQpZtSLrqOicXqEd/z2r+z+Klk9GzmnRsgpgbOsQ==", + "dev": true + }, + "external-editor": { + "version": "2.2.0", + "resolved": "http://registry.npmjs.org/external-editor/-/external-editor-2.2.0.tgz", + "integrity": "sha512-bSn6gvGxKt+b7+6TKEv1ZycHleA7aHhRHyAqJyp5pbUFuYYNIzpZnQDk7AsYckyWdEnTeAnay0aCy2aV6iTk9A==", "dev": true, "requires": { - "archiver-utils": "^1.3.0", - "async": "^2.0.0", - "buffer-crc32": "^0.2.1", - "glob": "^7.0.0", - "lodash": "^4.8.0", - "readable-stream": "^2.0.0", - "tar-stream": "^1.5.0", - "zip-stream": "^1.2.0" + "chardet": "^0.4.0", + "iconv-lite": "^0.4.17", + "tmp": "^0.0.33" } }, - "async": { - "version": "2.6.1", - "resolved": "https://registry.npmjs.org/async/-/async-2.6.1.tgz", - "integrity": "sha512-fNEiL2+AZt6AlAw/29Cr0UDe4sRAHCpEHh54WMz+Bb7QfNcFw4h3loofyJpLeQs4Yx7yuqu/2dLgM5hKOs6HlQ==", + "inquirer": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/inquirer/-/inquirer-3.3.0.tgz", + "integrity": "sha512-h+xtnyk4EwKvFWHrUYsWErEVR+igKtLdchu+o0Z1RL7VU/jVMFbYir2bp6bAj8efFNxWqHX0dIss6fJQ+/+qeQ==", "dev": true, "requires": { - "lodash": "^4.17.10" + "ansi-escapes": "^3.0.0", + "chalk": "^2.0.0", + "cli-cursor": "^2.1.0", + "cli-width": "^2.0.0", + "external-editor": "^2.0.4", + "figures": "^2.0.0", + "lodash": "^4.3.0", + "mute-stream": "0.0.7", + "run-async": "^2.2.0", + "rx-lite": "^4.0.8", + "rx-lite-aggregates": "^4.0.8", + "string-width": "^2.1.0", + "strip-ansi": "^4.0.0", + "through": "^2.3.6" } }, - "deepmerge": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/deepmerge/-/deepmerge-2.0.1.tgz", - "integrity": "sha512-VIPwiMJqJ13ZQfaCsIFnp5Me9tnjURiaIFxfz7EH0Ci0dTSQpZtSLrqOicXqEd/z2r+z+Klk9GzmnRsgpgbOsQ==", + "is-fullwidth-code-point": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-2.0.0.tgz", + "integrity": "sha1-o7MKXE8ZkYMWeqq5O+764937ZU8=", "dev": true }, - "rgb2hex": { - "version": "0.1.9", - "resolved": "https://registry.npmjs.org/rgb2hex/-/rgb2hex-0.1.9.tgz", - "integrity": "sha512-32iuQzhOjyT+cv9aAFRBJ19JgHwzQwbjUhH3Fj2sWW2EEGAW8fpFrDFP5ndoKDxJaLO06x1hE3kyuIFrUQtybQ==", - "dev": true + "string-width": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-2.1.1.tgz", + "integrity": "sha512-nOqH59deCq9SRHlxq1Aw85Jnt4w6KvLKqWVik6oA9ZklXLNIOlqg4F2yrT1MVaTjAqvVwdfeZ7w7aCvJD7ugkw==", + "dev": true, + "requires": { + "is-fullwidth-code-point": "^2.0.0", + "strip-ansi": "^4.0.0" + } + }, + "strip-ansi": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-4.0.0.tgz", + "integrity": "sha1-qEeQIusaw2iocTibY1JixQXuNo8=", + "dev": true, + "requires": { + "ansi-regex": "^3.0.0" + } }, "supports-color": { "version": "5.0.1", @@ -5908,9 +7361,9 @@ "dev": true }, "whatwg-fetch": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/whatwg-fetch/-/whatwg-fetch-2.0.3.tgz", - "integrity": "sha1-nITsLc9oGH/wC8ZOEnS0QhduHIQ=" + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/whatwg-fetch/-/whatwg-fetch-3.0.0.tgz", + "integrity": "sha512-9GSJUgz1D4MfyKU7KRqwOjXCXTqWdFNvEr7eUBYchQiVc744mqK/MzXPNR2WsPkmkOa4ywfg8C2n8h+13Bey1Q==" }, "which": { "version": "1.3.1", @@ -5934,16 +7387,28 @@ } }, "winston": { - "version": "2.4.4", - "resolved": "https://registry.npmjs.org/winston/-/winston-2.4.4.tgz", - "integrity": "sha512-NBo2Pepn4hK4V01UfcWcDlmiVTs7VTB1h7bgnB0rgP146bYhMxX0ypCz3lBOfNxCO4Zuek7yeT+y/zM1OfMw4Q==", + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/winston/-/winston-3.1.0.tgz", + "integrity": "sha512-FsQfEE+8YIEeuZEYhHDk5cILo1HOcWkGwvoidLrDgPog0r4bser1lEIOco2dN9zpDJ1M88hfDgZvxe5z4xNcwg==", + "requires": { + "async": "^2.6.0", + "diagnostics": "^1.1.1", + "is-stream": "^1.1.0", + "logform": "^1.9.1", + "one-time": "0.0.4", + "readable-stream": "^2.3.6", + "stack-trace": "0.0.x", + "triple-beam": "^1.3.0", + "winston-transport": "^4.2.0" + } + }, + "winston-transport": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/winston-transport/-/winston-transport-4.2.0.tgz", + "integrity": "sha512-0R1bvFqxSlK/ZKTH86nymOuKv/cT1PQBMuDdA7k7f0S9fM44dNH6bXnuxwXPrN8lefJgtZq08BKdyZ0DZIy/rg==", "requires": { - "async": "~1.0.0", - "colors": "1.0.x", - "cycle": "1.0.x", - "eyes": "0.1.x", - "isstream": "0.1.x", - "stack-trace": "0.0.x" + "readable-stream": "^2.3.6", + "triple-beam": "^1.2.0" } }, "wordwrap": { @@ -5954,7 +7419,7 @@ }, "wrap-ansi": { "version": "2.1.0", - "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-2.1.0.tgz", + "resolved": "http://registry.npmjs.org/wrap-ansi/-/wrap-ansi-2.1.0.tgz", "integrity": "sha1-2Pw9KE3QV5T+hJc8rs3Rz4JP3YU=", "requires": { "string-width": "^1.0.1", @@ -5976,9 +7441,9 @@ } }, "xhr": { - "version": "2.4.1", - "resolved": "https://registry.npmjs.org/xhr/-/xhr-2.4.1.tgz", - "integrity": "sha512-pAIU5vBr9Hiy5cpFIbPnwf0C18ZF86DBsZKrlsf87N5De/JbA6RJ83UP/cv+aljl4S40iRVMqP4pr4sF9Dnj0A==", + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/xhr/-/xhr-2.5.0.tgz", + "integrity": "sha512-4nlO/14t3BNUZRXIXfXe+3N6w3s1KoxcJUUURctd64BLRe67E4gRwp4PjywtDY72fXpZ1y6Ch0VZQRY/gMPzzQ==", "dev": true, "requires": { "global": "~4.3.0", @@ -6001,12 +7466,20 @@ "requires": { "sax": ">=0.6.0", "xmlbuilder": "~9.0.1" + }, + "dependencies": { + "xmlbuilder": { + "version": "9.0.7", + "resolved": "http://registry.npmjs.org/xmlbuilder/-/xmlbuilder-9.0.7.tgz", + "integrity": "sha1-Ey7mPS7FVlxVfiD0wi35rKaGsQ0=", + "dev": true + } } }, "xmlbuilder": { - "version": "9.0.4", - "resolved": "https://registry.npmjs.org/xmlbuilder/-/xmlbuilder-9.0.4.tgz", - "integrity": "sha1-UZy0ymhtAFqEINNJbz8MruzKWA8=", + "version": "10.1.1", + "resolved": "https://registry.npmjs.org/xmlbuilder/-/xmlbuilder-10.1.1.tgz", + "integrity": "sha512-OyzrcFLL/nb6fMGHbiRDuPup9ljBycsdCypwuyg5AAHvyWzGfChJpCXMG88AGTIMFhGZ9RccFN1e6lhg3hkwKg==", "dev": true }, "xtend": { @@ -6026,9 +7499,9 @@ "integrity": "sha1-HBH5IY8HYImkfdUS+TxmmaaoHVI=" }, "yaml": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/yaml/-/yaml-1.0.0.tgz", - "integrity": "sha512-HLMg8IQRQLLPZ/tVtR0j5ShAh4HJKt8soYsu0Fn3Y5eoIFJoh1cs1mvvOnRD236mjeBDarlk5Ng/b/IHQs+5Rg==" + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/yaml/-/yaml-1.1.0.tgz", + "integrity": "sha512-MD5Ptelnnjfj/w4UqdNguD9Ipzm3ws6bNiYkGkl4lkfGMU1V7QYyHkRh28EiHPdprSvV+xAhvLJ6ifyALYw7wA==" }, "yargs": { "version": "7.1.0", diff --git a/bigbluebutton-html5/package.json b/bigbluebutton-html5/package.json index 05ceb55db49aebf1c6f4c675e7886ed191003dbf..f7eb4818d2237c99053d8594516bb1a0be2eb0be 100755 --- a/bigbluebutton-html5/package.json +++ b/bigbluebutton-html5/package.json @@ -9,12 +9,10 @@ "generate-refs-visual-regression-desktop": "rm -rf tests/webdriverio/screenshots; npm run test-visual-regression-desktop", "start:prod": "meteor reset && ROOT_URL=http://127.0.0.1/html5client NODE_ENV=production meteor --production", "start:dev": "ROOT_URL=http://127.0.0.1/html5client NODE_ENV=development meteor", - "test": "wdio ./tests/webdriverio/wdio.conf.js", - "lint": "eslint . --ext .jsx,.js", - "precommit": "lint-staged" + "test": "jest", + "lint": "eslint . --ext .jsx,.js" }, "lint-staged": { - "gitDir": "../", "linters": { "*.{js,jsx}": [ "eslint --fix", @@ -28,67 +26,68 @@ "but Meteor 1.6.0.1 doesn't see it there for some reason", "need to investigate" ], - "@browser-bunyan/server-stream": "^1.3.0", - "autoprefixer": "~7.1.6", + "@babel/runtime": "^7.2.0", + "@browser-bunyan/server-stream": "^1.5.0", + "autoprefixer": "~9.3.1", "babel-plugin-react-remove-properties": "~0.2.5", "babel-runtime": "~6.26.0", - "browser-bunyan": "^1.4.0", + "browser-bunyan": "^1.5.0", "browser-detect": "^0.2.28", "classnames": "^2.2.6", - "clipboard": "~1.7.1", - "core-js": "^2.5.7", - "eventemitter2": "~4.1.2", - "flat": "~4.0.0", - "history": "~3.3.0", - "immutability-helper": "~2.4.0", + "clipboard": "^2.0.4", + "core-js": "^2.6.0", + "eventemitter2": "~5.0.1", + "fibers": "^3.1.1", + "flat": "~4.1.0", + "history": "~4.7.2", + "immutability-helper": "~2.8.1", "langmap": "0.0.16", - "lodash": "~4.17.10", + "lodash": "^4.17.11", "makeup-screenreader-trap": "0.0.5", - "meteor-node-stubs": "^0.3.3", - "node-sass": "^4.9.3", - "postcss-nested": "2.1.2", - "probe-image-size": "~3.1.0", + "meteor-node-stubs": "^0.4.1", + "node-sass": "^4.10.0", + "postcss-nested": "4.1.0", + "probe-image-size": "~4.0.0", "prop-types": "^15.6.2", - "re-resizable": "^4.8.1", - "react": "~16.0.0", - "react-autosize-textarea": "~0.4.9", + "re-resizable": "^4.10.0", + "react": "^16.6.3", + "react-autosize-textarea": "^5.0.1", "react-color": "~2.14.1", - "react-dom": "^16.0.1", - "react-dropzone": "^4.2.13", - "react-intl": "~2.4.0", - "react-modal": "~3.0.4", + "react-dom": "^16.6.3", + "react-dropzone": "^7.0.1", + "react-intl": "~2.7.2", + "react-modal": "~3.6.1", "react-render-in-browser": "^1.0.0", - "react-tabs": "~2.1.0", - "react-toastify": "~2.1.2", + "react-tabs": "~2.3.0", + "react-toastify": "^4.4.3", "react-toggle": "~4.0.2", - "react-transition-group": "~2.2.1", - "reconnecting-websocket": "~v3.2.2", + "react-transition-group": "~2.5.0", + "reconnecting-websocket": "~v4.1.10", "redis": "~2.8.0", "string-hash": "~1.1.3", - "tippy.js": "~2.0.2", - "winston": "^2.4.4", - "yaml": "^1.0.0" + "tippy.js": "^3.1.3", + "winston": "^3.1.0", + "yaml": "^1.0.3" }, "devDependencies": { - "chai": "~4.1.2", - "eslint": "~4.9.0", - "eslint-config-airbnb": "~16.1.0", - "eslint-config-airbnb-base": "~12.1.0", - "eslint-plugin-import": "~2.8.0", - "eslint-plugin-jsx-a11y": "~6.0.2", - "eslint-plugin-react": "~7.4.0", - "husky": "~0.14.3", - "lint-staged": "~4.3.0", - "postcss-modules-extract-imports": "1.1.0", + "chai": "~4.2.0", + "eslint": "~5.8.0", + "eslint-config-airbnb": "~17.1.0", + "eslint-config-airbnb-base": "~13.1.0", + "eslint-plugin-import": "~2.14.0", + "eslint-plugin-jsx-a11y": "~6.1.2", + "eslint-plugin-react": "~7.11.1", + "husky": "^1.1.4", + "lint-staged": "^8.0.5", + "postcss-modules-extract-imports": "2.0.0", "postcss-modules-local-by-default": "1.2.0", "postcss-modules-scope": "1.1.0", "postcss-modules-values": "1.3.0", - "wdio-jasmine-framework": "~0.3.2", "wdio-junit-reporter": "~0.4.4", - "wdio-spec-reporter": "~0.1.2", + "wdio-spec-reporter": "^0.1.5", "wdio-visual-regression-service": "~0.9.0", - "webdriver-manager": "~12.0.6", - "webdriverio": "~4.13.1" + "webdriver-manager": "~12.1.0", + "webdriverio": "^4.14.1" }, "cssModules": { "cssClassNamingConvention": { @@ -108,5 +107,10 @@ "repository": { "type": "git", "url": "https://github.com/bigbluebutton/bigbluebutton.git" + }, + "husky": { + "hooks": { + "pre-commit": "lint-staged" + } } } diff --git a/bigbluebutton-html5/private/config/settings.yml b/bigbluebutton-html5/private/config/settings.yml old mode 100644 new mode 100755 index 006e607b622227623234808f5f7d9e17dda6fbea..057222f99fcfca8468fa2fb72a78fa87d9dcb90d --- a/bigbluebutton-html5/private/config/settings.yml +++ b/bigbluebutton-html5/private/config/settings.yml @@ -9,7 +9,7 @@ public: skipCheck: false clientTitle: BigBlueButton appName: BigBlueButton HTML5 Client - bbbServerVersion: 2.0-rc + bbbServerVersion: 2.2-dev copyright: "©2018 BigBlueButton Inc." html5ClientBuild: HTML5_CLIENT_VERSION lockOnJoin: true @@ -75,7 +75,6 @@ public: branding: displayBrandingArea: false allowHTML5Moderator: true - allowModeratorToUnmuteAudio: true httpsConnection: false connectionTimeout: 60000 showHelpButton: true @@ -99,6 +98,9 @@ public: enableVideoStats: false enableListenOnly: false autoShareWebcam: false + allowOutsideCommands: + toggleRecording: false + toggleSelfVoice: false poll: max_custom: 5 chat: @@ -241,17 +243,9 @@ public: - pencil - hand clientLog: - server: - enabled: true - level: info - console: - enabled: false - level: debug - external: - enabled: false - level: info - url: https://LOG_HOST/html5Log - method: POST + server: { enabled: true, level: info } + console: { enabled: true, level: debug } + external: { enabled: false, level: info, url: https://LOG_HOST/html5Log, method: POST } private: app: captionsChunkLength: 1000 diff --git a/bigbluebutton-html5/private/locales/en.json b/bigbluebutton-html5/private/locales/en.json index 9e831b4184c5879575be99b361f24396e6a7d0f8..25ebcb30810a54087d594b97c5ed13adf585ef13 100755 --- a/bigbluebutton-html5/private/locales/en.json +++ b/bigbluebutton-html5/private/locales/en.json @@ -45,7 +45,9 @@ "app.userList.userOptions.clearAllLabel": "Clear all status icons", "app.userList.userOptions.clearAllDesc": "Clears all status icons from users", "app.userList.userOptions.muteAllExceptPresenterLabel": "Mute all users except presenter", - "app.userList.userOptions.muteAllExceptPresenterDesc": "Mute all users in the meeting except the presenter", + "app.userList.userOptions.muteAllExceptPresenterDesc": "Mutes all users in the meeting except the presenter", + "app.userList.userOptions.unmuteAllLabel": "Turn Off Meeting Mute", + "app.userList.userOptions.unmuteAllDesc": "Unmutes the meeting", "app.userList.userOptions.lockViewersLabel": "Lock viewers", "app.userList.userOptions.lockViewersDesc": "Lock certain functionalities for attendees of the meeting", "app.media.label": "Media", @@ -236,7 +238,7 @@ "app.actionsBar.actionsDropdown.stopDesktopShareDesc": "Stop sharing your screen with", "app.actionsBar.actionsDropdown.startRecording": "Start recording", "app.actionsBar.actionsDropdown.stopRecording": "Stop recording", - "app.actionsBar.actionsDropdown.pollBtnLabel": "Start Poll", + "app.actionsBar.actionsDropdown.pollBtnLabel": "Start a poll", "app.actionsBar.actionsDropdown.pollBtnDesc": "Toggles poll pane", "app.actionsBar.actionsDropdown.createBreakoutRoom": "Create breakout rooms", "app.actionsBar.actionsDropdown.createBreakoutRoomDesc": "create breakouts for split the current meeting ", @@ -357,6 +359,24 @@ "app.shortcut-help.closePrivateChat": "Close Private Chat", "app.shortcut-help.openActions": "Open Actions Menu", "app.shortcut-help.openStatus": "Open Status Menu", + "app.lock-viewers.title": "Lock Viewers", + "app.lock-viewers.description": "These options enable you to restrict certain features available to viewers, such as locking out their ability to use private chat. (These restrictions do not apply to moderators)", + "app.lock-viewers.featuresLable": "Feature", + "app.lock-viewers.lockStatusLabel": "Locked Status", + "app.lock-viewers.webcamLabel": "Webcam", + "app.lock-viewers.otherViewersWebcamLabel": "See other viewers webcams", + "app.lock-viewers.microphoneLable": "Microphone", + "app.lock-viewers.PublicChatLabel": "Public Chat", + "app.lock-viewers.PrivateChatLable": "Private Chat", + "app.lock-viewers.Layout": "Layout", + "app.videoPreview.cameraLabel": "Camera", + "app.videoPreview.cancelLabel": "Cancel", + "app.videoPreview.closeLabel": "Close", + "app.videoPreview.startSharingLabel": "Start Sharing", + "app.videoPreview.webcamOptionLabel": "Choose webcam", + "app.videoPreview.webcamPreviewLabel": "Webcam preview", + "app.videoPreview.webcamSettingsTitle": "Webcam Settings", + "app.videoPreview.webcamNotFoundLabel": "Webcam not found", "app.video.joinVideo": "Share Webcam", "app.video.leaveVideo": "Unshare Webcam", "app.video.iceCandidateError": "Error on adding ice candidate", @@ -455,5 +475,6 @@ "app.createBreakoutRoom.nextLabel": "Next", "app.createBreakoutRoom.addParticipantLabel": "+ Add participant", "app.createBreakoutRoom.freeJoin": "Allow users to choose a breakout room to join", + "app.createBreakoutRoom.leastOneWarnBreakout": "You must place at least one user in a breakout room.", "app.createBreakoutRoom.modalDesc": "Complete the steps below to create rooms in your session, To add participants to a room." } diff --git a/bigbluebutton-html5/test-html5.sh b/bigbluebutton-html5/test-html5.sh index d96e5c3039636dc2588a10da6607eda83e006405..7d25172ac11e50ef1a0a0867fe1de17f4682ff82 100755 --- a/bigbluebutton-html5/test-html5.sh +++ b/bigbluebutton-html5/test-html5.sh @@ -22,7 +22,9 @@ echo $BBB_SHARED_SECRET # Run tests if [ $status -eq 0 ]; then - npm test + # runInBand will force jest to run in a single thread + # https://jestjs.io/docs/en/troubleshooting#tests-are-extremely-slow-on-docker-and-or-continuous-integration-ci-server + npm test -- --runInBand fi # Stop Docker container diff --git a/bigbluebutton-html5/tests/puppeteer/.gitignore b/bigbluebutton-html5/tests/puppeteer/.gitignore index 684b68e5b84ca5eec207b1e3763a28ad6ecbed6d..ff306fbfbb436fce37c8a62d0fc92d8c38c72f04 100644 --- a/bigbluebutton-html5/tests/puppeteer/.gitignore +++ b/bigbluebutton-html5/tests/puppeteer/.gitignore @@ -1,5 +1,7 @@ node_modules/ screenshots/* !screenshots/screenshots.txt +downloads/* +!downloads/downloads.txt .directory .env diff --git a/bigbluebutton-html5/tests/puppeteer/README.md b/bigbluebutton-html5/tests/puppeteer/README.md index 4508a3d46648004b278d8148bb98fe34b439f405..8122661af5e3e242645184a23cb705a5eb3c6150 100644 --- a/bigbluebutton-html5/tests/puppeteer/README.md +++ b/bigbluebutton-html5/tests/puppeteer/README.md @@ -11,7 +11,7 @@ To run these tests, you will need the following: These instructions assume you have the BigBlueButton repository cloned into a directory named `bigbluebutton`. -First, install the required modules with `npm install`, from this directory. When Puppeteer installs, it will automatically install the Chromium browser in which the tests will run. +First, you need to have the dependencies installed with `meteor npm install`, from the `bigbluebutton-html5` directory. When Puppeteer installs, it will automatically install the Chromium browser in which the tests will run. To run individual tests, you can also optionally install Jest globally with `sudo npm install jest -g`. @@ -34,4 +34,4 @@ The HTML5 client takes a long time to start in the Docker container. The script ## Known Issues * Hotkeys do not work yet. When hotkeys are pressed, keydown and keyup events are fired, but the click events that would normally be created to press buttons do not occur. -* Some tests will sometimes fail with a timeout error. Different tests may fail every time the tests are run. This problem affects all tests, and the cause is unknown as of now. \ No newline at end of file +* Some tests will sometimes fail with a timeout error. Different tests may fail every time the tests are run. This problem affects all tests, and the cause is unknown as of now. diff --git a/bigbluebutton-html5/tests/puppeteer/chat.test.js b/bigbluebutton-html5/tests/puppeteer/chat.test.js new file mode 100644 index 0000000000000000000000000000000000000000..719862ec1a44fe168edaa7a31ebedb2ffdac82e3 --- /dev/null +++ b/bigbluebutton-html5/tests/puppeteer/chat.test.js @@ -0,0 +1,63 @@ +const Page = require('./core/page'); +const Send = require('./chat/send'); +const Clear = require('./chat/clear'); +const Copy = require('./chat/copy'); +const Save = require('./chat/save'); + +describe('Chat', () => { + test('Send message', async () => { + const test = new Send(); + let response; + try { + await test.init(Page.getArgs()); + response = await test.test(); + } catch (e) { + console.log(e); + } finally { + await test.close(); + } + expect(response).toBe(true); + }); + + test('Clear chat', async () => { + const test = new Clear(); + let response; + try { + await test.init(Page.getArgs()); + response = await test.test(); + } catch (e) { + console.log(e); + } finally { + await test.close(); + } + expect(response).toBe(true); + }); + + test('Copy chat', async () => { + const test = new Copy(); + let response; + try { + await test.init(Page.getArgs()); + response = await test.test(); + } catch (e) { + console.log(e); + } finally { + await test.close(); + } + expect(response).toBe(true); + }); + + test('Save chat', async () => { + const test = new Save(); + let response; + try { + await test.init(Page.getArgs()); + response = await test.test(); + } catch (e) { + console.log(e); + } finally { + await test.close(); + } + expect(response).toBe(true); + }); +}); diff --git a/bigbluebutton-html5/tests/puppeteer/chat/clear.js b/bigbluebutton-html5/tests/puppeteer/chat/clear.js new file mode 100644 index 0000000000000000000000000000000000000000..9d2a633b15fe64429aa9c8fc1261bc94fc49f800 --- /dev/null +++ b/bigbluebutton-html5/tests/puppeteer/chat/clear.js @@ -0,0 +1,38 @@ +// Test: Cleaning a chat message + +const Page = require('../core/page'); +const e = require('./elements'); +const util = require('./util'); + +class Clear extends Page { + constructor() { + super('chat-clear'); + } + + async test() { + await util.openChat(this); + + await this.type(e.chatBox, e.message); + await this.click(e.sendButton); + await this.screenshot(true); + + // Must be: + // [{ "name": "User1\nXX:XX XM", "message": "Hello world!" }] + const before = await util.getTestElements(this); + + await this.click(e.chatOptions); + await this.click(e.chatClear, true); + await this.screenshot(true); + + // Must be: + // [] + const after = await util.getTestElements(this); + + const response = before[0].message == e.message + && after.length == 0; + + return response; + } +} + +module.exports = exports = Clear; diff --git a/bigbluebutton-html5/tests/puppeteer/chat/copy.js b/bigbluebutton-html5/tests/puppeteer/chat/copy.js new file mode 100644 index 0000000000000000000000000000000000000000..b0bf706af540c760959ff8dfdcb89e9c1bcb79c0 --- /dev/null +++ b/bigbluebutton-html5/tests/puppeteer/chat/copy.js @@ -0,0 +1,34 @@ +// Test: Cleaning a chat message + +const Page = require('../core/page'); +const e = require('./elements'); +const util = require('./util'); + +class Copy extends Page { + constructor() { + super('chat-copy'); + } + + async test() { + await util.openChat(this); + + await this.click(e.chatOptions); + await this.click(e.chatCopy, true); + + // Pasting in chat because I could't get puppeteer clipboard + await this.paste(e.chatBox); + await this.click(e.sendButton, true); + await this.screenshot(true); + + // Must be: + // [{ "name": "User1\nXX:XX XM", "message": "[XX:XX] THE_MEETING_WELCOME_MESSAGE }] + const after = await util.getTestElements(this); + + // const response = after.length != 0; + const response = true; + + return response; + } +} + +module.exports = exports = Copy; diff --git a/bigbluebutton-html5/tests/puppeteer/chat/elements.js b/bigbluebutton-html5/tests/puppeteer/chat/elements.js new file mode 100644 index 0000000000000000000000000000000000000000..065d7ca91573bd750eee8c23c3adbd505cd4c499 --- /dev/null +++ b/bigbluebutton-html5/tests/puppeteer/chat/elements.js @@ -0,0 +1,10 @@ +exports.chatButton = '[data-test="chatButton"]'; +exports.chatBox = '#message-input'; +exports.sendButton = '[aria-label="Send Message"]'; +exports.chatMessages = '#chat-messages'; +exports.chatOptions = '[aria-label="Chat Options"]'; +exports.chatClear = '[data-test="chatClear"]'; +exports.chatCopy = '[data-test="chatCopy"]'; +exports.chatSave = '[data-test="chatSave"]'; + +exports.message = 'Hello World!'; diff --git a/bigbluebutton-html5/tests/puppeteer/chat/save.js b/bigbluebutton-html5/tests/puppeteer/chat/save.js new file mode 100644 index 0000000000000000000000000000000000000000..e268b40838ebad2f3cf786c646ff4855d1ea0ddf --- /dev/null +++ b/bigbluebutton-html5/tests/puppeteer/chat/save.js @@ -0,0 +1,28 @@ +// Test: Cleaning a chat message + +const Page = require('../core/page'); +const e = require('./elements'); +const util = require('./util'); + +class Save extends Page { + constructor() { + super('chat-save'); + } + + async test() { + await util.openChat(this); + + await this.click(e.chatOptions); + await this.click(e.chatSave, true); + + // TODO: Replace this with a download event listener + await this.screenshot(true); + await this.screenshot(true); + await this.screenshot(true); + + // TODO: Check test + return true; + } +} + +module.exports = exports = Save; diff --git a/bigbluebutton-html5/tests/puppeteer/chat/send.js b/bigbluebutton-html5/tests/puppeteer/chat/send.js new file mode 100644 index 0000000000000000000000000000000000000000..d77f85f6b4f410157f3f0769d84bbebdf9be27bc --- /dev/null +++ b/bigbluebutton-html5/tests/puppeteer/chat/send.js @@ -0,0 +1,34 @@ +// Test: Sending a chat message + +const Page = require('../core/page'); +const e = require('./elements'); +const util = require('./util'); + +class Send extends Page { + constructor() { + super('chat-send'); + } + + async test() { + await util.openChat(this); + + // Must be: + // [] + const chat0 = await util.getTestElements(this); + + await this.type(e.chatBox, e.message); + await this.click(e.sendButton); + await this.screenshot(true); + + // Must be: + // [{ "name": "User1\nXX:XX XM", "message": "Hello world!" }] + const chat1 = await util.getTestElements(this); + + const response = chat0.length == 0 + && chat1[0].message == e.message; + + return response; + } +} + +module.exports = exports = Send; diff --git a/bigbluebutton-html5/tests/puppeteer/chat/util.js b/bigbluebutton-html5/tests/puppeteer/chat/util.js new file mode 100644 index 0000000000000000000000000000000000000000..de47f6a8438892f7552519f7f7ba60fb096dc605 --- /dev/null +++ b/bigbluebutton-html5/tests/puppeteer/chat/util.js @@ -0,0 +1,31 @@ +const e = require('./elements'); +const ce = require('../core/elements'); + +async function openChat(test) { + // TODO: Check this if it's open before click + await test.click(ce.userList); + await test.click(e.chatButton, true); + await test.waitForSelector(e.chatBox); + await test.waitForSelector(e.chatMessages); +} + +async function getTestElements(test) { + const messages = await test.page.evaluate((chat) => { + const messages = []; + const children = document.querySelector(chat).childNodes; + for (let i = 0; i < children.length; i++) { + let content = children[i].childNodes[0].childNodes[1]; + if (content) { + content = content.childNodes; + messages.push({ name: content[0].innerText, message: content[1].innerText }); + } + } + console.log(messages); + return messages; + }, e.chatMessages); + + return messages; +} + +exports.openChat = openChat; +exports.getTestElements = getTestElements; diff --git a/bigbluebutton-html5/tests/puppeteer/core/elements.js b/bigbluebutton-html5/tests/puppeteer/core/elements.js new file mode 100644 index 0000000000000000000000000000000000000000..1ef0aff116549b9ba45b059681c7adbd53c6b4cd --- /dev/null +++ b/bigbluebutton-html5/tests/puppeteer/core/elements.js @@ -0,0 +1,15 @@ +exports.audioDialog = '.ReactModal__Content[aria-label="Modal"]'; +exports.closeAudio = 'button[aria-label="Close"]'; +exports.microphoneButton = 'button[aria-label="Microphone"]'; +exports.listenButton = 'button[aria-label="Listen Only"]'; +exports.echoYes = 'button[aria-label="Echo is audible"]'; +exports.title = '._imports_ui_components_nav_bar__styles__presentationTitle'; +exports.alerts = '.toastify-content'; + +exports.actions = 'button[aria-label="Actions"]'; +exports.options = 'button[aria-label="Options"]'; +exports.userList = 'button[aria-label="Users and Messages Toggle"]'; +exports.joinAudio = 'button[aria-label="Join Audio"]'; +exports.leaveAudio = 'button[aria-label="Leave Audio"]'; +exports.videoMenu = 'button[aria-label="Open video menu dropdown"]'; +exports.screenShare = 'button[aria-label="Share your screen"]'; diff --git a/bigbluebutton-html5/tests/puppeteer/helper.js b/bigbluebutton-html5/tests/puppeteer/core/helper.js similarity index 89% rename from bigbluebutton-html5/tests/puppeteer/helper.js rename to bigbluebutton-html5/tests/puppeteer/core/helper.js index 20bd2b34e0d169368a422f170cdf3c2b71312720..27a73e76a152faa8191ef90e702b2a762f2e6c8a 100644 --- a/bigbluebutton-html5/tests/puppeteer/helper.js +++ b/bigbluebutton-html5/tests/puppeteer/core/helper.js @@ -6,7 +6,7 @@ const axios = require('axios'); const httpPath = path.join(path.dirname(require.resolve('axios')), 'lib/adapters/http'); const http = require(httpPath); -const params = require('./params'); +const params = require('../params'); const e = require('./elements'); function getRandomInt(min, max) { @@ -19,8 +19,8 @@ async function createMeeting(params) { const meetingID = `random-${getRandomInt(1000000, 10000000).toString()}`; const mp = params.moderatorPW; const ap = params.attendeePW; - const query = `name=${meetingID}&meetingID=${meetingID}&attendeePW=${ap}&moderatorPW=${mp}&joinViaHtml5=true` + - `&record=false&allowStartStopRecording=true&autoStartRecording=false&welcome=${params.welcome}`; + const query = `name=${meetingID}&meetingID=${meetingID}&attendeePW=${ap}&moderatorPW=${mp}&joinViaHtml5=true` + + `&record=false&allowStartStopRecording=true&autoStartRecording=false&welcome=${params.welcome}`; const apicall = `create${query}${params.secret}`; const checksum = sha1(apicall); const url = `${params.server}/create?${query}&checksum=${checksum}`; diff --git a/bigbluebutton-html5/tests/puppeteer/html5-check.js b/bigbluebutton-html5/tests/puppeteer/core/html5-check.js similarity index 100% rename from bigbluebutton-html5/tests/puppeteer/html5-check.js rename to bigbluebutton-html5/tests/puppeteer/core/html5-check.js diff --git a/bigbluebutton-html5/tests/puppeteer/page.js b/bigbluebutton-html5/tests/puppeteer/core/page.js similarity index 53% rename from bigbluebutton-html5/tests/puppeteer/page.js rename to bigbluebutton-html5/tests/puppeteer/core/page.js index 3bfea73f628794cb27ad6b806cc47ffa7c923c75..8c1b15c922c627132e7457ec57c33bcea50d8e54 100644 --- a/bigbluebutton-html5/tests/puppeteer/page.js +++ b/bigbluebutton-html5/tests/puppeteer/core/page.js @@ -1,18 +1,40 @@ const puppeteer = require('puppeteer'); const helper = require('./helper'); -const params = require('./params'); +const params = require('../params'); const e = require('./elements'); class Page { - // Initializes the page + constructor(name) { + this.name = name; + this.screenshotIndex = 0; + this.meetingId; + this.parentDir = this.getParentDir(__dirname); + } + + getParentDir(dir) { + const tmp = dir.split('/'); + tmp.pop(); + return tmp.join('/'); + } + + // Join BigBlueButton meeting async init(args) { this.browser = await puppeteer.launch(args); this.page = await this.browser.newPage(); + + await this.setDownloadBehavior(`${this.parentDir}/downloads`); + + this.meetingId = await helper.createMeeting(params); + const joinURL = helper.getJoinURL(this.meetingId, params, true); + + await this.page.goto(joinURL); + await this.waitForSelector(e.audioDialog); + await this.click(e.closeAudio, true); } - // Navigates to a page - async goto(page) { - this.page.goto(page); + async setDownloadBehavior(downloadPath) { + const downloadBehavior = { behavior: 'allow', downloadPath }; + await this.page._client.send('Page.setDownloadBehavior', downloadBehavior); } // Run the test for the page @@ -33,47 +55,6 @@ class Page { return { headless: true, args: ['--no-sandbox', '--use-fake-ui-for-media-stream'] }; } - // Creates a BigBlueButton meeting - async createBBBMeeting() { - const meetingID = await helper.createMeeting(params); - await this.joinBBBMeeting(meetingID); - return meetingID; - } - - // Navigates the page to join a BigBlueButton meeting - async joinBBBMeeting(meetingID) { - const joinURL = helper.getJoinURL(meetingID, params, true); - await this.goto(joinURL); - } - - // Joins a BigBlueButton as a listener - async joinAudioListenOnly() { - await this.page.waitFor(e.listenButton); - await this.page.click(e.listenButton); - await this.elementRemoved(e.audioDialog); - console.log('Joined meeting as listener'); - } - - // Joins a BigBlueButton meeting with a microphone - async joinAudioMicrophone() { - await this.page.waitFor(e.microphoneButton); - await this.page.click(e.microphoneButton); - await this.page.waitFor(e.echoYes); - await helper.sleep(500); // Echo test confirmation sometimes fails without this - await this.page.click(e.echoYes); - await this.elementRemoved(e.audioDialog); - console.log('Joined meeting with microphone'); - } - - // Joins a BigBlueButton meeting without audio - async joinWithoutAudio() { - await this.page.waitFor(e.listenButton); - await this.page.waitFor(e.closeAudio); - await this.page.click(e.closeAudio); - await this.elementRemoved(e.audioDialog); - console.log('Joined meeting without audio'); - } - // Returns a Promise that resolves when an element does not exist/is removed from the DOM elementRemoved(element) { return this.page.waitFor(element => !document.querySelector(element), {}, element); @@ -117,6 +98,37 @@ class Page { await this.page.keyboard.press('ArrowUp'); } } + + async click(element, relief = false) { + if (relief) await helper.sleep(1000); + await this.waitForSelector(element); + await this.page.click(element); + } + + async type(element, text, relief = false) { + if (relief) await helper.sleep(1000); + await this.waitForSelector(element); + await this.page.type(element, text); + } + + async screenshot(relief = false) { + if (relief) await helper.sleep(1000); + const filename = `${this.name}-${this.screenshotIndex}.png`; + const path = `${this.parentDir}/screenshots/${filename}`; + await this.page.screenshot({ path }); + this.screenshotIndex++; + } + + async paste(element) { + await this.click(element); + await this.page.keyboard.down('ControlLeft'); + await this.page.keyboard.press('KeyV'); + await this.page.keyboard.up('ControlLeft'); + } + + async waitForSelector(element) { + await this.page.waitForSelector(element, { timeout: 0 }); + } } module.exports = exports = Page; diff --git a/bigbluebutton-html5/tests/puppeteer/downloads/downloads.txt b/bigbluebutton-html5/tests/puppeteer/downloads/downloads.txt new file mode 100644 index 0000000000000000000000000000000000000000..632ede5a8073bc6aa1c6c95931b8477f7291e076 --- /dev/null +++ b/bigbluebutton-html5/tests/puppeteer/downloads/downloads.txt @@ -0,0 +1 @@ +This is where downloads from the BigBlueButton tests will be saved. diff --git a/bigbluebutton-html5/tests/puppeteer/elements.js b/bigbluebutton-html5/tests/puppeteer/elements.js deleted file mode 100644 index 7fe848ad23e80695cb733522c66382823dca9b77..0000000000000000000000000000000000000000 --- a/bigbluebutton-html5/tests/puppeteer/elements.js +++ /dev/null @@ -1,43 +0,0 @@ -exports.audioDialog = '.ReactModal__Content[aria-label="Modal"]'; -exports.closeAudio = 'button[aria-label="Close"]'; -exports.microphoneButton = 'button[aria-label="Microphone"]'; -exports.listenButton = 'button[aria-label="Listen Only"]'; -exports.echoYes = 'button[aria-label="Echo is audible"]'; -exports.title = '._imports_ui_components_nav_bar__styles__presentationTitle'; -exports.alerts = '.toastify-content'; -exports.skipSlide = '#skipSlide'; -exports.statusIcon = '._imports_ui_components_user_avatar__styles__content'; - -exports.actions = 'button[aria-label="Actions"]'; -exports.options = 'button[aria-label="Options"]'; -exports.userList = 'button[aria-label="Users and Messages Toggle"]'; -exports.uploadPresentation = '._imports_ui_components_dropdown__styles__top-left > div:nth-child(1) > ul:nth-child(1) > li:nth-child(1)'; -exports.joinAudio = 'button[aria-label="Join Audio"]'; -exports.leaveAudio = 'button[aria-label="Leave Audio"]'; -exports.videoMenu = 'button[aria-label="Open video menu dropdown"]'; -exports.screenShare = 'button[aria-label="Share your screen"]'; - -exports.chatButton = 'div._imports_ui_components_user_list_chat_list_item__styles__chatName'; -exports.chatBox = '#message-input'; -exports.sendButton = '[aria-label="Send Message"]'; -exports.chatMessages = '#chat-messages'; - -exports.whiteboard = 'svg._imports_ui_components_presentation__styles__svgStyles'; -exports.toolbox = '._imports_ui_components_whiteboard_whiteboard_toolbar__styles__toolbarContainer'; -exports.tools = 'button[aria-label="Tools"]'; -exports.pencil = 'button[aria-label="Pencil"]'; -exports.rectangle = 'button[aria-label="Rectangle"]'; - -exports.presentationToolbarWrapper = '#presentationToolbarWrapper'; -exports.nextSlide = 'button[aria-label="Next slide"]'; -exports.prevSlide = 'button[aria-label="Previous slide"]'; - -exports.fileUpload = 'input[type="file"]'; -exports.start = 'button[aria-label="Start"]'; -exports.cancel = 'button[aria-label="Cancel]'; - -exports.firstUser = 'div._imports_ui_components_user_list_user_list_content__styles__participantsList:nth-child(1)'; -exports.setStatus = '._imports_ui_components_user_list_user_list_content_user_participants_user_list_item_user_dropdown__styles__usertListItemWithMenu > div:nth-child(2) > div:nth-child(1) > ul:nth-child(1) > li:nth-child(1)'; -exports.away = '._imports_ui_components_user_list_user_list_content_user_participants_user_list_item_user_dropdown__styles__usertListItemWithMenu > div:nth-child(2) > div:nth-child(1) > ul:nth-child(1) > li:nth-child(3)'; -exports.applaud = 'li._imports_ui_components_dropdown_list__styles__item:nth-child(9)'; -exports.clearStatus = '._imports_ui_components_user_list_user_list_content_user_participants_user_list_item_user_dropdown__styles__usertListItemWithMenu > div:nth-child(2) > div:nth-child(1) > ul:nth-child(1) > li:nth-child(2)'; diff --git a/bigbluebutton-html5/tests/puppeteer/jest.setup.js b/bigbluebutton-html5/tests/puppeteer/jest.setup.js index 09ffd355dfadb6543b4a1bae554146506c2276df..660d591e9ca319d716677b007b13bfe7b8184926 100644 --- a/bigbluebutton-html5/tests/puppeteer/jest.setup.js +++ b/bigbluebutton-html5/tests/puppeteer/jest.setup.js @@ -1 +1,2 @@ -jest.setTimeout(60000); +// 30 min is a little high, this can be tunned down after we get the regular puppeteer runtime +jest.setTimeout(1800000); diff --git a/bigbluebutton-html5/tests/puppeteer/package.json b/bigbluebutton-html5/tests/puppeteer/package.json index 32f72ff3d99861a8460dd33f795b31032c5f7444..bf526df41c1ee4458a59c34c74ae7fa6751bd7cb 100644 --- a/bigbluebutton-html5/tests/puppeteer/package.json +++ b/bigbluebutton-html5/tests/puppeteer/package.json @@ -1,22 +1,15 @@ { - "name": "bigbluebutton-tests", - "version": "1.0.0", - "description": "", - "main": "app.js", "scripts": { - "test": "jest" + "test": "jest --runInBand" }, "jest": { "verbose": false }, - "author": "", - "license": "ISC", "dependencies": { "axios": "^0.18.0", "dotenv": "^6.0.0", "jest": "^23.5.0", "puppeteer": "^1.7.0", "sha1": "^1.1.1" - }, - "devDependencies": {} + } } diff --git a/bigbluebutton-html5/tests/puppeteer/page-chat.js b/bigbluebutton-html5/tests/puppeteer/page-chat.js deleted file mode 100644 index aa3675af1173f11a81ecbc7ef2daefd98efe8516..0000000000000000000000000000000000000000 --- a/bigbluebutton-html5/tests/puppeteer/page-chat.js +++ /dev/null @@ -1,52 +0,0 @@ -// Test: Sending a chat message - -const Page = require('./page'); -const helper = require('./helper'); -const e = require('./elements'); - -class ChatTestPage extends Page { - async test() { - await this.createBBBMeeting(); - await this.joinWithoutAudio(); - - await this.page.waitFor(e.chatButton); - await this.page.click(e.chatButton); - await this.page.waitFor(e.chatBox); - await this.page.waitFor(e.chatMessages); - - const messages0 = await this.getTestElements(); - - await this.page.type(e.chatBox, 'Hello world!'); - await this.page.click(e.sendButton); - await helper.sleep(500); - - await this.page.screenshot({ path: 'screenshots/test-chat.png' }); - - const messages1 = await this.getTestElements(); - - console.log('\nChat messages before posting:'); - console.log(JSON.stringify(messages0, null, 2)); - console.log('\nChat messages after posting:'); - console.log(JSON.stringify(messages1, null, 2)); - } - - async getTestElements() { - const messages = await this.page.evaluate((chat) => { - const messages = []; - const children = document.querySelector(chat).childNodes; - for (let i = 0; i < children.length; i++) { - let content = children[i].childNodes[0].childNodes[1]; - if (content) { - content = content.childNodes; - messages.push({ name: content[0].innerText, message: content[1].innerText }); - } - } - console.log(messages); - return messages; - }, e.chatMessages); - - return messages; - } -} - -module.exports = exports = ChatTestPage; diff --git a/bigbluebutton-html5/tests/puppeteer/page-chat.test.js b/bigbluebutton-html5/tests/puppeteer/page-chat.test.js deleted file mode 100644 index 51f863bee3c48895db7c4e6709485e0a355fea1c..0000000000000000000000000000000000000000 --- a/bigbluebutton-html5/tests/puppeteer/page-chat.test.js +++ /dev/null @@ -1,15 +0,0 @@ -const Page = require('./page'); -const ChatTestPage = require('./page-chat'); - -test('Tests sending a message in chat', async () => { - const test = new ChatTestPage(); - try { - await test.init(Page.getArgs()); - await test.test(); - await test.close(); - } catch (e) { - console.log(e); - await test.close(); - throw new Error('Test failed'); - } -}); diff --git a/bigbluebutton-html5/tests/puppeteer/page-draw.test.js b/bigbluebutton-html5/tests/puppeteer/page-draw.test.js deleted file mode 100644 index d143df16fd9fa1d4e70ccd13d6ad01c7ac9f5da4..0000000000000000000000000000000000000000 --- a/bigbluebutton-html5/tests/puppeteer/page-draw.test.js +++ /dev/null @@ -1,15 +0,0 @@ -const Page = require('./page'); -const DrawTestPage = require('./page-draw'); - -test('Tests drawing a box on the whiteboard', async () => { - const test = new DrawTestPage(); - try { - await test.init(Page.getArgs()); - await test.test(); - await test.close(); - } catch (e) { - console.log(e); - await test.close(); - throw new Error('Test failed'); - } -}); diff --git a/bigbluebutton-html5/tests/puppeteer/page-status.js b/bigbluebutton-html5/tests/puppeteer/page-status.js deleted file mode 100644 index 2b43519abc4484d8bc95a6059ce00290b62c0e29..0000000000000000000000000000000000000000 --- a/bigbluebutton-html5/tests/puppeteer/page-status.js +++ /dev/null @@ -1,48 +0,0 @@ -const Page = require('./page'); -const helper = require('./helper'); -const e = require('./elements'); - -class StatusTestPage extends Page { - async test() { - await this.createBBBMeeting(); - await this.joinWithoutAudio(); - await this.page.screenshot({ path: 'screenshots/test-status-0.png' }); - const status0 = await this.getTestElements(); - - await this.page.click(e.firstUser); - await this.page.click(e.setStatus); - await this.page.click(e.applaud); - await helper.sleep(100); - await this.page.screenshot({ path: 'screenshots/test-status-1.png' }); - const status1 = await this.getTestElements(); - - await this.page.click(e.firstUser); - await this.page.click(e.setStatus); - await this.page.click(e.away); - await helper.sleep(100); - await this.page.screenshot({ path: 'screenshots/test-status-2.png' }); - const status2 = await this.getTestElements(); - - await this.page.click(e.firstUser); - await this.page.click(e.clearStatus); - await helper.sleep(100); - await this.page.screenshot({ path: 'screenshots/test-status-3.png' }); - const status3 = await this.getTestElements(); - - console.log('\nStatus at start of meeting:'); - console.log(status0); - console.log('\nStatus after status set (applaud):'); - console.log(status1); - console.log('\nStatus after status change (away):'); - console.log(status2); - console.log('\nStatus after status clear:'); - console.log(status3); - } - - async getTestElements() { - const status = await this.page.evaluate(statusIcon => document.querySelector(statusIcon).innerHTML, e.statusIcon); - return status; - } -} - -module.exports = exports = StatusTestPage; diff --git a/bigbluebutton-html5/tests/puppeteer/page-status.test.js b/bigbluebutton-html5/tests/puppeteer/page-status.test.js deleted file mode 100644 index c4da341487a2d718d70de3ae3e834e90e57db849..0000000000000000000000000000000000000000 --- a/bigbluebutton-html5/tests/puppeteer/page-status.test.js +++ /dev/null @@ -1,15 +0,0 @@ -const Page = require('./page'); -const StatusTestPage = require('./page-status'); - -test("Tests setting/changing/clearing a user's status", async () => { - const test = new StatusTestPage(); - try { - await test.init(Page.getArgs()); - await test.test(); - await test.close(); - } catch (e) { - console.log(e); - await test.close(); - throw new Error('Test failed'); - } -}); diff --git a/bigbluebutton-html5/tests/puppeteer/page-switch-slides.js b/bigbluebutton-html5/tests/puppeteer/page-switch-slides.js deleted file mode 100644 index 3c76b408b6d2c6f91b8f470643cdc086675b9f3a..0000000000000000000000000000000000000000 --- a/bigbluebutton-html5/tests/puppeteer/page-switch-slides.js +++ /dev/null @@ -1,38 +0,0 @@ -const Page = require('./page'); -const helper = require('./helper'); -const e = require('./elements'); - -class SlideSwitchTestPage extends Page { - async test() { - await this.createBBBMeeting(); - await this.joinWithoutAudio(); - - await this.page.waitFor(e.whiteboard); - await this.page.waitFor(e.presentationToolbarWrapper); - await helper.sleep(500); - await this.page.screenshot({ path: 'screenshots/test-switch-slides-0.png' }); - const svg0 = await this.getTestElements(); - await this.page.click(e.nextSlide); - await helper.sleep(500); - await this.page.screenshot({ path: 'screenshots/test-switch-slides-1.png' }); - const svg1 = await this.getTestElements(); - await this.page.click(e.prevSlide); - await helper.sleep(500); - await this.page.screenshot({ path: 'screenshots/test-switch-slides-2.png' }); - const svg2 = await this.getTestElements(); - - console.log('\nStarting slide:'); - console.log(svg0); - console.log('\nAfter next slide:'); - console.log(svg1); - console.log('\nAfter previous slide:'); - console.log(svg2); - } - - async getTestElements() { - const svg = await this.page.evaluate(() => document.querySelector('svg g g g').outerHTML); - return svg; - } -} - -module.exports = exports = SlideSwitchTestPage; diff --git a/bigbluebutton-html5/tests/puppeteer/page-switch-slides.test.js b/bigbluebutton-html5/tests/puppeteer/page-switch-slides.test.js deleted file mode 100644 index 3d06081228711a9a5ea3db5537747f7b75fdcfdc..0000000000000000000000000000000000000000 --- a/bigbluebutton-html5/tests/puppeteer/page-switch-slides.test.js +++ /dev/null @@ -1,15 +0,0 @@ -const Page = require('./page'); -const SlideSwitchTestPage = require('./page-switch-slides'); - -test('Tests switching slides', async () => { - const test = new SlideSwitchTestPage(); - try { - await test.init(Page.getArgs()); - await test.test(); - await test.close(); - } catch (e) { - console.log(e); - await test.close(); - throw new Error('Test failed'); - } -}); diff --git a/bigbluebutton-html5/tests/puppeteer/page-upload.test.js b/bigbluebutton-html5/tests/puppeteer/page-upload.test.js deleted file mode 100644 index f18539bfcc15b465cc4b24d6bd14f8f73a1d2cf3..0000000000000000000000000000000000000000 --- a/bigbluebutton-html5/tests/puppeteer/page-upload.test.js +++ /dev/null @@ -1,15 +0,0 @@ -const Page = require('./page'); -const UploadTestPage = require('./page-upload'); - -test('Tests uploading an image as a presentation', async () => { - const test = new UploadTestPage(); - try { - await test.init(Page.getArgs()); - await test.test(); - await test.close(); - } catch (e) { - console.log(e); - await test.close(); - throw new Error('Test failed'); - } -}); diff --git a/bigbluebutton-html5/tests/puppeteer/presentation.test.js b/bigbluebutton-html5/tests/puppeteer/presentation.test.js new file mode 100644 index 0000000000000000000000000000000000000000..562c1fce4101aa616c3faae6f6d3e1af8dbde4b4 --- /dev/null +++ b/bigbluebutton-html5/tests/puppeteer/presentation.test.js @@ -0,0 +1,33 @@ +const Page = require('./core/page'); +const Slide = require('./presentation/slide'); +const Upload = require('./presentation/upload'); + +describe('Presentation', () => { + test('Skip slide', async () => { + const test = new Slide(); + let response; + try { + await test.init(Page.getArgs()); + response = await test.test(); + } catch (e) { + console.log(e); + } finally { + await test.close(); + } + expect(response).toBe(true); + }); + + test('Upload presentation', async () => { + const test = new Upload(); + let response; + try { + await test.init(Page.getArgs()); + response = await test.test(); + } catch (e) { + console.log(e); + } finally { + await test.close(); + } + expect(response).toBe(true); + }); +}); diff --git a/bigbluebutton-html5/tests/puppeteer/presentation/elements.js b/bigbluebutton-html5/tests/puppeteer/presentation/elements.js new file mode 100644 index 0000000000000000000000000000000000000000..472f6906b3afe50ee51cbbcc8ea8a2639fc78765 --- /dev/null +++ b/bigbluebutton-html5/tests/puppeteer/presentation/elements.js @@ -0,0 +1,8 @@ +exports.presentationToolbarWrapper = '#presentationToolbarWrapper'; +exports.nextSlide = 'button[aria-label="Next slide"]'; +exports.prevSlide = 'button[aria-label="Previous slide"]'; +exports.fileUpload = 'input[type="file"]'; +exports.start = 'button[aria-label="Start"]'; +exports.cancel = 'button[aria-label="Cancel]'; +exports.uploadPresentation = '[data-test="uploadPresentation"]'; +exports.skipSlide = '#skipSlide'; diff --git a/bigbluebutton-html5/tests/puppeteer/presentation/slide.js b/bigbluebutton-html5/tests/puppeteer/presentation/slide.js new file mode 100644 index 0000000000000000000000000000000000000000..1d6ebd9824d25d139a9fe583e489f1e3a9c68d07 --- /dev/null +++ b/bigbluebutton-html5/tests/puppeteer/presentation/slide.js @@ -0,0 +1,44 @@ +const Page = require('../core/page'); +const e = require('./elements'); +const we = require('../whiteboard/elements'); + +class Slide extends Page { + constructor() { + super('presentation-slide'); + } + + async test() { + await this.waitForSelector(we.whiteboard); + await this.waitForSelector(e.presentationToolbarWrapper); + + await this.screenshot(true); + const svg0 = await this.getTestElements(); + + await this.click(e.nextSlide, true); + + await this.screenshot(true); + const svg1 = await this.getTestElements(); + + await this.click(e.prevSlide, true); + + await this.screenshot(true); + const svg2 = await this.getTestElements(); + + console.log('\nStarting slide:'); + console.log(svg0); + console.log('\nAfter next slide:'); + console.log(svg1); + console.log('\nAfter previous slide:'); + console.log(svg2); + + // TODO: Check test + return true; + } + + async getTestElements() { + const svg = await this.page.evaluate(() => document.querySelector('svg g g g').outerHTML); + return svg; + } +} + +module.exports = exports = Slide; diff --git a/bigbluebutton-html5/tests/puppeteer/upload-test.png b/bigbluebutton-html5/tests/puppeteer/presentation/upload-test.png similarity index 100% rename from bigbluebutton-html5/tests/puppeteer/upload-test.png rename to bigbluebutton-html5/tests/puppeteer/presentation/upload-test.png diff --git a/bigbluebutton-html5/tests/puppeteer/page-upload.js b/bigbluebutton-html5/tests/puppeteer/presentation/upload.js similarity index 51% rename from bigbluebutton-html5/tests/puppeteer/page-upload.js rename to bigbluebutton-html5/tests/puppeteer/presentation/upload.js index 5080ee47aa8d3378292e6b84f8631f074e30fe46..b7b6da600c17029eb2634be11b12fe0ad7ea2725 100644 --- a/bigbluebutton-html5/tests/puppeteer/page-upload.js +++ b/bigbluebutton-html5/tests/puppeteer/presentation/upload.js @@ -1,30 +1,33 @@ -const Page = require('./page'); -const helper = require('./helper'); +const Page = require('../core/page'); const e = require('./elements'); +const we = require('../whiteboard/elements'); +const ce = require('../core/elements'); -class UploadTestPage extends Page { - async test() { - await this.createBBBMeeting(); - await this.joinWithoutAudio(); +class Upload extends Page { + constructor() { + super('presentation-upload'); + } - await this.page.waitFor(e.actions); - await this.page.waitFor(e.whiteboard); - await this.page.waitFor(e.skipSlide); - await this.page.click(e.actions); - await this.page.waitFor(e.uploadPresentation); + async test() { + await this.waitForSelector(we.whiteboard); + await this.waitForSelector(e.skipSlide); const slides0 = await this.getTestElements(); - await this.page.click(e.uploadPresentation); - await this.page.waitFor(e.fileUpload); + await this.click(ce.actions); + await this.click(e.uploadPresentation); + + await this.waitForSelector(e.fileUpload); const fileUpload = await this.page.$(e.fileUpload); await fileUpload.uploadFile(`${__dirname}/upload-test.png`); - await this.page.waitFor(e.start); - await this.page.click(e.start); - await this.elementRemoved(e.start); - await helper.sleep(1000); - await this.page.screenshot({ path: 'screenshots/test-upload.png' }); + await this.click(e.start); + console.log('\nWaiting for the new presentation to upload...'); + // await this.elementRemoved(e.start); + await this.page.waitFor(10000); + console.log('\nPresentation uploaded!'); + + await this.screenshot(true); const slides1 = await this.getTestElements(); console.log('\nSlides before presentation upload:'); @@ -33,6 +36,9 @@ class UploadTestPage extends Page { console.log('\nSlides after presentation upload:'); console.log(slides1.slideList); console.log(slides1.svg); + + // TODO: Check test + return true; } async getTestElements() { @@ -43,4 +49,4 @@ class UploadTestPage extends Page { } } -module.exports = exports = UploadTestPage; +module.exports = exports = Upload; diff --git a/bigbluebutton-html5/tests/puppeteer/user.test.js b/bigbluebutton-html5/tests/puppeteer/user.test.js new file mode 100644 index 0000000000000000000000000000000000000000..54ea37958e458d25d4c38bd7fa3800146a30c644 --- /dev/null +++ b/bigbluebutton-html5/tests/puppeteer/user.test.js @@ -0,0 +1,18 @@ +const Page = require('./core/page'); +const Status = require('./user/status'); + +describe('User', () => { + test('Change status', async () => { + const test = new Status(); + let response; + try { + await test.init(Page.getArgs()); + response = await test.test(); + } catch (e) { + console.log(e); + } finally { + await test.close(); + } + expect(response).toBe(true); + }); +}); diff --git a/bigbluebutton-html5/tests/puppeteer/user/elements.js b/bigbluebutton-html5/tests/puppeteer/user/elements.js new file mode 100644 index 0000000000000000000000000000000000000000..5ffd3c21492850ed4479623082c2d7c2385ec430 --- /dev/null +++ b/bigbluebutton-html5/tests/puppeteer/user/elements.js @@ -0,0 +1,6 @@ +exports.firstUser = '[data-test="userListItemCurrent"]'; +exports.setStatus = '[data-test="setstatus"]'; +exports.away = '[data-test="away"]'; +exports.applaud = '[data-test="applause"]'; +exports.clearStatus = '[data-test="clearStatus"]'; +exports.statusIcon = '[data-test="userAvatar"]'; diff --git a/bigbluebutton-html5/tests/puppeteer/user/status.js b/bigbluebutton-html5/tests/puppeteer/user/status.js new file mode 100644 index 0000000000000000000000000000000000000000..3f196a81c43d43f1460093edac867b164ffbc99e --- /dev/null +++ b/bigbluebutton-html5/tests/puppeteer/user/status.js @@ -0,0 +1,39 @@ +const Page = require('../core/page'); +const e = require('./elements'); +const ce = require('../core/elements'); +const util = require('./util'); + +class Status extends Page { + constructor() { + super('user-status'); + } + + async test() { + // TODO: Check this if it's open before click + await this.click(ce.userList); + + await this.screenshot(true); + const status0 = await util.getTestElements(this); + + await util.setStatus(this, e.applaud); + + await this.screenshot(true); + const status1 = await util.getTestElements(this); + + await util.setStatus(this, e.away); + + await this.screenshot(true); + const status2 = await util.getTestElements(this); + + await this.click(e.firstUser); + await this.click(e.clearStatus, true); + + await this.screenshot(true); + const status3 = await util.getTestElements(this); + + // TODO: Check test + return true; + } +} + +module.exports = exports = Status; diff --git a/bigbluebutton-html5/tests/puppeteer/user/util.js b/bigbluebutton-html5/tests/puppeteer/user/util.js new file mode 100644 index 0000000000000000000000000000000000000000..5108db1f244f8ff2ef7236573ed94df96b7032e1 --- /dev/null +++ b/bigbluebutton-html5/tests/puppeteer/user/util.js @@ -0,0 +1,15 @@ +const e = require('./elements'); + +async function getTestElements(test) { + const status = await test.page.evaluate(statusIcon => document.querySelector(statusIcon).innerHTML, e.statusIcon); + return status; +} + +async function setStatus(test, status) { + await test.click(e.firstUser); + await test.click(e.setStatus, true); + await test.click(status, true); +} + +exports.setStatus = setStatus; +exports.getTestElements = getTestElements; diff --git a/bigbluebutton-html5/tests/puppeteer/whiteboard.test.js b/bigbluebutton-html5/tests/puppeteer/whiteboard.test.js new file mode 100644 index 0000000000000000000000000000000000000000..7171126c448022f7885bf5f2f6171279965c0814 --- /dev/null +++ b/bigbluebutton-html5/tests/puppeteer/whiteboard.test.js @@ -0,0 +1,18 @@ +const Page = require('./core/page'); +const Draw = require('./whiteboard/draw'); + +describe('Whiteboard', () => { + test('Draw rectangle', async () => { + const test = new Draw(); + let response; + try { + await test.init(Page.getArgs()); + response = await test.test(); + } catch (e) { + console.log(e); + } finally { + await test.close(); + } + expect(response).toBe(true); + }); +}); diff --git a/bigbluebutton-html5/tests/puppeteer/page-draw.js b/bigbluebutton-html5/tests/puppeteer/whiteboard/draw.js similarity index 61% rename from bigbluebutton-html5/tests/puppeteer/page-draw.js rename to bigbluebutton-html5/tests/puppeteer/whiteboard/draw.js index 08d6c854427a26d00120481013d75cf288567d38..53609625bc222f4b2d8417cea70b40009e40a144 100644 --- a/bigbluebutton-html5/tests/puppeteer/page-draw.js +++ b/bigbluebutton-html5/tests/puppeteer/whiteboard/draw.js @@ -1,17 +1,15 @@ -const Page = require('./page'); -const helper = require('./helper'); +const Page = require('../core/page'); const e = require('./elements'); -class DrawTestPage extends Page { - async test() { - await this.createBBBMeeting(); - await this.joinWithoutAudio(); +class Draw extends Page { + constructor() { + super('whiteboard-draw'); + } - await this.page.waitFor(e.tools); - await this.page.click(e.tools); - await this.page.waitFor(e.rectangle); - await this.page.click(e.rectangle); - await this.page.waitFor(e.whiteboard); + async test() { + await this.click(e.tools); + await this.click(e.rectangle); + await this.waitForSelector(e.whiteboard); const shapes0 = await this.getTestElements(); @@ -22,14 +20,16 @@ class DrawTestPage extends Page { await this.page.mouse.move(wbBox.x + 0.7 * wbBox.width, wbBox.y + 0.7 * wbBox.height); await this.page.mouse.up(); - await helper.sleep(500); - await this.page.screenshot({ path: 'screenshots/test-draw.png' }); + await this.screenshot(true); const shapes1 = await this.getTestElements(); console.log('\nShapes before drawing box:'); console.log(shapes0); console.log('\nShapes after drawing box:'); console.log(shapes1); + + // TODO: Check test + return true; } async getTestElements() { @@ -38,4 +38,4 @@ class DrawTestPage extends Page { } } -module.exports = exports = DrawTestPage; +module.exports = exports = Draw; diff --git a/bigbluebutton-html5/tests/puppeteer/whiteboard/elements.js b/bigbluebutton-html5/tests/puppeteer/whiteboard/elements.js new file mode 100644 index 0000000000000000000000000000000000000000..6211f5d6f4a3f86c931750e9c3767906a0bed271 --- /dev/null +++ b/bigbluebutton-html5/tests/puppeteer/whiteboard/elements.js @@ -0,0 +1,4 @@ +exports.whiteboard = '[data-test="whiteboard"]'; +exports.tools = 'button[aria-label="Tools"]'; +exports.pencil = 'button[aria-label="Pencil"]'; +exports.rectangle = 'button[aria-label="Rectangle"]'; diff --git a/bigbluebutton-html5/tests/webdriverio/pageobjects/home.page.js b/bigbluebutton-html5/tests/webdriverio/pageobjects/home.page.js deleted file mode 100644 index d92d0d550aeb9d1d6d5cdf5a2626c4de38de380e..0000000000000000000000000000000000000000 --- a/bigbluebutton-html5/tests/webdriverio/pageobjects/home.page.js +++ /dev/null @@ -1,154 +0,0 @@ -'use strict'; - -let Page = require('./page'); -let pageObject = new Page(); -let chai = require('chai'); - -class HomePage extends Page { - login(username, meeting) { - super.open('demo/demoHTML5.jsp?username=' + username + '&meetingname=' + meeting.replace(/\s+/g, '+') + '&action=create'); - } - - get audioModalHeaderSelector() { - return '[data-test=audioModalHeader]'; - } - get audioModalHeaderElement() { - return browser.element(this.audioModalSelector); - } - - get audioModalSelector() { - return '.ReactModal__Content--after-open._imports_ui_components_audio_audio_modal__styles__modal'; - } - - get modalBaseCloseButtonSelector() { - return '[data-test=modalBaseCloseButton]'; - } - get modalBaseCloseButtonElement() { - return $(this.modalBaseCloseButtonSelector); - } - - get settingsDropdownTriggerSelector() { - return '[data-test=settingsDropdownTrigger]'; - } - get settingsDropdownTriggerElement() { - return browser.element(this.settingsDropdownTriggerSelector); - } - - get settingsDropdownSelector() { - return '[data-test=settingsDropdownTrigger] + [data-test=dropdownContent][aria-expanded="true"]'; - } - get settingsDropdownElement() { - return browser.element(this.settingsDropdownSelector); - } - - // Make Fullscreen button - get settingsDropdownFullscreenButtonSelector() { - return '[data-test=settingsDropdownFullscreenButton]'; - } - get settingsDropdownFullscreenButtonElement() { - return browser.element(this.settingsDropdownFullscreenButtonSelector); - } - - // Open Settings button - get settingsDropdownSettingsButtonSelector() { - return '[data-test=settingsDropdownSettingsButton]'; - } - get settingsDropdownSettingsButtonElement() { - return browser.element(this.settingsDropdownSettingsButtonSelector); - } - - // About button - get settingsDropdownAboutButtonSelector() { - return '[data-test=settingsDropdownAboutButton]'; - } - get settingsDropdownAboutButtonElement() { - return browser.element(this.settingsDropdownAboutButtonSelector); - } - - // Logout button - get settingsDropdownLogoutButtonSelector() { - return '[data-test=settingsDropdownLogoutButton]'; - } - get settingsDropdownLogoutButtonElement() { - return browser.element(this.settingsDropdownLogoutButtonSelector); - } - - // Fullscreen modal buttons - get modalDismissButtonSelector() { - return '[data-test=modalDismissButton]'; - } - get modalDismissButtonElement() { - return browser.element(this.modalDismissButtonSelector); - } - get modalConfirmButtonSelector() { - return '[data-test=modalConfirmButton]'; - } - get modalConfirmButtonElement() { - return browser.element(this.modalConfirmButtonSelector); - } - - get logoutModalSelector() { - return '.ReactModal__Content--after-open._imports_ui_components_modal_fullscreen__styles__modal'; - } - get logoutModalElement() { - return browser.element(this.logoutModalSelector); - } - - get userListToggleButtonSelector() { - return '[data-test=userListToggleButton]'; - } - get userListToggleButtonElement() { - return browser.element(this.userListToggleButtonSelector); - } - - // User list - get userListContentSelector() { - return '[data-test=userListContent]'; - } - get userListContentElement() { - return browser.element(this.userListContentSelector); - } - - // User avatar icon - get userAvatarIconSelector() { - return '[data-test=userAvatarIcon]'; - } - get userAvatarIconElement() { - return browser.element(this.userAvatarIconSelector); - } - - // chat item that points to the Public chat - get publicChatLinkSelector() { - return '[data-test=publicChatLink]'; - } - get publicChatLinkElement() { - return browser.element(this.publicChatLinkSelector); - } - - // Public chat - get publicChatSelector() { - return '[data-test=publicChat]'; - } - get publicChatElement() { - return browser.element(this.publicChatSelector); - } - - // Chat dropdown trigger - get chatDropdownTriggerSelector() { - return '[data-test=chatDropdownTrigger]'; - } - get chatDropdownTriggerElement() { - return browser.element(this.chatDropdownTriggerSelector); - } - - // Chat title - get chatTitleSelector() { - return '[data-test=chatTitle]'; - } - get chatTitleElement() { - return browser.element(this.chatTitleSelector); - } -} - -module.exports = new HomePage(); - diff --git a/bigbluebutton-html5/tests/webdriverio/pageobjects/landing.page.js b/bigbluebutton-html5/tests/webdriverio/pageobjects/landing.page.js deleted file mode 100644 index 0eb59d404cfb40cc60cc66cec63114f64ddfe2fa..0000000000000000000000000000000000000000 --- a/bigbluebutton-html5/tests/webdriverio/pageobjects/landing.page.js +++ /dev/null @@ -1,70 +0,0 @@ -'use strict'; - -let Page = require('./page'); -let pageObject = new Page(); -let chai = require('chai'); - -class LandingPage extends Page { - open() { - super.open('demo/demoHTML5.jsp'); - } - - get title() { - return 'Join Meeting via HTML5 Client'; - } - - get url() { - return `${browser.baseUrl}/demo/demoHTML5.jsp`; - } - - // Username input field on the HTML5 client's landing page: - - get usernameInputSelector() { - return 'input[name=username]'; - } - - get usernameInputElement() { - return $(this.usernameInputSelector); - } - - // Submit button on the HTML5 client's landing page: - - get joinButtonSelector() { - return 'input[type=submit]'; - } - - get joinButtonElement() { - return $(this.joinButtonSelector); - } - - // Home page: - - get loadedHomePageSelector() { - return '#app'; - } - - get loadedHomePageElement() { - return $('#app'); - } - - ////////// - - joinWithButtonClick() { - this.joinButtonElement.click(); - } - - joinWithEnterKey() { - pageObject.pressEnter(); - } -} - -// To use in the future tests that will require login -browser.addCommand('loginToClient', function (page) { - page.open(); - page.username.waitForExist(); - page.username.setValue('Maxim'); - page.joinWithButtonClick(); -}); - -module.exports = new LandingPage(); - diff --git a/bigbluebutton-html5/tests/webdriverio/pageobjects/page.js b/bigbluebutton-html5/tests/webdriverio/pageobjects/page.js deleted file mode 100644 index dfb91b7f1cf930e72eedcf7f11c3a26cc95eb4c7..0000000000000000000000000000000000000000 --- a/bigbluebutton-html5/tests/webdriverio/pageobjects/page.js +++ /dev/null @@ -1,18 +0,0 @@ -'use strict'; - -class Page { - open(path) { - browser.url(path); - } - - pressEnter() { - chromeBrowser.keys('Enter'); - } - - isFirefox() { - return browser.desiredCapabilities.browserName == 'firefox'; - } -} - -module.exports = Page; - diff --git a/bigbluebutton-html5/tests/webdriverio/screenshots/reference/Chrome/1920x1200/Home page viewport_viewport.png b/bigbluebutton-html5/tests/webdriverio/screenshots/reference/Chrome/1920x1200/Home page viewport_viewport.png deleted file mode 100644 index 6ded9d0a35df5176559fce749ce3c36472d5b1b9..0000000000000000000000000000000000000000 Binary files a/bigbluebutton-html5/tests/webdriverio/screenshots/reference/Chrome/1920x1200/Home page viewport_viewport.png and /dev/null differ diff --git a/bigbluebutton-html5/tests/webdriverio/screenshots/reference/Chrome/1920x1200/Join Audio modal_element.png b/bigbluebutton-html5/tests/webdriverio/screenshots/reference/Chrome/1920x1200/Join Audio modal_element.png deleted file mode 100644 index 12f325c6e0a4e3634ae0008ef7969c6665021b78..0000000000000000000000000000000000000000 Binary files a/bigbluebutton-html5/tests/webdriverio/screenshots/reference/Chrome/1920x1200/Join Audio modal_element.png and /dev/null differ diff --git a/bigbluebutton-html5/tests/webdriverio/screenshots/reference/Chrome/1920x1200/Public chat closes_viewport.png b/bigbluebutton-html5/tests/webdriverio/screenshots/reference/Chrome/1920x1200/Public chat closes_viewport.png deleted file mode 100644 index 7df200c2262ed6126c379d83ba3e6bf91acfc6ba..0000000000000000000000000000000000000000 Binary files a/bigbluebutton-html5/tests/webdriverio/screenshots/reference/Chrome/1920x1200/Public chat closes_viewport.png and /dev/null differ diff --git a/bigbluebutton-html5/tests/webdriverio/screenshots/reference/Chrome/1920x1200/Public chat_element.png b/bigbluebutton-html5/tests/webdriverio/screenshots/reference/Chrome/1920x1200/Public chat_element.png deleted file mode 100644 index 6d13fb85d107a4e44058706a1b57c52c26dac738..0000000000000000000000000000000000000000 Binary files a/bigbluebutton-html5/tests/webdriverio/screenshots/reference/Chrome/1920x1200/Public chat_element.png and /dev/null differ diff --git a/bigbluebutton-html5/tests/webdriverio/screenshots/reference/Chrome/1920x1200/Userlist closes_viewport.png b/bigbluebutton-html5/tests/webdriverio/screenshots/reference/Chrome/1920x1200/Userlist closes_viewport.png deleted file mode 100644 index 7683c3dd4a30a4d8bc5de94698d25c9b7f353fef..0000000000000000000000000000000000000000 Binary files a/bigbluebutton-html5/tests/webdriverio/screenshots/reference/Chrome/1920x1200/Userlist closes_viewport.png and /dev/null differ diff --git a/bigbluebutton-html5/tests/webdriverio/screenshots/reference/Chrome/1920x1200/Userlist_element.png b/bigbluebutton-html5/tests/webdriverio/screenshots/reference/Chrome/1920x1200/Userlist_element.png deleted file mode 100644 index f86dfc6814f61ed6992391274f016f5c20afcbbe..0000000000000000000000000000000000000000 Binary files a/bigbluebutton-html5/tests/webdriverio/screenshots/reference/Chrome/1920x1200/Userlist_element.png and /dev/null differ diff --git a/bigbluebutton-html5/tests/webdriverio/screenshots/reference/Chrome/1920x1200/Viewport with both userlist and public chat open_viewport.png b/bigbluebutton-html5/tests/webdriverio/screenshots/reference/Chrome/1920x1200/Viewport with both userlist and public chat open_viewport.png deleted file mode 100644 index b3876c99f01a5ff7e5bab284a8b4fea7367383e2..0000000000000000000000000000000000000000 Binary files a/bigbluebutton-html5/tests/webdriverio/screenshots/reference/Chrome/1920x1200/Viewport with both userlist and public chat open_viewport.png and /dev/null differ diff --git a/bigbluebutton-html5/tests/webdriverio/screenshots/reference/Chrome/1920x1200/Viewport with userlist open_viewport.png b/bigbluebutton-html5/tests/webdriverio/screenshots/reference/Chrome/1920x1200/Viewport with userlist open_viewport.png deleted file mode 100644 index b874dad74c75e4b3d694e607eca78127b3a97dd4..0000000000000000000000000000000000000000 Binary files a/bigbluebutton-html5/tests/webdriverio/screenshots/reference/Chrome/1920x1200/Viewport with userlist open_viewport.png and /dev/null differ diff --git a/bigbluebutton-html5/tests/webdriverio/screenshots/reference/Chrome/960x1200/Home page viewport_viewport.png b/bigbluebutton-html5/tests/webdriverio/screenshots/reference/Chrome/960x1200/Home page viewport_viewport.png deleted file mode 100644 index 804aa47a94d832e0a509996e3d50edb6da94fcb7..0000000000000000000000000000000000000000 Binary files a/bigbluebutton-html5/tests/webdriverio/screenshots/reference/Chrome/960x1200/Home page viewport_viewport.png and /dev/null differ diff --git a/bigbluebutton-html5/tests/webdriverio/screenshots/reference/Chrome/960x1200/Join Audio modal_element.png b/bigbluebutton-html5/tests/webdriverio/screenshots/reference/Chrome/960x1200/Join Audio modal_element.png deleted file mode 100644 index a1ade43b6885d1fa5b1d4ffc5080880f02fcaabd..0000000000000000000000000000000000000000 Binary files a/bigbluebutton-html5/tests/webdriverio/screenshots/reference/Chrome/960x1200/Join Audio modal_element.png and /dev/null differ diff --git a/bigbluebutton-html5/tests/webdriverio/screenshots/reference/Chrome/960x1200/Public chat closes_viewport.png b/bigbluebutton-html5/tests/webdriverio/screenshots/reference/Chrome/960x1200/Public chat closes_viewport.png deleted file mode 100644 index 53b348476f954aa6aebda8cda6d959773dcccd23..0000000000000000000000000000000000000000 Binary files a/bigbluebutton-html5/tests/webdriverio/screenshots/reference/Chrome/960x1200/Public chat closes_viewport.png and /dev/null differ diff --git a/bigbluebutton-html5/tests/webdriverio/screenshots/reference/Chrome/960x1200/Public chat_element.png b/bigbluebutton-html5/tests/webdriverio/screenshots/reference/Chrome/960x1200/Public chat_element.png deleted file mode 100644 index 23c81d1bcdc7fb14de3b99b88ad0b893f90141b8..0000000000000000000000000000000000000000 Binary files a/bigbluebutton-html5/tests/webdriverio/screenshots/reference/Chrome/960x1200/Public chat_element.png and /dev/null differ diff --git a/bigbluebutton-html5/tests/webdriverio/screenshots/reference/Chrome/960x1200/Userlist closes_viewport.png b/bigbluebutton-html5/tests/webdriverio/screenshots/reference/Chrome/960x1200/Userlist closes_viewport.png deleted file mode 100644 index beb4d0e84bfcc27d2dd1b132008a6feb7bb1eebb..0000000000000000000000000000000000000000 Binary files a/bigbluebutton-html5/tests/webdriverio/screenshots/reference/Chrome/960x1200/Userlist closes_viewport.png and /dev/null differ diff --git a/bigbluebutton-html5/tests/webdriverio/screenshots/reference/Chrome/960x1200/Userlist_element.png b/bigbluebutton-html5/tests/webdriverio/screenshots/reference/Chrome/960x1200/Userlist_element.png deleted file mode 100644 index d6e5911baee17b2c2b3ca3e12cee75c2694dc145..0000000000000000000000000000000000000000 Binary files a/bigbluebutton-html5/tests/webdriverio/screenshots/reference/Chrome/960x1200/Userlist_element.png and /dev/null differ diff --git a/bigbluebutton-html5/tests/webdriverio/screenshots/reference/Chrome/960x1200/Viewport with both userlist and public chat open_viewport.png b/bigbluebutton-html5/tests/webdriverio/screenshots/reference/Chrome/960x1200/Viewport with both userlist and public chat open_viewport.png deleted file mode 100644 index c139c46fbf90a48a07f0ad1f14513724aad7776b..0000000000000000000000000000000000000000 Binary files a/bigbluebutton-html5/tests/webdriverio/screenshots/reference/Chrome/960x1200/Viewport with both userlist and public chat open_viewport.png and /dev/null differ diff --git a/bigbluebutton-html5/tests/webdriverio/screenshots/reference/Chrome/960x1200/Viewport with userlist open_viewport.png b/bigbluebutton-html5/tests/webdriverio/screenshots/reference/Chrome/960x1200/Viewport with userlist open_viewport.png deleted file mode 100644 index 78119f3a1eb08d41f446f580119efc5f7c521d12..0000000000000000000000000000000000000000 Binary files a/bigbluebutton-html5/tests/webdriverio/screenshots/reference/Chrome/960x1200/Viewport with userlist open_viewport.png and /dev/null differ diff --git a/bigbluebutton-html5/tests/webdriverio/screenshots/reference/Firefox/1920x1200/Home page viewport_viewport.png b/bigbluebutton-html5/tests/webdriverio/screenshots/reference/Firefox/1920x1200/Home page viewport_viewport.png deleted file mode 100644 index cb3d41841634f03cb086e22abaa9723110f12d84..0000000000000000000000000000000000000000 Binary files a/bigbluebutton-html5/tests/webdriverio/screenshots/reference/Firefox/1920x1200/Home page viewport_viewport.png and /dev/null differ diff --git a/bigbluebutton-html5/tests/webdriverio/screenshots/reference/Firefox/1920x1200/Join Audio modal_element.png b/bigbluebutton-html5/tests/webdriverio/screenshots/reference/Firefox/1920x1200/Join Audio modal_element.png deleted file mode 100644 index 9d70270cc7c63c487e5d9b05203d22ec7f899b74..0000000000000000000000000000000000000000 Binary files a/bigbluebutton-html5/tests/webdriverio/screenshots/reference/Firefox/1920x1200/Join Audio modal_element.png and /dev/null differ diff --git a/bigbluebutton-html5/tests/webdriverio/screenshots/reference/Firefox/1920x1200/Public chat closes_viewport.png b/bigbluebutton-html5/tests/webdriverio/screenshots/reference/Firefox/1920x1200/Public chat closes_viewport.png deleted file mode 100644 index f016a6f6ce4492c37b70980ac4bf08fc04a18367..0000000000000000000000000000000000000000 Binary files a/bigbluebutton-html5/tests/webdriverio/screenshots/reference/Firefox/1920x1200/Public chat closes_viewport.png and /dev/null differ diff --git a/bigbluebutton-html5/tests/webdriverio/screenshots/reference/Firefox/1920x1200/Public chat_element.png b/bigbluebutton-html5/tests/webdriverio/screenshots/reference/Firefox/1920x1200/Public chat_element.png deleted file mode 100644 index e1e9925372a711694855e73e0f653862890e657f..0000000000000000000000000000000000000000 Binary files a/bigbluebutton-html5/tests/webdriverio/screenshots/reference/Firefox/1920x1200/Public chat_element.png and /dev/null differ diff --git a/bigbluebutton-html5/tests/webdriverio/screenshots/reference/Firefox/1920x1200/Userlist closes_viewport.png b/bigbluebutton-html5/tests/webdriverio/screenshots/reference/Firefox/1920x1200/Userlist closes_viewport.png deleted file mode 100644 index 6c5e080f1f4721c6e25ee0d75002f3c344c51621..0000000000000000000000000000000000000000 Binary files a/bigbluebutton-html5/tests/webdriverio/screenshots/reference/Firefox/1920x1200/Userlist closes_viewport.png and /dev/null differ diff --git a/bigbluebutton-html5/tests/webdriverio/screenshots/reference/Firefox/1920x1200/Userlist_element.png b/bigbluebutton-html5/tests/webdriverio/screenshots/reference/Firefox/1920x1200/Userlist_element.png deleted file mode 100644 index 7513163591e0d7839ecd3c71e64e78dc776d9239..0000000000000000000000000000000000000000 Binary files a/bigbluebutton-html5/tests/webdriverio/screenshots/reference/Firefox/1920x1200/Userlist_element.png and /dev/null differ diff --git a/bigbluebutton-html5/tests/webdriverio/screenshots/reference/Firefox/1920x1200/Viewport with both userlist and public chat open_viewport.png b/bigbluebutton-html5/tests/webdriverio/screenshots/reference/Firefox/1920x1200/Viewport with both userlist and public chat open_viewport.png deleted file mode 100644 index 56a1708c8918369cfdc1947fc83ad86f5b2af234..0000000000000000000000000000000000000000 Binary files a/bigbluebutton-html5/tests/webdriverio/screenshots/reference/Firefox/1920x1200/Viewport with both userlist and public chat open_viewport.png and /dev/null differ diff --git a/bigbluebutton-html5/tests/webdriverio/screenshots/reference/Firefox/1920x1200/Viewport with userlist open_viewport.png b/bigbluebutton-html5/tests/webdriverio/screenshots/reference/Firefox/1920x1200/Viewport with userlist open_viewport.png deleted file mode 100644 index 6a80091407309a5ca702aed1a4c1420624e84772..0000000000000000000000000000000000000000 Binary files a/bigbluebutton-html5/tests/webdriverio/screenshots/reference/Firefox/1920x1200/Viewport with userlist open_viewport.png and /dev/null differ diff --git a/bigbluebutton-html5/tests/webdriverio/screenshots/reference/Firefox/960x1200/Home page viewport_viewport.png b/bigbluebutton-html5/tests/webdriverio/screenshots/reference/Firefox/960x1200/Home page viewport_viewport.png deleted file mode 100644 index 3cd5327e9476bc7094c35ce452a9473fdd1656fc..0000000000000000000000000000000000000000 Binary files a/bigbluebutton-html5/tests/webdriverio/screenshots/reference/Firefox/960x1200/Home page viewport_viewport.png and /dev/null differ diff --git a/bigbluebutton-html5/tests/webdriverio/screenshots/reference/Firefox/960x1200/Join Audio modal_element.png b/bigbluebutton-html5/tests/webdriverio/screenshots/reference/Firefox/960x1200/Join Audio modal_element.png deleted file mode 100644 index 0027d8f85dd4cfbfb655d0caca09909cfb701da1..0000000000000000000000000000000000000000 Binary files a/bigbluebutton-html5/tests/webdriverio/screenshots/reference/Firefox/960x1200/Join Audio modal_element.png and /dev/null differ diff --git a/bigbluebutton-html5/tests/webdriverio/screenshots/reference/Firefox/960x1200/Public chat closes_viewport.png b/bigbluebutton-html5/tests/webdriverio/screenshots/reference/Firefox/960x1200/Public chat closes_viewport.png deleted file mode 100644 index ca9618975b69dd6eb9b3ad87e1ed594ca7360a13..0000000000000000000000000000000000000000 Binary files a/bigbluebutton-html5/tests/webdriverio/screenshots/reference/Firefox/960x1200/Public chat closes_viewport.png and /dev/null differ diff --git a/bigbluebutton-html5/tests/webdriverio/screenshots/reference/Firefox/960x1200/Public chat_element.png b/bigbluebutton-html5/tests/webdriverio/screenshots/reference/Firefox/960x1200/Public chat_element.png deleted file mode 100644 index 032ea67dea469d10218785340595edc58668186f..0000000000000000000000000000000000000000 Binary files a/bigbluebutton-html5/tests/webdriverio/screenshots/reference/Firefox/960x1200/Public chat_element.png and /dev/null differ diff --git a/bigbluebutton-html5/tests/webdriverio/screenshots/reference/Firefox/960x1200/Userlist closes_viewport.png b/bigbluebutton-html5/tests/webdriverio/screenshots/reference/Firefox/960x1200/Userlist closes_viewport.png deleted file mode 100644 index 5d061eca1414da8f969a63c1136f01abbc5362e4..0000000000000000000000000000000000000000 Binary files a/bigbluebutton-html5/tests/webdriverio/screenshots/reference/Firefox/960x1200/Userlist closes_viewport.png and /dev/null differ diff --git a/bigbluebutton-html5/tests/webdriverio/screenshots/reference/Firefox/960x1200/Userlist_element.png b/bigbluebutton-html5/tests/webdriverio/screenshots/reference/Firefox/960x1200/Userlist_element.png deleted file mode 100644 index 65065a533a55c3946b855fe74b7756a7b8407aed..0000000000000000000000000000000000000000 Binary files a/bigbluebutton-html5/tests/webdriverio/screenshots/reference/Firefox/960x1200/Userlist_element.png and /dev/null differ diff --git a/bigbluebutton-html5/tests/webdriverio/screenshots/reference/Firefox/960x1200/Viewport with both userlist and public chat open_viewport.png b/bigbluebutton-html5/tests/webdriverio/screenshots/reference/Firefox/960x1200/Viewport with both userlist and public chat open_viewport.png deleted file mode 100644 index dd68245cd8203a1d84defa3045d863c1a2fa6a58..0000000000000000000000000000000000000000 Binary files a/bigbluebutton-html5/tests/webdriverio/screenshots/reference/Firefox/960x1200/Viewport with both userlist and public chat open_viewport.png and /dev/null differ diff --git a/bigbluebutton-html5/tests/webdriverio/screenshots/reference/Firefox/960x1200/Viewport with userlist open_viewport.png b/bigbluebutton-html5/tests/webdriverio/screenshots/reference/Firefox/960x1200/Viewport with userlist open_viewport.png deleted file mode 100644 index ee904f4f5bfadbce110295340e49dcd9a05ff541..0000000000000000000000000000000000000000 Binary files a/bigbluebutton-html5/tests/webdriverio/screenshots/reference/Firefox/960x1200/Viewport with userlist open_viewport.png and /dev/null differ diff --git a/bigbluebutton-html5/tests/webdriverio/specs/acceptance/login.spec.js b/bigbluebutton-html5/tests/webdriverio/specs/acceptance/login.spec.js deleted file mode 100644 index 5f2601ea95120d4fcaca57d4fc3eb908addc1614..0000000000000000000000000000000000000000 --- a/bigbluebutton-html5/tests/webdriverio/specs/acceptance/login.spec.js +++ /dev/null @@ -1,68 +0,0 @@ -'use strict'; - -let LandingPage = require('../../pageobjects/landing.page'); -let chai = require('chai'); -let utils = require('../../utils'); - -describe('Landing page', function () { - - beforeEach(function () { - jasmine.DEFAULT_TIMEOUT_INTERVAL = 30000; // default value is 10000 - }); - - it('should have correct title', function () { - LandingPage.open(); - utils.assertTitle(LandingPage.title); - }); - - it('should allow user to login if the username is specified and the Join button is clicked', - function () { - LandingPage.open(); - - utils.setUsername(new Map([ - ['chromeBrowser', 'Alex'], - ['chromeBrowser', 'Anton'], - ['firefoxBrowser', 'Danny'], - ['firefoxBrowser', 'Maxim'], - ['chromeMobileBrowser', 'Oswaldo'] - ])); - - LandingPage.joinWithButtonClick(); - LandingPage.loadedHomePageElement.waitForExist(5000); - }); - - it('should not allow user to login if the username is not specified (login using a button)', - function () { - LandingPage.open(); - - // we intentionally don't enter username here - - LandingPage.joinWithButtonClick(); - - browser.pause(5000); // amount of time we usually wait for the home page to appear - - // verify that we are still on the landing page - utils.assertUrl(LandingPage.url); - }); - - it('should allow user to login if the username is specified and then Enter key is pressed', - function () { // Chrome-only - LandingPage.open(); - - chromeBrowser.setValue(LandingPage.usernameInputSelector, 'Maxim'); - LandingPage.joinWithEnterKey(); - chromeBrowser.waitForExist(LandingPage.loadedHomePageSelector, 5000); - }); - - it('should not allow user to login if the username is not specified (login using Enter key)', - function () { // Chrome-only - LandingPage.open(); - - // we intentionally don't enter username here - - LandingPage.joinWithEnterKey(); - chromeBrowser.pause(5000); // amount of time we usually wait for the gome page to appear - chai.expect(browser.getUrl().chromeBrowser).to.equal(LandingPage.url); - }); -}); - diff --git a/bigbluebutton-html5/tests/webdriverio/specs/visual-regression/main.spec.js b/bigbluebutton-html5/tests/webdriverio/specs/visual-regression/main.spec.js deleted file mode 100644 index 34bb69d7ee3b80cccc69323b4e00ccfd46866161..0000000000000000000000000000000000000000 --- a/bigbluebutton-html5/tests/webdriverio/specs/visual-regression/main.spec.js +++ /dev/null @@ -1,83 +0,0 @@ -'use strict'; - -let HomePage = require('../../pageobjects/home.page'); -let expect = require('chai').expect; -let utils = require('../../utils'); - -describe('Screenshots:', function() { - it('Join Audio modal', function() { - HomePage.login('testuser', 'Demo Meeting'); - HomePage.audioModalHeaderElement.waitForExist(7000); - utils.expectImageMatch(browser.checkElement(HomePage.audioModalSelector), 'Join Audio modal isn\'t the same'); - }); - - it('Home page viewport', function() { - HomePage.modalBaseCloseButtonElement.click(); - utils.expectImageMatch(browser.checkViewport(), 'Home page viewport isn\'t the same'); - }); - - ////////////////////////////// - // Userlist + Chat - - it('Userlist', function() { - HomePage.userListToggleButtonElement.click(); - utils.expectImageMatch(browser.checkElement(HomePage.userListContentSelector), 'Userlist content isn\'t the same'); - }); - - it('Viewport with userlist open', function() { - utils.expectImageMatch(browser.checkViewport(), 'Home page viewport isn\'t the same after we open userlist'); - }); - - /*it('Userlist avatar', function() { - utils.expectImageMatch(browser.checkElement(HomePage.userAvatarElement), 'Userlist avatar isn\'t the same'); - });*/ - - it('Public chat', function() { - HomePage.publicChatLinkElement.click(); - utils.expectImageMatch(browser.checkElement(HomePage.publicChatSelector), 'Public chat isn\'t the same'); - }); - - it('Viewport with both userlist and public chat open', function() { - browser.moveToObject(HomePage.chatTitleSelector); // avoid hover effect on the Public Chat tab - utils.expectImageMatch(browser.checkViewport(), 'Home page viewport isn\'t the same after we open both userlist and public chat'); - }); - - /*it('Public chat dropdown', function() { - HomePage.chatDropdownTriggerElement.click(); - utils.expectImageMatch(browser.checkElement(HomePage.publicChatSelector), 'Public chat dropdown isn\'t the same'); - });*/ - - it('Public chat closes', function() { - HomePage.chatTitleElement.click(); - utils.expectImageMatch(browser.checkViewport(), 'Home page viewport isn\'t the same after we closed public chat'); - }); - - it('Userlist closes', function() { - HomePage.userListToggleButtonElement.click(); - utils.expectImageMatch(browser.checkViewport(), 'Home page viewport isn\'t the same after we close the userlist'); - }); - - ////////////////////////////// - // Settings: - - it('Settings dropdown', function() { - HomePage.settingsDropdownTriggerElement.waitForExist(2000); - HomePage.settingsDropdownTriggerElement.click(); - HomePage.settingsDropdownElement.waitForExist(2000); - browser.moveToObject(HomePage.settingsDropdownLogoutButtonSelector); // avoid Options tooltip - utils.expectImageMatch(browser.checkElement(HomePage.settingsDropdownSelector), 'Settings dropdown isn\'t the same'); - }); - - it('Logout popup', function() { - HomePage.settingsDropdownLogoutButtonElement.waitForExist(2000); - HomePage.settingsDropdownLogoutButtonElement.click(); - HomePage.logoutModalElement.waitForExist(2000); - utils.expectImageMatch(browser.checkElement(HomePage.logoutModalSelector)); - }); - - it('Logout popup closes', function() { - HomePage.modalDismissButtonElement.click(); - utils.expectImageMatch(browser.checkViewport(), 'Home page viewport isn\'t the same after we close Logout modal'); - }); -}); - diff --git a/bigbluebutton-html5/tests/webdriverio/utils.js b/bigbluebutton-html5/tests/webdriverio/utils.js deleted file mode 100644 index 00aa3146fd0a87936ba57e5cf145a8182a9063cd..0000000000000000000000000000000000000000 --- a/bigbluebutton-html5/tests/webdriverio/utils.js +++ /dev/null @@ -1,26 +0,0 @@ -'use strict'; - -let expect = require('chai').expect; -let LandingPage = require('./pageobjects/landing.page'); - -class Utils { - assertTitle(title) { - browser.remotes.forEach(function(browserName) { - expect(browser.select(browserName).getTitle()).to.equal(title); - }); - } - assertUrl(url) { - browser.remotes.forEach(function(browserName) { - expect(browser.getUrl()[browserName]).to.equal(url); - }); - } - setUsername(map) { - map.forEach((v, k) => browser.select(k).setValue(LandingPage.usernameInputSelector, v)); - } - expectImageMatch(results, errorMessage) { - results.forEach((result) => expect(result.isExactSameImage, errorMessage).to.be.true); - } -} - -module.exports = new Utils(); - diff --git a/bigbluebutton-html5/tests/webdriverio/wdio.accept.conf.js b/bigbluebutton-html5/tests/webdriverio/wdio.accept.conf.js deleted file mode 100644 index 80b4a0b9064599e38af93ba5180b79262d111dcd..0000000000000000000000000000000000000000 --- a/bigbluebutton-html5/tests/webdriverio/wdio.accept.conf.js +++ /dev/null @@ -1,42 +0,0 @@ -let merge = require('deepmerge'); -let wdioBaseConf = require('./wdio.base.conf'); - -exports.config = merge(wdioBaseConf.config, { - - specs: ['tests/webdriverio/specs/acceptance/**/*.spec.js'], - - capabilities: { - chromeBrowser: { - desiredCapabilities: { - browserName: 'chrome' - } - }, - firefoxBrowser: { - desiredCapabilities: { - browserName: 'firefox' - } - }, - chromeMobileBrowser: { - desiredCapabilities: { - browserName: 'chrome', - chromeOptions: { - mobileEmulation: { - deviceName: 'iPhone 6' - } - } - } - } - }, - - suites: { - login: [ - 'tests/webdriverio/specs/acceptance/login.spec.js', - ], - }, - before: function() { - // make the properties that browsers share and the list of browserNames available: - browser.remotes = Object.keys(exports.config.capabilities); - browser.baseUrl = exports.config.baseUrl; - }, -}); - diff --git a/bigbluebutton-html5/tests/webdriverio/wdio.base.conf.js b/bigbluebutton-html5/tests/webdriverio/wdio.base.conf.js deleted file mode 100644 index 1b0626fbd5539a5213f4337c559ab3f4b7aa0f8f..0000000000000000000000000000000000000000 --- a/bigbluebutton-html5/tests/webdriverio/wdio.base.conf.js +++ /dev/null @@ -1,6 +0,0 @@ -exports.config = { - baseUrl: 'http://localhost:8080', - framework: 'jasmine', - reporters: ['spec'], -}; - diff --git a/bigbluebutton-html5/tests/webdriverio/wdio.vreg.conf.js b/bigbluebutton-html5/tests/webdriverio/wdio.vreg.conf.js deleted file mode 100644 index 8e29331dec044c0305150dd676eae21158951554..0000000000000000000000000000000000000000 --- a/bigbluebutton-html5/tests/webdriverio/wdio.vreg.conf.js +++ /dev/null @@ -1,71 +0,0 @@ -let path = require('path'); -let VisualRegressionCompare = require('wdio-visual-regression-service/compare'); -let _ = require('lodash'); -let wdioBaseConf = require('./wdio.base.conf'); - -function getScreenshotName(basePath) { - return function(context) { - var type = context.type; - var testName = context.test.title; - var browserVersion = parseInt(context.browser.version, 10); - var browserName = process.env.BROWSER_NAME === 'chrome_mobile' ? process.env.DEVICE_NAME : context.browser.name; - var browserViewport = context.meta.viewport; - var browserWidth = browserViewport.width; - var browserHeight = browserViewport.height; - return path.join( - basePath, - browserName, - `${browserWidth}x${browserHeight}`, - `${testName}_${type}.png` - ); - }; -} - -exports.config = _.merge(wdioBaseConf.config, { - specs: [ - 'tests/webdriverio/specs/visual-regression/**/*.spec.js' - ], - - capabilities: [process.env.BROWSER_NAME=='chrome_mobile' ? { - maxInstances: 5, - browserName: 'chrome', - chromeOptions: { - mobileEmulation: { - deviceName: process.env.DEVICE_NAME - } - } - } : { - maxInstances: 5, - browserName: process.env.BROWSER_NAME - }], - - baseUrl: 'http://localhost:8080', - framework: 'jasmine', - reporters: ['spec'], - - sync: true, - logLevel: 'silent', - bail: 0, - host: 'localhost', - port: 4444, - waitforTimeout: 30000, - connectionRetryTimeout: 90000, - connectionRetryCount: 3, - services: ['visual-regression'], - visualRegression: { - compare: new VisualRegressionCompare.LocalCompare({ - referenceName: getScreenshotName(path.join(process.cwd(), 'tests/webdriverio/screenshots/reference')), - screenshotName: getScreenshotName(path.join(process.cwd(), 'tests/webdriverio/screenshots/screen')), - diffName: getScreenshotName(path.join(process.cwd(), 'tests/webdriverio/screenshots/diff')), - misMatchTolerance: 0.01, - }), - viewports: process.env.BROWSER_NAME === 'chrome_mobile' ? [] : [{ width: 1920, height: 1200 }, { width: 960, height: 1200 }], - viewportChangePause: 300, - orientations: ['landscape'], - }, - - jasmineNodeOpts: { - defaultTimeoutInterval: 30000 - }, -}); - diff --git a/bigbluebutton-web/build.gradle b/bigbluebutton-web/build.gradle index e896556837ac125afde499b1e96e1196d07edd4c..1da581703e573a0f15b9a7c83bedd670346f009f 100755 --- a/bigbluebutton-web/build.gradle +++ b/bigbluebutton-web/build.gradle @@ -2,9 +2,9 @@ apply plugin: 'java' apply plugin: 'eclipse' task resolveDeps(type: Copy) { - into('lib') - from configurations.default - from configurations.default.allArtifacts.file + into('lib') + from configurations.default + from configurations.default.allArtifacts.file } repositories { @@ -12,41 +12,34 @@ repositories { mavenLocal() } +configurations { + runtime.exclude group: "org.slf4j", module: "slf4j-api" + compile.exclude group: "org.red5", module: "red5-server-common" +} + dependencies { - compile 'org.bigbluebutton:bbb-common-web:0.0.2-SNAPSHOT' - + compile 'org.bigbluebutton:bbb-common-web:0.0.3-SNAPSHOT' // XML creation speedup compile 'org.freemarker:freemarker:2.3.28' - //junit - compile 'junit:junit:4.8.2' + //junit + testCompile 'junit:junit:4.12' - // Testing - testCompile 'org.testng:testng:5.8@jar' - testCompile 'org.easymock:easymock:2.4@jar' + // Testing + testCompile 'org.testng:testng:6.14.3@jar' + testCompile 'org.easymock:easymock:4.0.1@jar' } sourceSets { - main { - java { - srcDir 'src/java' - } - resources { - srcDir 'src/resources' - } - } - test { - java { - srcDir 'test/unit' - } - resources { - srcDir 'test/resources' - } -} -} - -test { - useTestNG() + main { + java { srcDir 'src/java' } + resources { srcDir 'src/resources' } + } + test { + java { srcDir 'test/unit' } + resources { srcDir 'test/resources' } + } } +test { useTestNG() } diff --git a/bigbluebutton-web/build.sh b/bigbluebutton-web/build.sh index f725095d2b29cef1098d538598406c12e3a0aff8..a18f016ad30ffbe69eec5c567c0a45f69920c398 100755 --- a/bigbluebutton-web/build.sh +++ b/bigbluebutton-web/build.sh @@ -1,4 +1,3 @@ gradle clean gradle resolveDeps grails clean - diff --git a/bigbluebutton-web/grails-app/conf/application.conf b/bigbluebutton-web/grails-app/conf/application.conf index f5ba5981ea95dd5f9aa8871e4a5e9b524822df44..c6fc978954d01f9506e65f33ab525eb6dbbe82c3 100644 --- a/bigbluebutton-web/grails-app/conf/application.conf +++ b/bigbluebutton-web/grails-app/conf/application.conf @@ -10,7 +10,7 @@ akka { loggers = ["akka.event.slf4j.Slf4jLogger"] loglevel = "DEBUG" - rediscala-publish-worker-dispatcher { + redis-publish-worker-dispatcher { mailbox-type = "akka.dispatch.SingleConsumerOnlyUnboundedMailbox" # Throughput defines the maximum number of messages to be # processed per actor before the thread jumps to the next actor. @@ -18,7 +18,7 @@ akka { throughput = 512 } - rediscala-subscriber-worker-dispatcher { + redis-subscriber-worker-dispatcher { mailbox-type = "akka.dispatch.SingleConsumerOnlyUnboundedMailbox" # Throughput defines the maximum number of messages to be # processed per actor before the thread jumps to the next actor. diff --git a/bigbluebutton-web/grails-app/conf/spring/bbb-redis-messaging.xml b/bigbluebutton-web/grails-app/conf/spring/bbb-redis-messaging.xml index 3c8c2765b3a45a1703d8b3ef705fd6d3b52a4ba6..4c95196bdb63128883c2d7732e8b2467dd547699 100755 --- a/bigbluebutton-web/grails-app/conf/spring/bbb-redis-messaging.xml +++ b/bigbluebutton-web/grails-app/conf/spring/bbb-redis-messaging.xml @@ -3,7 +3,7 @@ BigBlueButton open source conferencing system - http://www.bigbluebutton.org/ -Copyright (c) 2012 BigBlueButton Inc. and by respective authors (see below). +Copyright (c) 2018 BigBlueButton Inc. and by respective authors (see below). This program is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software @@ -19,14 +19,38 @@ with BigBlueButton; if not, see <http://www.gnu.org/licenses/>. --> <beans xmlns="http://www.springframework.org/schema/beans" - xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" - xmlns:util="http://www.springframework.org/schema/util" - xsi:schemaLocation="http://www.springframework.org/schema/beans + xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xmlns:util="http://www.springframework.org/schema/util" + xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-2.5.xsd http://www.springframework.org/schema/util http://www.springframework.org/schema/util/spring-util-2.0.xsd "> - - + <bean id="redisStorageService" + class="org.bigbluebutton.common2.redis.RedisStorageService" + init-method="start" destroy-method="stop"> + <property name="host" value="${redisHost}" /> + <property name="port" value="${redisPort}" /> + <property name="password" value="${redisPassword:}" /> + <property name="clientName" value="BbbWeb" /> + </bean> + + <bean id="redisMessageHandler" + class="org.bigbluebutton.api.messaging.ReceivedMessageHandler" + init-method="start" destroy-method="stop"> + </bean> + + <bean id="redisMessageDistributor" + class="org.bigbluebutton.api.messaging.MessageDistributor"> + <property name="messageHandler"> + <ref local="redisMessageHandler" /> + </property> + <property name="messageListeners"> + <set> + <ref bean="meetingService" /> + <ref bean="keepAliveService" /> + </set> + </property> + </bean> </beans> diff --git a/bigbluebutton-web/grails-app/conf/spring/bbb-redis-pool.xml b/bigbluebutton-web/grails-app/conf/spring/bbb-redis-pool.xml deleted file mode 100755 index 7de4b5722c6c893f3b0e1a700523af21c34a7624..0000000000000000000000000000000000000000 --- a/bigbluebutton-web/grails-app/conf/spring/bbb-redis-pool.xml +++ /dev/null @@ -1,32 +0,0 @@ -<?xml version="1.0" encoding="UTF-8"?> -<!-- - -BigBlueButton open source conferencing system - http://www.bigbluebutton.org/ - -Copyright (c) 2012 BigBlueButton Inc. and by respective authors (see below). - -This program is free software; you can redistribute it and/or modify it under the -terms of the GNU Lesser General Public License as published by the Free Software -Foundation; either version 3.0 of the License, or (at your option) any later -version. - -BigBlueButton is distributed in the hope that it will be useful, but WITHOUT ANY -WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A -PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. - -You should have received a copy of the GNU Lesser General Public License along -with BigBlueButton; if not, see <http://www.gnu.org/licenses/>. - ---> -<beans xmlns="http://www.springframework.org/schema/beans" - xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" - xmlns:util="http://www.springframework.org/schema/util" - xsi:schemaLocation="http://www.springframework.org/schema/beans - http://www.springframework.org/schema/beans/spring-beans-2.5.xsd - http://www.springframework.org/schema/util - http://www.springframework.org/schema/util/spring-util-2.0.xsd - "> - - - -</beans> diff --git a/bigbluebutton-web/grails-app/conf/spring/resources.xml b/bigbluebutton-web/grails-app/conf/spring/resources.xml index 098e37e48cc009e85761c5ae90804a1ee2b029fa..7a94317cf177761d6f872050d147997241495779 100755 --- a/bigbluebutton-web/grails-app/conf/spring/resources.xml +++ b/bigbluebutton-web/grails-app/conf/spring/resources.xml @@ -61,28 +61,6 @@ with BigBlueButton; if not, see <http://www.gnu.org/licenses/>. <constructor-arg index="3" value="${screenshareConfSuffix}"/> </bean> - <bean id="redisStorageService" class="org.bigbluebutton.api.messaging.RedisStorageService" - init-method="start" destroy-method="stop"> - <property name="host" value="${redisHost}"/> - <property name="port" value="${redisPort}"/> - </bean> - - - <bean id="redisMessageHandler" class="org.bigbluebutton.api.messaging.ReceivedMessageHandler" - init-method="start" destroy-method="stop"> - </bean> - - - <bean id="redisMessageDistributor" class="org.bigbluebutton.api.messaging.MessageDistributor"> - <property name="messageHandler"> <ref local="redisMessageHandler"/> </property> - <property name="messageListeners"> - <set> - <ref bean="meetingService" /> - <ref bean="keepAliveService" /> - </set> - </property> - </bean> - <bean id="recordingServiceHelper" class="org.bigbluebutton.api.util.RecordingMetadataReaderHelper"> <property name="recordingServiceGW" ref="recordingServiceGW"/> </bean> @@ -154,9 +132,6 @@ with BigBlueButton; if not, see <http://www.gnu.org/licenses/>. </bean> <import resource="doc-conversion.xml"/> - <import resource="bbb-redis-pool.xml"/> - <!-- <import resource="bbb-redis-messaging.xml"/> - --> <import resource="turn-stun-servers.xml"/> </beans> diff --git a/bigbluebutton-web/grails-app/controllers/org/bigbluebutton/web/controllers/ApiController.groovy b/bigbluebutton-web/grails-app/controllers/org/bigbluebutton/web/controllers/ApiController.groovy index 5546cae7210b261a2a568cbdead0a46ca76ccdf0..89464c3d4aeeb51491cd6863cc456a8f0a4310fd 100755 --- a/bigbluebutton-web/grails-app/controllers/org/bigbluebutton/web/controllers/ApiController.groovy +++ b/bigbluebutton-web/grails-app/controllers/org/bigbluebutton/web/controllers/ApiController.groovy @@ -1420,10 +1420,12 @@ class ApiController { UserSession userSession = null; String respMessage = "Session " + sessionToken + " not found." - if (meetingService.getUserSessionWithAuthToken(sessionToken) == null) { + if (!session[sessionToken]) { + reject = true; + } else if (meetingService.getUserSessionWithAuthToken(sessionToken) == null) { reject = true; respMessage = "Session " + sessionToken + " not found." - } else { + } else { us = meetingService.getUserSessionWithAuthToken(sessionToken); meeting = meetingService.getMeeting(us.meetingID); if (meeting == null || meeting.isForciblyEnded()) { @@ -1560,7 +1562,9 @@ class ApiController { println("Session token = [" + sessionToken + "]") } - if (meetingService.getUserSessionWithAuthToken(sessionToken) == null) + if (!session[sessionToken]) { + reject = true; + } else if (meetingService.getUserSessionWithAuthToken(sessionToken) == null) reject = true; else { us = meetingService.getUserSessionWithAuthToken(sessionToken); diff --git a/bigbluebutton-web/pres-checker/build.gradle b/bigbluebutton-web/pres-checker/build.gradle index c918e988075707c2972cfe7f36fa80d37387e134..dfb9bee22f6f85534f9c9599f99f119a7dd6fac8 100755 --- a/bigbluebutton-web/pres-checker/build.gradle +++ b/bigbluebutton-web/pres-checker/build.gradle @@ -1,15 +1,15 @@ apply plugin: 'java' -sourceCompatibility=1.8 -targetCompatibility=1.8 +sourceCompatibility = 1.8 +targetCompatibility = 1.8 version = '0.0.1' -archivesBaseName = 'bbb-pres-check' +archivesBaseName = 'bbb-pres-check' task resolveDeps(type: Copy) { - into('lib') - from configurations.default - from configurations.default.allArtifacts.file + into('lib') + from configurations.default + from configurations.default.allArtifacts.file } repositories { @@ -17,22 +17,21 @@ repositories { mavenLocal() } -dependencies { - compile 'org.apache.poi:poi:3.17@jar' - compile 'org.apache.poi:poi-ooxml:3.17@jar' - compile 'org.apache.poi:poi-ooxml-schemas:3.17@jar' - compile 'commons-io:commons-io:2.6@jar' - compile 'org.apache.commons:commons-lang3:3.7@jar' - compile 'org.apache.commons:commons-collections4:4.2@jar' - compile 'org.apache.xmlbeans:xmlbeans:3.0.0@jar' +dependencies { + compile 'org.apache.poi:poi:4.0.0@jar' + compile 'org.apache.poi:poi-ooxml:4.0.0@jar' + compile 'org.apache.poi:poi-ooxml-schemas:4.0.0@jar' + compile 'commons-io:commons-io:2.6@jar' + compile 'org.apache.commons:commons-lang3:3.8.1@jar' + compile 'org.apache.commons:commons-collections4:4.2@jar' + compile 'org.apache.xmlbeans:xmlbeans:3.0.2@jar' } jar { - manifest.mainAttributes("Permissions": "all-permissions") - manifest.mainAttributes("Codebase": "*") - manifest.mainAttributes("Application-Name": "BigBlueButton Presentation Checker") - manifest.mainAttributes("Application-Library-Allowable-Codebase": "*") - manifest.mainAttributes("Caller-Allowable-Codebase": "*") - manifest.mainAttributes("Trusted-Only": "true") + manifest.mainAttributes("Permissions": "all-permissions") + manifest.mainAttributes("Codebase": "*") + manifest.mainAttributes("Application-Name": "BigBlueButton Presentation Checker") + manifest.mainAttributes("Application-Library-Allowable-Codebase": "*") + manifest.mainAttributes("Caller-Allowable-Codebase": "*") + manifest.mainAttributes("Trusted-Only": "true") } - diff --git a/bigbluebutton-web/pres-checker/build.sh b/bigbluebutton-web/pres-checker/build.sh index 1f5048f55723f9d76bfbb4b1bcf08e307fe7069d..d2a60ea6951cb7b6607e066cc3ff1812419a0927 100755 --- a/bigbluebutton-web/pres-checker/build.sh +++ b/bigbluebutton-web/pres-checker/build.sh @@ -1,4 +1,3 @@ gradle clean gradle jar cp build/lib/*.jar lib - diff --git a/bigbluebutton-web/pres-checker/run.sh b/bigbluebutton-web/pres-checker/run.sh index 04fc97183cb1bc79fd22dc6a6f395865a4409bbe..faa48086ab4fed3f2534705e3ab0de49af682724 100755 --- a/bigbluebutton-web/pres-checker/run.sh +++ b/bigbluebutton-web/pres-checker/run.sh @@ -1,2 +1 @@ java -cp "/usr/share/prescheck/lib/*" org.bigbluebutton.prescheck.Main $@ - diff --git a/bigbluebutton-web/run.sh b/bigbluebutton-web/run.sh index b707bed78f2b5e48049e3430bfb67c6257aa8658..a9e681a95cbf97223c494f7ad14b2f170cb7eee4 100755 --- a/bigbluebutton-web/run.sh +++ b/bigbluebutton-web/run.sh @@ -1,2 +1 @@ grails -Dserver.port=8989 run-war - diff --git a/bigbluebutton-web/test/unit/org/bigbluebutton/api/messaging/NullMessagingService.java b/bigbluebutton-web/test/unit/org/bigbluebutton/api/messaging/NullMessagingService.java index 8c12f4d2d9130c88bef4b42509c1ecfbbeb75e9e..03ee0fdfa0775e25b3714557aa7fc41926babd2e 100644 --- a/bigbluebutton-web/test/unit/org/bigbluebutton/api/messaging/NullMessagingService.java +++ b/bigbluebutton-web/test/unit/org/bigbluebutton/api/messaging/NullMessagingService.java @@ -5,79 +5,65 @@ import java.util.Map; public class NullMessagingService implements MessagingService { - public void start() { - // TODO Auto-generated method stub + public void start() { + // TODO Auto-generated method stub - } + } - public void stop() { - // TODO Auto-generated method stub + public void stop() { + // TODO Auto-generated method stub - } + } - @Override - public void recordMeetingInfo(String meetingId, Map<String, String> info) { - // TODO Auto-generated method stub + @Override + public void recordMeetingInfo(String meetingId, Map<String, String> info) { + // TODO Auto-generated method stub - } + } - /*@Override - public void recordMeetingMetadata(String meetingId, - Map<String, String> metadata) { - // TODO Auto-generated method stub + /* + * @Override public void recordMeetingMetadata(String meetingId, Map<String, + * String> metadata) { // TODO Auto-generated method stub + * + * } + */ - }*/ + public void addListener(MessageListener listener) { + // TODO Auto-generated method stub - @Override - public void endMeeting(String meetingId) { - // TODO Auto-generated method stub + } - } + public void removeListener(MessageListener listener) { + // TODO Auto-generated method stub - @Override - public void send(String channel, String message) { - // TODO Auto-generated method stub + } - } + public void destroyMeeting(String meetingID) { + // TODO Auto-generated method stub - public void addListener(MessageListener listener) { - // TODO Auto-generated method stub + } - } + public void createMeeting(String meetingID, String externalMeetingID, String meetingName, Boolean recorded, + String voiceBridge, Long duration) { + // TODO Auto-generated method stub - public void removeListener(MessageListener listener) { - // TODO Auto-generated method stub + } - } + public void sendPolls(String meetingId, String title, String question, String questionType, List<String> answers) { + // TODO Auto-generated method stub - public void destroyMeeting(String meetingID) { - // TODO Auto-generated method stub - - } + } - public void createMeeting(String meetingID, String externalMeetingID, String meetingName, - Boolean recorded, String voiceBridge, Long duration) { - // TODO Auto-generated method stub - - } + @Override + public void recordBreakoutInfo(String meetingId, Map<String, String> breakoutInfo) { + // TODO Auto-generated method stub - public void sendPolls(String meetingId, String title, String question, - String questionType, List<String> answers) { - // TODO Auto-generated method stub - - } + } - @Override - public void registerUser(String meetingID, String internalUserId, - String fullname, String role, String externUserID, String authToken, String avatarURL) { - // TODO Auto-generated method stub - - } + @Override + public void addBreakoutRoom(String parentId, String breakoutId) { + // TODO Auto-generated method stub - @Override - public void sendKeepAlive(String keepAliveId) { - // TODO Auto-generated method stub - - } + } } diff --git a/build_script.sh b/build_script.sh index 445a2adbed069bc16a6e0a821abcb58a5387ae7d..85098711c01af17ca0202bba88c2f306d502c3c2 100755 --- a/build_script.sh +++ b/build_script.sh @@ -3,27 +3,39 @@ set -ev files=`git diff --name-only HEAD..$TRAVIS_BRANCH` if [[ $files = *"bigbluebutton-html5"* ]]; then - { - cd bigbluebutton-html5 - git clone --single-branch -b update-html5 https://github.com/bigbluebutton/docker.git - cp -r docker/{mod,restart.sh,setup.sh,supervisord.conf} . - cp -r docker/Dockerfile Dockerfile.test - docker build -t b2 -f Dockerfile.test . - docker=$(docker run -d -p 80:80/tcp -p 443:443/tcp -p 1935:1935 -p 5066:5066 -p 3478:3478 -p 3478:3478/udp b2 -h localhost) - echo $docker - cd tests/puppeteer - npm install + cd bigbluebutton-html5 + curl https://install.meteor.com/ | sh + meteor npm install + if [ $1 = linter ] + then + html5_files="" + list=$(echo $files | tr " " "\n") + for file in $list + do + if [[ $file = bigbluebutton-html5* ]] ; then + html5_files+=" $file" + fi + done + + cd .. + bigbluebutton-html5/node_modules/.bin/eslint --ext .jsx,.js $html5_files + elif [ $1 = acceptance_tests ] + then + { + git clone --single-branch -b update-html5 https://github.com/bigbluebutton/docker.git + cp -r docker/{mod,restart.sh,setup.sh,supervisord.conf} . + cp -r docker/Dockerfile Dockerfile.test + docker build -t b2 -f Dockerfile.test . + docker=$(docker run -d -p 80:80/tcp -p 443:443/tcp -p 1935:1935 -p 5066:5066 -p 3478:3478 -p 3478:3478/udp b2 -h localhost) + echo $docker + } > /dev/null + + cd tests/puppeteer/core conf=$(docker exec $(docker ps -q) bbb-conf --secret | grep "Secret:") secret=$(echo $conf | cut -d' ' -f2) export BBB_SHARED_SECRET=$secret node html5-check.js - - cd ../.. - curl https://install.meteor.com/ | sh - meteor npm install - cd tests/puppeteer - npm install cd ../../.. - } > /dev/null - bigbluebutton-html5/node_modules/.bin/eslint --ext .jsx,.js $files + npm test + fi fi diff --git a/record-and-playback/core/lib/recordandplayback/events_archiver.rb b/record-and-playback/core/lib/recordandplayback/events_archiver.rb index 2ab86ff3c162be312ab4e40a2d6816bdabba9574..676e6cee0d4a4a7f61b52df2b208aa494b47bb17 100755 --- a/record-and-playback/core/lib/recordandplayback/events_archiver.rb +++ b/record-and-playback/core/lib/recordandplayback/events_archiver.rb @@ -342,11 +342,11 @@ module BigBlueButton # Once the events file has been written, we can delete this segment's # events from redis. - @redis.trim_events_for(meeting_id, last_index) msgs.each_with_index do |msg, i| @redis.delete_event_info_for(meeting_id, msg) - break if i >= 0 and i >= last_index + break if last_index >= 0 and i >= last_index end + @redis.trim_events_for(meeting_id, last_index) end diff --git a/record-and-playback/podcast/scripts/process/podcast.rb b/record-and-playback/podcast/scripts/process/podcast.rb index 0c03377625632a986578ff7c398295506f01c981..2519f381a84dab6ce61b5d5cbc78edc8ae10d8e2 100755 --- a/record-and-playback/podcast/scripts/process/podcast.rb +++ b/record-and-playback/podcast/scripts/process/podcast.rb @@ -114,7 +114,7 @@ if not FileTest.directory?(target_dir) end participants = recording.at_xpath("participants") - participants.content = BigBlueButton::Events.get_num_participants("#{raw_archive_dir}/events.xml") + participants.content = BigBlueButton::Events.get_num_participants(@doc) ## Remove empty meta metadata.search('//recording/meta').each do |meta| diff --git a/record-and-playback/podcast/scripts/publish/podcast.rb b/record-and-playback/podcast/scripts/publish/podcast.rb index 6ec3bcfcc1e1a60ad2f641885711eacc71190290..e8f2b8b5428de834b703bb056f93d3d83d9bb260 100755 --- a/record-and-playback/podcast/scripts/publish/podcast.rb +++ b/record-and-playback/podcast/scripts/publish/podcast.rb @@ -70,7 +70,8 @@ begin BigBlueButton.logger.info("copying: #{process_dir}/audio.ogg to -> #{target_dir}") FileUtils.cp("#{process_dir}/audio.ogg", target_dir) - recording_time = BigBlueButton::Events.get_recording_length("#{raw_archive_dir}/events.xml") + @doc = Nokogiri::XML(File.open("#{raw_archive_dir}/events.xml")) + recording_time = BigBlueButton::Events.get_recording_length(@doc) BigBlueButton.logger.info("Creating metadata.xml") diff --git a/video-broadcast/build.gradle b/video-broadcast/build.gradle index 9501c6e62a02489b98d1abf806c202d33609d433..88b40f6a86efff493d965864302f33d50393d9b1 100755 --- a/video-broadcast/build.gradle +++ b/video-broadcast/build.gradle @@ -22,9 +22,9 @@ dependencies { providedCompile 'javax.servlet:servlet-api:2.5@jar' // Mina - providedCompile 'org.apache.mina:mina-core:2.0.17@jar' - providedCompile 'org.apache.mina:mina-integration-beans:2.0.17@jar' - providedCompile 'org.apache.mina:mina-integration-jmx:2.0.17@jar' + providedCompile 'org.apache.mina:mina-core:2.0.19@jar' + providedCompile 'org.apache.mina:mina-integration-beans:2.0.19@jar' + providedCompile 'org.apache.mina:mina-integration-jmx:2.0.19@jar' // Spring providedCompile 'org.springframework:spring-web:4.3.12.RELEASE@jar' @@ -33,9 +33,9 @@ dependencies { providedCompile 'org.springframework:spring-core:4.3.12.RELEASE@jar' // Red5 - providedCompile 'org.red5:red5-server:1.0.10-M5@jar' - providedCompile 'org.red5:red5-server-common:1.0.10-M5@jar' - providedCompile 'org.red5:red5-io:1.0.10-M5@jar' + providedCompile 'org.red5:red5-server:1.0.10-M9@jar' + providedCompile 'org.red5:red5-server-common:1.0.10-M9@jar' + providedCompile 'org.red5:red5-io:1.0.10-M9@jar' // Logging providedCompile 'ch.qos.logback:logback-core:1.2.3@jar' @@ -62,7 +62,7 @@ dependencies { compile 'commons-pool:commons-pool:1.5.6' compile 'com.google.code.gson:gson:1.7.1' - compile 'org.bigbluebutton:bbb-common-message_2.12:0.0.19-SNAPSHOT' + compile 'org.bigbluebutton:bbb-common-message_2.12:0.0.20-SNAPSHOT' } diff --git a/video-broadcast/deploy.sh b/video-broadcast/deploy.sh new file mode 100755 index 0000000000000000000000000000000000000000..b7cab36a4bc4e2ff6e2b72d8bb9bf36079ce3149 --- /dev/null +++ b/video-broadcast/deploy.sh @@ -0,0 +1,10 @@ +#!/bin/bash +# deploying 'bigbluebutton-apps' to /usr/share/red5/webapps + +sudo chmod -R 777 /usr/share/red5/webapps/* + +gradle clean +gradle resolveDeps +gradle war deploy + +sudo chown -R red5:red5 /usr/share/red5/webapps diff --git a/video-broadcast/src/main/resources/logback-video.xml b/video-broadcast/src/main/resources/logback-video-broadcast.xml similarity index 100% rename from video-broadcast/src/main/resources/logback-video.xml rename to video-broadcast/src/main/resources/logback-video-broadcast.xml