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>&nbsp;</td>
+                        <td style="text-align: right; ">Full Name:</td>
+                        <td style="width: 5px; ">&nbsp;</td>
+                        <td style="text-align: left "><input type="text" autofocus required name="username" /></td>
+                </tr>
+
+                <tr>
+                        <td>&nbsp;</td>
+                        <td style="text-align: right; ">Meeting Name:</td>
+                        <td style="width: 5px; ">&nbsp;</td>
+                        <td style="text-align: left "><input type="text" required name="meetingname" value="Demo Meeting" /></td>
+                <tr>
+
+                <tr>
+                        <td>&nbsp;</td>
+                        <td style="text-align: right; ">Moderator Role:</td>
+                        <td style="width: 5px; ">&nbsp;</td>
+                        <td style="text-align: left "><input type=checkbox name=isModerator value="true" checked></td>
+                <tr>
+
+                <tr>
+                        <td>&nbsp;</td>
+                        <td>&nbsp;</td>
+                        <td>&nbsp;</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 &amp; as it will break the JNLP XML
-#codecOptions=crf=36&amp;preset=veryfast&amp;tune=animation,zerolatency&amp;frameRate=12.0&amp;keyFrameInterval=6
-codecOptions=crf=38&amp;preset=veryfast&amp;tune=zerolatency&amp;frameRate=5.0&amp;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 &amp; as it will break the JNLP XML
+#codecOptions=crf=36&amp;preset=veryfast&amp;tune=animation,zerolatency&amp;frameRate=12.0&amp;keyFrameInterval=6
+codecOptions=crf=38&amp;preset=veryfast&amp;tune=zerolatency&amp;frameRate=5.0&amp;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 &amp;&amp; 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)}&nbsp;
+          {intl.formatMessage(intlMessages.dropzoneLabel)}
+&nbsp;
           <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 {
                 &nbsp;({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