diff --git a/.gitignore b/.gitignore
index c0e6cc0f5146d49d61a218808d1dc381294da3b7..3d7e03bc182743d1c4330904c359390dffabd3ae 100644
--- a/.gitignore
+++ b/.gitignore
@@ -32,3 +32,4 @@ clients/flash/**/build
 clients/flash/**/.gradle
 **/.idea/*
 *.iml
+*~
diff --git a/akka-bbb-apps/src/main/scala/org/bigbluebutton/core/apps/users/UserJoinMeetingAfterReconnectReqMsgHdlr.scala b/akka-bbb-apps/src/main/scala/org/bigbluebutton/core/apps/users/UserJoinMeetingAfterReconnectReqMsgHdlr.scala
index b53f1dcffee6cf060dc4967490a739eb71dea226..7d3c61bea73dfe3d7d2ebfb3dbdf443aa8cbcfeb 100755
--- a/akka-bbb-apps/src/main/scala/org/bigbluebutton/core/apps/users/UserJoinMeetingAfterReconnectReqMsgHdlr.scala
+++ b/akka-bbb-apps/src/main/scala/org/bigbluebutton/core/apps/users/UserJoinMeetingAfterReconnectReqMsgHdlr.scala
@@ -4,7 +4,6 @@ import org.bigbluebutton.common2.msgs.UserJoinMeetingAfterReconnectReqMsg
 import org.bigbluebutton.core.apps.breakout.BreakoutHdlrHelpers
 import org.bigbluebutton.core.apps.voice.UserJoinedVoiceConfEvtMsgHdlr
 import org.bigbluebutton.core.domain.MeetingState2x
-import org.bigbluebutton.core.models.VoiceUsers
 import org.bigbluebutton.core.running.{ BaseMeetingActor, HandlerHelpers, LiveMeeting, OutMsgRouter }
 
 trait UserJoinMeetingAfterReconnectReqMsgHdlr extends HandlerHelpers with BreakoutHdlrHelpers with UserJoinedVoiceConfEvtMsgHdlr {
diff --git a/akka-bbb-apps/src/main/scala/org/bigbluebutton/core/apps/voice/UserJoinedVoiceConfEvtMsgHdlr.scala b/akka-bbb-apps/src/main/scala/org/bigbluebutton/core/apps/voice/UserJoinedVoiceConfEvtMsgHdlr.scala
index 15e35a59bf57d996fe77dee9856fa304bccf7379..33b0d4cbc528f179aebe58fe0a8bb1b7f66005ad 100755
--- a/akka-bbb-apps/src/main/scala/org/bigbluebutton/core/apps/voice/UserJoinedVoiceConfEvtMsgHdlr.scala
+++ b/akka-bbb-apps/src/main/scala/org/bigbluebutton/core/apps/voice/UserJoinedVoiceConfEvtMsgHdlr.scala
@@ -5,6 +5,7 @@ import org.bigbluebutton.core.apps.breakout.BreakoutHdlrHelpers
 import org.bigbluebutton.core.models.{ VoiceUserState, VoiceUsers }
 import org.bigbluebutton.core.running.{ BaseMeetingActor, LiveMeeting, OutMsgRouter }
 import org.bigbluebutton.core2.MeetingStatus2x
+import org.bigbluebutton.core2.message.senders.MsgBuilder
 
 trait UserJoinedVoiceConfEvtMsgHdlr extends BreakoutHdlrHelpers {
   this: BaseMeetingActor =>
@@ -46,6 +47,12 @@ trait UserJoinedVoiceConfEvtMsgHdlr extends BreakoutHdlrHelpers {
     val voiceUserState = VoiceUserState(intId, voiceUserId, callingWith, callerIdName, callerIdNum, muted, talking, listenOnly = isListenOnly)
     VoiceUsers.add(liveMeeting.voiceUsers, voiceUserState)
 
+    if (MeetingStatus2x.isMeetingMuted(liveMeeting.status)) {
+      val event = MsgBuilder.buildMuteUserInVoiceConfSysMsg(liveMeeting.props.meetingProp.intId, liveMeeting.props.voiceProp.voiceConf,
+        voiceUserId, true)
+      outGW.send(event)
+    }
+
     broadcastEvent(voiceUserState)
 
     if (liveMeeting.props.meetingProp.isBreakout) {
@@ -80,4 +87,5 @@ trait UserJoinedVoiceConfEvtMsgHdlr extends BreakoutHdlrHelpers {
     val event = StartRecordingVoiceConfSysMsg(header, body)
     BbbCommonEnvCoreMsg(envelope, event)
   }
+
 }
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 6bd0ddf833e731e48b414ca2e8ef34b66f28e206..86351c0c3685a4c9936d9bbf128e7874a45488b1 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
@@ -131,6 +131,13 @@ class MeetingActor(
 
   var lastRttTestSentOn = System.currentTimeMillis()
 
+  // Initialize if the meeting is muted on start
+  if (props.voiceProp.muteOnStart) {
+    MeetingStatus2x.muteMeeting(liveMeeting.status)
+  } else {
+    MeetingStatus2x.unmuteMeeting(liveMeeting.status)
+  }
+
   /*******************************************************************/
   //object FakeTestData extends FakeTestData
   //FakeTestData.createFakeUsers(liveMeeting)
diff --git a/bbb-common-message/src/main/scala/org/bigbluebutton/common2/domain/Meeting2x.scala b/bbb-common-message/src/main/scala/org/bigbluebutton/common2/domain/Meeting2x.scala
index d12fb9c7f81646bdc199d9fda95006f28ac17fd1..b4e0e92aa2ba50dcf4f371142d687e2a0624180d 100755
--- a/bbb-common-message/src/main/scala/org/bigbluebutton/common2/domain/Meeting2x.scala
+++ b/bbb-common-message/src/main/scala/org/bigbluebutton/common2/domain/Meeting2x.scala
@@ -16,7 +16,7 @@ case class RecordProp(record: Boolean, autoStartRecording: Boolean, allowStartSt
 
 case class WelcomeProp(welcomeMsgTemplate: String, welcomeMsg: String, modOnlyMessage: String)
 
-case class VoiceProp(telVoice: String, voiceConf: String, dialNumber: String)
+case class VoiceProp(telVoice: String, voiceConf: String, dialNumber: String, muteOnStart: Boolean)
 
 case class UsersProp(maxUsers: Int, webcamsOnlyForModerator: Boolean, guestPolicy: String)
 
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 9e5a6e40eff03f7eadbafc7cf100c94a4d0cbd85..b698ec2bdfaeb9a273f37521c75f34e8ea5656c3 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
@@ -266,7 +266,8 @@ public class MeetingService implements MessageListener {
       m.isBreakout(), m.getSequence(), m.getMetadata(), m.getGuestPolicy(), m.getWelcomeMessageTemplate(),
       m.getWelcomeMessage(), m.getModeratorOnlyMessage(), m.getDialNumber(), m.getMaxUsers(),
       m.getMaxInactivityTimeoutMinutes(), m.getWarnMinutesBeforeMax(),
-      m.getMeetingExpireIfNoUserJoinedInMinutes(), m.getmeetingExpireWhenLastUserLeftInMinutes());
+      m.getMeetingExpireIfNoUserJoinedInMinutes(), m.getmeetingExpireWhenLastUserLeftInMinutes(),
+            m.getMuteOnStart());
 
   }
 
diff --git a/bbb-common-web/src/main/java/org/bigbluebutton/api/ParamsProcessorUtil.java b/bbb-common-web/src/main/java/org/bigbluebutton/api/ParamsProcessorUtil.java
index c449b15adeea1131b728bfe8acfdc12df53bccf6..ca452ed7ad398ff08a7c42afdd32475e537c0df6 100755
--- a/bbb-common-web/src/main/java/org/bigbluebutton/api/ParamsProcessorUtil.java
+++ b/bbb-common-web/src/main/java/org/bigbluebutton/api/ParamsProcessorUtil.java
@@ -81,6 +81,7 @@ public class ParamsProcessorUtil {
     private boolean autoStartRecording;
     private boolean allowStartStopRecording;
     private boolean webcamsOnlyForModerator;
+    private boolean defaultMuteOnStart = false;
 
     private String defaultConfigXML = null;
 
@@ -352,6 +353,9 @@ public class ParamsProcessorUtil {
         int meetingDuration = processMeetingDuration(params.get("duration"));
         int logoutTimer = processMeetingDuration(params.get("logoutTimer"));
 
+        // Hardcode to zero as we don't use this feature in 2.0.x (ralam dec 18, 2017)
+		logoutTimer = 0;
+
         // set is breakout room property
         boolean isBreakout = false;
         if (!StringUtils.isEmpty(params.get("isBreakout"))) {
@@ -463,7 +467,9 @@ public class ParamsProcessorUtil {
         meeting.storeConfig(true, configXML);
 
         if (!StringUtils.isEmpty(params.get("moderatorOnlyMessage"))) {
-            String moderatorOnlyMessage = params.get("moderatorOnlyMessage");
+            String moderatorOnlyMessageTemplate = params.get("moderatorOnlyMessage");
+			String moderatorOnlyMessage = substituteKeywords(moderatorOnlyMessageTemplate,
+					dialNumber, telVoice, meetingName);
             meeting.setModeratorOnlyMessage(moderatorOnlyMessage);
         }
 
@@ -478,6 +484,19 @@ public class ParamsProcessorUtil {
             meeting.setParentMeetingId(parentMeetingId);
         }
 
+		if (!StringUtils.isEmpty(params.get("logo"))) {
+			meeting.setCustomLogoURL(params.get("logo"));
+		}
+
+		if (!StringUtils.isEmpty(params.get("copyright"))) {
+			meeting.setCustomCopyright(params.get("copyright"));
+		}
+		Boolean muteOnStart = defaultMuteOnStart;
+		if (!StringUtils.isEmpty(params.get("muteOnStart"))) {
+        	muteOnStart = Boolean.parseBoolean(params.get("muteOnStart"));
+        }
+
+		meeting.setMuteOnStart(muteOnStart);
         return meeting;
     }
 	
@@ -892,6 +911,15 @@ public class ParamsProcessorUtil {
 		meetingExpireIfNoUserJoinedInMinutes = value;
 	}
 
+	public void setMuteOnStart(Boolean mute) {
+		defaultMuteOnStart = mute;
+	}
+
+	public Boolean getMuteOnStart() {
+		return defaultMuteOnStart;
+	}
+
+
 	public ArrayList<String> decodeIds(String encodeid) {
 		ArrayList<String> ids=new ArrayList<String>();
 		try {
diff --git a/bbb-common-web/src/main/java/org/bigbluebutton/api/domain/Meeting.java b/bbb-common-web/src/main/java/org/bigbluebutton/api/domain/Meeting.java
index cd0fd6c9394bb8b4c33e28c622c343529637dd12..627d3346670c09571a00b64afa5b7045b2721650 100755
--- a/bbb-common-web/src/main/java/org/bigbluebutton/api/domain/Meeting.java
+++ b/bbb-common-web/src/main/java/org/bigbluebutton/api/domain/Meeting.java
@@ -63,6 +63,9 @@ public class Meeting {
 	private final ConcurrentMap<String, Config> configs;
 	private final Boolean isBreakout;
 	private final List<String> breakoutRooms = new ArrayList<String>();
+	private String customLogoURL = "";
+	private String customCopyright = "";
+	private Boolean muteOnStart = false;
 
 	private Integer maxInactivityTimeoutMinutes = 120;
 	private Integer warnMinutesBeforeMax = 5;
@@ -165,8 +168,8 @@ public class Meeting {
 		return createdTime;
 	}
 
-	public Integer setSequence(Integer s) {
-        return sequence = s;
+	public void setSequence(Integer s) {
+        sequence = s;
     }
 
 	public Integer getSequence() {
@@ -221,8 +224,8 @@ public class Meeting {
 		return intMeetingId;
 	}
 
-	public String setParentMeetingId(String p) {
-        return parentMeetingId = p;
+	public void setParentMeetingId(String p) {
+        parentMeetingId = p;
     }
 
 	public String getParentMeetingId() {
@@ -292,7 +295,31 @@ public class Meeting {
 	public boolean hasUserJoined() {
 		return userHasJoined;
 	}
-	
+
+	public String getCustomLogoURL() {
+		return customLogoURL;
+	}
+
+	public void setCustomLogoURL(String url) {
+		customLogoURL = url;
+	}
+
+	public void setCustomCopyright(String copyright) {
+    	customCopyright = copyright;
+	}
+
+	public String getCustomCopyright() {
+    	return customCopyright;
+	}
+
+	public void setMuteOnStart(Boolean mute) {
+    	muteOnStart = mute;
+	}
+
+	public Boolean getMuteOnStart() {
+    	return muteOnStart;
+	}
+
 	public void userJoined(User user) {
 	    userHasJoined = true;
 	    this.users.put(user.getInternalUserId(), user);
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 d40b7de845da69b301ee9e74c816c99bbe3d0c45..aa1bb8f4998c72a1ff9605cbd73b5cdeed9d5e06 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
@@ -19,7 +19,8 @@ public interface IBbbWebApiGWApp {
                      String dialNumber, Integer maxUsers,
                      Integer maxInactivityTimeoutMinutes, Integer warnMinutesBeforeMax,
                      Integer meetingExpireIfNoUserJoinedInMinutes,
-                     Integer meetingExpireWhenLastUserLeftInMinutes);
+                     Integer meetingExpireWhenLastUserLeftInMinutes,
+                     Boolean muteOnStart);
 
   void registerUser(String meetingID, String internalUserId, String fullname, String role,
                     String externUserID, String authToken, String avatarURL, Boolean guest, Boolean authed);
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 231b511c3d5451409a0933018d9ab7ecb84fde69..d05ba5f789c5d1fc4a6cd19885bb9afe7b3b7023 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
@@ -87,7 +87,8 @@ class BbbWebApiGWApp(val oldMessageReceivedGW: OldMessageReceivedGW,
                    dialNumber: String, maxUsers: java.lang.Integer, maxInactivityTimeoutMinutes: java.lang.Integer,
                     warnMinutesBeforeMax: java.lang.Integer,
                     meetingExpireIfNoUserJoinedInMinutes: java.lang.Integer,
-                    meetingExpireWhenLastUserLeftInMinutes: java.lang.Integer): Unit = {
+                    meetingExpireWhenLastUserLeftInMinutes: java.lang.Integer,
+                    muteOnStart: java.lang.Boolean): Unit = {
 
     val meetingProp = MeetingProp(name = meetingName, extId = extMeetingId, intId = meetingId,
       isBreakout = isBreakout.booleanValue())
@@ -104,7 +105,7 @@ class BbbWebApiGWApp(val oldMessageReceivedGW: OldMessageReceivedGW,
     val breakoutProps = BreakoutProps(parentId = parentMeetingId, sequence = sequence.intValue(), breakoutRooms = Vector())
     val welcomeProp = WelcomeProp(welcomeMsgTemplate = welcomeMsgTemplate, welcomeMsg = welcomeMsg,
       modOnlyMessage = modOnlyMessage)
-    val voiceProp = VoiceProp(telVoice = voiceBridge, voiceConf = voiceBridge, dialNumber = dialNumber)
+    val voiceProp = VoiceProp(telVoice = voiceBridge, voiceConf = voiceBridge, dialNumber = dialNumber, muteOnStart = muteOnStart.booleanValue())
     val usersProp = UsersProp(maxUsers = maxUsers.intValue(), webcamsOnlyForModerator = webcamsOnlyForModerator.booleanValue(),
       guestPolicy = guestPolicy)
     val metadataProp = MetadataProp(mapAsScalaMap(metadata).toMap)
diff --git a/bigbluebutton-client/branding/default/style/css/V2Theme.css b/bigbluebutton-client/branding/default/style/css/V2Theme.css
index 87a3e5af58bdb6b61b9de6d09b1d31547cf11871..7130ffb6952d999bdd5f17701629942ff27d3f8b 100755
--- a/bigbluebutton-client/branding/default/style/css/V2Theme.css
+++ b/bigbluebutton-client/branding/default/style/css/V2Theme.css
@@ -145,6 +145,8 @@ phonecomponents|MuteMeButton {
 	backgroundColor : #FFFFFF;
 	paddingTop      : 0;
 	paddingBottom   : 6;
+	verticalAlign   : middle;
+	verticalGap     : 0;
 }
 
 .breakoutRoomRibbon {
@@ -156,7 +158,7 @@ phonecomponents|MuteMeButton {
 }
 
 .topBoxStyle {
-	paddingTop    : 6;
+	paddingTop    : 0;
 	paddingBottom : 0;
 	paddingLeft   : 8;
 	paddingRight  : 8;
diff --git a/bigbluebutton-client/locale/en_US/bbbResources.properties b/bigbluebutton-client/locale/en_US/bbbResources.properties
index 4b2c0164583c7e302a5a3c6c05b87823d635ec9e..9fd3ab0bd9d8ca57b502ffbe9b84a0f25544cc11 100755
--- a/bigbluebutton-client/locale/en_US/bbbResources.properties
+++ b/bigbluebutton-client/locale/en_US/bbbResources.properties
@@ -215,7 +215,7 @@ bbb.users.usersGrid.mediaItemRenderer.pushToTalk = Unmute {0}
 bbb.users.usersGrid.mediaItemRenderer.pushToMute = Mute {0}
 bbb.users.usersGrid.mediaItemRenderer.pushToLock = Lock {0}
 bbb.users.usersGrid.mediaItemRenderer.pushToUnlock = Unlock {0}
-bbb.users.usersGrid.mediaItemRenderer.kickUser = Kick {0}
+bbb.users.usersGrid.mediaItemRenderer.kickUser = Remove {0}
 bbb.users.usersGrid.mediaItemRenderer.webcam = Webcam shared
 bbb.users.usersGrid.mediaItemRenderer.micOff = Microphone off
 bbb.users.usersGrid.mediaItemRenderer.micOn = Microphone on
@@ -513,7 +513,7 @@ bbb.logout.unknown = Your client has lost connection with the server
 bbb.logout.guestkickedout = The moderator didn't allow you to join this meeting
 bbb.logout.usercommand = You have logged out of the conference
 bbb.logour.breakoutRoomClose = Your browser window will be closed
-bbb.logout.ejectedFromMeeting = A moderator has kicked you out of the meeting.
+bbb.logout.ejectedFromMeeting = You have been removed from the meeting.
 bbb.logout.refresh.message = If this logout was unexpected click the button below to reconnect.
 bbb.logout.refresh.label = Reconnect
 bbb.settings.title = Settings
@@ -711,7 +711,7 @@ bbb.shortcutkey.present.fitPage.function = Fit slides to page
 bbb.shortcutkey.users.makePresenter = 89
 bbb.shortcutkey.users.makePresenter.function = Make selected person presenter
 bbb.shortcutkey.users.kick = 69
-bbb.shortcutkey.users.kick.function = Kick selected person from the meeting
+bbb.shortcutkey.users.kick.function = Remove selected person from the meeting
 bbb.shortcutkey.users.mute = 83
 bbb.shortcutkey.users.mute.function = Mute or unmute selected person
 bbb.shortcutkey.users.muteall = 65
@@ -829,6 +829,7 @@ bbb.users.breakout.timerForRoom.toolTip = Time left for this breakout room
 bbb.users.breakout.timer.toolTip = Time left for breakout rooms
 bbb.users.breakout.calculatingRemainingTime = Calculating remaining time...
 bbb.users.breakout.closing = Closing
+bbb.users.breakout.closewarning.text = Breakout rooms are closing in a minute.
 bbb.users.breakout.rooms = Rooms
 bbb.users.breakout.roomsCombo.accessibilityName = Number of rooms to create
 bbb.users.breakout.room = Room
diff --git a/bigbluebutton-client/resources/config.xml.template b/bigbluebutton-client/resources/config.xml.template
index 0ddab1bbc734a0b18aa95ab92ade5e2156ce0f73..d9b3d99a38f6ff24cc2b828df14530e64ed05ac3 100755
--- a/bigbluebutton-client/resources/config.xml.template
+++ b/bigbluebutton-client/resources/config.xml.template
@@ -16,8 +16,7 @@
             showToolbar="true" showFooter="true" showMeetingName="true" showHelpButton="true"
             showLogoutWindow="true" showLayoutTools="true" confirmLogout="true" showNetworkMonitor="false"
             showRecordingNotification="true" logoutOnStopRecording="false"/>
-    <meeting muteOnStart="false" />
-    <breakoutRooms enabled="true" record="false" />
+    <breakoutRooms enabled="true" record="false" privateChateEnabled="true"/>
     <logging enabled="true" logTarget="trace" level="info" format="{dateUTC} {time} :: {name} :: [{logLevel}] {message}" uri="http://HOST/log" logPattern=".*"/>
     <lock disableCam="false" disableMic="false" disablePrivateChat="false"
           disablePublicChat="false" lockedLayout="false" lockOnJoin="true" lockOnJoinConfigurable="false"/>
@@ -49,6 +48,7 @@
 			uri="rtmp://HOST/screenshare"
 			showButton="true"
 			enablePause="true"
+			tryKurentoWebRTC="false"
 			tryWebRTCFirst="false"
 			chromeExtensionLink=""
 			chromeExtensionKey=""
diff --git a/bigbluebutton-client/resources/prod/BigBlueButton.html b/bigbluebutton-client/resources/prod/BigBlueButton.html
index 3084a19be3d5a7046dcc2b85c99e92861ba8f699..ecd8710c8d5107671da8ae4674687292d4098fca 100755
--- a/bigbluebutton-client/resources/prod/BigBlueButton.html
+++ b/bigbluebutton-client/resources/prod/BigBlueButton.html
@@ -142,8 +142,8 @@
     <script src="lib/verto-min.js" language="javascript"></script>
     <script src="lib/verto_extension.js" language="javascript"></script>
 
-    <script src="lib/kurento-utils.min.js" language="javascript"></script>
     <script src="lib/kurento-extension.js" language="javascript"></script>
+    <script src="lib/kurento-utils.js" language="javascript"></script>
 
     <script src="lib/bbb_api_bridge.js?v=VERSION" language="javascript"></script>
     <script src="lib/sip.js?v=VERSION" language="javascript"></script>
diff --git a/bigbluebutton-client/resources/prod/lib/kurento-extension.js b/bigbluebutton-client/resources/prod/lib/kurento-extension.js
index cd9c5fab9026142ecae3133278762eabdf33fd9b..3aa4e9243994311f35964cb79379d1a53cc74f74 100644
--- a/bigbluebutton-client/resources/prod/lib/kurento-extension.js
+++ b/bigbluebutton-client/resources/prod/lib/kurento-extension.js
@@ -1,6 +1,7 @@
 var isFirefox = typeof window.InstallTrigger !== 'undefined';
 var isOpera = !!window.opera || navigator.userAgent.indexOf(' OPR/') >= 0;
 var isChrome = !!window.chrome && !isOpera;
+var isSafari = navigator.userAgent.indexOf("Safari") >= 0 && !isChrome;
 var kurentoHandler = null;
 
 Kurento = function (
@@ -20,7 +21,7 @@ Kurento = function (
   this.screenConstraints = {};
   this.mediaCallback = null;
 
-  this.voiceBridge = voiceBridge;
+  this.voiceBridge = voiceBridge + '-SCREENSHARE';
   this.internalMeetingId = internalMeetingId;
 
   this.vid_width = window.screen.width;
@@ -33,9 +34,8 @@ Kurento = function (
 
   this.caller_id_name = conferenceUsername;
   this.caller_id_number = conferenceUsername;
-  this.pingInterval;
 
-  this.kurentoPort = "kurento-screenshare";
+  this.kurentoPort = "bbb-webrtc-sfu";
   this.hostName = window.location.hostname;
   this.socketUrl = 'wss://' + this.hostName + '/' + this.kurentoPort;
 
@@ -43,6 +43,7 @@ Kurento = function (
 
   if (chromeExtension != null) {
     this.chromeExtension = chromeExtension;
+    window.chromeExtension = chromeExtension;
   }
 
   if (onFail != null) {
@@ -57,21 +58,44 @@ Kurento = function (
 
 this.KurentoManager= function () {
   this.kurentoVideo = null;
-  this.kurentoScreenShare = null;
+  this.kurentoScreenshare = null;
 };
 
 KurentoManager.prototype.exitScreenShare = function () {
-  if (this.kurentoScreenShare != null) {
-    if(kurentoHandler.pingInterval) {
-      clearInterval(kurentoHandler.pingInterval);
+  console.log("  [exitScreenShare] Exiting screensharing");
+  if(typeof this.kurentoScreenshare !== 'undefined' && this.kurentoScreenshare) {
+    if(this.kurentoScreenshare.ws !== null) {
+      this.kurentoScreenshare.ws.onclose = function(){};
+      this.kurentoScreenshare.ws.close();
     }
-    if(kurentoHandler.ws !== null) {
-      kurentoHandler.ws.onclose = function(){};
-      kurentoHandler.ws.close();
+
+    this.kurentoScreenshare.disposeScreenShare();
+    this.kurentoScreenshare = null;
+  }
+
+  if (this.kurentoScreenshare) {
+    this.kurentoScreenshare = null;
+  }
+
+  if(typeof this.kurentoVideo !== 'undefined' && this.kurentoVideo) {
+    this.exitVideo();
+  }
+};
+
+KurentoManager.prototype.exitVideo = function () {
+  console.log("  [exitScreenShare] Exiting screensharing viewing");
+  if(typeof this.kurentoVideo !== 'undefined' && this.kurentoVideo) {
+    if(this.kurentoVideo.ws !== null) {
+      this.kurentoVideo.ws.onclose = function(){};
+      this.kurentoVideo.ws.close();
     }
-    kurentoHandler.disposeScreenShare();
-    this.kurentoScreenShare = null;
-    kurentoHandler = null;
+
+    this.kurentoVideo.disposeScreenShare();
+    this.kurentoVideo = null;
+  }
+
+  if (this.kurentoVideo) {
+    this.kurentoVideo = null;
   }
 };
 
@@ -79,24 +103,21 @@ KurentoManager.prototype.shareScreen = function (tag) {
   this.exitScreenShare();
   var obj = Object.create(Kurento.prototype);
   Kurento.apply(obj, arguments);
-  this.kurentoScreenShare = obj;
-  kurentoHandler = obj;
-  this.kurentoScreenShare.setScreenShare(tag);
+  this.kurentoScreenshare = obj;
+  this.kurentoScreenshare.setScreenShare(tag);
 };
 
-// Still unused, part of the HTML5 implementation
 KurentoManager.prototype.joinWatchVideo = function (tag) {
   this.exitVideo();
   var obj = Object.create(Kurento.prototype);
   Kurento.apply(obj, arguments);
   this.kurentoVideo = obj;
-  kurentoHandler = obj;
   this.kurentoVideo.setWatchVideo(tag);
 };
 
 
 Kurento.prototype.setScreenShare = function (tag) {
-  this.mediaCallback = this.makeShare;
+  this.mediaCallback = this.makeShare.bind(this);
   this.create(tag);
 };
 
@@ -112,19 +133,18 @@ Kurento.prototype.init = function () {
     console.log("this browser supports websockets");
     this.ws = new WebSocket(this.socketUrl);
 
-    this.ws.onmessage = this.onWSMessage;
-    this.ws.onclose = function (close) {
+    this.ws.onmessage = this.onWSMessage.bind(this);
+    this.ws.onclose = (close) => {
       kurentoManager.exitScreenShare();
       self.onFail("Websocket connection closed");
     };
-    this.ws.onerror = function (error) {
+    this.ws.onerror = (error) => {
       kurentoManager.exitScreenShare();
       self.onFail("Websocket connection error");
     };
-    this.ws.onopen = function() {
-      self.pingInterval = setInterval(self.ping, 3000);
+    this.ws.onopen = function () {
       self.mediaCallback();
-    };
+    }.bind(self);
   }
   else
     console.log("this browser does not support websockets");
@@ -135,15 +155,16 @@ Kurento.prototype.onWSMessage = function (message) {
   switch (parsedMessage.id) {
 
     case 'presenterResponse':
-      kurentoHandler.presenterResponse(parsedMessage);
+      this.presenterResponse(parsedMessage);
+      break;
+    case 'viewerResponse':
+      this.viewerResponse(parsedMessage);
       break;
     case 'stopSharing':
       kurentoManager.exitScreenShare();
       break;
     case 'iceCandidate':
-      kurentoHandler.webRtcPeer.addIceCandidate(parsedMessage.candidate);
-      break;
-    case 'pong':
+      this.webRtcPeer.addIceCandidate(parsedMessage.candidate);
       break;
     default:
       console.error('Unrecognized message', parsedMessage);
@@ -156,21 +177,33 @@ Kurento.prototype.setRenderTag = function (tag) {
 
 Kurento.prototype.presenterResponse = function (message) {
   if (message.response != 'accepted') {
-    var errorMsg = message.message ? message.message : 'Unknow error';
-    console.warn('Call not accepted for the following reason: ' + errorMsg);
+    var errorMsg = message.message ? message.message : 'Unknown error';
+    console.warn('Call not accepted for the following reason: ' + JSON.stringify(errorMsg, null, 2));
     kurentoManager.exitScreenShare();
-    kurentoHandler.onFail(errorMessage);
+    this.onFail(errorMessage);
   } else {
     console.log("Presenter call was accepted with SDP => " + message.sdpAnswer);
     this.webRtcPeer.processAnswer(message.sdpAnswer);
   }
 }
 
+Kurento.prototype.viewerResponse = function (message) {
+  if (message.response != 'accepted') {
+    var errorMsg = message.message ? message.message : 'Unknown error';
+    console.warn('Call not accepted for the following reason: ' + errorMsg);
+    kurentoManager.exitScreenShare();
+    this.onFail(errorMessage);
+  } else {
+    console.log("Viewer call was accepted with SDP => " + message.sdpAnswer);
+    this.webRtcPeer.processAnswer(message.sdpAnswer);
+  }
+}
+
 Kurento.prototype.serverResponse = function (message) {
   if (message.response != 'accepted') {
     var errorMsg = message.message ? message.message : 'Unknow error';
     console.warn('Call not accepted for the following reason: ' + errorMsg);
-    kurentoHandler.dispose();
+    kurentoManager.exitScreenShare();
   } else {
     this.webRtcPeer.processAnswer(message.sdpAnswer);
   }
@@ -178,89 +211,105 @@ Kurento.prototype.serverResponse = function (message) {
 
 Kurento.prototype.makeShare = function() {
   var self = this;
-  console.log("Kurento.prototype.makeShare " + JSON.stringify(this.webRtcPeer, null, 2));
   if (!this.webRtcPeer) {
-
     var options = {
-      onicecandidate : this.onIceCandidate
+      onicecandidate : self.onIceCandidate.bind(self)
     }
 
-    console.log("Peer options " + JSON.stringify(options, null, 2));
-
-    kurentoHandler.startScreenStreamFrom();
-
+    this.startScreenStreamFrom();
   }
 }
 
 Kurento.prototype.onOfferPresenter = function (error, offerSdp) {
+  let self = this;
   if(error)  {
     console.log("Kurento.prototype.onOfferPresenter Error " + error);
-    kurentoHandler.onFail(error);
+    this.onFail(error);
     return;
   }
 
   var message = {
     id : 'presenter',
     type: 'screenshare',
-    internalMeetingId: kurentoHandler.internalMeetingId,
-    voiceBridge: kurentoHandler.voiceBridge,
-    callerName : kurentoHandler.caller_id_name,
+    role: 'presenter',
+    internalMeetingId: self.internalMeetingId,
+    voiceBridge: self.voiceBridge,
+    callerName : self.caller_id_name,
     sdpOffer : offerSdp,
-    vh: kurentoHandler.vid_height,
-    vw: kurentoHandler.vid_width
+    vh: self.vid_height,
+    vw: self.vid_width
   };
   console.log("onOfferPresenter sending to screenshare server => " + JSON.stringify(message, null, 2));
-  kurentoHandler.sendMessage(message);
+  this.sendMessage(message);
 }
 
 Kurento.prototype.startScreenStreamFrom = function () {
-  var screenInfo = null;
-  var _this = this;
+  var self = this;
   if (!!window.chrome) {
-    if (!_this.chromeExtension) {
-      _this.logError({
+    if (!self.chromeExtension) {
+      self.logError({
         status:  'failed',
         message: 'Missing Chrome Extension key',
       });
-      _this.onFail();
+      self.onFail();
       return;
     }
   }
   // TODO it would be nice to check those constraints
-  _this.screenConstraints.video = {};
+  if (typeof screenConstraints !== undefined) {
+    self.screenConstraints = {};
+  }
+  self.screenConstraints.video = {};
 
+  console.log(self);
   var options = {
-    //localVideo: this.renderTag,
-    onicecandidate : _this.onIceCandidate,
-    mediaConstraints : _this.screenConstraints,
+    localVideo: document.getElementById(this.renderTag),
+    onicecandidate : self.onIceCandidate.bind(self),
+    mediaConstraints : self.screenConstraints,
     sendSource : 'desktop'
   };
 
   console.log(" Peer options => " + JSON.stringify(options, null, 2));
 
-  _this.webRtcPeer = kurentoUtils.WebRtcPeer.WebRtcPeerSendonly(options, function(error) {
+  self.webRtcPeer = kurentoUtils.WebRtcPeer.WebRtcPeerSendonly(options, function(error) {
     if(error)  {
       console.log("WebRtcPeerSendonly constructor error " + JSON.stringify(error, null, 2));
-      kurentoHandler.onFail(error);
+      self.onFail(error);
       return kurentoManager.exitScreenShare();
     }
 
-    _this.webRtcPeer.generateOffer(_this.onOfferPresenter);
+    self.webRtcPeer.generateOffer(self.onOfferPresenter.bind(self));
     console.log("Generated peer offer w/ options "  + JSON.stringify(options));
   });
 }
 
-Kurento.prototype.onIceCandidate = function(candidate) {
+Kurento.prototype.onIceCandidate = function (candidate) {
+  let self = this;
   console.log('Local candidate' + JSON.stringify(candidate));
 
   var message = {
     id : 'onIceCandidate',
+    role: 'presenter',
     type: 'screenshare',
-    voiceBridge: kurentoHandler.voiceBridge,
+    voiceBridge: self.voiceBridge,
     candidate : candidate
   }
-  console.log("this object " + JSON.stringify(this, null, 2));
-  kurentoHandler.sendMessage(message);
+  this.sendMessage(message);
+}
+
+Kurento.prototype.onViewerIceCandidate = function (candidate) {
+  let self = this;
+  console.log('Viewer local candidate' + JSON.stringify(candidate));
+
+  var message = {
+    id : 'viewerIceCandidate',
+    role: 'viewer',
+    type: 'screenshare',
+    voiceBridge: self.voiceBridge,
+    candidate : candidate,
+    callerName: self.caller_id_name
+  }
+  this.sendMessage(message);
 }
 
 Kurento.prototype.setWatchVideo = function (tag) {
@@ -276,60 +325,50 @@ Kurento.prototype.viewer = function () {
   if (!this.webRtcPeer) {
 
     var options = {
-      remoteVideo: this.renderTag,
-      onicecandidate : onIceCandidate
+      remoteVideo: document.getElementById(this.renderTag),
+      onicecandidate : this.onViewerIceCandidate.bind(this)
     }
 
-    webRtcPeer = kurentoUtils.WebRtcPeer.WebRtcPeerRecvonly(options, function(error) {
+    self.webRtcPeer = kurentoUtils.WebRtcPeer.WebRtcPeerRecvonly(options, function(error) {
       if(error) {
-        return kurentoHandler.onFail(error);
+        return self.onFail(error);
       }
 
-      this.generateOffer(onOfferViewer);
+      this.generateOffer(self.onOfferViewer.bind(self));
     });
   }
 };
 
 Kurento.prototype.onOfferViewer = function (error, offerSdp) {
+  let self = this;
   if(error)  {
     console.log("Kurento.prototype.onOfferViewer Error " + error);
-    return kurentoHandler.onFail();
+    return this.onFail();
   }
   var message = {
     id : 'viewer',
     type: 'screenshare',
-    internalMeetingId: kurentoHandler.internalMeetingId,
-    voiceBridge: kurentoHandler.voiceBridge,
-    callerName : kurentoHandler.caller_id_name,
+    role: 'viewer',
+    internalMeetingId: self.internalMeetingId,
+    voiceBridge: self.voiceBridge,
+    callerName : self.caller_id_name,
     sdpOffer : offerSdp
   };
 
   console.log("onOfferViewer sending to screenshare server => " + JSON.stringify(message, null, 2));
-  kurentoHandler.sendMessage(message);
+  this.sendMessage(message);
 };
 
-Kurento.prototype.ping = function() {
-   var message = {
-    id : 'ping',
-    type: 'screenshare',
-    internalMeetingId: kurentoHandler.internalMeetingId,
-    voiceBridge: kurentoHandler.voiceBridge,
-    callerName : kurentoHandler.caller_id_name,
-  };
-
-  kurentoHandler.sendMessage(message);
-}
-
 Kurento.prototype.stop = function() {
-  if (this.webRtcPeer) {
-    var message = {
-      id : 'stop',
-      type : 'screenshare',
-      voiceBridge: kurentoHandler.voiceBridge
-    }
-    kurentoHandler.sendMessage(message);
-    kurentoHandler.disposeScreenShare();
-  }
+  //if (this.webRtcPeer) {
+  //  var message = {
+  //    id : 'stop',
+  //    type : 'screenshare',
+  //    voiceBridge: kurentoHandler.voiceBridge
+  //  }
+  //  kurentoHandler.sendMessage(message);
+  //  kurentoHandler.disposeScreenShare();
+  //}
 }
 
 Kurento.prototype.dispose = function() {
@@ -360,19 +399,6 @@ Kurento.prototype.logError = function (obj) {
   console.error(obj);
 };
 
-Kurento.prototype.getChromeScreenConstraints = function(callback, extensionId) {
-  chrome.runtime.sendMessage(extensionId, {
-    getStream: true,
-    sources: [
-      "window",
-      "screen",
-      "tab"
-    ]},
-    function(response) {
-      console.log(response);
-      callback(response);
-    });
-};
 
 Kurento.normalizeCallback = function (callback) {
   if (typeof callback == 'function') {
@@ -389,30 +415,42 @@ Kurento.normalizeCallback = function (callback) {
 
 // this function explains how to use above methods/objects
 window.getScreenConstraints = function(sendSource, callback) {
-  var _this = this;
-  var chromeMediaSourceId = sendSource;
-  if(isChrome) {
-    kurentoHandler.getChromeScreenConstraints (function (constraints) {
+  let chromeMediaSourceId = sendSource;
+  let screenConstraints = {video: {}};
 
-      var sourceId = constraints.streamId;
+  if(isChrome) {
+    getChromeScreenConstraints ((constraints) => {
+      let sourceId = constraints.streamId;
 
       // this statement sets gets 'sourceId" and sets "chromeMediaSourceId"
-      kurentoHandler.screenConstraints.video.chromeMediaSource = { exact: [sendSource]};
-      kurentoHandler.screenConstraints.video.chromeMediaSourceId= sourceId;
-      console.log("getScreenConstraints for Chrome returns => " +JSON.stringify(kurentoHandler.screenConstraints, null, 2));
+      screenConstraints.video.chromeMediaSource = { exact: [sendSource]};
+      screenConstraints.video.chromeMediaSourceId = sourceId;
+      console.log("getScreenConstraints for Chrome returns => ");
+      console.log(screenConstraints);
       // now invoking native getUserMedia API
-      callback(null, kurentoHandler.screenConstraints);
+      callback(null, screenConstraints);
 
-    }, kurentoHandler.chromeExtension);
+    }, chromeExtension);
   }
   else if (isFirefox) {
-    kurentoHandler.screenConstraints.video.mediaSource= "screen";
-    kurentoHandler.screenConstraints.video.width= {max: kurentoHandler.vid_width};
-    kurentoHandler.screenConstraints.video.height = {max:  kurentoHandler.vid_height};
+    screenConstraints.video.mediaSource= "window";
+    screenConstraints.video.width= {max: "1280"};
+    screenConstraints.video.height = {max:  "720"};
 
-    console.log("getScreenConstraints for Firefox returns => " +JSON.stringify(kurentoHandler.screenConstraints, null, 2));
+    console.log("getScreenConstraints for Firefox returns => ");
+    console.log(screenConstraints);
     // now invoking native getUserMedia API
-    callback(null, kurentoHandler.screenConstraints);
+    callback(null, screenConstraints);
+  }
+  else if(isSafari) {
+    screenConstraints.video.mediaSource= "screen";
+    screenConstraints.video.width= {max: window.screen.width};
+    screenConstraints.video.height = {max:  window.screen.vid_height};
+
+    console.log("getScreenConstraints for Safari returns => ");
+    console.log(screenConstraints);
+    // now invoking native getUserMedia API
+    callback(null, screenConstraints);
   }
 }
 
@@ -437,3 +475,22 @@ window.kurentoWatchVideo = function () {
   window.kurentoInitialize();
   window.kurentoManager.joinWatchVideo.apply(window.kurentoManager, arguments);
 };
+
+window.kurentoExitVideo = function () {
+  window.kurentoInitialize();
+  window.kurentoManager.exitVideo();
+}
+
+window.getChromeScreenConstraints = function(callback, extensionId) {
+  chrome.runtime.sendMessage(extensionId, {
+    getStream: true,
+    sources: [
+      "window",
+      "screen",
+      "tab"
+    ]},
+    function(response) {
+      console.log(response);
+      callback(response);
+    });
+};;
diff --git a/bigbluebutton-client/resources/prod/lib/kurento-utils.js b/bigbluebutton-client/resources/prod/lib/kurento-utils.js
new file mode 100644
index 0000000000000000000000000000000000000000..d171093e8784054a9f42264a0dccb960c5c25d10
--- /dev/null
+++ b/bigbluebutton-client/resources/prod/lib/kurento-utils.js
@@ -0,0 +1,4362 @@
+(function(f){if(typeof exports==="object"&&typeof module!=="undefined"){module.exports=f()}else if(typeof define==="function"&&define.amd){define([],f)}else{var g;if(typeof window!=="undefined"){g=window}else if(typeof global!=="undefined"){g=global}else if(typeof self!=="undefined"){g=self}else{g=this}g.kurentoUtils = f()}})(function(){var define,module,exports;return (function e(t,n,r){function s(o,u){if(!n[o]){if(!t[o]){var a=typeof require=="function"&&require;if(!u&&a)return a(o,!0);if(i)return i(o,!0);var f=new Error("Cannot find module '"+o+"'");throw f.code="MODULE_NOT_FOUND",f}var l=n[o]={exports:{}};t[o][0].call(l.exports,function(e){var n=t[o][1][e];return s(n?n:e)},l,l.exports,e,t,n,r)}return n[o].exports}var i=typeof require=="function"&&require;for(var o=0;o<r.length;o++)s(r[o]);return s})({1:[function(require,module,exports){
+var freeice = require('freeice');
+var inherits = require('inherits');
+var UAParser = require('ua-parser-js');
+var uuid = require('uuid');
+var hark = require('hark');
+var EventEmitter = require('events').EventEmitter;
+var recursive = require('merge').recursive.bind(undefined, true);
+var sdpTranslator = require('sdp-translator');
+var logger = window.Logger || console;
+try {
+    require('kurento-browser-extensions');
+} catch (error) {
+    if (typeof getScreenConstraints === 'undefined') {
+        logger.warn('screen sharing is not available');
+        getScreenConstraints = function getScreenConstraints(sendSource, callback) {
+            callback(new Error('This library is not enabled for screen sharing'));
+        };
+    }
+}
+var MEDIA_CONSTRAINTS = {
+        audio: true,
+        video: {
+            width: 640,
+            framerate: 15
+        }
+    };
+var ua = window && window.navigator ? window.navigator.userAgent : '';
+var parser = new UAParser(ua);
+var browser = parser.getBrowser();
+var usePlanB = false;
+if (browser.name === 'Chrome' || browser.name === 'Chromium') {
+    logger.debug(browser.name + ': using SDP PlanB');
+    usePlanB = true;
+}
+function noop(error) {
+    if (error)
+        logger.error(error);
+}
+function trackStop(track) {
+    track.stop && track.stop();
+}
+function streamStop(stream) {
+    stream.getTracks().forEach(trackStop);
+}
+var dumpSDP = function (description) {
+    if (typeof description === 'undefined' || description === null) {
+        return '';
+    }
+    return 'type: ' + description.type + '\r\n' + description.sdp;
+};
+function bufferizeCandidates(pc, onerror) {
+    var candidatesQueue = [];
+    pc.addEventListener('signalingstatechange', function () {
+        if (this.signalingState === 'stable') {
+            while (candidatesQueue.length) {
+                var entry = candidatesQueue.shift();
+                this.addIceCandidate(entry.candidate, entry.callback, entry.callback);
+            }
+        }
+    });
+    return function (candidate, callback) {
+        callback = callback || onerror;
+        switch (pc.signalingState) {
+        case 'closed':
+            callback(new Error('PeerConnection object is closed'));
+            break;
+        case 'stable':
+            if (pc.remoteDescription) {
+                pc.addIceCandidate(candidate, callback, callback);
+                break;
+            }
+        default:
+            candidatesQueue.push({
+                candidate: candidate,
+                callback: callback
+            });
+        }
+    };
+}
+function removeFIDFromOffer(sdp) {
+    var n = sdp.indexOf('a=ssrc-group:FID');
+    if (n > 0) {
+        return sdp.slice(0, n);
+    } else {
+        return sdp;
+    }
+}
+function getSimulcastInfo(videoStream) {
+    var videoTracks = videoStream.getVideoTracks();
+    if (!videoTracks.length) {
+        logger.warn('No video tracks available in the video stream');
+        return '';
+    }
+    var lines = [
+            'a=x-google-flag:conference',
+            'a=ssrc-group:SIM 1 2 3',
+            'a=ssrc:1 cname:localVideo',
+            'a=ssrc:1 msid:' + videoStream.id + ' ' + videoTracks[0].id,
+            'a=ssrc:1 mslabel:' + videoStream.id,
+            'a=ssrc:1 label:' + videoTracks[0].id,
+            'a=ssrc:2 cname:localVideo',
+            'a=ssrc:2 msid:' + videoStream.id + ' ' + videoTracks[0].id,
+            'a=ssrc:2 mslabel:' + videoStream.id,
+            'a=ssrc:2 label:' + videoTracks[0].id,
+            'a=ssrc:3 cname:localVideo',
+            'a=ssrc:3 msid:' + videoStream.id + ' ' + videoTracks[0].id,
+            'a=ssrc:3 mslabel:' + videoStream.id,
+            'a=ssrc:3 label:' + videoTracks[0].id
+        ];
+    lines.push('');
+    return lines.join('\n');
+}
+function WebRtcPeer(mode, options, callback) {
+    if (!(this instanceof WebRtcPeer)) {
+        return new WebRtcPeer(mode, options, callback);
+    }
+    WebRtcPeer.super_.call(this);
+    if (options instanceof Function) {
+        callback = options;
+        options = undefined;
+    }
+    options = options || {};
+    callback = (callback || noop).bind(this);
+    var self = this;
+    var localVideo = options.localVideo;
+    var remoteVideo = options.remoteVideo;
+    var videoStream = options.videoStream;
+    var audioStream = options.audioStream;
+    var mediaConstraints = options.mediaConstraints;
+    var connectionConstraints = options.connectionConstraints;
+    var pc = options.peerConnection;
+    var sendSource = options.sendSource || 'webcam';
+    var dataChannelConfig = options.dataChannelConfig;
+    var useDataChannels = options.dataChannels || false;
+    var dataChannel;
+    var guid = uuid.v4();
+    var configuration = recursive({ iceServers: freeice() }, options.configuration);
+    var onicecandidate = options.onicecandidate;
+    if (onicecandidate)
+        this.on('icecandidate', onicecandidate);
+    var oncandidategatheringdone = options.oncandidategatheringdone;
+    if (oncandidategatheringdone) {
+        this.on('candidategatheringdone', oncandidategatheringdone);
+    }
+    var simulcast = options.simulcast;
+    var multistream = options.multistream;
+    var interop = new sdpTranslator.Interop();
+    var candidatesQueueOut = [];
+    var candidategatheringdone = false;
+    Object.defineProperties(this, {
+        'peerConnection': {
+            get: function () {
+                return pc;
+            }
+        },
+        'id': {
+            value: options.id || guid,
+            writable: false
+        },
+        'remoteVideo': {
+            get: function () {
+                return remoteVideo;
+            }
+        },
+        'localVideo': {
+            get: function () {
+                return localVideo;
+            }
+        },
+        'dataChannel': {
+            get: function () {
+                return dataChannel;
+            }
+        },
+        'currentFrame': {
+            get: function () {
+                if (!remoteVideo)
+                    return;
+                if (remoteVideo.readyState < remoteVideo.HAVE_CURRENT_DATA)
+                    throw new Error('No video stream data available');
+                var canvas = document.createElement('canvas');
+                canvas.width = remoteVideo.videoWidth;
+                canvas.height = remoteVideo.videoHeight;
+                canvas.getContext('2d').drawImage(remoteVideo, 0, 0);
+                return canvas;
+            }
+        }
+    });
+    if (!pc) {
+        pc = new RTCPeerConnection(configuration);
+        if (useDataChannels && !dataChannel) {
+            var dcId = 'WebRtcPeer-' + self.id;
+            var dcOptions = undefined;
+            if (dataChannelConfig) {
+                dcId = dataChannelConfig.id || dcId;
+                dcOptions = dataChannelConfig.options;
+            }
+            dataChannel = pc.createDataChannel(dcId, dcOptions);
+            if (dataChannelConfig) {
+                dataChannel.onopen = dataChannelConfig.onopen;
+                dataChannel.onclose = dataChannelConfig.onclose;
+                dataChannel.onmessage = dataChannelConfig.onmessage;
+                dataChannel.onbufferedamountlow = dataChannelConfig.onbufferedamountlow;
+                dataChannel.onerror = dataChannelConfig.onerror || noop;
+            }
+        }
+    }
+    pc.addEventListener('icecandidate', function (event) {
+        var candidate = event.candidate;
+        if (EventEmitter.listenerCount(self, 'icecandidate') || EventEmitter.listenerCount(self, 'candidategatheringdone')) {
+            if (candidate) {
+                var cand;
+                if (multistream && usePlanB) {
+                    cand = interop.candidateToUnifiedPlan(candidate);
+                } else {
+                    cand = candidate;
+                }
+                self.emit('icecandidate', cand);
+                candidategatheringdone = false;
+            } else if (!candidategatheringdone) {
+                self.emit('candidategatheringdone');
+                candidategatheringdone = true;
+            }
+        } else if (!candidategatheringdone) {
+            candidatesQueueOut.push(candidate);
+            if (!candidate)
+                candidategatheringdone = true;
+        }
+    });
+    pc.ontrack = options.onaddstream;
+    pc.onnegotiationneeded = options.onnegotiationneeded;
+    this.on('newListener', function (event, listener) {
+        if (event === 'icecandidate' || event === 'candidategatheringdone') {
+            while (candidatesQueueOut.length) {
+                var candidate = candidatesQueueOut.shift();
+                if (!candidate === (event === 'candidategatheringdone')) {
+                    listener(candidate);
+                }
+            }
+        }
+    });
+    var addIceCandidate = bufferizeCandidates(pc);
+    this.addIceCandidate = function (iceCandidate, callback) {
+        var candidate;
+        if (multistream && usePlanB) {
+            candidate = interop.candidateToPlanB(iceCandidate);
+        } else {
+            candidate = new RTCIceCandidate(iceCandidate);
+        }
+        logger.debug('Remote ICE candidate received', iceCandidate);
+        callback = (callback || noop).bind(this);
+        addIceCandidate(candidate, callback);
+    };
+    this.generateOffer = function (callback) {
+        callback = callback.bind(this);
+        var offerAudio = true;
+        var offerVideo = true;
+        if (mediaConstraints) {
+            offerAudio = typeof mediaConstraints.audio === 'boolean' ? mediaConstraints.audio : true;
+            offerVideo = typeof mediaConstraints.video === 'boolean' ? mediaConstraints.video : true;
+        }
+        var browserDependantConstraints = {
+                offerToReceiveAudio: mode !== 'sendonly' && offerAudio,
+                offerToReceiveVideo: mode !== 'sendonly' && offerVideo
+            };
+        var constraints = browserDependantConstraints;
+        logger.debug('constraints: ' + JSON.stringify(constraints));
+        pc.createOffer(constraints).then(function (offer) {
+            logger.debug('Created SDP offer');
+            offer = mangleSdpToAddSimulcast(offer);
+            return pc.setLocalDescription(offer);
+        }).then(function () {
+            var localDescription = pc.localDescription;
+            logger.debug('Local description set', localDescription.sdp);
+            if (multistream && usePlanB) {
+                localDescription = interop.toUnifiedPlan(localDescription);
+                logger.debug('offer::origPlanB->UnifiedPlan', dumpSDP(localDescription));
+            }
+            callback(null, localDescription.sdp, self.processAnswer.bind(self));
+        }).catch(callback);
+    };
+    this.getLocalSessionDescriptor = function () {
+        return pc.localDescription;
+    };
+    this.getRemoteSessionDescriptor = function () {
+        return pc.remoteDescription;
+    };
+    function setRemoteVideo() {
+        if (remoteVideo) {
+            var stream = pc.getRemoteStreams()[0];
+            remoteVideo.pause();
+            remoteVideo.srcObject = stream;
+            remoteVideo.load();
+            logger.info('Remote URL:', remoteVideo.srcObject);
+        }
+    }
+    this.showLocalVideo = function () {
+        localVideo.srcObject = videoStream;
+        localVideo.muted = true;
+    };
+    this.send = function (data) {
+        if (dataChannel && dataChannel.readyState === 'open') {
+            dataChannel.send(data);
+        } else {
+            logger.warn('Trying to send data over a non-existing or closed data channel');
+        }
+    };
+    this.processAnswer = function (sdpAnswer, callback) {
+        callback = (callback || noop).bind(this);
+        var answer = new RTCSessionDescription({
+                type: 'answer',
+                sdp: sdpAnswer
+            });
+        if (multistream && usePlanB) {
+            var planBAnswer = interop.toPlanB(answer);
+            logger.debug('asnwer::planB', dumpSDP(planBAnswer));
+            answer = planBAnswer;
+        }
+        logger.debug('SDP answer received, setting remote description');
+        if (pc.signalingState === 'closed') {
+            return callback('PeerConnection is closed');
+        }
+        pc.setRemoteDescription(answer, function () {
+            setRemoteVideo();
+            callback();
+        }, callback);
+    };
+    this.processOffer = function (sdpOffer, callback) {
+        callback = callback.bind(this);
+        var offer = new RTCSessionDescription({
+                type: 'offer',
+                sdp: sdpOffer
+            });
+        if (multistream && usePlanB) {
+            var planBOffer = interop.toPlanB(offer);
+            logger.debug('offer::planB', dumpSDP(planBOffer));
+            offer = planBOffer;
+        }
+        logger.debug('SDP offer received, setting remote description');
+        if (pc.signalingState === 'closed') {
+            return callback('PeerConnection is closed');
+        }
+        pc.setRemoteDescription(offer).then(function () {
+            return setRemoteVideo();
+        }).then(function () {
+            return pc.createAnswer();
+        }).then(function (answer) {
+            answer = mangleSdpToAddSimulcast(answer);
+            logger.debug('Created SDP answer');
+            return pc.setLocalDescription(answer);
+        }).then(function () {
+            var localDescription = pc.localDescription;
+            if (multistream && usePlanB) {
+                localDescription = interop.toUnifiedPlan(localDescription);
+                logger.debug('answer::origPlanB->UnifiedPlan', dumpSDP(localDescription));
+            }
+            logger.debug('Local description set', localDescription.sdp);
+            callback(null, localDescription.sdp);
+        }).catch(callback);
+    };
+    function mangleSdpToAddSimulcast(answer) {
+        if (simulcast) {
+            if (browser.name === 'Chrome' || browser.name === 'Chromium') {
+                logger.debug('Adding multicast info');
+                answer = new RTCSessionDescription({
+                    'type': answer.type,
+                    'sdp': removeFIDFromOffer(answer.sdp) + getSimulcastInfo(videoStream)
+                });
+            } else {
+                logger.warn('Simulcast is only available in Chrome browser.');
+            }
+        }
+        return answer;
+    }
+    function start() {
+        if (pc.signalingState === 'closed') {
+            callback('The peer connection object is in "closed" state. This is most likely due to an invocation of the dispose method before accepting in the dialogue');
+        }
+        if (videoStream && localVideo) {
+            self.showLocalVideo();
+        }
+        if (videoStream) {
+            pc.addStream(videoStream);
+        }
+        if (audioStream) {
+            pc.addStream(audioStream);
+        }
+        var browser = parser.getBrowser();
+        if (mode === 'sendonly' && (browser.name === 'Chrome' || browser.name === 'Chromium') && browser.major === 39) {
+            mode = 'sendrecv';
+        }
+        callback();
+    }
+    if (mode !== 'recvonly' && !videoStream && !audioStream) {
+        function getMedia(constraints) {
+            if (constraints === undefined) {
+                constraints = MEDIA_CONSTRAINTS;
+            }
+            navigator.mediaDevices.getUserMedia(constraints).then(function (stream) {
+                videoStream = stream;
+                start();
+            }).catch(callback);
+        }
+        if (sendSource === 'webcam') {
+            getMedia(mediaConstraints);
+        } else {
+            getScreenConstraints(sendSource, function (error, constraints_) {
+                if (error)
+                    return callback(error);
+                constraints = [mediaConstraints];
+                constraints.unshift(constraints_);
+                getMedia(recursive.apply(undefined, constraints));
+            }, guid);
+        }
+    } else {
+        setTimeout(start, 0);
+    }
+    this.on('_dispose', function () {
+        if (localVideo) {
+            localVideo.pause();
+            localVideo.src = '';
+            localVideo.load();
+            localVideo.muted = false;
+        }
+        if (remoteVideo) {
+            remoteVideo.pause();
+            remoteVideo.src = '';
+            remoteVideo.load();
+        }
+        self.removeAllListeners();
+        if (window.cancelChooseDesktopMedia !== undefined) {
+            window.cancelChooseDesktopMedia(guid);
+        }
+    });
+}
+inherits(WebRtcPeer, EventEmitter);
+function createEnableDescriptor(type) {
+    var method = 'get' + type + 'Tracks';
+    return {
+        enumerable: true,
+        get: function () {
+            if (!this.peerConnection)
+                return;
+            var streams = this.peerConnection.getLocalStreams();
+            if (!streams.length)
+                return;
+            for (var i = 0, stream; stream = streams[i]; i++) {
+                var tracks = stream[method]();
+                for (var j = 0, track; track = tracks[j]; j++)
+                    if (!track.enabled)
+                        return false;
+            }
+            return true;
+        },
+        set: function (value) {
+            function trackSetEnable(track) {
+                track.enabled = value;
+            }
+            this.peerConnection.getLocalStreams().forEach(function (stream) {
+                stream[method]().forEach(trackSetEnable);
+            });
+        }
+    };
+}
+Object.defineProperties(WebRtcPeer.prototype, {
+    'enabled': {
+        enumerable: true,
+        get: function () {
+            return this.audioEnabled && this.videoEnabled;
+        },
+        set: function (value) {
+            this.audioEnabled = this.videoEnabled = value;
+        }
+    },
+    'audioEnabled': createEnableDescriptor('Audio'),
+    'videoEnabled': createEnableDescriptor('Video')
+});
+WebRtcPeer.prototype.getLocalStream = function (index) {
+    if (this.peerConnection) {
+        return this.peerConnection.getLocalStreams()[index || 0];
+    }
+};
+WebRtcPeer.prototype.getRemoteStream = function (index) {
+    if (this.peerConnection) {
+        return this.peerConnection.getRemoteStreams()[index || 0];
+    }
+};
+WebRtcPeer.prototype.dispose = function () {
+    logger.debug('Disposing WebRtcPeer');
+    var pc = this.peerConnection;
+    var dc = this.dataChannel;
+    try {
+        if (dc) {
+            if (dc.signalingState === 'closed')
+                return;
+            dc.close();
+        }
+        if (pc) {
+            if (pc.signalingState === 'closed')
+                return;
+            pc.getLocalStreams().forEach(streamStop);
+            pc.close();
+        }
+    } catch (err) {
+        logger.warn('Exception disposing webrtc peer ' + err);
+    }
+    this.emit('_dispose');
+};
+function WebRtcPeerRecvonly(options, callback) {
+    if (!(this instanceof WebRtcPeerRecvonly)) {
+        return new WebRtcPeerRecvonly(options, callback);
+    }
+    WebRtcPeerRecvonly.super_.call(this, 'recvonly', options, callback);
+}
+inherits(WebRtcPeerRecvonly, WebRtcPeer);
+function WebRtcPeerSendonly(options, callback) {
+    if (!(this instanceof WebRtcPeerSendonly)) {
+        return new WebRtcPeerSendonly(options, callback);
+    }
+    WebRtcPeerSendonly.super_.call(this, 'sendonly', options, callback);
+}
+inherits(WebRtcPeerSendonly, WebRtcPeer);
+function WebRtcPeerSendrecv(options, callback) {
+    if (!(this instanceof WebRtcPeerSendrecv)) {
+        return new WebRtcPeerSendrecv(options, callback);
+    }
+    WebRtcPeerSendrecv.super_.call(this, 'sendrecv', options, callback);
+}
+inherits(WebRtcPeerSendrecv, WebRtcPeer);
+function harkUtils(stream, options) {
+    return hark(stream, options);
+}
+exports.bufferizeCandidates = bufferizeCandidates;
+exports.WebRtcPeerRecvonly = WebRtcPeerRecvonly;
+exports.WebRtcPeerSendonly = WebRtcPeerSendonly;
+exports.WebRtcPeerSendrecv = WebRtcPeerSendrecv;
+exports.hark = harkUtils;
+},{"events":4,"freeice":5,"hark":8,"inherits":9,"kurento-browser-extensions":10,"merge":11,"sdp-translator":18,"ua-parser-js":21,"uuid":23}],2:[function(require,module,exports){
+if (window.addEventListener)
+    module.exports = require('./index');
+},{"./index":3}],3:[function(require,module,exports){
+var WebRtcPeer = require('./WebRtcPeer');
+exports.WebRtcPeer = WebRtcPeer;
+},{"./WebRtcPeer":1}],4:[function(require,module,exports){
+// Copyright Joyent, Inc. and other Node contributors.
+//
+// Permission is hereby granted, free of charge, to any person obtaining a
+// copy of this software and associated documentation files (the
+// "Software"), to deal in the Software without restriction, including
+// without limitation the rights to use, copy, modify, merge, publish,
+// distribute, sublicense, and/or sell copies of the Software, and to permit
+// persons to whom the Software is furnished to do so, subject to the
+// following conditions:
+//
+// The above copyright notice and this permission notice shall be included
+// in all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
+// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN
+// NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
+// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
+// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE
+// USE OR OTHER DEALINGS IN THE SOFTWARE.
+
+function EventEmitter() {
+  this._events = this._events || {};
+  this._maxListeners = this._maxListeners || undefined;
+}
+module.exports = EventEmitter;
+
+// Backwards-compat with node 0.10.x
+EventEmitter.EventEmitter = EventEmitter;
+
+EventEmitter.prototype._events = undefined;
+EventEmitter.prototype._maxListeners = undefined;
+
+// By default EventEmitters will print a warning if more than 10 listeners are
+// added to it. This is a useful default which helps finding memory leaks.
+EventEmitter.defaultMaxListeners = 10;
+
+// Obviously not all Emitters should be limited to 10. This function allows
+// that to be increased. Set to zero for unlimited.
+EventEmitter.prototype.setMaxListeners = function(n) {
+  if (!isNumber(n) || n < 0 || isNaN(n))
+    throw TypeError('n must be a positive number');
+  this._maxListeners = n;
+  return this;
+};
+
+EventEmitter.prototype.emit = function(type) {
+  var er, handler, len, args, i, listeners;
+
+  if (!this._events)
+    this._events = {};
+
+  // If there is no 'error' event listener then throw.
+  if (type === 'error') {
+    if (!this._events.error ||
+        (isObject(this._events.error) && !this._events.error.length)) {
+      er = arguments[1];
+      if (er instanceof Error) {
+        throw er; // Unhandled 'error' event
+      } else {
+        // At least give some kind of context to the user
+        var err = new Error('Uncaught, unspecified "error" event. (' + er + ')');
+        err.context = er;
+        throw err;
+      }
+    }
+  }
+
+  handler = this._events[type];
+
+  if (isUndefined(handler))
+    return false;
+
+  if (isFunction(handler)) {
+    switch (arguments.length) {
+      // fast cases
+      case 1:
+        handler.call(this);
+        break;
+      case 2:
+        handler.call(this, arguments[1]);
+        break;
+      case 3:
+        handler.call(this, arguments[1], arguments[2]);
+        break;
+      // slower
+      default:
+        args = Array.prototype.slice.call(arguments, 1);
+        handler.apply(this, args);
+    }
+  } else if (isObject(handler)) {
+    args = Array.prototype.slice.call(arguments, 1);
+    listeners = handler.slice();
+    len = listeners.length;
+    for (i = 0; i < len; i++)
+      listeners[i].apply(this, args);
+  }
+
+  return true;
+};
+
+EventEmitter.prototype.addListener = function(type, listener) {
+  var m;
+
+  if (!isFunction(listener))
+    throw TypeError('listener must be a function');
+
+  if (!this._events)
+    this._events = {};
+
+  // To avoid recursion in the case that type === "newListener"! Before
+  // adding it to the listeners, first emit "newListener".
+  if (this._events.newListener)
+    this.emit('newListener', type,
+              isFunction(listener.listener) ?
+              listener.listener : listener);
+
+  if (!this._events[type])
+    // Optimize the case of one listener. Don't need the extra array object.
+    this._events[type] = listener;
+  else if (isObject(this._events[type]))
+    // If we've already got an array, just append.
+    this._events[type].push(listener);
+  else
+    // Adding the second element, need to change to array.
+    this._events[type] = [this._events[type], listener];
+
+  // Check for listener leak
+  if (isObject(this._events[type]) && !this._events[type].warned) {
+    if (!isUndefined(this._maxListeners)) {
+      m = this._maxListeners;
+    } else {
+      m = EventEmitter.defaultMaxListeners;
+    }
+
+    if (m && m > 0 && this._events[type].length > m) {
+      this._events[type].warned = true;
+      console.error('(node) warning: possible EventEmitter memory ' +
+                    'leak detected. %d listeners added. ' +
+                    'Use emitter.setMaxListeners() to increase limit.',
+                    this._events[type].length);
+      if (typeof console.trace === 'function') {
+        // not supported in IE 10
+        console.trace();
+      }
+    }
+  }
+
+  return this;
+};
+
+EventEmitter.prototype.on = EventEmitter.prototype.addListener;
+
+EventEmitter.prototype.once = function(type, listener) {
+  if (!isFunction(listener))
+    throw TypeError('listener must be a function');
+
+  var fired = false;
+
+  function g() {
+    this.removeListener(type, g);
+
+    if (!fired) {
+      fired = true;
+      listener.apply(this, arguments);
+    }
+  }
+
+  g.listener = listener;
+  this.on(type, g);
+
+  return this;
+};
+
+// emits a 'removeListener' event iff the listener was removed
+EventEmitter.prototype.removeListener = function(type, listener) {
+  var list, position, length, i;
+
+  if (!isFunction(listener))
+    throw TypeError('listener must be a function');
+
+  if (!this._events || !this._events[type])
+    return this;
+
+  list = this._events[type];
+  length = list.length;
+  position = -1;
+
+  if (list === listener ||
+      (isFunction(list.listener) && list.listener === listener)) {
+    delete this._events[type];
+    if (this._events.removeListener)
+      this.emit('removeListener', type, listener);
+
+  } else if (isObject(list)) {
+    for (i = length; i-- > 0;) {
+      if (list[i] === listener ||
+          (list[i].listener && list[i].listener === listener)) {
+        position = i;
+        break;
+      }
+    }
+
+    if (position < 0)
+      return this;
+
+    if (list.length === 1) {
+      list.length = 0;
+      delete this._events[type];
+    } else {
+      list.splice(position, 1);
+    }
+
+    if (this._events.removeListener)
+      this.emit('removeListener', type, listener);
+  }
+
+  return this;
+};
+
+EventEmitter.prototype.removeAllListeners = function(type) {
+  var key, listeners;
+
+  if (!this._events)
+    return this;
+
+  // not listening for removeListener, no need to emit
+  if (!this._events.removeListener) {
+    if (arguments.length === 0)
+      this._events = {};
+    else if (this._events[type])
+      delete this._events[type];
+    return this;
+  }
+
+  // emit removeListener for all listeners on all events
+  if (arguments.length === 0) {
+    for (key in this._events) {
+      if (key === 'removeListener') continue;
+      this.removeAllListeners(key);
+    }
+    this.removeAllListeners('removeListener');
+    this._events = {};
+    return this;
+  }
+
+  listeners = this._events[type];
+
+  if (isFunction(listeners)) {
+    this.removeListener(type, listeners);
+  } else if (listeners) {
+    // LIFO order
+    while (listeners.length)
+      this.removeListener(type, listeners[listeners.length - 1]);
+  }
+  delete this._events[type];
+
+  return this;
+};
+
+EventEmitter.prototype.listeners = function(type) {
+  var ret;
+  if (!this._events || !this._events[type])
+    ret = [];
+  else if (isFunction(this._events[type]))
+    ret = [this._events[type]];
+  else
+    ret = this._events[type].slice();
+  return ret;
+};
+
+EventEmitter.prototype.listenerCount = function(type) {
+  if (this._events) {
+    var evlistener = this._events[type];
+
+    if (isFunction(evlistener))
+      return 1;
+    else if (evlistener)
+      return evlistener.length;
+  }
+  return 0;
+};
+
+EventEmitter.listenerCount = function(emitter, type) {
+  return emitter.listenerCount(type);
+};
+
+function isFunction(arg) {
+  return typeof arg === 'function';
+}
+
+function isNumber(arg) {
+  return typeof arg === 'number';
+}
+
+function isObject(arg) {
+  return typeof arg === 'object' && arg !== null;
+}
+
+function isUndefined(arg) {
+  return arg === void 0;
+}
+
+},{}],5:[function(require,module,exports){
+/* jshint node: true */
+'use strict';
+
+var normalice = require('normalice');
+
+/**
+  # freeice
+
+  The `freeice` module is a simple way of getting random STUN or TURN server
+  for your WebRTC application.  The list of servers (just STUN at this stage)
+  were sourced from this [gist](https://gist.github.com/zziuni/3741933).
+
+  ## Example Use
+
+  The following demonstrates how you can use `freeice` with
+  [rtc-quickconnect](https://github.com/rtc-io/rtc-quickconnect):
+
+  <<< examples/quickconnect.js
+
+  As the `freeice` module generates ice servers in a list compliant with the
+  WebRTC spec you will be able to use it with raw `RTCPeerConnection`
+  constructors and other WebRTC libraries.
+
+  ## Hey, don't use my STUN/TURN server!
+
+  If for some reason your free STUN or TURN server ends up in the
+  list of servers ([stun](https://github.com/DamonOehlman/freeice/blob/master/stun.json) or
+  [turn](https://github.com/DamonOehlman/freeice/blob/master/turn.json))
+  that is used in this module, you can feel
+  free to open an issue on this repository and those servers will be removed
+  within 24 hours (or sooner).  This is the quickest and probably the most
+  polite way to have something removed (and provides us some visibility
+  if someone opens a pull request requesting that a server is added).
+
+  ## Please add my server!
+
+  If you have a server that you wish to add to the list, that's awesome! I'm
+  sure I speak on behalf of a whole pile of WebRTC developers who say thanks.
+  To get it into the list, feel free to either open a pull request or if you
+  find that process a bit daunting then just create an issue requesting
+  the addition of the server (make sure you provide all the details, and if
+  you have a Terms of Service then including that in the PR/issue would be
+  awesome).
+
+  ## I know of a free server, can I add it?
+
+  Sure, if you do your homework and make sure it is ok to use (I'm currently
+  in the process of reviewing the terms of those STUN servers included from
+  the original list).  If it's ok to go, then please see the previous entry
+  for how to add it.
+
+  ## Current List of Servers
+
+  * current as at the time of last `README.md` file generation
+
+  ### STUN
+
+  <<< stun.json
+
+  ### TURN
+
+  <<< turn.json
+
+**/
+
+var freeice = module.exports = function(opts) {
+  // if a list of servers has been provided, then use it instead of defaults
+  var servers = {
+    stun: (opts || {}).stun || require('./stun.json'),
+    turn: (opts || {}).turn || require('./turn.json')
+  };
+
+  var stunCount = (opts || {}).stunCount || 2;
+  var turnCount = (opts || {}).turnCount || 0;
+  var selected;
+
+  function getServers(type, count) {
+    var out = [];
+    var input = [].concat(servers[type]);
+    var idx;
+
+    while (input.length && out.length < count) {
+      idx = (Math.random() * input.length) | 0;
+      out = out.concat(input.splice(idx, 1));
+    }
+
+    return out.map(function(url) {
+        //If it's a not a string, don't try to "normalice" it otherwise using type:url will screw it up
+        if ((typeof url !== 'string') && (! (url instanceof String))) {
+            return url;
+        } else {
+            return normalice(type + ':' + url);
+        }
+    });
+  }
+
+  // add stun servers
+  selected = [].concat(getServers('stun', stunCount));
+
+  if (turnCount) {
+    selected = selected.concat(getServers('turn', turnCount));
+  }
+
+  return selected;
+};
+
+},{"./stun.json":6,"./turn.json":7,"normalice":12}],6:[function(require,module,exports){
+module.exports=[
+  "stun.l.google.com:19302",
+  "stun1.l.google.com:19302",
+  "stun2.l.google.com:19302",
+  "stun3.l.google.com:19302",
+  "stun4.l.google.com:19302",
+  "stun.ekiga.net",
+  "stun.ideasip.com",
+  "stun.schlund.de",
+  "stun.stunprotocol.org:3478",
+  "stun.voiparound.com",
+  "stun.voipbuster.com",
+  "stun.voipstunt.com",
+  "stun.voxgratia.org",
+  "stun.services.mozilla.com"
+]
+
+},{}],7:[function(require,module,exports){
+module.exports=[]
+
+},{}],8:[function(require,module,exports){
+var WildEmitter = require('wildemitter');
+
+function getMaxVolume (analyser, fftBins) {
+  var maxVolume = -Infinity;
+  analyser.getFloatFrequencyData(fftBins);
+
+  for(var i=4, ii=fftBins.length; i < ii; i++) {
+    if (fftBins[i] > maxVolume && fftBins[i] < 0) {
+      maxVolume = fftBins[i];
+    }
+  };
+
+  return maxVolume;
+}
+
+
+var audioContextType = window.AudioContext || window.webkitAudioContext;
+// use a single audio context due to hardware limits
+var audioContext = null;
+module.exports = function(stream, options) {
+  var harker = new WildEmitter();
+
+
+  // make it not break in non-supported browsers
+  if (!audioContextType) return harker;
+
+  //Config
+  var options = options || {},
+      smoothing = (options.smoothing || 0.1),
+      interval = (options.interval || 50),
+      threshold = options.threshold,
+      play = options.play,
+      history = options.history || 10,
+      running = true;
+
+  //Setup Audio Context
+  if (!audioContext) {
+    audioContext = new audioContextType();
+  }
+  var sourceNode, fftBins, analyser;
+
+  analyser = audioContext.createAnalyser();
+  analyser.fftSize = 512;
+  analyser.smoothingTimeConstant = smoothing;
+  fftBins = new Float32Array(analyser.fftSize);
+
+  if (stream.jquery) stream = stream[0];
+  if (stream instanceof HTMLAudioElement || stream instanceof HTMLVideoElement) {
+    //Audio Tag
+    sourceNode = audioContext.createMediaElementSource(stream);
+    if (typeof play === 'undefined') play = true;
+    threshold = threshold || -50;
+  } else {
+    //WebRTC Stream
+    sourceNode = audioContext.createMediaStreamSource(stream);
+    threshold = threshold || -50;
+  }
+
+  sourceNode.connect(analyser);
+  if (play) analyser.connect(audioContext.destination);
+
+  harker.speaking = false;
+
+  harker.setThreshold = function(t) {
+    threshold = t;
+  };
+
+  harker.setInterval = function(i) {
+    interval = i;
+  };
+  
+  harker.stop = function() {
+    running = false;
+    harker.emit('volume_change', -100, threshold);
+    if (harker.speaking) {
+      harker.speaking = false;
+      harker.emit('stopped_speaking');
+    }
+  };
+  harker.speakingHistory = [];
+  for (var i = 0; i < history; i++) {
+      harker.speakingHistory.push(0);
+  }
+
+  // Poll the analyser node to determine if speaking
+  // and emit events if changed
+  var looper = function() {
+    setTimeout(function() {
+    
+      //check if stop has been called
+      if(!running) {
+        return;
+      }
+      
+      var currentVolume = getMaxVolume(analyser, fftBins);
+
+      harker.emit('volume_change', currentVolume, threshold);
+
+      var history = 0;
+      if (currentVolume > threshold && !harker.speaking) {
+        // trigger quickly, short history
+        for (var i = harker.speakingHistory.length - 3; i < harker.speakingHistory.length; i++) {
+          history += harker.speakingHistory[i];
+        }
+        if (history >= 2) {
+          harker.speaking = true;
+          harker.emit('speaking');
+        }
+      } else if (currentVolume < threshold && harker.speaking) {
+        for (var i = 0; i < harker.speakingHistory.length; i++) {
+          history += harker.speakingHistory[i];
+        }
+        if (history == 0) {
+          harker.speaking = false;
+          harker.emit('stopped_speaking');
+        }
+      }
+      harker.speakingHistory.shift();
+      harker.speakingHistory.push(0 + (currentVolume > threshold));
+
+      looper();
+    }, interval);
+  };
+  looper();
+
+
+  return harker;
+}
+
+},{"wildemitter":24}],9:[function(require,module,exports){
+if (typeof Object.create === 'function') {
+  // implementation from standard node.js 'util' module
+  module.exports = function inherits(ctor, superCtor) {
+    ctor.super_ = superCtor
+    ctor.prototype = Object.create(superCtor.prototype, {
+      constructor: {
+        value: ctor,
+        enumerable: false,
+        writable: true,
+        configurable: true
+      }
+    });
+  };
+} else {
+  // old school shim for old browsers
+  module.exports = function inherits(ctor, superCtor) {
+    ctor.super_ = superCtor
+    var TempCtor = function () {}
+    TempCtor.prototype = superCtor.prototype
+    ctor.prototype = new TempCtor()
+    ctor.prototype.constructor = ctor
+  }
+}
+
+},{}],10:[function(require,module,exports){
+// Does nothing at all.
+
+},{}],11:[function(require,module,exports){
+/*!
+ * @name JavaScript/NodeJS Merge v1.2.0
+ * @author yeikos
+ * @repository https://github.com/yeikos/js.merge
+
+ * Copyright 2014 yeikos - MIT license
+ * https://raw.github.com/yeikos/js.merge/master/LICENSE
+ */
+
+;(function(isNode) {
+
+	/**
+	 * Merge one or more objects 
+	 * @param bool? clone
+	 * @param mixed,... arguments
+	 * @return object
+	 */
+
+	var Public = function(clone) {
+
+		return merge(clone === true, false, arguments);
+
+	}, publicName = 'merge';
+
+	/**
+	 * Merge two or more objects recursively 
+	 * @param bool? clone
+	 * @param mixed,... arguments
+	 * @return object
+	 */
+
+	Public.recursive = function(clone) {
+
+		return merge(clone === true, true, arguments);
+
+	};
+
+	/**
+	 * Clone the input removing any reference
+	 * @param mixed input
+	 * @return mixed
+	 */
+
+	Public.clone = function(input) {
+
+		var output = input,
+			type = typeOf(input),
+			index, size;
+
+		if (type === 'array') {
+
+			output = [];
+			size = input.length;
+
+			for (index=0;index<size;++index)
+
+				output[index] = Public.clone(input[index]);
+
+		} else if (type === 'object') {
+
+			output = {};
+
+			for (index in input)
+
+				output[index] = Public.clone(input[index]);
+
+		}
+
+		return output;
+
+	};
+
+	/**
+	 * Merge two objects recursively
+	 * @param mixed input
+	 * @param mixed extend
+	 * @return mixed
+	 */
+
+	function merge_recursive(base, extend) {
+
+		if (typeOf(base) !== 'object')
+
+			return extend;
+
+		for (var key in extend) {
+
+			if (typeOf(base[key]) === 'object' && typeOf(extend[key]) === 'object') {
+
+				base[key] = merge_recursive(base[key], extend[key]);
+
+			} else {
+
+				base[key] = extend[key];
+
+			}
+
+		}
+
+		return base;
+
+	}
+
+	/**
+	 * Merge two or more objects
+	 * @param bool clone
+	 * @param bool recursive
+	 * @param array argv
+	 * @return object
+	 */
+
+	function merge(clone, recursive, argv) {
+
+		var result = argv[0],
+			size = argv.length;
+
+		if (clone || typeOf(result) !== 'object')
+
+			result = {};
+
+		for (var index=0;index<size;++index) {
+
+			var item = argv[index],
+
+				type = typeOf(item);
+
+			if (type !== 'object') continue;
+
+			for (var key in item) {
+
+				var sitem = clone ? Public.clone(item[key]) : item[key];
+
+				if (recursive) {
+
+					result[key] = merge_recursive(result[key], sitem);
+
+				} else {
+
+					result[key] = sitem;
+
+				}
+
+			}
+
+		}
+
+		return result;
+
+	}
+
+	/**
+	 * Get type of variable
+	 * @param mixed input
+	 * @return string
+	 *
+	 * @see http://jsperf.com/typeofvar
+	 */
+
+	function typeOf(input) {
+
+		return ({}).toString.call(input).slice(8, -1).toLowerCase();
+
+	}
+
+	if (isNode) {
+
+		module.exports = Public;
+
+	} else {
+
+		window[publicName] = Public;
+
+	}
+
+})(typeof module === 'object' && module && typeof module.exports === 'object' && module.exports);
+},{}],12:[function(require,module,exports){
+/**
+  # normalice
+
+  Normalize an ice server configuration object (or plain old string) into a format
+  that is usable in all browsers supporting WebRTC.  Primarily this module is designed
+  to help with the transition of the `url` attribute of the configuration object to
+  the `urls` attribute.
+
+  ## Example Usage
+
+  <<< examples/simple.js
+
+**/
+
+var protocols = [
+  'stun:',
+  'turn:'
+];
+
+module.exports = function(input) {
+  var url = (input || {}).url || input;
+  var protocol;
+  var parts;
+  var output = {};
+
+  // if we don't have a string url, then allow the input to passthrough
+  if (typeof url != 'string' && (! (url instanceof String))) {
+    return input;
+  }
+
+  // trim the url string, and convert to an array
+  url = url.trim();
+
+  // if the protocol is not known, then passthrough
+  protocol = protocols[protocols.indexOf(url.slice(0, 5))];
+  if (! protocol) {
+    return input;
+  }
+
+  // now let's attack the remaining url parts
+  url = url.slice(5);
+  parts = url.split('@');
+
+  output.username = input.username;
+  output.credential = input.credential;
+  // if we have an authentication part, then set the credentials
+  if (parts.length > 1) {
+    url = parts[1];
+    parts = parts[0].split(':');
+
+    // add the output credential and username
+    output.username = parts[0];
+    output.credential = (input || {}).credential || parts[1] || '';
+  }
+
+  output.url = protocol + url;
+  output.urls = [ output.url ];
+
+  return output;
+};
+
+},{}],13:[function(require,module,exports){
+var grammar = module.exports = {
+  v: [{
+      name: 'version',
+      reg: /^(\d*)$/
+  }],
+  o: [{ //o=- 20518 0 IN IP4 203.0.113.1
+    // NB: sessionId will be a String in most cases because it is huge
+    name: 'origin',
+    reg: /^(\S*) (\d*) (\d*) (\S*) IP(\d) (\S*)/,
+    names: ['username', 'sessionId', 'sessionVersion', 'netType', 'ipVer', 'address'],
+    format: "%s %s %d %s IP%d %s"
+  }],
+  // default parsing of these only (though some of these feel outdated)
+  s: [{ name: 'name' }],
+  i: [{ name: 'description' }],
+  u: [{ name: 'uri' }],
+  e: [{ name: 'email' }],
+  p: [{ name: 'phone' }],
+  z: [{ name: 'timezones' }], // TODO: this one can actually be parsed properly..
+  r: [{ name: 'repeats' }],   // TODO: this one can also be parsed properly
+  //k: [{}], // outdated thing ignored
+  t: [{ //t=0 0
+    name: 'timing',
+    reg: /^(\d*) (\d*)/,
+    names: ['start', 'stop'],
+    format: "%d %d"
+  }],
+  c: [{ //c=IN IP4 10.47.197.26
+      name: 'connection',
+      reg: /^IN IP(\d) (\S*)/,
+      names: ['version', 'ip'],
+      format: "IN IP%d %s"
+  }],
+  b: [{ //b=AS:4000
+      push: 'bandwidth',
+      reg: /^(TIAS|AS|CT|RR|RS):(\d*)/,
+      names: ['type', 'limit'],
+      format: "%s:%s"
+  }],
+  m: [{ //m=video 51744 RTP/AVP 126 97 98 34 31
+      // NB: special - pushes to session
+      // TODO: rtp/fmtp should be filtered by the payloads found here?
+      reg: /^(\w*) (\d*) ([\w\/]*)(?: (.*))?/,
+      names: ['type', 'port', 'protocol', 'payloads'],
+      format: "%s %d %s %s"
+  }],
+  a: [
+    { //a=rtpmap:110 opus/48000/2
+      push: 'rtp',
+      reg: /^rtpmap:(\d*) ([\w\-]*)(?:\s*\/(\d*)(?:\s*\/(\S*))?)?/,
+      names: ['payload', 'codec', 'rate', 'encoding'],
+      format: function (o) {
+        return (o.encoding) ?
+          "rtpmap:%d %s/%s/%s":
+          o.rate ?
+          "rtpmap:%d %s/%s":
+          "rtpmap:%d %s";
+      }
+    },
+    {
+      //a=fmtp:108 profile-level-id=24;object=23;bitrate=64000
+      //a=fmtp:111 minptime=10; useinbandfec=1
+      push: 'fmtp',
+      reg: /^fmtp:(\d*) ([\S| ]*)/,
+      names: ['payload', 'config'],
+      format: "fmtp:%d %s"
+    },
+    { //a=control:streamid=0
+        name: 'control',
+        reg: /^control:(.*)/,
+        format: "control:%s"
+    },
+    { //a=rtcp:65179 IN IP4 193.84.77.194
+      name: 'rtcp',
+      reg: /^rtcp:(\d*)(?: (\S*) IP(\d) (\S*))?/,
+      names: ['port', 'netType', 'ipVer', 'address'],
+      format: function (o) {
+        return (o.address != null) ?
+          "rtcp:%d %s IP%d %s":
+          "rtcp:%d";
+      }
+    },
+    { //a=rtcp-fb:98 trr-int 100
+      push: 'rtcpFbTrrInt',
+      reg: /^rtcp-fb:(\*|\d*) trr-int (\d*)/,
+      names: ['payload', 'value'],
+      format: "rtcp-fb:%d trr-int %d"
+    },
+    { //a=rtcp-fb:98 nack rpsi
+      push: 'rtcpFb',
+      reg: /^rtcp-fb:(\*|\d*) ([\w-_]*)(?: ([\w-_]*))?/,
+      names: ['payload', 'type', 'subtype'],
+      format: function (o) {
+        return (o.subtype != null) ?
+          "rtcp-fb:%s %s %s":
+          "rtcp-fb:%s %s";
+      }
+    },
+    { //a=extmap:2 urn:ietf:params:rtp-hdrext:toffset
+      //a=extmap:1/recvonly URI-gps-string
+      push: 'ext',
+      reg: /^extmap:([\w_\/]*) (\S*)(?: (\S*))?/,
+      names: ['value', 'uri', 'config'], // value may include "/direction" suffix
+      format: function (o) {
+        return (o.config != null) ?
+          "extmap:%s %s %s":
+          "extmap:%s %s";
+      }
+    },
+    {
+      //a=crypto:1 AES_CM_128_HMAC_SHA1_80 inline:PS1uQCVeeCFCanVmcjkpPywjNWhcYD0mXXtxaVBR|2^20|1:32
+      push: 'crypto',
+      reg: /^crypto:(\d*) ([\w_]*) (\S*)(?: (\S*))?/,
+      names: ['id', 'suite', 'config', 'sessionConfig'],
+      format: function (o) {
+        return (o.sessionConfig != null) ?
+          "crypto:%d %s %s %s":
+          "crypto:%d %s %s";
+      }
+    },
+    { //a=setup:actpass
+      name: 'setup',
+      reg: /^setup:(\w*)/,
+      format: "setup:%s"
+    },
+    { //a=mid:1
+      name: 'mid',
+      reg: /^mid:([^\s]*)/,
+      format: "mid:%s"
+    },
+    { //a=msid:0c8b064d-d807-43b4-b434-f92a889d8587 98178685-d409-46e0-8e16-7ef0db0db64a
+      name: 'msid',
+      reg: /^msid:(.*)/,
+      format: "msid:%s"
+    },
+    { //a=ptime:20
+      name: 'ptime',
+      reg: /^ptime:(\d*)/,
+      format: "ptime:%d"
+    },
+    { //a=maxptime:60
+      name: 'maxptime',
+      reg: /^maxptime:(\d*)/,
+      format: "maxptime:%d"
+    },
+    { //a=sendrecv
+      name: 'direction',
+      reg: /^(sendrecv|recvonly|sendonly|inactive)/
+    },
+    { //a=ice-lite
+      name: 'icelite',
+      reg: /^(ice-lite)/
+    },
+    { //a=ice-ufrag:F7gI
+      name: 'iceUfrag',
+      reg: /^ice-ufrag:(\S*)/,
+      format: "ice-ufrag:%s"
+    },
+    { //a=ice-pwd:x9cml/YzichV2+XlhiMu8g
+      name: 'icePwd',
+      reg: /^ice-pwd:(\S*)/,
+      format: "ice-pwd:%s"
+    },
+    { //a=fingerprint:SHA-1 00:11:22:33:44:55:66:77:88:99:AA:BB:CC:DD:EE:FF:00:11:22:33
+      name: 'fingerprint',
+      reg: /^fingerprint:(\S*) (\S*)/,
+      names: ['type', 'hash'],
+      format: "fingerprint:%s %s"
+    },
+    {
+      //a=candidate:0 1 UDP 2113667327 203.0.113.1 54400 typ host
+      //a=candidate:1162875081 1 udp 2113937151 192.168.34.75 60017 typ host generation 0
+      //a=candidate:3289912957 2 udp 1845501695 193.84.77.194 60017 typ srflx raddr 192.168.34.75 rport 60017 generation 0
+      //a=candidate:229815620 1 tcp 1518280447 192.168.150.19 60017 typ host tcptype active generation 0
+      //a=candidate:3289912957 2 tcp 1845501695 193.84.77.194 60017 typ srflx raddr 192.168.34.75 rport 60017 tcptype passive generation 0
+      push:'candidates',
+      reg: /^candidate:(\S*) (\d*) (\S*) (\d*) (\S*) (\d*) typ (\S*)(?: raddr (\S*) rport (\d*))?(?: tcptype (\S*))?(?: generation (\d*))?/,
+      names: ['foundation', 'component', 'transport', 'priority', 'ip', 'port', 'type', 'raddr', 'rport', 'tcptype', 'generation'],
+      format: function (o) {
+        var str = "candidate:%s %d %s %d %s %d typ %s";
+
+        str += (o.raddr != null) ? " raddr %s rport %d" : "%v%v";
+
+        // NB: candidate has three optional chunks, so %void middles one if it's missing
+        str += (o.tcptype != null) ? " tcptype %s" : "%v";
+
+        if (o.generation != null) {
+          str += " generation %d";
+        }
+        return str;
+      }
+    },
+    { //a=end-of-candidates (keep after the candidates line for readability)
+      name: 'endOfCandidates',
+      reg: /^(end-of-candidates)/
+    },
+    { //a=remote-candidates:1 203.0.113.1 54400 2 203.0.113.1 54401 ...
+      name: 'remoteCandidates',
+      reg: /^remote-candidates:(.*)/,
+      format: "remote-candidates:%s"
+    },
+    { //a=ice-options:google-ice
+      name: 'iceOptions',
+      reg: /^ice-options:(\S*)/,
+      format: "ice-options:%s"
+    },
+    { //a=ssrc:2566107569 cname:t9YU8M1UxTF8Y1A1
+      push: "ssrcs",
+      reg: /^ssrc:(\d*) ([\w_]*):(.*)/,
+      names: ['id', 'attribute', 'value'],
+      format: "ssrc:%d %s:%s"
+    },
+    { //a=ssrc-group:FEC 1 2
+      push: "ssrcGroups",
+      reg: /^ssrc-group:(\w*) (.*)/,
+      names: ['semantics', 'ssrcs'],
+      format: "ssrc-group:%s %s"
+    },
+    { //a=msid-semantic: WMS Jvlam5X3SX1OP6pn20zWogvaKJz5Hjf9OnlV
+      name: "msidSemantic",
+      reg: /^msid-semantic:\s?(\w*) (\S*)/,
+      names: ['semantic', 'token'],
+      format: "msid-semantic: %s %s" // space after ":" is not accidental
+    },
+    { //a=group:BUNDLE audio video
+      push: 'groups',
+      reg: /^group:(\w*) (.*)/,
+      names: ['type', 'mids'],
+      format: "group:%s %s"
+    },
+    { //a=rtcp-mux
+      name: 'rtcpMux',
+      reg: /^(rtcp-mux)/
+    },
+    { //a=rtcp-rsize
+      name: 'rtcpRsize',
+      reg: /^(rtcp-rsize)/
+    },
+    { // any a= that we don't understand is kepts verbatim on media.invalid
+      push: 'invalid',
+      names: ["value"]
+    }
+  ]
+};
+
+// set sensible defaults to avoid polluting the grammar with boring details
+Object.keys(grammar).forEach(function (key) {
+  var objs = grammar[key];
+  objs.forEach(function (obj) {
+    if (!obj.reg) {
+      obj.reg = /(.*)/;
+    }
+    if (!obj.format) {
+      obj.format = "%s";
+    }
+  });
+});
+
+},{}],14:[function(require,module,exports){
+var parser = require('./parser');
+var writer = require('./writer');
+
+exports.write = writer;
+exports.parse = parser.parse;
+exports.parseFmtpConfig = parser.parseFmtpConfig;
+exports.parsePayloads = parser.parsePayloads;
+exports.parseRemoteCandidates = parser.parseRemoteCandidates;
+
+},{"./parser":15,"./writer":16}],15:[function(require,module,exports){
+var toIntIfInt = function (v) {
+  return String(Number(v)) === v ? Number(v) : v;
+};
+
+var attachProperties = function (match, location, names, rawName) {
+  if (rawName && !names) {
+    location[rawName] = toIntIfInt(match[1]);
+  }
+  else {
+    for (var i = 0; i < names.length; i += 1) {
+      if (match[i+1] != null) {
+        location[names[i]] = toIntIfInt(match[i+1]);
+      }
+    }
+  }
+};
+
+var parseReg = function (obj, location, content) {
+  var needsBlank = obj.name && obj.names;
+  if (obj.push && !location[obj.push]) {
+    location[obj.push] = [];
+  }
+  else if (needsBlank && !location[obj.name]) {
+    location[obj.name] = {};
+  }
+  var keyLocation = obj.push ?
+    {} :  // blank object that will be pushed
+    needsBlank ? location[obj.name] : location; // otherwise, named location or root
+
+  attachProperties(content.match(obj.reg), keyLocation, obj.names, obj.name);
+
+  if (obj.push) {
+    location[obj.push].push(keyLocation);
+  }
+};
+
+var grammar = require('./grammar');
+var validLine = RegExp.prototype.test.bind(/^([a-z])=(.*)/);
+
+exports.parse = function (sdp) {
+  var session = {}
+    , media = []
+    , location = session; // points at where properties go under (one of the above)
+
+  // parse lines we understand
+  sdp.split(/(\r\n|\r|\n)/).filter(validLine).forEach(function (l) {
+    var type = l[0];
+    var content = l.slice(2);
+    if (type === 'm') {
+      media.push({rtp: [], fmtp: []});
+      location = media[media.length-1]; // point at latest media line
+    }
+
+    for (var j = 0; j < (grammar[type] || []).length; j += 1) {
+      var obj = grammar[type][j];
+      if (obj.reg.test(content)) {
+        return parseReg(obj, location, content);
+      }
+    }
+  });
+
+  session.media = media; // link it up
+  return session;
+};
+
+var fmtpReducer = function (acc, expr) {
+  var s = expr.split('=');
+  if (s.length === 2) {
+    acc[s[0]] = toIntIfInt(s[1]);
+  }
+  return acc;
+};
+
+exports.parseFmtpConfig = function (str) {
+  return str.split(/\;\s?/).reduce(fmtpReducer, {});
+};
+
+exports.parsePayloads = function (str) {
+  return str.split(' ').map(Number);
+};
+
+exports.parseRemoteCandidates = function (str) {
+  var candidates = [];
+  var parts = str.split(' ').map(toIntIfInt);
+  for (var i = 0; i < parts.length; i += 3) {
+    candidates.push({
+      component: parts[i],
+      ip: parts[i + 1],
+      port: parts[i + 2]
+    });
+  }
+  return candidates;
+};
+
+},{"./grammar":13}],16:[function(require,module,exports){
+var grammar = require('./grammar');
+
+// customized util.format - discards excess arguments and can void middle ones
+var formatRegExp = /%[sdv%]/g;
+var format = function (formatStr) {
+  var i = 1;
+  var args = arguments;
+  var len = args.length;
+  return formatStr.replace(formatRegExp, function (x) {
+    if (i >= len) {
+      return x; // missing argument
+    }
+    var arg = args[i];
+    i += 1;
+    switch (x) {
+      case '%%':
+        return '%';
+      case '%s':
+        return String(arg);
+      case '%d':
+        return Number(arg);
+      case '%v':
+        return '';
+    }
+  });
+  // NB: we discard excess arguments - they are typically undefined from makeLine
+};
+
+var makeLine = function (type, obj, location) {
+  var str = obj.format instanceof Function ?
+    (obj.format(obj.push ? location : location[obj.name])) :
+    obj.format;
+
+  var args = [type + '=' + str];
+  if (obj.names) {
+    for (var i = 0; i < obj.names.length; i += 1) {
+      var n = obj.names[i];
+      if (obj.name) {
+        args.push(location[obj.name][n]);
+      }
+      else { // for mLine and push attributes
+        args.push(location[obj.names[i]]);
+      }
+    }
+  }
+  else {
+    args.push(location[obj.name]);
+  }
+  return format.apply(null, args);
+};
+
+// RFC specified order
+// TODO: extend this with all the rest
+var defaultOuterOrder = [
+  'v', 'o', 's', 'i',
+  'u', 'e', 'p', 'c',
+  'b', 't', 'r', 'z', 'a'
+];
+var defaultInnerOrder = ['i', 'c', 'b', 'a'];
+
+
+module.exports = function (session, opts) {
+  opts = opts || {};
+  // ensure certain properties exist
+  if (session.version == null) {
+    session.version = 0; // "v=0" must be there (only defined version atm)
+  }
+  if (session.name == null) {
+    session.name = " "; // "s= " must be there if no meaningful name set
+  }
+  session.media.forEach(function (mLine) {
+    if (mLine.payloads == null) {
+      mLine.payloads = "";
+    }
+  });
+
+  var outerOrder = opts.outerOrder || defaultOuterOrder;
+  var innerOrder = opts.innerOrder || defaultInnerOrder;
+  var sdp = [];
+
+  // loop through outerOrder for matching properties on session
+  outerOrder.forEach(function (type) {
+    grammar[type].forEach(function (obj) {
+      if (obj.name in session && session[obj.name] != null) {
+        sdp.push(makeLine(type, obj, session));
+      }
+      else if (obj.push in session && session[obj.push] != null) {
+        session[obj.push].forEach(function (el) {
+          sdp.push(makeLine(type, obj, el));
+        });
+      }
+    });
+  });
+
+  // then for each media line, follow the innerOrder
+  session.media.forEach(function (mLine) {
+    sdp.push(makeLine('m', grammar.m[0], mLine));
+
+    innerOrder.forEach(function (type) {
+      grammar[type].forEach(function (obj) {
+        if (obj.name in mLine && mLine[obj.name] != null) {
+          sdp.push(makeLine(type, obj, mLine));
+        }
+        else if (obj.push in mLine && mLine[obj.push] != null) {
+          mLine[obj.push].forEach(function (el) {
+            sdp.push(makeLine(type, obj, el));
+          });
+        }
+      });
+    });
+  });
+
+  return sdp.join('\r\n') + '\r\n';
+};
+
+},{"./grammar":13}],17:[function(require,module,exports){
+/* Copyright @ 2015 Atlassian Pty Ltd
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+module.exports = function arrayEquals(array) {
+    // if the other array is a falsy value, return
+    if (!array)
+        return false;
+
+    // compare lengths - can save a lot of time
+    if (this.length != array.length)
+        return false;
+
+    for (var i = 0, l = this.length; i < l; i++) {
+        // Check if we have nested arrays
+        if (this[i] instanceof Array && array[i] instanceof Array) {
+            // recurse into the nested arrays
+            if (!arrayEquals.apply(this[i], [array[i]]))
+                return false;
+        } else if (this[i] != array[i]) {
+            // Warning - two different object instances will never be equal:
+            // {x:20} != {x:20}
+            return false;
+        }
+    }
+    return true;
+};
+
+
+},{}],18:[function(require,module,exports){
+/* Copyright @ 2015 Atlassian Pty Ltd
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+exports.Interop = require('./interop');
+
+},{"./interop":19}],19:[function(require,module,exports){
+/* Copyright @ 2015 Atlassian Pty Ltd
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+/* global RTCSessionDescription */
+/* global RTCIceCandidate */
+/* jshint -W097 */
+"use strict";
+
+var transform = require('./transform');
+var arrayEquals = require('./array-equals');
+
+function Interop() {
+
+    /**
+     * This map holds the most recent Unified Plan offer/answer SDP that was
+     * converted to Plan B, with the SDP type ('offer' or 'answer') as keys and
+     * the SDP string as values.
+     *
+     * @type {{}}
+     */
+    this.cache = {
+        mlB2UMap : {},
+        mlU2BMap : {}
+    };
+}
+
+module.exports = Interop;
+
+/**
+ * Changes the candidate args to match with the related Unified Plan
+ */
+Interop.prototype.candidateToUnifiedPlan = function(candidate) {
+    var cand = new RTCIceCandidate(candidate);
+
+    cand.sdpMLineIndex = this.cache.mlB2UMap[cand.sdpMLineIndex];
+    /* TODO: change sdpMid to (audio|video)-SSRC */
+
+    return cand;
+};
+
+/**
+ * Changes the candidate args to match with the related Plan B
+ */
+Interop.prototype.candidateToPlanB = function(candidate) {
+    var cand = new RTCIceCandidate(candidate);
+
+    if (cand.sdpMid.indexOf('audio') === 0) {
+      cand.sdpMid = 'audio';
+    } else if (cand.sdpMid.indexOf('video') === 0) {
+      cand.sdpMid = 'video';
+    } else {
+      throw new Error('candidate with ' + cand.sdpMid + ' not allowed');
+    }
+
+    cand.sdpMLineIndex = this.cache.mlU2BMap[cand.sdpMLineIndex];
+
+    return cand;
+};
+
+/**
+ * Returns the index of the first m-line with the given media type and with a
+ * direction which allows sending, in the last Unified Plan description with
+ * type "answer" converted to Plan B. Returns {null} if there is no saved
+ * answer, or if none of its m-lines with the given type allow sending.
+ * @param type the media type ("audio" or "video").
+ * @returns {*}
+ */
+Interop.prototype.getFirstSendingIndexFromAnswer = function(type) {
+    if (!this.cache.answer) {
+        return null;
+    }
+
+    var session = transform.parse(this.cache.answer);
+    if (session && session.media && Array.isArray(session.media)){
+        for (var i = 0; i < session.media.length; i++) {
+            if (session.media[i].type == type &&
+                (!session.media[i].direction /* default to sendrecv */ ||
+                    session.media[i].direction === 'sendrecv' ||
+                    session.media[i].direction === 'sendonly')){
+                return i;
+            }
+        }
+    }
+
+    return null;
+};
+
+/**
+ * This method transforms a Unified Plan SDP to an equivalent Plan B SDP. A
+ * PeerConnection wrapper transforms the SDP to Plan B before passing it to the
+ * application.
+ *
+ * @param desc
+ * @returns {*}
+ */
+Interop.prototype.toPlanB = function(desc) {
+    var self = this;
+    //#region Preliminary input validation.
+
+    if (typeof desc !== 'object' || desc === null ||
+        typeof desc.sdp !== 'string') {
+        console.warn('An empty description was passed as an argument.');
+        return desc;
+    }
+
+    // Objectify the SDP for easier manipulation.
+    var session = transform.parse(desc.sdp);
+
+    // If the SDP contains no media, there's nothing to transform.
+    if (typeof session.media === 'undefined' ||
+        !Array.isArray(session.media) || session.media.length === 0) {
+        console.warn('The description has no media.');
+        return desc;
+    }
+
+    // Try some heuristics to "make sure" this is a Unified Plan SDP. Plan B
+    // SDP has a video, an audio and a data "channel" at most.
+    if (session.media.length <= 3 && session.media.every(function(m) {
+            return ['video', 'audio', 'data'].indexOf(m.mid) !== -1;
+        })) {
+        console.warn('This description does not look like Unified Plan.');
+        return desc;
+    }
+
+    //#endregion
+
+    // HACK https://bugzilla.mozilla.org/show_bug.cgi?id=1113443
+    var sdp = desc.sdp;
+    var rewrite = false;
+    for (var i = 0; i < session.media.length; i++) {
+        var uLine = session.media[i];
+        uLine.rtp.forEach(function(rtp) {
+            if (rtp.codec === 'NULL')
+            {
+                rewrite = true;
+                var offer = transform.parse(self.cache.offer);
+                rtp.codec = offer.media[i].rtp[0].codec;
+            }
+        });
+    }
+    if (rewrite) {
+        sdp = transform.write(session);
+    }
+
+    // Unified Plan SDP is our "precious". Cache it for later use in the Plan B
+    // -> Unified Plan transformation.
+    this.cache[desc.type] = sdp;
+
+    //#region Convert from Unified Plan to Plan B.
+
+    // We rebuild the session.media array.
+    var media = session.media;
+    session.media = [];
+
+    // Associative array that maps channel types to channel objects for fast
+    // access to channel objects by their type, e.g. type2bl['audio']->channel
+    // obj.
+    var type2bl = {};
+
+    // Used to build the group:BUNDLE value after the channels construction
+    // loop.
+    var types = [];
+
+    media.forEach(function(uLine) {
+        // rtcp-mux is required in the Plan B SDP.
+        if ((typeof uLine.rtcpMux !== 'string' ||
+            uLine.rtcpMux !== 'rtcp-mux') &&
+            uLine.direction !== 'inactive') {
+            throw new Error('Cannot convert to Plan B because m-lines ' +
+                'without the rtcp-mux attribute were found.');
+        }
+
+        // If we don't have a channel for this uLine.type OR the selected is
+        // inactive, then select this uLine as the channel basis.
+        if (typeof type2bl[uLine.type] === 'undefined' ||
+            type2bl[uLine.type].direction === 'inactive') {
+            type2bl[uLine.type] = uLine;
+        }
+
+        if (uLine.protocol != type2bl[uLine.type].protocol) {
+          throw new Error('Cannot convert to Plan B because m-lines ' +
+              'have different protocols and this library does not have ' +
+              'support for that');
+        }
+
+        if (uLine.payloads != type2bl[uLine.type].payloads) {
+          throw new Error('Cannot convert to Plan B because m-lines ' +
+              'have different payloads and this library does not have ' +
+              'support for that');
+        }
+
+    });
+
+    // Implode the Unified Plan m-lines/tracks into Plan B channels.
+    media.forEach(function(uLine) {
+        if (uLine.type === 'application') {
+            session.media.push(uLine);
+            types.push(uLine.mid);
+            return;
+        }
+
+        // Add sources to the channel and handle a=msid.
+        if (typeof uLine.sources === 'object') {
+            Object.keys(uLine.sources).forEach(function(ssrc) {
+                if (typeof type2bl[uLine.type].sources !== 'object')
+                    type2bl[uLine.type].sources = {};
+
+                // Assign the sources to the channel.
+                type2bl[uLine.type].sources[ssrc] =
+                    uLine.sources[ssrc];
+
+                if (typeof uLine.msid !== 'undefined') {
+                    // In Plan B the msid is an SSRC attribute. Also, we don't
+                    // care about the obsolete label and mslabel attributes.
+                    //
+                    // Note that it is not guaranteed that the uLine will
+                    // have an msid. recvonly channels in particular don't have
+                    // one.
+                    type2bl[uLine.type].sources[ssrc].msid =
+                        uLine.msid;
+                }
+                // NOTE ssrcs in ssrc groups will share msids, as
+                // draft-uberti-rtcweb-plan-00 mandates.
+            });
+        }
+
+        // Add ssrc groups to the channel.
+        if (typeof uLine.ssrcGroups !== 'undefined' &&
+                Array.isArray(uLine.ssrcGroups)) {
+
+            // Create the ssrcGroups array, if it's not defined.
+            if (typeof type2bl[uLine.type].ssrcGroups === 'undefined' ||
+                    !Array.isArray(type2bl[uLine.type].ssrcGroups)) {
+                type2bl[uLine.type].ssrcGroups = [];
+            }
+
+            type2bl[uLine.type].ssrcGroups =
+                type2bl[uLine.type].ssrcGroups.concat(
+                    uLine.ssrcGroups);
+        }
+
+        if (type2bl[uLine.type] === uLine) {
+            // Plan B mids are in ['audio', 'video', 'data']
+            uLine.mid = uLine.type;
+
+            // Plan B doesn't support/need the bundle-only attribute.
+            delete uLine.bundleOnly;
+
+            // In Plan B the msid is an SSRC attribute.
+            delete uLine.msid;
+
+	    if (uLine.type == media[0].type) {
+	      types.unshift(uLine.type);
+	      // Add the channel to the new media array.
+	      session.media.unshift(uLine);
+	    } else {
+	      types.push(uLine.type);
+	      // Add the channel to the new media array.
+	      session.media.push(uLine);
+	    }
+        }
+    });
+
+    if (typeof session.groups !== 'undefined') {
+      // We regenerate the BUNDLE group with the new mids.
+      session.groups.some(function(group) {
+	  if (group.type === 'BUNDLE') {
+	      group.mids = types.join(' ');
+	      return true;
+	  }
+      });
+    }
+
+    // msid semantic
+    session.msidSemantic = {
+        semantic: 'WMS',
+        token: '*'
+    };
+
+    var resStr = transform.write(session);
+
+    return new RTCSessionDescription({
+        type: desc.type,
+        sdp: resStr
+    });
+
+    //#endregion
+};
+
+/* follow rules defined in RFC4145 */
+function addSetupAttr(uLine) {
+    if (typeof uLine.setup === 'undefined') {
+        return;
+    }
+
+    if (uLine.setup === "active") {
+            uLine.setup = "passive";
+    } else if (uLine.setup === "passive") {
+        uLine.setup = "active";
+    }
+}
+
+/**
+ * This method transforms a Plan B SDP to an equivalent Unified Plan SDP. A
+ * PeerConnection wrapper transforms the SDP to Unified Plan before passing it
+ * to FF.
+ *
+ * @param desc
+ * @returns {*}
+ */
+Interop.prototype.toUnifiedPlan = function(desc) {
+    var self = this;
+    //#region Preliminary input validation.
+
+    if (typeof desc !== 'object' || desc === null ||
+        typeof desc.sdp !== 'string') {
+        console.warn('An empty description was passed as an argument.');
+        return desc;
+    }
+
+    var session = transform.parse(desc.sdp);
+
+    // If the SDP contains no media, there's nothing to transform.
+    if (typeof session.media === 'undefined' ||
+        !Array.isArray(session.media) || session.media.length === 0) {
+        console.warn('The description has no media.');
+        return desc;
+    }
+
+    // Try some heuristics to "make sure" this is a Plan B SDP. Plan B SDP has
+    // a video, an audio and a data "channel" at most.
+    if (session.media.length > 3 || !session.media.every(function(m) {
+            return ['video', 'audio', 'data'].indexOf(m.mid) !== -1;
+        })) {
+        console.warn('This description does not look like Plan B.');
+        return desc;
+    }
+
+    // Make sure this Plan B SDP can be converted to a Unified Plan SDP.
+    var mids = [];
+    session.media.forEach(function(m) {
+        mids.push(m.mid);
+    });
+
+    var hasBundle = false;
+    if (typeof session.groups !== 'undefined' &&
+        Array.isArray(session.groups)) {
+        hasBundle = session.groups.every(function(g) {
+            return g.type !== 'BUNDLE' ||
+                arrayEquals.apply(g.mids.sort(), [mids.sort()]);
+        });
+    }
+
+    if (!hasBundle) {
+        var mustBeBundle = false;
+
+        session.media.forEach(function(m) {
+            if (m.direction !== 'inactive') {
+                mustBeBundle = true;
+            }
+        });
+
+        if (mustBeBundle) {
+            throw new Error("Cannot convert to Unified Plan because m-lines that" +
+              " are not bundled were found.");
+        }
+    }
+
+    //#endregion
+
+
+    //#region Convert from Plan B to Unified Plan.
+
+    // Unfortunately, a Plan B offer/answer doesn't have enough information to
+    // rebuild an equivalent Unified Plan offer/answer.
+    //
+    // For example, if this is a local answer (in Unified Plan style) that we
+    // convert to Plan B prior to handing it over to the application (the
+    // PeerConnection wrapper called us, for instance, after a successful
+    // createAnswer), we want to remember the m-line at which we've seen the
+    // (local) SSRC. That's because when the application wants to do call the
+    // SLD method, forcing us to do the inverse transformation (from Plan B to
+    // Unified Plan), we need to know to which m-line to assign the (local)
+    // SSRC. We also need to know all the other m-lines that the original
+    // answer had and include them in the transformed answer as well.
+    //
+    // Another example is if this is a remote offer that we convert to Plan B
+    // prior to giving it to the application, we want to remember the mid at
+    // which we've seen the (remote) SSRC.
+    //
+    // In the iteration that follows, we use the cached Unified Plan (if it
+    // exists) to assign mids to ssrcs.
+
+    var type;
+    if (desc.type === 'answer') {
+        type = 'offer';
+    } else if (desc.type === 'offer') {
+        type = 'answer';
+    } else {
+        throw new Error("Type '" + desc.type + "' not supported.");
+    }
+
+    var cached;
+    if (typeof this.cache[type] !== 'undefined') {
+        cached = transform.parse(this.cache[type]);
+    }
+
+    var recvonlySsrcs = {
+        audio: {},
+        video: {}
+    };
+
+    // A helper map that sends mids to m-line objects. We use it later to
+    // rebuild the Unified Plan style session.media array.
+    var mid2ul = {};
+    var bIdx = 0;
+    var uIdx = 0;
+
+    var sources2ul = {};
+
+    var candidates;
+    var iceUfrag;
+    var icePwd;
+    var fingerprint;
+    var payloads = {};
+    var rtcpFb = {};
+    var rtp = {};
+
+    session.media.forEach(function(bLine) {
+        if ((typeof bLine.rtcpMux !== 'string' ||
+            bLine.rtcpMux !== 'rtcp-mux') &&
+            bLine.direction !== 'inactive') {
+            throw new Error("Cannot convert to Unified Plan because m-lines " +
+                "without the rtcp-mux attribute were found.");
+        }
+
+        if (bLine.type === 'application') {
+            mid2ul[bLine.mid] = bLine;
+            return;
+        }
+
+        // With rtcp-mux and bundle all the channels should have the same ICE
+        // stuff.
+        var sources = bLine.sources;
+        var ssrcGroups = bLine.ssrcGroups;
+        var port = bLine.port;
+
+        /* Chrome adds different candidates even using bundle, so we concat the candidates list */
+        if (typeof bLine.candidates != 'undefined') {
+            if (typeof candidates != 'undefined') {
+                candidates = candidates.concat(bLine.candidates);
+            } else {
+                candidates = bLine.candidates;
+            }
+        }
+
+        if ((typeof iceUfrag != 'undefined') && (typeof bLine.iceUfrag != 'undefined') && (iceUfrag != bLine.iceUfrag)) {
+            throw new Error("Only BUNDLE supported, iceUfrag must be the same for all m-lines.\n" +
+                            "\tLast iceUfrag: " + iceUfrag + "\n" +
+                            "\tNew iceUfrag: " + bLine.iceUfrag
+            );
+        }
+
+        if (typeof bLine.iceUfrag != 'undefined') {
+                iceUfrag = bLine.iceUfrag;
+        }
+
+        if ((typeof icePwd != 'undefined') && (typeof bLine.icePwd != 'undefined') && (icePwd != bLine.icePwd)) {
+            throw new Error("Only BUNDLE supported, icePwd must be the same for all m-lines.\n" +
+                            "\tLast icePwd: " + icePwd + "\n" +
+                            "\tNew icePwd: " + bLine.icePwd
+            );
+        }
+
+        if (typeof bLine.icePwd != 'undefined') {
+                icePwd = bLine.icePwd;
+        }
+
+        if ((typeof fingerprint != 'undefined') && (typeof bLine.fingerprint != 'undefined') &&
+            (fingerprint.type != bLine.fingerprint.type || fingerprint.hash != bLine.fingerprint.hash)) {
+            throw new Error("Only BUNDLE supported, fingerprint must be the same for all m-lines.\n" +
+                            "\tLast fingerprint: " + JSON.stringify(fingerprint) + "\n" +
+                            "\tNew fingerprint: " + JSON.stringify(bLine.fingerprint)
+            );
+        }
+
+        if (typeof bLine.fingerprint != 'undefined') {
+                fingerprint = bLine.fingerprint;
+        }
+
+        payloads[bLine.type] = bLine.payloads;
+        rtcpFb[bLine.type] = bLine.rtcpFb;
+        rtp[bLine.type] = bLine.rtp;
+
+        // inverted ssrc group map
+        var ssrc2group = {};
+        if (typeof ssrcGroups !== 'undefined' && Array.isArray(ssrcGroups)) {
+            ssrcGroups.forEach(function (ssrcGroup) {
+                // XXX This might brake if an SSRC is in more than one group
+                // for some reason.
+                if (typeof ssrcGroup.ssrcs !== 'undefined' &&
+                    Array.isArray(ssrcGroup.ssrcs)) {
+                    ssrcGroup.ssrcs.forEach(function (ssrc) {
+                        if (typeof ssrc2group[ssrc] === 'undefined') {
+                            ssrc2group[ssrc] = [];
+                        }
+
+                        ssrc2group[ssrc].push(ssrcGroup);
+                    });
+                }
+            });
+        }
+
+        // ssrc to m-line index.
+        var ssrc2ml = {};
+
+        if (typeof sources === 'object') {
+
+            // We'll use the "bLine" object as a prototype for each new "mLine"
+            // that we create, but first we need to clean it up a bit.
+            delete bLine.sources;
+            delete bLine.ssrcGroups;
+            delete bLine.candidates;
+            delete bLine.iceUfrag;
+            delete bLine.icePwd;
+            delete bLine.fingerprint;
+            delete bLine.port;
+            delete bLine.mid;
+
+            // Explode the Plan B channel sources with one m-line per source.
+            Object.keys(sources).forEach(function(ssrc) {
+
+                // The (unified) m-line for this SSRC. We either create it from
+                // scratch or, if it's a grouped SSRC, we re-use a related
+                // mline. In other words, if the source is grouped with another
+                // source, put the two together in the same m-line.
+                var uLine;
+
+                // We assume here that we are the answerer in the O/A, so any
+                // offers which we translate come from the remote side, while
+                // answers are local. So the check below is to make that we
+                // handle receive-only SSRCs in a special way only if they come
+                // from the remote side.
+                if (desc.type==='offer') {
+                    // We want to detect SSRCs which are used by a remote peer
+                    // in an m-line with direction=recvonly (i.e. they are
+                    // being used for RTCP only).
+                    // This information would have gotten lost if the remote
+                    // peer used Unified Plan and their local description was
+                    // translated to Plan B. So we use the lack of an MSID
+                    // attribute to deduce a "receive only" SSRC.
+                    if (!sources[ssrc].msid) {
+                        recvonlySsrcs[bLine.type][ssrc] = sources[ssrc];
+                        // Receive-only SSRCs must not create new m-lines. We
+                        // will assign them to an existing m-line later.
+                        return;
+                    }
+                }
+
+                if (typeof ssrc2group[ssrc] !== 'undefined' &&
+                    Array.isArray(ssrc2group[ssrc])) {
+                    ssrc2group[ssrc].some(function (ssrcGroup) {
+                        // ssrcGroup.ssrcs *is* an Array, no need to check
+                        // again here.
+                        return ssrcGroup.ssrcs.some(function (related) {
+                            if (typeof ssrc2ml[related] === 'object') {
+                                uLine = ssrc2ml[related];
+                                return true;
+                            }
+                        });
+                    });
+                }
+
+                if (typeof uLine === 'object') {
+                    // the m-line already exists. Just add the source.
+                    uLine.sources[ssrc] = sources[ssrc];
+                    delete sources[ssrc].msid;
+                } else {
+                    // Use the "bLine" as a prototype for the "uLine".
+                    uLine = Object.create(bLine);
+                    ssrc2ml[ssrc] = uLine;
+
+                    if (typeof sources[ssrc].msid !== 'undefined') {
+                        // Assign the msid of the source to the m-line. Note
+                        // that it is not guaranteed that the source will have
+                        // msid. In particular "recvonly" sources don't have an
+                        // msid. Note that "recvonly" is a term only defined
+                        // for m-lines.
+                        uLine.msid = sources[ssrc].msid;
+                        delete sources[ssrc].msid;
+                    }
+
+                    // We assign one SSRC per media line.
+                    uLine.sources = {};
+                    uLine.sources[ssrc] = sources[ssrc];
+                    uLine.ssrcGroups = ssrc2group[ssrc];
+
+                    // Use the cached Unified Plan SDP (if it exists) to assign
+                    // SSRCs to mids.
+                    if (typeof cached !== 'undefined' &&
+                        typeof cached.media !== 'undefined' &&
+                        Array.isArray(cached.media)) {
+
+                        cached.media.forEach(function (m) {
+                            if (typeof m.sources === 'object') {
+                                Object.keys(m.sources).forEach(function (s) {
+                                    if (s === ssrc) {
+                                        uLine.mid = m.mid;
+                                    }
+                                });
+                            }
+                        });
+                    }
+
+                    if (typeof uLine.mid === 'undefined') {
+
+                        // If this is an SSRC that we see for the first time
+                        // assign it a new mid. This is typically the case when
+                        // this method is called to transform a remote
+                        // description for the first time or when there is a
+                        // new SSRC in the remote description because a new
+                        // peer has joined the conference. Local SSRCs should
+                        // have already been added to the map in the toPlanB
+                        // method.
+                        //
+                        // Because FF generates answers in Unified Plan style,
+                        // we MUST already have a cached answer with all the
+                        // local SSRCs mapped to some m-line/mid.
+
+                        uLine.mid = [bLine.type, '-', ssrc].join('');
+                    }
+
+                    // Include the candidates in the 1st media line.
+                    uLine.candidates = candidates;
+                    uLine.iceUfrag = iceUfrag;
+                    uLine.icePwd = icePwd;
+                    uLine.fingerprint = fingerprint;
+                    uLine.port = port;
+
+                    mid2ul[uLine.mid] = uLine;
+                    sources2ul[uIdx] = uLine.sources;
+
+                    self.cache.mlU2BMap[uIdx] = bIdx;
+                    if (typeof self.cache.mlB2UMap[bIdx] === 'undefined') {
+                      self.cache.mlB2UMap[bIdx] = uIdx;
+                    }
+                    uIdx++;
+                }
+            });
+        } else {
+          var uLine = bLine;
+
+          uLine.candidates = candidates;
+          uLine.iceUfrag = iceUfrag;
+          uLine.icePwd = icePwd;
+          uLine.fingerprint = fingerprint;
+          uLine.port = port;
+
+          mid2ul[uLine.mid] = uLine;
+
+          self.cache.mlU2BMap[uIdx] = bIdx;
+          if (typeof self.cache.mlB2UMap[bIdx] === 'undefined') {
+            self.cache.mlB2UMap[bIdx] = uIdx;
+          }
+        }
+
+        bIdx++;
+    });
+
+    // Rebuild the media array in the right order and add the missing mLines
+    // (missing from the Plan B SDP).
+    session.media = [];
+    mids = []; // reuse
+
+    if (desc.type === 'answer') {
+
+        // The media lines in the answer must match the media lines in the
+        // offer. The order is important too. Here we assume that Firefox is
+        // the answerer, so we merely have to use the reconstructed (unified)
+        // answer to update the cached (unified) answer accordingly.
+        //
+        // In the general case, one would have to use the cached (unified)
+        // offer to find the m-lines that are missing from the reconstructed
+        // answer, potentially grabbing them from the cached (unified) answer.
+        // One has to be careful with this approach because inactive m-lines do
+        // not always have an mid, making it tricky (impossible?) to find where
+        // exactly and which m-lines are missing from the reconstructed answer.
+
+        for (var i = 0; i < cached.media.length; i++) {
+            var uLine = cached.media[i];
+
+            delete uLine.msid;
+            delete uLine.sources;
+            delete uLine.ssrcGroups;
+
+            if (typeof sources2ul[i] === 'undefined') {
+              if (!uLine.direction
+                  || uLine.direction === 'sendrecv')
+                  uLine.direction = 'recvonly';
+              else if (uLine.direction === 'sendonly')
+                  uLine.direction = 'inactive';
+            } else {
+              if (!uLine.direction
+                  || uLine.direction === 'sendrecv')
+                  uLine.direction = 'sendrecv';
+              else if (uLine.direction === 'recvonly')
+                  uLine.direction = 'sendonly';
+            }
+
+            uLine.sources = sources2ul[i];
+            uLine.candidates = candidates;
+            uLine.iceUfrag = iceUfrag;
+            uLine.icePwd = icePwd;
+            uLine.fingerprint = fingerprint;
+
+            uLine.rtp = rtp[uLine.type];
+            uLine.payloads = payloads[uLine.type];
+            uLine.rtcpFb = rtcpFb[uLine.type];
+
+            session.media.push(uLine);
+
+            if (typeof uLine.mid === 'string') {
+                // inactive lines don't/may not have an mid.
+                mids.push(uLine.mid);
+            }
+        }
+    } else {
+
+        // SDP offer/answer (and the JSEP spec) forbids removing an m-section
+        // under any circumstances. If we are no longer interested in sending a
+        // track, we just remove the msid and ssrc attributes and set it to
+        // either a=recvonly (as the reofferer, we must use recvonly if the
+        // other side was previously sending on the m-section, but we can also
+        // leave the possibility open if it wasn't previously in use), or
+        // a=inactive.
+
+        if (typeof cached !== 'undefined' &&
+            typeof cached.media !== 'undefined' &&
+            Array.isArray(cached.media)) {
+            cached.media.forEach(function(uLine) {
+                mids.push(uLine.mid);
+                if (typeof mid2ul[uLine.mid] !== 'undefined') {
+                    session.media.push(mid2ul[uLine.mid]);
+                } else {
+                    delete uLine.msid;
+                    delete uLine.sources;
+                    delete uLine.ssrcGroups;
+
+                    if (!uLine.direction
+                        || uLine.direction === 'sendrecv') {
+                        uLine.direction = 'sendonly';
+                    }
+                    if (!uLine.direction
+                        || uLine.direction === 'recvonly') {
+                        uLine.direction = 'inactive';
+                    }
+
+                    addSetupAttr (uLine);
+                    session.media.push(uLine);
+                }
+            });
+        }
+
+        // Add all the remaining (new) m-lines of the transformed SDP.
+        Object.keys(mid2ul).forEach(function(mid) {
+            if (mids.indexOf(mid) === -1) {
+                mids.push(mid);
+                if (mid2ul[mid].direction === 'recvonly') {
+                    // This is a remote recvonly channel. Add its SSRC to the
+                    // appropriate sendrecv or sendonly channel.
+                    // TODO(gp) what if we don't have sendrecv/sendonly
+                    // channel?
+
+                    var done = false;
+
+                    session.media.some(function (uLine) {
+                        if ((uLine.direction === 'sendrecv' ||
+                            uLine.direction === 'sendonly') &&
+                            uLine.type === mid2ul[mid].type) {
+                            // mid2ul[mid] shouldn't have any ssrc-groups
+                            Object.keys(mid2ul[mid].sources).forEach(
+                                function (ssrc) {
+                                uLine.sources[ssrc] =
+                                    mid2ul[mid].sources[ssrc];
+                            });
+
+                            done = true;
+                            return true;
+                        }
+                    });
+
+                    if (!done) {
+                        session.media.push(mid2ul[mid]);
+                    }
+                } else {
+                    session.media.push(mid2ul[mid]);
+                }
+            }
+        });
+    }
+
+    // After we have constructed the Plan Unified m-lines we can figure out
+    // where (in which m-line) to place the 'recvonly SSRCs'.
+    // Note: we assume here that we are the answerer in the O/A, so any offers
+    // which we translate come from the remote side, while answers are local
+    // (and so our last local description is cached as an 'answer').
+    ["audio", "video"].forEach(function (type) {
+        if (!session || !session.media || !Array.isArray(session.media))
+            return;
+
+        var idx = null;
+        if (Object.keys(recvonlySsrcs[type]).length > 0) {
+            idx = self.getFirstSendingIndexFromAnswer(type);
+            if (idx === null){
+                // If this is the first offer we receive, we don't have a
+                // cached answer. Assume that we will be sending media using
+                // the first m-line for each media type.
+
+                for (var i = 0; i < session.media.length; i++) {
+                    if (session.media[i].type === type) {
+                        idx = i;
+                        break;
+                    }
+                }
+            }
+        }
+
+        if (idx && session.media.length > idx) {
+            var mLine = session.media[idx];
+            Object.keys(recvonlySsrcs[type]).forEach(function(ssrc) {
+                if (mLine.sources && mLine.sources[ssrc]) {
+                    console.warn("Replacing an existing SSRC.");
+                }
+                if (!mLine.sources) {
+                    mLine.sources = {};
+                }
+
+                mLine.sources[ssrc] = recvonlySsrcs[type][ssrc];
+            });
+        }
+    });
+
+    if (typeof session.groups !== 'undefined') {
+      // We regenerate the BUNDLE group (since we regenerated the mids)
+      session.groups.some(function(group) {
+	  if (group.type === 'BUNDLE') {
+	      group.mids = mids.join(' ');
+	      return true;
+	  }
+      });
+    }
+
+    // msid semantic
+    session.msidSemantic = {
+        semantic: 'WMS',
+        token: '*'
+    };
+
+    var resStr = transform.write(session);
+
+    // Cache the transformed SDP (Unified Plan) for later re-use in this
+    // function.
+    this.cache[desc.type] = resStr;
+
+    return new RTCSessionDescription({
+        type: desc.type,
+        sdp: resStr
+    });
+
+    //#endregion
+};
+
+},{"./array-equals":17,"./transform":20}],20:[function(require,module,exports){
+/* Copyright @ 2015 Atlassian Pty Ltd
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+var transform = require('sdp-transform');
+
+exports.write = function(session, opts) {
+
+  if (typeof session !== 'undefined' &&
+      typeof session.media !== 'undefined' &&
+      Array.isArray(session.media)) {
+
+    session.media.forEach(function (mLine) {
+      // expand sources to ssrcs
+      if (typeof mLine.sources !== 'undefined' &&
+        Object.keys(mLine.sources).length !== 0) {
+          mLine.ssrcs = [];
+          Object.keys(mLine.sources).forEach(function (ssrc) {
+            var source = mLine.sources[ssrc];
+            Object.keys(source).forEach(function (attribute) {
+              mLine.ssrcs.push({
+                id: ssrc,
+                attribute: attribute,
+                value: source[attribute]
+              });
+            });
+          });
+          delete mLine.sources;
+        }
+
+      // join ssrcs in ssrc groups
+      if (typeof mLine.ssrcGroups !== 'undefined' &&
+        Array.isArray(mLine.ssrcGroups)) {
+          mLine.ssrcGroups.forEach(function (ssrcGroup) {
+            if (typeof ssrcGroup.ssrcs !== 'undefined' &&
+                Array.isArray(ssrcGroup.ssrcs)) {
+              ssrcGroup.ssrcs = ssrcGroup.ssrcs.join(' ');
+            }
+          });
+        }
+    });
+  }
+
+  // join group mids
+  if (typeof session !== 'undefined' &&
+      typeof session.groups !== 'undefined' && Array.isArray(session.groups)) {
+
+    session.groups.forEach(function (g) {
+      if (typeof g.mids !== 'undefined' && Array.isArray(g.mids)) {
+        g.mids = g.mids.join(' ');
+      }
+    });
+  }
+
+  return transform.write(session, opts);
+};
+
+exports.parse = function(sdp) {
+  var session = transform.parse(sdp);
+
+  if (typeof session !== 'undefined' && typeof session.media !== 'undefined' &&
+      Array.isArray(session.media)) {
+
+    session.media.forEach(function (mLine) {
+      // group sources attributes by ssrc
+      if (typeof mLine.ssrcs !== 'undefined' && Array.isArray(mLine.ssrcs)) {
+        mLine.sources = {};
+        mLine.ssrcs.forEach(function (ssrc) {
+          if (!mLine.sources[ssrc.id])
+          mLine.sources[ssrc.id] = {};
+        mLine.sources[ssrc.id][ssrc.attribute] = ssrc.value;
+        });
+
+        delete mLine.ssrcs;
+      }
+
+      // split ssrcs in ssrc groups
+      if (typeof mLine.ssrcGroups !== 'undefined' &&
+        Array.isArray(mLine.ssrcGroups)) {
+          mLine.ssrcGroups.forEach(function (ssrcGroup) {
+            if (typeof ssrcGroup.ssrcs === 'string') {
+              ssrcGroup.ssrcs = ssrcGroup.ssrcs.split(' ');
+            }
+          });
+        }
+    });
+  }
+  // split group mids
+  if (typeof session !== 'undefined' &&
+      typeof session.groups !== 'undefined' && Array.isArray(session.groups)) {
+
+    session.groups.forEach(function (g) {
+      if (typeof g.mids === 'string') {
+        g.mids = g.mids.split(' ');
+      }
+    });
+  }
+
+  return session;
+};
+
+
+},{"sdp-transform":14}],21:[function(require,module,exports){
+/**
+ * UAParser.js v0.7.17
+ * Lightweight JavaScript-based User-Agent string parser
+ * https://github.com/faisalman/ua-parser-js
+ *
+ * Copyright © 2012-2016 Faisal Salman <fyzlman@gmail.com>
+ * Dual licensed under GPLv2 & MIT
+ */
+
+(function (window, undefined) {
+
+    'use strict';
+
+    //////////////
+    // Constants
+    /////////////
+
+
+    var LIBVERSION  = '0.7.17',
+        EMPTY       = '',
+        UNKNOWN     = '?',
+        FUNC_TYPE   = 'function',
+        UNDEF_TYPE  = 'undefined',
+        OBJ_TYPE    = 'object',
+        STR_TYPE    = 'string',
+        MAJOR       = 'major', // deprecated
+        MODEL       = 'model',
+        NAME        = 'name',
+        TYPE        = 'type',
+        VENDOR      = 'vendor',
+        VERSION     = 'version',
+        ARCHITECTURE= 'architecture',
+        CONSOLE     = 'console',
+        MOBILE      = 'mobile',
+        TABLET      = 'tablet',
+        SMARTTV     = 'smarttv',
+        WEARABLE    = 'wearable',
+        EMBEDDED    = 'embedded';
+
+
+    ///////////
+    // Helper
+    //////////
+
+
+    var util = {
+        extend : function (regexes, extensions) {
+            var margedRegexes = {};
+            for (var i in regexes) {
+                if (extensions[i] && extensions[i].length % 2 === 0) {
+                    margedRegexes[i] = extensions[i].concat(regexes[i]);
+                } else {
+                    margedRegexes[i] = regexes[i];
+                }
+            }
+            return margedRegexes;
+        },
+        has : function (str1, str2) {
+          if (typeof str1 === "string") {
+            return str2.toLowerCase().indexOf(str1.toLowerCase()) !== -1;
+          } else {
+            return false;
+          }
+        },
+        lowerize : function (str) {
+            return str.toLowerCase();
+        },
+        major : function (version) {
+            return typeof(version) === STR_TYPE ? version.replace(/[^\d\.]/g,'').split(".")[0] : undefined;
+        },
+        trim : function (str) {
+          return str.replace(/^[\s\uFEFF\xA0]+|[\s\uFEFF\xA0]+$/g, '');
+        }
+    };
+
+
+    ///////////////
+    // Map helper
+    //////////////
+
+
+    var mapper = {
+
+        rgx : function (ua, arrays) {
+
+            //var result = {},
+            var i = 0, j, k, p, q, matches, match;//, args = arguments;
+
+            /*// construct object barebones
+            for (p = 0; p < args[1].length; p++) {
+                q = args[1][p];
+                result[typeof q === OBJ_TYPE ? q[0] : q] = undefined;
+            }*/
+
+            // loop through all regexes maps
+            while (i < arrays.length && !matches) {
+
+                var regex = arrays[i],       // even sequence (0,2,4,..)
+                    props = arrays[i + 1];   // odd sequence (1,3,5,..)
+                j = k = 0;
+
+                // try matching uastring with regexes
+                while (j < regex.length && !matches) {
+
+                    matches = regex[j++].exec(ua);
+
+                    if (!!matches) {
+                        for (p = 0; p < props.length; p++) {
+                            match = matches[++k];
+                            q = props[p];
+                            // check if given property is actually array
+                            if (typeof q === OBJ_TYPE && q.length > 0) {
+                                if (q.length == 2) {
+                                    if (typeof q[1] == FUNC_TYPE) {
+                                        // assign modified match
+                                        this[q[0]] = q[1].call(this, match);
+                                    } else {
+                                        // assign given value, ignore regex match
+                                        this[q[0]] = q[1];
+                                    }
+                                } else if (q.length == 3) {
+                                    // check whether function or regex
+                                    if (typeof q[1] === FUNC_TYPE && !(q[1].exec && q[1].test)) {
+                                        // call function (usually string mapper)
+                                        this[q[0]] = match ? q[1].call(this, match, q[2]) : undefined;
+                                    } else {
+                                        // sanitize match using given regex
+                                        this[q[0]] = match ? match.replace(q[1], q[2]) : undefined;
+                                    }
+                                } else if (q.length == 4) {
+                                        this[q[0]] = match ? q[3].call(this, match.replace(q[1], q[2])) : undefined;
+                                }
+                            } else {
+                                this[q] = match ? match : undefined;
+                            }
+                        }
+                    }
+                }
+                i += 2;
+            }
+            // console.log(this);
+            //return this;
+        },
+
+        str : function (str, map) {
+
+            for (var i in map) {
+                // check if array
+                if (typeof map[i] === OBJ_TYPE && map[i].length > 0) {
+                    for (var j = 0; j < map[i].length; j++) {
+                        if (util.has(map[i][j], str)) {
+                            return (i === UNKNOWN) ? undefined : i;
+                        }
+                    }
+                } else if (util.has(map[i], str)) {
+                    return (i === UNKNOWN) ? undefined : i;
+                }
+            }
+            return str;
+        }
+    };
+
+
+    ///////////////
+    // String map
+    //////////////
+
+
+    var maps = {
+
+        browser : {
+            oldsafari : {
+                version : {
+                    '1.0'   : '/8',
+                    '1.2'   : '/1',
+                    '1.3'   : '/3',
+                    '2.0'   : '/412',
+                    '2.0.2' : '/416',
+                    '2.0.3' : '/417',
+                    '2.0.4' : '/419',
+                    '?'     : '/'
+                }
+            }
+        },
+
+        device : {
+            amazon : {
+                model : {
+                    'Fire Phone' : ['SD', 'KF']
+                }
+            },
+            sprint : {
+                model : {
+                    'Evo Shift 4G' : '7373KT'
+                },
+                vendor : {
+                    'HTC'       : 'APA',
+                    'Sprint'    : 'Sprint'
+                }
+            }
+        },
+
+        os : {
+            windows : {
+                version : {
+                    'ME'        : '4.90',
+                    'NT 3.11'   : 'NT3.51',
+                    'NT 4.0'    : 'NT4.0',
+                    '2000'      : 'NT 5.0',
+                    'XP'        : ['NT 5.1', 'NT 5.2'],
+                    'Vista'     : 'NT 6.0',
+                    '7'         : 'NT 6.1',
+                    '8'         : 'NT 6.2',
+                    '8.1'       : 'NT 6.3',
+                    '10'        : ['NT 6.4', 'NT 10.0'],
+                    'RT'        : 'ARM'
+                }
+            }
+        }
+    };
+
+
+    //////////////
+    // Regex map
+    /////////////
+
+
+    var regexes = {
+
+        browser : [[
+
+            // Presto based
+            /(opera\smini)\/([\w\.-]+)/i,                                       // Opera Mini
+            /(opera\s[mobiletab]+).+version\/([\w\.-]+)/i,                      // Opera Mobi/Tablet
+            /(opera).+version\/([\w\.]+)/i,                                     // Opera > 9.80
+            /(opera)[\/\s]+([\w\.]+)/i                                          // Opera < 9.80
+            ], [NAME, VERSION], [
+
+            /(opios)[\/\s]+([\w\.]+)/i                                          // Opera mini on iphone >= 8.0
+            ], [[NAME, 'Opera Mini'], VERSION], [
+
+            /\s(opr)\/([\w\.]+)/i                                               // Opera Webkit
+            ], [[NAME, 'Opera'], VERSION], [
+
+            // Mixed
+            /(kindle)\/([\w\.]+)/i,                                             // Kindle
+            /(lunascape|maxthon|netfront|jasmine|blazer)[\/\s]?([\w\.]+)*/i,
+                                                                                // Lunascape/Maxthon/Netfront/Jasmine/Blazer
+
+            // Trident based
+            /(avant\s|iemobile|slim|baidu)(?:browser)?[\/\s]?([\w\.]*)/i,
+                                                                                // Avant/IEMobile/SlimBrowser/Baidu
+            /(?:ms|\()(ie)\s([\w\.]+)/i,                                        // Internet Explorer
+
+            // Webkit/KHTML based
+            /(rekonq)\/([\w\.]+)*/i,                                            // Rekonq
+            /(chromium|flock|rockmelt|midori|epiphany|silk|skyfire|ovibrowser|bolt|iron|vivaldi|iridium|phantomjs|bowser)\/([\w\.-]+)/i
+                                                                                // Chromium/Flock/RockMelt/Midori/Epiphany/Silk/Skyfire/Bolt/Iron/Iridium/PhantomJS/Bowser
+            ], [NAME, VERSION], [
+
+            /(trident).+rv[:\s]([\w\.]+).+like\sgecko/i                         // IE11
+            ], [[NAME, 'IE'], VERSION], [
+
+            /(edge)\/((\d+)?[\w\.]+)/i                                          // Microsoft Edge
+            ], [NAME, VERSION], [
+
+            /(yabrowser)\/([\w\.]+)/i                                           // Yandex
+            ], [[NAME, 'Yandex'], VERSION], [
+
+            /(puffin)\/([\w\.]+)/i                                              // Puffin
+            ], [[NAME, 'Puffin'], VERSION], [
+
+            /((?:[\s\/])uc?\s?browser|(?:juc.+)ucweb)[\/\s]?([\w\.]+)/i
+                                                                                // UCBrowser
+            ], [[NAME, 'UCBrowser'], VERSION], [
+
+            /(comodo_dragon)\/([\w\.]+)/i                                       // Comodo Dragon
+            ], [[NAME, /_/g, ' '], VERSION], [
+
+            /(micromessenger)\/([\w\.]+)/i                                      // WeChat
+            ], [[NAME, 'WeChat'], VERSION], [
+
+            /(QQ)\/([\d\.]+)/i                                                  // QQ, aka ShouQ
+            ], [NAME, VERSION], [
+
+            /m?(qqbrowser)[\/\s]?([\w\.]+)/i                                    // QQBrowser
+            ], [NAME, VERSION], [
+
+            /xiaomi\/miuibrowser\/([\w\.]+)/i                                   // MIUI Browser
+            ], [VERSION, [NAME, 'MIUI Browser']], [
+
+            /;fbav\/([\w\.]+);/i                                                // Facebook App for iOS & Android
+            ], [VERSION, [NAME, 'Facebook']], [
+
+            /headlesschrome(?:\/([\w\.]+)|\s)/i                                 // Chrome Headless
+            ], [VERSION, [NAME, 'Chrome Headless']], [
+
+            /\swv\).+(chrome)\/([\w\.]+)/i                                      // Chrome WebView
+            ], [[NAME, /(.+)/, '$1 WebView'], VERSION], [
+
+            /((?:oculus|samsung)browser)\/([\w\.]+)/i
+            ], [[NAME, /(.+(?:g|us))(.+)/, '$1 $2'], VERSION], [                // Oculus / Samsung Browser
+
+            /android.+version\/([\w\.]+)\s+(?:mobile\s?safari|safari)*/i        // Android Browser
+            ], [VERSION, [NAME, 'Android Browser']], [
+
+            /(chrome|omniweb|arora|[tizenoka]{5}\s?browser)\/v?([\w\.]+)/i
+                                                                                // Chrome/OmniWeb/Arora/Tizen/Nokia
+            ], [NAME, VERSION], [
+
+            /(dolfin)\/([\w\.]+)/i                                              // Dolphin
+            ], [[NAME, 'Dolphin'], VERSION], [
+
+            /((?:android.+)crmo|crios)\/([\w\.]+)/i                             // Chrome for Android/iOS
+            ], [[NAME, 'Chrome'], VERSION], [
+
+            /(coast)\/([\w\.]+)/i                                               // Opera Coast
+            ], [[NAME, 'Opera Coast'], VERSION], [
+
+            /fxios\/([\w\.-]+)/i                                                // Firefox for iOS
+            ], [VERSION, [NAME, 'Firefox']], [
+
+            /version\/([\w\.]+).+?mobile\/\w+\s(safari)/i                       // Mobile Safari
+            ], [VERSION, [NAME, 'Mobile Safari']], [
+
+            /version\/([\w\.]+).+?(mobile\s?safari|safari)/i                    // Safari & Safari Mobile
+            ], [VERSION, NAME], [
+
+            /webkit.+?(gsa)\/([\w\.]+).+?(mobile\s?safari|safari)(\/[\w\.]+)/i  // Google Search Appliance on iOS
+            ], [[NAME, 'GSA'], VERSION], [
+
+            /webkit.+?(mobile\s?safari|safari)(\/[\w\.]+)/i                     // Safari < 3.0
+            ], [NAME, [VERSION, mapper.str, maps.browser.oldsafari.version]], [
+
+            /(konqueror)\/([\w\.]+)/i,                                          // Konqueror
+            /(webkit|khtml)\/([\w\.]+)/i
+            ], [NAME, VERSION], [
+
+            // Gecko based
+            /(navigator|netscape)\/([\w\.-]+)/i                                 // Netscape
+            ], [[NAME, 'Netscape'], VERSION], [
+            /(swiftfox)/i,                                                      // Swiftfox
+            /(icedragon|iceweasel|camino|chimera|fennec|maemo\sbrowser|minimo|conkeror)[\/\s]?([\w\.\+]+)/i,
+                                                                                // IceDragon/Iceweasel/Camino/Chimera/Fennec/Maemo/Minimo/Conkeror
+            /(firefox|seamonkey|k-meleon|icecat|iceape|firebird|phoenix)\/([\w\.-]+)/i,
+                                                                                // Firefox/SeaMonkey/K-Meleon/IceCat/IceApe/Firebird/Phoenix
+            /(mozilla)\/([\w\.]+).+rv\:.+gecko\/\d+/i,                          // Mozilla
+
+            // Other
+            /(polaris|lynx|dillo|icab|doris|amaya|w3m|netsurf|sleipnir)[\/\s]?([\w\.]+)/i,
+                                                                                // Polaris/Lynx/Dillo/iCab/Doris/Amaya/w3m/NetSurf/Sleipnir
+            /(links)\s\(([\w\.]+)/i,                                            // Links
+            /(gobrowser)\/?([\w\.]+)*/i,                                        // GoBrowser
+            /(ice\s?browser)\/v?([\w\._]+)/i,                                   // ICE Browser
+            /(mosaic)[\/\s]([\w\.]+)/i                                          // Mosaic
+            ], [NAME, VERSION]
+
+            /* /////////////////////
+            // Media players BEGIN
+            ////////////////////////
+
+            , [
+
+            /(apple(?:coremedia|))\/((\d+)[\w\._]+)/i,                          // Generic Apple CoreMedia
+            /(coremedia) v((\d+)[\w\._]+)/i
+            ], [NAME, VERSION], [
+
+            /(aqualung|lyssna|bsplayer)\/((\d+)?[\w\.-]+)/i                     // Aqualung/Lyssna/BSPlayer
+            ], [NAME, VERSION], [
+
+            /(ares|ossproxy)\s((\d+)[\w\.-]+)/i                                 // Ares/OSSProxy
+            ], [NAME, VERSION], [
+
+            /(audacious|audimusicstream|amarok|bass|core|dalvik|gnomemplayer|music on console|nsplayer|psp-internetradioplayer|videos)\/((\d+)[\w\.-]+)/i,
+                                                                                // Audacious/AudiMusicStream/Amarok/BASS/OpenCORE/Dalvik/GnomeMplayer/MoC
+                                                                                // NSPlayer/PSP-InternetRadioPlayer/Videos
+            /(clementine|music player daemon)\s((\d+)[\w\.-]+)/i,               // Clementine/MPD
+            /(lg player|nexplayer)\s((\d+)[\d\.]+)/i,
+            /player\/(nexplayer|lg player)\s((\d+)[\w\.-]+)/i                   // NexPlayer/LG Player
+            ], [NAME, VERSION], [
+            /(nexplayer)\s((\d+)[\w\.-]+)/i                                     // Nexplayer
+            ], [NAME, VERSION], [
+
+            /(flrp)\/((\d+)[\w\.-]+)/i                                          // Flip Player
+            ], [[NAME, 'Flip Player'], VERSION], [
+
+            /(fstream|nativehost|queryseekspider|ia-archiver|facebookexternalhit)/i
+                                                                                // FStream/NativeHost/QuerySeekSpider/IA Archiver/facebookexternalhit
+            ], [NAME], [
+
+            /(gstreamer) souphttpsrc (?:\([^\)]+\)){0,1} libsoup\/((\d+)[\w\.-]+)/i
+                                                                                // Gstreamer
+            ], [NAME, VERSION], [
+
+            /(htc streaming player)\s[\w_]+\s\/\s((\d+)[\d\.]+)/i,              // HTC Streaming Player
+            /(java|python-urllib|python-requests|wget|libcurl)\/((\d+)[\w\.-_]+)/i,
+                                                                                // Java/urllib/requests/wget/cURL
+            /(lavf)((\d+)[\d\.]+)/i                                             // Lavf (FFMPEG)
+            ], [NAME, VERSION], [
+
+            /(htc_one_s)\/((\d+)[\d\.]+)/i                                      // HTC One S
+            ], [[NAME, /_/g, ' '], VERSION], [
+
+            /(mplayer)(?:\s|\/)(?:(?:sherpya-){0,1}svn)(?:-|\s)(r\d+(?:-\d+[\w\.-]+){0,1})/i
+                                                                                // MPlayer SVN
+            ], [NAME, VERSION], [
+
+            /(mplayer)(?:\s|\/|[unkow-]+)((\d+)[\w\.-]+)/i                      // MPlayer
+            ], [NAME, VERSION], [
+
+            /(mplayer)/i,                                                       // MPlayer (no other info)
+            /(yourmuze)/i,                                                      // YourMuze
+            /(media player classic|nero showtime)/i                             // Media Player Classic/Nero ShowTime
+            ], [NAME], [
+
+            /(nero (?:home|scout))\/((\d+)[\w\.-]+)/i                           // Nero Home/Nero Scout
+            ], [NAME, VERSION], [
+
+            /(nokia\d+)\/((\d+)[\w\.-]+)/i                                      // Nokia
+            ], [NAME, VERSION], [
+
+            /\s(songbird)\/((\d+)[\w\.-]+)/i                                    // Songbird/Philips-Songbird
+            ], [NAME, VERSION], [
+
+            /(winamp)3 version ((\d+)[\w\.-]+)/i,                               // Winamp
+            /(winamp)\s((\d+)[\w\.-]+)/i,
+            /(winamp)mpeg\/((\d+)[\w\.-]+)/i
+            ], [NAME, VERSION], [
+
+            /(ocms-bot|tapinradio|tunein radio|unknown|winamp|inlight radio)/i  // OCMS-bot/tap in radio/tunein/unknown/winamp (no other info)
+                                                                                // inlight radio
+            ], [NAME], [
+
+            /(quicktime|rma|radioapp|radioclientapplication|soundtap|totem|stagefright|streamium)\/((\d+)[\w\.-]+)/i
+                                                                                // QuickTime/RealMedia/RadioApp/RadioClientApplication/
+                                                                                // SoundTap/Totem/Stagefright/Streamium
+            ], [NAME, VERSION], [
+
+            /(smp)((\d+)[\d\.]+)/i                                              // SMP
+            ], [NAME, VERSION], [
+
+            /(vlc) media player - version ((\d+)[\w\.]+)/i,                     // VLC Videolan
+            /(vlc)\/((\d+)[\w\.-]+)/i,
+            /(xbmc|gvfs|xine|xmms|irapp)\/((\d+)[\w\.-]+)/i,                    // XBMC/gvfs/Xine/XMMS/irapp
+            /(foobar2000)\/((\d+)[\d\.]+)/i,                                    // Foobar2000
+            /(itunes)\/((\d+)[\d\.]+)/i                                         // iTunes
+            ], [NAME, VERSION], [
+
+            /(wmplayer)\/((\d+)[\w\.-]+)/i,                                     // Windows Media Player
+            /(windows-media-player)\/((\d+)[\w\.-]+)/i
+            ], [[NAME, /-/g, ' '], VERSION], [
+
+            /windows\/((\d+)[\w\.-]+) upnp\/[\d\.]+ dlnadoc\/[\d\.]+ (home media server)/i
+                                                                                // Windows Media Server
+            ], [VERSION, [NAME, 'Windows']], [
+
+            /(com\.riseupradioalarm)\/((\d+)[\d\.]*)/i                          // RiseUP Radio Alarm
+            ], [NAME, VERSION], [
+
+            /(rad.io)\s((\d+)[\d\.]+)/i,                                        // Rad.io
+            /(radio.(?:de|at|fr))\s((\d+)[\d\.]+)/i
+            ], [[NAME, 'rad.io'], VERSION]
+
+            //////////////////////
+            // Media players END
+            ////////////////////*/
+
+        ],
+
+        cpu : [[
+
+            /(?:(amd|x(?:(?:86|64)[_-])?|wow|win)64)[;\)]/i                     // AMD64
+            ], [[ARCHITECTURE, 'amd64']], [
+
+            /(ia32(?=;))/i                                                      // IA32 (quicktime)
+            ], [[ARCHITECTURE, util.lowerize]], [
+
+            /((?:i[346]|x)86)[;\)]/i                                            // IA32
+            ], [[ARCHITECTURE, 'ia32']], [
+
+            // PocketPC mistakenly identified as PowerPC
+            /windows\s(ce|mobile);\sppc;/i
+            ], [[ARCHITECTURE, 'arm']], [
+
+            /((?:ppc|powerpc)(?:64)?)(?:\smac|;|\))/i                           // PowerPC
+            ], [[ARCHITECTURE, /ower/, '', util.lowerize]], [
+
+            /(sun4\w)[;\)]/i                                                    // SPARC
+            ], [[ARCHITECTURE, 'sparc']], [
+
+            /((?:avr32|ia64(?=;))|68k(?=\))|arm(?:64|(?=v\d+;))|(?=atmel\s)avr|(?:irix|mips|sparc)(?:64)?(?=;)|pa-risc)/i
+                                                                                // IA64, 68K, ARM/64, AVR/32, IRIX/64, MIPS/64, SPARC/64, PA-RISC
+            ], [[ARCHITECTURE, util.lowerize]]
+        ],
+
+        device : [[
+
+            /\((ipad|playbook);[\w\s\);-]+(rim|apple)/i                         // iPad/PlayBook
+            ], [MODEL, VENDOR, [TYPE, TABLET]], [
+
+            /applecoremedia\/[\w\.]+ \((ipad)/                                  // iPad
+            ], [MODEL, [VENDOR, 'Apple'], [TYPE, TABLET]], [
+
+            /(apple\s{0,1}tv)/i                                                 // Apple TV
+            ], [[MODEL, 'Apple TV'], [VENDOR, 'Apple']], [
+
+            /(archos)\s(gamepad2?)/i,                                           // Archos
+            /(hp).+(touchpad)/i,                                                // HP TouchPad
+            /(hp).+(tablet)/i,                                                  // HP Tablet
+            /(kindle)\/([\w\.]+)/i,                                             // Kindle
+            /\s(nook)[\w\s]+build\/(\w+)/i,                                     // Nook
+            /(dell)\s(strea[kpr\s\d]*[\dko])/i                                  // Dell Streak
+            ], [VENDOR, MODEL, [TYPE, TABLET]], [
+
+            /(kf[A-z]+)\sbuild\/[\w\.]+.*silk\//i                               // Kindle Fire HD
+            ], [MODEL, [VENDOR, 'Amazon'], [TYPE, TABLET]], [
+            /(sd|kf)[0349hijorstuw]+\sbuild\/[\w\.]+.*silk\//i                  // Fire Phone
+            ], [[MODEL, mapper.str, maps.device.amazon.model], [VENDOR, 'Amazon'], [TYPE, MOBILE]], [
+
+            /\((ip[honed|\s\w*]+);.+(apple)/i                                   // iPod/iPhone
+            ], [MODEL, VENDOR, [TYPE, MOBILE]], [
+            /\((ip[honed|\s\w*]+);/i                                            // iPod/iPhone
+            ], [MODEL, [VENDOR, 'Apple'], [TYPE, MOBILE]], [
+
+            /(blackberry)[\s-]?(\w+)/i,                                         // BlackBerry
+            /(blackberry|benq|palm(?=\-)|sonyericsson|acer|asus|dell|meizu|motorola|polytron)[\s_-]?([\w-]+)*/i,
+                                                                                // BenQ/Palm/Sony-Ericsson/Acer/Asus/Dell/Meizu/Motorola/Polytron
+            /(hp)\s([\w\s]+\w)/i,                                               // HP iPAQ
+            /(asus)-?(\w+)/i                                                    // Asus
+            ], [VENDOR, MODEL, [TYPE, MOBILE]], [
+            /\(bb10;\s(\w+)/i                                                   // BlackBerry 10
+            ], [MODEL, [VENDOR, 'BlackBerry'], [TYPE, MOBILE]], [
+                                                                                // Asus Tablets
+            /android.+(transfo[prime\s]{4,10}\s\w+|eeepc|slider\s\w+|nexus 7|padfone)/i
+            ], [MODEL, [VENDOR, 'Asus'], [TYPE, TABLET]], [
+
+            /(sony)\s(tablet\s[ps])\sbuild\//i,                                  // Sony
+            /(sony)?(?:sgp.+)\sbuild\//i
+            ], [[VENDOR, 'Sony'], [MODEL, 'Xperia Tablet'], [TYPE, TABLET]], [
+            /android.+\s([c-g]\d{4}|so[-l]\w+)\sbuild\//i
+            ], [MODEL, [VENDOR, 'Sony'], [TYPE, MOBILE]], [
+
+            /\s(ouya)\s/i,                                                      // Ouya
+            /(nintendo)\s([wids3u]+)/i                                          // Nintendo
+            ], [VENDOR, MODEL, [TYPE, CONSOLE]], [
+
+            /android.+;\s(shield)\sbuild/i                                      // Nvidia
+            ], [MODEL, [VENDOR, 'Nvidia'], [TYPE, CONSOLE]], [
+
+            /(playstation\s[34portablevi]+)/i                                   // Playstation
+            ], [MODEL, [VENDOR, 'Sony'], [TYPE, CONSOLE]], [
+
+            /(sprint\s(\w+))/i                                                  // Sprint Phones
+            ], [[VENDOR, mapper.str, maps.device.sprint.vendor], [MODEL, mapper.str, maps.device.sprint.model], [TYPE, MOBILE]], [
+
+            /(lenovo)\s?(S(?:5000|6000)+(?:[-][\w+]))/i                         // Lenovo tablets
+            ], [VENDOR, MODEL, [TYPE, TABLET]], [
+
+            /(htc)[;_\s-]+([\w\s]+(?=\))|\w+)*/i,                               // HTC
+            /(zte)-(\w+)*/i,                                                    // ZTE
+            /(alcatel|geeksphone|lenovo|nexian|panasonic|(?=;\s)sony)[_\s-]?([\w-]+)*/i
+                                                                                // Alcatel/GeeksPhone/Lenovo/Nexian/Panasonic/Sony
+            ], [VENDOR, [MODEL, /_/g, ' '], [TYPE, MOBILE]], [
+
+            /(nexus\s9)/i                                                       // HTC Nexus 9
+            ], [MODEL, [VENDOR, 'HTC'], [TYPE, TABLET]], [
+
+            /d\/huawei([\w\s-]+)[;\)]/i,
+            /(nexus\s6p)/i                                                      // Huawei
+            ], [MODEL, [VENDOR, 'Huawei'], [TYPE, MOBILE]], [
+
+            /(microsoft);\s(lumia[\s\w]+)/i                                     // Microsoft Lumia
+            ], [VENDOR, MODEL, [TYPE, MOBILE]], [
+
+            /[\s\(;](xbox(?:\sone)?)[\s\);]/i                                   // Microsoft Xbox
+            ], [MODEL, [VENDOR, 'Microsoft'], [TYPE, CONSOLE]], [
+            /(kin\.[onetw]{3})/i                                                // Microsoft Kin
+            ], [[MODEL, /\./g, ' '], [VENDOR, 'Microsoft'], [TYPE, MOBILE]], [
+
+                                                                                // Motorola
+            /\s(milestone|droid(?:[2-4x]|\s(?:bionic|x2|pro|razr))?(:?\s4g)?)[\w\s]+build\//i,
+            /mot[\s-]?(\w+)*/i,
+            /(XT\d{3,4}) build\//i,
+            /(nexus\s6)/i
+            ], [MODEL, [VENDOR, 'Motorola'], [TYPE, MOBILE]], [
+            /android.+\s(mz60\d|xoom[\s2]{0,2})\sbuild\//i
+            ], [MODEL, [VENDOR, 'Motorola'], [TYPE, TABLET]], [
+
+            /hbbtv\/\d+\.\d+\.\d+\s+\([\w\s]*;\s*(\w[^;]*);([^;]*)/i            // HbbTV devices
+            ], [[VENDOR, util.trim], [MODEL, util.trim], [TYPE, SMARTTV]], [
+
+            /hbbtv.+maple;(\d+)/i
+            ], [[MODEL, /^/, 'SmartTV'], [VENDOR, 'Samsung'], [TYPE, SMARTTV]], [
+
+            /\(dtv[\);].+(aquos)/i                                              // Sharp
+            ], [MODEL, [VENDOR, 'Sharp'], [TYPE, SMARTTV]], [
+
+            /android.+((sch-i[89]0\d|shw-m380s|gt-p\d{4}|gt-n\d+|sgh-t8[56]9|nexus 10))/i,
+            /((SM-T\w+))/i
+            ], [[VENDOR, 'Samsung'], MODEL, [TYPE, TABLET]], [                  // Samsung
+            /smart-tv.+(samsung)/i
+            ], [VENDOR, [TYPE, SMARTTV], MODEL], [
+            /((s[cgp]h-\w+|gt-\w+|galaxy\snexus|sm-\w[\w\d]+))/i,
+            /(sam[sung]*)[\s-]*(\w+-?[\w-]*)*/i,
+            /sec-((sgh\w+))/i
+            ], [[VENDOR, 'Samsung'], MODEL, [TYPE, MOBILE]], [
+
+            /sie-(\w+)*/i                                                       // Siemens
+            ], [MODEL, [VENDOR, 'Siemens'], [TYPE, MOBILE]], [
+
+            /(maemo|nokia).*(n900|lumia\s\d+)/i,                                // Nokia
+            /(nokia)[\s_-]?([\w-]+)*/i
+            ], [[VENDOR, 'Nokia'], MODEL, [TYPE, MOBILE]], [
+
+            /android\s3\.[\s\w;-]{10}(a\d{3})/i                                 // Acer
+            ], [MODEL, [VENDOR, 'Acer'], [TYPE, TABLET]], [
+
+            /android.+([vl]k\-?\d{3})\s+build/i                                 // LG Tablet
+            ], [MODEL, [VENDOR, 'LG'], [TYPE, TABLET]], [
+            /android\s3\.[\s\w;-]{10}(lg?)-([06cv9]{3,4})/i                     // LG Tablet
+            ], [[VENDOR, 'LG'], MODEL, [TYPE, TABLET]], [
+            /(lg) netcast\.tv/i                                                 // LG SmartTV
+            ], [VENDOR, MODEL, [TYPE, SMARTTV]], [
+            /(nexus\s[45])/i,                                                   // LG
+            /lg[e;\s\/-]+(\w+)*/i,
+            /android.+lg(\-?[\d\w]+)\s+build/i
+            ], [MODEL, [VENDOR, 'LG'], [TYPE, MOBILE]], [
+
+            /android.+(ideatab[a-z0-9\-\s]+)/i                                  // Lenovo
+            ], [MODEL, [VENDOR, 'Lenovo'], [TYPE, TABLET]], [
+
+            /linux;.+((jolla));/i                                               // Jolla
+            ], [VENDOR, MODEL, [TYPE, MOBILE]], [
+
+            /((pebble))app\/[\d\.]+\s/i                                         // Pebble
+            ], [VENDOR, MODEL, [TYPE, WEARABLE]], [
+
+            /android.+;\s(oppo)\s?([\w\s]+)\sbuild/i                            // OPPO
+            ], [VENDOR, MODEL, [TYPE, MOBILE]], [
+
+            /crkey/i                                                            // Google Chromecast
+            ], [[MODEL, 'Chromecast'], [VENDOR, 'Google']], [
+
+            /android.+;\s(glass)\s\d/i                                          // Google Glass
+            ], [MODEL, [VENDOR, 'Google'], [TYPE, WEARABLE]], [
+
+            /android.+;\s(pixel c)\s/i                                          // Google Pixel C
+            ], [MODEL, [VENDOR, 'Google'], [TYPE, TABLET]], [
+
+            /android.+;\s(pixel xl|pixel)\s/i                                   // Google Pixel
+            ], [MODEL, [VENDOR, 'Google'], [TYPE, MOBILE]], [
+
+            /android.+(\w+)\s+build\/hm\1/i,                                    // Xiaomi Hongmi 'numeric' models
+            /android.+(hm[\s\-_]*note?[\s_]*(?:\d\w)?)\s+build/i,               // Xiaomi Hongmi
+            /android.+(mi[\s\-_]*(?:one|one[\s_]plus|note lte)?[\s_]*(?:\d\w)?)\s+build/i,    // Xiaomi Mi
+            /android.+(redmi[\s\-_]*(?:note)?(?:[\s_]*[\w\s]+)?)\s+build/i      // Redmi Phones
+            ], [[MODEL, /_/g, ' '], [VENDOR, 'Xiaomi'], [TYPE, MOBILE]], [
+            /android.+(mi[\s\-_]*(?:pad)?(?:[\s_]*[\w\s]+)?)\s+build/i          // Mi Pad tablets
+            ],[[MODEL, /_/g, ' '], [VENDOR, 'Xiaomi'], [TYPE, TABLET]], [
+            /android.+;\s(m[1-5]\snote)\sbuild/i                                // Meizu Tablet
+            ], [MODEL, [VENDOR, 'Meizu'], [TYPE, TABLET]], [
+
+            /android.+a000(1)\s+build/i                                         // OnePlus
+            ], [MODEL, [VENDOR, 'OnePlus'], [TYPE, MOBILE]], [
+
+            /android.+[;\/]\s*(RCT[\d\w]+)\s+build/i                            // RCA Tablets
+            ], [MODEL, [VENDOR, 'RCA'], [TYPE, TABLET]], [
+
+            /android.+[;\/]\s*(Venue[\d\s]*)\s+build/i                          // Dell Venue Tablets
+            ], [MODEL, [VENDOR, 'Dell'], [TYPE, TABLET]], [
+
+            /android.+[;\/]\s*(Q[T|M][\d\w]+)\s+build/i                         // Verizon Tablet
+            ], [MODEL, [VENDOR, 'Verizon'], [TYPE, TABLET]], [
+
+            /android.+[;\/]\s+(Barnes[&\s]+Noble\s+|BN[RT])(V?.*)\s+build/i     // Barnes & Noble Tablet
+            ], [[VENDOR, 'Barnes & Noble'], MODEL, [TYPE, TABLET]], [
+
+            /android.+[;\/]\s+(TM\d{3}.*\b)\s+build/i                           // Barnes & Noble Tablet
+            ], [MODEL, [VENDOR, 'NuVision'], [TYPE, TABLET]], [
+
+            /android.+[;\/]\s*(zte)?.+(k\d{2})\s+build/i                        // ZTE K Series Tablet
+            ], [[VENDOR, 'ZTE'], MODEL, [TYPE, TABLET]], [
+
+            /android.+[;\/]\s*(gen\d{3})\s+build.*49h/i                         // Swiss GEN Mobile
+            ], [MODEL, [VENDOR, 'Swiss'], [TYPE, MOBILE]], [
+
+            /android.+[;\/]\s*(zur\d{3})\s+build/i                              // Swiss ZUR Tablet
+            ], [MODEL, [VENDOR, 'Swiss'], [TYPE, TABLET]], [
+
+            /android.+[;\/]\s*((Zeki)?TB.*\b)\s+build/i                         // Zeki Tablets
+            ], [MODEL, [VENDOR, 'Zeki'], [TYPE, TABLET]], [
+
+            /(android).+[;\/]\s+([YR]\d{2}x?.*)\s+build/i,
+            /android.+[;\/]\s+(Dragon[\-\s]+Touch\s+|DT)(.+)\s+build/i          // Dragon Touch Tablet
+            ], [[VENDOR, 'Dragon Touch'], MODEL, [TYPE, TABLET]], [
+
+            /android.+[;\/]\s*(NS-?.+)\s+build/i                                // Insignia Tablets
+            ], [MODEL, [VENDOR, 'Insignia'], [TYPE, TABLET]], [
+
+            /android.+[;\/]\s*((NX|Next)-?.+)\s+build/i                         // NextBook Tablets
+            ], [MODEL, [VENDOR, 'NextBook'], [TYPE, TABLET]], [
+
+            /android.+[;\/]\s*(Xtreme\_?)?(V(1[045]|2[015]|30|40|60|7[05]|90))\s+build/i
+            ], [[VENDOR, 'Voice'], MODEL, [TYPE, MOBILE]], [                    // Voice Xtreme Phones
+
+            /android.+[;\/]\s*(LVTEL\-?)?(V1[12])\s+build/i                     // LvTel Phones
+            ], [[VENDOR, 'LvTel'], MODEL, [TYPE, MOBILE]], [
+
+            /android.+[;\/]\s*(V(100MD|700NA|7011|917G).*\b)\s+build/i          // Envizen Tablets
+            ], [MODEL, [VENDOR, 'Envizen'], [TYPE, TABLET]], [
+
+            /android.+[;\/]\s*(Le[\s\-]+Pan)[\s\-]+(.*\b)\s+build/i             // Le Pan Tablets
+            ], [VENDOR, MODEL, [TYPE, TABLET]], [
+
+            /android.+[;\/]\s*(Trio[\s\-]*.*)\s+build/i                         // MachSpeed Tablets
+            ], [MODEL, [VENDOR, 'MachSpeed'], [TYPE, TABLET]], [
+
+            /android.+[;\/]\s*(Trinity)[\-\s]*(T\d{3})\s+build/i                // Trinity Tablets
+            ], [VENDOR, MODEL, [TYPE, TABLET]], [
+
+            /android.+[;\/]\s*TU_(1491)\s+build/i                               // Rotor Tablets
+            ], [MODEL, [VENDOR, 'Rotor'], [TYPE, TABLET]], [
+
+            /android.+(KS(.+))\s+build/i                                        // Amazon Kindle Tablets
+            ], [MODEL, [VENDOR, 'Amazon'], [TYPE, TABLET]], [
+
+            /android.+(Gigaset)[\s\-]+(Q.+)\s+build/i                           // Gigaset Tablets
+            ], [VENDOR, MODEL, [TYPE, TABLET]], [
+
+            /\s(tablet|tab)[;\/]/i,                                             // Unidentifiable Tablet
+            /\s(mobile)(?:[;\/]|\ssafari)/i                                     // Unidentifiable Mobile
+            ], [[TYPE, util.lowerize], VENDOR, MODEL], [
+
+            /(android.+)[;\/].+build/i                                          // Generic Android Device
+            ], [MODEL, [VENDOR, 'Generic']]
+
+
+        /*//////////////////////////
+            // TODO: move to string map
+            ////////////////////////////
+
+            /(C6603)/i                                                          // Sony Xperia Z C6603
+            ], [[MODEL, 'Xperia Z C6603'], [VENDOR, 'Sony'], [TYPE, MOBILE]], [
+            /(C6903)/i                                                          // Sony Xperia Z 1
+            ], [[MODEL, 'Xperia Z 1'], [VENDOR, 'Sony'], [TYPE, MOBILE]], [
+
+            /(SM-G900[F|H])/i                                                   // Samsung Galaxy S5
+            ], [[MODEL, 'Galaxy S5'], [VENDOR, 'Samsung'], [TYPE, MOBILE]], [
+            /(SM-G7102)/i                                                       // Samsung Galaxy Grand 2
+            ], [[MODEL, 'Galaxy Grand 2'], [VENDOR, 'Samsung'], [TYPE, MOBILE]], [
+            /(SM-G530H)/i                                                       // Samsung Galaxy Grand Prime
+            ], [[MODEL, 'Galaxy Grand Prime'], [VENDOR, 'Samsung'], [TYPE, MOBILE]], [
+            /(SM-G313HZ)/i                                                      // Samsung Galaxy V
+            ], [[MODEL, 'Galaxy V'], [VENDOR, 'Samsung'], [TYPE, MOBILE]], [
+            /(SM-T805)/i                                                        // Samsung Galaxy Tab S 10.5
+            ], [[MODEL, 'Galaxy Tab S 10.5'], [VENDOR, 'Samsung'], [TYPE, TABLET]], [
+            /(SM-G800F)/i                                                       // Samsung Galaxy S5 Mini
+            ], [[MODEL, 'Galaxy S5 Mini'], [VENDOR, 'Samsung'], [TYPE, MOBILE]], [
+            /(SM-T311)/i                                                        // Samsung Galaxy Tab 3 8.0
+            ], [[MODEL, 'Galaxy Tab 3 8.0'], [VENDOR, 'Samsung'], [TYPE, TABLET]], [
+
+            /(T3C)/i                                                            // Advan Vandroid T3C
+            ], [MODEL, [VENDOR, 'Advan'], [TYPE, TABLET]], [
+            /(ADVAN T1J\+)/i                                                    // Advan Vandroid T1J+
+            ], [[MODEL, 'Vandroid T1J+'], [VENDOR, 'Advan'], [TYPE, TABLET]], [
+            /(ADVAN S4A)/i                                                      // Advan Vandroid S4A
+            ], [[MODEL, 'Vandroid S4A'], [VENDOR, 'Advan'], [TYPE, MOBILE]], [
+
+            /(V972M)/i                                                          // ZTE V972M
+            ], [MODEL, [VENDOR, 'ZTE'], [TYPE, MOBILE]], [
+
+            /(i-mobile)\s(IQ\s[\d\.]+)/i                                        // i-mobile IQ
+            ], [VENDOR, MODEL, [TYPE, MOBILE]], [
+            /(IQ6.3)/i                                                          // i-mobile IQ IQ 6.3
+            ], [[MODEL, 'IQ 6.3'], [VENDOR, 'i-mobile'], [TYPE, MOBILE]], [
+            /(i-mobile)\s(i-style\s[\d\.]+)/i                                   // i-mobile i-STYLE
+            ], [VENDOR, MODEL, [TYPE, MOBILE]], [
+            /(i-STYLE2.1)/i                                                     // i-mobile i-STYLE 2.1
+            ], [[MODEL, 'i-STYLE 2.1'], [VENDOR, 'i-mobile'], [TYPE, MOBILE]], [
+
+            /(mobiistar touch LAI 512)/i                                        // mobiistar touch LAI 512
+            ], [[MODEL, 'Touch LAI 512'], [VENDOR, 'mobiistar'], [TYPE, MOBILE]], [
+
+            /////////////
+            // END TODO
+            ///////////*/
+
+        ],
+
+        engine : [[
+
+            /windows.+\sedge\/([\w\.]+)/i                                       // EdgeHTML
+            ], [VERSION, [NAME, 'EdgeHTML']], [
+
+            /(presto)\/([\w\.]+)/i,                                             // Presto
+            /(webkit|trident|netfront|netsurf|amaya|lynx|w3m)\/([\w\.]+)/i,     // WebKit/Trident/NetFront/NetSurf/Amaya/Lynx/w3m
+            /(khtml|tasman|links)[\/\s]\(?([\w\.]+)/i,                          // KHTML/Tasman/Links
+            /(icab)[\/\s]([23]\.[\d\.]+)/i                                      // iCab
+            ], [NAME, VERSION], [
+
+            /rv\:([\w\.]+).*(gecko)/i                                           // Gecko
+            ], [VERSION, NAME]
+        ],
+
+        os : [[
+
+            // Windows based
+            /microsoft\s(windows)\s(vista|xp)/i                                 // Windows (iTunes)
+            ], [NAME, VERSION], [
+            /(windows)\snt\s6\.2;\s(arm)/i,                                     // Windows RT
+            /(windows\sphone(?:\sos)*)[\s\/]?([\d\.\s]+\w)*/i,                  // Windows Phone
+            /(windows\smobile|windows)[\s\/]?([ntce\d\.\s]+\w)/i
+            ], [NAME, [VERSION, mapper.str, maps.os.windows.version]], [
+            /(win(?=3|9|n)|win\s9x\s)([nt\d\.]+)/i
+            ], [[NAME, 'Windows'], [VERSION, mapper.str, maps.os.windows.version]], [
+
+            // Mobile/Embedded OS
+            /\((bb)(10);/i                                                      // BlackBerry 10
+            ], [[NAME, 'BlackBerry'], VERSION], [
+            /(blackberry)\w*\/?([\w\.]+)*/i,                                    // Blackberry
+            /(tizen)[\/\s]([\w\.]+)/i,                                          // Tizen
+            /(android|webos|palm\sos|qnx|bada|rim\stablet\sos|meego|contiki)[\/\s-]?([\w\.]+)*/i,
+                                                                                // Android/WebOS/Palm/QNX/Bada/RIM/MeeGo/Contiki
+            /linux;.+(sailfish);/i                                              // Sailfish OS
+            ], [NAME, VERSION], [
+            /(symbian\s?os|symbos|s60(?=;))[\/\s-]?([\w\.]+)*/i                 // Symbian
+            ], [[NAME, 'Symbian'], VERSION], [
+            /\((series40);/i                                                    // Series 40
+            ], [NAME], [
+            /mozilla.+\(mobile;.+gecko.+firefox/i                               // Firefox OS
+            ], [[NAME, 'Firefox OS'], VERSION], [
+
+            // Console
+            /(nintendo|playstation)\s([wids34portablevu]+)/i,                   // Nintendo/Playstation
+
+            // GNU/Linux based
+            /(mint)[\/\s\(]?(\w+)*/i,                                           // Mint
+            /(mageia|vectorlinux)[;\s]/i,                                       // Mageia/VectorLinux
+            /(joli|[kxln]?ubuntu|debian|[open]*suse|gentoo|(?=\s)arch|slackware|fedora|mandriva|centos|pclinuxos|redhat|zenwalk|linpus)[\/\s-]?(?!chrom)([\w\.-]+)*/i,
+                                                                                // Joli/Ubuntu/Debian/SUSE/Gentoo/Arch/Slackware
+                                                                                // Fedora/Mandriva/CentOS/PCLinuxOS/RedHat/Zenwalk/Linpus
+            /(hurd|linux)\s?([\w\.]+)*/i,                                       // Hurd/Linux
+            /(gnu)\s?([\w\.]+)*/i                                               // GNU
+            ], [NAME, VERSION], [
+
+            /(cros)\s[\w]+\s([\w\.]+\w)/i                                       // Chromium OS
+            ], [[NAME, 'Chromium OS'], VERSION],[
+
+            // Solaris
+            /(sunos)\s?([\w\.]+\d)*/i                                           // Solaris
+            ], [[NAME, 'Solaris'], VERSION], [
+
+            // BSD based
+            /\s([frentopc-]{0,4}bsd|dragonfly)\s?([\w\.]+)*/i                   // FreeBSD/NetBSD/OpenBSD/PC-BSD/DragonFly
+            ], [NAME, VERSION],[
+
+            /(haiku)\s(\w+)/i                                                  // Haiku
+            ], [NAME, VERSION],[
+
+            /cfnetwork\/.+darwin/i,
+            /ip[honead]+(?:.*os\s([\w]+)\slike\smac|;\sopera)/i                 // iOS
+            ], [[VERSION, /_/g, '.'], [NAME, 'iOS']], [
+
+            /(mac\sos\sx)\s?([\w\s\.]+\w)*/i,
+            /(macintosh|mac(?=_powerpc)\s)/i                                    // Mac OS
+            ], [[NAME, 'Mac OS'], [VERSION, /_/g, '.']], [
+
+            // Other
+            /((?:open)?solaris)[\/\s-]?([\w\.]+)*/i,                            // Solaris
+            /(aix)\s((\d)(?=\.|\)|\s)[\w\.]*)*/i,                               // AIX
+            /(plan\s9|minix|beos|os\/2|amigaos|morphos|risc\sos|openvms)/i,
+                                                                                // Plan9/Minix/BeOS/OS2/AmigaOS/MorphOS/RISCOS/OpenVMS
+            /(unix)\s?([\w\.]+)*/i                                              // UNIX
+            ], [NAME, VERSION]
+        ]
+    };
+
+
+    /////////////////
+    // Constructor
+    ////////////////
+    /*
+    var Browser = function (name, version) {
+        this[NAME] = name;
+        this[VERSION] = version;
+    };
+    var CPU = function (arch) {
+        this[ARCHITECTURE] = arch;
+    };
+    var Device = function (vendor, model, type) {
+        this[VENDOR] = vendor;
+        this[MODEL] = model;
+        this[TYPE] = type;
+    };
+    var Engine = Browser;
+    var OS = Browser;
+    */
+    var UAParser = function (uastring, extensions) {
+
+        if (typeof uastring === 'object') {
+            extensions = uastring;
+            uastring = undefined;
+        }
+
+        if (!(this instanceof UAParser)) {
+            return new UAParser(uastring, extensions).getResult();
+        }
+
+        var ua = uastring || ((window && window.navigator && window.navigator.userAgent) ? window.navigator.userAgent : EMPTY);
+        var rgxmap = extensions ? util.extend(regexes, extensions) : regexes;
+        //var browser = new Browser();
+        //var cpu = new CPU();
+        //var device = new Device();
+        //var engine = new Engine();
+        //var os = new OS();
+
+        this.getBrowser = function () {
+            var browser = { name: undefined, version: undefined };
+            mapper.rgx.call(browser, ua, rgxmap.browser);
+            browser.major = util.major(browser.version); // deprecated
+            return browser;
+        };
+        this.getCPU = function () {
+            var cpu = { architecture: undefined };
+            mapper.rgx.call(cpu, ua, rgxmap.cpu);
+            return cpu;
+        };
+        this.getDevice = function () {
+            var device = { vendor: undefined, model: undefined, type: undefined };
+            mapper.rgx.call(device, ua, rgxmap.device);
+            return device;
+        };
+        this.getEngine = function () {
+            var engine = { name: undefined, version: undefined };
+            mapper.rgx.call(engine, ua, rgxmap.engine);
+            return engine;
+        };
+        this.getOS = function () {
+            var os = { name: undefined, version: undefined };
+            mapper.rgx.call(os, ua, rgxmap.os);
+            return os;
+        };
+        this.getResult = function () {
+            return {
+                ua      : this.getUA(),
+                browser : this.getBrowser(),
+                engine  : this.getEngine(),
+                os      : this.getOS(),
+                device  : this.getDevice(),
+                cpu     : this.getCPU()
+            };
+        };
+        this.getUA = function () {
+            return ua;
+        };
+        this.setUA = function (uastring) {
+            ua = uastring;
+            //browser = new Browser();
+            //cpu = new CPU();
+            //device = new Device();
+            //engine = new Engine();
+            //os = new OS();
+            return this;
+        };
+        return this;
+    };
+
+    UAParser.VERSION = LIBVERSION;
+    UAParser.BROWSER = {
+        NAME    : NAME,
+        MAJOR   : MAJOR, // deprecated
+        VERSION : VERSION
+    };
+    UAParser.CPU = {
+        ARCHITECTURE : ARCHITECTURE
+    };
+    UAParser.DEVICE = {
+        MODEL   : MODEL,
+        VENDOR  : VENDOR,
+        TYPE    : TYPE,
+        CONSOLE : CONSOLE,
+        MOBILE  : MOBILE,
+        SMARTTV : SMARTTV,
+        TABLET  : TABLET,
+        WEARABLE: WEARABLE,
+        EMBEDDED: EMBEDDED
+    };
+    UAParser.ENGINE = {
+        NAME    : NAME,
+        VERSION : VERSION
+    };
+    UAParser.OS = {
+        NAME    : NAME,
+        VERSION : VERSION
+    };
+    //UAParser.Utils = util;
+
+    ///////////
+    // Export
+    //////////
+
+
+    // check js environment
+    if (typeof(exports) !== UNDEF_TYPE) {
+        // nodejs env
+        if (typeof module !== UNDEF_TYPE && module.exports) {
+            exports = module.exports = UAParser;
+        }
+        // TODO: test!!!!!!!!
+        /*
+        if (require && require.main === module && process) {
+            // cli
+            var jsonize = function (arr) {
+                var res = [];
+                for (var i in arr) {
+                    res.push(new UAParser(arr[i]).getResult());
+                }
+                process.stdout.write(JSON.stringify(res, null, 2) + '\n');
+            };
+            if (process.stdin.isTTY) {
+                // via args
+                jsonize(process.argv.slice(2));
+            } else {
+                // via pipe
+                var str = '';
+                process.stdin.on('readable', function() {
+                    var read = process.stdin.read();
+                    if (read !== null) {
+                        str += read;
+                    }
+                });
+                process.stdin.on('end', function () {
+                    jsonize(str.replace(/\n$/, '').split('\n'));
+                });
+            }
+        }
+        */
+        exports.UAParser = UAParser;
+    } else {
+        // requirejs env (optional)
+        if (typeof(define) === FUNC_TYPE && define.amd) {
+            define(function () {
+                return UAParser;
+            });
+        } else if (window) {
+            // browser env
+            window.UAParser = UAParser;
+        }
+    }
+
+    // jQuery/Zepto specific (optional)
+    // Note:
+    //   In AMD env the global scope should be kept clean, but jQuery is an exception.
+    //   jQuery always exports to global scope, unless jQuery.noConflict(true) is used,
+    //   and we should catch that.
+    var $ = window && (window.jQuery || window.Zepto);
+    if (typeof $ !== UNDEF_TYPE) {
+        var parser = new UAParser();
+        $.ua = parser.getResult();
+        $.ua.get = function () {
+            return parser.getUA();
+        };
+        $.ua.set = function (uastring) {
+            parser.setUA(uastring);
+            var result = parser.getResult();
+            for (var prop in result) {
+                $.ua[prop] = result[prop];
+            }
+        };
+    }
+
+})(typeof window === 'object' ? window : this);
+
+},{}],22:[function(require,module,exports){
+(function (global){
+
+var rng;
+
+var crypto = global.crypto || global.msCrypto; // for IE 11
+if (crypto && crypto.getRandomValues) {
+  // WHATWG crypto-based RNG - http://wiki.whatwg.org/wiki/Crypto
+  // Moderately fast, high quality
+  var _rnds8 = new Uint8Array(16);
+  rng = function whatwgRNG() {
+    crypto.getRandomValues(_rnds8);
+    return _rnds8;
+  };
+}
+
+if (!rng) {
+  // Math.random()-based (RNG)
+  //
+  // If all else fails, use Math.random().  It's fast, but is of unspecified
+  // quality.
+  var  _rnds = new Array(16);
+  rng = function() {
+    for (var i = 0, r; i < 16; i++) {
+      if ((i & 0x03) === 0) r = Math.random() * 0x100000000;
+      _rnds[i] = r >>> ((i & 0x03) << 3) & 0xff;
+    }
+
+    return _rnds;
+  };
+}
+
+module.exports = rng;
+
+
+}).call(this,typeof global !== "undefined" ? global : typeof self !== "undefined" ? self : typeof window !== "undefined" ? window : {})
+},{}],23:[function(require,module,exports){
+//     uuid.js
+//
+//     Copyright (c) 2010-2012 Robert Kieffer
+//     MIT License - http://opensource.org/licenses/mit-license.php
+
+// Unique ID creation requires a high quality random # generator.  We feature
+// detect to determine the best RNG source, normalizing to a function that
+// returns 128-bits of randomness, since that's what's usually required
+var _rng = require('./rng');
+
+// Maps for number <-> hex string conversion
+var _byteToHex = [];
+var _hexToByte = {};
+for (var i = 0; i < 256; i++) {
+  _byteToHex[i] = (i + 0x100).toString(16).substr(1);
+  _hexToByte[_byteToHex[i]] = i;
+}
+
+// **`parse()` - Parse a UUID into it's component bytes**
+function parse(s, buf, offset) {
+  var i = (buf && offset) || 0, ii = 0;
+
+  buf = buf || [];
+  s.toLowerCase().replace(/[0-9a-f]{2}/g, function(oct) {
+    if (ii < 16) { // Don't overflow!
+      buf[i + ii++] = _hexToByte[oct];
+    }
+  });
+
+  // Zero out remaining bytes if string was short
+  while (ii < 16) {
+    buf[i + ii++] = 0;
+  }
+
+  return buf;
+}
+
+// **`unparse()` - Convert UUID byte array (ala parse()) into a string**
+function unparse(buf, offset) {
+  var i = offset || 0, bth = _byteToHex;
+  return  bth[buf[i++]] + bth[buf[i++]] +
+          bth[buf[i++]] + bth[buf[i++]] + '-' +
+          bth[buf[i++]] + bth[buf[i++]] + '-' +
+          bth[buf[i++]] + bth[buf[i++]] + '-' +
+          bth[buf[i++]] + bth[buf[i++]] + '-' +
+          bth[buf[i++]] + bth[buf[i++]] +
+          bth[buf[i++]] + bth[buf[i++]] +
+          bth[buf[i++]] + bth[buf[i++]];
+}
+
+// **`v1()` - Generate time-based UUID**
+//
+// Inspired by https://github.com/LiosK/UUID.js
+// and http://docs.python.org/library/uuid.html
+
+// random #'s we need to init node and clockseq
+var _seedBytes = _rng();
+
+// Per 4.5, create and 48-bit node id, (47 random bits + multicast bit = 1)
+var _nodeId = [
+  _seedBytes[0] | 0x01,
+  _seedBytes[1], _seedBytes[2], _seedBytes[3], _seedBytes[4], _seedBytes[5]
+];
+
+// Per 4.2.2, randomize (14 bit) clockseq
+var _clockseq = (_seedBytes[6] << 8 | _seedBytes[7]) & 0x3fff;
+
+// Previous uuid creation time
+var _lastMSecs = 0, _lastNSecs = 0;
+
+// See https://github.com/broofa/node-uuid for API details
+function v1(options, buf, offset) {
+  var i = buf && offset || 0;
+  var b = buf || [];
+
+  options = options || {};
+
+  var clockseq = options.clockseq !== undefined ? options.clockseq : _clockseq;
+
+  // UUID timestamps are 100 nano-second units since the Gregorian epoch,
+  // (1582-10-15 00:00).  JSNumbers aren't precise enough for this, so
+  // time is handled internally as 'msecs' (integer milliseconds) and 'nsecs'
+  // (100-nanoseconds offset from msecs) since unix epoch, 1970-01-01 00:00.
+  var msecs = options.msecs !== undefined ? options.msecs : new Date().getTime();
+
+  // Per 4.2.1.2, use count of uuid's generated during the current clock
+  // cycle to simulate higher resolution clock
+  var nsecs = options.nsecs !== undefined ? options.nsecs : _lastNSecs + 1;
+
+  // Time since last uuid creation (in msecs)
+  var dt = (msecs - _lastMSecs) + (nsecs - _lastNSecs)/10000;
+
+  // Per 4.2.1.2, Bump clockseq on clock regression
+  if (dt < 0 && options.clockseq === undefined) {
+    clockseq = clockseq + 1 & 0x3fff;
+  }
+
+  // Reset nsecs if clock regresses (new clockseq) or we've moved onto a new
+  // time interval
+  if ((dt < 0 || msecs > _lastMSecs) && options.nsecs === undefined) {
+    nsecs = 0;
+  }
+
+  // Per 4.2.1.2 Throw error if too many uuids are requested
+  if (nsecs >= 10000) {
+    throw new Error('uuid.v1(): Can\'t create more than 10M uuids/sec');
+  }
+
+  _lastMSecs = msecs;
+  _lastNSecs = nsecs;
+  _clockseq = clockseq;
+
+  // Per 4.1.4 - Convert from unix epoch to Gregorian epoch
+  msecs += 12219292800000;
+
+  // `time_low`
+  var tl = ((msecs & 0xfffffff) * 10000 + nsecs) % 0x100000000;
+  b[i++] = tl >>> 24 & 0xff;
+  b[i++] = tl >>> 16 & 0xff;
+  b[i++] = tl >>> 8 & 0xff;
+  b[i++] = tl & 0xff;
+
+  // `time_mid`
+  var tmh = (msecs / 0x100000000 * 10000) & 0xfffffff;
+  b[i++] = tmh >>> 8 & 0xff;
+  b[i++] = tmh & 0xff;
+
+  // `time_high_and_version`
+  b[i++] = tmh >>> 24 & 0xf | 0x10; // include version
+  b[i++] = tmh >>> 16 & 0xff;
+
+  // `clock_seq_hi_and_reserved` (Per 4.2.2 - include variant)
+  b[i++] = clockseq >>> 8 | 0x80;
+
+  // `clock_seq_low`
+  b[i++] = clockseq & 0xff;
+
+  // `node`
+  var node = options.node || _nodeId;
+  for (var n = 0; n < 6; n++) {
+    b[i + n] = node[n];
+  }
+
+  return buf ? buf : unparse(b);
+}
+
+// **`v4()` - Generate random UUID**
+
+// See https://github.com/broofa/node-uuid for API details
+function v4(options, buf, offset) {
+  // Deprecated - 'format' argument, as supported in v1.2
+  var i = buf && offset || 0;
+
+  if (typeof(options) == 'string') {
+    buf = options == 'binary' ? new Array(16) : null;
+    options = null;
+  }
+  options = options || {};
+
+  var rnds = options.random || (options.rng || _rng)();
+
+  // Per 4.4, set bits for version and `clock_seq_hi_and_reserved`
+  rnds[6] = (rnds[6] & 0x0f) | 0x40;
+  rnds[8] = (rnds[8] & 0x3f) | 0x80;
+
+  // Copy bytes to buffer, if provided
+  if (buf) {
+    for (var ii = 0; ii < 16; ii++) {
+      buf[i + ii] = rnds[ii];
+    }
+  }
+
+  return buf || unparse(rnds);
+}
+
+// Export public API
+var uuid = v4;
+uuid.v1 = v1;
+uuid.v4 = v4;
+uuid.parse = parse;
+uuid.unparse = unparse;
+
+module.exports = uuid;
+
+},{"./rng":22}],24:[function(require,module,exports){
+/*
+WildEmitter.js is a slim little event emitter by @henrikjoreteg largely based
+on @visionmedia's Emitter from UI Kit.
+
+Why? I wanted it standalone.
+
+I also wanted support for wildcard emitters like this:
+
+emitter.on('*', function (eventName, other, event, payloads) {
+
+});
+
+emitter.on('somenamespace*', function (eventName, payloads) {
+
+});
+
+Please note that callbacks triggered by wildcard registered events also get
+the event name as the first argument.
+*/
+
+module.exports = WildEmitter;
+
+function WildEmitter() { }
+
+WildEmitter.mixin = function (constructor) {
+    var prototype = constructor.prototype || constructor;
+
+    prototype.isWildEmitter= true;
+
+    // Listen on the given `event` with `fn`. Store a group name if present.
+    prototype.on = function (event, groupName, fn) {
+        this.callbacks = this.callbacks || {};
+        var hasGroup = (arguments.length === 3),
+            group = hasGroup ? arguments[1] : undefined,
+            func = hasGroup ? arguments[2] : arguments[1];
+        func._groupName = group;
+        (this.callbacks[event] = this.callbacks[event] || []).push(func);
+        return this;
+    };
+
+    // Adds an `event` listener that will be invoked a single
+    // time then automatically removed.
+    prototype.once = function (event, groupName, fn) {
+        var self = this,
+            hasGroup = (arguments.length === 3),
+            group = hasGroup ? arguments[1] : undefined,
+            func = hasGroup ? arguments[2] : arguments[1];
+        function on() {
+            self.off(event, on);
+            func.apply(this, arguments);
+        }
+        this.on(event, group, on);
+        return this;
+    };
+
+    // Unbinds an entire group
+    prototype.releaseGroup = function (groupName) {
+        this.callbacks = this.callbacks || {};
+        var item, i, len, handlers;
+        for (item in this.callbacks) {
+            handlers = this.callbacks[item];
+            for (i = 0, len = handlers.length; i < len; i++) {
+                if (handlers[i]._groupName === groupName) {
+                    //console.log('removing');
+                    // remove it and shorten the array we're looping through
+                    handlers.splice(i, 1);
+                    i--;
+                    len--;
+                }
+            }
+        }
+        return this;
+    };
+
+    // Remove the given callback for `event` or all
+    // registered callbacks.
+    prototype.off = function (event, fn) {
+        this.callbacks = this.callbacks || {};
+        var callbacks = this.callbacks[event],
+            i;
+
+        if (!callbacks) return this;
+
+        // remove all handlers
+        if (arguments.length === 1) {
+            delete this.callbacks[event];
+            return this;
+        }
+
+        // remove specific handler
+        i = callbacks.indexOf(fn);
+        callbacks.splice(i, 1);
+        if (callbacks.length === 0) {
+            delete this.callbacks[event];
+        }
+        return this;
+    };
+
+    /// Emit `event` with the given args.
+    // also calls any `*` handlers
+    prototype.emit = function (event) {
+        this.callbacks = this.callbacks || {};
+        var args = [].slice.call(arguments, 1),
+            callbacks = this.callbacks[event],
+            specialCallbacks = this.getWildcardCallbacks(event),
+            i,
+            len,
+            item,
+            listeners;
+
+        if (callbacks) {
+            listeners = callbacks.slice();
+            for (i = 0, len = listeners.length; i < len; ++i) {
+                if (!listeners[i]) {
+                    break;
+                }
+                listeners[i].apply(this, args);
+            }
+        }
+
+        if (specialCallbacks) {
+            len = specialCallbacks.length;
+            listeners = specialCallbacks.slice();
+            for (i = 0, len = listeners.length; i < len; ++i) {
+                if (!listeners[i]) {
+                    break;
+                }
+                listeners[i].apply(this, [event].concat(args));
+            }
+        }
+
+        return this;
+    };
+
+    // Helper for for finding special wildcard event handlers that match the event
+    prototype.getWildcardCallbacks = function (eventName) {
+        this.callbacks = this.callbacks || {};
+        var item,
+            split,
+            result = [];
+
+        for (item in this.callbacks) {
+            split = item.split('*');
+            if (item === '*' || (split.length === 2 && eventName.slice(0, split[0].length) === split[0])) {
+                result = result.concat(this.callbacks[item]);
+            }
+        }
+        return result;
+    };
+
+};
+
+WildEmitter.mixin(WildEmitter);
+
+},{}]},{},[2])(2)
+});
\ No newline at end of file
diff --git a/bigbluebutton-client/resources/prod/lib/kurento-utils.min.js b/bigbluebutton-client/resources/prod/lib/kurento-utils.min.js
deleted file mode 100644
index 4e88a14cb52432fe068dd62adf6ec94350103ae2..0000000000000000000000000000000000000000
--- a/bigbluebutton-client/resources/prod/lib/kurento-utils.min.js
+++ /dev/null
@@ -1,56 +0,0 @@
-(function(f){if(typeof exports==="object"&&typeof module!=="undefined"){module.exports=f()}else if(typeof define==="function"&&define.amd){define([],f)}else{var g;if(typeof window!=="undefined"){g=window}else if(typeof global!=="undefined"){g=global}else if(typeof self!=="undefined"){g=self}else{g=this}g.kurentoUtils = f()}})(function(){var define,module,exports;return (function e(t,n,r){function s(o,u){if(!n[o]){if(!t[o]){var a=typeof require=="function"&&require;if(!u&&a)return a(o,!0);if(i)return i(o,!0);var f=new Error("Cannot find module '"+o+"'");throw f.code="MODULE_NOT_FOUND",f}var l=n[o]={exports:{}};t[o][0].call(l.exports,function(e){var n=t[o][1][e];return s(n?n:e)},l,l.exports,e,t,n,r)}return n[o].exports}var i=typeof require=="function"&&require;for(var o=0;o<r.length;o++)s(r[o]);return s})({1:[function(require,module,exports){
-function noop(e){e&&logger.error(e)}function trackStop(e){e.stop&&e.stop()}function streamStop(e){e.getTracks().forEach(trackStop)}function bufferizeCandidates(e,n){var t=[];return e.addEventListener("signalingstatechange",function(){if("stable"===this.signalingState)for(;t.length;){var e=t.shift();this.addIceCandidate(e.candidate,e.callback,e.callback)}}),function(r,i){switch(i=i||n,e.signalingState){case"closed":i(new Error("PeerConnection object is closed"));break;case"stable":if(e.remoteDescription){e.addIceCandidate(r,i,i);break}default:t.push({candidate:r,callback:i})}}}function removeFIDFromOffer(e){var n=e.indexOf("a=ssrc-group:FID");return n>0?e.slice(0,n):e}function getSimulcastInfo(e){var n=e.getVideoTracks();if(!n.length)return logger.warn("No video tracks available in the video stream"),"";var t=["a=x-google-flag:conference","a=ssrc-group:SIM 1 2 3","a=ssrc:1 cname:localVideo","a=ssrc:1 msid:"+e.id+" "+n[0].id,"a=ssrc:1 mslabel:"+e.id,"a=ssrc:1 label:"+n[0].id,"a=ssrc:2 cname:localVideo","a=ssrc:2 msid:"+e.id+" "+n[0].id,"a=ssrc:2 mslabel:"+e.id,"a=ssrc:2 label:"+n[0].id,"a=ssrc:3 cname:localVideo","a=ssrc:3 msid:"+e.id+" "+n[0].id,"a=ssrc:3 mslabel:"+e.id,"a=ssrc:3 label:"+n[0].id];return t.push(""),t.join("\n")}function WebRtcPeer(e,n,t){function r(){if(l){var e=p.getRemoteStreams()[0],n=e?URL.createObjectURL(e):"";l.pause(),l.src=n,l.load(),logger.info("Remote URL:",n)}}function i(e){return w&&("Chrome"===browser.name||"Chromium"===browser.name?(logger.info("Adding multicast info"),e=new RTCSessionDescription({type:e.type,sdp:removeFIDFromOffer(e.sdp)+getSimulcastInfo(u)})):logger.warn("Simulcast is only available in Chrome browser.")),e}function o(){"closed"===p.signalingState&&t('The peer connection object is in "closed" state. This is most likely due to an invocation of the dispose method before accepting in the dialogue'),u&&d&&c.showLocalVideo(),u&&p.addStream(u),f&&p.addStream(f);var n=parser.getBrowser();"sendonly"!==e||"Chrome"!==n.name&&"Chromium"!==n.name||39!==n.major||(e="sendrecv"),t()}function a(e){void 0===e&&(e=MEDIA_CONSTRAINTS),navigator.mediaDevices.getUserMedia(e).then(function(e){u=e,o()}).catch(t)}if(!(this instanceof WebRtcPeer))return new WebRtcPeer(e,n,t);WebRtcPeer.super_.call(this),n instanceof Function&&(t=n,n=void 0),n=n||{},t=(t||noop).bind(this);var s,c=this,d=n.localVideo,l=n.remoteVideo,u=n.videoStream,f=n.audioStream,g=n.mediaConstraints,p=(n.connectionConstraints,n.peerConnection),h=n.sendSource||"webcam",m=n.dataChannelConfig,v=n.dataChannels||!1,b=uuid.v4(),P=recursive({iceServers:freeice()},n.configuration),S=n.onicecandidate;S&&this.on("icecandidate",S);var R=n.oncandidategatheringdone;R&&this.on("candidategatheringdone",R);var w=n.simulcast,C=n.multistream,y=new sdpTranslator.Interop,D=[],W=!1;if(Object.defineProperties(this,{peerConnection:{get:function(){return p}},id:{value:n.id||b,writable:!1},remoteVideo:{get:function(){return l}},localVideo:{get:function(){return d}},dataChannel:{get:function(){return s}},currentFrame:{get:function(){if(l){if(l.readyState<l.HAVE_CURRENT_DATA)throw new Error("No video stream data available");var e=document.createElement("canvas");return e.width=l.videoWidth,e.height=l.videoHeight,e.getContext("2d").drawImage(l,0,0),e}}}}),!p&&(p=new RTCPeerConnection(P),v&&!s)){var E="WebRtcPeer-"+c.id,T=void 0;m&&(E=m.id||E,T=m.options),s=p.createDataChannel(E,T),m&&(s.onopen=m.onopen,s.onclose=m.onclose,s.onmessage=m.onmessage,s.onbufferedamountlow=m.onbufferedamountlow,s.onerror=m.onerror||noop)}p.addEventListener("icecandidate",function(e){var n=e.candidate;if(EventEmitter.listenerCount(c,"icecandidate")||EventEmitter.listenerCount(c,"candidategatheringdone"))if(n){var t;t=C&&usePlanB?y.candidateToUnifiedPlan(n):n,c.emit("icecandidate",t),W=!1}else W||(c.emit("candidategatheringdone"),W=!0);else W||(D.push(n),n||(W=!0))}),p.ontrack=n.onaddstream,p.onnegotiationneeded=n.onnegotiationneeded,this.on("newListener",function(e,n){if("icecandidate"===e||"candidategatheringdone"===e)for(;D.length;){var t=D.shift();!t==("candidategatheringdone"===e)&&n(t)}});var k=bufferizeCandidates(p);this.addIceCandidate=function(e,n){var t;t=C&&usePlanB?y.candidateToPlanB(e):new RTCIceCandidate(e),logger.debug("Remote ICE candidate received",e),n=(n||noop).bind(this),k(t,n)},this.generateOffer=function(n){n=n.bind(this);var t=!0,r=!0;g&&(t="boolean"!=typeof g.audio||g.audio,r="boolean"!=typeof g.video||g.video);var o={offerToReceiveAudio:"sendonly"!==e&&t,offerToReceiveVideo:"sendonly"!==e&&r},a=o;logger.info("constraints: "+JSON.stringify(a)),p.createOffer(a).then(function(e){return logger.info("Created SDP offer"),e=i(e),p.setLocalDescription(e)}).then(function(){var e=p.localDescription;logger.info("Local description set",e.sdp),C&&usePlanB&&(e=y.toUnifiedPlan(e),logger.info("offer::origPlanB->UnifiedPlan",dumpSDP(e))),n(null,e.sdp,c.processAnswer.bind(c))}).catch(n)},this.getLocalSessionDescriptor=function(){return p.localDescription},this.getRemoteSessionDescriptor=function(){return p.remoteDescription},this.showLocalVideo=function(){d.src=URL.createObjectURL(u),d.muted=!0},this.send=function(e){s&&"open"===s.readyState?s.send(e):logger.warn("Trying to send data over a non-existing or closed data channel")},this.processAnswer=function(e,n){n=(n||noop).bind(this);var t=new RTCSessionDescription({type:"answer",sdp:e});if(C&&usePlanB){var i=y.toPlanB(t);logger.info("asnwer::planB",dumpSDP(i)),t=i}if(logger.info("SDP answer received, setting remote description"),"closed"===p.signalingState)return n("PeerConnection is closed");p.setRemoteDescription(t,function(){r(),n()},n)},this.processOffer=function(e,n){n=n.bind(this);var t=new RTCSessionDescription({type:"offer",sdp:e});if(C&&usePlanB){var o=y.toPlanB(t);logger.info("offer::planB",dumpSDP(o)),t=o}if(logger.info("SDP offer received, setting remote description"),"closed"===p.signalingState)return n("PeerConnection is closed");p.setRemoteDescription(t).then(function(){return r()}).then(function(){return p.createAnswer()}).then(function(e){return e=i(e),logger.info("Created SDP answer"),p.setLocalDescription(e)}).then(function(){var e=p.localDescription;C&&usePlanB&&(e=y.toUnifiedPlan(e),logger.info("answer::origPlanB->UnifiedPlan",dumpSDP(e))),logger.info("Local description set",e.sdp),n(null,e.sdp)}).catch(n)},"recvonly"===e||u||f?setTimeout(o,0):"webcam"===h?a(g):getScreenConstraints(h,function(e,n){if(e)return t(e);constraints=[g],constraints.unshift(n),a(recursive.apply(void 0,constraints))},b),this.on("_dispose",function(){d&&(d.pause(),d.src="",d.load(),d.muted=!1),l&&(l.pause(),l.src="",l.load()),c.removeAllListeners(),void 0!==window.cancelChooseDesktopMedia&&window.cancelChooseDesktopMedia(b)})}function createEnableDescriptor(e){var n="get"+e+"Tracks";return{enumerable:!0,get:function(){if(this.peerConnection){var e=this.peerConnection.getLocalStreams();if(e.length){for(var t,r=0;t=e[r];r++)for(var i,o=t[n](),a=0;i=o[a];a++)if(!i.enabled)return!1;return!0}}},set:function(e){function t(n){n.enabled=e}this.peerConnection.getLocalStreams().forEach(function(e){e[n]().forEach(t)})}}}function WebRtcPeerRecvonly(e,n){if(!(this instanceof WebRtcPeerRecvonly))return new WebRtcPeerRecvonly(e,n);WebRtcPeerRecvonly.super_.call(this,"recvonly",e,n)}function WebRtcPeerSendonly(e,n){if(!(this instanceof WebRtcPeerSendonly))return new WebRtcPeerSendonly(e,n);WebRtcPeerSendonly.super_.call(this,"sendonly",e,n)}function WebRtcPeerSendrecv(e,n){if(!(this instanceof WebRtcPeerSendrecv))return new WebRtcPeerSendrecv(e,n);WebRtcPeerSendrecv.super_.call(this,"sendrecv",e,n)}function harkUtils(e,n){return hark(e,n)}var freeice=require("freeice"),inherits=require("inherits"),UAParser=require("ua-parser-js"),uuid=require("uuid"),hark=require("hark"),EventEmitter=require("events").EventEmitter,recursive=require("merge").recursive.bind(void 0,!0),sdpTranslator=require("sdp-translator"),logger=window.Logger||console;try{require("kurento-browser-extensions")}catch(e){"undefined"==typeof getScreenConstraints&&(logger.warn("screen sharing is not available"),getScreenConstraints=function(e,n){n(new Error("This library is not enabled for screen sharing"))})}var MEDIA_CONSTRAINTS={audio:!0,video:{width:640,framerate:15}},ua=window&&window.navigator?window.navigator.userAgent:"",parser=new UAParser(ua),browser=parser.getBrowser(),usePlanB=!1;"Chrome"!==browser.name&&"Chromium"!==browser.name||(logger.info(browser.name+": using SDP PlanB"),usePlanB=!0);var dumpSDP=function(e){return void 0===e||null===e?"":"type: "+e.type+"\r\n"+e.sdp};inherits(WebRtcPeer,EventEmitter),Object.defineProperties(WebRtcPeer.prototype,{enabled:{enumerable:!0,get:function(){return this.audioEnabled&&this.videoEnabled},set:function(e){this.audioEnabled=this.videoEnabled=e}},audioEnabled:createEnableDescriptor("Audio"),videoEnabled:createEnableDescriptor("Video")}),WebRtcPeer.prototype.getLocalStream=function(e){if(this.peerConnection)return this.peerConnection.getLocalStreams()[e||0]},WebRtcPeer.prototype.getRemoteStream=function(e){if(this.peerConnection)return this.peerConnection.getRemoteStreams()[e||0]},WebRtcPeer.prototype.dispose=function(){logger.info("Disposing WebRtcPeer");var e=this.peerConnection,n=this.dataChannel;try{if(n){if("closed"===n.signalingState)return;n.close()}if(e){if("closed"===e.signalingState)return;e.getLocalStreams().forEach(streamStop),e.close()}}catch(e){logger.warn("Exception disposing webrtc peer "+e)}this.emit("_dispose")},inherits(WebRtcPeerRecvonly,WebRtcPeer),inherits(WebRtcPeerSendonly,WebRtcPeer),inherits(WebRtcPeerSendrecv,WebRtcPeer),exports.bufferizeCandidates=bufferizeCandidates,exports.WebRtcPeerRecvonly=WebRtcPeerRecvonly,exports.WebRtcPeerSendonly=WebRtcPeerSendonly,exports.WebRtcPeerSendrecv=WebRtcPeerSendrecv,exports.hark=harkUtils;
-},{"events":4,"freeice":5,"hark":8,"inherits":9,"kurento-browser-extensions":10,"merge":11,"sdp-translator":18,"ua-parser-js":21,"uuid":23}],2:[function(require,module,exports){
-window.addEventListener&&(module.exports=require("./index"));
-},{"./index":3}],3:[function(require,module,exports){
-var WebRtcPeer=require("./WebRtcPeer");exports.WebRtcPeer=WebRtcPeer;
-},{"./WebRtcPeer":1}],4:[function(require,module,exports){
-function EventEmitter(){this._events=this._events||{},this._maxListeners=this._maxListeners||void 0}function isFunction(e){return"function"==typeof e}function isNumber(e){return"number"==typeof e}function isObject(e){return"object"==typeof e&&null!==e}function isUndefined(e){return void 0===e}module.exports=EventEmitter,EventEmitter.EventEmitter=EventEmitter,EventEmitter.prototype._events=void 0,EventEmitter.prototype._maxListeners=void 0,EventEmitter.defaultMaxListeners=10,EventEmitter.prototype.setMaxListeners=function(e){if(!isNumber(e)||e<0||isNaN(e))throw TypeError("n must be a positive number");return this._maxListeners=e,this},EventEmitter.prototype.emit=function(e){var t,i,n,s,r,o;if(this._events||(this._events={}),"error"===e&&(!this._events.error||isObject(this._events.error)&&!this._events.error.length)){if((t=arguments[1])instanceof Error)throw t;var h=new Error('Uncaught, unspecified "error" event. ('+t+")");throw h.context=t,h}if(i=this._events[e],isUndefined(i))return!1;if(isFunction(i))switch(arguments.length){case 1:i.call(this);break;case 2:i.call(this,arguments[1]);break;case 3:i.call(this,arguments[1],arguments[2]);break;default:s=Array.prototype.slice.call(arguments,1),i.apply(this,s)}else if(isObject(i))for(s=Array.prototype.slice.call(arguments,1),o=i.slice(),n=o.length,r=0;r<n;r++)o[r].apply(this,s);return!0},EventEmitter.prototype.addListener=function(e,t){var i;if(!isFunction(t))throw TypeError("listener must be a function");return this._events||(this._events={}),this._events.newListener&&this.emit("newListener",e,isFunction(t.listener)?t.listener:t),this._events[e]?isObject(this._events[e])?this._events[e].push(t):this._events[e]=[this._events[e],t]:this._events[e]=t,isObject(this._events[e])&&!this._events[e].warned&&(i=isUndefined(this._maxListeners)?EventEmitter.defaultMaxListeners:this._maxListeners)&&i>0&&this._events[e].length>i&&(this._events[e].warned=!0,console.error("(node) warning: possible EventEmitter memory leak detected. %d listeners added. Use emitter.setMaxListeners() to increase limit.",this._events[e].length),"function"==typeof console.trace&&console.trace()),this},EventEmitter.prototype.on=EventEmitter.prototype.addListener,EventEmitter.prototype.once=function(e,t){function i(){this.removeListener(e,i),n||(n=!0,t.apply(this,arguments))}if(!isFunction(t))throw TypeError("listener must be a function");var n=!1;return i.listener=t,this.on(e,i),this},EventEmitter.prototype.removeListener=function(e,t){var i,n,s,r;if(!isFunction(t))throw TypeError("listener must be a function");if(!this._events||!this._events[e])return this;if(i=this._events[e],s=i.length,n=-1,i===t||isFunction(i.listener)&&i.listener===t)delete this._events[e],this._events.removeListener&&this.emit("removeListener",e,t);else if(isObject(i)){for(r=s;r-- >0;)if(i[r]===t||i[r].listener&&i[r].listener===t){n=r;break}if(n<0)return this;1===i.length?(i.length=0,delete this._events[e]):i.splice(n,1),this._events.removeListener&&this.emit("removeListener",e,t)}return this},EventEmitter.prototype.removeAllListeners=function(e){var t,i;if(!this._events)return this;if(!this._events.removeListener)return 0===arguments.length?this._events={}:this._events[e]&&delete this._events[e],this;if(0===arguments.length){for(t in this._events)"removeListener"!==t&&this.removeAllListeners(t);return this.removeAllListeners("removeListener"),this._events={},this}if(i=this._events[e],isFunction(i))this.removeListener(e,i);else if(i)for(;i.length;)this.removeListener(e,i[i.length-1]);return delete this._events[e],this},EventEmitter.prototype.listeners=function(e){return this._events&&this._events[e]?isFunction(this._events[e])?[this._events[e]]:this._events[e].slice():[]},EventEmitter.prototype.listenerCount=function(e){if(this._events){var t=this._events[e];if(isFunction(t))return 1;if(t)return t.length}return 0},EventEmitter.listenerCount=function(e,t){return e.listenerCount(t)};
-},{}],5:[function(require,module,exports){
-"use strict";var normalice=require("normalice"),freeice=module.exports=function(n){function t(n,t){for(var r,u=[],o=[].concat(e[n]);o.length&&u.length<t;)r=Math.random()*o.length|0,u=u.concat(o.splice(r,1));return u.map(function(t){return"string"==typeof t||t instanceof String?normalice(n+":"+t):t})}var r,e={stun:(n||{}).stun||require("./stun.json"),turn:(n||{}).turn||require("./turn.json")},u=(n||{}).stunCount||2,o=(n||{}).turnCount||0;return r=[].concat(t("stun",u)),o&&(r=r.concat(t("turn",o))),r};
-},{"./stun.json":6,"./turn.json":7,"normalice":12}],6:[function(require,module,exports){
-module.exports=["stun.l.google.com:19302","stun1.l.google.com:19302","stun2.l.google.com:19302","stun3.l.google.com:19302","stun4.l.google.com:19302","stun.ekiga.net","stun.ideasip.com","stun.schlund.de","stun.stunprotocol.org:3478","stun.voiparound.com","stun.voipbuster.com","stun.voipstunt.com","stun.voxgratia.org","stun.services.mozilla.com"]
-},{}],7:[function(require,module,exports){
-module.exports=[]
-},{}],8:[function(require,module,exports){
-function getMaxVolume(e,t){var i=-1/0;e.getFloatFrequencyData(t);for(var n=4,o=t.length;n<o;n++)t[n]>i&&t[n]<0&&(i=t[n]);return i}var WildEmitter=require("wildemitter"),audioContextType=window.AudioContext||window.webkitAudioContext,audioContext=null;module.exports=function(e,t){var i=new WildEmitter;if(!audioContextType)return i;var t=t||{},n=t.smoothing||.1,o=t.interval||50,a=t.threshold,r=t.play,s=t.history||10,u=!0;audioContext||(audioContext=new audioContextType);var p,g,d;d=audioContext.createAnalyser(),d.fftSize=512,d.smoothingTimeConstant=n,g=new Float32Array(d.fftSize),e.jquery&&(e=e[0]),e instanceof HTMLAudioElement||e instanceof HTMLVideoElement?(p=audioContext.createMediaElementSource(e),void 0===r&&(r=!0),a=a||-50):(p=audioContext.createMediaStreamSource(e),a=a||-50),p.connect(d),r&&d.connect(audioContext.destination),i.speaking=!1,i.setThreshold=function(e){a=e},i.setInterval=function(e){o=e},i.stop=function(){u=!1,i.emit("volume_change",-100,a),i.speaking&&(i.speaking=!1,i.emit("stopped_speaking"))},i.speakingHistory=[];for(var l=0;l<s;l++)i.speakingHistory.push(0);var f=function(){setTimeout(function(){if(u){var e=getMaxVolume(d,g);i.emit("volume_change",e,a);var t=0;if(e>a&&!i.speaking){for(var n=i.speakingHistory.length-3;n<i.speakingHistory.length;n++)t+=i.speakingHistory[n];t>=2&&(i.speaking=!0,i.emit("speaking"))}else if(e<a&&i.speaking){for(var n=0;n<i.speakingHistory.length;n++)t+=i.speakingHistory[n];0==t&&(i.speaking=!1,i.emit("stopped_speaking"))}i.speakingHistory.shift(),i.speakingHistory.push(0+(e>a)),f()}},o)};return f(),i};
-},{"wildemitter":24}],9:[function(require,module,exports){
-"function"==typeof Object.create?module.exports=function(t,e){t.super_=e,t.prototype=Object.create(e.prototype,{constructor:{value:t,enumerable:!1,writable:!0,configurable:!0}})}:module.exports=function(t,e){t.super_=e;var o=function(){};o.prototype=e.prototype,t.prototype=new o,t.prototype.constructor=t};
-},{}],10:[function(require,module,exports){
-
-},{}],11:[function(require,module,exports){
-!function(e){function o(e,r){if("object"!==n(e))return r;for(var t in r)"object"===n(e[t])&&"object"===n(r[t])?e[t]=o(e[t],r[t]):e[t]=r[t];return e}function r(e,r,c){var u=c[0],f=c.length;(e||"object"!==n(u))&&(u={});for(var i=0;i<f;++i){var l=c[i];if("object"===n(l))for(var a in l){var v=e?t.clone(l[a]):l[a];u[a]=r?o(u[a],v):v}}return u}function n(e){return{}.toString.call(e).slice(8,-1).toLowerCase()}var t=function(e){return r(!0===e,!1,arguments)};t.recursive=function(e){return r(!0===e,!0,arguments)},t.clone=function(e){var o,r,c=e,u=n(e);if("array"===u)for(c=[],r=e.length,o=0;o<r;++o)c[o]=t.clone(e[o]);else if("object"===u){c={};for(o in e)c[o]=t.clone(e[o])}return c},e?module.exports=t:window.merge=t}("object"==typeof module&&module&&"object"==typeof module.exports&&module.exports);
-},{}],12:[function(require,module,exports){
-var protocols=["stun:","turn:"];module.exports=function(e){var r,t,n=(e||{}).url||e,l={};return"string"==typeof n||n instanceof String?(n=n.trim(),(r=protocols[protocols.indexOf(n.slice(0,5))])?(n=n.slice(5),t=n.split("@"),l.username=e.username,l.credential=e.credential,t.length>1&&(n=t[1],t=t[0].split(":"),l.username=t[0],l.credential=(e||{}).credential||t[1]||""),l.url=r+n,l.urls=[l.url],l):e):e};
-},{}],13:[function(require,module,exports){
-var grammar=module.exports={v:[{name:"version",reg:/^(\d*)$/}],o:[{name:"origin",reg:/^(\S*) (\d*) (\d*) (\S*) IP(\d) (\S*)/,names:["username","sessionId","sessionVersion","netType","ipVer","address"],format:"%s %s %d %s IP%d %s"}],s:[{name:"name"}],i:[{name:"description"}],u:[{name:"uri"}],e:[{name:"email"}],p:[{name:"phone"}],z:[{name:"timezones"}],r:[{name:"repeats"}],t:[{name:"timing",reg:/^(\d*) (\d*)/,names:["start","stop"],format:"%d %d"}],c:[{name:"connection",reg:/^IN IP(\d) (\S*)/,names:["version","ip"],format:"IN IP%d %s"}],b:[{push:"bandwidth",reg:/^(TIAS|AS|CT|RR|RS):(\d*)/,names:["type","limit"],format:"%s:%s"}],m:[{reg:/^(\w*) (\d*) ([\w\/]*)(?: (.*))?/,names:["type","port","protocol","payloads"],format:"%s %d %s %s"}],a:[{push:"rtp",reg:/^rtpmap:(\d*) ([\w\-]*)(?:\s*\/(\d*)(?:\s*\/(\S*))?)?/,names:["payload","codec","rate","encoding"],format:function(e){return e.encoding?"rtpmap:%d %s/%s/%s":e.rate?"rtpmap:%d %s/%s":"rtpmap:%d %s"}},{push:"fmtp",reg:/^fmtp:(\d*) ([\S| ]*)/,names:["payload","config"],format:"fmtp:%d %s"},{name:"control",reg:/^control:(.*)/,format:"control:%s"},{name:"rtcp",reg:/^rtcp:(\d*)(?: (\S*) IP(\d) (\S*))?/,names:["port","netType","ipVer","address"],format:function(e){return null!=e.address?"rtcp:%d %s IP%d %s":"rtcp:%d"}},{push:"rtcpFbTrrInt",reg:/^rtcp-fb:(\*|\d*) trr-int (\d*)/,names:["payload","value"],format:"rtcp-fb:%d trr-int %d"},{push:"rtcpFb",reg:/^rtcp-fb:(\*|\d*) ([\w-_]*)(?: ([\w-_]*))?/,names:["payload","type","subtype"],format:function(e){return null!=e.subtype?"rtcp-fb:%s %s %s":"rtcp-fb:%s %s"}},{push:"ext",reg:/^extmap:([\w_\/]*) (\S*)(?: (\S*))?/,names:["value","uri","config"],format:function(e){return null!=e.config?"extmap:%s %s %s":"extmap:%s %s"}},{push:"crypto",reg:/^crypto:(\d*) ([\w_]*) (\S*)(?: (\S*))?/,names:["id","suite","config","sessionConfig"],format:function(e){return null!=e.sessionConfig?"crypto:%d %s %s %s":"crypto:%d %s %s"}},{name:"setup",reg:/^setup:(\w*)/,format:"setup:%s"},{name:"mid",reg:/^mid:([^\s]*)/,format:"mid:%s"},{name:"msid",reg:/^msid:(.*)/,format:"msid:%s"},{name:"ptime",reg:/^ptime:(\d*)/,format:"ptime:%d"},{name:"maxptime",reg:/^maxptime:(\d*)/,format:"maxptime:%d"},{name:"direction",reg:/^(sendrecv|recvonly|sendonly|inactive)/},{name:"icelite",reg:/^(ice-lite)/},{name:"iceUfrag",reg:/^ice-ufrag:(\S*)/,format:"ice-ufrag:%s"},{name:"icePwd",reg:/^ice-pwd:(\S*)/,format:"ice-pwd:%s"},{name:"fingerprint",reg:/^fingerprint:(\S*) (\S*)/,names:["type","hash"],format:"fingerprint:%s %s"},{push:"candidates",reg:/^candidate:(\S*) (\d*) (\S*) (\d*) (\S*) (\d*) typ (\S*)(?: raddr (\S*) rport (\d*))?(?: tcptype (\S*))?(?: generation (\d*))?/,names:["foundation","component","transport","priority","ip","port","type","raddr","rport","tcptype","generation"],format:function(e){var r="candidate:%s %d %s %d %s %d typ %s";return r+=null!=e.raddr?" raddr %s rport %d":"%v%v",r+=null!=e.tcptype?" tcptype %s":"%v",null!=e.generation&&(r+=" generation %d"),r}},{name:"endOfCandidates",reg:/^(end-of-candidates)/},{name:"remoteCandidates",reg:/^remote-candidates:(.*)/,format:"remote-candidates:%s"},{name:"iceOptions",reg:/^ice-options:(\S*)/,format:"ice-options:%s"},{push:"ssrcs",reg:/^ssrc:(\d*) ([\w_]*):(.*)/,names:["id","attribute","value"],format:"ssrc:%d %s:%s"},{push:"ssrcGroups",reg:/^ssrc-group:(\w*) (.*)/,names:["semantics","ssrcs"],format:"ssrc-group:%s %s"},{name:"msidSemantic",reg:/^msid-semantic:\s?(\w*) (\S*)/,names:["semantic","token"],format:"msid-semantic: %s %s"},{push:"groups",reg:/^group:(\w*) (.*)/,names:["type","mids"],format:"group:%s %s"},{name:"rtcpMux",reg:/^(rtcp-mux)/},{name:"rtcpRsize",reg:/^(rtcp-rsize)/},{push:"invalid",names:["value"]}]};Object.keys(grammar).forEach(function(e){grammar[e].forEach(function(e){e.reg||(e.reg=/(.*)/),e.format||(e.format="%s")})});
-},{}],14:[function(require,module,exports){
-var parser=require("./parser"),writer=require("./writer");exports.write=writer,exports.parse=parser.parse,exports.parseFmtpConfig=parser.parseFmtpConfig,exports.parsePayloads=parser.parsePayloads,exports.parseRemoteCandidates=parser.parseRemoteCandidates;
-},{"./parser":15,"./writer":16}],15:[function(require,module,exports){
-var toIntIfInt=function(t){return String(Number(t))===t?Number(t):t},attachProperties=function(t,r,e,n){if(n&&!e)r[n]=toIntIfInt(t[1]);else for(var a=0;a<e.length;a+=1)null!=t[a+1]&&(r[e[a]]=toIntIfInt(t[a+1]))},parseReg=function(t,r,e){var n=t.name&&t.names;t.push&&!r[t.push]?r[t.push]=[]:n&&!r[t.name]&&(r[t.name]={});var a=t.push?{}:n?r[t.name]:r;attachProperties(e.match(t.reg),a,t.names,t.name),t.push&&r[t.push].push(a)},grammar=require("./grammar"),validLine=RegExp.prototype.test.bind(/^([a-z])=(.*)/);exports.parse=function(t){var r={},e=[],n=r;return t.split(/(\r\n|\r|\n)/).filter(validLine).forEach(function(t){var r=t[0],a=t.slice(2);"m"===r&&(e.push({rtp:[],fmtp:[]}),n=e[e.length-1]);for(var p=0;p<(grammar[r]||[]).length;p+=1){var s=grammar[r][p];if(s.reg.test(a))return parseReg(s,n,a)}}),r.media=e,r};var fmtpReducer=function(t,r){var e=r.split("=");return 2===e.length&&(t[e[0]]=toIntIfInt(e[1])),t};exports.parseFmtpConfig=function(t){return t.split(/\;\s?/).reduce(fmtpReducer,{})},exports.parsePayloads=function(t){return t.split(" ").map(Number)},exports.parseRemoteCandidates=function(t){for(var r=[],e=t.split(" ").map(toIntIfInt),n=0;n<e.length;n+=3)r.push({component:e[n],ip:e[n+1],port:e[n+2]});return r};
-},{"./grammar":13}],16:[function(require,module,exports){
-var grammar=require("./grammar"),formatRegExp=/%[sdv%]/g,format=function(n){var r=1,e=arguments,a=e.length;return n.replace(formatRegExp,function(n){if(r>=a)return n;var u=e[r];switch(r+=1,n){case"%%":return"%";case"%s":return String(u);case"%d":return Number(u);case"%v":return""}})},makeLine=function(n,r,e){var a=r.format instanceof Function?r.format(r.push?e:e[r.name]):r.format,u=[n+"="+a];if(r.names)for(var m=0;m<r.names.length;m+=1){var t=r.names[m];r.name?u.push(e[r.name][t]):u.push(e[r.names[m]])}else u.push(e[r.name]);return format.apply(null,u)},defaultOuterOrder=["v","o","s","i","u","e","p","c","b","t","r","z","a"],defaultInnerOrder=["i","c","b","a"];module.exports=function(n,r){r=r||{},null==n.version&&(n.version=0),null==n.name&&(n.name=" "),n.media.forEach(function(n){null==n.payloads&&(n.payloads="")});var e=r.outerOrder||defaultOuterOrder,a=r.innerOrder||defaultInnerOrder,u=[];return e.forEach(function(r){grammar[r].forEach(function(e){e.name in n&&null!=n[e.name]?u.push(makeLine(r,e,n)):e.push in n&&null!=n[e.push]&&n[e.push].forEach(function(n){u.push(makeLine(r,e,n))})})}),n.media.forEach(function(n){u.push(makeLine("m",grammar.m[0],n)),a.forEach(function(r){grammar[r].forEach(function(e){e.name in n&&null!=n[e.name]?u.push(makeLine(r,e,n)):e.push in n&&null!=n[e.push]&&n[e.push].forEach(function(n){u.push(makeLine(r,e,n))})})})}),u.join("\r\n")+"\r\n"};
-},{"./grammar":13}],17:[function(require,module,exports){
-module.exports=function r(t){if(!t)return!1;if(this.length!=t.length)return!1;for(var e=0,n=this.length;e<n;e++)if(this[e]instanceof Array&&t[e]instanceof Array){if(!r.apply(this[e],[t[e]]))return!1}else if(this[e]!=t[e])return!1;return!0};
-},{}],18:[function(require,module,exports){
-exports.Interop=require("./interop");
-},{"./interop":19}],19:[function(require,module,exports){
-"use strict";function Interop(){this.cache={mlB2UMap:{},mlU2BMap:{}}}function addSetupAttr(e){void 0!==e.setup&&("active"===e.setup?e.setup="passive":"passive"===e.setup&&(e.setup="active"))}var transform=require("./transform"),arrayEquals=require("./array-equals");module.exports=Interop,Interop.prototype.candidateToUnifiedPlan=function(e){var r=new RTCIceCandidate(e);return r.sdpMLineIndex=this.cache.mlB2UMap[r.sdpMLineIndex],r},Interop.prototype.candidateToPlanB=function(e){var r=new RTCIceCandidate(e);if(0===r.sdpMid.indexOf("audio"))r.sdpMid="audio";else{if(0!==r.sdpMid.indexOf("video"))throw new Error("candidate with "+r.sdpMid+" not allowed");r.sdpMid="video"}return r.sdpMLineIndex=this.cache.mlU2BMap[r.sdpMLineIndex],r},Interop.prototype.getFirstSendingIndexFromAnswer=function(e){if(!this.cache.answer)return null;var r=transform.parse(this.cache.answer);if(r&&r.media&&Array.isArray(r.media))for(var i=0;i<r.media.length;i++)if(r.media[i].type==e&&(!r.media[i].direction||"sendrecv"===r.media[i].direction||"sendonly"===r.media[i].direction))return i;return null},Interop.prototype.toPlanB=function(e){var r=this;if("object"!=typeof e||null===e||"string"!=typeof e.sdp)return console.warn("An empty description was passed as an argument."),e;var i=transform.parse(e.sdp);if(void 0===i.media||!Array.isArray(i.media)||0===i.media.length)return console.warn("The description has no media."),e;if(i.media.length<=3&&i.media.every(function(e){return-1!==["video","audio","data"].indexOf(e.mid)}))return console.warn("This description does not look like Unified Plan."),e;for(var t=e.sdp,o=!1,n=0;n<i.media.length;n++){i.media[n].rtp.forEach(function(e){if("NULL"===e.codec){o=!0;var i=transform.parse(r.cache.offer);e.codec=i.media[n].rtp[0].codec}})}o&&(t=transform.write(i)),this.cache[e.type]=t;var s=i.media;i.media=[];var a={},d=[];s.forEach(function(e){if(("string"!=typeof e.rtcpMux||"rtcp-mux"!==e.rtcpMux)&&"inactive"!==e.direction)throw new Error("Cannot convert to Plan B because m-lines without the rtcp-mux attribute were found.");if(void 0!==a[e.type]&&"inactive"!==a[e.type].direction||(a[e.type]=e),e.protocol!=a[e.type].protocol)throw new Error("Cannot convert to Plan B because m-lines have different protocols and this library does not have support for that");if(e.payloads!=a[e.type].payloads)throw new Error("Cannot convert to Plan B because m-lines have different payloads and this library does not have support for that")}),s.forEach(function(e){if("application"===e.type)return i.media.push(e),void d.push(e.mid);"object"==typeof e.sources&&Object.keys(e.sources).forEach(function(r){"object"!=typeof a[e.type].sources&&(a[e.type].sources={}),a[e.type].sources[r]=e.sources[r],void 0!==e.msid&&(a[e.type].sources[r].msid=e.msid)}),void 0!==e.ssrcGroups&&Array.isArray(e.ssrcGroups)&&(void 0!==a[e.type].ssrcGroups&&Array.isArray(a[e.type].ssrcGroups)||(a[e.type].ssrcGroups=[]),a[e.type].ssrcGroups=a[e.type].ssrcGroups.concat(e.ssrcGroups)),a[e.type]===e&&(e.mid=e.type,delete e.bundleOnly,delete e.msid,e.type==s[0].type?(d.unshift(e.type),i.media.unshift(e)):(d.push(e.type),i.media.push(e)))}),void 0!==i.groups&&i.groups.some(function(e){if("BUNDLE"===e.type)return e.mids=d.join(" "),!0}),i.msidSemantic={semantic:"WMS",token:"*"};var c=transform.write(i);return new RTCSessionDescription({type:e.type,sdp:c})},Interop.prototype.toUnifiedPlan=function(e){var r=this;if("object"!=typeof e||null===e||"string"!=typeof e.sdp)return console.warn("An empty description was passed as an argument."),e;var i=transform.parse(e.sdp);if(void 0===i.media||!Array.isArray(i.media)||0===i.media.length)return console.warn("The description has no media."),e;if(i.media.length>3||!i.media.every(function(e){return-1!==["video","audio","data"].indexOf(e.mid)}))return console.warn("This description does not look like Plan B."),e;var t=[];i.media.forEach(function(e){t.push(e.mid)});var o=!1;if(void 0!==i.groups&&Array.isArray(i.groups)&&(o=i.groups.every(function(e){return"BUNDLE"!==e.type||arrayEquals.apply(e.mids.sort(),[t.sort()])})),!o){var n=!1;if(i.media.forEach(function(e){"inactive"!==e.direction&&(n=!0)}),n)throw new Error("Cannot convert to Unified Plan because m-lines that are not bundled were found.")}var s;if("answer"===e.type)s="offer";else{if("offer"!==e.type)throw new Error("Type '"+e.type+"' not supported.");s="answer"}var a;void 0!==this.cache[s]&&(a=transform.parse(this.cache[s]));var d,c,p,u,f={audio:{},video:{}},m={},y=0,l=0,v={},h={},w={},g={};if(i.media.forEach(function(i){if(("string"!=typeof i.rtcpMux||"rtcp-mux"!==i.rtcpMux)&&"inactive"!==i.direction)throw new Error("Cannot convert to Unified Plan because m-lines without the rtcp-mux attribute were found.");if("application"===i.type)return void(m[i.mid]=i);var t=i.sources,o=i.ssrcGroups,n=i.port;if(void 0!==i.candidates&&(d=void 0!==d?d.concat(i.candidates):i.candidates),void 0!==c&&void 0!==i.iceUfrag&&c!=i.iceUfrag)throw new Error("Only BUNDLE supported, iceUfrag must be the same for all m-lines.\n\tLast iceUfrag: "+c+"\n\tNew iceUfrag: "+i.iceUfrag);if(void 0!==i.iceUfrag&&(c=i.iceUfrag),void 0!==p&&void 0!==i.icePwd&&p!=i.icePwd)throw new Error("Only BUNDLE supported, icePwd must be the same for all m-lines.\n\tLast icePwd: "+p+"\n\tNew icePwd: "+i.icePwd);if(void 0!==i.icePwd&&(p=i.icePwd),void 0!==u&&void 0!==i.fingerprint&&(u.type!=i.fingerprint.type||u.hash!=i.fingerprint.hash))throw new Error("Only BUNDLE supported, fingerprint must be the same for all m-lines.\n\tLast fingerprint: "+JSON.stringify(u)+"\n\tNew fingerprint: "+JSON.stringify(i.fingerprint));void 0!==i.fingerprint&&(u=i.fingerprint),h[i.type]=i.payloads,w[i.type]=i.rtcpFb,g[i.type]=i.rtp;var s={};void 0!==o&&Array.isArray(o)&&o.forEach(function(e){void 0!==e.ssrcs&&Array.isArray(e.ssrcs)&&e.ssrcs.forEach(function(r){void 0===s[r]&&(s[r]=[]),s[r].push(e)})});var E={};if("object"==typeof t)delete i.sources,delete i.ssrcGroups,delete i.candidates,delete i.iceUfrag,delete i.icePwd,delete i.fingerprint,delete i.port,delete i.mid,Object.keys(t).forEach(function(o){var h;if("offer"===e.type&&!t[o].msid)return void(f[i.type][o]=t[o]);void 0!==s[o]&&Array.isArray(s[o])&&s[o].some(function(e){return e.ssrcs.some(function(e){if("object"==typeof E[e])return h=E[e],!0})}),"object"==typeof h?(h.sources[o]=t[o],delete t[o].msid):(h=Object.create(i),E[o]=h,void 0!==t[o].msid&&(h.msid=t[o].msid,delete t[o].msid),h.sources={},h.sources[o]=t[o],h.ssrcGroups=s[o],void 0!==a&&void 0!==a.media&&Array.isArray(a.media)&&a.media.forEach(function(e){"object"==typeof e.sources&&Object.keys(e.sources).forEach(function(r){r===o&&(h.mid=e.mid)})}),void 0===h.mid&&(h.mid=[i.type,"-",o].join("")),h.candidates=d,h.iceUfrag=c,h.icePwd=p,h.fingerprint=u,h.port=n,m[h.mid]=h,v[l]=h.sources,r.cache.mlU2BMap[l]=y,void 0===r.cache.mlB2UMap[y]&&(r.cache.mlB2UMap[y]=l),l++)});else{var U=i;U.candidates=d,U.iceUfrag=c,U.icePwd=p,U.fingerprint=u,U.port=n,m[U.mid]=U,r.cache.mlU2BMap[l]=y,void 0===r.cache.mlB2UMap[y]&&(r.cache.mlB2UMap[y]=l)}y++}),i.media=[],t=[],"answer"===e.type)for(var E=0;E<a.media.length;E++){var U=a.media[E];delete U.msid,delete U.sources,delete U.ssrcGroups,void 0===v[E]?U.direction&&"sendrecv"!==U.direction?"sendonly"===U.direction&&(U.direction="inactive"):U.direction="recvonly":U.direction&&"sendrecv"!==U.direction?"recvonly"===U.direction&&(U.direction="sendonly"):U.direction="sendrecv",U.sources=v[E],U.candidates=d,U.iceUfrag=c,U.icePwd=p,U.fingerprint=u,U.rtp=g[U.type],U.payloads=h[U.type],U.rtcpFb=w[U.type],i.media.push(U),"string"==typeof U.mid&&t.push(U.mid)}else void 0!==a&&void 0!==a.media&&Array.isArray(a.media)&&a.media.forEach(function(e){t.push(e.mid),void 0!==m[e.mid]?i.media.push(m[e.mid]):(delete e.msid,delete e.sources,delete e.ssrcGroups,e.direction&&"sendrecv"!==e.direction||(e.direction="sendonly"),e.direction&&"recvonly"!==e.direction||(e.direction="inactive"),addSetupAttr(e),i.media.push(e))}),Object.keys(m).forEach(function(e){if(-1===t.indexOf(e))if(t.push(e),"recvonly"===m[e].direction){var r=!1;i.media.some(function(i){if(("sendrecv"===i.direction||"sendonly"===i.direction)&&i.type===m[e].type)return Object.keys(m[e].sources).forEach(function(r){i.sources[r]=m[e].sources[r]}),r=!0,!0}),r||i.media.push(m[e])}else i.media.push(m[e])});["audio","video"].forEach(function(e){if(i&&i.media&&Array.isArray(i.media)){var t=null;if(Object.keys(f[e]).length>0&&null===(t=r.getFirstSendingIndexFromAnswer(e)))for(var o=0;o<i.media.length;o++)if(i.media[o].type===e){t=o;break}if(t&&i.media.length>t){var n=i.media[t];Object.keys(f[e]).forEach(function(r){n.sources&&n.sources[r]&&console.warn("Replacing an existing SSRC."),n.sources||(n.sources={}),n.sources[r]=f[e][r]})}}}),void 0!==i.groups&&i.groups.some(function(e){if("BUNDLE"===e.type)return e.mids=t.join(" "),!0}),i.msidSemantic={semantic:"WMS",token:"*"};var b=transform.write(i);return this.cache[e.type]=b,new RTCSessionDescription({type:e.type,sdp:b})};
-},{"./array-equals":17,"./transform":20}],20:[function(require,module,exports){
-var transform=require("sdp-transform");exports.write=function(s,r){return void 0!==s&&void 0!==s.media&&Array.isArray(s.media)&&s.media.forEach(function(s){void 0!==s.sources&&0!==Object.keys(s.sources).length&&(s.ssrcs=[],Object.keys(s.sources).forEach(function(r){var o=s.sources[r];Object.keys(o).forEach(function(i){s.ssrcs.push({id:r,attribute:i,value:o[i]})})}),delete s.sources),void 0!==s.ssrcGroups&&Array.isArray(s.ssrcGroups)&&s.ssrcGroups.forEach(function(s){void 0!==s.ssrcs&&Array.isArray(s.ssrcs)&&(s.ssrcs=s.ssrcs.join(" "))})}),void 0!==s&&void 0!==s.groups&&Array.isArray(s.groups)&&s.groups.forEach(function(s){void 0!==s.mids&&Array.isArray(s.mids)&&(s.mids=s.mids.join(" "))}),transform.write(s,r)},exports.parse=function(s){var r=transform.parse(s);return void 0!==r&&void 0!==r.media&&Array.isArray(r.media)&&r.media.forEach(function(s){void 0!==s.ssrcs&&Array.isArray(s.ssrcs)&&(s.sources={},s.ssrcs.forEach(function(r){s.sources[r.id]||(s.sources[r.id]={}),s.sources[r.id][r.attribute]=r.value}),delete s.ssrcs),void 0!==s.ssrcGroups&&Array.isArray(s.ssrcGroups)&&s.ssrcGroups.forEach(function(s){"string"==typeof s.ssrcs&&(s.ssrcs=s.ssrcs.split(" "))})}),void 0!==r&&void 0!==r.groups&&Array.isArray(r.groups)&&r.groups.forEach(function(s){"string"==typeof s.mids&&(s.mids=s.mids.split(" "))}),r};
-},{"sdp-transform":14}],21:[function(require,module,exports){
-!function(i,e){"use strict";var s="model",o="name",r="type",n="vendor",a="version",t="mobile",w="tablet",d={extend:function(i,e){var s={};for(var o in i)e[o]&&e[o].length%2==0?s[o]=e[o].concat(i[o]):s[o]=i[o];return s},has:function(i,e){return"string"==typeof i&&-1!==e.toLowerCase().indexOf(i.toLowerCase())},lowerize:function(i){return i.toLowerCase()},major:function(i){return"string"==typeof i?i.replace(/[^\d\.]/g,"").split(".")[0]:void 0},trim:function(i){return i.replace(/^[\s\uFEFF\xA0]+|[\s\uFEFF\xA0]+$/g,"")}},l={rgx:function(){for(var i,e,s,o,r,n,a,t=0,w=arguments;t<w.length&&!n;){var d=w[t],l=w[t+1];if(void 0===i){i={};for(o in l)l.hasOwnProperty(o)&&(r=l[o],"object"==typeof r?i[r[0]]=void 0:i[r]=void 0)}for(e=s=0;e<d.length&&!n;)if(n=d[e++].exec(this.getUA()))for(o=0;o<l.length;o++)a=n[++s],r=l[o],"object"==typeof r&&r.length>0?2==r.length?"function"==typeof r[1]?i[r[0]]=r[1].call(this,a):i[r[0]]=r[1]:3==r.length?"function"!=typeof r[1]||r[1].exec&&r[1].test?i[r[0]]=a?a.replace(r[1],r[2]):void 0:i[r[0]]=a?r[1].call(this,a,r[2]):void 0:4==r.length&&(i[r[0]]=a?r[3].call(this,a.replace(r[1],r[2])):void 0):i[r]=a||void 0;t+=2}return i},str:function(i,e){for(var s in e)if("object"==typeof e[s]&&e[s].length>0){for(var o=0;o<e[s].length;o++)if(d.has(e[s][o],i))return"?"===s?void 0:s}else if(d.has(e[s],i))return"?"===s?void 0:s;return i}},c={browser:{oldsafari:{version:{"1.0":"/8",1.2:"/1",1.3:"/3","2.0":"/412","2.0.2":"/416","2.0.3":"/417","2.0.4":"/419","?":"/"}}},device:{amazon:{model:{"Fire Phone":["SD","KF"]}},sprint:{model:{"Evo Shift 4G":"7373KT"},vendor:{HTC:"APA",Sprint:"Sprint"}}},os:{windows:{version:{ME:"4.90","NT 3.11":"NT3.51","NT 4.0":"NT4.0",2000:"NT 5.0",XP:["NT 5.1","NT 5.2"],Vista:"NT 6.0",7:"NT 6.1",8:"NT 6.2",8.1:"NT 6.3",10:["NT 6.4","NT 10.0"],RT:"ARM"}}}},u={browser:[[/(opera\smini)\/([\w\.-]+)/i,/(opera\s[mobiletab]+).+version\/([\w\.-]+)/i,/(opera).+version\/([\w\.]+)/i,/(opera)[\/\s]+([\w\.]+)/i],[o,a],[/(opios)[\/\s]+([\w\.]+)/i],[[o,"Opera Mini"],a],[/\s(opr)\/([\w\.]+)/i],[[o,"Opera"],a],[/(kindle)\/([\w\.]+)/i,/(lunascape|maxthon|netfront|jasmine|blazer)[\/\s]?([\w\.]+)*/i,/(avant\s|iemobile|slim|baidu)(?:browser)?[\/\s]?([\w\.]*)/i,/(?:ms|\()(ie)\s([\w\.]+)/i,/(rekonq)\/([\w\.]+)*/i,/(chromium|flock|rockmelt|midori|epiphany|silk|skyfire|ovibrowser|bolt|iron|vivaldi|iridium|phantomjs)\/([\w\.-]+)/i],[o,a],[/(trident).+rv[:\s]([\w\.]+).+like\sgecko/i],[[o,"IE"],a],[/(edge)\/((\d+)?[\w\.]+)/i],[o,a],[/(yabrowser)\/([\w\.]+)/i],[[o,"Yandex"],a],[/(comodo_dragon)\/([\w\.]+)/i],[[o,/_/g," "],a],[/(micromessenger)\/([\w\.]+)/i],[[o,"WeChat"],a],[/xiaomi\/miuibrowser\/([\w\.]+)/i],[a,[o,"MIUI Browser"]],[/\swv\).+(chrome)\/([\w\.]+)/i],[[o,/(.+)/,"$1 WebView"],a],[/android.+samsungbrowser\/([\w\.]+)/i,/android.+version\/([\w\.]+)\s+(?:mobile\s?safari|safari)*/i],[a,[o,"Android Browser"]],[/(chrome|omniweb|arora|[tizenoka]{5}\s?browser)\/v?([\w\.]+)/i,/(qqbrowser)[\/\s]?([\w\.]+)/i],[o,a],[/(uc\s?browser)[\/\s]?([\w\.]+)/i,/ucweb.+(ucbrowser)[\/\s]?([\w\.]+)/i,/juc.+(ucweb)[\/\s]?([\w\.]+)/i],[[o,"UCBrowser"],a],[/(dolfin)\/([\w\.]+)/i],[[o,"Dolphin"],a],[/((?:android.+)crmo|crios)\/([\w\.]+)/i],[[o,"Chrome"],a],[/;fbav\/([\w\.]+);/i],[a,[o,"Facebook"]],[/fxios\/([\w\.-]+)/i],[a,[o,"Firefox"]],[/version\/([\w\.]+).+?mobile\/\w+\s(safari)/i],[a,[o,"Mobile Safari"]],[/version\/([\w\.]+).+?(mobile\s?safari|safari)/i],[a,o],[/webkit.+?(mobile\s?safari|safari)(\/[\w\.]+)/i],[o,[a,l.str,c.browser.oldsafari.version]],[/(konqueror)\/([\w\.]+)/i,/(webkit|khtml)\/([\w\.]+)/i],[o,a],[/(navigator|netscape)\/([\w\.-]+)/i],[[o,"Netscape"],a],[/(swiftfox)/i,/(icedragon|iceweasel|camino|chimera|fennec|maemo\sbrowser|minimo|conkeror)[\/\s]?([\w\.\+]+)/i,/(firefox|seamonkey|k-meleon|icecat|iceape|firebird|phoenix)\/([\w\.-]+)/i,/(mozilla)\/([\w\.]+).+rv\:.+gecko\/\d+/i,/(polaris|lynx|dillo|icab|doris|amaya|w3m|netsurf|sleipnir)[\/\s]?([\w\.]+)/i,/(links)\s\(([\w\.]+)/i,/(gobrowser)\/?([\w\.]+)*/i,/(ice\s?browser)\/v?([\w\._]+)/i,/(mosaic)[\/\s]([\w\.]+)/i],[o,a]],cpu:[[/(?:(amd|x(?:(?:86|64)[_-])?|wow|win)64)[;\)]/i],[["architecture","amd64"]],[/(ia32(?=;))/i],[["architecture",d.lowerize]],[/((?:i[346]|x)86)[;\)]/i],[["architecture","ia32"]],[/windows\s(ce|mobile);\sppc;/i],[["architecture","arm"]],[/((?:ppc|powerpc)(?:64)?)(?:\smac|;|\))/i],[["architecture",/ower/,"",d.lowerize]],[/(sun4\w)[;\)]/i],[["architecture","sparc"]],[/((?:avr32|ia64(?=;))|68k(?=\))|arm(?:64|(?=v\d+;))|(?=atmel\s)avr|(?:irix|mips|sparc)(?:64)?(?=;)|pa-risc)/i],[["architecture",d.lowerize]]],device:[[/\((ipad|playbook);[\w\s\);-]+(rim|apple)/i],[s,n,[r,w]],[/applecoremedia\/[\w\.]+ \((ipad)/],[s,[n,"Apple"],[r,w]],[/(apple\s{0,1}tv)/i],[[s,"Apple TV"],[n,"Apple"]],[/(archos)\s(gamepad2?)/i,/(hp).+(touchpad)/i,/(hp).+(tablet)/i,/(kindle)\/([\w\.]+)/i,/\s(nook)[\w\s]+build\/(\w+)/i,/(dell)\s(strea[kpr\s\d]*[\dko])/i],[n,s,[r,w]],[/(kf[A-z]+)\sbuild\/[\w\.]+.*silk\//i],[s,[n,"Amazon"],[r,w]],[/(sd|kf)[0349hijorstuw]+\sbuild\/[\w\.]+.*silk\//i],[[s,l.str,c.device.amazon.model],[n,"Amazon"],[r,t]],[/\((ip[honed|\s\w*]+);.+(apple)/i],[s,n,[r,t]],[/\((ip[honed|\s\w*]+);/i],[s,[n,"Apple"],[r,t]],[/(blackberry)[\s-]?(\w+)/i,/(blackberry|benq|palm(?=\-)|sonyericsson|acer|asus|dell|huawei|meizu|motorola|polytron)[\s_-]?([\w-]+)*/i,/(hp)\s([\w\s]+\w)/i,/(asus)-?(\w+)/i],[n,s,[r,t]],[/\(bb10;\s(\w+)/i],[s,[n,"BlackBerry"],[r,t]],[/android.+(transfo[prime\s]{4,10}\s\w+|eeepc|slider\s\w+|nexus 7|padfone)/i],[s,[n,"Asus"],[r,w]],[/(sony)\s(tablet\s[ps])\sbuild\//i,/(sony)?(?:sgp.+)\sbuild\//i],[[n,"Sony"],[s,"Xperia Tablet"],[r,w]],[/(?:sony)?(?:(?:(?:c|d)\d{4})|(?:so[-l].+))\sbuild\//i],[[n,"Sony"],[s,"Xperia Phone"],[r,t]],[/\s(ouya)\s/i,/(nintendo)\s([wids3u]+)/i],[n,s,[r,"console"]],[/android.+;\s(shield)\sbuild/i],[s,[n,"Nvidia"],[r,"console"]],[/(playstation\s[34portablevi]+)/i],[s,[n,"Sony"],[r,"console"]],[/(sprint\s(\w+))/i],[[n,l.str,c.device.sprint.vendor],[s,l.str,c.device.sprint.model],[r,t]],[/(lenovo)\s?(S(?:5000|6000)+(?:[-][\w+]))/i],[n,s,[r,w]],[/(htc)[;_\s-]+([\w\s]+(?=\))|\w+)*/i,/(zte)-(\w+)*/i,/(alcatel|geeksphone|huawei|lenovo|nexian|panasonic|(?=;\s)sony)[_\s-]?([\w-]+)*/i],[n,[s,/_/g," "],[r,t]],[/(nexus\s9)/i],[s,[n,"HTC"],[r,w]],[/(nexus\s6p)/i],[s,[n,"Huawei"],[r,t]],[/(microsoft);\s(lumia[\s\w]+)/i],[n,s,[r,t]],[/[\s\(;](xbox(?:\sone)?)[\s\);]/i],[s,[n,"Microsoft"],[r,"console"]],[/(kin\.[onetw]{3})/i],[[s,/\./g," "],[n,"Microsoft"],[r,t]],[/\s(milestone|droid(?:[2-4x]|\s(?:bionic|x2|pro|razr))?(:?\s4g)?)[\w\s]+build\//i,/mot[\s-]?(\w+)*/i,/(XT\d{3,4}) build\//i,/(nexus\s6)/i],[s,[n,"Motorola"],[r,t]],[/android.+\s(mz60\d|xoom[\s2]{0,2})\sbuild\//i],[s,[n,"Motorola"],[r,w]],[/hbbtv\/\d+\.\d+\.\d+\s+\([\w\s]*;\s*(\w[^;]*);([^;]*)/i],[[n,d.trim],[s,d.trim],[r,"smarttv"]],[/hbbtv.+maple;(\d+)/i],[[s,/^/,"SmartTV"],[n,"Samsung"],[r,"smarttv"]],[/\(dtv[\);].+(aquos)/i],[s,[n,"Sharp"],[r,"smarttv"]],[/android.+((sch-i[89]0\d|shw-m380s|gt-p\d{4}|gt-n\d+|sgh-t8[56]9|nexus 10))/i,/((SM-T\w+))/i],[[n,"Samsung"],s,[r,w]],[/smart-tv.+(samsung)/i],[n,[r,"smarttv"],s],[/((s[cgp]h-\w+|gt-\w+|galaxy\snexus|sm-\w[\w\d]+))/i,/(sam[sung]*)[\s-]*(\w+-?[\w-]*)*/i,/sec-((sgh\w+))/i],[[n,"Samsung"],s,[r,t]],[/sie-(\w+)*/i],[s,[n,"Siemens"],[r,t]],[/(maemo|nokia).*(n900|lumia\s\d+)/i,/(nokia)[\s_-]?([\w-]+)*/i],[[n,"Nokia"],s,[r,t]],[/android\s3\.[\s\w;-]{10}(a\d{3})/i],[s,[n,"Acer"],[r,w]],[/android\s3\.[\s\w;-]{10}(lg?)-([06cv9]{3,4})/i],[[n,"LG"],s,[r,w]],[/(lg) netcast\.tv/i],[n,s,[r,"smarttv"]],[/(nexus\s[45])/i,/lg[e;\s\/-]+(\w+)*/i],[s,[n,"LG"],[r,t]],[/android.+(ideatab[a-z0-9\-\s]+)/i],[s,[n,"Lenovo"],[r,w]],[/linux;.+((jolla));/i],[n,s,[r,t]],[/((pebble))app\/[\d\.]+\s/i],[n,s,[r,"wearable"]],[/android.+;\s(glass)\s\d/i],[s,[n,"Google"],[r,"wearable"]],[/android.+(\w+)\s+build\/hm\1/i,/android.+(hm[\s\-_]*note?[\s_]*(?:\d\w)?)\s+build/i,/android.+(mi[\s\-_]*(?:one|one[\s_]plus|note lte)?[\s_]*(?:\d\w)?)\s+build/i],[[s,/_/g," "],[n,"Xiaomi"],[r,t]],[/android.+a000(1)\s+build/i],[s,[n,"OnePlus"],[r,t]],[/\s(tablet)[;\/]/i,/\s(mobile)(?:[;\/]|\ssafari)/i],[[r,d.lowerize],n,s]],engine:[[/windows.+\sedge\/([\w\.]+)/i],[a,[o,"EdgeHTML"]],[/(presto)\/([\w\.]+)/i,/(webkit|trident|netfront|netsurf|amaya|lynx|w3m)\/([\w\.]+)/i,/(khtml|tasman|links)[\/\s]\(?([\w\.]+)/i,/(icab)[\/\s]([23]\.[\d\.]+)/i],[o,a],[/rv\:([\w\.]+).*(gecko)/i],[a,o]],os:[[/microsoft\s(windows)\s(vista|xp)/i],[o,a],[/(windows)\snt\s6\.2;\s(arm)/i,/(windows\sphone(?:\sos)*)[\s\/]?([\d\.\s]+\w)*/i,/(windows\smobile|windows)[\s\/]?([ntce\d\.\s]+\w)/i],[o,[a,l.str,c.os.windows.version]],[/(win(?=3|9|n)|win\s9x\s)([nt\d\.]+)/i],[[o,"Windows"],[a,l.str,c.os.windows.version]],[/\((bb)(10);/i],[[o,"BlackBerry"],a],[/(blackberry)\w*\/?([\w\.]+)*/i,/(tizen)[\/\s]([\w\.]+)/i,/(android|webos|palm\sos|qnx|bada|rim\stablet\sos|meego|contiki)[\/\s-]?([\w\.]+)*/i,/linux;.+(sailfish);/i],[o,a],[/(symbian\s?os|symbos|s60(?=;))[\/\s-]?([\w\.]+)*/i],[[o,"Symbian"],a],[/\((series40);/i],[o],[/mozilla.+\(mobile;.+gecko.+firefox/i],[[o,"Firefox OS"],a],[/(nintendo|playstation)\s([wids34portablevu]+)/i,/(mint)[\/\s\(]?(\w+)*/i,/(mageia|vectorlinux)[;\s]/i,/(joli|[kxln]?ubuntu|debian|[open]*suse|gentoo|(?=\s)arch|slackware|fedora|mandriva|centos|pclinuxos|redhat|zenwalk|linpus)[\/\s-]?(?!chrom)([\w\.-]+)*/i,/(hurd|linux)\s?([\w\.]+)*/i,/(gnu)\s?([\w\.]+)*/i],[o,a],[/(cros)\s[\w]+\s([\w\.]+\w)/i],[[o,"Chromium OS"],a],[/(sunos)\s?([\w\.]+\d)*/i],[[o,"Solaris"],a],[/\s([frentopc-]{0,4}bsd|dragonfly)\s?([\w\.]+)*/i],[o,a],[/(haiku)\s(\w+)/i],[o,a],[/(ip[honead]+)(?:.*os\s([\w]+)*\slike\smac|;\sopera)/i],[[o,"iOS"],[a,/_/g,"."]],[/(mac\sos\sx)\s?([\w\s\.]+\w)*/i,/(macintosh|mac(?=_powerpc)\s)/i],[[o,"Mac OS"],[a,/_/g,"."]],[/((?:open)?solaris)[\/\s-]?([\w\.]+)*/i,/(aix)\s((\d)(?=\.|\)|\s)[\w\.]*)*/i,/(plan\s9|minix|beos|os\/2|amigaos|morphos|risc\sos|openvms)/i,/(unix)\s?([\w\.]+)*/i],[o,a]]},p=function(e,s){if(!(this instanceof p))return new p(e,s).getResult();var o=e||(i&&i.navigator&&i.navigator.userAgent?i.navigator.userAgent:""),r=s?d.extend(u,s):u;return this.getBrowser=function(){var i=l.rgx.apply(this,r.browser);return i.major=d.major(i.version),i},this.getCPU=function(){return l.rgx.apply(this,r.cpu)},this.getDevice=function(){return l.rgx.apply(this,r.device)},this.getEngine=function(){return l.rgx.apply(this,r.engine)},this.getOS=function(){return l.rgx.apply(this,r.os)},this.getResult=function(){return{ua:this.getUA(),browser:this.getBrowser(),engine:this.getEngine(),os:this.getOS(),device:this.getDevice(),cpu:this.getCPU()}},this.getUA=function(){return o},this.setUA=function(i){return o=i,this},this};p.VERSION="0.7.12",p.BROWSER={NAME:o,MAJOR:"major",VERSION:a},p.CPU={ARCHITECTURE:"architecture"},p.DEVICE={MODEL:s,VENDOR:n,TYPE:r,CONSOLE:"console",MOBILE:t,SMARTTV:"smarttv",TABLET:w,WEARABLE:"wearable",EMBEDDED:"embedded"},p.ENGINE={NAME:o,VERSION:a},p.OS={NAME:o,VERSION:a},"undefined"!=typeof exports?("undefined"!=typeof module&&module.exports&&(exports=module.exports=p),exports.UAParser=p):"function"==typeof define&&define.amd?define(function(){return p}):i.UAParser=p;var m=i.jQuery||i.Zepto;if(void 0!==m){var b=new p;m.ua=b.getResult(),m.ua.get=function(){return b.getUA()},m.ua.set=function(i){b.setUA(i);var e=b.getResult();for(var s in e)m.ua[s]=e[s]}}}("object"==typeof window?window:this);
-},{}],22:[function(require,module,exports){
-(function (global){
-var rng,crypto=global.crypto||global.msCrypto;if(crypto&&crypto.getRandomValues){var _rnds8=new Uint8Array(16);rng=function(){return crypto.getRandomValues(_rnds8),_rnds8}}if(!rng){var _rnds=new Array(16);rng=function(){for(var r,n=0;n<16;n++)0==(3&n)&&(r=4294967296*Math.random()),_rnds[n]=r>>>((3&n)<<3)&255;return _rnds}}module.exports=rng;
-}).call(this,typeof global !== "undefined" ? global : typeof self !== "undefined" ? self : typeof window !== "undefined" ? window : {})
-
-},{}],23:[function(require,module,exports){
-function parse(e,s,r){var t=s&&r||0,n=0;for(s=s||[],e.toLowerCase().replace(/[0-9a-f]{2}/g,function(e){n<16&&(s[t+n++]=_hexToByte[e])});n<16;)s[t+n++]=0;return s}function unparse(e,s){var r=s||0,t=_byteToHex;return t[e[r++]]+t[e[r++]]+t[e[r++]]+t[e[r++]]+"-"+t[e[r++]]+t[e[r++]]+"-"+t[e[r++]]+t[e[r++]]+"-"+t[e[r++]]+t[e[r++]]+"-"+t[e[r++]]+t[e[r++]]+t[e[r++]]+t[e[r++]]+t[e[r++]]+t[e[r++]]}function v1(e,s,r){var t=s&&r||0,n=s||[];e=e||{};var o=void 0!==e.clockseq?e.clockseq:_clockseq,a=void 0!==e.msecs?e.msecs:(new Date).getTime(),u=void 0!==e.nsecs?e.nsecs:_lastNSecs+1,c=a-_lastMSecs+(u-_lastNSecs)/1e4;if(c<0&&void 0===e.clockseq&&(o=o+1&16383),(c<0||a>_lastMSecs)&&void 0===e.nsecs&&(u=0),u>=1e4)throw new Error("uuid.v1(): Can't create more than 10M uuids/sec");_lastMSecs=a,_lastNSecs=u,_clockseq=o,a+=122192928e5;var i=(1e4*(268435455&a)+u)%4294967296;n[t++]=i>>>24&255,n[t++]=i>>>16&255,n[t++]=i>>>8&255,n[t++]=255&i;var _=a/4294967296*1e4&268435455;n[t++]=_>>>8&255,n[t++]=255&_,n[t++]=_>>>24&15|16,n[t++]=_>>>16&255,n[t++]=o>>>8|128,n[t++]=255&o;for(var d=e.node||_nodeId,v=0;v<6;v++)n[t+v]=d[v];return s||unparse(n)}function v4(e,s,r){var t=s&&r||0;"string"==typeof e&&(s="binary"==e?new Array(16):null,e=null),e=e||{};var n=e.random||(e.rng||_rng)();if(n[6]=15&n[6]|64,n[8]=63&n[8]|128,s)for(var o=0;o<16;o++)s[t+o]=n[o];return s||unparse(n)}for(var _rng=require("./rng"),_byteToHex=[],_hexToByte={},i=0;i<256;i++)_byteToHex[i]=(i+256).toString(16).substr(1),_hexToByte[_byteToHex[i]]=i;var _seedBytes=_rng(),_nodeId=[1|_seedBytes[0],_seedBytes[1],_seedBytes[2],_seedBytes[3],_seedBytes[4],_seedBytes[5]],_clockseq=16383&(_seedBytes[6]<<8|_seedBytes[7]),_lastMSecs=0,_lastNSecs=0,uuid=v4;uuid.v1=v1,uuid.v4=v4,uuid.parse=parse,uuid.unparse=unparse,module.exports=uuid;
-},{"./rng":22}],24:[function(require,module,exports){
-function WildEmitter(){}module.exports=WildEmitter,WildEmitter.mixin=function(t){var l=t.prototype||t;l.isWildEmitter=!0,l.on=function(t,l,i){this.callbacks=this.callbacks||{};var s=3===arguments.length,c=s?arguments[1]:void 0,a=s?arguments[2]:arguments[1];return a._groupName=c,(this.callbacks[t]=this.callbacks[t]||[]).push(a),this},l.once=function(t,l,i){function s(){c.off(t,s),h.apply(this,arguments)}var c=this,a=3===arguments.length,e=a?arguments[1]:void 0,h=a?arguments[2]:arguments[1];return this.on(t,e,s),this},l.releaseGroup=function(t){this.callbacks=this.callbacks||{};var l,i,s,c;for(l in this.callbacks)for(c=this.callbacks[l],i=0,s=c.length;i<s;i++)c[i]._groupName===t&&(c.splice(i,1),i--,s--);return this},l.off=function(t,l){this.callbacks=this.callbacks||{};var i,s=this.callbacks[t];return s?1===arguments.length?(delete this.callbacks[t],this):(i=s.indexOf(l),s.splice(i,1),0===s.length&&delete this.callbacks[t],this):this},l.emit=function(t){this.callbacks=this.callbacks||{};var l,i,s,c=[].slice.call(arguments,1),a=this.callbacks[t],e=this.getWildcardCallbacks(t);if(a)for(s=a.slice(),l=0,i=s.length;l<i&&s[l];++l)s[l].apply(this,c);if(e)for(i=e.length,s=e.slice(),l=0,i=s.length;l<i&&s[l];++l)s[l].apply(this,[t].concat(c));return this},l.getWildcardCallbacks=function(t){this.callbacks=this.callbacks||{};var l,i,s=[];for(l in this.callbacks)i=l.split("*"),("*"===l||2===i.length&&t.slice(0,i[0].length)===i[0])&&(s=s.concat(this.callbacks[l]));return s}},WildEmitter.mixin(WildEmitter);
-},{}]},{},[2])(2)
-});
-
-
-//# sourceMappingURL=kurento-utils.map
\ No newline at end of file
diff --git a/bigbluebutton-client/resources/prod/lib/reconnecting-websocket.min.js b/bigbluebutton-client/resources/prod/lib/reconnecting-websocket.min.js
new file mode 100644
index 0000000000000000000000000000000000000000..3015099ac17bb3ec057e545e21a4db524265d74f
--- /dev/null
+++ b/bigbluebutton-client/resources/prod/lib/reconnecting-websocket.min.js
@@ -0,0 +1 @@
+!function(a,b){"function"==typeof define&&define.amd?define([],b):"undefined"!=typeof module&&module.exports?module.exports=b():a.ReconnectingWebSocket=b()}(this,function(){function a(b,c,d){function l(a,b){var c=document.createEvent("CustomEvent");return c.initCustomEvent(a,!1,!1,b),c}var e={debug:!1,automaticOpen:!0,reconnectInterval:1e3,maxReconnectInterval:3e4,reconnectDecay:1.5,timeoutInterval:2e3};d||(d={});for(var f in e)this[f]="undefined"!=typeof d[f]?d[f]:e[f];this.url=b,this.reconnectAttempts=0,this.readyState=WebSocket.CONNECTING,this.protocol=null;var h,g=this,i=!1,j=!1,k=document.createElement("div");k.addEventListener("open",function(a){g.onopen(a)}),k.addEventListener("close",function(a){g.onclose(a)}),k.addEventListener("connecting",function(a){g.onconnecting(a)}),k.addEventListener("message",function(a){g.onmessage(a)}),k.addEventListener("error",function(a){g.onerror(a)}),this.addEventListener=k.addEventListener.bind(k),this.removeEventListener=k.removeEventListener.bind(k),this.dispatchEvent=k.dispatchEvent.bind(k),this.open=function(b){h=new WebSocket(g.url,c||[]),b||k.dispatchEvent(l("connecting")),(g.debug||a.debugAll)&&console.debug("ReconnectingWebSocket","attempt-connect",g.url);var d=h,e=setTimeout(function(){(g.debug||a.debugAll)&&console.debug("ReconnectingWebSocket","connection-timeout",g.url),j=!0,d.close(),j=!1},g.timeoutInterval);h.onopen=function(){clearTimeout(e),(g.debug||a.debugAll)&&console.debug("ReconnectingWebSocket","onopen",g.url),g.protocol=h.protocol,g.readyState=WebSocket.OPEN,g.reconnectAttempts=0;var d=l("open");d.isReconnect=b,b=!1,k.dispatchEvent(d)},h.onclose=function(c){if(clearTimeout(e),h=null,i)g.readyState=WebSocket.CLOSED,k.dispatchEvent(l("close"));else{g.readyState=WebSocket.CONNECTING;var d=l("connecting");d.code=c.code,d.reason=c.reason,d.wasClean=c.wasClean,k.dispatchEvent(d),b||j||((g.debug||a.debugAll)&&console.debug("ReconnectingWebSocket","onclose",g.url),k.dispatchEvent(l("close")));var e=g.reconnectInterval*Math.pow(g.reconnectDecay,g.reconnectAttempts);setTimeout(function(){g.reconnectAttempts++,g.open(!0)},e>g.maxReconnectInterval?g.maxReconnectInterval:e)}},h.onmessage=function(b){(g.debug||a.debugAll)&&console.debug("ReconnectingWebSocket","onmessage",g.url,b.data);var c=l("message");c.data=b.data,k.dispatchEvent(c)},h.onerror=function(b){(g.debug||a.debugAll)&&console.debug("ReconnectingWebSocket","onerror",g.url,b),k.dispatchEvent(l("error"))}},1==this.automaticOpen&&this.open(!1),this.send=function(b){if(h)return(g.debug||a.debugAll)&&console.debug("ReconnectingWebSocket","send",g.url,b),h.send(b);throw"INVALID_STATE_ERR : Pausing to reconnect websocket"},this.close=function(a,b){"undefined"==typeof a&&(a=1e3),i=!0,h&&h.close(a,b)},this.refresh=function(){h&&h.close()}}return a.prototype.onopen=function(){},a.prototype.onclose=function(){},a.prototype.onconnecting=function(){},a.prototype.onmessage=function(){},a.prototype.onerror=function(){},a.debugAll=!1,a.CONNECTING=WebSocket.CONNECTING,a.OPEN=WebSocket.OPEN,a.CLOSING=WebSocket.CLOSING,a.CLOSED=WebSocket.CLOSED,a});
diff --git a/bigbluebutton-client/src/org/bigbluebutton/core/TimerUtil.as b/bigbluebutton-client/src/org/bigbluebutton/core/TimerUtil.as
old mode 100644
new mode 100755
index c997d978643fff0c1373576afe0b7d1396de4458..8f526a8a975f45a55cfd43764b3c05438f9ff880
--- a/bigbluebutton-client/src/org/bigbluebutton/core/TimerUtil.as
+++ b/bigbluebutton-client/src/org/bigbluebutton/core/TimerUtil.as
@@ -21,24 +21,35 @@ package org.bigbluebutton.core {
 	import flash.events.TimerEvent;
 	import flash.utils.Dictionary;
 	import flash.utils.Timer;
-
+	
+	import mx.controls.Alert;
 	import mx.controls.Label;
-
-	import org.bigbluebutton.util.i18n.ResourceUtil;
+	import mx.managers.PopUpManager;
+	
+	import org.bigbluebutton.util.i18n.ResourceUtil;
 
 	public final class TimerUtil {
 		public static var timers:Dictionary = new Dictionary(true);
 
-		public static function setCountDownTimer(label:Label, seconds:int):void {
+		public static function setCountDownTimer(label:Label, seconds:int, showMinuteWarning:Boolean=false):void {
 			var timer:Timer = getTimer(label.id, seconds);
+			var minuteWarningShown:Boolean = false;
+			var minuteAlert:Alert = null;
 			if (!timer.hasEventListener(TimerEvent.TIMER)) {
 				timer.addEventListener(TimerEvent.TIMER, function():void {
 					var remainingSeconds:int = timer.repeatCount - timer.currentCount;
 					var formattedTime:String = (Math.floor(remainingSeconds / 60)) + ":" + (remainingSeconds % 60 >= 10 ? "" : "0") + (remainingSeconds % 60);
 					label.text = formattedTime;
+					if (remainingSeconds < 60 && showMinuteWarning && !minuteWarningShown) {
+						minuteAlert = Alert.show(ResourceUtil.getInstance().getString('bbb.users.breakout.closewarning.text'));
+						minuteWarningShown = true;
+					}
 				});
 				timer.addEventListener(TimerEvent.TIMER_COMPLETE, function():void {
 					label.text = ResourceUtil.getInstance().getString('bbb.users.breakout.closing');
+					if (minuteAlert != null) {
+						PopUpManager.removePopUp(minuteAlert);
+					}
 				});
 			} else {
 				timer.stop();
diff --git a/bigbluebutton-client/src/org/bigbluebutton/core/model/Meeting.as b/bigbluebutton-client/src/org/bigbluebutton/core/model/Meeting.as
index bcb722efa2e73665e23dcd26bdf6da290c546191..4e830b5aaa34322b5281f48bcc5570cfd31312f8 100755
--- a/bigbluebutton-client/src/org/bigbluebutton/core/model/Meeting.as
+++ b/bigbluebutton-client/src/org/bigbluebutton/core/model/Meeting.as
@@ -17,6 +17,7 @@ package org.bigbluebutton.core.model
     public var webcamsOnlyForModerator:Boolean = false;
     public var metadata:Object = null;
     public var muteOnStart:Boolean = false;
-   
+    public var customLogo:String = "";
+    public var customCopyright:String = "";
   }
 }
\ No newline at end of file
diff --git a/bigbluebutton-client/src/org/bigbluebutton/main/model/options/MeetingOptions.as b/bigbluebutton-client/src/org/bigbluebutton/main/model/options/MeetingOptions.as
deleted file mode 100644
index 46d64b267dbeeb61c0636d4b91c27da137b5836b..0000000000000000000000000000000000000000
--- a/bigbluebutton-client/src/org/bigbluebutton/main/model/options/MeetingOptions.as
+++ /dev/null
@@ -1,31 +0,0 @@
-/**
- * BigBlueButton open source conferencing system - http://www.bigbluebutton.org/
- *
- * Copyright (c) 2015 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.main.model.options {
-	import org.bigbluebutton.core.Options;
-
-	public class MeetingOptions extends Options {
-
-		[Bindable]
-		public var muteOnStart:Boolean = false;
-
-		public function MeetingOptions() {
-			name = "meeting";
-		}
-	}
-}
diff --git a/bigbluebutton-client/src/org/bigbluebutton/main/model/users/EnterApiResponse.as b/bigbluebutton-client/src/org/bigbluebutton/main/model/users/EnterApiResponse.as
index 75e82373b018508fcacd6920f7627620b4f10bc4..9f64e43ab01607d721f206503babec22d67c3238 100755
--- a/bigbluebutton-client/src/org/bigbluebutton/main/model/users/EnterApiResponse.as
+++ b/bigbluebutton-client/src/org/bigbluebutton/main/model/users/EnterApiResponse.as
@@ -11,13 +11,14 @@ package org.bigbluebutton.main.model.users
     public var authToken: String;
     public var customdata:Object = new Object();
     public var logoutUrl: String;
-	public var logoutTimer : int;
+    public var logoutTimer : int;
     public var defaultLayout: String;
     public var avatarURL: String;
     public var dialnumber: String;
     public var voiceConf: String;
     public var welcome: String;
-
+    public var customLogo:String;
+    public var customCopyright:String;
     public var meetingName: String;
     public var extMeetingId: String;
     public var intMeetingId: String;
@@ -28,5 +29,6 @@ package org.bigbluebutton.main.model.users
     public var webcamsOnlyForModerator: Boolean;
     public var metadata: Object = new Object();
     public var modOnlyMessage: String;
+		public var muteOnStart:Boolean = false;
   }
 }
\ No newline at end of file
diff --git a/bigbluebutton-client/src/org/bigbluebutton/main/model/users/JoinService.as b/bigbluebutton-client/src/org/bigbluebutton/main/model/users/JoinService.as
index 4123ea05060ef2d85a75cc97aee884a0573f1c6c..cae0f8b1863c0faa1f1514214f5cf5d2da7f0904 100755
--- a/bigbluebutton-client/src/org/bigbluebutton/main/model/users/JoinService.as
+++ b/bigbluebutton-client/src/org/bigbluebutton/main/model/users/JoinService.as
@@ -118,7 +118,7 @@ package org.bigbluebutton.main.model.users
     
     private function handleComplete(e:Event):void {			
       var result:Object = JSON.parse(e.target.data);
-      
+
       var logData:Object = UsersUtil.initLogData();
       logData.tags = ["initialization"];
       
@@ -158,9 +158,10 @@ package org.bigbluebutton.main.model.users
 
         apiResponse.welcome = result.response.welcome;
         apiResponse.logoutUrl = processLogoutUrl(result.response);
-		apiResponse.logoutTimer = result.response.logoutTimer;
+        apiResponse.logoutTimer = result.response.logoutTimer;
         apiResponse.defaultLayout = result.response.defaultLayout;
-        apiResponse.avatarURL = result.response.avatarURL
+        apiResponse.avatarURL = result.response.avatarURL;
+        
         apiResponse.customdata = new Object();
         
         if (result.response.customdata) {
@@ -178,7 +179,11 @@ package org.bigbluebutton.main.model.users
         if (result.response.hasOwnProperty("modOnlyMessage")) {
           apiResponse.modOnlyMessage = result.response.modOnlyMessage;
         }
-        
+				
+				apiResponse.customLogo = result.response.customLogoURL;
+        apiResponse.customCopyright = result.response.customCopyright;
+				apiResponse.muteOnStart = result.response.muteOnStart as Boolean;
+				
         if (_resultListener != null) _resultListener(true, apiResponse);
       }
       
diff --git a/bigbluebutton-client/src/org/bigbluebutton/main/model/users/UserService.as b/bigbluebutton-client/src/org/bigbluebutton/main/model/users/UserService.as
index 65d13894007e22b47379a62e55c017f212650ada..5011a5a141d58c089816d7d654994ef7198d7b84 100755
--- a/bigbluebutton-client/src/org/bigbluebutton/main/model/users/UserService.as
+++ b/bigbluebutton-client/src/org/bigbluebutton/main/model/users/UserService.as
@@ -22,6 +22,7 @@ package org.bigbluebutton.main.model.users
 	
 	import flash.external.ExternalInterface;
 	import flash.net.NetConnection;
+	
 	import org.as3commons.logging.api.ILogger;
 	import org.as3commons.logging.api.getClassLogger;
 	import org.bigbluebutton.core.BBB;
@@ -40,7 +41,6 @@ package org.bigbluebutton.main.model.users
 	import org.bigbluebutton.main.events.SuccessfulLoginEvent;
 	import org.bigbluebutton.main.events.UserServicesEvent;
 	import org.bigbluebutton.main.model.options.ApplicationOptions;
-	import org.bigbluebutton.main.model.options.MeetingOptions;
 	import org.bigbluebutton.main.model.users.events.BroadcastStartedEvent;
 	import org.bigbluebutton.main.model.users.events.BroadcastStoppedEvent;
 	import org.bigbluebutton.main.model.users.events.ChangeRoleEvent;
@@ -49,8 +49,9 @@ package org.bigbluebutton.main.model.users
 	import org.bigbluebutton.main.model.users.events.KickUserEvent;
 	import org.bigbluebutton.main.model.users.events.RoleChangeEvent;
 	import org.bigbluebutton.main.model.users.events.UsersConnectionEvent;
+	import org.bigbluebutton.modules.users.events.MeetingMutedEvent;
 	import org.bigbluebutton.modules.users.services.MessageReceiver;
-	import org.bigbluebutton.modules.users.services.MessageSender;
+	import org.bigbluebutton.modules.users.services.MessageSender;
 
 	public class UserService {
 		private static const LOGGER:ILogger = getClassLogger(UserService);      
@@ -96,8 +97,7 @@ package org.bigbluebutton.main.model.users
 		
 		private function joinListener(success:Boolean, result: EnterApiResponse):void {
 			if (success) {        
-				var meetingOptions : MeetingOptions = Options.getOptions(MeetingOptions) as MeetingOptions;
-        
+
         LiveMeeting.inst().me.id = result.intUserId
         LiveMeeting.inst().me.name = result.username;
         LiveMeeting.inst().me.externalId = result.extUserId;
@@ -128,8 +128,10 @@ package org.bigbluebutton.main.model.users
         LiveMeeting.inst().meeting.allowStartStopRecording = result.allowStartStopRecording;
         LiveMeeting.inst().meeting.webcamsOnlyForModerator = result.webcamsOnlyForModerator;
         LiveMeeting.inst().meeting.metadata = result.metadata;
-        LiveMeeting.inst().meeting.muteOnStart = meetingOptions.muteOnStart;
-        
+        LiveMeeting.inst().meeting.muteOnStart = result.muteOnStart;
+				LiveMeeting.inst().meetingStatus.isMeetingMuted = result.muteOnStart;
+        LiveMeeting.inst().meeting.customLogo = result.customLogo;
+				LiveMeeting.inst().meeting.customCopyright = result.customCopyright;
 				
 				// assign the meeting name to the document title
 				ExternalInterface.call("setTitle", result.meetingName);
@@ -137,6 +139,8 @@ package org.bigbluebutton.main.model.users
 				var e:ConferenceCreatedEvent = new ConferenceCreatedEvent(ConferenceCreatedEvent.CONFERENCE_CREATED_EVENT);
 				dispatcher.dispatchEvent(e);
 				
+				// Send event to trigger meeting muted initialization of meeting (ralam dec 21, 2017)
+				dispatcher.dispatchEvent(new MeetingMutedEvent());
 				connect();
 			}
 		}
diff --git a/bigbluebutton-client/src/org/bigbluebutton/main/views/MainApplicationShell.mxml b/bigbluebutton-client/src/org/bigbluebutton/main/views/MainApplicationShell.mxml
index 0e83c132d6f695282b1ca13cc90b93861d324f8d..7be3ab4585794140f9cda79d84c380f52198c858 100755
--- a/bigbluebutton-client/src/org/bigbluebutton/main/views/MainApplicationShell.mxml
+++ b/bigbluebutton-client/src/org/bigbluebutton/main/views/MainApplicationShell.mxml
@@ -84,6 +84,7 @@ with BigBlueButton; if not, see <http://www.gnu.org/licenses/>.
 		<mate:Listener type="{BBBEvent.WAITING_FOR_MODERATOR_ACCEPTANCE}" method="openWaitWindow" />
 		<mate:Listener type="{BBBEvent.RECONNECT_DISCONNECTED_EVENT}" method="closeWaitWindow"/>
     	<mate:Listener type="{RoundTripLatencyReceivedEvent.ROUND_TRIP_LATENCY_RECEIVED}" method="handleRoundTripLatencyReceivedEvent"/>
+		<mate:Listener type="{ConferenceCreatedEvent.CONFERENCE_CREATED_EVENT}" method="handleConferenceCreatedEvent" />
     
 	</fx:Declarations>
 	<fx:Script>
@@ -125,6 +126,7 @@ with BigBlueButton; if not, see <http://www.gnu.org/licenses/>.
 			import org.bigbluebutton.core.events.NewGuestWaitingEvent;
 			import org.bigbluebutton.core.events.RoundTripLatencyReceivedEvent;
 			import org.bigbluebutton.core.events.SwitchedLayoutEvent;
+			import org.bigbluebutton.core.model.LiveMeeting;
 			import org.bigbluebutton.core.vo.LockSettingsVO;
 			import org.bigbluebutton.main.events.AppVersionEvent;
 			import org.bigbluebutton.main.events.BBBEvent;
@@ -143,6 +145,7 @@ with BigBlueButton; if not, see <http://www.gnu.org/licenses/>.
 			import org.bigbluebutton.main.model.options.BrowserVersionsOptions;
 			import org.bigbluebutton.main.model.options.LanguageOptions;
 			import org.bigbluebutton.main.model.options.LayoutOptions;
+			import org.bigbluebutton.main.model.users.events.ConferenceCreatedEvent;
 			import org.bigbluebutton.main.model.users.events.ConnectionFailedEvent;
 			import org.bigbluebutton.modules.phone.events.AudioSelectionWindowEvent;
 			import org.bigbluebutton.modules.phone.events.FlashMicSettingsEvent;
@@ -188,7 +191,7 @@ with BigBlueButton; if not, see <http://www.gnu.org/licenses/>.
 			
 			[Bindable] private var showToolbarOpt:Boolean = true;
 			[Bindable] private var _showToolbar:Boolean = true;
-			private const DEFAULT_TOOLBAR_HEIGHT:Number = 50;
+			public static const DEFAULT_TOOLBAR_HEIGHT:Number = 50;
 			[Bindable] private var toolbarHeight:Number = DEFAULT_TOOLBAR_HEIGHT;
 			[Bindable] private var showFooterOpt:Boolean = true;
 			[Bindable] private var _showFooter:Boolean = true;
@@ -245,12 +248,20 @@ with BigBlueButton; if not, see <http://www.gnu.org/licenses/>.
 				_respTimer.start();
 			}
 			
+			private function handleConferenceCreatedEvent(event:ConferenceCreatedEvent):void {
+				updateCopyrightText();
+			}
+			
 			private function updateCopyrightText():void {
 				if (StringUtils.isEmpty(brandingOptions.copyright)) {
 					copyrightText = ResourceUtil.getInstance().getString('bbb.mainshell.copyrightLabel2',[appVersion]);
 				} else {
 					copyrightText = String(brandingOptions.copyright).replace("{0}", appVersion);
 				}
+				
+				if (!StringUtils.isEmpty(LiveMeeting.inst().meeting.customCopyright)) {
+					copyrightText = LiveMeeting.inst().meeting.customCopyright;
+				}
 			}
 			
 			private function initQuote() : void {
@@ -685,7 +696,7 @@ with BigBlueButton; if not, see <http://www.gnu.org/licenses/>.
                 if (e is ConnectionFailedEvent) {
                     showlogoutWindow((e as ConnectionFailedEvent).type);
                 } else
-                    showlogoutWindow("You have logged out of the conference");
+                    showlogoutWindow(ConnectionFailedEvent.USER_EJECTED_FROM_MEETING);
             }
 
             private function handleExitApplicationEvent(e:ExitApplicationEvent = null):void {
@@ -762,7 +773,10 @@ with BigBlueButton; if not, see <http://www.gnu.org/licenses/>.
 
 			private function updateToolbarHeight():void {
 				if (toolbarHeight != 0) {
-					toolbarHeight = Math.max(DEFAULT_TOOLBAR_HEIGHT, toolbar.logo.height + toolbar.quickLinks.includeInLayout ? toolbar.quickLinks.height : 0);
+					toolbarHeight = DEFAULT_TOOLBAR_HEIGHT;
+					if (toolbar.quickLinks.includeInLayout) {
+						toolbarHeight += toolbar.quickLinks.height;
+					}
 					if (UsersUtil.isBreakout()) {
 						toolbarHeight += toolbar.breakoutRibbon.height;
 					}
diff --git a/bigbluebutton-client/src/org/bigbluebutton/main/views/MainToolbar.mxml b/bigbluebutton-client/src/org/bigbluebutton/main/views/MainToolbar.mxml
index 6f025457530d125c63cce4c05671624cb0d5ad58..e6f0bcc07d35c723782824906f14156ef0090eea 100755
--- a/bigbluebutton-client/src/org/bigbluebutton/main/views/MainToolbar.mxml
+++ b/bigbluebutton-client/src/org/bigbluebutton/main/views/MainToolbar.mxml
@@ -151,7 +151,7 @@ with BigBlueButton; if not, see <http://www.gnu.org/licenses/>.
 				if (!Accessibility.active) {
 					quickLinks.removeAllChildren();
 				} else {
-					quickLinks.includeInLayout = true;
+					quickLinks.visible = quickLinks.includeInLayout = true;
 				}
 			}
 
@@ -178,14 +178,14 @@ with BigBlueButton; if not, see <http://www.gnu.org/licenses/>.
 			  break;
          }
        }
-								
+
 			public function displayToolbar():void{
 				if (toolbarOptions.showToolbar) {
 					showToolbar = true;
 				} else {
 					showToolbar = false;
 				}
-				
+
 				if (toolbarOptions.showHelpButton) {
 					showHelpBtn = true;
 				} else {
@@ -200,7 +200,7 @@ with BigBlueButton; if not, see <http://www.gnu.org/licenses/>.
 				if (!timeRemaining.visible && e.timeLeftInSec <= 1800) {
 					timeRemaining.visible = true;
 				}
-				TimerUtil.setCountDownTimer(timeRemaining, e.timeLeftInSec);
+				TimerUtil.setCountDownTimer(timeRemaining, e.timeLeftInSec, true);
 			}
 
 			private function retrieveMeetingName(e:ConferenceCreatedEvent):void {
@@ -211,13 +211,14 @@ with BigBlueButton; if not, see <http://www.gnu.org/licenses/>.
 					}
 				}
 
-				if (UsersUtil.isBreakout()) {
-					breakoutRibbon.visible = breakoutRibbon.includeInLayout = true;
-					var sequence:String = StringUtils.substringAfterLast(UsersUtil.getMeetingName(), " ");
-					sequence = StringUtils.substringBefore(sequence, ")");
-					breakoutLabel.text = ResourceUtil.getInstance().getString("bbb.users.breakout.youareinroom", [sequence]);
+				var customLogo: String = LiveMeeting.inst().meeting.customLogo;
+				
+				if (customLogo != "") {
+					logo.source = LiveMeeting.inst().meeting.customLogo;
 				}
 				
+				initBreakoutRibbon();
+				
 				if (LiveMeeting.inst().me.logoutTimer > 0 ) {
 					idleLogoutButton.startTimer(LiveMeeting.inst().me.logoutTimer);
 				} else {
@@ -226,6 +227,15 @@ with BigBlueButton; if not, see <http://www.gnu.org/licenses/>.
 
 				logFlashPlayerCapabilities();
 			}
+			
+			private function initBreakoutRibbon() : void {
+				if (UsersUtil.isBreakout()) {
+					breakoutRibbon.visible = breakoutRibbon.includeInLayout = true;
+					var sequence:String = StringUtils.substringAfterLast(UsersUtil.getMeetingName(), " ");
+					sequence = StringUtils.substringBefore(sequence, ")");
+					breakoutLabel.text = ResourceUtil.getInstance().getString("bbb.users.breakout.youareinroom", [sequence]);
+				}
+			}
 
       private function refreshModeratorButtonsVisibility(e:*):void {
           showGuestSettingsButton = UsersUtil.amIModerator() && usersOptions.enableGuestUI;
@@ -422,6 +432,8 @@ with BigBlueButton; if not, see <http://www.gnu.org/licenses/>.
 				}
 				
 				btnLogout.styleName = "logoutButtonStyle" + styleNameExt;
+				
+				initBreakoutRibbon();
 			}
 
 			private function openSettings(e:Event = null):void{
@@ -531,10 +543,10 @@ with BigBlueButton; if not, see <http://www.gnu.org/licenses/>.
 						   tabIndices="{[recordBtn, webRTCAudioStatus, shortcutKeysBtn, helpBtn, btnLogout]}"/>
 	</fx:Declarations>
 
-	<mx:VBox id="mainBox" styleName="toolbarMainBox" width="100%" horizontalScrollPolicy="off" verticalAlign="top">
+	<mx:VBox id="mainBox" styleName="toolbarMainBox" width="100%" height="100%" horizontalScrollPolicy="off">
 		<!-- Breakout room  Ribbon-->
 		<mx:HBox id="breakoutRibbon" width="100%" height="30"
-				 styleName="breakoutRoomRibbon" 
+				 styleName="breakoutRoomRibbon"
 				 visible="false" includeInLayout="false" >
 			<mx:Label id="breakoutLabel" />
 			<mx:Label text="|" visible="{timeRemaining.visible}" includeInLayout="{timeRemaining.visible}"/>
@@ -543,18 +555,18 @@ with BigBlueButton; if not, see <http://www.gnu.org/licenses/>.
 					  toolTip="{ResourceUtil.getInstance().getString('bbb.users.breakout.timerForRoom.toolTip')}"/>
 		</mx:HBox>
 		<!-- Top bar -->
-		<mx:HBox id="topBox" width="100%" verticalAlign="middle" horizontalScrollPolicy="off" styleName="topBoxStyle">
-			<mx:HBox id="titleBox" width="40%" horizontalAlign="left" verticalAlign="middle" horizontalScrollPolicy="off">
+		<mx:HBox id="topBox" width="100%" height="{MainApplicationShell.DEFAULT_TOOLBAR_HEIGHT}" verticalAlign="middle" horizontalScrollPolicy="off" styleName="topBoxStyle">
+			<mx:HBox id="titleBox" width="40%" height="100%" horizontalAlign="left" verticalAlign="middle" horizontalScrollPolicy="off">
 				<mx:Image id="logo" right="20" maxHeight="35" ioError="hideLogo()" />
 				<mx:VRule id="logoSperatator" styleName="toolbarSeparator" height="10" />
 				<mx:Label id="meetingNameLbl" minWidth="1" maxWidth="{titleBox.width - logo.width - 20}" styleName="meetingNameLabelStyle" truncateToFit="true"/>
 			</mx:HBox>
-			<mx:HBox id="actionBox" width="30%" horizontalAlign="center" verticalAlign="middle" horizontalScrollPolicy="off">
+			<mx:HBox id="actionBox" width="30%" height="100%" horizontalAlign="center" verticalAlign="middle" horizontalScrollPolicy="off">
 				<mx:HBox id="addedBtnsMicrophone" />
 				<mx:HBox id="addedBtnsWebcam" />
 				<mx:HBox id="addedBtnsDeskShare" />
 			</mx:HBox>
-			<mx:HBox id="rightBox" width="40%" horizontalAlign="right" verticalAlign="middle" horizontalScrollPolicy="off">
+			<mx:HBox id="rightBox" width="40%" height="100%" horizontalAlign="right" verticalAlign="middle" horizontalScrollPolicy="off">
 				<views:RecordButton id="recordBtn" visible="{showRecordButton}" includeInLayout="{showRecordButton}"/>
 				<views:WebRTCAudioStatus id="webRTCAudioStatus" height="30"/>
 				
@@ -592,7 +604,7 @@ with BigBlueButton; if not, see <http://www.gnu.org/licenses/>.
 		</mx:HBox>
 
 		<!-- Accessibilty Quick Links -->
-		<mx:HBox id="quickLinks" includeInLayout="false" width="100%" horizontalAlign="center">
+		<mx:HBox id="quickLinks" visible="false" includeInLayout="false" width="100%" height="30" horizontalAlign="center" verticalAlign="middle">
 			<mx:LinkButton id="usersLinkBtn" click="onQuickLinkClicked('users')" label="{ResourceUtil.getInstance().getString('bbb.users.quickLink.label')}"
 						   accessibilityDescription="{usersLinkBtn.label}" toolTip="{usersLinkBtn.label}"
 						   height="30" styleName="quickWindowLinkStyle" />
diff --git a/bigbluebutton-client/src/org/bigbluebutton/modules/chat/views/AddChatTabBox.mxml b/bigbluebutton-client/src/org/bigbluebutton/modules/chat/views/AddChatTabBox.mxml
index 70155be04396be5833ed5606a65963b35a2c3003..b298c9ede90c7dd5741616921f4dc8bd5249f089 100755
--- a/bigbluebutton-client/src/org/bigbluebutton/modules/chat/views/AddChatTabBox.mxml
+++ b/bigbluebutton-client/src/org/bigbluebutton/modules/chat/views/AddChatTabBox.mxml
@@ -36,28 +36,29 @@ with BigBlueButton; if not, see <http://www.gnu.org/licenses/>.
   
     <fx:Script>
         <![CDATA[
-          import com.asfusion.mate.events.Dispatcher;
-          
-          import mx.collections.ArrayCollection;
-          import mx.controls.Alert;
-          import mx.events.CloseEvent;
-          
-          import org.as3commons.logging.api.ILogger;
-          import org.as3commons.logging.api.getClassLogger;
-          import org.bigbluebutton.core.EventConstants;
-          import org.bigbluebutton.core.Options;
-          import org.bigbluebutton.core.UsersUtil;
-          import org.bigbluebutton.core.events.CoreEvent;
-          import org.bigbluebutton.core.events.LockControlEvent;
-          import org.bigbluebutton.core.model.LiveMeeting;
-          import org.bigbluebutton.main.events.UserJoinedEvent;
-          import org.bigbluebutton.main.events.UserLeftEvent;
-          import org.bigbluebutton.main.model.users.events.ChangeMyRole;
-          import org.bigbluebutton.modules.chat.events.ChatNoiseEnabledEvent;
-          import org.bigbluebutton.modules.chat.events.ChatOptionsEvent;
-          import org.bigbluebutton.modules.chat.events.ChatToolbarButtonEvent;
-          import org.bigbluebutton.modules.chat.model.ChatOptions;
-          import org.bigbluebutton.util.i18n.ResourceUtil;
+			import com.asfusion.mate.events.Dispatcher;
+			
+			import mx.collections.ArrayCollection;
+			import mx.controls.Alert;
+			import mx.events.CloseEvent;
+			
+			import org.as3commons.logging.api.ILogger;
+			import org.as3commons.logging.api.getClassLogger;
+			import org.bigbluebutton.core.EventConstants;
+			import org.bigbluebutton.core.Options;
+			import org.bigbluebutton.core.UsersUtil;
+			import org.bigbluebutton.core.events.CoreEvent;
+			import org.bigbluebutton.core.events.LockControlEvent;
+			import org.bigbluebutton.core.model.LiveMeeting;
+			import org.bigbluebutton.main.events.UserJoinedEvent;
+			import org.bigbluebutton.main.events.UserLeftEvent;
+			import org.bigbluebutton.main.model.users.events.ChangeMyRole;
+			import org.bigbluebutton.modules.chat.events.ChatNoiseEnabledEvent;
+			import org.bigbluebutton.modules.chat.events.ChatOptionsEvent;
+			import org.bigbluebutton.modules.chat.events.ChatToolbarButtonEvent;
+			import org.bigbluebutton.modules.chat.model.ChatOptions;
+			import org.bigbluebutton.modules.users.model.BreakoutRoomsOptions;
+			import org.bigbluebutton.util.i18n.ResourceUtil;
             
 			private static const LOGGER:ILogger = getClassLogger(AddChatTabBox);      
 			
@@ -65,7 +66,8 @@ with BigBlueButton; if not, see <http://www.gnu.org/licenses/>.
             [Bindable] public var chatView:ChatView;
             [Bindable] private var fontSizes:Array = ['8', '10', '12', '14', '16', '18'];
             
-            [Bindable] public var chatOptions:ChatOptions;		
+			[Bindable] public var chatOptions:ChatOptions;		
+			[Bindable] public var breakoutOptions:BreakoutRoomsOptions;		
           [Bindable] private var clrBtnVisible:Boolean = false;
           
           private var globalDispatcher:Dispatcher = new Dispatcher();
@@ -87,11 +89,15 @@ with BigBlueButton; if not, see <http://www.gnu.org/licenses/>.
               handler.populateAllUsers()
                 users = handler.users;
                 chatOptions = Options.getOptions(ChatOptions) as ChatOptions;
+				breakoutOptions = Options.getOptions(BreakoutRoomsOptions) as BreakoutRoomsOptions;
 
-                if (!chatOptions.privateEnabled) {
+                if ((!UsersUtil.isBreakout() && !chatOptions.privateEnabled) ||
+					(UsersUtil.isBreakout() && !breakoutOptions.privateChateEnabled )
+				) {
                     usersList.includeInLayout = usersList.visible = false;
+					lblSelect.includeInLayout = lblSelect.visible = false;
                 }
-                
+
                 if (fontSizes.indexOf(chatOptions.fontSize) != -1) {
                     cmbFontSize.selectedItem = chatOptions.fontSize;
                     changeFontSize(); // have to manually call it because the change event doesn't fire
@@ -200,10 +206,8 @@ with BigBlueButton; if not, see <http://www.gnu.org/licenses/>.
 	</fx:Declarations>
     
     <common:AdvancedLabel id="lblSelect" styleName="chatOptionsLabel" width="100%"
-			  text="{ResourceUtil.getInstance().getString('bbb.chat.privateChatSelect')}" 
-			  visible="{chatOptions.privateEnabled}" includeInLayout="{chatOptions.privateEnabled}"/>
+			  text="{ResourceUtil.getInstance().getString('bbb.chat.privateChatSelect')}" />
     <mx:List id="usersList" height="50%" width="100%" dataProvider="{users}" dragEnabled="false" 
-             visible="{chatOptions.privateEnabled}" includeInLayout="{chatOptions.privateEnabled}"
              itemRenderer="org.bigbluebutton.modules.chat.views.UserRenderer"
              labelField="name"
              itemClick="openPrivateChat(event)"
diff --git a/bigbluebutton-client/src/org/bigbluebutton/modules/chat/views/ChatWindowEventHandler.as b/bigbluebutton-client/src/org/bigbluebutton/modules/chat/views/ChatWindowEventHandler.as
index a00e298ceda2083cc08deb1a7a6b425bb46b1a7d..5387844119d087c92dc5f9c0c9b9e79b8130270a 100755
--- a/bigbluebutton-client/src/org/bigbluebutton/modules/chat/views/ChatWindowEventHandler.as
+++ b/bigbluebutton-client/src/org/bigbluebutton/modules/chat/views/ChatWindowEventHandler.as
@@ -1,6 +1,7 @@
 package org.bigbluebutton.modules.chat.views
 {
   import mx.collections.ArrayCollection;
+  import mx.collections.Sort;
   
   import org.bigbluebutton.core.UsersUtil;
   import org.bigbluebutton.core.model.LiveMeeting;
@@ -11,11 +12,15 @@ package org.bigbluebutton.modules.chat.views
   public class ChatWindowEventHandler
   {
     [Bindable] public var users:ArrayCollection = new ArrayCollection();
-    
-    
+
+	private var sort:Sort;
+
     public function ChatWindowEventHandler()
     {
-      users.refresh();
+		sort = new Sort();
+		sort.compareFunction = sortFunction;
+		users.sort = sort;
+        users.refresh();
     }
     
     public function populateAllUsers():void {
@@ -67,6 +72,23 @@ package org.bigbluebutton.modules.chat.views
     public function handleUserLeftEvent(userId: String):void {
       removeUser(userId, users);
     }
-    
+
+	private function sortFunction(a:Object, b:Object, array:Array = null):int {
+		/*
+		* Check name (case-insensitive) in the event of a tie up above. If the name
+		* is the same then use userID which should be unique making the order the same
+		* across all clients.
+		*/
+		if (a.name.toLowerCase() < b.name.toLowerCase())
+			return -1;
+		else if (a.name.toLowerCase() > b.name.toLowerCase())
+			return 1;
+		else if (a.userId.toLowerCase() > b.userId.toLowerCase())
+			return -1;
+		else if (a.userId.toLowerCase() < b.userId.toLowerCase())
+			return 1;
+		return 0;
+	}
+
   }
-}
\ No newline at end of file
+}
diff --git a/bigbluebutton-client/src/org/bigbluebutton/modules/users/model/BreakoutRoomsOptions.as b/bigbluebutton-client/src/org/bigbluebutton/modules/users/model/BreakoutRoomsOptions.as
index 9bf93d783993d4182fb0a0c20cda97dd5ceabc93..74589a46aad10e7bf977dfdd8aa3cd8dca98b33a 100644
--- a/bigbluebutton-client/src/org/bigbluebutton/modules/users/model/BreakoutRoomsOptions.as
+++ b/bigbluebutton-client/src/org/bigbluebutton/modules/users/model/BreakoutRoomsOptions.as
@@ -27,6 +27,9 @@ package org.bigbluebutton.modules.users.model {
 		[Bindable]
 		public var record:Boolean = true;
 
+		[Bindable]
+		public var privateChateEnabled:Boolean = true;
+
 		public function BreakoutRoomsOptions() {
 			name = "breakoutRooms";
 		}
diff --git a/bigbluebutton-client/src/org/bigbluebutton/modules/users/services/MessageReceiver.as b/bigbluebutton-client/src/org/bigbluebutton/modules/users/services/MessageReceiver.as
index 1100f5c2ca28630f96da3e04c27b1259e39a16de..5abe2ee166bca9e59f40b336ab51397477ac9ad0 100755
--- a/bigbluebutton-client/src/org/bigbluebutton/modules/users/services/MessageReceiver.as
+++ b/bigbluebutton-client/src/org/bigbluebutton/modules/users/services/MessageReceiver.as
@@ -63,6 +63,8 @@ package org.bigbluebutton.modules.users.services
     
     public var onAllowedToJoin:Function = null;
     private static var globalDispatcher:Dispatcher = new Dispatcher();
+
+    private static var flashWebcamPattern:RegExp = /^([A-z0-9]+)-([A-z0-9]+)-([A-z0-9]+)$/;
     
     public function MessageReceiver() {
       BBB.initConnectionManager().addMessageListener(this);
@@ -434,15 +436,18 @@ package org.bigbluebutton.modules.users.services
         var userId: String = media.userId as String;
         var attributes: Object = media.attributes as Object;
         var viewers: Array = media.viewers as Array;
-        
-        var webcamStream: MediaStream = new MediaStream(streamId, userId);
-        webcamStream.streamId = streamId;
-        webcamStream.userId = userId;
-        webcamStream.attributes = attributes;
-        webcamStream.viewers = viewers;
-        
-        LOGGER.debug("STREAM = " + JSON.stringify(webcamStream));
-        LiveMeeting.inst().webcams.add(webcamStream);
+
+
+        if (isValidFlashWebcamStream(streamId)) {
+          var webcamStream: MediaStream = new MediaStream(streamId, userId);
+          webcamStream.streamId = streamId;
+          webcamStream.userId = userId;
+          webcamStream.attributes = attributes;
+          webcamStream.viewers = viewers;
+
+          LOGGER.debug("STREAM = " + JSON.stringify(webcamStream));
+          LiveMeeting.inst().webcams.add(webcamStream);
+        }
       }
     }
     
@@ -487,7 +492,7 @@ package org.bigbluebutton.modules.users.services
       logData.tags = ["users"];
       logData.status = "user_ejected";
       logData.message = "User ejected from meeting.";
-      LOGGER.info(JSON.stringify(logData));
+      LOGGER.debug(JSON.stringify(logData));
     }
     
     private function handleUserLocked(msg:Object):void {
@@ -698,21 +703,23 @@ package org.bigbluebutton.modules.users.services
     private function handleUserBroadcastCamStartedEvtMsg(msg:Object):void {
       var userId: String = msg.body.userId as String; 
       var streamId: String = msg.body.stream as String;
-      
       var logData:Object = UsersUtil.initLogData();
       logData.tags = ["webcam"];
       logData.message = "UserBroadcastCamStartedEvtMsg server message";
       logData.user.webcamStream = streamId;
-      LOGGER.info(JSON.stringify(logData));
-      
-      var mediaStream: MediaStream = new MediaStream(streamId, userId)
-      LiveMeeting.inst().webcams.add(mediaStream);
-      
-      var webUser: User2x = UsersUtil.getUser(userId);
-      if (webUser != null) {
-        sendStreamStartedEvent(userId, webUser.name, streamId);
+
+      if (isValidFlashWebcamStream(streamId)) {
+
+        LOGGER.info(JSON.stringify(logData));
+
+        var mediaStream: MediaStream = new MediaStream(streamId, userId)
+          LiveMeeting.inst().webcams.add(mediaStream);
+
+        var webUser: User2x = UsersUtil.getUser(userId);
+        if (webUser != null) {
+          sendStreamStartedEvent(userId, webUser.name, streamId);
+        }
       }
-      
     }
     
     private function sendStreamStartedEvent(userId: String, name: String, stream: String):void{
@@ -839,6 +846,10 @@ package org.bigbluebutton.modules.users.services
       }
     }
     
+    private function isValidFlashWebcamStream(streamId: String):Boolean{
+      return flashWebcamPattern.test(streamId);
+    }
+
     public function handleGuestPolicyChanged(msg:Object):void {
       var header: Object = msg.header as Object;
       var body: Object = msg.body as Object;
diff --git a/bigbluebutton-config/web/index_html5_vs_flash.html b/bigbluebutton-config/web/index_html5_vs_flash.html
index 0a7e2fb139de4d9a6888ecdfa806e6e7b0dd0328..fb92c0d8a9d21807db2734abf0e6ca83ce1f1cfd 100644
--- a/bigbluebutton-config/web/index_html5_vs_flash.html
+++ b/bigbluebutton-config/web/index_html5_vs_flash.html
@@ -85,12 +85,12 @@
        <div >
           <h2>BigBlueButton HTML5 client test server</h2>
           <p> <a href="http://bigbluebutton.org/" target="_blank">BigBlueButton</a> is an open source web conferencing system for on-line learning. This is a public test server for the BigBlueButton <a href="http://docs.bigbluebutton.org/html/html5-overview.html">HTML5 client</a> currently under development.</p>
-	<p> Our goal for the upcoming release of the HTML5 client is to implement all the <a href="https://youtu.be/oh0bEk3YSwI">viewer capabilities</a> of the Flash client.  Students join online classes as a viewer.  The HTML5 client will give remote students the ability to join from their Android mobile devices.  Users using the Flash and HTML5 clients can join the same meeting (hence the two choices above).  We built the HTML5 client using web real-time communication (WebRTC), <a href="https://facebook.github.io/react/">React</a>, and <a href="https://www.mongodb.com/">MongoDB</a>.</p>
-	<p>  The HTML5 works well with desktop and Android devices (phone and tablets) as they all support WebRTC.  Apple does not (yet) support WebRTC in Safari for iOS devices, but don't worry -- we are working in parallel on app for iOS devices.   What can this developer build of the HTML5 client do right now?  Pretty much everything the Flash client can do for viewers except (a) view a desktop sharing stream from the presenter and (b) send/receive webcam streams.  We're working on (a) and (b).  For now, we are really happy to share with you our progress and get <a href="https://docs.google.com/forms/d/1gFz5JdN3vD6jxhlVskFYgtEKEcexdDnUzpkwUXwQ4OY/viewform?usp=send_for">your feedback</a> on what has been implemeted so far.  Enjoy!</p>
+        <p> Our goal for the upcoming release of the HTML5 client is to implement all the <a href="https://youtu.be/oh0bEk3YSwI">viewer capabilities</a> of the Flash client.  Students join online classes as a viewer.  The HTML5 client will give remote students the ability to join from their Android and Apple (iOS 11+) devices.  Users using the Flash and HTML5 clients can join the same meeting (hence the two choices above).  We built the HTML5 client using web real-time communication (WebRTC), <a href="https://facebook.github.io/react/">React</a>, and <a href="https://www.mongodb.com/">MongoDB</a>.</p>
+        <p>  What can this developer build of the HTML5 client do right now?  Pretty much everything the Flash client can do for viewers except (a) view a desktop sharing stream from the presenter and (b) send/receive webcam streams.  We're working on (a) and (b).  For now, we are really happy to share with you our progress and get <a href="https://docs.google.com/forms/d/1gFz5JdN3vD6jxhlVskFYgtEKEcexdDnUzpkwUXwQ4OY/viewform?usp=send_for">your feedback</a> on what has been implemeted so far.  Enjoy!</p>
 
           <h4>For Developers</h4>
           <p> The BigBlueButton project is <a href="http://bigbluebutton.org/support">supported</a> by a community of developers that care about good design and a streamlined user experience. </p>
-          <p>See <a href="/demo/demo1.jsp" target="_blank">API examples </a> for how to integrate BigBlueButton with your project.</p>
+          <p>See <a href="http://docs.bigblubutton.org" target="_blank">Documentation</a> for more information on how you can integrate BigBlueButton with your project.</p>
         </div>
         <div class="span one"></div>
 
@@ -98,6 +98,7 @@
 
 
 
+
       <hr class="featurette-divider">
 
       <!-- BigBlueButton Features -->
diff --git a/bigbluebutton-html5/.meteor/packages b/bigbluebutton-html5/.meteor/packages
index 4e0798450fd859f0aa48f292616b23ca22add1ba..ba2f9a3eaf3e33ce6b109584f62126ae2ddc181e 100644
--- a/bigbluebutton-html5/.meteor/packages
+++ b/bigbluebutton-html5/.meteor/packages
@@ -3,7 +3,6 @@
 # 'meteor add' and 'meteor remove' will edit this file for you,
 # but you can also edit it by hand.
 
-4commerce:env-settings
 standard-app-packages@1.0.9
 arunoda:npm@0.2.6
 amplify
@@ -15,11 +14,11 @@ cfs:power-queue
 cfs:reactive-list
 cfs:micro-queue
 reactive-var@1.0.11
-ecmascript@0.8.1
+ecmascript@0.9.0
 react-meteor-data
-standard-minifier-css@1.3.4
-standard-minifier-js@2.1.1
+standard-minifier-css@1.3.5
+standard-minifier-js@2.2.0
 nathantreid:css-modules
-shell-server@0.2.4
-http@1.2.12
-dynamic-import@0.1.1
+shell-server@0.3.0
+http@1.3.0
+dynamic-import@0.2.0
diff --git a/bigbluebutton-html5/.meteor/release b/bigbluebutton-html5/.meteor/release
index 1e7fc5b564ccbd425b5a7ee92916415e2d81e0e3..56a7a07fee76d4cf2cf564cfb7a2c134ea8f74cc 100644
--- a/bigbluebutton-html5/.meteor/release
+++ b/bigbluebutton-html5/.meteor/release
@@ -1 +1 @@
-METEOR@1.5.1
+METEOR@1.6.0.1
diff --git a/bigbluebutton-html5/.meteor/versions b/bigbluebutton-html5/.meteor/versions
index bb0f238f9d25923dc74d55b2eb7b905bbb144f4e..67cecfd531c4b5594f28711bd8c8a3ca5378ec2f 100644
--- a/bigbluebutton-html5/.meteor/versions
+++ b/bigbluebutton-html5/.meteor/versions
@@ -1,16 +1,15 @@
-4commerce:env-settings@1.2.0
 aldeed:simple-schema@1.5.3
-allow-deny@1.0.6
+allow-deny@1.1.0
 amplify@1.0.0
 arunoda:npm@0.2.6
 autoupdate@1.3.12
-babel-compiler@6.19.4
-babel-runtime@1.0.1
+babel-compiler@6.24.7
+babel-runtime@1.1.1
 base64@1.0.10
 binary-heap@1.0.10
 blaze@2.3.2
 blaze-tools@1.0.10
-boilerplate-generator@1.1.1
+boilerplate-generator@1.3.1
 caching-compiler@1.1.9
 caching-html-compiler@1.1.2
 callback-hook@1.0.10
@@ -21,74 +20,74 @@ cfs:reactive-list@0.0.9
 cfs:reactive-property@0.0.4
 check@1.2.5
 clinical:nightwatch@2.0.1
-coffeescript@1.12.6_1
-ddp@1.3.0
-ddp-client@2.0.0
-ddp-common@1.2.9
-ddp-server@2.0.0
+coffeescript@1.12.7_3
+coffeescript-compiler@1.12.7_3
+ddp@1.4.0
+ddp-client@2.2.0
+ddp-common@1.3.0
+ddp-server@2.1.1
 deps@1.0.12
 diff-sequence@1.0.7
-dynamic-import@0.1.1
-ecmascript@0.8.2
-ecmascript-runtime@0.4.1
-ecmascript-runtime-client@0.4.3
-ecmascript-runtime-server@0.4.1
-ejson@1.0.13
+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
+ejson@1.1.0
 fastclick@1.0.13
 francocatena:status@1.5.3
 geojson-utils@1.0.10
 html-tools@1.0.11
 htmljs@1.0.11
-http@1.2.12
+http@1.3.0
 id-map@1.0.9
 jquery@1.11.10
 launch-screen@1.1.1
 livedata@1.0.18
-logging@1.1.17
+logging@1.1.19
 mdg:validation-error@0.5.1
-meteor@1.7.0
+meteor@1.8.2
 meteor-platform@1.2.6
-meteorblackbelt:underscore-deep@0.0.4
 meteorspark:util@0.2.0
 minifier-css@1.2.16
-minifier-js@2.1.1
-minimongo@1.2.1
+minifier-js@2.2.2
+minimongo@1.4.3
 mizzao:timesync@0.5.0
 mobile-status-bar@1.0.14
-modules@0.9.2
-modules-runtime@0.8.0
-mongo@1.1.22
+modules@0.11.2
+modules-runtime@0.9.1
+mongo@1.3.1
+mongo-dev-server@1.1.0
 mongo-id@1.0.6
-nathantreid:css-modules@2.7.3
-npm-mongo@2.2.30
+nathantreid:css-modules@2.8.0
+npm-mongo@2.2.33
 observe-sequence@1.0.16
 ordered-dict@1.0.9
-promise@0.8.9
+promise@0.10.0
 raix:eventemitter@0.1.3
 random@1.0.10
 react-meteor-data@0.2.15
-reactive-dict@1.1.9
+reactive-dict@1.2.0
 reactive-var@1.0.11
 reload@1.1.11
 retry@1.0.9
 routepolicy@1.0.12
 session@1.1.7
-shell-server@0.2.4
+shell-server@0.3.1
 spacebars@1.0.15
-spacebars-compiler@1.1.2
+spacebars-compiler@1.1.3
 standard-app-packages@1.0.9
-standard-minifier-css@1.3.4
-standard-minifier-js@2.1.1
+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.2
+templating-compiler@1.3.3
 templating-runtime@1.3.2
 templating-tools@1.1.2
 tmeasday:check-npm-versions@0.3.1
 tracker@1.1.3
-udondan:yml@3.2.2_1
 ui@1.0.13
 underscore@1.0.10
 url@1.1.0
-webapp@1.3.17
+webapp@1.4.0
 webapp-hashing@1.0.9
diff --git a/bigbluebutton-html5/client/main.html b/bigbluebutton-html5/client/main.html
index a6755761ce70f542831a8bfef0278de062de1746..9a2ddb62a87da032a79f56a3b56b96ad81417825 100644
--- a/bigbluebutton-html5/client/main.html
+++ b/bigbluebutton-html5/client/main.html
@@ -53,8 +53,14 @@
   <script src="/client/lib/jquery.json-2.4.min.js"></script>
   <script src="/client/lib/verto-min.js"></script>
   <script src="/client/lib/verto_extension.js"></script>
+  <script src="/client/lib/reconnecting-websocket.min.js"></script>
+  <script src="/client/lib/kurento-utils.js"></script>
+  <script src="/client/lib/kurento-extension.js"></script>
+  <script src="/html5client/js/adapter.js"></script>
+  <script src="/html5client/js/adjust-videos.js"></script>
 
   <audio id="remote-media" autoPlay="autoplay">
-      <track kind="captions" /> {/* These captions are brought to you by eslint */}
+    <track kind="captions" /> {/* These captions are brought to you by eslint */}
   </audio>
+
 </body>
diff --git a/bigbluebutton-html5/client/stylesheets/bbb-icons.css b/bigbluebutton-html5/client/stylesheets/bbb-icons.css
old mode 100755
new mode 100644
index efda7c393348ed712996ee5a1b21ec0d2bc671a2..98d69da5daff2fb30ba0d196fa883340ce26627a
--- a/bigbluebutton-html5/client/stylesheets/bbb-icons.css
+++ b/bigbluebutton-html5/client/stylesheets/bbb-icons.css
@@ -53,7 +53,7 @@
 .icon-bbb-fit_to_screen:before {
   content: "\e929";
 }
-.icon-bbb-linte_tool:before {
+.icon-bbb-line_tool:before {
   content: "\e91c";
 }
 .icon-bbb-circle_tool:before {
diff --git a/bigbluebutton-html5/imports/api/annotations/server/methods/clearWhiteboard.js b/bigbluebutton-html5/imports/api/annotations/server/methods/clearWhiteboard.js
index f34888838cdb1a8f5a07b3dcf6399f74650315b9..33a0eddeba6d1070f09b4234fb1c30c4aec556f5 100644
--- a/bigbluebutton-html5/imports/api/annotations/server/methods/clearWhiteboard.js
+++ b/bigbluebutton-html5/imports/api/annotations/server/methods/clearWhiteboard.js
@@ -5,7 +5,7 @@ import { Meteor } from 'meteor/meteor';
 import { check } from 'meteor/check';
 
 export default function clearWhiteboard(credentials, whiteboardId) {
-  const REDIS_CONFIG = Meteor.settings.redis;
+  const REDIS_CONFIG = Meteor.settings.private.redis;
   const CHANNEL = REDIS_CONFIG.channels.toAkkaApps;
   const EVENT_NAME = 'ClearWhiteboardPubMsg';
 
@@ -18,9 +18,7 @@ export default function clearWhiteboard(credentials, whiteboardId) {
 
   const allowed = Acl.can('methods.clearWhiteboard', credentials) || getMultiUserStatus(meetingId);
   if (!allowed) {
-    throw new Meteor.Error(
-      'not-allowed', `User ${requesterUserId} is not allowed to clear the whiteboard`,
-    );
+    throw new Meteor.Error('not-allowed', `User ${requesterUserId} is not allowed to clear the whiteboard`);
   }
 
   const payload = {
diff --git a/bigbluebutton-html5/imports/api/annotations/server/methods/sendAnnotation.js b/bigbluebutton-html5/imports/api/annotations/server/methods/sendAnnotation.js
index 0b24f0712b937424494abc7b7d298b60e3bb533d..99c16cb7a1ae65102c2d397d283d90a2cc0492fc 100644
--- a/bigbluebutton-html5/imports/api/annotations/server/methods/sendAnnotation.js
+++ b/bigbluebutton-html5/imports/api/annotations/server/methods/sendAnnotation.js
@@ -22,7 +22,7 @@ function isLastMessage(annotation, userId) {
 }
 
 export default function sendAnnotation(credentials, annotation) {
-  const REDIS_CONFIG = Meteor.settings.redis;
+  const REDIS_CONFIG = Meteor.settings.private.redis;
   const CHANNEL = REDIS_CONFIG.channels.toAkkaApps;
   const EVENT_NAME = 'SendWhiteboardAnnotationPubMsg';
 
@@ -45,9 +45,7 @@ export default function sendAnnotation(credentials, annotation) {
     isLastMessage(annotation, requesterUserId);
 
   if (!allowed) {
-    throw new Meteor.Error(
-      'not-allowed', `User ${requesterUserId} is not allowed to send an annotation`,
-    );
+    throw new Meteor.Error('not-allowed', `User ${requesterUserId} is not allowed to send an annotation`);
   }
 
   const payload = {
diff --git a/bigbluebutton-html5/imports/api/annotations/server/methods/undoAnnotation.js b/bigbluebutton-html5/imports/api/annotations/server/methods/undoAnnotation.js
index ddb84a4d244e2ae0ee9b9c341e8c0495d6484468..aeaa01707613fd085f13f9aa54f2be7ba37c9ea0 100644
--- a/bigbluebutton-html5/imports/api/annotations/server/methods/undoAnnotation.js
+++ b/bigbluebutton-html5/imports/api/annotations/server/methods/undoAnnotation.js
@@ -5,7 +5,7 @@ import { Meteor } from 'meteor/meteor';
 import { check } from 'meteor/check';
 
 export default function undoAnnotation(credentials, whiteboardId) {
-  const REDIS_CONFIG = Meteor.settings.redis;
+  const REDIS_CONFIG = Meteor.settings.private.redis;
   const CHANNEL = REDIS_CONFIG.channels.toAkkaApps;
   const EVENT_NAME = 'UndoWhiteboardPubMsg';
 
@@ -18,9 +18,7 @@ export default function undoAnnotation(credentials, whiteboardId) {
 
   const allowed = Acl.can('methods.undoAnnotation', credentials) || getMultiUserStatus(meetingId);
   if (!allowed) {
-    throw new Meteor.Error(
-      'not-allowed', `User ${requesterUserId} is not allowed to undo the annotation`,
-    );
+    throw new Meteor.Error('not-allowed', `User ${requesterUserId} is not allowed to undo the annotation`);
   }
 
   const payload = {
diff --git a/bigbluebutton-html5/imports/api/annotations/server/modifiers/addAnnotation.js b/bigbluebutton-html5/imports/api/annotations/server/modifiers/addAnnotation.js
index ba11154b81ad76f6dc10b4fb6d1bed4434a56592..c5e0dda1e8f20c69fc336cb5055d65cc8ecb107f 100644
--- a/bigbluebutton-html5/imports/api/annotations/server/modifiers/addAnnotation.js
+++ b/bigbluebutton-html5/imports/api/annotations/server/modifiers/addAnnotation.js
@@ -7,7 +7,9 @@ const ANNOTATION_TYPE_PENCIL = 'pencil';
 
 // line, triangle, ellipse, rectangle
 function handleCommonAnnotation(meetingId, whiteboardId, userId, annotation) {
-  const { id, status, annotationType, annotationInfo, wbId, position } = annotation;
+  const {
+    id, status, annotationType, annotationInfo, wbId, position,
+  } = annotation;
 
   const selector = {
     meetingId,
@@ -33,7 +35,9 @@ function handleCommonAnnotation(meetingId, whiteboardId, userId, annotation) {
 }
 
 function handleTextUpdate(meetingId, whiteboardId, userId, annotation) {
-  const { id, status, annotationType, annotationInfo, wbId, position } = annotation;
+  const {
+    id, status, annotationType, annotationInfo, wbId, position,
+  } = annotation;
 
   const selector = {
     meetingId,
@@ -67,10 +71,12 @@ function handlePencilUpdate(meetingId, whiteboardId, userId, annotation) {
   const DRAW_UPDATE = ANOTATION_STATUSES.update;
   const DRAW_END = ANOTATION_STATUSES.end;
 
-  const SERVER_CONFIG = Meteor.settings.app;
+  const SERVER_CONFIG = Meteor.settings.private.app;
   const PENCIL_CHUNK_SIZE = SERVER_CONFIG.pencilChunkLength || 100;
 
-  const { id, status, annotationType, annotationInfo, wbId, position } = annotation;
+  const {
+    id, status, annotationType, annotationInfo, wbId, position,
+  } = annotation;
 
   const baseSelector = {
     meetingId,
diff --git a/bigbluebutton-html5/imports/api/captions/server/handlers/captionHistory.js b/bigbluebutton-html5/imports/api/captions/server/handlers/captionHistory.js
index c3cca42eb793ec75440e9cc6911ac88618b5c2f4..2486b5e131e6950e6ee2983db173fb49ca800b93 100644
--- a/bigbluebutton-html5/imports/api/captions/server/handlers/captionHistory.js
+++ b/bigbluebutton-html5/imports/api/captions/server/handlers/captionHistory.js
@@ -1,57 +1,57 @@
-import _ from 'lodash';
-import Captions from '/imports/api/captions';
-import { check } from 'meteor/check';
-import addCaption from '../modifiers/addCaption';
-
-export default function handleCaptionHistory({ body }, meetingId) {
-  const SERVER_CONFIG = Meteor.settings.app;
-  const CAPTION_CHUNK_LENGTH = SERVER_CONFIG.captionsChunkLength || 1000;
-
-  const captionHistory = body.history;
-
-  check(meetingId, String);
-  check(captionHistory, Object);
-
-  const captionsAdded = [];
-  _.each(captionHistory, (caption, locale) => {
-    const ownerId = caption[0];
-    let captions = caption[1].slice(0);
-    const chunks = [];
-
-    if (captions.length === 0) {
-      chunks.push('');
-    } else {
-      while (captions.length > 0) {
-        if (captions.length > CAPTION_CHUNK_LENGTH) {
-          chunks.push(captions.slice(0, CAPTION_CHUNK_LENGTH));
-          captions = captions.slice(CAPTION_CHUNK_LENGTH);
-        } else {
-          chunks.push(captions);
-          captions = captions.slice(captions.length);
-        }
-      }
-    }
-
-    const selectorToRemove = {
-      meetingId,
-      locale,
-      'captionHistory.index': { $gt: (chunks.length - 1) },
-    };
-
-    Captions.remove(selectorToRemove);
-
-    chunks.forEach((chunkCaptions, index) => {
-      const captionHistoryObject = {
-        locale,
-        ownerId,
-        chunkCaptions,
-        index,
-        next: (index < chunks.length - 1) ? index + 1 : undefined,
-      };
-
-      captionsAdded.push(addCaption(meetingId, locale, captionHistoryObject));
-    });
-  });
-
-  return captionsAdded;
-}
+import _ from 'lodash';
+import Captions from '/imports/api/captions';
+import { check } from 'meteor/check';
+import addCaption from '../modifiers/addCaption';
+
+export default function handleCaptionHistory({ body }, meetingId) {
+  const SERVER_CONFIG = Meteor.settings.private.app;
+  const CAPTION_CHUNK_LENGTH = SERVER_CONFIG.captionsChunkLength || 1000;
+
+  const captionHistory = body.history;
+
+  check(meetingId, String);
+  check(captionHistory, Object);
+
+  const captionsAdded = [];
+  _.each(captionHistory, (caption, locale) => {
+    const ownerId = caption[0];
+    let captions = caption[1].slice(0);
+    const chunks = [];
+
+    if (captions.length === 0) {
+      chunks.push('');
+    } else {
+      while (captions.length > 0) {
+        if (captions.length > CAPTION_CHUNK_LENGTH) {
+          chunks.push(captions.slice(0, CAPTION_CHUNK_LENGTH));
+          captions = captions.slice(CAPTION_CHUNK_LENGTH);
+        } else {
+          chunks.push(captions);
+          captions = captions.slice(captions.length);
+        }
+      }
+    }
+
+    const selectorToRemove = {
+      meetingId,
+      locale,
+      'captionHistory.index': { $gt: (chunks.length - 1) },
+    };
+
+    Captions.remove(selectorToRemove);
+
+    chunks.forEach((chunkCaptions, index) => {
+      const captionHistoryObject = {
+        locale,
+        ownerId,
+        chunkCaptions,
+        index,
+        next: (index < chunks.length - 1) ? index + 1 : undefined,
+      };
+
+      captionsAdded.push(addCaption(meetingId, locale, captionHistoryObject));
+    });
+  });
+
+  return captionsAdded;
+}
diff --git a/bigbluebutton-html5/imports/api/captions/server/handlers/captionUpdate.js b/bigbluebutton-html5/imports/api/captions/server/handlers/captionUpdate.js
index 1c10cfbf4131b87f9adfa077a63e51652519ad3e..caf067f0b68061fdd3e0731b2b6b51488bcdaa6e 100644
--- a/bigbluebutton-html5/imports/api/captions/server/handlers/captionUpdate.js
+++ b/bigbluebutton-html5/imports/api/captions/server/handlers/captionUpdate.js
@@ -1,178 +1,181 @@
-import Captions from '/imports/api/captions';
-import { check } from 'meteor/check';
-import addCaption from '../modifiers/addCaption';
-
-export default function handleCaptionUpdate({ body }, meetingId) {
-  const SERVER_CONFIG = Meteor.settings.app;
-  const CAPTION_CHUNK_LENGTH = SERVER_CONFIG.captionsChunkLength || 1000;
-
-  const { locale } = body;
-
-  check(meetingId, String);
-  check(locale, String);
-
-  const captionsObjects = Captions.find({
-    meetingId,
-    locale,
-  }, {
-    sort: {
-      locale: 1,
-      'captionHistory.index': 1,
-    },
-  }).fetch();
-
-  const objectsToUpdate = [];
-  if (captionsObjects != null) {
-    let startIndex;
-    let endIndex;
-    let length = 0;
-    let current = captionsObjects[0];
-
-    // looking for a start index and end index
-    // (end index only for the case when they are in the same block)
-    while (current != null) {
-      length += current.captionHistory.captions.length;
-
-      // if length is bigger than start index - we found our start index
-      if (length >= body.startIndex && startIndex == undefined) {
-        // check if it's a new character somewhere in the middle of captions text
-        if (length - 1 >= body.startIndex) {
-          startIndex = body.startIndex - (length - current.captionHistory.captions.length);
-
-          // check to see if the endIndex is in the same object as startIndex
-          if (length - 1 >= body.endIndex) {
-            endIndex = body.endIndex - (length - current.captionHistory.captions.length);
-            const _captions = current.captionHistory.captions;
-            current.captionHistory.captions = _captions.slice(0, startIndex) +
-              body.text +
-              _captions.slice(endIndex);
-            objectsToUpdate.push(current);
-            break;
-
-            // end index is not in the same object as startIndex, we will find it later
-          } else {
-            current.captionHistory.captions = current.captionHistory.captions.slice(0, startIndex) +
-              body.text;
-            objectsToUpdate.push(current);
-            break;
-          }
-
-          // separate case for appending new characters to the very end of the string
-        } else if (current.captionHistory.next == null &&
-          length == body.startIndex &&
-          length == body.startIndex) {
-          startIndex = 1;
-          endIndex = 1;
-          current.captionHistory.captions += body.text;
-          objectsToUpdate.push(current);
-        }
-      }
-
-      current = captionsObjects[current.captionHistory.next];
-    }
-
-    // looking for end index here if it wasn't in the same object as start index
-    if (startIndex != undefined && endIndex == undefined) {
-      current = captionsObjects[current.captionHistory.next];
-      while (current != null) {
-        length += current.captionHistory.captions.length;
-
-        // check to see if the endIndex is in the current object
-        if (length - 1 >= body.endIndex) {
-          endIndex = body.endIndex - (length - current.captionHistory.captions.length);
-          current.captionHistory.captions = current.captionHistory.captions.slice(endIndex);
-          objectsToUpdate.push(current);
-
-          break;
-
-          // if endIndex wasn't in the current object, that means this whole object was deleted
-          // initializing string to ''
-        } else {
-          current.captionHistory.captions = '';
-          objectsToUpdate.push(current);
-        }
-
-        current = captionsObjects[current.captionHistory.next];
-      }
-    }
-
-    // looking for the strings which exceed the limit and split them into multiple objects
-    let maxIndex = captionsObjects.length - 1;
-    for (let i = 0; i < objectsToUpdate.length; i++) {
-      if (objectsToUpdate[i].captionHistory.captions.length > CAPTION_CHUNK_LENGTH) {
-        // string is too large. Check if the next object exists and if it can
-        // accomodate the part of the string that exceeds the limits
-        const _nextIndex = objectsToUpdate[i].captionHistory.next;
-        if (_nextIndex != null &&
-          captionsObjects[_nextIndex].captionHistory.captions.length < CAPTION_CHUNK_LENGTH) {
-          const extraString = objectsToUpdate[i].captionHistory.captions.slice(CAPTION_CHUNK_LENGTH);
-
-          // could assign it directly, but our linter complained
-          let _captions = objectsToUpdate[i].captionHistory.captions;
-          _captions = _captions.slice(0, CAPTION_CHUNK_LENGTH);
-          objectsToUpdate[i].captionHistory.captions = _captions;
-
-          // check to see if the next object was added to objectsToUpdate array
-          if (objectsToUpdate[i + 1] != null &&
-            objectsToUpdate[i].captionHistory.next == objectsToUpdate[i + 1].captionHistory.index) {
-            objectsToUpdate[i + 1].captionHistory.captions = extraString +
-              objectsToUpdate[i + 1].captionHistory.captions;
-
-            // next object wasn't added to objectsToUpdate array, adding it from captionsObjects array.
-          } else {
-            const nextObj = captionsObjects[objectsToUpdate[i].captionHistory.next];
-            nextObj.captionHistory.captions = extraString + nextObj.captionHistory.captions;
-            objectsToUpdate.push(nextObj);
-          }
-
-          // next object was full already, so we create another and insert it in between them
-        } else {
-          // need to take a current object out of the objectsToUpdate and add it back after
-          // every other object, so that Captions collection could be updated in a proper order
-          const tempObj = objectsToUpdate.splice(i, 1);
-          let extraString = tempObj[0].captionHistory.captions.slice(CAPTION_CHUNK_LENGTH);
-
-          tempObj[0].captionHistory.captions =
-            tempObj[0].captionHistory.captions.slice(0, CAPTION_CHUNK_LENGTH);
-
-          maxIndex += 1;
-          const tempIndex = tempObj[0].captionHistory.next;
-          tempObj[0].captionHistory.next = maxIndex;
-
-          while (extraString.length != 0) {
-            const entry = {
-              meetingId,
-              locale,
-              captionHistory: {
-                locale,
-                ownerId: tempObj[0].captionHistory.ownerId,
-                captions: extraString.slice(0, CAPTION_CHUNK_LENGTH),
-                index: maxIndex,
-                next: null,
-              },
-            };
-            maxIndex += 1;
-            extraString = extraString.slice(CAPTION_CHUNK_LENGTH);
-            if (extraString.length > 0) {
-              entry.captionHistory.next = maxIndex;
-            } else {
-              entry.captionHistory.next = tempIndex;
-            }
-
-            objectsToUpdate.push(entry);
-          }
-
-          objectsToUpdate.push(tempObj[0]);
-        }
-      }
-    }
-  }
-
-  const captionsAdded = [];
-  objectsToUpdate.forEach((entry) => {
-    const { _id, meetingId, locale, captionHistory } = entry;
-    captionsAdded.push(addCaption(meetingId, locale, captionHistory, _id));
-  });
-
-  return captionsAdded;
-}
+import Captions from '/imports/api/captions';
+import { check } from 'meteor/check';
+import addCaption from '../modifiers/addCaption';
+
+export default function handleCaptionUpdate({ body }, meetingId) {
+  const SERVER_CONFIG = Meteor.settings.private.app;
+  const CAPTION_CHUNK_LENGTH = SERVER_CONFIG.captionsChunkLength || 1000;
+
+  const { locale } = body;
+
+  check(meetingId, String);
+  check(locale, String);
+
+  const captionsObjects = Captions.find({
+    meetingId,
+    locale,
+  }, {
+    sort: {
+      locale: 1,
+      'captionHistory.index': 1,
+    },
+  }).fetch();
+
+  const objectsToUpdate = [];
+  if (captionsObjects != null) {
+    let startIndex;
+    let endIndex;
+    let length = 0;
+    let current = captionsObjects[0];
+
+    // looking for a start index and end index
+    // (end index only for the case when they are in the same block)
+    while (current != null) {
+      length += current.captionHistory.captions.length;
+
+      // if length is bigger than start index - we found our start index
+      if (length >= body.startIndex && startIndex === undefined) {
+        // check if it's a new character somewhere in the middle of captions text
+        if (length - 1 >= body.startIndex) {
+          startIndex = body.startIndex - (length - current.captionHistory.captions.length);
+
+          // check to see if the endIndex is in the same object as startIndex
+          if (length - 1 >= body.endIndex) {
+            endIndex = body.endIndex - (length - current.captionHistory.captions.length);
+            const _captions = current.captionHistory.captions;
+            current.captionHistory.captions = _captions.slice(0, startIndex) +
+              body.text +
+              _captions.slice(endIndex);
+            objectsToUpdate.push(current);
+            break;
+
+            // end index is not in the same object as startIndex, we will find it later
+          } else {
+            current.captionHistory.captions = current.captionHistory.captions.slice(0, startIndex) +
+              body.text;
+            objectsToUpdate.push(current);
+            break;
+          }
+
+          // separate case for appending new characters to the very end of the string
+        } else if (current.captionHistory.next == null &&
+          length === body.startIndex &&
+          length === body.startIndex) {
+          startIndex = 1;
+          endIndex = 1;
+          current.captionHistory.captions += body.text;
+          objectsToUpdate.push(current);
+        }
+      }
+
+      current = captionsObjects[current.captionHistory.next];
+    }
+
+    // looking for end index here if it wasn't in the same object as start index
+    if (startIndex !== undefined && endIndex === undefined) {
+      current = captionsObjects[current.captionHistory.next];
+      while (current != null) {
+        length += current.captionHistory.captions.length;
+
+        // check to see if the endIndex is in the current object
+        if (length - 1 >= body.endIndex) {
+          endIndex = body.endIndex - (length - current.captionHistory.captions.length);
+          current.captionHistory.captions = current.captionHistory.captions.slice(endIndex);
+          objectsToUpdate.push(current);
+
+          break;
+
+          // if endIndex wasn't in the current object, that means this whole object was deleted
+          // initializing string to ''
+        } else {
+          current.captionHistory.captions = '';
+          objectsToUpdate.push(current);
+        }
+
+        current = captionsObjects[current.captionHistory.next];
+      }
+    }
+
+    // looking for the strings which exceed the limit and split them into multiple objects
+    let maxIndex = captionsObjects.length - 1;
+    for (let i = 0; i < objectsToUpdate.length; i += 1) {
+      if (objectsToUpdate[i].captionHistory.captions.length > CAPTION_CHUNK_LENGTH) {
+        // string is too large. Check if the next object exists and if it can
+        // accomodate the part of the string that exceeds the limits
+        const _nextIndex = objectsToUpdate[i].captionHistory.next;
+        if (_nextIndex != null &&
+          captionsObjects[_nextIndex].captionHistory.captions.length < CAPTION_CHUNK_LENGTH) {
+          const extraString = objectsToUpdate[i].captionHistory.captions.slice(CAPTION_CHUNK_LENGTH);
+
+          // could assign it directly, but our linter complained
+          let _captions = objectsToUpdate[i].captionHistory.captions;
+          _captions = _captions.slice(0, CAPTION_CHUNK_LENGTH);
+          objectsToUpdate[i].captionHistory.captions = _captions;
+
+          // check to see if the next object was added to objectsToUpdate array
+          if (objectsToUpdate[i + 1] != null &&
+            objectsToUpdate[i].captionHistory.next === objectsToUpdate[i + 1].captionHistory.index) {
+            objectsToUpdate[i + 1].captionHistory.captions = extraString +
+              objectsToUpdate[i + 1].captionHistory.captions;
+
+            // next object wasn't added to objectsToUpdate array
+            // adding it from captionsObjects array.
+          } else {
+            const nextObj = captionsObjects[objectsToUpdate[i].captionHistory.next];
+            nextObj.captionHistory.captions = extraString + nextObj.captionHistory.captions;
+            objectsToUpdate.push(nextObj);
+          }
+
+          // next object was full already, so we create another and insert it in between them
+        } else {
+          // need to take a current object out of the objectsToUpdate and add it back after
+          // every other object, so that Captions collection could be updated in a proper order
+          const tempObj = objectsToUpdate.splice(i, 1);
+          let extraString = tempObj[0].captionHistory.captions.slice(CAPTION_CHUNK_LENGTH);
+
+          tempObj[0].captionHistory.captions =
+            tempObj[0].captionHistory.captions.slice(0, CAPTION_CHUNK_LENGTH);
+
+          maxIndex += 1;
+          const tempIndex = tempObj[0].captionHistory.next;
+          tempObj[0].captionHistory.next = maxIndex;
+
+          while (extraString.length !== 0) {
+            const entry = {
+              meetingId,
+              locale,
+              captionHistory: {
+                locale,
+                ownerId: tempObj[0].captionHistory.ownerId,
+                captions: extraString.slice(0, CAPTION_CHUNK_LENGTH),
+                index: maxIndex,
+                next: null,
+              },
+            };
+            maxIndex += 1;
+            extraString = extraString.slice(CAPTION_CHUNK_LENGTH);
+            if (extraString.length > 0) {
+              entry.captionHistory.next = maxIndex;
+            } else {
+              entry.captionHistory.next = tempIndex;
+            }
+
+            objectsToUpdate.push(entry);
+          }
+
+          objectsToUpdate.push(tempObj[0]);
+        }
+      }
+    }
+  }
+
+  const captionsAdded = [];
+  objectsToUpdate.forEach((entry) => {
+    const {
+      _id, captionHistory,
+    } = entry;
+    captionsAdded.push(addCaption(meetingId, locale, captionHistory, _id));
+  });
+
+  return captionsAdded;
+}
diff --git a/bigbluebutton-html5/imports/api/chat/server/methods/clearPublicChatHistory.js b/bigbluebutton-html5/imports/api/chat/server/methods/clearPublicChatHistory.js
index 35bf713a682591339737ba3dde3d7a226c6a7539..6805d4b2689983f96dc3a7426129f6615c809f15 100644
--- a/bigbluebutton-html5/imports/api/chat/server/methods/clearPublicChatHistory.js
+++ b/bigbluebutton-html5/imports/api/chat/server/methods/clearPublicChatHistory.js
@@ -3,7 +3,7 @@ import { check } from 'meteor/check';
 import RedisPubSub from '/imports/startup/server/redis';
 
 export default function clearPublicChatHistory(credentials) {
-  const REDIS_CONFIG = Meteor.settings.redis;
+  const REDIS_CONFIG = Meteor.settings.private.redis;
   const CHANNEL = REDIS_CONFIG.channels.toAkkaApps;
   const { meetingId, requesterUserId, requesterToken } = credentials;
 
diff --git a/bigbluebutton-html5/imports/api/chat/server/methods/sendChat.js b/bigbluebutton-html5/imports/api/chat/server/methods/sendChat.js
index e93dee56d407bc1dd7ab5729251077ecb0aba31d..5c0ecf86f5c424dda0f422699774711bfc3308da 100644
--- a/bigbluebutton-html5/imports/api/chat/server/methods/sendChat.js
+++ b/bigbluebutton-html5/imports/api/chat/server/methods/sendChat.js
@@ -27,7 +27,7 @@ const parseMessage = (message) => {
 };
 
 export default function sendChat(credentials, message) {
-  const REDIS_CONFIG = Meteor.settings.redis;
+  const REDIS_CONFIG = Meteor.settings.private.redis;
   const CHANNEL = REDIS_CONFIG.channels.toAkkaApps;
 
   const CHAT_CONFIG = Meteor.settings.public.chat;
@@ -50,6 +50,8 @@ export default function sendChat(credentials, message) {
     eventName = 'SendPublicMessagePubMsg';
   }
 
-  return RedisPubSub.publishUserMessage(CHANNEL, eventName, meetingId, requesterUserId,
-   { message: parsedMessage });
+  return RedisPubSub.publishUserMessage(
+    CHANNEL, eventName, meetingId, requesterUserId,
+    { message: parsedMessage },
+  );
 }
diff --git a/bigbluebutton-html5/imports/api/cursor/server/methods/publishCursorUpdate.js b/bigbluebutton-html5/imports/api/cursor/server/methods/publishCursorUpdate.js
index 93608939774271ec13980a1fbc79ff5d8840c936..bde4f486a8c7c44b6292998ac8d5e7234ce6a646 100644
--- a/bigbluebutton-html5/imports/api/cursor/server/methods/publishCursorUpdate.js
+++ b/bigbluebutton-html5/imports/api/cursor/server/methods/publishCursorUpdate.js
@@ -6,7 +6,7 @@ import { check } from 'meteor/check';
 
 
 export default function publishCursorUpdate(credentials, payload) {
-  const REDIS_CONFIG = Meteor.settings.redis;
+  const REDIS_CONFIG = Meteor.settings.private.redis;
   const CHANNEL = REDIS_CONFIG.channels.toAkkaApps;
   const EVENT_NAME = 'SendCursorPositionPubMsg';
 
@@ -22,9 +22,7 @@ export default function publishCursorUpdate(credentials, payload) {
 
   const allowed = Acl.can('methods.moveCursor', credentials) || getMultiUserStatus(meetingId);
   if (!allowed) {
-    throw new Meteor.Error(
-      'not-allowed', `User ${requesterUserId} is not allowed to move the cursor`,
-    );
+    throw new Meteor.Error('not-allowed', `User ${requesterUserId} is not allowed to move the cursor`);
   }
 
   return RedisPubSub.publishUserMessage(CHANNEL, EVENT_NAME, meetingId, requesterUserId, payload);
diff --git a/bigbluebutton-html5/imports/api/meetings/server/methods/endMeeting.js b/bigbluebutton-html5/imports/api/meetings/server/methods/endMeeting.js
index a563891295d5cf38fde22ad07ce6ea8356e8f606..c86dcbc4009d5d832ddcda80d1b2740969e1b688 100644
--- a/bigbluebutton-html5/imports/api/meetings/server/methods/endMeeting.js
+++ b/bigbluebutton-html5/imports/api/meetings/server/methods/endMeeting.js
@@ -4,7 +4,7 @@ import RedisPubSub from '/imports/startup/server/redis';
 import Logger from '/imports/startup/server/logger';
 
 export default function endMeeting(credentials) {
-  const REDIS_CONFIG = Meteor.settings.redis;
+  const REDIS_CONFIG = Meteor.settings.private.redis;
   const CHANNEL = REDIS_CONFIG.channels.toAkkaApps;
   const EVENT_NAME = 'LogoutAndEndMeetingCmdMsg';
 
diff --git a/bigbluebutton-html5/imports/api/meetings/server/modifiers/addMeeting.js b/bigbluebutton-html5/imports/api/meetings/server/modifiers/addMeeting.js
index d18c31243958ec2e3c5d5f4b991f3e1a5ce578c1..3fbba42c1bd47339b60c71d51652925eadf305a6 100644
--- a/bigbluebutton-html5/imports/api/meetings/server/modifiers/addMeeting.js
+++ b/bigbluebutton-html5/imports/api/meetings/server/modifiers/addMeeting.js
@@ -51,6 +51,7 @@ export default function addMeeting(meeting) {
       voiceConf: String,
       dialNumber: String,
       telVoice: String,
+      muteOnStart: Boolean,
     },
     screenshareProps: {
       red5ScreenshareIp: String,
diff --git a/bigbluebutton-html5/imports/api/polls/server/eventHandlers.js b/bigbluebutton-html5/imports/api/polls/server/eventHandlers.js
index 0f3062c9ffb56a9deb8599d4e7d5ea93f644ec14..91bf131a7535153f50f3930a285da21b87e0ce67 100644
--- a/bigbluebutton-html5/imports/api/polls/server/eventHandlers.js
+++ b/bigbluebutton-html5/imports/api/polls/server/eventHandlers.js
@@ -7,4 +7,4 @@ import handleUserVoted from './handlers/userVoted';
 RedisPubSub.on('PollShowResultEvtMsg', handlePollPublished);
 RedisPubSub.on('PollStartedEvtMsg', handlePollStarted);
 RedisPubSub.on('PollStoppedEvtMsg', handlePollStopped);
-RedisPubSub.on('UserRespondedToPollEvtMsg', handleUserVoted);
+RedisPubSub.on('PollUpdatedEvtMsg', handleUserVoted);
diff --git a/bigbluebutton-html5/imports/api/polls/server/handlers/userVoted.js b/bigbluebutton-html5/imports/api/polls/server/handlers/userVoted.js
index 5b80a46d28386a9921d3653bf78801775411fdf2..d73d8b67909854027850b16ecadcfe1829cb6105 100644
--- a/bigbluebutton-html5/imports/api/polls/server/handlers/userVoted.js
+++ b/bigbluebutton-html5/imports/api/polls/server/handlers/userVoted.js
@@ -3,11 +3,20 @@ import updateVotes from '../modifiers/updateVotes';
 
 export default function userVoted({ body }, meetingId) {
   const { poll } = body;
-  const { presenterId } = body;
 
   check(meetingId, String);
-  check(poll, Object);
-  check(presenterId, String);
+  check(poll, {
+    id: String,
+    answers: [
+      {
+        id: Number,
+        key: String,
+        numVotes: Number,
+      },
+    ],
+    numRespondents: Number,
+    numResponders: Number,
+  });
 
-  return updateVotes(poll, meetingId, presenterId);
+  return updateVotes(poll, meetingId);
 }
diff --git a/bigbluebutton-html5/imports/api/polls/server/methods/publishVote.js b/bigbluebutton-html5/imports/api/polls/server/methods/publishVote.js
index bc59d38e06d85cadeb75388256af9206beb725c2..8cebcc3cadc5a2c6a352f9b308e824b232fb1b11 100644
--- a/bigbluebutton-html5/imports/api/polls/server/methods/publishVote.js
+++ b/bigbluebutton-html5/imports/api/polls/server/methods/publishVote.js
@@ -4,12 +4,17 @@ import Polls from '/imports/api/polls';
 import Logger from '/imports/startup/server/logger';
 
 export default function publishVote(credentials, id, pollAnswerId) { // TODO discuss location
-  const REDIS_CONFIG = Meteor.settings.redis;
+  const REDIS_CONFIG = Meteor.settings.private.redis;
   const CHANNEL = REDIS_CONFIG.channels.toAkkaApps;
   const EVENT_NAME = 'RespondToPollReqMsg';
 
   const { meetingId, requesterUserId } = credentials;
 
+  /*
+   We keep an array of people who were in the meeting at the time the poll
+   was started. The poll is published to them only.
+   Once they vote - their ID is removed and they cannot see the poll anymore
+   */
   const currentPoll = Polls.findOne({
     users: requesterUserId,
     meetingId,
diff --git a/bigbluebutton-html5/imports/api/polls/server/modifiers/updateVotes.js b/bigbluebutton-html5/imports/api/polls/server/modifiers/updateVotes.js
index 3cb37bd1dab58047d9bb316397a9b1f089adaac0..30815aafa1c1d1d8fc90e15f5cc983dc5de3f40d 100644
--- a/bigbluebutton-html5/imports/api/polls/server/modifiers/updateVotes.js
+++ b/bigbluebutton-html5/imports/api/polls/server/modifiers/updateVotes.js
@@ -3,19 +3,17 @@ import { check } from 'meteor/check';
 import Logger from '/imports/startup/server/logger';
 import flat from 'flat';
 
-export default function updateVotes(poll, meetingId, requesterId) {
+export default function updateVotes(poll, meetingId) {
   check(meetingId, String);
-  check(requesterId, String);
   check(poll, Object);
 
   const {
     id,
     answers,
+    numResponders,
+    numRespondents,
   } = poll;
 
-  const { numResponders } = poll;
-  const { numRespondents } = poll;
-
   check(id, String);
   check(answers, Array);
 
@@ -24,15 +22,11 @@ export default function updateVotes(poll, meetingId, requesterId) {
 
   const selector = {
     meetingId,
-    requester: requesterId,
     id,
   };
 
   const modifier = {
-    $set: Object.assign(
-      { requester: requesterId },
-      flat(poll, { safe: true }),
-    ),
+    $set: flat(poll, { safe: true }),
   };
 
   const cb = (err) => {
diff --git a/bigbluebutton-html5/imports/api/presentations/server/eventHandlers.js b/bigbluebutton-html5/imports/api/presentations/server/eventHandlers.js
index 54a044bb2fc637761ba6a3e4f813f2cfb1704a06..620a0eac506f6d063eedc17db71dabffccbbd585 100644
--- a/bigbluebutton-html5/imports/api/presentations/server/eventHandlers.js
+++ b/bigbluebutton-html5/imports/api/presentations/server/eventHandlers.js
@@ -7,6 +7,7 @@ import handlePresentationConversionUpdate from './handlers/presentationConversio
 
 RedisPubSub.on('SyncGetPresentationInfoRespMsg', handlePresentationInfoReply);
 RedisPubSub.on('PresentationPageGeneratedEvtMsg', handlePresentationConversionUpdate);
+RedisPubSub.on('PresentationPageCountErrorEvtMsg', handlePresentationConversionUpdate);
 RedisPubSub.on('PresentationConversionUpdateEvtMsg', handlePresentationConversionUpdate);
 RedisPubSub.on('PresentationConversionCompletedEvtMsg', handlePresentationAdded);
 RedisPubSub.on('RemovePresentationEvtMsg', handlePresentationRemove);
diff --git a/bigbluebutton-html5/imports/api/presentations/server/handlers/presentationConversionUpdate.js b/bigbluebutton-html5/imports/api/presentations/server/handlers/presentationConversionUpdate.js
index a8c192261342d621083d34ba86fed75dd7b520c4..4b82690f75f589cccb1addafbe98831119012d40 100644
--- a/bigbluebutton-html5/imports/api/presentations/server/handlers/presentationConversionUpdate.js
+++ b/bigbluebutton-html5/imports/api/presentations/server/handlers/presentationConversionUpdate.js
@@ -49,6 +49,7 @@ export default function handlePresentationConversionUpdate({ body }, meetingId)
       statusModifier.id = presentationId;
       statusModifier.name = presentationName;
       statusModifier['conversion.error'] = true;
+      statusModifier['conversion.done'] = true;
       break;
 
     case GENERATED_SLIDE_KEY:
diff --git a/bigbluebutton-html5/imports/api/presentations/server/methods/removePresentation.js b/bigbluebutton-html5/imports/api/presentations/server/methods/removePresentation.js
index 97b450995ec445b1a4925e48a8cdd2162c469269..28d780b260e22a18dbb6f7aa94cd5c5fe9a71f80 100644
--- a/bigbluebutton-html5/imports/api/presentations/server/methods/removePresentation.js
+++ b/bigbluebutton-html5/imports/api/presentations/server/methods/removePresentation.js
@@ -4,7 +4,7 @@ import Presentations from '/imports/api/presentations';
 
 export default function removePresentation(credentials, presentationId) {
   const PRESENTATION_CONFIG = Meteor.settings.public.presentation;
-  const REDIS_CONFIG = Meteor.settings.redis;
+  const REDIS_CONFIG = Meteor.settings.private.redis;
   const CHANNEL = REDIS_CONFIG.channels.toAkkaApps;
   const EVENT_NAME = 'RemovePresentationPubMsg';
 
diff --git a/bigbluebutton-html5/imports/api/presentations/server/methods/setPresentation.js b/bigbluebutton-html5/imports/api/presentations/server/methods/setPresentation.js
index 26fb25e9dd4b4da6db784bcae25f8e23cdee7b1a..3a82d64ef0018e924ba650f62f32484a69cb05a4 100644
--- a/bigbluebutton-html5/imports/api/presentations/server/methods/setPresentation.js
+++ b/bigbluebutton-html5/imports/api/presentations/server/methods/setPresentation.js
@@ -3,7 +3,7 @@ import { check } from 'meteor/check';
 import Presentations from '/imports/api/presentations';
 
 export default function setPresentation(credentials, presentationId) {
-  const REDIS_CONFIG = Meteor.settings.redis;
+  const REDIS_CONFIG = Meteor.settings.private.redis;
   const CHANNEL = REDIS_CONFIG.channels.toAkkaApps;
   const EVENT_NAME = 'SetCurrentPresentationPubMsg';
 
diff --git a/bigbluebutton-html5/imports/api/screenshare/client/bridge/index.js b/bigbluebutton-html5/imports/api/screenshare/client/bridge/index.js
index 5b953b2ea43f93c1add309adf8b5eb185d52ae17..2c6f548690c4ffbe20d8352b7ef5dab2392630e5 100644
--- a/bigbluebutton-html5/imports/api/screenshare/client/bridge/index.js
+++ b/bigbluebutton-html5/imports/api/screenshare/client/bridge/index.js
@@ -1,5 +1,7 @@
 import VertoBridge from './verto';
+import KurentoBridge from './kurento';
 
-const screenshareBridge = new VertoBridge();
+//const screenshareBridge = new VertoBridge();
+const screenshareBridge = new KurentoBridge();
 
 export default screenshareBridge;
diff --git a/bigbluebutton-html5/imports/api/screenshare/client/bridge/kurento.js b/bigbluebutton-html5/imports/api/screenshare/client/bridge/kurento.js
new file mode 100755
index 0000000000000000000000000000000000000000..77e10ef49f17aa622bb35ba843f86e073fd885d6
--- /dev/null
+++ b/bigbluebutton-html5/imports/api/screenshare/client/bridge/kurento.js
@@ -0,0 +1,51 @@
+import Users from '/imports/api/users';
+import Auth from '/imports/ui/services/auth';
+import BridgeService from './service';
+
+const CHROME_EXTENSION_KEY = Meteor.settings.public.kurento.chromeExtensionKey;
+
+const getUserId = () => {
+  const userID = Auth.userID;
+  return userID;
+}
+
+const getMeetingId = () => {
+  const meetingID = Auth.meetingID;
+  return meetingID;
+}
+
+const getUsername = () => {
+  return Users.findOne({ userId: getUserId() }).name;
+}
+
+export default class KurentoScreenshareBridge {
+  kurentoWatchVideo() {
+    window.kurentoWatchVideo(
+      'screenshareVideo',
+      BridgeService.getConferenceBridge(),
+      getUsername(),
+      getMeetingId(),
+      null,
+      null,
+    );
+  }
+
+  kurentoExitVideo() {
+    window.kurentoExitVideo();
+  }
+
+  kurentoShareScreen() {
+    window.kurentoShareScreen(
+      'screenshareVideo',
+      BridgeService.getConferenceBridge(),
+      getUsername(),
+      getMeetingId(),
+      null,
+      CHROME_EXTENSION_KEY,
+    );
+  }
+
+  kurentoExitScreenShare() {
+    window.kurentoExitScreenShare();
+  }
+}
diff --git a/bigbluebutton-html5/imports/api/screenshare/server/handlers/screenshareStarted.js b/bigbluebutton-html5/imports/api/screenshare/server/handlers/screenshareStarted.js
index 92b24864c11fe4e244538a58fc0f594d61cdcbe8..aacc7d1eeab570c5cbd5169f0105164bccd2c99d 100644
--- a/bigbluebutton-html5/imports/api/screenshare/server/handlers/screenshareStarted.js
+++ b/bigbluebutton-html5/imports/api/screenshare/server/handlers/screenshareStarted.js
@@ -1,7 +1,7 @@
 import { check } from 'meteor/check';
 import addScreenshare from '../modifiers/addScreenshare';
 
-export default function handleBroadcastStartedVoice({ body }, meetingId) {
+export default function handleScreenshareStarted({ body }, meetingId) {
   check(meetingId, String);
   check(body, Object);
 
diff --git a/bigbluebutton-html5/imports/api/screenshare/server/handlers/screenshareStopped.js b/bigbluebutton-html5/imports/api/screenshare/server/handlers/screenshareStopped.js
index d0308ab5a312c3de18cc23175ce23e9237a92372..11e2871f0c7fb7ab0ad494fd8bb5336774b4fd55 100644
--- a/bigbluebutton-html5/imports/api/screenshare/server/handlers/screenshareStopped.js
+++ b/bigbluebutton-html5/imports/api/screenshare/server/handlers/screenshareStopped.js
@@ -1,7 +1,7 @@
 import { check } from 'meteor/check';
 import clearScreenshare from '../modifiers/clearScreenshare';
 
-export default function handleBroadcastStartedVoice({ body }, meetingId) {
+export default function handleScreenshareStopped({ body }, meetingId) {
   const { screenshareConf } = body;
 
   check(meetingId, String);
diff --git a/bigbluebutton-html5/imports/api/slides/server/methods/switchSlide.js b/bigbluebutton-html5/imports/api/slides/server/methods/switchSlide.js
index 6cfead8ce108166325920b4b52cea70706936ea5..b748b4ff691ad19e13501da0b0208b900879991c 100644
--- a/bigbluebutton-html5/imports/api/slides/server/methods/switchSlide.js
+++ b/bigbluebutton-html5/imports/api/slides/server/methods/switchSlide.js
@@ -5,7 +5,7 @@ import { check } from 'meteor/check';
 import RedisPubSub from '/imports/startup/server/redis';
 
 export default function switchSlide(credentials, slideNumber) {
-  const REDIS_CONFIG = Meteor.settings.redis;
+  const REDIS_CONFIG = Meteor.settings.private.redis;
 
   const CHANNEL = REDIS_CONFIG.channels.toAkkaApps;
   const EVENT_NAME = 'SetCurrentPagePubMsg';
@@ -25,8 +25,7 @@ export default function switchSlide(credentials, slideNumber) {
   const Presentation = Presentations.findOne(selector);
 
   if (!Presentation) {
-    throw new Meteor.Error(
-      'presentation-not-found', 'You need a presentation to be able to switch slides');
+    throw new Meteor.Error('presentation-not-found', 'You need a presentation to be able to switch slides');
   }
 
   const Slide = Slides.findOne({
@@ -36,8 +35,7 @@ export default function switchSlide(credentials, slideNumber) {
   });
 
   if (!Slide) {
-    throw new Meteor.Error(
-      'slide-not-found', `Slide number ${slideNumber} not found in the current presentation`);
+    throw new Meteor.Error('slide-not-found', `Slide number ${slideNumber} not found in the current presentation`);
   }
 
   const payload = {
diff --git a/bigbluebutton-html5/imports/api/slides/server/modifiers/addSlide.js b/bigbluebutton-html5/imports/api/slides/server/modifiers/addSlide.js
index 64bcfc9bada2af0530214048c4303a2654412933..d9d9262fed20e82c01ba2a085b92dd066f01a880 100644
--- a/bigbluebutton-html5/imports/api/slides/server/modifiers/addSlide.js
+++ b/bigbluebutton-html5/imports/api/slides/server/modifiers/addSlide.js
@@ -9,7 +9,7 @@ import { SVG, PNG } from '/imports/utils/mimeTypes';
 import calculateSlideData from '/imports/api/slides/server/helpers';
 
 const requestWhiteboardHistory = (meetingId, slideId) => {
-  const REDIS_CONFIG = Meteor.settings.redis;
+  const REDIS_CONFIG = Meteor.settings.private.redis;
   const CHANNEL = REDIS_CONFIG.channels.toAkkaApps;
   const EVENT_NAME = 'GetWhiteboardAnnotationsReqMsg';
   const USER_ID = 'nodeJSapp';
@@ -27,9 +27,7 @@ const fetchImageSizes = imageUri =>
   probe(imageUri)
     .then((result) => {
       if (!SUPPORTED_TYPES.includes(result.mime)) {
-        throw new Meteor.Error(
-          'invalid-image-type', `received ${result.mime} expecting ${SUPPORTED_TYPES.join()}`,
-        );
+        throw new Meteor.Error('invalid-image-type', `received ${result.mime} expecting ${SUPPORTED_TYPES.join()}`);
       }
 
       return {
diff --git a/bigbluebutton-html5/imports/api/users/server/handlers/validateAuthToken.js b/bigbluebutton-html5/imports/api/users/server/handlers/validateAuthToken.js
index 5eb376625ec631ae79e1482e5f6728c51b920b55..3066102abc7010614fa933f7f0c0fa6b998aea20 100644
--- a/bigbluebutton-html5/imports/api/users/server/handlers/validateAuthToken.js
+++ b/bigbluebutton-html5/imports/api/users/server/handlers/validateAuthToken.js
@@ -69,9 +69,7 @@ export default function handleValidateAuthToken({ body }, meetingId) {
         addWelcomeChatMessage(meetingId, userId);
       }
 
-      return Logger.info(`Validated auth token as ${valid
-       }${+' user='}${userId} meeting=${meetingId}`,
-      );
+      return Logger.info(`Validated auth token as ${valid} user=${userId} meeting=${meetingId}`);
     }
 
     return Logger.info('No auth to validate');
diff --git a/bigbluebutton-html5/imports/api/users/server/methods/assignPresenter.js b/bigbluebutton-html5/imports/api/users/server/methods/assignPresenter.js
index 8b4727e31cd468ce2a998bfb3caa59f82a8524b7..add2afe1d06beac5bdc405a88f64344d849f3084 100644
--- a/bigbluebutton-html5/imports/api/users/server/methods/assignPresenter.js
+++ b/bigbluebutton-html5/imports/api/users/server/methods/assignPresenter.js
@@ -5,7 +5,7 @@ import Logger from '/imports/startup/server/logger';
 import Users from '/imports/api/users';
 
 export default function assignPresenter(credentials, userId) {
-  const REDIS_CONFIG = Meteor.settings.redis;
+  const REDIS_CONFIG = Meteor.settings.private.redis;
   const CHANNEL = REDIS_CONFIG.channels.toAkkaApps;
   const EVENT_NAME = 'AssignPresenterReqMsg';
 
diff --git a/bigbluebutton-html5/imports/api/users/server/methods/changeRole.js b/bigbluebutton-html5/imports/api/users/server/methods/changeRole.js
index ada268d17e34ac6584f0d0a04df9afe0ef3623e9..beff64791c4a9654e1c0213050b1ad43b41d121c 100644
--- a/bigbluebutton-html5/imports/api/users/server/methods/changeRole.js
+++ b/bigbluebutton-html5/imports/api/users/server/methods/changeRole.js
@@ -5,7 +5,7 @@ import Logger from '/imports/startup/server/logger';
 import Users from '/imports/api/users';
 
 export default function changeRole(credentials, userId, role) {
-  const REDIS_CONFIG = Meteor.settings.redis;
+  const REDIS_CONFIG = Meteor.settings.private.redis;
   const CHANNEL = REDIS_CONFIG.channels.toAkkaApps;
   const EVENT_NAME = 'ChangeUserRoleCmdMsg';
 
@@ -22,8 +22,7 @@ export default function changeRole(credentials, userId, role) {
   });
 
   if (!User) {
-    throw new Meteor.Error(
-      'user-not-found', `You need a valid user to be able to set '${role}'`);
+    throw new Meteor.Error('user-not-found', `You need a valid user to be able to set '${role}'`);
   }
 
   const payload = {
diff --git a/bigbluebutton-html5/imports/api/users/server/methods/kickUser.js b/bigbluebutton-html5/imports/api/users/server/methods/kickUser.js
index b9aed12653043a833600551d435f4be75a96ccbf..645f68f012b5a3b109649aa08a5fd4756aaf2c25 100644
--- a/bigbluebutton-html5/imports/api/users/server/methods/kickUser.js
+++ b/bigbluebutton-html5/imports/api/users/server/methods/kickUser.js
@@ -3,7 +3,7 @@ import { check } from 'meteor/check';
 import RedisPubSub from '/imports/startup/server/redis';
 
 export default function kickUser(credentials, userId) {
-  const REDIS_CONFIG = Meteor.settings.redis;
+  const REDIS_CONFIG = Meteor.settings.private.redis;
   const CHANNEL = REDIS_CONFIG.channels.toAkkaApps;
   const EVENT_NAME = 'EjectUserFromMeetingCmdMsg';
 
diff --git a/bigbluebutton-html5/imports/api/users/server/methods/setEmojiStatus.js b/bigbluebutton-html5/imports/api/users/server/methods/setEmojiStatus.js
index aacd08dd21eea5c4dbe7f9db3abc5faf3317aec6..c63e65ed058183e52592ab45f7ca6adfc6bc3c3a 100644
--- a/bigbluebutton-html5/imports/api/users/server/methods/setEmojiStatus.js
+++ b/bigbluebutton-html5/imports/api/users/server/methods/setEmojiStatus.js
@@ -4,7 +4,7 @@ import RedisPubSub from '/imports/startup/server/redis';
 import Logger from '/imports/startup/server/logger';
 
 export default function setEmojiStatus(credentials, userId, status) {
-  const REDIS_CONFIG = Meteor.settings.redis;
+  const REDIS_CONFIG = Meteor.settings.private.redis;
   const CHANNEL = REDIS_CONFIG.channels.toAkkaApps;
   const EVENT_NAME = 'ChangeUserEmojiCmdMsg';
 
diff --git a/bigbluebutton-html5/imports/api/users/server/methods/userJoin.js b/bigbluebutton-html5/imports/api/users/server/methods/userJoin.js
index a3de48d80e69af87a609c268719f0da08bbd16eb..b182fff2c0c8920efdcfe3146299dfcde80bc061 100644
--- a/bigbluebutton-html5/imports/api/users/server/methods/userJoin.js
+++ b/bigbluebutton-html5/imports/api/users/server/methods/userJoin.js
@@ -4,7 +4,7 @@ import RedisPubSub from '/imports/startup/server/redis';
 import Logger from '/imports/startup/server/logger';
 
 export default function userJoin(meetingId, userId, authToken) {
-  const REDIS_CONFIG = Meteor.settings.redis;
+  const REDIS_CONFIG = Meteor.settings.private.redis;
   const CHANNEL = REDIS_CONFIG.channels.toAkkaApps;
   const EVENT_NAME = 'UserJoinMeetingReqMsg';
 
diff --git a/bigbluebutton-html5/imports/api/users/server/methods/userLeaving.js b/bigbluebutton-html5/imports/api/users/server/methods/userLeaving.js
index 678e9daf810a22f3ccc76963c09396a78a3a0dfe..87663d2786dbb1d46b6e53cb6d94c314454503d4 100644
--- a/bigbluebutton-html5/imports/api/users/server/methods/userLeaving.js
+++ b/bigbluebutton-html5/imports/api/users/server/methods/userLeaving.js
@@ -7,7 +7,7 @@ import Users from '/imports/api/users';
 const OFFLINE_CONNECTION_STATUS = 'offline';
 
 export default function userLeaving(credentials, userId) {
-  const REDIS_CONFIG = Meteor.settings.redis;
+  const REDIS_CONFIG = Meteor.settings.private.redis;
   const CHANNEL = REDIS_CONFIG.channels.toAkkaApps;
   const EVENT_NAME = 'UserLeaveReqMsg';
 
@@ -24,8 +24,7 @@ export default function userLeaving(credentials, userId) {
 
   const User = Users.findOne(selector);
   if (!User) {
-    throw new Meteor.Error(
-      'user-not-found', `Could not find ${userId} in ${meetingId}: cannot complete userLeaving`);
+    throw new Meteor.Error('user-not-found', `Could not find ${userId} in ${meetingId}: cannot complete userLeaving`);
   }
 
   if (User.connectionStatus === OFFLINE_CONNECTION_STATUS) {
diff --git a/bigbluebutton-html5/imports/api/users/server/methods/validateAuthToken.js b/bigbluebutton-html5/imports/api/users/server/methods/validateAuthToken.js
index ee14153561432ef184cf14cf4951b863cdbe5a2d..95c33053e1778fe3ad437e597e9b784de640f88b 100644
--- a/bigbluebutton-html5/imports/api/users/server/methods/validateAuthToken.js
+++ b/bigbluebutton-html5/imports/api/users/server/methods/validateAuthToken.js
@@ -9,7 +9,7 @@ import setConnectionStatus from '../modifiers/setConnectionStatus';
 const ONLINE_CONNECTION_STATUS = 'online';
 
 export default function validateAuthToken(credentials) {
-  const REDIS_CONFIG = Meteor.settings.redis;
+  const REDIS_CONFIG = Meteor.settings.private.redis;
   const CHANNEL = REDIS_CONFIG.channels.toAkkaApps;
   const EVENT_NAME = 'ValidateAuthTokenReqMsg';
 
@@ -37,7 +37,7 @@ export default function validateAuthToken(credentials) {
 
   Logger.info(`User '${
     requesterUserId
-    }' is trying to validate auth token for meeting '${meetingId}'`);
+  }' is trying to validate auth token for meeting '${meetingId}'`);
 
   return RedisPubSub.publishUserMessage(CHANNEL, EVENT_NAME, meetingId, requesterUserId, payload);
 }
diff --git a/bigbluebutton-html5/imports/api/users/server/modifiers/createDummyUser.js b/bigbluebutton-html5/imports/api/users/server/modifiers/createDummyUser.js
index e7e6dadf64c245eb4c97d1da057ee9c131c67eb8..9625884aae452b1408ed62cc25f80e4f66e8af7b 100644
--- a/bigbluebutton-html5/imports/api/users/server/modifiers/createDummyUser.js
+++ b/bigbluebutton-html5/imports/api/users/server/modifiers/createDummyUser.js
@@ -27,10 +27,8 @@ export default function createDummyUser(meetingId, userId, authToken) {
       return;
     }
     if (numChanged) {
-      Logger.info(`Created dummy user 2x id=${userId} token=${authToken} meeting=${meetingId}`);
+      Logger.info(`Created dummy user id=${userId} token=${authToken} meeting=${meetingId}`);
     }
-
-    Logger.info(`Created dummy user id=${userId} token=${authToken} meeting=${meetingId}`);
   };
 
   return Users.insert(doc, cb);
diff --git a/bigbluebutton-html5/imports/api/video/server/eventHandlers.js b/bigbluebutton-html5/imports/api/video/server/eventHandlers.js
new file mode 100644
index 0000000000000000000000000000000000000000..0e773b08eafe12ba8138ab8523b8a4c2e5196a82
--- /dev/null
+++ b/bigbluebutton-html5/imports/api/video/server/eventHandlers.js
@@ -0,0 +1,6 @@
+import RedisPubSub from '/imports/startup/server/redis';
+import handleUserSharedHtml5Webcam from './handlers/userSharedHtml5Webcam';
+import handleUserUnsharedHtml5Webcam from './handlers/userUnsharedHtml5Webcam';
+
+RedisPubSub.on('UserBroadcastCamStartedEvtMsg', handleUserSharedHtml5Webcam);
+RedisPubSub.on('UserBroadcastCamStoppedEvtMsg', handleUserUnsharedHtml5Webcam);
diff --git a/bigbluebutton-html5/imports/api/video/server/handlers/userSharedHtml5Webcam.js b/bigbluebutton-html5/imports/api/video/server/handlers/userSharedHtml5Webcam.js
new file mode 100644
index 0000000000000000000000000000000000000000..1c54c72af1533a75e45137c2672153d019b59ef5
--- /dev/null
+++ b/bigbluebutton-html5/imports/api/video/server/handlers/userSharedHtml5Webcam.js
@@ -0,0 +1,19 @@
+import sharedWebcam from '../modifiers/sharedWebcam';
+import { check } from 'meteor/check';
+
+export default function handleUserSharedHtml5Webcam({ header, body }, meetingId ) {
+  const { userId, stream } = body;
+  const isValidStream = Match.Where((stream) => {
+    check(stream, String);
+    // Checking if the stream name is a flash one
+    const regexp = /^([A-z0-9]+)-([A-z0-9]+)-([A-z0-9]+)$/;
+    return !regexp.test(stream);
+  });
+
+  check(header, Object);
+  check(meetingId, String);
+  check(userId, String);
+  check(stream, isValidStream);
+
+  return sharedWebcam(meetingId, userId);
+}
diff --git a/bigbluebutton-html5/imports/api/video/server/handlers/userUnsharedHtml5Webcam.js b/bigbluebutton-html5/imports/api/video/server/handlers/userUnsharedHtml5Webcam.js
new file mode 100644
index 0000000000000000000000000000000000000000..8b17ebc7d1badf2601e707b5547494d9c75922a2
--- /dev/null
+++ b/bigbluebutton-html5/imports/api/video/server/handlers/userUnsharedHtml5Webcam.js
@@ -0,0 +1,19 @@
+import unsharedWebcam from '../modifiers/unsharedWebcam';
+import { check } from 'meteor/check';
+
+export default function handleUserUnsharedHtml5Webcam({ header, body }, meetingId) {
+  const { userId, stream } = body;
+  const isValidStream = Match.Where((stream) => {
+    check(stream, String);
+    // Checking if the stream name is a flash one
+    const regexp = /^([A-z0-9]+)-([A-z0-9]+)-([A-z0-9]+)$/;
+    return !regexp.test(stream);
+  });
+
+  check(header, Object);
+  check(meetingId, String);
+  check(userId, String);
+  check(stream, isValidStream);
+
+  return unsharedWebcam(meetingId, userId);
+}
diff --git a/bigbluebutton-html5/imports/api/video/server/index.js b/bigbluebutton-html5/imports/api/video/server/index.js
new file mode 100644
index 0000000000000000000000000000000000000000..9a7510e23ef5b6306a53e4b5743c7810976a3c18
--- /dev/null
+++ b/bigbluebutton-html5/imports/api/video/server/index.js
@@ -0,0 +1,2 @@
+import './eventHandlers';
+import './methods';
diff --git a/bigbluebutton-html5/imports/api/video/server/methods.js b/bigbluebutton-html5/imports/api/video/server/methods.js
new file mode 100644
index 0000000000000000000000000000000000000000..1f1dd46f1096c1479436e90cf776da4d57c1ac2f
--- /dev/null
+++ b/bigbluebutton-html5/imports/api/video/server/methods.js
@@ -0,0 +1,7 @@
+import { Meteor } from 'meteor/meteor';
+import userShareWebcam from './methods/userShareWebcam';
+import userUnshareWebcam from './methods/userUnshareWebcam';
+
+Meteor.methods({
+  userShareWebcam, userUnshareWebcam,
+});
diff --git a/bigbluebutton-html5/imports/api/video/server/methods/userShareWebcam.js b/bigbluebutton-html5/imports/api/video/server/methods/userShareWebcam.js
new file mode 100644
index 0000000000000000000000000000000000000000..5873500523dd6ddab8e7953308014edebfeaf428
--- /dev/null
+++ b/bigbluebutton-html5/imports/api/video/server/methods/userShareWebcam.js
@@ -0,0 +1,31 @@
+import { Meteor } from 'meteor/meteor';
+import { check } from 'meteor/check';
+import Logger from '/imports/startup/server/logger';
+import RedisPubSub from '/imports/startup/server/redis';
+
+export default function userShareWebcam(credentials, message) {
+  const REDIS_CONFIG = Meteor.settings.private.redis;
+  const CHANNEL = REDIS_CONFIG.channels.toAkkaApps;
+  const EVENT_NAME = 'UserBroadcastCamStartMsg';
+
+  const { meetingId, requesterUserId, requesterToken } = credentials;
+
+  Logger.info(' user sharing webcam: ', credentials);
+
+  check(meetingId, String);
+  check(requesterUserId, String);
+  check(requesterToken, String);
+  // check(message, Object);
+
+  // const actionName = 'joinVideo';
+  /* TODO throw an error if user has no permission to share webcam
+  if (!isAllowedTo(actionName, credentials)) {
+    throw new Meteor.Error('not-allowed', `You are not allowed to share webcam`);
+  } */
+
+  const payload = {
+    stream: message,
+  };
+
+  return RedisPubSub.publishUserMessage(CHANNEL, EVENT_NAME, meetingId, requesterUserId, payload);
+}
diff --git a/bigbluebutton-html5/imports/api/video/server/methods/userUnshareWebcam.js b/bigbluebutton-html5/imports/api/video/server/methods/userUnshareWebcam.js
new file mode 100644
index 0000000000000000000000000000000000000000..9e562e07457e183408840d2111b8b73a7ab2a60d
--- /dev/null
+++ b/bigbluebutton-html5/imports/api/video/server/methods/userUnshareWebcam.js
@@ -0,0 +1,31 @@
+import { Meteor } from 'meteor/meteor';
+import { check } from 'meteor/check';
+import Logger from '/imports/startup/server/logger';
+import RedisPubSub from '/imports/startup/server/redis';
+
+export default function userUnshareWebcam(credentials, message) {
+  const REDIS_CONFIG = Meteor.settings.private.redis;
+  const CHANNEL = REDIS_CONFIG.channels.toAkkaApps;
+  const EVENT_NAME = 'UserBroadcastCamStopMsg';
+
+  const { meetingId, requesterUserId, requesterToken } = credentials;
+
+  Logger.info(' user unsharing webcam: ', credentials);
+
+  check(meetingId, String);
+  check(requesterUserId, String);
+  check(requesterToken, String);
+  // check(message, Object);
+
+  // const actionName = 'joinVideo';
+  /* TODO throw an error if user has no permission to share webcam
+  if (!isAllowedTo(actionName, credentials)) {
+    throw new Meteor.Error('not-allowed', `You are not allowed to share webcam`);
+  } */
+
+  const payload = {
+    stream: message,
+  };
+
+  return RedisPubSub.publishUserMessage(CHANNEL, EVENT_NAME, meetingId, requesterUserId, payload);
+}
diff --git a/bigbluebutton-html5/imports/api/video/server/modifiers/sharedWebcam.js b/bigbluebutton-html5/imports/api/video/server/modifiers/sharedWebcam.js
new file mode 100644
index 0000000000000000000000000000000000000000..749407ffc44860feb53b2389a2f68ce6d2e60bf5
--- /dev/null
+++ b/bigbluebutton-html5/imports/api/video/server/modifiers/sharedWebcam.js
@@ -0,0 +1,33 @@
+import Logger from '/imports/startup/server/logger';
+import Users from '/imports/api/users';
+import { check }  from 'meteor/check';
+
+export default function sharedWebcam(meetingId, userId) {
+  check(meetingId, String);
+  check(userId, String);
+
+  const selector = {
+    meetingId,
+    userId,
+  };
+
+  const modifier = {
+    $set: {
+      meetingId,
+      userId,
+      has_stream: true,
+    },
+  };
+
+  const cb = (err, numChanged) => {
+    if (err) {
+      return Logger.error(`Adding user to collection: ${err}`);
+    }
+
+    if (numChanged) {
+      return Logger.info(`Upserted user id=${userId} meeting=${meetingId}`);
+    }
+  };
+
+  return Users.upsert(selector, modifier, cb);
+}
diff --git a/bigbluebutton-html5/imports/api/video/server/modifiers/unsharedWebcam.js b/bigbluebutton-html5/imports/api/video/server/modifiers/unsharedWebcam.js
new file mode 100644
index 0000000000000000000000000000000000000000..0ef8f0be36d552cd1610f739b30515f75308fac2
--- /dev/null
+++ b/bigbluebutton-html5/imports/api/video/server/modifiers/unsharedWebcam.js
@@ -0,0 +1,33 @@
+import Logger from '/imports/startup/server/logger';
+import Users from '/imports/api/users';
+import { check } from 'meteor/check';
+
+export default function unsharedWebcam(meetingId, userId) {
+  check(meetingId, String);
+  check(userId, String);
+
+  const selector = {
+    meetingId,
+    userId,
+  };
+
+  const modifier = {
+    $set: {
+      meetingId,
+      userId,
+      has_stream: false,
+    },
+  };
+
+  const cb = (err, numChanged) => {
+    if (err) {
+      return Logger.error(`Adding user to collection: ${err}`);
+    }
+
+    if (numChanged) {
+      return Logger.info(`Upserted user id=${userId} meeting=${meetingId}`);
+    }
+  };
+
+  return Users.upsert(selector, modifier, cb);
+}
diff --git a/bigbluebutton-html5/imports/api/voice-users/server/handlers/joinVoiceUser.js b/bigbluebutton-html5/imports/api/voice-users/server/handlers/joinVoiceUser.js
index 0b0d8d65b40aa61425519c4a4423ca55256e27f7..949df6d6cf29a61606bd150bf2deb1fd62bd2fff 100644
--- a/bigbluebutton-html5/imports/api/voice-users/server/handlers/joinVoiceUser.js
+++ b/bigbluebutton-html5/imports/api/voice-users/server/handlers/joinVoiceUser.js
@@ -1,5 +1,7 @@
 import { check } from 'meteor/check';
 
+import Logger from '/imports/startup/server/logger';
+import Users from '/imports/api/users';
 import addVoiceUser from '../modifiers/addVoiceUser';
 
 export default function handleJoinVoiceUser({ body }, meetingId) {
@@ -7,6 +9,74 @@ export default function handleJoinVoiceUser({ body }, meetingId) {
   voiceUser.joined = true;
 
   check(meetingId, String);
+  check(voiceUser, {
+    voiceConf: String,
+    intId: String,
+    voiceUserId: String,
+    callerName: String,
+    callerNum: String,
+    muted: Boolean,
+    talking: Boolean,
+    callingWith: String,
+    listenOnly: Boolean,
+    joined: Boolean,
+  });
+
+  const {
+    intId,
+    callerName,
+  } = voiceUser;
+
+  if (intId.toString().startsWith('v_')) {
+    /* voice-only user - called into the conference */
+
+    const selector = {
+      meetingId,
+      userId: intId,
+    };
+
+    const USER_CONFIG = Meteor.settings.public.user;
+    const ROLE_VIEWER = USER_CONFIG.role_viewer;
+
+    const modifier = {
+      $set: {
+        meetingId,
+        connectionStatus: 'online',
+        roles: [ROLE_VIEWER.toLowerCase()],
+        sortName: callerName.trim().toLowerCase(),
+        color: '#ffffff', // TODO
+        intId,
+        extId: intId, // TODO
+        name: callerName,
+        role: ROLE_VIEWER.toLowerCase(),
+        guest: false,
+        authed: true,
+        waitingForAcceptance: false,
+        emoji: 'none',
+        presenter: false,
+        locked: false, // TODO
+        avatar: '',
+      },
+    };
+
+    const cb = (err, numChanged) => {
+      if (err) {
+        return Logger.error(`Adding call-in user to VoiceUser collection: ${err}`);
+      }
+
+      const { insertedId } = numChanged;
+      if (insertedId) {
+        return Logger.info(`Added a call-in user id=${intId} meeting=${meetingId}`);
+      }
+
+      return Logger.info(`Upserted a call-in user id=${intId} meeting=${meetingId}`);
+    };
+
+    Users.upsert(selector, modifier, cb);
+  } else {
+
+    /* there is a corresponding web user in Users collection -- no need to add new one */
+  }
 
   return addVoiceUser(meetingId, voiceUser);
 }
diff --git a/bigbluebutton-html5/imports/api/voice-users/server/handlers/leftVoiceUser.js b/bigbluebutton-html5/imports/api/voice-users/server/handlers/leftVoiceUser.js
index e2e4baaa154249d57a35afad4bffa01ee4c9cde8..5fb2e788b3a950300cc9b731ad6bae99e35389f7 100644
--- a/bigbluebutton-html5/imports/api/voice-users/server/handlers/leftVoiceUser.js
+++ b/bigbluebutton-html5/imports/api/voice-users/server/handlers/leftVoiceUser.js
@@ -1,11 +1,20 @@
 import { check } from 'meteor/check';
 
-import removeVoiceUser from '../modifiers/removeVoiceUser';
+import removeVoiceUser from '/imports/api/voice-users/server/modifiers/removeVoiceUser';
+import removeUser from '/imports/api/users/server/modifiers/removeUser';
 
 export default function handleVoiceUpdate({ body }, meetingId) {
   const voiceUser = body;
 
   check(meetingId, String);
+  check(voiceUser, {
+    voiceConf: String,
+    intId: String,
+    voiceUserId: String,
+  });
 
+  const { intId } = voiceUser;
+
+  removeUser(meetingId, intId);
   return removeVoiceUser(meetingId, voiceUser);
 }
diff --git a/bigbluebutton-html5/imports/api/voice-users/server/methods.js b/bigbluebutton-html5/imports/api/voice-users/server/methods.js
index 8b1515111a87a191036adc41126c8a5cf2e8e99b..33e995e1146a1e31c7ea248897fc76befc096792 100644
--- a/bigbluebutton-html5/imports/api/voice-users/server/methods.js
+++ b/bigbluebutton-html5/imports/api/voice-users/server/methods.js
@@ -2,10 +2,12 @@ import { Meteor } from 'meteor/meteor';
 import mapToAcl from '/imports/startup/mapToAcl';
 import listenOnlyToggle from './methods/listenOnlyToggle';
 import muteToggle from './methods/muteToggle';
+import ejectUserFromVoice from './methods/ejectUserFromVoice';
 
-Meteor.methods(mapToAcl(['methods.listenOnlyToggle', 'methods.toggleSelfVoice', 'methods.toggleVoice',
-], {
+Meteor.methods(mapToAcl(['methods.listenOnlyToggle', 'methods.toggleSelfVoice',
+  'methods.toggleVoice', 'methods.ejectUserFromVoice'], {
   listenOnlyToggle,
   toggleSelfVoice: (credentials) => { muteToggle(credentials, credentials.requesterUserId); },
   toggleVoice: muteToggle,
+  ejectUserFromVoice,
 }));
diff --git a/bigbluebutton-html5/imports/api/voice-users/server/methods/ejectUserFromVoice.js b/bigbluebutton-html5/imports/api/voice-users/server/methods/ejectUserFromVoice.js
new file mode 100644
index 0000000000000000000000000000000000000000..bc8fe6f20054cbb59ed1247923aa65583b1e5b89
--- /dev/null
+++ b/bigbluebutton-html5/imports/api/voice-users/server/methods/ejectUserFromVoice.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 ejectUserFromVoice(credentials, userId) {
+  const REDIS_CONFIG = Meteor.settings.private.redis;
+  const CHANNEL = REDIS_CONFIG.channels.toAkkaApps;
+  const EVENT_NAME = 'EjectUserFromVoiceCmdMsg';
+
+  const { requesterUserId, meetingId } = credentials;
+
+  check(meetingId, String);
+  check(requesterUserId, String);
+  check(userId, String);
+
+  const payload = {
+    userId,
+    ejectedBy: requesterUserId,
+  };
+
+  return RedisPubSub.publishUserMessage(CHANNEL, EVENT_NAME, meetingId, requesterUserId, payload);
+}
diff --git a/bigbluebutton-html5/imports/api/voice-users/server/methods/listenOnlyToggle.js b/bigbluebutton-html5/imports/api/voice-users/server/methods/listenOnlyToggle.js
index 38077b43feb48b1826aaac672513801be95c871f..fc53115057a4911f4051049b86d9d2dca97e42dc 100644
--- a/bigbluebutton-html5/imports/api/voice-users/server/methods/listenOnlyToggle.js
+++ b/bigbluebutton-html5/imports/api/voice-users/server/methods/listenOnlyToggle.js
@@ -6,7 +6,7 @@ import Meetings from '/imports/api/meetings';
 import VoiceUsers from '/imports/api/voice-users';
 
 export default function listenOnlyToggle(credentials, isJoining = true) {
-  const REDIS_CONFIG = Meteor.settings.redis;
+  const REDIS_CONFIG = Meteor.settings.private.redis;
   const CHANNEL = REDIS_CONFIG.channels.toAkkaApps;
 
   const { meetingId, requesterUserId } = credentials;
@@ -30,8 +30,7 @@ export default function listenOnlyToggle(credentials, isJoining = true) {
   const Meeting = Meetings.findOne({ meetingId });
 
   if (!VoiceUser) {
-    throw new Meteor.Error(
-      'user-not-found', 'You need a valid user to be able to toggle audio');
+    throw new Meteor.Error('user-not-found', 'You need a valid user to be able to toggle audio');
   }
 
   // check(User.user.name, String);
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 ea1d4dedd33eacfab09d102f91d291917435a43a..a5377f177326825bc6c7c38fa038575aeebc7936 100644
--- a/bigbluebutton-html5/imports/api/voice-users/server/methods/muteToggle.js
+++ b/bigbluebutton-html5/imports/api/voice-users/server/methods/muteToggle.js
@@ -5,7 +5,7 @@ import Users from '/imports/api/users';
 import VoiceUsers from '/imports/api/voice-users';
 
 export default function muteToggle(credentials, userId) {
-  const REDIS_CONFIG = Meteor.settings.redis;
+  const REDIS_CONFIG = Meteor.settings.private.redis;
   const CHANNEL = REDIS_CONFIG.channels.toAkkaApps;
   const EVENT_NAME = 'MuteUserCmdMsg';
   const APP_CONFIG = Meteor.settings.public.app;
diff --git a/bigbluebutton-html5/imports/api/whiteboard-multi-user/server/methods/changeWhiteboardAccess.js b/bigbluebutton-html5/imports/api/whiteboard-multi-user/server/methods/changeWhiteboardAccess.js
index a1edfb3afa8210f5942a16571ba0a8523cdb12ed..92c7ff86f57e20aaea29644355532b19905a4586 100644
--- a/bigbluebutton-html5/imports/api/whiteboard-multi-user/server/methods/changeWhiteboardAccess.js
+++ b/bigbluebutton-html5/imports/api/whiteboard-multi-user/server/methods/changeWhiteboardAccess.js
@@ -3,7 +3,7 @@ import { Meteor } from 'meteor/meteor';
 import { check } from 'meteor/check';
 
 export default function changeWhiteboardAccess(credentials, multiUser) {
-  const REDIS_CONFIG = Meteor.settings.redis;
+  const REDIS_CONFIG = Meteor.settings.private.redis;
   const CHANNEL = REDIS_CONFIG.channels.toAkkaApps;
   const EVENT_NAME = 'ModifyWhiteboardAccessPubMsg';
 
diff --git a/bigbluebutton-html5/imports/startup/client/base.jsx b/bigbluebutton-html5/imports/startup/client/base.jsx
index d825b5dc704208a5eea0c51fc5d469ee351e829d..94c8f38fd074425a6306cf10fb09f1bf293ba131 100644
--- a/bigbluebutton-html5/imports/startup/client/base.jsx
+++ b/bigbluebutton-html5/imports/startup/client/base.jsx
@@ -1,5 +1,6 @@
 import React, { Component } from 'react';
-import { createContainer } from 'meteor/react-meteor-data';
+import { withTracker } from 'meteor/react-meteor-data';
+import { withRouter } from 'react-router';
 import PropTypes from 'prop-types';
 import Auth from '/imports/ui/services/auth';
 import AppContainer from '/imports/ui/components/app/container';
@@ -83,25 +84,32 @@ Base.defaultProps = defaultProps;
 
 const SUBSCRIPTIONS_NAME = [
   'users', 'chat', 'cursor', 'meetings', 'polls', 'presentations', 'annotations',
-  'slides', 'captions', 'breakouts', 'voiceUsers', 'whiteboard-multi-user',
+  'slides', 'captions', 'breakouts', 'voiceUsers', 'whiteboard-multi-user', 'screenshare',
 ];
 
-const BaseContainer = createContainer(({ params }) => {
+const BaseContainer = withRouter(withTracker(({ params, router }) => {
   if (params.errorCode) return params;
 
   if (!Auth.loggedIn) {
-    return {
-      errorCode: 401,
-      error: 'You are unauthorized to access this meeting',
-    };
+    return router.push('/logout');
   }
 
-  const credentials = Auth.credentials;
-  const subscriptionsHandlers = SUBSCRIPTIONS_NAME.map(name => Meteor.subscribe(name, credentials));
+  const { credentials } = Auth;
+
+  const subscriptionErrorHandler = {
+    onError: (error) => {
+      console.error(error);
+      return router.push('/logout');
+    },
+  };
+
+  const subscriptionsHandlers = SUBSCRIPTIONS_NAME.map(name =>
+    Meteor.subscribe(name, credentials, subscriptionErrorHandler));
+
   return {
     locale: Settings.application.locale,
     subscriptionsReady: subscriptionsHandlers.every(handler => handler.ready()),
   };
-}, Base);
+})(Base));
 
 export default BaseContainer;
diff --git a/bigbluebutton-html5/imports/startup/mapToAcl.js b/bigbluebutton-html5/imports/startup/mapToAcl.js
index 6b4d4755eb7459fd6a52612759724c20c656783e..a21cfbd94589fe511cc0f173fb31a942327ed39a 100644
--- a/bigbluebutton-html5/imports/startup/mapToAcl.js
+++ b/bigbluebutton-html5/imports/startup/mapToAcl.js
@@ -1,13 +1,14 @@
 import Acl from '/imports/startup/acl';
 import { Meteor } from 'meteor/meteor';
-import Logger from '/imports/startup/server/logger';
 
 const injectAclActionCheck = (name, handler) => (
   (...args) => {
     const credentials = args[0];
     if (!Acl.can(name, credentials)) {
-      throw new Meteor.Error('acl-not-allowed',
-        `The user can't perform the action "${name}".`);
+      throw new Meteor.Error(
+        'acl-not-allowed',
+        `The user can't perform the action "${name}".`,
+      );
     }
 
     return handler(...args);
@@ -18,8 +19,10 @@ const injectAclSubscribeCheck = (name, handler) => (
   (...args) => {
     const credentials = args[args.length - 1];
     if (!Acl.can(name, ...credentials)) {
-      Logger.error(`acl-not-allowed, the user can't perform the subscription "${name}".`);
-      return [];
+      throw new Meteor.Error(
+        'acl-not-allowed',
+        `The user can't perform the subscription "${name}".`,
+      );
     }
 
     return handler(...credentials);
diff --git a/bigbluebutton-html5/imports/startup/server/index.js b/bigbluebutton-html5/imports/startup/server/index.js
index 34a859972f8986ec0832ac74d3de56a875950f8b..8d23b4450c5ca1a019e25ae5667c21751c4a74af 100644
--- a/bigbluebutton-html5/imports/startup/server/index.js
+++ b/bigbluebutton-html5/imports/startup/server/index.js
@@ -3,14 +3,11 @@ import Logger from './logger';
 import Redis from './redis';
 import locales from '../../utils/locales';
 
-let DEFAULT_LANGUAGE = null;
 const availableLocales = [];
 
 Meteor.startup(() => {
   const APP_CONFIG = Meteor.settings.public.app;
-  Logger.info(`SERVER STARTED. ENV=${Meteor.settings.runtime.env}`, APP_CONFIG);
-
-  DEFAULT_LANGUAGE = Meteor.settings.public.app.defaultSettings.application.locale
+  Logger.info(`SERVER STARTED. DEV_ENV=${Meteor.isDevelopment} PROD_ENV=${Meteor.isProduction}`, APP_CONFIG);
 });
 
 WebApp.connectHandlers.use('/check', (req, res) => {
@@ -24,7 +21,7 @@ WebApp.connectHandlers.use('/check', (req, res) => {
 WebApp.connectHandlers.use('/locale', (req, res) => {
   const APP_CONFIG = Meteor.settings.public.app;
   const defaultLocale = APP_CONFIG.defaultSettings.application.locale;
-  const localeRegion = req.query.locale.split(/[-_]/g);;
+  const localeRegion = req.query.locale.split(/[-_]/g);
   const localeList = [defaultLocale, localeRegion[0]];
 
   let normalizedLocale = localeRegion[0];
@@ -39,7 +36,7 @@ WebApp.connectHandlers.use('/locale', (req, res) => {
     try {
       const data = Assets.getText(`locales/${locale}.json`);
       messages = Object.assign(messages, JSON.parse(data));
-      normalizedLocale = locale
+      normalizedLocale = locale;
     } catch (e) {
       // Getting here means the locale is not available on the files.
     }
diff --git a/bigbluebutton-html5/imports/startup/server/logger.js b/bigbluebutton-html5/imports/startup/server/logger.js
index 360c75656fed0ab71303ce0884d3c8676a564b74..25226349319cec1a258e07082b21c0de04f743c9 100644
--- a/bigbluebutton-html5/imports/startup/server/logger.js
+++ b/bigbluebutton-html5/imports/startup/server/logger.js
@@ -4,7 +4,9 @@ import Winston from 'winston';
 const Logger = new Winston.Logger();
 
 Logger.configure({
-  levels: { error: 0, warn: 1, info: 2, verbose: 3, debug: 4 },
+  levels: {
+    error: 0, warn: 1, info: 2, verbose: 3, debug: 4,
+  },
   colors: {
     error: 'red',
     warn: 'yellow',
@@ -23,15 +25,15 @@ Logger.add(Winston.transports.Console, {
 });
 
 Meteor.startup(() => {
-  const LOG_CONFIG = Meteor.settings.log || {};
-  let filename = LOG_CONFIG.filename;
+  const LOG_CONFIG = Meteor.settings.private.log || {};
+  let { filename } = LOG_CONFIG;
 
   // Set Logger message level priority for the console
   Logger.transports.console.level = LOG_CONFIG.level;
 
   // Determine file to write logs to
   if (filename) {
-    if (Meteor.settings.runtime.env === 'development') {
+    if (Meteor.isDevelopment) {
       const path = Npm.require('path');
       filename = path.join(process.env.PWD, filename);
     }
diff --git a/bigbluebutton-html5/imports/startup/server/redis.js b/bigbluebutton-html5/imports/startup/server/redis.js
index 4e15cf962aea24a8f79b2d77b3655f2a3a7acd9f..1a2cec69747591bf404b47ea051616c433abd44e 100644
--- a/bigbluebutton-html5/imports/startup/server/redis.js
+++ b/bigbluebutton-html5/imports/startup/server/redis.js
@@ -47,8 +47,8 @@ class MettingMessageQueue {
     const { envelope } = data.parsedMessage;
     const { header } = data.parsedMessage.core;
     const { body } = data.parsedMessage.core;
+    const { meetingId } = header;
     const eventName = header.name;
-    const meetingId = header.meetingId;
     const isAsync = this.asyncMessages.includes(channel)
       || this.asyncMessages.includes(eventName);
 
@@ -91,7 +91,6 @@ class MettingMessageQueue {
 }
 
 class RedisPubSub {
-
   static handlePublishError(err) {
     if (err) {
       Logger.error(err);
@@ -136,7 +135,7 @@ class RedisPubSub {
     if (this.didSendRequestEvent) return;
 
     // populate collections with pre-existing data
-    const REDIS_CONFIG = Meteor.settings.redis;
+    const REDIS_CONFIG = Meteor.settings.private.redis;
     const CHANNEL = REDIS_CONFIG.channels.toAkkaApps;
     const EVENT_NAME = 'GetAllMeetingsReqMsg';
 
@@ -229,7 +228,7 @@ class RedisPubSub {
 const RedisPubSubSingleton = new RedisPubSub();
 
 Meteor.startup(() => {
-  const REDIS_CONFIG = Meteor.settings.redis;
+  const REDIS_CONFIG = Meteor.settings.private.redis;
 
   RedisPubSubSingleton.updateConfig(REDIS_CONFIG);
   RedisPubSubSingleton.init();
diff --git a/bigbluebutton-html5/imports/ui/components/about/container.jsx b/bigbluebutton-html5/imports/ui/components/about/container.jsx
index ea2dc8fb964e907249ed8acc38811beace0260ea..6999424fe6d79549065865496c76fb9845c87b21 100644
--- a/bigbluebutton-html5/imports/ui/components/about/container.jsx
+++ b/bigbluebutton-html5/imports/ui/components/about/container.jsx
@@ -1,5 +1,5 @@
 import React from 'react';
-import { createContainer } from 'meteor/react-meteor-data';
+import { withTracker } from 'meteor/react-meteor-data';
 
 import AboutComponent from './component';
 
@@ -16,4 +16,4 @@ const getClientBuildInfo = function () {
   };
 };
 
-export default createContainer(() => getClientBuildInfo(), AboutContainer);
+export default withTracker(() => getClientBuildInfo())(AboutContainer);
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 0c71b45665cec3428751f1d6d33ba73eb1398463..41d7800a78ba4db3de9d43a4e4fc411291aabf95 100644
--- 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,17 +1,15 @@
 import React, { Component } from 'react';
 import PropTypes from 'prop-types';
 import { defineMessages, injectIntl, intlShape } from 'react-intl';
-
 import Button from '/imports/ui/components/button/component';
 import Dropdown from '/imports/ui/components/dropdown/component';
 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 PresentationUploaderContainer from '/imports/ui/components/presentation/presentation-uploader/container';
 import { withModalMounter } from '/imports/ui/components/modal/service';
-import styles from '../styles';
+import { styles } from '../styles';
 
 const propTypes = {
   isUserPresenter: PropTypes.bool.isRequired,
@@ -32,6 +30,22 @@ const intlMessages = defineMessages({
     id: 'app.actionsBar.actionsDropdown.presentationDesc',
     description: 'adds context to upload presentation option',
   },
+  desktopShareLabel: {
+    id: 'app.actionsBar.actionsDropdown.desktopShareLabel',
+    description: 'Desktop Share option label',
+  },
+  stopDesktopShareLabel: {
+    id: 'app.actionsBar.actionsDropdown.stopDesktopShareLabel',
+    description: 'Stop Desktop Share option label',
+  },
+  desktopShareDesc: {
+    id: 'app.actionsBar.actionsDropdown.desktopShareDesc',
+    description: 'adds context to desktop share option',
+  },
+  stopDesktopShareDesc: {
+    id: 'app.actionsBar.actionsDropdown.stopDesktopShareDesc',
+    description: 'adds context to stop desktop share option',
+  },
 });
 
 class ActionsDropdown extends Component {
@@ -53,7 +67,13 @@ class ActionsDropdown extends Component {
   }
 
   render() {
-    const { intl, isUserPresenter } = this.props;
+    const {
+      intl,
+      isUserPresenter,
+      handleShareScreen,
+      handleUnshareScreen,
+      isVideoBroadcasting,
+    } = this.props;
 
     if (!isUserPresenter) return null;
 
@@ -61,6 +81,8 @@ class ActionsDropdown extends Component {
       <Dropdown ref={(ref) => { this._dropdown = ref; }} >
         <DropdownTrigger tabIndex={0} >
           <Button
+            hideLabel
+            aria-label={intl.formatMessage(intlMessages.actionsLabel)}
             className={styles.button}
             label={intl.formatMessage(intlMessages.actionsLabel)}
             icon="plus"
@@ -78,6 +100,18 @@ class ActionsDropdown extends Component {
               description={intl.formatMessage(intlMessages.presentationDesc)}
               onClick={this.handlePresentationClick}
             />
+            <DropdownListItem
+              icon="desktop"
+              label={intl.formatMessage(intlMessages.desktopShareLabel)}
+              description={intl.formatMessage(intlMessages.desktopShareDesc)}
+              onClick={handleShareScreen}
+            />
+            <DropdownListItem
+              icon="desktop"
+              label={intl.formatMessage(intlMessages.stopDesktopShareLabel)}
+              description={intl.formatMessage(intlMessages.stopDesktopShareDesc)}
+              onClick={handleUnshareScreen}
+            />
           </DropdownList>
         </DropdownContent>
       </Dropdown>
diff --git a/bigbluebutton-html5/imports/ui/components/actions-bar/component.jsx b/bigbluebutton-html5/imports/ui/components/actions-bar/component.jsx
index 74482af8407f3fe4d6d71c3eb91d014b6a897779..3d13ae4cffba0713e6af1ef9ab0a5ac28301ee3c 100644
--- a/bigbluebutton-html5/imports/ui/components/actions-bar/component.jsx
+++ b/bigbluebutton-html5/imports/ui/components/actions-bar/component.jsx
@@ -1,22 +1,33 @@
 import React from 'react';
-import styles from './styles.scss';
+import { styles } from './styles.scss';
 import EmojiSelect from './emoji-select/component';
 import ActionsDropdown from './actions-dropdown/component';
 import AudioControlsContainer from '../audio/audio-controls/container';
+import JoinVideoOptionsContainer from '../video-dock/video-menu/container';
 
 const ActionsBar = ({
   isUserPresenter,
+  handleExitVideo,
+  handleJoinVideo,
+  handleShareScreen,
+  handleUnshareScreen,
+  isVideoBroadcasting,
   emojiList,
   emojiSelected,
   handleEmojiChange,
 }) => (
   <div className={styles.actionsbar}>
     <div className={styles.left}>
-      <ActionsDropdown {...{ isUserPresenter }} />
+      <ActionsDropdown {...{ isUserPresenter, handleShareScreen, handleUnshareScreen, isVideoBroadcasting}} />
     </div>
     <div className={styles.center}>
       <AudioControlsContainer />
-      {/* <JoinVideo /> */}
+      {Meteor.settings.public.kurento.enableVideo ?
+        <JoinVideoOptionsContainer
+          handleJoinVideo={handleJoinVideo}
+          handleCloseVideo={handleExitVideo}
+        />
+      : null}
       <EmojiSelect options={emojiList} selected={emojiSelected} onChange={handleEmojiChange} />
     </div>
   </div>
diff --git a/bigbluebutton-html5/imports/ui/components/actions-bar/container.jsx b/bigbluebutton-html5/imports/ui/components/actions-bar/container.jsx
index 943d138689e941a85ebf25c44ffb0dffbb5f31a6..a753c2cf520479cc08b2ae54f6a3fc0bffff0b72 100644
--- a/bigbluebutton-html5/imports/ui/components/actions-bar/container.jsx
+++ b/bigbluebutton-html5/imports/ui/components/actions-bar/container.jsx
@@ -1,13 +1,21 @@
 import React from 'react';
-import { createContainer } from 'meteor/react-meteor-data';
+import { withTracker } from 'meteor/react-meteor-data';
 import ActionsBar from './component';
 import Service from './service';
+import VideoService from '../video-dock/service';
+import { shareScreen, unshareScreen, isVideoBroadcasting } from '../screenshare/service';
 
 const ActionsBarContainer = props => <ActionsBar {...props} />;
 
-export default createContainer(() => ({
+export default withTracker(() => ({
   isUserPresenter: Service.isUserPresenter(),
   emojiList: Service.getEmojiList(),
   emojiSelected: Service.getEmoji(),
   handleEmojiChange: Service.setEmoji,
-}), ActionsBarContainer);
+  handleExitVideo: () => VideoService.exitVideo(),
+  handleJoinVideo: () => VideoService.joinVideo(),
+  handleShareScreen: () => shareScreen(),
+  handleUnshareScreen: () => unshareScreen(),
+  isVideoBroadcasting: () => isVideoBroadcasting(),
+
+}))(ActionsBarContainer);
diff --git a/bigbluebutton-html5/imports/ui/components/actions-bar/emoji-select/component.jsx b/bigbluebutton-html5/imports/ui/components/actions-bar/emoji-select/component.jsx
index f0644270e4c87c558f76a53f6d48be1a9de4eb03..5462e5586e4c623303d9a3347a1d6d1fbc09fb1a 100644
--- a/bigbluebutton-html5/imports/ui/components/actions-bar/emoji-select/component.jsx
+++ b/bigbluebutton-html5/imports/ui/components/actions-bar/emoji-select/component.jsx
@@ -9,7 +9,7 @@ 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 DropdownListSeparator from '/imports/ui/components/dropdown/list/separator/component';
-import styles from '../styles';
+import { styles } from '../styles';
 
 const intlMessages = defineMessages({
   statusTriggerLabel: {
@@ -42,17 +42,21 @@ const EmojiSelect = ({
   const statuses = Object.keys(options);
   const lastStatus = statuses.pop();
 
+  const statusLabel = statuses.indexOf(selected) === -1 ?
+    intl.formatMessage(intlMessages.statusTriggerLabel)
+    : intl.formatMessage({ id: `app.actionsBar.emojiMenu.${selected}Label` });
+
   return (
     <Dropdown autoFocus>
       <DropdownTrigger tabIndex={0}>
         <Button
           className={styles.button}
-          label={intl.formatMessage(intlMessages.statusTriggerLabel)}
-          aria-label={intl.formatMessage(intlMessages.changeStatusLabel)}
+          label={statusLabel}
+          aria-label={statusLabel}
           aria-describedby="currentStatus"
           icon={options[selected !== lastStatus ? selected : statuses[1]]}
           ghost={false}
-          hideLabel={false}
+          hideLabel
           circle
           size="lg"
           color="primary"
diff --git a/bigbluebutton-html5/imports/ui/components/actions-bar/video-button/component.jsx b/bigbluebutton-html5/imports/ui/components/actions-bar/video-button/component.jsx
index 7cdbe22ac2a5df1da1dddfc67a2d46183c2547fe..71bd4c06e87b5e9a3e11701a57b06d13a887fe04 100644
--- a/bigbluebutton-html5/imports/ui/components/actions-bar/video-button/component.jsx
+++ b/bigbluebutton-html5/imports/ui/components/actions-bar/video-button/component.jsx
@@ -1,6 +1,6 @@
 import React from 'react';
 import Button from '/imports/ui/components/button/component';
-// import styles from '../styles.scss';
+// import { styles } from '../styles.scss';
 
 export default class JoinVideo extends React.Component {
 
diff --git a/bigbluebutton-html5/imports/ui/components/app/component.jsx b/bigbluebutton-html5/imports/ui/components/app/component.jsx
index c12233cb70c94832dee4fd4fd16f67c1b052d904..eaa3f40354ae6245a9a840c2edb71860c75068b0 100644
--- a/bigbluebutton-html5/imports/ui/components/app/component.jsx
+++ b/bigbluebutton-html5/imports/ui/components/app/component.jsx
@@ -9,7 +9,7 @@ import ModalContainer from '../modal/container';
 import NotificationsBarContainer from '../notifications-bar/container';
 import AudioContainer from '../audio/container';
 import ChatNotificationContainer from '../chat/notification/container';
-import styles from './styles';
+import { styles } from './styles';
 
 const intlMessages = defineMessages({
   userListLabel: {
diff --git a/bigbluebutton-html5/imports/ui/components/app/container.jsx b/bigbluebutton-html5/imports/ui/components/app/container.jsx
index 88abd7c9b6e1e4d076700b4a8e27247437c05537..05d1ef72c97b74cf553f50ccf155fa2eaedc468e 100644
--- a/bigbluebutton-html5/imports/ui/components/app/container.jsx
+++ b/bigbluebutton-html5/imports/ui/components/app/container.jsx
@@ -1,5 +1,5 @@
 import React, { cloneElement } from 'react';
-import { createContainer } from 'meteor/react-meteor-data';
+import { withTracker } from 'meteor/react-meteor-data';
 import { withRouter } from 'react-router';
 import { defineMessages, injectIntl } from 'react-intl';
 import PropTypes from 'prop-types';
@@ -27,7 +27,7 @@ const propTypes = {
   navbar: PropTypes.node,
   actionsbar: PropTypes.node,
   media: PropTypes.node,
-  location: PropTypes.object.isRequired,
+  location: PropTypes.shape({}).isRequired,
 };
 
 const defaultProps = {
@@ -39,7 +39,7 @@ const defaultProps = {
 const intlMessages = defineMessages({
   kickedMessage: {
     id: 'app.error.kicked',
-    description: 'Message when the user is kicked out of the meeting',
+    description: 'Message when the user is removed from the conference',
   },
   waitingApprovalMessage: {
     id: 'app.guest.waiting',
@@ -72,8 +72,7 @@ const AppContainer = (props) => {
   );
 };
 
-export default withRouter(injectIntl(withModalMounter(createContainer((
-  { router, intl, baseControls }) => {
+export default withRouter(injectIntl(withModalMounter(withTracker(({ router, intl, baseControls }) => {
   const currentUser = Users.findOne({ userId: Auth.userID });
   const isMeetingBreakout = meetingIsBreakout();
 
@@ -84,10 +83,10 @@ export default withRouter(injectIntl(withModalMounter(createContainer((
   // Displayed error messages according to the mode (kicked, end meeting)
   const sendToError = (code, message) => {
     Auth.clearCredentials()
-        .then(() => {
-          router.push(`/error/${code}`);
-          baseControls.updateErrorState(message);
-        });
+      .then(() => {
+        router.push(`/error/${code}`);
+        baseControls.updateErrorState(message);
+      });
   };
 
   // Check if user is kicked out of the session
@@ -118,7 +117,7 @@ export default withRouter(injectIntl(withModalMounter(createContainer((
     closedCaption: getCaptionsStatus() ? <ClosedCaptionsContainer /> : null,
     fontSize: getFontSize(),
   };
-}, AppContainer))));
+})(AppContainer))));
 
 AppContainer.defaultProps = defaultProps;
 AppContainer.propTypes = propTypes;
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 4f121166a8b1825140100108391a53c0dfea6f9b..043a7d7c2e66a375b89adb592b5b28d901daa7fd 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,28 @@
 import React 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 styles from './styles';
+import { styles } from './styles';
+import cx from 'classnames';
+
+const intlMessages = defineMessages({
+  joinAudio: {
+    id: 'app.audio.joinAudio',
+    description: 'Join audio button label',
+  },
+  leaveAudio: {
+    id: 'app.audio.leaveAudio',
+    description: 'Leave audio button label',
+  },
+  muteAudio: {
+    id: 'app.actionsBar.muteLabel',
+    description: 'Mute audio button label',
+  },
+  unmuteAudio: {
+    id: 'app.actionsBar.unmuteLabel',
+    description: 'Unmute audio button label',
+  },
+});
 
 const propTypes = {
   handleToggleMuteMicrophone: PropTypes.func.isRequired,
@@ -12,6 +32,7 @@ const propTypes = {
   unmute: PropTypes.bool.isRequired,
   mute: PropTypes.bool.isRequired,
   join: PropTypes.bool.isRequired,
+  intl: intlShape.isRequired,
   glow: PropTypes.bool,
 };
 
@@ -28,6 +49,7 @@ const AudioControls = ({
   disable,
   glow,
   join,
+  intl,
 }) => (
   <span className={styles.container}>
     {mute ?
@@ -35,7 +57,9 @@ const AudioControls = ({
         className={glow ? cx(styles.button, styles.glow) : styles.button}
         onClick={handleToggleMuteMicrophone}
         disabled={disable}
-        label={unmute ? 'Unmute' : 'Mute'}
+        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"
@@ -45,7 +69,9 @@ const AudioControls = ({
       className={styles.button}
       onClick={join ? handleLeaveAudio : handleJoinAudio}
       disabled={disable}
-      label={join ? 'Leave Audio' : 'Join Audio'}
+      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"
@@ -56,4 +82,4 @@ const AudioControls = ({
 AudioControls.propTypes = propTypes;
 AudioControls.defaultProps = defaultProps;
 
-export default AudioControls;
+export default injectIntl(AudioControls);
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 83c595cb064c76983fdebc287c9a320e824485de..51578831f3ca51845c74adb06735c635abaa86ca 100644
--- a/bigbluebutton-html5/imports/ui/components/audio/audio-controls/container.jsx
+++ b/bigbluebutton-html5/imports/ui/components/audio/audio-controls/container.jsx
@@ -1,5 +1,5 @@
 import React from 'react';
-import { createContainer } from 'meteor/react-meteor-data';
+import { withTracker } from 'meteor/react-meteor-data';
 import { withModalMounter } from '/imports/ui/components/modal/service';
 import AudioControls from './component';
 import AudioModalContainer from '../audio-modal/container';
@@ -7,14 +7,14 @@ import Service from '../service';
 
 const AudioControlsContainer = props => <AudioControls {...props} />;
 
-export default withModalMounter(createContainer(({ 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));
+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));
diff --git a/bigbluebutton-html5/imports/ui/components/audio/audio-modal/component.jsx b/bigbluebutton-html5/imports/ui/components/audio/audio-modal/component.jsx
index 012a4caf8536274488ac776e7f130a21967d1555..ae9f35fc94ed2bc6afa79bb7ce5b30e997a2cc1e 100644
--- a/bigbluebutton-html5/imports/ui/components/audio/audio-modal/component.jsx
+++ b/bigbluebutton-html5/imports/ui/components/audio/audio-modal/component.jsx
@@ -3,7 +3,7 @@ import PropTypes from 'prop-types';
 import ModalBase from '/imports/ui/components/modal/base/component';
 import Button from '/imports/ui/components/button/component';
 import { defineMessages, injectIntl, intlShape } from 'react-intl';
-import styles from './styles';
+import { styles } from './styles';
 import PermissionsOverlay from '../permissions-overlay/component';
 import AudioSettings from '../audio-settings/component';
 import EchoTest from '../echo-test/component';
diff --git a/bigbluebutton-html5/imports/ui/components/audio/audio-modal/container.jsx b/bigbluebutton-html5/imports/ui/components/audio/audio-modal/container.jsx
index 543fe3e91b61dea0ff57fd3d54b2d16e9dc87144..b69051954fc5d59eed6d171846025cd7cd5ff111 100644
--- a/bigbluebutton-html5/imports/ui/components/audio/audio-modal/container.jsx
+++ b/bigbluebutton-html5/imports/ui/components/audio/audio-modal/container.jsx
@@ -1,41 +1,41 @@
 import React from 'react';
-import { createContainer } from 'meteor/react-meteor-data';
+import { withTracker } from 'meteor/react-meteor-data';
 import { withModalMounter } from '/imports/ui/components/modal/service';
 import AudioModal from './component';
 import Service from '../service';
 
 const AudioModalContainer = props => <AudioModal {...props} />;
 
-export default withModalMounter(createContainer(({ mountModal }) =>
-   ({
-     closeModal: () => {
-       if (!Service.isConnecting()) mountModal(null);
-     },
-     joinMicrophone: () =>
-       new Promise((resolve, reject) => {
-         Service.transferCall().then(() => {
-           mountModal(null);
-           resolve();
-         }).catch(() => {
-           Service.exitAudio();
-           reject();
-         });
-       }),
-     joinListenOnly: () => Service.joinListenOnly().then(() => mountModal(null)),
-     leaveEchoTest: () => {
-       if (!Service.isEchoTest()) {
-         return Promise.resolve();
-       }
-       return Service.exitAudio();
-     },
-     changeInputDevice: inputDeviceId => Service.changeInputDevice(inputDeviceId),
-     changeOutputDevice: outputDeviceId => Service.changeOutputDevice(outputDeviceId),
-     joinEchoTest: () => Service.joinEchoTest(),
-     exitAudio: () => Service.exitAudio(),
-     isConnecting: Service.isConnecting(),
-     isConnected: Service.isConnected(),
-     isEchoTest: Service.isEchoTest(),
-     inputDeviceId: Service.inputDeviceId(),
-     outputDeviceId: Service.outputDeviceId(),
-     showPermissionsOvelay: Service.isWaitingPermissions(),
-   }), AudioModalContainer));
+export default withModalMounter(withTracker(({ mountModal }) =>
+  ({
+    closeModal: () => {
+      if (!Service.isConnecting()) mountModal(null);
+    },
+    joinMicrophone: () =>
+      new Promise((resolve, reject) => {
+        Service.transferCall().then(() => {
+          mountModal(null);
+          resolve();
+        }).catch(() => {
+          Service.exitAudio();
+          reject();
+        });
+      }),
+    joinListenOnly: () => Service.joinListenOnly().then(() => mountModal(null)),
+    leaveEchoTest: () => {
+      if (!Service.isEchoTest()) {
+        return Promise.resolve();
+      }
+      return Service.exitAudio();
+    },
+    changeInputDevice: inputDeviceId => Service.changeInputDevice(inputDeviceId),
+    changeOutputDevice: outputDeviceId => Service.changeOutputDevice(outputDeviceId),
+    joinEchoTest: () => Service.joinEchoTest(),
+    exitAudio: () => Service.exitAudio(),
+    isConnecting: Service.isConnecting(),
+    isConnected: Service.isConnected(),
+    isEchoTest: Service.isEchoTest(),
+    inputDeviceId: Service.inputDeviceId(),
+    outputDeviceId: Service.outputDeviceId(),
+    showPermissionsOvelay: Service.isWaitingPermissions(),
+  }))(AudioModalContainer));
diff --git a/bigbluebutton-html5/imports/ui/components/audio/audio-settings/component.jsx b/bigbluebutton-html5/imports/ui/components/audio/audio-settings/component.jsx
index 848caa5e1c79e4d266c34d87f9d0f2ebd4060760..65984d34fdd3b7311add6268c239e3df18cd83de 100644
--- a/bigbluebutton-html5/imports/ui/components/audio/audio-settings/component.jsx
+++ b/bigbluebutton-html5/imports/ui/components/audio/audio-settings/component.jsx
@@ -6,7 +6,7 @@ import { withModalMounter } from '/imports/ui/components/modal/service';
 import DeviceSelector from '/imports/ui/components/audio/device-selector/component';
 import AudioTestContainer from '/imports/ui/components/audio/audio-test/container';
 import cx from 'classnames';
-import styles from './styles';
+import { styles } from './styles';
 
 const propTypes = {
   intl: intlShape.isRequired,
diff --git a/bigbluebutton-html5/imports/ui/components/audio/audio-test/component.jsx b/bigbluebutton-html5/imports/ui/components/audio/audio-test/component.jsx
index cb57d7fca081160092b97dbdcf90301e0eb41c36..cd89de97c77e44d43181147eabd23815b2f3e431 100644
--- a/bigbluebutton-html5/imports/ui/components/audio/audio-test/component.jsx
+++ b/bigbluebutton-html5/imports/ui/components/audio/audio-test/component.jsx
@@ -2,7 +2,7 @@ import React from 'react';
 import PropTypes from 'prop-types';
 import Button from '/imports/ui/components/button/component';
 import { defineMessages, intlShape, injectIntl } from 'react-intl';
-import styles from './styles.scss';
+import { styles } from './styles.scss';
 
 const propTypes = {
   intl: intlShape.isRequired,
diff --git a/bigbluebutton-html5/imports/ui/components/audio/audio-test/container.jsx b/bigbluebutton-html5/imports/ui/components/audio/audio-test/container.jsx
index 8e74d4fb66e964df7834c81b5cc31effa0395885..58e1f0501c52bbd7699876b0d4c11a5f40eada77 100644
--- a/bigbluebutton-html5/imports/ui/components/audio/audio-test/container.jsx
+++ b/bigbluebutton-html5/imports/ui/components/audio/audio-test/container.jsx
@@ -1,15 +1,15 @@
 import React from 'react';
-import { createContainer } from 'meteor/react-meteor-data';
+import { withTracker } from 'meteor/react-meteor-data';
 import Service from '/imports/ui/components/audio/service';
 import AudioTest from './component';
 
 const AudioTestContainer = props => <AudioTest {...props} />;
 
-export default createContainer(() => ({
+export default withTracker(() => ({
   outputDeviceId: Service.outputDeviceId(),
   handlePlayAudioSample: (deviceId) => {
     const sound = new Audio('resources/sounds/audioSample.mp3');
     if (deviceId && sound.setSinkId) sound.setSinkId(deviceId);
     sound.play();
   },
-}), AudioTestContainer);
+}))(AudioTestContainer);
diff --git a/bigbluebutton-html5/imports/ui/components/audio/container.jsx b/bigbluebutton-html5/imports/ui/components/audio/container.jsx
index c44556f1c6052d4efc4b2327df2169337e7a26ac..27371ff45c1b0d1d51c001b1815d39bcda4a942f 100644
--- a/bigbluebutton-html5/imports/ui/components/audio/container.jsx
+++ b/bigbluebutton-html5/imports/ui/components/audio/container.jsx
@@ -1,8 +1,9 @@
 import React from 'react';
-import { createContainer } from 'meteor/react-meteor-data';
+import { withTracker } from 'meteor/react-meteor-data';
 import { withModalMounter } from '/imports/ui/components/modal/service';
 import { injectIntl, defineMessages } from 'react-intl';
 import PropTypes from 'prop-types';
+import Breakouts from '/imports/api/breakouts';
 import Service from './service';
 import Audio from './component';
 import AudioModalContainer from './audio-modal/container';
@@ -55,10 +56,20 @@ const AudioContainer = props => <Audio {...props} />;
 
 let didMountAutoJoin = false;
 
-export default withModalMounter(injectIntl(createContainer(({ mountModal, intl }) => {
+export default withModalMounter(injectIntl(withTracker(({ mountModal, intl }) => {
   const APP_CONFIG = Meteor.settings.public.app;
 
   const { autoJoinAudio } = APP_CONFIG;
+  const openAudioModal = mountModal.bind(
+    null,
+    <AudioModalContainer />,
+  );
+
+  Breakouts.find().observeChanges({
+    removed() {
+      setTimeout(() => openAudioModal(), 0);
+    },
+  });
 
   const messages = {
     info: {
@@ -80,11 +91,11 @@ export default withModalMounter(injectIntl(createContainer(({ mountModal, intl }
       Service.init(messages);
       Service.changeOutputDevice(document.querySelector('#remote-media').sinkId);
       if (!autoJoinAudio || didMountAutoJoin) return;
-      mountModal(<AudioModalContainer />);
+      openAudioModal();
       didMountAutoJoin = true;
     },
   };
-}, AudioContainer)));
+})(AudioContainer)));
 
 AudioContainer.propTypes = propTypes;
 AudioContainer.defaultProps = defaultProps;
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 7c68859eb02a25eb199a7b38f2de24b301c70b74..5e32ca79f6b1e8da44eca5e2aab665a1eb9dcfb7 100644
--- a/bigbluebutton-html5/imports/ui/components/audio/device-selector/component.jsx
+++ b/bigbluebutton-html5/imports/ui/components/audio/device-selector/component.jsx
@@ -2,7 +2,7 @@ import React, { Component } from 'react';
 import _ from 'lodash';
 import PropTypes from 'prop-types';
 import cx from 'classnames';
-import styles from '../audio-modal/styles';
+import { styles } from '../audio-modal/styles';
 
 const propTypes = {
   kind: PropTypes.oneOf(['audioinput', 'audiooutput', 'videoinput']),
diff --git a/bigbluebutton-html5/imports/ui/components/audio/echo-test/component.jsx b/bigbluebutton-html5/imports/ui/components/audio/echo-test/component.jsx
index a437d2de5876bbd94db87ca7931e5d5aae9e1ca4..3cb2c70f4e511e047d621486f7c743f9e45f553f 100644
--- a/bigbluebutton-html5/imports/ui/components/audio/echo-test/component.jsx
+++ b/bigbluebutton-html5/imports/ui/components/audio/echo-test/component.jsx
@@ -2,7 +2,7 @@ import React, { Component } from 'react';
 import PropTypes from 'prop-types';
 import Button from '/imports/ui/components/button/component';
 import { defineMessages, intlShape, injectIntl } from 'react-intl';
-import styles from './styles';
+import { styles } from './styles';
 
 const intlMessages = defineMessages({
   yes: {
diff --git a/bigbluebutton-html5/imports/ui/components/audio/help/component.jsx b/bigbluebutton-html5/imports/ui/components/audio/help/component.jsx
index 1e36cc2b9cbf00a120a94a8473f9f58f1cc47292..c5c0cd3b0b181245d857f2b7593da694488cb6ab 100644
--- a/bigbluebutton-html5/imports/ui/components/audio/help/component.jsx
+++ b/bigbluebutton-html5/imports/ui/components/audio/help/component.jsx
@@ -1,7 +1,7 @@
 import React, { Component } from 'react';
 import Button from '/imports/ui/components/button/component';
 import { injectIntl, defineMessages } from 'react-intl';
-import styles from './styles';
+import { styles } from './styles';
 
 const intlMessages = defineMessages({
   descriptionHelp: {
diff --git a/bigbluebutton-html5/imports/ui/components/audio/permissions-overlay/component.jsx b/bigbluebutton-html5/imports/ui/components/audio/permissions-overlay/component.jsx
index a086055ea7f25db818f29bf96fc51941c4bc0e6e..18098c9cb6be62914fb3fd225a389d08f5733cc3 100644
--- a/bigbluebutton-html5/imports/ui/components/audio/permissions-overlay/component.jsx
+++ b/bigbluebutton-html5/imports/ui/components/audio/permissions-overlay/component.jsx
@@ -1,6 +1,6 @@
 import React, { Component } from 'react';
 import { injectIntl, intlShape, defineMessages } from 'react-intl';
-import styles from './styles';
+import { styles } from './styles';
 
 const propTypes = {
   intl: intlShape.isRequired,
@@ -30,6 +30,10 @@ class PermissionsOverlay extends Component {
         top: '210px',
         left: '605px',
       },
+      Safari: {
+        top: '100px',
+        left: '100px',
+      },
     };
 
     const browser = window.bowser.name;
diff --git a/bigbluebutton-html5/imports/ui/components/button/component.jsx b/bigbluebutton-html5/imports/ui/components/button/component.jsx
index d15656fedd7fc503618946249006d47f3d71ea82..9756058446e8f5eb79028c0ce198eab91baa4ff8 100644
--- a/bigbluebutton-html5/imports/ui/components/button/component.jsx
+++ b/bigbluebutton-html5/imports/ui/components/button/component.jsx
@@ -1,7 +1,8 @@
 import React from 'react';
 import PropTypes from 'prop-types';
 import cx from 'classnames';
-import styles from './styles';
+import Tooltip from '/imports/ui/components/tooltip/component';
+import { styles } from './styles';
 import Icon from '../icon/component';
 import BaseButton from './base/component';
 
@@ -86,7 +87,6 @@ const defaultProps = {
 };
 
 export default class Button extends BaseButton {
-
   _getClassNames() {
     const {
       size,
@@ -113,8 +113,26 @@ export default class Button extends BaseButton {
   }
 
   render() {
-    const renderFuncName = this.props.circle ?
-      'renderCircle' : 'renderDefault';
+    const {
+      circle,
+      hideLabel,
+      label,
+      'aria-label' : ariaLabel
+    } = this.props;
+
+    const renderFuncName = circle ? 'renderCircle' : 'renderDefault';
+
+    if (hideLabel) {
+      const tooltipLabel = label ? label : ariaLabel;
+
+      return (
+        <Tooltip
+          title={tooltipLabel}
+        >
+          {this[renderFuncName]()}
+        </Tooltip>
+      );
+    }
 
     return this[renderFuncName]();
   }
diff --git a/bigbluebutton-html5/imports/ui/components/button/styles.scss b/bigbluebutton-html5/imports/ui/components/button/styles.scss
index 265380ea764a600c07ac93ccc8bf5ee214d2829a..89a0a15a201aec54775690b5dff355e1ae9222ec 100644
--- a/bigbluebutton-html5/imports/ui/components/button/styles.scss
+++ b/bigbluebutton-html5/imports/ui/components/button/styles.scss
@@ -43,7 +43,7 @@ $btn-jumbo-padding: $jumbo-padding-y $jumbo-padding-x;
   display: inline-block;
   border-radius: $border-size;
   font-weight: $btn-font-weight;
-  line-height: 1.5;
+  line-height: 1;
   text-align: center;
   white-space: nowrap;
   vertical-align: middle;
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 c63a4f1958430b71208d1e967174168dfb0868af..31f91b029122b8ff39439be6b4d6d3fab22b7195 100644
--- a/bigbluebutton-html5/imports/ui/components/chat/chat-dropdown/component.jsx
+++ b/bigbluebutton-html5/imports/ui/components/chat/chat-dropdown/component.jsx
@@ -13,7 +13,7 @@ import Acl from '/imports/startup/acl';
 import Button from '/imports/ui/components/button/component';
 
 import ChatService from './../service';
-import styles from './styles';
+import { styles } from './styles';
 
 const intlMessages = defineMessages({
   clear: {
@@ -127,7 +127,9 @@ class ChatDropdown extends Component {
             circle
             hideLabel
             color="primary"
+            label={intl.formatMessage(intlMessages.options)}
             aria-label={intl.formatMessage(intlMessages.options)}
+            onClick={() => null}
           />
         </DropdownTrigger>
         <DropdownContent placement="bottom right">
diff --git a/bigbluebutton-html5/imports/ui/components/chat/component.jsx b/bigbluebutton-html5/imports/ui/components/chat/component.jsx
index 75ab3deeddd6cfdea4004c67878d1e4354331a12..1570474fe802fb6f30a5ac5102fa568f42d9b8c0 100644
--- a/bigbluebutton-html5/imports/ui/components/chat/component.jsx
+++ b/bigbluebutton-html5/imports/ui/components/chat/component.jsx
@@ -3,7 +3,8 @@ import PropTypes from 'prop-types';
 import { Link } from 'react-router';
 import { defineMessages, injectIntl } from 'react-intl';
 import injectWbResizeEvent from '/imports/ui/components/presentation/resize-wrapper/component';
-import styles from './styles';
+import Button from '/imports/ui/components/button/component';
+import { styles } from './styles';
 import MessageForm from './message-form/component';
 import MessageList from './message-list/component';
 import ChatDropdown from './chat-dropdown/component';
@@ -56,10 +57,17 @@ const Chat = (props) => {
             <Link
               to="/users"
               role="button"
-              className={styles.closeIcon}
-              aria-label={intl.formatMessage(intlMessages.closeChatLabel, { 0: title })}
+              tabIndex={-1}
             >
-              <Icon iconName="close" onClick={() => actions.handleClosePrivateChat(chatID)} />
+              <Button
+                className={styles.closeBtn}
+                icon="close"
+                size="md"
+                hideLabel
+                onClick={() => actions.handleClosePrivateChat(chatID)}
+                aria-label={intl.formatMessage(intlMessages.closeChatLabel, { 0: title })}
+                label={intl.formatMessage(intlMessages.closeChatLabel, { 0: title })}
+              />
             </Link> :
             <ChatDropdown />
         }
@@ -94,17 +102,13 @@ const propTypes = {
   chatID: PropTypes.string.isRequired,
   chatName: PropTypes.string.isRequired,
   title: PropTypes.string.isRequired,
-  messages: PropTypes.arrayOf(
-    PropTypes.objectOf(
-      PropTypes.oneOfType([
-        PropTypes.array,
-        PropTypes.string,
-        PropTypes.number,
-        PropTypes.object,
-      ]),
-    ).isRequired,
-  ).isRequired,
-  scrollPosition: PropTypes.number.isRequired,
+  messages: PropTypes.arrayOf(PropTypes.objectOf(PropTypes.oneOfType([
+    PropTypes.array,
+    PropTypes.string,
+    PropTypes.number,
+    PropTypes.object,
+  ])).isRequired).isRequired,
+  scrollPosition: PropTypes.number,
   hasUnreadMessages: PropTypes.bool.isRequired,
   lastReadMessageTime: PropTypes.number.isRequired,
   partnerIsLoggedOut: PropTypes.bool.isRequired,
@@ -122,4 +126,9 @@ const propTypes = {
   }).isRequired,
 };
 
+const defaultProps = {
+  scrollPosition: 0,
+};
+
 Chat.propTypes = propTypes;
+Chat.defaultProps = defaultProps;
diff --git a/bigbluebutton-html5/imports/ui/components/chat/container.jsx b/bigbluebutton-html5/imports/ui/components/chat/container.jsx
index 088af86a6cdb495cde3446ad9cea55315b20b3b2..37d2e99eecadf6e1c18573b4dc2d996abbae99b4 100644
--- a/bigbluebutton-html5/imports/ui/components/chat/container.jsx
+++ b/bigbluebutton-html5/imports/ui/components/chat/container.jsx
@@ -1,6 +1,6 @@
-import React from 'react';
+import React, {Component} from 'react';
 import { defineMessages, injectIntl } from 'react-intl';
-import { createContainer } from 'meteor/react-meteor-data';
+import { withTracker } from 'meteor/react-meteor-data';
 import Chat from './component';
 import ChatService from './service';
 
@@ -27,14 +27,21 @@ const intlMessages = defineMessages({
   },
 });
 
-const ChatContainer = props =>
-  (
-    <Chat {...props}>
-      {props.children}
-    </Chat>
-  );
+class ChatContainer extends Component {
+  componentDidMount() {
+    // in case of reopening a chat, need to make sure it's removed from closed list
+    ChatService.removeFromClosedChatsSession(this.props.chatID);
+  }
+  render() {
+    return (
+      <Chat {...this.props}>
+        {this.props.children}
+      </Chat>
+    );
+  }
+}
 
-export default injectIntl(createContainer(({ params, intl }) => {
+export default injectIntl(withTracker(({ params, intl }) => {
   const chatID = params.chatID || PUBLIC_CHAT_KEY;
 
   let messages = [];
@@ -116,4 +123,4 @@ export default injectIntl(createContainer(({ params, intl }) => {
       handleReadMessage: timestamp => ChatService.updateUnreadMessage(chatID, timestamp),
     },
   };
-}, ChatContainer));
+})(ChatContainer));
diff --git a/bigbluebutton-html5/imports/ui/components/chat/message-form/component.jsx b/bigbluebutton-html5/imports/ui/components/chat/message-form/component.jsx
index dd280476795687d6c12f7a7b7cf5bcf54ab126a3..98716c98ee166b7555f5246028247c8dc593cafe 100644
--- a/bigbluebutton-html5/imports/ui/components/chat/message-form/component.jsx
+++ b/bigbluebutton-html5/imports/ui/components/chat/message-form/component.jsx
@@ -2,7 +2,7 @@ import React, { Component } from 'react';
 import { defineMessages, injectIntl } from 'react-intl';
 import cx from 'classnames';
 import TextareaAutosize from 'react-autosize-textarea';
-import styles from './styles';
+import { styles } from './styles';
 import Button from '../../button/component';
 
 const propTypes = {
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 089d6d92fcd80421c287de8bd2c91fe26d81eeba..61f4f5b8dcad3d9e59ca07a2e5b804dd2115d589 100644
--- a/bigbluebutton-html5/imports/ui/components/chat/message-list/component.jsx
+++ b/bigbluebutton-html5/imports/ui/components/chat/message-list/component.jsx
@@ -3,7 +3,7 @@ import PropTypes from 'prop-types';
 import { defineMessages, injectIntl } from 'react-intl';
 import _ from 'lodash';
 import Button from '/imports/ui/components/button/component';
-import styles from './styles';
+import { styles } from './styles';
 import MessageListItem from './message-list-item/component';
 
 const propTypes = {
diff --git a/bigbluebutton-html5/imports/ui/components/chat/message-list/message-list-item/component.jsx b/bigbluebutton-html5/imports/ui/components/chat/message-list/message-list-item/component.jsx
index 6d00b97e138321be956a5aaa3f7e0211756b3253..6db5ab50c8836f8dbe0c3633e3d37f64077c30cb 100644
--- a/bigbluebutton-html5/imports/ui/components/chat/message-list/message-list-item/component.jsx
+++ b/bigbluebutton-html5/imports/ui/components/chat/message-list/message-list-item/component.jsx
@@ -7,7 +7,7 @@ import _ from 'lodash';
 import UserAvatar from '/imports/ui/components/user-avatar/component';
 import Message from './message/component';
 
-import styles from './styles';
+import { styles } from './styles';
 
 const propTypes = {
   user: PropTypes.object,
diff --git a/bigbluebutton-html5/imports/ui/components/chat/notification/container.jsx b/bigbluebutton-html5/imports/ui/components/chat/notification/container.jsx
index ade6871a4c75c8d840c759296888dd34b61b65b2..b195501cf0d5fc73ec8a24fcd339c63e190696ec 100644
--- a/bigbluebutton-html5/imports/ui/components/chat/notification/container.jsx
+++ b/bigbluebutton-html5/imports/ui/components/chat/notification/container.jsx
@@ -1,5 +1,5 @@
 import React from 'react';
-import { createContainer } from 'meteor/react-meteor-data';
+import { withTracker } from 'meteor/react-meteor-data';
 import UserListService from '/imports/ui/components/user-list/service';
 import Settings from '/imports/ui/services/settings';
 import ChatNotification from './component';
@@ -8,7 +8,7 @@ const ChatNotificationContainer = props => (
   <ChatNotification {...props} />
 );
 
-export default createContainer(() => {
+export default withTracker(() => {
   const AppSettings = Settings.application;
   const openChats = UserListService.getOpenChats();
 
@@ -17,4 +17,4 @@ export default createContainer(() => {
     disableNotify: !AppSettings.chatPushNotifications,
     openChats,
   };
-}, ChatNotificationContainer);
+})(ChatNotificationContainer);
diff --git a/bigbluebutton-html5/imports/ui/components/chat/service.js b/bigbluebutton-html5/imports/ui/components/chat/service.js
index a030bf752b1949a6e8e2a25d585de987efd441c6..53f9994cd175d77f4164bd550d3386c2562dca2c 100644
--- a/bigbluebutton-html5/imports/ui/components/chat/service.js
+++ b/bigbluebutton-html5/imports/ui/components/chat/service.js
@@ -195,6 +195,14 @@ const closePrivateChat = (chatID) => {
   }
 };
 
+// if this private chat has been added to the list of closed ones, remove it
+const removeFromClosedChatsSession = (chatID) => {
+  const currentClosedChats = Storage.getItem(CLOSED_CHAT_LIST_KEY);
+  if (_.indexOf(currentClosedChats, chatID) > -1) {
+    Storage.setItem(CLOSED_CHAT_LIST_KEY, _.without(currentClosedChats, chatID));
+  }
+};
+
 // We decode to prevent HTML5 escaped characters.
 const htmlDecode = (input) => {
   const e = document.createElement('div');
@@ -229,6 +237,7 @@ export default {
   updateUnreadMessage,
   sendMessage,
   closePrivateChat,
+  removeFromClosedChatsSession,
   exportChat,
   clearPublicChatHistory,
 };
diff --git a/bigbluebutton-html5/imports/ui/components/chat/styles.scss b/bigbluebutton-html5/imports/ui/components/chat/styles.scss
index 3fb72049bdb51b8700ba500b6147faca4d6901d0..fc45c9915d073631aae5e273506aa5c945f94c06 100644
--- a/bigbluebutton-html5/imports/ui/components/chat/styles.scss
+++ b/bigbluebutton-html5/imports/ui/components/chat/styles.scss
@@ -1,6 +1,8 @@
 @import "/imports/ui/stylesheets/mixins/focus";
 @import "/imports/ui/stylesheets/variables/_all";
 
+$icon-offset: -.4em;
+
 .chat {
   background-color: #fff;
   padding: $md-padding-x;
@@ -36,7 +38,22 @@
   flex: 1;
 }
 
-.closeIcon {
+.closeBtn {
+  background-color: $color-white;
   flex: 0 0;
-  margin-left: $sm-padding-x / 2;
+  padding: 0 0.25rem !important;
+
+  i {
+    font-size: 0.85em;
+    color: $color-gray-dark !important;
+    top: $icon-offset;
+  }
+
+  &:focus,
+  &:hover{
+    background-color: $color-white !important;
+    i{
+      color: $color-gray;
+    }
+  }
 }
diff --git a/bigbluebutton-html5/imports/ui/components/checkbox/component.jsx b/bigbluebutton-html5/imports/ui/components/checkbox/component.jsx
index e59be89a507bfa0d72c88d37de117ebd719fabf1..c290664a4350031dab77ad6576b0a1055c3aa48f 100644
--- a/bigbluebutton-html5/imports/ui/components/checkbox/component.jsx
+++ b/bigbluebutton-html5/imports/ui/components/checkbox/component.jsx
@@ -2,7 +2,7 @@ import React, { Component } from 'react';
 import PropTypes from 'prop-types';
 import cx from 'classnames';
 import Icon from '../icon/component';
-import styles from './styles';
+import { styles } from './styles';
 
 const propTypes = {
   disabled: PropTypes.bool,
diff --git a/bigbluebutton-html5/imports/ui/components/closed-captions/component.jsx b/bigbluebutton-html5/imports/ui/components/closed-captions/component.jsx
index 9831e593e3acc68327c6fbdc9578f003a3f4f907..1a5952544ea418c0b50287cd494e99dd0ec525ac 100644
--- a/bigbluebutton-html5/imports/ui/components/closed-captions/component.jsx
+++ b/bigbluebutton-html5/imports/ui/components/closed-captions/component.jsx
@@ -1,7 +1,7 @@
 import React, { Component } from 'react';
 import PropTypes from 'prop-types';
 import injectWbResizeEvent from '/imports/ui/components/presentation/resize-wrapper/component';
-import styles from './styles.scss';
+import { styles } from './styles.scss';
 
 class ClosedCaptions extends Component {
   constructor(props) {
diff --git a/bigbluebutton-html5/imports/ui/components/closed-captions/container.jsx b/bigbluebutton-html5/imports/ui/components/closed-captions/container.jsx
index a8da014e7cb5ca165425f3ea9df6c2c6e36bfe18..d6c03fb2a8b2ecea879f4079236a2b39b10e5525 100644
--- a/bigbluebutton-html5/imports/ui/components/closed-captions/container.jsx
+++ b/bigbluebutton-html5/imports/ui/components/closed-captions/container.jsx
@@ -1,10 +1,10 @@
 import React from 'react';
-import { createContainer } from 'meteor/react-meteor-data';
+import { withTracker } from 'meteor/react-meteor-data';
 import ClosedCaptionsService from './service';
 import ClosedCaptions from './component';
 
 const ClosedCaptionsContainer = props => (
   <ClosedCaptions {...props} />
- );
+);
 
-export default createContainer(() => ClosedCaptionsService.getCCData(), ClosedCaptionsContainer);
+export default withTracker(() => ClosedCaptionsService.getCCData())(ClosedCaptionsContainer);
diff --git a/bigbluebutton-html5/imports/ui/components/dropdown/component.jsx b/bigbluebutton-html5/imports/ui/components/dropdown/component.jsx
index bee993194a6419b08ad1b1034266c502f4d66962..f21ca63601b2051a97727e09c519bac7b706854a 100644
--- a/bigbluebutton-html5/imports/ui/components/dropdown/component.jsx
+++ b/bigbluebutton-html5/imports/ui/components/dropdown/component.jsx
@@ -4,7 +4,7 @@ import { findDOMNode } from 'react-dom';
 import cx from 'classnames';
 import { defineMessages, injectIntl } from 'react-intl';
 import Button from '/imports/ui/components/button/component';
-import styles from './styles';
+import { styles } from './styles';
 import DropdownTrigger from './trigger/component';
 import DropdownContent from './content/component';
 
@@ -25,27 +25,21 @@ const propTypes = {
     const children = props[propName];
 
     if (!children || children.length < 2) {
-      return new Error(
-        `Invalid prop \`${propName}\` supplied to` +
-        ` \`${componentName}\`. Validation failed.`,
-      );
+      return new Error(`Invalid prop \`${propName}\` supplied to` +
+        ` \`${componentName}\`. Validation failed.`);
     }
 
     const trigger = children.find(x => x.type === DropdownTrigger);
     const content = children.find(x => x.type === DropdownContent);
 
     if (!trigger) {
-      return new Error(
-        `Invalid prop \`${propName}\` supplied to` +
-        ` \`${componentName}\`. Missing \`DropdownTrigger\`. Validation failed.`,
-      );
+      return new Error(`Invalid prop \`${propName}\` supplied to` +
+        ` \`${componentName}\`. Missing \`DropdownTrigger\`. Validation failed.`);
     }
 
     if (!content) {
-      return new Error(
-        `Invalid prop \`${propName}\` supplied to` +
-        ` \`${componentName}\`. Missing \`DropdownContent\`. Validation failed.`,
-      );
+      return new Error(`Invalid prop \`${propName}\` supplied to` +
+        ` \`${componentName}\`. Missing \`DropdownContent\`. Validation failed.`);
     }
 
     return null;
@@ -70,66 +64,48 @@ class Dropdown extends Component {
     this.state = { isOpen: false };
     this.handleShow = this.handleShow.bind(this);
     this.handleHide = this.handleHide.bind(this);
-    this.handleStateCallback = this.handleStateCallback.bind(this);
     this.handleToggle = this.handleToggle.bind(this);
     this.handleWindowClick = this.handleWindowClick.bind(this);
   }
 
   componentDidUpdate(prevProps, prevState) {
-    if (prevState.isOpen !== this.props.isOpen
-      && this.state.isOpen !== this.props.isOpen) {
-      this.setState({ isOpen: this.props.isOpen }, this.handleStateCallback);
+    if (this.state.isOpen && !prevState.isOpen) {
+      this.props.onShow();
     }
-  }
-
-  handleStateCallback() {
-    const { onShow, onHide } = this.props;
 
-    if (this.state.isOpen && onShow) {
-      onShow();
-    } else if (onHide) {
-      onHide();
+    if (!this.state.isOpen && prevState.isOpen) {
+      this.props.onHide();
     }
   }
 
   handleShow() {
-    const { addEventListener } = window;
-    addEventListener('click', this.handleWindowClick, false);
-
-    this.setState({ isOpen: true }, this.handleStateCallback);
+    this.setState({ isOpen: true }, () => {
+      const { addEventListener } = window;
+      addEventListener('click', this.handleWindowClick, true);
+    });
   }
 
   handleHide() {
-    const { removeEventListener } = window;
-    removeEventListener('click', this.handleWindowClick, false);
-
-    const { autoFocus } = this.props;
-
-    this.setState({ isOpen: false }, this.handleStateCallback);
-
-    if (autoFocus) {
-      const triggerElement = findDOMNode(this.trigger);
-      triggerElement.focus();
-    }
+    this.setState({ isOpen: false }, () => {
+      const { removeEventListener } = window;
+      removeEventListener('click', this.handleWindowClick, true);
+    });
   }
 
   handleWindowClick(event) {
-    if (this.state.isOpen) {
-      const dropdownElement = findDOMNode(this);
-      const shouldUpdateState = event.target !== dropdownElement &&
-        !dropdownElement.contains(event.target) &&
-        this.state.isOpen;
-
-      if (shouldUpdateState) {
-        this.handleHide();
-      }
+    const triggerElement = findDOMNode(this.trigger);
+
+    if (!this.state.isOpen
+      || triggerElement === event.target
+      || triggerElement.contains(event.target)) {
+      return;
     }
+
+    this.handleHide();
   }
 
   handleToggle() {
-    this.state.isOpen ?
-      this.handleHide() :
-      this.handleShow();
+    return this.state.isOpen ? this.handleHide() : this.handleShow();
   }
 
   render() {
@@ -175,8 +151,8 @@ class Dropdown extends Component {
           <Button
             className={styles.close}
             label={intl.formatMessage(intlMessages.close)}
-            size={'lg'}
-            color={'default'}
+            size="lg"
+            color="default"
             onClick={this.handleHide}
           /> : null}
       </div>
diff --git a/bigbluebutton-html5/imports/ui/components/dropdown/content/component.jsx b/bigbluebutton-html5/imports/ui/components/dropdown/content/component.jsx
index 70f2e4d6e8f8065384741047eb005599451ffb31..d86484429c4869cd000e80012b3c6753c02c22fc 100644
--- a/bigbluebutton-html5/imports/ui/components/dropdown/content/component.jsx
+++ b/bigbluebutton-html5/imports/ui/components/dropdown/content/component.jsx
@@ -1,7 +1,7 @@
 import React, { Component, Children, cloneElement } from 'react';
 import PropTypes from 'prop-types';
 import cx from 'classnames';
-import styles from '../styles';
+import { styles } from '../styles';
 
 const PLACEMENTS = [
   'top left', 'top', 'top right',
diff --git a/bigbluebutton-html5/imports/ui/components/dropdown/list/component.jsx b/bigbluebutton-html5/imports/ui/components/dropdown/list/component.jsx
index c630ce581258133921daa4ff243768389bf8a57f..6d824b289e6200047af5f5052f9b0606b8cf674b 100644
--- a/bigbluebutton-html5/imports/ui/components/dropdown/list/component.jsx
+++ b/bigbluebutton-html5/imports/ui/components/dropdown/list/component.jsx
@@ -2,10 +2,11 @@ import React, { Component, Children, cloneElement } from 'react';
 import PropTypes from 'prop-types';
 import cx from 'classnames';
 import KEY_CODES from '/imports/utils/keyCodes';
-import styles from './styles';
+import { styles } from './styles';
 import ListItem from './item/component';
 import ListSeparator from './separator/component';
 import ListTitle from './title/component';
+import UserActions from '../../user-list/user-list-content/user-participants/user-list-item/user-action/component';
 
 const propTypes = {
  /*  We should recheck this proptype, sometimes we need to create an container and send to dropdown,
@@ -14,11 +15,10 @@ const propTypes = {
   children: PropTypes.arrayOf((propValue, key, componentName, location, propFullName) => {
     if (propValue[key].type !== ListItem &&
       propValue[key].type !== ListSeparator &&
-      propValue[key].type !== ListTitle) {
-      return new Error(
-        `Invalid prop \`${propFullName}\` supplied to` +
-        ` \`${componentName}\`. Validation failed.`,
-      );
+      propValue[key].type !== ListTitle &&
+      propValue[key].type !== UserActions) {
+      return new Error(`Invalid prop \`${propFullName}\` supplied to` +
+        ` \`${componentName}\`. Validation failed.`);
     }
     return true;
   }).isRequired,
diff --git a/bigbluebutton-html5/imports/ui/components/dropdown/list/item/component.jsx b/bigbluebutton-html5/imports/ui/components/dropdown/list/item/component.jsx
index 901bebf5c610e285dfc12e7bcfd66f85ffd89e06..5b47b95110905fc470c0d193935396a2a9aa6ddb 100644
--- a/bigbluebutton-html5/imports/ui/components/dropdown/list/item/component.jsx
+++ b/bigbluebutton-html5/imports/ui/components/dropdown/list/item/component.jsx
@@ -3,7 +3,7 @@ import PropTypes from 'prop-types';
 import _ from 'lodash';
 import cx from 'classnames';
 import Icon from '/imports/ui/components/icon/component';
-import styles from '../styles';
+import { styles } from '../styles';
 
 const propTypes = {
   icon: PropTypes.string,
diff --git a/bigbluebutton-html5/imports/ui/components/dropdown/list/separator/component.jsx b/bigbluebutton-html5/imports/ui/components/dropdown/list/separator/component.jsx
index de3f7bdfdd186ad502486b6147dffdc09c580d2f..cf975378bc7d4d9fc385f8c99455eb5ddd51b9b5 100644
--- a/bigbluebutton-html5/imports/ui/components/dropdown/list/separator/component.jsx
+++ b/bigbluebutton-html5/imports/ui/components/dropdown/list/separator/component.jsx
@@ -1,7 +1,7 @@
 import React from 'react';
 import PropTypes from 'prop-types';
 import cx from 'classnames';
-import styles from '../styles';
+import { styles } from '../styles';
 
 const DropdownListSeparator = ({ style, className }) =>
   (
diff --git a/bigbluebutton-html5/imports/ui/components/dropdown/list/title/component.jsx b/bigbluebutton-html5/imports/ui/components/dropdown/list/title/component.jsx
index 69cadebbc58cdcf03c3fc4c6dfd877a5967a49fb..229d432691b5eefff5160b47e753ec8a995dac1e 100644
--- a/bigbluebutton-html5/imports/ui/components/dropdown/list/title/component.jsx
+++ b/bigbluebutton-html5/imports/ui/components/dropdown/list/title/component.jsx
@@ -1,6 +1,6 @@
 import React, { Component } from 'react';
 import PropTypes from 'prop-types';
-import styles from '../styles';
+import { styles } from '../styles';
 
 const propTypes = {
   description: PropTypes.string,
diff --git a/bigbluebutton-html5/imports/ui/components/error-screen/component.jsx b/bigbluebutton-html5/imports/ui/components/error-screen/component.jsx
index 7efe777b56346e1d02f3172b1e57b84771427398..45a4295ee073f14430f071d4ab2665334371f6a5 100644
--- a/bigbluebutton-html5/imports/ui/components/error-screen/component.jsx
+++ b/bigbluebutton-html5/imports/ui/components/error-screen/component.jsx
@@ -3,7 +3,7 @@ import PropTypes from 'prop-types';
 import { defineMessages, injectIntl } from 'react-intl';
 import Button from '/imports/ui/components/button/component';
 
-import styles from './styles.scss';
+import { styles } from './styles.scss';
 
 const intlMessages = defineMessages({
   500: {
diff --git a/bigbluebutton-html5/imports/ui/components/loading-screen/component.jsx b/bigbluebutton-html5/imports/ui/components/loading-screen/component.jsx
index 4bc756297bbf06b1acad04f491ae7780fa6ecab5..dcb56c35ccc03f59a35e9298dcc47d013d78a8f4 100644
--- a/bigbluebutton-html5/imports/ui/components/loading-screen/component.jsx
+++ b/bigbluebutton-html5/imports/ui/components/loading-screen/component.jsx
@@ -1,5 +1,5 @@
 import React from 'react';
-import styles from './styles.scss';
+import { styles } from './styles.scss';
 
 const LoadingScreen = ({ children }) => (
   <div className={styles.background}>
diff --git a/bigbluebutton-html5/imports/ui/components/logout-confirmation/component.jsx b/bigbluebutton-html5/imports/ui/components/logout-confirmation/component.jsx
index 2b214c2b658b7c804d646b3e0e47a6343cc4f00c..d1aa2092a4f88652d498ccf942fc6fb8a620b39e 100644
--- a/bigbluebutton-html5/imports/ui/components/logout-confirmation/component.jsx
+++ b/bigbluebutton-html5/imports/ui/components/logout-confirmation/component.jsx
@@ -1,14 +1,16 @@
 import React from 'react';
 import PropTypes from 'prop-types';
 import { withRouter } from 'react-router';
-import { defineMessages, injectIntl, intlShape } from 'react-intl';
+import { defineMessages, injectIntl } from 'react-intl';
 import Button from '/imports/ui/components/button/component';
 import Modal from '/imports/ui/components/modal/fullscreen/component';
-import styles from './styles';
+import { styles } from './styles';
 
 const propTypes = {
   handleEndMeeting: PropTypes.func.isRequired,
-  intl: PropTypes.shape(intlShape).isRequired,
+  intl: PropTypes.shape({
+    formatMessage: PropTypes.func.isRequired,
+  }).isRequired,
   router: PropTypes.object.isRequired,
   showEndMeeting: PropTypes.bool.isRequired,
 };
diff --git a/bigbluebutton-html5/imports/ui/components/logout-confirmation/container.jsx b/bigbluebutton-html5/imports/ui/components/logout-confirmation/container.jsx
index 3dbc153b43a07a10f315dc6b6570bbd1518b1bbf..f9faba90e49503a30c44c7641425193c8946e823 100644
--- a/bigbluebutton-html5/imports/ui/components/logout-confirmation/container.jsx
+++ b/bigbluebutton-html5/imports/ui/components/logout-confirmation/container.jsx
@@ -1,6 +1,6 @@
 import React from 'react';
 import { meetingIsBreakout } from '/imports/ui/components/app/service';
-import { createContainer } from 'meteor/react-meteor-data';
+import { withTracker } from 'meteor/react-meteor-data';
 import LogoutConfirmation from './component';
 import {
   isModerator,
@@ -11,10 +11,7 @@ const LogoutConfirmationContainer = props => (
   <LogoutConfirmation {...props} />
 );
 
-export default createContainer(() => {
-  return {
-    showEndMeeting: !meetingIsBreakout() &&
-                    isModerator(),
-    handleEndMeeting: endMeeting,
-  }
-}, LogoutConfirmationContainer);
+export default withTracker(() => ({
+  showEndMeeting: !meetingIsBreakout() && isModerator(),
+  handleEndMeeting: endMeeting,
+}))(LogoutConfirmationContainer);
diff --git a/bigbluebutton-html5/imports/ui/components/media/component.jsx b/bigbluebutton-html5/imports/ui/components/media/component.jsx
index 9ad9c6b79803a1120ae842fb2bddf00e5923f2cf..d1adb2553d9d169104fdd745476a8d91d48d1fe0 100644
--- a/bigbluebutton-html5/imports/ui/components/media/component.jsx
+++ b/bigbluebutton-html5/imports/ui/components/media/component.jsx
@@ -1,6 +1,6 @@
 import React, { Component } from 'react';
 import PropTypes from 'prop-types';
-import styles from './styles';
+import { styles } from './styles';
 
 const propTypes = {
   content: PropTypes.element.isRequired,
diff --git a/bigbluebutton-html5/imports/ui/components/media/container.jsx b/bigbluebutton-html5/imports/ui/components/media/container.jsx
index de9221e18afa228ef46256da32d02c13a5e6e2f3..d56e6b0ec7a7252a81d851ff1a816f9a05ae87e5 100644
--- a/bigbluebutton-html5/imports/ui/components/media/container.jsx
+++ b/bigbluebutton-html5/imports/ui/components/media/container.jsx
@@ -1,5 +1,5 @@
 import React, { Component } from 'react';
-import { createContainer } from 'meteor/react-meteor-data';
+import { withTracker } from 'meteor/react-meteor-data';
 import Media from './component';
 import MediaService from './service';
 import PresentationAreaContainer from '../presentation/container';
@@ -8,7 +8,7 @@ import ScreenshareContainer from '../screenshare/container';
 import DefaultContent from '../presentation/default-content/component';
 
 const defaultProps = {
-  overlay: null, // <VideoDockContainer/>,
+  overlay: <VideoDockContainer />,
   content: <PresentationAreaContainer />,
   defaultContent: <DefaultContent />,
 };
@@ -52,7 +52,7 @@ class MediaContainer extends Component {
 
 MediaContainer.defaultProps = defaultProps;
 
-export default createContainer(() => {
+export default withTracker(() => {
   const data = {};
   data.currentPresentation = MediaService.getPresentationInfo();
 
@@ -71,4 +71,4 @@ export default createContainer(() => {
   }
 
   return data;
-}, MediaContainer);
+})(MediaContainer);
diff --git a/bigbluebutton-html5/imports/ui/components/media/service.js b/bigbluebutton-html5/imports/ui/components/media/service.js
index d583c089f19be3a4c79ea3e9d21b9f3f1164ad9b..9523f64779f3dc4eee14ac7f0f138856d0384a1d 100644
--- a/bigbluebutton-html5/imports/ui/components/media/service.js
+++ b/bigbluebutton-html5/imports/ui/components/media/service.js
@@ -17,11 +17,11 @@ function shouldShowWhiteboard() {
 }
 
 function shouldShowScreenshare() {
-  return isVideoBroadcasting();
+  return isVideoBroadcasting() && Meteor.settings.public.kurento.enableScreensharing;
 }
 
 function shouldShowOverlay() {
-  return false;
+  return Meteor.settings.public.kurento.enableVideo;
 }
 
 export default {
diff --git a/bigbluebutton-html5/imports/ui/components/modal/base/component.jsx b/bigbluebutton-html5/imports/ui/components/modal/base/component.jsx
index ac205b922da2fd9c1b8d5b79093e6bb6d033179f..ba3170e06f08509762eb71ea3724635f6a8c8ddd 100644
--- a/bigbluebutton-html5/imports/ui/components/modal/base/component.jsx
+++ b/bigbluebutton-html5/imports/ui/components/modal/base/component.jsx
@@ -1,7 +1,7 @@
 import React, { Component } from 'react';
 import PropTypes from 'prop-types';
 import ReactModal from 'react-modal';
-import styles from './styles.scss';
+import { styles } from './styles.scss';
 
 const propTypes = {
   overlayClassName: PropTypes.string.isRequired,
diff --git a/bigbluebutton-html5/imports/ui/components/modal/container.jsx b/bigbluebutton-html5/imports/ui/components/modal/container.jsx
index beeed571db1a054b88d974f0325551824e57273b..a1bbd0ed6684475c3858b59ebe8d73f30f50921e 100644
--- a/bigbluebutton-html5/imports/ui/components/modal/container.jsx
+++ b/bigbluebutton-html5/imports/ui/components/modal/container.jsx
@@ -1,8 +1,5 @@
 import React from 'react';
-import { createContainer } from 'meteor/react-meteor-data';
+import { withTracker } from 'meteor/react-meteor-data';
 import { getModal } from './service';
 
-export default createContainer(
-  () => ({ modalComponent: getModal() }),
-  ({ modalComponent }) => modalComponent,
-);
+export default withTracker(() => ({ modalComponent: getModal() }))(({ modalComponent }) => modalComponent);
diff --git a/bigbluebutton-html5/imports/ui/components/modal/fullscreen/component.jsx b/bigbluebutton-html5/imports/ui/components/modal/fullscreen/component.jsx
index b2651e3fd004141bd2cdec179bda764edcb688ff..da8452292959d9e3431ed2d3dae58e5182f76374 100644
--- a/bigbluebutton-html5/imports/ui/components/modal/fullscreen/component.jsx
+++ b/bigbluebutton-html5/imports/ui/components/modal/fullscreen/component.jsx
@@ -3,7 +3,7 @@ import PropTypes from 'prop-types';
 import Button from '/imports/ui/components/button/component';
 import cx from 'classnames';
 import ModalBase, { withModalState } from '../base/component';
-import styles from './styles.scss';
+import { styles } from './styles.scss';
 
 const propTypes = {
   title: PropTypes.string.isRequired,
diff --git a/bigbluebutton-html5/imports/ui/components/modal/simple/component.jsx b/bigbluebutton-html5/imports/ui/components/modal/simple/component.jsx
index 6791ecacfadb9950996b2f5253270ded465707bc..8c1ff95ce4ebf987cc2a0ef756add92400247c26 100644
--- a/bigbluebutton-html5/imports/ui/components/modal/simple/component.jsx
+++ b/bigbluebutton-html5/imports/ui/components/modal/simple/component.jsx
@@ -3,7 +3,7 @@ import PropTypes from 'prop-types';
 import cx from 'classnames';
 import Button from '/imports/ui/components/button/component';
 import ModalBase, { withModalState } from '../base/component';
-import styles from './styles';
+import { styles } from './styles';
 
 const propTypes = {
   title: PropTypes.string.isRequired,
diff --git a/bigbluebutton-html5/imports/ui/components/nav-bar/component.jsx b/bigbluebutton-html5/imports/ui/components/nav-bar/component.jsx
index e03f27f0d4b56e1100c3f2e0ff430ffd4541f1f6..acb206cb91a8e52f4d007463c0400afec5d90ed8 100644
--- a/bigbluebutton-html5/imports/ui/components/nav-bar/component.jsx
+++ b/bigbluebutton-html5/imports/ui/components/nav-bar/component.jsx
@@ -2,7 +2,7 @@ import React, { Component } from 'react';
 import PropTypes from 'prop-types';
 import _ from 'lodash';
 import cx from 'classnames';
-import styles from './styles.scss';
+import { styles } from './styles.scss';
 import Button from '../button/component';
 import RecordingIndicator from './recording-indicator/component';
 import SettingsDropdownContainer from './settings-dropdown/container';
diff --git a/bigbluebutton-html5/imports/ui/components/nav-bar/container.jsx b/bigbluebutton-html5/imports/ui/components/nav-bar/container.jsx
index 47ba92261c9dece2ca4ed9c684a9158e3a5c71c8..13e0a1cbd519d2068b4b060be75c9936bdc1727f 100644
--- a/bigbluebutton-html5/imports/ui/components/nav-bar/container.jsx
+++ b/bigbluebutton-html5/imports/ui/components/nav-bar/container.jsx
@@ -1,5 +1,5 @@
-import React, { Component } from 'react';
-import { createContainer } from 'meteor/react-meteor-data';
+import React from 'react';
+import { withTracker } from 'meteor/react-meteor-data';
 import { withRouter } from 'react-router';
 import Meetings from '/imports/api/meetings';
 import Auth from '/imports/ui/services/auth';
@@ -18,7 +18,7 @@ const NavBarContainer = ({ children, ...props }) => (
   </NavBar>
 );
 
-export default withRouter(createContainer(({ location, router }) => {
+export default withRouter(withTracker(({ location, router }) => {
   let meetingTitle;
   let meetingRecorded;
 
@@ -69,4 +69,4 @@ export default withRouter(createContainer(({ location, router }) => {
       }
     },
   };
-}, NavBarContainer));
+})(NavBarContainer));
diff --git a/bigbluebutton-html5/imports/ui/components/nav-bar/recording-indicator/component.jsx b/bigbluebutton-html5/imports/ui/components/nav-bar/recording-indicator/component.jsx
index bb249f902a9274a1ce67bfc7bcdb7e38cf9dd57e..60c0cf7cda7b1c6c064067c1b77a6eec44062ce7 100644
--- a/bigbluebutton-html5/imports/ui/components/nav-bar/recording-indicator/component.jsx
+++ b/bigbluebutton-html5/imports/ui/components/nav-bar/recording-indicator/component.jsx
@@ -1,5 +1,5 @@
 import React from 'react';
-import styles from './styles';
+import { styles } from './styles';
 
 const RecordingIndicator = ({ beingRecorded }) => {
   if (!beingRecorded) return null;
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 7454a183b9c1cd1c6381b5242f3841a566753122..69a3b3096c4793f0ec6ad30ed8cdc92d2c2dea07 100644
--- 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,7 +1,7 @@
 import React, { Component } 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';
@@ -16,7 +16,7 @@ import DropdownList from '/imports/ui/components/dropdown/list/component';
 import DropdownListItem from '/imports/ui/components/dropdown/list/item/component';
 import DropdownListSeparator from '/imports/ui/components/dropdown/list/separator/component';
 
-import styles from '../styles';
+import { styles } from '../styles';
 
 const intlMessages = defineMessages({
   optionsLabel: {
@@ -75,6 +75,53 @@ class SettingsDropdown extends Component {
 
     this.onActionsShow = this.onActionsShow.bind(this);
     this.onActionsHide = this.onActionsHide.bind(this);
+    this.getListItems = this.getListItems.bind(this);
+  }
+
+  componentWillMount() {
+    const { intl, isFullScreen, mountModal } = this.props;
+
+    let fullscreenLabel = intl.formatMessage(intlMessages.fullscreenLabel);
+    let fullscreenDesc = intl.formatMessage(intlMessages.fullscreenDesc);
+    let fullscreenIcon = 'fullscreen';
+
+    if (isFullScreen) {
+      fullscreenLabel = intl.formatMessage(intlMessages.exitFullscreenLabel);
+      fullscreenDesc = intl.formatMessage(intlMessages.exitFullscreenDesc);
+      fullscreenIcon = 'exit_fullscreen';
+    }
+
+    this.menuItems = [
+      (<DropdownListItem
+        key={_.uniqueId('list-item-')}
+        icon={fullscreenIcon}
+        label={fullscreenLabel}
+        description={fullscreenDesc}
+        onClick={this.props.handleToggleFullscreen}
+      />),
+      (<DropdownListItem
+        key={_.uniqueId('list-item-')}
+        icon="settings"
+        label={intl.formatMessage(intlMessages.settingsLabel)}
+        description={intl.formatMessage(intlMessages.settingsDesc)}
+        onClick={() => mountModal(<SettingsMenuContainer />)}
+      />),
+      (<DropdownListItem
+        key={_.uniqueId('list-item-')}
+        icon="about"
+        label={intl.formatMessage(intlMessages.aboutLabel)}
+        description={intl.formatMessage(intlMessages.aboutDesc)}
+        onClick={() => mountModal(<AboutContainer />)}
+      />),
+      (<DropdownListSeparator key={_.uniqueId('list-separator-')} />),
+      (<DropdownListItem
+        key={_.uniqueId('list-item-')}
+        icon="logout"
+        label={intl.formatMessage(intlMessages.leaveSessionLabel)}
+        description={intl.formatMessage(intlMessages.leaveSessionDesc)}
+        onClick={() => mountModal(<LogoutConfirmationContainer />)}
+      />),
+    ];
   }
 
   onActionsShow() {
@@ -89,18 +136,16 @@ class SettingsDropdown extends Component {
     });
   }
 
-  render() {
-    const { intl, mountModal, isFullScreen } = this.props;
+  getListItems() {
+    const iOS = /iPad|iPhone|iPod/.test(navigator.userAgent);
 
-    let fullscreenLabel = intl.formatMessage(intlMessages.fullscreenLabel);
-    let fullscreenDesc = intl.formatMessage(intlMessages.fullscreenDesc);
-    let fullscreenIcon = 'fullscreen';
+    // we slice the list item to be hidden, for iOS devices, in order to avoid the error
+    // thrown if the DropdownList receives a null value.
+    return (iOS) ? this.menuItems.slice(1) : this.menuItems;
+  }
 
-    if (isFullScreen) {
-      fullscreenLabel = intl.formatMessage(intlMessages.exitFullscreenLabel);
-      fullscreenDesc = intl.formatMessage(intlMessages.exitFullscreenDesc);
-      fullscreenIcon = 'exit_fullscreen';
-    }
+  render() {
+    const { intl } = this.props;
 
     return (
       <Dropdown
@@ -125,31 +170,9 @@ class SettingsDropdown extends Component {
         </DropdownTrigger>
         <DropdownContent placement="bottom right">
           <DropdownList>
-            <DropdownListItem
-              icon={fullscreenIcon}
-              label={fullscreenLabel}
-              description={fullscreenDesc}
-              onClick={this.props.handleToggleFullscreen}
-            />
-            <DropdownListItem
-              icon="settings"
-              label={intl.formatMessage(intlMessages.settingsLabel)}
-              description={intl.formatMessage(intlMessages.settingsDesc)}
-              onClick={() => mountModal(<SettingsMenuContainer />)}
-            />
-            <DropdownListItem
-              icon="about"
-              label={intl.formatMessage(intlMessages.aboutLabel)}
-              description={intl.formatMessage(intlMessages.aboutDesc)}
-              onClick={() => mountModal(<AboutContainer />)}
-            />
-            <DropdownListSeparator />
-            <DropdownListItem
-              icon="logout"
-              label={intl.formatMessage(intlMessages.leaveSessionLabel)}
-              description={intl.formatMessage(intlMessages.leaveSessionDesc)}
-              onClick={() => mountModal(<LogoutConfirmationContainer />)}
-            />
+            {
+              this.getListItems()
+            }
           </DropdownList>
         </DropdownContent>
       </Dropdown>
diff --git a/bigbluebutton-html5/imports/ui/components/notifications-bar/component.jsx b/bigbluebutton-html5/imports/ui/components/notifications-bar/component.jsx
index 378d27ff5d6c29ed3b669376cb597728e617011c..3ed8f4afef2ecc69440aea6a2234fe8506d739a1 100644
--- a/bigbluebutton-html5/imports/ui/components/notifications-bar/component.jsx
+++ b/bigbluebutton-html5/imports/ui/components/notifications-bar/component.jsx
@@ -2,7 +2,7 @@ import React from 'react';
 import PropTypes from 'prop-types';
 import cx from 'classnames';
 import injectWbResizeEvent from '/imports/ui/components/presentation/resize-wrapper/component';
-import styles from './styles.scss';
+import { styles } from './styles.scss';
 
 const COLORS = [
   'default', 'primary', 'danger', 'success',
diff --git a/bigbluebutton-html5/imports/ui/components/notifications-bar/container.jsx b/bigbluebutton-html5/imports/ui/components/notifications-bar/container.jsx
index 8383ca6749af08445a249177f769e549108adf0c..4c03cf309be3c40c18476d0df776eb63e71f5fc1 100644
--- a/bigbluebutton-html5/imports/ui/components/notifications-bar/container.jsx
+++ b/bigbluebutton-html5/imports/ui/components/notifications-bar/container.jsx
@@ -1,5 +1,5 @@
 import { Meteor } from 'meteor/meteor';
-import { createContainer } from 'meteor/react-meteor-data';
+import { withTracker } from 'meteor/react-meteor-data';
 import React from 'react';
 import { defineMessages, injectIntl } from 'react-intl';
 import _ from 'lodash';
@@ -108,7 +108,7 @@ const startCounter = (sec, set, get, interval) => {
   }, 1000);
 };
 
-export default injectIntl(createContainer(({ intl }) => {
+export default injectIntl(withTracker(({ intl }) => {
   const { status, connected, retryTime } = Meteor.status();
   const data = {};
 
@@ -150,10 +150,12 @@ export default injectIntl(createContainer(({ intl }) => {
       const roomRemainingTime = currentBreakout.timeRemaining;
 
       if (!timeRemainingInterval && roomRemainingTime) {
-        timeRemainingInterval = startCounter(roomRemainingTime,
-                                             setTimeRemaining,
-                                             getTimeRemaining,
-                                             timeRemainingInterval);
+        timeRemainingInterval = startCounter(
+          roomRemainingTime,
+          setTimeRemaining,
+          getTimeRemaining,
+          timeRemainingInterval,
+        );
       }
     } else if (timeRemainingInterval) {
       clearInterval(timeRemainingInterval);
@@ -178,4 +180,4 @@ export default injectIntl(createContainer(({ intl }) => {
 
   data.color = 'primary';
   return data;
-}, NotificationsBarContainer));
+})(NotificationsBarContainer));
diff --git a/bigbluebutton-html5/imports/ui/components/polling/component.jsx b/bigbluebutton-html5/imports/ui/components/polling/component.jsx
index 97fa2bf55013d83aa87e723f71f1859f79f9f346..1ec189e4302a38f3b43c911fdade35a3d3ad15d6 100644
--- a/bigbluebutton-html5/imports/ui/components/polling/component.jsx
+++ b/bigbluebutton-html5/imports/ui/components/polling/component.jsx
@@ -3,7 +3,7 @@ import PropTypes from 'prop-types';
 import Button from '/imports/ui/components/button/component';
 import { defineMessages, injectIntl } from 'react-intl';
 import injectWbResizeEvent from '/imports/ui/components/presentation/resize-wrapper/component';
-import styles from './styles.scss';
+import { styles } from './styles.scss';
 
 const intlMessages = defineMessages({
   pollingTitleLabel: {
diff --git a/bigbluebutton-html5/imports/ui/components/polling/container.jsx b/bigbluebutton-html5/imports/ui/components/polling/container.jsx
index 02541c21d641c10381a9ed6dbd64b496d469fa44..f3ea1cba73d22e5a5de7bd51156d8ffcec954ef8 100644
--- a/bigbluebutton-html5/imports/ui/components/polling/container.jsx
+++ b/bigbluebutton-html5/imports/ui/components/polling/container.jsx
@@ -1,15 +1,25 @@
 import React from 'react';
-import { createContainer } from 'meteor/react-meteor-data';
+import PropTypes from 'prop-types';
+import { withTracker } from 'meteor/react-meteor-data';
 import PollingService from './service';
 import PollingComponent from './component';
 
-const PollingContainer = ({
-  pollExists, poll, handleVote, ...props
-}) => {
-  if (!pollExists) return null;
-  return <PollingComponent poll={poll} handleVote={handleVote} {...props} />;
+const propTypes = {
+  pollExists: PropTypes.bool.isRequired,
 };
-export default createContainer(() => {
+
+const PollingContainer = ({ pollExists, ...props }) => {
+  if (pollExists) {
+    return (
+      <PollingComponent {...props} />
+    );
+  }
+  return null;
+};
+
+PollingContainer.propTypes = propTypes;
+
+export default withTracker(() => {
   const data = PollingService.mapPolls();
   return data;
-}, PollingContainer);
+})(PollingContainer);
diff --git a/bigbluebutton-html5/imports/ui/components/presentation/component.jsx b/bigbluebutton-html5/imports/ui/components/presentation/component.jsx
index 087d4d8481b24fe5952c28ab529b867295408739..f4a4869d7cb7213432db9d3638b74c2375b09b4d 100644
--- a/bigbluebutton-html5/imports/ui/components/presentation/component.jsx
+++ b/bigbluebutton-html5/imports/ui/components/presentation/component.jsx
@@ -9,7 +9,7 @@ import AnnotationGroupContainer from '../whiteboard/annotation-group/container';
 import PresentationToolbarContainer from './presentation-toolbar/container';
 import PresentationOverlayContainer from './presentation-overlay/container';
 import Slide from './slide/component';
-import styles from './styles.scss';
+import { styles } from './styles.scss';
 
 
 export default class PresentationArea extends Component {
diff --git a/bigbluebutton-html5/imports/ui/components/presentation/container.jsx b/bigbluebutton-html5/imports/ui/components/presentation/container.jsx
index a45c819198873819ff534ca223e081c503bd7eac..0c66ef27d84c9d5ac51086de0f3bd6322150c338 100644
--- a/bigbluebutton-html5/imports/ui/components/presentation/container.jsx
+++ b/bigbluebutton-html5/imports/ui/components/presentation/container.jsx
@@ -1,5 +1,5 @@
 import React from 'react';
-import { createContainer } from 'meteor/react-meteor-data';
+import { withTracker } from 'meteor/react-meteor-data';
 import PresentationAreaService from './service';
 import PresentationArea from './component';
 
@@ -7,8 +7,8 @@ const PresentationAreaContainer = props => (
   <PresentationArea {...props} />
 );
 
-export default createContainer(() => ({
+export default withTracker(() => ({
   currentSlide: PresentationAreaService.getCurrentSlide(),
   userIsPresenter: PresentationAreaService.isPresenter(),
   multiUser: PresentationAreaService.getMultiUserStatus(),
-}), PresentationAreaContainer);
+}))(PresentationAreaContainer);
diff --git a/bigbluebutton-html5/imports/ui/components/presentation/cursor/container.jsx b/bigbluebutton-html5/imports/ui/components/presentation/cursor/container.jsx
index 00598801636fefc7a58f52c3f637f23970f02eea..0aeff9aed4b0588fbcf9cf759df9103d28b546ec 100644
--- a/bigbluebutton-html5/imports/ui/components/presentation/cursor/container.jsx
+++ b/bigbluebutton-html5/imports/ui/components/presentation/cursor/container.jsx
@@ -1,6 +1,6 @@
 import React, { Component } from 'react';
 import PropTypes from 'prop-types';
-import { createContainer } from 'meteor/react-meteor-data';
+import { withTracker } from 'meteor/react-meteor-data';
 import CursorService from './service';
 import Cursor from './component';
 
@@ -40,30 +40,29 @@ class CursorContainer extends Component {
     }
     return null;
   }
-
 }
 
 
-export default createContainer((params) => {
+export default withTracker((params) => {
   const { cursorId } = params;
 
   const cursor = CursorService.getCurrentCursor(cursorId);
-  let cursorX = -1;
-  let cursorY = -1;
-  let userName = '';
 
   if (cursor) {
-    cursorX = cursor.x;
-    cursorY = cursor.y;
-    userName = cursor.userName;
+    const { x: cursorX, y: cursorY, userName } = cursor;
+    return {
+      cursorX,
+      cursorY,
+      userName,
+    };
   }
 
   return {
-    cursorX,
-    cursorY,
-    userName,
+    cursorX: -1,
+    cursorY: -1,
+    userName: '',
   };
-}, CursorContainer);
+})(CursorContainer);
 
 
 CursorContainer.propTypes = {
diff --git a/bigbluebutton-html5/imports/ui/components/presentation/cursor/cursor-wrapper-container/container.jsx b/bigbluebutton-html5/imports/ui/components/presentation/cursor/cursor-wrapper-container/container.jsx
index 96c2d724d8f5855605e543878c654de9235881b3..9f4da4e8d079f556199310b7353a127ec907260f 100644
--- a/bigbluebutton-html5/imports/ui/components/presentation/cursor/cursor-wrapper-container/container.jsx
+++ b/bigbluebutton-html5/imports/ui/components/presentation/cursor/cursor-wrapper-container/container.jsx
@@ -1,7 +1,7 @@
 /*
   The purpose of this wrapper is to fetch an array of active cursor iDs only
   and map them to the CursorContainer.
-  The reason for this is that Meteor tracks only the properties defined inside createContainer
+  The reason for this is that Meteor tracks only the properties defined inside withTracker
   and if we fetch the whole array of Cursors right away (let's say in the multi-user mode),
   then the whole array would be re-rendered every time one of the Cursors is changed.
 
@@ -13,7 +13,7 @@
 
 import React from 'react';
 import PropTypes from 'prop-types';
-import { createContainer } from 'meteor/react-meteor-data';
+import { withTracker } from 'meteor/react-meteor-data';
 import CursorWrapperService from './service';
 import CursorContainer from '../container';
 
@@ -35,12 +35,11 @@ const CursorWrapperContainer = ({ presenterCursorId, multiUserCursorIds, ...rest
         cursorId={cursorId._id}
         presenter={false}
         {...rest}
-      />),
-    )}
+      />))}
   </g>
 );
 
-export default createContainer(() => {
+export default withTracker(() => {
   const { presenterCursorId, multiUserCursorIds } = CursorWrapperService.getCurrentCursorIds();
   const isMultiUser = CursorWrapperService.getMultiUserStatus();
 
@@ -49,7 +48,7 @@ export default createContainer(() => {
     multiUserCursorIds,
     isMultiUser,
   };
-}, CursorWrapperContainer);
+})(CursorWrapperContainer);
 
 
 CursorWrapperContainer.propTypes = {
diff --git a/bigbluebutton-html5/imports/ui/components/presentation/default-content/component.jsx b/bigbluebutton-html5/imports/ui/components/presentation/default-content/component.jsx
index eb3e917766fc9a7d017ac26d43fc5ed324b624d4..fa57cc2e724b334966a9aef70e17c24482f46af2 100644
--- a/bigbluebutton-html5/imports/ui/components/presentation/default-content/component.jsx
+++ b/bigbluebutton-html5/imports/ui/components/presentation/default-content/component.jsx
@@ -1,7 +1,7 @@
 import React, { Component } from 'react';
 import { FormattedMessage, FormattedDate } from 'react-intl';
 import { TransitionGroup, CSSTransition } from 'react-transition-group';
-import styles from './styles.scss';
+import { styles } from './styles.scss';
 import Button from '../../button/component';
 
 export default class DefaultContent extends Component {
diff --git a/bigbluebutton-html5/imports/ui/components/presentation/presentation-overlay/container.jsx b/bigbluebutton-html5/imports/ui/components/presentation/presentation-overlay/container.jsx
index 6600b4ab97409efc09c7804846911cb90203adc1..89f5e39c6f79db9ed7cca302a0edb8b629de28ac 100644
--- a/bigbluebutton-html5/imports/ui/components/presentation/presentation-overlay/container.jsx
+++ b/bigbluebutton-html5/imports/ui/components/presentation/presentation-overlay/container.jsx
@@ -1,6 +1,6 @@
 import React from 'react';
 import PropTypes from 'prop-types';
-import { createContainer } from 'meteor/react-meteor-data';
+import { withTracker } from 'meteor/react-meteor-data';
 import PresentationOverlayService from './service';
 import PresentationOverlay from './component';
 
@@ -10,9 +10,9 @@ const PresentationOverlayContainer = ({ children, ...rest }) => (
   </PresentationOverlay>
 );
 
-export default createContainer(() => ({
+export default withTracker(() => ({
   updateCursor: PresentationOverlayService.updateCursor,
-}), PresentationOverlayContainer);
+}))(PresentationOverlayContainer);
 
 PresentationOverlayContainer.propTypes = {
   children: PropTypes.node,
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 95a5321fef63dd41052b1e0e6434848c2f920e52..951246efc2e0bfbeb0e687bc133a738ad40567e0 100644
--- a/bigbluebutton-html5/imports/ui/components/presentation/presentation-toolbar/component.jsx
+++ b/bigbluebutton-html5/imports/ui/components/presentation/presentation-toolbar/component.jsx
@@ -3,7 +3,7 @@ import PropTypes from 'prop-types';
 import { defineMessages, injectIntl, FormattedMessage } from 'react-intl';
 import injectWbResizeEvent from '/imports/ui/components/presentation/resize-wrapper/component';
 import Button from '/imports/ui/components/button/component';
-import styles from './styles.scss';
+import { styles } from './styles.scss';
 
 const intlMessages = defineMessages({
   previousSlideLabel: {
diff --git a/bigbluebutton-html5/imports/ui/components/presentation/presentation-toolbar/container.jsx b/bigbluebutton-html5/imports/ui/components/presentation/presentation-toolbar/container.jsx
index e414e27505f36303a80b5aaa1ccbd20d144a81ef..6de339c0759f0a2bd04dd54bc60c55a65245a540 100644
--- a/bigbluebutton-html5/imports/ui/components/presentation/presentation-toolbar/container.jsx
+++ b/bigbluebutton-html5/imports/ui/components/presentation/presentation-toolbar/container.jsx
@@ -1,6 +1,6 @@
 import React from 'react';
 import PropTypes from 'prop-types';
-import { createContainer } from 'meteor/react-meteor-data';
+import { withTracker } from 'meteor/react-meteor-data';
 import PresentationToolbarService from './service';
 import PresentationToolbar from './component';
 
@@ -25,7 +25,7 @@ const PresentationToolbarContainer = (props) => {
   return null;
 };
 
-export default createContainer((params) => {
+export default withTracker((params) => {
   const data = PresentationToolbarService.getSlideData(params);
 
   const {
@@ -45,15 +45,12 @@ export default createContainer((params) => {
         PresentationToolbarService.skipToSlide(event),
     },
   };
-}, PresentationToolbarContainer);
+})(PresentationToolbarContainer);
 
 PresentationToolbarContainer.propTypes = {
   // Number of current slide being displayed
   currentSlideNum: PropTypes.number.isRequired,
 
-  // PresentationId of the current presentation
-  presentationId: PropTypes.string.isRequired,
-
   // Is the user a presenter
   userIsPresenter: PropTypes.bool.isRequired,
 
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 1fa248dc5ae578906d578a24f4a09cf72c6c683c..dcb546d924925289750f8377e52c1bc3a1c089cf 100644
--- a/bigbluebutton-html5/imports/ui/components/presentation/presentation-uploader/component.jsx
+++ b/bigbluebutton-html5/imports/ui/components/presentation/presentation-uploader/component.jsx
@@ -4,12 +4,14 @@ import { defineMessages, injectIntl, intlShape } from 'react-intl';
 import Dropzone from 'react-dropzone';
 import update from 'immutability-helper';
 import cx from 'classnames';
+import _ from 'lodash';
 
+import { notify } from '/imports/ui/services/notification';
 import ModalFullscreen from '/imports/ui/components/modal/fullscreen/component';
 import Icon from '/imports/ui/components/icon/component';
 import ButtonBase from '/imports/ui/components/button/base/component';
 import Checkbox from '/imports/ui/components/checkbox/component';
-import styles from './styles.scss';
+import { styles } from './styles.scss';
 
 const propTypes = {
   intl: intlShape.isRequired,
@@ -28,10 +30,12 @@ const propTypes = {
 };
 
 const defaultProps = {
-  defaultFileName: 'default.pdf',
 };
 
 const intlMessages = defineMessages({
+  current: {
+    id: 'app.presentationUploder.currentBadge',
+  },
   title: {
     id: 'app.presentationUploder.title',
     description: 'title of the modal',
@@ -68,6 +72,10 @@ const intlMessages = defineMessages({
     id: 'app.presentationUploder.fileToUpload',
     description: 'message used in the file selected for upload',
   },
+  genericError: {
+    id: 'app.presentationUploder.genericError',
+    description: 'generic error while uploading/converting',
+  },
   uploadProcess: {
     id: 'app.presentationUploder.upload.progress',
     description: 'message that indicates the percentage of the upload',
@@ -84,9 +92,12 @@ const intlMessages = defineMessages({
     id: 'app.presentationUploder.conversion.genericConversionStatus',
     description: 'indicates that file is being converted',
   },
+  TIMEOUT: {
+    id: 'app.presentationUploder.conversion.timeout',
+  },
   GENERATING_THUMBNAIL: {
     id: 'app.presentationUploder.conversion.generatingThumbnail',
-    description: 's that it is generating thumbnails',
+    description: 'indicatess that it is generating thumbnails',
   },
   GENERATING_SVGIMAGES: {
     id: 'app.presentationUploder.conversion.generatingSvg',
@@ -96,14 +107,21 @@ const intlMessages = defineMessages({
     id: 'app.presentationUploder.conversion.generatedSlides',
     description: 'warns that were slides generated',
   },
+  PAGE_COUNT_EXCEEDED: {
+    id: 'app.presentationUploder.conversion.pageCountExceeded',
+    description: 'warns the user that the conversion failed because of the page count',
+  },
 });
 
 class PresentationUploader extends Component {
   constructor(props) {
     super(props);
 
+    const currentPres = props.presentations.find(p => p.isCurrent);
+
     this.state = {
       presentations: props.presentations,
+      oldCurrentId: currentPres ? currentPres.id : -1,
       preventClosing: false,
       disableActions: false,
     };
@@ -118,27 +136,19 @@ class PresentationUploader extends Component {
     this.deepMergeUpdateFileKey = this.deepMergeUpdateFileKey.bind(this);
   }
 
-  componentWillReceiveProps(nextProps) {
-    const nextPresentations = nextProps.presentations;
-
-    // Update only the conversion state when receiving new props
-    nextPresentations.forEach((file) => {
-      this.updateFileKey(file.filename, 'id', file.id);
-      this.deepMergeUpdateFileKey(file.id, 'conversion', file.conversion);
-    });
-  }
-
   updateFileKey(id, key, value, operation = '$set') {
     this.setState(({ presentations }) => {
-      // Compare id and filename since non-uploaded files dont have a real id
-      const fileIndex = presentations.findIndex(f => f.id === id || f.filename === id);
+      const fileIndex = presentations.findIndex(f => f.id === id);
 
       return fileIndex === -1 ? false : {
         presentations: update(presentations, {
-          [fileIndex]: { $apply: file =>
-            update(file, { [key]: {
-              [operation]: value,
-            } }),
+          [fileIndex]: {
+            $apply: file =>
+              update(file, {
+                [key]: {
+                  [operation]: value,
+                },
+              }),
           },
         }),
       };
@@ -150,26 +160,54 @@ class PresentationUploader extends Component {
     this.updateFileKey(id, key, applyValue, '$apply');
   }
 
+  isDefault(presentation) {
+    const { defaultFileName } = this.props;
+    return presentation.filename === defaultFileName
+      && !presentation.id.includes(defaultFileName);
+  }
+
   handleConfirm() {
-    const { presentations } = this.state;
+    const presentationsToSave = this.state.presentations
+      .filter(p => !p.upload.error && !p.conversion.error);
 
     this.setState({
       disableActions: true,
       preventClosing: true,
+      presentations: presentationsToSave,
     });
 
-    return this.props.handleSave(presentations)
+    return this.props.handleSave(presentationsToSave)
       .then(() => {
+        const hasError = this.state.presentations.some(p => p.upload.error || p.conversion.error);
+        if (!hasError) {
+          this.setState({
+            disableActions: false,
+            preventClosing: false,
+          });
+
+          return;
+        }
+
+        // if theres error we dont want to close the modal
         this.setState({
           disableActions: false,
-          preventClosing: false,
+          preventClosing: true,
+        }, () => {
+          // if the selected current has error we revert back to the old one
+          const newCurrent = this.state.presentations.find(p => p.isCurrent);
+          if (newCurrent.upload.error || newCurrent.conversion.error) {
+            this.handleCurrentChange(this.state.oldCurrentId);
+          }
         });
       })
       .catch((error) => {
+        notify(this.props.intl.formatMessage(intlMessages.genericError), 'error');
+
+        console.error(error);
+
         this.setState({
           disableActions: false,
           preventClosing: true,
-          error,
         });
       });
   }
@@ -184,52 +222,62 @@ class PresentationUploader extends Component {
   }
 
   handleFiledrop(files) {
-    const presentationsToUpload = files.map(file => ({
-      file,
-      id: file.name,
-      filename: file.name,
-      isCurrent: false,
-      conversion: { done: false, error: false },
-      upload: { done: false, error: false, progress: 0 },
-      onProgress: (event) => {
-        if (!event.lengthComputable) {
-          this.deepMergeUpdateFileKey(file.name, 'upload', {
-            progress: 100,
-            done: true,
+    const presentationsToUpload = files.map((file) => {
+      const id = _.uniqueId(file.name);
+
+      return {
+        file,
+        id,
+        filename: file.name,
+        isCurrent: false,
+        conversion: { done: false, error: false },
+        upload: { done: false, error: false, progress: 0 },
+        onProgress: (event) => {
+          if (!event.lengthComputable) {
+            this.deepMergeUpdateFileKey(id, 'upload', {
+              progress: 100,
+              done: true,
+            });
+
+            return;
+          }
+
+          this.deepMergeUpdateFileKey(id, 'upload', {
+            progress: (event.loaded / event.total) * 100,
+            done: event.loaded === event.total,
           });
-
-          return;
-        }
-
-        this.deepMergeUpdateFileKey(file.name, 'upload', {
-          progress: (event.loaded / event.total) * 100,
-          done: event.loaded === event.total,
-        });
-      },
-      onError: (error) => {
-        this.deepMergeUpdateFileKey(file.name, 'upload', { error });
-      },
-    }));
+        },
+        onConversion: (conversion) => {
+          this.deepMergeUpdateFileKey(id, 'conversion', conversion);
+        },
+        onUpload: (upload) => {
+          this.deepMergeUpdateFileKey(id, 'upload', upload);
+        },
+        onDone: (newId) => {
+          this.updateFileKey(id, 'id', newId);
+        },
+      };
+    });
 
     this.setState(({ presentations }) => ({
       presentations: presentations.concat(presentationsToUpload),
     }));
   }
 
-  handleCurrentChange(item) {
+  handleCurrentChange(id) {
     const { presentations, disableActions } = this.state;
     if (disableActions) return;
 
     const currentIndex = presentations.findIndex(p => p.isCurrent);
-    const newCurrentIndex = presentations.indexOf(item);
+    const newCurrentIndex = presentations.findIndex(p => p.id === id);
 
     const commands = {};
 
     // we can end up without a current presentation
     if (currentIndex !== -1) {
       commands[currentIndex] = {
-        $apply: (_) => {
-          const p = _;
+        $apply: (presentation) => {
+          const p = presentation;
           p.isCurrent = false;
           return p;
         },
@@ -237,8 +285,8 @@ class PresentationUploader extends Component {
     }
 
     commands[newCurrentIndex] = {
-      $apply: (_) => {
-        const p = _;
+      $apply: (presentation) => {
+        const p = presentation;
         p.isCurrent = true;
         return p;
       },
@@ -256,14 +304,6 @@ class PresentationUploader extends Component {
     if (disableActions) return;
 
     const toRemoveIndex = presentations.indexOf(item);
-    const toRemove = presentations[toRemoveIndex];
-
-
-    if (toRemove.isCurrent) {
-      const defaultPresentation =
-        presentations.find(_ => _.filename === this.props.defaultFileName);
-      this.handleCurrentChange(defaultPresentation);
-    }
 
     this.setState({
       presentations: update(presentations, {
@@ -276,7 +316,17 @@ class PresentationUploader extends Component {
     const { presentations } = this.state;
 
     const presentationsSorted = presentations
-      .sort((a, b) => b.filename === this.props.defaultFileName);
+      .sort((a, b) => {
+        // Sort by ID first so files with the same name have the same order
+        if (a.id > b.id) {
+          return 1;
+        }
+        if (a.id < b.id) {
+          return -1;
+        }
+        return 0;
+      })
+      .sort((a, b) => this.isDefault(b));
 
     return (
       <div className={styles.fileList}>
@@ -303,12 +353,12 @@ class PresentationUploader extends Component {
     }
 
     if (item.upload.done && item.upload.error) {
-      const errorMessage = intlMessages[item.upload.error.code] || intlMessages.genericError;
+      const errorMessage = intlMessages[item.upload.status] || intlMessages.genericError;
       return intl.formatMessage(errorMessage);
     }
 
-    if (!item.conversion.done && item.conversion.error) {
-      const errorMessage = intlMessages[status] || intlMessages.genericError;
+    if (item.conversion.done && item.conversion.error) {
+      const errorMessage = intlMessages[item.conversion.status] || intlMessages.genericError;
       return intl.formatMessage(errorMessage);
     }
 
@@ -329,19 +379,23 @@ class PresentationUploader extends Component {
   }
 
   renderPresentationItem(item) {
-    const { disableActions } = this.state;
-
-    const isProcessing = (!item.conversion.done && item.upload.done)
-      || (!item.upload.done && item.upload.progress > 0);
-    const itemClassName = {};
-
-    itemClassName[styles.tableItemNew] = item.id === item.filename;
-    itemClassName[styles.tableItemUploading] = !item.upload.done;
-    itemClassName[styles.tableItemProcessing] = !item.conversion.done && item.upload.done;
-    itemClassName[styles.tableItemError] = item.conversion.error || item.upload.error;
-    itemClassName[styles.tableItemAnimated] = isProcessing;
+    const { disableActions, oldCurrentId } = this.state;
+
+    const isActualCurrent = item.id === oldCurrentId;
+    const isUploading = !item.upload.done && item.upload.progress > 0;
+    const isConverting = !item.conversion.done && item.upload.done;
+    const hasError = item.conversion.error || item.upload.error;
+    const isProcessing = (isUploading || isConverting) && !hasError;
+
+    const itemClassName = {
+      [styles.tableItemNew]: item.id.indexOf(item.filename) !== -1,
+      [styles.tableItemUploading]: isUploading,
+      [styles.tableItemConverting]: isConverting,
+      [styles.tableItemError]: hasError,
+      [styles.tableItemAnimated]: isProcessing,
+    };
 
-    const hideRemove = isProcessing || item.filename === this.props.defaultFileName;
+    const hideRemove = this.isDefault(item);
 
     return (
       <tr
@@ -349,33 +403,44 @@ class PresentationUploader extends Component {
         className={cx(itemClassName)}
       >
         <td className={styles.tableItemIcon}>
-          <Icon iconName={'file'} />
+          <Icon iconName="file" />
         </td>
-        <th className={styles.tableItemName}>
+        {
+          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>
         </th>
-        <td className={styles.tableItemStatus}>
+        <td className={styles.tableItemStatus} colSpan={hasError ? 2 : 0}>
           {this.renderPresentationItemStatus(item)}
         </td>
-        <td className={styles.tableItemActions}>
-          <Checkbox
-            disabled={disableActions}
-            ariaLabel={'Set as current presentation'}
-            className={styles.itemAction}
-            checked={item.isCurrent}
-            onChange={() => this.handleCurrentChange(item)}
-          />
-          { hideRemove ? null : (
-            <ButtonBase
+        { hasError ? null : (
+          <td className={styles.tableItemActions}>
+            <Checkbox
               disabled={disableActions}
-              className={cx(styles.itemAction, styles.itemActionRemove)}
-              label={'Remove presentation'}
-              onClick={() => this.handleRemove(item)}
-            >
-              <Icon iconName={'delete'} />
-            </ButtonBase>
-          )}
-        </td>
+              ariaLabel="Set as current presentation"
+              className={styles.itemAction}
+              checked={item.isCurrent}
+              onChange={() => this.handleCurrentChange(item.id)}
+            />
+            { hideRemove ? null : (
+              <ButtonBase
+                disabled={disableActions}
+                className={cx(styles.itemAction, styles.itemActionRemove)}
+                label="Remove presentation"
+                onClick={() => this.handleRemove(item)}
+              >
+                <Icon iconName="delete" />
+              </ButtonBase>
+            )}
+          </td>
+        )}
       </tr>
     );
   }
@@ -404,7 +469,7 @@ class PresentationUploader extends Component {
         disablePreview
         onDrop={this.handleFiledrop}
       >
-        <Icon className={styles.dropzoneIcon} iconName={'upload'} />
+        <Icon className={styles.dropzoneIcon} iconName="upload" />
         <p className={styles.dropzoneMessage}>
           {intl.formatMessage(intlMessages.dropzoneLabel)}&nbsp;
           <span className={styles.dropzoneLink}>
diff --git a/bigbluebutton-html5/imports/ui/components/presentation/presentation-uploader/container.jsx b/bigbluebutton-html5/imports/ui/components/presentation/presentation-uploader/container.jsx
index e90e66b8490c22b17470e46888b5d1a8b72f38aa..abcac9f716a19149009636b1d831e56c6b141062 100644
--- a/bigbluebutton-html5/imports/ui/components/presentation/presentation-uploader/container.jsx
+++ b/bigbluebutton-html5/imports/ui/components/presentation/presentation-uploader/container.jsx
@@ -1,5 +1,5 @@
 import React from 'react';
-import { createContainer } from 'meteor/react-meteor-data';
+import { withTracker } from 'meteor/react-meteor-data';
 
 import Service from './service';
 import PresentationUploader from './component';
@@ -8,7 +8,7 @@ const PresentationUploaderContainer = props => (
   <PresentationUploader {...props} />
 );
 
-export default createContainer(() => {
+export default withTracker(() => {
   const PRESENTATION_CONFIG = Meteor.settings.public.presentation;
   const currentPresentations = Service.getPresentations();
 
@@ -24,4 +24,4 @@ export default createContainer(() => {
       PRESENTATION_CONFIG.uploadEndpoint,
     ),
   };
-}, PresentationUploaderContainer);
+})(PresentationUploaderContainer);
diff --git a/bigbluebutton-html5/imports/ui/components/presentation/presentation-uploader/service.js b/bigbluebutton-html5/imports/ui/components/presentation/presentation-uploader/service.js
index 54daf9c1b614664950a4a1c5a2a6d3603d6aadb3..763530a5d48f806100c5f3e1897556fb01b943fe 100644
--- a/bigbluebutton-html5/imports/ui/components/presentation/presentation-uploader/service.js
+++ b/bigbluebutton-html5/imports/ui/components/presentation/presentation-uploader/service.js
@@ -1,6 +1,7 @@
 import Presentations from '/imports/api/presentations';
 import Auth from '/imports/ui/services/auth';
 import { makeCall } from '/imports/ui/services/api';
+import _ from 'lodash';
 
 const CONVERSION_TIMEOUT = 300000;
 
@@ -29,7 +30,9 @@ const futch = (url, opts = {}, onProgress) => new Promise((res, rej) => {
 
 const getPresentations = () =>
   Presentations
-    .find()
+    .find({
+      'conversion.error': false,
+    })
     .fetch()
     .map(presentation => ({
       id: presentation.id,
@@ -39,36 +42,40 @@ const getPresentations = () =>
       conversion: presentation.conversion || { done: true, error: false },
     }));
 
-const observePresentationConversion = (meetingId, filename) => new Promise((resolve, reject) => {
-  const conversionTimeout = setTimeout(() => {
-    reject({
-      filename,
-      message: 'Conversion timeout.',
-    });
-  }, CONVERSION_TIMEOUT);
-
-  const didValidate = (doc) => {
-    clearTimeout(conversionTimeout);
-    resolve(doc);
-  };
-
-  Tracker.autorun((c) => {
-    /* FIXME: With two presentations with the same name this will not work as expected */
-    const query = Presentations.find({ meetingId });
-
-    query.observe({
-      changed: (newDoc) => {
-        if (newDoc.name !== filename) return;
-        if (newDoc.conversion.done) {
-          c.stop();
-          didValidate(newDoc);
-        }
-      },
+const observePresentationConversion = (meetingId, filename, onConversion) =>
+  new Promise((resolve) => {
+    const conversionTimeout = setTimeout(() => {
+      onConversion({
+        done: true,
+        error: true,
+        status: 'TIMEOUT',
+      });
+    }, CONVERSION_TIMEOUT);
+
+    const didValidate = (doc) => {
+      clearTimeout(conversionTimeout);
+      resolve(doc);
+    };
+
+    Tracker.autorun((c) => {
+      const query = Presentations.find({ meetingId });
+
+      query.observe({
+        changed: (newDoc) => {
+          if (newDoc.name !== filename) return;
+
+          onConversion(newDoc.conversion);
+
+          if (newDoc.conversion.done) {
+            c.stop();
+            didValidate(newDoc);
+          }
+        },
+      });
     });
   });
-});
 
-const uploadAndConvertPresentation = (file, meetingID, endpoint, onError, onProgress) => {
+const uploadAndConvertPresentation = (file, meetingID, endpoint, onUpload, onProgress, onConversion) => {
   const data = new FormData();
   data.append('presentation_name', file.name);
   data.append('Filename', file.name);
@@ -84,19 +91,17 @@ const uploadAndConvertPresentation = (file, meetingID, endpoint, onError, onProg
   };
 
   return futch(endpoint, opts, onProgress)
-    .then(() => observePresentationConversion(meetingID, file.name))
+    .then(() => observePresentationConversion(meetingID, file.name, onConversion))
     // Trap the error so we can have parallel upload
     .catch((error) => {
-      onError(error);
-      return observePresentationConversion(meetingID, file.name);
+      onUpload({ error: true, done: true, status: error.code });
+      return Promise.resolve();
     });
 };
 
 const uploadAndConvertPresentations = (presentationsToUpload, meetingID, uploadEndpoint) =>
-  Promise.all(
-    presentationsToUpload.map(p =>
-      uploadAndConvertPresentation(p.file, meetingID, uploadEndpoint, p.onError, p.onProgress)),
-  );
+  Promise.all(presentationsToUpload.map(p =>
+    uploadAndConvertPresentation(p.file, meetingID, uploadEndpoint, p.onUpload, p.onProgress, p.onConversion)));
 
 const setPresentation = presentationID => makeCall('setPresentation', presentationID);
 
@@ -106,27 +111,42 @@ const removePresentations = presentationsToRemove =>
   Promise.all(presentationsToRemove.map(p => removePresentation(p.id)));
 
 const persistPresentationChanges = (oldState, newState, uploadEndpoint) => {
-  const presentationsToUpload = newState.filter(_ => !oldState.includes(_));
-  const presentationsToRemove = oldState.filter(_ => !newState.includes(_));
-  const currentPresentation = newState.find(_ => _.isCurrent);
-
-  return new Promise((resolve, reject) =>
-    uploadAndConvertPresentations(presentationsToUpload, Auth.meetingID, uploadEndpoint)
-      .then((presentations) => {
-        if (!presentations.length && !currentPresentation) return Promise.resolve();
-
-        // If its a newly uploaded presentation we need to get its id from promise result
-        const currentPresentationId =
-          currentPresentation.id !== currentPresentation.filename ?
-          currentPresentation.id :
-          presentations[presentationsToUpload.findIndex(_ => _ === currentPresentation)].id;
-
-        return setPresentation(currentPresentationId);
-      })
-      .then(removePresentations.bind(null, presentationsToRemove))
-      .then(resolve)
-      .catch(reject),
-  );
+  const presentationsToUpload = newState.filter(p => !p.upload.done);
+  const presentationsToRemove = oldState.filter(p => !_.find(newState, ['id', p.id]));
+
+  let currentPresentation = newState.find(p => p.isCurrent);
+
+  return uploadAndConvertPresentations(presentationsToUpload, Auth.meetingID, uploadEndpoint)
+    .then((presentations) => {
+      if (!presentations.length && !currentPresentation) return Promise.resolve();
+
+      // Update the presentation with their new ids
+      presentations.forEach((p, i) => {
+        if (p === undefined) return;
+        presentationsToUpload[i].onDone(p.id);
+      });
+
+      return Promise.resolve(presentations);
+    })
+    .then((presentations) => {
+      if (currentPresentation === undefined) {
+        return Promise.resolve();
+      }
+
+      // If its a newly uploaded presentation we need to get it from promise result
+      if (!currentPresentation.conversion.done) {
+        const currentIndex = presentationsToUpload.findIndex(p => p === currentPresentation);
+        currentPresentation = presentations[currentIndex];
+      }
+
+      // skip setting as current if error happened
+      if (currentPresentation.conversion.error) {
+        return Promise.resolve();
+      }
+
+      return setPresentation(currentPresentation.id);
+    })
+    .then(removePresentations.bind(null, presentationsToRemove));
 };
 
 export default {
diff --git a/bigbluebutton-html5/imports/ui/components/presentation/presentation-uploader/styles.scss b/bigbluebutton-html5/imports/ui/components/presentation/presentation-uploader/styles.scss
index 9d240b7f072e892204f64fd52b55df0285d9d0a5..275aa3218cd33f5a170dc8acf17e5cf64b082a66 100644
--- a/bigbluebutton-html5/imports/ui/components/presentation/presentation-uploader/styles.scss
+++ b/bigbluebutton-html5/imports/ui/components/presentation/presentation-uploader/styles.scss
@@ -57,12 +57,14 @@ $item-height: 1rem;
 
 .tableItemIcon,
 .tableItemActions,
-.tableItemStatus {
+.tableItemStatus,
+.tableItemCurrent {
   width: 1%;
 }
 
 .tableItemActions {
   min-width: 68px; // size of the 2 icons (check/trash)
+  text-align: right;
 }
 
 .tableItemIcon > i {
@@ -75,7 +77,7 @@ $item-height: 1rem;
   position: relative;
 
   &:before {
-    content: '&nbsp;';
+    content: "\00a0";
     visibility: hidden;
   }
 
@@ -84,10 +86,13 @@ $item-height: 1rem;
     position: absolute;
     left: 0;
     right: 0;
-    padding: 0 $sm-padding-x;
   }
 }
 
+.tableItemCurrent {
+  padding-left: 0;
+}
+
 .tableItemStatus {
   text-align: right;
 }
@@ -100,7 +105,7 @@ $item-height: 1rem;
   background-color: transparentize($color-primary, .75);
 }
 
-.tableItemProcessing {
+.tableItemConverting {
   background-color: transparentize($color-success, .75);
 }
 
@@ -183,3 +188,18 @@ $item-height: 1rem;
   font-size: 80%;
   display: block;
 }
+
+.currentLabel {
+  display: inline;
+  padding: .25em .5em;
+  font-size: 75%;
+  font-weight: 700;
+  line-height: 1;
+  color: $color-white;
+  background: $color-primary;
+  text-align: center;
+  white-space: nowrap;
+  vertical-align: baseline;
+  border-radius: .25em;
+  text-transform: uppercase;
+}
diff --git a/bigbluebutton-html5/imports/ui/components/screenshare/component.jsx b/bigbluebutton-html5/imports/ui/components/screenshare/component.jsx
index 093de370e6ef54ab0be0c1ed601359aa9f67c039..c381280101b799860a9807c0cabff1e0c31199fe 100644
--- a/bigbluebutton-html5/imports/ui/components/screenshare/component.jsx
+++ b/bigbluebutton-html5/imports/ui/components/screenshare/component.jsx
@@ -7,7 +7,7 @@ export default class ScreenshareComponent extends React.Component {
 
   render() {
     return (
-      <video id="screenshareVideo" style={{ height: '100%', width: '100%' }} />
+      <video id="screenshareVideo" style={{ height: '100%', width: '100%' }} autoPlay playsInline />
     );
   }
 }
diff --git a/bigbluebutton-html5/imports/ui/components/screenshare/container.jsx b/bigbluebutton-html5/imports/ui/components/screenshare/container.jsx
index 37a2ba3c6fbf2e9d73949c144e2bf0ce7a420d0b..b10659a3dbb169acb2bc2f5a27f8d35b6001796b 100644
--- a/bigbluebutton-html5/imports/ui/components/screenshare/container.jsx
+++ b/bigbluebutton-html5/imports/ui/components/screenshare/container.jsx
@@ -1,25 +1,26 @@
 import React from 'react';
-import { createContainer } from 'meteor/react-meteor-data';
+import { withTracker } from 'meteor/react-meteor-data';
 import { isVideoBroadcasting, presenterScreenshareHasEnded,
   presenterScreenshareHasStarted } from './service';
 import ScreenshareComponent from './component';
 
 class ScreenshareContainer extends React.Component {
+  componentWillUnmount() {
+    this.props.presenterScreenshareHasEnded();
+  }
+
   render() {
     if (this.props.isVideoBroadcasting()) {
       return <ScreenshareComponent {...this.props} />;
     }
-  }
 
-  componentWillUnmount() {
-    this.props.presenterScreenshareHasEnded();
+    return null;
   }
-
 }
 
-export default createContainer(() => ({
+export default withTracker(() => ({
   isVideoBroadcasting,
   presenterScreenshareHasStarted,
   presenterScreenshareHasEnded,
-}), ScreenshareContainer);
+}))(ScreenshareContainer);
 
diff --git a/bigbluebutton-html5/imports/ui/components/screenshare/service.js b/bigbluebutton-html5/imports/ui/components/screenshare/service.js
index cdb7ecc5ef638abb9c47709b390b6e2b68bab139..33470b2ecd571fe97d01092772178b9240ecaf88 100644
--- a/bigbluebutton-html5/imports/ui/components/screenshare/service.js
+++ b/bigbluebutton-html5/imports/ui/components/screenshare/service.js
@@ -1,33 +1,46 @@
 import Screenshare from '/imports/api/screenshare';
 import VertoBridge from '/imports/api/screenshare/client/bridge';
+import KurentoBridge from '/imports/api/screenshare/client/bridge';
 import PresentationService from '/imports/ui/components/presentation/service';
 
 // when the meeting information has been updated check to see if it was
 // screensharing. If it has changed either trigger a call to receive video
 // and display it, or end the call and hide the video
-function isVideoBroadcasting() {
+const isVideoBroadcasting = () => {
   const ds = Screenshare.findOne({});
 
   if (!ds) {
     return false;
   }
-  return ds.screenshare.stream && !PresentationService.isPresenter();
+
+  // TODO commented out isPresenter to enable screen viewing to the presenter
+  return ds.screenshare.stream; // && !PresentationService.isPresenter();
 }
 
 // if remote screenshare has been ended disconnect and hide the video stream
-function presenterScreenshareHasEnded() {
-  // references a function in the global namespace inside verto_extension.js
+const presenterScreenshareHasEnded = () => {
+  // references a function in the global namespace inside kurento-extension.js
   // that we load dynamically
-  VertoBridge.vertoExitVideo();
+  KurentoBridge.kurentoExitVideo();
 }
 
 // if remote screenshare has been started connect and display the video stream
-function presenterScreenshareHasStarted() {
-  // references a function in the global namespace inside verto_extension.js
+const presenterScreenshareHasStarted = () => {
+  // references a function in the global namespace inside kurento-extension.js
   // that we load dynamically
-  VertoBridge.vertoWatchVideo();
+  //VertoBridge.vertoWatchVideo();
+  KurentoBridge.kurentoWatchVideo();
+}
+
+const shareScreen = () => {
+  KurentoBridge.kurentoShareScreen();
+}
+
+const unshareScreen = () => {
+  console.log("Exiting screenshare");
+  KurentoBridge.kurentoExitScreenShare();
 }
 
 export {
-  isVideoBroadcasting, presenterScreenshareHasEnded, presenterScreenshareHasStarted,
+  isVideoBroadcasting, presenterScreenshareHasEnded, presenterScreenshareHasStarted, shareScreen, unshareScreen,
 };
diff --git a/bigbluebutton-html5/imports/ui/components/settings/component.jsx b/bigbluebutton-html5/imports/ui/components/settings/component.jsx
index f0f50d876667d3d262e60382e1ca93968f60ce8a..956f0df0b07f60d7416214306f8ddc5d1474e46f 100644
--- a/bigbluebutton-html5/imports/ui/components/settings/component.jsx
+++ b/bigbluebutton-html5/imports/ui/components/settings/component.jsx
@@ -9,7 +9,7 @@ import PropTypes from 'prop-types';
 
 import { withModalMounter } from '../modal/service';
 import Icon from '../icon/component';
-import styles from './styles';
+import { styles } from './styles';
 
 const intlMessages = defineMessages({
   appTabLabel: {
diff --git a/bigbluebutton-html5/imports/ui/components/settings/container.jsx b/bigbluebutton-html5/imports/ui/components/settings/container.jsx
index b5ff6d69bfd58d33d4f6407b76f71136ea2e1b76..2fe0599d976d0f597f93948069c64b6e21865c13 100644
--- a/bigbluebutton-html5/imports/ui/components/settings/container.jsx
+++ b/bigbluebutton-html5/imports/ui/components/settings/container.jsx
@@ -1,25 +1,20 @@
-import React, { Component } from 'react';
-import PropTypes from 'prop-types';
-import { createContainer } from 'meteor/react-meteor-data';
-import Settings from './component';
+import React from 'react';
+import { withTracker } from 'meteor/react-meteor-data';
 import SettingsService from '/imports/ui/services/settings';
+import Settings from './component';
 
 import {
-    getClosedCaptionLocales,
-    getUserRoles,
-    updateSettings,
-    getAvailableLocales,
-  } from './service';
+  getClosedCaptionLocales,
+  getUserRoles,
+  updateSettings,
+  getAvailableLocales,
+} from './service';
 
-class SettingsContainer extends Component {
-  render() {
-    return (
-      <Settings {...this.props} />
-    );
-  }
-}
+const SettingsContainer = props => (
+  <Settings {...props} />
+);
 
-export default createContainer(() => ({
+export default withTracker(() => ({
   audio: SettingsService.audio,
   video: SettingsService.video,
   application: SettingsService.application,
@@ -29,4 +24,4 @@ export default createContainer(() => ({
   locales: getClosedCaptionLocales(),
   availableLocales: getAvailableLocales(),
   isModerator: getUserRoles() === 'MODERATOR',
-}), SettingsContainer);
+}))(SettingsContainer);
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 650b646ddda0267b3eed20cd364bf3425c274b22..4a744a5e3e44d53060b766806f5274e22cd87df5 100644
--- a/bigbluebutton-html5/imports/ui/components/settings/submenus/application/component.jsx
+++ b/bigbluebutton-html5/imports/ui/components/settings/submenus/application/component.jsx
@@ -4,7 +4,7 @@ import cx from 'classnames';
 import Toggle from '/imports/ui/components/switch/component';
 import { defineMessages, injectIntl } from 'react-intl';
 import BaseMenu from '../base/component';
-import styles from '../styles';
+import { styles } from '../styles';
 
 const MIN_FONTSIZE = 0;
 const MAX_FONTSIZE = 4;
@@ -195,8 +195,7 @@ class ApplicationMenu extends BaseMenu {
                     { availableLocales.map((locale, index) =>
                       (<option key={index} value={locale.locale}>
                         {locale.name}
-                      </option>),
-                    ) }
+                      </option>)) }
                   </select>
                 : null }
               </label>
diff --git a/bigbluebutton-html5/imports/ui/components/settings/submenus/application/container.jsx b/bigbluebutton-html5/imports/ui/components/settings/submenus/application/container.jsx
index 3320793888732fad6b57bd585d718d637df28617..b9011514dd1d53ea7551f4abda72077f85ce2758 100644
--- a/bigbluebutton-html5/imports/ui/components/settings/submenus/application/container.jsx
+++ b/bigbluebutton-html5/imports/ui/components/settings/submenus/application/container.jsx
@@ -1,15 +1,15 @@
 import React from 'react';
-import { createContainer } from 'meteor/react-meteor-data';
+import { withTracker } from 'meteor/react-meteor-data';
 import Application from './component';
 
 
-const ApplicationContainer = props => (
+const ApplicationContainer = ({ children, ...props }) => (
   <Application {...props}>
-    {props.children}
+    {children}
   </Application>
-    );
+);
 
-export default createContainer(() => ({
+export default withTracker(() => ({
   fontSizes: [
     '12px',
     '14px',
@@ -17,4 +17,4 @@ export default createContainer(() => ({
     '18px',
     '20px',
   ],
-}), ApplicationContainer);
+}))(ApplicationContainer);
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 7eff5472cd0c082bda5dd5d84fa815faaad1be11..50a7e11f3ca06b3d192c0634663fb5a9a657aeeb 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,5 +1,5 @@
 import React from 'react';
-import styles from '../styles';
+import { styles } from '../styles';
 import cx from 'classnames';
 import BaseMenu from '../base/component';
 import Toggle from '/imports/ui/components/switch/component';
diff --git a/bigbluebutton-html5/imports/ui/components/settings/submenus/closed-captions/container.jsx b/bigbluebutton-html5/imports/ui/components/settings/submenus/closed-captions/container.jsx
index 53979f92925a2f9ce234b53d1b678a879012e145..e3d5c8861fe8ce2e8d600d7d9f49e4adb3e9bcb9 100644
--- a/bigbluebutton-html5/imports/ui/components/settings/submenus/closed-captions/container.jsx
+++ b/bigbluebutton-html5/imports/ui/components/settings/submenus/closed-captions/container.jsx
@@ -1,17 +1,12 @@
-import React, { Component } from 'react';
-import PropTypes from 'prop-types';
-import { createContainer } from 'meteor/react-meteor-data';
+import React from 'react';
+import { withTracker } from 'meteor/react-meteor-data';
 import ClosedCaptionsMenu from './component';
 import Service from './service';
 
-class ClosedCaptionsMenuContainer extends Component {
-  render() {
-    return (
-      <ClosedCaptionsMenu {...this.props}>
-        {this.props.children}
-      </ClosedCaptionsMenu>
-    );
-  }
-}
+const ClosedCaptionsMenuContainer = ({ children, ...props }) => (
+  <ClosedCaptionsMenu {...props}>
+    {children}
+  </ClosedCaptionsMenu>
+);
 
-export default createContainer(() => Service.getClosedCaptionSettings(), ClosedCaptionsMenuContainer);
+export default withTracker(() => Service.getClosedCaptionSettings(), ClosedCaptionsMenuContainer);
diff --git a/bigbluebutton-html5/imports/ui/components/settings/submenus/participants/component.jsx b/bigbluebutton-html5/imports/ui/components/settings/submenus/participants/component.jsx
index 779ac5cd5a5370aec55f6bfdd66d8ab0cf63cb30..83071940d7c97856a0125d4774ab0b2d346fc332 100644
--- a/bigbluebutton-html5/imports/ui/components/settings/submenus/participants/component.jsx
+++ b/bigbluebutton-html5/imports/ui/components/settings/submenus/participants/component.jsx
@@ -5,7 +5,7 @@ import cx from 'classnames';
 import { defineMessages, injectIntl } from 'react-intl';
 import _ from 'lodash';
 import BaseMenu from '../base/component';
-import styles from '../styles';
+import { styles } from '../styles';
 
 const intlMessages = defineMessages({
   participantsTitle: {
diff --git a/bigbluebutton-html5/imports/ui/components/settings/submenus/video/component.jsx b/bigbluebutton-html5/imports/ui/components/settings/submenus/video/component.jsx
index 4db656f01b51969a9b769636447d75419e64b5a6..51bd64c2f3c9511ef0b90b73d08975d5481d8cad 100644
--- a/bigbluebutton-html5/imports/ui/components/settings/submenus/video/component.jsx
+++ b/bigbluebutton-html5/imports/ui/components/settings/submenus/video/component.jsx
@@ -3,7 +3,7 @@ import Toggle from '/imports/ui/components/switch/component';
 import cx from 'classnames';
 import { defineMessages, injectIntl } from 'react-intl';
 import BaseMenu from '../base/component';
-import styles from '../styles';
+import { styles } from '../styles';
 
 const intlMessages = defineMessages({
   videoSectionTitle: {
diff --git a/bigbluebutton-html5/imports/ui/components/toast/component.jsx b/bigbluebutton-html5/imports/ui/components/toast/component.jsx
index 3929ec7c0817a0c9f34662be157801e8e5a0587f..e3adfcdbc02bbc56a6f33fc53e6338d3f7cce786 100644
--- a/bigbluebutton-html5/imports/ui/components/toast/component.jsx
+++ b/bigbluebutton-html5/imports/ui/components/toast/component.jsx
@@ -3,7 +3,7 @@ import PropTypes from 'prop-types';
 import { toast } from 'react-toastify';
 
 import Icon from '../icon/component';
-import styles from './styles';
+import { styles } from './styles';
 
 const propTypes = {
   icon: PropTypes.string,
diff --git a/bigbluebutton-html5/imports/ui/components/toast/container.jsx b/bigbluebutton-html5/imports/ui/components/toast/container.jsx
index 26b92d5b989d5a26aec58f448ac81a15de90d195..59242e12224ef5f4120f0be657c741136d7523d1 100644
--- a/bigbluebutton-html5/imports/ui/components/toast/container.jsx
+++ b/bigbluebutton-html5/imports/ui/components/toast/container.jsx
@@ -1,6 +1,7 @@
 import React from 'react';
+import Breakouts from '/imports/api/breakouts';
 import { ToastContainer as Toastify } from 'react-toastify';
-import { createContainer } from 'meteor/react-meteor-data';
+import { withTracker } from 'meteor/react-meteor-data';
 import { defineMessages, injectIntl } from 'react-intl';
 import injectNotify from '/imports/ui/components/toast/inject-notify/component';
 
@@ -8,9 +9,14 @@ import Auth from '/imports/ui/services/auth';
 import Meetings from '/imports/api/meetings';
 
 import Icon from '../icon/component';
-import styles from './styles';
+import { styles } from './styles';
 
 const intlMessages = defineMessages({
+
+  toastBreakoutRoomEnded: {
+    id: 'app.toast.breakoutRoomEnded',
+    description: 'message when the breakout room is ended',
+  },
   notificationRecordingStart: {
     id: 'app.notification.recordingStart',
     description: 'Notification for when the recording start',
@@ -32,7 +38,13 @@ class ToastContainer extends React.Component {
   }
 }
 
-export default injectIntl(injectNotify(createContainer(({ notify, intl }) => {
+export default injectIntl(injectNotify(withTracker(({ notify, intl }) => {
+  Breakouts.find().observeChanges({
+    removed() {
+      notify(intl.formatMessage(intlMessages.toastBreakoutRoomEnded), 'info', 'rooms');
+    },
+  });
+
   const meetingId = Auth.meetingID;
 
   Meetings.find({ meetingId }).observeChanges({
@@ -59,4 +71,4 @@ export default injectIntl(injectNotify(createContainer(({ notify, intl }) => {
     closeOnClick: true,
     pauseOnHover: true,
   };
-}, ToastContainer)));
+})(ToastContainer)));
diff --git a/bigbluebutton-html5/imports/ui/components/tooltip/component.jsx b/bigbluebutton-html5/imports/ui/components/tooltip/component.jsx
new file mode 100644
index 0000000000000000000000000000000000000000..dd8b694ec96427cc22b0995c042313308379da71
--- /dev/null
+++ b/bigbluebutton-html5/imports/ui/components/tooltip/component.jsx
@@ -0,0 +1,86 @@
+import React, { Component } from 'react';
+import PropTypes from 'prop-types';
+import Tippy from 'tippy.js';
+import _ from 'lodash';
+import cx from 'classnames';
+import { ESCAPE } from '/imports/utils/keyCodes';
+
+const propTypes = {
+  title: PropTypes.string.isRequired,
+  position: PropTypes.oneOf(['bottom']),
+  children: PropTypes.element.isRequired,
+  className: PropTypes.string,
+};
+
+const defaultProps = {
+  position: 'bottom',
+  className: null,
+};
+
+class Tooltip extends Component {
+  constructor(props) {
+    super(props);
+
+    this.tippySelectorId = _.uniqueId('tippy-');
+    this.onShow = this.onShow.bind(this);
+    this.onHide = this.onHide.bind(this);
+    this.handleEscapeHide = this.handleEscapeHide.bind(this);
+    this.delay = [250, 100];
+    this.dynamicTitle = true;
+  }
+
+  componentDidMount() {
+    const {
+      position,
+    } = this.props;
+
+    const options = {
+      position,
+      dynamicTitle: this.dynamicTitle,
+      delay: this.delay,
+      onShow: this.onShow,
+      onHide: this.onHide,
+    };
+
+    this.tooltip = Tippy(`#${this.tippySelectorId}`, options);
+  }
+
+  onShow() {
+    document.addEventListener('keyup', this.handleEscapeHide);
+  }
+
+  onHide() {
+    document.removeEventListener('keyup', this.handleEscapeHide);
+  }
+
+  handleEscapeHide(e) {
+    if (e.keyCode !== ESCAPE) return;
+
+    this.tooltip.tooltips[0].hide();
+  }
+
+  render() {
+    const {
+      children,
+      className,
+      title,
+      ...restProps
+    } = this.props;
+
+    const WrappedComponent = React.Children.only(children);
+
+    const WrappedComponentBound = React.cloneElement(WrappedComponent, {
+      ...restProps,
+      title,
+      id: this.tippySelectorId,
+      className: cx(children.props.className, className),
+    });
+
+    return WrappedComponentBound;
+  }
+}
+
+export default Tooltip;
+
+Tooltip.defaultProps = defaultProps;
+Tooltip.propTypes = propTypes;
diff --git a/bigbluebutton-html5/imports/ui/components/user-avatar/component.jsx b/bigbluebutton-html5/imports/ui/components/user-avatar/component.jsx
index 7201a52a2a0a5b8ba6c981c8467a9203d1917424..93b28165f6c9923ca0ed406121487050e18291dd 100644
--- a/bigbluebutton-html5/imports/ui/components/user-avatar/component.jsx
+++ b/bigbluebutton-html5/imports/ui/components/user-avatar/component.jsx
@@ -2,7 +2,7 @@ import React from 'react';
 import PropTypes from 'prop-types';
 import cx from 'classnames';
 
-import styles from './styles';
+import { styles } from './styles';
 
 const propTypes = {
   children: PropTypes.node.isRequired,
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 c5cd7bb62943a8f06d8718db1ea0170d1d76ab25..0dc1867befbd1b47d59493a1106ca2472edabdb5 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
@@ -1,7 +1,7 @@
 import React from 'react';
 import PropTypes from 'prop-types';
 import Icon from '/imports/ui/components/icon/component';
-import styles from './styles';
+import { styles } from './styles';
 
 const propTypes = {
   icon: PropTypes.string.isRequired,
diff --git a/bigbluebutton-html5/imports/ui/components/user-list/chat-list-item/chat-unread-messages/component.jsx b/bigbluebutton-html5/imports/ui/components/user-list/chat-list-item/chat-unread-messages/component.jsx
index 5769c46351a65f531e8e40d61c1180416b054745..a6ac2cb9ff972422e2165fc0aae2d786e6b899f1 100644
--- a/bigbluebutton-html5/imports/ui/components/user-list/chat-list-item/chat-unread-messages/component.jsx
+++ b/bigbluebutton-html5/imports/ui/components/user-list/chat-list-item/chat-unread-messages/component.jsx
@@ -1,7 +1,7 @@
 import React from 'react';
 import PropTypes from 'prop-types';
 import { defineMessages, injectIntl } from 'react-intl';
-import styles from './styles';
+import { styles } from './styles';
 
 const intlMessages = defineMessages({
   unreadPlural: {
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 8600181af8daa7a6a7d5bfaf1fc379d794c2e529..96d76f4e49ecd51a3e7e2377a9866f522379015c 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
@@ -3,7 +3,7 @@ import PropTypes from 'prop-types';
 import { withRouter, Link } from 'react-router';
 import cx from 'classnames';
 import { defineMessages, injectIntl } from 'react-intl';
-import styles from './styles';
+import { styles } from './styles';
 import ChatAvatar from './chat-avatar/component';
 import ChatIcon from './chat-icon/component';
 import ChatUnreadCounter from './chat-unread-messages/component';
diff --git a/bigbluebutton-html5/imports/ui/components/user-list/component.jsx b/bigbluebutton-html5/imports/ui/components/user-list/component.jsx
index 5b4d61c507fa8c0795fd7b01608f9f3cd436bbba..00daa5eb17a19d10311dda6688d70bd8c9fa9813 100644
--- a/bigbluebutton-html5/imports/ui/components/user-list/component.jsx
+++ b/bigbluebutton-html5/imports/ui/components/user-list/component.jsx
@@ -4,8 +4,7 @@ import PropTypes from 'prop-types';
 import { withRouter } from 'react-router';
 
 import injectWbResizeEvent from '/imports/ui/components/presentation/resize-wrapper/component';
-import styles from './styles';
-import UserListHeader from './user-list-header/component';
+import { styles } from './styles';
 import UserContent from './user-list-content/component';
 
 const propTypes = {
@@ -27,6 +26,7 @@ const propTypes = {
   kickUser: PropTypes.func.isRequired,
   toggleVoice: PropTypes.func.isRequired,
   changeRole: PropTypes.func.isRequired,
+  roving: PropTypes.func.isRequired,
 };
 
 const defaultProps = {
@@ -54,10 +54,6 @@ class UserList extends Component {
   render() {
     return (
       <div className={styles.userList}>
-        {/* <UserListHeader
-          intl={this.props.intl}
-          compact={this.state.compact}
-        /> */}
         {<UserContent
           intl={this.props.intl}
           openChats={this.props.openChats}
@@ -75,6 +71,7 @@ class UserList extends Component {
           normalizeEmojiName={this.props.normalizeEmojiName}
           isMeetingLocked={this.props.isMeetingLocked}
           isPublicChat={this.props.isPublicChat}
+          roving={this.props.roving}
         />}
       </div>
     );
diff --git a/bigbluebutton-html5/imports/ui/components/user-list/container.jsx b/bigbluebutton-html5/imports/ui/components/user-list/container.jsx
index eff13f2dd01100f5cf7533c6c29fa40a7b9cc26e..9242762db3534a0bac57c7a47244dec84f667686 100644
--- a/bigbluebutton-html5/imports/ui/components/user-list/container.jsx
+++ b/bigbluebutton-html5/imports/ui/components/user-list/container.jsx
@@ -1,19 +1,35 @@
 import React from 'react';
-import { createContainer } from 'meteor/react-meteor-data';
+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 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,
+  isBreakoutRoom: PropTypes.bool.isRequired,
+  getAvailableActions: PropTypes.func.isRequired,
+  normalizeEmojiName: PropTypes.func.isRequired,
+  isMeetingLocked: PropTypes.func.isRequired,
+  isPublicChat: PropTypes.func.isRequired,
+  setEmojiStatus: PropTypes.func.isRequired,
+  assignPresenter: PropTypes.func.isRequired,
+  kickUser: PropTypes.func.isRequired,
+  toggleVoice: PropTypes.func.isRequired,
+  changeRole: PropTypes.func.isRequired,
+  roving: PropTypes.func.isRequired,
+};
+
 const UserListContainer = (props) => {
   const {
     users,
     currentUser,
     openChats,
-    openChat,
-    userActions,
     isBreakoutRoom,
-    children,
     meeting,
     getAvailableActions,
     normalizeEmojiName,
@@ -24,7 +40,8 @@ const UserListContainer = (props) => {
     kickUser,
     toggleVoice,
     changeRole,
-    } = props;
+    roving,
+  } = props;
 
   return (
     <UserList
@@ -32,31 +49,28 @@ const UserListContainer = (props) => {
       meeting={meeting}
       currentUser={currentUser}
       openChats={openChats}
-      openChat={openChat}
       isBreakoutRoom={isBreakoutRoom}
       setEmojiStatus={setEmojiStatus}
       assignPresenter={assignPresenter}
       kickUser={kickUser}
       toggleVoice={toggleVoice}
       changeRole={changeRole}
-      userActions={userActions}
       getAvailableActions={getAvailableActions}
       normalizeEmojiName={normalizeEmojiName}
       isMeetingLocked={isMeetingLocked}
       isPublicChat={isPublicChat}
-    >
-      {children}
-    </UserList>
+      roving={roving}
+    />
   );
 };
 
-export default createContainer(({ params }) => ({
+UserListContainer.propTypes = propTypes;
+
+export default withTracker(({ params }) => ({
   users: Service.getUsers(),
   meeting: Meetings.findOne({}),
   currentUser: Service.getCurrentUser(),
   openChats: Service.getOpenChats(params.chatID),
-  openChat: params.chatID,
-  userActions: Service.userActions,
   isBreakoutRoom: meetingIsBreakout(),
   getAvailableActions: Service.getAvailableActions,
   normalizeEmojiName: Service.normalizeEmojiName,
@@ -67,4 +81,5 @@ export default createContainer(({ params }) => ({
   kickUser: Service.kickUser,
   toggleVoice: Service.toggleVoice,
   changeRole: Service.changeRole,
-}), UserListContainer);
+  roving: Service.roving,
+}))(UserListContainer);
diff --git a/bigbluebutton-html5/imports/ui/components/user-list/service.js b/bigbluebutton-html5/imports/ui/components/user-list/service.js
index 72941fdbdbcf9914f2c4cd26c45c9d61c91be58c..1116627ff613bc3a08bacf591457e32fcace4a58 100644
--- a/bigbluebutton-html5/imports/ui/components/user-list/service.js
+++ b/bigbluebutton-html5/imports/ui/components/user-list/service.js
@@ -8,6 +8,7 @@ import mapUser from '/imports/ui/services/user/mapUser';
 import { EMOJI_STATUSES } from '/imports/utils/statuses';
 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;
@@ -214,27 +215,46 @@ const getOpenChats = (chatID) => {
     .sort(sortChats);
 };
 
+const isVoiceOnlyUser = userId => userId.toString().startsWith('v_');
+
 const getAvailableActions = (currentUser, user, router, isBreakoutRoom) => {
+  const isDialInUser = isVoiceOnlyUser(user.id) || user.isPhoneUser;
+
   const hasAuthority = currentUser.isModerator || user.isCurrent;
-  const allowedToChatPrivately = !user.isCurrent;
+
+  const allowedToChatPrivately = !user.isCurrent && !isDialInUser;
+
   const allowedToMuteAudio = hasAuthority
                             && user.isVoiceUser
                             && !user.isMuted
                             && !user.isListenOnly;
+
   const allowedToUnmuteAudio = hasAuthority
                               && user.isVoiceUser
                               && !user.isListenOnly
                               && user.isMuted
                               && (ALLOW_MODERATOR_TO_UNMUTE_AUDIO || user.isCurrent);
-  const allowedToResetStatus = hasAuthority && user.emoji.status !== EMOJI_STATUSES.none;
+
+  const allowedToResetStatus = hasAuthority
+      && user.emoji.status !== EMOJI_STATUSES.none
+      && !isDialInUser;
 
   // if currentUser is a moderator, allow kicking other users
   const allowedToKick = currentUser.isModerator && !user.isCurrent && !isBreakoutRoom;
 
-  const allowedToSetPresenter = currentUser.isModerator && !user.isPresenter;
+  const allowedToSetPresenter = currentUser.isModerator
+      && !user.isPresenter
+      && !isDialInUser;
+
+  const allowedToPromote = currentUser.isModerator
+      && !user.isCurrent
+      && !user.isModerator
+      && !isDialInUser;
 
-  const allowedToPromote = currentUser.isModerator && !user.isCurrent && !user.isModerator;
-  const allowedToDemote = currentUser.isModerator && !user.isCurrent && user.isModerator;
+  const allowedToDemote = currentUser.isModerator
+      && !user.isCurrent
+      && user.isModerator
+      && !isDialInUser;
 
   return {
     allowedToChatPrivately,
@@ -281,12 +301,54 @@ const setEmojiStatus = (userId) => { makeCall('setEmojiStatus', userId, 'none');
 
 const assignPresenter = (userId) => { makeCall('assignPresenter', userId); };
 
-const kickUser = (userId) => { makeCall('kickUser', userId); };
+const kickUser = (userId) => {
+  if (isVoiceOnlyUser(userId)) {
+    makeCall('ejectUserFromVoice', userId);
+  } else {
+    makeCall('kickUser', userId);
+  }
+};
 
 const toggleVoice = (userId) => { makeCall('toggleVoice', userId); };
 
 const changeRole = (userId, role) => { makeCall('changeRole', userId, role); };
 
+const roving = (event, itemCount, changeState) => {
+  if (this.selectedIndex === undefined) {
+    this.selectedIndex = -1;
+  }
+
+  if ([KEY_CODES.ESCAPE, KEY_CODES.TAB].includes(event.keyCode)) {
+    document.activeElement.blur();
+    this.selectedIndex = -1;
+    changeState(this.selectedIndex);
+  }
+
+  if (event.keyCode === KEY_CODES.ARROW_DOWN) {
+    this.selectedIndex += 1;
+
+    if (this.selectedIndex === itemCount) {
+      this.selectedIndex = 0;
+    }
+
+    changeState(this.selectedIndex);
+  }
+
+  if (event.keyCode === KEY_CODES.ARROW_UP) {
+    this.selectedIndex -= 1;
+
+    if (this.selectedIndex < 0) {
+      this.selectedIndex = itemCount - 1;
+    }
+
+    changeState(this.selectedIndex);
+  }
+
+  if ([KEY_CODES.ARROW_RIGHT, KEY_CODES.SPACE].includes(event.keyCode)) {
+    document.activeElement.firstChild.click();
+  }
+};
+
 export default {
   setEmojiStatus,
   assignPresenter,
@@ -300,4 +362,5 @@ export default {
   normalizeEmojiName,
   isMeetingLocked,
   isPublicChat,
+  roving,
 };
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 4d701358e8e199b6c816d33a30f0d24c344e6ea0..4089ff461f530eef446cc23e10bb42872202e103 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,7 +1,6 @@
 import React, { Component } from 'react';
 import PropTypes from 'prop-types';
-import KEY_CODES from '/imports/utils/keyCodes';
-import styles from './styles';
+import { styles } from './styles';
 import UserParticipants from './user-participants/component';
 import UserMessages from './user-messages/component';
 
@@ -24,6 +23,7 @@ const propTypes = {
   kickUser: PropTypes.func.isRequired,
   toggleVoice: PropTypes.func.isRequired,
   changeRole: PropTypes.func.isRequired,
+  roving: PropTypes.func.isRequired,
 };
 
 const defaultProps = {
@@ -35,82 +35,6 @@ const defaultProps = {
 };
 
 class UserContent extends Component {
-
-  static focusElement(active, element) {
-    const modifiedActive = active;
-    const modifiedElement = element;
-    if (!modifiedActive.getAttribute('role') === 'tabpanel') {
-      modifiedActive.tabIndex = -1;
-    }
-    modifiedElement.tabIndex = 0;
-    modifiedElement.focus();
-  }
-
-  static removeFocusFromChildren(children, numberOfItems) {
-    const modifiedChildren = children;
-    for (let i = 0; i < numberOfItems; i += 1) {
-      modifiedChildren.childNodes[i].tabIndex = -1;
-    }
-  }
-
-  constructor(props) {
-    super(props);
-
-    this.rovingIndex = this.rovingIndex.bind(this);
-    this.focusList = this.focusList.bind(this);
-    this.focusedItemIndex = -1;
-  }
-
-  focusList(list) {
-    const focusList = list;
-    document.activeElement.tabIndex = -1;
-    this.focusedItemIndex = -1;
-    focusList.tabIndex = 0;
-    focusList.focus();
-  }
-
-
-  rovingIndex(event, list, items, numberOfItems) {
-    const active = document.activeElement;
-    const changedItems = items;
-
-    if (event.keyCode === KEY_CODES.TAB) {
-      if (this.focusedItemIndex !== -1) {
-        this.focusedItemIndex = 0;
-        UserContent.removeFocusFromChildren(changedItems, numberOfItems);
-      }
-    }
-
-    if (event.keyCode === KEY_CODES.ESCAPE
-      || this.focusedItemIndex < 0
-      || this.focusedItemIndex > numberOfItems) {
-      this.focusList(list);
-    }
-
-    if ([KEY_CODES.ARROW_RIGHT, KEY_CODES.ARROW_SPACE].includes(event.keyCode)) {
-      active.firstChild.click();
-    }
-
-    if (event.keyCode === KEY_CODES.ARROW_DOWN) {
-      this.focusedItemIndex += 1;
-
-      if (this.focusedItemIndex === numberOfItems) {
-        this.focusedItemIndex = 0;
-      }
-      UserContent.focusElement(active, changedItems.childNodes[this.focusedItemIndex]);
-    }
-
-    if (event.keyCode === KEY_CODES.ARROW_UP) {
-      this.focusedItemIndex -= 1;
-
-      if (this.focusedItemIndex < 0) {
-        this.focusedItemIndex = numberOfItems - 1;
-      }
-
-      UserContent.focusElement(active, changedItems.childNodes[this.focusedItemIndex]);
-    }
-  }
-
   render() {
     return (
       <div className={styles.content}>
@@ -119,7 +43,7 @@ class UserContent extends Component {
           openChats={this.props.openChats}
           compact={this.props.compact}
           intl={this.props.intl}
-          rovingIndex={this.rovingIndex}
+          roving={this.props.roving}
         />
         <UserParticipants
           users={this.props.users}
@@ -135,8 +59,8 @@ class UserContent extends Component {
           changeRole={this.props.changeRole}
           getAvailableActions={this.props.getAvailableActions}
           normalizeEmojiName={this.props.normalizeEmojiName}
-          rovingIndex={this.rovingIndex}
           isMeetingLocked={this.props.isMeetingLocked}
+          roving={this.props.roving}
         />
       </div>
     );
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 acbc8846aa7aafc2bc2c48c2af9f26519e5d2c7d..e114f61446facb5a5271b0f878abf7b369e1bcd3 100644
--- 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
@@ -10,14 +10,18 @@
 }
 
 .scrollableList {
-  pointer-events: none;
   @include elementFocus($list-item-bg-hover);
   @include scrollbox-vertical($user-list-bg);
+
+  &:active {
+    box-shadow: none;
+    border-radius: none;
+  }
 }
 
 .list {
   margin-left: $md-padding-y;
-  pointer-events: all;
+  margin-bottom: 1px;
 }
 
 .smallTitle {
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 7d07842213a8da2ff5aa741f3564f9529b20bad6..d71b6270cface4869da5c74fe5dc260a732460e3 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
@@ -3,7 +3,7 @@ import { TransitionGroup, CSSTransition } from 'react-transition-group';
 import PropTypes from 'prop-types';
 import { defineMessages } from 'react-intl';
 import cx from 'classnames';
-import styles from '/imports/ui/components/user-list/user-list-content/styles';
+import { styles } from '/imports/ui/components/user-list/user-list-content/styles';
 import ChatListItem from './../../chat-list-item/component';
 
 const propTypes = {
@@ -13,8 +13,8 @@ const propTypes = {
   intl: PropTypes.shape({
     formatMessage: PropTypes.func.isRequired,
   }).isRequired,
-  rovingIndex: PropTypes.func.isRequired,
   isPublicChat: PropTypes.func.isRequired,
+  roving: PropTypes.func.isRequired,
 };
 
 const defaultProps = {
@@ -32,80 +32,102 @@ const listTransition = {
 };
 
 const intlMessages = defineMessages({
-  usersTitle: {
-    id: 'app.userList.usersTitle',
-    description: 'Title for the Header',
-  },
   messagesTitle: {
     id: 'app.userList.messagesTitle',
     description: 'Title for the messages list',
   },
-  participantsTitle: {
-    id: 'app.userList.participantsTitle',
-    description: 'Title for the Users list',
-  },
-  toggleCompactView: {
-    id: 'app.userList.toggleCompactView.label',
-    description: 'Toggle user list view mode',
-  },
-  ChatLabel: {
-    id: 'app.userList.menu.chat.label',
-    description: 'Save the changes and close the settings menu',
-  },
-  ClearStatusLabel: {
-    id: 'app.userList.menu.clearStatus.label',
-    description: 'Clear the emoji status of this user',
-  },
-  MakePresenterLabel: {
-    id: 'app.userList.menu.makePresenter.label',
-    description: 'Set this user to be the presenter in this meeting',
-  },
-  KickUserLabel: {
-    id: 'app.userList.menu.kickUser.label',
-    description: 'Forcefully remove this user from the meeting',
-  },
-  MuteUserAudioLabel: {
-    id: 'app.userList.menu.muteUserAudio.label',
-    description: 'Forcefully mute this user',
-  },
-  UnmuteUserAudioLabel: {
-    id: 'app.userList.menu.unmuteUserAudio.label',
-    description: 'Forcefully unmute this user',
-  },
-  PromoteUserLabel: {
-    id: 'app.userList.menu.promoteUser.label',
-    description: 'Forcefully promote this viewer to a moderator',
-  },
-  DemoteUserLabel: {
-    id: 'app.userList.menu.demoteUser.label',
-    description: 'Forcefully demote this moderator to a viewer',
-  },
 });
 
 class UserMessages extends Component {
+  constructor() {
+    super();
+
+    this.state = {
+      index: -1,
+    };
+
+    this.openChatRefs = [];
+    this.selectedIndex = -1;
+
+    this.focusOpenChatItem = this.focusOpenChatItem.bind(this);
+    this.changeState = this.changeState.bind(this);
+  }
+
   componentDidMount() {
     if (!this.props.compact) {
       this._msgsList.addEventListener(
         'keydown',
-        event => this.props.rovingIndex(
+        event => this.props.roving(
           event,
-          this._msgsList,
-          this._msgItems,
           this.props.openChats.length,
+          this.changeState,
         ),
       );
     }
   }
 
-  render() {
+  componentDidUpdate(prevProps, prevState) {
+    if (this.state.index === -1) {
+      return;
+    }
+
+    if (this.state.index !== prevState.index) {
+      this.focusOpenChatItem(this.state.index);
+    }
+  }
+
+  getOpenChats() {
     const {
       openChats,
       openChat,
-      intl,
       compact,
       isPublicChat,
     } = this.props;
 
+    let index = -1;
+
+    return openChats.map(chat => (
+      <CSSTransition
+        classNames={listTransition}
+        appear
+        enter
+        exit={false}
+        timeout={0}
+        component="div"
+        className={cx(styles.chatsList)}
+        key={chat.id}
+      >
+        <div ref={(node) => { this.openChatRefs[index += 1] = node; }}>
+          <ChatListItem
+            isPublicChat={isPublicChat}
+            compact={compact}
+            openChat={openChat}
+            chat={chat}
+            tabIndex={-1}
+          />
+        </div>
+      </CSSTransition>
+    ));
+  }
+
+  changeState(newIndex) {
+    this.setState({ index: newIndex });
+  }
+
+  focusOpenChatItem(index) {
+    if (!this.openChatRefs[index]) {
+      return;
+    }
+
+    this.openChatRefs[index].firstChild.focus();
+  }
+
+  render() {
+    const {
+      intl,
+      compact,
+    } = this.props;
+
     return (
       <div className={styles.messages}>
         {
@@ -120,28 +142,9 @@ class UserMessages extends Component {
           className={styles.scrollableList}
           ref={(ref) => { this._msgsList = ref; }}
         >
-          <div ref={(ref) => { this._msgItems = ref; }} className={styles.list}>
-            <TransitionGroup>
-              {openChats.map(chat => (
-                <CSSTransition
-                  classNames={listTransition}
-                  appear
-                  enter
-                  exit={false}
-                  timeout={0}
-                  component="div"
-                  className={cx(styles.chatsList)}
-                  key={chat.id}
-                >
-                  <ChatListItem
-                    isPublicChat={isPublicChat}
-                    compact={compact}
-                    openChat={openChat}
-                    chat={chat}
-                    tabIndex={-1}
-                  />
-                </CSSTransition>
-              ))}
+          <div className={styles.list}>
+            <TransitionGroup ref={(ref) => { this._msgItems = ref; }} >
+              { this.getOpenChats() }
             </TransitionGroup>
           </div>
         </div>
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 577565a59e4c9cc1103b920a4f8fedd128590907..cf5c7121a0b99c27f3662ab3fb81373f910aeae9 100644
--- 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
@@ -3,7 +3,7 @@ import { TransitionGroup, CSSTransition } from 'react-transition-group';
 import PropTypes from 'prop-types';
 import { defineMessages } from 'react-intl';
 import cx from 'classnames';
-import styles from '/imports/ui/components/user-list/user-list-content/styles';
+import { styles } from '/imports/ui/components/user-list/user-list-content/styles';
 import UserListItem from './user-list-item/component';
 
 const propTypes = {
@@ -23,7 +23,7 @@ const propTypes = {
   getAvailableActions: PropTypes.func.isRequired,
   normalizeEmojiName: PropTypes.func.isRequired,
   isMeetingLocked: PropTypes.func.isRequired,
-  rovingIndex: PropTypes.func.isRequired,
+  roving: PropTypes.func.isRequired,
 };
 
 const defaultProps = {
@@ -48,18 +48,6 @@ const intlMessages = defineMessages({
     id: 'app.userList.usersTitle',
     description: 'Title for the Header',
   },
-  messagesTitle: {
-    id: 'app.userList.messagesTitle',
-    description: 'Title for the messages list',
-  },
-  participantsTitle: {
-    id: 'app.userList.participantsTitle',
-    description: 'Title for the Users list',
-  },
-  toggleCompactView: {
-    id: 'app.userList.toggleCompactView.label',
-    description: 'Toggle user list view mode',
-  },
   ChatLabel: {
     id: 'app.userList.menu.chat.label',
     description: 'Save the changes and close the settings menu',
@@ -98,88 +86,157 @@ class UserParticipants extends Component {
   constructor() {
     super();
 
+    this.state = {
+      index: -1,
+    };
+
+    this.userRefs = [];
+    this.selectedIndex = -1;
+
     this.getScrollContainerRef = this.getScrollContainerRef.bind(this);
+    this.focusUserItem = this.focusUserItem.bind(this);
+    this.changeState = this.changeState.bind(this);
+    this.getUsers = this.getUsers.bind(this);
   }
 
   componentDidMount() {
     if (!this.props.compact) {
       this.refScrollContainer.addEventListener(
         'keydown',
-        event => this.props.rovingIndex(
+        event => this.props.roving(
           event,
-          this.refScrollContainer,
-          this.refScrollItems,
           this.props.users.length,
+          this.changeState,
         ),
       );
     }
   }
 
+  componentDidUpdate(prevProps, prevState) {
+    if (this.state.index === -1) {
+      return;
+    }
+
+    if (this.state.index !== prevState.index) {
+      this.focusUserItem(this.state.index);
+    }
+  }
+
   getScrollContainerRef() {
     return this.refScrollContainer;
   }
 
-  render() {
+  getUsers() {
     const {
-      users,
-      currentUser,
+      compact,
       isBreakoutRoom,
-      intl,
+      currentUser,
       meeting,
       getAvailableActions,
       normalizeEmojiName,
       isMeetingLocked,
-      compact,
-      setEmojiStatus,
+      users,
+      intl,
+      changeRole,
       assignPresenter,
+      setEmojiStatus,
       kickUser,
       toggleVoice,
-      changeRole,
     } = this.props;
 
     const userActions =
-      {
-        openChat: {
-          label: () => intl.formatMessage(intlMessages.ChatLabel),
-          handler: (router, user) => router.push(`/users/chat/${user.id}`),
-          icon: 'chat',
-        },
-        clearStatus: {
-          label: () => intl.formatMessage(intlMessages.ClearStatusLabel),
-          handler: user => setEmojiStatus(user.id, 'none'),
-          icon: 'clear_status',
-        },
-        setPresenter: {
-          label: () => intl.formatMessage(intlMessages.MakePresenterLabel),
-          handler: user => assignPresenter(user.id),
-          icon: 'presentation',
-        },
-        kick: {
-          label: user => intl.formatMessage(intlMessages.KickUserLabel, { 0: user.name }),
-          handler: user => kickUser(user.id),
-          icon: 'circle_close',
-        },
-        mute: {
-          label: () => intl.formatMessage(intlMessages.MuteUserAudioLabel),
-          handler: user => toggleVoice(user.id),
-          icon: 'audio_off',
-        },
-        unmute: {
-          label: () => intl.formatMessage(intlMessages.UnmuteUserAudioLabel),
-          handler: user => toggleVoice(user.id),
-          icon: 'audio_on',
-        },
-        promote: {
-          label: user => intl.formatMessage(intlMessages.PromoteUserLabel, { 0: user.name }),
-          handler: user => changeRole(user.id, 'MODERATOR'),
-          icon: 'promote',
-        },
-        demote: {
-          label: user => intl.formatMessage(intlMessages.DemoteUserLabel, { 0: user.name }),
-          handler: user => changeRole(user.id, 'VIEWER'),
-          icon: 'user',
-        },
-      };
+    {
+      openChat: {
+        label: () => intl.formatMessage(intlMessages.ChatLabel),
+        handler: (router, user) => router.push(`/users/chat/${user.id}`),
+        icon: 'chat',
+      },
+      clearStatus: {
+        label: () => intl.formatMessage(intlMessages.ClearStatusLabel),
+        handler: user => setEmojiStatus(user.id, 'none'),
+        icon: 'clear_status',
+      },
+      setPresenter: {
+        label: () => intl.formatMessage(intlMessages.MakePresenterLabel),
+        handler: user => assignPresenter(user.id),
+        icon: 'presentation',
+      },
+      kick: {
+        label: user => intl.formatMessage(intlMessages.KickUserLabel, { 0: user.name }),
+        handler: user => kickUser(user.id),
+        icon: 'circle_close',
+      },
+      mute: {
+        label: () => intl.formatMessage(intlMessages.MuteUserAudioLabel),
+        handler: user => toggleVoice(user.id),
+        icon: 'audio_off',
+      },
+      unmute: {
+        label: () => intl.formatMessage(intlMessages.UnmuteUserAudioLabel),
+        handler: user => toggleVoice(user.id),
+        icon: 'audio_on',
+      },
+      promote: {
+        label: user => intl.formatMessage(intlMessages.PromoteUserLabel, { 0: user.name }),
+        handler: user => changeRole(user.id, 'MODERATOR'),
+        icon: 'promote',
+      },
+      demote: {
+        label: user => intl.formatMessage(intlMessages.DemoteUserLabel, { 0: user.name }),
+        handler: user => changeRole(user.id, 'VIEWER'),
+        icon: 'user',
+      },
+    };
+
+    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
+            compact={compact}
+            isBreakoutRoom={isBreakoutRoom}
+            user={user}
+            currentUser={currentUser}
+            userActions={userActions}
+            meeting={meeting}
+            getAvailableActions={getAvailableActions}
+            normalizeEmojiName={normalizeEmojiName}
+            isMeetingLocked={isMeetingLocked}
+            getScrollContainerRef={this.getScrollContainerRef}
+          />
+        </div>
+      </CSSTransition>
+    ));
+  }
+
+  focusUserItem(index) {
+    if (!this.userRefs[index]) {
+      return;
+    }
+
+    this.userRefs[index].firstChild.focus();
+  }
+
+  changeState(newIndex) {
+    this.setState({ index: newIndex });
+  }
+
+  render() {
+    const {
+      users,
+      intl,
+      compact,
+    } = this.props;
 
     return (
       <div className={styles.participants}>
@@ -196,33 +253,9 @@ class UserParticipants extends Component {
           tabIndex={0}
           ref={(ref) => { this.refScrollContainer = ref; }}
         >
-          <div ref={(ref) => { this.refScrollItems = ref; }} className={styles.list}>
-            <TransitionGroup>
-              { users.map(user => (
-                <CSSTransition
-                  classNames={listTransition}
-                  appear
-                  enter
-                  exit
-                  timeout={0}
-                  component="div"
-                  className={cx(styles.participantsList)}
-                  key={user.id}
-                >
-                  <UserListItem
-                    compact={compact}
-                    isBreakoutRoom={isBreakoutRoom}
-                    user={user}
-                    currentUser={currentUser}
-                    userActions={userActions}
-                    meeting={meeting}
-                    getAvailableActions={getAvailableActions}
-                    normalizeEmojiName={normalizeEmojiName}
-                    isMeetingLocked={isMeetingLocked}
-                    getScrollContainerRef={this.getScrollContainerRef}
-                  />
-                </CSSTransition>
-              ))}
+          <div className={styles.list}>
+            <TransitionGroup ref={(ref) => { this.refScrollItems = ref; }}>
+              { this.getUsers() }
             </TransitionGroup>
           </div>
         </div>
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 4fbb487363cec7295a8169cfb5c9a99f8b61c90e..fe70a3f4a39eef557baaacc5bc2a803c9477af27 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
@@ -25,7 +25,7 @@ const propTypes = {
   }).isRequired,
   userActions: PropTypes.shape({}).isRequired,
   router: PropTypes.shape({}).isRequired,
-  isBreakoutRoom: PropTypes.bool.isRequired,
+  isBreakoutRoom: PropTypes.bool,
   getAvailableActions: PropTypes.func.isRequired,
   meeting: PropTypes.shape({}).isRequired,
   isMeetingLocked: PropTypes.func.isRequired,
@@ -34,12 +34,10 @@ const propTypes = {
 };
 
 const defaultProps = {
-  shouldShowActions: false,
   isBreakoutRoom: false,
 };
 
 class UserListItem extends Component {
-
   static createAction(action, ...options) {
     return (
       <UserAction
diff --git a/bigbluebutton-html5/imports/ui/components/user-list/user-list-content/user-participants/user-list-item/user-action/component.jsx b/bigbluebutton-html5/imports/ui/components/user-list/user-list-content/user-participants/user-list-item/user-action/component.jsx
index f1fa3bdcd017a4dd46670ecc5c5447b1aadc6174..8be32c6163f4b0a4ec6a321d625cad280032c460 100644
--- a/bigbluebutton-html5/imports/ui/components/user-list/user-list-content/user-participants/user-list-item/user-action/component.jsx
+++ b/bigbluebutton-html5/imports/ui/components/user-list/user-list-content/user-participants/user-list-item/user-action/component.jsx
@@ -9,21 +9,20 @@ const propTypes = {
   options: PropTypes.arrayOf(PropTypes.shape({})).isRequired,
 };
 
-const UserActions = (props) => {
-  const { key, icon, label, handler, options } = props;
+export default class UserActions extends React.PureComponent {
+  render() {
+    const { key, icon, label, handler, options } = this.props;
 
-  const userAction = (
-    <DropdownListItem
-      key={key}
-      icon={icon}
-      label={label}
-      defaultMessage={label}
-      onClick={() => handler.call(this, ...options)}
-    />
-  );
-
-  return userAction;
-};
+    return (
+      <DropdownListItem
+        key={key}
+        icon={icon}
+        label={label}
+        defaultMessage={label}
+        onClick={() => handler.call(this, ...options)}
+      />
+    );
+  }
+}
 
 UserActions.propTypes = propTypes;
-export default UserActions;
diff --git a/bigbluebutton-html5/imports/ui/components/user-list/user-list-content/user-participants/user-list-item/user-icons/component.jsx b/bigbluebutton-html5/imports/ui/components/user-list/user-list-content/user-participants/user-list-item/user-icons/component.jsx
index 752796dd32f908c36a82c810eb8be66659223c66..fd945bc2b79cce55c2164ec5bc5fb3ab387de788 100644
--- a/bigbluebutton-html5/imports/ui/components/user-list/user-list-content/user-participants/user-list-item/user-icons/component.jsx
+++ b/bigbluebutton-html5/imports/ui/components/user-list/user-list-content/user-participants/user-list-item/user-icons/component.jsx
@@ -1,7 +1,7 @@
 import React from 'react';
 import PropTypes from 'prop-types';
 import Icon from '/imports/ui/components/icon/component';
-import styles from './styles';
+import { styles } from './styles';
 
 const propTypes = {
   user: PropTypes.shape({
diff --git a/bigbluebutton-html5/imports/ui/components/user-list/user-list-content/user-participants/user-list-item/user-list-content/component.jsx b/bigbluebutton-html5/imports/ui/components/user-list/user-list-content/user-participants/user-list-item/user-list-content/component.jsx
index 9af1a7808be463022222e4aa582b5e3106c32d82..5fed67c2ea2d7421ce12b34c995dcdbd1b099ff0 100644
--- a/bigbluebutton-html5/imports/ui/components/user-list/user-list-content/user-participants/user-list-item/user-list-content/component.jsx
+++ b/bigbluebutton-html5/imports/ui/components/user-list/user-list-content/user-participants/user-list-item/user-list-content/component.jsx
@@ -11,7 +11,7 @@ import DropdownContent from '/imports/ui/components/dropdown/content/component';
 import DropdownList from '/imports/ui/components/dropdown/list/component';
 import DropdownListSeparator from '/imports/ui/components/dropdown/list/separator/component';
 import DropdownListTitle from '/imports/ui/components/dropdown/list/title/component';
-import styles from './styles';
+import { styles } from './styles';
 import UserName from './../user-name/component';
 import UserIcons from './../user-icons/component';
 
@@ -57,7 +57,6 @@ const propTypes = {
 
 
 class UserListContent extends Component {
-
   /**
    * Return true if the content fit on the screen, false otherwise.
    *
@@ -85,15 +84,24 @@ class UserListContent extends Component {
     this.getDropdownMenuParent = this.getDropdownMenuParent.bind(this);
   }
 
+  componentWillMount() {
+    this.title = _.uniqueId('dropdown-title-');
+    this.seperator = _.uniqueId('action-separator-');
+  }
+
   componentDidUpdate() {
     this.checkDropdownDirection();
   }
 
   onActionsShow() {
-    const dropdown = findDOMNode(this.dropdown);
+    const dropdown = this.getDropdownMenuParent();
     const scrollContainer = this.props.getScrollContainerRef();
     const dropdownTrigger = dropdown.children[0];
 
+    const list = findDOMNode(this.list);
+    const children = [].slice.call(list.children);
+    children.find(child => child.getAttribute('role') === 'menuitem').focus();
+
     this.setState({
       isActionsOpen: true,
       dropdownVisible: false,
@@ -129,7 +137,7 @@ class UserListContent extends Component {
    */
   checkDropdownDirection() {
     if (this.isDropdownActivedByUser()) {
-      const dropdown = findDOMNode(this.dropdown);
+      const dropdown = this.getDropdownMenuParent();
       const dropdownTrigger = dropdown.children[0];
       const dropdownContent = dropdown.children[1];
 
@@ -140,8 +148,10 @@ class UserListContent extends Component {
       };
 
       const isDropdownVisible =
-        UserListContent.checkIfDropdownIsVisible(dropdownContent.offsetTop,
-          dropdownContent.offsetHeight);
+        UserListContent.checkIfDropdownIsVisible(
+          dropdownContent.offsetTop,
+          dropdownContent.offsetHeight,
+        );
 
       if (!isDropdownVisible) {
         const offsetPageTop =
@@ -162,12 +172,6 @@ class UserListContent extends Component {
   */
   isDropdownActivedByUser() {
     const { isActionsOpen, dropdownVisible } = this.state;
-    const list = findDOMNode(this.list);
-
-    if (isActionsOpen && dropdownVisible) {
-      const children = [].slice.call(list.children);
-      children.find(child => child.getAttribute('role') === 'menuitem').focus();
-    }
 
     return isActionsOpen && !dropdownVisible;
   }
@@ -201,13 +205,15 @@ class UserListContent extends Component {
       ? intl.formatMessage(messages.presenter)
       : '';
 
-    const userAriaLabel = intl.formatMessage(messages.userAriaLabel,
+    const userAriaLabel = intl.formatMessage(
+      messages.userAriaLabel,
       {
         0: user.name,
         1: presenter,
         2: you,
         3: user.emoji.status,
-      });
+      },
+    );
 
     const contents = (
       <div
@@ -280,13 +286,14 @@ class UserListContent extends Component {
           >
             {
               [
-                (<DropdownListTitle
-                  description={intl.formatMessage(messages.menuTitleContext)}
-                  key={_.uniqueId('dropdown-list-title')}
-                >
-                  {user.name}
-                </DropdownListTitle>),
-                (<DropdownListSeparator key={_.uniqueId('action-separator')} />),
+                (
+                  <DropdownListTitle
+                    description={intl.formatMessage(messages.menuTitleContext)}
+                    key={this.title}
+                  >
+                    {user.name}
+                  </DropdownListTitle>),
+                (<DropdownListSeparator key={this.seperator} />),
               ].concat(actions)
             }
           </DropdownList>
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 28f4df490187d90372724678c73ad352eb16f538..4b67eb7770f3f9b9653b7f7449166c0190beaa86 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
@@ -2,7 +2,7 @@ import React from 'react';
 import PropTypes from 'prop-types';
 import { defineMessages } from 'react-intl';
 import Icon from '/imports/ui/components/icon/component';
-import styles from './styles';
+import { styles } from './styles';
 
 
 const messages = defineMessages({
diff --git a/bigbluebutton-html5/imports/ui/components/user-list/user-list-header/component.jsx b/bigbluebutton-html5/imports/ui/components/user-list/user-list-header/component.jsx
deleted file mode 100644
index c78e204fa1d831fa1c90b537749e4d40fe89a113..0000000000000000000000000000000000000000
--- a/bigbluebutton-html5/imports/ui/components/user-list/user-list-header/component.jsx
+++ /dev/null
@@ -1,43 +0,0 @@
-import React from 'react';
-import { defineMessages } from 'react-intl';
-import PropTypes from 'prop-types';
-import styles from './styles';
-
-const intlMessages = defineMessages({
-  participantsTitle: {
-    id: 'app.userList.participantsTitle',
-    description: 'Title for the Users list',
-  },
-});
-
-const propTypes = {
-  compact: PropTypes.bool,
-  intl: PropTypes.shape({ formatMessage: PropTypes.func.isRequired }).isRequired,
-};
-
-const defaultProps = {
-  compact: false,
-};
-
-const UserListHeader = props => (
-  <div className={styles.header}>
-    {
-      !props.compact ?
-        <div className={styles.headerTitle} role="banner">
-          {props.intl.formatMessage(intlMessages.participantsTitle)}
-        </div> : null
-    }
-    {/* <Button
-        label={intl.formatMessage(intlMessages.toggleCompactView)}
-        hideLabel
-        icon={!this.state.compact ? 'left_arrow' : 'right_arrow'}
-        className={styles.btnToggle}
-        onClick={this.handleToggleCompactView}
-      /> */}
-  </div>
-);
-
-UserListHeader.propTypes = propTypes;
-UserListHeader.defaultProps = defaultProps;
-
-export default UserListHeader;
diff --git a/bigbluebutton-html5/imports/ui/components/user-list/user-list-header/styles.scss b/bigbluebutton-html5/imports/ui/components/user-list/user-list-header/styles.scss
deleted file mode 100644
index 8a9410dffb3d491f88cb1ef49814b3c64a8df9bb..0000000000000000000000000000000000000000
--- a/bigbluebutton-html5/imports/ui/components/user-list/user-list-header/styles.scss
+++ /dev/null
@@ -1,18 +0,0 @@
-@import "/imports/ui/components/user-list/styles.scss";
-
-.header {
-  @extend %flex-column;
-  justify-content: left;
-  flex-grow: 0;
-  display: flex;
-  flex-direction: row;
-  padding: 0 $md-padding-x;
-  margin: $md-padding-x 0;
-}
-
-.headerTitle {
-  flex: 0;
-  font-size: 1rem;
-  font-weight: 600;
-  color: $color-heading;
-}
diff --git a/bigbluebutton-html5/imports/ui/components/video-dock/component.jsx b/bigbluebutton-html5/imports/ui/components/video-dock/component.jsx
index fcd9e12dbd8bff5bd7965e6b0cc626e36b7bd76c..a6ed22b5cdc0b34f65fb6b7d62ae20deaca6ea63 100644
--- a/bigbluebutton-html5/imports/ui/components/video-dock/component.jsx
+++ b/bigbluebutton-html5/imports/ui/components/video-dock/component.jsx
@@ -1,11 +1,504 @@
-import React from 'react';
+import React, { Component } from 'react';
 import ScreenshareContainer from '/imports/ui/components/screenshare/container';
-import styles from './styles';
+import { styles } from './styles';
+import { log } from '/imports/ui/services/api';
 
-const VideoDock = () => (
-  <div className={styles.videoDock}>
-    <ScreenshareContainer />
-  </div>
-);
 
-export default VideoDock;
+class VideoElement extends Component {
+  constructor(props) {
+    super(props);
+  }
+
+  render() {
+    return <video id={`video-elem-${this.props.videoId}`} width={320} height={240} autoPlay={true} playsInline={true} />;
+  }
+
+  componentDidMount() {
+    this.props.onMount(this.props.videoId, false);
+  }
+}
+
+export default class VideoDock extends Component {
+  constructor(props) {
+    super(props);
+
+    // Set a valid bbb-webrtc-sfu application server socket in the settings
+    this.ws = new ReconnectingWebSocket(Meteor.settings.public.kurento.wsUrl);
+    this.wsQueue = [];
+    this.webRtcPeers = {};
+    this.reconnectWebcam = false;
+    this.reconnectList = false;
+    this.sharedCameraTimeout = null;
+    this.subscribedCamerasTimeouts = [];
+
+    this.state = {
+      videos: {},
+      sharedWebcam : false,
+    };
+
+    this.sendUserShareWebcam = props.sendUserShareWebcam.bind(this);
+    this.sendUserUnshareWebcam = props.sendUserUnshareWebcam.bind(this);
+
+    this.unshareWebcam = this.unshareWebcam.bind(this);
+    this.shareWebcam = this.shareWebcam.bind(this);
+
+    this.onWsOpen = this.onWsOpen.bind(this);
+    this.onWsClose = this.onWsClose.bind(this);
+    this.onWsMessage = this.onWsMessage.bind(this);
+  }
+
+  setupReconnectVideos() {
+    for (id in this.webRtcPeers) {
+      this.disconnected(id);
+      this.stop(id);
+    }
+  }
+
+  reconnectVideos() {
+    for (i in this.reconnectList) {
+      const id = this.reconnectList[i];
+
+      // TODO: base this on BBB API users instead of using memory
+      if (id != this.myId) {
+        setTimeout(() => {
+          log('debug', ` [camera] Trying to reconnect camera ${id}`);
+          this.start(id, false);
+        }, 5000);
+      }
+    }
+
+    if (this.reconnectWebcam) {
+      log('debug', ` [camera] Trying to re-share ${this.myId} after reconnect.`);
+      this.start(this.myId, true);
+    }
+
+    this.reconnectWebcam = false;
+    this.reconnectList = [];
+  }
+
+  componentDidMount() {
+    const ws = this.ws;
+    const { users } = this.props;
+    const id = users[0].userId;
+
+    for (let i = 0; i < users.length; i++) {
+      if (users[i].has_stream && users[i].userId !== id) {
+        this.start(users[i].userId, false);
+      }
+    }
+
+    document.addEventListener('joinVideo', this.shareWebcam.bind(this));// TODO find a better way to do this
+    document.addEventListener('exitVideo', this.unshareWebcam.bind(this));
+
+    window.addEventListener('resize', this.adjustVideos);
+
+    ws.addEventListener('message', this.onWsMessage);
+  }
+
+  componentWillMount () {
+    this.ws.addEventListener('open', this.onWsOpen);
+    this.ws.addEventListener('close', this.onWsClose);
+  }
+
+  componentWillUnmount () {
+    document.removeEventListener('joinVideo', this.shareWebcam);
+    document.removeEventListener('exitVideo', this.shareWebcam);
+    window.removeEventListener('resize', this.adjustVideos);
+
+    this.ws.removeEventListener('message', this.onWsMessage);
+    this.ws.removeEventListener('open', this.onWsOpen);
+    this.ws.removeEventListener('close', this.onWsClose);
+    // Close websocket connection to prevent multiple reconnects from happening
+    this.ws.close();
+  }
+
+  adjustVideos () {
+    window.adjustVideos('webcamArea', true);
+  }
+
+  onWsOpen () {
+    log('debug', '------ Websocket connection opened.');
+
+    // -- Resend queued messages that happened when socket was not connected
+    while (this.wsQueue.length > 0) {
+      this.sendMessage(this.wsQueue.pop());
+    }
+
+    this.reconnectVideos();
+  }
+
+  onWsClose (error) {
+    log('debug', '------ Websocket connection closed.');
+
+    this.setupReconnectVideos();
+  }
+
+  onWsMessage (msg) {
+    const parsedMessage = JSON.parse(msg.data);
+
+    console.log('Received message new ws message: ');
+    console.log(parsedMessage);
+
+    switch (parsedMessage.id) {
+
+      case 'startResponse':
+        this.startResponse(parsedMessage);
+        break;
+
+      case 'error':
+        this.handleError(parsedMessage);
+        break;
+
+      case 'playStart':
+        this.handlePlayStart(parsedMessage);
+        break;
+
+      case 'playStop':
+        this.handlePlayStop(parsedMessage);
+
+        break;
+
+      case 'iceCandidate':
+
+        const webRtcPeer = this.webRtcPeers[parsedMessage.cameraId];
+
+        if (webRtcPeer !== null) {
+          if (webRtcPeer.didSDPAnswered) {
+            webRtcPeer.addIceCandidate(parsedMessage.candidate, (err) => {
+              if (err) {
+                return log('error', `Error adding candidate: ${err}`);
+              }
+            });
+          } else {
+            webRtcPeer.iceQueue.push(parsedMessage.candidate);
+          }
+        } else {
+          log('error', ' [ICE] Message arrived before webRtcPeer?');
+        }
+        break;
+    }
+  };
+
+  start(id, shareWebcam) {
+    const that = this;
+
+    console.log(`Starting video call for video: ${id} with ${shareWebcam}`);
+
+    if (shareWebcam) {
+      this.setState({sharedWebcam: true});
+      this.initWebRTC(id, true);
+    } else {
+      // initWebRTC with shareWebcam false will be called after react mounts the element
+      this.createVideoTag(id);
+    }
+  }
+
+  initWebRTC(id, shareWebcam) {
+    let that = this;
+
+    const onIceCandidate = function (candidate) {
+      const message = {
+        type: 'video',
+        role: shareWebcam ? 'share' : 'viewer',
+        id: 'onIceCandidate',
+        candidate,
+        cameraId: id,
+      };
+      that.sendMessage(message);
+    };
+
+    let videoConstraints = {};
+    if (!!navigator.userAgent.match(/Version\/[\d\.]+.*Safari/)) { // Custom constraints for Safari
+      videoConstraints = {
+        width: {min:320, max:640},
+        height: {min:240, max:480}
+      }
+    } else {
+      videoConstraints = {
+        width: {min: 320, ideal: 320},
+        height: {min: 240, ideal:240},
+        frameRate: {min: 5, ideal: 10}
+      };
+    }
+
+    let options = {
+      mediaConstraints: {
+        audio: false,
+        video: videoConstraints
+      },
+      onicecandidate: onIceCandidate,
+    };
+
+    let peerObj;
+    if (shareWebcam) {
+      options.localVideo = this.refs.videoInput;
+      peerObj = kurentoUtils.WebRtcPeer.WebRtcPeerSendonly;
+    } else {
+      peerObj = kurentoUtils.WebRtcPeer.WebRtcPeerRecvonly;
+      options.remoteVideo = document.getElementById(`video-elem-${id}`);
+    }
+
+    let webRtcPeer = new peerObj(options, function (error) {
+      if (error) {
+        log('error', ' WebRTC peerObj create error');
+
+        that.destroyWebRTCPeer(id);
+        that.destroyVideoTag(id);
+
+        return log('error', error);
+      }
+
+      this.didSDPAnswered = false;
+      this.iceQueue = [];
+
+      that.webRtcPeers[id] = webRtcPeer;
+      if (shareWebcam) {
+        that.sharedWebcam = webRtcPeer;
+        that.myId = id;
+      }
+
+      this.generateOffer((error, offerSdp) => {
+        if (error) {
+          log('error', ' WebRtc generate offer error');
+
+          that.destroyWebRTCPeer(id);
+          that.destroyVideoTag(id);
+
+          return log('error', error);
+        }
+
+        console.log(`Invoking SDP offer callback function ${location.host}`);
+        const message = {
+          type: 'video',
+          role: shareWebcam ? 'share' : 'viewer',
+          id: 'start',
+          sdpOffer: offerSdp,
+          cameraId: id,
+        };
+        that.sendMessage(message);
+      });
+      while (this.iceQueue.length) {
+        let candidate = this.iceQueue.shift();
+        this.addIceCandidate(candidate, (err) => {
+          if (err) {
+            return console.error(`Error adding candidate: ${err}`);
+          }
+        });
+      }
+      this.didSDPAnswered = true;
+    });
+  }
+
+  disconnected(id) {
+    if (this.sharedWebcam) {
+      log('debug', ' [camera] Webcam disconnected, will try re-share webcam later.');
+      this.reconnectWebcam = true;
+    } else {
+      this.reconnectList.push(id);
+
+      log('debug', ` [camera] ${id} disconnected, will try re-subscribe later.`);
+    }
+  }
+
+  stop(id) {
+    const { users } = this.props;
+    this.sendMessage({
+      type: 'video',
+      role: id == users[0].userId ? 'share' : 'viewer',
+      id: 'stop',
+      cameraId: id,
+    });
+
+    this.destroyWebRTCPeer(id);
+    this.destroyVideoTag(id);
+  }
+
+  createVideoTag(id) {
+    let videos = this.state.videos;
+
+    videos[id] = true;
+    this.setState({videos: videos})
+  }
+
+  destroyVideoTag(id) {
+    let videos = this.state.videos;
+
+    delete videos[id];
+    this.setState({videos: videos});
+
+    if (id == this.myId) {
+      this.setState({sharedWebcam: false});
+    }
+  }
+
+  destroyWebRTCPeer(id) {
+    const webRtcPeer = this.webRtcPeers[id];
+
+    if (webRtcPeer) {
+      log('info', 'Stopping WebRTC peer');
+
+      if (id == this.myId && this.sharedWebcam) {
+        this.sharedWebcam.dispose();
+        this.sharedWebcam = null;
+      }
+
+      webRtcPeer.dispose();
+      delete this.webRtcPeers[id];
+    } else {
+      log('info', 'No WebRTC peer to stop (not an error)');
+    }
+  }
+
+  shareWebcam() {
+    const { users } = this.props;
+    const id = users[0].userId;
+
+    if (this.connectedToMediaServer()) {
+      this.start(id, true);
+    } else {
+      log("error", "Not connected to media server BRA");
+    }
+  }
+
+  unshareWebcam() {
+    log('info', 'Unsharing webcam');
+    const { users } = this.props;
+    const id = users[0].userId;
+    this.sendUserUnshareWebcam(id);
+  }
+
+  startResponse(message) {
+    const id = message.cameraId;
+    const webRtcPeer = this.webRtcPeers[id];
+
+    if (message.sdpAnswer == null) {
+      return log('debug', 'Null sdp answer. Camera unplugged?');
+    }
+
+    if (webRtcPeer == null) {
+      return log('debug', 'Null webrtc peer ????');
+    }
+
+    log('info', 'SDP answer received from server. Processing ...');
+
+    webRtcPeer.processAnswer(message.sdpAnswer, (error) => {
+      if (error) {
+        return log('error', error);
+      }
+    });
+
+    this.sendUserShareWebcam(id);
+  }
+
+  sendMessage(message) {
+    const ws = this.ws;
+
+    if (this.connectedToMediaServer()) {
+      const jsonMessage = JSON.stringify(message);
+      console.log(`Sending message: ${jsonMessage}`);
+      ws.send(jsonMessage, (error) => {
+        if (error) {
+          console.error(`client: Websocket error "${error}" on message "${jsonMessage.id}"`);
+        }
+      });
+    } else {
+      // No need to queue video stop messages
+      if (message.id != 'stop') {
+        this.wsQueue.push(message);
+      }
+    }
+  }
+
+  connectedToMediaServer() {
+    return this.ws.readyState === WebSocket.OPEN;
+  }
+
+  connectionStatus() {
+    return this.ws.readyState;
+  }
+
+  handlePlayStop(message) {
+    log('info', 'Handle play stop <--------------------');
+    log('error', message);
+
+    const { users } = this.props;
+
+    if (message.cameraId == users[0].userId) {
+      this.unshareWebcam();
+    } else {
+      this.stop(message.cameraId);
+    }
+  }
+
+  handlePlayStart(message) {
+    log('info', 'Handle play start <===================');
+  }
+
+  handleError(message) {
+    console.error(' Handle error --------------------->');
+    log('debug', message.message);
+  }
+
+  componentDidUpdate() {
+    this.adjustVideos();
+  }
+
+  render() {
+    let cssClass;
+    if (this.state.sharedWebcam) {
+      cssClass = styles.sharedWebcamVideoLocal;
+    }
+    else {
+      cssClass = styles.sharedWebcamVideo;
+    }
+
+    return (
+
+      <div className={styles.videoDock}>
+        <div id="webcamArea">
+          {Object.keys(this.state.videos).map((id) => {
+            return (<VideoElement videoId={id} key={id} onMount={this.initWebRTC.bind(this)} />);
+          })}
+          <video autoPlay={true} playsInline={true} muted={true} id="shareWebcamVideo" className={cssClass} ref="videoInput" />
+        </div>
+      </div>
+    );
+  }
+
+  shouldComponentUpdate(nextProps, nextState) {
+    const { users } = this.props;
+    const nextUsers = nextProps.users;
+    const id = users[0].userId;
+
+    if (users) {
+      let suc = false;
+
+      for (let i = 0; i < users.length; i++) {
+        if (users && users[i] &&
+              nextUsers && nextUsers[i]) {
+          if (users[i].has_stream !== nextUsers[i].has_stream) {
+            console.log(`User ${nextUsers[i].has_stream ? '' : 'un'}shared webcam ${users[i].userId}`);
+
+            if (nextUsers[i].has_stream) {
+              if (id !== users[i].userId) {
+                this.start(users[i].userId, false);
+              }
+            } else {
+              this.stop(users[i].userId);
+            }
+
+            if (!nextUsers[i].has_stream) {
+              this.destroyVideoTag(users[i].userId);
+            }
+
+            suc = suc || true;
+          }
+        }
+      }
+
+      return true;
+    }
+
+    return false;
+  }
+}
diff --git a/bigbluebutton-html5/imports/ui/components/video-dock/container.jsx b/bigbluebutton-html5/imports/ui/components/video-dock/container.jsx
index ad18e501e980831cb554c5070b75b55c51a12f16..d4eb1e71401dbe23e0e8168771b5d9c32b0e81b5 100644
--- a/bigbluebutton-html5/imports/ui/components/video-dock/container.jsx
+++ b/bigbluebutton-html5/imports/ui/components/video-dock/container.jsx
@@ -1,15 +1,16 @@
 import React from 'react';
-import { createContainer } from 'meteor/react-meteor-data';
-
+import { withTracker } from 'meteor/react-meteor-data';
 import VideoDock from './component';
+import VideoService from './service';
 
-const VideoDockContainer = props => (
-  <VideoDock>
-    {props.children}
+const VideoDockContainer = ({ children, ...props }) => (
+  <VideoDock {...props}>
+    {children}
   </VideoDock>
 );
 
-export default createContainer(() => {
-  const data = {};
-  return data;
-}, VideoDockContainer);
+export default withTracker(() => ({
+  sendUserShareWebcam: VideoService.sendUserShareWebcam,
+  sendUserUnshareWebcam: VideoService.sendUserUnshareWebcam,
+  users: VideoService.getAllUsers(),
+}))(VideoDockContainer);
diff --git a/bigbluebutton-html5/imports/ui/components/video-dock/service.js b/bigbluebutton-html5/imports/ui/components/video-dock/service.js
new file mode 100644
index 0000000000000000000000000000000000000000..284b3a15b29ba39b1230674e507c7d522f663d3a
--- /dev/null
+++ b/bigbluebutton-html5/imports/ui/components/video-dock/service.js
@@ -0,0 +1,26 @@
+import { makeCall } from '/imports/ui/services/api';
+import Users from '/imports/api/users';
+
+const joinVideo = () => {
+  const joinVideoEvent = new Event('joinVideo');
+  document.dispatchEvent(joinVideoEvent);
+};
+
+const exitVideo = () => {
+  const exitVideoEvent = new Event('exitVideo');
+  document.dispatchEvent(exitVideoEvent);
+};
+
+const sendUserShareWebcam = (stream) => {
+  makeCall('userShareWebcam', stream);
+};
+
+const sendUserUnshareWebcam = (stream) => {
+  makeCall('userUnshareWebcam', stream);
+};
+
+const getAllUsers = () => Users.find().fetch();
+
+export default {
+  sendUserShareWebcam, sendUserUnshareWebcam, joinVideo, exitVideo, getAllUsers,
+};
diff --git a/bigbluebutton-html5/imports/ui/components/video-dock/styles.scss b/bigbluebutton-html5/imports/ui/components/video-dock/styles.scss
index a37e967d4ce74c1e91f784b5d0fac052d3fd6009..2dce62f218a11fd0b3ffbc6fc7e9c561bb4a6c37 100644
--- a/bigbluebutton-html5/imports/ui/components/video-dock/styles.scss
+++ b/bigbluebutton-html5/imports/ui/components/video-dock/styles.scss
@@ -7,9 +7,16 @@
   bottom: 0;
   left: 0;
 
-  background-image: url(https://avatars.slack-edge.com/2016-01-04/17715243383_99a961f4cb2bf2cde5c4_512.jpg);
   background-size: cover;
   background-position: center;
   box-shadow: 0 0 5px rgba(0, 0, 0, .5);
   border-radius: .2rem;
 }
+
+.sharedWebcamVideo {
+  display: none;
+}
+
+.sharedWebcamVideoLocal {
+  display: normal;
+}
diff --git a/bigbluebutton-html5/imports/ui/components/video-dock/video-menu/component.jsx b/bigbluebutton-html5/imports/ui/components/video-dock/video-menu/component.jsx
new file mode 100644
index 0000000000000000000000000000000000000000..6d729bdbd319406377e4d5a4f90183d94f6c7bc7
--- /dev/null
+++ b/bigbluebutton-html5/imports/ui/components/video-dock/video-menu/component.jsx
@@ -0,0 +1,61 @@
+import React from 'react';
+import PropTypes from 'prop-types';
+import Button from '/imports/ui/components/button/component';
+import { defineMessages, injectIntl, intlShape } from 'react-intl';
+
+const intlMessages = defineMessages({
+  joinVideo: {
+    id: 'app.video.joinVideo',
+    description: 'Join video button label',
+  },
+  leaveVideo: {
+    id: 'app.video.leaveVideo',
+    description: 'Leave video button label',
+  },
+});
+
+const JoinVideoOptions = (props) => {
+  const {
+    intl,
+    isSharingVideo,
+    handleJoinVideo,
+    handleCloseVideo,
+  } = props;
+
+  if (isSharingVideo) {
+    return (
+      <Button
+        onClick={handleCloseVideo}
+        label={intl.formatMessage(intlMessages.leaveVideo)}
+        hideLabel
+        aria-label={intl.formatMessage(intlMessages.leaveVideo)}
+        color="danger"
+        icon="video"
+        size="lg"
+        circle
+      />
+    );
+  }
+
+  return (
+    <Button
+      onClick={handleJoinVideo}
+      label={intl.formatMessage(intlMessages.joinVideo)}
+      hideLabel
+      aria-label={intl.formatMessage(intlMessages.joinVideo)}
+      color="primary"
+      icon="video_off"
+      size="lg"
+      circle
+    />
+  );
+};
+
+JoinVideoOptions.propTypes = {
+  intl: intlShape.isRequired,
+  isSharingVideo: PropTypes.bool.isRequired,
+  handleJoinVideo: PropTypes.func.isRequired,
+  handleCloseVideo: PropTypes.func.isRequired,
+};
+
+export default injectIntl(JoinVideoOptions);
diff --git a/bigbluebutton-html5/imports/ui/components/video-dock/video-menu/container.jsx b/bigbluebutton-html5/imports/ui/components/video-dock/video-menu/container.jsx
new file mode 100644
index 0000000000000000000000000000000000000000..e90d3e1799cc47e60604151ca304fb7838e5d147
--- /dev/null
+++ b/bigbluebutton-html5/imports/ui/components/video-dock/video-menu/container.jsx
@@ -0,0 +1,15 @@
+import React from 'react';
+import { withTracker } from 'meteor/react-meteor-data';
+import JoinVideoOptions from './component';
+import VideoMenuService from './service';
+
+const JoinVideoOptionsContainer = props => (<JoinVideoOptions {...props} />);
+
+export default withTracker((params) => {
+  const isSharingVideo = VideoMenuService.isSharingVideo();
+  return {
+    isSharingVideo,
+    handleJoinVideo: params.handleJoinVideo,
+    handleCloseVideo: params.handleCloseVideo,
+  };
+})(JoinVideoOptionsContainer);
diff --git a/bigbluebutton-html5/imports/ui/components/video-dock/video-menu/service.js b/bigbluebutton-html5/imports/ui/components/video-dock/video-menu/service.js
new file mode 100644
index 0000000000000000000000000000000000000000..92a58cd21afc50dac0196a3d19d325fea668e125
--- /dev/null
+++ b/bigbluebutton-html5/imports/ui/components/video-dock/video-menu/service.js
@@ -0,0 +1,12 @@
+import Users from '/imports/api/users';
+import Auth from '/imports/ui/services/auth/index';
+
+const isSharingVideo = () => {
+  const userId = Auth.userID;
+  const user = Users.findOne({ userId: userId });
+  return user.has_stream ? true : false;
+};
+
+export default {
+  isSharingVideo
+};
diff --git a/bigbluebutton-html5/imports/ui/components/whiteboard/annotation-factory/reactive-annotation/container.jsx b/bigbluebutton-html5/imports/ui/components/whiteboard/annotation-factory/reactive-annotation/container.jsx
index 9124ab46ec73e3126a0da66fa889927339c69c9a..51c5b5be59502c085628ef57326255b93ed655da 100644
--- a/bigbluebutton-html5/imports/ui/components/whiteboard/annotation-factory/reactive-annotation/container.jsx
+++ b/bigbluebutton-html5/imports/ui/components/whiteboard/annotation-factory/reactive-annotation/container.jsx
@@ -1,6 +1,6 @@
 import React from 'react';
 import PropTypes from 'prop-types';
-import { createContainer } from 'meteor/react-meteor-data';
+import { withTracker } from 'meteor/react-meteor-data';
 import ReactiveAnnotationService from './service';
 import ReactiveAnnotation from './component';
 
@@ -19,14 +19,14 @@ const ReactiveAnnotationContainer = (props) => {
   return null;
 };
 
-export default createContainer((params) => {
+export default withTracker((params) => {
   const { shapeId } = params;
   const annotation = ReactiveAnnotationService.getAnnotationById(shapeId);
 
   return {
     annotation,
   };
-}, ReactiveAnnotationContainer);
+})(ReactiveAnnotationContainer);
 
 ReactiveAnnotationContainer.propTypes = {
   annotation: PropTypes.objectOf(PropTypes.oneOfType([
diff --git a/bigbluebutton-html5/imports/ui/components/whiteboard/annotation-group/container.jsx b/bigbluebutton-html5/imports/ui/components/whiteboard/annotation-group/container.jsx
index 53f3fecc97d31ccd05f1e58d19f7225bfd7d367c..1c697b061f31d0b0635ef602ea9c1fde3c6d2ae6 100644
--- a/bigbluebutton-html5/imports/ui/components/whiteboard/annotation-group/container.jsx
+++ b/bigbluebutton-html5/imports/ui/components/whiteboard/annotation-group/container.jsx
@@ -1,6 +1,6 @@
 import React from 'react';
 import PropTypes from 'prop-types';
-import { createContainer } from 'meteor/react-meteor-data';
+import { withTracker } from 'meteor/react-meteor-data';
 import AnnotationGroupService from './service';
 import AnnotationGroup from './component';
 
@@ -12,14 +12,14 @@ const AnnotationGroupContainer = props => (
   />
 );
 
-export default createContainer((params) => {
+export default withTracker((params) => {
   const { whiteboardId } = params;
   const annotationsInfo = AnnotationGroupService.getCurrentAnnotationsInfo(whiteboardId);
 
   return {
     annotationsInfo,
   };
-}, AnnotationGroupContainer);
+})(AnnotationGroupContainer);
 
 AnnotationGroupContainer.propTypes = {
   // initial width and height of the slide; required to calculate the annotations' coordinates
diff --git a/bigbluebutton-html5/imports/ui/components/whiteboard/annotations/text/container.jsx b/bigbluebutton-html5/imports/ui/components/whiteboard/annotations/text/container.jsx
index b58769135eca5a864281be91a64b7fb4885aa9de..e910f81acb45a8d82949b3be8972d9c71f8d0864 100644
--- a/bigbluebutton-html5/imports/ui/components/whiteboard/annotations/text/container.jsx
+++ b/bigbluebutton-html5/imports/ui/components/whiteboard/annotations/text/container.jsx
@@ -1,5 +1,5 @@
 import React from 'react';
-import { createContainer } from 'meteor/react-meteor-data';
+import { withTracker } from 'meteor/react-meteor-data';
 import TextShapeService from './service';
 import TextDrawComponent from './component';
 
@@ -7,7 +7,7 @@ const TextDrawContainer = props => (
   <TextDrawComponent {...props} />
 );
 
-export default createContainer((params) => {
+export default withTracker((params) => {
   const isPresenter = TextShapeService.isPresenter();
   const isMultiUser = TextShapeService.getMultiUserStatus();
   const activeTextShapeId = TextShapeService.activeTextShapeId();
@@ -21,4 +21,4 @@ export default createContainer((params) => {
     setTextShapeValue: TextShapeService.setTextShapeValue,
     resetTextShapeActiveId: TextShapeService.resetTextShapeActiveId,
   };
-}, TextDrawContainer);
+})(TextDrawContainer);
diff --git a/bigbluebutton-html5/imports/ui/components/whiteboard/whiteboard-overlay/container.jsx b/bigbluebutton-html5/imports/ui/components/whiteboard/whiteboard-overlay/container.jsx
index 6db90e596807ee7bf6529ab8e974de4a2ffe56b8..d9580c9a8143fb4578c51d748c540478fdbbc041 100644
--- a/bigbluebutton-html5/imports/ui/components/whiteboard/whiteboard-overlay/container.jsx
+++ b/bigbluebutton-html5/imports/ui/components/whiteboard/whiteboard-overlay/container.jsx
@@ -1,5 +1,5 @@
 import React from 'react';
-import { createContainer } from 'meteor/react-meteor-data';
+import { withTracker } from 'meteor/react-meteor-data';
 import PropTypes from 'prop-types';
 import WhiteboardOverlayService from './service';
 import WhiteboardOverlay from './component';
@@ -13,13 +13,13 @@ const WhiteboardOverlayContainer = (props) => {
   return null;
 };
 
-export default createContainer(() => ({
+export default withTracker(() => ({
   sendAnnotation: WhiteboardOverlayService.sendAnnotation,
   setTextShapeActiveId: WhiteboardOverlayService.setTextShapeActiveId,
   resetTextShapeSession: WhiteboardOverlayService.resetTextShapeSession,
   drawSettings: WhiteboardOverlayService.getWhiteboardToolbarValues(),
   userId: WhiteboardOverlayService.getCurrentUserId(),
-}), WhiteboardOverlayContainer);
+}))(WhiteboardOverlayContainer);
 
 
 WhiteboardOverlayContainer.propTypes = {
diff --git a/bigbluebutton-html5/imports/ui/components/whiteboard/whiteboard-overlay/pan-zoom-draw-listener/component.jsx b/bigbluebutton-html5/imports/ui/components/whiteboard/whiteboard-overlay/pan-zoom-draw-listener/component.jsx
index 2818cd19e58a47bb9d49404119588174c3ce731a..13ad6c3c6a33f754106d9b2d1887c0276da0acf1 100644
--- a/bigbluebutton-html5/imports/ui/components/whiteboard/whiteboard-overlay/pan-zoom-draw-listener/component.jsx
+++ b/bigbluebutton-html5/imports/ui/components/whiteboard/whiteboard-overlay/pan-zoom-draw-listener/component.jsx
@@ -1,6 +1,6 @@
 import React from 'react';
 // import PropTypes from 'prop-types';
-// import styles from '../styles.scss';
+// import { styles } from '../styles.scss';
 
 export default class PanZoomDrawListener extends React.Component {
   constructor(props) {
diff --git a/bigbluebutton-html5/imports/ui/components/whiteboard/whiteboard-overlay/pencil-draw-listener/component.jsx b/bigbluebutton-html5/imports/ui/components/whiteboard/whiteboard-overlay/pencil-draw-listener/component.jsx
index 50937ae453251923082c483eba3a11c571590894..0c55f14f8c67dafbcd1481ec8ab3b906c1f505cb 100644
--- a/bigbluebutton-html5/imports/ui/components/whiteboard/whiteboard-overlay/pencil-draw-listener/component.jsx
+++ b/bigbluebutton-html5/imports/ui/components/whiteboard/whiteboard-overlay/pencil-draw-listener/component.jsx
@@ -1,6 +1,6 @@
 import React, { Component } from 'react';
 import PropTypes from 'prop-types';
-import styles from '../styles.scss';
+import { styles } from '../styles.scss';
 
 const ANNOTATION_CONFIG = Meteor.settings.public.whiteboard.annotations;
 const MESSAGE_FREQUENCY = ANNOTATION_CONFIG.message_frequency;
diff --git a/bigbluebutton-html5/imports/ui/components/whiteboard/whiteboard-overlay/shape-draw-listener/component.jsx b/bigbluebutton-html5/imports/ui/components/whiteboard/whiteboard-overlay/shape-draw-listener/component.jsx
index 6907d264b9a7c45ea3a4e4d152961b6fd342befa..cc60e45bb5fe04d3be22ff30f62fb871348eb01b 100644
--- a/bigbluebutton-html5/imports/ui/components/whiteboard/whiteboard-overlay/shape-draw-listener/component.jsx
+++ b/bigbluebutton-html5/imports/ui/components/whiteboard/whiteboard-overlay/shape-draw-listener/component.jsx
@@ -1,6 +1,6 @@
 import React, { Component } from 'react';
 import PropTypes from 'prop-types';
-import styles from '../styles.scss';
+import { styles } from '../styles.scss';
 
 const ANNOTATION_CONFIG = Meteor.settings.public.whiteboard.annotations;
 const MESSAGE_FREQUENCY = ANNOTATION_CONFIG.message_frequency;
diff --git a/bigbluebutton-html5/imports/ui/components/whiteboard/whiteboard-overlay/text-draw-listener/component.jsx b/bigbluebutton-html5/imports/ui/components/whiteboard/whiteboard-overlay/text-draw-listener/component.jsx
index 1e12e94cde29485aea82c7ae80967b4d81ce4234..8737e2464f17726c9a50b17ac6079461091245b1 100644
--- a/bigbluebutton-html5/imports/ui/components/whiteboard/whiteboard-overlay/text-draw-listener/component.jsx
+++ b/bigbluebutton-html5/imports/ui/components/whiteboard/whiteboard-overlay/text-draw-listener/component.jsx
@@ -1,6 +1,6 @@
 import React, { Component } from 'react';
 import PropTypes from 'prop-types';
-import styles from '../styles.scss';
+import { styles } from '../styles.scss';
 
 const ANNOTATION_CONFIG = Meteor.settings.public.whiteboard.annotations;
 const DRAW_START = ANNOTATION_CONFIG.status.start;
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 d9fb758a6831c8503a912a2be7824d930ef6b673..8347fd10a967084c46296c31ae2680f8cb337aa6 100644
--- a/bigbluebutton-html5/imports/ui/components/whiteboard/whiteboard-toolbar/component.jsx
+++ b/bigbluebutton-html5/imports/ui/components/whiteboard/whiteboard-toolbar/component.jsx
@@ -3,7 +3,7 @@ import PropTypes from 'prop-types';
 import cx from 'classnames';
 import { HEXToINTColor, INTToHEXColor } from '/imports/utils/hexInt';
 import injectWbResizeEvent from '/imports/ui/components/presentation/resize-wrapper/component';
-import styles from './styles.scss';
+import { styles } from './styles.scss';
 import ToolbarMenuItem from './toolbar-menu-item/component';
 import ToolbarSubmenu from './toolbar-submenu/component';
 
@@ -25,8 +25,8 @@ class WhiteboardToolbar extends Component {
 
       // variables to keep current selected draw settings
       annotationSelected: {
-        icon: 'hand',
-        value: 'hand',
+        icon: 'pen_tool',
+        value: 'pencil',
       },
       thicknessSelected: { value: 4 },
       colorSelected: { value: '#000000' },
@@ -326,6 +326,7 @@ class WhiteboardToolbar extends Component {
   renderThicknessItem() {
     return (
       <ToolbarMenuItem
+        disabled={this.state.annotationSelected.value === 'hand'}
         label={'Thickness List'}
         onItemClick={this.displaySubMenu}
         objectToReturn={'thicknessList'}
@@ -389,6 +390,7 @@ class WhiteboardToolbar extends Component {
   renderColorItem() {
     return (
       <ToolbarMenuItem
+        disabled={this.state.annotationSelected.value === 'hand'}
         label={'Color List'}
         onItemClick={this.displaySubMenu}
         objectToReturn={'colorList'}
diff --git a/bigbluebutton-html5/imports/ui/components/whiteboard/whiteboard-toolbar/container.jsx b/bigbluebutton-html5/imports/ui/components/whiteboard/whiteboard-toolbar/container.jsx
index 7c48c619b3a9fd8543a9c593e258081dee0e7879..ed45e4208db7d1983bfab6898642b9ccf748c561 100644
--- a/bigbluebutton-html5/imports/ui/components/whiteboard/whiteboard-toolbar/container.jsx
+++ b/bigbluebutton-html5/imports/ui/components/whiteboard/whiteboard-toolbar/container.jsx
@@ -1,13 +1,13 @@
 import React from 'react';
-import { createContainer } from 'meteor/react-meteor-data';
+import { withTracker } from 'meteor/react-meteor-data';
 import WhiteboardToolbarService from './service';
 import WhiteboardToolbar from './component';
 
 const WhiteboardToolbarContainer = props => (
   <WhiteboardToolbar {...props} />
-  );
+);
 
-export default createContainer(() => ({
+export default withTracker(() => ({
   actions: {
     undoAnnotation: WhiteboardToolbarService.undoAnnotation,
     clearWhiteboard: WhiteboardToolbarService.clearWhiteboard,
@@ -23,4 +23,4 @@ export default createContainer(() => ({
   textShapeActiveId: WhiteboardToolbarService.getTextShapeActiveId(),
   multiUser: WhiteboardToolbarService.getMultiUserStatus(),
   isPresenter: WhiteboardToolbarService.isPresenter(),
-}), WhiteboardToolbarContainer);
+}))(WhiteboardToolbarContainer);
diff --git a/bigbluebutton-html5/imports/ui/components/whiteboard/whiteboard-toolbar/toolbar-menu-item/component.jsx b/bigbluebutton-html5/imports/ui/components/whiteboard/whiteboard-toolbar/toolbar-menu-item/component.jsx
index 03888407293c5a2fa3a27c50bf8fd8668d50a781..0fb421517f3701be8d7a96a8a92eedc969c05dc5 100644
--- a/bigbluebutton-html5/imports/ui/components/whiteboard/whiteboard-toolbar/toolbar-menu-item/component.jsx
+++ b/bigbluebutton-html5/imports/ui/components/whiteboard/whiteboard-toolbar/toolbar-menu-item/component.jsx
@@ -2,7 +2,7 @@ import React, { Component } from 'react';
 import PropTypes from 'prop-types';
 import Button from '/imports/ui/components/button/component';
 import _ from 'lodash';
-import styles from '../styles';
+import { styles } from '../styles';
 
 export default class ToolbarMenuItem extends Component {
   constructor() {
@@ -68,6 +68,7 @@ export default class ToolbarMenuItem extends Component {
           onBlur={this.props.onBlur}
           className={this.props.className}
           setRef={this.setRef}
+          disabled={this.props.disabled}
         />
         {this.props.children}
       </div>
diff --git a/bigbluebutton-html5/imports/ui/components/whiteboard/whiteboard-toolbar/toolbar-submenu-item/component.jsx b/bigbluebutton-html5/imports/ui/components/whiteboard/whiteboard-toolbar/toolbar-submenu-item/component.jsx
index f66e1d58e2d301664cd94eccc813aea8112a373a..3595608a6d81eba3cb803045171b6731a7905e50 100644
--- a/bigbluebutton-html5/imports/ui/components/whiteboard/whiteboard-toolbar/toolbar-submenu-item/component.jsx
+++ b/bigbluebutton-html5/imports/ui/components/whiteboard/whiteboard-toolbar/toolbar-submenu-item/component.jsx
@@ -2,7 +2,7 @@ import React, { Component } from 'react';
 import PropTypes from 'prop-types';
 import Button from '/imports/ui/components/button/component';
 import _ from 'lodash';
-import styles from '../styles';
+import { styles } from '../styles';
 
 export default class ToolbarSubmenuItem extends Component {
   constructor() {
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 645a586a1586381e0362de9f66788d37d237c2c5..ba0b80b9b4a5965e9ebe502afc82bdc882f71e13 100644
--- 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
@@ -1,7 +1,7 @@
 import React, { Component } from 'react';
 import PropTypes from 'prop-types';
 import cx from 'classnames';
-import styles from '../styles';
+import { styles } from '../styles';
 import ToolbarSubmenuItem from '../toolbar-submenu-item/component';
 
 export default class ToolbarSubmenu extends Component {
@@ -80,7 +80,7 @@ export default class ToolbarSubmenu extends Component {
         {objectsToRender ? objectsToRender.map(obj =>
           (
             <ToolbarSubmenuItem
-              label={label}
+              label={obj.value}
               icon={!customIcon ? obj.icon : null}
               customIcon={customIcon ? ToolbarSubmenu.getCustomIcon(type, obj) : null}
               onItemClick={this.onItemClick}
diff --git a/bigbluebutton-html5/imports/ui/services/auth/index.js b/bigbluebutton-html5/imports/ui/services/auth/index.js
index 42dfce94f3a5e2972050eed480354b7c329baebb..92fa54f5dd0b676874385ae4054f7bc6f1f8435c 100644
--- a/bigbluebutton-html5/imports/ui/services/auth/index.js
+++ b/bigbluebutton-html5/imports/ui/services/auth/index.js
@@ -1,4 +1,4 @@
-
+/* eslint prefer-promise-reject-errors: 0 */
 import { Tracker } from 'meteor/tracker';
 
 import Storage from '/imports/ui/services/storage/session';
@@ -134,78 +134,46 @@ class Auth {
   authenticate(force) {
     if (this.loggedIn && !force) return Promise.resolve();
 
-    return this._subscribeToCurrentUser()
-      .then(this._addObserverToValidatedField.bind(this));
-  }
-
-  _subscribeToCurrentUser() {
-    const credentials = this.credentials;
-
-    return new Promise((resolve, reject) => {
-      Tracker.autorun((c) => {
-        if (!(credentials.meetingId && credentials.requesterToken && credentials.requesterUserId)) {
-          reject({
-            error: 500,
-            description: 'Authentication subscription failed due to missing credentials.',
-          });
-        }
-
-        setTimeout(() => {
-          c.stop();
-          reject({
-            error: 500,
-            description: 'Authentication subscription timeout.',
-          });
-        }, 5000);
-
-        const subscription = Meteor.subscribe('current-user', credentials);
-        if (!subscription.ready()) return;
-
-        resolve(c);
+    if (!(this.meetingID && this.userID && this.token)) {
+      return Promise.reject({
+        error: 401,
+        description: 'Authentication failed due to missing credentials.',
       });
-    });
+    }
+
+    return this.validateAuthToken();
   }
 
-  _addObserverToValidatedField(prevComp) {
+  validateAuthToken() {
     return new Promise((resolve, reject) => {
+      let computation = null;
+
       const validationTimeout = setTimeout(() => {
-        clearTimeout(validationTimeout);
-        prevComp.stop();
-        this.clearCredentials();
+        computation.stop();
         reject({
-          error: 500,
+          error: 401,
           description: 'Authentication timeout.',
         });
       }, CONNECTION_TIMEOUT);
 
-      const didValidate = () => {
-        this.loggedIn = true;
-        clearTimeout(validationTimeout);
-        prevComp.stop();
-        resolve();
-      };
-
       Tracker.autorun((c) => {
+        computation = c;
+        const subscription = Meteor.subscribe('current-user', this.credentials);
+
+        if (!subscription.ready()) return;
+
         const selector = { meetingId: this.meetingID, userId: this.userID };
-        const query = Users.find(selector);
-
-        query.observeChanges({
-          changed: (id, fields) => {
-            if (fields.validated === true) {
-              c.stop();
-              didValidate();
-            }
-
-            if (fields.validated === false) {
-              c.stop();
-              this.clearCredentials();
-              reject({
-                error: 401,
-                description: 'Authentication failed.',
-              });
-            }
-          },
-        });
+        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.validated === true) {
+          computation.stop();
+          clearTimeout(validationTimeout);
+          this.loggedIn = true;
+          resolve();
+        }
       });
 
       makeCall('validateAuthToken');
diff --git a/bigbluebutton-html5/imports/ui/stylesheets/variables/general.scss b/bigbluebutton-html5/imports/ui/stylesheets/variables/general.scss
index 824a9340970ee11969b5a8c2208f12afa48ff71f..88f7cde2563f68919749aea436ad6d65ce9825cb 100644
--- a/bigbluebutton-html5/imports/ui/stylesheets/variables/general.scss
+++ b/bigbluebutton-html5/imports/ui/stylesheets/variables/general.scss
@@ -4,13 +4,13 @@ $border-size-large: 3px;
 $border-radius: .2rem;
 
 $sm-padding-x: .75rem;
-$sm-padding-y: .25rem;
+$sm-padding-y: .3rem;
 
 $md-padding-x: 1rem;
-$md-padding-y: .375rem;
+$md-padding-y: .45rem;
 
 $lg-padding-x: 1.25rem;
-$lg-padding-y: .5rem;
+$lg-padding-y: 0.6rem;
 
 $jumbo-padding-x: 3.025rem;
-$jumbo-padding-y: 1.25rem;
+$jumbo-padding-y: 1.5rem;
diff --git a/bigbluebutton-html5/package-lock.json b/bigbluebutton-html5/package-lock.json
index 759240950ac7240db1e39ab69d5984252504b7f4..11bab643c9f78c12f785ba8acbd62d85712046de 100644
--- a/bigbluebutton-html5/package-lock.json
+++ b/bigbluebutton-html5/package-lock.json
@@ -3,295 +3,173 @@
   "requires": true,
   "lockfileVersion": 1,
   "dependencies": {
-    "abbrev": {
-      "version": "https://registry.npmjs.org/abbrev/-/abbrev-1.1.1.tgz",
-      "integrity": "sha1-+PLIh60Qv2f2NPAFtph/7TF5qsg="
-    },
-    "acorn": {
-      "version": "https://registry.npmjs.org/acorn/-/acorn-5.2.1.tgz",
-      "integrity": "sha1-MXrHghgmwixwLWYYmrg1lnXxNdc=",
-      "dev": true
+    "attr-accept": {
+      "version": "1.1.0",
+      "resolved": "https://registry.npmjs.org/attr-accept/-/attr-accept-1.1.0.tgz",
+      "integrity": "sha1-tc01In8WOTWo8d4Q7T66FpQfa+Y="
     },
-    "acorn-jsx": {
-      "version": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-3.0.1.tgz",
-      "integrity": "sha1-r9+UiPsezvyDSPb7IvRk4ypYs2s=",
+    "autoprefixer": {
+      "version": "7.1.6",
+      "resolved": "https://registry.npmjs.org/autoprefixer/-/autoprefixer-7.1.6.tgz",
+      "integrity": "sha512-C9yv/UF3X+eJTi/zvfxuyfxmLibYrntpF3qoJYrMeQwgUJOZrZvpJiMG2FMQ3qnhWtF/be4pYONBBw95ZGe3vA==",
       "dev": true,
       "requires": {
-        "acorn": "https://registry.npmjs.org/acorn/-/acorn-3.3.0.tgz"
+        "browserslist": "2.11.0",
+        "caniuse-lite": "1.0.30000789",
+        "normalize-range": "0.1.2",
+        "num2fraction": "1.2.2",
+        "postcss": "6.0.16",
+        "postcss-value-parser": "3.3.0"
       },
       "dependencies": {
-        "acorn": {
-          "version": "https://registry.npmjs.org/acorn/-/acorn-3.3.0.tgz",
-          "integrity": "sha1-ReN/s56No/JbruP/U2niu18iAXo=",
+        "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.1"
+          }
+        },
+        "browserslist": {
+          "version": "2.11.0",
+          "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-2.11.0.tgz",
+          "integrity": "sha512-mNYp0RNeu1xueGuJFSXkU+K0nH+dBE/gcjtyhtNKfU8hwdrVIfoA7i5iFSjOmzkGdL2QaO7YX9ExiVPE7AY9JA==",
+          "dev": true,
+          "requires": {
+            "caniuse-lite": "1.0.30000789",
+            "electron-to-chromium": "1.3.30"
+          }
+        },
+        "caniuse-lite": {
+          "version": "1.0.30000789",
+          "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30000789.tgz",
+          "integrity": "sha1-Lj2TeyZxM/Y2Ne9/RB+sZjYPyIk=",
           "dev": true
-        }
-      }
-    },
-    "ajv": {
-      "version": "https://registry.npmjs.org/ajv/-/ajv-5.3.0.tgz",
-      "integrity": "sha1-RBT/dKUIecII7l/cgm4ywwNUnto=",
-      "requires": {
-        "co": "https://registry.npmjs.org/co/-/co-4.6.0.tgz",
-        "fast-deep-equal": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-1.0.0.tgz",
-        "fast-json-stable-stringify": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.0.0.tgz",
-        "json-schema-traverse": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.3.1.tgz"
-      }
-    },
-    "ajv-keywords": {
-      "version": "https://registry.npmjs.org/ajv-keywords/-/ajv-keywords-2.1.1.tgz",
-      "integrity": "sha1-YXmX/F9gV2iUxDX5QNgZ4TW4B2I=",
-      "dev": true
-    },
-    "amdefine": {
-      "version": "https://registry.npmjs.org/amdefine/-/amdefine-1.0.1.tgz",
-      "integrity": "sha1-SlKCrBZHKek2Gbz9OtFR+BfOkfU="
-    },
-    "ansi-escapes": {
-      "version": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-3.0.0.tgz",
-      "integrity": "sha1-7D6LTp+AZPwCw6ybZfHCdb2o75I=",
-      "dev": true
-    },
-    "ansi-regex": {
-      "version": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-2.1.1.tgz",
-      "integrity": "sha1-w7M6te42DYbg5ijwRorn7yfWVN8="
-    },
-    "ansi-styles": {
-      "version": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-2.2.1.tgz",
-      "integrity": "sha1-tDLdM1i2NM914eRmQ2gkBTPB3b4="
-    },
-    "any-promise": {
-      "version": "https://registry.npmjs.org/any-promise/-/any-promise-1.3.0.tgz",
-      "integrity": "sha1-q8av7tzqUugJzcA3au0845Y10X8="
-    },
-    "app-root-path": {
-      "version": "https://registry.npmjs.org/app-root-path/-/app-root-path-2.0.1.tgz",
-      "integrity": "sha1-zWLc+OT9WkF+/GZNLlsQZTxlG0Y=",
-      "dev": true
-    },
-    "aproba": {
-      "version": "https://registry.npmjs.org/aproba/-/aproba-1.2.0.tgz",
-      "integrity": "sha1-aALmJk79GMeQobDVF/DyYnvyyUo="
-    },
-    "archiver": {
-      "version": "https://registry.npmjs.org/archiver/-/archiver-1.3.0.tgz",
-      "integrity": "sha1-TyGU1tj5nfP1MeaIHxTxXVX6ryI=",
-      "dev": true,
-      "requires": {
-        "archiver-utils": "https://registry.npmjs.org/archiver-utils/-/archiver-utils-1.3.0.tgz",
-        "async": "https://registry.npmjs.org/async/-/async-2.5.0.tgz",
-        "buffer-crc32": "https://registry.npmjs.org/buffer-crc32/-/buffer-crc32-0.2.13.tgz",
-        "glob": "https://registry.npmjs.org/glob/-/glob-7.1.2.tgz",
-        "lodash": "https://registry.npmjs.org/lodash/-/lodash-4.17.4.tgz",
-        "readable-stream": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.3.tgz",
-        "tar-stream": "https://registry.npmjs.org/tar-stream/-/tar-stream-1.5.4.tgz",
-        "walkdir": "https://registry.npmjs.org/walkdir/-/walkdir-0.0.11.tgz",
-        "zip-stream": "https://registry.npmjs.org/zip-stream/-/zip-stream-1.2.0.tgz"
-      },
-      "dependencies": {
-        "async": {
-          "version": "https://registry.npmjs.org/async/-/async-2.5.0.tgz",
-          "integrity": "sha1-hDGQ/WtzV6C54clW7d3V7IRitU0=",
+        },
+        "chalk": {
+          "version": "2.3.0",
+          "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.3.0.tgz",
+          "integrity": "sha512-Az5zJR2CBujap2rqXGaJKaPHyJ0IrUimvYNX+ncCy8PJP4ltOGTrHUIo097ZaL2zMeKYpiCdqDvS6zdrTFok3Q==",
           "dev": true,
           "requires": {
-            "lodash": "https://registry.npmjs.org/lodash/-/lodash-4.17.4.tgz"
+            "ansi-styles": "3.2.0",
+            "escape-string-regexp": "1.0.5",
+            "supports-color": "4.5.0"
+          },
+          "dependencies": {
+            "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"
+              }
+            }
           }
-        }
-      }
-    },
-    "archiver-utils": {
-      "version": "https://registry.npmjs.org/archiver-utils/-/archiver-utils-1.3.0.tgz",
-      "integrity": "sha1-5QtMCccL89aA4y/xt5lOn52JUXQ=",
-      "dev": true,
-      "requires": {
-        "glob": "https://registry.npmjs.org/glob/-/glob-7.1.2.tgz",
-        "graceful-fs": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.1.11.tgz",
-        "lazystream": "https://registry.npmjs.org/lazystream/-/lazystream-1.0.0.tgz",
-        "lodash": "https://registry.npmjs.org/lodash/-/lodash-4.17.4.tgz",
-        "normalize-path": "https://registry.npmjs.org/normalize-path/-/normalize-path-2.1.1.tgz",
-        "readable-stream": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.3.tgz"
-      },
-      "dependencies": {
-        "normalize-path": {
-          "version": "https://registry.npmjs.org/normalize-path/-/normalize-path-2.1.1.tgz",
-          "integrity": "sha1-GrKLVW4Zg2Oowab35vogE3/mrtk=",
+        },
+        "color-convert": {
+          "version": "1.9.1",
+          "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.1.tgz",
+          "integrity": "sha512-mjGanIiwQJskCC18rPR6OmrZ6fm2Lc7PeGFYwCmy5J34wC6F1PzdGL6xeMfmgicfYcNLGuVFA3WzXtIDCQSZxQ==",
+          "dev": true,
+          "requires": {
+            "color-name": "1.1.3"
+          }
+        },
+        "color-name": {
+          "version": "1.1.3",
+          "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz",
+          "integrity": "sha1-p9BVi9icQveV3UIyj3QIMcpTvCU=",
+          "dev": true
+        },
+        "electron-to-chromium": {
+          "version": "1.3.30",
+          "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.3.30.tgz",
+          "integrity": "sha512-zx1Prv7kYLfc4OA60FhxGbSo4qrEjgSzpo1/37i7l9ltXPYOoQBtjQxY9KmsgfHnBxHlBGXwLlsbt/gub1w5lw==",
+          "dev": true,
+          "requires": {
+            "electron-releases": "2.1.0"
+          }
+        },
+        "escape-string-regexp": {
+          "version": "1.0.5",
+          "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz",
+          "integrity": "sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ=",
+          "dev": true
+        },
+        "has-flag": {
+          "version": "2.0.0",
+          "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-2.0.0.tgz",
+          "integrity": "sha1-6CB68cx7MNRGzHC3NLXovhj4jVE=",
+          "dev": true
+        },
+        "normalize-range": {
+          "version": "0.1.2",
+          "resolved": "https://registry.npmjs.org/normalize-range/-/normalize-range-0.1.2.tgz",
+          "integrity": "sha1-LRDAa9/TEuqXd2laTShDlFa3WUI=",
+          "dev": true
+        },
+        "num2fraction": {
+          "version": "1.2.2",
+          "resolved": "https://registry.npmjs.org/num2fraction/-/num2fraction-1.2.2.tgz",
+          "integrity": "sha1-b2gragJ6Tp3fpFZM0lidHU5mnt4=",
+          "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",
+            "supports-color": "5.1.0"
+          }
+        },
+        "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=",
+          "dev": true
+        },
+        "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
+        },
+        "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": {
-            "remove-trailing-separator": "https://registry.npmjs.org/remove-trailing-separator/-/remove-trailing-separator-1.1.0.tgz"
+            "has-flag": "2.0.0"
           }
         }
       }
     },
-    "are-we-there-yet": {
-      "version": "https://registry.npmjs.org/are-we-there-yet/-/are-we-there-yet-1.1.4.tgz",
-      "integrity": "sha1-u13KOCu5TwXhUZQ3PRb9O6HKEQ0=",
-      "requires": {
-        "delegates": "https://registry.npmjs.org/delegates/-/delegates-1.0.0.tgz",
-        "readable-stream": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.3.tgz"
-      }
-    },
-    "argparse": {
-      "version": "https://registry.npmjs.org/argparse/-/argparse-1.0.9.tgz",
-      "integrity": "sha1-c9g7wmP4bpf4zE9rrhsOkKfSLIY=",
-      "dev": true,
-      "requires": {
-        "sprintf-js": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz"
-      }
-    },
-    "aria-query": {
-      "version": "https://registry.npmjs.org/aria-query/-/aria-query-0.7.0.tgz",
-      "integrity": "sha1-SvEKHmFXPd6gzzuZtRxSwFtCTSQ=",
-      "dev": true,
-      "requires": {
-        "ast-types-flow": "https://registry.npmjs.org/ast-types-flow/-/ast-types-flow-0.0.7.tgz"
-      }
-    },
-    "array-find-index": {
-      "version": "https://registry.npmjs.org/array-find-index/-/array-find-index-1.0.2.tgz",
-      "integrity": "sha1-3wEKoSh+Fku9pvlyOwqWoexBh6E="
-    },
-    "array-includes": {
-      "version": "https://registry.npmjs.org/array-includes/-/array-includes-3.0.3.tgz",
-      "integrity": "sha1-GEtI9i2S10UrsxsyMWXH+L0CJm0=",
-      "dev": true,
-      "requires": {
-        "define-properties": "https://registry.npmjs.org/define-properties/-/define-properties-1.1.2.tgz",
-        "es-abstract": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.9.0.tgz"
-      }
-    },
-    "array-union": {
-      "version": "https://registry.npmjs.org/array-union/-/array-union-1.0.2.tgz",
-      "integrity": "sha1-mjRBDk9OPaI96jdb5b5w8kd47Dk=",
-      "dev": true,
-      "requires": {
-        "array-uniq": "https://registry.npmjs.org/array-uniq/-/array-uniq-1.0.3.tgz"
-      }
-    },
-    "array-uniq": {
-      "version": "https://registry.npmjs.org/array-uniq/-/array-uniq-1.0.3.tgz",
-      "integrity": "sha1-r2rId6Jcx/dOBYiUdThY39sk/bY=",
-      "dev": true
-    },
-    "arrify": {
-      "version": "https://registry.npmjs.org/arrify/-/arrify-1.0.1.tgz",
-      "integrity": "sha1-iYUI2iIm84DfkEcoRWhJwVAaSw0=",
-      "dev": true
-    },
-    "asap": {
-      "version": "https://registry.npmjs.org/asap/-/asap-2.0.6.tgz",
-      "integrity": "sha1-5QNHYR1+aQlDIIu9r+vLwvuGbUY="
-    },
-    "asn1": {
-      "version": "https://registry.npmjs.org/asn1/-/asn1-0.2.3.tgz",
-      "integrity": "sha1-2sh4dxPJlmhJ/IGAd36+nB3fO4Y="
-    },
-    "assert-plus": {
-      "version": "https://registry.npmjs.org/assert-plus/-/assert-plus-1.0.0.tgz",
-      "integrity": "sha1-8S4PPF13sLHN2RRpQuTpbB5N1SU="
-    },
-    "assertion-error": {
-      "version": "https://registry.npmjs.org/assertion-error/-/assertion-error-1.0.2.tgz",
-      "integrity": "sha1-E8pRXYYgbaC6xm6DTdOX2HWBCUw=",
-      "dev": true
-    },
-    "ast-types-flow": {
-      "version": "https://registry.npmjs.org/ast-types-flow/-/ast-types-flow-0.0.7.tgz",
-      "integrity": "sha1-9wtzXGvKGlycItmCw+Oef+ujva0=",
-      "dev": true
-    },
-    "async": {
-      "version": "https://registry.npmjs.org/async/-/async-1.0.0.tgz",
-      "integrity": "sha1-+PwEyjoTeErenhZBr5hXjPvWR6k="
-    },
-    "async-foreach": {
-      "version": "https://registry.npmjs.org/async-foreach/-/async-foreach-0.1.3.tgz",
-      "integrity": "sha1-NhIfhFwFeBct5Bmpfb6x0W7DRUI="
-    },
-    "asynckit": {
-      "version": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz",
-      "integrity": "sha1-x57Zf380y48robyXkLzDZkdLS3k="
-    },
-    "atob": {
-      "version": "https://registry.npmjs.org/atob/-/atob-1.1.3.tgz",
-      "integrity": "sha1-lfE2KbEsOlGl0hWr3OKqnzL4B3M=",
-      "dev": true
-    },
-    "attr-accept": {
-      "version": "https://registry.npmjs.org/attr-accept/-/attr-accept-1.1.0.tgz",
-      "integrity": "sha1-tc01In8WOTWo8d4Q7T66FpQfa+Y="
-    },
-    "autoprefixer": {
-      "version": "https://registry.npmjs.org/autoprefixer/-/autoprefixer-7.1.6.tgz",
-      "integrity": "sha1-+5MwOfdK90qD5xIlznjZ/Vi6hNc=",
-      "dev": true,
-      "requires": {
-        "browserslist": "https://registry.npmjs.org/browserslist/-/browserslist-2.6.1.tgz",
-        "caniuse-lite": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30000756.tgz",
-        "normalize-range": "https://registry.npmjs.org/normalize-range/-/normalize-range-0.1.2.tgz",
-        "num2fraction": "https://registry.npmjs.org/num2fraction/-/num2fraction-1.2.2.tgz",
-        "postcss": "https://registry.npmjs.org/postcss/-/postcss-6.0.13.tgz",
-        "postcss-value-parser": "https://registry.npmjs.org/postcss-value-parser/-/postcss-value-parser-3.3.0.tgz"
-      }
-    },
-    "autosize": {
-      "version": "https://registry.npmjs.org/autosize/-/autosize-3.0.21.tgz",
-      "integrity": "sha1-8YL0DRd1fZeKE5pMnKQMTA5EhgM="
-    },
-    "aws-sign2": {
-      "version": "https://registry.npmjs.org/aws-sign2/-/aws-sign2-0.7.0.tgz",
-      "integrity": "sha1-tG6JCTSpWR8tL2+G1+ap8bP+dqg="
-    },
-    "aws4": {
-      "version": "https://registry.npmjs.org/aws4/-/aws4-1.6.0.tgz",
-      "integrity": "sha1-g+9cqGCysy5KDe7e6MdxudtXRx4="
-    },
-    "axobject-query": {
-      "version": "https://registry.npmjs.org/axobject-query/-/axobject-query-0.1.0.tgz",
-      "integrity": "sha1-YvWdvFnJ+SQnWco0mWDnov48NsA=",
-      "dev": true,
-      "requires": {
-        "ast-types-flow": "https://registry.npmjs.org/ast-types-flow/-/ast-types-flow-0.0.7.tgz"
-      }
-    },
-    "babel-code-frame": {
-      "version": "https://registry.npmjs.org/babel-code-frame/-/babel-code-frame-6.26.0.tgz",
-      "integrity": "sha1-Y/1D99weO7fONZR9uP42mj9Yx0s=",
-      "dev": true,
-      "requires": {
-        "chalk": "https://registry.npmjs.org/chalk/-/chalk-1.1.3.tgz",
-        "esutils": "https://registry.npmjs.org/esutils/-/esutils-2.0.2.tgz",
-        "js-tokens": "https://registry.npmjs.org/js-tokens/-/js-tokens-3.0.2.tgz"
-      }
-    },
     "babel-runtime": {
-      "version": "https://registry.npmjs.org/babel-runtime/-/babel-runtime-6.26.0.tgz",
+      "version": "6.26.0",
+      "resolved": "https://registry.npmjs.org/babel-runtime/-/babel-runtime-6.26.0.tgz",
       "integrity": "sha1-llxwWGaOgrVde/4E/yM3vItWR/4=",
       "requires": {
-        "core-js": "https://registry.npmjs.org/core-js/-/core-js-2.5.1.tgz",
-        "regenerator-runtime": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.11.0.tgz"
-      }
-    },
-    "balanced-match": {
-      "version": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.0.tgz",
-      "integrity": "sha1-ibTRmasr7kneFk6gK4nORi1xt2c="
-    },
-    "bcrypt-pbkdf": {
-      "version": "https://registry.npmjs.org/bcrypt-pbkdf/-/bcrypt-pbkdf-1.0.1.tgz",
-      "integrity": "sha1-Y7xdy2EzG5K8Bf1SiVPDNGKgb40=",
-      "optional": true,
-      "requires": {
-        "tweetnacl": "https://registry.npmjs.org/tweetnacl/-/tweetnacl-0.14.5.tgz"
-      }
-    },
-    "bindings": {
-      "version": "https://registry.npmjs.org/bindings/-/bindings-1.3.0.tgz",
-      "integrity": "sha1-s0b27PapX1qBXFg5/HzbIlAvHtc="
-    },
-    "bl": {
-      "version": "https://registry.npmjs.org/bl/-/bl-1.2.1.tgz",
-      "integrity": "sha1-ysMo977kVzDUBLaSID/LWQ4XLV4=",
-      "dev": true,
-      "requires": {
-        "readable-stream": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.3.tgz"
+        "core-js": "2.5.3",
+        "regenerator-runtime": "0.11.1"
+      },
+      "dependencies": {
+        "core-js": {
+          "version": "2.5.3",
+          "resolved": "https://registry.npmjs.org/core-js/-/core-js-2.5.3.tgz",
+          "integrity": "sha1-isw4NFgk8W2DZbfJtCWRaOjtYD4="
+        },
+        "regenerator-runtime": {
+          "version": "0.11.1",
+          "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.11.1.tgz",
+          "integrity": "sha512-MguG95oij0fC3QV3URf4V2SDYGJhJnJGqvIIgdECeODCT98wSWDAJ94SSuVpYQUoTcGUIL6L4yNB7j1DFFHSBg=="
+        }
       }
     },
     "block-stream": {
@@ -301,4726 +179,8261 @@
         "inherits": "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz"
       }
     },
-    "boom": {
-      "version": "https://registry.npmjs.org/boom/-/boom-4.3.1.tgz",
-      "integrity": "sha1-T4owBctKfjiJ90kDD9JbluAdLjE=",
-      "requires": {
-        "hoek": "https://registry.npmjs.org/hoek/-/hoek-4.2.0.tgz"
-      }
-    },
-    "brace-expansion": {
-      "version": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.8.tgz",
-      "integrity": "sha1-wHshHHyVLsH479Uad+8NHTmQopI=",
-      "requires": {
-        "balanced-match": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.0.tgz",
-        "concat-map": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz"
-      }
-    },
-    "browserslist": {
-      "version": "https://registry.npmjs.org/browserslist/-/browserslist-2.6.1.tgz",
-      "integrity": "sha1-zGWgWtYTHr2ibwdvKCK6G8gmN2s=",
-      "dev": true,
-      "requires": {
-        "caniuse-lite": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30000756.tgz",
-        "electron-to-chromium": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.3.27.tgz"
-      }
-    },
-    "buffer-crc32": {
-      "version": "https://registry.npmjs.org/buffer-crc32/-/buffer-crc32-0.2.13.tgz",
-      "integrity": "sha1-DTM+PwDqxQqhRUq9MO+MKl2ackI=",
-      "dev": true
-    },
-    "builtin-modules": {
-      "version": "https://registry.npmjs.org/builtin-modules/-/builtin-modules-1.1.1.tgz",
-      "integrity": "sha1-Jw8HbFpywC9bZaR9+Uxf46J4iS8="
-    },
-    "caller-path": {
-      "version": "https://registry.npmjs.org/caller-path/-/caller-path-0.1.0.tgz",
-      "integrity": "sha1-lAhe9jWB7NPaqSREqP6U6CV3dR8=",
-      "dev": true,
-      "requires": {
-        "callsites": "https://registry.npmjs.org/callsites/-/callsites-0.2.0.tgz"
-      }
-    },
-    "callsites": {
-      "version": "https://registry.npmjs.org/callsites/-/callsites-0.2.0.tgz",
-      "integrity": "sha1-r6uWJikQp/M8GaV3WCXGnzTjUMo=",
-      "dev": true
-    },
-    "camelcase": {
-      "version": "https://registry.npmjs.org/camelcase/-/camelcase-2.1.1.tgz",
-      "integrity": "sha1-fB0W1nmhu+WcoCys7PsBHiAfWh8="
-    },
-    "camelcase-keys": {
-      "version": "https://registry.npmjs.org/camelcase-keys/-/camelcase-keys-2.1.0.tgz",
-      "integrity": "sha1-MIvur/3ygRkFHvodkyITyRuPkuc=",
-      "requires": {
-        "camelcase": "https://registry.npmjs.org/camelcase/-/camelcase-2.1.1.tgz",
-        "map-obj": "https://registry.npmjs.org/map-obj/-/map-obj-1.0.1.tgz"
-      }
-    },
-    "caniuse-lite": {
-      "version": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30000756.tgz",
-      "integrity": "sha1-PacBwVIbn6uHAExt58l/pH2+qtI=",
-      "dev": true
-    },
-    "caseless": {
-      "version": "https://registry.npmjs.org/caseless/-/caseless-0.12.0.tgz",
-      "integrity": "sha1-G2gcIf+EAzyCZUMJBolCDRhxUdw="
-    },
     "chai": {
-      "version": "https://registry.npmjs.org/chai/-/chai-4.1.2.tgz",
+      "version": "4.1.2",
+      "resolved": "https://registry.npmjs.org/chai/-/chai-4.1.2.tgz",
       "integrity": "sha1-D2RYS6ZC8PKs4oBiefTwbKI61zw=",
       "dev": true,
       "requires": {
-        "assertion-error": "https://registry.npmjs.org/assertion-error/-/assertion-error-1.0.2.tgz",
-        "check-error": "https://registry.npmjs.org/check-error/-/check-error-1.0.2.tgz",
-        "deep-eql": "https://registry.npmjs.org/deep-eql/-/deep-eql-3.0.1.tgz",
-        "get-func-name": "https://registry.npmjs.org/get-func-name/-/get-func-name-2.0.0.tgz",
-        "pathval": "https://registry.npmjs.org/pathval/-/pathval-1.1.0.tgz",
-        "type-detect": "https://registry.npmjs.org/type-detect/-/type-detect-4.0.3.tgz"
-      }
-    },
-    "chain-function": {
-      "version": "https://registry.npmjs.org/chain-function/-/chain-function-1.0.0.tgz",
-      "integrity": "sha1-DUqzfn4Y6tC9xHuSB2QRjOWHM9w="
-    },
-    "chalk": {
-      "version": "https://registry.npmjs.org/chalk/-/chalk-1.1.3.tgz",
-      "integrity": "sha1-qBFcVeSnAv5NFQq9OHKCKn4J/Jg=",
-      "requires": {
-        "ansi-styles": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-2.2.1.tgz",
-        "escape-string-regexp": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz",
-        "has-ansi": "https://registry.npmjs.org/has-ansi/-/has-ansi-2.0.0.tgz",
-        "strip-ansi": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-3.0.1.tgz",
-        "supports-color": "https://registry.npmjs.org/supports-color/-/supports-color-2.0.0.tgz"
-      }
-    },
-    "check-error": {
-      "version": "https://registry.npmjs.org/check-error/-/check-error-1.0.2.tgz",
-      "integrity": "sha1-V00xLt2Iu13YkS6Sht1sCu1KrII=",
-      "dev": true
-    },
-    "ci-info": {
-      "version": "https://registry.npmjs.org/ci-info/-/ci-info-1.1.1.tgz",
-      "integrity": "sha1-R7RN8RjEjSWXtW00Ln4leRBgFxo=",
-      "dev": true
-    },
-    "circular-json": {
-      "version": "https://registry.npmjs.org/circular-json/-/circular-json-0.3.3.tgz",
-      "integrity": "sha1-gVyZ6oT2gJUp0vRXkb34JxE1LWY=",
-      "dev": true
-    },
-    "classnames": {
-      "version": "https://registry.npmjs.org/classnames/-/classnames-2.2.5.tgz",
-      "integrity": "sha1-+zgB1FNGdknvNgPH1hoCvRKb3m0="
-    },
-    "cli-cursor": {
-      "version": "https://registry.npmjs.org/cli-cursor/-/cli-cursor-2.1.0.tgz",
-      "integrity": "sha1-s12sN2R5+sw+lHR9QdDQ9SOP/LU=",
-      "dev": true,
-      "requires": {
-        "restore-cursor": "https://registry.npmjs.org/restore-cursor/-/restore-cursor-2.0.0.tgz"
-      }
-    },
-    "cli-spinners": {
-      "version": "https://registry.npmjs.org/cli-spinners/-/cli-spinners-0.1.2.tgz",
-      "integrity": "sha1-u3ZNiOGF+54eaiofGXcjGPYF4xw=",
-      "dev": true
-    },
-    "cli-truncate": {
-      "version": "https://registry.npmjs.org/cli-truncate/-/cli-truncate-0.2.1.tgz",
-      "integrity": "sha1-nxXPuwcFAFNpIWxiasfQWrkN1XQ=",
-      "dev": true,
-      "requires": {
-        "slice-ansi": "https://registry.npmjs.org/slice-ansi/-/slice-ansi-0.0.4.tgz",
-        "string-width": "https://registry.npmjs.org/string-width/-/string-width-1.0.2.tgz"
+        "assertion-error": "1.1.0",
+        "check-error": "1.0.2",
+        "deep-eql": "3.0.1",
+        "get-func-name": "2.0.0",
+        "pathval": "1.1.0",
+        "type-detect": "4.0.5"
       },
       "dependencies": {
-        "slice-ansi": {
-          "version": "https://registry.npmjs.org/slice-ansi/-/slice-ansi-0.0.4.tgz",
-          "integrity": "sha1-7b+JA/ZvfOL46v1s7tZeJkyDGzU=",
+        "assertion-error": {
+          "version": "1.1.0",
+          "resolved": "https://registry.npmjs.org/assertion-error/-/assertion-error-1.1.0.tgz",
+          "integrity": "sha512-jgsaNduz+ndvGyFt3uSuWqvy4lCnIJiovtouQN5JZHOKCS2QuhEdbcQHFhVksz2N2U9hXJo8odG7ETyWlEeuDw==",
+          "dev": true
+        },
+        "check-error": {
+          "version": "1.0.2",
+          "resolved": "https://registry.npmjs.org/check-error/-/check-error-1.0.2.tgz",
+          "integrity": "sha1-V00xLt2Iu13YkS6Sht1sCu1KrII=",
+          "dev": true
+        },
+        "deep-eql": {
+          "version": "3.0.1",
+          "resolved": "https://registry.npmjs.org/deep-eql/-/deep-eql-3.0.1.tgz",
+          "integrity": "sha512-+QeIQyN5ZuO+3Uk5DYh6/1eKO0m0YmJFGNmFHGACpf1ClL1nmlV/p4gNgbl2pJGxgXb4faqo6UE+M5ACEMyVcw==",
+          "dev": true,
+          "requires": {
+            "type-detect": "4.0.5"
+          }
+        },
+        "get-func-name": {
+          "version": "2.0.0",
+          "resolved": "https://registry.npmjs.org/get-func-name/-/get-func-name-2.0.0.tgz",
+          "integrity": "sha1-6td0q+5y4gQJQzoGY2YCPdaIekE=",
+          "dev": true
+        },
+        "pathval": {
+          "version": "1.1.0",
+          "resolved": "https://registry.npmjs.org/pathval/-/pathval-1.1.0.tgz",
+          "integrity": "sha1-uULm1L3mUwBe9rcTYd74cn0GReA=",
+          "dev": true
+        },
+        "type-detect": {
+          "version": "4.0.5",
+          "resolved": "https://registry.npmjs.org/type-detect/-/type-detect-4.0.5.tgz",
+          "integrity": "sha512-N9IvkQslUGYGC24RkJk1ba99foK6TkwC2FHAEBlQFBP0RxQZS8ZpJuAZcwiY/w9ZJHFQb1aOXBI60OdxhTrwEQ==",
           "dev": true
         }
       }
     },
-    "cli-width": {
-      "version": "https://registry.npmjs.org/cli-width/-/cli-width-2.2.0.tgz",
-      "integrity": "sha1-/xnt6Kml5XkyQUewwR8PvLq+1jk=",
+    "chardet": {
+      "version": "0.4.2",
+      "resolved": "https://registry.npmjs.org/chardet/-/chardet-0.4.2.tgz",
+      "integrity": "sha1-tUc7M9yXxCTl2Y3IfVXU2KKci/I=",
       "dev": true
     },
+    "classnames": {
+      "version": "2.2.5",
+      "resolved": "https://registry.npmjs.org/classnames/-/classnames-2.2.5.tgz",
+      "integrity": "sha1-+zgB1FNGdknvNgPH1hoCvRKb3m0="
+    },
     "clipboard": {
-      "version": "https://registry.npmjs.org/clipboard/-/clipboard-1.7.1.tgz",
+      "version": "1.7.1",
+      "resolved": "https://registry.npmjs.org/clipboard/-/clipboard-1.7.1.tgz",
       "integrity": "sha1-Ng1taUbpmnof7zleQrqStem1oWs=",
       "requires": {
-        "good-listener": "https://registry.npmjs.org/good-listener/-/good-listener-1.2.2.tgz",
-        "select": "https://registry.npmjs.org/select/-/select-1.1.2.tgz",
-        "tiny-emitter": "https://registry.npmjs.org/tiny-emitter/-/tiny-emitter-2.0.2.tgz"
-      }
-    },
-    "cliui": {
-      "version": "https://registry.npmjs.org/cliui/-/cliui-3.2.0.tgz",
-      "integrity": "sha1-EgYBU3qRbSmUD5NNo7SNWFo5IT0=",
-      "requires": {
-        "string-width": "https://registry.npmjs.org/string-width/-/string-width-1.0.2.tgz",
-        "strip-ansi": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-3.0.1.tgz",
-        "wrap-ansi": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-2.1.0.tgz"
-      }
-    },
-    "co": {
-      "version": "https://registry.npmjs.org/co/-/co-4.6.0.tgz",
-      "integrity": "sha1-bqa989hTrlTMuOR7+gvz+QMfsYQ="
-    },
-    "code-point-at": {
-      "version": "https://registry.npmjs.org/code-point-at/-/code-point-at-1.1.0.tgz",
-      "integrity": "sha1-DQcLTQQ6W+ozovGkDi7bPZpMz3c="
-    },
-    "color-convert": {
-      "version": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.0.tgz",
-      "integrity": "sha1-Gsz5fdc5uYO/mU1W/sj5WFNkG3o=",
-      "dev": true,
-      "requires": {
-        "color-name": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz"
-      }
-    },
-    "color-name": {
-      "version": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz",
-      "integrity": "sha1-p9BVi9icQveV3UIyj3QIMcpTvCU=",
-      "dev": true
-    },
-    "colors": {
-      "version": "https://registry.npmjs.org/colors/-/colors-1.0.3.tgz",
-      "integrity": "sha1-BDP0TYCWgP3rYO0mDxsMJi6CpAs="
-    },
-    "combined-stream": {
-      "version": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.5.tgz",
-      "integrity": "sha1-k4NwpXtKUd6ix3wV1cX9+JUWQAk=",
-      "requires": {
-        "delayed-stream": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz"
+        "good-listener": "1.2.2",
+        "select": "1.1.2",
+        "tiny-emitter": "2.0.2"
+      },
+      "dependencies": {
+        "delegate": {
+          "version": "3.2.0",
+          "resolved": "https://registry.npmjs.org/delegate/-/delegate-3.2.0.tgz",
+          "integrity": "sha512-IofjkYBZaZivn0V8nnsMJGBr4jVLxHDheKSW88PyxS5QC4Vo9ZbZVvhzlSxY87fVq3STR6r+4cGepyHkcWOQSw=="
+        },
+        "good-listener": {
+          "version": "1.2.2",
+          "resolved": "https://registry.npmjs.org/good-listener/-/good-listener-1.2.2.tgz",
+          "integrity": "sha1-1TswzfkxPf+33JoNR3CWqm0UXFA=",
+          "requires": {
+            "delegate": "3.2.0"
+          }
+        },
+        "select": {
+          "version": "1.1.2",
+          "resolved": "https://registry.npmjs.org/select/-/select-1.1.2.tgz",
+          "integrity": "sha1-DnNQrN7ICxEIUoeG7B1EGNEbOW0="
+        },
+        "tiny-emitter": {
+          "version": "2.0.2",
+          "resolved": "https://registry.npmjs.org/tiny-emitter/-/tiny-emitter-2.0.2.tgz",
+          "integrity": "sha512-2NM0auVBGft5tee/OxP4PI3d8WItkDM+fPnaRAVo6xTDI2knbz9eC5ArWGqtGlYqiH3RU5yMpdyTTO7MguC4ow=="
+        }
       }
     },
-    "commander": {
-      "version": "https://registry.npmjs.org/commander/-/commander-2.11.0.tgz",
-      "integrity": "sha1-FXFS/R56bI2YpbcVzzdt+SgARWM=",
+    "core-js": {
+      "version": "2.5.3",
+      "resolved": "https://registry.npmjs.org/core-js/-/core-js-2.5.3.tgz",
+      "integrity": "sha1-isw4NFgk8W2DZbfJtCWRaOjtYD4="
+    },
+    "electron-releases": {
+      "version": "2.1.0",
+      "resolved": "https://registry.npmjs.org/electron-releases/-/electron-releases-2.1.0.tgz",
+      "integrity": "sha512-cyKFD1bTE/UgULXfaueIN1k5EPFzs+FRc/rvCY5tIynefAPqopQEgjr0EzY+U3Dqrk/G4m9tXSPuZ77v6dL/Rw==",
       "dev": true
     },
-    "compress-commons": {
-      "version": "https://registry.npmjs.org/compress-commons/-/compress-commons-1.2.2.tgz",
-      "integrity": "sha1-UkqfEJA/OoEzibAiXSfEi7dRiQ8=",
+    "eslint": {
+      "version": "4.9.0",
+      "resolved": "https://registry.npmjs.org/eslint/-/eslint-4.9.0.tgz",
+      "integrity": "sha1-doedJ0BoJhsZH+Dy9Wx0wvQgjos=",
       "dev": true,
       "requires": {
-        "buffer-crc32": "https://registry.npmjs.org/buffer-crc32/-/buffer-crc32-0.2.13.tgz",
-        "crc32-stream": "https://registry.npmjs.org/crc32-stream/-/crc32-stream-2.0.0.tgz",
-        "normalize-path": "https://registry.npmjs.org/normalize-path/-/normalize-path-2.1.1.tgz",
-        "readable-stream": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.3.tgz"
+        "ajv": "5.5.2",
+        "babel-code-frame": "6.26.0",
+        "chalk": "2.3.0",
+        "concat-stream": "1.6.0",
+        "cross-spawn": "5.1.0",
+        "debug": "3.1.0",
+        "doctrine": "2.1.0",
+        "eslint-scope": "3.7.1",
+        "espree": "3.5.2",
+        "esquery": "1.0.0",
+        "estraverse": "4.2.0",
+        "esutils": "2.0.2",
+        "file-entry-cache": "2.0.0",
+        "functional-red-black-tree": "1.0.1",
+        "glob": "7.1.2",
+        "globals": "9.18.0",
+        "ignore": "3.3.7",
+        "imurmurhash": "0.1.4",
+        "inquirer": "3.3.0",
+        "is-resolvable": "1.0.1",
+        "js-yaml": "3.10.0",
+        "json-stable-stringify": "1.0.1",
+        "levn": "0.3.0",
+        "lodash": "4.17.4",
+        "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",
+        "require-uncached": "1.0.3",
+        "semver": "5.4.1",
+        "strip-ansi": "4.0.0",
+        "strip-json-comments": "2.0.1",
+        "table": "4.0.2",
+        "text-table": "0.2.0"
       },
       "dependencies": {
-        "normalize-path": {
-          "version": "https://registry.npmjs.org/normalize-path/-/normalize-path-2.1.1.tgz",
-          "integrity": "sha1-GrKLVW4Zg2Oowab35vogE3/mrtk=",
+        "acorn": {
+          "version": "5.3.0",
+          "resolved": "https://registry.npmjs.org/acorn/-/acorn-5.3.0.tgz",
+          "integrity": "sha512-Yej+zOJ1Dm/IMZzzj78OntP/r3zHEaKcyNoU2lAaxPtrseM6rF0xwqoz5Q5ysAiED9hTjI2hgtvLXitlCN1/Ug==",
+          "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": {
-            "remove-trailing-separator": "https://registry.npmjs.org/remove-trailing-separator/-/remove-trailing-separator-1.1.0.tgz"
+            "acorn": "3.3.0"
+          },
+          "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
+            }
           }
-        }
-      }
-    },
-    "computed-style": {
-      "version": "https://registry.npmjs.org/computed-style/-/computed-style-0.1.4.tgz",
-      "integrity": "sha1-fzRP2FhLLkJb7cpKGvwOMAuwXXQ="
-    },
-    "concat-map": {
-      "version": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz",
-      "integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s="
-    },
-    "concat-stream": {
-      "version": "https://registry.npmjs.org/concat-stream/-/concat-stream-1.6.0.tgz",
-      "integrity": "sha1-CqxmL9Ur54lk1VMvaUeE5wEQrPc=",
-      "dev": true,
-      "requires": {
-        "inherits": "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz",
-        "readable-stream": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.3.tgz",
-        "typedarray": "https://registry.npmjs.org/typedarray/-/typedarray-0.0.6.tgz"
-      }
-    },
-    "console-control-strings": {
-      "version": "https://registry.npmjs.org/console-control-strings/-/console-control-strings-1.1.0.tgz",
-      "integrity": "sha1-PXz0Rk22RG6mRL9LOVB/mFEAjo4="
-    },
-    "contains-path": {
-      "version": "https://registry.npmjs.org/contains-path/-/contains-path-0.1.0.tgz",
-      "integrity": "sha1-/ozxhP9mcLa67wGp1IYaXL7EEgo=",
-      "dev": true
-    },
-    "core-js": {
-      "version": "https://registry.npmjs.org/core-js/-/core-js-2.5.1.tgz",
-      "integrity": "sha1-rmh03GaTd4m4B1T/VCjfZoGcpQs="
-    },
-    "core-util-is": {
-      "version": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.2.tgz",
-      "integrity": "sha1-tf1UIgqivFq1eqtxQMlAdUUDwac="
-    },
-    "cosmiconfig": {
-      "version": "https://registry.npmjs.org/cosmiconfig/-/cosmiconfig-1.1.0.tgz",
-      "integrity": "sha1-DeoPmATv37kp+7GxiOJVU+oFPTc=",
-      "dev": true,
-      "requires": {
-        "graceful-fs": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.1.11.tgz",
-        "js-yaml": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.10.0.tgz",
-        "minimist": "https://registry.npmjs.org/minimist/-/minimist-1.2.0.tgz",
-        "object-assign": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz",
-        "os-homedir": "https://registry.npmjs.org/os-homedir/-/os-homedir-1.0.2.tgz",
-        "parse-json": "https://registry.npmjs.org/parse-json/-/parse-json-2.2.0.tgz",
-        "pinkie-promise": "https://registry.npmjs.org/pinkie-promise/-/pinkie-promise-2.0.1.tgz",
-        "require-from-string": "https://registry.npmjs.org/require-from-string/-/require-from-string-1.2.1.tgz"
-      }
-    },
-    "crc": {
-      "version": "https://registry.npmjs.org/crc/-/crc-3.5.0.tgz",
-      "integrity": "sha1-mLi6fUiWZbo5efWbITgTdBAaGWQ=",
-      "dev": true
-    },
-    "crc32-stream": {
-      "version": "https://registry.npmjs.org/crc32-stream/-/crc32-stream-2.0.0.tgz",
-      "integrity": "sha1-483TtN8xaN10494/u8t7KX/pCPQ=",
-      "dev": true,
-      "requires": {
-        "crc": "https://registry.npmjs.org/crc/-/crc-3.5.0.tgz",
-        "readable-stream": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.3.tgz"
-      }
-    },
-    "create-react-class": {
-      "version": "https://registry.npmjs.org/create-react-class/-/create-react-class-15.6.2.tgz",
-      "integrity": "sha1-zx7RXxKq1/FO9fLf4F5sQvke8Co=",
-      "requires": {
-        "fbjs": "https://registry.npmjs.org/fbjs/-/fbjs-0.8.16.tgz",
-        "loose-envify": "https://registry.npmjs.org/loose-envify/-/loose-envify-1.3.1.tgz",
-        "object-assign": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz"
-      }
-    },
-    "cross-spawn": {
-      "version": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-3.0.1.tgz",
-      "integrity": "sha1-ElYDfsufDF9549bvE14wdwGEuYI=",
-      "requires": {
-        "lru-cache": "https://registry.npmjs.org/lru-cache/-/lru-cache-4.1.1.tgz",
-        "which": "https://registry.npmjs.org/which/-/which-1.3.0.tgz"
-      }
-    },
-    "cryptiles": {
-      "version": "https://registry.npmjs.org/cryptiles/-/cryptiles-3.1.2.tgz",
-      "integrity": "sha1-qJ+7Ig9c4l7FboxKqKT9e1sNKf4=",
-      "requires": {
-        "boom": "https://registry.npmjs.org/boom/-/boom-5.2.0.tgz"
-      },
-      "dependencies": {
-        "boom": {
-          "version": "https://registry.npmjs.org/boom/-/boom-5.2.0.tgz",
-          "integrity": "sha1-XdnabuOl8wIHdDYpDLcX0/SlTgI=",
-          "requires": {
-            "hoek": "https://registry.npmjs.org/hoek/-/hoek-4.2.0.tgz"
-          }
-        }
-      }
-    },
-    "css": {
-      "version": "https://registry.npmjs.org/css/-/css-2.2.1.tgz",
-      "integrity": "sha1-c6TIHehdtmTU7mdPfUcIXjstVdw=",
-      "dev": true,
-      "requires": {
-        "inherits": "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz",
-        "source-map": "https://registry.npmjs.org/source-map/-/source-map-0.1.43.tgz",
-        "source-map-resolve": "https://registry.npmjs.org/source-map-resolve/-/source-map-resolve-0.3.1.tgz",
-        "urix": "https://registry.npmjs.org/urix/-/urix-0.1.0.tgz"
-      },
-      "dependencies": {
-        "source-map": {
-          "version": "https://registry.npmjs.org/source-map/-/source-map-0.1.43.tgz",
-          "integrity": "sha1-wkvBRspRfBRx9drL4lcbK3+eM0Y=",
+        },
+        "ajv": {
+          "version": "5.5.2",
+          "resolved": "https://registry.npmjs.org/ajv/-/ajv-5.5.2.tgz",
+          "integrity": "sha1-c7Xuyj+rZT49P5Qis0GtQiBdyWU=",
           "dev": true,
           "requires": {
-            "amdefine": "https://registry.npmjs.org/amdefine/-/amdefine-1.0.1.tgz"
+            "co": "4.6.0",
+            "fast-deep-equal": "1.0.0",
+            "fast-json-stable-stringify": "2.0.0",
+            "json-schema-traverse": "0.3.1"
           }
-        }
-      }
-    },
-    "css-parse": {
-      "version": "https://registry.npmjs.org/css-parse/-/css-parse-2.0.0.tgz",
-      "integrity": "sha1-pGjuZnwW2BzPBcWMONKpfHgNv9Q=",
-      "dev": true,
-      "requires": {
-        "css": "https://registry.npmjs.org/css/-/css-2.2.1.tgz"
-      }
-    },
-    "css-selector-tokenizer": {
-      "version": "https://registry.npmjs.org/css-selector-tokenizer/-/css-selector-tokenizer-0.7.0.tgz",
-      "integrity": "sha1-5piEdK6MlTR3v15+/s/OzNnPTIY=",
-      "dev": true,
-      "requires": {
-        "cssesc": "https://registry.npmjs.org/cssesc/-/cssesc-0.1.0.tgz",
-        "fastparse": "https://registry.npmjs.org/fastparse/-/fastparse-1.1.1.tgz",
-        "regexpu-core": "https://registry.npmjs.org/regexpu-core/-/regexpu-core-1.0.0.tgz"
-      }
-    },
-    "css-value": {
-      "version": "https://registry.npmjs.org/css-value/-/css-value-0.0.1.tgz",
-      "integrity": "sha1-Xv1sLupeof1rasV+wEJ7GEUkJOo=",
-      "dev": true
-    },
-    "cssesc": {
-      "version": "https://registry.npmjs.org/cssesc/-/cssesc-0.1.0.tgz",
-      "integrity": "sha1-yBSQPkViM3GgR3tAEJqq++6t27Q=",
-      "dev": true
-    },
-    "currently-unhandled": {
-      "version": "https://registry.npmjs.org/currently-unhandled/-/currently-unhandled-0.4.1.tgz",
-      "integrity": "sha1-mI3zP+qxke95mmE2nddsF635V+o=",
-      "requires": {
-        "array-find-index": "https://registry.npmjs.org/array-find-index/-/array-find-index-1.0.2.tgz"
-      }
-    },
-    "cycle": {
-      "version": "https://registry.npmjs.org/cycle/-/cycle-1.0.3.tgz",
-      "integrity": "sha1-IegLK+hYD5i0aPN5QwZisEbDStI="
-    },
-    "damerau-levenshtein": {
-      "version": "https://registry.npmjs.org/damerau-levenshtein/-/damerau-levenshtein-1.0.4.tgz",
-      "integrity": "sha1-AxkcQyy27qFou3fzpV/9zLiXhRQ=",
-      "dev": true
-    },
-    "dashdash": {
-      "version": "https://registry.npmjs.org/dashdash/-/dashdash-1.14.1.tgz",
-      "integrity": "sha1-hTz6D3y+L+1d4gMmuN1YEDX24vA=",
-      "requires": {
-        "assert-plus": "https://registry.npmjs.org/assert-plus/-/assert-plus-1.0.0.tgz"
-      }
-    },
-    "date-fns": {
-      "version": "https://registry.npmjs.org/date-fns/-/date-fns-1.29.0.tgz",
-      "integrity": "sha1-EuYJzcuTUScxHQTTMzTilgoqVOY=",
-      "dev": true
-    },
-    "date-format": {
-      "version": "https://registry.npmjs.org/date-format/-/date-format-0.0.2.tgz",
-      "integrity": "sha1-+v1Ej3IRXvHitzkVWukvK+bCjdE=",
-      "dev": true
-    },
-    "debug": {
-      "version": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz",
-      "integrity": "sha1-XRKFFd8TT/Mn6QpMk/Tgd6U2NB8=",
-      "requires": {
-        "ms": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz"
-      }
-    },
-    "decamelize": {
-      "version": "https://registry.npmjs.org/decamelize/-/decamelize-1.2.0.tgz",
-      "integrity": "sha1-9lNNFRSCabIDUue+4m9QH5oZEpA="
-    },
-    "decompress-response": {
-      "version": "https://registry.npmjs.org/decompress-response/-/decompress-response-3.3.0.tgz",
-      "integrity": "sha1-gKTdMjdIOEv6JICDYirt7Jgq3/M=",
-      "requires": {
-        "mimic-response": "https://registry.npmjs.org/mimic-response/-/mimic-response-1.0.0.tgz"
-      }
-    },
-    "deep-eql": {
-      "version": "https://registry.npmjs.org/deep-eql/-/deep-eql-3.0.1.tgz",
-      "integrity": "sha1-38lARACtHI/gI+faHfHBR8S0RN8=",
-      "dev": true,
-      "requires": {
-        "type-detect": "https://registry.npmjs.org/type-detect/-/type-detect-4.0.3.tgz"
-      }
-    },
-    "deep-is": {
-      "version": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.3.tgz",
-      "integrity": "sha1-s2nW+128E+7PUk+RsHD+7cNXzzQ=",
-      "dev": true
-    },
-    "deepmerge": {
-      "version": "https://registry.npmjs.org/deepmerge/-/deepmerge-1.5.2.tgz",
-      "integrity": "sha1-EEmdhohEza1P7ghC34x/bwyVp1M="
-    },
-    "define-properties": {
-      "version": "https://registry.npmjs.org/define-properties/-/define-properties-1.1.2.tgz",
-      "integrity": "sha1-g6c/L+pWmJj7c3GTyPhzyvbUXJQ=",
-      "dev": true,
-      "requires": {
-        "foreach": "https://registry.npmjs.org/foreach/-/foreach-2.0.5.tgz",
-        "object-keys": "https://registry.npmjs.org/object-keys/-/object-keys-1.0.11.tgz"
-      }
-    },
-    "del": {
-      "version": "https://registry.npmjs.org/del/-/del-2.2.2.tgz",
-      "integrity": "sha1-wSyYHQZ4RshLyvhiz/kw2Qf/0ag=",
-      "dev": true,
-      "requires": {
-        "globby": "https://registry.npmjs.org/globby/-/globby-5.0.0.tgz",
-        "is-path-cwd": "https://registry.npmjs.org/is-path-cwd/-/is-path-cwd-1.0.0.tgz",
-        "is-path-in-cwd": "https://registry.npmjs.org/is-path-in-cwd/-/is-path-in-cwd-1.0.0.tgz",
-        "object-assign": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz",
-        "pify": "https://registry.npmjs.org/pify/-/pify-2.3.0.tgz",
-        "pinkie-promise": "https://registry.npmjs.org/pinkie-promise/-/pinkie-promise-2.0.1.tgz",
-        "rimraf": "https://registry.npmjs.org/rimraf/-/rimraf-2.6.2.tgz"
-      }
-    },
-    "delayed-stream": {
-      "version": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz",
-      "integrity": "sha1-3zrhmayt+31ECqrgsp4icrJOxhk="
-    },
-    "delegate": {
-      "version": "https://registry.npmjs.org/delegate/-/delegate-3.1.3.tgz",
-      "integrity": "sha1-moJRp3fXAl+qVXN7w7BxdCEnqf0="
-    },
-    "delegates": {
-      "version": "https://registry.npmjs.org/delegates/-/delegates-1.0.0.tgz",
-      "integrity": "sha1-hMbhWbgZBP3KWaDvRM2HDTElD5o="
-    },
-    "doctrine": {
-      "version": "https://registry.npmjs.org/doctrine/-/doctrine-2.0.0.tgz",
-      "integrity": "sha1-xz2NKQnSIpHhoAejlYBNqLZl/mM=",
-      "dev": true,
-      "requires": {
-        "esutils": "https://registry.npmjs.org/esutils/-/esutils-2.0.2.tgz",
-        "isarray": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz"
-      }
-    },
-    "dom-helpers": {
-      "version": "https://registry.npmjs.org/dom-helpers/-/dom-helpers-3.2.1.tgz",
-      "integrity": "sha1-MgPgf+0he9H0JLAZc1WC/Deyglo="
-    },
-    "double-ended-queue": {
-      "version": "https://registry.npmjs.org/double-ended-queue/-/double-ended-queue-2.1.0-0.tgz",
-      "integrity": "sha1-ED01J/0xUo9AGIEwyEHv3XgmTlw="
-    },
-    "duplexer3": {
-      "version": "https://registry.npmjs.org/duplexer3/-/duplexer3-0.1.4.tgz",
-      "integrity": "sha1-7gHdHKwO08vH/b6jfcCo8c4ALOI="
-    },
-    "ecc-jsbn": {
-      "version": "https://registry.npmjs.org/ecc-jsbn/-/ecc-jsbn-0.1.1.tgz",
-      "integrity": "sha1-D8c6ntXw1Tw4GTOYUj735UN3dQU=",
-      "optional": true,
-      "requires": {
-        "jsbn": "https://registry.npmjs.org/jsbn/-/jsbn-0.1.1.tgz"
-      }
-    },
-    "ejs": {
-      "version": "https://registry.npmjs.org/ejs/-/ejs-2.5.7.tgz",
-      "integrity": "sha1-zIcsFoiArjxxiXYv1f/ACJbJUYo=",
-      "dev": true
-    },
-    "electron-to-chromium": {
-      "version": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.3.27.tgz",
-      "integrity": "sha1-eOy4o5kGYYe7N07t412ccFZagD0=",
-      "dev": true
-    },
-    "elegant-spinner": {
-      "version": "https://registry.npmjs.org/elegant-spinner/-/elegant-spinner-1.0.1.tgz",
-      "integrity": "sha1-2wQ1IcldfjA/2PNFvtwzSc+wcp4=",
-      "dev": true
-    },
-    "emoji-regex": {
-      "version": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-6.5.1.tgz",
-      "integrity": "sha1-m66pKbFVVlwR6kHGYm6qZc75ksI=",
-      "dev": true
-    },
-    "encoding": {
-      "version": "https://registry.npmjs.org/encoding/-/encoding-0.1.12.tgz",
-      "integrity": "sha1-U4tm8+5izRq1HsMjgp0flIDHS+s=",
-      "requires": {
-        "iconv-lite": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.19.tgz"
-      }
-    },
-    "end-of-stream": {
-      "version": "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.4.0.tgz",
-      "integrity": "sha1-epDYM+/abPpurA9JSduw+tOmMgY=",
-      "dev": true,
-      "requires": {
-        "once": "https://registry.npmjs.org/once/-/once-1.4.0.tgz"
-      }
-    },
-    "error-ex": {
-      "version": "https://registry.npmjs.org/error-ex/-/error-ex-1.3.1.tgz",
-      "integrity": "sha1-+FWobOYa3E6GIcPNoh56dhLDqNw=",
-      "requires": {
-        "is-arrayish": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.2.1.tgz"
-      }
-    },
-    "es-abstract": {
-      "version": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.9.0.tgz",
-      "integrity": "sha1-aQgpoHyuNrIi5/2bdcDQVz6yUic=",
-      "dev": true,
-      "requires": {
-        "es-to-primitive": "https://registry.npmjs.org/es-to-primitive/-/es-to-primitive-1.1.1.tgz",
-        "function-bind": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.1.tgz",
-        "has": "https://registry.npmjs.org/has/-/has-1.0.1.tgz",
-        "is-callable": "https://registry.npmjs.org/is-callable/-/is-callable-1.1.3.tgz",
-        "is-regex": "https://registry.npmjs.org/is-regex/-/is-regex-1.0.4.tgz"
-      }
-    },
-    "es-to-primitive": {
-      "version": "https://registry.npmjs.org/es-to-primitive/-/es-to-primitive-1.1.1.tgz",
-      "integrity": "sha1-RTVSSKiJeQNLZ5Lhm7gfK3l13Q0=",
-      "dev": true,
-      "requires": {
-        "is-callable": "https://registry.npmjs.org/is-callable/-/is-callable-1.1.3.tgz",
-        "is-date-object": "https://registry.npmjs.org/is-date-object/-/is-date-object-1.0.1.tgz",
-        "is-symbol": "https://registry.npmjs.org/is-symbol/-/is-symbol-1.0.1.tgz"
-      }
-    },
-    "escape-string-regexp": {
-      "version": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz",
-      "integrity": "sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ="
-    },
-    "eslint": {
-      "version": "https://registry.npmjs.org/eslint/-/eslint-4.9.0.tgz",
-      "integrity": "sha1-doedJ0BoJhsZH+Dy9Wx0wvQgjos=",
-      "dev": true,
-      "requires": {
-        "ajv": "https://registry.npmjs.org/ajv/-/ajv-5.3.0.tgz",
-        "babel-code-frame": "https://registry.npmjs.org/babel-code-frame/-/babel-code-frame-6.26.0.tgz",
-        "chalk": "https://registry.npmjs.org/chalk/-/chalk-2.3.0.tgz",
-        "concat-stream": "https://registry.npmjs.org/concat-stream/-/concat-stream-1.6.0.tgz",
-        "cross-spawn": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-5.1.0.tgz",
-        "debug": "https://registry.npmjs.org/debug/-/debug-3.1.0.tgz",
-        "doctrine": "https://registry.npmjs.org/doctrine/-/doctrine-2.0.0.tgz",
-        "eslint-scope": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-3.7.1.tgz",
-        "espree": "https://registry.npmjs.org/espree/-/espree-3.5.1.tgz",
-        "esquery": "https://registry.npmjs.org/esquery/-/esquery-1.0.0.tgz",
-        "estraverse": "https://registry.npmjs.org/estraverse/-/estraverse-4.2.0.tgz",
-        "esutils": "https://registry.npmjs.org/esutils/-/esutils-2.0.2.tgz",
-        "file-entry-cache": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-2.0.0.tgz",
-        "functional-red-black-tree": "https://registry.npmjs.org/functional-red-black-tree/-/functional-red-black-tree-1.0.1.tgz",
-        "glob": "https://registry.npmjs.org/glob/-/glob-7.1.2.tgz",
-        "globals": "https://registry.npmjs.org/globals/-/globals-9.18.0.tgz",
-        "ignore": "https://registry.npmjs.org/ignore/-/ignore-3.3.7.tgz",
-        "imurmurhash": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz",
-        "inquirer": "https://registry.npmjs.org/inquirer/-/inquirer-3.3.0.tgz",
-        "is-resolvable": "https://registry.npmjs.org/is-resolvable/-/is-resolvable-1.0.0.tgz",
-        "js-yaml": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.10.0.tgz",
-        "json-stable-stringify": "https://registry.npmjs.org/json-stable-stringify/-/json-stable-stringify-1.0.1.tgz",
-        "levn": "https://registry.npmjs.org/levn/-/levn-0.3.0.tgz",
-        "lodash": "https://registry.npmjs.org/lodash/-/lodash-4.17.4.tgz",
-        "minimatch": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz",
-        "mkdirp": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.1.tgz",
-        "natural-compare": "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz",
-        "optionator": "https://registry.npmjs.org/optionator/-/optionator-0.8.2.tgz",
-        "path-is-inside": "https://registry.npmjs.org/path-is-inside/-/path-is-inside-1.0.2.tgz",
-        "pluralize": "https://registry.npmjs.org/pluralize/-/pluralize-7.0.0.tgz",
-        "progress": "https://registry.npmjs.org/progress/-/progress-2.0.0.tgz",
-        "require-uncached": "https://registry.npmjs.org/require-uncached/-/require-uncached-1.0.3.tgz",
-        "semver": "https://registry.npmjs.org/semver/-/semver-5.4.1.tgz",
-        "strip-ansi": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-4.0.0.tgz",
-        "strip-json-comments": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-2.0.1.tgz",
-        "table": "https://registry.npmjs.org/table/-/table-4.0.2.tgz",
-        "text-table": "https://registry.npmjs.org/text-table/-/text-table-0.2.0.tgz"
-      },
-      "dependencies": {
+        },
+        "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
+        },
+        "ansi-escapes": {
+          "version": "3.0.0",
+          "resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-3.0.0.tgz",
+          "integrity": "sha512-O/klc27mWNUigtv0F8NJWbLF00OcegQalkqKURWdosW08YZKi4m6CnSUSvIZG1otNJbTWhN01Hhz389DW7mvDQ==",
+          "dev": true
+        },
         "ansi-regex": {
-          "version": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-3.0.0.tgz",
-          "integrity": "sha1-7QMXwyIGT3lGbAKWa922Bas32Zg=",
+          "version": "2.1.1",
+          "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-2.1.1.tgz",
+          "integrity": "sha1-w7M6te42DYbg5ijwRorn7yfWVN8=",
           "dev": true
         },
         "ansi-styles": {
-          "version": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.0.tgz",
-          "integrity": "sha1-wVm41b4PnlpvNG2rlPFs4CIWG4g=",
+          "version": "2.2.1",
+          "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-2.2.1.tgz",
+          "integrity": "sha1-tDLdM1i2NM914eRmQ2gkBTPB3b4=",
+          "dev": true
+        },
+        "argparse": {
+          "version": "1.0.9",
+          "resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.9.tgz",
+          "integrity": "sha1-c9g7wmP4bpf4zE9rrhsOkKfSLIY=",
           "dev": true,
           "requires": {
-            "color-convert": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.0.tgz"
+            "sprintf-js": "1.0.3"
           }
         },
-        "chalk": {
-          "version": "https://registry.npmjs.org/chalk/-/chalk-2.3.0.tgz",
-          "integrity": "sha1-tepI78nBeT3MybR2fJORTT8tUro=",
+        "array-union": {
+          "version": "1.0.2",
+          "resolved": "https://registry.npmjs.org/array-union/-/array-union-1.0.2.tgz",
+          "integrity": "sha1-mjRBDk9OPaI96jdb5b5w8kd47Dk=",
           "dev": true,
           "requires": {
-            "ansi-styles": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.0.tgz",
-            "escape-string-regexp": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz",
-            "supports-color": "https://registry.npmjs.org/supports-color/-/supports-color-4.5.0.tgz"
+            "array-uniq": "1.0.3"
           }
         },
-        "cross-spawn": {
-          "version": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-5.1.0.tgz",
-          "integrity": "sha1-6L0O/uWPz/b4+UUQoKVUu/ojVEk=",
+        "array-uniq": {
+          "version": "1.0.3",
+          "resolved": "https://registry.npmjs.org/array-uniq/-/array-uniq-1.0.3.tgz",
+          "integrity": "sha1-r2rId6Jcx/dOBYiUdThY39sk/bY=",
+          "dev": true
+        },
+        "arrify": {
+          "version": "1.0.1",
+          "resolved": "https://registry.npmjs.org/arrify/-/arrify-1.0.1.tgz",
+          "integrity": "sha1-iYUI2iIm84DfkEcoRWhJwVAaSw0=",
+          "dev": true
+        },
+        "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": {
-            "lru-cache": "https://registry.npmjs.org/lru-cache/-/lru-cache-4.1.1.tgz",
-            "shebang-command": "https://registry.npmjs.org/shebang-command/-/shebang-command-1.2.0.tgz",
-            "which": "https://registry.npmjs.org/which/-/which-1.3.0.tgz"
+            "chalk": "1.1.3",
+            "esutils": "2.0.2",
+            "js-tokens": "3.0.2"
+          },
+          "dependencies": {
+            "chalk": {
+              "version": "1.1.3",
+              "resolved": "https://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.5",
+                "has-ansi": "2.0.0",
+                "strip-ansi": "3.0.1",
+                "supports-color": "2.0.0"
+              }
+            },
+            "strip-ansi": {
+              "version": "3.0.1",
+              "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-3.0.1.tgz",
+              "integrity": "sha1-ajhfuIU9lS1f8F0Oiq+UJ43GPc8=",
+              "dev": true,
+              "requires": {
+                "ansi-regex": "2.1.1"
+              }
+            }
           }
         },
-        "debug": {
-          "version": "https://registry.npmjs.org/debug/-/debug-3.1.0.tgz",
-          "integrity": "sha1-W7WgZyYotkFJVmuhaBnmFRjGcmE=",
+        "balanced-match": {
+          "version": "1.0.0",
+          "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.0.tgz",
+          "integrity": "sha1-ibTRmasr7kneFk6gK4nORi1xt2c=",
+          "dev": true
+        },
+        "brace-expansion": {
+          "version": "1.1.8",
+          "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.8.tgz",
+          "integrity": "sha1-wHshHHyVLsH479Uad+8NHTmQopI=",
           "dev": true,
           "requires": {
-            "ms": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz"
+            "balanced-match": "1.0.0",
+            "concat-map": "0.0.1"
           }
         },
-        "strip-ansi": {
-          "version": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-4.0.0.tgz",
-          "integrity": "sha1-qEeQIusaw2iocTibY1JixQXuNo8=",
+        "caller-path": {
+          "version": "0.1.0",
+          "resolved": "https://registry.npmjs.org/caller-path/-/caller-path-0.1.0.tgz",
+          "integrity": "sha1-lAhe9jWB7NPaqSREqP6U6CV3dR8=",
           "dev": true,
           "requires": {
-            "ansi-regex": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-3.0.0.tgz"
+            "callsites": "0.2.0"
           }
         },
-        "supports-color": {
-          "version": "https://registry.npmjs.org/supports-color/-/supports-color-4.5.0.tgz",
-          "integrity": "sha1-vnoN5ITexcXN34s9WRJQRJEvY1s=",
+        "callsites": {
+          "version": "0.2.0",
+          "resolved": "https://registry.npmjs.org/callsites/-/callsites-0.2.0.tgz",
+          "integrity": "sha1-r6uWJikQp/M8GaV3WCXGnzTjUMo=",
+          "dev": true
+        },
+        "chalk": {
+          "version": "2.3.0",
+          "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.3.0.tgz",
+          "integrity": "sha512-Az5zJR2CBujap2rqXGaJKaPHyJ0IrUimvYNX+ncCy8PJP4ltOGTrHUIo097ZaL2zMeKYpiCdqDvS6zdrTFok3Q==",
           "dev": true,
           "requires": {
-            "has-flag": "https://registry.npmjs.org/has-flag/-/has-flag-2.0.0.tgz"
+            "ansi-styles": "3.2.0",
+            "escape-string-regexp": "1.0.5",
+            "supports-color": "4.5.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.1"
+              }
+            },
+            "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": "https://registry.npmjs.org/eslint-config-airbnb/-/eslint-config-airbnb-16.1.0.tgz",
-      "integrity": "sha1-JUa/sCzJ/pIoS/FyPM8uh7xFykY=",
-      "dev": true,
-      "requires": {
-        "eslint-config-airbnb-base": "https://registry.npmjs.org/eslint-config-airbnb-base/-/eslint-config-airbnb-base-12.1.0.tgz"
-      }
-    },
-    "eslint-config-airbnb-base": {
-      "version": "https://registry.npmjs.org/eslint-config-airbnb-base/-/eslint-config-airbnb-base-12.1.0.tgz",
-      "integrity": "sha1-OGRB5UoSzNlXsKklZKS6/r10eUQ=",
-      "dev": true,
-      "requires": {
-        "eslint-restricted-globals": "https://registry.npmjs.org/eslint-restricted-globals/-/eslint-restricted-globals-0.1.1.tgz"
-      }
-    },
-    "eslint-import-resolver-node": {
-      "version": "https://registry.npmjs.org/eslint-import-resolver-node/-/eslint-import-resolver-node-0.3.1.tgz",
-      "integrity": "sha1-RCJXTN5mqaewmZOO5NUIoZng48w=",
-      "dev": true,
-      "requires": {
-        "debug": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz",
-        "resolve": "https://registry.npmjs.org/resolve/-/resolve-1.5.0.tgz"
-      }
-    },
-    "eslint-module-utils": {
-      "version": "https://registry.npmjs.org/eslint-module-utils/-/eslint-module-utils-2.1.1.tgz",
-      "integrity": "sha1-q67IJBd2E7ipWymWOeG2+s9HNEk=",
-      "dev": true,
-      "requires": {
-        "debug": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz",
-        "pkg-dir": "https://registry.npmjs.org/pkg-dir/-/pkg-dir-1.0.0.tgz"
-      }
-    },
-    "eslint-plugin-import": {
-      "version": "https://registry.npmjs.org/eslint-plugin-import/-/eslint-plugin-import-2.8.0.tgz",
-      "integrity": "sha1-+htu8x/LPFAcCYWcG4bx/FuYaJQ=",
-      "dev": true,
-      "requires": {
-        "builtin-modules": "https://registry.npmjs.org/builtin-modules/-/builtin-modules-1.1.1.tgz",
-        "contains-path": "https://registry.npmjs.org/contains-path/-/contains-path-0.1.0.tgz",
-        "debug": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz",
-        "doctrine": "https://registry.npmjs.org/doctrine/-/doctrine-1.5.0.tgz",
-        "eslint-import-resolver-node": "https://registry.npmjs.org/eslint-import-resolver-node/-/eslint-import-resolver-node-0.3.1.tgz",
-        "eslint-module-utils": "https://registry.npmjs.org/eslint-module-utils/-/eslint-module-utils-2.1.1.tgz",
-        "has": "https://registry.npmjs.org/has/-/has-1.0.1.tgz",
-        "lodash.cond": "https://registry.npmjs.org/lodash.cond/-/lodash.cond-4.5.2.tgz",
-        "minimatch": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz",
-        "read-pkg-up": "https://registry.npmjs.org/read-pkg-up/-/read-pkg-up-2.0.0.tgz"
-      },
-      "dependencies": {
-        "doctrine": {
-          "version": "https://registry.npmjs.org/doctrine/-/doctrine-1.5.0.tgz",
-          "integrity": "sha1-N53Ocw9hZvds76TmcHoVmwLFpvo=",
+        },
+        "circular-json": {
+          "version": "0.3.3",
+          "resolved": "https://registry.npmjs.org/circular-json/-/circular-json-0.3.3.tgz",
+          "integrity": "sha512-UZK3NBx2Mca+b5LsG7bY183pHWt5Y1xts4P3Pz7ENTwGVnJOUWbRb3ocjvX7hx9tq/yTAdclXm9sZ38gNuem4A==",
+          "dev": true
+        },
+        "cli-cursor": {
+          "version": "2.1.0",
+          "resolved": "https://registry.npmjs.org/cli-cursor/-/cli-cursor-2.1.0.tgz",
+          "integrity": "sha1-s12sN2R5+sw+lHR9QdDQ9SOP/LU=",
           "dev": true,
           "requires": {
-            "esutils": "https://registry.npmjs.org/esutils/-/esutils-2.0.2.tgz",
-            "isarray": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz"
+            "restore-cursor": "2.0.0"
           }
         },
-        "find-up": {
-          "version": "https://registry.npmjs.org/find-up/-/find-up-2.1.0.tgz",
-          "integrity": "sha1-RdG35QbHF93UgndaK3eSCjwMV6c=",
+        "cli-width": {
+          "version": "2.2.0",
+          "resolved": "https://registry.npmjs.org/cli-width/-/cli-width-2.2.0.tgz",
+          "integrity": "sha1-/xnt6Kml5XkyQUewwR8PvLq+1jk=",
+          "dev": true
+        },
+        "co": {
+          "version": "4.6.0",
+          "resolved": "https://registry.npmjs.org/co/-/co-4.6.0.tgz",
+          "integrity": "sha1-bqa989hTrlTMuOR7+gvz+QMfsYQ=",
+          "dev": true
+        },
+        "color-convert": {
+          "version": "1.9.1",
+          "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.1.tgz",
+          "integrity": "sha512-mjGanIiwQJskCC18rPR6OmrZ6fm2Lc7PeGFYwCmy5J34wC6F1PzdGL6xeMfmgicfYcNLGuVFA3WzXtIDCQSZxQ==",
           "dev": true,
           "requires": {
-            "locate-path": "https://registry.npmjs.org/locate-path/-/locate-path-2.0.0.tgz"
+            "color-name": "1.1.3"
           }
         },
-        "load-json-file": {
-          "version": "https://registry.npmjs.org/load-json-file/-/load-json-file-2.0.0.tgz",
-          "integrity": "sha1-eUfkIUmvgNaWy/eXvKq8/h/inKg=",
+        "color-name": {
+          "version": "1.1.3",
+          "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz",
+          "integrity": "sha1-p9BVi9icQveV3UIyj3QIMcpTvCU=",
+          "dev": true
+        },
+        "concat-map": {
+          "version": "0.0.1",
+          "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz",
+          "integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=",
+          "dev": true
+        },
+        "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": {
-            "graceful-fs": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.1.11.tgz",
-            "parse-json": "https://registry.npmjs.org/parse-json/-/parse-json-2.2.0.tgz",
-            "pify": "https://registry.npmjs.org/pify/-/pify-2.3.0.tgz",
-            "strip-bom": "https://registry.npmjs.org/strip-bom/-/strip-bom-3.0.0.tgz"
+            "inherits": "2.0.3",
+            "readable-stream": "2.3.3",
+            "typedarray": "0.0.6"
           }
         },
-        "path-type": {
-          "version": "https://registry.npmjs.org/path-type/-/path-type-2.0.0.tgz",
-          "integrity": "sha1-8BLMuEFbcJb8LaoQVMPXI4lZTHM=",
+        "core-util-is": {
+          "version": "1.0.2",
+          "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.2.tgz",
+          "integrity": "sha1-tf1UIgqivFq1eqtxQMlAdUUDwac=",
+          "dev": true
+        },
+        "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=",
           "dev": true,
           "requires": {
-            "pify": "https://registry.npmjs.org/pify/-/pify-2.3.0.tgz"
+            "lru-cache": "4.1.1",
+            "shebang-command": "1.2.0",
+            "which": "1.3.0"
           }
         },
-        "read-pkg": {
-          "version": "https://registry.npmjs.org/read-pkg/-/read-pkg-2.0.0.tgz",
-          "integrity": "sha1-jvHAYjxqbbDcZxPEv6xGMysjaPg=",
+        "debug": {
+          "version": "3.1.0",
+          "resolved": "https://registry.npmjs.org/debug/-/debug-3.1.0.tgz",
+          "integrity": "sha512-OX8XqP7/1a9cqkxYw2yXss15f26NKWBpDXQd0/uK/KPqdQhxbPa994hnzjcE2VqQpDslf55723cKPUOGSmMY3g==",
           "dev": true,
           "requires": {
-            "load-json-file": "https://registry.npmjs.org/load-json-file/-/load-json-file-2.0.0.tgz",
-            "normalize-package-data": "https://registry.npmjs.org/normalize-package-data/-/normalize-package-data-2.4.0.tgz",
-            "path-type": "https://registry.npmjs.org/path-type/-/path-type-2.0.0.tgz"
+            "ms": "2.0.0"
           }
         },
-        "read-pkg-up": {
-          "version": "https://registry.npmjs.org/read-pkg-up/-/read-pkg-up-2.0.0.tgz",
-          "integrity": "sha1-a3KoBImE4MQeeVEP1en6mbO1Sb4=",
+        "deep-is": {
+          "version": "0.1.3",
+          "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.3.tgz",
+          "integrity": "sha1-s2nW+128E+7PUk+RsHD+7cNXzzQ=",
+          "dev": true
+        },
+        "del": {
+          "version": "2.2.2",
+          "resolved": "https://registry.npmjs.org/del/-/del-2.2.2.tgz",
+          "integrity": "sha1-wSyYHQZ4RshLyvhiz/kw2Qf/0ag=",
           "dev": true,
           "requires": {
-            "find-up": "https://registry.npmjs.org/find-up/-/find-up-2.1.0.tgz",
-            "read-pkg": "https://registry.npmjs.org/read-pkg/-/read-pkg-2.0.0.tgz"
+            "globby": "5.0.0",
+            "is-path-cwd": "1.0.0",
+            "is-path-in-cwd": "1.0.0",
+            "object-assign": "4.1.1",
+            "pify": "2.3.0",
+            "pinkie-promise": "2.0.1",
+            "rimraf": "2.6.2"
           }
         },
-        "strip-bom": {
-          "version": "https://registry.npmjs.org/strip-bom/-/strip-bom-3.0.0.tgz",
-          "integrity": "sha1-IzTBjpx1n3vdVv3vfprj1YjmjtM=",
+        "doctrine": {
+          "version": "2.1.0",
+          "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-2.1.0.tgz",
+          "integrity": "sha512-35mSku4ZXK0vfCuHEDAwt55dg2jNajHZ1odvF+8SSr82EsZY4QmXfuWso8oEd8zRhVObSN18aM0CjSdoBX7zIw==",
+          "dev": true,
+          "requires": {
+            "esutils": "2.0.2"
+          }
+        },
+        "escape-string-regexp": {
+          "version": "1.0.5",
+          "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz",
+          "integrity": "sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ=",
           "dev": true
-        }
-      }
-    },
-    "eslint-plugin-jsx-a11y": {
-      "version": "https://registry.npmjs.org/eslint-plugin-jsx-a11y/-/eslint-plugin-jsx-a11y-6.0.2.tgz",
-      "integrity": "sha1-ZZJ3p1iwNsMFp+ShMFfDAc075z8=",
-      "dev": true,
-      "requires": {
-        "aria-query": "https://registry.npmjs.org/aria-query/-/aria-query-0.7.0.tgz",
-        "array-includes": "https://registry.npmjs.org/array-includes/-/array-includes-3.0.3.tgz",
-        "ast-types-flow": "https://registry.npmjs.org/ast-types-flow/-/ast-types-flow-0.0.7.tgz",
-        "axobject-query": "https://registry.npmjs.org/axobject-query/-/axobject-query-0.1.0.tgz",
-        "damerau-levenshtein": "https://registry.npmjs.org/damerau-levenshtein/-/damerau-levenshtein-1.0.4.tgz",
-        "emoji-regex": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-6.5.1.tgz",
-        "jsx-ast-utils": "https://registry.npmjs.org/jsx-ast-utils/-/jsx-ast-utils-1.4.1.tgz"
-      }
-    },
-    "eslint-plugin-react": {
-      "version": "https://registry.npmjs.org/eslint-plugin-react/-/eslint-plugin-react-7.4.0.tgz",
-      "integrity": "sha1-MAqVhhuXKcCH02LdZKvMNRp0Nko=",
-      "dev": true,
-      "requires": {
-        "doctrine": "https://registry.npmjs.org/doctrine/-/doctrine-2.0.0.tgz",
-        "has": "https://registry.npmjs.org/has/-/has-1.0.1.tgz",
-        "jsx-ast-utils": "https://registry.npmjs.org/jsx-ast-utils/-/jsx-ast-utils-2.0.1.tgz",
-        "prop-types": "https://registry.npmjs.org/prop-types/-/prop-types-15.6.0.tgz"
-      },
-      "dependencies": {
-        "jsx-ast-utils": {
-          "version": "https://registry.npmjs.org/jsx-ast-utils/-/jsx-ast-utils-2.0.1.tgz",
-          "integrity": "sha1-6AGxs5mF4g//yHtA43SAgOLcrH8=",
+        },
+        "eslint-scope": {
+          "version": "3.7.1",
+          "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-3.7.1.tgz",
+          "integrity": "sha1-PWPD7f2gLgbgGkUq2IyqzHzctug=",
           "dev": true,
           "requires": {
-            "array-includes": "https://registry.npmjs.org/array-includes/-/array-includes-3.0.3.tgz"
+            "esrecurse": "4.2.0",
+            "estraverse": "4.2.0"
           }
-        }
-      }
-    },
-    "eslint-restricted-globals": {
-      "version": "https://registry.npmjs.org/eslint-restricted-globals/-/eslint-restricted-globals-0.1.1.tgz",
-      "integrity": "sha1-NfDVy8ZMLj7WLpO0saevBbp+1Nc=",
-      "dev": true
-    },
-    "eslint-scope": {
-      "version": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-3.7.1.tgz",
-      "integrity": "sha1-PWPD7f2gLgbgGkUq2IyqzHzctug=",
-      "dev": true,
-      "requires": {
-        "esrecurse": "https://registry.npmjs.org/esrecurse/-/esrecurse-4.2.0.tgz",
-        "estraverse": "https://registry.npmjs.org/estraverse/-/estraverse-4.2.0.tgz"
-      }
-    },
-    "espree": {
-      "version": "https://registry.npmjs.org/espree/-/espree-3.5.1.tgz",
-      "integrity": "sha1-DJiLirRttTEAoZVK5LqZXd0n2H4=",
-      "dev": true,
-      "requires": {
-        "acorn": "https://registry.npmjs.org/acorn/-/acorn-5.2.1.tgz",
-        "acorn-jsx": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-3.0.1.tgz"
-      }
-    },
-    "esprima": {
-      "version": "https://registry.npmjs.org/esprima/-/esprima-4.0.0.tgz",
-      "integrity": "sha1-RJnt3NERDgshi6zy+n9/WfVcqAQ=",
-      "dev": true
-    },
-    "esquery": {
-      "version": "https://registry.npmjs.org/esquery/-/esquery-1.0.0.tgz",
-      "integrity": "sha1-z7qLV9f7qT8XKYqKAGoEzaE9gPo=",
-      "dev": true,
-      "requires": {
-        "estraverse": "https://registry.npmjs.org/estraverse/-/estraverse-4.2.0.tgz"
-      }
-    },
-    "esrecurse": {
-      "version": "https://registry.npmjs.org/esrecurse/-/esrecurse-4.2.0.tgz",
-      "integrity": "sha1-+pVo2Y04I/mkHZHpAtyrnqblsWM=",
-      "dev": true,
-      "requires": {
-        "estraverse": "https://registry.npmjs.org/estraverse/-/estraverse-4.2.0.tgz",
-        "object-assign": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz"
-      }
-    },
-    "estraverse": {
-      "version": "https://registry.npmjs.org/estraverse/-/estraverse-4.2.0.tgz",
-      "integrity": "sha1-De4/7TH81GlhjOc0IJn8GvoL2xM=",
-      "dev": true
-    },
-    "esutils": {
-      "version": "https://registry.npmjs.org/esutils/-/esutils-2.0.2.tgz",
-      "integrity": "sha1-Cr9PHKpbyx96nYrMbepPqqBLrJs=",
-      "dev": true
-    },
-    "eventemitter2": {
-      "version": "https://registry.npmjs.org/eventemitter2/-/eventemitter2-4.1.2.tgz",
-      "integrity": "sha1-DhqEd6+CGm7zmVsxG/dMI6UkfxU="
-    },
-    "execa": {
-      "version": "https://registry.npmjs.org/execa/-/execa-0.8.0.tgz",
-      "integrity": "sha1-2NdrvBtVIX7RkP1t1J08d07PyNo=",
-      "dev": true,
-      "requires": {
-        "cross-spawn": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-5.1.0.tgz",
-        "get-stream": "https://registry.npmjs.org/get-stream/-/get-stream-3.0.0.tgz",
-        "is-stream": "https://registry.npmjs.org/is-stream/-/is-stream-1.1.0.tgz",
-        "npm-run-path": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-2.0.2.tgz",
-        "p-finally": "https://registry.npmjs.org/p-finally/-/p-finally-1.0.0.tgz",
-        "signal-exit": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.2.tgz",
-        "strip-eof": "https://registry.npmjs.org/strip-eof/-/strip-eof-1.0.0.tgz"
-      },
-      "dependencies": {
-        "cross-spawn": {
-          "version": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-5.1.0.tgz",
-          "integrity": "sha1-6L0O/uWPz/b4+UUQoKVUu/ojVEk=",
+        },
+        "espree": {
+          "version": "3.5.2",
+          "resolved": "https://registry.npmjs.org/espree/-/espree-3.5.2.tgz",
+          "integrity": "sha512-sadKeYwaR/aJ3stC2CdvgXu1T16TdYN+qwCpcWbMnGJ8s0zNWemzrvb2GbD4OhmJ/fwpJjudThAlLobGbWZbCQ==",
           "dev": true,
           "requires": {
-            "lru-cache": "https://registry.npmjs.org/lru-cache/-/lru-cache-4.1.1.tgz",
-            "shebang-command": "https://registry.npmjs.org/shebang-command/-/shebang-command-1.2.0.tgz",
-            "which": "https://registry.npmjs.org/which/-/which-1.3.0.tgz"
+            "acorn": "5.3.0",
+            "acorn-jsx": "3.0.1"
           }
-        }
-      }
-    },
-    "exenv": {
-      "version": "https://registry.npmjs.org/exenv/-/exenv-1.2.2.tgz",
-      "integrity": "sha1-KueOhdmJQVhnCwPUe+wfA72Ru50="
-    },
-    "exit": {
-      "version": "https://registry.npmjs.org/exit/-/exit-0.1.2.tgz",
-      "integrity": "sha1-BjJjj42HfMghB9MKD/8aF8uhzQw=",
-      "dev": true
-    },
-    "exit-hook": {
-      "version": "https://registry.npmjs.org/exit-hook/-/exit-hook-1.1.1.tgz",
-      "integrity": "sha1-8FyiM7SMBdVP/wd2XfhQfpXAL/g=",
-      "dev": true
-    },
-    "extend": {
-      "version": "https://registry.npmjs.org/extend/-/extend-3.0.1.tgz",
-      "integrity": "sha1-p1Xqe8Gt/MWjHOfnYtuq3F5jZEQ="
-    },
-    "external-editor": {
-      "version": "https://registry.npmjs.org/external-editor/-/external-editor-2.0.5.tgz",
-      "integrity": "sha1-UsJJo5gbm6GHx8rPW+tQvx2Rprw=",
-      "dev": true,
-      "requires": {
-        "iconv-lite": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.19.tgz",
-        "jschardet": "https://registry.npmjs.org/jschardet/-/jschardet-1.6.0.tgz",
-        "tmp": "https://registry.npmjs.org/tmp/-/tmp-0.0.33.tgz"
-      }
-    },
-    "extsprintf": {
-      "version": "https://registry.npmjs.org/extsprintf/-/extsprintf-1.3.0.tgz",
-      "integrity": "sha1-lpGEQOMEGnpBT4xS48V06zw+HgU="
-    },
-    "eyes": {
-      "version": "https://registry.npmjs.org/eyes/-/eyes-0.1.8.tgz",
-      "integrity": "sha1-Ys8SAjTGg3hdkCNIqADvPgzCC8A="
-    },
-    "fast-deep-equal": {
-      "version": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-1.0.0.tgz",
-      "integrity": "sha1-liVqO8l1WV6zbYLpkp0GDYk0Of8="
-    },
-    "fast-json-stable-stringify": {
-      "version": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.0.0.tgz",
-      "integrity": "sha1-1RQsDK7msRifh9OnYREGT4bIu/I="
-    },
-    "fast-levenshtein": {
-      "version": "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz",
-      "integrity": "sha1-PYpcZog6FqMMqGQ+hR8Zuqd5eRc=",
-      "dev": true
-    },
-    "fastparse": {
-      "version": "https://registry.npmjs.org/fastparse/-/fastparse-1.1.1.tgz",
-      "integrity": "sha1-0eJkOzipTXWDtHkGDmxK/8lAcfg=",
-      "dev": true
-    },
-    "fbjs": {
-      "version": "https://registry.npmjs.org/fbjs/-/fbjs-0.8.16.tgz",
-      "integrity": "sha1-XmdDL1UNxBtXK/VYR7ispk5TN9s=",
-      "requires": {
-        "core-js": "https://registry.npmjs.org/core-js/-/core-js-1.2.7.tgz",
-        "isomorphic-fetch": "https://registry.npmjs.org/isomorphic-fetch/-/isomorphic-fetch-2.2.1.tgz",
-        "loose-envify": "https://registry.npmjs.org/loose-envify/-/loose-envify-1.3.1.tgz",
-        "object-assign": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz",
-        "promise": "https://registry.npmjs.org/promise/-/promise-7.3.1.tgz",
-        "setimmediate": "https://registry.npmjs.org/setimmediate/-/setimmediate-1.0.5.tgz",
-        "ua-parser-js": "https://registry.npmjs.org/ua-parser-js/-/ua-parser-js-0.7.17.tgz"
-      },
-      "dependencies": {
-        "core-js": {
-          "version": "https://registry.npmjs.org/core-js/-/core-js-1.2.7.tgz",
-          "integrity": "sha1-ZSKUwUZR2yj6k70tX/KYOk8IxjY="
-        }
-      }
-    },
-    "fibers": {
-      "version": "https://registry.npmjs.org/fibers/-/fibers-2.0.0.tgz",
-      "integrity": "sha1-8m0Krx+ZmV++HLPzQO+sCL2p3Es=",
-      "dev": true
-    },
-    "figures": {
-      "version": "https://registry.npmjs.org/figures/-/figures-2.0.0.tgz",
-      "integrity": "sha1-OrGi0qYsi/tDGgyUy3l6L84nyWI=",
-      "dev": true,
-      "requires": {
-        "escape-string-regexp": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz"
-      }
-    },
-    "file-entry-cache": {
-      "version": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-2.0.0.tgz",
-      "integrity": "sha1-w5KZDD5oR4PYOLjISkXYoEhFg2E=",
-      "dev": true,
-      "requires": {
-        "flat-cache": "https://registry.npmjs.org/flat-cache/-/flat-cache-1.3.0.tgz",
-        "object-assign": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz"
-      }
-    },
-    "find-up": {
-      "version": "https://registry.npmjs.org/find-up/-/find-up-1.1.2.tgz",
-      "integrity": "sha1-ay6YIrGizgpgq2TWEOzK1TyyTQ8=",
-      "requires": {
-        "path-exists": "https://registry.npmjs.org/path-exists/-/path-exists-2.1.0.tgz",
-        "pinkie-promise": "https://registry.npmjs.org/pinkie-promise/-/pinkie-promise-2.0.1.tgz"
-      }
-    },
-    "flat": {
-      "version": "https://registry.npmjs.org/flat/-/flat-4.0.0.tgz",
-      "integrity": "sha1-Orx/O1iOZM533EL9Wao1gGYi/qg=",
-      "requires": {
-        "is-buffer": "https://registry.npmjs.org/is-buffer/-/is-buffer-1.1.6.tgz"
-      }
-    },
-    "flat-cache": {
-      "version": "https://registry.npmjs.org/flat-cache/-/flat-cache-1.3.0.tgz",
-      "integrity": "sha1-0wMLMrOBVPTjt+nHCfSQ9++XxIE=",
-      "dev": true,
-      "requires": {
-        "circular-json": "https://registry.npmjs.org/circular-json/-/circular-json-0.3.3.tgz",
-        "del": "https://registry.npmjs.org/del/-/del-2.2.2.tgz",
-        "graceful-fs": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.1.11.tgz",
-        "write": "https://registry.npmjs.org/write/-/write-0.2.1.tgz"
-      }
-    },
-    "flatten": {
-      "version": "https://registry.npmjs.org/flatten/-/flatten-1.0.2.tgz",
-      "integrity": "sha1-2uRqnXj74lKSJYzB54CkHZXAN4I=",
-      "dev": true
-    },
-    "foreach": {
-      "version": "https://registry.npmjs.org/foreach/-/foreach-2.0.5.tgz",
-      "integrity": "sha1-C+4AUBiusmDQo6865ljdATbsG5k=",
-      "dev": true
-    },
-    "forever-agent": {
-      "version": "https://registry.npmjs.org/forever-agent/-/forever-agent-0.6.1.tgz",
-      "integrity": "sha1-+8cfDEGt6zf5bFd60e1C2P2sypE="
-    },
-    "form-data": {
-      "version": "https://registry.npmjs.org/form-data/-/form-data-2.3.1.tgz",
-      "integrity": "sha1-b7lPvXGIUwbXPRXMSX/kzE7NRL8=",
-      "requires": {
-        "asynckit": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz",
-        "combined-stream": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.5.tgz",
-        "mime-types": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.17.tgz"
-      }
-    },
-    "fs.realpath": {
-      "version": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz",
-      "integrity": "sha1-FQStJSMVjKpA20onh8sBQRmU6k8="
-    },
-    "fstream": {
-      "version": "https://registry.npmjs.org/fstream/-/fstream-1.0.11.tgz",
-      "integrity": "sha1-XB+x8RdHcRTwYyoOtLcbPLD9MXE=",
-      "requires": {
-        "graceful-fs": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.1.11.tgz",
-        "inherits": "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz",
-        "mkdirp": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.1.tgz",
-        "rimraf": "https://registry.npmjs.org/rimraf/-/rimraf-2.6.2.tgz"
-      }
-    },
-    "function-bind": {
-      "version": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.1.tgz",
-      "integrity": "sha1-pWiZ0+o8m6uHS7l3O3xe3pL0iV0=",
-      "dev": true
-    },
-    "functional-red-black-tree": {
-      "version": "https://registry.npmjs.org/functional-red-black-tree/-/functional-red-black-tree-1.0.1.tgz",
-      "integrity": "sha1-GwqzvVU7Kg1jmdKcDj6gslIHgyc=",
-      "dev": true
-    },
-    "gauge": {
-      "version": "https://registry.npmjs.org/gauge/-/gauge-2.7.4.tgz",
-      "integrity": "sha1-LANAXHU4w51+s3sxcCLjJfsBi/c=",
-      "requires": {
-        "aproba": "https://registry.npmjs.org/aproba/-/aproba-1.2.0.tgz",
-        "console-control-strings": "https://registry.npmjs.org/console-control-strings/-/console-control-strings-1.1.0.tgz",
-        "has-unicode": "https://registry.npmjs.org/has-unicode/-/has-unicode-2.0.1.tgz",
-        "object-assign": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz",
-        "signal-exit": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.2.tgz",
-        "string-width": "https://registry.npmjs.org/string-width/-/string-width-1.0.2.tgz",
-        "strip-ansi": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-3.0.1.tgz",
-        "wide-align": "https://registry.npmjs.org/wide-align/-/wide-align-1.1.2.tgz"
-      }
-    },
-    "gaze": {
-      "version": "https://registry.npmjs.org/gaze/-/gaze-1.1.2.tgz",
-      "integrity": "sha1-hHIkZ3rbiHDWeSV+0ziP22HkAQU=",
-      "requires": {
-        "globule": "https://registry.npmjs.org/globule/-/globule-1.2.0.tgz"
-      }
-    },
-    "get-caller-file": {
-      "version": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-1.0.2.tgz",
-      "integrity": "sha1-9wLmMSfn4jHBYKgMFVSstw1QR+U="
-    },
-    "get-func-name": {
-      "version": "https://registry.npmjs.org/get-func-name/-/get-func-name-2.0.0.tgz",
-      "integrity": "sha1-6td0q+5y4gQJQzoGY2YCPdaIekE=",
-      "dev": true
-    },
-    "get-own-enumerable-property-symbols": {
-      "version": "https://registry.npmjs.org/get-own-enumerable-property-symbols/-/get-own-enumerable-property-symbols-2.0.1.tgz",
-      "integrity": "sha1-XErYfyg0xLm06EVJ3B4GUPs4wks=",
-      "dev": true
-    },
-    "get-stdin": {
-      "version": "https://registry.npmjs.org/get-stdin/-/get-stdin-4.0.1.tgz",
-      "integrity": "sha1-uWjGsKBDhDJJAui/Gl3zJXmkUP4="
-    },
-    "get-stream": {
-      "version": "https://registry.npmjs.org/get-stream/-/get-stream-3.0.0.tgz",
-      "integrity": "sha1-jpQ9E1jcN1VQVOy+LtsFqhdO3hQ="
-    },
-    "getpass": {
-      "version": "https://registry.npmjs.org/getpass/-/getpass-0.1.7.tgz",
-      "integrity": "sha1-Xv+OPmhNVprkyysSgmBOi6YhSfo=",
-      "requires": {
-        "assert-plus": "https://registry.npmjs.org/assert-plus/-/assert-plus-1.0.0.tgz"
-      }
-    },
-    "glob": {
-      "version": "https://registry.npmjs.org/glob/-/glob-7.1.2.tgz",
-      "integrity": "sha1-wZyd+aAocC1nhhI4SmVSQExjbRU=",
-      "requires": {
-        "fs.realpath": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz",
-        "inflight": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz",
-        "inherits": "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz",
-        "minimatch": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz",
-        "once": "https://registry.npmjs.org/once/-/once-1.4.0.tgz",
-        "path-is-absolute": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz"
-      }
-    },
-    "globals": {
-      "version": "https://registry.npmjs.org/globals/-/globals-9.18.0.tgz",
-      "integrity": "sha1-qjiWs+abSH8X4x7SFD1pqOMMLYo=",
-      "dev": true
-    },
-    "globby": {
-      "version": "https://registry.npmjs.org/globby/-/globby-5.0.0.tgz",
-      "integrity": "sha1-69hGZ8oNuzMLmbz8aOrCvFQ3Dg0=",
-      "dev": true,
-      "requires": {
-        "array-union": "https://registry.npmjs.org/array-union/-/array-union-1.0.2.tgz",
-        "arrify": "https://registry.npmjs.org/arrify/-/arrify-1.0.1.tgz",
-        "glob": "https://registry.npmjs.org/glob/-/glob-7.1.2.tgz",
-        "object-assign": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz",
-        "pify": "https://registry.npmjs.org/pify/-/pify-2.3.0.tgz",
-        "pinkie-promise": "https://registry.npmjs.org/pinkie-promise/-/pinkie-promise-2.0.1.tgz"
-      }
-    },
-    "globule": {
-      "version": "https://registry.npmjs.org/globule/-/globule-1.2.0.tgz",
-      "integrity": "sha1-HcScaCLdnoovoAuiopUAboZkvQk=",
-      "requires": {
-        "glob": "https://registry.npmjs.org/glob/-/glob-7.1.2.tgz",
-        "lodash": "https://registry.npmjs.org/lodash/-/lodash-4.17.4.tgz",
-        "minimatch": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz"
-      }
-    },
-    "good-listener": {
-      "version": "https://registry.npmjs.org/good-listener/-/good-listener-1.2.2.tgz",
-      "integrity": "sha1-1TswzfkxPf+33JoNR3CWqm0UXFA=",
-      "requires": {
-        "delegate": "https://registry.npmjs.org/delegate/-/delegate-3.1.3.tgz"
-      }
-    },
-    "got": {
-      "version": "https://registry.npmjs.org/got/-/got-7.1.0.tgz",
-      "integrity": "sha1-BUUP2ECU5rvqVvRRpDqcKJFmOFo=",
-      "requires": {
-        "decompress-response": "https://registry.npmjs.org/decompress-response/-/decompress-response-3.3.0.tgz",
-        "duplexer3": "https://registry.npmjs.org/duplexer3/-/duplexer3-0.1.4.tgz",
-        "get-stream": "https://registry.npmjs.org/get-stream/-/get-stream-3.0.0.tgz",
-        "is-plain-obj": "https://registry.npmjs.org/is-plain-obj/-/is-plain-obj-1.1.0.tgz",
-        "is-retry-allowed": "https://registry.npmjs.org/is-retry-allowed/-/is-retry-allowed-1.1.0.tgz",
-        "is-stream": "https://registry.npmjs.org/is-stream/-/is-stream-1.1.0.tgz",
-        "isurl": "https://registry.npmjs.org/isurl/-/isurl-1.0.0.tgz",
-        "lowercase-keys": "https://registry.npmjs.org/lowercase-keys/-/lowercase-keys-1.0.0.tgz",
-        "p-cancelable": "https://registry.npmjs.org/p-cancelable/-/p-cancelable-0.3.0.tgz",
-        "p-timeout": "https://registry.npmjs.org/p-timeout/-/p-timeout-1.2.0.tgz",
-        "safe-buffer": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.1.tgz",
-        "timed-out": "https://registry.npmjs.org/timed-out/-/timed-out-4.0.1.tgz",
-        "url-parse-lax": "https://registry.npmjs.org/url-parse-lax/-/url-parse-lax-1.0.0.tgz",
-        "url-to-options": "https://registry.npmjs.org/url-to-options/-/url-to-options-1.0.1.tgz"
-      }
-    },
-    "graceful-fs": {
-      "version": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.1.11.tgz",
-      "integrity": "sha1-Dovf5NHduIVNZOBOp8AOKgJuVlg="
-    },
-    "har-schema": {
-      "version": "https://registry.npmjs.org/har-schema/-/har-schema-2.0.0.tgz",
-      "integrity": "sha1-qUwiJOvKwEeCoNkDVSHyRzW37JI="
-    },
-    "har-validator": {
-      "version": "https://registry.npmjs.org/har-validator/-/har-validator-5.0.3.tgz",
-      "integrity": "sha1-ukAsJmGU8VlW7xXg/PJCmT9qff0=",
-      "requires": {
-        "ajv": "https://registry.npmjs.org/ajv/-/ajv-5.3.0.tgz",
-        "har-schema": "https://registry.npmjs.org/har-schema/-/har-schema-2.0.0.tgz"
-      }
-    },
-    "has": {
-      "version": "https://registry.npmjs.org/has/-/has-1.0.1.tgz",
-      "integrity": "sha1-hGFzP1OLCDfJNh45qauelwTcLyg=",
-      "dev": true,
-      "requires": {
-        "function-bind": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.1.tgz"
-      }
-    },
-    "has-ansi": {
-      "version": "https://registry.npmjs.org/has-ansi/-/has-ansi-2.0.0.tgz",
-      "integrity": "sha1-NPUEnOHs3ysGSa8+8k5F7TVBbZE=",
-      "requires": {
-        "ansi-regex": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-2.1.1.tgz"
-      }
-    },
-    "has-flag": {
-      "version": "https://registry.npmjs.org/has-flag/-/has-flag-2.0.0.tgz",
-      "integrity": "sha1-6CB68cx7MNRGzHC3NLXovhj4jVE=",
-      "dev": true
-    },
-    "has-symbol-support-x": {
-      "version": "https://registry.npmjs.org/has-symbol-support-x/-/has-symbol-support-x-1.4.1.tgz",
-      "integrity": "sha1-ZuwuN34MfXzO2wejqE13UQ/xvEw="
-    },
-    "has-to-string-tag-x": {
-      "version": "https://registry.npmjs.org/has-to-string-tag-x/-/has-to-string-tag-x-1.4.1.tgz",
-      "integrity": "sha1-oEWrOD17SyASoAFIqwql8pAETU0=",
-      "requires": {
-        "has-symbol-support-x": "https://registry.npmjs.org/has-symbol-support-x/-/has-symbol-support-x-1.4.1.tgz"
-      }
-    },
-    "has-unicode": {
-      "version": "https://registry.npmjs.org/has-unicode/-/has-unicode-2.0.1.tgz",
-      "integrity": "sha1-4Ob+aijPUROIVeCG0Wkedx3iqLk="
-    },
-    "hawk": {
-      "version": "https://registry.npmjs.org/hawk/-/hawk-6.0.2.tgz",
-      "integrity": "sha1-r02RTrBl+bXOTZ0RwcshJu7MMDg=",
-      "requires": {
-        "boom": "https://registry.npmjs.org/boom/-/boom-4.3.1.tgz",
-        "cryptiles": "https://registry.npmjs.org/cryptiles/-/cryptiles-3.1.2.tgz",
-        "hoek": "https://registry.npmjs.org/hoek/-/hoek-4.2.0.tgz",
-        "sntp": "https://registry.npmjs.org/sntp/-/sntp-2.1.0.tgz"
-      }
-    },
-    "hiredis": {
-      "version": "https://registry.npmjs.org/hiredis/-/hiredis-0.5.0.tgz",
-      "integrity": "sha1-2wOpi+zXAD0TwmAEOs7s+s31m4c=",
-      "requires": {
-        "bindings": "https://registry.npmjs.org/bindings/-/bindings-1.3.0.tgz",
-        "nan": "https://registry.npmjs.org/nan/-/nan-2.7.0.tgz"
-      }
-    },
-    "history": {
-      "version": "https://registry.npmjs.org/history/-/history-3.3.0.tgz",
-      "integrity": "sha1-/O3M6PEpdTcVRdc1RhAzV5ptrpw=",
-      "requires": {
-        "invariant": "https://registry.npmjs.org/invariant/-/invariant-2.2.2.tgz",
-        "loose-envify": "https://registry.npmjs.org/loose-envify/-/loose-envify-1.3.1.tgz",
-        "query-string": "https://registry.npmjs.org/query-string/-/query-string-4.3.4.tgz",
-        "warning": "https://registry.npmjs.org/warning/-/warning-3.0.0.tgz"
-      }
-    },
-    "hoek": {
-      "version": "https://registry.npmjs.org/hoek/-/hoek-4.2.0.tgz",
-      "integrity": "sha1-ctnQdU9/4lyi0BrY+PmpRJqJUm0="
-    },
-    "hoist-non-react-statics": {
-      "version": "https://registry.npmjs.org/hoist-non-react-statics/-/hoist-non-react-statics-1.2.0.tgz",
-      "integrity": "sha1-qkSM8JhtVcxAdzsXF0t90GbLfPs="
-    },
-    "hosted-git-info": {
-      "version": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-2.5.0.tgz",
-      "integrity": "sha1-bWDjSzq7yDEwYsO3mO+NkBoHrzw="
-    },
-    "http-signature": {
-      "version": "https://registry.npmjs.org/http-signature/-/http-signature-1.2.0.tgz",
-      "integrity": "sha1-muzZJRFHcvPZW2WmCruPfBj7rOE=",
-      "requires": {
-        "assert-plus": "https://registry.npmjs.org/assert-plus/-/assert-plus-1.0.0.tgz",
-        "jsprim": "https://registry.npmjs.org/jsprim/-/jsprim-1.4.1.tgz",
-        "sshpk": "https://registry.npmjs.org/sshpk/-/sshpk-1.13.1.tgz"
-      }
-    },
-    "humanize-duration": {
-      "version": "https://registry.npmjs.org/humanize-duration/-/humanize-duration-3.10.1.tgz",
-      "integrity": "sha1-ZbVQwKoJUVbst8NA20TuC99xr0s=",
-      "dev": true
-    },
-    "husky": {
-      "version": "https://registry.npmjs.org/husky/-/husky-0.14.3.tgz",
-      "integrity": "sha1-xp7XTi0neXaaF7qDmbVM4LY8EsM=",
-      "dev": true,
-      "requires": {
-        "is-ci": "https://registry.npmjs.org/is-ci/-/is-ci-1.0.10.tgz",
-        "normalize-path": "https://registry.npmjs.org/normalize-path/-/normalize-path-1.0.0.tgz",
-        "strip-indent": "https://registry.npmjs.org/strip-indent/-/strip-indent-2.0.0.tgz"
-      },
-      "dependencies": {
-        "strip-indent": {
-          "version": "https://registry.npmjs.org/strip-indent/-/strip-indent-2.0.0.tgz",
-          "integrity": "sha1-XvjbKV0B5u1sv3qrlpmNeCJSe2g=",
-          "dev": true
-        }
-      }
-    },
-    "iconv-lite": {
-      "version": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.19.tgz",
-      "integrity": "sha1-90aPYBNfXl2tM5nAqBvpoWA6CCs="
-    },
-    "icss-replace-symbols": {
-      "version": "https://registry.npmjs.org/icss-replace-symbols/-/icss-replace-symbols-1.1.0.tgz",
-      "integrity": "sha1-Bupvg2ead0njhs/h/oEq5dsiPe0=",
-      "dev": true
-    },
-    "ignore": {
-      "version": "https://registry.npmjs.org/ignore/-/ignore-3.3.7.tgz",
-      "integrity": "sha1-YSKJv7PCIOGGpYEYYY1b6MG6sCE=",
-      "dev": true
-    },
-    "immutability-helper": {
-      "version": "https://registry.npmjs.org/immutability-helper/-/immutability-helper-2.4.0.tgz",
-      "integrity": "sha1-ANQh4pV8F/DweBR18F/9g35zRY0=",
-      "requires": {
-        "invariant": "https://registry.npmjs.org/invariant/-/invariant-2.2.2.tgz"
-      }
-    },
-    "imurmurhash": {
-      "version": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz",
-      "integrity": "sha1-khi5srkoojixPcT7a21XbyMUU+o=",
-      "dev": true
-    },
-    "in-publish": {
-      "version": "https://registry.npmjs.org/in-publish/-/in-publish-2.0.0.tgz",
-      "integrity": "sha1-4g/146KvwmkDILbcVSaCqcf631E="
-    },
-    "indent-string": {
-      "version": "https://registry.npmjs.org/indent-string/-/indent-string-2.1.0.tgz",
-      "integrity": "sha1-ji1INIdCEhtKghi3oTfppSBJ3IA=",
-      "requires": {
-        "repeating": "https://registry.npmjs.org/repeating/-/repeating-2.0.1.tgz"
-      }
-    },
-    "indexes-of": {
-      "version": "https://registry.npmjs.org/indexes-of/-/indexes-of-1.0.1.tgz",
-      "integrity": "sha1-8w9xbI4r00bHtn0985FVZqfAVgc=",
-      "dev": true
-    },
-    "inflight": {
-      "version": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz",
-      "integrity": "sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk=",
-      "requires": {
-        "once": "https://registry.npmjs.org/once/-/once-1.4.0.tgz",
-        "wrappy": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz"
-      }
-    },
-    "inherits": {
-      "version": "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz",
-      "integrity": "sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4="
-    },
-    "inquirer": {
-      "version": "https://registry.npmjs.org/inquirer/-/inquirer-3.3.0.tgz",
-      "integrity": "sha1-ndLyrXZdyrH/BEO0kUQqILoifck=",
-      "dev": true,
-      "requires": {
-        "ansi-escapes": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-3.0.0.tgz",
-        "chalk": "https://registry.npmjs.org/chalk/-/chalk-2.3.0.tgz",
-        "cli-cursor": "https://registry.npmjs.org/cli-cursor/-/cli-cursor-2.1.0.tgz",
-        "cli-width": "https://registry.npmjs.org/cli-width/-/cli-width-2.2.0.tgz",
-        "external-editor": "https://registry.npmjs.org/external-editor/-/external-editor-2.0.5.tgz",
-        "figures": "https://registry.npmjs.org/figures/-/figures-2.0.0.tgz",
-        "lodash": "https://registry.npmjs.org/lodash/-/lodash-4.17.4.tgz",
-        "mute-stream": "https://registry.npmjs.org/mute-stream/-/mute-stream-0.0.7.tgz",
-        "run-async": "https://registry.npmjs.org/run-async/-/run-async-2.3.0.tgz",
-        "rx-lite": "https://registry.npmjs.org/rx-lite/-/rx-lite-4.0.8.tgz",
-        "rx-lite-aggregates": "https://registry.npmjs.org/rx-lite-aggregates/-/rx-lite-aggregates-4.0.8.tgz",
-        "string-width": "https://registry.npmjs.org/string-width/-/string-width-2.1.1.tgz",
-        "strip-ansi": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-4.0.0.tgz",
-        "through": "https://registry.npmjs.org/through/-/through-2.3.8.tgz"
-      },
-      "dependencies": {
-        "ansi-regex": {
-          "version": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-3.0.0.tgz",
-          "integrity": "sha1-7QMXwyIGT3lGbAKWa922Bas32Zg=",
+        },
+        "esprima": {
+          "version": "4.0.0",
+          "resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.0.tgz",
+          "integrity": "sha512-oftTcaMu/EGrEIu904mWteKIv8vMuOgGYo7EhVJJN00R/EED9DCua/xxHRdYnKtcECzVg7xOWhflvJMnqcFZjw==",
           "dev": true
         },
-        "ansi-styles": {
-          "version": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.0.tgz",
-          "integrity": "sha1-wVm41b4PnlpvNG2rlPFs4CIWG4g=",
+        "esquery": {
+          "version": "1.0.0",
+          "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.0.0.tgz",
+          "integrity": "sha1-z7qLV9f7qT8XKYqKAGoEzaE9gPo=",
           "dev": true,
           "requires": {
-            "color-convert": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.0.tgz"
+            "estraverse": "4.2.0"
           }
         },
-        "chalk": {
-          "version": "https://registry.npmjs.org/chalk/-/chalk-2.3.0.tgz",
-          "integrity": "sha1-tepI78nBeT3MybR2fJORTT8tUro=",
+        "esrecurse": {
+          "version": "4.2.0",
+          "resolved": "https://registry.npmjs.org/esrecurse/-/esrecurse-4.2.0.tgz",
+          "integrity": "sha1-+pVo2Y04I/mkHZHpAtyrnqblsWM=",
           "dev": true,
           "requires": {
-            "ansi-styles": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.0.tgz",
-            "escape-string-regexp": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz",
-            "supports-color": "https://registry.npmjs.org/supports-color/-/supports-color-4.5.0.tgz"
+            "estraverse": "4.2.0",
+            "object-assign": "4.1.1"
           }
         },
-        "is-fullwidth-code-point": {
-          "version": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-2.0.0.tgz",
-          "integrity": "sha1-o7MKXE8ZkYMWeqq5O+764937ZU8=",
+        "estraverse": {
+          "version": "4.2.0",
+          "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-4.2.0.tgz",
+          "integrity": "sha1-De4/7TH81GlhjOc0IJn8GvoL2xM=",
           "dev": true
         },
-        "string-width": {
-          "version": "https://registry.npmjs.org/string-width/-/string-width-2.1.1.tgz",
-          "integrity": "sha1-q5Pyeo3BPSjKyBXEYhQ6bZASrp4=",
+        "esutils": {
+          "version": "2.0.2",
+          "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.2.tgz",
+          "integrity": "sha1-Cr9PHKpbyx96nYrMbepPqqBLrJs=",
+          "dev": true
+        },
+        "external-editor": {
+          "version": "2.1.0",
+          "resolved": "https://registry.npmjs.org/external-editor/-/external-editor-2.1.0.tgz",
+          "integrity": "sha512-E44iT5QVOUJBKij4IIV3uvxuNlbKS38Tw1HiupxEIHPv9qtC2PrDYohbXV5U+1jnfIXttny8gUhj+oZvflFlzA==",
           "dev": true,
           "requires": {
-            "is-fullwidth-code-point": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-2.0.0.tgz",
-            "strip-ansi": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-4.0.0.tgz"
+            "chardet": "0.4.2",
+            "iconv-lite": "0.4.19",
+            "tmp": "0.0.33"
           }
         },
-        "strip-ansi": {
-          "version": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-4.0.0.tgz",
-          "integrity": "sha1-qEeQIusaw2iocTibY1JixQXuNo8=",
+        "fast-deep-equal": {
+          "version": "1.0.0",
+          "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-1.0.0.tgz",
+          "integrity": "sha1-liVqO8l1WV6zbYLpkp0GDYk0Of8=",
+          "dev": true
+        },
+        "fast-json-stable-stringify": {
+          "version": "2.0.0",
+          "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.0.0.tgz",
+          "integrity": "sha1-1RQsDK7msRifh9OnYREGT4bIu/I=",
+          "dev": true
+        },
+        "fast-levenshtein": {
+          "version": "2.0.6",
+          "resolved": "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz",
+          "integrity": "sha1-PYpcZog6FqMMqGQ+hR8Zuqd5eRc=",
+          "dev": true
+        },
+        "figures": {
+          "version": "2.0.0",
+          "resolved": "https://registry.npmjs.org/figures/-/figures-2.0.0.tgz",
+          "integrity": "sha1-OrGi0qYsi/tDGgyUy3l6L84nyWI=",
           "dev": true,
           "requires": {
-            "ansi-regex": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-3.0.0.tgz"
+            "escape-string-regexp": "1.0.5"
           }
         },
-        "supports-color": {
-          "version": "https://registry.npmjs.org/supports-color/-/supports-color-4.5.0.tgz",
-          "integrity": "sha1-vnoN5ITexcXN34s9WRJQRJEvY1s=",
+        "file-entry-cache": {
+          "version": "2.0.0",
+          "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-2.0.0.tgz",
+          "integrity": "sha1-w5KZDD5oR4PYOLjISkXYoEhFg2E=",
           "dev": true,
           "requires": {
-            "has-flag": "https://registry.npmjs.org/has-flag/-/has-flag-2.0.0.tgz"
+            "flat-cache": "1.3.0",
+            "object-assign": "4.1.1"
           }
-        }
-      }
-    },
-    "intl-format-cache": {
-      "version": "https://registry.npmjs.org/intl-format-cache/-/intl-format-cache-2.0.5.tgz",
-      "integrity": "sha1-tITO/Lk1PzdPJd44mjzuoa8Y18k="
-    },
-    "intl-messageformat": {
-      "version": "https://registry.npmjs.org/intl-messageformat/-/intl-messageformat-2.2.0.tgz",
-      "integrity": "sha1-NFvNRt5jC3aDMwwuUhd/9eq0hPw=",
-      "requires": {
-        "intl-messageformat-parser": "https://registry.npmjs.org/intl-messageformat-parser/-/intl-messageformat-parser-1.4.0.tgz"
-      }
-    },
-    "intl-messageformat-parser": {
-      "version": "https://registry.npmjs.org/intl-messageformat-parser/-/intl-messageformat-parser-1.4.0.tgz",
-      "integrity": "sha1-tD1FqXRoytvkQzHXS7Ho3qRPwHU="
-    },
-    "intl-relativeformat": {
-      "version": "https://registry.npmjs.org/intl-relativeformat/-/intl-relativeformat-2.1.0.tgz",
-      "integrity": "sha1-AQ8RBYAiUfQKxH0OPhogE0iiVd8=",
-      "requires": {
-        "intl-messageformat": "https://registry.npmjs.org/intl-messageformat/-/intl-messageformat-2.2.0.tgz"
-      }
-    },
-    "invariant": {
-      "version": "https://registry.npmjs.org/invariant/-/invariant-2.2.2.tgz",
-      "integrity": "sha1-nh9WrArNtr8wMwbzOL47IErmA2A=",
-      "requires": {
-        "loose-envify": "https://registry.npmjs.org/loose-envify/-/loose-envify-1.3.1.tgz"
-      }
-    },
-    "invert-kv": {
-      "version": "https://registry.npmjs.org/invert-kv/-/invert-kv-1.0.0.tgz",
-      "integrity": "sha1-EEqOSqym09jNFXqO+L+rLXo//bY="
-    },
-    "is-arrayish": {
-      "version": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.2.1.tgz",
-      "integrity": "sha1-d8mYQFJ6qOyxqLppe4BkWnqSap0="
-    },
-    "is-buffer": {
-      "version": "https://registry.npmjs.org/is-buffer/-/is-buffer-1.1.6.tgz",
-      "integrity": "sha1-76ouqdqg16suoTqXsritUf776L4="
-    },
-    "is-builtin-module": {
-      "version": "https://registry.npmjs.org/is-builtin-module/-/is-builtin-module-1.0.0.tgz",
-      "integrity": "sha1-VAVy0096wxGfj3bDDLwbHgN6/74=",
-      "requires": {
-        "builtin-modules": "https://registry.npmjs.org/builtin-modules/-/builtin-modules-1.1.1.tgz"
-      }
-    },
-    "is-callable": {
-      "version": "https://registry.npmjs.org/is-callable/-/is-callable-1.1.3.tgz",
-      "integrity": "sha1-hut1OSgF3cM69xySoO7fdO52BLI=",
-      "dev": true
-    },
-    "is-ci": {
-      "version": "https://registry.npmjs.org/is-ci/-/is-ci-1.0.10.tgz",
-      "integrity": "sha1-9zkzayYyNlBhqdSCcM1WrjNpMY4=",
-      "dev": true,
-      "requires": {
-        "ci-info": "https://registry.npmjs.org/ci-info/-/ci-info-1.1.1.tgz"
-      }
-    },
-    "is-date-object": {
-      "version": "https://registry.npmjs.org/is-date-object/-/is-date-object-1.0.1.tgz",
-      "integrity": "sha1-mqIOtq7rv/d/vTPnTKAbM1gdOhY=",
-      "dev": true
-    },
-    "is-extglob": {
-      "version": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz",
-      "integrity": "sha1-qIwCU1eR8C7TfHahueqXc8gz+MI=",
-      "dev": true
-    },
-    "is-finite": {
-      "version": "https://registry.npmjs.org/is-finite/-/is-finite-1.0.2.tgz",
-      "integrity": "sha1-zGZ3aVYCvlUO8R6LSqYwU0K20Ko=",
-      "requires": {
-        "number-is-nan": "https://registry.npmjs.org/number-is-nan/-/number-is-nan-1.0.1.tgz"
-      }
-    },
-    "is-fullwidth-code-point": {
-      "version": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-1.0.0.tgz",
-      "integrity": "sha1-754xOG8DGn8NZDr4L95QxFfvAMs=",
-      "requires": {
-        "number-is-nan": "https://registry.npmjs.org/number-is-nan/-/number-is-nan-1.0.1.tgz"
-      }
-    },
-    "is-glob": {
-      "version": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.0.tgz",
-      "integrity": "sha1-lSHHaEXMJhCoUgPd8ICpWML/q8A=",
-      "dev": true,
-      "requires": {
-        "is-extglob": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz"
-      }
-    },
-    "is-obj": {
-      "version": "https://registry.npmjs.org/is-obj/-/is-obj-1.0.1.tgz",
-      "integrity": "sha1-PkcprB9f3gJc19g6iW2rn09n2w8=",
-      "dev": true
-    },
-    "is-object": {
-      "version": "https://registry.npmjs.org/is-object/-/is-object-1.0.1.tgz",
-      "integrity": "sha1-iVJojF7C/9awPsyF52ngKQMINHA="
-    },
-    "is-path-cwd": {
-      "version": "https://registry.npmjs.org/is-path-cwd/-/is-path-cwd-1.0.0.tgz",
-      "integrity": "sha1-0iXsIxMuie3Tj9p2dHLmLmXxEG0=",
-      "dev": true
-    },
-    "is-path-in-cwd": {
-      "version": "https://registry.npmjs.org/is-path-in-cwd/-/is-path-in-cwd-1.0.0.tgz",
-      "integrity": "sha1-ZHdYK4IU1gI0YJRWcAO+ip6sBNw=",
-      "dev": true,
-      "requires": {
-        "is-path-inside": "https://registry.npmjs.org/is-path-inside/-/is-path-inside-1.0.0.tgz"
-      }
-    },
-    "is-path-inside": {
-      "version": "https://registry.npmjs.org/is-path-inside/-/is-path-inside-1.0.0.tgz",
-      "integrity": "sha1-/AbloWg/vaE95mev9xe7wQpI838=",
-      "dev": true,
-      "requires": {
-        "path-is-inside": "https://registry.npmjs.org/path-is-inside/-/path-is-inside-1.0.2.tgz"
-      }
-    },
-    "is-plain-obj": {
-      "version": "https://registry.npmjs.org/is-plain-obj/-/is-plain-obj-1.1.0.tgz",
-      "integrity": "sha1-caUMhCnfync8kqOQpKA7OfzVHT4="
-    },
-    "is-promise": {
-      "version": "https://registry.npmjs.org/is-promise/-/is-promise-2.1.0.tgz",
-      "integrity": "sha1-eaKp7OfwlugPNtKy87wWwf9L8/o=",
-      "dev": true
-    },
-    "is-regex": {
-      "version": "https://registry.npmjs.org/is-regex/-/is-regex-1.0.4.tgz",
-      "integrity": "sha1-VRdIm1RwkbCTDglWVM7SXul+lJE=",
-      "dev": true,
-      "requires": {
-        "has": "https://registry.npmjs.org/has/-/has-1.0.1.tgz"
-      }
-    },
-    "is-regexp": {
-      "version": "https://registry.npmjs.org/is-regexp/-/is-regexp-1.0.0.tgz",
-      "integrity": "sha1-/S2INUXEa6xaYz57mgnof6LLUGk=",
-      "dev": true
-    },
-    "is-resolvable": {
-      "version": "https://registry.npmjs.org/is-resolvable/-/is-resolvable-1.0.0.tgz",
-      "integrity": "sha1-jfV8YeouPFAUCNEA+wE8+NbgzGI=",
-      "dev": true,
-      "requires": {
-        "tryit": "https://registry.npmjs.org/tryit/-/tryit-1.0.3.tgz"
-      }
-    },
-    "is-retry-allowed": {
-      "version": "https://registry.npmjs.org/is-retry-allowed/-/is-retry-allowed-1.1.0.tgz",
-      "integrity": "sha1-EaBgVotnM5REAz0BJaYaINVk+zQ="
-    },
-    "is-stream": {
-      "version": "https://registry.npmjs.org/is-stream/-/is-stream-1.1.0.tgz",
-      "integrity": "sha1-EtSj3U5o4Lec6428hBc66A2RykQ="
-    },
-    "is-symbol": {
-      "version": "https://registry.npmjs.org/is-symbol/-/is-symbol-1.0.1.tgz",
-      "integrity": "sha1-PMWfAAJRlLarLjjbrmaJJWtmBXI=",
-      "dev": true
-    },
-    "is-typedarray": {
-      "version": "https://registry.npmjs.org/is-typedarray/-/is-typedarray-1.0.0.tgz",
-      "integrity": "sha1-5HnICFjfDBsR3dppQPlgEfzaSpo="
-    },
-    "is-utf8": {
-      "version": "https://registry.npmjs.org/is-utf8/-/is-utf8-0.2.1.tgz",
-      "integrity": "sha1-Sw2hRCEE0bM2NA6AeX6GXPOffXI="
-    },
-    "isarray": {
-      "version": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz",
-      "integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE="
-    },
-    "isexe": {
-      "version": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz",
-      "integrity": "sha1-6PvzdNxVb/iUehDcsFctYz8s+hA="
-    },
-    "isomorphic-fetch": {
-      "version": "https://registry.npmjs.org/isomorphic-fetch/-/isomorphic-fetch-2.2.1.tgz",
-      "integrity": "sha1-YRrhrPFPXoH3KVB0coGf6XM1WKk=",
-      "requires": {
-        "node-fetch": "https://registry.npmjs.org/node-fetch/-/node-fetch-1.7.3.tgz",
-        "whatwg-fetch": "https://registry.npmjs.org/whatwg-fetch/-/whatwg-fetch-2.0.3.tgz"
-      }
-    },
-    "isstream": {
-      "version": "https://registry.npmjs.org/isstream/-/isstream-0.1.2.tgz",
-      "integrity": "sha1-R+Y/evVa+m+S4VAOaQ64uFKcCZo="
-    },
-    "isurl": {
-      "version": "https://registry.npmjs.org/isurl/-/isurl-1.0.0.tgz",
-      "integrity": "sha1-sn9PSfPNqj6kSgpbfzRi5u3DnWc=",
-      "requires": {
-        "has-to-string-tag-x": "https://registry.npmjs.org/has-to-string-tag-x/-/has-to-string-tag-x-1.4.1.tgz",
-        "is-object": "https://registry.npmjs.org/is-object/-/is-object-1.0.1.tgz"
-      }
-    },
-    "jasmine": {
-      "version": "https://registry.npmjs.org/jasmine/-/jasmine-2.8.0.tgz",
-      "integrity": "sha1-awicChFXax8W3xG4AUbZHU6Lij4=",
-      "dev": true,
-      "requires": {
-        "exit": "https://registry.npmjs.org/exit/-/exit-0.1.2.tgz",
-        "glob": "https://registry.npmjs.org/glob/-/glob-7.1.2.tgz",
-        "jasmine-core": "https://registry.npmjs.org/jasmine-core/-/jasmine-core-2.8.0.tgz"
-      }
-    },
-    "jasmine-core": {
-      "version": "https://registry.npmjs.org/jasmine-core/-/jasmine-core-2.8.0.tgz",
-      "integrity": "sha1-vMl5rh+f0FcB5F5S5l06XWPxok4=",
-      "dev": true
-    },
-    "jest-get-type": {
-      "version": "https://registry.npmjs.org/jest-get-type/-/jest-get-type-21.2.0.tgz",
-      "integrity": "sha1-9jdqudtLYNgeOfMHScbEZvQNSiM=",
-      "dev": true
-    },
-    "jest-validate": {
-      "version": "https://registry.npmjs.org/jest-validate/-/jest-validate-21.2.1.tgz",
-      "integrity": "sha1-zAy8plPNVJN7pPKhEXlndFMN08c=",
-      "dev": true,
-      "requires": {
-        "chalk": "https://registry.npmjs.org/chalk/-/chalk-2.3.0.tgz",
-        "jest-get-type": "https://registry.npmjs.org/jest-get-type/-/jest-get-type-21.2.0.tgz",
-        "leven": "https://registry.npmjs.org/leven/-/leven-2.1.0.tgz",
-        "pretty-format": "https://registry.npmjs.org/pretty-format/-/pretty-format-21.2.1.tgz"
-      },
-      "dependencies": {
-        "ansi-styles": {
-          "version": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.0.tgz",
-          "integrity": "sha1-wVm41b4PnlpvNG2rlPFs4CIWG4g=",
+        },
+        "flat-cache": {
+          "version": "1.3.0",
+          "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-1.3.0.tgz",
+          "integrity": "sha1-0wMLMrOBVPTjt+nHCfSQ9++XxIE=",
           "dev": true,
           "requires": {
-            "color-convert": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.0.tgz"
+            "circular-json": "0.3.3",
+            "del": "2.2.2",
+            "graceful-fs": "4.1.11",
+            "write": "0.2.1"
           }
         },
-        "chalk": {
-          "version": "https://registry.npmjs.org/chalk/-/chalk-2.3.0.tgz",
-          "integrity": "sha1-tepI78nBeT3MybR2fJORTT8tUro=",
+        "fs.realpath": {
+          "version": "1.0.0",
+          "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz",
+          "integrity": "sha1-FQStJSMVjKpA20onh8sBQRmU6k8=",
+          "dev": true
+        },
+        "functional-red-black-tree": {
+          "version": "1.0.1",
+          "resolved": "https://registry.npmjs.org/functional-red-black-tree/-/functional-red-black-tree-1.0.1.tgz",
+          "integrity": "sha1-GwqzvVU7Kg1jmdKcDj6gslIHgyc=",
+          "dev": true
+        },
+        "glob": {
+          "version": "7.1.2",
+          "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.2.tgz",
+          "integrity": "sha512-MJTUg1kjuLeQCJ+ccE4Vpa6kKVXkPYJ2mOCQyUuKLcLQsdrMCpBPUi8qVE6+YuaJkozeA9NusTAw3hLr8Xe5EQ==",
           "dev": true,
           "requires": {
-            "ansi-styles": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.0.tgz",
-            "escape-string-regexp": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz",
-            "supports-color": "https://registry.npmjs.org/supports-color/-/supports-color-4.5.0.tgz"
+            "fs.realpath": "1.0.0",
+            "inflight": "1.0.6",
+            "inherits": "2.0.3",
+            "minimatch": "3.0.4",
+            "once": "1.4.0",
+            "path-is-absolute": "1.0.1"
           }
         },
-        "supports-color": {
-          "version": "https://registry.npmjs.org/supports-color/-/supports-color-4.5.0.tgz",
-          "integrity": "sha1-vnoN5ITexcXN34s9WRJQRJEvY1s=",
+        "globals": {
+          "version": "9.18.0",
+          "resolved": "https://registry.npmjs.org/globals/-/globals-9.18.0.tgz",
+          "integrity": "sha512-S0nG3CLEQiY/ILxqtztTWH/3iRRdyBLw6KMDxnKMchrtbj2OFmehVh0WUCfW3DUrIgx/qFrJPICrq4Z4sTR9UQ==",
+          "dev": true
+        },
+        "globby": {
+          "version": "5.0.0",
+          "resolved": "https://registry.npmjs.org/globby/-/globby-5.0.0.tgz",
+          "integrity": "sha1-69hGZ8oNuzMLmbz8aOrCvFQ3Dg0=",
           "dev": true,
           "requires": {
-            "has-flag": "https://registry.npmjs.org/has-flag/-/has-flag-2.0.0.tgz"
+            "array-union": "1.0.2",
+            "arrify": "1.0.1",
+            "glob": "7.1.2",
+            "object-assign": "4.1.1",
+            "pify": "2.3.0",
+            "pinkie-promise": "2.0.1"
           }
-        }
-      }
-    },
-    "js-base64": {
-      "version": "https://registry.npmjs.org/js-base64/-/js-base64-2.3.2.tgz",
-      "integrity": "sha1-p5qSNmY3K1gPjif1GEXG9+j7+68="
-    },
-    "js-tokens": {
-      "version": "https://registry.npmjs.org/js-tokens/-/js-tokens-3.0.2.tgz",
-      "integrity": "sha1-mGbfOVECEw449/mWvOtlRDIJwls="
-    },
-    "js-yaml": {
-      "version": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.10.0.tgz",
-      "integrity": "sha1-LnhEFka9RoLpY/IrbpKCPDCcYtw=",
-      "dev": true,
-      "requires": {
-        "argparse": "https://registry.npmjs.org/argparse/-/argparse-1.0.9.tgz",
-        "esprima": "https://registry.npmjs.org/esprima/-/esprima-4.0.0.tgz"
-      }
-    },
-    "jsbn": {
-      "version": "https://registry.npmjs.org/jsbn/-/jsbn-0.1.1.tgz",
-      "integrity": "sha1-peZUwuWi3rXyAdls77yoDA7y9RM=",
-      "optional": true
-    },
-    "jschardet": {
-      "version": "https://registry.npmjs.org/jschardet/-/jschardet-1.6.0.tgz",
-      "integrity": "sha1-x9GnHtz/KDnbL57DD8XV69PBpng=",
-      "dev": true
-    },
-    "jsesc": {
-      "version": "https://registry.npmjs.org/jsesc/-/jsesc-0.5.0.tgz",
-      "integrity": "sha1-597mbjXW/Bb3EP6R1c9p9w8IkR0=",
-      "dev": true
-    },
-    "json-schema": {
-      "version": "https://registry.npmjs.org/json-schema/-/json-schema-0.2.3.tgz",
-      "integrity": "sha1-tIDIkuWaLwWVTOcnvT8qTogvnhM="
-    },
-    "json-schema-traverse": {
-      "version": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.3.1.tgz",
-      "integrity": "sha1-NJptRMU6Ud6JtAgFxdXlm0F9M0A="
-    },
-    "json-stable-stringify": {
-      "version": "https://registry.npmjs.org/json-stable-stringify/-/json-stable-stringify-1.0.1.tgz",
-      "integrity": "sha1-mnWdOcXy/1A/1TAGRu1EX4jE+a8=",
-      "dev": true,
-      "requires": {
-        "jsonify": "https://registry.npmjs.org/jsonify/-/jsonify-0.0.0.tgz"
-      }
-    },
-    "json-stringify-safe": {
-      "version": "https://registry.npmjs.org/json-stringify-safe/-/json-stringify-safe-5.0.1.tgz",
-      "integrity": "sha1-Epai1Y/UXxmg9s4B1lcB4sc1tus="
-    },
-    "jsonify": {
-      "version": "https://registry.npmjs.org/jsonify/-/jsonify-0.0.0.tgz",
-      "integrity": "sha1-LHS27kHZPKUbe1qu6PUDYx0lKnM=",
-      "dev": true
-    },
-    "jsprim": {
-      "version": "https://registry.npmjs.org/jsprim/-/jsprim-1.4.1.tgz",
-      "integrity": "sha1-MT5mvB5cwG5Di8G3SZwuXFastqI=",
-      "requires": {
-        "assert-plus": "https://registry.npmjs.org/assert-plus/-/assert-plus-1.0.0.tgz",
-        "extsprintf": "https://registry.npmjs.org/extsprintf/-/extsprintf-1.3.0.tgz",
-        "json-schema": "https://registry.npmjs.org/json-schema/-/json-schema-0.2.3.tgz",
-        "verror": "https://registry.npmjs.org/verror/-/verror-1.10.0.tgz"
-      }
-    },
-    "jsx-ast-utils": {
-      "version": "https://registry.npmjs.org/jsx-ast-utils/-/jsx-ast-utils-1.4.1.tgz",
-      "integrity": "sha1-OGchPo3Xm/Ho8jAMDPwe+xgsDfE=",
-      "dev": true
-    },
-    "junit-report-builder": {
-      "version": "https://registry.npmjs.org/junit-report-builder/-/junit-report-builder-1.2.0.tgz",
-      "integrity": "sha1-aJfM1e49gFGRpL5vEse+hQ5QwNY=",
-      "dev": true,
-      "requires": {
-        "date-format": "https://registry.npmjs.org/date-format/-/date-format-0.0.2.tgz",
-        "lodash": "https://registry.npmjs.org/lodash/-/lodash-3.10.1.tgz",
-        "mkdirp": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.1.tgz",
-        "xmlbuilder": "https://registry.npmjs.org/xmlbuilder/-/xmlbuilder-2.6.5.tgz"
-      },
-      "dependencies": {
-        "lodash": {
-          "version": "https://registry.npmjs.org/lodash/-/lodash-3.10.1.tgz",
-          "integrity": "sha1-W/Rejkm6QYnhfUgnid/RW9FAt7Y=",
+        },
+        "graceful-fs": {
+          "version": "4.1.11",
+          "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.1.11.tgz",
+          "integrity": "sha1-Dovf5NHduIVNZOBOp8AOKgJuVlg=",
           "dev": true
         },
-        "xmlbuilder": {
-          "version": "https://registry.npmjs.org/xmlbuilder/-/xmlbuilder-2.6.5.tgz",
-          "integrity": "sha1-b/etYPty0idk8AehZLd/K/FABSY=",
+        "has-ansi": {
+          "version": "2.0.0",
+          "resolved": "https://registry.npmjs.org/has-ansi/-/has-ansi-2.0.0.tgz",
+          "integrity": "sha1-NPUEnOHs3ysGSa8+8k5F7TVBbZE=",
           "dev": true,
           "requires": {
-            "lodash": "https://registry.npmjs.org/lodash/-/lodash-3.10.1.tgz"
+            "ansi-regex": "2.1.1"
           }
-        }
-      }
-    },
-    "lazystream": {
-      "version": "https://registry.npmjs.org/lazystream/-/lazystream-1.0.0.tgz",
-      "integrity": "sha1-9plf4PggOS9hOWvolGJAe7dxaOQ=",
-      "dev": true,
-      "requires": {
-        "readable-stream": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.3.tgz"
-      }
-    },
-    "lcid": {
-      "version": "https://registry.npmjs.org/lcid/-/lcid-1.0.0.tgz",
-      "integrity": "sha1-MIrMr6C8SDo4Z7S28rlQYlHRuDU=",
-      "requires": {
-        "invert-kv": "https://registry.npmjs.org/invert-kv/-/invert-kv-1.0.0.tgz"
-      }
-    },
-    "leven": {
-      "version": "https://registry.npmjs.org/leven/-/leven-2.1.0.tgz",
-      "integrity": "sha1-wuep93IJTe6dNCAq6KzORoeHVYA=",
-      "dev": true
-    },
-    "levn": {
-      "version": "https://registry.npmjs.org/levn/-/levn-0.3.0.tgz",
-      "integrity": "sha1-OwmSTt+fCDwEkP3UwLxEIeBHZO4=",
-      "dev": true,
-      "requires": {
-        "prelude-ls": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.1.2.tgz",
-        "type-check": "https://registry.npmjs.org/type-check/-/type-check-0.3.2.tgz"
-      }
-    },
-    "line-height": {
-      "version": "https://registry.npmjs.org/line-height/-/line-height-0.3.1.tgz",
-      "integrity": "sha1-SxIF7d4YKHKl76PI9iCzGHqcVMk=",
-      "requires": {
-        "computed-style": "https://registry.npmjs.org/computed-style/-/computed-style-0.1.4.tgz"
-      }
-    },
-    "lint-staged": {
-      "version": "https://registry.npmjs.org/lint-staged/-/lint-staged-4.3.0.tgz",
-      "integrity": "sha1-7Qd5rZpCwNxiuzJE5SKHC0ESWHk=",
-      "dev": true,
-      "requires": {
-        "app-root-path": "https://registry.npmjs.org/app-root-path/-/app-root-path-2.0.1.tgz",
-        "chalk": "https://registry.npmjs.org/chalk/-/chalk-2.3.0.tgz",
-        "commander": "https://registry.npmjs.org/commander/-/commander-2.11.0.tgz",
-        "cosmiconfig": "https://registry.npmjs.org/cosmiconfig/-/cosmiconfig-1.1.0.tgz",
-        "execa": "https://registry.npmjs.org/execa/-/execa-0.8.0.tgz",
-        "is-glob": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.0.tgz",
-        "jest-validate": "https://registry.npmjs.org/jest-validate/-/jest-validate-21.2.1.tgz",
-        "listr": "https://registry.npmjs.org/listr/-/listr-0.12.0.tgz",
-        "lodash": "https://registry.npmjs.org/lodash/-/lodash-4.17.4.tgz",
-        "log-symbols": "https://registry.npmjs.org/log-symbols/-/log-symbols-2.1.0.tgz",
-        "minimatch": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz",
-        "npm-which": "https://registry.npmjs.org/npm-which/-/npm-which-3.0.1.tgz",
-        "p-map": "https://registry.npmjs.org/p-map/-/p-map-1.2.0.tgz",
-        "staged-git-files": "https://registry.npmjs.org/staged-git-files/-/staged-git-files-0.0.4.tgz",
-        "stringify-object": "https://registry.npmjs.org/stringify-object/-/stringify-object-3.2.1.tgz"
-      },
-      "dependencies": {
-        "ansi-styles": {
-          "version": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.0.tgz",
-          "integrity": "sha1-wVm41b4PnlpvNG2rlPFs4CIWG4g=",
+        },
+        "has-flag": {
+          "version": "2.0.0",
+          "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-2.0.0.tgz",
+          "integrity": "sha1-6CB68cx7MNRGzHC3NLXovhj4jVE=",
+          "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==",
+          "dev": true
+        },
+        "ignore": {
+          "version": "3.3.7",
+          "resolved": "https://registry.npmjs.org/ignore/-/ignore-3.3.7.tgz",
+          "integrity": "sha512-YGG3ejvBNHRqu0559EOxxNFihD0AjpvHlC/pdGKd3X3ofe+CoJkYazwNJYTNebqpPKN+VVQbh4ZFn1DivMNuHA==",
+          "dev": true
+        },
+        "imurmurhash": {
+          "version": "0.1.4",
+          "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz",
+          "integrity": "sha1-khi5srkoojixPcT7a21XbyMUU+o=",
+          "dev": true
+        },
+        "inflight": {
+          "version": "1.0.6",
+          "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz",
+          "integrity": "sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk=",
           "dev": true,
           "requires": {
-            "color-convert": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.0.tgz"
+            "once": "1.4.0",
+            "wrappy": "1.0.2"
           }
         },
-        "chalk": {
-          "version": "https://registry.npmjs.org/chalk/-/chalk-2.3.0.tgz",
-          "integrity": "sha1-tepI78nBeT3MybR2fJORTT8tUro=",
+        "inherits": {
+          "version": "2.0.3",
+          "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz",
+          "integrity": "sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4=",
+          "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==",
           "dev": true,
           "requires": {
-            "ansi-styles": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.0.tgz",
-            "escape-string-regexp": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz",
-            "supports-color": "https://registry.npmjs.org/supports-color/-/supports-color-4.5.0.tgz"
+            "ansi-escapes": "3.0.0",
+            "chalk": "2.3.0",
+            "cli-cursor": "2.1.0",
+            "cli-width": "2.2.0",
+            "external-editor": "2.1.0",
+            "figures": "2.0.0",
+            "lodash": "4.17.4",
+            "mute-stream": "0.0.7",
+            "run-async": "2.3.0",
+            "rx-lite": "4.0.8",
+            "rx-lite-aggregates": "4.0.8",
+            "string-width": "2.1.1",
+            "strip-ansi": "4.0.0",
+            "through": "2.3.8"
           }
         },
-        "supports-color": {
-          "version": "https://registry.npmjs.org/supports-color/-/supports-color-4.5.0.tgz",
-          "integrity": "sha1-vnoN5ITexcXN34s9WRJQRJEvY1s=",
+        "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
+        },
+        "is-path-cwd": {
+          "version": "1.0.0",
+          "resolved": "https://registry.npmjs.org/is-path-cwd/-/is-path-cwd-1.0.0.tgz",
+          "integrity": "sha1-0iXsIxMuie3Tj9p2dHLmLmXxEG0=",
+          "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=",
           "dev": true,
           "requires": {
-            "has-flag": "https://registry.npmjs.org/has-flag/-/has-flag-2.0.0.tgz"
+            "is-path-inside": "1.0.1"
           }
-        }
-      }
-    },
-    "listr": {
-      "version": "https://registry.npmjs.org/listr/-/listr-0.12.0.tgz",
-      "integrity": "sha1-a84sD1YD+klYDqF81qAMwOX6RRo=",
-      "dev": true,
-      "requires": {
-        "chalk": "https://registry.npmjs.org/chalk/-/chalk-1.1.3.tgz",
-        "cli-truncate": "https://registry.npmjs.org/cli-truncate/-/cli-truncate-0.2.1.tgz",
-        "figures": "https://registry.npmjs.org/figures/-/figures-1.7.0.tgz",
-        "indent-string": "https://registry.npmjs.org/indent-string/-/indent-string-2.1.0.tgz",
-        "is-promise": "https://registry.npmjs.org/is-promise/-/is-promise-2.1.0.tgz",
-        "is-stream": "https://registry.npmjs.org/is-stream/-/is-stream-1.1.0.tgz",
-        "listr-silent-renderer": "https://registry.npmjs.org/listr-silent-renderer/-/listr-silent-renderer-1.1.1.tgz",
-        "listr-update-renderer": "https://registry.npmjs.org/listr-update-renderer/-/listr-update-renderer-0.2.0.tgz",
-        "listr-verbose-renderer": "https://registry.npmjs.org/listr-verbose-renderer/-/listr-verbose-renderer-0.4.1.tgz",
-        "log-symbols": "https://registry.npmjs.org/log-symbols/-/log-symbols-1.0.2.tgz",
-        "log-update": "https://registry.npmjs.org/log-update/-/log-update-1.0.2.tgz",
-        "ora": "https://registry.npmjs.org/ora/-/ora-0.2.3.tgz",
-        "p-map": "https://registry.npmjs.org/p-map/-/p-map-1.2.0.tgz",
-        "rxjs": "https://registry.npmjs.org/rxjs/-/rxjs-5.5.2.tgz",
-        "stream-to-observable": "https://registry.npmjs.org/stream-to-observable/-/stream-to-observable-0.1.0.tgz",
-        "strip-ansi": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-3.0.1.tgz"
-      },
-      "dependencies": {
-        "figures": {
-          "version": "https://registry.npmjs.org/figures/-/figures-1.7.0.tgz",
-          "integrity": "sha1-y+Hjr/zxzUS4DK3+0o3Hk6lwHS4=",
+        },
+        "is-path-inside": {
+          "version": "1.0.1",
+          "resolved": "https://registry.npmjs.org/is-path-inside/-/is-path-inside-1.0.1.tgz",
+          "integrity": "sha1-jvW33lBDej/cprToZe96pVy0gDY=",
           "dev": true,
           "requires": {
-            "escape-string-regexp": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz",
-            "object-assign": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz"
+            "path-is-inside": "1.0.2"
           }
         },
-        "log-symbols": {
-          "version": "https://registry.npmjs.org/log-symbols/-/log-symbols-1.0.2.tgz",
-          "integrity": "sha1-N2/3tY6jCGoPCfrMdGF+ylAeGhg=",
+        "is-promise": {
+          "version": "2.1.0",
+          "resolved": "https://registry.npmjs.org/is-promise/-/is-promise-2.1.0.tgz",
+          "integrity": "sha1-eaKp7OfwlugPNtKy87wWwf9L8/o=",
+          "dev": true
+        },
+        "is-resolvable": {
+          "version": "1.0.1",
+          "resolved": "https://registry.npmjs.org/is-resolvable/-/is-resolvable-1.0.1.tgz",
+          "integrity": "sha512-y5CXYbzvB3jTnWAZH1Nl7ykUWb6T3BcTs56HUruwBf8MhF56n1HWqhDWnVFo8GHrUPDgvUUNVhrc2U8W7iqz5g==",
+          "dev": true
+        },
+        "isarray": {
+          "version": "1.0.0",
+          "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz",
+          "integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=",
+          "dev": true
+        },
+        "isexe": {
+          "version": "2.0.0",
+          "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz",
+          "integrity": "sha1-6PvzdNxVb/iUehDcsFctYz8s+hA=",
+          "dev": true
+        },
+        "js-tokens": {
+          "version": "3.0.2",
+          "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-3.0.2.tgz",
+          "integrity": "sha1-mGbfOVECEw449/mWvOtlRDIJwls=",
+          "dev": true
+        },
+        "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==",
           "dev": true,
           "requires": {
-            "chalk": "https://registry.npmjs.org/chalk/-/chalk-1.1.3.tgz"
+            "argparse": "1.0.9",
+            "esprima": "4.0.0"
           }
-        }
-      }
-    },
-    "listr-silent-renderer": {
-      "version": "https://registry.npmjs.org/listr-silent-renderer/-/listr-silent-renderer-1.1.1.tgz",
-      "integrity": "sha1-kktaN1cVN3C/Go4/v3S4u/P5JC4=",
-      "dev": true
-    },
-    "listr-update-renderer": {
-      "version": "https://registry.npmjs.org/listr-update-renderer/-/listr-update-renderer-0.2.0.tgz",
-      "integrity": "sha1-yoDhd5tOcCZoB+ju0a1qvjmFUPk=",
-      "dev": true,
-      "requires": {
-        "chalk": "https://registry.npmjs.org/chalk/-/chalk-1.1.3.tgz",
-        "cli-truncate": "https://registry.npmjs.org/cli-truncate/-/cli-truncate-0.2.1.tgz",
-        "elegant-spinner": "https://registry.npmjs.org/elegant-spinner/-/elegant-spinner-1.0.1.tgz",
-        "figures": "https://registry.npmjs.org/figures/-/figures-1.7.0.tgz",
-        "indent-string": "https://registry.npmjs.org/indent-string/-/indent-string-3.2.0.tgz",
-        "log-symbols": "https://registry.npmjs.org/log-symbols/-/log-symbols-1.0.2.tgz",
-        "log-update": "https://registry.npmjs.org/log-update/-/log-update-1.0.2.tgz",
-        "strip-ansi": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-3.0.1.tgz"
-      },
-      "dependencies": {
-        "figures": {
-          "version": "https://registry.npmjs.org/figures/-/figures-1.7.0.tgz",
-          "integrity": "sha1-y+Hjr/zxzUS4DK3+0o3Hk6lwHS4=",
+        },
+        "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=",
+          "dev": true
+        },
+        "json-stable-stringify": {
+          "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": {
-            "escape-string-regexp": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz",
-            "object-assign": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz"
+            "jsonify": "0.0.0"
           }
         },
-        "indent-string": {
-          "version": "https://registry.npmjs.org/indent-string/-/indent-string-3.2.0.tgz",
-          "integrity": "sha1-Sl/W0nzDMvN+VBmlBNu4NxBckok=",
+        "jsonify": {
+          "version": "0.0.0",
+          "resolved": "https://registry.npmjs.org/jsonify/-/jsonify-0.0.0.tgz",
+          "integrity": "sha1-LHS27kHZPKUbe1qu6PUDYx0lKnM=",
           "dev": true
         },
-        "log-symbols": {
-          "version": "https://registry.npmjs.org/log-symbols/-/log-symbols-1.0.2.tgz",
-          "integrity": "sha1-N2/3tY6jCGoPCfrMdGF+ylAeGhg=",
+        "levn": {
+          "version": "0.3.0",
+          "resolved": "https://registry.npmjs.org/levn/-/levn-0.3.0.tgz",
+          "integrity": "sha1-OwmSTt+fCDwEkP3UwLxEIeBHZO4=",
           "dev": true,
           "requires": {
-            "chalk": "https://registry.npmjs.org/chalk/-/chalk-1.1.3.tgz"
+            "prelude-ls": "1.1.2",
+            "type-check": "0.3.2"
           }
-        }
-      }
-    },
-    "listr-verbose-renderer": {
-      "version": "https://registry.npmjs.org/listr-verbose-renderer/-/listr-verbose-renderer-0.4.1.tgz",
-      "integrity": "sha1-ggb0z21S3cWCfl/RSYng6WWTOjU=",
-      "dev": true,
-      "requires": {
-        "chalk": "https://registry.npmjs.org/chalk/-/chalk-1.1.3.tgz",
-        "cli-cursor": "https://registry.npmjs.org/cli-cursor/-/cli-cursor-1.0.2.tgz",
-        "date-fns": "https://registry.npmjs.org/date-fns/-/date-fns-1.29.0.tgz",
-        "figures": "https://registry.npmjs.org/figures/-/figures-1.7.0.tgz"
-      },
-      "dependencies": {
-        "cli-cursor": {
-          "version": "https://registry.npmjs.org/cli-cursor/-/cli-cursor-1.0.2.tgz",
-          "integrity": "sha1-ZNo/fValRBLll5S9Ytw1KV6PKYc=",
+        },
+        "lru-cache": {
+          "version": "4.1.1",
+          "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-4.1.1.tgz",
+          "integrity": "sha512-q4spe4KTfsAS1SUHLO0wz8Qiyf1+vMIAgpRYioFYDMNqKfHQbg+AVDH3i4fvpl71/P1L0dBl+fQi+P37UYf0ew==",
           "dev": true,
           "requires": {
-            "restore-cursor": "https://registry.npmjs.org/restore-cursor/-/restore-cursor-1.0.1.tgz"
+            "pseudomap": "1.0.2",
+            "yallist": "2.1.2"
           }
         },
-        "figures": {
-          "version": "https://registry.npmjs.org/figures/-/figures-1.7.0.tgz",
-          "integrity": "sha1-y+Hjr/zxzUS4DK3+0o3Hk6lwHS4=",
+        "mimic-fn": {
+          "version": "1.1.0",
+          "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-1.1.0.tgz",
+          "integrity": "sha1-5md4PZLonb00KBi1IwudYqZyrRg=",
+          "dev": true
+        },
+        "minimatch": {
+          "version": "3.0.4",
+          "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz",
+          "integrity": "sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==",
           "dev": true,
           "requires": {
-            "escape-string-regexp": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz",
-            "object-assign": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz"
+            "brace-expansion": "1.1.8"
           }
         },
-        "onetime": {
-          "version": "https://registry.npmjs.org/onetime/-/onetime-1.1.0.tgz",
-          "integrity": "sha1-ofeDj4MUxRbwXs78vEzP4EtO14k=",
+        "minimist": {
+          "version": "0.0.8",
+          "resolved": "https://registry.npmjs.org/minimist/-/minimist-0.0.8.tgz",
+          "integrity": "sha1-hX/Kv8M5fSYluCKCYuhqp6ARsF0=",
           "dev": true
         },
-        "restore-cursor": {
-          "version": "https://registry.npmjs.org/restore-cursor/-/restore-cursor-1.0.1.tgz",
-          "integrity": "sha1-NGYfRohjJ/7SmRR5FSJS35LapUE=",
+        "mkdirp": {
+          "version": "0.5.1",
+          "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.1.tgz",
+          "integrity": "sha1-MAV0OOrGz3+MR2fzhkjWaX11yQM=",
           "dev": true,
           "requires": {
-            "exit-hook": "https://registry.npmjs.org/exit-hook/-/exit-hook-1.1.1.tgz",
-            "onetime": "https://registry.npmjs.org/onetime/-/onetime-1.1.0.tgz"
+            "minimist": "0.0.8"
           }
-        }
-      }
-    },
-    "load-json-file": {
-      "version": "https://registry.npmjs.org/load-json-file/-/load-json-file-1.1.0.tgz",
-      "integrity": "sha1-lWkFcI1YtLq0wiYbBPWfMcmTdMA=",
-      "requires": {
-        "graceful-fs": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.1.11.tgz",
-        "parse-json": "https://registry.npmjs.org/parse-json/-/parse-json-2.2.0.tgz",
-        "pify": "https://registry.npmjs.org/pify/-/pify-2.3.0.tgz",
-        "pinkie-promise": "https://registry.npmjs.org/pinkie-promise/-/pinkie-promise-2.0.1.tgz",
-        "strip-bom": "https://registry.npmjs.org/strip-bom/-/strip-bom-2.0.0.tgz"
-      }
-    },
-    "locate-path": {
-      "version": "https://registry.npmjs.org/locate-path/-/locate-path-2.0.0.tgz",
-      "integrity": "sha1-K1aLJl7slExtnA3pw9u7ygNUzY4=",
-      "dev": true,
-      "requires": {
-        "p-locate": "https://registry.npmjs.org/p-locate/-/p-locate-2.0.0.tgz",
-        "path-exists": "https://registry.npmjs.org/path-exists/-/path-exists-3.0.0.tgz"
-      },
-      "dependencies": {
-        "path-exists": {
-          "version": "https://registry.npmjs.org/path-exists/-/path-exists-3.0.0.tgz",
-          "integrity": "sha1-zg6+ql94yxiSXqfYENe1mwEP1RU=",
+        },
+        "ms": {
+          "version": "2.0.0",
+          "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz",
+          "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=",
           "dev": true
-        }
-      }
-    },
-    "lodash": {
-      "version": "https://registry.npmjs.org/lodash/-/lodash-4.17.4.tgz",
-      "integrity": "sha1-eCA6TRwyiuHYbcpkYONptX9AVa4="
-    },
-    "lodash.assign": {
-      "version": "https://registry.npmjs.org/lodash.assign/-/lodash.assign-4.2.0.tgz",
-      "integrity": "sha1-DZnzzNem0mHRm9rrkkUAXShYCOc="
-    },
-    "lodash.clonedeep": {
-      "version": "https://registry.npmjs.org/lodash.clonedeep/-/lodash.clonedeep-4.5.0.tgz",
-      "integrity": "sha1-4j8/nE+Pvd6HJSnBBxhXoIblzO8="
-    },
-    "lodash.cond": {
-      "version": "https://registry.npmjs.org/lodash.cond/-/lodash.cond-4.5.2.tgz",
-      "integrity": "sha1-9HGh2khr5g9quVXRcRVSPdHSVdU=",
-      "dev": true
-    },
-    "lodash.mergewith": {
-      "version": "https://registry.npmjs.org/lodash.mergewith/-/lodash.mergewith-4.6.0.tgz",
-      "integrity": "sha1-FQzwoWeR9ZA7iJHqsVRgknS96lU="
-    },
-    "log-symbols": {
-      "version": "https://registry.npmjs.org/log-symbols/-/log-symbols-2.1.0.tgz",
-      "integrity": "sha1-81+mDieIMrU43E3dy7R4pF0+O+Y=",
-      "dev": true,
-      "requires": {
-        "chalk": "https://registry.npmjs.org/chalk/-/chalk-2.3.0.tgz"
-      },
-      "dependencies": {
-        "ansi-styles": {
-          "version": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.0.tgz",
-          "integrity": "sha1-wVm41b4PnlpvNG2rlPFs4CIWG4g=",
+        },
+        "mute-stream": {
+          "version": "0.0.7",
+          "resolved": "https://registry.npmjs.org/mute-stream/-/mute-stream-0.0.7.tgz",
+          "integrity": "sha1-MHXOk7whuPq0PhvE2n6BFe0ee6s=",
+          "dev": true
+        },
+        "natural-compare": {
+          "version": "1.4.0",
+          "resolved": "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz",
+          "integrity": "sha1-Sr6/7tdUHywnrPspvbvRXI1bpPc=",
+          "dev": true
+        },
+        "object-assign": {
+          "version": "4.1.1",
+          "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz",
+          "integrity": "sha1-IQmtx5ZYh8/AXLvUQsrIv7s2CGM=",
+          "dev": true
+        },
+        "once": {
+          "version": "1.4.0",
+          "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz",
+          "integrity": "sha1-WDsap3WWHUsROsF9nFC6753Xa9E=",
           "dev": true,
           "requires": {
-            "color-convert": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.0.tgz"
+            "wrappy": "1.0.2"
           }
         },
-        "chalk": {
-          "version": "https://registry.npmjs.org/chalk/-/chalk-2.3.0.tgz",
-          "integrity": "sha1-tepI78nBeT3MybR2fJORTT8tUro=",
+        "onetime": {
+          "version": "2.0.1",
+          "resolved": "https://registry.npmjs.org/onetime/-/onetime-2.0.1.tgz",
+          "integrity": "sha1-BnQoIw/WdEOyeUsiu6UotoZ5YtQ=",
           "dev": true,
           "requires": {
-            "ansi-styles": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.0.tgz",
-            "escape-string-regexp": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz",
-            "supports-color": "https://registry.npmjs.org/supports-color/-/supports-color-4.5.0.tgz"
+            "mimic-fn": "1.1.0"
           }
         },
-        "supports-color": {
-          "version": "https://registry.npmjs.org/supports-color/-/supports-color-4.5.0.tgz",
-          "integrity": "sha1-vnoN5ITexcXN34s9WRJQRJEvY1s=",
+        "optionator": {
+          "version": "0.8.2",
+          "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.8.2.tgz",
+          "integrity": "sha1-NkxeQJ0/TWMB1sC0wFu6UBgK62Q=",
           "dev": true,
           "requires": {
-            "has-flag": "https://registry.npmjs.org/has-flag/-/has-flag-2.0.0.tgz"
+            "deep-is": "0.1.3",
+            "fast-levenshtein": "2.0.6",
+            "levn": "0.3.0",
+            "prelude-ls": "1.1.2",
+            "type-check": "0.3.2",
+            "wordwrap": "1.0.0"
           }
-        }
-      }
-    },
-    "log-update": {
-      "version": "https://registry.npmjs.org/log-update/-/log-update-1.0.2.tgz",
-      "integrity": "sha1-GZKfZMQJPS0ucHWh2tivWcKWuNE=",
-      "dev": true,
-      "requires": {
-        "ansi-escapes": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-1.4.0.tgz",
-        "cli-cursor": "https://registry.npmjs.org/cli-cursor/-/cli-cursor-1.0.2.tgz"
-      },
-      "dependencies": {
-        "ansi-escapes": {
-          "version": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-1.4.0.tgz",
-          "integrity": "sha1-06ioOzGapneTZisT52HHkRQiMG4=",
+        },
+        "os-tmpdir": {
+          "version": "1.0.2",
+          "resolved": "https://registry.npmjs.org/os-tmpdir/-/os-tmpdir-1.0.2.tgz",
+          "integrity": "sha1-u+Z0BseaqFxc/sdm/lc0VV36EnQ=",
           "dev": true
         },
-        "cli-cursor": {
-          "version": "https://registry.npmjs.org/cli-cursor/-/cli-cursor-1.0.2.tgz",
-          "integrity": "sha1-ZNo/fValRBLll5S9Ytw1KV6PKYc=",
-          "dev": true,
-          "requires": {
-            "restore-cursor": "https://registry.npmjs.org/restore-cursor/-/restore-cursor-1.0.1.tgz"
-          }
+        "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=",
+          "dev": true
         },
-        "onetime": {
-          "version": "https://registry.npmjs.org/onetime/-/onetime-1.1.0.tgz",
-          "integrity": "sha1-ofeDj4MUxRbwXs78vEzP4EtO14k=",
+        "path-is-inside": {
+          "version": "1.0.2",
+          "resolved": "https://registry.npmjs.org/path-is-inside/-/path-is-inside-1.0.2.tgz",
+          "integrity": "sha1-NlQX3t5EQw0cEa9hAn+s8HS9/FM=",
           "dev": true
         },
-        "restore-cursor": {
-          "version": "https://registry.npmjs.org/restore-cursor/-/restore-cursor-1.0.1.tgz",
-          "integrity": "sha1-NGYfRohjJ/7SmRR5FSJS35LapUE=",
+        "pify": {
+          "version": "2.3.0",
+          "resolved": "https://registry.npmjs.org/pify/-/pify-2.3.0.tgz",
+          "integrity": "sha1-7RQaasBDqEnqWISY59yosVMw6Qw=",
+          "dev": true
+        },
+        "pinkie": {
+          "version": "2.0.4",
+          "resolved": "https://registry.npmjs.org/pinkie/-/pinkie-2.0.4.tgz",
+          "integrity": "sha1-clVrgM+g1IqXToDnckjoDtT3+HA=",
+          "dev": true
+        },
+        "pinkie-promise": {
+          "version": "2.0.1",
+          "resolved": "https://registry.npmjs.org/pinkie-promise/-/pinkie-promise-2.0.1.tgz",
+          "integrity": "sha1-ITXW36ejWMBprJsXh3YogihFD/o=",
           "dev": true,
           "requires": {
-            "exit-hook": "https://registry.npmjs.org/exit-hook/-/exit-hook-1.1.1.tgz",
-            "onetime": "https://registry.npmjs.org/onetime/-/onetime-1.1.0.tgz"
-          }
-        }
-      }
-    },
-    "loose-envify": {
-      "version": "https://registry.npmjs.org/loose-envify/-/loose-envify-1.3.1.tgz",
-      "integrity": "sha1-0aitM/qc4OcT1l/dCsi3SNR4yEg=",
-      "requires": {
-        "js-tokens": "https://registry.npmjs.org/js-tokens/-/js-tokens-3.0.2.tgz"
-      }
-    },
-    "loud-rejection": {
-      "version": "https://registry.npmjs.org/loud-rejection/-/loud-rejection-1.6.0.tgz",
-      "integrity": "sha1-W0b4AUft7leIcPCG0Eghz5mOVR8=",
-      "requires": {
-        "currently-unhandled": "https://registry.npmjs.org/currently-unhandled/-/currently-unhandled-0.4.1.tgz",
-        "signal-exit": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.2.tgz"
-      }
-    },
-    "lowercase-keys": {
-      "version": "https://registry.npmjs.org/lowercase-keys/-/lowercase-keys-1.0.0.tgz",
-      "integrity": "sha1-TjNms55/VFfjXxMkvfb4jQv8cwY="
-    },
-    "lru-cache": {
-      "version": "https://registry.npmjs.org/lru-cache/-/lru-cache-4.1.1.tgz",
-      "integrity": "sha1-Yi4y6CSItJJ5EUpPns9F581rulU=",
-      "requires": {
-        "pseudomap": "https://registry.npmjs.org/pseudomap/-/pseudomap-1.0.2.tgz",
-        "yallist": "https://registry.npmjs.org/yallist/-/yallist-2.1.2.tgz"
-      }
-    },
-    "map-obj": {
-      "version": "https://registry.npmjs.org/map-obj/-/map-obj-1.0.1.tgz",
-      "integrity": "sha1-2TPOuSBdgr3PSIb2dCvcK03qFG0="
-    },
-    "material-colors": {
-      "version": "https://registry.npmjs.org/material-colors/-/material-colors-1.2.5.tgz",
-      "integrity": "sha1-UpJZPmdUyxvMK5gDDk4Najr8nqE="
-    },
-    "meow": {
-      "version": "https://registry.npmjs.org/meow/-/meow-3.7.0.tgz",
-      "integrity": "sha1-cstmi0JSKCkKu/qFaJJYcwioAfs=",
-      "requires": {
-        "camelcase-keys": "https://registry.npmjs.org/camelcase-keys/-/camelcase-keys-2.1.0.tgz",
-        "decamelize": "https://registry.npmjs.org/decamelize/-/decamelize-1.2.0.tgz",
-        "loud-rejection": "https://registry.npmjs.org/loud-rejection/-/loud-rejection-1.6.0.tgz",
-        "map-obj": "https://registry.npmjs.org/map-obj/-/map-obj-1.0.1.tgz",
-        "minimist": "https://registry.npmjs.org/minimist/-/minimist-1.2.0.tgz",
-        "normalize-package-data": "https://registry.npmjs.org/normalize-package-data/-/normalize-package-data-2.4.0.tgz",
-        "object-assign": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz",
-        "read-pkg-up": "https://registry.npmjs.org/read-pkg-up/-/read-pkg-up-1.0.1.tgz",
-        "redent": "https://registry.npmjs.org/redent/-/redent-1.0.0.tgz",
-        "trim-newlines": "https://registry.npmjs.org/trim-newlines/-/trim-newlines-1.0.0.tgz"
-      }
-    },
-    "meteor-node-stubs": {
-      "version": "https://registry.npmjs.org/meteor-node-stubs/-/meteor-node-stubs-0.3.2.tgz",
-      "integrity": "sha1-LJIkqRGx5mwUHGoXsaPcktw4d5U=",
-      "requires": {
-        "assert": "https://registry.npmjs.org/assert/-/assert-1.4.1.tgz",
-        "browserify-zlib": "https://registry.npmjs.org/browserify-zlib/-/browserify-zlib-0.1.4.tgz",
-        "buffer": "https://registry.npmjs.org/buffer/-/buffer-4.9.1.tgz",
-        "console-browserify": "https://registry.npmjs.org/console-browserify/-/console-browserify-1.1.0.tgz",
-        "constants-browserify": "https://registry.npmjs.org/constants-browserify/-/constants-browserify-1.0.0.tgz",
-        "crypto-browserify": "https://registry.npmjs.org/crypto-browserify/-/crypto-browserify-3.11.1.tgz",
-        "domain-browser": "https://registry.npmjs.org/domain-browser/-/domain-browser-1.1.7.tgz",
-        "events": "https://registry.npmjs.org/events/-/events-1.1.1.tgz",
-        "http-browserify": "https://registry.npmjs.org/http-browserify/-/http-browserify-1.7.0.tgz",
-        "https-browserify": "https://registry.npmjs.org/https-browserify/-/https-browserify-0.0.1.tgz",
-        "os-browserify": "https://registry.npmjs.org/os-browserify/-/os-browserify-0.2.1.tgz",
-        "path-browserify": "https://registry.npmjs.org/path-browserify/-/path-browserify-0.0.0.tgz",
-        "process": "https://registry.npmjs.org/process/-/process-0.11.10.tgz",
-        "punycode": "https://registry.npmjs.org/punycode/-/punycode-1.4.1.tgz",
-        "querystring-es3": "https://registry.npmjs.org/querystring-es3/-/querystring-es3-0.2.1.tgz",
-        "readable-stream": "git+https://github.com/meteor/readable-stream.git#d64a64aa6061b9b6855feff4d09e58fb3b2e4502",
-        "stream-browserify": "https://registry.npmjs.org/stream-browserify/-/stream-browserify-2.0.1.tgz",
-        "string_decoder": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.0.3.tgz",
-        "timers-browserify": "https://registry.npmjs.org/timers-browserify/-/timers-browserify-1.4.2.tgz",
-        "tty-browserify": "https://registry.npmjs.org/tty-browserify/-/tty-browserify-0.0.0.tgz",
-        "url": "https://registry.npmjs.org/url/-/url-0.11.0.tgz",
-        "util": "https://registry.npmjs.org/util/-/util-0.10.3.tgz",
-        "vm-browserify": "https://registry.npmjs.org/vm-browserify/-/vm-browserify-0.0.4.tgz"
-      },
-      "dependencies": {
-        "asn1.js": {
-          "version": "https://registry.npmjs.org/asn1.js/-/asn1.js-4.9.1.tgz",
-          "integrity": "sha1-SLokC0WpKA6UdImQull9IWYX/UA=",
-          "requires": {
-            "bn.js": "https://registry.npmjs.org/bn.js/-/bn.js-4.11.8.tgz",
-            "inherits": "https://registry.npmjs.org/inherits/-/inherits-2.0.1.tgz",
-            "minimalistic-assert": "https://registry.npmjs.org/minimalistic-assert/-/minimalistic-assert-1.0.0.tgz"
+            "pinkie": "2.0.4"
           }
         },
-        "assert": {
-          "version": "https://registry.npmjs.org/assert/-/assert-1.4.1.tgz",
-          "integrity": "sha1-mZEtWRg2tab1s0XA8H7vwI/GXZE=",
-          "requires": {
-            "util": "https://registry.npmjs.org/util/-/util-0.10.3.tgz"
-          }
+        "pluralize": {
+          "version": "7.0.0",
+          "resolved": "https://registry.npmjs.org/pluralize/-/pluralize-7.0.0.tgz",
+          "integrity": "sha512-ARhBOdzS3e41FbkW/XWrTEtukqqLoK5+Z/4UeDaLuSW+39JPeFgs4gCGqsrJHVZX0fUrx//4OF0K1CUGwlIFow==",
+          "dev": true
         },
-        "balanced-match": {
-          "version": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.0.tgz",
-          "integrity": "sha1-ibTRmasr7kneFk6gK4nORi1xt2c="
+        "prelude-ls": {
+          "version": "1.1.2",
+          "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.1.2.tgz",
+          "integrity": "sha1-IZMqVJ9eUv/ZqCf1cOBL5iqX2lQ=",
+          "dev": true
         },
-        "Base64": {
-          "version": "https://registry.npmjs.org/Base64/-/Base64-0.2.1.tgz",
-          "integrity": "sha1-ujpCMHCOGGcFBl5mur3Uw1z2ACg="
+        "process-nextick-args": {
+          "version": "1.0.7",
+          "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-1.0.7.tgz",
+          "integrity": "sha1-FQ4gt1ZZCtP5EJPyWk8q2L/zC6M=",
+          "dev": true
         },
-        "base64-js": {
-          "version": "https://registry.npmjs.org/base64-js/-/base64-js-1.2.1.tgz",
-          "integrity": "sha512-dwVUVIXsBZXwTuwnXI9RK8sBmgq09NDHzyR9SAph9eqk76gKK2JSQmZARC2zRC81JC2QTtxD0ARU5qTS25gIGw=="
+        "progress": {
+          "version": "2.0.0",
+          "resolved": "https://registry.npmjs.org/progress/-/progress-2.0.0.tgz",
+          "integrity": "sha1-ihvjZr+Pwj2yvSPxDG/pILQ4nR8=",
+          "dev": true
         },
-        "bn.js": {
-          "version": "https://registry.npmjs.org/bn.js/-/bn.js-4.11.8.tgz",
-          "integrity": "sha512-ItfYfPLkWHUjckQCk8xC+LwxgK8NYcXywGigJgSwOP8Y2iyWT4f2vsZnoOXTTbo+o5yXmIUJ4gn5538SO5S3gA=="
+        "pseudomap": {
+          "version": "1.0.2",
+          "resolved": "https://registry.npmjs.org/pseudomap/-/pseudomap-1.0.2.tgz",
+          "integrity": "sha1-8FKijacOYYkX7wqKw0wa5aaChrM=",
+          "dev": true
         },
-        "brace-expansion": {
-          "version": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.8.tgz",
-          "integrity": "sha1-wHshHHyVLsH479Uad+8NHTmQopI=",
+        "readable-stream": {
+          "version": "2.3.3",
+          "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.3.tgz",
+          "integrity": "sha512-m+qzzcn7KUxEmd1gMbchF+Y2eIUbieUaxkWtptyHywrX0rE8QEYqPC07Vuy4Wm32/xE16NcdBctb8S0Xe/5IeQ==",
+          "dev": true,
           "requires": {
-            "balanced-match": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.0.tgz",
-            "concat-map": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz"
+            "core-util-is": "1.0.2",
+            "inherits": "2.0.3",
+            "isarray": "1.0.0",
+            "process-nextick-args": "1.0.7",
+            "safe-buffer": "5.1.1",
+            "string_decoder": "1.0.3",
+            "util-deprecate": "1.0.2"
           }
         },
-        "brorand": {
-          "version": "https://registry.npmjs.org/brorand/-/brorand-1.1.0.tgz",
-          "integrity": "sha1-EsJe/kCkXjwyPrhnWgoM5XsiNx8="
-        },
-        "browserify-aes": {
-          "version": "https://registry.npmjs.org/browserify-aes/-/browserify-aes-1.1.0.tgz",
-          "integrity": "sha512-W2bIMLYoZ9oow7TyePpMJk9l9LY7O3R61a/68bVCDOtnJynnwe3ZeW2IzzSkrQnPKNdJrxVDn3ALZNisSBwb7g==",
+        "require-uncached": {
+          "version": "1.0.3",
+          "resolved": "https://registry.npmjs.org/require-uncached/-/require-uncached-1.0.3.tgz",
+          "integrity": "sha1-Tg1W1slmL9MeQwEcS5WqSZVUIdM=",
+          "dev": true,
           "requires": {
-            "buffer-xor": "https://registry.npmjs.org/buffer-xor/-/buffer-xor-1.0.3.tgz",
-            "cipher-base": "https://registry.npmjs.org/cipher-base/-/cipher-base-1.0.4.tgz",
-            "create-hash": "https://registry.npmjs.org/create-hash/-/create-hash-1.1.3.tgz",
-            "evp_bytestokey": "https://registry.npmjs.org/evp_bytestokey/-/evp_bytestokey-1.0.3.tgz",
-            "inherits": "https://registry.npmjs.org/inherits/-/inherits-2.0.1.tgz",
-            "safe-buffer": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.1.tgz"
+            "caller-path": "0.1.0",
+            "resolve-from": "1.0.1"
           }
         },
-        "browserify-cipher": {
-          "version": "https://registry.npmjs.org/browserify-cipher/-/browserify-cipher-1.0.0.tgz",
-          "integrity": "sha1-mYgkSHS/XtTijalWZtzWasj8Njo=",
-          "requires": {
-            "browserify-aes": "https://registry.npmjs.org/browserify-aes/-/browserify-aes-1.1.0.tgz",
-            "browserify-des": "https://registry.npmjs.org/browserify-des/-/browserify-des-1.0.0.tgz",
-            "evp_bytestokey": "https://registry.npmjs.org/evp_bytestokey/-/evp_bytestokey-1.0.3.tgz"
-          }
+        "resolve-from": {
+          "version": "1.0.1",
+          "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-1.0.1.tgz",
+          "integrity": "sha1-Jsv+k10a7uq7Kbw/5a6wHpPUQiY=",
+          "dev": true
         },
-        "browserify-des": {
-          "version": "https://registry.npmjs.org/browserify-des/-/browserify-des-1.0.0.tgz",
-          "integrity": "sha1-2qJ3cXRwki7S/hhZQRihdUOXId0=",
+        "restore-cursor": {
+          "version": "2.0.0",
+          "resolved": "https://registry.npmjs.org/restore-cursor/-/restore-cursor-2.0.0.tgz",
+          "integrity": "sha1-n37ih/gv0ybU/RYpI9YhKe7g368=",
+          "dev": true,
           "requires": {
-            "cipher-base": "https://registry.npmjs.org/cipher-base/-/cipher-base-1.0.4.tgz",
-            "des.js": "https://registry.npmjs.org/des.js/-/des.js-1.0.0.tgz",
-            "inherits": "https://registry.npmjs.org/inherits/-/inherits-2.0.1.tgz"
+            "onetime": "2.0.1",
+            "signal-exit": "3.0.2"
           }
         },
-        "browserify-rsa": {
-          "version": "https://registry.npmjs.org/browserify-rsa/-/browserify-rsa-4.0.1.tgz",
-          "integrity": "sha1-IeCr+vbyApzy+vsTNWenAdQTVSQ=",
+        "rimraf": {
+          "version": "2.6.2",
+          "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.6.2.tgz",
+          "integrity": "sha512-lreewLK/BlghmxtfH36YYVg1i8IAce4TI7oao75I1g245+6BctqTVQiBP3YUJ9C6DQOXJmkYR9X9fCLtCOJc5w==",
+          "dev": true,
           "requires": {
-            "bn.js": "https://registry.npmjs.org/bn.js/-/bn.js-4.11.8.tgz",
-            "randombytes": "https://registry.npmjs.org/randombytes/-/randombytes-2.0.5.tgz"
+            "glob": "7.1.2"
           }
         },
-        "browserify-sign": {
-          "version": "https://registry.npmjs.org/browserify-sign/-/browserify-sign-4.0.4.tgz",
-          "integrity": "sha1-qk62jl17ZYuqa/alfmMMvXqT0pg=",
+        "run-async": {
+          "version": "2.3.0",
+          "resolved": "https://registry.npmjs.org/run-async/-/run-async-2.3.0.tgz",
+          "integrity": "sha1-A3GrSuC91yDUFm19/aZP96RFpsA=",
+          "dev": true,
           "requires": {
-            "bn.js": "https://registry.npmjs.org/bn.js/-/bn.js-4.11.8.tgz",
-            "browserify-rsa": "https://registry.npmjs.org/browserify-rsa/-/browserify-rsa-4.0.1.tgz",
-            "create-hash": "https://registry.npmjs.org/create-hash/-/create-hash-1.1.3.tgz",
-            "create-hmac": "https://registry.npmjs.org/create-hmac/-/create-hmac-1.1.6.tgz",
-            "elliptic": "https://registry.npmjs.org/elliptic/-/elliptic-6.4.0.tgz",
-            "inherits": "https://registry.npmjs.org/inherits/-/inherits-2.0.1.tgz",
-            "parse-asn1": "https://registry.npmjs.org/parse-asn1/-/parse-asn1-5.1.0.tgz"
+            "is-promise": "2.1.0"
           }
         },
-        "browserify-zlib": {
-          "version": "https://registry.npmjs.org/browserify-zlib/-/browserify-zlib-0.1.4.tgz",
-          "integrity": "sha1-uzX4pRn2AOD6a4SFJByXnQFB+y0=",
-          "requires": {
-            "pako": "https://registry.npmjs.org/pako/-/pako-0.2.9.tgz"
-          }
+        "rx-lite": {
+          "version": "4.0.8",
+          "resolved": "https://registry.npmjs.org/rx-lite/-/rx-lite-4.0.8.tgz",
+          "integrity": "sha1-Cx4Rr4vESDbwSmQH6S2kJGe3lEQ=",
+          "dev": true
         },
-        "buffer": {
-          "version": "https://registry.npmjs.org/buffer/-/buffer-4.9.1.tgz",
-          "integrity": "sha1-bRu2AbB6TvztlwlBMgkwJ8lbwpg=",
+        "rx-lite-aggregates": {
+          "version": "4.0.8",
+          "resolved": "https://registry.npmjs.org/rx-lite-aggregates/-/rx-lite-aggregates-4.0.8.tgz",
+          "integrity": "sha1-dTuHqJoRyVRnxKwWJsTvxOBcZ74=",
+          "dev": true,
           "requires": {
-            "base64-js": "https://registry.npmjs.org/base64-js/-/base64-js-1.2.1.tgz",
-            "ieee754": "https://registry.npmjs.org/ieee754/-/ieee754-1.1.8.tgz",
-            "isarray": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz"
+            "rx-lite": "4.0.8"
           }
         },
-        "buffer-xor": {
-          "version": "https://registry.npmjs.org/buffer-xor/-/buffer-xor-1.0.3.tgz",
-          "integrity": "sha1-JuYe0UIvtw3ULm42cp7VHYVf6Nk="
+        "safe-buffer": {
+          "version": "5.1.1",
+          "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.1.tgz",
+          "integrity": "sha512-kKvNJn6Mm93gAczWVJg7wH+wGYWNrDHdWvpUmHyEsgCtIwwo3bqPtV4tR5tuPaUhTOo/kvhVwd8XwwOllGYkbg==",
+          "dev": true
         },
-        "cipher-base": {
-          "version": "https://registry.npmjs.org/cipher-base/-/cipher-base-1.0.4.tgz",
-          "integrity": "sha512-Kkht5ye6ZGmwv40uUDZztayT2ThLQGfnj/T71N/XzeZeo3nf8foyW7zGTsPYkEya3m5f3cAypH+qe7YOrM1U2Q==",
+        "semver": {
+          "version": "5.4.1",
+          "resolved": "https://registry.npmjs.org/semver/-/semver-5.4.1.tgz",
+          "integrity": "sha512-WfG/X9+oATh81XtllIo/I8gOiY9EXRdv1cQdyykeXK17YcUW3EXUAi2To4pcH6nZtJPr7ZOpM5OMyWJZm+8Rsg==",
+          "dev": true
+        },
+        "shebang-command": {
+          "version": "1.2.0",
+          "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-1.2.0.tgz",
+          "integrity": "sha1-RKrGW2lbAzmJaMOfNj/uXer98eo=",
+          "dev": true,
           "requires": {
-            "inherits": "https://registry.npmjs.org/inherits/-/inherits-2.0.1.tgz",
-            "safe-buffer": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.1.tgz"
+            "shebang-regex": "1.0.0"
           }
         },
-        "concat-map": {
-          "version": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz",
-          "integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s="
+        "shebang-regex": {
+          "version": "1.0.0",
+          "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-1.0.0.tgz",
+          "integrity": "sha1-2kL0l0DAtC2yypcoVxyxkMmO/qM=",
+          "dev": true
         },
-        "console-browserify": {
-          "version": "https://registry.npmjs.org/console-browserify/-/console-browserify-1.1.0.tgz",
-          "integrity": "sha1-8CQcRXMKn8YyOyBtvzjtx0HQuxA=",
+        "signal-exit": {
+          "version": "3.0.2",
+          "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.2.tgz",
+          "integrity": "sha1-tf3AjxKH6hF4Yo5BXiUTK3NkbG0=",
+          "dev": true
+        },
+        "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==",
+          "dev": true,
           "requires": {
-            "date-now": "https://registry.npmjs.org/date-now/-/date-now-0.1.4.tgz"
+            "is-fullwidth-code-point": "2.0.0"
           }
         },
-        "constants-browserify": {
-          "version": "https://registry.npmjs.org/constants-browserify/-/constants-browserify-1.0.0.tgz",
-          "integrity": "sha1-wguW2MYXdIqvHBYCF2DNJ/y4y3U="
+        "sprintf-js": {
+          "version": "1.0.3",
+          "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz",
+          "integrity": "sha1-BOaSb2YolTVPPdAVIDYzuFcpfiw=",
+          "dev": true
         },
-        "create-ecdh": {
-          "version": "https://registry.npmjs.org/create-ecdh/-/create-ecdh-4.0.0.tgz",
-          "integrity": "sha1-iIxyNZbN92EvZJgjPuvXo1MBc30=",
+        "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": {
-            "bn.js": "https://registry.npmjs.org/bn.js/-/bn.js-4.11.8.tgz",
-            "elliptic": "https://registry.npmjs.org/elliptic/-/elliptic-6.4.0.tgz"
+            "is-fullwidth-code-point": "2.0.0",
+            "strip-ansi": "4.0.0"
           }
         },
-        "create-hash": {
-          "version": "https://registry.npmjs.org/create-hash/-/create-hash-1.1.3.tgz",
-          "integrity": "sha1-YGBCrIuSYnUPSDyt2rD1gZFy2P0=",
+        "string_decoder": {
+          "version": "1.0.3",
+          "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.0.3.tgz",
+          "integrity": "sha512-4AH6Z5fzNNBcH+6XDMfA/BTt87skxqJlO0lAh3Dker5zThcAxG6mKz+iGu308UKoPPQ8Dcqx/4JhujzltRa+hQ==",
+          "dev": true,
           "requires": {
-            "cipher-base": "https://registry.npmjs.org/cipher-base/-/cipher-base-1.0.4.tgz",
-            "inherits": "https://registry.npmjs.org/inherits/-/inherits-2.0.1.tgz",
-            "ripemd160": "https://registry.npmjs.org/ripemd160/-/ripemd160-2.0.1.tgz",
-            "sha.js": "https://registry.npmjs.org/sha.js/-/sha.js-2.4.9.tgz"
+            "safe-buffer": "5.1.1"
           }
         },
-        "create-hmac": {
-          "version": "https://registry.npmjs.org/create-hmac/-/create-hmac-1.1.6.tgz",
-          "integrity": "sha1-rLniIaThe9sHbpBlfEK5PjcmzwY=",
+        "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": {
-            "cipher-base": "https://registry.npmjs.org/cipher-base/-/cipher-base-1.0.4.tgz",
-            "create-hash": "https://registry.npmjs.org/create-hash/-/create-hash-1.1.3.tgz",
-            "inherits": "https://registry.npmjs.org/inherits/-/inherits-2.0.1.tgz",
-            "ripemd160": "https://registry.npmjs.org/ripemd160/-/ripemd160-2.0.1.tgz",
-            "safe-buffer": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.1.tgz",
-            "sha.js": "https://registry.npmjs.org/sha.js/-/sha.js-2.4.9.tgz"
+            "ansi-regex": "3.0.0"
+          },
+          "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
+            }
           }
         },
-        "crypto-browserify": {
-          "version": "https://registry.npmjs.org/crypto-browserify/-/crypto-browserify-3.11.1.tgz",
-          "integrity": "sha512-Na7ZlwCOqoaW5RwUK1WpXws2kv8mNhWdTlzob0UXulk6G9BDbyiJaGTYBIX61Ozn9l1EPPJpICZb4DaOpT9NlQ==",
+        "strip-json-comments": {
+          "version": "2.0.1",
+          "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-2.0.1.tgz",
+          "integrity": "sha1-PFMZQukIwml8DsNEhYwobHygpgo=",
+          "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=",
+          "dev": true
+        },
+        "table": {
+          "version": "4.0.2",
+          "resolved": "https://registry.npmjs.org/table/-/table-4.0.2.tgz",
+          "integrity": "sha512-UUkEAPdSGxtRpiV9ozJ5cMTtYiqz7Ni1OGqLXRCynrvzdtR1p+cfOWe2RJLwvUG8hNanaSRjecIqwOjqeatDsA==",
+          "dev": true,
           "requires": {
-            "browserify-cipher": "https://registry.npmjs.org/browserify-cipher/-/browserify-cipher-1.0.0.tgz",
-            "browserify-sign": "https://registry.npmjs.org/browserify-sign/-/browserify-sign-4.0.4.tgz",
-            "create-ecdh": "https://registry.npmjs.org/create-ecdh/-/create-ecdh-4.0.0.tgz",
-            "create-hash": "https://registry.npmjs.org/create-hash/-/create-hash-1.1.3.tgz",
-            "create-hmac": "https://registry.npmjs.org/create-hmac/-/create-hmac-1.1.6.tgz",
-            "diffie-hellman": "https://registry.npmjs.org/diffie-hellman/-/diffie-hellman-5.0.2.tgz",
-            "inherits": "https://registry.npmjs.org/inherits/-/inherits-2.0.1.tgz",
-            "pbkdf2": "https://registry.npmjs.org/pbkdf2/-/pbkdf2-3.0.14.tgz",
-            "public-encrypt": "https://registry.npmjs.org/public-encrypt/-/public-encrypt-4.0.0.tgz",
-            "randombytes": "https://registry.npmjs.org/randombytes/-/randombytes-2.0.5.tgz"
+            "ajv": "5.5.2",
+            "ajv-keywords": "2.1.1",
+            "chalk": "2.3.0",
+            "lodash": "4.17.4",
+            "slice-ansi": "1.0.0",
+            "string-width": "2.1.1"
           }
         },
-        "date-now": {
-          "version": "https://registry.npmjs.org/date-now/-/date-now-0.1.4.tgz",
-          "integrity": "sha1-6vQ5/U1ISK105cx9vvIAZyueNFs="
+        "text-table": {
+          "version": "0.2.0",
+          "resolved": "https://registry.npmjs.org/text-table/-/text-table-0.2.0.tgz",
+          "integrity": "sha1-f17oI66AUgfACvLfSoTsP8+lcLQ=",
+          "dev": true
         },
-        "des.js": {
-          "version": "https://registry.npmjs.org/des.js/-/des.js-1.0.0.tgz",
-          "integrity": "sha1-wHTS4qpqipoH29YfmhXCzYPsjsw=",
+        "through": {
+          "version": "2.3.8",
+          "resolved": "https://registry.npmjs.org/through/-/through-2.3.8.tgz",
+          "integrity": "sha1-DdTJ/6q8NXlgsbckEV1+Doai4fU=",
+          "dev": true
+        },
+        "tmp": {
+          "version": "0.0.33",
+          "resolved": "https://registry.npmjs.org/tmp/-/tmp-0.0.33.tgz",
+          "integrity": "sha512-jRCJlojKnZ3addtTOjdIqoRuPEKBvNXcGYqzO6zWZX8KfKEpnGY5jfggJQ3EjKuu8D4bJRr0y+cYJFmYbImXGw==",
+          "dev": true,
           "requires": {
-            "inherits": "https://registry.npmjs.org/inherits/-/inherits-2.0.1.tgz",
-            "minimalistic-assert": "https://registry.npmjs.org/minimalistic-assert/-/minimalistic-assert-1.0.0.tgz"
+            "os-tmpdir": "1.0.2"
           }
         },
-        "diffie-hellman": {
-          "version": "https://registry.npmjs.org/diffie-hellman/-/diffie-hellman-5.0.2.tgz",
-          "integrity": "sha1-tYNXOScM/ias9jIJn97SoH8gnl4=",
+        "type-check": {
+          "version": "0.3.2",
+          "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.3.2.tgz",
+          "integrity": "sha1-WITKtRLPHTVeP7eE8wgEsrUg23I=",
+          "dev": true,
           "requires": {
-            "bn.js": "https://registry.npmjs.org/bn.js/-/bn.js-4.11.8.tgz",
-            "miller-rabin": "https://registry.npmjs.org/miller-rabin/-/miller-rabin-4.0.1.tgz",
-            "randombytes": "https://registry.npmjs.org/randombytes/-/randombytes-2.0.5.tgz"
+            "prelude-ls": "1.1.2"
           }
         },
-        "domain-browser": {
-          "version": "https://registry.npmjs.org/domain-browser/-/domain-browser-1.1.7.tgz",
-          "integrity": "sha1-hnqksJP6oF8d4IwG9NeyH9+GmLw="
+        "typedarray": {
+          "version": "0.0.6",
+          "resolved": "https://registry.npmjs.org/typedarray/-/typedarray-0.0.6.tgz",
+          "integrity": "sha1-hnrHTjhkGHsdPUfZlqeOxciDB3c=",
+          "dev": true
         },
-        "elliptic": {
-          "version": "https://registry.npmjs.org/elliptic/-/elliptic-6.4.0.tgz",
-          "integrity": "sha1-ysmvh2LIWDYYcAPI3+GT5eLq5d8=",
+        "util-deprecate": {
+          "version": "1.0.2",
+          "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz",
+          "integrity": "sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8=",
+          "dev": true
+        },
+        "which": {
+          "version": "1.3.0",
+          "resolved": "https://registry.npmjs.org/which/-/which-1.3.0.tgz",
+          "integrity": "sha512-xcJpopdamTuY5duC/KnTTNBraPK54YwpenP4lzxU8H91GudWpFv38u0CKjclE1Wi2EH2EDz5LRcHcKbCIzqGyg==",
+          "dev": true,
           "requires": {
-            "bn.js": "https://registry.npmjs.org/bn.js/-/bn.js-4.11.8.tgz",
-            "brorand": "https://registry.npmjs.org/brorand/-/brorand-1.1.0.tgz",
-            "hash.js": "https://registry.npmjs.org/hash.js/-/hash.js-1.1.3.tgz",
-            "hmac-drbg": "https://registry.npmjs.org/hmac-drbg/-/hmac-drbg-1.0.1.tgz",
-            "inherits": "https://registry.npmjs.org/inherits/-/inherits-2.0.1.tgz",
-            "minimalistic-assert": "https://registry.npmjs.org/minimalistic-assert/-/minimalistic-assert-1.0.0.tgz",
-            "minimalistic-crypto-utils": "https://registry.npmjs.org/minimalistic-crypto-utils/-/minimalistic-crypto-utils-1.0.1.tgz"
+            "isexe": "2.0.0"
           }
         },
-        "events": {
-          "version": "https://registry.npmjs.org/events/-/events-1.1.1.tgz",
-          "integrity": "sha1-nr23Y1rQmccNzEwqH1AEKI6L2SQ="
+        "wordwrap": {
+          "version": "1.0.0",
+          "resolved": "https://registry.npmjs.org/wordwrap/-/wordwrap-1.0.0.tgz",
+          "integrity": "sha1-J1hIEIkUVqQXHI0CJkQa3pDLyus=",
+          "dev": true
         },
-        "evp_bytestokey": {
-          "version": "https://registry.npmjs.org/evp_bytestokey/-/evp_bytestokey-1.0.3.tgz",
-          "integrity": "sha512-/f2Go4TognH/KvCISP7OUsHn85hT9nUkxxA9BEWxFn+Oj9o8ZNLm/40hdlgSLyuOimsrTKLUMEorQexp/aPQeA==",
+        "wrappy": {
+          "version": "1.0.2",
+          "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz",
+          "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=",
+          "dev": true
+        },
+        "write": {
+          "version": "0.2.1",
+          "resolved": "https://registry.npmjs.org/write/-/write-0.2.1.tgz",
+          "integrity": "sha1-X8A4KOJkzqP+kUVUdvejxWbLB1c=",
+          "dev": true,
           "requires": {
-            "md5.js": "https://registry.npmjs.org/md5.js/-/md5.js-1.3.4.tgz",
-            "safe-buffer": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.1.tgz"
+            "mkdirp": "0.5.1"
           }
         },
-        "fs.realpath": {
-          "version": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz",
-          "integrity": "sha1-FQStJSMVjKpA20onh8sBQRmU6k8="
+        "yallist": {
+          "version": "2.1.2",
+          "resolved": "https://registry.npmjs.org/yallist/-/yallist-2.1.2.tgz",
+          "integrity": "sha1-HBH5IY8HYImkfdUS+TxmmaaoHVI=",
+          "dev": true
+        }
+      }
+    },
+    "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==",
+      "dev": true,
+      "requires": {
+        "eslint-config-airbnb-base": "12.1.0"
+      }
+    },
+    "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==",
+      "dev": true,
+      "requires": {
+        "eslint-restricted-globals": "0.1.1"
+      },
+      "dependencies": {
+        "eslint-restricted-globals": {
+          "version": "0.1.1",
+          "resolved": "https://registry.npmjs.org/eslint-restricted-globals/-/eslint-restricted-globals-0.1.1.tgz",
+          "integrity": "sha1-NfDVy8ZMLj7WLpO0saevBbp+1Nc=",
+          "dev": true
+        }
+      }
+    },
+    "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==",
+      "dev": true,
+      "requires": {
+        "builtin-modules": "1.1.1",
+        "contains-path": "0.1.0",
+        "debug": "2.6.9",
+        "doctrine": "1.5.0",
+        "eslint-import-resolver-node": "0.3.2",
+        "eslint-module-utils": "2.1.1",
+        "has": "1.0.1",
+        "lodash.cond": "4.5.2",
+        "minimatch": "3.0.4",
+        "read-pkg-up": "2.0.0"
+      },
+      "dependencies": {
+        "balanced-match": {
+          "version": "1.0.0",
+          "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.0.tgz",
+          "integrity": "sha1-ibTRmasr7kneFk6gK4nORi1xt2c=",
+          "dev": true
         },
-        "glob": {
-          "version": "https://registry.npmjs.org/glob/-/glob-7.1.2.tgz",
-          "integrity": "sha1-wZyd+aAocC1nhhI4SmVSQExjbRU=",
+        "brace-expansion": {
+          "version": "1.1.8",
+          "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.8.tgz",
+          "integrity": "sha1-wHshHHyVLsH479Uad+8NHTmQopI=",
+          "dev": true,
           "requires": {
-            "fs.realpath": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz",
-            "inflight": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz",
-            "inherits": "https://registry.npmjs.org/inherits/-/inherits-2.0.1.tgz",
-            "minimatch": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz",
-            "once": "https://registry.npmjs.org/once/-/once-1.4.0.tgz",
-            "path-is-absolute": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz"
+            "balanced-match": "1.0.0",
+            "concat-map": "0.0.1"
           }
         },
-        "hash-base": {
-          "version": "https://registry.npmjs.org/hash-base/-/hash-base-2.0.2.tgz",
-          "integrity": "sha1-ZuodhW206KVHDK32/OI65SRO8uE=",
+        "builtin-modules": {
+          "version": "1.1.1",
+          "resolved": "https://registry.npmjs.org/builtin-modules/-/builtin-modules-1.1.1.tgz",
+          "integrity": "sha1-Jw8HbFpywC9bZaR9+Uxf46J4iS8=",
+          "dev": true
+        },
+        "concat-map": {
+          "version": "0.0.1",
+          "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz",
+          "integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=",
+          "dev": true
+        },
+        "contains-path": {
+          "version": "0.1.0",
+          "resolved": "https://registry.npmjs.org/contains-path/-/contains-path-0.1.0.tgz",
+          "integrity": "sha1-/ozxhP9mcLa67wGp1IYaXL7EEgo=",
+          "dev": true
+        },
+        "debug": {
+          "version": "2.6.9",
+          "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz",
+          "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==",
+          "dev": true,
           "requires": {
-            "inherits": "https://registry.npmjs.org/inherits/-/inherits-2.0.1.tgz"
+            "ms": "2.0.0"
           }
         },
-        "hash.js": {
-          "version": "https://registry.npmjs.org/hash.js/-/hash.js-1.1.3.tgz",
-          "integrity": "sha512-/UETyP0W22QILqS+6HowevwhEFJ3MBJnwTf75Qob9Wz9t0DPuisL8kW8YZMK62dHAKE1c1p+gY1TtOLY+USEHA==",
+        "doctrine": {
+          "version": "1.5.0",
+          "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-1.5.0.tgz",
+          "integrity": "sha1-N53Ocw9hZvds76TmcHoVmwLFpvo=",
+          "dev": true,
           "requires": {
-            "inherits": "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz",
-            "minimalistic-assert": "https://registry.npmjs.org/minimalistic-assert/-/minimalistic-assert-1.0.0.tgz"
-          },
-          "dependencies": {
-            "inherits": {
-              "version": "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz",
-              "integrity": "sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4="
-            }
+            "esutils": "2.0.2",
+            "isarray": "1.0.0"
           }
         },
-        "hmac-drbg": {
-          "version": "https://registry.npmjs.org/hmac-drbg/-/hmac-drbg-1.0.1.tgz",
-          "integrity": "sha1-0nRXAQJabHdabFRXk+1QL8DGSaE=",
+        "error-ex": {
+          "version": "1.3.1",
+          "resolved": "https://registry.npmjs.org/error-ex/-/error-ex-1.3.1.tgz",
+          "integrity": "sha1-+FWobOYa3E6GIcPNoh56dhLDqNw=",
+          "dev": true,
           "requires": {
-            "hash.js": "https://registry.npmjs.org/hash.js/-/hash.js-1.1.3.tgz",
-            "minimalistic-assert": "https://registry.npmjs.org/minimalistic-assert/-/minimalistic-assert-1.0.0.tgz",
-            "minimalistic-crypto-utils": "https://registry.npmjs.org/minimalistic-crypto-utils/-/minimalistic-crypto-utils-1.0.1.tgz"
+            "is-arrayish": "0.2.1"
           }
         },
-        "http-browserify": {
-          "version": "https://registry.npmjs.org/http-browserify/-/http-browserify-1.7.0.tgz",
-          "integrity": "sha1-M3la3nLfiKz7/TZ3PO/tp2RzWyA=",
+        "eslint-import-resolver-node": {
+          "version": "0.3.2",
+          "resolved": "https://registry.npmjs.org/eslint-import-resolver-node/-/eslint-import-resolver-node-0.3.2.tgz",
+          "integrity": "sha512-sfmTqJfPSizWu4aymbPr4Iidp5yKm8yDkHp+Ir3YiTHiiDfxh69mOUsmiqW6RZ9zRXFaF64GtYmN7e+8GHBv6Q==",
+          "dev": true,
           "requires": {
-            "Base64": "https://registry.npmjs.org/Base64/-/Base64-0.2.1.tgz",
-            "inherits": "https://registry.npmjs.org/inherits/-/inherits-2.0.1.tgz"
+            "debug": "2.6.9",
+            "resolve": "1.5.0"
           }
         },
-        "https-browserify": {
-          "version": "https://registry.npmjs.org/https-browserify/-/https-browserify-0.0.1.tgz",
-          "integrity": "sha1-P5E2XKvmC3ftDruiS0VOPgnZWoI="
+        "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==",
+          "dev": true,
+          "requires": {
+            "debug": "2.6.9",
+            "pkg-dir": "1.0.0"
+          }
         },
-        "ieee754": {
-          "version": "https://registry.npmjs.org/ieee754/-/ieee754-1.1.8.tgz",
-          "integrity": "sha1-vjPUCsEO8ZJnAfbwii2G+/0a0+Q="
+        "esutils": {
+          "version": "2.0.2",
+          "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.2.tgz",
+          "integrity": "sha1-Cr9PHKpbyx96nYrMbepPqqBLrJs=",
+          "dev": true
         },
-        "indexof": {
-          "version": "https://registry.npmjs.org/indexof/-/indexof-0.0.1.tgz",
-          "integrity": "sha1-gtwzbSMrkGIXnQWrMpOmYFn9Q10="
+        "find-up": {
+          "version": "1.1.2",
+          "resolved": "https://registry.npmjs.org/find-up/-/find-up-1.1.2.tgz",
+          "integrity": "sha1-ay6YIrGizgpgq2TWEOzK1TyyTQ8=",
+          "dev": true,
+          "requires": {
+            "path-exists": "2.1.0",
+            "pinkie-promise": "2.0.1"
+          }
         },
-        "inflight": {
-          "version": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz",
-          "integrity": "sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk=",
+        "function-bind": {
+          "version": "1.1.1",
+          "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.1.tgz",
+          "integrity": "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==",
+          "dev": true
+        },
+        "graceful-fs": {
+          "version": "4.1.11",
+          "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.1.11.tgz",
+          "integrity": "sha1-Dovf5NHduIVNZOBOp8AOKgJuVlg=",
+          "dev": true
+        },
+        "has": {
+          "version": "1.0.1",
+          "resolved": "https://registry.npmjs.org/has/-/has-1.0.1.tgz",
+          "integrity": "sha1-hGFzP1OLCDfJNh45qauelwTcLyg=",
+          "dev": true,
           "requires": {
-            "once": "https://registry.npmjs.org/once/-/once-1.4.0.tgz",
-            "wrappy": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz"
+            "function-bind": "1.1.1"
           }
         },
-        "inherits": {
-          "version": "https://registry.npmjs.org/inherits/-/inherits-2.0.1.tgz",
-          "integrity": "sha1-sX0I0ya0Qj5Wjv9xn5GwscvfafE="
+        "hosted-git-info": {
+          "version": "2.5.0",
+          "resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-2.5.0.tgz",
+          "integrity": "sha512-pNgbURSuab90KbTqvRPsseaTxOJCZBD0a7t+haSN33piP9cCM4l0CqdzAif2hUqm716UovKB2ROmiabGAKVXyg==",
+          "dev": true
+        },
+        "is-arrayish": {
+          "version": "0.2.1",
+          "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.2.1.tgz",
+          "integrity": "sha1-d8mYQFJ6qOyxqLppe4BkWnqSap0=",
+          "dev": true
+        },
+        "is-builtin-module": {
+          "version": "1.0.0",
+          "resolved": "https://registry.npmjs.org/is-builtin-module/-/is-builtin-module-1.0.0.tgz",
+          "integrity": "sha1-VAVy0096wxGfj3bDDLwbHgN6/74=",
+          "dev": true,
+          "requires": {
+            "builtin-modules": "1.1.1"
+          }
         },
         "isarray": {
-          "version": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz",
-          "integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE="
+          "version": "1.0.0",
+          "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz",
+          "integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=",
+          "dev": true
         },
-        "md5.js": {
-          "version": "https://registry.npmjs.org/md5.js/-/md5.js-1.3.4.tgz",
-          "integrity": "sha1-6b296UogpawYsENA/Fdk1bCdkB0=",
+        "load-json-file": {
+          "version": "2.0.0",
+          "resolved": "https://registry.npmjs.org/load-json-file/-/load-json-file-2.0.0.tgz",
+          "integrity": "sha1-eUfkIUmvgNaWy/eXvKq8/h/inKg=",
+          "dev": true,
+          "requires": {
+            "graceful-fs": "4.1.11",
+            "parse-json": "2.2.0",
+            "pify": "2.3.0",
+            "strip-bom": "3.0.0"
+          }
+        },
+        "locate-path": {
+          "version": "2.0.0",
+          "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-2.0.0.tgz",
+          "integrity": "sha1-K1aLJl7slExtnA3pw9u7ygNUzY4=",
+          "dev": true,
           "requires": {
-            "hash-base": "https://registry.npmjs.org/hash-base/-/hash-base-3.0.4.tgz",
-            "inherits": "https://registry.npmjs.org/inherits/-/inherits-2.0.1.tgz"
+            "p-locate": "2.0.0",
+            "path-exists": "3.0.0"
           },
           "dependencies": {
-            "hash-base": {
-              "version": "https://registry.npmjs.org/hash-base/-/hash-base-3.0.4.tgz",
-              "integrity": "sha1-X8hoaEfs1zSZQDMZprCj8/auSRg=",
-              "requires": {
-                "inherits": "https://registry.npmjs.org/inherits/-/inherits-2.0.1.tgz",
-                "safe-buffer": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.1.tgz"
-              }
+            "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
             }
           }
         },
-        "miller-rabin": {
-          "version": "https://registry.npmjs.org/miller-rabin/-/miller-rabin-4.0.1.tgz",
-          "integrity": "sha512-115fLhvZVqWwHPbClyntxEVfVDfl9DLLTuJvq3g2O/Oxi8AiNouAHvDSzHS0viUJc+V5vm3eq91Xwqn9dp4jRA==",
+        "lodash.cond": {
+          "version": "4.5.2",
+          "resolved": "https://registry.npmjs.org/lodash.cond/-/lodash.cond-4.5.2.tgz",
+          "integrity": "sha1-9HGh2khr5g9quVXRcRVSPdHSVdU=",
+          "dev": true
+        },
+        "minimatch": {
+          "version": "3.0.4",
+          "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz",
+          "integrity": "sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==",
+          "dev": true,
           "requires": {
-            "bn.js": "https://registry.npmjs.org/bn.js/-/bn.js-4.11.8.tgz",
-            "brorand": "https://registry.npmjs.org/brorand/-/brorand-1.1.0.tgz"
+            "brace-expansion": "1.1.8"
           }
         },
-        "minimalistic-assert": {
-          "version": "https://registry.npmjs.org/minimalistic-assert/-/minimalistic-assert-1.0.0.tgz",
-          "integrity": "sha1-cCvi3aazf0g2vLP121ZkG2Sh09M="
-        },
-        "minimalistic-crypto-utils": {
-          "version": "https://registry.npmjs.org/minimalistic-crypto-utils/-/minimalistic-crypto-utils-1.0.1.tgz",
-          "integrity": "sha1-9sAMHAsIIkblxNmd+4x8CDsrWCo="
+        "ms": {
+          "version": "2.0.0",
+          "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz",
+          "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=",
+          "dev": true
         },
-        "minimatch": {
-          "version": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz",
-          "integrity": "sha1-UWbihkV/AzBgZL5Ul+jbsMPTIIM=",
+        "normalize-package-data": {
+          "version": "2.4.0",
+          "resolved": "https://registry.npmjs.org/normalize-package-data/-/normalize-package-data-2.4.0.tgz",
+          "integrity": "sha512-9jjUFbTPfEy3R/ad/2oNbKtW9Hgovl5O1FvFWKkKblNXoN/Oou6+9+KKohPK13Yc3/TyunyWhJp6gvRNR/PPAw==",
+          "dev": true,
           "requires": {
-            "brace-expansion": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.8.tgz"
+            "hosted-git-info": "2.5.0",
+            "is-builtin-module": "1.0.0",
+            "semver": "5.4.1",
+            "validate-npm-package-license": "3.0.1"
           }
         },
-        "once": {
-          "version": "https://registry.npmjs.org/once/-/once-1.4.0.tgz",
-          "integrity": "sha1-WDsap3WWHUsROsF9nFC6753Xa9E=",
+        "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==",
+          "dev": true,
           "requires": {
-            "wrappy": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz"
+            "p-try": "1.0.0"
           }
         },
-        "os-browserify": {
-          "version": "https://registry.npmjs.org/os-browserify/-/os-browserify-0.2.1.tgz",
-          "integrity": "sha1-Y/xMzuXS13Y9Jrv4YBB45sLgBE8="
-        },
-        "pako": {
-          "version": "https://registry.npmjs.org/pako/-/pako-0.2.9.tgz",
-          "integrity": "sha1-8/dSL073gjSNqBYbrZ7P1Rv4OnU="
-        },
-        "parse-asn1": {
-          "version": "https://registry.npmjs.org/parse-asn1/-/parse-asn1-5.1.0.tgz",
-          "integrity": "sha1-N8T5t+06tlx0gXtfJICTf7+XxxI=",
+        "p-locate": {
+          "version": "2.0.0",
+          "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-2.0.0.tgz",
+          "integrity": "sha1-IKAQOyIqcMj9OcwuWAaA893l7EM=",
+          "dev": true,
           "requires": {
-            "asn1.js": "https://registry.npmjs.org/asn1.js/-/asn1.js-4.9.1.tgz",
-            "browserify-aes": "https://registry.npmjs.org/browserify-aes/-/browserify-aes-1.1.0.tgz",
-            "create-hash": "https://registry.npmjs.org/create-hash/-/create-hash-1.1.3.tgz",
-            "evp_bytestokey": "https://registry.npmjs.org/evp_bytestokey/-/evp_bytestokey-1.0.3.tgz",
-            "pbkdf2": "https://registry.npmjs.org/pbkdf2/-/pbkdf2-3.0.14.tgz"
+            "p-limit": "1.2.0"
           }
         },
-        "path-browserify": {
-          "version": "https://registry.npmjs.org/path-browserify/-/path-browserify-0.0.0.tgz",
-          "integrity": "sha1-oLhwcpquIUAFt9UDLsLLuw+0RRo="
-        },
-        "path-is-absolute": {
-          "version": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz",
-          "integrity": "sha1-F0uSaHNVNP+8es5r9TpanhtcX18="
+        "parse-json": {
+          "version": "2.2.0",
+          "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-2.2.0.tgz",
+          "integrity": "sha1-9ID0BDTvgHQfhGkJn43qGPVaTck=",
+          "dev": true,
+          "requires": {
+            "error-ex": "1.3.1"
+          }
         },
-        "pbkdf2": {
-          "version": "https://registry.npmjs.org/pbkdf2/-/pbkdf2-3.0.14.tgz",
-          "integrity": "sha512-gjsZW9O34fm0R7PaLHRJmLLVfSoesxztjPjE9o6R+qtVJij90ltg1joIovN9GKrRW3t1PzhDDG3UMEMFfZ+1wA==",
+        "path-exists": {
+          "version": "2.1.0",
+          "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-2.1.0.tgz",
+          "integrity": "sha1-D+tsZPD8UY2adU3V77YscCJ2H0s=",
+          "dev": true,
           "requires": {
-            "create-hash": "https://registry.npmjs.org/create-hash/-/create-hash-1.1.3.tgz",
-            "create-hmac": "https://registry.npmjs.org/create-hmac/-/create-hmac-1.1.6.tgz",
-            "ripemd160": "https://registry.npmjs.org/ripemd160/-/ripemd160-2.0.1.tgz",
-            "safe-buffer": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.1.tgz",
-            "sha.js": "https://registry.npmjs.org/sha.js/-/sha.js-2.4.9.tgz"
+            "pinkie-promise": "2.0.1"
           }
         },
-        "process": {
-          "version": "https://registry.npmjs.org/process/-/process-0.11.10.tgz",
-          "integrity": "sha1-czIwDoQBYb2j5podHZGn1LwW8YI="
+        "path-parse": {
+          "version": "1.0.5",
+          "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.5.tgz",
+          "integrity": "sha1-PBrfhx6pzWyUMbbqK9dKD/BVxME=",
+          "dev": true
         },
-        "public-encrypt": {
-          "version": "https://registry.npmjs.org/public-encrypt/-/public-encrypt-4.0.0.tgz",
-          "integrity": "sha1-OfaZ86RlYN1eusvKaTyvfGXBjMY=",
+        "path-type": {
+          "version": "2.0.0",
+          "resolved": "https://registry.npmjs.org/path-type/-/path-type-2.0.0.tgz",
+          "integrity": "sha1-8BLMuEFbcJb8LaoQVMPXI4lZTHM=",
+          "dev": true,
           "requires": {
-            "bn.js": "https://registry.npmjs.org/bn.js/-/bn.js-4.11.8.tgz",
-            "browserify-rsa": "https://registry.npmjs.org/browserify-rsa/-/browserify-rsa-4.0.1.tgz",
-            "create-hash": "https://registry.npmjs.org/create-hash/-/create-hash-1.1.3.tgz",
-            "parse-asn1": "https://registry.npmjs.org/parse-asn1/-/parse-asn1-5.1.0.tgz",
-            "randombytes": "https://registry.npmjs.org/randombytes/-/randombytes-2.0.5.tgz"
+            "pify": "2.3.0"
           }
         },
-        "punycode": {
-          "version": "https://registry.npmjs.org/punycode/-/punycode-1.4.1.tgz",
-          "integrity": "sha1-wNWmOycYgArY4esPpSachN1BhF4="
+        "pify": {
+          "version": "2.3.0",
+          "resolved": "https://registry.npmjs.org/pify/-/pify-2.3.0.tgz",
+          "integrity": "sha1-7RQaasBDqEnqWISY59yosVMw6Qw=",
+          "dev": true
         },
-        "querystring": {
-          "version": "https://registry.npmjs.org/querystring/-/querystring-0.2.0.tgz",
-          "integrity": "sha1-sgmEkgO7Jd+CDadW50cAWHhSFiA="
+        "pinkie": {
+          "version": "2.0.4",
+          "resolved": "https://registry.npmjs.org/pinkie/-/pinkie-2.0.4.tgz",
+          "integrity": "sha1-clVrgM+g1IqXToDnckjoDtT3+HA=",
+          "dev": true
         },
-        "querystring-es3": {
-          "version": "https://registry.npmjs.org/querystring-es3/-/querystring-es3-0.2.1.tgz",
-          "integrity": "sha1-nsYfeQSYdXB9aUFFlv2Qek1xHnM="
+        "pinkie-promise": {
+          "version": "2.0.1",
+          "resolved": "https://registry.npmjs.org/pinkie-promise/-/pinkie-promise-2.0.1.tgz",
+          "integrity": "sha1-ITXW36ejWMBprJsXh3YogihFD/o=",
+          "dev": true,
+          "requires": {
+            "pinkie": "2.0.4"
+          }
         },
-        "randombytes": {
-          "version": "https://registry.npmjs.org/randombytes/-/randombytes-2.0.5.tgz",
-          "integrity": "sha512-8T7Zn1AhMsQ/HI1SjcCfT/t4ii3eAqco3yOcSzS4mozsOz69lHLsoMXmF9nZgnFanYscnSlUSgs8uZyKzpE6kg==",
+        "pkg-dir": {
+          "version": "1.0.0",
+          "resolved": "https://registry.npmjs.org/pkg-dir/-/pkg-dir-1.0.0.tgz",
+          "integrity": "sha1-ektQio1bstYp1EcFb/TpyTFM89Q=",
+          "dev": true,
           "requires": {
-            "safe-buffer": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.1.tgz"
+            "find-up": "1.1.2"
           }
         },
-        "readable-stream": {
-          "version": "git+https://github.com/meteor/readable-stream.git#d64a64aa6061b9b6855feff4d09e58fb3b2e4502",
+        "read-pkg": {
+          "version": "2.0.0",
+          "resolved": "https://registry.npmjs.org/read-pkg/-/read-pkg-2.0.0.tgz",
+          "integrity": "sha1-jvHAYjxqbbDcZxPEv6xGMysjaPg=",
+          "dev": true,
           "requires": {
-            "inherits": "2.0.3",
-            "isarray": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz",
-            "process-nextick-args": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-1.0.7.tgz",
-            "safe-buffer": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.1.tgz",
-            "string_decoder": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.0.3.tgz",
-            "util-deprecate": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz"
+            "load-json-file": "2.0.0",
+            "normalize-package-data": "2.4.0",
+            "path-type": "2.0.0"
+          }
+        },
+        "read-pkg-up": {
+          "version": "2.0.0",
+          "resolved": "https://registry.npmjs.org/read-pkg-up/-/read-pkg-up-2.0.0.tgz",
+          "integrity": "sha1-a3KoBImE4MQeeVEP1en6mbO1Sb4=",
+          "dev": true,
+          "requires": {
+            "find-up": "2.1.0",
+            "read-pkg": "2.0.0"
           },
           "dependencies": {
-            "inherits": {
-              "version": "2.0.3",
-              "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz",
-              "integrity": "sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4="
+            "find-up": {
+              "version": "2.1.0",
+              "resolved": "https://registry.npmjs.org/find-up/-/find-up-2.1.0.tgz",
+              "integrity": "sha1-RdG35QbHF93UgndaK3eSCjwMV6c=",
+              "dev": true,
+              "requires": {
+                "locate-path": "2.0.0"
+              }
             }
           }
         },
-        "rimraf": {
-          "version": "https://registry.npmjs.org/rimraf/-/rimraf-2.6.2.tgz",
-          "integrity": "sha1-LtgVDSShbqhlHm1u8PR8QVjOejY=",
+        "resolve": {
+          "version": "1.5.0",
+          "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.5.0.tgz",
+          "integrity": "sha512-hgoSGrc3pjzAPHNBg+KnFcK2HwlHTs/YrAGUr6qgTVUZmXv1UEXXl0bZNBKMA9fud6lRYFdPGz0xXxycPzmmiw==",
+          "dev": true,
           "requires": {
-            "glob": "https://registry.npmjs.org/glob/-/glob-7.1.2.tgz"
+            "path-parse": "1.0.5"
           }
         },
-        "ripemd160": {
-          "version": "https://registry.npmjs.org/ripemd160/-/ripemd160-2.0.1.tgz",
-          "integrity": "sha1-D0WEKVxTo2KK9+bXmsohzlfRxuc=",
+        "semver": {
+          "version": "5.4.1",
+          "resolved": "https://registry.npmjs.org/semver/-/semver-5.4.1.tgz",
+          "integrity": "sha512-WfG/X9+oATh81XtllIo/I8gOiY9EXRdv1cQdyykeXK17YcUW3EXUAi2To4pcH6nZtJPr7ZOpM5OMyWJZm+8Rsg==",
+          "dev": true
+        },
+        "spdx-correct": {
+          "version": "1.0.2",
+          "resolved": "https://registry.npmjs.org/spdx-correct/-/spdx-correct-1.0.2.tgz",
+          "integrity": "sha1-SzBz2TP/UfORLwOsVRlJikFQ20A=",
+          "dev": true,
           "requires": {
-            "hash-base": "https://registry.npmjs.org/hash-base/-/hash-base-2.0.2.tgz",
-            "inherits": "https://registry.npmjs.org/inherits/-/inherits-2.0.1.tgz"
+            "spdx-license-ids": "1.2.2"
           }
         },
-        "safe-buffer": {
-          "version": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.1.tgz",
-          "integrity": "sha512-kKvNJn6Mm93gAczWVJg7wH+wGYWNrDHdWvpUmHyEsgCtIwwo3bqPtV4tR5tuPaUhTOo/kvhVwd8XwwOllGYkbg=="
+        "spdx-expression-parse": {
+          "version": "1.0.4",
+          "resolved": "https://registry.npmjs.org/spdx-expression-parse/-/spdx-expression-parse-1.0.4.tgz",
+          "integrity": "sha1-m98vIOH0DtRH++JzJmGR/O1RYmw=",
+          "dev": true
         },
-        "sha.js": {
-          "version": "https://registry.npmjs.org/sha.js/-/sha.js-2.4.9.tgz",
-          "integrity": "sha512-G8zektVqbiPHrylgew9Zg1VRB1L/DtXNUVAM6q4QLy8NE3qtHlFXTf8VLL4k1Yl6c7NMjtZUTdXV+X44nFaT6A==",
+        "spdx-license-ids": {
+          "version": "1.2.2",
+          "resolved": "https://registry.npmjs.org/spdx-license-ids/-/spdx-license-ids-1.2.2.tgz",
+          "integrity": "sha1-yd96NCRZSt5r0RkA1ZZpbcBrrFc=",
+          "dev": true
+        },
+        "strip-bom": {
+          "version": "3.0.0",
+          "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-3.0.0.tgz",
+          "integrity": "sha1-IzTBjpx1n3vdVv3vfprj1YjmjtM=",
+          "dev": true
+        },
+        "validate-npm-package-license": {
+          "version": "3.0.1",
+          "resolved": "https://registry.npmjs.org/validate-npm-package-license/-/validate-npm-package-license-3.0.1.tgz",
+          "integrity": "sha1-KAS6vnEq0zeUWaz74kdGqywwP7w=",
+          "dev": true,
+          "requires": {
+            "spdx-correct": "1.0.2",
+            "spdx-expression-parse": "1.0.4"
+          }
+        }
+      }
+    },
+    "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=",
+      "dev": true,
+      "requires": {
+        "aria-query": "0.7.0",
+        "array-includes": "3.0.3",
+        "ast-types-flow": "0.0.7",
+        "axobject-query": "0.1.0",
+        "damerau-levenshtein": "1.0.4",
+        "emoji-regex": "6.5.1",
+        "jsx-ast-utils": "2.0.1"
+      },
+      "dependencies": {
+        "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==",
+          "dev": true,
           "requires": {
-            "inherits": "https://registry.npmjs.org/inherits/-/inherits-2.0.1.tgz",
-            "safe-buffer": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.1.tgz"
+            "ast-types-flow": "0.0.7"
           }
         },
-        "stream-browserify": {
-          "version": "https://registry.npmjs.org/stream-browserify/-/stream-browserify-2.0.1.tgz",
-          "integrity": "sha1-ZiZu5fm9uZQKTkUUyvtDu3Hlyds=",
+        "array-includes": {
+          "version": "3.0.3",
+          "resolved": "https://registry.npmjs.org/array-includes/-/array-includes-3.0.3.tgz",
+          "integrity": "sha1-GEtI9i2S10UrsxsyMWXH+L0CJm0=",
+          "dev": true,
           "requires": {
-            "inherits": "https://registry.npmjs.org/inherits/-/inherits-2.0.1.tgz",
-            "readable-stream": "git+https://github.com/meteor/readable-stream.git#d64a64aa6061b9b6855feff4d09e58fb3b2e4502"
+            "define-properties": "1.1.2",
+            "es-abstract": "1.10.0"
           }
         },
-        "string_decoder": {
-          "version": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.0.3.tgz",
-          "integrity": "sha512-4AH6Z5fzNNBcH+6XDMfA/BTt87skxqJlO0lAh3Dker5zThcAxG6mKz+iGu308UKoPPQ8Dcqx/4JhujzltRa+hQ==",
+        "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
+        },
+        "axobject-query": {
+          "version": "0.1.0",
+          "resolved": "https://registry.npmjs.org/axobject-query/-/axobject-query-0.1.0.tgz",
+          "integrity": "sha1-YvWdvFnJ+SQnWco0mWDnov48NsA=",
+          "dev": true,
           "requires": {
-            "safe-buffer": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.1.tgz"
+            "ast-types-flow": "0.0.7"
           }
         },
-        "timers-browserify": {
-          "version": "https://registry.npmjs.org/timers-browserify/-/timers-browserify-1.4.2.tgz",
-          "integrity": "sha1-ycWLV1voQHN1y14kYtrO50NZ9B0=",
+        "damerau-levenshtein": {
+          "version": "1.0.4",
+          "resolved": "https://registry.npmjs.org/damerau-levenshtein/-/damerau-levenshtein-1.0.4.tgz",
+          "integrity": "sha1-AxkcQyy27qFou3fzpV/9zLiXhRQ=",
+          "dev": true
+        },
+        "define-properties": {
+          "version": "1.1.2",
+          "resolved": "https://registry.npmjs.org/define-properties/-/define-properties-1.1.2.tgz",
+          "integrity": "sha1-g6c/L+pWmJj7c3GTyPhzyvbUXJQ=",
+          "dev": true,
           "requires": {
-            "process": "https://registry.npmjs.org/process/-/process-0.11.10.tgz"
+            "foreach": "2.0.5",
+            "object-keys": "1.0.11"
           }
         },
-        "tty-browserify": {
-          "version": "https://registry.npmjs.org/tty-browserify/-/tty-browserify-0.0.0.tgz",
-          "integrity": "sha1-oVe6QC2iTpv5V/mqadUk7tQpAaY="
+        "emoji-regex": {
+          "version": "6.5.1",
+          "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-6.5.1.tgz",
+          "integrity": "sha512-PAHp6TxrCy7MGMFidro8uikr+zlJJKJ/Q6mm2ExZ7HwkyR9lSVFfE3kt36qcwa24BQL7y0G9axycGjK1A/0uNQ==",
+          "dev": true
         },
-        "url": {
-          "version": "https://registry.npmjs.org/url/-/url-0.11.0.tgz",
-          "integrity": "sha1-ODjpfPxgUh63PFJajlW/3Z4uKPE=",
+        "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==",
+          "dev": true,
           "requires": {
-            "punycode": "https://registry.npmjs.org/punycode/-/punycode-1.3.2.tgz",
-            "querystring": "https://registry.npmjs.org/querystring/-/querystring-0.2.0.tgz"
-          },
-          "dependencies": {
-            "punycode": {
-              "version": "https://registry.npmjs.org/punycode/-/punycode-1.3.2.tgz",
-              "integrity": "sha1-llOgNvt8HuQjQvIyXM7v6jkmxI0="
-            }
+            "es-to-primitive": "1.1.1",
+            "function-bind": "1.1.1",
+            "has": "1.0.1",
+            "is-callable": "1.1.3",
+            "is-regex": "1.0.4"
           }
         },
-        "util": {
-          "version": "https://registry.npmjs.org/util/-/util-0.10.3.tgz",
-          "integrity": "sha1-evsa/lCAUkZInj23/g7TeTNqwPk=",
+        "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=",
+          "dev": true,
           "requires": {
-            "inherits": "https://registry.npmjs.org/inherits/-/inherits-2.0.1.tgz"
+            "is-callable": "1.1.3",
+            "is-date-object": "1.0.1",
+            "is-symbol": "1.0.1"
           }
         },
-        "vm-browserify": {
-          "version": "https://registry.npmjs.org/vm-browserify/-/vm-browserify-0.0.4.tgz",
-          "integrity": "sha1-XX6kW7755Kb/ZflUOOCofDV9WnM=",
+        "foreach": {
+          "version": "2.0.5",
+          "resolved": "https://registry.npmjs.org/foreach/-/foreach-2.0.5.tgz",
+          "integrity": "sha1-C+4AUBiusmDQo6865ljdATbsG5k=",
+          "dev": true
+        },
+        "function-bind": {
+          "version": "1.1.1",
+          "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.1.tgz",
+          "integrity": "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==",
+          "dev": true
+        },
+        "has": {
+          "version": "1.0.1",
+          "resolved": "https://registry.npmjs.org/has/-/has-1.0.1.tgz",
+          "integrity": "sha1-hGFzP1OLCDfJNh45qauelwTcLyg=",
+          "dev": true,
           "requires": {
-            "indexof": "https://registry.npmjs.org/indexof/-/indexof-0.0.1.tgz"
+            "function-bind": "1.1.1"
           }
         },
-        "wrappy": {
-          "version": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz",
-          "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8="
-        }
-      }
-    },
-    "mime-db": {
-      "version": "https://registry.npmjs.org/mime-db/-/mime-db-1.30.0.tgz",
-      "integrity": "sha1-dMZD2i3Z1qRTmZY0ZbJtXKfXHwE="
-    },
-    "mime-types": {
-      "version": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.17.tgz",
-      "integrity": "sha1-Cdejk/A+mVp5+K+Fe3Cp4KsWVXo=",
-      "requires": {
-        "mime-db": "https://registry.npmjs.org/mime-db/-/mime-db-1.30.0.tgz"
-      }
-    },
-    "mimic-fn": {
-      "version": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-1.1.0.tgz",
-      "integrity": "sha1-5md4PZLonb00KBi1IwudYqZyrRg=",
-      "dev": true
-    },
-    "mimic-response": {
-      "version": "https://registry.npmjs.org/mimic-response/-/mimic-response-1.0.0.tgz",
-      "integrity": "sha1-3z02Uqc/3ta5sLJBRub9BSNTRY4="
-    },
-    "minimatch": {
-      "version": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz",
-      "integrity": "sha1-UWbihkV/AzBgZL5Ul+jbsMPTIIM=",
-      "requires": {
-        "brace-expansion": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.8.tgz"
-      }
-    },
-    "minimist": {
-      "version": "https://registry.npmjs.org/minimist/-/minimist-1.2.0.tgz",
-      "integrity": "sha1-o1AIsg9BOD7sH7kU9M1d95omQoQ="
-    },
-    "mkdirp": {
-      "version": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.1.tgz",
-      "integrity": "sha1-MAV0OOrGz3+MR2fzhkjWaX11yQM=",
-      "requires": {
-        "minimist": "https://registry.npmjs.org/minimist/-/minimist-0.0.8.tgz"
-      },
-      "dependencies": {
-        "minimist": {
-          "version": "https://registry.npmjs.org/minimist/-/minimist-0.0.8.tgz",
-          "integrity": "sha1-hX/Kv8M5fSYluCKCYuhqp6ARsF0="
+        "is-callable": {
+          "version": "1.1.3",
+          "resolved": "https://registry.npmjs.org/is-callable/-/is-callable-1.1.3.tgz",
+          "integrity": "sha1-hut1OSgF3cM69xySoO7fdO52BLI=",
+          "dev": true
+        },
+        "is-date-object": {
+          "version": "1.0.1",
+          "resolved": "https://registry.npmjs.org/is-date-object/-/is-date-object-1.0.1.tgz",
+          "integrity": "sha1-mqIOtq7rv/d/vTPnTKAbM1gdOhY=",
+          "dev": true
+        },
+        "is-regex": {
+          "version": "1.0.4",
+          "resolved": "https://registry.npmjs.org/is-regex/-/is-regex-1.0.4.tgz",
+          "integrity": "sha1-VRdIm1RwkbCTDglWVM7SXul+lJE=",
+          "dev": true,
+          "requires": {
+            "has": "1.0.1"
+          }
+        },
+        "is-symbol": {
+          "version": "1.0.1",
+          "resolved": "https://registry.npmjs.org/is-symbol/-/is-symbol-1.0.1.tgz",
+          "integrity": "sha1-PMWfAAJRlLarLjjbrmaJJWtmBXI=",
+          "dev": true
+        },
+        "jsx-ast-utils": {
+          "version": "2.0.1",
+          "resolved": "https://registry.npmjs.org/jsx-ast-utils/-/jsx-ast-utils-2.0.1.tgz",
+          "integrity": "sha1-6AGxs5mF4g//yHtA43SAgOLcrH8=",
+          "dev": true,
+          "requires": {
+            "array-includes": "3.0.3"
+          }
+        },
+        "object-keys": {
+          "version": "1.0.11",
+          "resolved": "https://registry.npmjs.org/object-keys/-/object-keys-1.0.11.tgz",
+          "integrity": "sha1-xUYBd4rVYPEULODgG8yotW0TQm0=",
+          "dev": true
         }
       }
     },
-    "ms": {
-      "version": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz",
-      "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g="
-    },
-    "mute-stream": {
-      "version": "https://registry.npmjs.org/mute-stream/-/mute-stream-0.0.7.tgz",
-      "integrity": "sha1-MHXOk7whuPq0PhvE2n6BFe0ee6s=",
-      "dev": true
-    },
-    "nan": {
-      "version": "https://registry.npmjs.org/nan/-/nan-2.7.0.tgz",
-      "integrity": "sha1-2Vv3IeyHfgjbJ27T/G63j5CDrUY="
-    },
-    "natural-compare": {
-      "version": "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz",
-      "integrity": "sha1-Sr6/7tdUHywnrPspvbvRXI1bpPc=",
-      "dev": true
-    },
-    "next-tick": {
-      "version": "https://registry.npmjs.org/next-tick/-/next-tick-1.0.0.tgz",
-      "integrity": "sha1-yobR/ogoFpsBICCOPchCS524NCw="
-    },
-    "node-fetch": {
-      "version": "https://registry.npmjs.org/node-fetch/-/node-fetch-1.7.3.tgz",
-      "integrity": "sha1-mA9vcthSEaU0fGsrwYxbhMPrR+8=",
-      "requires": {
-        "encoding": "https://registry.npmjs.org/encoding/-/encoding-0.1.12.tgz",
-        "is-stream": "https://registry.npmjs.org/is-stream/-/is-stream-1.1.0.tgz"
-      }
-    },
-    "node-gyp": {
-      "version": "https://registry.npmjs.org/node-gyp/-/node-gyp-3.6.2.tgz",
-      "integrity": "sha1-m/vlRWIoYoSDjnUOrAUpWFP6HGA=",
+    "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==",
+      "dev": true,
       "requires": {
-        "fstream": "https://registry.npmjs.org/fstream/-/fstream-1.0.11.tgz",
-        "glob": "https://registry.npmjs.org/glob/-/glob-7.1.2.tgz",
-        "graceful-fs": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.1.11.tgz",
-        "minimatch": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz",
-        "mkdirp": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.1.tgz",
-        "nopt": "https://registry.npmjs.org/nopt/-/nopt-3.0.6.tgz",
-        "npmlog": "https://registry.npmjs.org/npmlog/-/npmlog-4.1.2.tgz",
-        "osenv": "https://registry.npmjs.org/osenv/-/osenv-0.1.4.tgz",
-        "request": "https://registry.npmjs.org/request/-/request-2.83.0.tgz",
-        "rimraf": "https://registry.npmjs.org/rimraf/-/rimraf-2.6.2.tgz",
-        "semver": "https://registry.npmjs.org/semver/-/semver-5.3.0.tgz",
-        "tar": "https://registry.npmjs.org/tar/-/tar-2.2.1.tgz",
-        "which": "https://registry.npmjs.org/which/-/which-1.3.0.tgz"
+        "doctrine": "2.1.0",
+        "has": "1.0.1",
+        "jsx-ast-utils": "2.0.1",
+        "prop-types": "15.6.0"
       },
       "dependencies": {
-        "semver": {
-          "version": "https://registry.npmjs.org/semver/-/semver-5.3.0.tgz",
-          "integrity": "sha1-myzl094C0XxgEq0yaqa00M9U+U8="
-        }
-      }
-    },
-    "node-sass": {
-      "version": "https://registry.npmjs.org/node-sass/-/node-sass-4.5.3.tgz",
-      "integrity": "sha1-0JydEXlkEjnRuX/8YjH9zsU+FWg=",
-      "requires": {
-        "async-foreach": "https://registry.npmjs.org/async-foreach/-/async-foreach-0.1.3.tgz",
-        "chalk": "https://registry.npmjs.org/chalk/-/chalk-1.1.3.tgz",
-        "cross-spawn": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-3.0.1.tgz",
-        "gaze": "https://registry.npmjs.org/gaze/-/gaze-1.1.2.tgz",
-        "get-stdin": "https://registry.npmjs.org/get-stdin/-/get-stdin-4.0.1.tgz",
-        "glob": "https://registry.npmjs.org/glob/-/glob-7.1.2.tgz",
-        "in-publish": "https://registry.npmjs.org/in-publish/-/in-publish-2.0.0.tgz",
-        "lodash.assign": "https://registry.npmjs.org/lodash.assign/-/lodash.assign-4.2.0.tgz",
-        "lodash.clonedeep": "https://registry.npmjs.org/lodash.clonedeep/-/lodash.clonedeep-4.5.0.tgz",
-        "lodash.mergewith": "https://registry.npmjs.org/lodash.mergewith/-/lodash.mergewith-4.6.0.tgz",
-        "meow": "https://registry.npmjs.org/meow/-/meow-3.7.0.tgz",
-        "mkdirp": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.1.tgz",
-        "nan": "https://registry.npmjs.org/nan/-/nan-2.7.0.tgz",
-        "node-gyp": "https://registry.npmjs.org/node-gyp/-/node-gyp-3.6.2.tgz",
-        "npmlog": "https://registry.npmjs.org/npmlog/-/npmlog-4.1.2.tgz",
-        "request": "https://registry.npmjs.org/request/-/request-2.83.0.tgz",
-        "sass-graph": "https://registry.npmjs.org/sass-graph/-/sass-graph-2.2.4.tgz",
-        "stdout-stream": "https://registry.npmjs.org/stdout-stream/-/stdout-stream-1.4.0.tgz"
-      }
-    },
-    "nopt": {
-      "version": "https://registry.npmjs.org/nopt/-/nopt-3.0.6.tgz",
-      "integrity": "sha1-xkZdvwirzU2zWTF/eaxopkayj/k=",
-      "requires": {
-        "abbrev": "https://registry.npmjs.org/abbrev/-/abbrev-1.1.1.tgz"
-      }
-    },
-    "normalize-package-data": {
-      "version": "https://registry.npmjs.org/normalize-package-data/-/normalize-package-data-2.4.0.tgz",
-      "integrity": "sha1-EvlaMH1YNSB1oEkHuErIvpisAS8=",
-      "requires": {
-        "hosted-git-info": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-2.5.0.tgz",
-        "is-builtin-module": "https://registry.npmjs.org/is-builtin-module/-/is-builtin-module-1.0.0.tgz",
-        "semver": "https://registry.npmjs.org/semver/-/semver-5.4.1.tgz",
-        "validate-npm-package-license": "https://registry.npmjs.org/validate-npm-package-license/-/validate-npm-package-license-3.0.1.tgz"
-      }
-    },
-    "normalize-path": {
-      "version": "https://registry.npmjs.org/normalize-path/-/normalize-path-1.0.0.tgz",
-      "integrity": "sha1-MtDkcvkf80VwHBWoMRAY07CpA3k=",
-      "dev": true
-    },
-    "normalize-range": {
-      "version": "https://registry.npmjs.org/normalize-range/-/normalize-range-0.1.2.tgz",
-      "integrity": "sha1-LRDAa9/TEuqXd2laTShDlFa3WUI=",
-      "dev": true
-    },
-    "npm-install-package": {
-      "version": "https://registry.npmjs.org/npm-install-package/-/npm-install-package-2.1.0.tgz",
-      "integrity": "sha1-1+/jz816sAYUuJbqUxGdyaslkSU=",
-      "dev": true
-    },
-    "npm-path": {
-      "version": "https://registry.npmjs.org/npm-path/-/npm-path-2.0.3.tgz",
-      "integrity": "sha1-Fc/04ciaONp39W9gVbJPl137K74=",
-      "dev": true,
-      "requires": {
-        "which": "https://registry.npmjs.org/which/-/which-1.3.0.tgz"
-      }
-    },
-    "npm-run-path": {
-      "version": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-2.0.2.tgz",
-      "integrity": "sha1-NakjLfo11wZ7TLLd8jV7GHFTbF8=",
-      "dev": true,
-      "requires": {
-        "path-key": "https://registry.npmjs.org/path-key/-/path-key-2.0.1.tgz"
-      }
-    },
-    "npm-which": {
-      "version": "https://registry.npmjs.org/npm-which/-/npm-which-3.0.1.tgz",
-      "integrity": "sha1-kiXybsOihcIJyuZ8OxGmtKtxQKo=",
-      "dev": true,
-      "requires": {
-        "commander": "https://registry.npmjs.org/commander/-/commander-2.11.0.tgz",
-        "npm-path": "https://registry.npmjs.org/npm-path/-/npm-path-2.0.3.tgz",
-        "which": "https://registry.npmjs.org/which/-/which-1.3.0.tgz"
-      }
-    },
-    "npmlog": {
-      "version": "https://registry.npmjs.org/npmlog/-/npmlog-4.1.2.tgz",
-      "integrity": "sha1-CKfyqL9zRgR3mp76StXMcXq7lUs=",
-      "requires": {
-        "are-we-there-yet": "https://registry.npmjs.org/are-we-there-yet/-/are-we-there-yet-1.1.4.tgz",
-        "console-control-strings": "https://registry.npmjs.org/console-control-strings/-/console-control-strings-1.1.0.tgz",
-        "gauge": "https://registry.npmjs.org/gauge/-/gauge-2.7.4.tgz",
-        "set-blocking": "https://registry.npmjs.org/set-blocking/-/set-blocking-2.0.0.tgz"
-      }
-    },
-    "num2fraction": {
-      "version": "https://registry.npmjs.org/num2fraction/-/num2fraction-1.2.2.tgz",
-      "integrity": "sha1-b2gragJ6Tp3fpFZM0lidHU5mnt4=",
-      "dev": true
-    },
-    "number-is-nan": {
-      "version": "https://registry.npmjs.org/number-is-nan/-/number-is-nan-1.0.1.tgz",
-      "integrity": "sha1-CXtgK1NCKlIsGvuHkDGDNpQaAR0="
-    },
-    "oauth-sign": {
-      "version": "https://registry.npmjs.org/oauth-sign/-/oauth-sign-0.8.2.tgz",
-      "integrity": "sha1-Rqarfwrq2N6unsBWV4C31O/rnUM="
-    },
-    "object-assign": {
-      "version": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz",
-      "integrity": "sha1-IQmtx5ZYh8/AXLvUQsrIv7s2CGM="
-    },
-    "object-keys": {
-      "version": "https://registry.npmjs.org/object-keys/-/object-keys-1.0.11.tgz",
-      "integrity": "sha1-xUYBd4rVYPEULODgG8yotW0TQm0=",
-      "dev": true
-    },
-    "object.assign": {
-      "version": "https://registry.npmjs.org/object.assign/-/object.assign-4.0.4.tgz",
-      "integrity": "sha1-scnMBE7xuf5jYG/BQau7MuFHMMw=",
-      "dev": true,
-      "requires": {
-        "define-properties": "https://registry.npmjs.org/define-properties/-/define-properties-1.1.2.tgz",
-        "function-bind": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.1.tgz",
-        "object-keys": "https://registry.npmjs.org/object-keys/-/object-keys-1.0.11.tgz"
-      }
-    },
-    "once": {
-      "version": "https://registry.npmjs.org/once/-/once-1.4.0.tgz",
-      "integrity": "sha1-WDsap3WWHUsROsF9nFC6753Xa9E=",
-      "requires": {
-        "wrappy": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz"
-      }
-    },
-    "onetime": {
-      "version": "https://registry.npmjs.org/onetime/-/onetime-2.0.1.tgz",
-      "integrity": "sha1-BnQoIw/WdEOyeUsiu6UotoZ5YtQ=",
-      "dev": true,
-      "requires": {
-        "mimic-fn": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-1.1.0.tgz"
-      }
-    },
-    "optimist": {
-      "version": "https://registry.npmjs.org/optimist/-/optimist-0.6.1.tgz",
-      "integrity": "sha1-2j6nRob6IaGaERwybpDrFaAZZoY=",
-      "dev": true,
-      "requires": {
-        "minimist": "https://registry.npmjs.org/minimist/-/minimist-0.0.10.tgz",
-        "wordwrap": "https://registry.npmjs.org/wordwrap/-/wordwrap-0.0.3.tgz"
-      },
-      "dependencies": {
-        "minimist": {
-          "version": "https://registry.npmjs.org/minimist/-/minimist-0.0.10.tgz",
-          "integrity": "sha1-3j+YVD2/lggr5IrRoMfNqDYwHc8=",
+        "array-includes": {
+          "version": "3.0.3",
+          "resolved": "https://registry.npmjs.org/array-includes/-/array-includes-3.0.3.tgz",
+          "integrity": "sha1-GEtI9i2S10UrsxsyMWXH+L0CJm0=",
+          "dev": true,
+          "requires": {
+            "define-properties": "1.1.2",
+            "es-abstract": "1.10.0"
+          }
+        },
+        "define-properties": {
+          "version": "1.1.2",
+          "resolved": "https://registry.npmjs.org/define-properties/-/define-properties-1.1.2.tgz",
+          "integrity": "sha1-g6c/L+pWmJj7c3GTyPhzyvbUXJQ=",
+          "dev": true,
+          "requires": {
+            "foreach": "2.0.5",
+            "object-keys": "1.0.11"
+          }
+        },
+        "doctrine": {
+          "version": "2.1.0",
+          "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-2.1.0.tgz",
+          "integrity": "sha512-35mSku4ZXK0vfCuHEDAwt55dg2jNajHZ1odvF+8SSr82EsZY4QmXfuWso8oEd8zRhVObSN18aM0CjSdoBX7zIw==",
+          "dev": true,
+          "requires": {
+            "esutils": "2.0.2"
+          }
+        },
+        "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==",
+          "dev": true,
+          "requires": {
+            "es-to-primitive": "1.1.1",
+            "function-bind": "1.1.1",
+            "has": "1.0.1",
+            "is-callable": "1.1.3",
+            "is-regex": "1.0.4"
+          }
+        },
+        "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=",
+          "dev": true,
+          "requires": {
+            "is-callable": "1.1.3",
+            "is-date-object": "1.0.1",
+            "is-symbol": "1.0.1"
+          }
+        },
+        "esutils": {
+          "version": "2.0.2",
+          "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.2.tgz",
+          "integrity": "sha1-Cr9PHKpbyx96nYrMbepPqqBLrJs=",
           "dev": true
         },
-        "wordwrap": {
-          "version": "https://registry.npmjs.org/wordwrap/-/wordwrap-0.0.3.tgz",
-          "integrity": "sha1-o9XabNXAvAAI03I0u68b7WMFkQc=",
+        "foreach": {
+          "version": "2.0.5",
+          "resolved": "https://registry.npmjs.org/foreach/-/foreach-2.0.5.tgz",
+          "integrity": "sha1-C+4AUBiusmDQo6865ljdATbsG5k=",
           "dev": true
-        }
-      }
-    },
-    "optionator": {
-      "version": "https://registry.npmjs.org/optionator/-/optionator-0.8.2.tgz",
-      "integrity": "sha1-NkxeQJ0/TWMB1sC0wFu6UBgK62Q=",
-      "dev": true,
-      "requires": {
-        "deep-is": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.3.tgz",
-        "fast-levenshtein": "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz",
-        "levn": "https://registry.npmjs.org/levn/-/levn-0.3.0.tgz",
-        "prelude-ls": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.1.2.tgz",
-        "type-check": "https://registry.npmjs.org/type-check/-/type-check-0.3.2.tgz",
-        "wordwrap": "https://registry.npmjs.org/wordwrap/-/wordwrap-1.0.0.tgz"
-      }
-    },
-    "ora": {
-      "version": "https://registry.npmjs.org/ora/-/ora-0.2.3.tgz",
-      "integrity": "sha1-N1J9Igrc1Tw5tzVx11QVbV22V6Q=",
-      "dev": true,
-      "requires": {
-        "chalk": "https://registry.npmjs.org/chalk/-/chalk-1.1.3.tgz",
-        "cli-cursor": "https://registry.npmjs.org/cli-cursor/-/cli-cursor-1.0.2.tgz",
-        "cli-spinners": "https://registry.npmjs.org/cli-spinners/-/cli-spinners-0.1.2.tgz",
-        "object-assign": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz"
-      },
-      "dependencies": {
-        "cli-cursor": {
-          "version": "https://registry.npmjs.org/cli-cursor/-/cli-cursor-1.0.2.tgz",
-          "integrity": "sha1-ZNo/fValRBLll5S9Ytw1KV6PKYc=",
+        },
+        "function-bind": {
+          "version": "1.1.1",
+          "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.1.tgz",
+          "integrity": "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==",
+          "dev": true
+        },
+        "has": {
+          "version": "1.0.1",
+          "resolved": "https://registry.npmjs.org/has/-/has-1.0.1.tgz",
+          "integrity": "sha1-hGFzP1OLCDfJNh45qauelwTcLyg=",
           "dev": true,
           "requires": {
-            "restore-cursor": "https://registry.npmjs.org/restore-cursor/-/restore-cursor-1.0.1.tgz"
+            "function-bind": "1.1.1"
           }
         },
-        "onetime": {
-          "version": "https://registry.npmjs.org/onetime/-/onetime-1.1.0.tgz",
-          "integrity": "sha1-ofeDj4MUxRbwXs78vEzP4EtO14k=",
+        "is-callable": {
+          "version": "1.1.3",
+          "resolved": "https://registry.npmjs.org/is-callable/-/is-callable-1.1.3.tgz",
+          "integrity": "sha1-hut1OSgF3cM69xySoO7fdO52BLI=",
           "dev": true
         },
-        "restore-cursor": {
-          "version": "https://registry.npmjs.org/restore-cursor/-/restore-cursor-1.0.1.tgz",
-          "integrity": "sha1-NGYfRohjJ/7SmRR5FSJS35LapUE=",
+        "is-date-object": {
+          "version": "1.0.1",
+          "resolved": "https://registry.npmjs.org/is-date-object/-/is-date-object-1.0.1.tgz",
+          "integrity": "sha1-mqIOtq7rv/d/vTPnTKAbM1gdOhY=",
+          "dev": true
+        },
+        "is-regex": {
+          "version": "1.0.4",
+          "resolved": "https://registry.npmjs.org/is-regex/-/is-regex-1.0.4.tgz",
+          "integrity": "sha1-VRdIm1RwkbCTDglWVM7SXul+lJE=",
+          "dev": true,
+          "requires": {
+            "has": "1.0.1"
+          }
+        },
+        "is-symbol": {
+          "version": "1.0.1",
+          "resolved": "https://registry.npmjs.org/is-symbol/-/is-symbol-1.0.1.tgz",
+          "integrity": "sha1-PMWfAAJRlLarLjjbrmaJJWtmBXI=",
+          "dev": true
+        },
+        "jsx-ast-utils": {
+          "version": "2.0.1",
+          "resolved": "https://registry.npmjs.org/jsx-ast-utils/-/jsx-ast-utils-2.0.1.tgz",
+          "integrity": "sha1-6AGxs5mF4g//yHtA43SAgOLcrH8=",
           "dev": true,
           "requires": {
-            "exit-hook": "https://registry.npmjs.org/exit-hook/-/exit-hook-1.1.1.tgz",
-            "onetime": "https://registry.npmjs.org/onetime/-/onetime-1.1.0.tgz"
+            "array-includes": "3.0.3"
           }
+        },
+        "object-keys": {
+          "version": "1.0.11",
+          "resolved": "https://registry.npmjs.org/object-keys/-/object-keys-1.0.11.tgz",
+          "integrity": "sha1-xUYBd4rVYPEULODgG8yotW0TQm0=",
+          "dev": true
         }
       }
     },
-    "os-homedir": {
-      "version": "https://registry.npmjs.org/os-homedir/-/os-homedir-1.0.2.tgz",
-      "integrity": "sha1-/7xJiDNuDoM94MFox+8VISGqf7M="
-    },
-    "os-locale": {
-      "version": "https://registry.npmjs.org/os-locale/-/os-locale-1.4.0.tgz",
-      "integrity": "sha1-IPnxeuKe00XoveWDsT0gCYA8FNk=",
-      "requires": {
-        "lcid": "https://registry.npmjs.org/lcid/-/lcid-1.0.0.tgz"
-      }
-    },
-    "os-tmpdir": {
-      "version": "https://registry.npmjs.org/os-tmpdir/-/os-tmpdir-1.0.2.tgz",
-      "integrity": "sha1-u+Z0BseaqFxc/sdm/lc0VV36EnQ="
-    },
-    "osenv": {
-      "version": "https://registry.npmjs.org/osenv/-/osenv-0.1.4.tgz",
-      "integrity": "sha1-Qv5tWVPfBsgGS+bxdsPQWqqjRkQ=",
-      "requires": {
-        "os-homedir": "https://registry.npmjs.org/os-homedir/-/os-homedir-1.0.2.tgz",
-        "os-tmpdir": "https://registry.npmjs.org/os-tmpdir/-/os-tmpdir-1.0.2.tgz"
-      }
-    },
-    "p-cancelable": {
-      "version": "https://registry.npmjs.org/p-cancelable/-/p-cancelable-0.3.0.tgz",
-      "integrity": "sha1-ueEjgAvOu3rBOkeb4ZW1B7mNMPo="
-    },
-    "p-finally": {
-      "version": "https://registry.npmjs.org/p-finally/-/p-finally-1.0.0.tgz",
-      "integrity": "sha1-P7z7FbiZpEEjs0ttzBi3JDNqLK4="
-    },
-    "p-limit": {
-      "version": "https://registry.npmjs.org/p-limit/-/p-limit-1.1.0.tgz",
-      "integrity": "sha1-sH/y2aXYi+yAYDWJWiurZqJ5iLw=",
-      "dev": true
-    },
-    "p-locate": {
-      "version": "https://registry.npmjs.org/p-locate/-/p-locate-2.0.0.tgz",
-      "integrity": "sha1-IKAQOyIqcMj9OcwuWAaA893l7EM=",
-      "dev": true,
-      "requires": {
-        "p-limit": "https://registry.npmjs.org/p-limit/-/p-limit-1.1.0.tgz"
-      }
-    },
-    "p-map": {
-      "version": "https://registry.npmjs.org/p-map/-/p-map-1.2.0.tgz",
-      "integrity": "sha1-5OlPMR6rvIYzoeeZCBZfyiYkG2s=",
-      "dev": true
-    },
-    "p-timeout": {
-      "version": "https://registry.npmjs.org/p-timeout/-/p-timeout-1.2.0.tgz",
-      "integrity": "sha1-mCD5lDTFgXhotPNICe5SkWYNW2w=",
-      "requires": {
-        "p-finally": "https://registry.npmjs.org/p-finally/-/p-finally-1.0.0.tgz"
-      }
-    },
-    "parse-json": {
-      "version": "https://registry.npmjs.org/parse-json/-/parse-json-2.2.0.tgz",
-      "integrity": "sha1-9ID0BDTvgHQfhGkJn43qGPVaTck=",
-      "requires": {
-        "error-ex": "https://registry.npmjs.org/error-ex/-/error-ex-1.3.1.tgz"
-      }
-    },
-    "path-exists": {
-      "version": "https://registry.npmjs.org/path-exists/-/path-exists-2.1.0.tgz",
-      "integrity": "sha1-D+tsZPD8UY2adU3V77YscCJ2H0s=",
-      "requires": {
-        "pinkie-promise": "https://registry.npmjs.org/pinkie-promise/-/pinkie-promise-2.0.1.tgz"
-      }
-    },
-    "path-is-absolute": {
-      "version": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz",
-      "integrity": "sha1-F0uSaHNVNP+8es5r9TpanhtcX18="
-    },
-    "path-is-inside": {
-      "version": "https://registry.npmjs.org/path-is-inside/-/path-is-inside-1.0.2.tgz",
-      "integrity": "sha1-NlQX3t5EQw0cEa9hAn+s8HS9/FM=",
-      "dev": true
-    },
-    "path-key": {
-      "version": "https://registry.npmjs.org/path-key/-/path-key-2.0.1.tgz",
-      "integrity": "sha1-QRyttXTFoUDTpLGRDUDYDMn0C0A=",
-      "dev": true
-    },
-    "path-parse": {
-      "version": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.5.tgz",
-      "integrity": "sha1-PBrfhx6pzWyUMbbqK9dKD/BVxME=",
-      "dev": true
+    "eventemitter2": {
+      "version": "4.1.2",
+      "resolved": "https://registry.npmjs.org/eventemitter2/-/eventemitter2-4.1.2.tgz",
+      "integrity": "sha1-DhqEd6+CGm7zmVsxG/dMI6UkfxU="
     },
-    "path-type": {
-      "version": "https://registry.npmjs.org/path-type/-/path-type-1.1.0.tgz",
-      "integrity": "sha1-WcRPfuSR2nBNpBXaWkBwuk+P5EE=",
+    "flat": {
+      "version": "4.0.0",
+      "resolved": "https://registry.npmjs.org/flat/-/flat-4.0.0.tgz",
+      "integrity": "sha512-ji/WMv2jdsE+LaznpkIF9Haax0sdpTBozrz/Dtg4qSRMfbs8oVg4ypJunIRYPiMLvH/ed6OflXbnbTIKJhtgeg==",
       "requires": {
-        "graceful-fs": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.1.11.tgz",
-        "pify": "https://registry.npmjs.org/pify/-/pify-2.3.0.tgz",
-        "pinkie-promise": "https://registry.npmjs.org/pinkie-promise/-/pinkie-promise-2.0.1.tgz"
+        "is-buffer": "1.1.6"
+      },
+      "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=="
+        }
       }
     },
-    "pathval": {
-      "version": "https://registry.npmjs.org/pathval/-/pathval-1.1.0.tgz",
-      "integrity": "sha1-uULm1L3mUwBe9rcTYd74cn0GReA=",
+    "has-symbols": {
+      "version": "1.0.0",
+      "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.0.tgz",
+      "integrity": "sha1-uhqPGvKg/DllD1yFA2dwQSIGO0Q=",
       "dev": true
     },
-    "performance-now": {
-      "version": "https://registry.npmjs.org/performance-now/-/performance-now-2.1.0.tgz",
-      "integrity": "sha1-Ywn04OX6kT7BxpMHrjZLSzd8nns="
-    },
-    "pify": {
-      "version": "https://registry.npmjs.org/pify/-/pify-2.3.0.tgz",
-      "integrity": "sha1-7RQaasBDqEnqWISY59yosVMw6Qw="
-    },
-    "pinkie": {
-      "version": "https://registry.npmjs.org/pinkie/-/pinkie-2.0.4.tgz",
-      "integrity": "sha1-clVrgM+g1IqXToDnckjoDtT3+HA="
-    },
-    "pinkie-promise": {
-      "version": "https://registry.npmjs.org/pinkie-promise/-/pinkie-promise-2.0.1.tgz",
-      "integrity": "sha1-ITXW36ejWMBprJsXh3YogihFD/o=",
-      "requires": {
-        "pinkie": "https://registry.npmjs.org/pinkie/-/pinkie-2.0.4.tgz"
-      }
-    },
-    "pkg-dir": {
-      "version": "https://registry.npmjs.org/pkg-dir/-/pkg-dir-1.0.0.tgz",
-      "integrity": "sha1-ektQio1bstYp1EcFb/TpyTFM89Q=",
-      "dev": true,
+    "hiredis": {
+      "version": "0.5.0",
+      "resolved": "https://registry.npmjs.org/hiredis/-/hiredis-0.5.0.tgz",
+      "integrity": "sha1-2wOpi+zXAD0TwmAEOs7s+s31m4c=",
       "requires": {
-        "find-up": "https://registry.npmjs.org/find-up/-/find-up-1.1.2.tgz"
+        "bindings": "1.3.0",
+        "nan": "2.8.0"
+      },
+      "dependencies": {
+        "bindings": {
+          "version": "1.3.0",
+          "resolved": "https://registry.npmjs.org/bindings/-/bindings-1.3.0.tgz",
+          "integrity": "sha512-DpLh5EzMR2kzvX1KIlVC0VkC3iZtHKTgdtZ0a3pglBZdaQFjt5S9g9xd1lE+YvXyfd6mtCeRnrUfOLYiTMlNSw=="
+        },
+        "nan": {
+          "version": "2.8.0",
+          "resolved": "https://registry.npmjs.org/nan/-/nan-2.8.0.tgz",
+          "integrity": "sha1-7XFfP+neArV6XmJS2QqWZ14fCFo="
+        }
       }
     },
-    "pluralize": {
-      "version": "https://registry.npmjs.org/pluralize/-/pluralize-7.0.0.tgz",
-      "integrity": "sha1-KYuJ34uTsCIdv0Ia0rGx6iP8Z3c=",
-      "dev": true
-    },
-    "postcss": {
-      "version": "https://registry.npmjs.org/postcss/-/postcss-6.0.13.tgz",
-      "integrity": "sha1-ueyrTuAMids+yTEUW9lZC78/El8=",
-      "dev": true,
+    "history": {
+      "version": "3.3.0",
+      "resolved": "https://registry.npmjs.org/history/-/history-3.3.0.tgz",
+      "integrity": "sha1-/O3M6PEpdTcVRdc1RhAzV5ptrpw=",
       "requires": {
-        "chalk": "https://registry.npmjs.org/chalk/-/chalk-2.3.0.tgz",
-        "source-map": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz",
-        "supports-color": "https://registry.npmjs.org/supports-color/-/supports-color-4.5.0.tgz"
+        "invariant": "2.2.2",
+        "loose-envify": "1.3.1",
+        "query-string": "4.3.4",
+        "warning": "3.0.0"
       },
       "dependencies": {
-        "ansi-styles": {
-          "version": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.0.tgz",
-          "integrity": "sha1-wVm41b4PnlpvNG2rlPFs4CIWG4g=",
-          "dev": true,
+        "invariant": {
+          "version": "2.2.2",
+          "resolved": "https://registry.npmjs.org/invariant/-/invariant-2.2.2.tgz",
+          "integrity": "sha1-nh9WrArNtr8wMwbzOL47IErmA2A=",
           "requires": {
-            "color-convert": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.0.tgz"
+            "loose-envify": "1.3.1"
           }
         },
-        "chalk": {
-          "version": "https://registry.npmjs.org/chalk/-/chalk-2.3.0.tgz",
-          "integrity": "sha1-tepI78nBeT3MybR2fJORTT8tUro=",
-          "dev": true,
+        "js-tokens": {
+          "version": "3.0.2",
+          "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-3.0.2.tgz",
+          "integrity": "sha1-mGbfOVECEw449/mWvOtlRDIJwls="
+        },
+        "loose-envify": {
+          "version": "1.3.1",
+          "resolved": "https://registry.npmjs.org/loose-envify/-/loose-envify-1.3.1.tgz",
+          "integrity": "sha1-0aitM/qc4OcT1l/dCsi3SNR4yEg=",
           "requires": {
-            "ansi-styles": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.0.tgz",
-            "escape-string-regexp": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz",
-            "supports-color": "https://registry.npmjs.org/supports-color/-/supports-color-4.5.0.tgz"
+            "js-tokens": "3.0.2"
           }
         },
-        "source-map": {
-          "version": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz",
-          "integrity": "sha1-dHIq8y6WFOnCh6jQu95IteLxomM=",
-          "dev": true
+        "object-assign": {
+          "version": "4.1.1",
+          "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz",
+          "integrity": "sha1-IQmtx5ZYh8/AXLvUQsrIv7s2CGM="
         },
-        "supports-color": {
-          "version": "https://registry.npmjs.org/supports-color/-/supports-color-4.5.0.tgz",
-          "integrity": "sha1-vnoN5ITexcXN34s9WRJQRJEvY1s=",
-          "dev": true,
+        "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.1",
+            "strict-uri-encode": "1.1.0"
+          }
+        },
+        "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="
+        },
+        "warning": {
+          "version": "3.0.0",
+          "resolved": "https://registry.npmjs.org/warning/-/warning-3.0.0.tgz",
+          "integrity": "sha1-MuU3fLVy3kqwR1O9+IIcAe1gW3w=",
           "requires": {
-            "has-flag": "https://registry.npmjs.org/has-flag/-/has-flag-2.0.0.tgz"
+            "loose-envify": "1.3.1"
           }
         }
       }
     },
-    "postcss-modules-extract-imports": {
-      "version": "https://registry.npmjs.org/postcss-modules-extract-imports/-/postcss-modules-extract-imports-1.1.0.tgz",
-      "integrity": "sha1-thTJcgvmgW6u41+zpfqh26agXds=",
-      "dev": true,
-      "requires": {
-        "postcss": "https://registry.npmjs.org/postcss/-/postcss-6.0.13.tgz"
-      }
-    },
-    "postcss-modules-local-by-default": {
-      "version": "https://registry.npmjs.org/postcss-modules-local-by-default/-/postcss-modules-local-by-default-1.2.0.tgz",
-      "integrity": "sha1-99gMOYxaOT+nlkRmvRlQCn1hwGk=",
-      "dev": true,
-      "requires": {
-        "css-selector-tokenizer": "https://registry.npmjs.org/css-selector-tokenizer/-/css-selector-tokenizer-0.7.0.tgz",
-        "postcss": "https://registry.npmjs.org/postcss/-/postcss-6.0.13.tgz"
-      }
-    },
-    "postcss-modules-scope": {
-      "version": "https://registry.npmjs.org/postcss-modules-scope/-/postcss-modules-scope-1.1.0.tgz",
-      "integrity": "sha1-1upkmUx5+XtipytCb75gVqGUu5A=",
-      "dev": true,
-      "requires": {
-        "css-selector-tokenizer": "https://registry.npmjs.org/css-selector-tokenizer/-/css-selector-tokenizer-0.7.0.tgz",
-        "postcss": "https://registry.npmjs.org/postcss/-/postcss-6.0.13.tgz"
-      }
-    },
-    "postcss-modules-values": {
-      "version": "https://registry.npmjs.org/postcss-modules-values/-/postcss-modules-values-1.3.0.tgz",
-      "integrity": "sha1-7P+p1+GSUYOJ9CrQ6D9yrsRW6iA=",
-      "dev": true,
-      "requires": {
-        "icss-replace-symbols": "https://registry.npmjs.org/icss-replace-symbols/-/icss-replace-symbols-1.1.0.tgz",
-        "postcss": "https://registry.npmjs.org/postcss/-/postcss-6.0.13.tgz"
-      }
-    },
-    "postcss-nested": {
-      "version": "https://registry.npmjs.org/postcss-nested/-/postcss-nested-2.1.2.tgz",
-      "integrity": "sha1-BAVygfljH+9oSFf7ARm64E7eA8Y=",
+    "husky": {
+      "version": "0.14.3",
+      "resolved": "https://registry.npmjs.org/husky/-/husky-0.14.3.tgz",
+      "integrity": "sha512-e21wivqHpstpoiWA/Yi8eFti8E+sQDSS53cpJsPptPs295QTOQR0ZwnHo2TXy1XOpZFD9rPOd3NpmqTK6uMLJA==",
       "dev": true,
       "requires": {
-        "postcss": "https://registry.npmjs.org/postcss/-/postcss-6.0.13.tgz",
-        "postcss-selector-parser": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-2.2.3.tgz"
+        "is-ci": "1.1.0",
+        "normalize-path": "1.0.0",
+        "strip-indent": "2.0.0"
+      },
+      "dependencies": {
+        "ci-info": {
+          "version": "1.1.2",
+          "resolved": "https://registry.npmjs.org/ci-info/-/ci-info-1.1.2.tgz",
+          "integrity": "sha512-uTGIPNx/nSpBdsF6xnseRXLLtfr9VLqkz8ZqHXr3Y7b6SftyRxBGjwMtJj1OhNbmlc1wZzLNAlAcvyIiE8a6ZA==",
+          "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==",
+          "dev": true,
+          "requires": {
+            "ci-info": "1.1.2"
+          }
+        },
+        "normalize-path": {
+          "version": "1.0.0",
+          "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-1.0.0.tgz",
+          "integrity": "sha1-MtDkcvkf80VwHBWoMRAY07CpA3k=",
+          "dev": true
+        },
+        "strip-indent": {
+          "version": "2.0.0",
+          "resolved": "https://registry.npmjs.org/strip-indent/-/strip-indent-2.0.0.tgz",
+          "integrity": "sha1-XvjbKV0B5u1sv3qrlpmNeCJSe2g=",
+          "dev": true
+        }
       }
     },
-    "postcss-selector-parser": {
-      "version": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-2.2.3.tgz",
-      "integrity": "sha1-+UN3iGBsPJrO4W/+jYsWKX8nu5A=",
-      "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==",
       "requires": {
-        "flatten": "https://registry.npmjs.org/flatten/-/flatten-1.0.2.tgz",
-        "indexes-of": "https://registry.npmjs.org/indexes-of/-/indexes-of-1.0.1.tgz",
-        "uniq": "https://registry.npmjs.org/uniq/-/uniq-1.0.1.tgz"
+        "invariant": "2.2.2"
+      },
+      "dependencies": {
+        "invariant": {
+          "version": "2.2.2",
+          "resolved": "https://registry.npmjs.org/invariant/-/invariant-2.2.2.tgz",
+          "integrity": "sha1-nh9WrArNtr8wMwbzOL47IErmA2A=",
+          "requires": {
+            "loose-envify": "1.3.1"
+          }
+        },
+        "js-tokens": {
+          "version": "3.0.2",
+          "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-3.0.2.tgz",
+          "integrity": "sha1-mGbfOVECEw449/mWvOtlRDIJwls="
+        },
+        "loose-envify": {
+          "version": "1.3.1",
+          "resolved": "https://registry.npmjs.org/loose-envify/-/loose-envify-1.3.1.tgz",
+          "integrity": "sha1-0aitM/qc4OcT1l/dCsi3SNR4yEg=",
+          "requires": {
+            "js-tokens": "3.0.2"
+          }
+        }
       }
     },
-    "postcss-value-parser": {
-      "version": "https://registry.npmjs.org/postcss-value-parser/-/postcss-value-parser-3.3.0.tgz",
-      "integrity": "sha1-h/OPnxj3dKSrTIojL1xc6IcqnRU=",
-      "dev": true
-    },
-    "prelude-ls": {
-      "version": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.1.2.tgz",
-      "integrity": "sha1-IZMqVJ9eUv/ZqCf1cOBL5iqX2lQ=",
-      "dev": true
-    },
-    "prepend-http": {
-      "version": "https://registry.npmjs.org/prepend-http/-/prepend-http-1.0.4.tgz",
-      "integrity": "sha1-1PRWKwzjaW5BrFLQ4ALlemNdxtw="
+    "inherits": {
+      "version": "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz",
+      "integrity": "sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4="
     },
-    "pretty-format": {
-      "version": "https://registry.npmjs.org/pretty-format/-/pretty-format-21.2.1.tgz",
-      "integrity": "sha1-rlQH888hBmzQEaobpfzntqLt2zY=",
+    "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": {
-        "ansi-regex": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-3.0.0.tgz",
-        "ansi-styles": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.0.tgz"
+        "app-root-path": "2.0.1",
+        "chalk": "2.3.0",
+        "commander": "2.12.2",
+        "cosmiconfig": "1.1.0",
+        "execa": "0.8.0",
+        "is-glob": "4.0.0",
+        "jest-validate": "21.2.1",
+        "listr": "0.12.0",
+        "lodash": "4.17.4",
+        "log-symbols": "2.1.0",
+        "minimatch": "3.0.4",
+        "npm-which": "3.0.1",
+        "p-map": "1.2.0",
+        "staged-git-files": "0.0.4",
+        "stringify-object": "3.2.1"
       },
       "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
+        },
         "ansi-regex": {
-          "version": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-3.0.0.tgz",
+          "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": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.0.tgz",
-          "integrity": "sha1-wVm41b4PnlpvNG2rlPFs4CIWG4g=",
+          "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": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.0.tgz"
+            "color-convert": "1.9.1"
           }
-        }
-      }
-    },
-    "probe-image-size": {
-      "version": "https://registry.npmjs.org/probe-image-size/-/probe-image-size-3.1.0.tgz",
-      "integrity": "sha1-50e+maDQqOUFiqcihUwkCSuS3WY=",
-      "requires": {
-        "any-promise": "https://registry.npmjs.org/any-promise/-/any-promise-1.3.0.tgz",
-        "deepmerge": "https://registry.npmjs.org/deepmerge/-/deepmerge-1.5.2.tgz",
-        "got": "https://registry.npmjs.org/got/-/got-7.1.0.tgz",
-        "inherits": "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz",
-        "next-tick": "https://registry.npmjs.org/next-tick/-/next-tick-1.0.0.tgz",
-        "stream-parser": "https://registry.npmjs.org/stream-parser/-/stream-parser-0.3.1.tgz"
-      }
-    },
-    "process-nextick-args": {
-      "version": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-1.0.7.tgz",
-      "integrity": "sha1-FQ4gt1ZZCtP5EJPyWk8q2L/zC6M="
-    },
-    "progress": {
-      "version": "https://registry.npmjs.org/progress/-/progress-2.0.0.tgz",
-      "integrity": "sha1-ihvjZr+Pwj2yvSPxDG/pILQ4nR8=",
-      "dev": true
-    },
-    "promise": {
-      "version": "https://registry.npmjs.org/promise/-/promise-7.3.1.tgz",
-      "integrity": "sha1-BktyYCsY+Q8pGSuLG8QY/9Hr078=",
+        },
+        "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
+        },
+        "argparse": {
+          "version": "1.0.9",
+          "resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.9.tgz",
+          "integrity": "sha1-c9g7wmP4bpf4zE9rrhsOkKfSLIY=",
+          "dev": true,
+          "requires": {
+            "sprintf-js": "1.0.3"
+          }
+        },
+        "balanced-match": {
+          "version": "1.0.0",
+          "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.0.tgz",
+          "integrity": "sha1-ibTRmasr7kneFk6gK4nORi1xt2c=",
+          "dev": true
+        },
+        "brace-expansion": {
+          "version": "1.1.8",
+          "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.8.tgz",
+          "integrity": "sha1-wHshHHyVLsH479Uad+8NHTmQopI=",
+          "dev": true,
+          "requires": {
+            "balanced-match": "1.0.0",
+            "concat-map": "0.0.1"
+          }
+        },
+        "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.2.0",
+            "escape-string-regexp": "1.0.5",
+            "supports-color": "4.5.0"
+          }
+        },
+        "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"
+          }
+        },
+        "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",
+          "integrity": "sha1-nxXPuwcFAFNpIWxiasfQWrkN1XQ=",
+          "dev": true,
+          "requires": {
+            "slice-ansi": "0.0.4",
+            "string-width": "1.0.2"
+          }
+        },
+        "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=",
+          "dev": true
+        },
+        "color-convert": {
+          "version": "1.9.1",
+          "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.1.tgz",
+          "integrity": "sha512-mjGanIiwQJskCC18rPR6OmrZ6fm2Lc7PeGFYwCmy5J34wC6F1PzdGL6xeMfmgicfYcNLGuVFA3WzXtIDCQSZxQ==",
+          "dev": true,
+          "requires": {
+            "color-name": "1.1.3"
+          }
+        },
+        "color-name": {
+          "version": "1.1.3",
+          "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz",
+          "integrity": "sha1-p9BVi9icQveV3UIyj3QIMcpTvCU=",
+          "dev": true
+        },
+        "commander": {
+          "version": "2.12.2",
+          "resolved": "https://registry.npmjs.org/commander/-/commander-2.12.2.tgz",
+          "integrity": "sha512-BFnaq5ZOGcDN7FlrtBT4xxkgIToalIIxwjxLWVJ8bGTpe1LroqMiqQXdA7ygc7CRvaYS+9zfPGFnJqFSayx+AA==",
+          "dev": true
+        },
+        "concat-map": {
+          "version": "0.0.1",
+          "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz",
+          "integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=",
+          "dev": true
+        },
+        "cosmiconfig": {
+          "version": "1.1.0",
+          "resolved": "https://registry.npmjs.org/cosmiconfig/-/cosmiconfig-1.1.0.tgz",
+          "integrity": "sha1-DeoPmATv37kp+7GxiOJVU+oFPTc=",
+          "dev": true,
+          "requires": {
+            "graceful-fs": "4.1.11",
+            "js-yaml": "3.10.0",
+            "minimist": "1.2.0",
+            "object-assign": "4.1.1",
+            "os-homedir": "1.0.2",
+            "parse-json": "2.2.0",
+            "pinkie-promise": "2.0.1",
+            "require-from-string": "1.2.1"
+          }
+        },
+        "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=",
+          "dev": true,
+          "requires": {
+            "lru-cache": "4.1.1",
+            "shebang-command": "1.2.0",
+            "which": "1.3.0"
+          }
+        },
+        "date-fns": {
+          "version": "1.29.0",
+          "resolved": "https://registry.npmjs.org/date-fns/-/date-fns-1.29.0.tgz",
+          "integrity": "sha512-lbTXWZ6M20cWH8N9S6afb0SBm6tMk+uUg6z3MqHPKE9atmsY3kJkTm8vKe93izJ2B2+q5MV990sM2CHgtAZaOw==",
+          "dev": true
+        },
+        "elegant-spinner": {
+          "version": "1.0.1",
+          "resolved": "https://registry.npmjs.org/elegant-spinner/-/elegant-spinner-1.0.1.tgz",
+          "integrity": "sha1-2wQ1IcldfjA/2PNFvtwzSc+wcp4=",
+          "dev": true
+        },
+        "error-ex": {
+          "version": "1.3.1",
+          "resolved": "https://registry.npmjs.org/error-ex/-/error-ex-1.3.1.tgz",
+          "integrity": "sha1-+FWobOYa3E6GIcPNoh56dhLDqNw=",
+          "dev": true,
+          "requires": {
+            "is-arrayish": "0.2.1"
+          }
+        },
+        "escape-string-regexp": {
+          "version": "1.0.5",
+          "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz",
+          "integrity": "sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ=",
+          "dev": true
+        },
+        "esprima": {
+          "version": "4.0.0",
+          "resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.0.tgz",
+          "integrity": "sha512-oftTcaMu/EGrEIu904mWteKIv8vMuOgGYo7EhVJJN00R/EED9DCua/xxHRdYnKtcECzVg7xOWhflvJMnqcFZjw==",
+          "dev": true
+        },
+        "execa": {
+          "version": "0.8.0",
+          "resolved": "https://registry.npmjs.org/execa/-/execa-0.8.0.tgz",
+          "integrity": "sha1-2NdrvBtVIX7RkP1t1J08d07PyNo=",
+          "dev": true,
+          "requires": {
+            "cross-spawn": "5.1.0",
+            "get-stream": "3.0.0",
+            "is-stream": "1.1.0",
+            "npm-run-path": "2.0.2",
+            "p-finally": "1.0.0",
+            "signal-exit": "3.0.2",
+            "strip-eof": "1.0.0"
+          }
+        },
+        "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
+        },
+        "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.1"
+          }
+        },
+        "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==",
+          "dev": true
+        },
+        "get-stream": {
+          "version": "3.0.0",
+          "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-3.0.0.tgz",
+          "integrity": "sha1-jpQ9E1jcN1VQVOy+LtsFqhdO3hQ=",
+          "dev": true
+        },
+        "graceful-fs": {
+          "version": "4.1.11",
+          "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.1.11.tgz",
+          "integrity": "sha1-Dovf5NHduIVNZOBOp8AOKgJuVlg=",
+          "dev": true
+        },
+        "has-ansi": {
+          "version": "2.0.0",
+          "resolved": "https://registry.npmjs.org/has-ansi/-/has-ansi-2.0.0.tgz",
+          "integrity": "sha1-NPUEnOHs3ysGSa8+8k5F7TVBbZE=",
+          "dev": true,
+          "requires": {
+            "ansi-regex": "2.1.1"
+          },
+          "dependencies": {
+            "ansi-regex": {
+              "version": "2.1.1",
+              "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-2.1.1.tgz",
+              "integrity": "sha1-w7M6te42DYbg5ijwRorn7yfWVN8=",
+              "dev": true
+            }
+          }
+        },
+        "has-flag": {
+          "version": "2.0.0",
+          "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-2.0.0.tgz",
+          "integrity": "sha1-6CB68cx7MNRGzHC3NLXovhj4jVE=",
+          "dev": true
+        },
+        "indent-string": {
+          "version": "2.1.0",
+          "resolved": "https://registry.npmjs.org/indent-string/-/indent-string-2.1.0.tgz",
+          "integrity": "sha1-ji1INIdCEhtKghi3oTfppSBJ3IA=",
+          "dev": true,
+          "requires": {
+            "repeating": "2.0.1"
+          }
+        },
+        "is-arrayish": {
+          "version": "0.2.1",
+          "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.2.1.tgz",
+          "integrity": "sha1-d8mYQFJ6qOyxqLppe4BkWnqSap0=",
+          "dev": true
+        },
+        "is-extglob": {
+          "version": "2.1.1",
+          "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz",
+          "integrity": "sha1-qIwCU1eR8C7TfHahueqXc8gz+MI=",
+          "dev": true
+        },
+        "is-finite": {
+          "version": "1.0.2",
+          "resolved": "https://registry.npmjs.org/is-finite/-/is-finite-1.0.2.tgz",
+          "integrity": "sha1-zGZ3aVYCvlUO8R6LSqYwU0K20Ko=",
+          "dev": true,
+          "requires": {
+            "number-is-nan": "1.0.1"
+          }
+        },
+        "is-fullwidth-code-point": {
+          "version": "1.0.0",
+          "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-1.0.0.tgz",
+          "integrity": "sha1-754xOG8DGn8NZDr4L95QxFfvAMs=",
+          "dev": true,
+          "requires": {
+            "number-is-nan": "1.0.1"
+          }
+        },
+        "is-glob": {
+          "version": "4.0.0",
+          "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.0.tgz",
+          "integrity": "sha1-lSHHaEXMJhCoUgPd8ICpWML/q8A=",
+          "dev": true,
+          "requires": {
+            "is-extglob": "2.1.1"
+          }
+        },
+        "is-obj": {
+          "version": "1.0.1",
+          "resolved": "https://registry.npmjs.org/is-obj/-/is-obj-1.0.1.tgz",
+          "integrity": "sha1-PkcprB9f3gJc19g6iW2rn09n2w8=",
+          "dev": true
+        },
+        "is-promise": {
+          "version": "2.1.0",
+          "resolved": "https://registry.npmjs.org/is-promise/-/is-promise-2.1.0.tgz",
+          "integrity": "sha1-eaKp7OfwlugPNtKy87wWwf9L8/o=",
+          "dev": true
+        },
+        "is-regexp": {
+          "version": "1.0.0",
+          "resolved": "https://registry.npmjs.org/is-regexp/-/is-regexp-1.0.0.tgz",
+          "integrity": "sha1-/S2INUXEa6xaYz57mgnof6LLUGk=",
+          "dev": true
+        },
+        "is-stream": {
+          "version": "1.1.0",
+          "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-1.1.0.tgz",
+          "integrity": "sha1-EtSj3U5o4Lec6428hBc66A2RykQ=",
+          "dev": true
+        },
+        "isexe": {
+          "version": "2.0.0",
+          "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz",
+          "integrity": "sha1-6PvzdNxVb/iUehDcsFctYz8s+hA=",
+          "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==",
+          "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==",
+          "dev": true,
+          "requires": {
+            "chalk": "2.3.0",
+            "jest-get-type": "21.2.0",
+            "leven": "2.1.0",
+            "pretty-format": "21.2.1"
+          }
+        },
+        "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==",
+          "dev": true,
+          "requires": {
+            "argparse": "1.0.9",
+            "esprima": "4.0.0"
+          }
+        },
+        "leven": {
+          "version": "2.1.0",
+          "resolved": "https://registry.npmjs.org/leven/-/leven-2.1.0.tgz",
+          "integrity": "sha1-wuep93IJTe6dNCAq6KzORoeHVYA=",
+          "dev": true
+        },
+        "listr": {
+          "version": "0.12.0",
+          "resolved": "https://registry.npmjs.org/listr/-/listr-0.12.0.tgz",
+          "integrity": "sha1-a84sD1YD+klYDqF81qAMwOX6RRo=",
+          "dev": true,
+          "requires": {
+            "chalk": "1.1.3",
+            "cli-truncate": "0.2.1",
+            "figures": "1.7.0",
+            "indent-string": "2.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.1",
+            "log-symbols": "1.0.2",
+            "log-update": "1.0.2",
+            "ora": "0.2.3",
+            "p-map": "1.2.0",
+            "rxjs": "5.5.6",
+            "stream-to-observable": "0.1.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": "https://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.5",
+                "has-ansi": "2.0.0",
+                "strip-ansi": "3.0.1",
+                "supports-color": "2.0.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.1.3"
+              }
+            },
+            "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-silent-renderer": {
+          "version": "1.1.1",
+          "resolved": "https://registry.npmjs.org/listr-silent-renderer/-/listr-silent-renderer-1.1.1.tgz",
+          "integrity": "sha1-kktaN1cVN3C/Go4/v3S4u/P5JC4=",
+          "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=",
+          "dev": true,
+          "requires": {
+            "chalk": "1.1.3",
+            "cli-truncate": "0.2.1",
+            "elegant-spinner": "1.0.1",
+            "figures": "1.7.0",
+            "indent-string": "3.2.0",
+            "log-symbols": "1.0.2",
+            "log-update": "1.0.2",
+            "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": "https://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.5",
+                "has-ansi": "2.0.0",
+                "strip-ansi": "3.0.1",
+                "supports-color": "2.0.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.1.3"
+              }
+            },
+            "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=",
+          "dev": true,
+          "requires": {
+            "chalk": "1.1.3",
+            "cli-cursor": "1.0.2",
+            "date-fns": "1.29.0",
+            "figures": "1.7.0"
+          },
+          "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": "https://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.5",
+                "has-ansi": "2.0.0",
+                "strip-ansi": "3.0.1",
+                "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=",
+              "dev": true
+            }
+          }
+        },
+        "log-symbols": {
+          "version": "2.1.0",
+          "resolved": "https://registry.npmjs.org/log-symbols/-/log-symbols-2.1.0.tgz",
+          "integrity": "sha512-zLeLrzMA1A2vRF1e/0Mo+LNINzi6jzBylHj5WqvQ/WK/5WCZt8si9SyN4p9llr/HRYvVR1AoXHRHl4WTHyQAzQ==",
+          "dev": true,
+          "requires": {
+            "chalk": "2.3.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.4.0",
+            "cli-cursor": "1.0.2"
+          }
+        },
+        "lru-cache": {
+          "version": "4.1.1",
+          "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-4.1.1.tgz",
+          "integrity": "sha512-q4spe4KTfsAS1SUHLO0wz8Qiyf1+vMIAgpRYioFYDMNqKfHQbg+AVDH3i4fvpl71/P1L0dBl+fQi+P37UYf0ew==",
+          "dev": true,
+          "requires": {
+            "pseudomap": "1.0.2",
+            "yallist": "2.1.2"
+          }
+        },
+        "minimatch": {
+          "version": "3.0.4",
+          "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz",
+          "integrity": "sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==",
+          "dev": true,
+          "requires": {
+            "brace-expansion": "1.1.8"
+          }
+        },
+        "minimist": {
+          "version": "1.2.0",
+          "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.0.tgz",
+          "integrity": "sha1-o1AIsg9BOD7sH7kU9M1d95omQoQ=",
+          "dev": true
+        },
+        "npm-path": {
+          "version": "2.0.4",
+          "resolved": "https://registry.npmjs.org/npm-path/-/npm-path-2.0.4.tgz",
+          "integrity": "sha512-IFsj0R9C7ZdR5cP+ET342q77uSRdtWOlWpih5eC+lu29tIDbNEgDbzgVJ5UFvYHWhxDZ5TFkJafFioO0pPQjCw==",
+          "dev": true,
+          "requires": {
+            "which": "1.3.0"
+          }
+        },
+        "npm-run-path": {
+          "version": "2.0.2",
+          "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-2.0.2.tgz",
+          "integrity": "sha1-NakjLfo11wZ7TLLd8jV7GHFTbF8=",
+          "dev": true,
+          "requires": {
+            "path-key": "2.0.1"
+          }
+        },
+        "npm-which": {
+          "version": "3.0.1",
+          "resolved": "https://registry.npmjs.org/npm-which/-/npm-which-3.0.1.tgz",
+          "integrity": "sha1-kiXybsOihcIJyuZ8OxGmtKtxQKo=",
+          "dev": true,
+          "requires": {
+            "commander": "2.12.2",
+            "npm-path": "2.0.4",
+            "which": "1.3.0"
+          }
+        },
+        "number-is-nan": {
+          "version": "1.0.1",
+          "resolved": "https://registry.npmjs.org/number-is-nan/-/number-is-nan-1.0.1.tgz",
+          "integrity": "sha1-CXtgK1NCKlIsGvuHkDGDNpQaAR0=",
+          "dev": true
+        },
+        "object-assign": {
+          "version": "4.1.1",
+          "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz",
+          "integrity": "sha1-IQmtx5ZYh8/AXLvUQsrIv7s2CGM=",
+          "dev": true
+        },
+        "onetime": {
+          "version": "1.1.0",
+          "resolved": "https://registry.npmjs.org/onetime/-/onetime-1.1.0.tgz",
+          "integrity": "sha1-ofeDj4MUxRbwXs78vEzP4EtO14k=",
+          "dev": true
+        },
+        "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.3",
+            "cli-cursor": "1.0.2",
+            "cli-spinners": "0.1.2",
+            "object-assign": "4.1.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": "https://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.5",
+                "has-ansi": "2.0.0",
+                "strip-ansi": "3.0.1",
+                "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=",
+              "dev": true
+            }
+          }
+        },
+        "os-homedir": {
+          "version": "1.0.2",
+          "resolved": "https://registry.npmjs.org/os-homedir/-/os-homedir-1.0.2.tgz",
+          "integrity": "sha1-/7xJiDNuDoM94MFox+8VISGqf7M=",
+          "dev": true
+        },
+        "p-finally": {
+          "version": "1.0.0",
+          "resolved": "https://registry.npmjs.org/p-finally/-/p-finally-1.0.0.tgz",
+          "integrity": "sha1-P7z7FbiZpEEjs0ttzBi3JDNqLK4=",
+          "dev": true
+        },
+        "p-map": {
+          "version": "1.2.0",
+          "resolved": "https://registry.npmjs.org/p-map/-/p-map-1.2.0.tgz",
+          "integrity": "sha512-r6zKACMNhjPJMTl8KcFH4li//gkrXWfbD6feV8l6doRHlzljFWGJ2AP6iKaCJXyZmAUMOPtvbW7EXkbWO/pLEA==",
+          "dev": true
+        },
+        "parse-json": {
+          "version": "2.2.0",
+          "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-2.2.0.tgz",
+          "integrity": "sha1-9ID0BDTvgHQfhGkJn43qGPVaTck=",
+          "dev": true,
+          "requires": {
+            "error-ex": "1.3.1"
+          }
+        },
+        "path-key": {
+          "version": "2.0.1",
+          "resolved": "https://registry.npmjs.org/path-key/-/path-key-2.0.1.tgz",
+          "integrity": "sha1-QRyttXTFoUDTpLGRDUDYDMn0C0A=",
+          "dev": true
+        },
+        "pinkie": {
+          "version": "2.0.4",
+          "resolved": "https://registry.npmjs.org/pinkie/-/pinkie-2.0.4.tgz",
+          "integrity": "sha1-clVrgM+g1IqXToDnckjoDtT3+HA=",
+          "dev": true
+        },
+        "pinkie-promise": {
+          "version": "2.0.1",
+          "resolved": "https://registry.npmjs.org/pinkie-promise/-/pinkie-promise-2.0.1.tgz",
+          "integrity": "sha1-ITXW36ejWMBprJsXh3YogihFD/o=",
+          "dev": true,
+          "requires": {
+            "pinkie": "2.0.4"
+          }
+        },
+        "pretty-format": {
+          "version": "21.2.1",
+          "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-21.2.1.tgz",
+          "integrity": "sha512-ZdWPGYAnYfcVP8yKA3zFjCn8s4/17TeYH28MXuC8vTp0o21eXjbFGcOAXZEaDaOFJjc3h2qa7HQNHNshhvoh2A==",
+          "dev": true,
+          "requires": {
+            "ansi-regex": "3.0.0",
+            "ansi-styles": "3.2.0"
+          }
+        },
+        "pseudomap": {
+          "version": "1.0.2",
+          "resolved": "https://registry.npmjs.org/pseudomap/-/pseudomap-1.0.2.tgz",
+          "integrity": "sha1-8FKijacOYYkX7wqKw0wa5aaChrM=",
+          "dev": true
+        },
+        "repeating": {
+          "version": "2.0.1",
+          "resolved": "https://registry.npmjs.org/repeating/-/repeating-2.0.1.tgz",
+          "integrity": "sha1-UhTFOpJtNVJwdSf7q0FdvAjQbdo=",
+          "dev": true,
+          "requires": {
+            "is-finite": "1.0.2"
+          }
+        },
+        "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
+        },
+        "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.1.1",
+            "onetime": "1.1.0"
+          }
+        },
+        "rxjs": {
+          "version": "5.5.6",
+          "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-5.5.6.tgz",
+          "integrity": "sha512-v4Q5HDC0FHAQ7zcBX7T2IL6O5ltl1a2GX4ENjPXg6SjDY69Cmx9v4113C99a4wGF16ClPv5Z8mghuYorVkg/kg==",
+          "dev": true,
+          "requires": {
+            "symbol-observable": "1.0.1"
+          }
+        },
+        "shebang-command": {
+          "version": "1.2.0",
+          "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-1.2.0.tgz",
+          "integrity": "sha1-RKrGW2lbAzmJaMOfNj/uXer98eo=",
+          "dev": true,
+          "requires": {
+            "shebang-regex": "1.0.0"
+          }
+        },
+        "shebang-regex": {
+          "version": "1.0.0",
+          "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-1.0.0.tgz",
+          "integrity": "sha1-2kL0l0DAtC2yypcoVxyxkMmO/qM=",
+          "dev": true
+        },
+        "signal-exit": {
+          "version": "3.0.2",
+          "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.2.tgz",
+          "integrity": "sha1-tf3AjxKH6hF4Yo5BXiUTK3NkbG0=",
+          "dev": true
+        },
+        "slice-ansi": {
+          "version": "0.0.4",
+          "resolved": "https://registry.npmjs.org/slice-ansi/-/slice-ansi-0.0.4.tgz",
+          "integrity": "sha1-7b+JA/ZvfOL46v1s7tZeJkyDGzU=",
+          "dev": true
+        },
+        "sprintf-js": {
+          "version": "1.0.3",
+          "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz",
+          "integrity": "sha1-BOaSb2YolTVPPdAVIDYzuFcpfiw=",
+          "dev": true
+        },
+        "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=",
+          "dev": true
+        },
+        "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=",
+          "dev": true
+        },
+        "string-width": {
+          "version": "1.0.2",
+          "resolved": "https://registry.npmjs.org/string-width/-/string-width-1.0.2.tgz",
+          "integrity": "sha1-EYvfW4zcUaKn5w0hHgfisLmxB9M=",
+          "dev": true,
+          "requires": {
+            "code-point-at": "1.1.0",
+            "is-fullwidth-code-point": "1.0.0",
+            "strip-ansi": "3.0.1"
+          }
+        },
+        "stringify-object": {
+          "version": "3.2.1",
+          "resolved": "https://registry.npmjs.org/stringify-object/-/stringify-object-3.2.1.tgz",
+          "integrity": "sha512-jPcQYw/52HUPP8uOE4kkjxl5bB9LfHkKCTptIk3qw7ozP5XMIMlHMLjt00GGSwW6DJAf/njY5EU6Vpwl4LlBKQ==",
+          "dev": true,
+          "requires": {
+            "get-own-enumerable-property-symbols": "2.0.1",
+            "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",
+          "integrity": "sha1-ajhfuIU9lS1f8F0Oiq+UJ43GPc8=",
+          "dev": true,
+          "requires": {
+            "ansi-regex": "2.1.1"
+          },
+          "dependencies": {
+            "ansi-regex": {
+              "version": "2.1.1",
+              "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-2.1.1.tgz",
+              "integrity": "sha1-w7M6te42DYbg5ijwRorn7yfWVN8=",
+              "dev": true
+            }
+          }
+        },
+        "strip-eof": {
+          "version": "1.0.0",
+          "resolved": "https://registry.npmjs.org/strip-eof/-/strip-eof-1.0.0.tgz",
+          "integrity": "sha1-u0P/VZim6wXYm1n80SnJgzE2Br8=",
+          "dev": true
+        },
+        "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"
+          }
+        },
+        "symbol-observable": {
+          "version": "1.0.1",
+          "resolved": "https://registry.npmjs.org/symbol-observable/-/symbol-observable-1.0.1.tgz",
+          "integrity": "sha1-g0D8RwLDEi310iKI+IKD9RPT/dQ=",
+          "dev": true
+        },
+        "which": {
+          "version": "1.3.0",
+          "resolved": "https://registry.npmjs.org/which/-/which-1.3.0.tgz",
+          "integrity": "sha512-xcJpopdamTuY5duC/KnTTNBraPK54YwpenP4lzxU8H91GudWpFv38u0CKjclE1Wi2EH2EDz5LRcHcKbCIzqGyg==",
+          "dev": true,
+          "requires": {
+            "isexe": "2.0.0"
+          }
+        },
+        "yallist": {
+          "version": "2.1.2",
+          "resolved": "https://registry.npmjs.org/yallist/-/yallist-2.1.2.tgz",
+          "integrity": "sha1-HBH5IY8HYImkfdUS+TxmmaaoHVI=",
+          "dev": true
+        }
+      }
+    },
+    "lodash": {
+      "version": "4.17.4",
+      "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.4.tgz",
+      "integrity": "sha1-eCA6TRwyiuHYbcpkYONptX9AVa4="
+    },
+    "meteor-node-stubs": {
+      "version": "0.3.2",
+      "resolved": "https://registry.npmjs.org/meteor-node-stubs/-/meteor-node-stubs-0.3.2.tgz",
+      "integrity": "sha512-l93SS/HutbqBRJODO2m7hup8cYI2acF5bB39+ZvP2BX8HMmCSCXeFH7v0sr4hD7zrVvHQA5UqS0pcDYKn0VM6g==",
+      "requires": {
+        "assert": "1.4.1",
+        "browserify-zlib": "0.1.4",
+        "buffer": "4.9.1",
+        "console-browserify": "1.1.0",
+        "constants-browserify": "1.0.0",
+        "crypto-browserify": "3.11.1",
+        "domain-browser": "1.1.7",
+        "events": "1.1.1",
+        "http-browserify": "1.7.0",
+        "https-browserify": "0.0.1",
+        "os-browserify": "0.2.1",
+        "path-browserify": "0.0.0",
+        "process": "0.11.10",
+        "punycode": "1.4.1",
+        "querystring-es3": "0.2.1",
+        "readable-stream": "git+https://github.com/meteor/readable-stream.git#d64a64aa6061b9b6855feff4d09e58fb3b2e4502",
+        "stream-browserify": "2.0.1",
+        "string_decoder": "1.0.3",
+        "timers-browserify": "1.4.2",
+        "tty-browserify": "0.0.0",
+        "url": "0.11.0",
+        "util": "0.10.3",
+        "vm-browserify": "0.0.4"
+      },
+      "dependencies": {
+        "Base64": {
+          "version": "0.2.1",
+          "resolved": "https://registry.npmjs.org/Base64/-/Base64-0.2.1.tgz",
+          "integrity": "sha1-ujpCMHCOGGcFBl5mur3Uw1z2ACg="
+        },
+        "asn1.js": {
+          "version": "4.9.1",
+          "resolved": "https://registry.npmjs.org/asn1.js/-/asn1.js-4.9.1.tgz",
+          "integrity": "sha1-SLokC0WpKA6UdImQull9IWYX/UA=",
+          "requires": {
+            "bn.js": "4.11.8",
+            "inherits": "2.0.1",
+            "minimalistic-assert": "1.0.0"
+          }
+        },
+        "assert": {
+          "version": "1.4.1",
+          "resolved": "https://registry.npmjs.org/assert/-/assert-1.4.1.tgz",
+          "integrity": "sha1-mZEtWRg2tab1s0XA8H7vwI/GXZE=",
+          "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.1",
+          "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.2.1.tgz",
+          "integrity": "sha512-dwVUVIXsBZXwTuwnXI9RK8sBmgq09NDHzyR9SAph9eqk76gKK2JSQmZARC2zRC81JC2QTtxD0ARU5qTS25gIGw=="
+        },
+        "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.8",
+          "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.8.tgz",
+          "integrity": "sha1-wHshHHyVLsH479Uad+8NHTmQopI=",
+          "requires": {
+            "balanced-match": "1.0.0",
+            "concat-map": "0.0.1"
+          }
+        },
+        "brorand": {
+          "version": "1.1.0",
+          "resolved": "https://registry.npmjs.org/brorand/-/brorand-1.1.0.tgz",
+          "integrity": "sha1-EsJe/kCkXjwyPrhnWgoM5XsiNx8="
+        },
+        "browserify-aes": {
+          "version": "1.1.0",
+          "resolved": "https://registry.npmjs.org/browserify-aes/-/browserify-aes-1.1.0.tgz",
+          "integrity": "sha512-W2bIMLYoZ9oow7TyePpMJk9l9LY7O3R61a/68bVCDOtnJynnwe3ZeW2IzzSkrQnPKNdJrxVDn3ALZNisSBwb7g==",
+          "requires": {
+            "buffer-xor": "1.0.3",
+            "cipher-base": "1.0.4",
+            "create-hash": "1.1.3",
+            "evp_bytestokey": "1.0.3",
+            "inherits": "2.0.1",
+            "safe-buffer": "5.1.1"
+          }
+        },
+        "browserify-cipher": {
+          "version": "1.0.0",
+          "resolved": "https://registry.npmjs.org/browserify-cipher/-/browserify-cipher-1.0.0.tgz",
+          "integrity": "sha1-mYgkSHS/XtTijalWZtzWasj8Njo=",
+          "requires": {
+            "browserify-aes": "1.1.0",
+            "browserify-des": "1.0.0",
+            "evp_bytestokey": "1.0.3"
+          }
+        },
+        "browserify-des": {
+          "version": "1.0.0",
+          "resolved": "https://registry.npmjs.org/browserify-des/-/browserify-des-1.0.0.tgz",
+          "integrity": "sha1-2qJ3cXRwki7S/hhZQRihdUOXId0=",
+          "requires": {
+            "cipher-base": "1.0.4",
+            "des.js": "1.0.0",
+            "inherits": "2.0.1"
+          }
+        },
+        "browserify-rsa": {
+          "version": "4.0.1",
+          "resolved": "https://registry.npmjs.org/browserify-rsa/-/browserify-rsa-4.0.1.tgz",
+          "integrity": "sha1-IeCr+vbyApzy+vsTNWenAdQTVSQ=",
+          "requires": {
+            "bn.js": "4.11.8",
+            "randombytes": "2.0.5"
+          }
+        },
+        "browserify-sign": {
+          "version": "4.0.4",
+          "resolved": "https://registry.npmjs.org/browserify-sign/-/browserify-sign-4.0.4.tgz",
+          "integrity": "sha1-qk62jl17ZYuqa/alfmMMvXqT0pg=",
+          "requires": {
+            "bn.js": "4.11.8",
+            "browserify-rsa": "4.0.1",
+            "create-hash": "1.1.3",
+            "create-hmac": "1.1.6",
+            "elliptic": "6.4.0",
+            "inherits": "2.0.1",
+            "parse-asn1": "5.1.0"
+          }
+        },
+        "browserify-zlib": {
+          "version": "0.1.4",
+          "resolved": "https://registry.npmjs.org/browserify-zlib/-/browserify-zlib-0.1.4.tgz",
+          "integrity": "sha1-uzX4pRn2AOD6a4SFJByXnQFB+y0=",
+          "requires": {
+            "pako": "0.2.9"
+          }
+        },
+        "buffer": {
+          "version": "4.9.1",
+          "resolved": "https://registry.npmjs.org/buffer/-/buffer-4.9.1.tgz",
+          "integrity": "sha1-bRu2AbB6TvztlwlBMgkwJ8lbwpg=",
+          "requires": {
+            "base64-js": "1.2.1",
+            "ieee754": "1.1.8",
+            "isarray": "1.0.0"
+          }
+        },
+        "buffer-xor": {
+          "version": "1.0.3",
+          "resolved": "https://registry.npmjs.org/buffer-xor/-/buffer-xor-1.0.3.tgz",
+          "integrity": "sha1-JuYe0UIvtw3ULm42cp7VHYVf6Nk="
+        },
+        "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==",
+          "requires": {
+            "inherits": "2.0.1",
+            "safe-buffer": "5.1.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=",
+          "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="
+        },
+        "create-ecdh": {
+          "version": "4.0.0",
+          "resolved": "https://registry.npmjs.org/create-ecdh/-/create-ecdh-4.0.0.tgz",
+          "integrity": "sha1-iIxyNZbN92EvZJgjPuvXo1MBc30=",
+          "requires": {
+            "bn.js": "4.11.8",
+            "elliptic": "6.4.0"
+          }
+        },
+        "create-hash": {
+          "version": "1.1.3",
+          "resolved": "https://registry.npmjs.org/create-hash/-/create-hash-1.1.3.tgz",
+          "integrity": "sha1-YGBCrIuSYnUPSDyt2rD1gZFy2P0=",
+          "requires": {
+            "cipher-base": "1.0.4",
+            "inherits": "2.0.1",
+            "ripemd160": "2.0.1",
+            "sha.js": "2.4.9"
+          }
+        },
+        "create-hmac": {
+          "version": "1.1.6",
+          "resolved": "https://registry.npmjs.org/create-hmac/-/create-hmac-1.1.6.tgz",
+          "integrity": "sha1-rLniIaThe9sHbpBlfEK5PjcmzwY=",
+          "requires": {
+            "cipher-base": "1.0.4",
+            "create-hash": "1.1.3",
+            "inherits": "2.0.1",
+            "ripemd160": "2.0.1",
+            "safe-buffer": "5.1.1",
+            "sha.js": "2.4.9"
+          }
+        },
+        "crypto-browserify": {
+          "version": "3.11.1",
+          "resolved": "https://registry.npmjs.org/crypto-browserify/-/crypto-browserify-3.11.1.tgz",
+          "integrity": "sha512-Na7ZlwCOqoaW5RwUK1WpXws2kv8mNhWdTlzob0UXulk6G9BDbyiJaGTYBIX61Ozn9l1EPPJpICZb4DaOpT9NlQ==",
+          "requires": {
+            "browserify-cipher": "1.0.0",
+            "browserify-sign": "4.0.4",
+            "create-ecdh": "4.0.0",
+            "create-hash": "1.1.3",
+            "create-hmac": "1.1.6",
+            "diffie-hellman": "5.0.2",
+            "inherits": "2.0.1",
+            "pbkdf2": "3.0.14",
+            "public-encrypt": "4.0.0",
+            "randombytes": "2.0.5"
+          }
+        },
+        "date-now": {
+          "version": "0.1.4",
+          "resolved": "https://registry.npmjs.org/date-now/-/date-now-0.1.4.tgz",
+          "integrity": "sha1-6vQ5/U1ISK105cx9vvIAZyueNFs="
+        },
+        "des.js": {
+          "version": "1.0.0",
+          "resolved": "https://registry.npmjs.org/des.js/-/des.js-1.0.0.tgz",
+          "integrity": "sha1-wHTS4qpqipoH29YfmhXCzYPsjsw=",
+          "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=",
+          "requires": {
+            "bn.js": "4.11.8",
+            "miller-rabin": "4.0.1",
+            "randombytes": "2.0.5"
+          }
+        },
+        "domain-browser": {
+          "version": "1.1.7",
+          "resolved": "https://registry.npmjs.org/domain-browser/-/domain-browser-1.1.7.tgz",
+          "integrity": "sha1-hnqksJP6oF8d4IwG9NeyH9+GmLw="
+        },
+        "elliptic": {
+          "version": "6.4.0",
+          "resolved": "https://registry.npmjs.org/elliptic/-/elliptic-6.4.0.tgz",
+          "integrity": "sha1-ysmvh2LIWDYYcAPI3+GT5eLq5d8=",
+          "requires": {
+            "bn.js": "4.11.8",
+            "brorand": "1.1.0",
+            "hash.js": "1.1.3",
+            "hmac-drbg": "1.0.1",
+            "inherits": "2.0.1",
+            "minimalistic-assert": "1.0.0",
+            "minimalistic-crypto-utils": "1.0.1"
+          }
+        },
+        "events": {
+          "version": "1.1.1",
+          "resolved": "https://registry.npmjs.org/events/-/events-1.1.1.tgz",
+          "integrity": "sha1-nr23Y1rQmccNzEwqH1AEKI6L2SQ="
+        },
+        "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==",
+          "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.6",
+            "inherits": "2.0.1",
+            "minimatch": "3.0.4",
+            "once": "1.4.0",
+            "path-is-absolute": "1.0.1"
+          }
+        },
+        "hash-base": {
+          "version": "2.0.2",
+          "resolved": "https://registry.npmjs.org/hash-base/-/hash-base-2.0.2.tgz",
+          "integrity": "sha1-ZuodhW206KVHDK32/OI65SRO8uE=",
+          "requires": {
+            "inherits": "2.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==",
+          "requires": {
+            "inherits": "2.0.3",
+            "minimalistic-assert": "1.0.0"
+          },
+          "dependencies": {
+            "inherits": {
+              "version": "2.0.3",
+              "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz",
+              "integrity": "sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4="
+            }
+          }
+        },
+        "hmac-drbg": {
+          "version": "1.0.1",
+          "resolved": "https://registry.npmjs.org/hmac-drbg/-/hmac-drbg-1.0.1.tgz",
+          "integrity": "sha1-0nRXAQJabHdabFRXk+1QL8DGSaE=",
+          "requires": {
+            "hash.js": "1.1.3",
+            "minimalistic-assert": "1.0.0",
+            "minimalistic-crypto-utils": "1.0.1"
+          }
+        },
+        "http-browserify": {
+          "version": "1.7.0",
+          "resolved": "https://registry.npmjs.org/http-browserify/-/http-browserify-1.7.0.tgz",
+          "integrity": "sha1-M3la3nLfiKz7/TZ3PO/tp2RzWyA=",
+          "requires": {
+            "Base64": "0.2.1",
+            "inherits": "2.0.1"
+          }
+        },
+        "https-browserify": {
+          "version": "0.0.1",
+          "resolved": "https://registry.npmjs.org/https-browserify/-/https-browserify-0.0.1.tgz",
+          "integrity": "sha1-P5E2XKvmC3ftDruiS0VOPgnZWoI="
+        },
+        "ieee754": {
+          "version": "1.1.8",
+          "resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.1.8.tgz",
+          "integrity": "sha1-vjPUCsEO8ZJnAfbwii2G+/0a0+Q="
+        },
+        "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.4.0",
+            "wrappy": "1.0.2"
+          }
+        },
+        "inherits": {
+          "version": "2.0.1",
+          "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.1.tgz",
+          "integrity": "sha1-sX0I0ya0Qj5Wjv9xn5GwscvfafE="
+        },
+        "isarray": {
+          "version": "1.0.0",
+          "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz",
+          "integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE="
+        },
+        "md5.js": {
+          "version": "1.3.4",
+          "resolved": "https://registry.npmjs.org/md5.js/-/md5.js-1.3.4.tgz",
+          "integrity": "sha1-6b296UogpawYsENA/Fdk1bCdkB0=",
+          "requires": {
+            "hash-base": "3.0.4",
+            "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.1.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==",
+          "requires": {
+            "bn.js": "4.11.8",
+            "brorand": "1.1.0"
+          }
+        },
+        "minimalistic-assert": {
+          "version": "1.0.0",
+          "resolved": "https://registry.npmjs.org/minimalistic-assert/-/minimalistic-assert-1.0.0.tgz",
+          "integrity": "sha1-cCvi3aazf0g2vLP121ZkG2Sh09M="
+        },
+        "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.8"
+          }
+        },
+        "once": {
+          "version": "1.4.0",
+          "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz",
+          "integrity": "sha1-WDsap3WWHUsROsF9nFC6753Xa9E=",
+          "requires": {
+            "wrappy": "1.0.2"
+          }
+        },
+        "os-browserify": {
+          "version": "0.2.1",
+          "resolved": "https://registry.npmjs.org/os-browserify/-/os-browserify-0.2.1.tgz",
+          "integrity": "sha1-Y/xMzuXS13Y9Jrv4YBB45sLgBE8="
+        },
+        "pako": {
+          "version": "0.2.9",
+          "resolved": "https://registry.npmjs.org/pako/-/pako-0.2.9.tgz",
+          "integrity": "sha1-8/dSL073gjSNqBYbrZ7P1Rv4OnU="
+        },
+        "parse-asn1": {
+          "version": "5.1.0",
+          "resolved": "https://registry.npmjs.org/parse-asn1/-/parse-asn1-5.1.0.tgz",
+          "integrity": "sha1-N8T5t+06tlx0gXtfJICTf7+XxxI=",
+          "requires": {
+            "asn1.js": "4.9.1",
+            "browserify-aes": "1.1.0",
+            "create-hash": "1.1.3",
+            "evp_bytestokey": "1.0.3",
+            "pbkdf2": "3.0.14"
+          }
+        },
+        "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="
+        },
+        "pbkdf2": {
+          "version": "3.0.14",
+          "resolved": "https://registry.npmjs.org/pbkdf2/-/pbkdf2-3.0.14.tgz",
+          "integrity": "sha512-gjsZW9O34fm0R7PaLHRJmLLVfSoesxztjPjE9o6R+qtVJij90ltg1joIovN9GKrRW3t1PzhDDG3UMEMFfZ+1wA==",
+          "requires": {
+            "create-hash": "1.1.3",
+            "create-hmac": "1.1.6",
+            "ripemd160": "2.0.1",
+            "safe-buffer": "5.1.1",
+            "sha.js": "2.4.9"
+          }
+        },
+        "process": {
+          "version": "0.11.10",
+          "resolved": "https://registry.npmjs.org/process/-/process-0.11.10.tgz",
+          "integrity": "sha1-czIwDoQBYb2j5podHZGn1LwW8YI="
+        },
+        "process-nextick-args": {
+          "version": "1.0.7",
+          "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-1.0.7.tgz",
+          "integrity": "sha1-FQ4gt1ZZCtP5EJPyWk8q2L/zC6M="
+        },
+        "public-encrypt": {
+          "version": "4.0.0",
+          "resolved": "https://registry.npmjs.org/public-encrypt/-/public-encrypt-4.0.0.tgz",
+          "integrity": "sha1-OfaZ86RlYN1eusvKaTyvfGXBjMY=",
+          "requires": {
+            "bn.js": "4.11.8",
+            "browserify-rsa": "4.0.1",
+            "create-hash": "1.1.3",
+            "parse-asn1": "5.1.0",
+            "randombytes": "2.0.5"
+          }
+        },
+        "punycode": {
+          "version": "1.4.1",
+          "resolved": "https://registry.npmjs.org/punycode/-/punycode-1.4.1.tgz",
+          "integrity": "sha1-wNWmOycYgArY4esPpSachN1BhF4="
+        },
+        "querystring": {
+          "version": "0.2.0",
+          "resolved": "https://registry.npmjs.org/querystring/-/querystring-0.2.0.tgz",
+          "integrity": "sha1-sgmEkgO7Jd+CDadW50cAWHhSFiA="
+        },
+        "querystring-es3": {
+          "version": "0.2.1",
+          "resolved": "https://registry.npmjs.org/querystring-es3/-/querystring-es3-0.2.1.tgz",
+          "integrity": "sha1-nsYfeQSYdXB9aUFFlv2Qek1xHnM="
+        },
+        "randombytes": {
+          "version": "2.0.5",
+          "resolved": "https://registry.npmjs.org/randombytes/-/randombytes-2.0.5.tgz",
+          "integrity": "sha512-8T7Zn1AhMsQ/HI1SjcCfT/t4ii3eAqco3yOcSzS4mozsOz69lHLsoMXmF9nZgnFanYscnSlUSgs8uZyKzpE6kg==",
+          "requires": {
+            "safe-buffer": "5.1.1"
+          }
+        },
+        "readable-stream": {
+          "version": "git+https://github.com/meteor/readable-stream.git#d64a64aa6061b9b6855feff4d09e58fb3b2e4502",
+          "requires": {
+            "inherits": "2.0.3",
+            "isarray": "1.0.0",
+            "process-nextick-args": "1.0.7",
+            "safe-buffer": "5.1.1",
+            "string_decoder": "1.0.3",
+            "util-deprecate": "1.0.2"
+          },
+          "dependencies": {
+            "inherits": {
+              "version": "2.0.3",
+              "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz",
+              "integrity": "sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4="
+            }
+          }
+        },
+        "rimraf": {
+          "version": "2.6.2",
+          "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.6.2.tgz",
+          "integrity": "sha512-lreewLK/BlghmxtfH36YYVg1i8IAce4TI7oao75I1g245+6BctqTVQiBP3YUJ9C6DQOXJmkYR9X9fCLtCOJc5w==",
+          "requires": {
+            "glob": "7.1.2"
+          }
+        },
+        "ripemd160": {
+          "version": "2.0.1",
+          "resolved": "https://registry.npmjs.org/ripemd160/-/ripemd160-2.0.1.tgz",
+          "integrity": "sha1-D0WEKVxTo2KK9+bXmsohzlfRxuc=",
+          "requires": {
+            "hash-base": "2.0.2",
+            "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=="
+        },
+        "sha.js": {
+          "version": "2.4.9",
+          "resolved": "https://registry.npmjs.org/sha.js/-/sha.js-2.4.9.tgz",
+          "integrity": "sha512-G8zektVqbiPHrylgew9Zg1VRB1L/DtXNUVAM6q4QLy8NE3qtHlFXTf8VLL4k1Yl6c7NMjtZUTdXV+X44nFaT6A==",
+          "requires": {
+            "inherits": "2.0.1",
+            "safe-buffer": "5.1.1"
+          }
+        },
+        "stream-browserify": {
+          "version": "2.0.1",
+          "resolved": "https://registry.npmjs.org/stream-browserify/-/stream-browserify-2.0.1.tgz",
+          "integrity": "sha1-ZiZu5fm9uZQKTkUUyvtDu3Hlyds=",
+          "requires": {
+            "inherits": "2.0.1",
+            "readable-stream": "git+https://github.com/meteor/readable-stream.git#d64a64aa6061b9b6855feff4d09e58fb3b2e4502"
+          }
+        },
+        "string_decoder": {
+          "version": "1.0.3",
+          "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.0.3.tgz",
+          "integrity": "sha512-4AH6Z5fzNNBcH+6XDMfA/BTt87skxqJlO0lAh3Dker5zThcAxG6mKz+iGu308UKoPPQ8Dcqx/4JhujzltRa+hQ==",
+          "requires": {
+            "safe-buffer": "5.1.1"
+          }
+        },
+        "timers-browserify": {
+          "version": "1.4.2",
+          "resolved": "https://registry.npmjs.org/timers-browserify/-/timers-browserify-1.4.2.tgz",
+          "integrity": "sha1-ycWLV1voQHN1y14kYtrO50NZ9B0=",
+          "requires": {
+            "process": "0.11.10"
+          }
+        },
+        "tty-browserify": {
+          "version": "0.0.0",
+          "resolved": "https://registry.npmjs.org/tty-browserify/-/tty-browserify-0.0.0.tgz",
+          "integrity": "sha1-oVe6QC2iTpv5V/mqadUk7tQpAaY="
+        },
+        "url": {
+          "version": "0.11.0",
+          "resolved": "https://registry.npmjs.org/url/-/url-0.11.0.tgz",
+          "integrity": "sha1-ODjpfPxgUh63PFJajlW/3Z4uKPE=",
+          "requires": {
+            "punycode": "1.3.2",
+            "querystring": "0.2.0"
+          },
+          "dependencies": {
+            "punycode": {
+              "version": "1.3.2",
+              "resolved": "https://registry.npmjs.org/punycode/-/punycode-1.3.2.tgz",
+              "integrity": "sha1-llOgNvt8HuQjQvIyXM7v6jkmxI0="
+            }
+          }
+        },
+        "util": {
+          "version": "0.10.3",
+          "resolved": "https://registry.npmjs.org/util/-/util-0.10.3.tgz",
+          "integrity": "sha1-evsa/lCAUkZInj23/g7TeTNqwPk=",
+          "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="
+        },
+        "vm-browserify": {
+          "version": "0.0.4",
+          "resolved": "https://registry.npmjs.org/vm-browserify/-/vm-browserify-0.0.4.tgz",
+          "integrity": "sha1-XX6kW7755Kb/ZflUOOCofDV9WnM=",
+          "requires": {
+            "indexof": "0.0.1"
+          }
+        },
+        "wrappy": {
+          "version": "1.0.2",
+          "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz",
+          "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8="
+        }
+      }
+    },
+    "node-sass": {
+      "version": "4.5.3",
+      "resolved": "https://registry.npmjs.org/node-sass/-/node-sass-4.5.3.tgz",
+      "integrity": "sha1-0JydEXlkEjnRuX/8YjH9zsU+FWg=",
+      "requires": {
+        "async-foreach": "0.1.3",
+        "chalk": "1.1.3",
+        "cross-spawn": "3.0.1",
+        "gaze": "1.1.2",
+        "get-stdin": "4.0.1",
+        "glob": "7.1.2",
+        "in-publish": "2.0.0",
+        "lodash.assign": "4.2.0",
+        "lodash.clonedeep": "4.5.0",
+        "lodash.mergewith": "4.6.0",
+        "meow": "3.7.0",
+        "mkdirp": "0.5.1",
+        "nan": "2.8.0",
+        "node-gyp": "3.6.2",
+        "npmlog": "4.1.2",
+        "request": "2.83.0",
+        "sass-graph": "2.2.4",
+        "stdout-stream": "1.4.0"
+      },
+      "dependencies": {
+        "abbrev": {
+          "version": "1.1.1",
+          "resolved": "https://registry.npmjs.org/abbrev/-/abbrev-1.1.1.tgz",
+          "integrity": "sha512-nne9/IiQ/hzIhY6pdDnbBtz7DjPTKrY00P/zvPSm5pOFkl6xuGrGnXn/VtTNNfNtAfZ9/1RtehkszU9qcTii0Q=="
+        },
+        "ajv": {
+          "version": "5.5.2",
+          "resolved": "https://registry.npmjs.org/ajv/-/ajv-5.5.2.tgz",
+          "integrity": "sha1-c7Xuyj+rZT49P5Qis0GtQiBdyWU=",
+          "requires": {
+            "co": "4.6.0",
+            "fast-deep-equal": "1.0.0",
+            "fast-json-stable-stringify": "2.0.0",
+            "json-schema-traverse": "0.3.1"
+          }
+        },
+        "amdefine": {
+          "version": "1.0.1",
+          "resolved": "https://registry.npmjs.org/amdefine/-/amdefine-1.0.1.tgz",
+          "integrity": "sha1-SlKCrBZHKek2Gbz9OtFR+BfOkfU="
+        },
+        "ansi-regex": {
+          "version": "2.1.1",
+          "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-2.1.1.tgz",
+          "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="
+        },
+        "aproba": {
+          "version": "1.2.0",
+          "resolved": "https://registry.npmjs.org/aproba/-/aproba-1.2.0.tgz",
+          "integrity": "sha512-Y9J6ZjXtoYh8RnXVCMOU/ttDmk1aBjunq9vO0ta5x85WDQiQfUF9sIPBITdbiiIVcBo03Hi3jMxigBtsddlXRw=="
+        },
+        "are-we-there-yet": {
+          "version": "1.1.4",
+          "resolved": "https://registry.npmjs.org/are-we-there-yet/-/are-we-there-yet-1.1.4.tgz",
+          "integrity": "sha1-u13KOCu5TwXhUZQ3PRb9O6HKEQ0=",
+          "requires": {
+            "delegates": "1.0.0",
+            "readable-stream": "2.3.3"
+          }
+        },
+        "array-find-index": {
+          "version": "1.0.2",
+          "resolved": "https://registry.npmjs.org/array-find-index/-/array-find-index-1.0.2.tgz",
+          "integrity": "sha1-3wEKoSh+Fku9pvlyOwqWoexBh6E="
+        },
+        "asn1": {
+          "version": "0.2.3",
+          "resolved": "https://registry.npmjs.org/asn1/-/asn1-0.2.3.tgz",
+          "integrity": "sha1-2sh4dxPJlmhJ/IGAd36+nB3fO4Y="
+        },
+        "assert-plus": {
+          "version": "1.0.0",
+          "resolved": "https://registry.npmjs.org/assert-plus/-/assert-plus-1.0.0.tgz",
+          "integrity": "sha1-8S4PPF13sLHN2RRpQuTpbB5N1SU="
+        },
+        "async-foreach": {
+          "version": "0.1.3",
+          "resolved": "https://registry.npmjs.org/async-foreach/-/async-foreach-0.1.3.tgz",
+          "integrity": "sha1-NhIfhFwFeBct5Bmpfb6x0W7DRUI="
+        },
+        "asynckit": {
+          "version": "0.4.0",
+          "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz",
+          "integrity": "sha1-x57Zf380y48robyXkLzDZkdLS3k="
+        },
+        "aws-sign2": {
+          "version": "0.7.0",
+          "resolved": "https://registry.npmjs.org/aws-sign2/-/aws-sign2-0.7.0.tgz",
+          "integrity": "sha1-tG6JCTSpWR8tL2+G1+ap8bP+dqg="
+        },
+        "aws4": {
+          "version": "1.6.0",
+          "resolved": "https://registry.npmjs.org/aws4/-/aws4-1.6.0.tgz",
+          "integrity": "sha1-g+9cqGCysy5KDe7e6MdxudtXRx4="
+        },
+        "balanced-match": {
+          "version": "1.0.0",
+          "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.0.tgz",
+          "integrity": "sha1-ibTRmasr7kneFk6gK4nORi1xt2c="
+        },
+        "bcrypt-pbkdf": {
+          "version": "1.0.1",
+          "resolved": "https://registry.npmjs.org/bcrypt-pbkdf/-/bcrypt-pbkdf-1.0.1.tgz",
+          "integrity": "sha1-Y7xdy2EzG5K8Bf1SiVPDNGKgb40=",
+          "optional": true,
+          "requires": {
+            "tweetnacl": "0.14.5"
+          }
+        },
+        "boom": {
+          "version": "4.3.1",
+          "resolved": "https://registry.npmjs.org/boom/-/boom-4.3.1.tgz",
+          "integrity": "sha1-T4owBctKfjiJ90kDD9JbluAdLjE=",
+          "requires": {
+            "hoek": "4.2.0"
+          }
+        },
+        "brace-expansion": {
+          "version": "1.1.8",
+          "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.8.tgz",
+          "integrity": "sha1-wHshHHyVLsH479Uad+8NHTmQopI=",
+          "requires": {
+            "balanced-match": "1.0.0",
+            "concat-map": "0.0.1"
+          }
+        },
+        "builtin-modules": {
+          "version": "1.1.1",
+          "resolved": "https://registry.npmjs.org/builtin-modules/-/builtin-modules-1.1.1.tgz",
+          "integrity": "sha1-Jw8HbFpywC9bZaR9+Uxf46J4iS8="
+        },
+        "camelcase": {
+          "version": "2.1.1",
+          "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-2.1.1.tgz",
+          "integrity": "sha1-fB0W1nmhu+WcoCys7PsBHiAfWh8="
+        },
+        "camelcase-keys": {
+          "version": "2.1.0",
+          "resolved": "https://registry.npmjs.org/camelcase-keys/-/camelcase-keys-2.1.0.tgz",
+          "integrity": "sha1-MIvur/3ygRkFHvodkyITyRuPkuc=",
+          "requires": {
+            "camelcase": "2.1.1",
+            "map-obj": "1.0.1"
+          }
+        },
+        "caseless": {
+          "version": "0.12.0",
+          "resolved": "https://registry.npmjs.org/caseless/-/caseless-0.12.0.tgz",
+          "integrity": "sha1-G2gcIf+EAzyCZUMJBolCDRhxUdw="
+        },
+        "chalk": {
+          "version": "1.1.3",
+          "resolved": "https://registry.npmjs.org/chalk/-/chalk-1.1.3.tgz",
+          "integrity": "sha1-qBFcVeSnAv5NFQq9OHKCKn4J/Jg=",
+          "requires": {
+            "ansi-styles": "2.2.1",
+            "escape-string-regexp": "1.0.5",
+            "has-ansi": "2.0.0",
+            "strip-ansi": "3.0.1",
+            "supports-color": "2.0.0"
+          }
+        },
+        "cliui": {
+          "version": "3.2.0",
+          "resolved": "https://registry.npmjs.org/cliui/-/cliui-3.2.0.tgz",
+          "integrity": "sha1-EgYBU3qRbSmUD5NNo7SNWFo5IT0=",
+          "requires": {
+            "string-width": "1.0.2",
+            "strip-ansi": "3.0.1",
+            "wrap-ansi": "2.1.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="
+        },
+        "combined-stream": {
+          "version": "1.0.5",
+          "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.5.tgz",
+          "integrity": "sha1-k4NwpXtKUd6ix3wV1cX9+JUWQAk=",
+          "requires": {
+            "delayed-stream": "1.0.0"
+          }
+        },
+        "concat-map": {
+          "version": "0.0.1",
+          "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz",
+          "integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s="
+        },
+        "console-control-strings": {
+          "version": "1.1.0",
+          "resolved": "https://registry.npmjs.org/console-control-strings/-/console-control-strings-1.1.0.tgz",
+          "integrity": "sha1-PXz0Rk22RG6mRL9LOVB/mFEAjo4="
+        },
+        "core-util-is": {
+          "version": "1.0.2",
+          "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.2.tgz",
+          "integrity": "sha1-tf1UIgqivFq1eqtxQMlAdUUDwac="
+        },
+        "cross-spawn": {
+          "version": "3.0.1",
+          "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-3.0.1.tgz",
+          "integrity": "sha1-ElYDfsufDF9549bvE14wdwGEuYI=",
+          "requires": {
+            "lru-cache": "4.1.1",
+            "which": "1.3.0"
+          }
+        },
+        "cryptiles": {
+          "version": "3.1.2",
+          "resolved": "https://registry.npmjs.org/cryptiles/-/cryptiles-3.1.2.tgz",
+          "integrity": "sha1-qJ+7Ig9c4l7FboxKqKT9e1sNKf4=",
+          "requires": {
+            "boom": "5.2.0"
+          },
+          "dependencies": {
+            "boom": {
+              "version": "5.2.0",
+              "resolved": "https://registry.npmjs.org/boom/-/boom-5.2.0.tgz",
+              "integrity": "sha512-Z5BTk6ZRe4tXXQlkqftmsAUANpXmuwlsF5Oov8ThoMbQRzdGTA1ngYRW160GexgOgjsFOKJz0LYhoNi+2AMBUw==",
+              "requires": {
+                "hoek": "4.2.0"
+              }
+            }
+          }
+        },
+        "currently-unhandled": {
+          "version": "0.4.1",
+          "resolved": "https://registry.npmjs.org/currently-unhandled/-/currently-unhandled-0.4.1.tgz",
+          "integrity": "sha1-mI3zP+qxke95mmE2nddsF635V+o=",
+          "requires": {
+            "array-find-index": "1.0.2"
+          }
+        },
+        "dashdash": {
+          "version": "1.14.1",
+          "resolved": "https://registry.npmjs.org/dashdash/-/dashdash-1.14.1.tgz",
+          "integrity": "sha1-hTz6D3y+L+1d4gMmuN1YEDX24vA=",
+          "requires": {
+            "assert-plus": "1.0.0"
+          }
+        },
+        "decamelize": {
+          "version": "1.2.0",
+          "resolved": "https://registry.npmjs.org/decamelize/-/decamelize-1.2.0.tgz",
+          "integrity": "sha1-9lNNFRSCabIDUue+4m9QH5oZEpA="
+        },
+        "delayed-stream": {
+          "version": "1.0.0",
+          "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz",
+          "integrity": "sha1-3zrhmayt+31ECqrgsp4icrJOxhk="
+        },
+        "delegates": {
+          "version": "1.0.0",
+          "resolved": "https://registry.npmjs.org/delegates/-/delegates-1.0.0.tgz",
+          "integrity": "sha1-hMbhWbgZBP3KWaDvRM2HDTElD5o="
+        },
+        "ecc-jsbn": {
+          "version": "0.1.1",
+          "resolved": "https://registry.npmjs.org/ecc-jsbn/-/ecc-jsbn-0.1.1.tgz",
+          "integrity": "sha1-D8c6ntXw1Tw4GTOYUj735UN3dQU=",
+          "optional": true,
+          "requires": {
+            "jsbn": "0.1.1"
+          }
+        },
+        "error-ex": {
+          "version": "1.3.1",
+          "resolved": "https://registry.npmjs.org/error-ex/-/error-ex-1.3.1.tgz",
+          "integrity": "sha1-+FWobOYa3E6GIcPNoh56dhLDqNw=",
+          "requires": {
+            "is-arrayish": "0.2.1"
+          }
+        },
+        "escape-string-regexp": {
+          "version": "1.0.5",
+          "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz",
+          "integrity": "sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ="
+        },
+        "extend": {
+          "version": "3.0.1",
+          "resolved": "https://registry.npmjs.org/extend/-/extend-3.0.1.tgz",
+          "integrity": "sha1-p1Xqe8Gt/MWjHOfnYtuq3F5jZEQ="
+        },
+        "extsprintf": {
+          "version": "1.3.0",
+          "resolved": "https://registry.npmjs.org/extsprintf/-/extsprintf-1.3.0.tgz",
+          "integrity": "sha1-lpGEQOMEGnpBT4xS48V06zw+HgU="
+        },
+        "fast-deep-equal": {
+          "version": "1.0.0",
+          "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-1.0.0.tgz",
+          "integrity": "sha1-liVqO8l1WV6zbYLpkp0GDYk0Of8="
+        },
+        "fast-json-stable-stringify": {
+          "version": "2.0.0",
+          "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.0.0.tgz",
+          "integrity": "sha1-1RQsDK7msRifh9OnYREGT4bIu/I="
+        },
+        "find-up": {
+          "version": "1.1.2",
+          "resolved": "https://registry.npmjs.org/find-up/-/find-up-1.1.2.tgz",
+          "integrity": "sha1-ay6YIrGizgpgq2TWEOzK1TyyTQ8=",
+          "requires": {
+            "path-exists": "2.1.0",
+            "pinkie-promise": "2.0.1"
+          }
+        },
+        "forever-agent": {
+          "version": "0.6.1",
+          "resolved": "https://registry.npmjs.org/forever-agent/-/forever-agent-0.6.1.tgz",
+          "integrity": "sha1-+8cfDEGt6zf5bFd60e1C2P2sypE="
+        },
+        "form-data": {
+          "version": "2.3.1",
+          "resolved": "https://registry.npmjs.org/form-data/-/form-data-2.3.1.tgz",
+          "integrity": "sha1-b7lPvXGIUwbXPRXMSX/kzE7NRL8=",
+          "requires": {
+            "asynckit": "0.4.0",
+            "combined-stream": "1.0.5",
+            "mime-types": "2.1.17"
+          }
+        },
+        "fs.realpath": {
+          "version": "1.0.0",
+          "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz",
+          "integrity": "sha1-FQStJSMVjKpA20onh8sBQRmU6k8="
+        },
+        "fstream": {
+          "version": "1.0.11",
+          "resolved": "https://registry.npmjs.org/fstream/-/fstream-1.0.11.tgz",
+          "integrity": "sha1-XB+x8RdHcRTwYyoOtLcbPLD9MXE=",
+          "requires": {
+            "graceful-fs": "4.1.11",
+            "inherits": "2.0.3",
+            "mkdirp": "0.5.1",
+            "rimraf": "2.6.2"
+          }
+        },
+        "gauge": {
+          "version": "2.7.4",
+          "resolved": "https://registry.npmjs.org/gauge/-/gauge-2.7.4.tgz",
+          "integrity": "sha1-LANAXHU4w51+s3sxcCLjJfsBi/c=",
+          "requires": {
+            "aproba": "1.2.0",
+            "console-control-strings": "1.1.0",
+            "has-unicode": "2.0.1",
+            "object-assign": "4.1.1",
+            "signal-exit": "3.0.2",
+            "string-width": "1.0.2",
+            "strip-ansi": "3.0.1",
+            "wide-align": "1.1.2"
+          }
+        },
+        "gaze": {
+          "version": "1.1.2",
+          "resolved": "https://registry.npmjs.org/gaze/-/gaze-1.1.2.tgz",
+          "integrity": "sha1-hHIkZ3rbiHDWeSV+0ziP22HkAQU=",
+          "requires": {
+            "globule": "1.2.0"
+          }
+        },
+        "get-caller-file": {
+          "version": "1.0.2",
+          "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-1.0.2.tgz",
+          "integrity": "sha1-9wLmMSfn4jHBYKgMFVSstw1QR+U="
+        },
+        "get-stdin": {
+          "version": "4.0.1",
+          "resolved": "https://registry.npmjs.org/get-stdin/-/get-stdin-4.0.1.tgz",
+          "integrity": "sha1-uWjGsKBDhDJJAui/Gl3zJXmkUP4="
+        },
+        "getpass": {
+          "version": "0.1.7",
+          "resolved": "https://registry.npmjs.org/getpass/-/getpass-0.1.7.tgz",
+          "integrity": "sha1-Xv+OPmhNVprkyysSgmBOi6YhSfo=",
+          "requires": {
+            "assert-plus": "1.0.0"
+          }
+        },
+        "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.6",
+            "inherits": "2.0.3",
+            "minimatch": "3.0.4",
+            "once": "1.4.0",
+            "path-is-absolute": "1.0.1"
+          }
+        },
+        "globule": {
+          "version": "1.2.0",
+          "resolved": "https://registry.npmjs.org/globule/-/globule-1.2.0.tgz",
+          "integrity": "sha1-HcScaCLdnoovoAuiopUAboZkvQk=",
+          "requires": {
+            "glob": "7.1.2",
+            "lodash": "4.17.4",
+            "minimatch": "3.0.4"
+          }
+        },
+        "graceful-fs": {
+          "version": "4.1.11",
+          "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.1.11.tgz",
+          "integrity": "sha1-Dovf5NHduIVNZOBOp8AOKgJuVlg="
+        },
+        "har-schema": {
+          "version": "2.0.0",
+          "resolved": "https://registry.npmjs.org/har-schema/-/har-schema-2.0.0.tgz",
+          "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=",
+          "requires": {
+            "ajv": "5.5.2",
+            "har-schema": "2.0.0"
+          }
+        },
+        "has-ansi": {
+          "version": "2.0.0",
+          "resolved": "https://registry.npmjs.org/has-ansi/-/has-ansi-2.0.0.tgz",
+          "integrity": "sha1-NPUEnOHs3ysGSa8+8k5F7TVBbZE=",
+          "requires": {
+            "ansi-regex": "2.1.1"
+          }
+        },
+        "has-unicode": {
+          "version": "2.0.1",
+          "resolved": "https://registry.npmjs.org/has-unicode/-/has-unicode-2.0.1.tgz",
+          "integrity": "sha1-4Ob+aijPUROIVeCG0Wkedx3iqLk="
+        },
+        "hawk": {
+          "version": "6.0.2",
+          "resolved": "https://registry.npmjs.org/hawk/-/hawk-6.0.2.tgz",
+          "integrity": "sha512-miowhl2+U7Qle4vdLqDdPt9m09K6yZhkLDTWGoUiUzrQCn+mHHSmfJgAyGaLRZbPmTqfFFjRV1QWCW0VWUJBbQ==",
+          "requires": {
+            "boom": "4.3.1",
+            "cryptiles": "3.1.2",
+            "hoek": "4.2.0",
+            "sntp": "2.1.0"
+          }
+        },
+        "hoek": {
+          "version": "4.2.0",
+          "resolved": "https://registry.npmjs.org/hoek/-/hoek-4.2.0.tgz",
+          "integrity": "sha512-v0XCLxICi9nPfYrS9RL8HbYnXi9obYAeLbSP00BmnZwCK9+Ih9WOjoZ8YoHCoav2csqn4FOz4Orldsy2dmDwmQ=="
+        },
+        "hosted-git-info": {
+          "version": "2.5.0",
+          "resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-2.5.0.tgz",
+          "integrity": "sha512-pNgbURSuab90KbTqvRPsseaTxOJCZBD0a7t+haSN33piP9cCM4l0CqdzAif2hUqm716UovKB2ROmiabGAKVXyg=="
+        },
+        "http-signature": {
+          "version": "1.2.0",
+          "resolved": "https://registry.npmjs.org/http-signature/-/http-signature-1.2.0.tgz",
+          "integrity": "sha1-muzZJRFHcvPZW2WmCruPfBj7rOE=",
+          "requires": {
+            "assert-plus": "1.0.0",
+            "jsprim": "1.4.1",
+            "sshpk": "1.13.1"
+          }
+        },
+        "in-publish": {
+          "version": "2.0.0",
+          "resolved": "https://registry.npmjs.org/in-publish/-/in-publish-2.0.0.tgz",
+          "integrity": "sha1-4g/146KvwmkDILbcVSaCqcf631E="
+        },
+        "indent-string": {
+          "version": "2.1.0",
+          "resolved": "https://registry.npmjs.org/indent-string/-/indent-string-2.1.0.tgz",
+          "integrity": "sha1-ji1INIdCEhtKghi3oTfppSBJ3IA=",
+          "requires": {
+            "repeating": "2.0.1"
+          }
+        },
+        "inflight": {
+          "version": "1.0.6",
+          "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz",
+          "integrity": "sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk=",
+          "requires": {
+            "once": "1.4.0",
+            "wrappy": "1.0.2"
+          }
+        },
+        "inherits": {
+          "version": "2.0.3",
+          "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz",
+          "integrity": "sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4="
+        },
+        "invert-kv": {
+          "version": "1.0.0",
+          "resolved": "https://registry.npmjs.org/invert-kv/-/invert-kv-1.0.0.tgz",
+          "integrity": "sha1-EEqOSqym09jNFXqO+L+rLXo//bY="
+        },
+        "is-arrayish": {
+          "version": "0.2.1",
+          "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.2.1.tgz",
+          "integrity": "sha1-d8mYQFJ6qOyxqLppe4BkWnqSap0="
+        },
+        "is-builtin-module": {
+          "version": "1.0.0",
+          "resolved": "https://registry.npmjs.org/is-builtin-module/-/is-builtin-module-1.0.0.tgz",
+          "integrity": "sha1-VAVy0096wxGfj3bDDLwbHgN6/74=",
+          "requires": {
+            "builtin-modules": "1.1.1"
+          }
+        },
+        "is-finite": {
+          "version": "1.0.2",
+          "resolved": "https://registry.npmjs.org/is-finite/-/is-finite-1.0.2.tgz",
+          "integrity": "sha1-zGZ3aVYCvlUO8R6LSqYwU0K20Ko=",
+          "requires": {
+            "number-is-nan": "1.0.1"
+          }
+        },
+        "is-fullwidth-code-point": {
+          "version": "1.0.0",
+          "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-1.0.0.tgz",
+          "integrity": "sha1-754xOG8DGn8NZDr4L95QxFfvAMs=",
+          "requires": {
+            "number-is-nan": "1.0.1"
+          }
+        },
+        "is-typedarray": {
+          "version": "1.0.0",
+          "resolved": "https://registry.npmjs.org/is-typedarray/-/is-typedarray-1.0.0.tgz",
+          "integrity": "sha1-5HnICFjfDBsR3dppQPlgEfzaSpo="
+        },
+        "is-utf8": {
+          "version": "0.2.1",
+          "resolved": "https://registry.npmjs.org/is-utf8/-/is-utf8-0.2.1.tgz",
+          "integrity": "sha1-Sw2hRCEE0bM2NA6AeX6GXPOffXI="
+        },
+        "isarray": {
+          "version": "1.0.0",
+          "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz",
+          "integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE="
+        },
+        "isexe": {
+          "version": "2.0.0",
+          "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz",
+          "integrity": "sha1-6PvzdNxVb/iUehDcsFctYz8s+hA="
+        },
+        "isstream": {
+          "version": "0.1.2",
+          "resolved": "https://registry.npmjs.org/isstream/-/isstream-0.1.2.tgz",
+          "integrity": "sha1-R+Y/evVa+m+S4VAOaQ64uFKcCZo="
+        },
+        "js-base64": {
+          "version": "2.4.0",
+          "resolved": "https://registry.npmjs.org/js-base64/-/js-base64-2.4.0.tgz",
+          "integrity": "sha512-Wehd+7Pf9tFvGb+ydPm9TjYjV8X1YHOVyG8QyELZxEMqOhemVwGRmoG8iQ/soqI3n8v4xn59zaLxiCJiaaRzKA=="
+        },
+        "jsbn": {
+          "version": "0.1.1",
+          "resolved": "https://registry.npmjs.org/jsbn/-/jsbn-0.1.1.tgz",
+          "integrity": "sha1-peZUwuWi3rXyAdls77yoDA7y9RM=",
+          "optional": 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="
+        },
+        "json-stringify-safe": {
+          "version": "5.0.1",
+          "resolved": "https://registry.npmjs.org/json-stringify-safe/-/json-stringify-safe-5.0.1.tgz",
+          "integrity": "sha1-Epai1Y/UXxmg9s4B1lcB4sc1tus="
+        },
+        "jsprim": {
+          "version": "1.4.1",
+          "resolved": "https://registry.npmjs.org/jsprim/-/jsprim-1.4.1.tgz",
+          "integrity": "sha1-MT5mvB5cwG5Di8G3SZwuXFastqI=",
+          "requires": {
+            "assert-plus": "1.0.0",
+            "extsprintf": "1.3.0",
+            "json-schema": "0.2.3",
+            "verror": "1.10.0"
+          }
+        },
+        "lcid": {
+          "version": "1.0.0",
+          "resolved": "https://registry.npmjs.org/lcid/-/lcid-1.0.0.tgz",
+          "integrity": "sha1-MIrMr6C8SDo4Z7S28rlQYlHRuDU=",
+          "requires": {
+            "invert-kv": "1.0.0"
+          }
+        },
+        "load-json-file": {
+          "version": "1.1.0",
+          "resolved": "https://registry.npmjs.org/load-json-file/-/load-json-file-1.1.0.tgz",
+          "integrity": "sha1-lWkFcI1YtLq0wiYbBPWfMcmTdMA=",
+          "requires": {
+            "graceful-fs": "4.1.11",
+            "parse-json": "2.2.0",
+            "pify": "2.3.0",
+            "pinkie-promise": "2.0.1",
+            "strip-bom": "2.0.0"
+          }
+        },
+        "lodash.assign": {
+          "version": "4.2.0",
+          "resolved": "https://registry.npmjs.org/lodash.assign/-/lodash.assign-4.2.0.tgz",
+          "integrity": "sha1-DZnzzNem0mHRm9rrkkUAXShYCOc="
+        },
+        "lodash.clonedeep": {
+          "version": "4.5.0",
+          "resolved": "https://registry.npmjs.org/lodash.clonedeep/-/lodash.clonedeep-4.5.0.tgz",
+          "integrity": "sha1-4j8/nE+Pvd6HJSnBBxhXoIblzO8="
+        },
+        "lodash.mergewith": {
+          "version": "4.6.0",
+          "resolved": "https://registry.npmjs.org/lodash.mergewith/-/lodash.mergewith-4.6.0.tgz",
+          "integrity": "sha1-FQzwoWeR9ZA7iJHqsVRgknS96lU="
+        },
+        "loud-rejection": {
+          "version": "1.6.0",
+          "resolved": "https://registry.npmjs.org/loud-rejection/-/loud-rejection-1.6.0.tgz",
+          "integrity": "sha1-W0b4AUft7leIcPCG0Eghz5mOVR8=",
+          "requires": {
+            "currently-unhandled": "0.4.1",
+            "signal-exit": "3.0.2"
+          }
+        },
+        "lru-cache": {
+          "version": "4.1.1",
+          "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-4.1.1.tgz",
+          "integrity": "sha512-q4spe4KTfsAS1SUHLO0wz8Qiyf1+vMIAgpRYioFYDMNqKfHQbg+AVDH3i4fvpl71/P1L0dBl+fQi+P37UYf0ew==",
+          "requires": {
+            "pseudomap": "1.0.2",
+            "yallist": "2.1.2"
+          }
+        },
+        "map-obj": {
+          "version": "1.0.1",
+          "resolved": "https://registry.npmjs.org/map-obj/-/map-obj-1.0.1.tgz",
+          "integrity": "sha1-2TPOuSBdgr3PSIb2dCvcK03qFG0="
+        },
+        "meow": {
+          "version": "3.7.0",
+          "resolved": "https://registry.npmjs.org/meow/-/meow-3.7.0.tgz",
+          "integrity": "sha1-cstmi0JSKCkKu/qFaJJYcwioAfs=",
+          "requires": {
+            "camelcase-keys": "2.1.0",
+            "decamelize": "1.2.0",
+            "loud-rejection": "1.6.0",
+            "map-obj": "1.0.1",
+            "minimist": "1.2.0",
+            "normalize-package-data": "2.4.0",
+            "object-assign": "4.1.1",
+            "read-pkg-up": "1.0.1",
+            "redent": "1.0.0",
+            "trim-newlines": "1.0.0"
+          }
+        },
+        "mime-db": {
+          "version": "1.30.0",
+          "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.30.0.tgz",
+          "integrity": "sha1-dMZD2i3Z1qRTmZY0ZbJtXKfXHwE="
+        },
+        "mime-types": {
+          "version": "2.1.17",
+          "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.17.tgz",
+          "integrity": "sha1-Cdejk/A+mVp5+K+Fe3Cp4KsWVXo=",
+          "requires": {
+            "mime-db": "1.30.0"
+          }
+        },
+        "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.8"
+          }
+        },
+        "minimist": {
+          "version": "1.2.0",
+          "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.0.tgz",
+          "integrity": "sha1-o1AIsg9BOD7sH7kU9M1d95omQoQ="
+        },
+        "mkdirp": {
+          "version": "0.5.1",
+          "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.1.tgz",
+          "integrity": "sha1-MAV0OOrGz3+MR2fzhkjWaX11yQM=",
+          "requires": {
+            "minimist": "0.0.8"
+          },
+          "dependencies": {
+            "minimist": {
+              "version": "0.0.8",
+              "resolved": "https://registry.npmjs.org/minimist/-/minimist-0.0.8.tgz",
+              "integrity": "sha1-hX/Kv8M5fSYluCKCYuhqp6ARsF0="
+            }
+          }
+        },
+        "nan": {
+          "version": "2.8.0",
+          "resolved": "https://registry.npmjs.org/nan/-/nan-2.8.0.tgz",
+          "integrity": "sha1-7XFfP+neArV6XmJS2QqWZ14fCFo="
+        },
+        "node-gyp": {
+          "version": "3.6.2",
+          "resolved": "https://registry.npmjs.org/node-gyp/-/node-gyp-3.6.2.tgz",
+          "integrity": "sha1-m/vlRWIoYoSDjnUOrAUpWFP6HGA=",
+          "requires": {
+            "fstream": "1.0.11",
+            "glob": "7.1.2",
+            "graceful-fs": "4.1.11",
+            "minimatch": "3.0.4",
+            "mkdirp": "0.5.1",
+            "nopt": "3.0.6",
+            "npmlog": "4.1.2",
+            "osenv": "0.1.4",
+            "request": "2.83.0",
+            "rimraf": "2.6.2",
+            "semver": "5.3.0",
+            "tar": "2.2.1",
+            "which": "1.3.0"
+          },
+          "dependencies": {
+            "semver": {
+              "version": "5.3.0",
+              "resolved": "https://registry.npmjs.org/semver/-/semver-5.3.0.tgz",
+              "integrity": "sha1-myzl094C0XxgEq0yaqa00M9U+U8="
+            }
+          }
+        },
+        "nopt": {
+          "version": "3.0.6",
+          "resolved": "https://registry.npmjs.org/nopt/-/nopt-3.0.6.tgz",
+          "integrity": "sha1-xkZdvwirzU2zWTF/eaxopkayj/k=",
+          "requires": {
+            "abbrev": "1.1.1"
+          }
+        },
+        "normalize-package-data": {
+          "version": "2.4.0",
+          "resolved": "https://registry.npmjs.org/normalize-package-data/-/normalize-package-data-2.4.0.tgz",
+          "integrity": "sha512-9jjUFbTPfEy3R/ad/2oNbKtW9Hgovl5O1FvFWKkKblNXoN/Oou6+9+KKohPK13Yc3/TyunyWhJp6gvRNR/PPAw==",
+          "requires": {
+            "hosted-git-info": "2.5.0",
+            "is-builtin-module": "1.0.0",
+            "semver": "5.4.1",
+            "validate-npm-package-license": "3.0.1"
+          }
+        },
+        "npmlog": {
+          "version": "4.1.2",
+          "resolved": "https://registry.npmjs.org/npmlog/-/npmlog-4.1.2.tgz",
+          "integrity": "sha512-2uUqazuKlTaSI/dC8AzicUck7+IrEaOnN/e0jd3Xtt1KcGpwx30v50mL7oPyr/h9bL3E4aZccVwpwP+5W9Vjkg==",
+          "requires": {
+            "are-we-there-yet": "1.1.4",
+            "console-control-strings": "1.1.0",
+            "gauge": "2.7.4",
+            "set-blocking": "2.0.0"
+          }
+        },
+        "number-is-nan": {
+          "version": "1.0.1",
+          "resolved": "https://registry.npmjs.org/number-is-nan/-/number-is-nan-1.0.1.tgz",
+          "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="
+        },
+        "object-assign": {
+          "version": "4.1.1",
+          "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz",
+          "integrity": "sha1-IQmtx5ZYh8/AXLvUQsrIv7s2CGM="
+        },
+        "once": {
+          "version": "1.4.0",
+          "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz",
+          "integrity": "sha1-WDsap3WWHUsROsF9nFC6753Xa9E=",
+          "requires": {
+            "wrappy": "1.0.2"
+          }
+        },
+        "os-homedir": {
+          "version": "1.0.2",
+          "resolved": "https://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",
+          "integrity": "sha1-IPnxeuKe00XoveWDsT0gCYA8FNk=",
+          "requires": {
+            "lcid": "1.0.0"
+          }
+        },
+        "os-tmpdir": {
+          "version": "1.0.2",
+          "resolved": "https://registry.npmjs.org/os-tmpdir/-/os-tmpdir-1.0.2.tgz",
+          "integrity": "sha1-u+Z0BseaqFxc/sdm/lc0VV36EnQ="
+        },
+        "osenv": {
+          "version": "0.1.4",
+          "resolved": "https://registry.npmjs.org/osenv/-/osenv-0.1.4.tgz",
+          "integrity": "sha1-Qv5tWVPfBsgGS+bxdsPQWqqjRkQ=",
+          "requires": {
+            "os-homedir": "1.0.2",
+            "os-tmpdir": "1.0.2"
+          }
+        },
+        "parse-json": {
+          "version": "2.2.0",
+          "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-2.2.0.tgz",
+          "integrity": "sha1-9ID0BDTvgHQfhGkJn43qGPVaTck=",
+          "requires": {
+            "error-ex": "1.3.1"
+          }
+        },
+        "path-exists": {
+          "version": "2.1.0",
+          "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-2.1.0.tgz",
+          "integrity": "sha1-D+tsZPD8UY2adU3V77YscCJ2H0s=",
+          "requires": {
+            "pinkie-promise": "2.0.1"
+          }
+        },
+        "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="
+        },
+        "path-type": {
+          "version": "1.1.0",
+          "resolved": "https://registry.npmjs.org/path-type/-/path-type-1.1.0.tgz",
+          "integrity": "sha1-WcRPfuSR2nBNpBXaWkBwuk+P5EE=",
+          "requires": {
+            "graceful-fs": "4.1.11",
+            "pify": "2.3.0",
+            "pinkie-promise": "2.0.1"
+          }
+        },
+        "performance-now": {
+          "version": "2.1.0",
+          "resolved": "https://registry.npmjs.org/performance-now/-/performance-now-2.1.0.tgz",
+          "integrity": "sha1-Ywn04OX6kT7BxpMHrjZLSzd8nns="
+        },
+        "pify": {
+          "version": "2.3.0",
+          "resolved": "https://registry.npmjs.org/pify/-/pify-2.3.0.tgz",
+          "integrity": "sha1-7RQaasBDqEnqWISY59yosVMw6Qw="
+        },
+        "pinkie": {
+          "version": "2.0.4",
+          "resolved": "https://registry.npmjs.org/pinkie/-/pinkie-2.0.4.tgz",
+          "integrity": "sha1-clVrgM+g1IqXToDnckjoDtT3+HA="
+        },
+        "pinkie-promise": {
+          "version": "2.0.1",
+          "resolved": "https://registry.npmjs.org/pinkie-promise/-/pinkie-promise-2.0.1.tgz",
+          "integrity": "sha1-ITXW36ejWMBprJsXh3YogihFD/o=",
+          "requires": {
+            "pinkie": "2.0.4"
+          }
+        },
+        "process-nextick-args": {
+          "version": "1.0.7",
+          "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-1.0.7.tgz",
+          "integrity": "sha1-FQ4gt1ZZCtP5EJPyWk8q2L/zC6M="
+        },
+        "pseudomap": {
+          "version": "1.0.2",
+          "resolved": "https://registry.npmjs.org/pseudomap/-/pseudomap-1.0.2.tgz",
+          "integrity": "sha1-8FKijacOYYkX7wqKw0wa5aaChrM="
+        },
+        "punycode": {
+          "version": "1.4.1",
+          "resolved": "https://registry.npmjs.org/punycode/-/punycode-1.4.1.tgz",
+          "integrity": "sha1-wNWmOycYgArY4esPpSachN1BhF4="
+        },
+        "qs": {
+          "version": "6.5.1",
+          "resolved": "https://registry.npmjs.org/qs/-/qs-6.5.1.tgz",
+          "integrity": "sha512-eRzhrN1WSINYCDCbrz796z37LOe3m5tmW7RQf6oBntukAG1nmovJvhnwHHRMAfeoItc1m2Hk02WER2aQ/iqs+A=="
+        },
+        "read-pkg": {
+          "version": "1.1.0",
+          "resolved": "https://registry.npmjs.org/read-pkg/-/read-pkg-1.1.0.tgz",
+          "integrity": "sha1-9f+qXs0pyzHAR0vKfXVra7KePyg=",
+          "requires": {
+            "load-json-file": "1.1.0",
+            "normalize-package-data": "2.4.0",
+            "path-type": "1.1.0"
+          }
+        },
+        "read-pkg-up": {
+          "version": "1.0.1",
+          "resolved": "https://registry.npmjs.org/read-pkg-up/-/read-pkg-up-1.0.1.tgz",
+          "integrity": "sha1-nWPBMnbAZZGNV/ACpX9AobZD+wI=",
+          "requires": {
+            "find-up": "1.1.2",
+            "read-pkg": "1.1.0"
+          }
+        },
+        "readable-stream": {
+          "version": "2.3.3",
+          "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.3.tgz",
+          "integrity": "sha512-m+qzzcn7KUxEmd1gMbchF+Y2eIUbieUaxkWtptyHywrX0rE8QEYqPC07Vuy4Wm32/xE16NcdBctb8S0Xe/5IeQ==",
+          "requires": {
+            "core-util-is": "1.0.2",
+            "inherits": "2.0.3",
+            "isarray": "1.0.0",
+            "process-nextick-args": "1.0.7",
+            "safe-buffer": "5.1.1",
+            "string_decoder": "1.0.3",
+            "util-deprecate": "1.0.2"
+          }
+        },
+        "redent": {
+          "version": "1.0.0",
+          "resolved": "https://registry.npmjs.org/redent/-/redent-1.0.0.tgz",
+          "integrity": "sha1-z5Fqsf1fHxbfsggi3W7H9zDCr94=",
+          "requires": {
+            "indent-string": "2.1.0",
+            "strip-indent": "1.0.1"
+          }
+        },
+        "repeating": {
+          "version": "2.0.1",
+          "resolved": "https://registry.npmjs.org/repeating/-/repeating-2.0.1.tgz",
+          "integrity": "sha1-UhTFOpJtNVJwdSf7q0FdvAjQbdo=",
+          "requires": {
+            "is-finite": "1.0.2"
+          }
+        },
+        "request": {
+          "version": "2.83.0",
+          "resolved": "https://registry.npmjs.org/request/-/request-2.83.0.tgz",
+          "integrity": "sha512-lR3gD69osqm6EYLk9wB/G1W/laGWjzH90t1vEa2xuxHD5KUrSzp9pUSfTm+YC5Nxt2T8nMPEvKlhbQayU7bgFw==",
+          "requires": {
+            "aws-sign2": "0.7.0",
+            "aws4": "1.6.0",
+            "caseless": "0.12.0",
+            "combined-stream": "1.0.5",
+            "extend": "3.0.1",
+            "forever-agent": "0.6.1",
+            "form-data": "2.3.1",
+            "har-validator": "5.0.3",
+            "hawk": "6.0.2",
+            "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",
+            "performance-now": "2.1.0",
+            "qs": "6.5.1",
+            "safe-buffer": "5.1.1",
+            "stringstream": "0.0.5",
+            "tough-cookie": "2.3.3",
+            "tunnel-agent": "0.6.0",
+            "uuid": "3.1.0"
+          }
+        },
+        "require-directory": {
+          "version": "2.1.1",
+          "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz",
+          "integrity": "sha1-jGStX9MNqxyXbiNE/+f3kqam30I="
+        },
+        "require-main-filename": {
+          "version": "1.0.1",
+          "resolved": "https://registry.npmjs.org/require-main-filename/-/require-main-filename-1.0.1.tgz",
+          "integrity": "sha1-l/cXtp1IeE9fUmpsWqj/3aBVpNE="
+        },
+        "rimraf": {
+          "version": "2.6.2",
+          "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.6.2.tgz",
+          "integrity": "sha512-lreewLK/BlghmxtfH36YYVg1i8IAce4TI7oao75I1g245+6BctqTVQiBP3YUJ9C6DQOXJmkYR9X9fCLtCOJc5w==",
+          "requires": {
+            "glob": "7.1.2"
+          }
+        },
+        "safe-buffer": {
+          "version": "5.1.1",
+          "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.1.tgz",
+          "integrity": "sha512-kKvNJn6Mm93gAczWVJg7wH+wGYWNrDHdWvpUmHyEsgCtIwwo3bqPtV4tR5tuPaUhTOo/kvhVwd8XwwOllGYkbg=="
+        },
+        "sass-graph": {
+          "version": "2.2.4",
+          "resolved": "https://registry.npmjs.org/sass-graph/-/sass-graph-2.2.4.tgz",
+          "integrity": "sha1-E/vWPNHK8JCLn9k0dq1DpR0eC0k=",
+          "requires": {
+            "glob": "7.1.2",
+            "lodash": "4.17.4",
+            "scss-tokenizer": "0.2.3",
+            "yargs": "7.1.0"
+          }
+        },
+        "scss-tokenizer": {
+          "version": "0.2.3",
+          "resolved": "https://registry.npmjs.org/scss-tokenizer/-/scss-tokenizer-0.2.3.tgz",
+          "integrity": "sha1-jrBtualyMzOCTT9VMGQRSYR85dE=",
+          "requires": {
+            "js-base64": "2.4.0",
+            "source-map": "0.4.4"
+          }
+        },
+        "semver": {
+          "version": "5.4.1",
+          "resolved": "https://registry.npmjs.org/semver/-/semver-5.4.1.tgz",
+          "integrity": "sha512-WfG/X9+oATh81XtllIo/I8gOiY9EXRdv1cQdyykeXK17YcUW3EXUAi2To4pcH6nZtJPr7ZOpM5OMyWJZm+8Rsg=="
+        },
+        "set-blocking": {
+          "version": "2.0.0",
+          "resolved": "https://registry.npmjs.org/set-blocking/-/set-blocking-2.0.0.tgz",
+          "integrity": "sha1-BF+XgtARrppoA93TgrJDkrPYkPc="
+        },
+        "signal-exit": {
+          "version": "3.0.2",
+          "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.2.tgz",
+          "integrity": "sha1-tf3AjxKH6hF4Yo5BXiUTK3NkbG0="
+        },
+        "sntp": {
+          "version": "2.1.0",
+          "resolved": "https://registry.npmjs.org/sntp/-/sntp-2.1.0.tgz",
+          "integrity": "sha512-FL1b58BDrqS3A11lJ0zEdnJ3UOKqVxawAkF3k7F0CVN7VQ34aZrV+G8BZ1WC9ZL7NyrwsW0oviwsWDgRuVYtJg==",
+          "requires": {
+            "hoek": "4.2.0"
+          }
+        },
+        "source-map": {
+          "version": "0.4.4",
+          "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.4.4.tgz",
+          "integrity": "sha1-66T12pwNyZneaAMti092FzZSA2s=",
+          "requires": {
+            "amdefine": "1.0.1"
+          }
+        },
+        "spdx-correct": {
+          "version": "1.0.2",
+          "resolved": "https://registry.npmjs.org/spdx-correct/-/spdx-correct-1.0.2.tgz",
+          "integrity": "sha1-SzBz2TP/UfORLwOsVRlJikFQ20A=",
+          "requires": {
+            "spdx-license-ids": "1.2.2"
+          }
+        },
+        "spdx-expression-parse": {
+          "version": "1.0.4",
+          "resolved": "https://registry.npmjs.org/spdx-expression-parse/-/spdx-expression-parse-1.0.4.tgz",
+          "integrity": "sha1-m98vIOH0DtRH++JzJmGR/O1RYmw="
+        },
+        "spdx-license-ids": {
+          "version": "1.2.2",
+          "resolved": "https://registry.npmjs.org/spdx-license-ids/-/spdx-license-ids-1.2.2.tgz",
+          "integrity": "sha1-yd96NCRZSt5r0RkA1ZZpbcBrrFc="
+        },
+        "sshpk": {
+          "version": "1.13.1",
+          "resolved": "https://registry.npmjs.org/sshpk/-/sshpk-1.13.1.tgz",
+          "integrity": "sha1-US322mKHFEMW3EwY/hzx2UBzm+M=",
+          "requires": {
+            "asn1": "0.2.3",
+            "assert-plus": "1.0.0",
+            "bcrypt-pbkdf": "1.0.1",
+            "dashdash": "1.14.1",
+            "ecc-jsbn": "0.1.1",
+            "getpass": "0.1.7",
+            "jsbn": "0.1.1",
+            "tweetnacl": "0.14.5"
+          }
+        },
+        "stdout-stream": {
+          "version": "1.4.0",
+          "resolved": "https://registry.npmjs.org/stdout-stream/-/stdout-stream-1.4.0.tgz",
+          "integrity": "sha1-osfIWH5U2UJ+qe2zrD8s1SLfN4s=",
+          "requires": {
+            "readable-stream": "2.3.3"
+          }
+        },
+        "string-width": {
+          "version": "1.0.2",
+          "resolved": "https://registry.npmjs.org/string-width/-/string-width-1.0.2.tgz",
+          "integrity": "sha1-EYvfW4zcUaKn5w0hHgfisLmxB9M=",
+          "requires": {
+            "code-point-at": "1.1.0",
+            "is-fullwidth-code-point": "1.0.0",
+            "strip-ansi": "3.0.1"
+          }
+        },
+        "string_decoder": {
+          "version": "1.0.3",
+          "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.0.3.tgz",
+          "integrity": "sha512-4AH6Z5fzNNBcH+6XDMfA/BTt87skxqJlO0lAh3Dker5zThcAxG6mKz+iGu308UKoPPQ8Dcqx/4JhujzltRa+hQ==",
+          "requires": {
+            "safe-buffer": "5.1.1"
+          }
+        },
+        "stringstream": {
+          "version": "0.0.5",
+          "resolved": "https://registry.npmjs.org/stringstream/-/stringstream-0.0.5.tgz",
+          "integrity": "sha1-TkhM1N5aC7vuGORjB3EKioFiGHg="
+        },
+        "strip-ansi": {
+          "version": "3.0.1",
+          "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-3.0.1.tgz",
+          "integrity": "sha1-ajhfuIU9lS1f8F0Oiq+UJ43GPc8=",
+          "requires": {
+            "ansi-regex": "2.1.1"
+          }
+        },
+        "strip-bom": {
+          "version": "2.0.0",
+          "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-2.0.0.tgz",
+          "integrity": "sha1-YhmoVhZSBJHzV4i9vxRHqZx+aw4=",
+          "requires": {
+            "is-utf8": "0.2.1"
+          }
+        },
+        "strip-indent": {
+          "version": "1.0.1",
+          "resolved": "https://registry.npmjs.org/strip-indent/-/strip-indent-1.0.1.tgz",
+          "integrity": "sha1-DHlipq3vp7vUrDZkYKY4VSrhoKI=",
+          "requires": {
+            "get-stdin": "4.0.1"
+          }
+        },
+        "supports-color": {
+          "version": "2.0.0",
+          "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-2.0.0.tgz",
+          "integrity": "sha1-U10EXOa2Nj+kARcIRimZXp3zJMc="
+        },
+        "tar": {
+          "version": "2.2.1",
+          "resolved": "https://registry.npmjs.org/tar/-/tar-2.2.1.tgz",
+          "integrity": "sha1-jk0qJWwOIYXGsYrWlK7JaLg8sdE=",
+          "requires": {
+            "block-stream": "https://registry.npmjs.org/block-stream/-/block-stream-0.0.9.tgz",
+            "fstream": "1.0.11",
+            "inherits": "2.0.3"
+          }
+        },
+        "tough-cookie": {
+          "version": "2.3.3",
+          "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-2.3.3.tgz",
+          "integrity": "sha1-C2GKVWW23qkL80JdBNVe3EdadWE=",
+          "requires": {
+            "punycode": "1.4.1"
+          }
+        },
+        "trim-newlines": {
+          "version": "1.0.0",
+          "resolved": "https://registry.npmjs.org/trim-newlines/-/trim-newlines-1.0.0.tgz",
+          "integrity": "sha1-WIeWa7WCpFA6QetST301ARgVphM="
+        },
+        "tunnel-agent": {
+          "version": "0.6.0",
+          "resolved": "https://registry.npmjs.org/tunnel-agent/-/tunnel-agent-0.6.0.tgz",
+          "integrity": "sha1-J6XeoGs2sEoKmWZ3SykIaPD8QP0=",
+          "requires": {
+            "safe-buffer": "5.1.1"
+          }
+        },
+        "tweetnacl": {
+          "version": "0.14.5",
+          "resolved": "https://registry.npmjs.org/tweetnacl/-/tweetnacl-0.14.5.tgz",
+          "integrity": "sha1-WuaBd/GS1EViadEIr6k/+HQ/T2Q=",
+          "optional": true
+        },
+        "util-deprecate": {
+          "version": "1.0.2",
+          "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz",
+          "integrity": "sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8="
+        },
+        "uuid": {
+          "version": "3.1.0",
+          "resolved": "https://registry.npmjs.org/uuid/-/uuid-3.1.0.tgz",
+          "integrity": "sha512-DIWtzUkw04M4k3bf1IcpS2tngXEL26YUD2M0tMDUpnUrz2hgzUBlD55a4FjdLGPvfHxS6uluGWvaVEqgBcVa+g=="
+        },
+        "validate-npm-package-license": {
+          "version": "3.0.1",
+          "resolved": "https://registry.npmjs.org/validate-npm-package-license/-/validate-npm-package-license-3.0.1.tgz",
+          "integrity": "sha1-KAS6vnEq0zeUWaz74kdGqywwP7w=",
+          "requires": {
+            "spdx-correct": "1.0.2",
+            "spdx-expression-parse": "1.0.4"
+          }
+        },
+        "verror": {
+          "version": "1.10.0",
+          "resolved": "https://registry.npmjs.org/verror/-/verror-1.10.0.tgz",
+          "integrity": "sha1-OhBcoXBTr1XW4nDB+CiGguGNpAA=",
+          "requires": {
+            "assert-plus": "1.0.0",
+            "core-util-is": "1.0.2",
+            "extsprintf": "1.3.0"
+          }
+        },
+        "which": {
+          "version": "1.3.0",
+          "resolved": "https://registry.npmjs.org/which/-/which-1.3.0.tgz",
+          "integrity": "sha512-xcJpopdamTuY5duC/KnTTNBraPK54YwpenP4lzxU8H91GudWpFv38u0CKjclE1Wi2EH2EDz5LRcHcKbCIzqGyg==",
+          "requires": {
+            "isexe": "2.0.0"
+          }
+        },
+        "which-module": {
+          "version": "1.0.0",
+          "resolved": "https://registry.npmjs.org/which-module/-/which-module-1.0.0.tgz",
+          "integrity": "sha1-u6Y8qGGUiZT/MHc2CJ47lgJsKk8="
+        },
+        "wide-align": {
+          "version": "1.1.2",
+          "resolved": "https://registry.npmjs.org/wide-align/-/wide-align-1.1.2.tgz",
+          "integrity": "sha512-ijDLlyQ7s6x1JgCLur53osjm/UXUYD9+0PbYKrBsYisYXzCxN+HC3mYDNy/dWdmf3AwqwU3CXwDCvsNgGK1S0w==",
+          "requires": {
+            "string-width": "1.0.2"
+          }
+        },
+        "wrap-ansi": {
+          "version": "2.1.0",
+          "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-2.1.0.tgz",
+          "integrity": "sha1-2Pw9KE3QV5T+hJc8rs3Rz4JP3YU=",
+          "requires": {
+            "string-width": "1.0.2",
+            "strip-ansi": "3.0.1"
+          }
+        },
+        "wrappy": {
+          "version": "1.0.2",
+          "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz",
+          "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8="
+        },
+        "y18n": {
+          "version": "3.2.1",
+          "resolved": "https://registry.npmjs.org/y18n/-/y18n-3.2.1.tgz",
+          "integrity": "sha1-bRX7qITAhnnA136I53WegR4H+kE="
+        },
+        "yallist": {
+          "version": "2.1.2",
+          "resolved": "https://registry.npmjs.org/yallist/-/yallist-2.1.2.tgz",
+          "integrity": "sha1-HBH5IY8HYImkfdUS+TxmmaaoHVI="
+        },
+        "yargs": {
+          "version": "7.1.0",
+          "resolved": "https://registry.npmjs.org/yargs/-/yargs-7.1.0.tgz",
+          "integrity": "sha1-a6MY6xaWFyf10oT46gA+jWFU0Mg=",
+          "requires": {
+            "camelcase": "3.0.0",
+            "cliui": "3.2.0",
+            "decamelize": "1.2.0",
+            "get-caller-file": "1.0.2",
+            "os-locale": "1.4.0",
+            "read-pkg-up": "1.0.1",
+            "require-directory": "2.1.1",
+            "require-main-filename": "1.0.1",
+            "set-blocking": "2.0.0",
+            "string-width": "1.0.2",
+            "which-module": "1.0.0",
+            "y18n": "3.2.1",
+            "yargs-parser": "5.0.0"
+          },
+          "dependencies": {
+            "camelcase": {
+              "version": "3.0.0",
+              "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-3.0.0.tgz",
+              "integrity": "sha1-MvxLn82vhF/N9+c7uXysImHwqwo="
+            }
+          }
+        },
+        "yargs-parser": {
+          "version": "5.0.0",
+          "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-5.0.0.tgz",
+          "integrity": "sha1-J17PDX/+Bcd+ZOfIbkzZS/DhIoo=",
+          "requires": {
+            "camelcase": "3.0.0"
+          },
+          "dependencies": {
+            "camelcase": {
+              "version": "3.0.0",
+              "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-3.0.0.tgz",
+              "integrity": "sha1-MvxLn82vhF/N9+c7uXysImHwqwo="
+            }
+          }
+        }
+      }
+    },
+    "p-try": {
+      "version": "1.0.0",
+      "resolved": "https://registry.npmjs.org/p-try/-/p-try-1.0.0.tgz",
+      "integrity": "sha1-y8ec26+P1CKOE/Yh8rGiN8GyB7M=",
+      "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="
+    },
+    "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=",
+      "dev": true,
+      "requires": {
+        "postcss": "6.0.16"
+      },
+      "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.1"
+          }
+        },
+        "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.2.0",
+            "escape-string-regexp": "1.0.5",
+            "supports-color": "4.5.0"
+          },
+          "dependencies": {
+            "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"
+              }
+            }
+          }
+        },
+        "color-convert": {
+          "version": "1.9.1",
+          "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.1.tgz",
+          "integrity": "sha512-mjGanIiwQJskCC18rPR6OmrZ6fm2Lc7PeGFYwCmy5J34wC6F1PzdGL6xeMfmgicfYcNLGuVFA3WzXtIDCQSZxQ==",
+          "dev": true,
+          "requires": {
+            "color-name": "1.1.3"
+          }
+        },
+        "color-name": {
+          "version": "1.1.3",
+          "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz",
+          "integrity": "sha1-p9BVi9icQveV3UIyj3QIMcpTvCU=",
+          "dev": true
+        },
+        "escape-string-regexp": {
+          "version": "1.0.5",
+          "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz",
+          "integrity": "sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ=",
+          "dev": true
+        },
+        "has-flag": {
+          "version": "2.0.0",
+          "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-2.0.0.tgz",
+          "integrity": "sha1-6CB68cx7MNRGzHC3NLXovhj4jVE=",
+          "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",
+            "supports-color": "5.1.0"
+          }
+        },
+        "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
+        },
+        "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"
+          }
+        }
+      }
+    },
+    "postcss-modules-local-by-default": {
+      "version": "1.2.0",
+      "resolved": "https://registry.npmjs.org/postcss-modules-local-by-default/-/postcss-modules-local-by-default-1.2.0.tgz",
+      "integrity": "sha1-99gMOYxaOT+nlkRmvRlQCn1hwGk=",
+      "dev": true,
+      "requires": {
+        "css-selector-tokenizer": "0.7.0",
+        "postcss": "6.0.16"
+      },
+      "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.1"
+          }
+        },
+        "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.2.0",
+            "escape-string-regexp": "1.0.5",
+            "supports-color": "4.5.0"
+          },
+          "dependencies": {
+            "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"
+              }
+            }
+          }
+        },
+        "color-convert": {
+          "version": "1.9.1",
+          "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.1.tgz",
+          "integrity": "sha512-mjGanIiwQJskCC18rPR6OmrZ6fm2Lc7PeGFYwCmy5J34wC6F1PzdGL6xeMfmgicfYcNLGuVFA3WzXtIDCQSZxQ==",
+          "dev": true,
+          "requires": {
+            "color-name": "1.1.3"
+          }
+        },
+        "color-name": {
+          "version": "1.1.3",
+          "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz",
+          "integrity": "sha1-p9BVi9icQveV3UIyj3QIMcpTvCU=",
+          "dev": true
+        },
+        "css-selector-tokenizer": {
+          "version": "0.7.0",
+          "resolved": "https://registry.npmjs.org/css-selector-tokenizer/-/css-selector-tokenizer-0.7.0.tgz",
+          "integrity": "sha1-5piEdK6MlTR3v15+/s/OzNnPTIY=",
+          "dev": true,
+          "requires": {
+            "cssesc": "0.1.0",
+            "fastparse": "1.1.1",
+            "regexpu-core": "1.0.0"
+          }
+        },
+        "cssesc": {
+          "version": "0.1.0",
+          "resolved": "https://registry.npmjs.org/cssesc/-/cssesc-0.1.0.tgz",
+          "integrity": "sha1-yBSQPkViM3GgR3tAEJqq++6t27Q=",
+          "dev": true
+        },
+        "escape-string-regexp": {
+          "version": "1.0.5",
+          "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz",
+          "integrity": "sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ=",
+          "dev": true
+        },
+        "fastparse": {
+          "version": "1.1.1",
+          "resolved": "https://registry.npmjs.org/fastparse/-/fastparse-1.1.1.tgz",
+          "integrity": "sha1-0eJkOzipTXWDtHkGDmxK/8lAcfg=",
+          "dev": true
+        },
+        "has-flag": {
+          "version": "2.0.0",
+          "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-2.0.0.tgz",
+          "integrity": "sha1-6CB68cx7MNRGzHC3NLXovhj4jVE=",
+          "dev": true
+        },
+        "jsesc": {
+          "version": "0.5.0",
+          "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-0.5.0.tgz",
+          "integrity": "sha1-597mbjXW/Bb3EP6R1c9p9w8IkR0=",
+          "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",
+            "supports-color": "5.1.0"
+          }
+        },
+        "regenerate": {
+          "version": "1.3.3",
+          "resolved": "https://registry.npmjs.org/regenerate/-/regenerate-1.3.3.tgz",
+          "integrity": "sha512-jVpo1GadrDAK59t/0jRx5VxYWQEDkkEKi6+HjE3joFVLfDOh9Xrdh0dF1eSq+BI/SwvTQ44gSscJ8N5zYL61sg==",
+          "dev": true
+        },
+        "regexpu-core": {
+          "version": "1.0.0",
+          "resolved": "https://registry.npmjs.org/regexpu-core/-/regexpu-core-1.0.0.tgz",
+          "integrity": "sha1-hqdj9Y7k18L2sQLkdkBQ3n7ZDGs=",
+          "dev": true,
+          "requires": {
+            "regenerate": "1.3.3",
+            "regjsgen": "0.2.0",
+            "regjsparser": "0.1.5"
+          }
+        },
+        "regjsgen": {
+          "version": "0.2.0",
+          "resolved": "https://registry.npmjs.org/regjsgen/-/regjsgen-0.2.0.tgz",
+          "integrity": "sha1-bAFq3qxVT3WCP+N6wFuS1aTtsfc=",
+          "dev": true
+        },
+        "regjsparser": {
+          "version": "0.1.5",
+          "resolved": "https://registry.npmjs.org/regjsparser/-/regjsparser-0.1.5.tgz",
+          "integrity": "sha1-fuj4Tcb6eS0/0K4ijSS9lJ6tIFw=",
+          "dev": true,
+          "requires": {
+            "jsesc": "0.5.0"
+          }
+        },
+        "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
+        },
+        "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"
+          }
+        }
+      }
+    },
+    "postcss-modules-scope": {
+      "version": "1.1.0",
+      "resolved": "https://registry.npmjs.org/postcss-modules-scope/-/postcss-modules-scope-1.1.0.tgz",
+      "integrity": "sha1-1upkmUx5+XtipytCb75gVqGUu5A=",
+      "dev": true,
       "requires": {
-        "asap": "https://registry.npmjs.org/asap/-/asap-2.0.6.tgz"
+        "css-selector-tokenizer": "0.7.0",
+        "postcss": "6.0.16"
+      },
+      "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.1"
+          }
+        },
+        "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.2.0",
+            "escape-string-regexp": "1.0.5",
+            "supports-color": "4.5.0"
+          },
+          "dependencies": {
+            "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"
+              }
+            }
+          }
+        },
+        "color-convert": {
+          "version": "1.9.1",
+          "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.1.tgz",
+          "integrity": "sha512-mjGanIiwQJskCC18rPR6OmrZ6fm2Lc7PeGFYwCmy5J34wC6F1PzdGL6xeMfmgicfYcNLGuVFA3WzXtIDCQSZxQ==",
+          "dev": true,
+          "requires": {
+            "color-name": "1.1.3"
+          }
+        },
+        "color-name": {
+          "version": "1.1.3",
+          "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz",
+          "integrity": "sha1-p9BVi9icQveV3UIyj3QIMcpTvCU=",
+          "dev": true
+        },
+        "css-selector-tokenizer": {
+          "version": "0.7.0",
+          "resolved": "https://registry.npmjs.org/css-selector-tokenizer/-/css-selector-tokenizer-0.7.0.tgz",
+          "integrity": "sha1-5piEdK6MlTR3v15+/s/OzNnPTIY=",
+          "dev": true,
+          "requires": {
+            "cssesc": "0.1.0",
+            "fastparse": "1.1.1",
+            "regexpu-core": "1.0.0"
+          }
+        },
+        "cssesc": {
+          "version": "0.1.0",
+          "resolved": "https://registry.npmjs.org/cssesc/-/cssesc-0.1.0.tgz",
+          "integrity": "sha1-yBSQPkViM3GgR3tAEJqq++6t27Q=",
+          "dev": true
+        },
+        "escape-string-regexp": {
+          "version": "1.0.5",
+          "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz",
+          "integrity": "sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ=",
+          "dev": true
+        },
+        "fastparse": {
+          "version": "1.1.1",
+          "resolved": "https://registry.npmjs.org/fastparse/-/fastparse-1.1.1.tgz",
+          "integrity": "sha1-0eJkOzipTXWDtHkGDmxK/8lAcfg=",
+          "dev": true
+        },
+        "has-flag": {
+          "version": "2.0.0",
+          "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-2.0.0.tgz",
+          "integrity": "sha1-6CB68cx7MNRGzHC3NLXovhj4jVE=",
+          "dev": true
+        },
+        "jsesc": {
+          "version": "0.5.0",
+          "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-0.5.0.tgz",
+          "integrity": "sha1-597mbjXW/Bb3EP6R1c9p9w8IkR0=",
+          "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",
+            "supports-color": "5.1.0"
+          }
+        },
+        "regenerate": {
+          "version": "1.3.3",
+          "resolved": "https://registry.npmjs.org/regenerate/-/regenerate-1.3.3.tgz",
+          "integrity": "sha512-jVpo1GadrDAK59t/0jRx5VxYWQEDkkEKi6+HjE3joFVLfDOh9Xrdh0dF1eSq+BI/SwvTQ44gSscJ8N5zYL61sg==",
+          "dev": true
+        },
+        "regexpu-core": {
+          "version": "1.0.0",
+          "resolved": "https://registry.npmjs.org/regexpu-core/-/regexpu-core-1.0.0.tgz",
+          "integrity": "sha1-hqdj9Y7k18L2sQLkdkBQ3n7ZDGs=",
+          "dev": true,
+          "requires": {
+            "regenerate": "1.3.3",
+            "regjsgen": "0.2.0",
+            "regjsparser": "0.1.5"
+          }
+        },
+        "regjsgen": {
+          "version": "0.2.0",
+          "resolved": "https://registry.npmjs.org/regjsgen/-/regjsgen-0.2.0.tgz",
+          "integrity": "sha1-bAFq3qxVT3WCP+N6wFuS1aTtsfc=",
+          "dev": true
+        },
+        "regjsparser": {
+          "version": "0.1.5",
+          "resolved": "https://registry.npmjs.org/regjsparser/-/regjsparser-0.1.5.tgz",
+          "integrity": "sha1-fuj4Tcb6eS0/0K4ijSS9lJ6tIFw=",
+          "dev": true,
+          "requires": {
+            "jsesc": "0.5.0"
+          }
+        },
+        "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
+        },
+        "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"
+          }
+        }
       }
     },
-    "prop-types": {
-      "version": "https://registry.npmjs.org/prop-types/-/prop-types-15.6.0.tgz",
-      "integrity": "sha1-zq8IMCL8RrSjX2nhPvda7Q1jmFY=",
+    "postcss-modules-values": {
+      "version": "1.3.0",
+      "resolved": "https://registry.npmjs.org/postcss-modules-values/-/postcss-modules-values-1.3.0.tgz",
+      "integrity": "sha1-7P+p1+GSUYOJ9CrQ6D9yrsRW6iA=",
+      "dev": true,
       "requires": {
-        "fbjs": "https://registry.npmjs.org/fbjs/-/fbjs-0.8.16.tgz",
-        "loose-envify": "https://registry.npmjs.org/loose-envify/-/loose-envify-1.3.1.tgz",
-        "object-assign": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz"
+        "icss-replace-symbols": "1.1.0",
+        "postcss": "6.0.16"
+      },
+      "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.1"
+          }
+        },
+        "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.2.0",
+            "escape-string-regexp": "1.0.5",
+            "supports-color": "4.5.0"
+          },
+          "dependencies": {
+            "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"
+              }
+            }
+          }
+        },
+        "color-convert": {
+          "version": "1.9.1",
+          "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.1.tgz",
+          "integrity": "sha512-mjGanIiwQJskCC18rPR6OmrZ6fm2Lc7PeGFYwCmy5J34wC6F1PzdGL6xeMfmgicfYcNLGuVFA3WzXtIDCQSZxQ==",
+          "dev": true,
+          "requires": {
+            "color-name": "1.1.3"
+          }
+        },
+        "color-name": {
+          "version": "1.1.3",
+          "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz",
+          "integrity": "sha1-p9BVi9icQveV3UIyj3QIMcpTvCU=",
+          "dev": true
+        },
+        "escape-string-regexp": {
+          "version": "1.0.5",
+          "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz",
+          "integrity": "sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ=",
+          "dev": true
+        },
+        "has-flag": {
+          "version": "2.0.0",
+          "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-2.0.0.tgz",
+          "integrity": "sha1-6CB68cx7MNRGzHC3NLXovhj4jVE=",
+          "dev": true
+        },
+        "icss-replace-symbols": {
+          "version": "1.1.0",
+          "resolved": "https://registry.npmjs.org/icss-replace-symbols/-/icss-replace-symbols-1.1.0.tgz",
+          "integrity": "sha1-Bupvg2ead0njhs/h/oEq5dsiPe0=",
+          "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",
+            "supports-color": "5.1.0"
+          }
+        },
+        "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
+        },
+        "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"
+          }
+        }
       }
     },
-    "pseudomap": {
-      "version": "https://registry.npmjs.org/pseudomap/-/pseudomap-1.0.2.tgz",
-      "integrity": "sha1-8FKijacOYYkX7wqKw0wa5aaChrM="
-    },
-    "punycode": {
-      "version": "https://registry.npmjs.org/punycode/-/punycode-1.4.1.tgz",
-      "integrity": "sha1-wNWmOycYgArY4esPpSachN1BhF4="
-    },
-    "q": {
-      "version": "https://registry.npmjs.org/q/-/q-1.5.1.tgz",
-      "integrity": "sha1-fjL3W0E4EpHQRhHxvxQQmsAGUdc=",
-      "dev": true
-    },
-    "qs": {
-      "version": "https://registry.npmjs.org/qs/-/qs-6.5.1.tgz",
-      "integrity": "sha1-NJzfbu+J7EXBLX1es/wMhwNDptg="
+    "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==",
+      "dev": true,
+      "requires": {
+        "postcss": "6.0.16",
+        "postcss-selector-parser": "2.2.3"
+      },
+      "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.1"
+          }
+        },
+        "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.2.0",
+            "escape-string-regexp": "1.0.5",
+            "supports-color": "4.5.0"
+          },
+          "dependencies": {
+            "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"
+              }
+            }
+          }
+        },
+        "color-convert": {
+          "version": "1.9.1",
+          "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.1.tgz",
+          "integrity": "sha512-mjGanIiwQJskCC18rPR6OmrZ6fm2Lc7PeGFYwCmy5J34wC6F1PzdGL6xeMfmgicfYcNLGuVFA3WzXtIDCQSZxQ==",
+          "dev": true,
+          "requires": {
+            "color-name": "1.1.3"
+          }
+        },
+        "color-name": {
+          "version": "1.1.3",
+          "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz",
+          "integrity": "sha1-p9BVi9icQveV3UIyj3QIMcpTvCU=",
+          "dev": true
+        },
+        "escape-string-regexp": {
+          "version": "1.0.5",
+          "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz",
+          "integrity": "sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ=",
+          "dev": true
+        },
+        "flatten": {
+          "version": "1.0.2",
+          "resolved": "https://registry.npmjs.org/flatten/-/flatten-1.0.2.tgz",
+          "integrity": "sha1-2uRqnXj74lKSJYzB54CkHZXAN4I=",
+          "dev": true
+        },
+        "has-flag": {
+          "version": "2.0.0",
+          "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-2.0.0.tgz",
+          "integrity": "sha1-6CB68cx7MNRGzHC3NLXovhj4jVE=",
+          "dev": true
+        },
+        "indexes-of": {
+          "version": "1.0.1",
+          "resolved": "https://registry.npmjs.org/indexes-of/-/indexes-of-1.0.1.tgz",
+          "integrity": "sha1-8w9xbI4r00bHtn0985FVZqfAVgc=",
+          "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",
+            "supports-color": "5.1.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=",
+          "dev": true,
+          "requires": {
+            "flatten": "1.0.2",
+            "indexes-of": "1.0.1",
+            "uniq": "1.0.1"
+          }
+        },
+        "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
+        },
+        "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"
+          }
+        },
+        "uniq": {
+          "version": "1.0.1",
+          "resolved": "https://registry.npmjs.org/uniq/-/uniq-1.0.1.tgz",
+          "integrity": "sha1-sxxa6CVIRKOoKBVBzisEuGWnNP8=",
+          "dev": true
+        }
+      }
     },
-    "query-string": {
-      "version": "https://registry.npmjs.org/query-string/-/query-string-4.3.4.tgz",
-      "integrity": "sha1-u7aTucqRXCMlFbIosaArYJBD2+s=",
+    "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=",
       "requires": {
-        "object-assign": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz",
-        "strict-uri-encode": "https://registry.npmjs.org/strict-uri-encode/-/strict-uri-encode-1.1.0.tgz"
+        "any-promise": "1.3.0",
+        "deepmerge": "1.5.2",
+        "got": "7.1.0",
+        "inherits": "2.0.3",
+        "next-tick": "1.0.0",
+        "stream-parser": "0.3.1"
+      },
+      "dependencies": {
+        "any-promise": {
+          "version": "1.3.0",
+          "resolved": "https://registry.npmjs.org/any-promise/-/any-promise-1.3.0.tgz",
+          "integrity": "sha1-q8av7tzqUugJzcA3au0845Y10X8="
+        },
+        "debug": {
+          "version": "2.6.9",
+          "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz",
+          "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==",
+          "requires": {
+            "ms": "2.0.0"
+          }
+        },
+        "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"
+          }
+        },
+        "deepmerge": {
+          "version": "1.5.2",
+          "resolved": "https://registry.npmjs.org/deepmerge/-/deepmerge-1.5.2.tgz",
+          "integrity": "sha512-95k0GDqvBjZavkuvzx/YqVLv/6YYa17fz6ILMSf7neqQITCPbnfEnQvEgMPNjH4kgobe7+WIL0yJEHku+H3qtQ=="
+        },
+        "duplexer3": {
+          "version": "0.1.4",
+          "resolved": "https://registry.npmjs.org/duplexer3/-/duplexer3-0.1.4.tgz",
+          "integrity": "sha1-7gHdHKwO08vH/b6jfcCo8c4ALOI="
+        },
+        "get-stream": {
+          "version": "3.0.0",
+          "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-3.0.0.tgz",
+          "integrity": "sha1-jpQ9E1jcN1VQVOy+LtsFqhdO3hQ="
+        },
+        "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.3.0",
+            "duplexer3": "0.1.4",
+            "get-stream": "3.0.0",
+            "is-plain-obj": "1.1.0",
+            "is-retry-allowed": "1.1.0",
+            "is-stream": "1.1.0",
+            "isurl": "1.0.0",
+            "lowercase-keys": "1.0.0",
+            "p-cancelable": "0.3.0",
+            "p-timeout": "1.2.1",
+            "safe-buffer": "5.1.1",
+            "timed-out": "4.0.1",
+            "url-parse-lax": "1.0.0",
+            "url-to-options": "1.0.1"
+          }
+        },
+        "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=="
+        },
+        "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"
+          }
+        },
+        "inherits": {
+          "version": "2.0.3",
+          "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz",
+          "integrity": "sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4="
+        },
+        "is-object": {
+          "version": "1.0.1",
+          "resolved": "https://registry.npmjs.org/is-object/-/is-object-1.0.1.tgz",
+          "integrity": "sha1-iVJojF7C/9awPsyF52ngKQMINHA="
+        },
+        "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-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="
+        },
+        "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.4.1",
+            "is-object": "1.0.1"
+          }
+        },
+        "lowercase-keys": {
+          "version": "1.0.0",
+          "resolved": "https://registry.npmjs.org/lowercase-keys/-/lowercase-keys-1.0.0.tgz",
+          "integrity": "sha1-TjNms55/VFfjXxMkvfb4jQv8cwY="
+        },
+        "mimic-response": {
+          "version": "1.0.0",
+          "resolved": "https://registry.npmjs.org/mimic-response/-/mimic-response-1.0.0.tgz",
+          "integrity": "sha1-3z02Uqc/3ta5sLJBRub9BSNTRY4="
+        },
+        "ms": {
+          "version": "2.0.0",
+          "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz",
+          "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g="
+        },
+        "next-tick": {
+          "version": "1.0.0",
+          "resolved": "https://registry.npmjs.org/next-tick/-/next-tick-1.0.0.tgz",
+          "integrity": "sha1-yobR/ogoFpsBICCOPchCS524NCw="
+        },
+        "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="
+        },
+        "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"
+          }
+        },
+        "prepend-http": {
+          "version": "1.0.4",
+          "resolved": "https://registry.npmjs.org/prepend-http/-/prepend-http-1.0.4.tgz",
+          "integrity": "sha1-1PRWKwzjaW5BrFLQ4ALlemNdxtw="
+        },
+        "safe-buffer": {
+          "version": "5.1.1",
+          "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.1.tgz",
+          "integrity": "sha512-kKvNJn6Mm93gAczWVJg7wH+wGYWNrDHdWvpUmHyEsgCtIwwo3bqPtV4tR5tuPaUhTOo/kvhVwd8XwwOllGYkbg=="
+        },
+        "stream-parser": {
+          "version": "0.3.1",
+          "resolved": "https://registry.npmjs.org/stream-parser/-/stream-parser-0.3.1.tgz",
+          "integrity": "sha1-FhhUhpRCACGhGC/wrxkRwSl2F3M=",
+          "requires": {
+            "debug": "2.6.9"
+          }
+        },
+        "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="
+        },
+        "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.4"
+          }
+        },
+        "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="
+        }
       }
     },
-    "querystring": {
-      "version": "https://registry.npmjs.org/querystring/-/querystring-0.2.0.tgz",
-      "integrity": "sha1-sgmEkgO7Jd+CDadW50cAWHhSFiA=",
-      "dev": true
+    "prop-types": {
+      "version": "15.6.0",
+      "resolved": "https://registry.npmjs.org/prop-types/-/prop-types-15.6.0.tgz",
+      "integrity": "sha1-zq8IMCL8RrSjX2nhPvda7Q1jmFY=",
+      "requires": {
+        "fbjs": "0.8.16",
+        "loose-envify": "1.3.1",
+        "object-assign": "4.1.1"
+      },
+      "dependencies": {
+        "asap": {
+          "version": "2.0.6",
+          "resolved": "https://registry.npmjs.org/asap/-/asap-2.0.6.tgz",
+          "integrity": "sha1-5QNHYR1+aQlDIIu9r+vLwvuGbUY="
+        },
+        "core-js": {
+          "version": "1.2.7",
+          "resolved": "https://registry.npmjs.org/core-js/-/core-js-1.2.7.tgz",
+          "integrity": "sha1-ZSKUwUZR2yj6k70tX/KYOk8IxjY="
+        },
+        "encoding": {
+          "version": "0.1.12",
+          "resolved": "https://registry.npmjs.org/encoding/-/encoding-0.1.12.tgz",
+          "integrity": "sha1-U4tm8+5izRq1HsMjgp0flIDHS+s=",
+          "requires": {
+            "iconv-lite": "0.4.19"
+          }
+        },
+        "fbjs": {
+          "version": "0.8.16",
+          "resolved": "https://registry.npmjs.org/fbjs/-/fbjs-0.8.16.tgz",
+          "integrity": "sha1-XmdDL1UNxBtXK/VYR7ispk5TN9s=",
+          "requires": {
+            "core-js": "1.2.7",
+            "isomorphic-fetch": "2.2.1",
+            "loose-envify": "1.3.1",
+            "object-assign": "4.1.1",
+            "promise": "7.3.1",
+            "setimmediate": "1.0.5",
+            "ua-parser-js": "0.7.17"
+          }
+        },
+        "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=="
+        },
+        "is-stream": {
+          "version": "1.1.0",
+          "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-1.1.0.tgz",
+          "integrity": "sha1-EtSj3U5o4Lec6428hBc66A2RykQ="
+        },
+        "isomorphic-fetch": {
+          "version": "2.2.1",
+          "resolved": "https://registry.npmjs.org/isomorphic-fetch/-/isomorphic-fetch-2.2.1.tgz",
+          "integrity": "sha1-YRrhrPFPXoH3KVB0coGf6XM1WKk=",
+          "requires": {
+            "node-fetch": "1.7.3",
+            "whatwg-fetch": "2.0.3"
+          }
+        },
+        "js-tokens": {
+          "version": "3.0.2",
+          "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-3.0.2.tgz",
+          "integrity": "sha1-mGbfOVECEw449/mWvOtlRDIJwls="
+        },
+        "loose-envify": {
+          "version": "1.3.1",
+          "resolved": "https://registry.npmjs.org/loose-envify/-/loose-envify-1.3.1.tgz",
+          "integrity": "sha1-0aitM/qc4OcT1l/dCsi3SNR4yEg=",
+          "requires": {
+            "js-tokens": "3.0.2"
+          }
+        },
+        "node-fetch": {
+          "version": "1.7.3",
+          "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-1.7.3.tgz",
+          "integrity": "sha512-NhZ4CsKx7cYm2vSrBAr2PvFOe6sWDf0UYLRqA6svUYg7+/TSfVAu49jYC4BvQ4Sms9SZgdqGBgroqfDhJdTyKQ==",
+          "requires": {
+            "encoding": "0.1.12",
+            "is-stream": "1.1.0"
+          }
+        },
+        "object-assign": {
+          "version": "4.1.1",
+          "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz",
+          "integrity": "sha1-IQmtx5ZYh8/AXLvUQsrIv7s2CGM="
+        },
+        "promise": {
+          "version": "7.3.1",
+          "resolved": "https://registry.npmjs.org/promise/-/promise-7.3.1.tgz",
+          "integrity": "sha512-nolQXZ/4L+bP/UGlkfaIujX9BKxGwmQ9OT4mOt5yvy8iK1h3wqTEJCijzGANTCCl9nWjY41juyAn2K3Q1hLLTg==",
+          "requires": {
+            "asap": "2.0.6"
+          }
+        },
+        "setimmediate": {
+          "version": "1.0.5",
+          "resolved": "https://registry.npmjs.org/setimmediate/-/setimmediate-1.0.5.tgz",
+          "integrity": "sha1-KQy7Iy4waULX1+qbg3Mqt4VvgoU="
+        },
+        "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=="
+        },
+        "whatwg-fetch": {
+          "version": "2.0.3",
+          "resolved": "https://registry.npmjs.org/whatwg-fetch/-/whatwg-fetch-2.0.3.tgz",
+          "integrity": "sha1-nITsLc9oGH/wC8ZOEnS0QhduHIQ="
+        }
+      }
     },
     "react": {
-      "version": "https://registry.npmjs.org/react/-/react-16.0.0.tgz",
+      "version": "16.0.0",
+      "resolved": "https://registry.npmjs.org/react/-/react-16.0.0.tgz",
       "integrity": "sha1-zn348ZQbA28Cssyp29DLHw6FXi0=",
       "requires": {
-        "fbjs": "https://registry.npmjs.org/fbjs/-/fbjs-0.8.16.tgz",
-        "loose-envify": "https://registry.npmjs.org/loose-envify/-/loose-envify-1.3.1.tgz",
-        "object-assign": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz",
-        "prop-types": "https://registry.npmjs.org/prop-types/-/prop-types-15.6.0.tgz"
+        "fbjs": "0.8.16",
+        "loose-envify": "1.3.1",
+        "object-assign": "4.1.1",
+        "prop-types": "15.6.0"
+      },
+      "dependencies": {
+        "asap": {
+          "version": "2.0.6",
+          "resolved": "https://registry.npmjs.org/asap/-/asap-2.0.6.tgz",
+          "integrity": "sha1-5QNHYR1+aQlDIIu9r+vLwvuGbUY="
+        },
+        "core-js": {
+          "version": "1.2.7",
+          "resolved": "https://registry.npmjs.org/core-js/-/core-js-1.2.7.tgz",
+          "integrity": "sha1-ZSKUwUZR2yj6k70tX/KYOk8IxjY="
+        },
+        "encoding": {
+          "version": "0.1.12",
+          "resolved": "https://registry.npmjs.org/encoding/-/encoding-0.1.12.tgz",
+          "integrity": "sha1-U4tm8+5izRq1HsMjgp0flIDHS+s=",
+          "requires": {
+            "iconv-lite": "0.4.19"
+          }
+        },
+        "fbjs": {
+          "version": "0.8.16",
+          "resolved": "https://registry.npmjs.org/fbjs/-/fbjs-0.8.16.tgz",
+          "integrity": "sha1-XmdDL1UNxBtXK/VYR7ispk5TN9s=",
+          "requires": {
+            "core-js": "1.2.7",
+            "isomorphic-fetch": "2.2.1",
+            "loose-envify": "1.3.1",
+            "object-assign": "4.1.1",
+            "promise": "7.3.1",
+            "setimmediate": "1.0.5",
+            "ua-parser-js": "0.7.17"
+          }
+        },
+        "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=="
+        },
+        "is-stream": {
+          "version": "1.1.0",
+          "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-1.1.0.tgz",
+          "integrity": "sha1-EtSj3U5o4Lec6428hBc66A2RykQ="
+        },
+        "isomorphic-fetch": {
+          "version": "2.2.1",
+          "resolved": "https://registry.npmjs.org/isomorphic-fetch/-/isomorphic-fetch-2.2.1.tgz",
+          "integrity": "sha1-YRrhrPFPXoH3KVB0coGf6XM1WKk=",
+          "requires": {
+            "node-fetch": "1.7.3",
+            "whatwg-fetch": "2.0.3"
+          }
+        },
+        "js-tokens": {
+          "version": "3.0.2",
+          "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-3.0.2.tgz",
+          "integrity": "sha1-mGbfOVECEw449/mWvOtlRDIJwls="
+        },
+        "loose-envify": {
+          "version": "1.3.1",
+          "resolved": "https://registry.npmjs.org/loose-envify/-/loose-envify-1.3.1.tgz",
+          "integrity": "sha1-0aitM/qc4OcT1l/dCsi3SNR4yEg=",
+          "requires": {
+            "js-tokens": "3.0.2"
+          }
+        },
+        "node-fetch": {
+          "version": "1.7.3",
+          "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-1.7.3.tgz",
+          "integrity": "sha512-NhZ4CsKx7cYm2vSrBAr2PvFOe6sWDf0UYLRqA6svUYg7+/TSfVAu49jYC4BvQ4Sms9SZgdqGBgroqfDhJdTyKQ==",
+          "requires": {
+            "encoding": "0.1.12",
+            "is-stream": "1.1.0"
+          }
+        },
+        "object-assign": {
+          "version": "4.1.1",
+          "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz",
+          "integrity": "sha1-IQmtx5ZYh8/AXLvUQsrIv7s2CGM="
+        },
+        "promise": {
+          "version": "7.3.1",
+          "resolved": "https://registry.npmjs.org/promise/-/promise-7.3.1.tgz",
+          "integrity": "sha512-nolQXZ/4L+bP/UGlkfaIujX9BKxGwmQ9OT4mOt5yvy8iK1h3wqTEJCijzGANTCCl9nWjY41juyAn2K3Q1hLLTg==",
+          "requires": {
+            "asap": "2.0.6"
+          }
+        },
+        "setimmediate": {
+          "version": "1.0.5",
+          "resolved": "https://registry.npmjs.org/setimmediate/-/setimmediate-1.0.5.tgz",
+          "integrity": "sha1-KQy7Iy4waULX1+qbg3Mqt4VvgoU="
+        },
+        "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=="
+        },
+        "whatwg-fetch": {
+          "version": "2.0.3",
+          "resolved": "https://registry.npmjs.org/whatwg-fetch/-/whatwg-fetch-2.0.3.tgz",
+          "integrity": "sha1-nITsLc9oGH/wC8ZOEnS0QhduHIQ="
+        }
       }
     },
     "react-autosize-textarea": {
-      "version": "https://registry.npmjs.org/react-autosize-textarea/-/react-autosize-textarea-0.4.9.tgz",
+      "version": "0.4.9",
+      "resolved": "https://registry.npmjs.org/react-autosize-textarea/-/react-autosize-textarea-0.4.9.tgz",
       "integrity": "sha1-jVXIX0xmWm1jWehK8oYQnFBKsps=",
       "requires": {
-        "autosize": "https://registry.npmjs.org/autosize/-/autosize-3.0.21.tgz",
-        "line-height": "https://registry.npmjs.org/line-height/-/line-height-0.3.1.tgz",
-        "prop-types": "https://registry.npmjs.org/prop-types/-/prop-types-15.6.0.tgz"
+        "autosize": "3.0.21",
+        "line-height": "0.3.1",
+        "prop-types": "15.6.0"
+      },
+      "dependencies": {
+        "autosize": {
+          "version": "3.0.21",
+          "resolved": "https://registry.npmjs.org/autosize/-/autosize-3.0.21.tgz",
+          "integrity": "sha1-8YL0DRd1fZeKE5pMnKQMTA5EhgM="
+        },
+        "computed-style": {
+          "version": "0.1.4",
+          "resolved": "https://registry.npmjs.org/computed-style/-/computed-style-0.1.4.tgz",
+          "integrity": "sha1-fzRP2FhLLkJb7cpKGvwOMAuwXXQ="
+        },
+        "line-height": {
+          "version": "0.3.1",
+          "resolved": "https://registry.npmjs.org/line-height/-/line-height-0.3.1.tgz",
+          "integrity": "sha1-SxIF7d4YKHKl76PI9iCzGHqcVMk=",
+          "requires": {
+            "computed-style": "0.1.4"
+          }
+        }
       }
     },
     "react-color": {
-      "version": "https://registry.npmjs.org/react-color/-/react-color-2.13.8.tgz",
+      "version": "2.13.8",
+      "resolved": "https://registry.npmjs.org/react-color/-/react-color-2.13.8.tgz",
       "integrity": "sha1-vMWPeaciub/DfEAuaM0Y8mlwruQ=",
       "requires": {
-        "lodash": "https://registry.npmjs.org/lodash/-/lodash-4.17.4.tgz",
-        "material-colors": "https://registry.npmjs.org/material-colors/-/material-colors-1.2.5.tgz",
-        "prop-types": "https://registry.npmjs.org/prop-types/-/prop-types-15.6.0.tgz",
-        "reactcss": "https://registry.npmjs.org/reactcss/-/reactcss-1.2.3.tgz",
-        "tinycolor2": "https://registry.npmjs.org/tinycolor2/-/tinycolor2-1.4.1.tgz"
+        "lodash": "4.17.4",
+        "material-colors": "1.2.5",
+        "prop-types": "15.6.0",
+        "reactcss": "1.2.3",
+        "tinycolor2": "1.4.1"
+      },
+      "dependencies": {
+        "material-colors": {
+          "version": "1.2.5",
+          "resolved": "https://registry.npmjs.org/material-colors/-/material-colors-1.2.5.tgz",
+          "integrity": "sha1-UpJZPmdUyxvMK5gDDk4Najr8nqE="
+        },
+        "reactcss": {
+          "version": "1.2.3",
+          "resolved": "https://registry.npmjs.org/reactcss/-/reactcss-1.2.3.tgz",
+          "integrity": "sha512-KiwVUcFu1RErkI97ywr8nvx8dNOpT03rbnma0SSalTYjkrPYaEajR4a/MRt6DZ46K6arDRbWMNHF+xH7G7n/8A==",
+          "requires": {
+            "lodash": "4.17.4"
+          }
+        },
+        "tinycolor2": {
+          "version": "1.4.1",
+          "resolved": "https://registry.npmjs.org/tinycolor2/-/tinycolor2-1.4.1.tgz",
+          "integrity": "sha1-9PrTM0R7wLB9TcjpIJ2POaisd+g="
+        }
       }
     },
     "react-dom": {
-      "version": "https://registry.npmjs.org/react-dom/-/react-dom-16.0.0.tgz",
+      "version": "16.0.0",
+      "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-16.0.0.tgz",
       "integrity": "sha1-nMMHnD3NcNTG4BuEqrKn40wwP1g=",
       "requires": {
-        "fbjs": "https://registry.npmjs.org/fbjs/-/fbjs-0.8.16.tgz",
-        "loose-envify": "https://registry.npmjs.org/loose-envify/-/loose-envify-1.3.1.tgz",
-        "object-assign": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz",
-        "prop-types": "https://registry.npmjs.org/prop-types/-/prop-types-15.6.0.tgz"
+        "fbjs": "0.8.16",
+        "loose-envify": "1.3.1",
+        "object-assign": "4.1.1",
+        "prop-types": "15.6.0"
+      },
+      "dependencies": {
+        "asap": {
+          "version": "2.0.6",
+          "resolved": "https://registry.npmjs.org/asap/-/asap-2.0.6.tgz",
+          "integrity": "sha1-5QNHYR1+aQlDIIu9r+vLwvuGbUY="
+        },
+        "core-js": {
+          "version": "1.2.7",
+          "resolved": "https://registry.npmjs.org/core-js/-/core-js-1.2.7.tgz",
+          "integrity": "sha1-ZSKUwUZR2yj6k70tX/KYOk8IxjY="
+        },
+        "encoding": {
+          "version": "0.1.12",
+          "resolved": "https://registry.npmjs.org/encoding/-/encoding-0.1.12.tgz",
+          "integrity": "sha1-U4tm8+5izRq1HsMjgp0flIDHS+s=",
+          "requires": {
+            "iconv-lite": "0.4.19"
+          }
+        },
+        "fbjs": {
+          "version": "0.8.16",
+          "resolved": "https://registry.npmjs.org/fbjs/-/fbjs-0.8.16.tgz",
+          "integrity": "sha1-XmdDL1UNxBtXK/VYR7ispk5TN9s=",
+          "requires": {
+            "core-js": "1.2.7",
+            "isomorphic-fetch": "2.2.1",
+            "loose-envify": "1.3.1",
+            "object-assign": "4.1.1",
+            "promise": "7.3.1",
+            "setimmediate": "1.0.5",
+            "ua-parser-js": "0.7.17"
+          }
+        },
+        "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=="
+        },
+        "is-stream": {
+          "version": "1.1.0",
+          "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-1.1.0.tgz",
+          "integrity": "sha1-EtSj3U5o4Lec6428hBc66A2RykQ="
+        },
+        "isomorphic-fetch": {
+          "version": "2.2.1",
+          "resolved": "https://registry.npmjs.org/isomorphic-fetch/-/isomorphic-fetch-2.2.1.tgz",
+          "integrity": "sha1-YRrhrPFPXoH3KVB0coGf6XM1WKk=",
+          "requires": {
+            "node-fetch": "1.7.3",
+            "whatwg-fetch": "2.0.3"
+          }
+        },
+        "js-tokens": {
+          "version": "3.0.2",
+          "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-3.0.2.tgz",
+          "integrity": "sha1-mGbfOVECEw449/mWvOtlRDIJwls="
+        },
+        "loose-envify": {
+          "version": "1.3.1",
+          "resolved": "https://registry.npmjs.org/loose-envify/-/loose-envify-1.3.1.tgz",
+          "integrity": "sha1-0aitM/qc4OcT1l/dCsi3SNR4yEg=",
+          "requires": {
+            "js-tokens": "3.0.2"
+          }
+        },
+        "node-fetch": {
+          "version": "1.7.3",
+          "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-1.7.3.tgz",
+          "integrity": "sha512-NhZ4CsKx7cYm2vSrBAr2PvFOe6sWDf0UYLRqA6svUYg7+/TSfVAu49jYC4BvQ4Sms9SZgdqGBgroqfDhJdTyKQ==",
+          "requires": {
+            "encoding": "0.1.12",
+            "is-stream": "1.1.0"
+          }
+        },
+        "object-assign": {
+          "version": "4.1.1",
+          "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz",
+          "integrity": "sha1-IQmtx5ZYh8/AXLvUQsrIv7s2CGM="
+        },
+        "promise": {
+          "version": "7.3.1",
+          "resolved": "https://registry.npmjs.org/promise/-/promise-7.3.1.tgz",
+          "integrity": "sha512-nolQXZ/4L+bP/UGlkfaIujX9BKxGwmQ9OT4mOt5yvy8iK1h3wqTEJCijzGANTCCl9nWjY41juyAn2K3Q1hLLTg==",
+          "requires": {
+            "asap": "2.0.6"
+          }
+        },
+        "setimmediate": {
+          "version": "1.0.5",
+          "resolved": "https://registry.npmjs.org/setimmediate/-/setimmediate-1.0.5.tgz",
+          "integrity": "sha1-KQy7Iy4waULX1+qbg3Mqt4VvgoU="
+        },
+        "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=="
+        },
+        "whatwg-fetch": {
+          "version": "2.0.3",
+          "resolved": "https://registry.npmjs.org/whatwg-fetch/-/whatwg-fetch-2.0.3.tgz",
+          "integrity": "sha1-nITsLc9oGH/wC8ZOEnS0QhduHIQ="
+        }
       }
     },
     "react-dropzone": {
-      "version": "https://registry.npmjs.org/react-dropzone/-/react-dropzone-4.2.1.tgz",
-      "integrity": "sha1-aV6AvQsGXxGB5p8tD20dXMcmZMk=",
+      "version": "4.2.3",
+      "resolved": "https://registry.npmjs.org/react-dropzone/-/react-dropzone-4.2.3.tgz",
+      "integrity": "sha512-QAXuGDqBUPC0p560pskC3yyS8I1jJUnzvZC0PHrd5NayYBQRD4poQfM1D/bxg4jhUaFU4avNhOB3ehMQd4JMvA==",
       "requires": {
-        "attr-accept": "https://registry.npmjs.org/attr-accept/-/attr-accept-1.1.0.tgz",
-        "prop-types": "https://registry.npmjs.org/prop-types/-/prop-types-15.6.0.tgz"
+        "attr-accept": "1.1.0",
+        "prop-types": "15.6.0"
       }
     },
     "react-intl": {
-      "version": "https://registry.npmjs.org/react-intl/-/react-intl-2.4.0.tgz",
+      "version": "2.4.0",
+      "resolved": "https://registry.npmjs.org/react-intl/-/react-intl-2.4.0.tgz",
       "integrity": "sha1-ZsFNyd+ac7L7v71gIXJugKYT6xU=",
       "requires": {
-        "intl-format-cache": "https://registry.npmjs.org/intl-format-cache/-/intl-format-cache-2.0.5.tgz",
-        "intl-messageformat": "https://registry.npmjs.org/intl-messageformat/-/intl-messageformat-2.2.0.tgz",
-        "intl-relativeformat": "https://registry.npmjs.org/intl-relativeformat/-/intl-relativeformat-2.1.0.tgz",
-        "invariant": "https://registry.npmjs.org/invariant/-/invariant-2.2.2.tgz"
+        "intl-format-cache": "2.1.0",
+        "intl-messageformat": "2.2.0",
+        "intl-relativeformat": "2.1.0",
+        "invariant": "2.2.2"
+      },
+      "dependencies": {
+        "intl-format-cache": {
+          "version": "2.1.0",
+          "resolved": "https://registry.npmjs.org/intl-format-cache/-/intl-format-cache-2.1.0.tgz",
+          "integrity": "sha1-BKNp/sv61tpgBbrh8UMzMy3PkxY="
+        },
+        "intl-messageformat": {
+          "version": "2.2.0",
+          "resolved": "https://registry.npmjs.org/intl-messageformat/-/intl-messageformat-2.2.0.tgz",
+          "integrity": "sha1-NFvNRt5jC3aDMwwuUhd/9eq0hPw=",
+          "requires": {
+            "intl-messageformat-parser": "1.4.0"
+          }
+        },
+        "intl-messageformat-parser": {
+          "version": "1.4.0",
+          "resolved": "https://registry.npmjs.org/intl-messageformat-parser/-/intl-messageformat-parser-1.4.0.tgz",
+          "integrity": "sha1-tD1FqXRoytvkQzHXS7Ho3qRPwHU="
+        },
+        "intl-relativeformat": {
+          "version": "2.1.0",
+          "resolved": "https://registry.npmjs.org/intl-relativeformat/-/intl-relativeformat-2.1.0.tgz",
+          "integrity": "sha1-AQ8RBYAiUfQKxH0OPhogE0iiVd8=",
+          "requires": {
+            "intl-messageformat": "2.2.0"
+          }
+        },
+        "invariant": {
+          "version": "2.2.2",
+          "resolved": "https://registry.npmjs.org/invariant/-/invariant-2.2.2.tgz",
+          "integrity": "sha1-nh9WrArNtr8wMwbzOL47IErmA2A=",
+          "requires": {
+            "loose-envify": "1.3.1"
+          }
+        },
+        "js-tokens": {
+          "version": "3.0.2",
+          "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-3.0.2.tgz",
+          "integrity": "sha1-mGbfOVECEw449/mWvOtlRDIJwls="
+        },
+        "loose-envify": {
+          "version": "1.3.1",
+          "resolved": "https://registry.npmjs.org/loose-envify/-/loose-envify-1.3.1.tgz",
+          "integrity": "sha1-0aitM/qc4OcT1l/dCsi3SNR4yEg=",
+          "requires": {
+            "js-tokens": "3.0.2"
+          }
+        }
       }
     },
     "react-modal": {
-      "version": "https://registry.npmjs.org/react-modal/-/react-modal-3.0.4.tgz",
-      "integrity": "sha1-YfPJpwGyfSAV/lrAxBnjyXfkCSo=",
+      "version": "3.0.4",
+      "resolved": "https://registry.npmjs.org/react-modal/-/react-modal-3.0.4.tgz",
+      "integrity": "sha512-IvRZxJkXiDqEIl4cxCccCzP37z+YOSN+yNOkYH99Ime+n9nPSowgxkX0KfHzR2ezP72LSS3Uln54JDZXUJmLdA==",
       "requires": {
-        "exenv": "https://registry.npmjs.org/exenv/-/exenv-1.2.2.tgz",
-        "prop-types": "https://registry.npmjs.org/prop-types/-/prop-types-15.6.0.tgz"
+        "exenv": "1.2.2",
+        "prop-types": "15.6.0"
+      },
+      "dependencies": {
+        "exenv": {
+          "version": "1.2.2",
+          "resolved": "https://registry.npmjs.org/exenv/-/exenv-1.2.2.tgz",
+          "integrity": "sha1-KueOhdmJQVhnCwPUe+wfA72Ru50="
+        }
       }
     },
     "react-router": {
-      "version": "https://registry.npmjs.org/react-router/-/react-router-3.0.5.tgz",
+      "version": "3.0.5",
+      "resolved": "https://registry.npmjs.org/react-router/-/react-router-3.0.5.tgz",
       "integrity": "sha1-w7eHN1gEWou8lWKu9P9LyMznwTY=",
       "requires": {
-        "create-react-class": "https://registry.npmjs.org/create-react-class/-/create-react-class-15.6.2.tgz",
-        "history": "https://registry.npmjs.org/history/-/history-3.3.0.tgz",
-        "hoist-non-react-statics": "https://registry.npmjs.org/hoist-non-react-statics/-/hoist-non-react-statics-1.2.0.tgz",
-        "invariant": "https://registry.npmjs.org/invariant/-/invariant-2.2.2.tgz",
-        "loose-envify": "https://registry.npmjs.org/loose-envify/-/loose-envify-1.3.1.tgz",
-        "prop-types": "https://registry.npmjs.org/prop-types/-/prop-types-15.6.0.tgz",
-        "warning": "https://registry.npmjs.org/warning/-/warning-3.0.0.tgz"
+        "create-react-class": "15.6.2",
+        "history": "3.3.0",
+        "hoist-non-react-statics": "1.2.0",
+        "invariant": "2.2.2",
+        "loose-envify": "1.3.1",
+        "prop-types": "15.6.0",
+        "warning": "3.0.0"
+      },
+      "dependencies": {
+        "asap": {
+          "version": "2.0.6",
+          "resolved": "https://registry.npmjs.org/asap/-/asap-2.0.6.tgz",
+          "integrity": "sha1-5QNHYR1+aQlDIIu9r+vLwvuGbUY="
+        },
+        "core-js": {
+          "version": "1.2.7",
+          "resolved": "https://registry.npmjs.org/core-js/-/core-js-1.2.7.tgz",
+          "integrity": "sha1-ZSKUwUZR2yj6k70tX/KYOk8IxjY="
+        },
+        "create-react-class": {
+          "version": "15.6.2",
+          "resolved": "https://registry.npmjs.org/create-react-class/-/create-react-class-15.6.2.tgz",
+          "integrity": "sha1-zx7RXxKq1/FO9fLf4F5sQvke8Co=",
+          "requires": {
+            "fbjs": "0.8.16",
+            "loose-envify": "1.3.1",
+            "object-assign": "4.1.1"
+          }
+        },
+        "encoding": {
+          "version": "0.1.12",
+          "resolved": "https://registry.npmjs.org/encoding/-/encoding-0.1.12.tgz",
+          "integrity": "sha1-U4tm8+5izRq1HsMjgp0flIDHS+s=",
+          "requires": {
+            "iconv-lite": "0.4.19"
+          }
+        },
+        "fbjs": {
+          "version": "0.8.16",
+          "resolved": "https://registry.npmjs.org/fbjs/-/fbjs-0.8.16.tgz",
+          "integrity": "sha1-XmdDL1UNxBtXK/VYR7ispk5TN9s=",
+          "requires": {
+            "core-js": "1.2.7",
+            "isomorphic-fetch": "2.2.1",
+            "loose-envify": "1.3.1",
+            "object-assign": "4.1.1",
+            "promise": "7.3.1",
+            "setimmediate": "1.0.5",
+            "ua-parser-js": "0.7.17"
+          }
+        },
+        "hoist-non-react-statics": {
+          "version": "1.2.0",
+          "resolved": "https://registry.npmjs.org/hoist-non-react-statics/-/hoist-non-react-statics-1.2.0.tgz",
+          "integrity": "sha1-qkSM8JhtVcxAdzsXF0t90GbLfPs="
+        },
+        "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=="
+        },
+        "invariant": {
+          "version": "2.2.2",
+          "resolved": "https://registry.npmjs.org/invariant/-/invariant-2.2.2.tgz",
+          "integrity": "sha1-nh9WrArNtr8wMwbzOL47IErmA2A=",
+          "requires": {
+            "loose-envify": "1.3.1"
+          }
+        },
+        "is-stream": {
+          "version": "1.1.0",
+          "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-1.1.0.tgz",
+          "integrity": "sha1-EtSj3U5o4Lec6428hBc66A2RykQ="
+        },
+        "isomorphic-fetch": {
+          "version": "2.2.1",
+          "resolved": "https://registry.npmjs.org/isomorphic-fetch/-/isomorphic-fetch-2.2.1.tgz",
+          "integrity": "sha1-YRrhrPFPXoH3KVB0coGf6XM1WKk=",
+          "requires": {
+            "node-fetch": "1.7.3",
+            "whatwg-fetch": "2.0.3"
+          }
+        },
+        "js-tokens": {
+          "version": "3.0.2",
+          "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-3.0.2.tgz",
+          "integrity": "sha1-mGbfOVECEw449/mWvOtlRDIJwls="
+        },
+        "loose-envify": {
+          "version": "1.3.1",
+          "resolved": "https://registry.npmjs.org/loose-envify/-/loose-envify-1.3.1.tgz",
+          "integrity": "sha1-0aitM/qc4OcT1l/dCsi3SNR4yEg=",
+          "requires": {
+            "js-tokens": "3.0.2"
+          }
+        },
+        "node-fetch": {
+          "version": "1.7.3",
+          "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-1.7.3.tgz",
+          "integrity": "sha512-NhZ4CsKx7cYm2vSrBAr2PvFOe6sWDf0UYLRqA6svUYg7+/TSfVAu49jYC4BvQ4Sms9SZgdqGBgroqfDhJdTyKQ==",
+          "requires": {
+            "encoding": "0.1.12",
+            "is-stream": "1.1.0"
+          }
+        },
+        "object-assign": {
+          "version": "4.1.1",
+          "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz",
+          "integrity": "sha1-IQmtx5ZYh8/AXLvUQsrIv7s2CGM="
+        },
+        "promise": {
+          "version": "7.3.1",
+          "resolved": "https://registry.npmjs.org/promise/-/promise-7.3.1.tgz",
+          "integrity": "sha512-nolQXZ/4L+bP/UGlkfaIujX9BKxGwmQ9OT4mOt5yvy8iK1h3wqTEJCijzGANTCCl9nWjY41juyAn2K3Q1hLLTg==",
+          "requires": {
+            "asap": "2.0.6"
+          }
+        },
+        "setimmediate": {
+          "version": "1.0.5",
+          "resolved": "https://registry.npmjs.org/setimmediate/-/setimmediate-1.0.5.tgz",
+          "integrity": "sha1-KQy7Iy4waULX1+qbg3Mqt4VvgoU="
+        },
+        "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=="
+        },
+        "warning": {
+          "version": "3.0.0",
+          "resolved": "https://registry.npmjs.org/warning/-/warning-3.0.0.tgz",
+          "integrity": "sha1-MuU3fLVy3kqwR1O9+IIcAe1gW3w=",
+          "requires": {
+            "loose-envify": "1.3.1"
+          }
+        },
+        "whatwg-fetch": {
+          "version": "2.0.3",
+          "resolved": "https://registry.npmjs.org/whatwg-fetch/-/whatwg-fetch-2.0.3.tgz",
+          "integrity": "sha1-nITsLc9oGH/wC8ZOEnS0QhduHIQ="
+        }
       }
     },
     "react-tabs": {
-      "version": "https://registry.npmjs.org/react-tabs/-/react-tabs-2.1.0.tgz",
-      "integrity": "sha1-uhhKUZ4KCAPPeQoesZvE/bpf0Oo=",
+      "version": "2.1.1",
+      "resolved": "https://registry.npmjs.org/react-tabs/-/react-tabs-2.1.1.tgz",
+      "integrity": "sha512-55jl6lsYmPTQarnjgrBU68WZlNtVSngpRxOc4iXm+Te27F9ixUr/IBTbhlhDCMiFJreP+cqu1OaMdNGY2Hg10A==",
       "requires": {
-        "classnames": "https://registry.npmjs.org/classnames/-/classnames-2.2.5.tgz",
-        "prop-types": "https://registry.npmjs.org/prop-types/-/prop-types-15.6.0.tgz"
+        "classnames": "2.2.5",
+        "prop-types": "15.6.0"
       }
     },
     "react-toastify": {
-      "version": "https://registry.npmjs.org/react-toastify/-/react-toastify-2.1.6.tgz",
-      "integrity": "sha1-Gkh/rSekjZ6u9FaDXpVevnmxp5A=",
+      "version": "2.1.7",
+      "resolved": "https://registry.npmjs.org/react-toastify/-/react-toastify-2.1.7.tgz",
+      "integrity": "sha512-iMS5wXiTDKXcWTIF055BmsSwJINcxs9+CUGeEPMSllU0I00IKfV2inb3xhRxrB7d+4QvPqWZAtDPTk6nz6o1nA==",
       "requires": {
-        "prop-types": "https://registry.npmjs.org/prop-types/-/prop-types-15.6.0.tgz",
-        "react-transition-group": "https://registry.npmjs.org/react-transition-group/-/react-transition-group-2.2.1.tgz"
+        "prop-types": "15.6.0",
+        "react-transition-group": "2.2.1"
       }
     },
     "react-toggle": {
-      "version": "https://registry.npmjs.org/react-toggle/-/react-toggle-4.0.2.tgz",
-      "integrity": "sha1-d/SHhg77h/r9GXZyotuMiFvhRA8=",
+      "version": "4.0.2",
+      "resolved": "https://registry.npmjs.org/react-toggle/-/react-toggle-4.0.2.tgz",
+      "integrity": "sha512-EPTWnN7gQHgEAUEmjheanZXNzY5TPnQeyyHfEs3YshaiWZf5WNjfYDrglO5F1Hl/dNveX18i4l0grTEsYH2Ccw==",
       "requires": {
-        "classnames": "https://registry.npmjs.org/classnames/-/classnames-2.2.5.tgz"
+        "classnames": "2.2.5"
       }
     },
     "react-transition-group": {
-      "version": "https://registry.npmjs.org/react-transition-group/-/react-transition-group-2.2.1.tgz",
-      "integrity": "sha1-6ftne3nmRV/TkbA4I6/oSEnfShA=",
-      "requires": {
-        "chain-function": "https://registry.npmjs.org/chain-function/-/chain-function-1.0.0.tgz",
-        "classnames": "https://registry.npmjs.org/classnames/-/classnames-2.2.5.tgz",
-        "dom-helpers": "https://registry.npmjs.org/dom-helpers/-/dom-helpers-3.2.1.tgz",
-        "loose-envify": "https://registry.npmjs.org/loose-envify/-/loose-envify-1.3.1.tgz",
-        "prop-types": "https://registry.npmjs.org/prop-types/-/prop-types-15.6.0.tgz",
-        "warning": "https://registry.npmjs.org/warning/-/warning-3.0.0.tgz"
-      }
-    },
-    "reactcss": {
-      "version": "https://registry.npmjs.org/reactcss/-/reactcss-1.2.3.tgz",
-      "integrity": "sha1-wAATh15Vexzw39mjaKHD2rO1SN0=",
-      "requires": {
-        "lodash": "https://registry.npmjs.org/lodash/-/lodash-4.17.4.tgz"
-      }
-    },
-    "read-pkg": {
-      "version": "https://registry.npmjs.org/read-pkg/-/read-pkg-1.1.0.tgz",
-      "integrity": "sha1-9f+qXs0pyzHAR0vKfXVra7KePyg=",
-      "requires": {
-        "load-json-file": "https://registry.npmjs.org/load-json-file/-/load-json-file-1.1.0.tgz",
-        "normalize-package-data": "https://registry.npmjs.org/normalize-package-data/-/normalize-package-data-2.4.0.tgz",
-        "path-type": "https://registry.npmjs.org/path-type/-/path-type-1.1.0.tgz"
-      }
-    },
-    "read-pkg-up": {
-      "version": "https://registry.npmjs.org/read-pkg-up/-/read-pkg-up-1.0.1.tgz",
-      "integrity": "sha1-nWPBMnbAZZGNV/ACpX9AobZD+wI=",
-      "requires": {
-        "find-up": "https://registry.npmjs.org/find-up/-/find-up-1.1.2.tgz",
-        "read-pkg": "https://registry.npmjs.org/read-pkg/-/read-pkg-1.1.0.tgz"
-      }
-    },
-    "readable-stream": {
-      "version": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.3.tgz",
-      "integrity": "sha1-No8lEtefnUb9/HE0mueHi7weuVw=",
-      "requires": {
-        "core-util-is": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.2.tgz",
-        "inherits": "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz",
-        "isarray": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz",
-        "process-nextick-args": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-1.0.7.tgz",
-        "safe-buffer": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.1.tgz",
-        "string_decoder": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.0.3.tgz",
-        "util-deprecate": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz"
-      }
-    },
-    "redent": {
-      "version": "https://registry.npmjs.org/redent/-/redent-1.0.0.tgz",
-      "integrity": "sha1-z5Fqsf1fHxbfsggi3W7H9zDCr94=",
-      "requires": {
-        "indent-string": "https://registry.npmjs.org/indent-string/-/indent-string-2.1.0.tgz",
-        "strip-indent": "https://registry.npmjs.org/strip-indent/-/strip-indent-1.0.1.tgz"
+      "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.3.1",
+        "loose-envify": "1.3.1",
+        "prop-types": "15.6.0",
+        "warning": "3.0.0"
+      },
+      "dependencies": {
+        "chain-function": {
+          "version": "1.0.0",
+          "resolved": "https://registry.npmjs.org/chain-function/-/chain-function-1.0.0.tgz",
+          "integrity": "sha1-DUqzfn4Y6tC9xHuSB2QRjOWHM9w="
+        },
+        "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=="
+        },
+        "js-tokens": {
+          "version": "3.0.2",
+          "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-3.0.2.tgz",
+          "integrity": "sha1-mGbfOVECEw449/mWvOtlRDIJwls="
+        },
+        "loose-envify": {
+          "version": "1.3.1",
+          "resolved": "https://registry.npmjs.org/loose-envify/-/loose-envify-1.3.1.tgz",
+          "integrity": "sha1-0aitM/qc4OcT1l/dCsi3SNR4yEg=",
+          "requires": {
+            "js-tokens": "3.0.2"
+          }
+        },
+        "warning": {
+          "version": "3.0.0",
+          "resolved": "https://registry.npmjs.org/warning/-/warning-3.0.0.tgz",
+          "integrity": "sha1-MuU3fLVy3kqwR1O9+IIcAe1gW3w=",
+          "requires": {
+            "loose-envify": "1.3.1"
+          }
+        }
       }
     },
     "redis": {
-      "version": "https://registry.npmjs.org/redis/-/redis-2.8.0.tgz",
-      "integrity": "sha1-ICKI4/WMSfYHnZevehDhMDrhSwI=",
-      "requires": {
-        "double-ended-queue": "https://registry.npmjs.org/double-ended-queue/-/double-ended-queue-2.1.0-0.tgz",
-        "redis-commands": "https://registry.npmjs.org/redis-commands/-/redis-commands-1.3.1.tgz",
-        "redis-parser": "https://registry.npmjs.org/redis-parser/-/redis-parser-2.6.0.tgz"
-      }
-    },
-    "redis-commands": {
-      "version": "https://registry.npmjs.org/redis-commands/-/redis-commands-1.3.1.tgz",
-      "integrity": "sha1-gdgm9F+pyLIBH0zXoP5ZfSQdRCs="
-    },
-    "redis-parser": {
-      "version": "https://registry.npmjs.org/redis-parser/-/redis-parser-2.6.0.tgz",
-      "integrity": "sha1-Uu0J2srBCPGmMcB+m2mUHnoZUEs="
-    },
-    "regenerate": {
-      "version": "https://registry.npmjs.org/regenerate/-/regenerate-1.3.3.tgz",
-      "integrity": "sha1-DDNtOYBVPXVcObWGrjsgqknIK38=",
-      "dev": true
-    },
-    "regenerator-runtime": {
-      "version": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.11.0.tgz",
-      "integrity": "sha1-flT+W1zNXWYk6mJVw0c74JC4AuE="
-    },
-    "regexpu-core": {
-      "version": "https://registry.npmjs.org/regexpu-core/-/regexpu-core-1.0.0.tgz",
-      "integrity": "sha1-hqdj9Y7k18L2sQLkdkBQ3n7ZDGs=",
-      "dev": true,
-      "requires": {
-        "regenerate": "https://registry.npmjs.org/regenerate/-/regenerate-1.3.3.tgz",
-        "regjsgen": "https://registry.npmjs.org/regjsgen/-/regjsgen-0.2.0.tgz",
-        "regjsparser": "https://registry.npmjs.org/regjsparser/-/regjsparser-0.1.5.tgz"
-      }
-    },
-    "regjsgen": {
-      "version": "https://registry.npmjs.org/regjsgen/-/regjsgen-0.2.0.tgz",
-      "integrity": "sha1-bAFq3qxVT3WCP+N6wFuS1aTtsfc=",
-      "dev": true
-    },
-    "regjsparser": {
-      "version": "https://registry.npmjs.org/regjsparser/-/regjsparser-0.1.5.tgz",
-      "integrity": "sha1-fuj4Tcb6eS0/0K4ijSS9lJ6tIFw=",
-      "dev": true,
-      "requires": {
-        "jsesc": "https://registry.npmjs.org/jsesc/-/jsesc-0.5.0.tgz"
-      }
-    },
-    "remove-trailing-separator": {
-      "version": "https://registry.npmjs.org/remove-trailing-separator/-/remove-trailing-separator-1.1.0.tgz",
-      "integrity": "sha1-wkvOKig62tW8P1jg1IJJuSN52O8=",
-      "dev": true
-    },
-    "repeating": {
-      "version": "https://registry.npmjs.org/repeating/-/repeating-2.0.1.tgz",
-      "integrity": "sha1-UhTFOpJtNVJwdSf7q0FdvAjQbdo=",
-      "requires": {
-        "is-finite": "https://registry.npmjs.org/is-finite/-/is-finite-1.0.2.tgz"
-      }
-    },
-    "request": {
-      "version": "https://registry.npmjs.org/request/-/request-2.83.0.tgz",
-      "integrity": "sha1-ygtl2gLtYpNYh4COb1EDgQNOM1Y=",
-      "requires": {
-        "aws-sign2": "https://registry.npmjs.org/aws-sign2/-/aws-sign2-0.7.0.tgz",
-        "aws4": "https://registry.npmjs.org/aws4/-/aws4-1.6.0.tgz",
-        "caseless": "https://registry.npmjs.org/caseless/-/caseless-0.12.0.tgz",
-        "combined-stream": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.5.tgz",
-        "extend": "https://registry.npmjs.org/extend/-/extend-3.0.1.tgz",
-        "forever-agent": "https://registry.npmjs.org/forever-agent/-/forever-agent-0.6.1.tgz",
-        "form-data": "https://registry.npmjs.org/form-data/-/form-data-2.3.1.tgz",
-        "har-validator": "https://registry.npmjs.org/har-validator/-/har-validator-5.0.3.tgz",
-        "hawk": "https://registry.npmjs.org/hawk/-/hawk-6.0.2.tgz",
-        "http-signature": "https://registry.npmjs.org/http-signature/-/http-signature-1.2.0.tgz",
-        "is-typedarray": "https://registry.npmjs.org/is-typedarray/-/is-typedarray-1.0.0.tgz",
-        "isstream": "https://registry.npmjs.org/isstream/-/isstream-0.1.2.tgz",
-        "json-stringify-safe": "https://registry.npmjs.org/json-stringify-safe/-/json-stringify-safe-5.0.1.tgz",
-        "mime-types": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.17.tgz",
-        "oauth-sign": "https://registry.npmjs.org/oauth-sign/-/oauth-sign-0.8.2.tgz",
-        "performance-now": "https://registry.npmjs.org/performance-now/-/performance-now-2.1.0.tgz",
-        "qs": "https://registry.npmjs.org/qs/-/qs-6.5.1.tgz",
-        "safe-buffer": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.1.tgz",
-        "stringstream": "https://registry.npmjs.org/stringstream/-/stringstream-0.0.5.tgz",
-        "tough-cookie": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-2.3.3.tgz",
-        "tunnel-agent": "https://registry.npmjs.org/tunnel-agent/-/tunnel-agent-0.6.0.tgz",
-        "uuid": "https://registry.npmjs.org/uuid/-/uuid-3.1.0.tgz"
-      }
-    },
-    "require-directory": {
-      "version": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz",
-      "integrity": "sha1-jGStX9MNqxyXbiNE/+f3kqam30I="
-    },
-    "require-from-string": {
-      "version": "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": "https://registry.npmjs.org/require-main-filename/-/require-main-filename-1.0.1.tgz",
-      "integrity": "sha1-l/cXtp1IeE9fUmpsWqj/3aBVpNE="
-    },
-    "require-uncached": {
-      "version": "https://registry.npmjs.org/require-uncached/-/require-uncached-1.0.3.tgz",
-      "integrity": "sha1-Tg1W1slmL9MeQwEcS5WqSZVUIdM=",
-      "dev": true,
-      "requires": {
-        "caller-path": "https://registry.npmjs.org/caller-path/-/caller-path-0.1.0.tgz",
-        "resolve-from": "https://registry.npmjs.org/resolve-from/-/resolve-from-1.0.1.tgz"
-      }
-    },
-    "resolve": {
-      "version": "https://registry.npmjs.org/resolve/-/resolve-1.5.0.tgz",
-      "integrity": "sha1-HwmsznlsmnYlefMbLBzEw83fnzY=",
-      "dev": true,
+      "version": "2.8.0",
+      "resolved": "https://registry.npmjs.org/redis/-/redis-2.8.0.tgz",
+      "integrity": "sha512-M1OkonEQwtRmZv4tEWF2VgpG0JWJ8Fv1PhlgT5+B+uNq2cA3Rt1Yt/ryoR+vQNOQcIEgdCdfH0jr3bDpihAw1A==",
       "requires": {
-        "path-parse": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.5.tgz"
-      }
-    },
-    "resolve-from": {
-      "version": "https://registry.npmjs.org/resolve-from/-/resolve-from-1.0.1.tgz",
-      "integrity": "sha1-Jsv+k10a7uq7Kbw/5a6wHpPUQiY=",
-      "dev": true
-    },
-    "resolve-url": {
-      "version": "https://registry.npmjs.org/resolve-url/-/resolve-url-0.2.1.tgz",
-      "integrity": "sha1-LGN/53yJOv0qZj/iGqkIAGjiBSo=",
-      "dev": true
-    },
-    "restore-cursor": {
-      "version": "https://registry.npmjs.org/restore-cursor/-/restore-cursor-2.0.0.tgz",
-      "integrity": "sha1-n37ih/gv0ybU/RYpI9YhKe7g368=",
-      "dev": true,
-      "requires": {
-        "onetime": "https://registry.npmjs.org/onetime/-/onetime-2.0.1.tgz",
-        "signal-exit": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.2.tgz"
+        "double-ended-queue": "2.1.0-0",
+        "redis-commands": "1.3.1",
+        "redis-parser": "2.6.0"
+      },
+      "dependencies": {
+        "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="
+        },
+        "redis-commands": {
+          "version": "1.3.1",
+          "resolved": "https://registry.npmjs.org/redis-commands/-/redis-commands-1.3.1.tgz",
+          "integrity": "sha1-gdgm9F+pyLIBH0zXoP5ZfSQdRCs="
+        },
+        "redis-parser": {
+          "version": "2.6.0",
+          "resolved": "https://registry.npmjs.org/redis-parser/-/redis-parser-2.6.0.tgz",
+          "integrity": "sha1-Uu0J2srBCPGmMcB+m2mUHnoZUEs="
+        }
       }
     },
-    "rgb2hex": {
-      "version": "https://registry.npmjs.org/rgb2hex/-/rgb2hex-0.1.0.tgz",
-      "integrity": "sha1-zNVfhgrgxcTqN1BLlY5ELY0SMls=",
-      "dev": true
-    },
-    "rimraf": {
-      "version": "https://registry.npmjs.org/rimraf/-/rimraf-2.6.2.tgz",
-      "integrity": "sha1-LtgVDSShbqhlHm1u8PR8QVjOejY=",
-      "requires": {
-        "glob": "https://registry.npmjs.org/glob/-/glob-7.1.2.tgz"
-      }
+    "string-hash": {
+      "version": "1.1.3",
+      "resolved": "https://registry.npmjs.org/string-hash/-/string-hash-1.1.3.tgz",
+      "integrity": "sha1-6Kr8CsGFW0Zmkp7X3RJ1311sgRs="
     },
-    "run-async": {
-      "version": "https://registry.npmjs.org/run-async/-/run-async-2.3.0.tgz",
-      "integrity": "sha1-A3GrSuC91yDUFm19/aZP96RFpsA=",
-      "dev": true,
+    "tippy.js": {
+      "version": "2.0.8",
+      "resolved": "https://registry.npmjs.org/tippy.js/-/tippy.js-2.0.8.tgz",
+      "integrity": "sha512-GbEV0iQHE9NS/J3W2OBTM3evYjJV4P88axZeB9E6iyJz8+h/m75fL8wg2OW/U0c98K7qnrgLZ0plPucBpULpDw==",
       "requires": {
-        "is-promise": "https://registry.npmjs.org/is-promise/-/is-promise-2.1.0.tgz"
+        "popper.js": "1.12.9"
       }
     },
-    "rx": {
-      "version": "https://registry.npmjs.org/rx/-/rx-4.1.0.tgz",
-      "integrity": "sha1-pfE/957zt0D+MKqAP7CfmIBdR4I=",
-      "dev": true
-    },
-    "rx-lite": {
-      "version": "https://registry.npmjs.org/rx-lite/-/rx-lite-4.0.8.tgz",
-      "integrity": "sha1-Cx4Rr4vESDbwSmQH6S2kJGe3lEQ=",
-      "dev": true
-    },
-    "rx-lite-aggregates": {
-      "version": "https://registry.npmjs.org/rx-lite-aggregates/-/rx-lite-aggregates-4.0.8.tgz",
-      "integrity": "sha1-dTuHqJoRyVRnxKwWJsTvxOBcZ74=",
+    "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": {
-        "rx-lite": "https://registry.npmjs.org/rx-lite/-/rx-lite-4.0.8.tgz"
+        "babel-runtime": "6.25.0",
+        "jasmine": "2.8.0",
+        "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.5.3",
+            "regenerator-runtime": "0.10.5"
+          }
+        },
+        "balanced-match": {
+          "version": "1.0.0",
+          "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.0.tgz",
+          "integrity": "sha1-ibTRmasr7kneFk6gK4nORi1xt2c=",
+          "dev": true
+        },
+        "brace-expansion": {
+          "version": "1.1.8",
+          "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.8.tgz",
+          "integrity": "sha1-wHshHHyVLsH479Uad+8NHTmQopI=",
+          "dev": true,
+          "requires": {
+            "balanced-match": "1.0.0",
+            "concat-map": "0.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=",
+          "dev": true
+        },
+        "core-js": {
+          "version": "2.5.3",
+          "resolved": "https://registry.npmjs.org/core-js/-/core-js-2.5.3.tgz",
+          "integrity": "sha1-isw4NFgk8W2DZbfJtCWRaOjtYD4=",
+          "dev": true
+        },
+        "define-properties": {
+          "version": "1.1.2",
+          "resolved": "https://registry.npmjs.org/define-properties/-/define-properties-1.1.2.tgz",
+          "integrity": "sha1-g6c/L+pWmJj7c3GTyPhzyvbUXJQ=",
+          "dev": true,
+          "requires": {
+            "foreach": "2.0.5",
+            "object-keys": "1.0.11"
+          }
+        },
+        "exit": {
+          "version": "0.1.2",
+          "resolved": "https://registry.npmjs.org/exit/-/exit-0.1.2.tgz",
+          "integrity": "sha1-BjJjj42HfMghB9MKD/8aF8uhzQw=",
+          "dev": true
+        },
+        "fibers": {
+          "version": "2.0.0",
+          "resolved": "https://registry.npmjs.org/fibers/-/fibers-2.0.0.tgz",
+          "integrity": "sha512-sLxo4rZVk7xLgAjb/6zEzHJfSALx6u6coN1z61XCOF7i6CyTdJawF4+RdpjCSeS8AP66eR2InScbYAz9RAVOgA==",
+          "dev": true
+        },
+        "foreach": {
+          "version": "2.0.5",
+          "resolved": "https://registry.npmjs.org/foreach/-/foreach-2.0.5.tgz",
+          "integrity": "sha1-C+4AUBiusmDQo6865ljdATbsG5k=",
+          "dev": true
+        },
+        "fs.realpath": {
+          "version": "1.0.0",
+          "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz",
+          "integrity": "sha1-FQStJSMVjKpA20onh8sBQRmU6k8=",
+          "dev": true
+        },
+        "function-bind": {
+          "version": "1.1.1",
+          "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.1.tgz",
+          "integrity": "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==",
+          "dev": true
+        },
+        "glob": {
+          "version": "7.1.2",
+          "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.2.tgz",
+          "integrity": "sha512-MJTUg1kjuLeQCJ+ccE4Vpa6kKVXkPYJ2mOCQyUuKLcLQsdrMCpBPUi8qVE6+YuaJkozeA9NusTAw3hLr8Xe5EQ==",
+          "dev": true,
+          "requires": {
+            "fs.realpath": "1.0.0",
+            "inflight": "1.0.6",
+            "inherits": "2.0.3",
+            "minimatch": "3.0.4",
+            "once": "1.4.0",
+            "path-is-absolute": "1.0.1"
+          }
+        },
+        "inflight": {
+          "version": "1.0.6",
+          "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz",
+          "integrity": "sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk=",
+          "dev": true,
+          "requires": {
+            "once": "1.4.0",
+            "wrappy": "1.0.2"
+          }
+        },
+        "inherits": {
+          "version": "2.0.3",
+          "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz",
+          "integrity": "sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4=",
+          "dev": true
+        },
+        "jasmine": {
+          "version": "2.8.0",
+          "resolved": "https://registry.npmjs.org/jasmine/-/jasmine-2.8.0.tgz",
+          "integrity": "sha1-awicChFXax8W3xG4AUbZHU6Lij4=",
+          "dev": true,
+          "requires": {
+            "exit": "0.1.2",
+            "glob": "7.1.2",
+            "jasmine-core": "2.8.0"
+          }
+        },
+        "jasmine-core": {
+          "version": "2.8.0",
+          "resolved": "https://registry.npmjs.org/jasmine-core/-/jasmine-core-2.8.0.tgz",
+          "integrity": "sha1-vMl5rh+f0FcB5F5S5l06XWPxok4=",
+          "dev": true
+        },
+        "minimatch": {
+          "version": "3.0.4",
+          "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz",
+          "integrity": "sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==",
+          "dev": true,
+          "requires": {
+            "brace-expansion": "1.1.8"
+          }
+        },
+        "object-keys": {
+          "version": "1.0.11",
+          "resolved": "https://registry.npmjs.org/object-keys/-/object-keys-1.0.11.tgz",
+          "integrity": "sha1-xUYBd4rVYPEULODgG8yotW0TQm0=",
+          "dev": true
+        },
+        "object.assign": {
+          "version": "4.1.0",
+          "resolved": "https://registry.npmjs.org/object.assign/-/object.assign-4.1.0.tgz",
+          "integrity": "sha512-exHJeq6kBKj58mqGyTQ9DFvrZC/eR6OwxzoM9YRoGBqrXYonaFyGiFMuc9VZrXf7DarreEwMpurG3dd+CNyW5w==",
+          "dev": true,
+          "requires": {
+            "define-properties": "1.1.2",
+            "function-bind": "1.1.1",
+            "has-symbols": "1.0.0",
+            "object-keys": "1.0.11"
+          }
+        },
+        "once": {
+          "version": "1.4.0",
+          "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz",
+          "integrity": "sha1-WDsap3WWHUsROsF9nFC6753Xa9E=",
+          "dev": true,
+          "requires": {
+            "wrappy": "1.0.2"
+          }
+        },
+        "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=",
+          "dev": true
+        },
+        "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-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.1.0"
+          }
+        },
+        "wrappy": {
+          "version": "1.0.2",
+          "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz",
+          "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=",
+          "dev": true
+        }
       }
     },
-    "rxjs": {
-      "version": "https://registry.npmjs.org/rxjs/-/rxjs-5.5.2.tgz",
-      "integrity": "sha1-KNQD8AcRIZZ/GK1mVWMlXVQjasM=",
+    "wdio-junit-reporter": {
+      "version": "0.3.1",
+      "resolved": "https://registry.npmjs.org/wdio-junit-reporter/-/wdio-junit-reporter-0.3.1.tgz",
+      "integrity": "sha1-d7G/RFDTGw+VuswW3Bunv8vY/gk=",
       "dev": true,
       "requires": {
-        "symbol-observable": "https://registry.npmjs.org/symbol-observable/-/symbol-observable-1.0.4.tgz"
-      }
-    },
-    "safe-buffer": {
-      "version": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.1.tgz",
-      "integrity": "sha1-iTMSr2myEj3vcfV4iQAWce6yyFM="
-    },
-    "sass-graph": {
-      "version": "https://registry.npmjs.org/sass-graph/-/sass-graph-2.2.4.tgz",
-      "integrity": "sha1-E/vWPNHK8JCLn9k0dq1DpR0eC0k=",
-      "requires": {
-        "glob": "https://registry.npmjs.org/glob/-/glob-7.1.2.tgz",
-        "lodash": "https://registry.npmjs.org/lodash/-/lodash-4.17.4.tgz",
-        "scss-tokenizer": "https://registry.npmjs.org/scss-tokenizer/-/scss-tokenizer-0.2.3.tgz",
-        "yargs": "https://registry.npmjs.org/yargs/-/yargs-7.1.0.tgz"
-      }
-    },
-    "sax": {
-      "version": "https://registry.npmjs.org/sax/-/sax-1.2.4.tgz",
-      "integrity": "sha1-KBYjTiN4vdxOU1T6tcqold9xANk="
-    },
-    "scss-tokenizer": {
-      "version": "https://registry.npmjs.org/scss-tokenizer/-/scss-tokenizer-0.2.3.tgz",
-      "integrity": "sha1-jrBtualyMzOCTT9VMGQRSYR85dE=",
-      "requires": {
-        "js-base64": "https://registry.npmjs.org/js-base64/-/js-base64-2.3.2.tgz",
-        "source-map": "https://registry.npmjs.org/source-map/-/source-map-0.4.4.tgz"
+        "junit-report-builder": "1.2.0",
+        "mkdirp": "0.5.1"
+      },
+      "dependencies": {
+        "date-format": {
+          "version": "0.0.2",
+          "resolved": "https://registry.npmjs.org/date-format/-/date-format-0.0.2.tgz",
+          "integrity": "sha1-+v1Ej3IRXvHitzkVWukvK+bCjdE=",
+          "dev": true
+        },
+        "junit-report-builder": {
+          "version": "1.2.0",
+          "resolved": "https://registry.npmjs.org/junit-report-builder/-/junit-report-builder-1.2.0.tgz",
+          "integrity": "sha1-aJfM1e49gFGRpL5vEse+hQ5QwNY=",
+          "dev": true,
+          "requires": {
+            "date-format": "0.0.2",
+            "lodash": "3.10.1",
+            "mkdirp": "0.5.1",
+            "xmlbuilder": "2.6.5"
+          }
+        },
+        "lodash": {
+          "version": "3.10.1",
+          "resolved": "https://registry.npmjs.org/lodash/-/lodash-3.10.1.tgz",
+          "integrity": "sha1-W/Rejkm6QYnhfUgnid/RW9FAt7Y=",
+          "dev": true
+        },
+        "minimist": {
+          "version": "0.0.8",
+          "resolved": "https://registry.npmjs.org/minimist/-/minimist-0.0.8.tgz",
+          "integrity": "sha1-hX/Kv8M5fSYluCKCYuhqp6ARsF0=",
+          "dev": true
+        },
+        "mkdirp": {
+          "version": "0.5.1",
+          "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.1.tgz",
+          "integrity": "sha1-MAV0OOrGz3+MR2fzhkjWaX11yQM=",
+          "dev": true,
+          "requires": {
+            "minimist": "0.0.8"
+          }
+        },
+        "xmlbuilder": {
+          "version": "2.6.5",
+          "resolved": "https://registry.npmjs.org/xmlbuilder/-/xmlbuilder-2.6.5.tgz",
+          "integrity": "sha1-b/etYPty0idk8AehZLd/K/FABSY=",
+          "dev": true,
+          "requires": {
+            "lodash": "3.10.1"
+          }
+        }
       }
     },
-    "select": {
-      "version": "https://registry.npmjs.org/select/-/select-1.1.2.tgz",
-      "integrity": "sha1-DnNQrN7ICxEIUoeG7B1EGNEbOW0="
-    },
-    "semver": {
-      "version": "https://registry.npmjs.org/semver/-/semver-5.4.1.tgz",
-      "integrity": "sha1-4FnAnYVx8FQII3M0M1BdOi8AsY4="
-    },
-    "set-blocking": {
-      "version": "https://registry.npmjs.org/set-blocking/-/set-blocking-2.0.0.tgz",
-      "integrity": "sha1-BF+XgtARrppoA93TgrJDkrPYkPc="
-    },
-    "setimmediate": {
-      "version": "https://registry.npmjs.org/setimmediate/-/setimmediate-1.0.5.tgz",
-      "integrity": "sha1-KQy7Iy4waULX1+qbg3Mqt4VvgoU="
-    },
-    "shebang-command": {
-      "version": "https://registry.npmjs.org/shebang-command/-/shebang-command-1.2.0.tgz",
-      "integrity": "sha1-RKrGW2lbAzmJaMOfNj/uXer98eo=",
+    "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==",
       "dev": true,
       "requires": {
-        "shebang-regex": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-1.0.0.tgz"
+        "babel-runtime": "6.26.0",
+        "chalk": "2.3.0",
+        "humanize-duration": "3.12.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.1"
+          }
+        },
+        "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.2.0",
+            "escape-string-regexp": "1.0.5",
+            "supports-color": "4.5.0"
+          }
+        },
+        "color-convert": {
+          "version": "1.9.1",
+          "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.1.tgz",
+          "integrity": "sha512-mjGanIiwQJskCC18rPR6OmrZ6fm2Lc7PeGFYwCmy5J34wC6F1PzdGL6xeMfmgicfYcNLGuVFA3WzXtIDCQSZxQ==",
+          "dev": true,
+          "requires": {
+            "color-name": "1.1.3"
+          }
+        },
+        "color-name": {
+          "version": "1.1.3",
+          "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz",
+          "integrity": "sha1-p9BVi9icQveV3UIyj3QIMcpTvCU=",
+          "dev": true
+        },
+        "escape-string-regexp": {
+          "version": "1.0.5",
+          "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz",
+          "integrity": "sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ=",
+          "dev": true
+        },
+        "has-flag": {
+          "version": "2.0.0",
+          "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-2.0.0.tgz",
+          "integrity": "sha1-6CB68cx7MNRGzHC3NLXovhj4jVE=",
+          "dev": true
+        },
+        "humanize-duration": {
+          "version": "3.12.1",
+          "resolved": "https://registry.npmjs.org/humanize-duration/-/humanize-duration-3.12.1.tgz",
+          "integrity": "sha512-Eu68Xnq5C38391em1zfVy8tiapQrOvTNTlWpax9smHMlEEUcudXrdMfXMoMRyZx4uODowYgi1AYiMzUXEbG+sA==",
+          "dev": true
+        },
+        "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"
+          }
+        }
       }
     },
-    "shebang-regex": {
-      "version": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-1.0.0.tgz",
-      "integrity": "sha1-2kL0l0DAtC2yypcoVxyxkMmO/qM=",
-      "dev": true
-    },
-    "signal-exit": {
-      "version": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.2.tgz",
-      "integrity": "sha1-tf3AjxKH6hF4Yo5BXiUTK3NkbG0="
-    },
-    "slice-ansi": {
-      "version": "https://registry.npmjs.org/slice-ansi/-/slice-ansi-1.0.0.tgz",
-      "integrity": "sha1-BE8aSdiEL/MHqta1Be0Xi9lQE00=",
+    "webdriverio": {
+      "version": "4.8.0",
+      "resolved": "https://registry.npmjs.org/webdriverio/-/webdriverio-4.8.0.tgz",
+      "integrity": "sha1-1Skpt0kID4mWf24WFAUcvIFy0TI=",
       "dev": true,
       "requires": {
-        "is-fullwidth-code-point": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-2.0.0.tgz"
+        "archiver": "1.3.0",
+        "babel-runtime": "6.23.0",
+        "css-parse": "2.0.0",
+        "css-value": "0.0.1",
+        "deepmerge": "1.3.2",
+        "ejs": "2.5.7",
+        "gaze": "1.1.2",
+        "glob": "7.1.2",
+        "inquirer": "3.0.6",
+        "json-stringify-safe": "5.0.1",
+        "mkdirp": "0.5.1",
+        "npm-install-package": "2.1.0",
+        "optimist": "0.6.1",
+        "q": "1.5.1",
+        "request": "2.81.0",
+        "rgb2hex": "0.1.0",
+        "safe-buffer": "5.0.1",
+        "supports-color": "3.2.3",
+        "url": "0.11.0",
+        "validator": "7.0.0",
+        "wdio-dot-reporter": "0.0.9",
+        "wgxpath": "1.0.0"
       },
       "dependencies": {
-        "is-fullwidth-code-point": {
-          "version": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-2.0.0.tgz",
-          "integrity": "sha1-o7MKXE8ZkYMWeqq5O+764937ZU8=",
+        "ajv": {
+          "version": "4.11.8",
+          "resolved": "https://registry.npmjs.org/ajv/-/ajv-4.11.8.tgz",
+          "integrity": "sha1-gv+wKynmYq5TvcIK8VlHcGc5xTY=",
+          "dev": true,
+          "requires": {
+            "co": "4.6.0",
+            "json-stable-stringify": "1.0.1"
+          }
+        },
+        "amdefine": {
+          "version": "1.0.1",
+          "resolved": "https://registry.npmjs.org/amdefine/-/amdefine-1.0.1.tgz",
+          "integrity": "sha1-SlKCrBZHKek2Gbz9OtFR+BfOkfU=",
+          "dev": true
+        },
+        "ansi-escapes": {
+          "version": "1.4.0",
+          "resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-1.4.0.tgz",
+          "integrity": "sha1-06ioOzGapneTZisT52HHkRQiMG4=",
+          "dev": true
+        },
+        "ansi-regex": {
+          "version": "2.1.1",
+          "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-2.1.1.tgz",
+          "integrity": "sha1-w7M6te42DYbg5ijwRorn7yfWVN8=",
+          "dev": true
+        },
+        "ansi-styles": {
+          "version": "2.2.1",
+          "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-2.2.1.tgz",
+          "integrity": "sha1-tDLdM1i2NM914eRmQ2gkBTPB3b4=",
+          "dev": true
+        },
+        "archiver": {
+          "version": "1.3.0",
+          "resolved": "https://registry.npmjs.org/archiver/-/archiver-1.3.0.tgz",
+          "integrity": "sha1-TyGU1tj5nfP1MeaIHxTxXVX6ryI=",
+          "dev": true,
+          "requires": {
+            "archiver-utils": "1.3.0",
+            "async": "2.6.0",
+            "buffer-crc32": "0.2.13",
+            "glob": "7.1.2",
+            "lodash": "4.17.4",
+            "readable-stream": "2.3.3",
+            "tar-stream": "1.5.5",
+            "walkdir": "0.0.11",
+            "zip-stream": "1.2.0"
+          }
+        },
+        "archiver-utils": {
+          "version": "1.3.0",
+          "resolved": "https://registry.npmjs.org/archiver-utils/-/archiver-utils-1.3.0.tgz",
+          "integrity": "sha1-5QtMCccL89aA4y/xt5lOn52JUXQ=",
+          "dev": true,
+          "requires": {
+            "glob": "7.1.2",
+            "graceful-fs": "4.1.11",
+            "lazystream": "1.0.0",
+            "lodash": "4.17.4",
+            "normalize-path": "2.1.1",
+            "readable-stream": "2.3.3"
+          }
+        },
+        "asn1": {
+          "version": "0.2.3",
+          "resolved": "https://registry.npmjs.org/asn1/-/asn1-0.2.3.tgz",
+          "integrity": "sha1-2sh4dxPJlmhJ/IGAd36+nB3fO4Y=",
+          "dev": true
+        },
+        "assert-plus": {
+          "version": "0.2.0",
+          "resolved": "https://registry.npmjs.org/assert-plus/-/assert-plus-0.2.0.tgz",
+          "integrity": "sha1-104bh+ev/A24qttwIfP+SBAasjQ=",
+          "dev": true
+        },
+        "async": {
+          "version": "2.6.0",
+          "resolved": "https://registry.npmjs.org/async/-/async-2.6.0.tgz",
+          "integrity": "sha512-xAfGg1/NTLBBKlHFmnd7PlmUW9KhVQIUuSrYem9xzFUZy13ScvtyGGejaae9iAVRiRq9+Cx7DPFaAAhCpyxyPw==",
+          "dev": true,
+          "requires": {
+            "lodash": "4.17.4"
+          }
+        },
+        "asynckit": {
+          "version": "0.4.0",
+          "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz",
+          "integrity": "sha1-x57Zf380y48robyXkLzDZkdLS3k=",
+          "dev": true
+        },
+        "atob": {
+          "version": "1.1.3",
+          "resolved": "https://registry.npmjs.org/atob/-/atob-1.1.3.tgz",
+          "integrity": "sha1-lfE2KbEsOlGl0hWr3OKqnzL4B3M=",
+          "dev": true
+        },
+        "aws-sign2": {
+          "version": "0.6.0",
+          "resolved": "https://registry.npmjs.org/aws-sign2/-/aws-sign2-0.6.0.tgz",
+          "integrity": "sha1-FDQt0428yU0OW4fXY81jYSwOeU8=",
+          "dev": true
+        },
+        "aws4": {
+          "version": "1.6.0",
+          "resolved": "https://registry.npmjs.org/aws4/-/aws4-1.6.0.tgz",
+          "integrity": "sha1-g+9cqGCysy5KDe7e6MdxudtXRx4=",
+          "dev": true
+        },
+        "babel-runtime": {
+          "version": "6.23.0",
+          "resolved": "https://registry.npmjs.org/babel-runtime/-/babel-runtime-6.23.0.tgz",
+          "integrity": "sha1-CpSJ8UTecO+zzkMArM2zKeL8VDs=",
+          "dev": true,
+          "requires": {
+            "core-js": "2.5.3",
+            "regenerator-runtime": "0.10.5"
+          }
+        },
+        "balanced-match": {
+          "version": "1.0.0",
+          "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.0.tgz",
+          "integrity": "sha1-ibTRmasr7kneFk6gK4nORi1xt2c=",
+          "dev": true
+        },
+        "bcrypt-pbkdf": {
+          "version": "1.0.1",
+          "resolved": "https://registry.npmjs.org/bcrypt-pbkdf/-/bcrypt-pbkdf-1.0.1.tgz",
+          "integrity": "sha1-Y7xdy2EzG5K8Bf1SiVPDNGKgb40=",
+          "dev": true,
+          "optional": true,
+          "requires": {
+            "tweetnacl": "0.14.5"
+          }
+        },
+        "bl": {
+          "version": "1.2.1",
+          "resolved": "https://registry.npmjs.org/bl/-/bl-1.2.1.tgz",
+          "integrity": "sha1-ysMo977kVzDUBLaSID/LWQ4XLV4=",
+          "dev": true,
+          "requires": {
+            "readable-stream": "2.3.3"
+          }
+        },
+        "boom": {
+          "version": "2.10.1",
+          "resolved": "https://registry.npmjs.org/boom/-/boom-2.10.1.tgz",
+          "integrity": "sha1-OciRjO/1eZ+D+UkqhI9iWt0Mdm8=",
+          "dev": true,
+          "requires": {
+            "hoek": "2.16.3"
+          }
+        },
+        "brace-expansion": {
+          "version": "1.1.8",
+          "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.8.tgz",
+          "integrity": "sha1-wHshHHyVLsH479Uad+8NHTmQopI=",
+          "dev": true,
+          "requires": {
+            "balanced-match": "1.0.0",
+            "concat-map": "0.0.1"
+          }
+        },
+        "buffer-crc32": {
+          "version": "0.2.13",
+          "resolved": "https://registry.npmjs.org/buffer-crc32/-/buffer-crc32-0.2.13.tgz",
+          "integrity": "sha1-DTM+PwDqxQqhRUq9MO+MKl2ackI=",
+          "dev": true
+        },
+        "caseless": {
+          "version": "0.12.0",
+          "resolved": "https://registry.npmjs.org/caseless/-/caseless-0.12.0.tgz",
+          "integrity": "sha1-G2gcIf+EAzyCZUMJBolCDRhxUdw=",
+          "dev": true
+        },
+        "chalk": {
+          "version": "1.1.3",
+          "resolved": "https://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.5",
+            "has-ansi": "2.0.0",
+            "strip-ansi": "3.0.1",
+            "supports-color": "2.0.0"
+          },
+          "dependencies": {
+            "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
+            }
+          }
+        },
+        "cli-cursor": {
+          "version": "2.1.0",
+          "resolved": "https://registry.npmjs.org/cli-cursor/-/cli-cursor-2.1.0.tgz",
+          "integrity": "sha1-s12sN2R5+sw+lHR9QdDQ9SOP/LU=",
+          "dev": true,
+          "requires": {
+            "restore-cursor": "2.0.0"
+          }
+        },
+        "cli-width": {
+          "version": "2.2.0",
+          "resolved": "https://registry.npmjs.org/cli-width/-/cli-width-2.2.0.tgz",
+          "integrity": "sha1-/xnt6Kml5XkyQUewwR8PvLq+1jk=",
+          "dev": true
+        },
+        "co": {
+          "version": "4.6.0",
+          "resolved": "https://registry.npmjs.org/co/-/co-4.6.0.tgz",
+          "integrity": "sha1-bqa989hTrlTMuOR7+gvz+QMfsYQ=",
+          "dev": true
+        },
+        "combined-stream": {
+          "version": "1.0.5",
+          "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.5.tgz",
+          "integrity": "sha1-k4NwpXtKUd6ix3wV1cX9+JUWQAk=",
+          "dev": true,
+          "requires": {
+            "delayed-stream": "1.0.0"
+          }
+        },
+        "compress-commons": {
+          "version": "1.2.2",
+          "resolved": "https://registry.npmjs.org/compress-commons/-/compress-commons-1.2.2.tgz",
+          "integrity": "sha1-UkqfEJA/OoEzibAiXSfEi7dRiQ8=",
+          "dev": true,
+          "requires": {
+            "buffer-crc32": "0.2.13",
+            "crc32-stream": "2.0.0",
+            "normalize-path": "2.1.1",
+            "readable-stream": "2.3.3"
+          }
+        },
+        "concat-map": {
+          "version": "0.0.1",
+          "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz",
+          "integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=",
+          "dev": true
+        },
+        "core-js": {
+          "version": "2.5.3",
+          "resolved": "https://registry.npmjs.org/core-js/-/core-js-2.5.3.tgz",
+          "integrity": "sha1-isw4NFgk8W2DZbfJtCWRaOjtYD4=",
+          "dev": true
+        },
+        "core-util-is": {
+          "version": "1.0.2",
+          "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.2.tgz",
+          "integrity": "sha1-tf1UIgqivFq1eqtxQMlAdUUDwac=",
+          "dev": true
+        },
+        "crc": {
+          "version": "3.5.0",
+          "resolved": "https://registry.npmjs.org/crc/-/crc-3.5.0.tgz",
+          "integrity": "sha1-mLi6fUiWZbo5efWbITgTdBAaGWQ=",
+          "dev": true
+        },
+        "crc32-stream": {
+          "version": "2.0.0",
+          "resolved": "https://registry.npmjs.org/crc32-stream/-/crc32-stream-2.0.0.tgz",
+          "integrity": "sha1-483TtN8xaN10494/u8t7KX/pCPQ=",
+          "dev": true,
+          "requires": {
+            "crc": "3.5.0",
+            "readable-stream": "2.3.3"
+          }
+        },
+        "cryptiles": {
+          "version": "2.0.5",
+          "resolved": "https://registry.npmjs.org/cryptiles/-/cryptiles-2.0.5.tgz",
+          "integrity": "sha1-O9/s3GCBR8HGcgL6KR59ylnqo7g=",
+          "dev": true,
+          "requires": {
+            "boom": "2.10.1"
+          }
+        },
+        "css": {
+          "version": "2.2.1",
+          "resolved": "https://registry.npmjs.org/css/-/css-2.2.1.tgz",
+          "integrity": "sha1-c6TIHehdtmTU7mdPfUcIXjstVdw=",
+          "dev": true,
+          "requires": {
+            "inherits": "2.0.3",
+            "source-map": "0.1.43",
+            "source-map-resolve": "0.3.1",
+            "urix": "0.1.0"
+          }
+        },
+        "css-parse": {
+          "version": "2.0.0",
+          "resolved": "https://registry.npmjs.org/css-parse/-/css-parse-2.0.0.tgz",
+          "integrity": "sha1-pGjuZnwW2BzPBcWMONKpfHgNv9Q=",
+          "dev": true,
+          "requires": {
+            "css": "2.2.1"
+          }
+        },
+        "css-value": {
+          "version": "0.0.1",
+          "resolved": "https://registry.npmjs.org/css-value/-/css-value-0.0.1.tgz",
+          "integrity": "sha1-Xv1sLupeof1rasV+wEJ7GEUkJOo=",
+          "dev": true
+        },
+        "dashdash": {
+          "version": "1.14.1",
+          "resolved": "https://registry.npmjs.org/dashdash/-/dashdash-1.14.1.tgz",
+          "integrity": "sha1-hTz6D3y+L+1d4gMmuN1YEDX24vA=",
+          "dev": true,
+          "requires": {
+            "assert-plus": "1.0.0"
+          },
+          "dependencies": {
+            "assert-plus": {
+              "version": "1.0.0",
+              "resolved": "https://registry.npmjs.org/assert-plus/-/assert-plus-1.0.0.tgz",
+              "integrity": "sha1-8S4PPF13sLHN2RRpQuTpbB5N1SU=",
+              "dev": true
+            }
+          }
+        },
+        "deepmerge": {
+          "version": "1.3.2",
+          "resolved": "https://registry.npmjs.org/deepmerge/-/deepmerge-1.3.2.tgz",
+          "integrity": "sha1-FmNpFinU2/42T6EqKk8KqGqjoFA=",
+          "dev": true
+        },
+        "delayed-stream": {
+          "version": "1.0.0",
+          "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz",
+          "integrity": "sha1-3zrhmayt+31ECqrgsp4icrJOxhk=",
+          "dev": true
+        },
+        "ecc-jsbn": {
+          "version": "0.1.1",
+          "resolved": "https://registry.npmjs.org/ecc-jsbn/-/ecc-jsbn-0.1.1.tgz",
+          "integrity": "sha1-D8c6ntXw1Tw4GTOYUj735UN3dQU=",
+          "dev": true,
+          "optional": true,
+          "requires": {
+            "jsbn": "0.1.1"
+          }
+        },
+        "ejs": {
+          "version": "2.5.7",
+          "resolved": "https://registry.npmjs.org/ejs/-/ejs-2.5.7.tgz",
+          "integrity": "sha1-zIcsFoiArjxxiXYv1f/ACJbJUYo=",
+          "dev": true
+        },
+        "end-of-stream": {
+          "version": "1.4.0",
+          "resolved": "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.4.0.tgz",
+          "integrity": "sha1-epDYM+/abPpurA9JSduw+tOmMgY=",
+          "dev": true,
+          "requires": {
+            "once": "1.4.0"
+          }
+        },
+        "escape-string-regexp": {
+          "version": "1.0.5",
+          "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz",
+          "integrity": "sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ=",
+          "dev": true
+        },
+        "extend": {
+          "version": "3.0.1",
+          "resolved": "https://registry.npmjs.org/extend/-/extend-3.0.1.tgz",
+          "integrity": "sha1-p1Xqe8Gt/MWjHOfnYtuq3F5jZEQ=",
           "dev": true
-        }
-      }
-    },
-    "sntp": {
-      "version": "https://registry.npmjs.org/sntp/-/sntp-2.1.0.tgz",
-      "integrity": "sha1-LGzsFP7cIiJznK+bXD2F0cxaLMg=",
-      "requires": {
-        "hoek": "https://registry.npmjs.org/hoek/-/hoek-4.2.0.tgz"
-      }
-    },
-    "source-map": {
-      "version": "https://registry.npmjs.org/source-map/-/source-map-0.4.4.tgz",
-      "integrity": "sha1-66T12pwNyZneaAMti092FzZSA2s=",
-      "requires": {
-        "amdefine": "https://registry.npmjs.org/amdefine/-/amdefine-1.0.1.tgz"
-      }
-    },
-    "source-map-resolve": {
-      "version": "https://registry.npmjs.org/source-map-resolve/-/source-map-resolve-0.3.1.tgz",
-      "integrity": "sha1-YQ9hIqRFuN1RU1oqcbeD38Ekh2E=",
-      "dev": true,
-      "requires": {
-        "atob": "https://registry.npmjs.org/atob/-/atob-1.1.3.tgz",
-        "resolve-url": "https://registry.npmjs.org/resolve-url/-/resolve-url-0.2.1.tgz",
-        "source-map-url": "https://registry.npmjs.org/source-map-url/-/source-map-url-0.3.0.tgz",
-        "urix": "https://registry.npmjs.org/urix/-/urix-0.1.0.tgz"
-      }
-    },
-    "source-map-url": {
-      "version": "https://registry.npmjs.org/source-map-url/-/source-map-url-0.3.0.tgz",
-      "integrity": "sha1-fsrxO1e80J2opAxdJp2zN5nUqvk=",
-      "dev": true
-    },
-    "spdx-correct": {
-      "version": "https://registry.npmjs.org/spdx-correct/-/spdx-correct-1.0.2.tgz",
-      "integrity": "sha1-SzBz2TP/UfORLwOsVRlJikFQ20A=",
-      "requires": {
-        "spdx-license-ids": "https://registry.npmjs.org/spdx-license-ids/-/spdx-license-ids-1.2.2.tgz"
-      }
-    },
-    "spdx-expression-parse": {
-      "version": "https://registry.npmjs.org/spdx-expression-parse/-/spdx-expression-parse-1.0.4.tgz",
-      "integrity": "sha1-m98vIOH0DtRH++JzJmGR/O1RYmw="
-    },
-    "spdx-license-ids": {
-      "version": "https://registry.npmjs.org/spdx-license-ids/-/spdx-license-ids-1.2.2.tgz",
-      "integrity": "sha1-yd96NCRZSt5r0RkA1ZZpbcBrrFc="
-    },
-    "sprintf-js": {
-      "version": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz",
-      "integrity": "sha1-BOaSb2YolTVPPdAVIDYzuFcpfiw=",
-      "dev": true
-    },
-    "sshpk": {
-      "version": "https://registry.npmjs.org/sshpk/-/sshpk-1.13.1.tgz",
-      "integrity": "sha1-US322mKHFEMW3EwY/hzx2UBzm+M=",
-      "requires": {
-        "asn1": "https://registry.npmjs.org/asn1/-/asn1-0.2.3.tgz",
-        "assert-plus": "https://registry.npmjs.org/assert-plus/-/assert-plus-1.0.0.tgz",
-        "bcrypt-pbkdf": "https://registry.npmjs.org/bcrypt-pbkdf/-/bcrypt-pbkdf-1.0.1.tgz",
-        "dashdash": "https://registry.npmjs.org/dashdash/-/dashdash-1.14.1.tgz",
-        "ecc-jsbn": "https://registry.npmjs.org/ecc-jsbn/-/ecc-jsbn-0.1.1.tgz",
-        "getpass": "https://registry.npmjs.org/getpass/-/getpass-0.1.7.tgz",
-        "jsbn": "https://registry.npmjs.org/jsbn/-/jsbn-0.1.1.tgz",
-        "tweetnacl": "https://registry.npmjs.org/tweetnacl/-/tweetnacl-0.14.5.tgz"
-      }
-    },
-    "stack-trace": {
-      "version": "https://registry.npmjs.org/stack-trace/-/stack-trace-0.0.10.tgz",
-      "integrity": "sha1-VHxws0fo0ytOEI6hoqFZ5f3eGcA="
-    },
-    "staged-git-files": {
-      "version": "https://registry.npmjs.org/staged-git-files/-/staged-git-files-0.0.4.tgz",
-      "integrity": "sha1-15fhtVHKemOd7AI33G60u5vhfTU=",
-      "dev": true
-    },
-    "stdout-stream": {
-      "version": "https://registry.npmjs.org/stdout-stream/-/stdout-stream-1.4.0.tgz",
-      "integrity": "sha1-osfIWH5U2UJ+qe2zrD8s1SLfN4s=",
-      "requires": {
-        "readable-stream": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.3.tgz"
-      }
-    },
-    "stream-parser": {
-      "version": "https://registry.npmjs.org/stream-parser/-/stream-parser-0.3.1.tgz",
-      "integrity": "sha1-FhhUhpRCACGhGC/wrxkRwSl2F3M=",
-      "requires": {
-        "debug": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz"
-      }
-    },
-    "stream-to-observable": {
-      "version": "https://registry.npmjs.org/stream-to-observable/-/stream-to-observable-0.1.0.tgz",
-      "integrity": "sha1-Rb8dny19wJvtgfHDB8Qw5ouEz/4=",
-      "dev": true
-    },
-    "strict-uri-encode": {
-      "version": "https://registry.npmjs.org/strict-uri-encode/-/strict-uri-encode-1.1.0.tgz",
-      "integrity": "sha1-J5siXfHVgrH1TmWt3UNS4Y+qBxM="
-    },
-    "string_decoder": {
-      "version": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.0.3.tgz",
-      "integrity": "sha1-D8Z9fBQYJd6UKC3VNr7GubzoYKs=",
-      "requires": {
-        "safe-buffer": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.1.tgz"
-      }
-    },
-    "string-hash": {
-      "version": "https://registry.npmjs.org/string-hash/-/string-hash-1.1.3.tgz",
-      "integrity": "sha1-6Kr8CsGFW0Zmkp7X3RJ1311sgRs="
-    },
-    "string-width": {
-      "version": "https://registry.npmjs.org/string-width/-/string-width-1.0.2.tgz",
-      "integrity": "sha1-EYvfW4zcUaKn5w0hHgfisLmxB9M=",
-      "requires": {
-        "code-point-at": "https://registry.npmjs.org/code-point-at/-/code-point-at-1.1.0.tgz",
-        "is-fullwidth-code-point": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-1.0.0.tgz",
-        "strip-ansi": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-3.0.1.tgz"
-      }
-    },
-    "stringify-object": {
-      "version": "https://registry.npmjs.org/stringify-object/-/stringify-object-3.2.1.tgz",
-      "integrity": "sha1-JyDC7/lAhUyBn27iUqrrWB8wYk0=",
-      "dev": true,
-      "requires": {
-        "get-own-enumerable-property-symbols": "https://registry.npmjs.org/get-own-enumerable-property-symbols/-/get-own-enumerable-property-symbols-2.0.1.tgz",
-        "is-obj": "https://registry.npmjs.org/is-obj/-/is-obj-1.0.1.tgz",
-        "is-regexp": "https://registry.npmjs.org/is-regexp/-/is-regexp-1.0.0.tgz"
-      }
-    },
-    "stringstream": {
-      "version": "https://registry.npmjs.org/stringstream/-/stringstream-0.0.5.tgz",
-      "integrity": "sha1-TkhM1N5aC7vuGORjB3EKioFiGHg="
-    },
-    "strip-ansi": {
-      "version": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-3.0.1.tgz",
-      "integrity": "sha1-ajhfuIU9lS1f8F0Oiq+UJ43GPc8=",
-      "requires": {
-        "ansi-regex": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-2.1.1.tgz"
-      }
-    },
-    "strip-bom": {
-      "version": "https://registry.npmjs.org/strip-bom/-/strip-bom-2.0.0.tgz",
-      "integrity": "sha1-YhmoVhZSBJHzV4i9vxRHqZx+aw4=",
-      "requires": {
-        "is-utf8": "https://registry.npmjs.org/is-utf8/-/is-utf8-0.2.1.tgz"
-      }
-    },
-    "strip-eof": {
-      "version": "https://registry.npmjs.org/strip-eof/-/strip-eof-1.0.0.tgz",
-      "integrity": "sha1-u0P/VZim6wXYm1n80SnJgzE2Br8=",
-      "dev": true
-    },
-    "strip-indent": {
-      "version": "https://registry.npmjs.org/strip-indent/-/strip-indent-1.0.1.tgz",
-      "integrity": "sha1-DHlipq3vp7vUrDZkYKY4VSrhoKI=",
-      "requires": {
-        "get-stdin": "https://registry.npmjs.org/get-stdin/-/get-stdin-4.0.1.tgz"
-      }
-    },
-    "strip-json-comments": {
-      "version": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-2.0.1.tgz",
-      "integrity": "sha1-PFMZQukIwml8DsNEhYwobHygpgo=",
-      "dev": true
-    },
-    "supports-color": {
-      "version": "https://registry.npmjs.org/supports-color/-/supports-color-2.0.0.tgz",
-      "integrity": "sha1-U10EXOa2Nj+kARcIRimZXp3zJMc="
-    },
-    "symbol-observable": {
-      "version": "https://registry.npmjs.org/symbol-observable/-/symbol-observable-1.0.4.tgz",
-      "integrity": "sha1-Kb9hXUqnEhvdiYsi1LP5vE4qoD0=",
-      "dev": true
-    },
-    "table": {
-      "version": "https://registry.npmjs.org/table/-/table-4.0.2.tgz",
-      "integrity": "sha1-ozRHN1OR52atNNNIbm4q7chNLjY=",
-      "dev": true,
-      "requires": {
-        "ajv": "https://registry.npmjs.org/ajv/-/ajv-5.3.0.tgz",
-        "ajv-keywords": "https://registry.npmjs.org/ajv-keywords/-/ajv-keywords-2.1.1.tgz",
-        "chalk": "https://registry.npmjs.org/chalk/-/chalk-2.3.0.tgz",
-        "lodash": "https://registry.npmjs.org/lodash/-/lodash-4.17.4.tgz",
-        "slice-ansi": "https://registry.npmjs.org/slice-ansi/-/slice-ansi-1.0.0.tgz",
-        "string-width": "https://registry.npmjs.org/string-width/-/string-width-2.1.1.tgz"
-      },
-      "dependencies": {
-        "ansi-regex": {
-          "version": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-3.0.0.tgz",
-          "integrity": "sha1-7QMXwyIGT3lGbAKWa922Bas32Zg=",
+        },
+        "external-editor": {
+          "version": "2.1.0",
+          "resolved": "https://registry.npmjs.org/external-editor/-/external-editor-2.1.0.tgz",
+          "integrity": "sha512-E44iT5QVOUJBKij4IIV3uvxuNlbKS38Tw1HiupxEIHPv9qtC2PrDYohbXV5U+1jnfIXttny8gUhj+oZvflFlzA==",
+          "dev": true,
+          "requires": {
+            "chardet": "0.4.2",
+            "iconv-lite": "0.4.19",
+            "tmp": "0.0.33"
+          }
+        },
+        "extsprintf": {
+          "version": "1.3.0",
+          "resolved": "https://registry.npmjs.org/extsprintf/-/extsprintf-1.3.0.tgz",
+          "integrity": "sha1-lpGEQOMEGnpBT4xS48V06zw+HgU=",
           "dev": true
         },
-        "ansi-styles": {
-          "version": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.0.tgz",
-          "integrity": "sha1-wVm41b4PnlpvNG2rlPFs4CIWG4g=",
+        "figures": {
+          "version": "2.0.0",
+          "resolved": "https://registry.npmjs.org/figures/-/figures-2.0.0.tgz",
+          "integrity": "sha1-OrGi0qYsi/tDGgyUy3l6L84nyWI=",
           "dev": true,
           "requires": {
-            "color-convert": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.0.tgz"
+            "escape-string-regexp": "1.0.5"
           }
         },
-        "chalk": {
-          "version": "https://registry.npmjs.org/chalk/-/chalk-2.3.0.tgz",
-          "integrity": "sha1-tepI78nBeT3MybR2fJORTT8tUro=",
+        "forever-agent": {
+          "version": "0.6.1",
+          "resolved": "https://registry.npmjs.org/forever-agent/-/forever-agent-0.6.1.tgz",
+          "integrity": "sha1-+8cfDEGt6zf5bFd60e1C2P2sypE=",
+          "dev": true
+        },
+        "form-data": {
+          "version": "2.1.4",
+          "resolved": "https://registry.npmjs.org/form-data/-/form-data-2.1.4.tgz",
+          "integrity": "sha1-M8GDrPGTJ27KqYFDpp6Uv+4XUNE=",
           "dev": true,
           "requires": {
-            "ansi-styles": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.0.tgz",
-            "escape-string-regexp": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz",
-            "supports-color": "https://registry.npmjs.org/supports-color/-/supports-color-4.5.0.tgz"
+            "asynckit": "0.4.0",
+            "combined-stream": "1.0.5",
+            "mime-types": "2.1.17"
           }
         },
-        "is-fullwidth-code-point": {
-          "version": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-2.0.0.tgz",
-          "integrity": "sha1-o7MKXE8ZkYMWeqq5O+764937ZU8=",
+        "fs.realpath": {
+          "version": "1.0.0",
+          "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz",
+          "integrity": "sha1-FQStJSMVjKpA20onh8sBQRmU6k8=",
           "dev": true
         },
-        "string-width": {
-          "version": "https://registry.npmjs.org/string-width/-/string-width-2.1.1.tgz",
-          "integrity": "sha1-q5Pyeo3BPSjKyBXEYhQ6bZASrp4=",
+        "gaze": {
+          "version": "1.1.2",
+          "resolved": "https://registry.npmjs.org/gaze/-/gaze-1.1.2.tgz",
+          "integrity": "sha1-hHIkZ3rbiHDWeSV+0ziP22HkAQU=",
           "dev": true,
           "requires": {
-            "is-fullwidth-code-point": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-2.0.0.tgz",
-            "strip-ansi": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-4.0.0.tgz"
+            "globule": "1.2.0"
           }
         },
-        "strip-ansi": {
-          "version": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-4.0.0.tgz",
-          "integrity": "sha1-qEeQIusaw2iocTibY1JixQXuNo8=",
+        "getpass": {
+          "version": "0.1.7",
+          "resolved": "https://registry.npmjs.org/getpass/-/getpass-0.1.7.tgz",
+          "integrity": "sha1-Xv+OPmhNVprkyysSgmBOi6YhSfo=",
           "dev": true,
           "requires": {
-            "ansi-regex": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-3.0.0.tgz"
+            "assert-plus": "1.0.0"
+          },
+          "dependencies": {
+            "assert-plus": {
+              "version": "1.0.0",
+              "resolved": "https://registry.npmjs.org/assert-plus/-/assert-plus-1.0.0.tgz",
+              "integrity": "sha1-8S4PPF13sLHN2RRpQuTpbB5N1SU=",
+              "dev": true
+            }
           }
         },
-        "supports-color": {
-          "version": "https://registry.npmjs.org/supports-color/-/supports-color-4.5.0.tgz",
-          "integrity": "sha1-vnoN5ITexcXN34s9WRJQRJEvY1s=",
+        "glob": {
+          "version": "7.1.2",
+          "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.2.tgz",
+          "integrity": "sha512-MJTUg1kjuLeQCJ+ccE4Vpa6kKVXkPYJ2mOCQyUuKLcLQsdrMCpBPUi8qVE6+YuaJkozeA9NusTAw3hLr8Xe5EQ==",
           "dev": true,
           "requires": {
-            "has-flag": "https://registry.npmjs.org/has-flag/-/has-flag-2.0.0.tgz"
+            "fs.realpath": "1.0.0",
+            "inflight": "1.0.6",
+            "inherits": "2.0.3",
+            "minimatch": "3.0.4",
+            "once": "1.4.0",
+            "path-is-absolute": "1.0.1"
           }
-        }
-      }
-    },
-    "tar": {
-      "version": "https://registry.npmjs.org/tar/-/tar-2.2.1.tgz",
-      "integrity": "sha1-jk0qJWwOIYXGsYrWlK7JaLg8sdE=",
-      "requires": {
-        "block-stream": "https://registry.npmjs.org/block-stream/-/block-stream-0.0.9.tgz",
-        "fstream": "https://registry.npmjs.org/fstream/-/fstream-1.0.11.tgz",
-        "inherits": "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz"
-      }
-    },
-    "tar-stream": {
-      "version": "https://registry.npmjs.org/tar-stream/-/tar-stream-1.5.4.tgz",
-      "integrity": "sha1-NlSc8E7RrumyowwBQyUiONr5QBY=",
-      "dev": true,
-      "requires": {
-        "bl": "https://registry.npmjs.org/bl/-/bl-1.2.1.tgz",
-        "end-of-stream": "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.4.0.tgz",
-        "readable-stream": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.3.tgz",
-        "xtend": "https://registry.npmjs.org/xtend/-/xtend-4.0.1.tgz"
-      }
-    },
-    "text-table": {
-      "version": "https://registry.npmjs.org/text-table/-/text-table-0.2.0.tgz",
-      "integrity": "sha1-f17oI66AUgfACvLfSoTsP8+lcLQ=",
-      "dev": true
-    },
-    "through": {
-      "version": "https://registry.npmjs.org/through/-/through-2.3.8.tgz",
-      "integrity": "sha1-DdTJ/6q8NXlgsbckEV1+Doai4fU=",
-      "dev": true
-    },
-    "timed-out": {
-      "version": "https://registry.npmjs.org/timed-out/-/timed-out-4.0.1.tgz",
-      "integrity": "sha1-8y6srFoXW+ol1/q1Zas+2HQe9W8="
-    },
-    "tiny-emitter": {
-      "version": "https://registry.npmjs.org/tiny-emitter/-/tiny-emitter-2.0.2.tgz",
-      "integrity": "sha1-gtJ0aKylrejl/R5tIrV91D69+3w="
-    },
-    "tinycolor2": {
-      "version": "https://registry.npmjs.org/tinycolor2/-/tinycolor2-1.4.1.tgz",
-      "integrity": "sha1-9PrTM0R7wLB9TcjpIJ2POaisd+g="
-    },
-    "tmp": {
-      "version": "https://registry.npmjs.org/tmp/-/tmp-0.0.33.tgz",
-      "integrity": "sha1-bTQzWIl2jSGyvNoKonfO07G/rfk=",
-      "dev": true,
-      "requires": {
-        "os-tmpdir": "https://registry.npmjs.org/os-tmpdir/-/os-tmpdir-1.0.2.tgz"
-      }
-    },
-    "tough-cookie": {
-      "version": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-2.3.3.tgz",
-      "integrity": "sha1-C2GKVWW23qkL80JdBNVe3EdadWE=",
-      "requires": {
-        "punycode": "https://registry.npmjs.org/punycode/-/punycode-1.4.1.tgz"
-      }
-    },
-    "trim-newlines": {
-      "version": "https://registry.npmjs.org/trim-newlines/-/trim-newlines-1.0.0.tgz",
-      "integrity": "sha1-WIeWa7WCpFA6QetST301ARgVphM="
-    },
-    "tryit": {
-      "version": "https://registry.npmjs.org/tryit/-/tryit-1.0.3.tgz",
-      "integrity": "sha1-OTvnMKlEb9Hq1tpZoBQwjzbCics=",
-      "dev": true
-    },
-    "tunnel-agent": {
-      "version": "https://registry.npmjs.org/tunnel-agent/-/tunnel-agent-0.6.0.tgz",
-      "integrity": "sha1-J6XeoGs2sEoKmWZ3SykIaPD8QP0=",
-      "requires": {
-        "safe-buffer": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.1.tgz"
-      }
-    },
-    "tweetnacl": {
-      "version": "https://registry.npmjs.org/tweetnacl/-/tweetnacl-0.14.5.tgz",
-      "integrity": "sha1-WuaBd/GS1EViadEIr6k/+HQ/T2Q=",
-      "optional": true
-    },
-    "type-check": {
-      "version": "https://registry.npmjs.org/type-check/-/type-check-0.3.2.tgz",
-      "integrity": "sha1-WITKtRLPHTVeP7eE8wgEsrUg23I=",
-      "dev": true,
-      "requires": {
-        "prelude-ls": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.1.2.tgz"
-      }
-    },
-    "type-detect": {
-      "version": "https://registry.npmjs.org/type-detect/-/type-detect-4.0.3.tgz",
-      "integrity": "sha1-Dj8mcLRAmbC0bChNE2p+9Jx0wuo=",
-      "dev": true
-    },
-    "typedarray": {
-      "version": "https://registry.npmjs.org/typedarray/-/typedarray-0.0.6.tgz",
-      "integrity": "sha1-hnrHTjhkGHsdPUfZlqeOxciDB3c=",
-      "dev": true
-    },
-    "ua-parser-js": {
-      "version": "https://registry.npmjs.org/ua-parser-js/-/ua-parser-js-0.7.17.tgz",
-      "integrity": "sha1-6exflJi57JEOeuOsYmqAXE0J7Kw="
-    },
-    "uniq": {
-      "version": "https://registry.npmjs.org/uniq/-/uniq-1.0.1.tgz",
-      "integrity": "sha1-sxxa6CVIRKOoKBVBzisEuGWnNP8=",
-      "dev": true
-    },
-    "urix": {
-      "version": "https://registry.npmjs.org/urix/-/urix-0.1.0.tgz",
-      "integrity": "sha1-2pN/emLiH+wf0Y1Js1wpNQZ6bHI=",
-      "dev": true
-    },
-    "url": {
-      "version": "https://registry.npmjs.org/url/-/url-0.11.0.tgz",
-      "integrity": "sha1-ODjpfPxgUh63PFJajlW/3Z4uKPE=",
-      "dev": true,
-      "requires": {
-        "punycode": "https://registry.npmjs.org/punycode/-/punycode-1.3.2.tgz",
-        "querystring": "https://registry.npmjs.org/querystring/-/querystring-0.2.0.tgz"
-      },
-      "dependencies": {
-        "punycode": {
-          "version": "https://registry.npmjs.org/punycode/-/punycode-1.3.2.tgz",
-          "integrity": "sha1-llOgNvt8HuQjQvIyXM7v6jkmxI0=",
+        },
+        "globule": {
+          "version": "1.2.0",
+          "resolved": "https://registry.npmjs.org/globule/-/globule-1.2.0.tgz",
+          "integrity": "sha1-HcScaCLdnoovoAuiopUAboZkvQk=",
+          "dev": true,
+          "requires": {
+            "glob": "7.1.2",
+            "lodash": "4.17.4",
+            "minimatch": "3.0.4"
+          }
+        },
+        "graceful-fs": {
+          "version": "4.1.11",
+          "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.1.11.tgz",
+          "integrity": "sha1-Dovf5NHduIVNZOBOp8AOKgJuVlg=",
           "dev": true
-        }
-      }
-    },
-    "url-parse-lax": {
-      "version": "https://registry.npmjs.org/url-parse-lax/-/url-parse-lax-1.0.0.tgz",
-      "integrity": "sha1-evjzA2Rem9eaJy56FKxovAYJ2nM=",
-      "requires": {
-        "prepend-http": "https://registry.npmjs.org/prepend-http/-/prepend-http-1.0.4.tgz"
-      }
-    },
-    "url-to-options": {
-      "version": "https://registry.npmjs.org/url-to-options/-/url-to-options-1.0.1.tgz",
-      "integrity": "sha1-FQWgOiiaSMvXpDTvuu7FBV9WM6k="
-    },
-    "util-deprecate": {
-      "version": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz",
-      "integrity": "sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8="
-    },
-    "uuid": {
-      "version": "https://registry.npmjs.org/uuid/-/uuid-3.1.0.tgz",
-      "integrity": "sha1-PdPT55Crwk17DToDT/q6vijrvAQ="
-    },
-    "validate-npm-package-license": {
-      "version": "https://registry.npmjs.org/validate-npm-package-license/-/validate-npm-package-license-3.0.1.tgz",
-      "integrity": "sha1-KAS6vnEq0zeUWaz74kdGqywwP7w=",
-      "requires": {
-        "spdx-correct": "https://registry.npmjs.org/spdx-correct/-/spdx-correct-1.0.2.tgz",
-        "spdx-expression-parse": "https://registry.npmjs.org/spdx-expression-parse/-/spdx-expression-parse-1.0.4.tgz"
-      }
-    },
-    "validator": {
-      "version": "https://registry.npmjs.org/validator/-/validator-7.0.0.tgz",
-      "integrity": "sha1-x03rgGNRL6w1VHk45vCxUEooL9I=",
-      "dev": true
-    },
-    "verror": {
-      "version": "https://registry.npmjs.org/verror/-/verror-1.10.0.tgz",
-      "integrity": "sha1-OhBcoXBTr1XW4nDB+CiGguGNpAA=",
-      "requires": {
-        "assert-plus": "https://registry.npmjs.org/assert-plus/-/assert-plus-1.0.0.tgz",
-        "core-util-is": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.2.tgz",
-        "extsprintf": "https://registry.npmjs.org/extsprintf/-/extsprintf-1.3.0.tgz"
-      }
-    },
-    "walkdir": {
-      "version": "https://registry.npmjs.org/walkdir/-/walkdir-0.0.11.tgz",
-      "integrity": "sha1-oW0CXrkxvQO1LzCMrtD0D86+lTI=",
-      "dev": true
-    },
-    "warning": {
-      "version": "https://registry.npmjs.org/warning/-/warning-3.0.0.tgz",
-      "integrity": "sha1-MuU3fLVy3kqwR1O9+IIcAe1gW3w=",
-      "requires": {
-        "loose-envify": "https://registry.npmjs.org/loose-envify/-/loose-envify-1.3.1.tgz"
-      }
-    },
-    "wdio-dot-reporter": {
-      "version": "https://registry.npmjs.org/wdio-dot-reporter/-/wdio-dot-reporter-0.0.9.tgz",
-      "integrity": "sha1-kpsq2v1J1rBTT9oGjocxm0fjj+U=",
-      "dev": true
-    },
-    "wdio-jasmine-framework": {
-      "version": "https://registry.npmjs.org/wdio-jasmine-framework/-/wdio-jasmine-framework-0.3.2.tgz",
-      "integrity": "sha1-l4QSFCEqm1f3DZB3Tw2bR759+YE=",
-      "dev": true,
-      "requires": {
-        "babel-runtime": "https://registry.npmjs.org/babel-runtime/-/babel-runtime-6.25.0.tgz",
-        "jasmine": "https://registry.npmjs.org/jasmine/-/jasmine-2.8.0.tgz",
-        "wdio-sync": "https://registry.npmjs.org/wdio-sync/-/wdio-sync-0.7.0.tgz"
-      },
-      "dependencies": {
-        "babel-runtime": {
-          "version": "https://registry.npmjs.org/babel-runtime/-/babel-runtime-6.25.0.tgz",
-          "integrity": "sha1-M7mOql1IK7AajRqmtDetKwGuxBw=",
+        },
+        "har-schema": {
+          "version": "1.0.5",
+          "resolved": "https://registry.npmjs.org/har-schema/-/har-schema-1.0.5.tgz",
+          "integrity": "sha1-0mMTX0MwfALGAq/I/pWXDAFRNp4=",
+          "dev": true
+        },
+        "har-validator": {
+          "version": "4.2.1",
+          "resolved": "https://registry.npmjs.org/har-validator/-/har-validator-4.2.1.tgz",
+          "integrity": "sha1-M0gdDxu/9gDdID11gSpqX7oALio=",
           "dev": true,
           "requires": {
-            "core-js": "https://registry.npmjs.org/core-js/-/core-js-2.5.1.tgz",
-            "regenerator-runtime": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.10.5.tgz"
+            "ajv": "4.11.8",
+            "har-schema": "1.0.5"
           }
         },
-        "regenerator-runtime": {
-          "version": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.10.5.tgz",
-          "integrity": "sha1-M2w+/BIgrc7dosn6tntaeVWjNlg=",
+        "has-ansi": {
+          "version": "2.0.0",
+          "resolved": "https://registry.npmjs.org/has-ansi/-/has-ansi-2.0.0.tgz",
+          "integrity": "sha1-NPUEnOHs3ysGSa8+8k5F7TVBbZE=",
+          "dev": true,
+          "requires": {
+            "ansi-regex": "2.1.1"
+          }
+        },
+        "has-flag": {
+          "version": "1.0.0",
+          "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-1.0.0.tgz",
+          "integrity": "sha1-nZ55MWXOAXoA8AQYxD+UKnsdEfo=",
           "dev": true
-        }
-      }
-    },
-    "wdio-junit-reporter": {
-      "version": "https://registry.npmjs.org/wdio-junit-reporter/-/wdio-junit-reporter-0.3.1.tgz",
-      "integrity": "sha1-d7G/RFDTGw+VuswW3Bunv8vY/gk=",
-      "dev": true,
-      "requires": {
-        "junit-report-builder": "https://registry.npmjs.org/junit-report-builder/-/junit-report-builder-1.2.0.tgz",
-        "mkdirp": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.1.tgz"
-      }
-    },
-    "wdio-spec-reporter": {
-      "version": "https://registry.npmjs.org/wdio-spec-reporter/-/wdio-spec-reporter-0.1.2.tgz",
-      "integrity": "sha1-kcb2CX+SrSUc0rZuOIUFxMDiR2A=",
-      "dev": true,
-      "requires": {
-        "babel-runtime": "https://registry.npmjs.org/babel-runtime/-/babel-runtime-6.25.0.tgz",
-        "humanize-duration": "https://registry.npmjs.org/humanize-duration/-/humanize-duration-3.10.1.tgz"
-      },
-      "dependencies": {
-        "babel-runtime": {
-          "version": "https://registry.npmjs.org/babel-runtime/-/babel-runtime-6.25.0.tgz",
-          "integrity": "sha1-M7mOql1IK7AajRqmtDetKwGuxBw=",
+        },
+        "hawk": {
+          "version": "3.1.3",
+          "resolved": "https://registry.npmjs.org/hawk/-/hawk-3.1.3.tgz",
+          "integrity": "sha1-B4REvXwWQLD+VA0sm3PVlnjo4cQ=",
           "dev": true,
           "requires": {
-            "core-js": "https://registry.npmjs.org/core-js/-/core-js-2.5.1.tgz",
-            "regenerator-runtime": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.10.5.tgz"
+            "boom": "2.10.1",
+            "cryptiles": "2.0.5",
+            "hoek": "2.16.3",
+            "sntp": "1.0.9"
           }
         },
-        "regenerator-runtime": {
-          "version": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.10.5.tgz",
-          "integrity": "sha1-M2w+/BIgrc7dosn6tntaeVWjNlg=",
+        "hoek": {
+          "version": "2.16.3",
+          "resolved": "https://registry.npmjs.org/hoek/-/hoek-2.16.3.tgz",
+          "integrity": "sha1-ILt0A9POo5jpHcRxCo/xuCdKJe0=",
           "dev": true
-        }
-      }
-    },
-    "wdio-sync": {
-      "version": "https://registry.npmjs.org/wdio-sync/-/wdio-sync-0.7.0.tgz",
-      "integrity": "sha1-L7B9JQEh3IH1Y1MWVCw8SaEiAWg=",
-      "dev": true,
-      "requires": {
-        "babel-runtime": "https://registry.npmjs.org/babel-runtime/-/babel-runtime-6.25.0.tgz",
-        "fibers": "https://registry.npmjs.org/fibers/-/fibers-2.0.0.tgz",
-        "object.assign": "https://registry.npmjs.org/object.assign/-/object.assign-4.0.4.tgz"
-      },
-      "dependencies": {
-        "babel-runtime": {
-          "version": "https://registry.npmjs.org/babel-runtime/-/babel-runtime-6.25.0.tgz",
-          "integrity": "sha1-M7mOql1IK7AajRqmtDetKwGuxBw=",
+        },
+        "http-signature": {
+          "version": "1.1.1",
+          "resolved": "https://registry.npmjs.org/http-signature/-/http-signature-1.1.1.tgz",
+          "integrity": "sha1-33LiZwZs0Kxn+3at+OE0qPvPkb8=",
           "dev": true,
           "requires": {
-            "core-js": "https://registry.npmjs.org/core-js/-/core-js-2.5.1.tgz",
-            "regenerator-runtime": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.10.5.tgz"
+            "assert-plus": "0.2.0",
+            "jsprim": "1.4.1",
+            "sshpk": "1.13.1"
           }
         },
-        "regenerator-runtime": {
-          "version": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.10.5.tgz",
-          "integrity": "sha1-M2w+/BIgrc7dosn6tntaeVWjNlg=",
+        "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==",
           "dev": true
-        }
-      }
-    },
-    "webdriverio": {
-      "version": "https://registry.npmjs.org/webdriverio/-/webdriverio-4.8.0.tgz",
-      "integrity": "sha1-1Skpt0kID4mWf24WFAUcvIFy0TI=",
-      "dev": true,
-      "requires": {
-        "archiver": "https://registry.npmjs.org/archiver/-/archiver-1.3.0.tgz",
-        "babel-runtime": "https://registry.npmjs.org/babel-runtime/-/babel-runtime-6.23.0.tgz",
-        "css-parse": "https://registry.npmjs.org/css-parse/-/css-parse-2.0.0.tgz",
-        "css-value": "https://registry.npmjs.org/css-value/-/css-value-0.0.1.tgz",
-        "deepmerge": "https://registry.npmjs.org/deepmerge/-/deepmerge-1.3.2.tgz",
-        "ejs": "https://registry.npmjs.org/ejs/-/ejs-2.5.7.tgz",
-        "gaze": "https://registry.npmjs.org/gaze/-/gaze-1.1.2.tgz",
-        "glob": "https://registry.npmjs.org/glob/-/glob-7.1.2.tgz",
-        "inquirer": "https://registry.npmjs.org/inquirer/-/inquirer-3.0.6.tgz",
-        "json-stringify-safe": "https://registry.npmjs.org/json-stringify-safe/-/json-stringify-safe-5.0.1.tgz",
-        "mkdirp": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.1.tgz",
-        "npm-install-package": "https://registry.npmjs.org/npm-install-package/-/npm-install-package-2.1.0.tgz",
-        "optimist": "https://registry.npmjs.org/optimist/-/optimist-0.6.1.tgz",
-        "q": "https://registry.npmjs.org/q/-/q-1.5.1.tgz",
-        "request": "https://registry.npmjs.org/request/-/request-2.81.0.tgz",
-        "rgb2hex": "https://registry.npmjs.org/rgb2hex/-/rgb2hex-0.1.0.tgz",
-        "safe-buffer": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.0.1.tgz",
-        "supports-color": "https://registry.npmjs.org/supports-color/-/supports-color-3.2.3.tgz",
-        "url": "https://registry.npmjs.org/url/-/url-0.11.0.tgz",
-        "validator": "https://registry.npmjs.org/validator/-/validator-7.0.0.tgz",
-        "wdio-dot-reporter": "https://registry.npmjs.org/wdio-dot-reporter/-/wdio-dot-reporter-0.0.9.tgz",
-        "wgxpath": "https://registry.npmjs.org/wgxpath/-/wgxpath-1.0.0.tgz"
-      },
-      "dependencies": {
-        "ajv": {
-          "version": "https://registry.npmjs.org/ajv/-/ajv-4.11.8.tgz",
-          "integrity": "sha1-gv+wKynmYq5TvcIK8VlHcGc5xTY=",
+        },
+        "inflight": {
+          "version": "1.0.6",
+          "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz",
+          "integrity": "sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk=",
+          "dev": true,
+          "requires": {
+            "once": "1.4.0",
+            "wrappy": "1.0.2"
+          }
+        },
+        "inherits": {
+          "version": "2.0.3",
+          "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz",
+          "integrity": "sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4=",
+          "dev": true
+        },
+        "inquirer": {
+          "version": "3.0.6",
+          "resolved": "https://registry.npmjs.org/inquirer/-/inquirer-3.0.6.tgz",
+          "integrity": "sha1-4EqqnQW3o8ubD0B9BDdfBEcZA0c=",
           "dev": true,
           "requires": {
-            "co": "https://registry.npmjs.org/co/-/co-4.6.0.tgz",
-            "json-stable-stringify": "https://registry.npmjs.org/json-stable-stringify/-/json-stable-stringify-1.0.1.tgz"
+            "ansi-escapes": "1.4.0",
+            "chalk": "1.1.3",
+            "cli-cursor": "2.1.0",
+            "cli-width": "2.2.0",
+            "external-editor": "2.1.0",
+            "figures": "2.0.0",
+            "lodash": "4.17.4",
+            "mute-stream": "0.0.7",
+            "run-async": "2.3.0",
+            "rx": "4.1.0",
+            "string-width": "2.1.1",
+            "strip-ansi": "3.0.1",
+            "through": "2.3.8"
           }
         },
-        "ansi-escapes": {
-          "version": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-1.4.0.tgz",
-          "integrity": "sha1-06ioOzGapneTZisT52HHkRQiMG4=",
+        "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
         },
-        "ansi-regex": {
-          "version": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-3.0.0.tgz",
-          "integrity": "sha1-7QMXwyIGT3lGbAKWa922Bas32Zg=",
+        "is-promise": {
+          "version": "2.1.0",
+          "resolved": "https://registry.npmjs.org/is-promise/-/is-promise-2.1.0.tgz",
+          "integrity": "sha1-eaKp7OfwlugPNtKy87wWwf9L8/o=",
           "dev": true
         },
-        "assert-plus": {
-          "version": "https://registry.npmjs.org/assert-plus/-/assert-plus-0.2.0.tgz",
-          "integrity": "sha1-104bh+ev/A24qttwIfP+SBAasjQ=",
+        "is-typedarray": {
+          "version": "1.0.0",
+          "resolved": "https://registry.npmjs.org/is-typedarray/-/is-typedarray-1.0.0.tgz",
+          "integrity": "sha1-5HnICFjfDBsR3dppQPlgEfzaSpo=",
           "dev": true
         },
-        "aws-sign2": {
-          "version": "https://registry.npmjs.org/aws-sign2/-/aws-sign2-0.6.0.tgz",
-          "integrity": "sha1-FDQt0428yU0OW4fXY81jYSwOeU8=",
+        "isarray": {
+          "version": "1.0.0",
+          "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz",
+          "integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=",
           "dev": true
         },
-        "babel-runtime": {
-          "version": "https://registry.npmjs.org/babel-runtime/-/babel-runtime-6.23.0.tgz",
-          "integrity": "sha1-CpSJ8UTecO+zzkMArM2zKeL8VDs=",
+        "isstream": {
+          "version": "0.1.2",
+          "resolved": "https://registry.npmjs.org/isstream/-/isstream-0.1.2.tgz",
+          "integrity": "sha1-R+Y/evVa+m+S4VAOaQ64uFKcCZo=",
+          "dev": true
+        },
+        "jsbn": {
+          "version": "0.1.1",
+          "resolved": "https://registry.npmjs.org/jsbn/-/jsbn-0.1.1.tgz",
+          "integrity": "sha1-peZUwuWi3rXyAdls77yoDA7y9RM=",
+          "dev": true,
+          "optional": true
+        },
+        "json-schema": {
+          "version": "0.2.3",
+          "resolved": "https://registry.npmjs.org/json-schema/-/json-schema-0.2.3.tgz",
+          "integrity": "sha1-tIDIkuWaLwWVTOcnvT8qTogvnhM=",
+          "dev": true
+        },
+        "json-stable-stringify": {
+          "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": {
-            "core-js": "https://registry.npmjs.org/core-js/-/core-js-2.5.1.tgz",
-            "regenerator-runtime": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.10.5.tgz"
+            "jsonify": "0.0.0"
           }
         },
-        "boom": {
-          "version": "https://registry.npmjs.org/boom/-/boom-2.10.1.tgz",
-          "integrity": "sha1-OciRjO/1eZ+D+UkqhI9iWt0Mdm8=",
+        "json-stringify-safe": {
+          "version": "5.0.1",
+          "resolved": "https://registry.npmjs.org/json-stringify-safe/-/json-stringify-safe-5.0.1.tgz",
+          "integrity": "sha1-Epai1Y/UXxmg9s4B1lcB4sc1tus=",
+          "dev": true
+        },
+        "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",
+          "integrity": "sha1-MT5mvB5cwG5Di8G3SZwuXFastqI=",
           "dev": true,
           "requires": {
-            "hoek": "https://registry.npmjs.org/hoek/-/hoek-2.16.3.tgz"
+            "assert-plus": "1.0.0",
+            "extsprintf": "1.3.0",
+            "json-schema": "0.2.3",
+            "verror": "1.10.0"
+          },
+          "dependencies": {
+            "assert-plus": {
+              "version": "1.0.0",
+              "resolved": "https://registry.npmjs.org/assert-plus/-/assert-plus-1.0.0.tgz",
+              "integrity": "sha1-8S4PPF13sLHN2RRpQuTpbB5N1SU=",
+              "dev": true
+            }
           }
         },
-        "cryptiles": {
-          "version": "https://registry.npmjs.org/cryptiles/-/cryptiles-2.0.5.tgz",
-          "integrity": "sha1-O9/s3GCBR8HGcgL6KR59ylnqo7g=",
+        "lazystream": {
+          "version": "1.0.0",
+          "resolved": "https://registry.npmjs.org/lazystream/-/lazystream-1.0.0.tgz",
+          "integrity": "sha1-9plf4PggOS9hOWvolGJAe7dxaOQ=",
           "dev": true,
           "requires": {
-            "boom": "https://registry.npmjs.org/boom/-/boom-2.10.1.tgz"
+            "readable-stream": "2.3.3"
           }
         },
-        "deepmerge": {
-          "version": "https://registry.npmjs.org/deepmerge/-/deepmerge-1.3.2.tgz",
-          "integrity": "sha1-FmNpFinU2/42T6EqKk8KqGqjoFA=",
+        "mime-db": {
+          "version": "1.30.0",
+          "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.30.0.tgz",
+          "integrity": "sha1-dMZD2i3Z1qRTmZY0ZbJtXKfXHwE=",
           "dev": true
         },
-        "form-data": {
-          "version": "https://registry.npmjs.org/form-data/-/form-data-2.1.4.tgz",
-          "integrity": "sha1-M8GDrPGTJ27KqYFDpp6Uv+4XUNE=",
+        "mime-types": {
+          "version": "2.1.17",
+          "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.17.tgz",
+          "integrity": "sha1-Cdejk/A+mVp5+K+Fe3Cp4KsWVXo=",
           "dev": true,
           "requires": {
-            "asynckit": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz",
-            "combined-stream": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.5.tgz",
-            "mime-types": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.17.tgz"
+            "mime-db": "1.30.0"
           }
         },
-        "har-schema": {
-          "version": "https://registry.npmjs.org/har-schema/-/har-schema-1.0.5.tgz",
-          "integrity": "sha1-0mMTX0MwfALGAq/I/pWXDAFRNp4=",
+        "mimic-fn": {
+          "version": "1.1.0",
+          "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-1.1.0.tgz",
+          "integrity": "sha1-5md4PZLonb00KBi1IwudYqZyrRg=",
           "dev": true
         },
-        "har-validator": {
-          "version": "https://registry.npmjs.org/har-validator/-/har-validator-4.2.1.tgz",
-          "integrity": "sha1-M0gdDxu/9gDdID11gSpqX7oALio=",
+        "minimatch": {
+          "version": "3.0.4",
+          "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz",
+          "integrity": "sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==",
           "dev": true,
           "requires": {
-            "ajv": "https://registry.npmjs.org/ajv/-/ajv-4.11.8.tgz",
-            "har-schema": "https://registry.npmjs.org/har-schema/-/har-schema-1.0.5.tgz"
+            "brace-expansion": "1.1.8"
           }
         },
-        "has-flag": {
-          "version": "https://registry.npmjs.org/has-flag/-/has-flag-1.0.0.tgz",
-          "integrity": "sha1-nZ55MWXOAXoA8AQYxD+UKnsdEfo=",
+        "minimist": {
+          "version": "0.0.8",
+          "resolved": "https://registry.npmjs.org/minimist/-/minimist-0.0.8.tgz",
+          "integrity": "sha1-hX/Kv8M5fSYluCKCYuhqp6ARsF0=",
           "dev": true
         },
-        "hawk": {
-          "version": "https://registry.npmjs.org/hawk/-/hawk-3.1.3.tgz",
-          "integrity": "sha1-B4REvXwWQLD+VA0sm3PVlnjo4cQ=",
+        "mkdirp": {
+          "version": "0.5.1",
+          "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.1.tgz",
+          "integrity": "sha1-MAV0OOrGz3+MR2fzhkjWaX11yQM=",
           "dev": true,
           "requires": {
-            "boom": "https://registry.npmjs.org/boom/-/boom-2.10.1.tgz",
-            "cryptiles": "https://registry.npmjs.org/cryptiles/-/cryptiles-2.0.5.tgz",
-            "hoek": "https://registry.npmjs.org/hoek/-/hoek-2.16.3.tgz",
-            "sntp": "https://registry.npmjs.org/sntp/-/sntp-1.0.9.tgz"
+            "minimist": "0.0.8"
           }
         },
-        "hoek": {
-          "version": "https://registry.npmjs.org/hoek/-/hoek-2.16.3.tgz",
-          "integrity": "sha1-ILt0A9POo5jpHcRxCo/xuCdKJe0=",
+        "mute-stream": {
+          "version": "0.0.7",
+          "resolved": "https://registry.npmjs.org/mute-stream/-/mute-stream-0.0.7.tgz",
+          "integrity": "sha1-MHXOk7whuPq0PhvE2n6BFe0ee6s=",
           "dev": true
         },
-        "http-signature": {
-          "version": "https://registry.npmjs.org/http-signature/-/http-signature-1.1.1.tgz",
-          "integrity": "sha1-33LiZwZs0Kxn+3at+OE0qPvPkb8=",
+        "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": {
-            "assert-plus": "https://registry.npmjs.org/assert-plus/-/assert-plus-0.2.0.tgz",
-            "jsprim": "https://registry.npmjs.org/jsprim/-/jsprim-1.4.1.tgz",
-            "sshpk": "https://registry.npmjs.org/sshpk/-/sshpk-1.13.1.tgz"
+            "remove-trailing-separator": "1.1.0"
           }
         },
-        "inquirer": {
-          "version": "https://registry.npmjs.org/inquirer/-/inquirer-3.0.6.tgz",
-          "integrity": "sha1-4EqqnQW3o8ubD0B9BDdfBEcZA0c=",
+        "npm-install-package": {
+          "version": "2.1.0",
+          "resolved": "https://registry.npmjs.org/npm-install-package/-/npm-install-package-2.1.0.tgz",
+          "integrity": "sha1-1+/jz816sAYUuJbqUxGdyaslkSU=",
+          "dev": true
+        },
+        "oauth-sign": {
+          "version": "0.8.2",
+          "resolved": "https://registry.npmjs.org/oauth-sign/-/oauth-sign-0.8.2.tgz",
+          "integrity": "sha1-Rqarfwrq2N6unsBWV4C31O/rnUM=",
+          "dev": true
+        },
+        "once": {
+          "version": "1.4.0",
+          "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz",
+          "integrity": "sha1-WDsap3WWHUsROsF9nFC6753Xa9E=",
           "dev": true,
           "requires": {
-            "ansi-escapes": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-1.4.0.tgz",
-            "chalk": "https://registry.npmjs.org/chalk/-/chalk-1.1.3.tgz",
-            "cli-cursor": "https://registry.npmjs.org/cli-cursor/-/cli-cursor-2.1.0.tgz",
-            "cli-width": "https://registry.npmjs.org/cli-width/-/cli-width-2.2.0.tgz",
-            "external-editor": "https://registry.npmjs.org/external-editor/-/external-editor-2.0.5.tgz",
-            "figures": "https://registry.npmjs.org/figures/-/figures-2.0.0.tgz",
-            "lodash": "https://registry.npmjs.org/lodash/-/lodash-4.17.4.tgz",
-            "mute-stream": "https://registry.npmjs.org/mute-stream/-/mute-stream-0.0.7.tgz",
-            "run-async": "https://registry.npmjs.org/run-async/-/run-async-2.3.0.tgz",
-            "rx": "https://registry.npmjs.org/rx/-/rx-4.1.0.tgz",
-            "string-width": "https://registry.npmjs.org/string-width/-/string-width-2.1.1.tgz",
-            "strip-ansi": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-3.0.1.tgz",
-            "through": "https://registry.npmjs.org/through/-/through-2.3.8.tgz"
+            "wrappy": "1.0.2"
           }
         },
-        "is-fullwidth-code-point": {
-          "version": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-2.0.0.tgz",
-          "integrity": "sha1-o7MKXE8ZkYMWeqq5O+764937ZU8=",
+        "onetime": {
+          "version": "2.0.1",
+          "resolved": "https://registry.npmjs.org/onetime/-/onetime-2.0.1.tgz",
+          "integrity": "sha1-BnQoIw/WdEOyeUsiu6UotoZ5YtQ=",
+          "dev": true,
+          "requires": {
+            "mimic-fn": "1.1.0"
+          }
+        },
+        "optimist": {
+          "version": "0.6.1",
+          "resolved": "https://registry.npmjs.org/optimist/-/optimist-0.6.1.tgz",
+          "integrity": "sha1-2j6nRob6IaGaERwybpDrFaAZZoY=",
+          "dev": true,
+          "requires": {
+            "minimist": "0.0.8",
+            "wordwrap": "0.0.3"
+          }
+        },
+        "os-tmpdir": {
+          "version": "1.0.2",
+          "resolved": "https://registry.npmjs.org/os-tmpdir/-/os-tmpdir-1.0.2.tgz",
+          "integrity": "sha1-u+Z0BseaqFxc/sdm/lc0VV36EnQ=",
+          "dev": true
+        },
+        "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=",
           "dev": true
         },
         "performance-now": {
-          "version": "https://registry.npmjs.org/performance-now/-/performance-now-0.2.0.tgz",
+          "version": "0.2.0",
+          "resolved": "https://registry.npmjs.org/performance-now/-/performance-now-0.2.0.tgz",
           "integrity": "sha1-M+8wxcd9TqIcWlOGnZG1bY8lVeU=",
           "dev": true
         },
+        "process-nextick-args": {
+          "version": "1.0.7",
+          "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-1.0.7.tgz",
+          "integrity": "sha1-FQ4gt1ZZCtP5EJPyWk8q2L/zC6M=",
+          "dev": true
+        },
+        "punycode": {
+          "version": "1.4.1",
+          "resolved": "https://registry.npmjs.org/punycode/-/punycode-1.4.1.tgz",
+          "integrity": "sha1-wNWmOycYgArY4esPpSachN1BhF4=",
+          "dev": true
+        },
+        "q": {
+          "version": "1.5.1",
+          "resolved": "https://registry.npmjs.org/q/-/q-1.5.1.tgz",
+          "integrity": "sha1-fjL3W0E4EpHQRhHxvxQQmsAGUdc=",
+          "dev": true
+        },
         "qs": {
-          "version": "https://registry.npmjs.org/qs/-/qs-6.4.0.tgz",
+          "version": "6.4.0",
+          "resolved": "https://registry.npmjs.org/qs/-/qs-6.4.0.tgz",
           "integrity": "sha1-E+JtKK1rD/qpExLNO/cI7TUecjM=",
           "dev": true
         },
+        "querystring": {
+          "version": "0.2.0",
+          "resolved": "https://registry.npmjs.org/querystring/-/querystring-0.2.0.tgz",
+          "integrity": "sha1-sgmEkgO7Jd+CDadW50cAWHhSFiA=",
+          "dev": true
+        },
+        "readable-stream": {
+          "version": "2.3.3",
+          "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.3.tgz",
+          "integrity": "sha512-m+qzzcn7KUxEmd1gMbchF+Y2eIUbieUaxkWtptyHywrX0rE8QEYqPC07Vuy4Wm32/xE16NcdBctb8S0Xe/5IeQ==",
+          "dev": true,
+          "requires": {
+            "core-util-is": "1.0.2",
+            "inherits": "2.0.3",
+            "isarray": "1.0.0",
+            "process-nextick-args": "1.0.7",
+            "safe-buffer": "5.1.1",
+            "string_decoder": "1.0.3",
+            "util-deprecate": "1.0.2"
+          },
+          "dependencies": {
+            "safe-buffer": {
+              "version": "5.1.1",
+              "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.1.tgz",
+              "integrity": "sha512-kKvNJn6Mm93gAczWVJg7wH+wGYWNrDHdWvpUmHyEsgCtIwwo3bqPtV4tR5tuPaUhTOo/kvhVwd8XwwOllGYkbg==",
+              "dev": true
+            }
+          }
+        },
         "regenerator-runtime": {
-          "version": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.10.5.tgz",
+          "version": "0.10.5",
+          "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.10.5.tgz",
           "integrity": "sha1-M2w+/BIgrc7dosn6tntaeVWjNlg=",
           "dev": true
         },
+        "remove-trailing-separator": {
+          "version": "1.1.0",
+          "resolved": "https://registry.npmjs.org/remove-trailing-separator/-/remove-trailing-separator-1.1.0.tgz",
+          "integrity": "sha1-wkvOKig62tW8P1jg1IJJuSN52O8=",
+          "dev": true
+        },
         "request": {
-          "version": "https://registry.npmjs.org/request/-/request-2.81.0.tgz",
+          "version": "2.81.0",
+          "resolved": "https://registry.npmjs.org/request/-/request-2.81.0.tgz",
           "integrity": "sha1-xpKJRqDgbF+Nb4qTM0af/aRimKA=",
           "dev": true,
           "requires": {
-            "aws-sign2": "https://registry.npmjs.org/aws-sign2/-/aws-sign2-0.6.0.tgz",
-            "aws4": "https://registry.npmjs.org/aws4/-/aws4-1.6.0.tgz",
-            "caseless": "https://registry.npmjs.org/caseless/-/caseless-0.12.0.tgz",
-            "combined-stream": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.5.tgz",
-            "extend": "https://registry.npmjs.org/extend/-/extend-3.0.1.tgz",
-            "forever-agent": "https://registry.npmjs.org/forever-agent/-/forever-agent-0.6.1.tgz",
-            "form-data": "https://registry.npmjs.org/form-data/-/form-data-2.1.4.tgz",
-            "har-validator": "https://registry.npmjs.org/har-validator/-/har-validator-4.2.1.tgz",
-            "hawk": "https://registry.npmjs.org/hawk/-/hawk-3.1.3.tgz",
-            "http-signature": "https://registry.npmjs.org/http-signature/-/http-signature-1.1.1.tgz",
-            "is-typedarray": "https://registry.npmjs.org/is-typedarray/-/is-typedarray-1.0.0.tgz",
-            "isstream": "https://registry.npmjs.org/isstream/-/isstream-0.1.2.tgz",
-            "json-stringify-safe": "https://registry.npmjs.org/json-stringify-safe/-/json-stringify-safe-5.0.1.tgz",
-            "mime-types": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.17.tgz",
-            "oauth-sign": "https://registry.npmjs.org/oauth-sign/-/oauth-sign-0.8.2.tgz",
-            "performance-now": "https://registry.npmjs.org/performance-now/-/performance-now-0.2.0.tgz",
-            "qs": "https://registry.npmjs.org/qs/-/qs-6.4.0.tgz",
-            "safe-buffer": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.0.1.tgz",
-            "stringstream": "https://registry.npmjs.org/stringstream/-/stringstream-0.0.5.tgz",
-            "tough-cookie": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-2.3.3.tgz",
-            "tunnel-agent": "https://registry.npmjs.org/tunnel-agent/-/tunnel-agent-0.6.0.tgz",
-            "uuid": "https://registry.npmjs.org/uuid/-/uuid-3.1.0.tgz"
+            "aws-sign2": "0.6.0",
+            "aws4": "1.6.0",
+            "caseless": "0.12.0",
+            "combined-stream": "1.0.5",
+            "extend": "3.0.1",
+            "forever-agent": "0.6.1",
+            "form-data": "2.1.4",
+            "har-validator": "4.2.1",
+            "hawk": "3.1.3",
+            "http-signature": "1.1.1",
+            "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",
+            "performance-now": "0.2.0",
+            "qs": "6.4.0",
+            "safe-buffer": "5.0.1",
+            "stringstream": "0.0.5",
+            "tough-cookie": "2.3.3",
+            "tunnel-agent": "0.6.0",
+            "uuid": "3.1.0"
+          }
+        },
+        "resolve-url": {
+          "version": "0.2.1",
+          "resolved": "https://registry.npmjs.org/resolve-url/-/resolve-url-0.2.1.tgz",
+          "integrity": "sha1-LGN/53yJOv0qZj/iGqkIAGjiBSo=",
+          "dev": true
+        },
+        "restore-cursor": {
+          "version": "2.0.0",
+          "resolved": "https://registry.npmjs.org/restore-cursor/-/restore-cursor-2.0.0.tgz",
+          "integrity": "sha1-n37ih/gv0ybU/RYpI9YhKe7g368=",
+          "dev": true,
+          "requires": {
+            "onetime": "2.0.1",
+            "signal-exit": "3.0.2"
+          }
+        },
+        "rgb2hex": {
+          "version": "0.1.0",
+          "resolved": "https://registry.npmjs.org/rgb2hex/-/rgb2hex-0.1.0.tgz",
+          "integrity": "sha1-zNVfhgrgxcTqN1BLlY5ELY0SMls=",
+          "dev": true
+        },
+        "run-async": {
+          "version": "2.3.0",
+          "resolved": "https://registry.npmjs.org/run-async/-/run-async-2.3.0.tgz",
+          "integrity": "sha1-A3GrSuC91yDUFm19/aZP96RFpsA=",
+          "dev": true,
+          "requires": {
+            "is-promise": "2.1.0"
           }
         },
+        "rx": {
+          "version": "4.1.0",
+          "resolved": "https://registry.npmjs.org/rx/-/rx-4.1.0.tgz",
+          "integrity": "sha1-pfE/957zt0D+MKqAP7CfmIBdR4I=",
+          "dev": true
+        },
         "safe-buffer": {
-          "version": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.0.1.tgz",
+          "version": "5.0.1",
+          "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.0.1.tgz",
           "integrity": "sha1-0mPKVGls2KMGtcplUekt5XkY++c=",
           "dev": true
         },
+        "signal-exit": {
+          "version": "3.0.2",
+          "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.2.tgz",
+          "integrity": "sha1-tf3AjxKH6hF4Yo5BXiUTK3NkbG0=",
+          "dev": true
+        },
         "sntp": {
-          "version": "https://registry.npmjs.org/sntp/-/sntp-1.0.9.tgz",
+          "version": "1.0.9",
+          "resolved": "https://registry.npmjs.org/sntp/-/sntp-1.0.9.tgz",
           "integrity": "sha1-ZUEYTMkK7qbG57NeJlkIJEPGYZg=",
           "dev": true,
           "requires": {
-            "hoek": "https://registry.npmjs.org/hoek/-/hoek-2.16.3.tgz"
+            "hoek": "2.16.3"
+          }
+        },
+        "source-map": {
+          "version": "0.1.43",
+          "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.1.43.tgz",
+          "integrity": "sha1-wkvBRspRfBRx9drL4lcbK3+eM0Y=",
+          "dev": true,
+          "requires": {
+            "amdefine": "1.0.1"
+          }
+        },
+        "source-map-resolve": {
+          "version": "0.3.1",
+          "resolved": "https://registry.npmjs.org/source-map-resolve/-/source-map-resolve-0.3.1.tgz",
+          "integrity": "sha1-YQ9hIqRFuN1RU1oqcbeD38Ekh2E=",
+          "dev": true,
+          "requires": {
+            "atob": "1.1.3",
+            "resolve-url": "0.2.1",
+            "source-map-url": "0.3.0",
+            "urix": "0.1.0"
+          }
+        },
+        "source-map-url": {
+          "version": "0.3.0",
+          "resolved": "https://registry.npmjs.org/source-map-url/-/source-map-url-0.3.0.tgz",
+          "integrity": "sha1-fsrxO1e80J2opAxdJp2zN5nUqvk=",
+          "dev": true
+        },
+        "sshpk": {
+          "version": "1.13.1",
+          "resolved": "https://registry.npmjs.org/sshpk/-/sshpk-1.13.1.tgz",
+          "integrity": "sha1-US322mKHFEMW3EwY/hzx2UBzm+M=",
+          "dev": true,
+          "requires": {
+            "asn1": "0.2.3",
+            "assert-plus": "1.0.0",
+            "bcrypt-pbkdf": "1.0.1",
+            "dashdash": "1.14.1",
+            "ecc-jsbn": "0.1.1",
+            "getpass": "0.1.7",
+            "jsbn": "0.1.1",
+            "tweetnacl": "0.14.5"
+          },
+          "dependencies": {
+            "assert-plus": {
+              "version": "1.0.0",
+              "resolved": "https://registry.npmjs.org/assert-plus/-/assert-plus-1.0.0.tgz",
+              "integrity": "sha1-8S4PPF13sLHN2RRpQuTpbB5N1SU=",
+              "dev": true
+            }
           }
         },
         "string-width": {
-          "version": "https://registry.npmjs.org/string-width/-/string-width-2.1.1.tgz",
-          "integrity": "sha1-q5Pyeo3BPSjKyBXEYhQ6bZASrp4=",
+          "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": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-2.0.0.tgz",
-            "strip-ansi": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-4.0.0.tgz"
+            "is-fullwidth-code-point": "2.0.0",
+            "strip-ansi": "4.0.0"
           },
           "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
+            },
             "strip-ansi": {
-              "version": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-4.0.0.tgz",
+              "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": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-3.0.0.tgz"
+                "ansi-regex": "3.0.0"
               }
             }
           }
         },
+        "string_decoder": {
+          "version": "1.0.3",
+          "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.0.3.tgz",
+          "integrity": "sha512-4AH6Z5fzNNBcH+6XDMfA/BTt87skxqJlO0lAh3Dker5zThcAxG6mKz+iGu308UKoPPQ8Dcqx/4JhujzltRa+hQ==",
+          "dev": true,
+          "requires": {
+            "safe-buffer": "5.1.1"
+          },
+          "dependencies": {
+            "safe-buffer": {
+              "version": "5.1.1",
+              "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.1.tgz",
+              "integrity": "sha512-kKvNJn6Mm93gAczWVJg7wH+wGYWNrDHdWvpUmHyEsgCtIwwo3bqPtV4tR5tuPaUhTOo/kvhVwd8XwwOllGYkbg==",
+              "dev": true
+            }
+          }
+        },
+        "stringstream": {
+          "version": "0.0.5",
+          "resolved": "https://registry.npmjs.org/stringstream/-/stringstream-0.0.5.tgz",
+          "integrity": "sha1-TkhM1N5aC7vuGORjB3EKioFiGHg=",
+          "dev": true
+        },
+        "strip-ansi": {
+          "version": "3.0.1",
+          "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-3.0.1.tgz",
+          "integrity": "sha1-ajhfuIU9lS1f8F0Oiq+UJ43GPc8=",
+          "dev": true,
+          "requires": {
+            "ansi-regex": "2.1.1"
+          }
+        },
         "supports-color": {
-          "version": "https://registry.npmjs.org/supports-color/-/supports-color-3.2.3.tgz",
+          "version": "3.2.3",
+          "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-3.2.3.tgz",
           "integrity": "sha1-ZawFBLOVQXHYpklGsq48u4pfVPY=",
           "dev": true,
           "requires": {
-            "has-flag": "https://registry.npmjs.org/has-flag/-/has-flag-1.0.0.tgz"
+            "has-flag": "1.0.0"
+          }
+        },
+        "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==",
+          "dev": true,
+          "requires": {
+            "bl": "1.2.1",
+            "end-of-stream": "1.4.0",
+            "readable-stream": "2.3.3",
+            "xtend": "4.0.1"
+          }
+        },
+        "through": {
+          "version": "2.3.8",
+          "resolved": "https://registry.npmjs.org/through/-/through-2.3.8.tgz",
+          "integrity": "sha1-DdTJ/6q8NXlgsbckEV1+Doai4fU=",
+          "dev": true
+        },
+        "tmp": {
+          "version": "0.0.33",
+          "resolved": "https://registry.npmjs.org/tmp/-/tmp-0.0.33.tgz",
+          "integrity": "sha512-jRCJlojKnZ3addtTOjdIqoRuPEKBvNXcGYqzO6zWZX8KfKEpnGY5jfggJQ3EjKuu8D4bJRr0y+cYJFmYbImXGw==",
+          "dev": true,
+          "requires": {
+            "os-tmpdir": "1.0.2"
+          }
+        },
+        "tough-cookie": {
+          "version": "2.3.3",
+          "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-2.3.3.tgz",
+          "integrity": "sha1-C2GKVWW23qkL80JdBNVe3EdadWE=",
+          "dev": true,
+          "requires": {
+            "punycode": "1.4.1"
+          }
+        },
+        "tunnel-agent": {
+          "version": "0.6.0",
+          "resolved": "https://registry.npmjs.org/tunnel-agent/-/tunnel-agent-0.6.0.tgz",
+          "integrity": "sha1-J6XeoGs2sEoKmWZ3SykIaPD8QP0=",
+          "dev": true,
+          "requires": {
+            "safe-buffer": "5.0.1"
+          }
+        },
+        "tweetnacl": {
+          "version": "0.14.5",
+          "resolved": "https://registry.npmjs.org/tweetnacl/-/tweetnacl-0.14.5.tgz",
+          "integrity": "sha1-WuaBd/GS1EViadEIr6k/+HQ/T2Q=",
+          "dev": true,
+          "optional": true
+        },
+        "urix": {
+          "version": "0.1.0",
+          "resolved": "https://registry.npmjs.org/urix/-/urix-0.1.0.tgz",
+          "integrity": "sha1-2pN/emLiH+wf0Y1Js1wpNQZ6bHI=",
+          "dev": true
+        },
+        "url": {
+          "version": "0.11.0",
+          "resolved": "https://registry.npmjs.org/url/-/url-0.11.0.tgz",
+          "integrity": "sha1-ODjpfPxgUh63PFJajlW/3Z4uKPE=",
+          "dev": true,
+          "requires": {
+            "punycode": "1.3.2",
+            "querystring": "0.2.0"
+          },
+          "dependencies": {
+            "punycode": {
+              "version": "1.3.2",
+              "resolved": "https://registry.npmjs.org/punycode/-/punycode-1.3.2.tgz",
+              "integrity": "sha1-llOgNvt8HuQjQvIyXM7v6jkmxI0=",
+              "dev": true
+            }
+          }
+        },
+        "util-deprecate": {
+          "version": "1.0.2",
+          "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz",
+          "integrity": "sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8=",
+          "dev": true
+        },
+        "uuid": {
+          "version": "3.1.0",
+          "resolved": "https://registry.npmjs.org/uuid/-/uuid-3.1.0.tgz",
+          "integrity": "sha512-DIWtzUkw04M4k3bf1IcpS2tngXEL26YUD2M0tMDUpnUrz2hgzUBlD55a4FjdLGPvfHxS6uluGWvaVEqgBcVa+g==",
+          "dev": true
+        },
+        "validator": {
+          "version": "7.0.0",
+          "resolved": "https://registry.npmjs.org/validator/-/validator-7.0.0.tgz",
+          "integrity": "sha1-x03rgGNRL6w1VHk45vCxUEooL9I=",
+          "dev": true
+        },
+        "verror": {
+          "version": "1.10.0",
+          "resolved": "https://registry.npmjs.org/verror/-/verror-1.10.0.tgz",
+          "integrity": "sha1-OhBcoXBTr1XW4nDB+CiGguGNpAA=",
+          "dev": true,
+          "requires": {
+            "assert-plus": "1.0.0",
+            "core-util-is": "1.0.2",
+            "extsprintf": "1.3.0"
+          },
+          "dependencies": {
+            "assert-plus": {
+              "version": "1.0.0",
+              "resolved": "https://registry.npmjs.org/assert-plus/-/assert-plus-1.0.0.tgz",
+              "integrity": "sha1-8S4PPF13sLHN2RRpQuTpbB5N1SU=",
+              "dev": true
+            }
+          }
+        },
+        "walkdir": {
+          "version": "0.0.11",
+          "resolved": "https://registry.npmjs.org/walkdir/-/walkdir-0.0.11.tgz",
+          "integrity": "sha1-oW0CXrkxvQO1LzCMrtD0D86+lTI=",
+          "dev": true
+        },
+        "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=",
+          "dev": true
+        },
+        "wgxpath": {
+          "version": "1.0.0",
+          "resolved": "https://registry.npmjs.org/wgxpath/-/wgxpath-1.0.0.tgz",
+          "integrity": "sha1-7vikudVYzEla06mit1FZfs2a9pA=",
+          "dev": true
+        },
+        "wordwrap": {
+          "version": "0.0.3",
+          "resolved": "https://registry.npmjs.org/wordwrap/-/wordwrap-0.0.3.tgz",
+          "integrity": "sha1-o9XabNXAvAAI03I0u68b7WMFkQc=",
+          "dev": true
+        },
+        "wrappy": {
+          "version": "1.0.2",
+          "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz",
+          "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=",
+          "dev": true
+        },
+        "xtend": {
+          "version": "4.0.1",
+          "resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.1.tgz",
+          "integrity": "sha1-pcbVMr5lbiPbgg77lDofBJmNY68=",
+          "dev": true
+        },
+        "zip-stream": {
+          "version": "1.2.0",
+          "resolved": "https://registry.npmjs.org/zip-stream/-/zip-stream-1.2.0.tgz",
+          "integrity": "sha1-qLxF9MG0lpnGuQGYuqyqzbzUugQ=",
+          "dev": true,
+          "requires": {
+            "archiver-utils": "1.3.0",
+            "compress-commons": "1.2.2",
+            "lodash": "4.17.4",
+            "readable-stream": "2.3.3"
           }
         }
       }
     },
-    "wgxpath": {
-      "version": "https://registry.npmjs.org/wgxpath/-/wgxpath-1.0.0.tgz",
-      "integrity": "sha1-7vikudVYzEla06mit1FZfs2a9pA=",
-      "dev": true
-    },
-    "whatwg-fetch": {
-      "version": "https://registry.npmjs.org/whatwg-fetch/-/whatwg-fetch-2.0.3.tgz",
-      "integrity": "sha1-nITsLc9oGH/wC8ZOEnS0QhduHIQ="
-    },
-    "which": {
-      "version": "https://registry.npmjs.org/which/-/which-1.3.0.tgz",
-      "integrity": "sha1-/wS9/AEO5UfXgL7DjhrBwnd9JTo=",
-      "requires": {
-        "isexe": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz"
-      }
-    },
-    "which-module": {
-      "version": "https://registry.npmjs.org/which-module/-/which-module-1.0.0.tgz",
-      "integrity": "sha1-u6Y8qGGUiZT/MHc2CJ47lgJsKk8="
-    },
-    "wide-align": {
-      "version": "https://registry.npmjs.org/wide-align/-/wide-align-1.1.2.tgz",
-      "integrity": "sha1-Vx4PGwYEY268DfwhsDObvjE0FxA=",
-      "requires": {
-        "string-width": "https://registry.npmjs.org/string-width/-/string-width-1.0.2.tgz"
-      }
-    },
     "winston": {
-      "version": "https://registry.npmjs.org/winston/-/winston-2.4.0.tgz",
+      "version": "2.4.0",
+      "resolved": "https://registry.npmjs.org/winston/-/winston-2.4.0.tgz",
       "integrity": "sha1-gIBQuT1SZh7Z+2wms/DIJnCLCu4=",
       "requires": {
-        "async": "https://registry.npmjs.org/async/-/async-1.0.0.tgz",
-        "colors": "https://registry.npmjs.org/colors/-/colors-1.0.3.tgz",
-        "cycle": "https://registry.npmjs.org/cycle/-/cycle-1.0.3.tgz",
-        "eyes": "https://registry.npmjs.org/eyes/-/eyes-0.1.8.tgz",
-        "isstream": "https://registry.npmjs.org/isstream/-/isstream-0.1.2.tgz",
-        "stack-trace": "https://registry.npmjs.org/stack-trace/-/stack-trace-0.0.10.tgz"
-      }
-    },
-    "wordwrap": {
-      "version": "https://registry.npmjs.org/wordwrap/-/wordwrap-1.0.0.tgz",
-      "integrity": "sha1-J1hIEIkUVqQXHI0CJkQa3pDLyus=",
-      "dev": true
-    },
-    "wrap-ansi": {
-      "version": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-2.1.0.tgz",
-      "integrity": "sha1-2Pw9KE3QV5T+hJc8rs3Rz4JP3YU=",
-      "requires": {
-        "string-width": "https://registry.npmjs.org/string-width/-/string-width-1.0.2.tgz",
-        "strip-ansi": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-3.0.1.tgz"
-      }
-    },
-    "wrappy": {
-      "version": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz",
-      "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8="
-    },
-    "write": {
-      "version": "https://registry.npmjs.org/write/-/write-0.2.1.tgz",
-      "integrity": "sha1-X8A4KOJkzqP+kUVUdvejxWbLB1c=",
-      "dev": true,
-      "requires": {
-        "mkdirp": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.1.tgz"
-      }
-    },
-    "xml2js": {
-      "version": "https://registry.npmjs.org/xml2js/-/xml2js-0.4.19.tgz",
-      "integrity": "sha1-aGwg8hMgnpSr8NG88e+qKRx4J6c=",
-      "requires": {
-        "sax": "https://registry.npmjs.org/sax/-/sax-1.2.4.tgz",
-        "xmlbuilder": "https://registry.npmjs.org/xmlbuilder/-/xmlbuilder-9.0.4.tgz"
-      }
-    },
-    "xmlbuilder": {
-      "version": "https://registry.npmjs.org/xmlbuilder/-/xmlbuilder-9.0.4.tgz",
-      "integrity": "sha1-UZy0ymhtAFqEINNJbz8MruzKWA8="
-    },
-    "xtend": {
-      "version": "https://registry.npmjs.org/xtend/-/xtend-4.0.1.tgz",
-      "integrity": "sha1-pcbVMr5lbiPbgg77lDofBJmNY68=",
-      "dev": true
-    },
-    "y18n": {
-      "version": "https://registry.npmjs.org/y18n/-/y18n-3.2.1.tgz",
-      "integrity": "sha1-bRX7qITAhnnA136I53WegR4H+kE="
-    },
-    "yallist": {
-      "version": "https://registry.npmjs.org/yallist/-/yallist-2.1.2.tgz",
-      "integrity": "sha1-HBH5IY8HYImkfdUS+TxmmaaoHVI="
-    },
-    "yargs": {
-      "version": "https://registry.npmjs.org/yargs/-/yargs-7.1.0.tgz",
-      "integrity": "sha1-a6MY6xaWFyf10oT46gA+jWFU0Mg=",
-      "requires": {
-        "camelcase": "https://registry.npmjs.org/camelcase/-/camelcase-3.0.0.tgz",
-        "cliui": "https://registry.npmjs.org/cliui/-/cliui-3.2.0.tgz",
-        "decamelize": "https://registry.npmjs.org/decamelize/-/decamelize-1.2.0.tgz",
-        "get-caller-file": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-1.0.2.tgz",
-        "os-locale": "https://registry.npmjs.org/os-locale/-/os-locale-1.4.0.tgz",
-        "read-pkg-up": "https://registry.npmjs.org/read-pkg-up/-/read-pkg-up-1.0.1.tgz",
-        "require-directory": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz",
-        "require-main-filename": "https://registry.npmjs.org/require-main-filename/-/require-main-filename-1.0.1.tgz",
-        "set-blocking": "https://registry.npmjs.org/set-blocking/-/set-blocking-2.0.0.tgz",
-        "string-width": "https://registry.npmjs.org/string-width/-/string-width-1.0.2.tgz",
-        "which-module": "https://registry.npmjs.org/which-module/-/which-module-1.0.0.tgz",
-        "y18n": "https://registry.npmjs.org/y18n/-/y18n-3.2.1.tgz",
-        "yargs-parser": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-5.0.0.tgz"
+        "async": "1.0.0",
+        "colors": "1.0.3",
+        "cycle": "1.0.3",
+        "eyes": "0.1.8",
+        "isstream": "0.1.2",
+        "stack-trace": "0.0.10"
       },
       "dependencies": {
-        "camelcase": {
-          "version": "https://registry.npmjs.org/camelcase/-/camelcase-3.0.0.tgz",
-          "integrity": "sha1-MvxLn82vhF/N9+c7uXysImHwqwo="
+        "async": {
+          "version": "1.0.0",
+          "resolved": "https://registry.npmjs.org/async/-/async-1.0.0.tgz",
+          "integrity": "sha1-+PwEyjoTeErenhZBr5hXjPvWR6k="
+        },
+        "colors": {
+          "version": "1.0.3",
+          "resolved": "https://registry.npmjs.org/colors/-/colors-1.0.3.tgz",
+          "integrity": "sha1-BDP0TYCWgP3rYO0mDxsMJi6CpAs="
+        },
+        "cycle": {
+          "version": "1.0.3",
+          "resolved": "https://registry.npmjs.org/cycle/-/cycle-1.0.3.tgz",
+          "integrity": "sha1-IegLK+hYD5i0aPN5QwZisEbDStI="
+        },
+        "eyes": {
+          "version": "0.1.8",
+          "resolved": "https://registry.npmjs.org/eyes/-/eyes-0.1.8.tgz",
+          "integrity": "sha1-Ys8SAjTGg3hdkCNIqADvPgzCC8A="
+        },
+        "isstream": {
+          "version": "0.1.2",
+          "resolved": "https://registry.npmjs.org/isstream/-/isstream-0.1.2.tgz",
+          "integrity": "sha1-R+Y/evVa+m+S4VAOaQ64uFKcCZo="
+        },
+        "stack-trace": {
+          "version": "0.0.10",
+          "resolved": "https://registry.npmjs.org/stack-trace/-/stack-trace-0.0.10.tgz",
+          "integrity": "sha1-VHxws0fo0ytOEI6hoqFZ5f3eGcA="
         }
       }
     },
-    "yargs-parser": {
-      "version": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-5.0.0.tgz",
-      "integrity": "sha1-J17PDX/+Bcd+ZOfIbkzZS/DhIoo=",
+    "xml2js": {
+      "version": "0.4.19",
+      "resolved": "https://registry.npmjs.org/xml2js/-/xml2js-0.4.19.tgz",
+      "integrity": "sha512-esZnJZJOiJR9wWKMyuvSE1y6Dq5LCuJanqhxslH2bxM6duahNZ+HMpCLhBQGZkbX6xRf8x1Y2eJlgt2q3qo49Q==",
       "requires": {
-        "camelcase": "https://registry.npmjs.org/camelcase/-/camelcase-3.0.0.tgz"
+        "sax": "1.2.4",
+        "xmlbuilder": "9.0.4"
       },
       "dependencies": {
-        "camelcase": {
-          "version": "https://registry.npmjs.org/camelcase/-/camelcase-3.0.0.tgz",
-          "integrity": "sha1-MvxLn82vhF/N9+c7uXysImHwqwo="
+        "sax": {
+          "version": "1.2.4",
+          "resolved": "https://registry.npmjs.org/sax/-/sax-1.2.4.tgz",
+          "integrity": "sha512-NqVDv9TpANUjFm0N8uM5GxL36UgKi9/atZw+x7YFnQ8ckwFGKrl4xX4yWtrey3UJm5nP1kUbnYgLopqWNSRhWw=="
+        },
+        "xmlbuilder": {
+          "version": "9.0.4",
+          "resolved": "https://registry.npmjs.org/xmlbuilder/-/xmlbuilder-9.0.4.tgz",
+          "integrity": "sha1-UZy0ymhtAFqEINNJbz8MruzKWA8="
         }
       }
-    },
-    "zip-stream": {
-      "version": "https://registry.npmjs.org/zip-stream/-/zip-stream-1.2.0.tgz",
-      "integrity": "sha1-qLxF9MG0lpnGuQGYuqyqzbzUugQ=",
-      "dev": true,
-      "requires": {
-        "archiver-utils": "https://registry.npmjs.org/archiver-utils/-/archiver-utils-1.3.0.tgz",
-        "compress-commons": "https://registry.npmjs.org/compress-commons/-/compress-commons-1.2.2.tgz",
-        "lodash": "https://registry.npmjs.org/lodash/-/lodash-4.17.4.tgz",
-        "readable-stream": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.3.tgz"
-      }
     }
   }
 }
diff --git a/bigbluebutton-html5/package.json b/bigbluebutton-html5/package.json
old mode 100755
new mode 100644
index 031ba58a0fa4bc8f9c2e1404d918969a1f8d41a5..fc95b44e923a100b80c2247bf523c0482bf375ff
--- a/bigbluebutton-html5/package.json
+++ b/bigbluebutton-html5/package.json
@@ -3,8 +3,8 @@
   "description": "BigBlueButton HTML5 Client",
   "scripts": {
     "start": "if test \"$NODE_ENV\" = \"production\" ; then npm run start:prod; else npm run start:dev; fi",
-    "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",
+    "start:prod": "meteor reset && ROOT_URL=http://127.0.0.1/html5client NODE_ENV=production meteor --production --settings private/config/settings-production.json",
+    "start:dev": "ROOT_URL=http://127.0.0.1/html5client NODE_ENV=development meteor --settings private/config/settings-development.json",
     "test": "wdio ./tests/webdriverio/wdio.conf.js",
     "lint": "eslint . --ext .jsx,.js",
     "precommit": "lint-staged"
@@ -20,6 +20,12 @@
   },
   "dependencies": {
     "babel-runtime": "~6.26.0",
+    "//": [
+      "core-js is included with babel-runtime",
+      "but Meteor 1.6.0.1 doesn't see it there for some reason",
+      "need to investigate"
+    ],
+    "core-js": "~2.5.3",
     "classnames": "~2.2.5",
     "clipboard": "~1.7.1",
     "eventemitter2": "~4.1.2",
@@ -41,11 +47,12 @@
     "react-modal": "~3.0.4",
     "react-router": "~3.0.2",
     "react-tabs": "~2.1.0",
-    "react-toggle": "~4.0.2",
     "react-toastify": "~2.1.2",
+    "react-toggle": "~4.0.2",
     "react-transition-group": "~2.2.1",
     "redis": "~2.8.0",
     "string-hash": "~1.1.3",
+    "tippy.js": "~2.0.2",
     "winston": "~2.4.0",
     "xml2js": "~0.4.19"
   },
diff --git a/bigbluebutton-html5/private/config/development/public/app.yaml b/bigbluebutton-html5/private/config/development/public/app.yaml
deleted file mode 100644
index 72c0a871cc8d56227b0d854ad75741db43c589e1..0000000000000000000000000000000000000000
--- a/bigbluebutton-html5/private/config/development/public/app.yaml
+++ /dev/null
@@ -1,5 +0,0 @@
-#  Application configurations
-app:
-  # Flag for HTTPS.
-  httpsConnection: false
-  connectionTimeout: 60000
diff --git a/bigbluebutton-html5/private/config/development/server/log.yaml b/bigbluebutton-html5/private/config/development/server/log.yaml
deleted file mode 100644
index 7479e006b1e5982eb69e1ae1346edc6f5fa987cc..0000000000000000000000000000000000000000
--- a/bigbluebutton-html5/private/config/development/server/log.yaml
+++ /dev/null
@@ -1,3 +0,0 @@
-log:
-  filename: '/log/development.log'
-  level: 'info'
diff --git a/bigbluebutton-html5/private/config/production/public/app.yaml b/bigbluebutton-html5/private/config/production/public/app.yaml
deleted file mode 100644
index c2007ba81858574cd68e7e32b4bdec315d9e9355..0000000000000000000000000000000000000000
--- a/bigbluebutton-html5/private/config/production/public/app.yaml
+++ /dev/null
@@ -1,5 +0,0 @@
-#  Application configurations
-app:
-  # Flag for HTTPS.
-  httpsConnection: true
-  connectionTimeout: 10000
diff --git a/bigbluebutton-html5/private/config/production/server/log.yaml b/bigbluebutton-html5/private/config/production/server/log.yaml
deleted file mode 100644
index ea241a6194da5c26585494f49ff1cc745e898b14..0000000000000000000000000000000000000000
--- a/bigbluebutton-html5/private/config/production/server/log.yaml
+++ /dev/null
@@ -1,3 +0,0 @@
-log:
-  filename: '/var/log/bigbluebutton/html5/html5client.log'
-  level: 'warn'
diff --git a/bigbluebutton-html5/private/config/public/acl.yaml b/bigbluebutton-html5/private/config/public/acl.yaml
deleted file mode 100644
index 3feda493b5f9ad7aa78b9dbe247d6cc3cab7fbc1..0000000000000000000000000000000000000000
--- a/bigbluebutton-html5/private/config/public/acl.yaml
+++ /dev/null
@@ -1,44 +0,0 @@
-acl:
-  viewer:
-    subscriptions:
-      - 'users'
-      - 'cursor'
-      - 'screenshare'
-      - 'meetings'
-      - 'polls'
-      - 'chat'
-      - 'presentations'
-      - 'annotations'
-      - 'slides'
-      - 'captions'
-      - 'breakouts'
-      - 'voiceUsers'
-      - 'whiteboard-multi-user'
-    methods:
-      - 'listenOnlyToggle'
-      - 'userLogout'
-      - 'setEmojiStatus'
-      - 'toggleSelfVoice'
-      - 'publishVote'
-      - 'sendChat'
-  moderator:
-    methods:
-      - 'assignPresenter'
-      - 'kickUser'
-      - 'muteUser'
-      - 'unmuteUser'
-      - 'endMeeting'
-      - 'toggleVoice'
-      - 'clearPublicChatHistory'
-      - 'changeRole'
-  presenter:
-    methods:
-      - 'assignPresenter'
-      - 'switchSlide'
-      - 'modifyWhiteboardAccess'
-      - 'undoAnnotation'
-      - 'clearWhiteboard'
-      - 'moveCursor'
-      - 'sendAnnotation'
-      - 'removePresentation'
-      - 'setPresentation'
diff --git a/bigbluebutton-html5/private/config/public/app.yaml b/bigbluebutton-html5/private/config/public/app.yaml
deleted file mode 100644
index 7dffde82262185e1195ca62804eba5c573807ac7..0000000000000000000000000000000000000000
--- a/bigbluebutton-html5/private/config/public/app.yaml
+++ /dev/null
@@ -1,57 +0,0 @@
-#  Application configurations
-app:
-  # Default font sizes for mobile / desktop
-  mobileFont: 16
-  desktopFont: 14
-
-  # Audio notification for unread chat message
-  audioChatNotification: false
-
-  # Will offer the user to join the audio when entering the meeting
-  autoJoinAudio: true
-  listenOnly: false
-  skipCheck: false
-
-  # Default global variables
-  appName: 'BigBlueButton HTML5 Client'
-  bbbServerVersion: '2.0-beta'
-  copyright: '©2017 BigBlueButton Inc.'
-  html5ClientBuild: 'HTML5_CLIENT_VERSION'
-  lockOnJoin: true
-
-  # Name displayed in the brower URL
-  basename: '/html5client'
-
-  #default settings for session storage
-  defaultSettings:
-    application:
-      chatAudioNotifications: false
-      chatPushNotifications: false
-      fontSize: '16px'
-      locale: 'en'
-    audio:
-      inputDeviceId: undefined
-      outputDeviceId: undefined
-    video:
-      viewParticipantsWebcams: true
-    cc:
-      backgroundColor: '#FFFFFF'
-      fontColor: '#000000'
-      enabled: false
-      fontFamily: 'Calibri'
-      fontSize: '16px'
-      # locale: undefined
-      takeOwnership: false
-    participants:
-      muteAll: false
-      lockAll: false
-      lockAll: false
-      microphone: false
-      publicChat: false
-      privateChat: false
-      layout: false
-
-  # The initial client version has limited moderator capabilities
-  # The following flag disables moderator-only features
-  allowHTML5Moderator: true
-  allowModeratorToUnmuteAudio: true
diff --git a/bigbluebutton-html5/private/config/public/chat.yaml b/bigbluebutton-html5/private/config/public/chat.yaml
deleted file mode 100644
index 81bc370d11c44b28707c34f2b75b30e6438a1437..0000000000000000000000000000000000000000
--- a/bigbluebutton-html5/private/config/public/chat.yaml
+++ /dev/null
@@ -1,21 +0,0 @@
-# Chat service configurations
-chat:
-  min_message_length: 1
-  max_message_length: 5000
-  grouping_messages_window: 60000
-  # Chat types
-  type_system: 'SYSTEM_MESSAGE'
-  type_public: 'PUBLIC_CHAT'
-  type_private: 'PRIVATE_CHAT'
-  # Chat ids and names
-  system_userid: 'SYSTEM_MESSAGE'
-  system_username: 'SYSTEM_MESSAGE'
-  public_id: 'public'
-  public_userid: 'public_chat_userid'
-  public_username: 'public_chat_username'
-  # Keys
-  storage_key: 'UNREAD_CHATS'
-  # Chat paths
-  path_route: 'users/chat/'
-  system_messages_keys:
-    chat_clear: 'PUBLIC_CHAT_CLEAR'
diff --git a/bigbluebutton-html5/private/config/public/media.yaml b/bigbluebutton-html5/private/config/public/media.yaml
deleted file mode 100644
index 23329f1e456b1db739173b5a88a89193635af40d..0000000000000000000000000000000000000000
--- a/bigbluebutton-html5/private/config/public/media.yaml
+++ /dev/null
@@ -1,16 +0,0 @@
-#  Media configurations
-media:
-  WebRTCHangupRetryInterval: 2000
-  # IP address of FreeSWITCH server for use of mod_verto and WebRTC deshsharing
-  vertoServerAddress: "HOST"
-  # Allows a caller to access a FreeSWITCH dialplan
-  freeswitchProfilePassword: "1234"  # TODO: Remove once Danny's submitted PULL
-  vertoPort: "8082"
-  # specifies whether to use SIP.js for audio over mod_verto
-  useSIPAudio: true
-  stunTurnServersFetchAddress: '/bigbluebutton/api/stuns'
-  mediaTag: '#remote-media'
-  callTransferTimeout: 5000
-  callHangupTimeout: 2000
-  callHangupMaximumRetries: 10
-  echoTestNumber: '9196'
diff --git a/bigbluebutton-html5/private/config/public/presentation.yaml b/bigbluebutton-html5/private/config/public/presentation.yaml
deleted file mode 100644
index c3af8a12cd622b25aa6f2db3117ed03509ce82bc..0000000000000000000000000000000000000000
--- a/bigbluebutton-html5/private/config/public/presentation.yaml
+++ /dev/null
@@ -1,24 +0,0 @@
-presentation:
-  defaultPresentationFile: 'default.pdf'
-  uploadEndpoint: '/bigbluebutton/presentation/upload'
-  uploadSizeMin: 0
-  uploadSizeMax: 50000000
-  uploadValidMimeTypes:
-    - 'application/vnd.ms-excel'
-    - 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet'
-    - 'application/msword'
-    - 'application/vnd.openxmlformats-officedocument.wordprocessingml.document'
-    - 'application/vnd.ms-powerpoint'
-    - 'application/vnd.openxmlformats-officedocument.presentationml.presentation'
-    - 'application/vnd.oasis.opendocument.text'
-    - 'application/rtf'
-    - 'text/plain'
-    - 'application/vnd.oasis.opendocument.spreadsheet'
-    - 'application/vnd.oasis.opendocument.presentation'
-    - 'application/vnd.oasis.opendocument.text'
-    - 'application/vnd.oasis.opendocument.graphics'
-    - 'application/vnd.oasis.opendocument.chart'
-    - 'application/vnd.oasis.opendocument.image'
-    - 'application/pdf'
-    - 'image/jpeg'
-    - 'image/png'
diff --git a/bigbluebutton-html5/private/config/public/user.yaml b/bigbluebutton-html5/private/config/public/user.yaml
deleted file mode 100644
index f3c874018c5cbd5a3bdae8886951be00a9617d9b..0000000000000000000000000000000000000000
--- a/bigbluebutton-html5/private/config/public/user.yaml
+++ /dev/null
@@ -1,4 +0,0 @@
-user:
-  role_moderator: 'MODERATOR'
-  role_viewer: 'VIEWER'
-  role_presenter: 'PRESENTER'
diff --git a/bigbluebutton-html5/private/config/public/whiteboard.yaml b/bigbluebutton-html5/private/config/public/whiteboard.yaml
deleted file mode 100644
index 40d6d0eb1ac4fdc589b17a437f3fbea0f78ac4f8..0000000000000000000000000000000000000000
--- a/bigbluebutton-html5/private/config/public/whiteboard.yaml
+++ /dev/null
@@ -1,59 +0,0 @@
-whiteboard:
-  #annotation statuses specific to the each type of annotation
-  annotations:
-    message_frequency: 50
-    status:
-      start: 'DRAW_START'
-      update: 'DRAW_UPDATE'
-      end: 'DRAW_END'
-  toolbar:
-    colors:
-      - value: '#000000'
-      - value: '#ffffff'
-      - value: '#ff0000'
-      - value: '#ff8800'
-      - value: '#ccff00'
-      - value: '#00ff00'
-      - value: '#00ffff'
-      - value: '#0088ff'
-      - value: '#0000ff'
-      - value: '#8800ff'
-      - value: '#ff00ff'
-      - value: '#c0c0c0'
-    thickness:
-      - value: 14
-      - value: 12
-      - value: 10
-      - value: 8
-      - value: 6
-      - value: 4
-      - value: 2
-    font_sizes: 
-      - value: 36
-      - value: 32
-      - value: 28
-      - value: 24
-      - value: 20
-      - value: 16
-    tools:
-      #text
-      - icon: 'text_tool'
-        value: 'text'
-      #line
-      - icon: 'linte_tool'
-        value: 'line'
-      #ellipse
-      - icon: 'circle_tool'
-        value: 'ellipse'
-      #triangle
-      - icon: 'triangle_tool'
-        value: 'triangle'
-      #rectangle
-      - icon: 'rectangle_tool'
-        value: 'rectangle'
-      #pencil
-      - icon: 'pen_tool'
-        value: 'pencil'
-      #pan and zoom hand
-      - icon: 'hand'
-        value: 'hand'
diff --git a/bigbluebutton-html5/private/config/server/app.yaml b/bigbluebutton-html5/private/config/server/app.yaml
deleted file mode 100644
index a7517aa7f3198a3194a705705c47ace4d14bf553..0000000000000000000000000000000000000000
--- a/bigbluebutton-html5/private/config/server/app.yaml
+++ /dev/null
@@ -1,3 +0,0 @@
-app:
-  captionsChunkLength: 1000
-  pencilChunkLength: 100
\ No newline at end of file
diff --git a/bigbluebutton-html5/private/config/server/redis.yaml b/bigbluebutton-html5/private/config/server/redis.yaml
deleted file mode 100644
index cefc6d8ebad4eb58ad0a8a9be4bf2f9f9b1ab2eb..0000000000000000000000000000000000000000
--- a/bigbluebutton-html5/private/config/server/redis.yaml
+++ /dev/null
@@ -1,14 +0,0 @@
-redis:
-  host: '127.0.0.1'
-  post: '6379'
-  timeout: 5000
-  debug: false
-  channels:
-    toAkkaApps: 'to-akka-apps-redis-channel'
-  subscribeTo:
-    - 'to-html5-redis-channel'
-    - 'from-akka-apps-*'
-  async:
-    - 'from-akka-apps-wb-redis-channel'
-  ignored:
-    - 'CheckAlivePongSysMsg'
diff --git a/bigbluebutton-html5/private/config/settings-development.json b/bigbluebutton-html5/private/config/settings-development.json
new file mode 100644
index 0000000000000000000000000000000000000000..6fe0bd94980bb03e38a65fa9c484a5f2fd3e5ff7
--- /dev/null
+++ b/bigbluebutton-html5/private/config/settings-development.json
@@ -0,0 +1,343 @@
+{
+  "public": {
+
+    "app": {
+      "mobileFont": 16,
+      "desktopFont": 14,
+      "audioChatNotification": false,
+      "autoJoinAudio": true,
+      "listenOnly": false,
+      "skipCheck": false,
+      "appName": "BigBlueButton HTML5 Client",
+      "bbbServerVersion": "2.0-beta",
+      "copyright": "©2017 BigBlueButton Inc.",
+      "html5ClientBuild": "HTML5_CLIENT_VERSION",
+      "lockOnJoin": true,
+      "basename": "/html5client",
+      "defaultSettings": {
+        "application": {
+          "chatAudioNotifications": false,
+          "chatPushNotifications": false,
+          "fontSize": "16px",
+          "locale": "en"
+        },
+        "audio": {
+          "inputDeviceId": "undefined",
+          "outputDeviceId": "undefined"
+        },
+        "video": {
+          "viewParticipantsWebcams": true
+        },
+        "cc": {
+          "backgroundColor": "#FFFFFF",
+          "fontColor": "#000000",
+          "enabled": false,
+          "fontFamily": "Calibri",
+          "fontSize": "16px",
+          "takeOwnership": false
+        },
+        "participants": {
+          "muteAll": false,
+          "lockAll": false,
+          "microphone": false,
+          "publicChat": false,
+          "privateChat": false,
+          "layout": false
+        }
+      },
+      "allowHTML5Moderator": true,
+      "allowModeratorToUnmuteAudio": true,
+      "httpsConnection": false,
+      "connectionTimeout": 60000
+    },
+
+    "kurento": {
+      "wsUrl": "HOST",
+      "chromeExtensionKey": "KEY",
+      "chromeExtensionLink": "LINK",
+      "enableScreensharing": false,
+      "enableVideo": false
+    },
+
+    "acl": {
+      "viewer": {
+        "subscriptions": [
+          "users",
+          "cursor",
+          "screenshare",
+          "meetings",
+          "polls",
+          "chat",
+          "presentations",
+          "annotations",
+          "slides",
+          "captions",
+          "breakouts",
+          "voiceUsers",
+          "whiteboard-multi-user"
+        ],
+        "methods": [
+          "listenOnlyToggle",
+          "userLogout",
+          "setEmojiStatus",
+          "toggleSelfVoice",
+          "publishVote",
+          "sendChat"
+        ]
+      },
+      "moderator": {
+        "methods": [
+          "assignPresenter",
+          "kickUser",
+          "muteUser",
+          "unmuteUser",
+          "endMeeting",
+          "toggleVoice",
+          "clearPublicChatHistory",
+          "changeRole",
+          "ejectUserFromVoice"
+        ]
+      },
+      "presenter": {
+        "methods": [
+          "assignPresenter",
+          "switchSlide",
+          "modifyWhiteboardAccess",
+          "undoAnnotation",
+          "clearWhiteboard",
+          "moveCursor",
+          "sendAnnotation",
+          "removePresentation",
+          "setPresentation"
+        ]
+      }
+    },
+
+    "chat": {
+      "min_message_length": 1,
+      "max_message_length": 5000,
+      "grouping_messages_window": 60000,
+      "type_system": "SYSTEM_MESSAGE",
+      "type_public": "PUBLIC_CHAT",
+      "type_private": "PRIVATE_CHAT",
+      "system_userid": "SYSTEM_MESSAGE",
+      "system_username": "SYSTEM_MESSAGE",
+      "public_id": "public",
+      "public_userid": "public_chat_userid",
+      "public_username": "public_chat_username",
+      "storage_key": "UNREAD_CHATS",
+      "path_route": "users/chat/",
+      "system_messages_keys": {
+        "chat_clear": "PUBLIC_CHAT_CLEAR"
+      }
+    },
+
+    "media": {
+      "WebRTCHangupRetryInterval": 2000,
+      "vertoServerAddress": "HOST",
+      "freeswitchProfilePassword": "1234",
+      "vertoPort": "8082",
+      "useSIPAudio": true,
+      "stunTurnServersFetchAddress": "/bigbluebutton/api/stuns",
+      "mediaTag": "#remote-media",
+      "callTransferTimeout": 5000,
+      "callHangupTimeout": 2000,
+      "callHangupMaximumRetries": 10,
+      "echoTestNumber": "9196"
+    },
+
+    "presentation": {
+      "defaultPresentationFile": "default.pdf",
+      "uploadEndpoint": "/bigbluebutton/presentation/upload",
+      "uploadSizeMin": 0,
+      "uploadSizeMax": 50000000,
+      "uploadValidMimeTypes": [
+        "application/vnd.ms-excel",
+        "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet",
+        "application/msword",
+        "application/vnd.openxmlformats-officedocument.wordprocessingml.document",
+        "application/vnd.ms-powerpoint",
+        "application/vnd.openxmlformats-officedocument.presentationml.presentation",
+        "application/vnd.oasis.opendocument.text",
+        "application/rtf",
+        "text/plain",
+        "application/vnd.oasis.opendocument.spreadsheet",
+        "application/vnd.oasis.opendocument.presentation",
+        "application/vnd.oasis.opendocument.text",
+        "application/vnd.oasis.opendocument.graphics",
+        "application/vnd.oasis.opendocument.chart",
+        "application/vnd.oasis.opendocument.image",
+        "application/pdf",
+        "image/jpeg",
+        "image/png"
+      ]
+    },
+
+    "user": {
+      "role_moderator": "MODERATOR",
+      "role_viewer": "VIEWER",
+      "role_presenter": "PRESENTER"
+    },
+
+    "whiteboard": {
+      "annotations": {
+        "message_frequency": 50,
+        "status": {
+          "start": "DRAW_START",
+          "update": "DRAW_UPDATE",
+          "end": "DRAW_END"
+        }
+      },
+      "toolbar": {
+        "colors": [
+          {
+            "value": "#000000"
+          },
+          {
+            "value": "#ffffff"
+          },
+          {
+            "value": "#ff0000"
+          },
+          {
+            "value": "#ff8800"
+          },
+          {
+            "value": "#ccff00"
+          },
+          {
+            "value": "#00ff00"
+          },
+          {
+            "value": "#00ffff"
+          },
+          {
+            "value": "#0088ff"
+          },
+          {
+            "value": "#0000ff"
+          },
+          {
+            "value": "#8800ff"
+          },
+          {
+            "value": "#ff00ff"
+          },
+          {
+            "value": "#c0c0c0"
+          }
+        ],
+        "thickness": [
+          {
+            "value": 14
+          },
+          {
+            "value": 12
+          },
+          {
+            "value": 10
+          },
+          {
+            "value": 8
+          },
+          {
+            "value": 6
+          },
+          {
+            "value": 4
+          },
+          {
+            "value": 2
+          }
+        ],
+        "font_sizes": [
+          {
+            "value": 36
+          },
+          {
+            "value": 32
+          },
+          {
+            "value": 28
+          },
+          {
+            "value": 24
+          },
+          {
+            "value": 20
+          },
+          {
+            "value": 16
+          }
+        ],
+        "tools": [
+          {
+            "icon": "text_tool",
+            "value": "text"
+          },
+          {
+            "icon": "line_tool",
+            "value": "line"
+          },
+          {
+            "icon": "circle_tool",
+            "value": "ellipse"
+          },
+          {
+            "icon": "triangle_tool",
+            "value": "triangle"
+          },
+          {
+            "icon": "rectangle_tool",
+            "value": "rectangle"
+          },
+          {
+            "icon": "pen_tool",
+            "value": "pencil"
+          },
+          {
+            "icon": "hand",
+            "value": "hand"
+          }
+        ]
+      }
+    }
+  },
+
+  "private": {
+
+    "app": {
+      "captionsChunkLength": 1000,
+      "pencilChunkLength": 100
+    },
+
+    "redis": {
+      "host": "127.0.0.1",
+      "post": "6379",
+      "timeout": 5000,
+      "debug": false,
+      "channels": {
+        "toAkkaApps": "to-akka-apps-redis-channel"
+      },
+      "subscribeTo": [
+        "to-html5-redis-channel",
+        "from-akka-apps-*"
+      ],
+      "async": [
+        "from-akka-apps-wb-redis-channel"
+      ],
+      "ignored": [
+        "CheckAlivePongSysMsg",
+        "DoLatencyTracerMsg"
+      ]
+    },
+
+
+    "log": {
+      "filename": "/log/development.log",
+      "level": "info"
+    }
+  }
+}
+
+
diff --git a/bigbluebutton-html5/private/config/settings-production.json b/bigbluebutton-html5/private/config/settings-production.json
new file mode 100644
index 0000000000000000000000000000000000000000..34a90db20dfeda59f133aa353879537b3fe6f441
--- /dev/null
+++ b/bigbluebutton-html5/private/config/settings-production.json
@@ -0,0 +1,340 @@
+{
+  "public": {
+
+    "app": {
+      "mobileFont": 16,
+      "desktopFont": 14,
+      "audioChatNotification": false,
+      "autoJoinAudio": true,
+      "listenOnly": false,
+      "skipCheck": false,
+      "appName": "BigBlueButton HTML5 Client",
+      "bbbServerVersion": "2.0-beta",
+      "copyright": "©2017 BigBlueButton Inc.",
+      "html5ClientBuild": "HTML5_CLIENT_VERSION",
+      "lockOnJoin": true,
+      "basename": "/html5client",
+      "defaultSettings": {
+        "application": {
+          "chatAudioNotifications": false,
+          "chatPushNotifications": false,
+          "fontSize": "16px",
+          "locale": "en"
+        },
+        "audio": {
+          "inputDeviceId": "undefined",
+          "outputDeviceId": "undefined"
+        },
+        "video": {
+          "viewParticipantsWebcams": true
+        },
+        "cc": {
+          "backgroundColor": "#FFFFFF",
+          "fontColor": "#000000",
+          "enabled": false,
+          "fontFamily": "Calibri",
+          "fontSize": "16px",
+          "takeOwnership": false
+        },
+        "participants": {
+          "muteAll": false,
+          "lockAll": false,
+          "microphone": false,
+          "publicChat": false,
+          "privateChat": false,
+          "layout": false
+        }
+      },
+      "allowHTML5Moderator": true,
+      "allowModeratorToUnmuteAudio": true,
+      "httpsConnection": true,
+      "connectionTimeout": 10000
+    },
+
+    "kurento": {
+      "wsUrl": "HOST",
+      "chromeExtensionKey": "KEY",
+      "chromeExtensionLink": "LINK",
+      "enableScreensharing": false,
+      "enableVideo": false
+    },
+
+    "acl": {
+      "viewer": {
+        "subscriptions": [
+          "users",
+          "cursor",
+          "screenshare",
+          "meetings",
+          "polls",
+          "chat",
+          "presentations",
+          "annotations",
+          "slides",
+          "captions",
+          "breakouts",
+          "voiceUsers",
+          "whiteboard-multi-user"
+        ],
+        "methods": [
+          "listenOnlyToggle",
+          "userLogout",
+          "setEmojiStatus",
+          "toggleSelfVoice",
+          "publishVote",
+          "sendChat"
+        ]
+      },
+      "moderator": {
+        "methods": [
+          "assignPresenter",
+          "kickUser",
+          "muteUser",
+          "unmuteUser",
+          "endMeeting",
+          "toggleVoice",
+          "clearPublicChatHistory",
+          "changeRole",
+          "ejectUserFromVoice"
+        ]
+      },
+      "presenter": {
+        "methods": [
+          "assignPresenter",
+          "switchSlide",
+          "modifyWhiteboardAccess",
+          "undoAnnotation",
+          "clearWhiteboard",
+          "moveCursor",
+          "sendAnnotation",
+          "removePresentation",
+          "setPresentation"
+        ]
+      }
+    },
+
+    "chat": {
+      "min_message_length": 1,
+      "max_message_length": 5000,
+      "grouping_messages_window": 60000,
+      "type_system": "SYSTEM_MESSAGE",
+      "type_public": "PUBLIC_CHAT",
+      "type_private": "PRIVATE_CHAT",
+      "system_userid": "SYSTEM_MESSAGE",
+      "system_username": "SYSTEM_MESSAGE",
+      "public_id": "public",
+      "public_userid": "public_chat_userid",
+      "public_username": "public_chat_username",
+      "storage_key": "UNREAD_CHATS",
+      "path_route": "users/chat/",
+      "system_messages_keys": {
+        "chat_clear": "PUBLIC_CHAT_CLEAR"
+      }
+    },
+
+    "media": {
+      "WebRTCHangupRetryInterval": 2000,
+      "vertoServerAddress": "HOST",
+      "freeswitchProfilePassword": "1234",
+      "vertoPort": "8082",
+      "useSIPAudio": true,
+      "stunTurnServersFetchAddress": "/bigbluebutton/api/stuns",
+      "mediaTag": "#remote-media",
+      "callTransferTimeout": 5000,
+      "callHangupTimeout": 2000,
+      "callHangupMaximumRetries": 10,
+      "echoTestNumber": "9196"
+    },
+
+    "presentation": {
+      "defaultPresentationFile": "default.pdf",
+      "uploadEndpoint": "/bigbluebutton/presentation/upload",
+      "uploadSizeMin": 0,
+      "uploadSizeMax": 50000000,
+      "uploadValidMimeTypes": [
+        "application/vnd.ms-excel",
+        "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet",
+        "application/msword",
+        "application/vnd.openxmlformats-officedocument.wordprocessingml.document",
+        "application/vnd.ms-powerpoint",
+        "application/vnd.openxmlformats-officedocument.presentationml.presentation",
+        "application/vnd.oasis.opendocument.text",
+        "application/rtf",
+        "text/plain",
+        "application/vnd.oasis.opendocument.spreadsheet",
+        "application/vnd.oasis.opendocument.presentation",
+        "application/vnd.oasis.opendocument.text",
+        "application/vnd.oasis.opendocument.graphics",
+        "application/vnd.oasis.opendocument.chart",
+        "application/vnd.oasis.opendocument.image",
+        "application/pdf",
+        "image/jpeg",
+        "image/png"
+      ]
+    },
+
+    "user": {
+      "role_moderator": "MODERATOR",
+      "role_viewer": "VIEWER",
+      "role_presenter": "PRESENTER"
+    },
+
+    "whiteboard": {
+      "annotations": {
+        "message_frequency": 50,
+        "status": {
+          "start": "DRAW_START",
+          "update": "DRAW_UPDATE",
+          "end": "DRAW_END"
+        }
+      },
+      "toolbar": {
+        "colors": [
+          {
+            "value": "#000000"
+          },
+          {
+            "value": "#ffffff"
+          },
+          {
+            "value": "#ff0000"
+          },
+          {
+            "value": "#ff8800"
+          },
+          {
+            "value": "#ccff00"
+          },
+          {
+            "value": "#00ff00"
+          },
+          {
+            "value": "#00ffff"
+          },
+          {
+            "value": "#0088ff"
+          },
+          {
+            "value": "#0000ff"
+          },
+          {
+            "value": "#8800ff"
+          },
+          {
+            "value": "#ff00ff"
+          },
+          {
+            "value": "#c0c0c0"
+          }
+        ],
+        "thickness": [
+          {
+            "value": 14
+          },
+          {
+            "value": 12
+          },
+          {
+            "value": 10
+          },
+          {
+            "value": 8
+          },
+          {
+            "value": 6
+          },
+          {
+            "value": 4
+          },
+          {
+            "value": 2
+          }
+        ],
+        "font_sizes": [
+          {
+            "value": 36
+          },
+          {
+            "value": 32
+          },
+          {
+            "value": 28
+          },
+          {
+            "value": 24
+          },
+          {
+            "value": 20
+          },
+          {
+            "value": 16
+          }
+        ],
+        "tools": [
+          {
+            "icon": "text_tool",
+            "value": "text"
+          },
+          {
+            "icon": "line_tool",
+            "value": "line"
+          },
+          {
+            "icon": "circle_tool",
+            "value": "ellipse"
+          },
+          {
+            "icon": "triangle_tool",
+            "value": "triangle"
+          },
+          {
+            "icon": "rectangle_tool",
+            "value": "rectangle"
+          },
+          {
+            "icon": "pen_tool",
+            "value": "pencil"
+          },
+          {
+            "icon": "hand",
+            "value": "hand"
+          }
+        ]
+      }
+    }
+  },
+
+  "private": {
+
+    "app": {
+      "captionsChunkLength": 1000,
+      "pencilChunkLength": 100
+    },
+
+    "redis": {
+      "host": "127.0.0.1",
+      "post": "6379",
+      "timeout": 5000,
+      "debug": false,
+      "channels": {
+        "toAkkaApps": "to-akka-apps-redis-channel"
+      },
+      "subscribeTo": [
+        "to-html5-redis-channel",
+        "from-akka-apps-*"
+      ],
+      "async": [
+        "from-akka-apps-wb-redis-channel"
+      ],
+      "ignored": [
+        "CheckAlivePongSysMsg",
+        "DoLatencyTracerMsg"
+      ]
+    },
+
+    "log": {
+      "filename": "/var/log/bigbluebutton/html5/html5client.log",
+      "level": "warn"
+    }
+  }
+}
\ No newline at end of file
diff --git a/bigbluebutton-html5/private/locales/en.json b/bigbluebutton-html5/private/locales/en.json
index da0db9ce2a17e369c09af49c13be26f9fddaeec7..a77a5f301c3e9ce31b8a5bcda48e36fce4e842c9 100644
--- a/bigbluebutton-html5/private/locales/en.json
+++ b/bigbluebutton-html5/private/locales/en.json
@@ -62,13 +62,17 @@
     "app.presentationUploder.dropzoneLabel": "Drag files here to upload",
     "app.presentationUploder.browseFilesLabel": "or browse for files",
     "app.presentationUploder.fileToUpload": "To be uploaded...",
+    "app.presentationUploder.currentBadge": "Current",
+    "app.presentationUploder.genericError": "Ops, something went wrong",
     "app.presentationUploder.upload.progress": "Uploading ({progress}%)",
-    "app.presentationUploder.upload.413": "File is too large.",
+    "app.presentationUploder.upload.413": "File is too large",
     "app.presentationUploder.conversion.conversionProcessingSlides": "Processing page {current} of {total}",
     "app.presentationUploder.conversion.genericConversionStatus": "Converting file...",
     "app.presentationUploder.conversion.generatingThumbnail": "Generating thumbnails...",
+    "app.presentationUploder.conversion.generatedSlides": "Slides generated...",
     "app.presentationUploder.conversion.generatingSvg": "Generating SVG images...",
-    "presentationUploder.conversion.generatedSlides": "Slides generated...",
+    "app.presentationUploder.conversion.pageCountExceeded": "Ops, the page count exceeded the limit",
+    "app.presentationUploder.conversion.timeout": "Ops, the conversion is taking too long",
     "app.polling.pollingTitle": "Polling Options",
     "app.failedMessage": "Apologies, trouble connecting to the server.",
     "app.connectingMessage": "Connecting...",
@@ -169,9 +173,11 @@
     "app.actionsBar.actionsDropdown.presentationLabel": "Upload a presentation",
     "app.actionsBar.actionsDropdown.initPollLabel": "Initiate a poll",
     "app.actionsBar.actionsDropdown.desktopShareLabel": "Share your screen",
+    "app.actionsBar.actionsDropdown.stopDesktopShareLabel": "Stop sharing your screen",
     "app.actionsBar.actionsDropdown.presentationDesc": "Upload your presentation",
     "app.actionsBar.actionsDropdown.initPollDesc": "Initiate a poll",
     "app.actionsBar.actionsDropdown.desktopShareDesc": "Share your screen with others",
+    "app.actionsBar.actionsDropdown.stopDesktopShareDesc": "Stop sharing your screen with",
     "app.actionsBar.emojiMenu.statusTriggerLabel": "Status",
     "app.actionsBar.emojiMenu.awayLabel": "Away",
     "app.actionsBar.emojiMenu.awayDesc": "Change your status to away",
@@ -240,6 +246,8 @@
     "app.audioManager.mediaError": "Error: There was an issue getting your media devices",
     "app.audio.joinAudio": "Join Audio",
     "app.audio.leaveAudio": "Leave Audio",
+    "app.video.joinVideo": "Share Webcam",
+    "app.video.leaveVideo": "Un-share Webcam",
     "app.audio.enterSessionLabel": "Enter Session",
     "app.audio.playSoundLabel": "Play Sound",
     "app.audio.backLabel": "Back",
@@ -253,7 +261,7 @@
     "app.audio.listenOnly.closeLabel": "Close",
     "app.audio.permissionsOverlay.title": "Allow BigBlueButton to use your Media Devices",
     "app.audio.permissionsOverlay.hint": "We need you to allow us to use your Media Devices in order to join you to the voice conference :)",
-    "app.error.kicked": "You have been kicked out of the meeting",
+    "app.error.kicked": "You have been removed from the conference",
     "app.error.meeting.ended": "You have logged out of the conference",
     "app.dropdown.close": "Close",
     "app.error.500": "Ops, something went wrong",
@@ -262,8 +270,11 @@
     "app.error.403": "Forbidden",
     "app.error.leaveLabel": "Log in again",
     "app.guest.waiting": "Waiting for approval to join",
+    "app.toast.breakoutRoomEnded": "The breakout room ended. Please rejoin in the audio.",
     "app.toast.chat.singular":"you have {0} new message in {1}",
     "app.toast.chat.plural":"you have {0} new messages in {1}",
     "app.notification.recordingStart": "This session is now being recorded",
-    "app.notification.recordingStop": "This session is not being recorded anymore"
+    "app.notification.recordingStop": "This session is not being recorded anymore",
+    "app.video.joinVideo": "Share webcam",
+    "app.video.leaveVideo": "Unshare webcam"
 }
diff --git a/bigbluebutton-html5/private/locales/pt_BR.json b/bigbluebutton-html5/private/locales/pt_BR.json
index 0cb562cf2bfb75eacb809fea852dedb35f4a4d0a..00caa32a14f37e6fdedab57ad5ee5e5c0e5e5618 100644
--- a/bigbluebutton-html5/private/locales/pt_BR.json
+++ b/bigbluebutton-html5/private/locales/pt_BR.json
@@ -204,6 +204,8 @@
     "app.audioModal.closeLabel": "Fechar",
     "app.audio.joinAudio": "Ativar áudio",
     "app.audio.leaveAudio": "Desativar áudio",
+    "app.video.joinVideo": "Compartilhar câmera",
+    "app.video.leaveVideo": "Des-compartilhar câmera",
     "app.audio.enterSessionLabel": "Entrar na reunião",
     "app.audio.playSoundLabel": "Tocar som de teste",
     "app.audio.backLabel": "Voltar",
diff --git a/bigbluebutton-html5/public/fonts/BbbIcons/bbb-icons.eot b/bigbluebutton-html5/public/fonts/BbbIcons/bbb-icons.eot
old mode 100755
new mode 100644
index c65dad077e11cf00b11d7904742902c8e1c8e2ca..b85bb2766efa3e159eefe8f1ec8d78fc778591c2
Binary files a/bigbluebutton-html5/public/fonts/BbbIcons/bbb-icons.eot and b/bigbluebutton-html5/public/fonts/BbbIcons/bbb-icons.eot differ
diff --git a/bigbluebutton-html5/public/fonts/BbbIcons/bbb-icons.svg b/bigbluebutton-html5/public/fonts/BbbIcons/bbb-icons.svg
old mode 100755
new mode 100644
index a7e696d0c8f47c39c14253ba056faf0283b94b03..641c7805e51d6c273470f6a7fa16a1dc5f6d0d94
--- a/bigbluebutton-html5/public/fonts/BbbIcons/bbb-icons.svg
+++ b/bigbluebutton-html5/public/fonts/BbbIcons/bbb-icons.svg
@@ -5,8 +5,8 @@
 -->
 <svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" version="1.1">
 <metadata>
-Created by FontForge 20161004 at Tue Jul 18 12:52:45 2017
- By ghazi
+Created by FontForge 20170731 at Tue Dec 12 20:29:15 2017
+ By BigBlueButton Inc.
 Copyright (c) 2016-2017, BigBlueButton Inc.
 </metadata>
 <defs>
@@ -22,7 +22,7 @@ Copyright (c) 2016-2017, BigBlueButton Inc.
     bbox="-3 -208 1191 826"
     underline-thickness="51"
     underline-position="-102"
-    unicode-range="U+0020-E94C"
+    unicode-range="U+0020-E94F"
   />
 <missing-glyph horiz-adv-x="1048" 
  />
@@ -33,6 +33,9 @@ Copyright (c) 2016-2017, BigBlueButton Inc.
     <glyph glyph-name="logout" unicode="&#xe900;" 
 d="M160 -117h84c19 0 30 -14 30 -32c0 -16 -7 -34 -30 -34h-84c-83 0 -150 66 -150 150v670c0 83 67 150 150 150h84c16 0 30 -17 30 -32c0 -16 -9 -32 -30 -32h-84c-47 0 -87 -40 -87 -86v-667c0 -47 40 -87 87 -87zM676 -9c-6 -5 -14 -8 -22 -8c-7 0 -15 3 -21 8
 s-8 12 -8 19c0 9 5 20 12 27l233 233h-625c-22 0 -35 12 -35 30s13 30 35 30h625l-233 233c-7 7 -11 16 -11 25s4 17 11 23c6 5 14 8 21 8c8 0 16 -3 22 -9l310 -310c-30 -30 -314 -309 -314 -309z" />
+    <glyph glyph-name="application" unicode="&#xe901;" 
+d="M952 679c31 0 55 -24 55 -55v-634c0 -31 -24 -55 -55 -55h-876c-34 0 -58 24 -58 55v634c3 31 27 55 58 55h876zM946 618h-863v-82h863v82zM83 0h863v471h-863v-471zM246 577c0 13 11 24 24 24s24 -11 24 -24s-11 -24 -24 -24s-24 11 -24 24zM148 577c0 13 11 24 24 24
+s24 -11 24 -24s-11 -24 -24 -24s-24 11 -24 24zM342 577c0 13 11 24 24 24s24 -11 24 -24s-11 -24 -24 -24s-24 11 -24 24z" />
     <glyph glyph-name="more" unicode="&#xe902;" 
 d="M17 307c0 60 49 109 109 109c61 0 110 -49 110 -109s-49 -109 -110 -109c-60 0 -109 49 -109 109zM403 307c0 60 49 109 109 109s109 -49 109 -109s-49 -109 -109 -109s-109 49 -109 109zM788 307c0 60 49 109 110 109c60 0 109 -49 109 -109s-49 -109 -109 -109
 c-61 0 -110 49 -110 109z" />
@@ -113,13 +116,14 @@ s-222 -492 -492 -492zM512 737c-236 0 -430 -194 -430 -430s194 -430 430 -430s430 1
 d="M512 737c-246 0 -444 -154 -444 -345c0 -102 62 -197 164 -262c22 -15 42 -41 42 -70c0 -3 -1 -6 -1 -9c-1 -7 -3 -17 -3 -17l-24 -75l116 89c14 10 34 17 51 17c31 0 61 -11 92 -11h7c246 0 444 154 444 342s-198 341 -444 341zM512 805c283 0 512 -191 512 -416
 s-229 -410 -512 -410h-7c-31 0 -61 4 -92 11c-77 -59 -158 -117 -236 -175c-3 -3 -10 -6 -17 -6c-14 0 -23 10 -23 24l68 218c3 9 -6 21 -10 24c-118 77 -195 191 -195 321c0 225 229 409 512 409z" />
     <glyph glyph-name="audio_on" unicode="&#xe91a;" 
-d="M539 -140l-290 290h-140c-51 0 -92 41 -92 92v130c0 51 41 92 92 92h140l290 290c5 5 12 8 19 8c15 0 29 -12 29 -28v-857c0 -13 -14 -25 -29 -25c-6 0 -13 2 -19 8zM72 375v-129c0 -21 13 -41 34 -41h153c8 0 16 -2 21 -7l252 -253v727l-252 -252c-6 -6 -14 -7 -21 -7
-h-150c-20 0 -37 -18 -37 -38z" />
+d="M742 -94c-25 0 -62 11 -83 26c-173 115 -407 349 -522 522c-14 21 -25 59 -25 84c0 35 20 83 44 108l18 18c25 24 73 44 108 44s83 -20 108 -44l28 -28c25 -25 44 -73 44 -108c0 -25 -11 -63 -25 -84c54 -64 148 -158 212 -212c21 14 58 24 84 24c34 0 82 -20 107 -44
+l28 -28c24 -25 44 -73 44 -108s-20 -83 -44 -108l-18 -18c-25 -24 -73 -44 -108 -44zM281 640c-19 0 -46 -13 -59 -26l-17 -17c-14 -14 -25 -40 -25 -59c0 -14 6 -34 14 -46c110 -167 336 -393 503 -504c12 -8 32 -14 46 -14c19 0 46 11 60 25l18 18c13 13 24 40 24 59
+s-12 47 -26 60l-28 28c-14 13 -40 24 -59 24s-46 -11 -60 -24c-5 -5 -16 -10 -24 -10c-7 0 -16 4 -22 8c-79 65 -196 182 -261 261c-4 6 -8 15 -8 22c0 8 5 19 10 24c14 12 25 40 25 60c0 19 -11 45 -25 59l-26 26c-13 14 -40 26 -60 26z" />
     <glyph glyph-name="audio_off" unicode="&#xe91b;" 
-d="M898 307l102 -102c4 -4 7 -11 7 -18s-3 -14 -7 -18c-6 -6 -13 -9 -20 -9c-6 0 -12 2 -17 7l-103 102l-106 -105c-4 -4 -10 -6 -16 -6c-8 0 -15 3 -21 9c-4 4 -6 10 -6 16c0 8 3 16 9 22l103 102l-103 102c-5 5 -8 12 -8 19s3 14 8 19s12 7 19 7s14 -2 19 -7l102 -102
-l103 102c4 4 11 7 17 7s14 -3 18 -7c5 -5 8 -14 8 -22c0 -6 -2 -12 -6 -16zM539 -140l-290 290h-140c-51 0 -92 41 -92 92v130c0 51 41 92 92 92h140l290 290c6 6 13 9 20 9c13 0 24 -11 24 -29v-857c0 -16 -12 -25 -25 -25c-7 0 -14 3 -19 8zM109 413
-c-20 0 -37 -14 -37 -34v-130c0 -26 13 -41 34 -41h153c7 0 16 -2 21 -7l252 -252v723l-252 -252c-6 -6 -14 -7 -21 -7h-150z" />
-    <glyph glyph-name="linte_tool" unicode="&#xe91c;" 
+d="M851 125h-39c-69 0 -135 54 -149 121c-42 3 -109 6 -151 6s-109 -3 -151 -6c-14 -67 -80 -121 -149 -121h-39c-84 0 -153 68 -153 152v26c0 68 55 135 122 149c101 21 267 37 370 37s269 -16 370 -37c67 -14 122 -81 122 -149v-26c0 -84 -69 -152 -153 -152zM512 320
+c62 0 124 -3 185 -9c17 -1 30 -16 30 -34c1 -46 38 -84 85 -84h39c47 0 84 38 85 84v26c-1 38 -31 75 -69 83c-97 19 -256 35 -356 35c-99 0 -258 -16 -356 -35c-37 -8 -68 -45 -68 -83v-26c0 -46 38 -84 84 -84h41c47 0 84 38 85 84c0 18 13 33 30 34c61 6 123 9 185 9z
+" />
+    <glyph glyph-name="line_tool" unicode="&#xe91c;" 
 d="M403 14l-58 29l269 538l58 -29zM703 802c60 0 109 -48 109 -109s-48 -110 -109 -110s-109 49 -109 110s49 109 109 109zM703 648c26 0 48 21 48 45s-21 44 -48 44c-24 0 -44 -20 -44 -44s20 -45 44 -45zM321 31c58 0 109 -49 109 -110s-48 -109 -109 -109
 s-109 48 -109 109s48 110 109 110zM321 -123c24 0 44 20 44 44c0 25 -20 45 -44 45c-27 0 -48 -21 -48 -45s21 -44 48 -44z" />
     <glyph glyph-name="circle_tool" unicode="&#xe91d;" 
@@ -215,9 +219,6 @@ d="M364 120l-4 3l-67 -67c56 -56 131 -88 212 -88h14c166 0 297 135 297 297v17c3 16
 c-88 0 -233 106 -233 106l-137 -135c-8 -8 -18 -12 -26 -12c-17 0 -31 14 -31 33c0 11 5 24 18 37l130 130c-39 60 -60 131 -60 202c0 8 14 20 25 24c3 1 5 1 8 1c16 0 34 -10 34 -25c0 -53 15 -106 43 -152l70 67c-14 28 -21 56 -21 88v339c0 56 22 109 64 148
 c39 42 92 64 148 64s108 -23 145 -64c46 -51 63 -92 63 -148v-25l180 181c10 10 21 14 31 14c19 0 34 -15 34 -33c0 -10 -4 -20 -15 -31l-226 -226v-219c0 -117 -95 -212 -212 -212c-56 0 -109 25 -148 64zM614 169c28 28 39 75 39 103v148l-247 -247l4 -4
 c28 -28 63 -42 102 -42s77 14 102 42zM371 272c0 -18 7 -43 7 -43l282 283v92c0 39 -14 77 -42 102c-28 28 -63 42 -102 42s-78 -14 -103 -42c-28 -25 -42 -60 -42 -99v-335z" />
-    <glyph glyph-name="application" unicode="&#xe901;" 
-d="M952 679c31 0 55 -24 55 -55v-634c0 -31 -24 -55 -55 -55h-876c-34 0 -58 24 -58 55v634c3 31 27 55 58 55h876zM946 618h-863v-82h863v82zM83 0h863v471h-863v-471zM246 577c0 13 11 24 24 24s24 -11 24 -24s-11 -24 -24 -24s-24 11 -24 24zM148 577c0 13 11 24 24 24
-s24 -11 24 -24s-11 -24 -24 -24s-24 11 -24 24zM342 577c0 13 11 24 24 24s24 -11 24 -24s-11 -24 -24 -24s-24 11 -24 24z" />
     <glyph glyph-name="about" unicode="&#xe933;" 
 d="M470 480c0 28 23 51 51 51s51 -23 51 -51s-23 -51 -51 -51s-51 23 -51 51zM512 -205c-283 0 -512 229 -512 512s229 512 512 512s512 -229 512 -512s-229 -512 -512 -512zM512 755c-247 0 -448 -201 -448 -448c0 -248 201 -449 448 -449s448 201 448 449
 c0 247 -201 448 -448 448zM567 68h-89v307h88z" />
@@ -311,5 +312,14 @@ c49 0 89 -40 89 -90v-418c0 -50 -40 -90 -89 -90zM89 557c-22 0 -40 -18 -40 -41v-41
     <glyph glyph-name="warning" unicode="&#xe94c;" 
 d="M453 113c0 31 27 58 58 58c32 0 59 -27 59 -58s-27 -58 -59 -58c-31 0 -58 27 -58 58zM512 -208c-283 0 -512 230 -512 512c0 283 229 512 512 512s512 -229 512 -512c0 -282 -229 -512 -512 -512zM512 751c-247 0 -447 -201 -447 -448s200 -448 447 -448
 c248 0 448 201 448 448s-202 448 -448 448zM508 227c-20 0 -37 16 -38 36l-13 249v2c0 21 17 38 38 38h34c22 0 39 -17 39 -38v-2l-13 -250c-1 -19 -18 -35 -38 -35h-9z" />
+    <glyph glyph-name="transfer_audio" unicode="&#xe94d;" 
+d="M558 -149c-6 0 -15 4 -19 9l-292 291h-138c-51 0 -92 41 -92 92v128c0 51 41 92 92 92h138l292 291c4 5 13 8 19 8c16 0 28 -12 28 -27v-856c0 -15 -12 -28 -28 -28zM109 409c-20 0 -37 -16 -37 -36v-132c0 -20 17 -36 37 -36h150c6 0 15 -4 19 -9l253 -252v724
+l-253 -252c-4 -5 -13 -8 -19 -8zM1007 307l-142 -142c-4 -4 -12 -7 -17 -7c-14 0 -25 10 -25 24c0 5 4 13 7 17l86 85h-249c-13 0 -24 11 -24 24s11 23 24 23h250l-86 86c-3 3 -7 11 -7 17c0 5 4 13 7 17c4 4 12 7 18 7c5 0 13 -3 17 -7z" />
+    <glyph glyph-name="room" unicode="&#xe94e;" 
+d="M58 693h908c32 0 58 -26 58 -58v-656c0 -32 -26 -58 -58 -58h-908c-32 0 -58 26 -58 58v656c0 32 26 58 58 58zM958 476h-892v-489h892v489zM66 542l892 -1v86h-892v-85zM134 584c0 14 11 25 25 25s25 -11 25 -25s-11 -25 -25 -25s-25 11 -25 25zM234 584
+c0 14 12 25 26 25s25 -11 25 -25s-11 -25 -25 -25s-26 11 -26 25zM336 584c0 14 11 25 25 25s25 -11 25 -25s-11 -25 -25 -25s-25 11 -25 25z" />
+    <glyph glyph-name="new_file" unicode="&#xe94f;" 
+d="M537 74c-16 0 -29 13 -29 29v102h-102c-15 1 -26 14 -26 28c0 15 11 28 26 29h102v103c1 14 14 26 29 26c14 0 27 -12 29 -26v-103h102c14 -1 26 -14 26 -29c0 -14 -12 -27 -26 -28h-102v-103c-1 -15 -14 -28 -29 -28zM905 585c14 -13 25 -40 25 -60v-645
+c0 -47 -38 -85 -85 -85h-614c-48 0 -85 38 -85 86v853c0 47 38 85 85 85h440zM689 713v-110c0 -13 10 -23 23 -23h112zM845 -143c13 0 23 10 23 23v636h-156c-47 0 -85 38 -85 85v155h-396c-13 0 -23 -11 -23 -23v-853c0 -13 10 -23 23 -23h614z" />
   </font>
 </defs></svg>
diff --git a/bigbluebutton-html5/public/fonts/BbbIcons/bbb-icons.ttf b/bigbluebutton-html5/public/fonts/BbbIcons/bbb-icons.ttf
old mode 100755
new mode 100644
index 44be832dcb5f447e8910a2d6843c7b73d6992421..9a7fe550dc73a495c9266db0cf4f691c3b6c808e
Binary files a/bigbluebutton-html5/public/fonts/BbbIcons/bbb-icons.ttf and b/bigbluebutton-html5/public/fonts/BbbIcons/bbb-icons.ttf differ
diff --git a/bigbluebutton-html5/public/fonts/BbbIcons/bbb-icons.woff b/bigbluebutton-html5/public/fonts/BbbIcons/bbb-icons.woff
old mode 100755
new mode 100644
index 002ddabc20e0d5f17caae5fe1bad1a598622819e..90768faaee918e7ad79bdb5cc87640d1749ea471
Binary files a/bigbluebutton-html5/public/fonts/BbbIcons/bbb-icons.woff and b/bigbluebutton-html5/public/fonts/BbbIcons/bbb-icons.woff differ
diff --git a/bigbluebutton-html5/public/js/adapter.js b/bigbluebutton-html5/public/js/adapter.js
new file mode 100644
index 0000000000000000000000000000000000000000..fc1d8c8ad9bf5df2141e314a6fcc09f01b627db4
--- /dev/null
+++ b/bigbluebutton-html5/public/js/adapter.js
@@ -0,0 +1,4450 @@
+(function(f){if(typeof exports==="object"&&typeof module!=="undefined"){module.exports=f()}else if(typeof define==="function"&&define.amd){define([],f)}else{var g;if(typeof window!=="undefined"){g=window}else if(typeof global!=="undefined"){g=global}else if(typeof self!=="undefined"){g=self}else{g=this}g.adapter = f()}})(function(){var define,module,exports;return (function e(t,n,r){function s(o,u){if(!n[o]){if(!t[o]){var a=typeof require=="function"&&require;if(!u&&a)return a(o,!0);if(i)return i(o,!0);var f=new Error("Cannot find module '"+o+"'");throw f.code="MODULE_NOT_FOUND",f}var l=n[o]={exports:{}};t[o][0].call(l.exports,function(e){var n=t[o][1][e];return s(n?n:e)},l,l.exports,e,t,n,r)}return n[o].exports}var i=typeof require=="function"&&require;for(var o=0;o<r.length;o++)s(r[o]);return s})({1:[function(require,module,exports){
+/*
+ *  Copyright (c) 2017 The WebRTC project authors. All Rights Reserved.
+ *
+ *  Use of this source code is governed by a BSD-style license
+ *  that can be found in the LICENSE file in the root of the source
+ *  tree.
+ */
+ /* eslint-env node */
+'use strict';
+
+var SDPUtils = require('sdp');
+
+function writeMediaSection(transceiver, caps, type, stream, dtlsRole) {
+  var sdp = SDPUtils.writeRtpDescription(transceiver.kind, caps);
+
+  // Map ICE parameters (ufrag, pwd) to SDP.
+  sdp += SDPUtils.writeIceParameters(
+      transceiver.iceGatherer.getLocalParameters());
+
+  // Map DTLS parameters to SDP.
+  sdp += SDPUtils.writeDtlsParameters(
+      transceiver.dtlsTransport.getLocalParameters(),
+      type === 'offer' ? 'actpass' : dtlsRole || 'active');
+
+  sdp += 'a=mid:' + transceiver.mid + '\r\n';
+
+  if (transceiver.direction) {
+    sdp += 'a=' + transceiver.direction + '\r\n';
+  } else if (transceiver.rtpSender && transceiver.rtpReceiver) {
+    sdp += 'a=sendrecv\r\n';
+  } else if (transceiver.rtpSender) {
+    sdp += 'a=sendonly\r\n';
+  } else if (transceiver.rtpReceiver) {
+    sdp += 'a=recvonly\r\n';
+  } else {
+    sdp += 'a=inactive\r\n';
+  }
+
+  if (transceiver.rtpSender) {
+    // spec.
+    var msid = 'msid:' + stream.id + ' ' +
+        transceiver.rtpSender.track.id + '\r\n';
+    sdp += 'a=' + msid;
+
+    // for Chrome.
+    sdp += 'a=ssrc:' + transceiver.sendEncodingParameters[0].ssrc +
+        ' ' + msid;
+    if (transceiver.sendEncodingParameters[0].rtx) {
+      sdp += 'a=ssrc:' + transceiver.sendEncodingParameters[0].rtx.ssrc +
+          ' ' + msid;
+      sdp += 'a=ssrc-group:FID ' +
+          transceiver.sendEncodingParameters[0].ssrc + ' ' +
+          transceiver.sendEncodingParameters[0].rtx.ssrc +
+          '\r\n';
+    }
+  }
+  // FIXME: this should be written by writeRtpDescription.
+  sdp += 'a=ssrc:' + transceiver.sendEncodingParameters[0].ssrc +
+      ' cname:' + SDPUtils.localCName + '\r\n';
+  if (transceiver.rtpSender && transceiver.sendEncodingParameters[0].rtx) {
+    sdp += 'a=ssrc:' + transceiver.sendEncodingParameters[0].rtx.ssrc +
+        ' cname:' + SDPUtils.localCName + '\r\n';
+  }
+  return sdp;
+}
+
+// Edge does not like
+// 1) stun: filtered after 14393 unless ?transport=udp is present
+// 2) turn: that does not have all of turn:host:port?transport=udp
+// 3) turn: with ipv6 addresses
+// 4) turn: occurring muliple times
+function filterIceServers(iceServers, edgeVersion) {
+  var hasTurn = false;
+  iceServers = JSON.parse(JSON.stringify(iceServers));
+  return iceServers.filter(function(server) {
+    if (server && (server.urls || server.url)) {
+      var urls = server.urls || server.url;
+      if (server.url && !server.urls) {
+        console.warn('RTCIceServer.url is deprecated! Use urls instead.');
+      }
+      var isString = typeof urls === 'string';
+      if (isString) {
+        urls = [urls];
+      }
+      urls = urls.filter(function(url) {
+        var validTurn = url.indexOf('turn:') === 0 &&
+            url.indexOf('transport=udp') !== -1 &&
+            url.indexOf('turn:[') === -1 &&
+            !hasTurn;
+
+        if (validTurn) {
+          hasTurn = true;
+          return true;
+        }
+        return url.indexOf('stun:') === 0 && edgeVersion >= 14393 &&
+            url.indexOf('?transport=udp') === -1;
+      });
+
+      delete server.url;
+      server.urls = isString ? urls[0] : urls;
+      return !!urls.length;
+    }
+    return false;
+  });
+}
+
+// Determines the intersection of local and remote capabilities.
+function getCommonCapabilities(localCapabilities, remoteCapabilities) {
+  var commonCapabilities = {
+    codecs: [],
+    headerExtensions: [],
+    fecMechanisms: []
+  };
+
+  var findCodecByPayloadType = function(pt, codecs) {
+    pt = parseInt(pt, 10);
+    for (var i = 0; i < codecs.length; i++) {
+      if (codecs[i].payloadType === pt ||
+          codecs[i].preferredPayloadType === pt) {
+        return codecs[i];
+      }
+    }
+  };
+
+  var rtxCapabilityMatches = function(lRtx, rRtx, lCodecs, rCodecs) {
+    var lCodec = findCodecByPayloadType(lRtx.parameters.apt, lCodecs);
+    var rCodec = findCodecByPayloadType(rRtx.parameters.apt, rCodecs);
+    return lCodec && rCodec &&
+        lCodec.name.toLowerCase() === rCodec.name.toLowerCase();
+  };
+
+  localCapabilities.codecs.forEach(function(lCodec) {
+    for (var i = 0; i < remoteCapabilities.codecs.length; i++) {
+      var rCodec = remoteCapabilities.codecs[i];
+      if (lCodec.name.toLowerCase() === rCodec.name.toLowerCase() &&
+          lCodec.clockRate === rCodec.clockRate) {
+        if (lCodec.name.toLowerCase() === 'rtx' &&
+            lCodec.parameters && rCodec.parameters.apt) {
+          // for RTX we need to find the local rtx that has a apt
+          // which points to the same local codec as the remote one.
+          if (!rtxCapabilityMatches(lCodec, rCodec,
+              localCapabilities.codecs, remoteCapabilities.codecs)) {
+            continue;
+          }
+        }
+        rCodec = JSON.parse(JSON.stringify(rCodec)); // deepcopy
+        // number of channels is the highest common number of channels
+        rCodec.numChannels = Math.min(lCodec.numChannels,
+            rCodec.numChannels);
+        // push rCodec so we reply with offerer payload type
+        commonCapabilities.codecs.push(rCodec);
+
+        // determine common feedback mechanisms
+        rCodec.rtcpFeedback = rCodec.rtcpFeedback.filter(function(fb) {
+          for (var j = 0; j < lCodec.rtcpFeedback.length; j++) {
+            if (lCodec.rtcpFeedback[j].type === fb.type &&
+                lCodec.rtcpFeedback[j].parameter === fb.parameter) {
+              return true;
+            }
+          }
+          return false;
+        });
+        // FIXME: also need to determine .parameters
+        //  see https://github.com/openpeer/ortc/issues/569
+        break;
+      }
+    }
+  });
+
+  localCapabilities.headerExtensions.forEach(function(lHeaderExtension) {
+    for (var i = 0; i < remoteCapabilities.headerExtensions.length;
+         i++) {
+      var rHeaderExtension = remoteCapabilities.headerExtensions[i];
+      if (lHeaderExtension.uri === rHeaderExtension.uri) {
+        commonCapabilities.headerExtensions.push(rHeaderExtension);
+        break;
+      }
+    }
+  });
+
+  // FIXME: fecMechanisms
+  return commonCapabilities;
+}
+
+// is action=setLocalDescription with type allowed in signalingState
+function isActionAllowedInSignalingState(action, type, signalingState) {
+  return {
+    offer: {
+      setLocalDescription: ['stable', 'have-local-offer'],
+      setRemoteDescription: ['stable', 'have-remote-offer']
+    },
+    answer: {
+      setLocalDescription: ['have-remote-offer', 'have-local-pranswer'],
+      setRemoteDescription: ['have-local-offer', 'have-remote-pranswer']
+    }
+  }[type][action].indexOf(signalingState) !== -1;
+}
+
+function maybeAddCandidate(iceTransport, candidate) {
+  // Edge's internal representation adds some fields therefore
+  // not all fieldѕ are taken into account.
+  var alreadyAdded = iceTransport.getRemoteCandidates()
+      .find(function(remoteCandidate) {
+        return candidate.foundation === remoteCandidate.foundation &&
+            candidate.ip === remoteCandidate.ip &&
+            candidate.port === remoteCandidate.port &&
+            candidate.priority === remoteCandidate.priority &&
+            candidate.protocol === remoteCandidate.protocol &&
+            candidate.type === remoteCandidate.type;
+      });
+  if (!alreadyAdded) {
+    iceTransport.addRemoteCandidate(candidate);
+  }
+  return !alreadyAdded;
+}
+
+module.exports = function(window, edgeVersion) {
+  var RTCPeerConnection = function(config) {
+    var self = this;
+
+    var _eventTarget = document.createDocumentFragment();
+    ['addEventListener', 'removeEventListener', 'dispatchEvent']
+        .forEach(function(method) {
+          self[method] = _eventTarget[method].bind(_eventTarget);
+        });
+
+    this.onicecandidate = null;
+    this.onaddstream = null;
+    this.ontrack = null;
+    this.onremovestream = null;
+    this.onsignalingstatechange = null;
+    this.oniceconnectionstatechange = null;
+    this.onicegatheringstatechange = null;
+    this.onnegotiationneeded = null;
+    this.ondatachannel = null;
+    this.canTrickleIceCandidates = null;
+
+    this.needNegotiation = false;
+
+    this.localStreams = [];
+    this.remoteStreams = [];
+
+    this.localDescription = null;
+    this.remoteDescription = null;
+
+    this.signalingState = 'stable';
+    this.iceConnectionState = 'new';
+    this.iceGatheringState = 'new';
+
+    config = JSON.parse(JSON.stringify(config || {}));
+
+    this.usingBundle = config.bundlePolicy === 'max-bundle';
+    if (config.rtcpMuxPolicy === 'negotiate') {
+      var e = new Error('rtcpMuxPolicy \'negotiate\' is not supported');
+      e.name = 'NotSupportedError';
+      throw(e);
+    } else if (!config.rtcpMuxPolicy) {
+      config.rtcpMuxPolicy = 'require';
+    }
+
+    switch (config.iceTransportPolicy) {
+      case 'all':
+      case 'relay':
+        break;
+      default:
+        config.iceTransportPolicy = 'all';
+        break;
+    }
+
+    switch (config.bundlePolicy) {
+      case 'balanced':
+      case 'max-compat':
+      case 'max-bundle':
+        break;
+      default:
+        config.bundlePolicy = 'balanced';
+        break;
+    }
+
+    config.iceServers = filterIceServers(config.iceServers || [], edgeVersion);
+
+    this._iceGatherers = [];
+    if (config.iceCandidatePoolSize) {
+      for (var i = config.iceCandidatePoolSize; i > 0; i--) {
+        this._iceGatherers = new window.RTCIceGatherer({
+          iceServers: config.iceServers,
+          gatherPolicy: config.iceTransportPolicy
+        });
+      }
+    } else {
+      config.iceCandidatePoolSize = 0;
+    }
+
+    this._config = config;
+
+    // per-track iceGathers, iceTransports, dtlsTransports, rtpSenders, ...
+    // everything that is needed to describe a SDP m-line.
+    this.transceivers = [];
+
+    this._sdpSessionId = SDPUtils.generateSessionId();
+    this._sdpSessionVersion = 0;
+
+    this._dtlsRole = undefined; // role for a=setup to use in answers.
+  };
+
+  RTCPeerConnection.prototype._emitGatheringStateChange = function() {
+    var event = new Event('icegatheringstatechange');
+    this.dispatchEvent(event);
+    if (typeof this.onicegatheringstatechange === 'function') {
+      this.onicegatheringstatechange(event);
+    }
+  };
+
+  RTCPeerConnection.prototype.getConfiguration = function() {
+    return this._config;
+  };
+
+  RTCPeerConnection.prototype.getLocalStreams = function() {
+    return this.localStreams;
+  };
+
+  RTCPeerConnection.prototype.getRemoteStreams = function() {
+    return this.remoteStreams;
+  };
+
+  // internal helper to create a transceiver object.
+  // (whih is not yet the same as the WebRTC 1.0 transceiver)
+  RTCPeerConnection.prototype._createTransceiver = function(kind) {
+    var hasBundleTransport = this.transceivers.length > 0;
+    var transceiver = {
+      track: null,
+      iceGatherer: null,
+      iceTransport: null,
+      dtlsTransport: null,
+      localCapabilities: null,
+      remoteCapabilities: null,
+      rtpSender: null,
+      rtpReceiver: null,
+      kind: kind,
+      mid: null,
+      sendEncodingParameters: null,
+      recvEncodingParameters: null,
+      stream: null,
+      wantReceive: true
+    };
+    if (this.usingBundle && hasBundleTransport) {
+      transceiver.iceTransport = this.transceivers[0].iceTransport;
+      transceiver.dtlsTransport = this.transceivers[0].dtlsTransport;
+    } else {
+      var transports = this._createIceAndDtlsTransports();
+      transceiver.iceTransport = transports.iceTransport;
+      transceiver.dtlsTransport = transports.dtlsTransport;
+    }
+    this.transceivers.push(transceiver);
+    return transceiver;
+  };
+
+  RTCPeerConnection.prototype.addTrack = function(track, stream) {
+    var transceiver;
+    for (var i = 0; i < this.transceivers.length; i++) {
+      if (!this.transceivers[i].track &&
+          this.transceivers[i].kind === track.kind) {
+        transceiver = this.transceivers[i];
+      }
+    }
+    if (!transceiver) {
+      transceiver = this._createTransceiver(track.kind);
+    }
+
+    this._maybeFireNegotiationNeeded();
+
+    if (this.localStreams.indexOf(stream) === -1) {
+      this.localStreams.push(stream);
+    }
+
+    transceiver.track = track;
+    transceiver.stream = stream;
+    transceiver.rtpSender = new window.RTCRtpSender(track,
+        transceiver.dtlsTransport);
+    return transceiver.rtpSender;
+  };
+
+  RTCPeerConnection.prototype.addStream = function(stream) {
+    var self = this;
+    if (edgeVersion >= 15025) {
+      stream.getTracks().forEach(function(track) {
+        self.addTrack(track, stream);
+      });
+    } else {
+      // Clone is necessary for local demos mostly, attaching directly
+      // to two different senders does not work (build 10547).
+      // Fixed in 15025 (or earlier)
+      var clonedStream = stream.clone();
+      stream.getTracks().forEach(function(track, idx) {
+        var clonedTrack = clonedStream.getTracks()[idx];
+        track.addEventListener('enabled', function(event) {
+          clonedTrack.enabled = event.enabled;
+        });
+      });
+      clonedStream.getTracks().forEach(function(track) {
+        self.addTrack(track, clonedStream);
+      });
+    }
+  };
+
+  RTCPeerConnection.prototype.removeStream = function(stream) {
+    var idx = this.localStreams.indexOf(stream);
+    if (idx > -1) {
+      this.localStreams.splice(idx, 1);
+      this._maybeFireNegotiationNeeded();
+    }
+  };
+
+  RTCPeerConnection.prototype.getSenders = function() {
+    return this.transceivers.filter(function(transceiver) {
+      return !!transceiver.rtpSender;
+    })
+    .map(function(transceiver) {
+      return transceiver.rtpSender;
+    });
+  };
+
+  RTCPeerConnection.prototype.getReceivers = function() {
+    return this.transceivers.filter(function(transceiver) {
+      return !!transceiver.rtpReceiver;
+    })
+    .map(function(transceiver) {
+      return transceiver.rtpReceiver;
+    });
+  };
+
+
+  RTCPeerConnection.prototype._createIceGatherer = function(sdpMLineIndex,
+      usingBundle) {
+    var self = this;
+    if (usingBundle && sdpMLineIndex > 0) {
+      return this.transceivers[0].iceGatherer;
+    } else if (this._iceGatherers.length) {
+      return this._iceGatherers.shift();
+    }
+    var iceGatherer = new window.RTCIceGatherer({
+      iceServers: this._config.iceServers,
+      gatherPolicy: this._config.iceTransportPolicy
+    });
+    Object.defineProperty(iceGatherer, 'state',
+        {value: 'new', writable: true}
+    );
+
+    this.transceivers[sdpMLineIndex].candidates = [];
+    this.transceivers[sdpMLineIndex].bufferCandidates = function(event) {
+      var end = !event.candidate || Object.keys(event.candidate).length === 0;
+      // polyfill since RTCIceGatherer.state is not implemented in
+      // Edge 10547 yet.
+      iceGatherer.state = end ? 'completed' : 'gathering';
+      if (self.transceivers[sdpMLineIndex].candidates !== null) {
+        self.transceivers[sdpMLineIndex].candidates.push(event.candidate);
+      }
+    };
+    iceGatherer.addEventListener('localcandidate',
+      this.transceivers[sdpMLineIndex].bufferCandidates);
+    return iceGatherer;
+  };
+
+  // start gathering from an RTCIceGatherer.
+  RTCPeerConnection.prototype._gather = function(mid, sdpMLineIndex) {
+    var self = this;
+    var iceGatherer = this.transceivers[sdpMLineIndex].iceGatherer;
+    if (iceGatherer.onlocalcandidate) {
+      return;
+    }
+    var candidates = this.transceivers[sdpMLineIndex].candidates;
+    this.transceivers[sdpMLineIndex].candidates = null;
+    iceGatherer.removeEventListener('localcandidate',
+      this.transceivers[sdpMLineIndex].bufferCandidates);
+    iceGatherer.onlocalcandidate = function(evt) {
+      if (self.usingBundle && sdpMLineIndex > 0) {
+        // if we know that we use bundle we can drop candidates with
+        // ѕdpMLineIndex > 0. If we don't do this then our state gets
+        // confused since we dispose the extra ice gatherer.
+        return;
+      }
+      var event = new Event('icecandidate');
+      event.candidate = {sdpMid: mid, sdpMLineIndex: sdpMLineIndex};
+
+      var cand = evt.candidate;
+      // Edge emits an empty object for RTCIceCandidateComplete‥
+      var end = !cand || Object.keys(cand).length === 0;
+      if (end) {
+        // polyfill since RTCIceGatherer.state is not implemented in
+        // Edge 10547 yet.
+        if (iceGatherer.state === 'new' || iceGatherer.state === 'gathering') {
+          iceGatherer.state = 'completed';
+        }
+      } else {
+        if (iceGatherer.state === 'new') {
+          iceGatherer.state = 'gathering';
+        }
+        // RTCIceCandidate doesn't have a component, needs to be added
+        cand.component = 1;
+        event.candidate.candidate = SDPUtils.writeCandidate(cand);
+      }
+
+      // update local description.
+      var sections = SDPUtils.splitSections(self.localDescription.sdp);
+      if (!end) {
+        sections[event.candidate.sdpMLineIndex + 1] +=
+            'a=' + event.candidate.candidate + '\r\n';
+      } else {
+        sections[event.candidate.sdpMLineIndex + 1] +=
+            'a=end-of-candidates\r\n';
+      }
+      self.localDescription.sdp = sections.join('');
+      var complete = self.transceivers.every(function(transceiver) {
+        return transceiver.iceGatherer &&
+            transceiver.iceGatherer.state === 'completed';
+      });
+
+      if (self.iceGatheringState !== 'gathering') {
+        self.iceGatheringState = 'gathering';
+        self._emitGatheringStateChange();
+      }
+
+      // Emit candidate. Also emit null candidate when all gatherers are
+      // complete.
+      if (!end) {
+        self.dispatchEvent(event);
+        if (typeof self.onicecandidate === 'function') {
+          self.onicecandidate(event);
+        }
+      }
+      if (complete) {
+        self.dispatchEvent(new Event('icecandidate'));
+        if (typeof self.onicecandidate === 'function') {
+          self.onicecandidate(new Event('icecandidate'));
+        }
+        self.iceGatheringState = 'complete';
+        self._emitGatheringStateChange();
+      }
+    };
+
+    // emit already gathered candidates.
+    window.setTimeout(function() {
+      candidates.forEach(function(candidate) {
+        var e = new Event('RTCIceGatherEvent');
+        e.candidate = candidate;
+        iceGatherer.onlocalcandidate(e);
+      });
+    }, 0);
+  };
+
+  // Create ICE transport and DTLS transport.
+  RTCPeerConnection.prototype._createIceAndDtlsTransports = function() {
+    var self = this;
+    var iceTransport = new window.RTCIceTransport(null);
+    iceTransport.onicestatechange = function() {
+      self._updateConnectionState();
+    };
+
+    var dtlsTransport = new window.RTCDtlsTransport(iceTransport);
+    dtlsTransport.ondtlsstatechange = function() {
+      self._updateConnectionState();
+    };
+    dtlsTransport.onerror = function() {
+      // onerror does not set state to failed by itself.
+      Object.defineProperty(dtlsTransport, 'state',
+          {value: 'failed', writable: true});
+      self._updateConnectionState();
+    };
+
+    return {
+      iceTransport: iceTransport,
+      dtlsTransport: dtlsTransport
+    };
+  };
+
+  // Destroy ICE gatherer, ICE transport and DTLS transport.
+  // Without triggering the callbacks.
+  RTCPeerConnection.prototype._disposeIceAndDtlsTransports = function(
+      sdpMLineIndex) {
+    var iceGatherer = this.transceivers[sdpMLineIndex].iceGatherer;
+    if (iceGatherer) {
+      delete iceGatherer.onlocalcandidate;
+      delete this.transceivers[sdpMLineIndex].iceGatherer;
+    }
+    var iceTransport = this.transceivers[sdpMLineIndex].iceTransport;
+    if (iceTransport) {
+      delete iceTransport.onicestatechange;
+      delete this.transceivers[sdpMLineIndex].iceTransport;
+    }
+    var dtlsTransport = this.transceivers[sdpMLineIndex].dtlsTransport;
+    if (dtlsTransport) {
+      delete dtlsTransport.ondtlsstatechange;
+      delete dtlsTransport.onerror;
+      delete this.transceivers[sdpMLineIndex].dtlsTransport;
+    }
+  };
+
+  // Start the RTP Sender and Receiver for a transceiver.
+  RTCPeerConnection.prototype._transceive = function(transceiver,
+      send, recv) {
+    var params = getCommonCapabilities(transceiver.localCapabilities,
+        transceiver.remoteCapabilities);
+    if (send && transceiver.rtpSender) {
+      params.encodings = transceiver.sendEncodingParameters;
+      params.rtcp = {
+        cname: SDPUtils.localCName,
+        compound: transceiver.rtcpParameters.compound
+      };
+      if (transceiver.recvEncodingParameters.length) {
+        params.rtcp.ssrc = transceiver.recvEncodingParameters[0].ssrc;
+      }
+      transceiver.rtpSender.send(params);
+    }
+    if (recv && transceiver.rtpReceiver && params.codecs.length > 0) {
+      // remove RTX field in Edge 14942
+      if (transceiver.kind === 'video'
+          && transceiver.recvEncodingParameters
+          && edgeVersion < 15019) {
+        transceiver.recvEncodingParameters.forEach(function(p) {
+          delete p.rtx;
+        });
+      }
+      params.encodings = transceiver.recvEncodingParameters;
+      params.rtcp = {
+        cname: transceiver.rtcpParameters.cname,
+        compound: transceiver.rtcpParameters.compound
+      };
+      if (transceiver.sendEncodingParameters.length) {
+        params.rtcp.ssrc = transceiver.sendEncodingParameters[0].ssrc;
+      }
+      transceiver.rtpReceiver.receive(params);
+    }
+  };
+
+  RTCPeerConnection.prototype.setLocalDescription = function(description) {
+    var self = this;
+    var args = arguments;
+
+    if (!isActionAllowedInSignalingState('setLocalDescription',
+        description.type, this.signalingState)) {
+      return new Promise(function(resolve, reject) {
+        var e = new Error('Can not set local ' + description.type +
+            ' in state ' + self.signalingState);
+        e.name = 'InvalidStateError';
+        if (args.length > 2 && typeof args[2] === 'function') {
+          args[2].apply(null, [e]);
+        }
+        reject(e);
+      });
+    }
+
+    var sections;
+    var sessionpart;
+    if (description.type === 'offer') {
+      // VERY limited support for SDP munging. Limited to:
+      // * changing the order of codecs
+      sections = SDPUtils.splitSections(description.sdp);
+      sessionpart = sections.shift();
+      sections.forEach(function(mediaSection, sdpMLineIndex) {
+        var caps = SDPUtils.parseRtpParameters(mediaSection);
+        self.transceivers[sdpMLineIndex].localCapabilities = caps;
+      });
+
+      this.transceivers.forEach(function(transceiver, sdpMLineIndex) {
+        self._gather(transceiver.mid, sdpMLineIndex);
+      });
+    } else if (description.type === 'answer') {
+      sections = SDPUtils.splitSections(self.remoteDescription.sdp);
+      sessionpart = sections.shift();
+      var isIceLite = SDPUtils.matchPrefix(sessionpart,
+          'a=ice-lite').length > 0;
+      sections.forEach(function(mediaSection, sdpMLineIndex) {
+        var transceiver = self.transceivers[sdpMLineIndex];
+        var iceGatherer = transceiver.iceGatherer;
+        var iceTransport = transceiver.iceTransport;
+        var dtlsTransport = transceiver.dtlsTransport;
+        var localCapabilities = transceiver.localCapabilities;
+        var remoteCapabilities = transceiver.remoteCapabilities;
+
+        // treat bundle-only as not-rejected.
+        var rejected = SDPUtils.isRejected(mediaSection) &&
+            !SDPUtils.matchPrefix(mediaSection, 'a=bundle-only').length === 1;
+
+        if (!rejected && !transceiver.isDatachannel) {
+          var remoteIceParameters = SDPUtils.getIceParameters(
+              mediaSection, sessionpart);
+          var remoteDtlsParameters = SDPUtils.getDtlsParameters(
+              mediaSection, sessionpart);
+          if (isIceLite) {
+            remoteDtlsParameters.role = 'server';
+          }
+
+          if (!self.usingBundle || sdpMLineIndex === 0) {
+            self._gather(transceiver.mid, sdpMLineIndex);
+            if (iceTransport.state === 'new') {
+              iceTransport.start(iceGatherer, remoteIceParameters,
+                  isIceLite ? 'controlling' : 'controlled');
+            }
+            if (dtlsTransport.state === 'new') {
+              dtlsTransport.start(remoteDtlsParameters);
+            }
+          }
+
+          // Calculate intersection of capabilities.
+          var params = getCommonCapabilities(localCapabilities,
+              remoteCapabilities);
+
+          // Start the RTCRtpSender. The RTCRtpReceiver for this
+          // transceiver has already been started in setRemoteDescription.
+          self._transceive(transceiver,
+              params.codecs.length > 0,
+              false);
+        }
+      });
+    }
+
+    this.localDescription = {
+      type: description.type,
+      sdp: description.sdp
+    };
+    switch (description.type) {
+      case 'offer':
+        this._updateSignalingState('have-local-offer');
+        break;
+      case 'answer':
+        this._updateSignalingState('stable');
+        break;
+      default:
+        throw new TypeError('unsupported type "' + description.type +
+            '"');
+    }
+
+    // If a success callback was provided, emit ICE candidates after it
+    // has been executed. Otherwise, emit callback after the Promise is
+    // resolved.
+    var cb = arguments.length > 1 && typeof arguments[1] === 'function' &&
+        arguments[1];
+    return new Promise(function(resolve) {
+      if (cb) {
+        cb.apply(null);
+      }
+      resolve();
+    });
+  };
+
+  RTCPeerConnection.prototype.setRemoteDescription = function(description) {
+    var self = this;
+    var args = arguments;
+
+    if (!isActionAllowedInSignalingState('setRemoteDescription',
+        description.type, this.signalingState)) {
+      return new Promise(function(resolve, reject) {
+        var e = new Error('Can not set remote ' + description.type +
+            ' in state ' + self.signalingState);
+        e.name = 'InvalidStateError';
+        if (args.length > 2 && typeof args[2] === 'function') {
+          args[2].apply(null, [e]);
+        }
+        reject(e);
+      });
+    }
+
+    var streams = {};
+    this.remoteStreams.forEach(function(stream) {
+      streams[stream.id] = stream;
+    });
+    var receiverList = [];
+    var sections = SDPUtils.splitSections(description.sdp);
+    var sessionpart = sections.shift();
+    var isIceLite = SDPUtils.matchPrefix(sessionpart,
+        'a=ice-lite').length > 0;
+    var usingBundle = SDPUtils.matchPrefix(sessionpart,
+        'a=group:BUNDLE ').length > 0;
+    this.usingBundle = usingBundle;
+    var iceOptions = SDPUtils.matchPrefix(sessionpart,
+        'a=ice-options:')[0];
+    if (iceOptions) {
+      this.canTrickleIceCandidates = iceOptions.substr(14).split(' ')
+          .indexOf('trickle') >= 0;
+    } else {
+      this.canTrickleIceCandidates = false;
+    }
+
+    sections.forEach(function(mediaSection, sdpMLineIndex) {
+      var lines = SDPUtils.splitLines(mediaSection);
+      var kind = SDPUtils.getKind(mediaSection);
+      // treat bundle-only as not-rejected.
+      var rejected = SDPUtils.isRejected(mediaSection) &&
+          !SDPUtils.matchPrefix(mediaSection, 'a=bundle-only').length === 1;
+      var protocol = lines[0].substr(2).split(' ')[2];
+
+      var direction = SDPUtils.getDirection(mediaSection, sessionpart);
+      var remoteMsid = SDPUtils.parseMsid(mediaSection);
+
+      var mid = SDPUtils.getMid(mediaSection) || SDPUtils.generateIdentifier();
+
+      // Reject datachannels which are not implemented yet.
+      if (kind === 'application' && protocol === 'DTLS/SCTP') {
+        self.transceivers[sdpMLineIndex] = {
+          mid: mid,
+          isDatachannel: true
+        };
+        return;
+      }
+
+      var transceiver;
+      var iceGatherer;
+      var iceTransport;
+      var dtlsTransport;
+      var rtpReceiver;
+      var sendEncodingParameters;
+      var recvEncodingParameters;
+      var localCapabilities;
+
+      var track;
+      // FIXME: ensure the mediaSection has rtcp-mux set.
+      var remoteCapabilities = SDPUtils.parseRtpParameters(mediaSection);
+      var remoteIceParameters;
+      var remoteDtlsParameters;
+      if (!rejected) {
+        remoteIceParameters = SDPUtils.getIceParameters(mediaSection,
+            sessionpart);
+        remoteDtlsParameters = SDPUtils.getDtlsParameters(mediaSection,
+            sessionpart);
+        remoteDtlsParameters.role = 'client';
+      }
+      recvEncodingParameters =
+          SDPUtils.parseRtpEncodingParameters(mediaSection);
+
+      var rtcpParameters = SDPUtils.parseRtcpParameters(mediaSection);
+
+      var isComplete = SDPUtils.matchPrefix(mediaSection,
+          'a=end-of-candidates', sessionpart).length > 0;
+      var cands = SDPUtils.matchPrefix(mediaSection, 'a=candidate:')
+          .map(function(cand) {
+            return SDPUtils.parseCandidate(cand);
+          })
+          .filter(function(cand) {
+            return cand.component === 1;
+          });
+
+      // Check if we can use BUNDLE and dispose transports.
+      if ((description.type === 'offer' || description.type === 'answer') &&
+          !rejected && usingBundle && sdpMLineIndex > 0 &&
+          self.transceivers[sdpMLineIndex]) {
+        self._disposeIceAndDtlsTransports(sdpMLineIndex);
+        self.transceivers[sdpMLineIndex].iceGatherer =
+            self.transceivers[0].iceGatherer;
+        self.transceivers[sdpMLineIndex].iceTransport =
+            self.transceivers[0].iceTransport;
+        self.transceivers[sdpMLineIndex].dtlsTransport =
+            self.transceivers[0].dtlsTransport;
+        if (self.transceivers[sdpMLineIndex].rtpSender) {
+          self.transceivers[sdpMLineIndex].rtpSender.setTransport(
+              self.transceivers[0].dtlsTransport);
+        }
+        if (self.transceivers[sdpMLineIndex].rtpReceiver) {
+          self.transceivers[sdpMLineIndex].rtpReceiver.setTransport(
+              self.transceivers[0].dtlsTransport);
+        }
+      }
+      if (description.type === 'offer' && !rejected) {
+        transceiver = self.transceivers[sdpMLineIndex] ||
+            self._createTransceiver(kind);
+        transceiver.mid = mid;
+
+        if (!transceiver.iceGatherer) {
+          transceiver.iceGatherer = self._createIceGatherer(sdpMLineIndex,
+              usingBundle);
+        }
+
+        if (cands.length && transceiver.iceTransport.state === 'new') {
+          if (isComplete && (!usingBundle || sdpMLineIndex === 0)) {
+            transceiver.iceTransport.setRemoteCandidates(cands);
+          } else {
+            cands.forEach(function(candidate) {
+              maybeAddCandidate(transceiver.iceTransport, candidate);
+            });
+          }
+        }
+
+        localCapabilities = window.RTCRtpReceiver.getCapabilities(kind);
+
+        // filter RTX until additional stuff needed for RTX is implemented
+        // in adapter.js
+        if (edgeVersion < 15019) {
+          localCapabilities.codecs = localCapabilities.codecs.filter(
+              function(codec) {
+                return codec.name !== 'rtx';
+              });
+        }
+
+        sendEncodingParameters = transceiver.sendEncodingParameters || [{
+          ssrc: (2 * sdpMLineIndex + 2) * 1001
+        }];
+
+        var isNewTrack = false;
+        if (direction === 'sendrecv' || direction === 'sendonly') {
+          isNewTrack = !transceiver.rtpReceiver;
+          rtpReceiver = transceiver.rtpReceiver ||
+              new window.RTCRtpReceiver(transceiver.dtlsTransport, kind);
+
+          if (isNewTrack) {
+            var stream;
+            track = rtpReceiver.track;
+            // FIXME: does not work with Plan B.
+            if (remoteMsid) {
+              if (!streams[remoteMsid.stream]) {
+                streams[remoteMsid.stream] = new window.MediaStream();
+                Object.defineProperty(streams[remoteMsid.stream], 'id', {
+                  get: function() {
+                    return remoteMsid.stream;
+                  }
+                });
+              }
+              Object.defineProperty(track, 'id', {
+                get: function() {
+                  return remoteMsid.track;
+                }
+              });
+              stream = streams[remoteMsid.stream];
+            } else {
+              if (!streams.default) {
+                streams.default = new window.MediaStream();
+              }
+              stream = streams.default;
+            }
+            stream.addTrack(track);
+            receiverList.push([track, rtpReceiver, stream]);
+          }
+        }
+
+        transceiver.localCapabilities = localCapabilities;
+        transceiver.remoteCapabilities = remoteCapabilities;
+        transceiver.rtpReceiver = rtpReceiver;
+        transceiver.rtcpParameters = rtcpParameters;
+        transceiver.sendEncodingParameters = sendEncodingParameters;
+        transceiver.recvEncodingParameters = recvEncodingParameters;
+
+        // Start the RTCRtpReceiver now. The RTPSender is started in
+        // setLocalDescription.
+        self._transceive(self.transceivers[sdpMLineIndex],
+            false,
+            isNewTrack);
+      } else if (description.type === 'answer' && !rejected) {
+        transceiver = self.transceivers[sdpMLineIndex];
+        iceGatherer = transceiver.iceGatherer;
+        iceTransport = transceiver.iceTransport;
+        dtlsTransport = transceiver.dtlsTransport;
+        rtpReceiver = transceiver.rtpReceiver;
+        sendEncodingParameters = transceiver.sendEncodingParameters;
+        localCapabilities = transceiver.localCapabilities;
+
+        self.transceivers[sdpMLineIndex].recvEncodingParameters =
+            recvEncodingParameters;
+        self.transceivers[sdpMLineIndex].remoteCapabilities =
+            remoteCapabilities;
+        self.transceivers[sdpMLineIndex].rtcpParameters = rtcpParameters;
+
+        if (cands.length && iceTransport.state === 'new') {
+          if ((isIceLite || isComplete) &&
+              (!usingBundle || sdpMLineIndex === 0)) {
+            iceTransport.setRemoteCandidates(cands);
+          } else {
+            cands.forEach(function(candidate) {
+              maybeAddCandidate(transceiver.iceTransport, candidate);
+            });
+          }
+        }
+
+        if (!usingBundle || sdpMLineIndex === 0) {
+          if (iceTransport.state === 'new') {
+            iceTransport.start(iceGatherer, remoteIceParameters,
+                'controlling');
+          }
+          if (dtlsTransport.state === 'new') {
+            dtlsTransport.start(remoteDtlsParameters);
+          }
+        }
+
+        self._transceive(transceiver,
+            direction === 'sendrecv' || direction === 'recvonly',
+            direction === 'sendrecv' || direction === 'sendonly');
+
+        if (rtpReceiver &&
+            (direction === 'sendrecv' || direction === 'sendonly')) {
+          track = rtpReceiver.track;
+          if (remoteMsid) {
+            if (!streams[remoteMsid.stream]) {
+              streams[remoteMsid.stream] = new window.MediaStream();
+            }
+            streams[remoteMsid.stream].addTrack(track);
+            receiverList.push([track, rtpReceiver, streams[remoteMsid.stream]]);
+          } else {
+            if (!streams.default) {
+              streams.default = new window.MediaStream();
+            }
+            streams.default.addTrack(track);
+            receiverList.push([track, rtpReceiver, streams.default]);
+          }
+        } else {
+          // FIXME: actually the receiver should be created later.
+          delete transceiver.rtpReceiver;
+        }
+      }
+    });
+
+    if (this._dtlsRole === undefined) {
+      this._dtlsRole = description.type === 'offer' ? 'active' : 'passive';
+    }
+
+    this.remoteDescription = {
+      type: description.type,
+      sdp: description.sdp
+    };
+    switch (description.type) {
+      case 'offer':
+        this._updateSignalingState('have-remote-offer');
+        break;
+      case 'answer':
+        this._updateSignalingState('stable');
+        break;
+      default:
+        throw new TypeError('unsupported type "' + description.type +
+            '"');
+    }
+    Object.keys(streams).forEach(function(sid) {
+      var stream = streams[sid];
+      if (stream.getTracks().length) {
+        if (self.remoteStreams.indexOf(stream) === -1) {
+          self.remoteStreams.push(stream);
+          var event = new Event('addstream');
+          event.stream = stream;
+          window.setTimeout(function() {
+            self.dispatchEvent(event);
+            if (typeof self.onaddstream === 'function') {
+              self.onaddstream(event);
+            }
+          });
+        }
+
+        receiverList.forEach(function(item) {
+          var track = item[0];
+          var receiver = item[1];
+          if (stream.id !== item[2].id) {
+            return;
+          }
+          var trackEvent = new Event('track');
+          trackEvent.track = track;
+          trackEvent.receiver = receiver;
+          trackEvent.transceiver = {receiver: receiver};
+          trackEvent.streams = [stream];
+          window.setTimeout(function() {
+            self.dispatchEvent(trackEvent);
+            if (typeof self.ontrack === 'function') {
+              self.ontrack(trackEvent);
+            }
+          });
+        });
+      }
+    });
+
+    // check whether addIceCandidate({}) was called within four seconds after
+    // setRemoteDescription.
+    window.setTimeout(function() {
+      if (!(self && self.transceivers)) {
+        return;
+      }
+      self.transceivers.forEach(function(transceiver) {
+        if (transceiver.iceTransport &&
+            transceiver.iceTransport.state === 'new' &&
+            transceiver.iceTransport.getRemoteCandidates().length > 0) {
+          console.warn('Timeout for addRemoteCandidate. Consider sending ' +
+              'an end-of-candidates notification');
+          transceiver.iceTransport.addRemoteCandidate({});
+        }
+      });
+    }, 4000);
+
+    return new Promise(function(resolve) {
+      if (args.length > 1 && typeof args[1] === 'function') {
+        args[1].apply(null);
+      }
+      resolve();
+    });
+  };
+
+  RTCPeerConnection.prototype.close = function() {
+    this.transceivers.forEach(function(transceiver) {
+      /* not yet
+      if (transceiver.iceGatherer) {
+        transceiver.iceGatherer.close();
+      }
+      */
+      if (transceiver.iceTransport) {
+        transceiver.iceTransport.stop();
+      }
+      if (transceiver.dtlsTransport) {
+        transceiver.dtlsTransport.stop();
+      }
+      if (transceiver.rtpSender) {
+        transceiver.rtpSender.stop();
+      }
+      if (transceiver.rtpReceiver) {
+        transceiver.rtpReceiver.stop();
+      }
+    });
+    // FIXME: clean up tracks, local streams, remote streams, etc
+    this._updateSignalingState('closed');
+  };
+
+  // Update the signaling state.
+  RTCPeerConnection.prototype._updateSignalingState = function(newState) {
+    this.signalingState = newState;
+    var event = new Event('signalingstatechange');
+    this.dispatchEvent(event);
+    if (typeof this.onsignalingstatechange === 'function') {
+      this.onsignalingstatechange(event);
+    }
+  };
+
+  // Determine whether to fire the negotiationneeded event.
+  RTCPeerConnection.prototype._maybeFireNegotiationNeeded = function() {
+    var self = this;
+    if (this.signalingState !== 'stable' || this.needNegotiation === true) {
+      return;
+    }
+    this.needNegotiation = true;
+    window.setTimeout(function() {
+      if (self.needNegotiation === false) {
+        return;
+      }
+      self.needNegotiation = false;
+      var event = new Event('negotiationneeded');
+      self.dispatchEvent(event);
+      if (typeof self.onnegotiationneeded === 'function') {
+        self.onnegotiationneeded(event);
+      }
+    }, 0);
+  };
+
+  // Update the connection state.
+  RTCPeerConnection.prototype._updateConnectionState = function() {
+    var newState;
+    var states = {
+      'new': 0,
+      closed: 0,
+      connecting: 0,
+      checking: 0,
+      connected: 0,
+      completed: 0,
+      disconnected: 0,
+      failed: 0
+    };
+    this.transceivers.forEach(function(transceiver) {
+      states[transceiver.iceTransport.state]++;
+      states[transceiver.dtlsTransport.state]++;
+    });
+    // ICETransport.completed and connected are the same for this purpose.
+    states.connected += states.completed;
+
+    newState = 'new';
+    if (states.failed > 0) {
+      newState = 'failed';
+    } else if (states.connecting > 0 || states.checking > 0) {
+      newState = 'connecting';
+    } else if (states.disconnected > 0) {
+      newState = 'disconnected';
+    } else if (states.new > 0) {
+      newState = 'new';
+    } else if (states.connected > 0 || states.completed > 0) {
+      newState = 'connected';
+    }
+
+    if (newState !== this.iceConnectionState) {
+      this.iceConnectionState = newState;
+      var event = new Event('iceconnectionstatechange');
+      this.dispatchEvent(event);
+      if (typeof this.oniceconnectionstatechange === 'function') {
+        this.oniceconnectionstatechange(event);
+      }
+    }
+  };
+
+  RTCPeerConnection.prototype.createOffer = function() {
+    var self = this;
+    var args = arguments;
+
+    var offerOptions;
+    if (arguments.length === 1 && typeof arguments[0] !== 'function') {
+      offerOptions = arguments[0];
+    } else if (arguments.length === 3) {
+      offerOptions = arguments[2];
+    }
+
+    var numAudioTracks = this.transceivers.filter(function(t) {
+      return t.kind === 'audio';
+    }).length;
+    var numVideoTracks = this.transceivers.filter(function(t) {
+      return t.kind === 'video';
+    }).length;
+
+    // Determine number of audio and video tracks we need to send/recv.
+    if (offerOptions) {
+      // Reject Chrome legacy constraints.
+      if (offerOptions.mandatory || offerOptions.optional) {
+        throw new TypeError(
+            'Legacy mandatory/optional constraints not supported.');
+      }
+      if (offerOptions.offerToReceiveAudio !== undefined) {
+        if (offerOptions.offerToReceiveAudio === true) {
+          numAudioTracks = 1;
+        } else if (offerOptions.offerToReceiveAudio === false) {
+          numAudioTracks = 0;
+        } else {
+          numAudioTracks = offerOptions.offerToReceiveAudio;
+        }
+      }
+      if (offerOptions.offerToReceiveVideo !== undefined) {
+        if (offerOptions.offerToReceiveVideo === true) {
+          numVideoTracks = 1;
+        } else if (offerOptions.offerToReceiveVideo === false) {
+          numVideoTracks = 0;
+        } else {
+          numVideoTracks = offerOptions.offerToReceiveVideo;
+        }
+      }
+    }
+
+    this.transceivers.forEach(function(transceiver) {
+      if (transceiver.kind === 'audio') {
+        numAudioTracks--;
+        if (numAudioTracks < 0) {
+          transceiver.wantReceive = false;
+        }
+      } else if (transceiver.kind === 'video') {
+        numVideoTracks--;
+        if (numVideoTracks < 0) {
+          transceiver.wantReceive = false;
+        }
+      }
+    });
+
+    // Create M-lines for recvonly streams.
+    while (numAudioTracks > 0 || numVideoTracks > 0) {
+      if (numAudioTracks > 0) {
+        this._createTransceiver('audio');
+        numAudioTracks--;
+      }
+      if (numVideoTracks > 0) {
+        this._createTransceiver('video');
+        numVideoTracks--;
+      }
+    }
+
+    var sdp = SDPUtils.writeSessionBoilerplate(this._sdpSessionId,
+        this._sdpSessionVersion++);
+    this.transceivers.forEach(function(transceiver, sdpMLineIndex) {
+      // For each track, create an ice gatherer, ice transport,
+      // dtls transport, potentially rtpsender and rtpreceiver.
+      var track = transceiver.track;
+      var kind = transceiver.kind;
+      var mid = SDPUtils.generateIdentifier();
+      transceiver.mid = mid;
+
+      if (!transceiver.iceGatherer) {
+        transceiver.iceGatherer = self._createIceGatherer(sdpMLineIndex,
+            self.usingBundle);
+      }
+
+      var localCapabilities = window.RTCRtpSender.getCapabilities(kind);
+      // filter RTX until additional stuff needed for RTX is implemented
+      // in adapter.js
+      if (edgeVersion < 15019) {
+        localCapabilities.codecs = localCapabilities.codecs.filter(
+            function(codec) {
+              return codec.name !== 'rtx';
+            });
+      }
+      localCapabilities.codecs.forEach(function(codec) {
+        // work around https://bugs.chromium.org/p/webrtc/issues/detail?id=6552
+        // by adding level-asymmetry-allowed=1
+        if (codec.name === 'H264' &&
+            codec.parameters['level-asymmetry-allowed'] === undefined) {
+          codec.parameters['level-asymmetry-allowed'] = '1';
+        }
+      });
+
+      // generate an ssrc now, to be used later in rtpSender.send
+      var sendEncodingParameters = transceiver.sendEncodingParameters || [{
+        ssrc: (2 * sdpMLineIndex + 1) * 1001
+      }];
+      if (track) {
+        // add RTX
+        if (edgeVersion >= 15019 && kind === 'video' &&
+            !sendEncodingParameters[0].rtx) {
+          sendEncodingParameters[0].rtx = {
+            ssrc: sendEncodingParameters[0].ssrc + 1
+          };
+        }
+      }
+
+      if (transceiver.wantReceive) {
+        transceiver.rtpReceiver = new window.RTCRtpReceiver(
+            transceiver.dtlsTransport, kind);
+      }
+
+      transceiver.localCapabilities = localCapabilities;
+      transceiver.sendEncodingParameters = sendEncodingParameters;
+    });
+
+    // always offer BUNDLE and dispose on return if not supported.
+    if (this._config.bundlePolicy !== 'max-compat') {
+      sdp += 'a=group:BUNDLE ' + this.transceivers.map(function(t) {
+        return t.mid;
+      }).join(' ') + '\r\n';
+    }
+    sdp += 'a=ice-options:trickle\r\n';
+
+    this.transceivers.forEach(function(transceiver, sdpMLineIndex) {
+      sdp += writeMediaSection(transceiver, transceiver.localCapabilities,
+          'offer', transceiver.stream, self._dtlsRole);
+      sdp += 'a=rtcp-rsize\r\n';
+
+      if (transceiver.iceGatherer && self.iceGatheringState !== 'new' &&
+          (sdpMLineIndex === 0 || !self.usingBundle)) {
+        transceiver.iceGatherer.getLocalCandidates().forEach(function(cand) {
+          cand.component = 1;
+          sdp += 'a=' + SDPUtils.writeCandidate(cand) + '\r\n';
+        });
+
+        if (transceiver.iceGatherer.state === 'completed') {
+          sdp += 'a=end-of-candidates\r\n';
+        }
+      }
+    });
+
+    var desc = new window.RTCSessionDescription({
+      type: 'offer',
+      sdp: sdp
+    });
+    return new Promise(function(resolve) {
+      if (args.length > 0 && typeof args[0] === 'function') {
+        args[0].apply(null, [desc]);
+        resolve();
+        return;
+      }
+      resolve(desc);
+    });
+  };
+
+  RTCPeerConnection.prototype.createAnswer = function() {
+    var self = this;
+    var args = arguments;
+
+    var sdp = SDPUtils.writeSessionBoilerplate(this._sdpSessionId,
+        this._sdpSessionVersion++);
+    if (this.usingBundle) {
+      sdp += 'a=group:BUNDLE ' + this.transceivers.map(function(t) {
+        return t.mid;
+      }).join(' ') + '\r\n';
+    }
+    var mediaSectionsInOffer = SDPUtils.splitSections(
+        this.remoteDescription.sdp).length - 1;
+    this.transceivers.forEach(function(transceiver, sdpMLineIndex) {
+      if (sdpMLineIndex + 1 > mediaSectionsInOffer) {
+        return;
+      }
+      if (transceiver.isDatachannel) {
+        sdp += 'm=application 0 DTLS/SCTP 5000\r\n' +
+            'c=IN IP4 0.0.0.0\r\n' +
+            'a=mid:' + transceiver.mid + '\r\n';
+        return;
+      }
+
+      // FIXME: look at direction.
+      if (transceiver.stream) {
+        var localTrack;
+        if (transceiver.kind === 'audio') {
+          localTrack = transceiver.stream.getAudioTracks()[0];
+        } else if (transceiver.kind === 'video') {
+          localTrack = transceiver.stream.getVideoTracks()[0];
+        }
+        if (localTrack) {
+          // add RTX
+          if (edgeVersion >= 15019 && transceiver.kind === 'video' &&
+              !transceiver.sendEncodingParameters[0].rtx) {
+            transceiver.sendEncodingParameters[0].rtx = {
+              ssrc: transceiver.sendEncodingParameters[0].ssrc + 1
+            };
+          }
+        }
+      }
+
+      // Calculate intersection of capabilities.
+      var commonCapabilities = getCommonCapabilities(
+          transceiver.localCapabilities,
+          transceiver.remoteCapabilities);
+
+      var hasRtx = commonCapabilities.codecs.filter(function(c) {
+        return c.name.toLowerCase() === 'rtx';
+      }).length;
+      if (!hasRtx && transceiver.sendEncodingParameters[0].rtx) {
+        delete transceiver.sendEncodingParameters[0].rtx;
+      }
+
+      sdp += writeMediaSection(transceiver, commonCapabilities,
+          'answer', transceiver.stream, self._dtlsRole);
+      if (transceiver.rtcpParameters &&
+          transceiver.rtcpParameters.reducedSize) {
+        sdp += 'a=rtcp-rsize\r\n';
+      }
+    });
+
+    var desc = new window.RTCSessionDescription({
+      type: 'answer',
+      sdp: sdp
+    });
+    return new Promise(function(resolve) {
+      if (args.length > 0 && typeof args[0] === 'function') {
+        args[0].apply(null, [desc]);
+        resolve();
+        return;
+      }
+      resolve(desc);
+    });
+  };
+
+  RTCPeerConnection.prototype.addIceCandidate = function(candidate) {
+    var err;
+    var sections;
+    if (!candidate || candidate.candidate === '') {
+      for (var j = 0; j < this.transceivers.length; j++) {
+        if (this.transceivers[j].isDatachannel) {
+          continue;
+        }
+        this.transceivers[j].iceTransport.addRemoteCandidate({});
+        sections = SDPUtils.splitSections(this.remoteDescription.sdp);
+        sections[j + 1] += 'a=end-of-candidates\r\n';
+        this.remoteDescription.sdp = sections.join('');
+        if (this.usingBundle) {
+          break;
+        }
+      }
+    } else if (!(candidate.sdpMLineIndex !== undefined || candidate.sdpMid)) {
+      throw new TypeError('sdpMLineIndex or sdpMid required');
+    } else if (!this.remoteDescription) {
+      err = new Error('Can not add ICE candidate without ' +
+          'a remote description');
+      err.name = 'InvalidStateError';
+    } else {
+      var sdpMLineIndex = candidate.sdpMLineIndex;
+      if (candidate.sdpMid) {
+        for (var i = 0; i < this.transceivers.length; i++) {
+          if (this.transceivers[i].mid === candidate.sdpMid) {
+            sdpMLineIndex = i;
+            break;
+          }
+        }
+      }
+      var transceiver = this.transceivers[sdpMLineIndex];
+      if (transceiver) {
+        if (transceiver.isDatachannel) {
+          return Promise.resolve();
+        }
+        var cand = Object.keys(candidate.candidate).length > 0 ?
+            SDPUtils.parseCandidate(candidate.candidate) : {};
+        // Ignore Chrome's invalid candidates since Edge does not like them.
+        if (cand.protocol === 'tcp' && (cand.port === 0 || cand.port === 9)) {
+          return Promise.resolve();
+        }
+        // Ignore RTCP candidates, we assume RTCP-MUX.
+        if (cand.component && cand.component !== 1) {
+          return Promise.resolve();
+        }
+        // when using bundle, avoid adding candidates to the wrong
+        // ice transport. And avoid adding candidates added in the SDP.
+        if (sdpMLineIndex === 0 || (sdpMLineIndex > 0 &&
+            transceiver.iceTransport !== this.transceivers[0].iceTransport)) {
+          if (!maybeAddCandidate(transceiver.iceTransport, cand)) {
+            err = new Error('Can not add ICE candidate');
+            err.name = 'OperationError';
+          }
+        }
+
+        if (!err) {
+          // update the remoteDescription.
+          var candidateString = candidate.candidate.trim();
+          if (candidateString.indexOf('a=') === 0) {
+            candidateString = candidateString.substr(2);
+          }
+          sections = SDPUtils.splitSections(this.remoteDescription.sdp);
+          sections[sdpMLineIndex + 1] += 'a=' +
+              (cand.type ? candidateString : 'end-of-candidates')
+              + '\r\n';
+          this.remoteDescription.sdp = sections.join('');
+        }
+      } else {
+        err = new Error('Can not add ICE candidate');
+        err.name = 'OperationError';
+      }
+    }
+    var args = arguments;
+    return new Promise(function(resolve, reject) {
+      if (err) {
+        if (args.length > 2 && typeof args[2] === 'function') {
+          args[2].apply(null, [err]);
+        }
+        reject(err);
+      } else {
+        if (args.length > 1 && typeof args[1] === 'function') {
+          args[1].apply(null);
+        }
+        resolve();
+      }
+    });
+  };
+
+  RTCPeerConnection.prototype.getStats = function() {
+    var promises = [];
+    this.transceivers.forEach(function(transceiver) {
+      ['rtpSender', 'rtpReceiver', 'iceGatherer', 'iceTransport',
+          'dtlsTransport'].forEach(function(method) {
+            if (transceiver[method]) {
+              promises.push(transceiver[method].getStats());
+            }
+          });
+    });
+    var cb = arguments.length > 1 && typeof arguments[1] === 'function' &&
+        arguments[1];
+    var fixStatsType = function(stat) {
+      return {
+        inboundrtp: 'inbound-rtp',
+        outboundrtp: 'outbound-rtp',
+        candidatepair: 'candidate-pair',
+        localcandidate: 'local-candidate',
+        remotecandidate: 'remote-candidate'
+      }[stat.type] || stat.type;
+    };
+    return new Promise(function(resolve) {
+      // shim getStats with maplike support
+      var results = new Map();
+      Promise.all(promises).then(function(res) {
+        res.forEach(function(result) {
+          Object.keys(result).forEach(function(id) {
+            result[id].type = fixStatsType(result[id]);
+            results.set(id, result[id]);
+          });
+        });
+        if (cb) {
+          cb.apply(null, results);
+        }
+        resolve(results);
+      });
+    });
+  };
+  return RTCPeerConnection;
+};
+
+},{"sdp":2}],2:[function(require,module,exports){
+ /* eslint-env node */
+'use strict';
+
+// SDP helpers.
+var SDPUtils = {};
+
+// Generate an alphanumeric identifier for cname or mids.
+// TODO: use UUIDs instead? https://gist.github.com/jed/982883
+SDPUtils.generateIdentifier = function() {
+  return Math.random().toString(36).substr(2, 10);
+};
+
+// The RTCP CNAME used by all peerconnections from the same JS.
+SDPUtils.localCName = SDPUtils.generateIdentifier();
+
+// Splits SDP into lines, dealing with both CRLF and LF.
+SDPUtils.splitLines = function(blob) {
+  return blob.trim().split('\n').map(function(line) {
+    return line.trim();
+  });
+};
+// Splits SDP into sessionpart and mediasections. Ensures CRLF.
+SDPUtils.splitSections = function(blob) {
+  var parts = blob.split('\nm=');
+  return parts.map(function(part, index) {
+    return (index > 0 ? 'm=' + part : part).trim() + '\r\n';
+  });
+};
+
+// Returns lines that start with a certain prefix.
+SDPUtils.matchPrefix = function(blob, prefix) {
+  return SDPUtils.splitLines(blob).filter(function(line) {
+    return line.indexOf(prefix) === 0;
+  });
+};
+
+// Parses an ICE candidate line. Sample input:
+// candidate:702786350 2 udp 41819902 8.8.8.8 60769 typ relay raddr 8.8.8.8
+// rport 55996"
+SDPUtils.parseCandidate = function(line) {
+  var parts;
+  // Parse both variants.
+  if (line.indexOf('a=candidate:') === 0) {
+    parts = line.substring(12).split(' ');
+  } else {
+    parts = line.substring(10).split(' ');
+  }
+
+  var candidate = {
+    foundation: parts[0],
+    component: parseInt(parts[1], 10),
+    protocol: parts[2].toLowerCase(),
+    priority: parseInt(parts[3], 10),
+    ip: parts[4],
+    port: parseInt(parts[5], 10),
+    // skip parts[6] == 'typ'
+    type: parts[7]
+  };
+
+  for (var i = 8; i < parts.length; i += 2) {
+    switch (parts[i]) {
+      case 'raddr':
+        candidate.relatedAddress = parts[i + 1];
+        break;
+      case 'rport':
+        candidate.relatedPort = parseInt(parts[i + 1], 10);
+        break;
+      case 'tcptype':
+        candidate.tcpType = parts[i + 1];
+        break;
+      case 'ufrag':
+        candidate.ufrag = parts[i + 1]; // for backward compability.
+        candidate.usernameFragment = parts[i + 1];
+        break;
+      default: // extension handling, in particular ufrag
+        candidate[parts[i]] = parts[i + 1];
+        break;
+    }
+  }
+  return candidate;
+};
+
+// Translates a candidate object into SDP candidate attribute.
+SDPUtils.writeCandidate = function(candidate) {
+  var sdp = [];
+  sdp.push(candidate.foundation);
+  sdp.push(candidate.component);
+  sdp.push(candidate.protocol.toUpperCase());
+  sdp.push(candidate.priority);
+  sdp.push(candidate.ip);
+  sdp.push(candidate.port);
+
+  var type = candidate.type;
+  sdp.push('typ');
+  sdp.push(type);
+  if (type !== 'host' && candidate.relatedAddress &&
+      candidate.relatedPort) {
+    sdp.push('raddr');
+    sdp.push(candidate.relatedAddress); // was: relAddr
+    sdp.push('rport');
+    sdp.push(candidate.relatedPort); // was: relPort
+  }
+  if (candidate.tcpType && candidate.protocol.toLowerCase() === 'tcp') {
+    sdp.push('tcptype');
+    sdp.push(candidate.tcpType);
+  }
+  if (candidate.ufrag) {
+    sdp.push('ufrag');
+    sdp.push(candidate.ufrag);
+  }
+  return 'candidate:' + sdp.join(' ');
+};
+
+// Parses an ice-options line, returns an array of option tags.
+// a=ice-options:foo bar
+SDPUtils.parseIceOptions = function(line) {
+  return line.substr(14).split(' ');
+}
+
+// Parses an rtpmap line, returns RTCRtpCoddecParameters. Sample input:
+// a=rtpmap:111 opus/48000/2
+SDPUtils.parseRtpMap = function(line) {
+  var parts = line.substr(9).split(' ');
+  var parsed = {
+    payloadType: parseInt(parts.shift(), 10) // was: id
+  };
+
+  parts = parts[0].split('/');
+
+  parsed.name = parts[0];
+  parsed.clockRate = parseInt(parts[1], 10); // was: clockrate
+  // was: channels
+  parsed.numChannels = parts.length === 3 ? parseInt(parts[2], 10) : 1;
+  return parsed;
+};
+
+// Generate an a=rtpmap line from RTCRtpCodecCapability or
+// RTCRtpCodecParameters.
+SDPUtils.writeRtpMap = function(codec) {
+  var pt = codec.payloadType;
+  if (codec.preferredPayloadType !== undefined) {
+    pt = codec.preferredPayloadType;
+  }
+  return 'a=rtpmap:' + pt + ' ' + codec.name + '/' + codec.clockRate +
+      (codec.numChannels !== 1 ? '/' + codec.numChannels : '') + '\r\n';
+};
+
+// Parses an a=extmap line (headerextension from RFC 5285). Sample input:
+// a=extmap:2 urn:ietf:params:rtp-hdrext:toffset
+// a=extmap:2/sendonly urn:ietf:params:rtp-hdrext:toffset
+SDPUtils.parseExtmap = function(line) {
+  var parts = line.substr(9).split(' ');
+  return {
+    id: parseInt(parts[0], 10),
+    direction: parts[0].indexOf('/') > 0 ? parts[0].split('/')[1] : 'sendrecv',
+    uri: parts[1]
+  };
+};
+
+// Generates a=extmap line from RTCRtpHeaderExtensionParameters or
+// RTCRtpHeaderExtension.
+SDPUtils.writeExtmap = function(headerExtension) {
+  return 'a=extmap:' + (headerExtension.id || headerExtension.preferredId) +
+      (headerExtension.direction && headerExtension.direction !== 'sendrecv'
+          ? '/' + headerExtension.direction
+          : '') +
+      ' ' + headerExtension.uri + '\r\n';
+};
+
+// Parses an ftmp line, returns dictionary. Sample input:
+// a=fmtp:96 vbr=on;cng=on
+// Also deals with vbr=on; cng=on
+SDPUtils.parseFmtp = function(line) {
+  var parsed = {};
+  var kv;
+  var parts = line.substr(line.indexOf(' ') + 1).split(';');
+  for (var j = 0; j < parts.length; j++) {
+    kv = parts[j].trim().split('=');
+    parsed[kv[0].trim()] = kv[1];
+  }
+  return parsed;
+};
+
+// Generates an a=ftmp line from RTCRtpCodecCapability or RTCRtpCodecParameters.
+SDPUtils.writeFmtp = function(codec) {
+  var line = '';
+  var pt = codec.payloadType;
+  if (codec.preferredPayloadType !== undefined) {
+    pt = codec.preferredPayloadType;
+  }
+  if (codec.parameters && Object.keys(codec.parameters).length) {
+    var params = [];
+    Object.keys(codec.parameters).forEach(function(param) {
+      params.push(param + '=' + codec.parameters[param]);
+    });
+    line += 'a=fmtp:' + pt + ' ' + params.join(';') + '\r\n';
+  }
+  return line;
+};
+
+// Parses an rtcp-fb line, returns RTCPRtcpFeedback object. Sample input:
+// a=rtcp-fb:98 nack rpsi
+SDPUtils.parseRtcpFb = function(line) {
+  var parts = line.substr(line.indexOf(' ') + 1).split(' ');
+  return {
+    type: parts.shift(),
+    parameter: parts.join(' ')
+  };
+};
+// Generate a=rtcp-fb lines from RTCRtpCodecCapability or RTCRtpCodecParameters.
+SDPUtils.writeRtcpFb = function(codec) {
+  var lines = '';
+  var pt = codec.payloadType;
+  if (codec.preferredPayloadType !== undefined) {
+    pt = codec.preferredPayloadType;
+  }
+  if (codec.rtcpFeedback && codec.rtcpFeedback.length) {
+    // FIXME: special handling for trr-int?
+    codec.rtcpFeedback.forEach(function(fb) {
+      lines += 'a=rtcp-fb:' + pt + ' ' + fb.type +
+      (fb.parameter && fb.parameter.length ? ' ' + fb.parameter : '') +
+          '\r\n';
+    });
+  }
+  return lines;
+};
+
+// Parses an RFC 5576 ssrc media attribute. Sample input:
+// a=ssrc:3735928559 cname:something
+SDPUtils.parseSsrcMedia = function(line) {
+  var sp = line.indexOf(' ');
+  var parts = {
+    ssrc: parseInt(line.substr(7, sp - 7), 10)
+  };
+  var colon = line.indexOf(':', sp);
+  if (colon > -1) {
+    parts.attribute = line.substr(sp + 1, colon - sp - 1);
+    parts.value = line.substr(colon + 1);
+  } else {
+    parts.attribute = line.substr(sp + 1);
+  }
+  return parts;
+};
+
+// Extracts the MID (RFC 5888) from a media section.
+// returns the MID or undefined if no mid line was found.
+SDPUtils.getMid = function(mediaSection) {
+  var mid = SDPUtils.matchPrefix(mediaSection, 'a=mid:')[0];
+  if (mid) {
+    return mid.substr(6);
+  }
+}
+
+SDPUtils.parseFingerprint = function(line) {
+  var parts = line.substr(14).split(' ');
+  return {
+    algorithm: parts[0].toLowerCase(), // algorithm is case-sensitive in Edge.
+    value: parts[1]
+  };
+};
+
+// Extracts DTLS parameters from SDP media section or sessionpart.
+// FIXME: for consistency with other functions this should only
+//   get the fingerprint line as input. See also getIceParameters.
+SDPUtils.getDtlsParameters = function(mediaSection, sessionpart) {
+  var lines = SDPUtils.matchPrefix(mediaSection + sessionpart,
+      'a=fingerprint:');
+  // Note: a=setup line is ignored since we use the 'auto' role.
+  // Note2: 'algorithm' is not case sensitive except in Edge.
+  return {
+    role: 'auto',
+    fingerprints: lines.map(SDPUtils.parseFingerprint)
+  };
+};
+
+// Serializes DTLS parameters to SDP.
+SDPUtils.writeDtlsParameters = function(params, setupType) {
+  var sdp = 'a=setup:' + setupType + '\r\n';
+  params.fingerprints.forEach(function(fp) {
+    sdp += 'a=fingerprint:' + fp.algorithm + ' ' + fp.value + '\r\n';
+  });
+  return sdp;
+};
+// Parses ICE information from SDP media section or sessionpart.
+// FIXME: for consistency with other functions this should only
+//   get the ice-ufrag and ice-pwd lines as input.
+SDPUtils.getIceParameters = function(mediaSection, sessionpart) {
+  var lines = SDPUtils.splitLines(mediaSection);
+  // Search in session part, too.
+  lines = lines.concat(SDPUtils.splitLines(sessionpart));
+  var iceParameters = {
+    usernameFragment: lines.filter(function(line) {
+      return line.indexOf('a=ice-ufrag:') === 0;
+    })[0].substr(12),
+    password: lines.filter(function(line) {
+      return line.indexOf('a=ice-pwd:') === 0;
+    })[0].substr(10)
+  };
+  return iceParameters;
+};
+
+// Serializes ICE parameters to SDP.
+SDPUtils.writeIceParameters = function(params) {
+  return 'a=ice-ufrag:' + params.usernameFragment + '\r\n' +
+      'a=ice-pwd:' + params.password + '\r\n';
+};
+
+// Parses the SDP media section and returns RTCRtpParameters.
+SDPUtils.parseRtpParameters = function(mediaSection) {
+  var description = {
+    codecs: [],
+    headerExtensions: [],
+    fecMechanisms: [],
+    rtcp: []
+  };
+  var lines = SDPUtils.splitLines(mediaSection);
+  var mline = lines[0].split(' ');
+  for (var i = 3; i < mline.length; i++) { // find all codecs from mline[3..]
+    var pt = mline[i];
+    var rtpmapline = SDPUtils.matchPrefix(
+        mediaSection, 'a=rtpmap:' + pt + ' ')[0];
+    if (rtpmapline) {
+      var codec = SDPUtils.parseRtpMap(rtpmapline);
+      var fmtps = SDPUtils.matchPrefix(
+          mediaSection, 'a=fmtp:' + pt + ' ');
+      // Only the first a=fmtp:<pt> is considered.
+      codec.parameters = fmtps.length ? SDPUtils.parseFmtp(fmtps[0]) : {};
+      codec.rtcpFeedback = SDPUtils.matchPrefix(
+          mediaSection, 'a=rtcp-fb:' + pt + ' ')
+        .map(SDPUtils.parseRtcpFb);
+      description.codecs.push(codec);
+      // parse FEC mechanisms from rtpmap lines.
+      switch (codec.name.toUpperCase()) {
+        case 'RED':
+        case 'ULPFEC':
+          description.fecMechanisms.push(codec.name.toUpperCase());
+          break;
+        default: // only RED and ULPFEC are recognized as FEC mechanisms.
+          break;
+      }
+    }
+  }
+  SDPUtils.matchPrefix(mediaSection, 'a=extmap:').forEach(function(line) {
+    description.headerExtensions.push(SDPUtils.parseExtmap(line));
+  });
+  // FIXME: parse rtcp.
+  return description;
+};
+
+// Generates parts of the SDP media section describing the capabilities /
+// parameters.
+SDPUtils.writeRtpDescription = function(kind, caps) {
+  var sdp = '';
+
+  // Build the mline.
+  sdp += 'm=' + kind + ' ';
+  sdp += caps.codecs.length > 0 ? '9' : '0'; // reject if no codecs.
+  sdp += ' UDP/TLS/RTP/SAVPF ';
+  sdp += caps.codecs.map(function(codec) {
+    if (codec.preferredPayloadType !== undefined) {
+      return codec.preferredPayloadType;
+    }
+    return codec.payloadType;
+  }).join(' ') + '\r\n';
+
+  sdp += 'c=IN IP4 0.0.0.0\r\n';
+  sdp += 'a=rtcp:9 IN IP4 0.0.0.0\r\n';
+
+  // Add a=rtpmap lines for each codec. Also fmtp and rtcp-fb.
+  caps.codecs.forEach(function(codec) {
+    sdp += SDPUtils.writeRtpMap(codec);
+    sdp += SDPUtils.writeFmtp(codec);
+    sdp += SDPUtils.writeRtcpFb(codec);
+  });
+  var maxptime = 0;
+  caps.codecs.forEach(function(codec) {
+    if (codec.maxptime > maxptime) {
+      maxptime = codec.maxptime;
+    }
+  });
+  if (maxptime > 0) {
+    sdp += 'a=maxptime:' + maxptime + '\r\n';
+  }
+  sdp += 'a=rtcp-mux\r\n';
+
+  caps.headerExtensions.forEach(function(extension) {
+    sdp += SDPUtils.writeExtmap(extension);
+  });
+  // FIXME: write fecMechanisms.
+  return sdp;
+};
+
+// Parses the SDP media section and returns an array of
+// RTCRtpEncodingParameters.
+SDPUtils.parseRtpEncodingParameters = function(mediaSection) {
+  var encodingParameters = [];
+  var description = SDPUtils.parseRtpParameters(mediaSection);
+  var hasRed = description.fecMechanisms.indexOf('RED') !== -1;
+  var hasUlpfec = description.fecMechanisms.indexOf('ULPFEC') !== -1;
+
+  // filter a=ssrc:... cname:, ignore PlanB-msid
+  var ssrcs = SDPUtils.matchPrefix(mediaSection, 'a=ssrc:')
+  .map(function(line) {
+    return SDPUtils.parseSsrcMedia(line);
+  })
+  .filter(function(parts) {
+    return parts.attribute === 'cname';
+  });
+  var primarySsrc = ssrcs.length > 0 && ssrcs[0].ssrc;
+  var secondarySsrc;
+
+  var flows = SDPUtils.matchPrefix(mediaSection, 'a=ssrc-group:FID')
+  .map(function(line) {
+    var parts = line.split(' ');
+    parts.shift();
+    return parts.map(function(part) {
+      return parseInt(part, 10);
+    });
+  });
+  if (flows.length > 0 && flows[0].length > 1 && flows[0][0] === primarySsrc) {
+    secondarySsrc = flows[0][1];
+  }
+
+  description.codecs.forEach(function(codec) {
+    if (codec.name.toUpperCase() === 'RTX' && codec.parameters.apt) {
+      var encParam = {
+        ssrc: primarySsrc,
+        codecPayloadType: parseInt(codec.parameters.apt, 10),
+        rtx: {
+          ssrc: secondarySsrc
+        }
+      };
+      encodingParameters.push(encParam);
+      if (hasRed) {
+        encParam = JSON.parse(JSON.stringify(encParam));
+        encParam.fec = {
+          ssrc: secondarySsrc,
+          mechanism: hasUlpfec ? 'red+ulpfec' : 'red'
+        };
+        encodingParameters.push(encParam);
+      }
+    }
+  });
+  if (encodingParameters.length === 0 && primarySsrc) {
+    encodingParameters.push({
+      ssrc: primarySsrc
+    });
+  }
+
+  // we support both b=AS and b=TIAS but interpret AS as TIAS.
+  var bandwidth = SDPUtils.matchPrefix(mediaSection, 'b=');
+  if (bandwidth.length) {
+    if (bandwidth[0].indexOf('b=TIAS:') === 0) {
+      bandwidth = parseInt(bandwidth[0].substr(7), 10);
+    } else if (bandwidth[0].indexOf('b=AS:') === 0) {
+      // use formula from JSEP to convert b=AS to TIAS value.
+      bandwidth = parseInt(bandwidth[0].substr(5), 10) * 1000 * 0.95
+          - (50 * 40 * 8);
+    } else {
+      bandwidth = undefined;
+    }
+    encodingParameters.forEach(function(params) {
+      params.maxBitrate = bandwidth;
+    });
+  }
+  return encodingParameters;
+};
+
+// parses http://draft.ortc.org/#rtcrtcpparameters*
+SDPUtils.parseRtcpParameters = function(mediaSection) {
+  var rtcpParameters = {};
+
+  var cname;
+  // Gets the first SSRC. Note that with RTX there might be multiple
+  // SSRCs.
+  var remoteSsrc = SDPUtils.matchPrefix(mediaSection, 'a=ssrc:')
+      .map(function(line) {
+        return SDPUtils.parseSsrcMedia(line);
+      })
+      .filter(function(obj) {
+        return obj.attribute === 'cname';
+      })[0];
+  if (remoteSsrc) {
+    rtcpParameters.cname = remoteSsrc.value;
+    rtcpParameters.ssrc = remoteSsrc.ssrc;
+  }
+
+  // Edge uses the compound attribute instead of reducedSize
+  // compound is !reducedSize
+  var rsize = SDPUtils.matchPrefix(mediaSection, 'a=rtcp-rsize');
+  rtcpParameters.reducedSize = rsize.length > 0;
+  rtcpParameters.compound = rsize.length === 0;
+
+  // parses the rtcp-mux attrÑ–bute.
+  // Note that Edge does not support unmuxed RTCP.
+  var mux = SDPUtils.matchPrefix(mediaSection, 'a=rtcp-mux');
+  rtcpParameters.mux = mux.length > 0;
+
+  return rtcpParameters;
+};
+
+// parses either a=msid: or a=ssrc:... msid lines and returns
+// the id of the MediaStream and MediaStreamTrack.
+SDPUtils.parseMsid = function(mediaSection) {
+  var parts;
+  var spec = SDPUtils.matchPrefix(mediaSection, 'a=msid:');
+  if (spec.length === 1) {
+    parts = spec[0].substr(7).split(' ');
+    return {stream: parts[0], track: parts[1]};
+  }
+  var planB = SDPUtils.matchPrefix(mediaSection, 'a=ssrc:')
+  .map(function(line) {
+    return SDPUtils.parseSsrcMedia(line);
+  })
+  .filter(function(parts) {
+    return parts.attribute === 'msid';
+  });
+  if (planB.length > 0) {
+    parts = planB[0].value.split(' ');
+    return {stream: parts[0], track: parts[1]};
+  }
+};
+
+// Generate a session ID for SDP.
+// https://tools.ietf.org/html/draft-ietf-rtcweb-jsep-20#section-5.2.1
+// recommends using a cryptographically random +ve 64-bit value
+// but right now this should be acceptable and within the right range
+SDPUtils.generateSessionId = function() {
+  return Math.random().toString().substr(2, 21);
+};
+
+// Write boilder plate for start of SDP
+// sessId argument is optional - if not supplied it will
+// be generated randomly
+// sessVersion is optional and defaults to 2
+SDPUtils.writeSessionBoilerplate = function(sessId, sessVer) {
+  var sessionId;
+  var version = sessVer !== undefined ? sessVer : 2;
+  if (sessId) {
+    sessionId = sessId;
+  } else {
+    sessionId = SDPUtils.generateSessionId();
+  }
+  // FIXME: sess-id should be an NTP timestamp.
+  return 'v=0\r\n' +
+      'o=thisisadapterortc ' + sessionId + ' ' + version + ' IN IP4 127.0.0.1\r\n' +
+      's=-\r\n' +
+      't=0 0\r\n';
+};
+
+SDPUtils.writeMediaSection = function(transceiver, caps, type, stream) {
+  var sdp = SDPUtils.writeRtpDescription(transceiver.kind, caps);
+
+  // Map ICE parameters (ufrag, pwd) to SDP.
+  sdp += SDPUtils.writeIceParameters(
+      transceiver.iceGatherer.getLocalParameters());
+
+  // Map DTLS parameters to SDP.
+  sdp += SDPUtils.writeDtlsParameters(
+      transceiver.dtlsTransport.getLocalParameters(),
+      type === 'offer' ? 'actpass' : 'active');
+
+  sdp += 'a=mid:' + transceiver.mid + '\r\n';
+
+  if (transceiver.direction) {
+    sdp += 'a=' + transceiver.direction + '\r\n';
+  } else if (transceiver.rtpSender && transceiver.rtpReceiver) {
+    sdp += 'a=sendrecv\r\n';
+  } else if (transceiver.rtpSender) {
+    sdp += 'a=sendonly\r\n';
+  } else if (transceiver.rtpReceiver) {
+    sdp += 'a=recvonly\r\n';
+  } else {
+    sdp += 'a=inactive\r\n';
+  }
+
+  if (transceiver.rtpSender) {
+    // spec.
+    var msid = 'msid:' + stream.id + ' ' +
+        transceiver.rtpSender.track.id + '\r\n';
+    sdp += 'a=' + msid;
+
+    // for Chrome.
+    sdp += 'a=ssrc:' + transceiver.sendEncodingParameters[0].ssrc +
+        ' ' + msid;
+    if (transceiver.sendEncodingParameters[0].rtx) {
+      sdp += 'a=ssrc:' + transceiver.sendEncodingParameters[0].rtx.ssrc +
+          ' ' + msid;
+      sdp += 'a=ssrc-group:FID ' +
+          transceiver.sendEncodingParameters[0].ssrc + ' ' +
+          transceiver.sendEncodingParameters[0].rtx.ssrc +
+          '\r\n';
+    }
+  }
+  // FIXME: this should be written by writeRtpDescription.
+  sdp += 'a=ssrc:' + transceiver.sendEncodingParameters[0].ssrc +
+      ' cname:' + SDPUtils.localCName + '\r\n';
+  if (transceiver.rtpSender && transceiver.sendEncodingParameters[0].rtx) {
+    sdp += 'a=ssrc:' + transceiver.sendEncodingParameters[0].rtx.ssrc +
+        ' cname:' + SDPUtils.localCName + '\r\n';
+  }
+  return sdp;
+};
+
+// Gets the direction from the mediaSection or the sessionpart.
+SDPUtils.getDirection = function(mediaSection, sessionpart) {
+  // Look for sendrecv, sendonly, recvonly, inactive, default to sendrecv.
+  var lines = SDPUtils.splitLines(mediaSection);
+  for (var i = 0; i < lines.length; i++) {
+    switch (lines[i]) {
+      case 'a=sendrecv':
+      case 'a=sendonly':
+      case 'a=recvonly':
+      case 'a=inactive':
+        return lines[i].substr(2);
+      default:
+        // FIXME: What should happen here?
+    }
+  }
+  if (sessionpart) {
+    return SDPUtils.getDirection(sessionpart);
+  }
+  return 'sendrecv';
+};
+
+SDPUtils.getKind = function(mediaSection) {
+  var lines = SDPUtils.splitLines(mediaSection);
+  var mline = lines[0].split(' ');
+  return mline[0].substr(2);
+};
+
+SDPUtils.isRejected = function(mediaSection) {
+  return mediaSection.split(' ', 2)[1] === '0';
+};
+
+SDPUtils.parseMLine = function(mediaSection) {
+  var lines = SDPUtils.splitLines(mediaSection);
+  var mline = lines[0].split(' ');
+  return {
+    kind: mline[0].substr(2),
+    port: parseInt(mline[1], 10),
+    protocol: mline[2],
+    fmt: mline.slice(3).join(' ')
+  };
+};
+
+// Expose public methods.
+if (typeof module === 'object') {
+  module.exports = SDPUtils;
+}
+
+},{}],3:[function(require,module,exports){
+(function (global){
+/*
+ *  Copyright (c) 2016 The WebRTC project authors. All Rights Reserved.
+ *
+ *  Use of this source code is governed by a BSD-style license
+ *  that can be found in the LICENSE file in the root of the source
+ *  tree.
+ */
+ /* eslint-env node */
+
+'use strict';
+
+var adapterFactory = require('./adapter_factory.js');
+module.exports = adapterFactory({window: global.window});
+
+}).call(this,typeof global !== "undefined" ? global : typeof self !== "undefined" ? self : typeof window !== "undefined" ? window : {})
+},{"./adapter_factory.js":4}],4:[function(require,module,exports){
+/*
+ *  Copyright (c) 2016 The WebRTC project authors. All Rights Reserved.
+ *
+ *  Use of this source code is governed by a BSD-style license
+ *  that can be found in the LICENSE file in the root of the source
+ *  tree.
+ */
+ /* eslint-env node */
+
+'use strict';
+
+var utils = require('./utils');
+// Shimming starts here.
+module.exports = function(dependencies, opts) {
+  var window = dependencies && dependencies.window;
+
+  var options = {
+    shimChrome: true,
+    shimFirefox: true,
+    shimEdge: true,
+    shimSafari: true,
+  };
+
+  for (var key in opts) {
+    if (hasOwnProperty.call(opts, key)) {
+      options[key] = opts[key];
+    }
+  }
+
+  // Utils.
+  var logging = utils.log;
+  var browserDetails = utils.detectBrowser(window);
+
+  // Export to the adapter global object visible in the browser.
+  var adapter = {
+    browserDetails: browserDetails,
+    extractVersion: utils.extractVersion,
+    disableLog: utils.disableLog,
+    disableWarnings: utils.disableWarnings
+  };
+
+  // Uncomment the line below if you want logging to occur, including logging
+  // for the switch statement below. Can also be turned on in the browser via
+  // adapter.disableLog(false), but then logging from the switch statement below
+  // will not appear.
+  // require('./utils').disableLog(false);
+
+  // Browser shims.
+  var chromeShim = require('./chrome/chrome_shim') || null;
+  var edgeShim = require('./edge/edge_shim') || null;
+  var firefoxShim = require('./firefox/firefox_shim') || null;
+  var safariShim = require('./safari/safari_shim') || null;
+  var commonShim = require('./common_shim') || null;
+
+  // Shim browser if found.
+  switch (browserDetails.browser) {
+    case 'chrome':
+      if (!chromeShim || !chromeShim.shimPeerConnection ||
+          !options.shimChrome) {
+        logging('Chrome shim is not included in this adapter release.');
+        return adapter;
+      }
+      logging('adapter.js shimming chrome.');
+      // Export to the adapter global object visible in the browser.
+      adapter.browserShim = chromeShim;
+      commonShim.shimCreateObjectURL(window);
+
+      chromeShim.shimGetUserMedia(window);
+      chromeShim.shimMediaStream(window);
+      chromeShim.shimSourceObject(window);
+      chromeShim.shimPeerConnection(window);
+      chromeShim.shimOnTrack(window);
+      chromeShim.shimAddTrackRemoveTrack(window);
+      chromeShim.shimGetSendersWithDtmf(window);
+
+      commonShim.shimRTCIceCandidate(window);
+      break;
+    case 'firefox':
+      if (!firefoxShim || !firefoxShim.shimPeerConnection ||
+          !options.shimFirefox) {
+        logging('Firefox shim is not included in this adapter release.');
+        return adapter;
+      }
+      logging('adapter.js shimming firefox.');
+      // Export to the adapter global object visible in the browser.
+      adapter.browserShim = firefoxShim;
+      commonShim.shimCreateObjectURL(window);
+
+      firefoxShim.shimGetUserMedia(window);
+      firefoxShim.shimSourceObject(window);
+      firefoxShim.shimPeerConnection(window);
+      firefoxShim.shimOnTrack(window);
+
+      commonShim.shimRTCIceCandidate(window);
+      break;
+    case 'edge':
+      if (!edgeShim || !edgeShim.shimPeerConnection || !options.shimEdge) {
+        logging('MS edge shim is not included in this adapter release.');
+        return adapter;
+      }
+      logging('adapter.js shimming edge.');
+      // Export to the adapter global object visible in the browser.
+      adapter.browserShim = edgeShim;
+      commonShim.shimCreateObjectURL(window);
+
+      edgeShim.shimGetUserMedia(window);
+      edgeShim.shimPeerConnection(window);
+      edgeShim.shimReplaceTrack(window);
+
+      // the edge shim implements the full RTCIceCandidate object.
+      break;
+    case 'safari':
+      if (!safariShim || !options.shimSafari) {
+        logging('Safari shim is not included in this adapter release.');
+        return adapter;
+      }
+      logging('adapter.js shimming safari.');
+      // Export to the adapter global object visible in the browser.
+      adapter.browserShim = safariShim;
+      commonShim.shimCreateObjectURL(window);
+
+      safariShim.shimRTCIceServerUrls(window);
+      safariShim.shimCallbacksAPI(window);
+      safariShim.shimLocalStreamsAPI(window);
+      safariShim.shimRemoteStreamsAPI(window);
+      safariShim.shimTrackEventTransceiver(window);
+      safariShim.shimGetUserMedia(window);
+      safariShim.shimCreateOfferLegacy(window);
+
+      commonShim.shimRTCIceCandidate(window);
+      break;
+    default:
+      logging('Unsupported browser!');
+      break;
+  }
+
+  return adapter;
+};
+
+},{"./chrome/chrome_shim":5,"./common_shim":7,"./edge/edge_shim":8,"./firefox/firefox_shim":10,"./safari/safari_shim":12,"./utils":13}],5:[function(require,module,exports){
+
+/*
+ *  Copyright (c) 2016 The WebRTC project authors. All Rights Reserved.
+ *
+ *  Use of this source code is governed by a BSD-style license
+ *  that can be found in the LICENSE file in the root of the source
+ *  tree.
+ */
+ /* eslint-env node */
+'use strict';
+var utils = require('../utils.js');
+var logging = utils.log;
+
+var chromeShim = {
+  shimMediaStream: function(window) {
+    window.MediaStream = window.MediaStream || window.webkitMediaStream;
+  },
+
+  shimOnTrack: function(window) {
+    if (typeof window === 'object' && window.RTCPeerConnection && !('ontrack' in
+        window.RTCPeerConnection.prototype)) {
+      Object.defineProperty(window.RTCPeerConnection.prototype, 'ontrack', {
+        get: function() {
+          return this._ontrack;
+        },
+        set: function(f) {
+          if (this._ontrack) {
+            this.removeEventListener('track', this._ontrack);
+          }
+          this.addEventListener('track', this._ontrack = f);
+        }
+      });
+      var origSetRemoteDescription =
+          window.RTCPeerConnection.prototype.setRemoteDescription;
+      window.RTCPeerConnection.prototype.setRemoteDescription = function() {
+        var pc = this;
+        if (!pc._ontrackpoly) {
+          pc._ontrackpoly = function(e) {
+            // onaddstream does not fire when a track is added to an existing
+            // stream. But stream.onaddtrack is implemented so we use that.
+            e.stream.addEventListener('addtrack', function(te) {
+              var receiver;
+              if (window.RTCPeerConnection.prototype.getReceivers) {
+                receiver = pc.getReceivers().find(function(r) {
+                  return r.track && r.track.id === te.track.id;
+                });
+              } else {
+                receiver = {track: te.track};
+              }
+
+              var event = new Event('track');
+              event.track = te.track;
+              event.receiver = receiver;
+              event.transceiver = {receiver: receiver};
+              event.streams = [e.stream];
+              pc.dispatchEvent(event);
+            });
+            e.stream.getTracks().forEach(function(track) {
+              var receiver;
+              if (window.RTCPeerConnection.prototype.getReceivers) {
+                receiver = pc.getReceivers().find(function(r) {
+                  return r.track && r.track.id === track.id;
+                });
+              } else {
+                receiver = {track: track};
+              }
+              var event = new Event('track');
+              event.track = track;
+              event.receiver = receiver;
+              event.transceiver = {receiver: receiver};
+              event.streams = [e.stream];
+              pc.dispatchEvent(event);
+            });
+          };
+          pc.addEventListener('addstream', pc._ontrackpoly);
+        }
+        return origSetRemoteDescription.apply(pc, arguments);
+      };
+    }
+  },
+
+  shimGetSendersWithDtmf: function(window) {
+    // Overrides addTrack/removeTrack, depends on shimAddTrackRemoveTrack.
+    if (typeof window === 'object' && window.RTCPeerConnection &&
+        !('getSenders' in window.RTCPeerConnection.prototype) &&
+        'createDTMFSender' in window.RTCPeerConnection.prototype) {
+      var shimSenderWithDtmf = function(pc, track) {
+        return {
+          track: track,
+          get dtmf() {
+            if (this._dtmf === undefined) {
+              if (track.kind === 'audio') {
+                this._dtmf = pc.createDTMFSender(track);
+              } else {
+                this._dtmf = null;
+              }
+            }
+            return this._dtmf;
+          },
+          _pc: pc
+        };
+      };
+
+      // augment addTrack when getSenders is not available.
+      if (!window.RTCPeerConnection.prototype.getSenders) {
+        window.RTCPeerConnection.prototype.getSenders = function() {
+          this._senders = this._senders || [];
+          return this._senders.slice(); // return a copy of the internal state.
+        };
+        var origAddTrack = window.RTCPeerConnection.prototype.addTrack;
+        window.RTCPeerConnection.prototype.addTrack = function(track, stream) {
+          var pc = this;
+          var sender = origAddTrack.apply(pc, arguments);
+          if (!sender) {
+            sender = shimSenderWithDtmf(pc, track);
+            pc._senders.push(sender);
+          }
+          return sender;
+        };
+
+        var origRemoveTrack = window.RTCPeerConnection.prototype.removeTrack;
+        window.RTCPeerConnection.prototype.removeTrack = function(sender) {
+          var pc = this;
+          origRemoveTrack.apply(pc, arguments);
+          var idx = pc._senders.indexOf(sender);
+          if (idx !== -1) {
+            pc._senders.splice(idx, 1);
+          }
+        };
+      }
+      var origAddStream = window.RTCPeerConnection.prototype.addStream;
+      window.RTCPeerConnection.prototype.addStream = function(stream) {
+        var pc = this;
+        pc._senders = pc._senders || [];
+        origAddStream.apply(pc, [stream]);
+        stream.getTracks().forEach(function(track) {
+          pc._senders.push(shimSenderWithDtmf(pc, track));
+        });
+      };
+
+      var origRemoveStream = window.RTCPeerConnection.prototype.removeStream;
+      window.RTCPeerConnection.prototype.removeStream = function(stream) {
+        var pc = this;
+        pc._senders = pc._senders || [];
+        origRemoveStream.apply(pc, [stream]);
+
+        stream.getTracks().forEach(function(track) {
+          var sender = pc._senders.find(function(s) {
+            return s.track === track;
+          });
+          if (sender) {
+            pc._senders.splice(pc._senders.indexOf(sender), 1); // remove sender
+          }
+        });
+      };
+    } else if (typeof window === 'object' && window.RTCPeerConnection &&
+               'getSenders' in window.RTCPeerConnection.prototype &&
+               'createDTMFSender' in window.RTCPeerConnection.prototype &&
+               window.RTCRtpSender &&
+               !('dtmf' in window.RTCRtpSender.prototype)) {
+      var origGetSenders = window.RTCPeerConnection.prototype.getSenders;
+      window.RTCPeerConnection.prototype.getSenders = function() {
+        var pc = this;
+        var senders = origGetSenders.apply(pc, []);
+        senders.forEach(function(sender) {
+          sender._pc = pc;
+        });
+        return senders;
+      };
+
+      Object.defineProperty(window.RTCRtpSender.prototype, 'dtmf', {
+        get: function() {
+          if (this._dtmf === undefined) {
+            if (this.track.kind === 'audio') {
+              this._dtmf = this._pc.createDTMFSender(this.track);
+            } else {
+              this._dtmf = null;
+            }
+          }
+          return this._dtmf;
+        }
+      });
+    }
+  },
+
+  shimSourceObject: function(window) {
+    var URL = window && window.URL;
+
+    if (typeof window === 'object') {
+      if (window.HTMLMediaElement &&
+        !('srcObject' in window.HTMLMediaElement.prototype)) {
+        // Shim the srcObject property, once, when HTMLMediaElement is found.
+        Object.defineProperty(window.HTMLMediaElement.prototype, 'srcObject', {
+          get: function() {
+            return this._srcObject;
+          },
+          set: function(stream) {
+            var self = this;
+            // Use _srcObject as a private property for this shim
+            this._srcObject = stream;
+            if (this.src) {
+              URL.revokeObjectURL(this.src);
+            }
+
+            if (!stream) {
+              this.src = '';
+              return undefined;
+            }
+            this.src = URL.createObjectURL(stream);
+            // We need to recreate the blob url when a track is added or
+            // removed. Doing it manually since we want to avoid a recursion.
+            stream.addEventListener('addtrack', function() {
+              if (self.src) {
+                URL.revokeObjectURL(self.src);
+              }
+              self.src = URL.createObjectURL(stream);
+            });
+            stream.addEventListener('removetrack', function() {
+              if (self.src) {
+                URL.revokeObjectURL(self.src);
+              }
+              self.src = URL.createObjectURL(stream);
+            });
+          }
+        });
+      }
+    }
+  },
+
+  shimAddTrackRemoveTrack: function(window) {
+    var browserDetails = utils.detectBrowser(window);
+    // shim addTrack and removeTrack.
+    if (window.RTCPeerConnection.prototype.addTrack &&
+        browserDetails.version >= 63) {
+      return;
+    }
+
+    // also shim pc.getLocalStreams when addTrack is shimmed
+    // to return the original streams.
+    var origGetLocalStreams = window.RTCPeerConnection.prototype
+        .getLocalStreams;
+    window.RTCPeerConnection.prototype.getLocalStreams = function() {
+      var self = this;
+      var nativeStreams = origGetLocalStreams.apply(this);
+      self._reverseStreams = self._reverseStreams || {};
+      return nativeStreams.map(function(stream) {
+        return self._reverseStreams[stream.id];
+      });
+    };
+
+    var origAddStream = window.RTCPeerConnection.prototype.addStream;
+    window.RTCPeerConnection.prototype.addStream = function(stream) {
+      var pc = this;
+      pc._streams = pc._streams || {};
+      pc._reverseStreams = pc._reverseStreams || {};
+
+      stream.getTracks().forEach(function(track) {
+        var alreadyExists = pc.getSenders().find(function(s) {
+          return s.track === track;
+        });
+        if (alreadyExists) {
+          throw new DOMException('Track already exists.',
+              'InvalidAccessError');
+        }
+      });
+      // Add identity mapping for consistency with addTrack.
+      // Unless this is being used with a stream from addTrack.
+      if (!pc._reverseStreams[stream.id]) {
+        var newStream = new window.MediaStream(stream.getTracks());
+        pc._streams[stream.id] = newStream;
+        pc._reverseStreams[newStream.id] = stream;
+        stream = newStream;
+      }
+      origAddStream.apply(pc, [stream]);
+    };
+
+    var origRemoveStream = window.RTCPeerConnection.prototype.removeStream;
+    window.RTCPeerConnection.prototype.removeStream = function(stream) {
+      var pc = this;
+      pc._streams = pc._streams || {};
+      pc._reverseStreams = pc._reverseStreams || {};
+
+      origRemoveStream.apply(pc, [(pc._streams[stream.id] || stream)]);
+      delete pc._reverseStreams[(pc._streams[stream.id] ?
+          pc._streams[stream.id].id : stream.id)];
+      delete pc._streams[stream.id];
+    };
+
+    window.RTCPeerConnection.prototype.addTrack = function(track, stream) {
+      var pc = this;
+      if (pc.signalingState === 'closed') {
+        throw new DOMException(
+          'The RTCPeerConnection\'s signalingState is \'closed\'.',
+          'InvalidStateError');
+      }
+      var streams = [].slice.call(arguments, 1);
+      if (streams.length !== 1 ||
+          !streams[0].getTracks().find(function(t) {
+            return t === track;
+          })) {
+        // this is not fully correct but all we can manage without
+        // [[associated MediaStreams]] internal slot.
+        throw new DOMException(
+          'The adapter.js addTrack polyfill only supports a single ' +
+          ' stream which is associated with the specified track.',
+          'NotSupportedError');
+      }
+
+      var alreadyExists = pc.getSenders().find(function(s) {
+        return s.track === track;
+      });
+      if (alreadyExists) {
+        throw new DOMException('Track already exists.',
+            'InvalidAccessError');
+      }
+
+      pc._streams = pc._streams || {};
+      pc._reverseStreams = pc._reverseStreams || {};
+      var oldStream = pc._streams[stream.id];
+      if (oldStream) {
+        // this is using odd Chrome behaviour, use with caution:
+        // https://bugs.chromium.org/p/webrtc/issues/detail?id=7815
+        // Note: we rely on the high-level addTrack/dtmf shim to
+        // create the sender with a dtmf sender.
+        oldStream.addTrack(track);
+
+        // Trigger ONN async.
+        Promise.resolve().then(function() {
+          pc.dispatchEvent(new Event('negotiationneeded'));
+        });
+      } else {
+        var newStream = new window.MediaStream([track]);
+        pc._streams[stream.id] = newStream;
+        pc._reverseStreams[newStream.id] = stream;
+        pc.addStream(newStream);
+      }
+      return pc.getSenders().find(function(s) {
+        return s.track === track;
+      });
+    };
+
+    // replace the internal stream id with the external one and
+    // vice versa.
+    function replaceInternalStreamId(pc, description) {
+      var sdp = description.sdp;
+      Object.keys(pc._reverseStreams || []).forEach(function(internalId) {
+        var externalStream = pc._reverseStreams[internalId];
+        var internalStream = pc._streams[externalStream.id];
+        sdp = sdp.replace(new RegExp(internalStream.id, 'g'),
+            externalStream.id);
+      });
+      return new RTCSessionDescription({
+        type: description.type,
+        sdp: sdp
+      });
+    }
+    function replaceExternalStreamId(pc, description) {
+      var sdp = description.sdp;
+      Object.keys(pc._reverseStreams || []).forEach(function(internalId) {
+        var externalStream = pc._reverseStreams[internalId];
+        var internalStream = pc._streams[externalStream.id];
+        sdp = sdp.replace(new RegExp(externalStream.id, 'g'),
+            internalStream.id);
+      });
+      return new RTCSessionDescription({
+        type: description.type,
+        sdp: sdp
+      });
+    }
+    ['createOffer', 'createAnswer'].forEach(function(method) {
+      var nativeMethod = window.RTCPeerConnection.prototype[method];
+      window.RTCPeerConnection.prototype[method] = function() {
+        var pc = this;
+        var args = arguments;
+        var isLegacyCall = arguments.length &&
+            typeof arguments[0] === 'function';
+        if (isLegacyCall) {
+          return nativeMethod.apply(pc, [
+            function(description) {
+              var desc = replaceInternalStreamId(pc, description);
+              args[0].apply(null, [desc]);
+            },
+            function(err) {
+              if (args[1]) {
+                args[1].apply(null, err);
+              }
+            }, arguments[2]
+          ]);
+        }
+        return nativeMethod.apply(pc, arguments)
+        .then(function(description) {
+          return replaceInternalStreamId(pc, description);
+        });
+      };
+    });
+
+    var origSetLocalDescription =
+        window.RTCPeerConnection.prototype.setLocalDescription;
+    window.RTCPeerConnection.prototype.setLocalDescription = function() {
+      var pc = this;
+      if (!arguments.length || !arguments[0].type) {
+        return origSetLocalDescription.apply(pc, arguments);
+      }
+      arguments[0] = replaceExternalStreamId(pc, arguments[0]);
+      return origSetLocalDescription.apply(pc, arguments);
+    };
+
+    // TODO: mangle getStats: https://w3c.github.io/webrtc-stats/#dom-rtcmediastreamstats-streamidentifier
+
+    var origLocalDescription = Object.getOwnPropertyDescriptor(
+        window.RTCPeerConnection.prototype, 'localDescription');
+    Object.defineProperty(window.RTCPeerConnection.prototype,
+        'localDescription', {
+          get: function() {
+            var pc = this;
+            var description = origLocalDescription.get.apply(this);
+            if (description.type === '') {
+              return description;
+            }
+            return replaceInternalStreamId(pc, description);
+          }
+        });
+
+    window.RTCPeerConnection.prototype.removeTrack = function(sender) {
+      var pc = this;
+      if (pc.signalingState === 'closed') {
+        throw new DOMException(
+          'The RTCPeerConnection\'s signalingState is \'closed\'.',
+          'InvalidStateError');
+      }
+      // We can not yet check for sender instanceof RTCRtpSender
+      // since we shim RTPSender. So we check if sender._pc is set.
+      if (!sender._pc) {
+        throw new DOMException('Argument 1 of RTCPeerConnection.removeTrack ' +
+            'does not implement interface RTCRtpSender.', 'TypeError');
+      }
+      var isLocal = sender._pc === pc;
+      if (!isLocal) {
+        throw new DOMException('Sender was not created by this connection.',
+            'InvalidAccessError');
+      }
+
+      // Search for the native stream the senders track belongs to.
+      pc._streams = pc._streams || {};
+      var stream;
+      Object.keys(pc._streams).forEach(function(streamid) {
+        var hasTrack = pc._streams[streamid].getTracks().find(function(track) {
+          return sender.track === track;
+        });
+        if (hasTrack) {
+          stream = pc._streams[streamid];
+        }
+      });
+
+      if (stream) {
+        if (stream.getTracks().length === 1) {
+          // if this is the last track of the stream, remove the stream. This
+          // takes care of any shimmed _senders.
+          pc.removeStream(pc._reverseStreams[stream.id]);
+        } else {
+          // relying on the same odd chrome behaviour as above.
+          stream.removeTrack(sender.track);
+        }
+        pc.dispatchEvent(new Event('negotiationneeded'));
+      }
+    };
+  },
+
+  shimPeerConnection: function(window) {
+    var browserDetails = utils.detectBrowser(window);
+
+    // The RTCPeerConnection object.
+    if (!window.RTCPeerConnection) {
+      window.RTCPeerConnection = function(pcConfig, pcConstraints) {
+        // Translate iceTransportPolicy to iceTransports,
+        // see https://code.google.com/p/webrtc/issues/detail?id=4869
+        // this was fixed in M56 along with unprefixing RTCPeerConnection.
+        logging('PeerConnection');
+        if (pcConfig && pcConfig.iceTransportPolicy) {
+          pcConfig.iceTransports = pcConfig.iceTransportPolicy;
+        }
+
+        return new window.webkitRTCPeerConnection(pcConfig, pcConstraints);
+      };
+      window.RTCPeerConnection.prototype =
+          window.webkitRTCPeerConnection.prototype;
+      // wrap static methods. Currently just generateCertificate.
+      if (window.webkitRTCPeerConnection.generateCertificate) {
+        Object.defineProperty(window.RTCPeerConnection, 'generateCertificate', {
+          get: function() {
+            return window.webkitRTCPeerConnection.generateCertificate;
+          }
+        });
+      }
+    } else {
+      // migrate from non-spec RTCIceServer.url to RTCIceServer.urls
+      var OrigPeerConnection = window.RTCPeerConnection;
+      window.RTCPeerConnection = function(pcConfig, pcConstraints) {
+        if (pcConfig && pcConfig.iceServers) {
+          var newIceServers = [];
+          for (var i = 0; i < pcConfig.iceServers.length; i++) {
+            var server = pcConfig.iceServers[i];
+            if (!server.hasOwnProperty('urls') &&
+                server.hasOwnProperty('url')) {
+              utils.deprecated('RTCIceServer.url', 'RTCIceServer.urls');
+              server = JSON.parse(JSON.stringify(server));
+              server.urls = server.url;
+              newIceServers.push(server);
+            } else {
+              newIceServers.push(pcConfig.iceServers[i]);
+            }
+          }
+          pcConfig.iceServers = newIceServers;
+        }
+        return new OrigPeerConnection(pcConfig, pcConstraints);
+      };
+      window.RTCPeerConnection.prototype = OrigPeerConnection.prototype;
+      // wrap static methods. Currently just generateCertificate.
+      Object.defineProperty(window.RTCPeerConnection, 'generateCertificate', {
+        get: function() {
+          return OrigPeerConnection.generateCertificate;
+        }
+      });
+    }
+
+    var origGetStats = window.RTCPeerConnection.prototype.getStats;
+    window.RTCPeerConnection.prototype.getStats = function(selector,
+        successCallback, errorCallback) {
+      var self = this;
+      var args = arguments;
+
+      // If selector is a function then we are in the old style stats so just
+      // pass back the original getStats format to avoid breaking old users.
+      if (arguments.length > 0 && typeof selector === 'function') {
+        return origGetStats.apply(this, arguments);
+      }
+
+      // When spec-style getStats is supported, return those when called with
+      // either no arguments or the selector argument is null.
+      if (origGetStats.length === 0 && (arguments.length === 0 ||
+          typeof arguments[0] !== 'function')) {
+        return origGetStats.apply(this, []);
+      }
+
+      var fixChromeStats_ = function(response) {
+        var standardReport = {};
+        var reports = response.result();
+        reports.forEach(function(report) {
+          var standardStats = {
+            id: report.id,
+            timestamp: report.timestamp,
+            type: {
+              localcandidate: 'local-candidate',
+              remotecandidate: 'remote-candidate'
+            }[report.type] || report.type
+          };
+          report.names().forEach(function(name) {
+            standardStats[name] = report.stat(name);
+          });
+          standardReport[standardStats.id] = standardStats;
+        });
+
+        return standardReport;
+      };
+
+      // shim getStats with maplike support
+      var makeMapStats = function(stats) {
+        return new Map(Object.keys(stats).map(function(key) {
+          return [key, stats[key]];
+        }));
+      };
+
+      if (arguments.length >= 2) {
+        var successCallbackWrapper_ = function(response) {
+          args[1](makeMapStats(fixChromeStats_(response)));
+        };
+
+        return origGetStats.apply(this, [successCallbackWrapper_,
+          arguments[0]]);
+      }
+
+      // promise-support
+      return new Promise(function(resolve, reject) {
+        origGetStats.apply(self, [
+          function(response) {
+            resolve(makeMapStats(fixChromeStats_(response)));
+          }, reject]);
+      }).then(successCallback, errorCallback);
+    };
+
+    // add promise support -- natively available in Chrome 51
+    if (browserDetails.version < 51) {
+      ['setLocalDescription', 'setRemoteDescription', 'addIceCandidate']
+          .forEach(function(method) {
+            var nativeMethod = window.RTCPeerConnection.prototype[method];
+            window.RTCPeerConnection.prototype[method] = function() {
+              var args = arguments;
+              var self = this;
+              var promise = new Promise(function(resolve, reject) {
+                nativeMethod.apply(self, [args[0], resolve, reject]);
+              });
+              if (args.length < 2) {
+                return promise;
+              }
+              return promise.then(function() {
+                args[1].apply(null, []);
+              },
+              function(err) {
+                if (args.length >= 3) {
+                  args[2].apply(null, [err]);
+                }
+              });
+            };
+          });
+    }
+
+    // promise support for createOffer and createAnswer. Available (without
+    // bugs) since M52: crbug/619289
+    if (browserDetails.version < 52) {
+      ['createOffer', 'createAnswer'].forEach(function(method) {
+        var nativeMethod = window.RTCPeerConnection.prototype[method];
+        window.RTCPeerConnection.prototype[method] = function() {
+          var self = this;
+          if (arguments.length < 1 || (arguments.length === 1 &&
+              typeof arguments[0] === 'object')) {
+            var opts = arguments.length === 1 ? arguments[0] : undefined;
+            return new Promise(function(resolve, reject) {
+              nativeMethod.apply(self, [resolve, reject, opts]);
+            });
+          }
+          return nativeMethod.apply(this, arguments);
+        };
+      });
+    }
+
+    // shim implicit creation of RTCSessionDescription/RTCIceCandidate
+    ['setLocalDescription', 'setRemoteDescription', 'addIceCandidate']
+        .forEach(function(method) {
+          var nativeMethod = window.RTCPeerConnection.prototype[method];
+          window.RTCPeerConnection.prototype[method] = function() {
+            arguments[0] = new ((method === 'addIceCandidate') ?
+                window.RTCIceCandidate :
+                window.RTCSessionDescription)(arguments[0]);
+            return nativeMethod.apply(this, arguments);
+          };
+        });
+
+    // support for addIceCandidate(null or undefined)
+    var nativeAddIceCandidate =
+        window.RTCPeerConnection.prototype.addIceCandidate;
+    window.RTCPeerConnection.prototype.addIceCandidate = function() {
+      if (!arguments[0]) {
+        if (arguments[1]) {
+          arguments[1].apply(null);
+        }
+        return Promise.resolve();
+      }
+      return nativeAddIceCandidate.apply(this, arguments);
+    };
+  }
+};
+
+
+// Expose public methods.
+module.exports = {
+  shimMediaStream: chromeShim.shimMediaStream,
+  shimOnTrack: chromeShim.shimOnTrack,
+  shimAddTrackRemoveTrack: chromeShim.shimAddTrackRemoveTrack,
+  shimGetSendersWithDtmf: chromeShim.shimGetSendersWithDtmf,
+  shimSourceObject: chromeShim.shimSourceObject,
+  shimPeerConnection: chromeShim.shimPeerConnection,
+  shimGetUserMedia: require('./getusermedia')
+};
+
+},{"../utils.js":13,"./getusermedia":6}],6:[function(require,module,exports){
+/*
+ *  Copyright (c) 2016 The WebRTC project authors. All Rights Reserved.
+ *
+ *  Use of this source code is governed by a BSD-style license
+ *  that can be found in the LICENSE file in the root of the source
+ *  tree.
+ */
+ /* eslint-env node */
+'use strict';
+var utils = require('../utils.js');
+var logging = utils.log;
+
+// Expose public methods.
+module.exports = function(window) {
+  var browserDetails = utils.detectBrowser(window);
+  var navigator = window && window.navigator;
+
+  var constraintsToChrome_ = function(c) {
+    if (typeof c !== 'object' || c.mandatory || c.optional) {
+      return c;
+    }
+    var cc = {};
+    Object.keys(c).forEach(function(key) {
+      if (key === 'require' || key === 'advanced' || key === 'mediaSource') {
+        return;
+      }
+      var r = (typeof c[key] === 'object') ? c[key] : {ideal: c[key]};
+      if (r.exact !== undefined && typeof r.exact === 'number') {
+        r.min = r.max = r.exact;
+      }
+      var oldname_ = function(prefix, name) {
+        if (prefix) {
+          return prefix + name.charAt(0).toUpperCase() + name.slice(1);
+        }
+        return (name === 'deviceId') ? 'sourceId' : name;
+      };
+      if (r.ideal !== undefined) {
+        cc.optional = cc.optional || [];
+        var oc = {};
+        if (typeof r.ideal === 'number') {
+          oc[oldname_('min', key)] = r.ideal;
+          cc.optional.push(oc);
+          oc = {};
+          oc[oldname_('max', key)] = r.ideal;
+          cc.optional.push(oc);
+        } else {
+          oc[oldname_('', key)] = r.ideal;
+          cc.optional.push(oc);
+        }
+      }
+      if (r.exact !== undefined && typeof r.exact !== 'number') {
+        cc.mandatory = cc.mandatory || {};
+        cc.mandatory[oldname_('', key)] = r.exact;
+      } else {
+        ['min', 'max'].forEach(function(mix) {
+          if (r[mix] !== undefined) {
+            cc.mandatory = cc.mandatory || {};
+            cc.mandatory[oldname_(mix, key)] = r[mix];
+          }
+        });
+      }
+    });
+    if (c.advanced) {
+      cc.optional = (cc.optional || []).concat(c.advanced);
+    }
+    return cc;
+  };
+
+  var shimConstraints_ = function(constraints, func) {
+    constraints = JSON.parse(JSON.stringify(constraints));
+    if (constraints && typeof constraints.audio === 'object') {
+      var remap = function(obj, a, b) {
+        if (a in obj && !(b in obj)) {
+          obj[b] = obj[a];
+          delete obj[a];
+        }
+      };
+      constraints = JSON.parse(JSON.stringify(constraints));
+      remap(constraints.audio, 'autoGainControl', 'googAutoGainControl');
+      remap(constraints.audio, 'noiseSuppression', 'googNoiseSuppression');
+      constraints.audio = constraintsToChrome_(constraints.audio);
+    }
+    if (constraints && typeof constraints.video === 'object') {
+      // Shim facingMode for mobile & surface pro.
+      var face = constraints.video.facingMode;
+      face = face && ((typeof face === 'object') ? face : {ideal: face});
+      var getSupportedFacingModeLies = browserDetails.version < 66;
+
+      if ((face && (face.exact === 'user' || face.exact === 'environment' ||
+                    face.ideal === 'user' || face.ideal === 'environment')) &&
+          !(navigator.mediaDevices.getSupportedConstraints &&
+            navigator.mediaDevices.getSupportedConstraints().facingMode &&
+            !getSupportedFacingModeLies)) {
+        delete constraints.video.facingMode;
+        var matches;
+        if (face.exact === 'environment' || face.ideal === 'environment') {
+          matches = ['back', 'rear'];
+        } else if (face.exact === 'user' || face.ideal === 'user') {
+          matches = ['front'];
+        }
+        if (matches) {
+          // Look for matches in label, or use last cam for back (typical).
+          return navigator.mediaDevices.enumerateDevices()
+          .then(function(devices) {
+            devices = devices.filter(function(d) {
+              return d.kind === 'videoinput';
+            });
+            var dev = devices.find(function(d) {
+              return matches.some(function(match) {
+                return d.label.toLowerCase().indexOf(match) !== -1;
+              });
+            });
+            if (!dev && devices.length && matches.indexOf('back') !== -1) {
+              dev = devices[devices.length - 1]; // more likely the back cam
+            }
+            if (dev) {
+              constraints.video.deviceId = face.exact ? {exact: dev.deviceId} :
+                                                        {ideal: dev.deviceId};
+            }
+            constraints.video = constraintsToChrome_(constraints.video);
+            logging('chrome: ' + JSON.stringify(constraints));
+            return func(constraints);
+          });
+        }
+      }
+      constraints.video = constraintsToChrome_(constraints.video);
+    }
+    logging('chrome: ' + JSON.stringify(constraints));
+    return func(constraints);
+  };
+
+  var shimError_ = function(e) {
+    return {
+      name: {
+        PermissionDeniedError: 'NotAllowedError',
+        InvalidStateError: 'NotReadableError',
+        DevicesNotFoundError: 'NotFoundError',
+        ConstraintNotSatisfiedError: 'OverconstrainedError',
+        TrackStartError: 'NotReadableError',
+        MediaDeviceFailedDueToShutdown: 'NotReadableError',
+        MediaDeviceKillSwitchOn: 'NotReadableError'
+      }[e.name] || e.name,
+      message: e.message,
+      constraint: e.constraintName,
+      toString: function() {
+        return this.name + (this.message && ': ') + this.message;
+      }
+    };
+  };
+
+  var getUserMedia_ = function(constraints, onSuccess, onError) {
+    shimConstraints_(constraints, function(c) {
+      navigator.webkitGetUserMedia(c, onSuccess, function(e) {
+        if (onError) {
+          onError(shimError_(e));
+        }
+      });
+    });
+  };
+
+  navigator.getUserMedia = getUserMedia_;
+
+  // Returns the result of getUserMedia as a Promise.
+  var getUserMediaPromise_ = function(constraints) {
+    return new Promise(function(resolve, reject) {
+      navigator.getUserMedia(constraints, resolve, reject);
+    });
+  };
+
+  if (!navigator.mediaDevices) {
+    navigator.mediaDevices = {
+      getUserMedia: getUserMediaPromise_,
+      enumerateDevices: function() {
+        return new Promise(function(resolve) {
+          var kinds = {audio: 'audioinput', video: 'videoinput'};
+          return window.MediaStreamTrack.getSources(function(devices) {
+            resolve(devices.map(function(device) {
+              return {label: device.label,
+                kind: kinds[device.kind],
+                deviceId: device.id,
+                groupId: ''};
+            }));
+          });
+        });
+      },
+      getSupportedConstraints: function() {
+        return {
+          deviceId: true, echoCancellation: true, facingMode: true,
+          frameRate: true, height: true, width: true
+        };
+      }
+    };
+  }
+
+  // A shim for getUserMedia method on the mediaDevices object.
+  // TODO(KaptenJansson) remove once implemented in Chrome stable.
+  if (!navigator.mediaDevices.getUserMedia) {
+    navigator.mediaDevices.getUserMedia = function(constraints) {
+      return getUserMediaPromise_(constraints);
+    };
+  } else {
+    // Even though Chrome 45 has navigator.mediaDevices and a getUserMedia
+    // function which returns a Promise, it does not accept spec-style
+    // constraints.
+    var origGetUserMedia = navigator.mediaDevices.getUserMedia.
+        bind(navigator.mediaDevices);
+    navigator.mediaDevices.getUserMedia = function(cs) {
+      return shimConstraints_(cs, function(c) {
+        return origGetUserMedia(c).then(function(stream) {
+          if (c.audio && !stream.getAudioTracks().length ||
+              c.video && !stream.getVideoTracks().length) {
+            stream.getTracks().forEach(function(track) {
+              track.stop();
+            });
+            throw new DOMException('', 'NotFoundError');
+          }
+          return stream;
+        }, function(e) {
+          return Promise.reject(shimError_(e));
+        });
+      });
+    };
+  }
+
+  // Dummy devicechange event methods.
+  // TODO(KaptenJansson) remove once implemented in Chrome stable.
+  if (typeof navigator.mediaDevices.addEventListener === 'undefined') {
+    navigator.mediaDevices.addEventListener = function() {
+      logging('Dummy mediaDevices.addEventListener called.');
+    };
+  }
+  if (typeof navigator.mediaDevices.removeEventListener === 'undefined') {
+    navigator.mediaDevices.removeEventListener = function() {
+      logging('Dummy mediaDevices.removeEventListener called.');
+    };
+  }
+};
+
+},{"../utils.js":13}],7:[function(require,module,exports){
+/*
+ *  Copyright (c) 2017 The WebRTC project authors. All Rights Reserved.
+ *
+ *  Use of this source code is governed by a BSD-style license
+ *  that can be found in the LICENSE file in the root of the source
+ *  tree.
+ */
+ /* eslint-env node */
+'use strict';
+
+var SDPUtils = require('sdp');
+var utils = require('./utils');
+
+// Wraps the peerconnection event eventNameToWrap in a function
+// which returns the modified event object.
+function wrapPeerConnectionEvent(window, eventNameToWrap, wrapper) {
+  if (!window.RTCPeerConnection) {
+    return;
+  }
+  var proto = window.RTCPeerConnection.prototype;
+  var nativeAddEventListener = proto.addEventListener;
+  proto.addEventListener = function(nativeEventName, cb) {
+    if (nativeEventName !== eventNameToWrap) {
+      return nativeAddEventListener.apply(this, arguments);
+    }
+    var wrappedCallback = function(e) {
+      cb(wrapper(e));
+    };
+    this._eventMap = this._eventMap || {};
+    this._eventMap[cb] = wrappedCallback;
+    return nativeAddEventListener.apply(this, [nativeEventName,
+      wrappedCallback]);
+  };
+
+  var nativeRemoveEventListener = proto.removeEventListener;
+  proto.removeEventListener = function(nativeEventName, cb) {
+    if (nativeEventName !== eventNameToWrap || !this._eventMap
+        || !this._eventMap[cb]) {
+      return nativeRemoveEventListener.apply(this, arguments);
+    }
+    var unwrappedCb = this._eventMap[cb];
+    delete this._eventMap[cb];
+    return nativeRemoveEventListener.apply(this, [nativeEventName,
+      unwrappedCb]);
+  };
+
+  Object.defineProperty(proto, 'on' + eventNameToWrap, {
+    get: function() {
+      return this['_on' + eventNameToWrap];
+    },
+    set: function(cb) {
+      if (this['_on' + eventNameToWrap]) {
+        this.removeEventListener(eventNameToWrap,
+            this['_on' + eventNameToWrap]);
+        delete this['_on' + eventNameToWrap];
+      }
+      if (cb) {
+        this.addEventListener(eventNameToWrap,
+            this['_on' + eventNameToWrap] = cb);
+      }
+    }
+  });
+}
+
+module.exports = {
+  shimRTCIceCandidate: function(window) {
+    // foundation is arbitrarily chosen as an indicator for full support for
+    // https://w3c.github.io/webrtc-pc/#rtcicecandidate-interface
+    if (window.RTCIceCandidate && 'foundation' in
+        window.RTCIceCandidate.prototype) {
+      return;
+    }
+
+    var NativeRTCIceCandidate = window.RTCIceCandidate;
+    window.RTCIceCandidate = function(args) {
+      // Remove the a= which shouldn't be part of the candidate string.
+      if (typeof args === 'object' && args.candidate &&
+          args.candidate.indexOf('a=') === 0) {
+        args = JSON.parse(JSON.stringify(args));
+        args.candidate = args.candidate.substr(2);
+      }
+
+      // Augment the native candidate with the parsed fields.
+      var nativeCandidate = new NativeRTCIceCandidate(args);
+      var parsedCandidate = SDPUtils.parseCandidate(args.candidate);
+      var augmentedCandidate = Object.assign(nativeCandidate,
+          parsedCandidate);
+
+      // Add a serializer that does not serialize the extra attributes.
+      augmentedCandidate.toJSON = function() {
+        return {
+          candidate: augmentedCandidate.candidate,
+          sdpMid: augmentedCandidate.sdpMid,
+          sdpMLineIndex: augmentedCandidate.sdpMLineIndex,
+          usernameFragment: augmentedCandidate.usernameFragment,
+        };
+      };
+      return augmentedCandidate;
+    };
+
+    // Hook up the augmented candidate in onicecandidate and
+    // addEventListener('icecandidate', ...)
+    wrapPeerConnectionEvent(window, 'icecandidate', function(e) {
+      if (e.candidate) {
+        Object.defineProperty(e, 'candidate', {
+          value: new window.RTCIceCandidate(e.candidate),
+          writable: 'false'
+        });
+      }
+      return e;
+    });
+  },
+
+  // shimCreateObjectURL must be called before shimSourceObject to avoid loop.
+
+  shimCreateObjectURL: function(window) {
+    var URL = window && window.URL;
+
+    if (!(typeof window === 'object' && window.HTMLMediaElement &&
+          'srcObject' in window.HTMLMediaElement.prototype &&
+        URL.createObjectURL && URL.revokeObjectURL)) {
+      // Only shim CreateObjectURL using srcObject if srcObject exists.
+      return undefined;
+    }
+
+    var nativeCreateObjectURL = URL.createObjectURL.bind(URL);
+    var nativeRevokeObjectURL = URL.revokeObjectURL.bind(URL);
+    var streams = new Map(), newId = 0;
+
+    URL.createObjectURL = function(stream) {
+      if ('getTracks' in stream) {
+        var url = 'polyblob:' + (++newId);
+        streams.set(url, stream);
+        utils.deprecated('URL.createObjectURL(stream)',
+            'elem.srcObject = stream');
+        return url;
+      }
+      return nativeCreateObjectURL(stream);
+    };
+    URL.revokeObjectURL = function(url) {
+      nativeRevokeObjectURL(url);
+      streams.delete(url);
+    };
+
+    var dsc = Object.getOwnPropertyDescriptor(window.HTMLMediaElement.prototype,
+                                              'src');
+    Object.defineProperty(window.HTMLMediaElement.prototype, 'src', {
+      get: function() {
+        return dsc.get.apply(this);
+      },
+      set: function(url) {
+        this.srcObject = streams.get(url) || null;
+        return dsc.set.apply(this, [url]);
+      }
+    });
+
+    var nativeSetAttribute = window.HTMLMediaElement.prototype.setAttribute;
+    window.HTMLMediaElement.prototype.setAttribute = function() {
+      if (arguments.length === 2 &&
+          ('' + arguments[0]).toLowerCase() === 'src') {
+        this.srcObject = streams.get(arguments[1]) || null;
+      }
+      return nativeSetAttribute.apply(this, arguments);
+    };
+  }
+};
+
+},{"./utils":13,"sdp":2}],8:[function(require,module,exports){
+/*
+ *  Copyright (c) 2016 The WebRTC project authors. All Rights Reserved.
+ *
+ *  Use of this source code is governed by a BSD-style license
+ *  that can be found in the LICENSE file in the root of the source
+ *  tree.
+ */
+ /* eslint-env node */
+'use strict';
+
+var utils = require('../utils');
+var shimRTCPeerConnection = require('rtcpeerconnection-shim');
+
+module.exports = {
+  shimGetUserMedia: require('./getusermedia'),
+  shimPeerConnection: function(window) {
+    var browserDetails = utils.detectBrowser(window);
+
+    if (window.RTCIceGatherer) {
+      // ORTC defines an RTCIceCandidate object but no constructor.
+      // Not implemented in Edge.
+      if (!window.RTCIceCandidate) {
+        window.RTCIceCandidate = function(args) {
+          return args;
+        };
+      }
+      // ORTC does not have a session description object but
+      // other browsers (i.e. Chrome) that will support both PC and ORTC
+      // in the future might have this defined already.
+      if (!window.RTCSessionDescription) {
+        window.RTCSessionDescription = function(args) {
+          return args;
+        };
+      }
+      // this adds an additional event listener to MediaStrackTrack that signals
+      // when a tracks enabled property was changed. Workaround for a bug in
+      // addStream, see below. No longer required in 15025+
+      if (browserDetails.version < 15025) {
+        var origMSTEnabled = Object.getOwnPropertyDescriptor(
+            window.MediaStreamTrack.prototype, 'enabled');
+        Object.defineProperty(window.MediaStreamTrack.prototype, 'enabled', {
+          set: function(value) {
+            origMSTEnabled.set.call(this, value);
+            var ev = new Event('enabled');
+            ev.enabled = value;
+            this.dispatchEvent(ev);
+          }
+        });
+      }
+    }
+
+    // ORTC defines the DTMF sender a bit different.
+    // https://github.com/w3c/ortc/issues/714
+    if (window.RTCRtpSender && !('dtmf' in window.RTCRtpSender.prototype)) {
+      Object.defineProperty(window.RTCRtpSender.prototype, 'dtmf', {
+        get: function() {
+          if (this._dtmf === undefined) {
+            if (this.track.kind === 'audio') {
+              this._dtmf = new window.RTCDtmfSender(this);
+            } else if (this.track.kind === 'video') {
+              this._dtmf = null;
+            }
+          }
+          return this._dtmf;
+        }
+      });
+    }
+
+    window.RTCPeerConnection =
+        shimRTCPeerConnection(window, browserDetails.version);
+  },
+  shimReplaceTrack: function(window) {
+    // ORTC has replaceTrack -- https://github.com/w3c/ortc/issues/614
+    if (window.RTCRtpSender &&
+        !('replaceTrack' in window.RTCRtpSender.prototype)) {
+      window.RTCRtpSender.prototype.replaceTrack =
+          window.RTCRtpSender.prototype.setTrack;
+    }
+  }
+};
+
+},{"../utils":13,"./getusermedia":9,"rtcpeerconnection-shim":1}],9:[function(require,module,exports){
+/*
+ *  Copyright (c) 2016 The WebRTC project authors. All Rights Reserved.
+ *
+ *  Use of this source code is governed by a BSD-style license
+ *  that can be found in the LICENSE file in the root of the source
+ *  tree.
+ */
+ /* eslint-env node */
+'use strict';
+
+// Expose public methods.
+module.exports = function(window) {
+  var navigator = window && window.navigator;
+
+  var shimError_ = function(e) {
+    return {
+      name: {PermissionDeniedError: 'NotAllowedError'}[e.name] || e.name,
+      message: e.message,
+      constraint: e.constraint,
+      toString: function() {
+        return this.name;
+      }
+    };
+  };
+
+  // getUserMedia error shim.
+  var origGetUserMedia = navigator.mediaDevices.getUserMedia.
+      bind(navigator.mediaDevices);
+  navigator.mediaDevices.getUserMedia = function(c) {
+    return origGetUserMedia(c).catch(function(e) {
+      return Promise.reject(shimError_(e));
+    });
+  };
+};
+
+},{}],10:[function(require,module,exports){
+/*
+ *  Copyright (c) 2016 The WebRTC project authors. All Rights Reserved.
+ *
+ *  Use of this source code is governed by a BSD-style license
+ *  that can be found in the LICENSE file in the root of the source
+ *  tree.
+ */
+ /* eslint-env node */
+'use strict';
+
+var utils = require('../utils');
+
+var firefoxShim = {
+  shimOnTrack: function(window) {
+    if (typeof window === 'object' && window.RTCPeerConnection && !('ontrack' in
+        window.RTCPeerConnection.prototype)) {
+      Object.defineProperty(window.RTCPeerConnection.prototype, 'ontrack', {
+        get: function() {
+          return this._ontrack;
+        },
+        set: function(f) {
+          if (this._ontrack) {
+            this.removeEventListener('track', this._ontrack);
+            this.removeEventListener('addstream', this._ontrackpoly);
+          }
+          this.addEventListener('track', this._ontrack = f);
+          this.addEventListener('addstream', this._ontrackpoly = function(e) {
+            e.stream.getTracks().forEach(function(track) {
+              var event = new Event('track');
+              event.track = track;
+              event.receiver = {track: track};
+              event.transceiver = {receiver: event.receiver};
+              event.streams = [e.stream];
+              this.dispatchEvent(event);
+            }.bind(this));
+          }.bind(this));
+        }
+      });
+    }
+    if (typeof window === 'object' && window.RTCTrackEvent &&
+        ('receiver' in window.RTCTrackEvent.prototype) &&
+        !('transceiver' in window.RTCTrackEvent.prototype)) {
+      Object.defineProperty(window.RTCTrackEvent.prototype, 'transceiver', {
+        get: function() {
+          return {receiver: this.receiver};
+        }
+      });
+    }
+  },
+
+  shimSourceObject: function(window) {
+    // Firefox has supported mozSrcObject since FF22, unprefixed in 42.
+    if (typeof window === 'object') {
+      if (window.HTMLMediaElement &&
+        !('srcObject' in window.HTMLMediaElement.prototype)) {
+        // Shim the srcObject property, once, when HTMLMediaElement is found.
+        Object.defineProperty(window.HTMLMediaElement.prototype, 'srcObject', {
+          get: function() {
+            return this.mozSrcObject;
+          },
+          set: function(stream) {
+            this.mozSrcObject = stream;
+          }
+        });
+      }
+    }
+  },
+
+  shimPeerConnection: function(window) {
+    var browserDetails = utils.detectBrowser(window);
+
+    if (typeof window !== 'object' || !(window.RTCPeerConnection ||
+        window.mozRTCPeerConnection)) {
+      return; // probably media.peerconnection.enabled=false in about:config
+    }
+    // The RTCPeerConnection object.
+    if (!window.RTCPeerConnection) {
+      window.RTCPeerConnection = function(pcConfig, pcConstraints) {
+        if (browserDetails.version < 38) {
+          // .urls is not supported in FF < 38.
+          // create RTCIceServers with a single url.
+          if (pcConfig && pcConfig.iceServers) {
+            var newIceServers = [];
+            for (var i = 0; i < pcConfig.iceServers.length; i++) {
+              var server = pcConfig.iceServers[i];
+              if (server.hasOwnProperty('urls')) {
+                for (var j = 0; j < server.urls.length; j++) {
+                  var newServer = {
+                    url: server.urls[j]
+                  };
+                  if (server.urls[j].indexOf('turn') === 0) {
+                    newServer.username = server.username;
+                    newServer.credential = server.credential;
+                  }
+                  newIceServers.push(newServer);
+                }
+              } else {
+                newIceServers.push(pcConfig.iceServers[i]);
+              }
+            }
+            pcConfig.iceServers = newIceServers;
+          }
+        }
+        return new window.mozRTCPeerConnection(pcConfig, pcConstraints);
+      };
+      window.RTCPeerConnection.prototype =
+          window.mozRTCPeerConnection.prototype;
+
+      // wrap static methods. Currently just generateCertificate.
+      if (window.mozRTCPeerConnection.generateCertificate) {
+        Object.defineProperty(window.RTCPeerConnection, 'generateCertificate', {
+          get: function() {
+            return window.mozRTCPeerConnection.generateCertificate;
+          }
+        });
+      }
+
+      window.RTCSessionDescription = window.mozRTCSessionDescription;
+      window.RTCIceCandidate = window.mozRTCIceCandidate;
+    }
+
+    // shim away need for obsolete RTCIceCandidate/RTCSessionDescription.
+    ['setLocalDescription', 'setRemoteDescription', 'addIceCandidate']
+        .forEach(function(method) {
+          var nativeMethod = window.RTCPeerConnection.prototype[method];
+          window.RTCPeerConnection.prototype[method] = function() {
+            arguments[0] = new ((method === 'addIceCandidate') ?
+                window.RTCIceCandidate :
+                window.RTCSessionDescription)(arguments[0]);
+            return nativeMethod.apply(this, arguments);
+          };
+        });
+
+    // support for addIceCandidate(null or undefined)
+    var nativeAddIceCandidate =
+        window.RTCPeerConnection.prototype.addIceCandidate;
+    window.RTCPeerConnection.prototype.addIceCandidate = function() {
+      if (!arguments[0]) {
+        if (arguments[1]) {
+          arguments[1].apply(null);
+        }
+        return Promise.resolve();
+      }
+      return nativeAddIceCandidate.apply(this, arguments);
+    };
+
+    // shim getStats with maplike support
+    var makeMapStats = function(stats) {
+      var map = new Map();
+      Object.keys(stats).forEach(function(key) {
+        map.set(key, stats[key]);
+        map[key] = stats[key];
+      });
+      return map;
+    };
+
+    var modernStatsTypes = {
+      inboundrtp: 'inbound-rtp',
+      outboundrtp: 'outbound-rtp',
+      candidatepair: 'candidate-pair',
+      localcandidate: 'local-candidate',
+      remotecandidate: 'remote-candidate'
+    };
+
+    var nativeGetStats = window.RTCPeerConnection.prototype.getStats;
+    window.RTCPeerConnection.prototype.getStats = function(
+      selector,
+      onSucc,
+      onErr
+    ) {
+      return nativeGetStats.apply(this, [selector || null])
+        .then(function(stats) {
+          if (browserDetails.version < 48) {
+            stats = makeMapStats(stats);
+          }
+          if (browserDetails.version < 53 && !onSucc) {
+            // Shim only promise getStats with spec-hyphens in type names
+            // Leave callback version alone; misc old uses of forEach before Map
+            try {
+              stats.forEach(function(stat) {
+                stat.type = modernStatsTypes[stat.type] || stat.type;
+              });
+            } catch (e) {
+              if (e.name !== 'TypeError') {
+                throw e;
+              }
+              // Avoid TypeError: "type" is read-only, in old versions. 34-43ish
+              stats.forEach(function(stat, i) {
+                stats.set(i, Object.assign({}, stat, {
+                  type: modernStatsTypes[stat.type] || stat.type
+                }));
+              });
+            }
+          }
+          return stats;
+        })
+        .then(onSucc, onErr);
+    };
+  }
+};
+
+// Expose public methods.
+module.exports = {
+  shimOnTrack: firefoxShim.shimOnTrack,
+  shimSourceObject: firefoxShim.shimSourceObject,
+  shimPeerConnection: firefoxShim.shimPeerConnection,
+  shimGetUserMedia: require('./getusermedia')
+};
+
+},{"../utils":13,"./getusermedia":11}],11:[function(require,module,exports){
+/*
+ *  Copyright (c) 2016 The WebRTC project authors. All Rights Reserved.
+ *
+ *  Use of this source code is governed by a BSD-style license
+ *  that can be found in the LICENSE file in the root of the source
+ *  tree.
+ */
+ /* eslint-env node */
+'use strict';
+
+var utils = require('../utils');
+var logging = utils.log;
+
+// Expose public methods.
+module.exports = function(window) {
+  var browserDetails = utils.detectBrowser(window);
+  var navigator = window && window.navigator;
+  var MediaStreamTrack = window && window.MediaStreamTrack;
+
+  var shimError_ = function(e) {
+    return {
+      name: {
+        InternalError: 'NotReadableError',
+        NotSupportedError: 'TypeError',
+        PermissionDeniedError: 'NotAllowedError',
+        SecurityError: 'NotAllowedError'
+      }[e.name] || e.name,
+      message: {
+        'The operation is insecure.': 'The request is not allowed by the ' +
+        'user agent or the platform in the current context.'
+      }[e.message] || e.message,
+      constraint: e.constraint,
+      toString: function() {
+        return this.name + (this.message && ': ') + this.message;
+      }
+    };
+  };
+
+  // getUserMedia constraints shim.
+  var getUserMedia_ = function(constraints, onSuccess, onError) {
+    var constraintsToFF37_ = function(c) {
+      if (typeof c !== 'object' || c.require) {
+        return c;
+      }
+      var require = [];
+      Object.keys(c).forEach(function(key) {
+        if (key === 'require' || key === 'advanced' || key === 'mediaSource') {
+          return;
+        }
+        var r = c[key] = (typeof c[key] === 'object') ?
+            c[key] : {ideal: c[key]};
+        if (r.min !== undefined ||
+            r.max !== undefined || r.exact !== undefined) {
+          require.push(key);
+        }
+        if (r.exact !== undefined) {
+          if (typeof r.exact === 'number') {
+            r. min = r.max = r.exact;
+          } else {
+            c[key] = r.exact;
+          }
+          delete r.exact;
+        }
+        if (r.ideal !== undefined) {
+          c.advanced = c.advanced || [];
+          var oc = {};
+          if (typeof r.ideal === 'number') {
+            oc[key] = {min: r.ideal, max: r.ideal};
+          } else {
+            oc[key] = r.ideal;
+          }
+          c.advanced.push(oc);
+          delete r.ideal;
+          if (!Object.keys(r).length) {
+            delete c[key];
+          }
+        }
+      });
+      if (require.length) {
+        c.require = require;
+      }
+      return c;
+    };
+    constraints = JSON.parse(JSON.stringify(constraints));
+    if (browserDetails.version < 38) {
+      logging('spec: ' + JSON.stringify(constraints));
+      if (constraints.audio) {
+        constraints.audio = constraintsToFF37_(constraints.audio);
+      }
+      if (constraints.video) {
+        constraints.video = constraintsToFF37_(constraints.video);
+      }
+      logging('ff37: ' + JSON.stringify(constraints));
+    }
+    return navigator.mozGetUserMedia(constraints, onSuccess, function(e) {
+      onError(shimError_(e));
+    });
+  };
+
+  // Returns the result of getUserMedia as a Promise.
+  var getUserMediaPromise_ = function(constraints) {
+    return new Promise(function(resolve, reject) {
+      getUserMedia_(constraints, resolve, reject);
+    });
+  };
+
+  // Shim for mediaDevices on older versions.
+  if (!navigator.mediaDevices) {
+    navigator.mediaDevices = {getUserMedia: getUserMediaPromise_,
+      addEventListener: function() { },
+      removeEventListener: function() { }
+    };
+  }
+  navigator.mediaDevices.enumerateDevices =
+      navigator.mediaDevices.enumerateDevices || function() {
+        return new Promise(function(resolve) {
+          var infos = [
+            {kind: 'audioinput', deviceId: 'default', label: '', groupId: ''},
+            {kind: 'videoinput', deviceId: 'default', label: '', groupId: ''}
+          ];
+          resolve(infos);
+        });
+      };
+
+  if (browserDetails.version < 41) {
+    // Work around http://bugzil.la/1169665
+    var orgEnumerateDevices =
+        navigator.mediaDevices.enumerateDevices.bind(navigator.mediaDevices);
+    navigator.mediaDevices.enumerateDevices = function() {
+      return orgEnumerateDevices().then(undefined, function(e) {
+        if (e.name === 'NotFoundError') {
+          return [];
+        }
+        throw e;
+      });
+    };
+  }
+  if (browserDetails.version < 49) {
+    var origGetUserMedia = navigator.mediaDevices.getUserMedia.
+        bind(navigator.mediaDevices);
+    navigator.mediaDevices.getUserMedia = function(c) {
+      return origGetUserMedia(c).then(function(stream) {
+        // Work around https://bugzil.la/802326
+        if (c.audio && !stream.getAudioTracks().length ||
+            c.video && !stream.getVideoTracks().length) {
+          stream.getTracks().forEach(function(track) {
+            track.stop();
+          });
+          throw new DOMException('The object can not be found here.',
+                                 'NotFoundError');
+        }
+        return stream;
+      }, function(e) {
+        return Promise.reject(shimError_(e));
+      });
+    };
+  }
+  if (!(browserDetails.version > 55 &&
+      'autoGainControl' in navigator.mediaDevices.getSupportedConstraints())) {
+    var remap = function(obj, a, b) {
+      if (a in obj && !(b in obj)) {
+        obj[b] = obj[a];
+        delete obj[a];
+      }
+    };
+
+    var nativeGetUserMedia = navigator.mediaDevices.getUserMedia.
+        bind(navigator.mediaDevices);
+    navigator.mediaDevices.getUserMedia = function(c) {
+      if (typeof c === 'object' && typeof c.audio === 'object') {
+        c = JSON.parse(JSON.stringify(c));
+        remap(c.audio, 'autoGainControl', 'mozAutoGainControl');
+        remap(c.audio, 'noiseSuppression', 'mozNoiseSuppression');
+      }
+      return nativeGetUserMedia(c);
+    };
+
+    if (MediaStreamTrack && MediaStreamTrack.prototype.getSettings) {
+      var nativeGetSettings = MediaStreamTrack.prototype.getSettings;
+      MediaStreamTrack.prototype.getSettings = function() {
+        var obj = nativeGetSettings.apply(this, arguments);
+        remap(obj, 'mozAutoGainControl', 'autoGainControl');
+        remap(obj, 'mozNoiseSuppression', 'noiseSuppression');
+        return obj;
+      };
+    }
+
+    if (MediaStreamTrack && MediaStreamTrack.prototype.applyConstraints) {
+      var nativeApplyConstraints = MediaStreamTrack.prototype.applyConstraints;
+      MediaStreamTrack.prototype.applyConstraints = function(c) {
+        if (this.kind === 'audio' && typeof c === 'object') {
+          c = JSON.parse(JSON.stringify(c));
+          remap(c, 'autoGainControl', 'mozAutoGainControl');
+          remap(c, 'noiseSuppression', 'mozNoiseSuppression');
+        }
+        return nativeApplyConstraints.apply(this, [c]);
+      };
+    }
+  }
+  navigator.getUserMedia = function(constraints, onSuccess, onError) {
+    if (browserDetails.version < 44) {
+      return getUserMedia_(constraints, onSuccess, onError);
+    }
+    // Replace Firefox 44+'s deprecation warning with unprefixed version.
+    utils.deprecated('navigator.getUserMedia',
+        'navigator.mediaDevices.getUserMedia');
+    navigator.mediaDevices.getUserMedia(constraints).then(onSuccess, onError);
+  };
+};
+
+},{"../utils":13}],12:[function(require,module,exports){
+/*
+ *  Copyright (c) 2016 The WebRTC project authors. All Rights Reserved.
+ *
+ *  Use of this source code is governed by a BSD-style license
+ *  that can be found in the LICENSE file in the root of the source
+ *  tree.
+ */
+'use strict';
+var utils = require('../utils');
+
+var safariShim = {
+  // TODO: DrAlex, should be here, double check against LayoutTests
+
+  // TODO: once the back-end for the mac port is done, add.
+  // TODO: check for webkitGTK+
+  // shimPeerConnection: function() { },
+
+  shimLocalStreamsAPI: function(window) {
+    if (typeof window !== 'object' || !window.RTCPeerConnection) {
+      return;
+    }
+    if (!('getLocalStreams' in window.RTCPeerConnection.prototype)) {
+      window.RTCPeerConnection.prototype.getLocalStreams = function() {
+        if (!this._localStreams) {
+          this._localStreams = [];
+        }
+        return this._localStreams;
+      };
+    }
+    if (!('getStreamById' in window.RTCPeerConnection.prototype)) {
+      window.RTCPeerConnection.prototype.getStreamById = function(id) {
+        var result = null;
+        if (this._localStreams) {
+          this._localStreams.forEach(function(stream) {
+            if (stream.id === id) {
+              result = stream;
+            }
+          });
+        }
+        if (this._remoteStreams) {
+          this._remoteStreams.forEach(function(stream) {
+            if (stream.id === id) {
+              result = stream;
+            }
+          });
+        }
+        return result;
+      };
+    }
+    if (!('addStream' in window.RTCPeerConnection.prototype)) {
+      var _addTrack = window.RTCPeerConnection.prototype.addTrack;
+      window.RTCPeerConnection.prototype.addStream = function(stream) {
+        if (!this._localStreams) {
+          this._localStreams = [];
+        }
+        if (this._localStreams.indexOf(stream) === -1) {
+          this._localStreams.push(stream);
+        }
+        var self = this;
+        stream.getTracks().forEach(function(track) {
+          _addTrack.call(self, track, stream);
+        });
+      };
+
+      window.RTCPeerConnection.prototype.addTrack = function(track, stream) {
+        if (stream) {
+          if (!this._localStreams) {
+            this._localStreams = [stream];
+          } else if (this._localStreams.indexOf(stream) === -1) {
+            this._localStreams.push(stream);
+          }
+        }
+        _addTrack.call(this, track, stream);
+      };
+    }
+    if (!('removeStream' in window.RTCPeerConnection.prototype)) {
+      window.RTCPeerConnection.prototype.removeStream = function(stream) {
+        if (!this._localStreams) {
+          this._localStreams = [];
+        }
+        var index = this._localStreams.indexOf(stream);
+        if (index === -1) {
+          return;
+        }
+        this._localStreams.splice(index, 1);
+        var self = this;
+        var tracks = stream.getTracks();
+        this.getSenders().forEach(function(sender) {
+          if (tracks.indexOf(sender.track) !== -1) {
+            self.removeTrack(sender);
+          }
+        });
+      };
+    }
+  },
+  shimRemoteStreamsAPI: function(window) {
+    if (typeof window !== 'object' || !window.RTCPeerConnection) {
+      return;
+    }
+    if (!('getRemoteStreams' in window.RTCPeerConnection.prototype)) {
+      window.RTCPeerConnection.prototype.getRemoteStreams = function() {
+        return this._remoteStreams ? this._remoteStreams : [];
+      };
+    }
+    if (!('onaddstream' in window.RTCPeerConnection.prototype)) {
+      Object.defineProperty(window.RTCPeerConnection.prototype, 'onaddstream', {
+        get: function() {
+          return this._onaddstream;
+        },
+        set: function(f) {
+          if (this._onaddstream) {
+            this.removeEventListener('addstream', this._onaddstream);
+            this.removeEventListener('track', this._onaddstreampoly);
+          }
+          this.addEventListener('addstream', this._onaddstream = f);
+          this.addEventListener('track', this._onaddstreampoly = function(e) {
+            var stream = e.streams[0];
+            if (!this._remoteStreams) {
+              this._remoteStreams = [];
+            }
+            if (this._remoteStreams.indexOf(stream) >= 0) {
+              return;
+            }
+            this._remoteStreams.push(stream);
+            var event = new Event('addstream');
+            event.stream = e.streams[0];
+            this.dispatchEvent(event);
+          }.bind(this));
+        }
+      });
+    }
+  },
+  shimCallbacksAPI: function(window) {
+    if (typeof window !== 'object' || !window.RTCPeerConnection) {
+      return;
+    }
+    var prototype = window.RTCPeerConnection.prototype;
+    var createOffer = prototype.createOffer;
+    var createAnswer = prototype.createAnswer;
+    var setLocalDescription = prototype.setLocalDescription;
+    var setRemoteDescription = prototype.setRemoteDescription;
+    var addIceCandidate = prototype.addIceCandidate;
+
+    prototype.createOffer = function(successCallback, failureCallback) {
+      var options = (arguments.length >= 2) ? arguments[2] : arguments[0];
+      var promise = createOffer.apply(this, [options]);
+      if (!failureCallback) {
+        return promise;
+      }
+      promise.then(successCallback, failureCallback);
+      return Promise.resolve();
+    };
+
+    prototype.createAnswer = function(successCallback, failureCallback) {
+      var options = (arguments.length >= 2) ? arguments[2] : arguments[0];
+      var promise = createAnswer.apply(this, [options]);
+      if (!failureCallback) {
+        return promise;
+      }
+      promise.then(successCallback, failureCallback);
+      return Promise.resolve();
+    };
+
+    var withCallback = function(description, successCallback, failureCallback) {
+      var promise = setLocalDescription.apply(this, [description]);
+      if (!failureCallback) {
+        return promise;
+      }
+      promise.then(successCallback, failureCallback);
+      return Promise.resolve();
+    };
+    prototype.setLocalDescription = withCallback;
+
+    withCallback = function(description, successCallback, failureCallback) {
+      var promise = setRemoteDescription.apply(this, [description]);
+      if (!failureCallback) {
+        return promise;
+      }
+      promise.then(successCallback, failureCallback);
+      return Promise.resolve();
+    };
+    prototype.setRemoteDescription = withCallback;
+
+    withCallback = function(candidate, successCallback, failureCallback) {
+      var promise = addIceCandidate.apply(this, [candidate]);
+      if (!failureCallback) {
+        return promise;
+      }
+      promise.then(successCallback, failureCallback);
+      return Promise.resolve();
+    };
+    prototype.addIceCandidate = withCallback;
+  },
+  shimGetUserMedia: function(window) {
+    var navigator = window && window.navigator;
+
+    if (!navigator.getUserMedia) {
+      if (navigator.webkitGetUserMedia) {
+        navigator.getUserMedia = navigator.webkitGetUserMedia.bind(navigator);
+      } else if (navigator.mediaDevices &&
+          navigator.mediaDevices.getUserMedia) {
+        navigator.getUserMedia = function(constraints, cb, errcb) {
+          navigator.mediaDevices.getUserMedia(constraints)
+          .then(cb, errcb);
+        }.bind(navigator);
+      }
+    }
+  },
+  shimRTCIceServerUrls: function(window) {
+    // migrate from non-spec RTCIceServer.url to RTCIceServer.urls
+    var OrigPeerConnection = window.RTCPeerConnection;
+    window.RTCPeerConnection = function(pcConfig, pcConstraints) {
+      if (pcConfig && pcConfig.iceServers) {
+        var newIceServers = [];
+        for (var i = 0; i < pcConfig.iceServers.length; i++) {
+          var server = pcConfig.iceServers[i];
+          if (!server.hasOwnProperty('urls') &&
+              server.hasOwnProperty('url')) {
+            utils.deprecated('RTCIceServer.url', 'RTCIceServer.urls');
+            server = JSON.parse(JSON.stringify(server));
+            server.urls = server.url;
+            delete server.url;
+            newIceServers.push(server);
+          } else {
+            newIceServers.push(pcConfig.iceServers[i]);
+          }
+        }
+        pcConfig.iceServers = newIceServers;
+      }
+      return new OrigPeerConnection(pcConfig, pcConstraints);
+    };
+    window.RTCPeerConnection.prototype = OrigPeerConnection.prototype;
+    // wrap static methods. Currently just generateCertificate.
+    if ('generateCertificate' in window.RTCPeerConnection) {
+      Object.defineProperty(window.RTCPeerConnection, 'generateCertificate', {
+        get: function() {
+          return OrigPeerConnection.generateCertificate;
+        }
+      });
+    }
+  },
+  shimTrackEventTransceiver: function(window) {
+    // Add event.transceiver member over deprecated event.receiver
+    if (typeof window === 'object' && window.RTCPeerConnection &&
+        ('receiver' in window.RTCTrackEvent.prototype) &&
+        // can't check 'transceiver' in window.RTCTrackEvent.prototype, as it is
+        // defined for some reason even when window.RTCTransceiver is not.
+        !window.RTCTransceiver) {
+      Object.defineProperty(window.RTCTrackEvent.prototype, 'transceiver', {
+        get: function() {
+          return {receiver: this.receiver};
+        }
+      });
+    }
+  },
+
+  shimCreateOfferLegacy: function(window) {
+    var origCreateOffer = window.RTCPeerConnection.prototype.createOffer;
+    window.RTCPeerConnection.prototype.createOffer = function(offerOptions) {
+      var pc = this;
+      if (offerOptions) {
+        var audioTransceiver = pc.getTransceivers().find(function(transceiver) {
+          return transceiver.sender.track &&
+              transceiver.sender.track.kind === 'audio';
+        });
+        if (offerOptions.offerToReceiveAudio === false && audioTransceiver) {
+          if (audioTransceiver.direction === 'sendrecv') {
+            audioTransceiver.setDirection('sendonly');
+          } else if (audioTransceiver.direction === 'recvonly') {
+            audioTransceiver.setDirection('inactive');
+          }
+        } else if (offerOptions.offerToReceiveAudio === true &&
+            !audioTransceiver) {
+          pc.addTransceiver('audio');
+        }
+
+        var videoTransceiver = pc.getTransceivers().find(function(transceiver) {
+          return transceiver.sender.track &&
+              transceiver.sender.track.kind === 'video';
+        });
+        if (offerOptions.offerToReceiveVideo === false && videoTransceiver) {
+          if (videoTransceiver.direction === 'sendrecv') {
+            videoTransceiver.setDirection('sendonly');
+          } else if (videoTransceiver.direction === 'recvonly') {
+            videoTransceiver.setDirection('inactive');
+          }
+        } else if (offerOptions.offerToReceiveVideo === true &&
+            !videoTransceiver) {
+          pc.addTransceiver('video');
+        }
+      }
+      return origCreateOffer.apply(pc, arguments);
+    };
+  }
+};
+
+// Expose public methods.
+module.exports = {
+  shimCallbacksAPI: safariShim.shimCallbacksAPI,
+  shimLocalStreamsAPI: safariShim.shimLocalStreamsAPI,
+  shimRemoteStreamsAPI: safariShim.shimRemoteStreamsAPI,
+  shimGetUserMedia: safariShim.shimGetUserMedia,
+  shimRTCIceServerUrls: safariShim.shimRTCIceServerUrls,
+  shimTrackEventTransceiver: safariShim.shimTrackEventTransceiver,
+  shimCreateOfferLegacy: safariShim.shimCreateOfferLegacy
+  // TODO
+  // shimPeerConnection: safariShim.shimPeerConnection
+};
+
+},{"../utils":13}],13:[function(require,module,exports){
+/*
+ *  Copyright (c) 2016 The WebRTC project authors. All Rights Reserved.
+ *
+ *  Use of this source code is governed by a BSD-style license
+ *  that can be found in the LICENSE file in the root of the source
+ *  tree.
+ */
+ /* eslint-env node */
+'use strict';
+
+var logDisabled_ = true;
+var deprecationWarnings_ = true;
+
+// Utility methods.
+var utils = {
+  disableLog: function(bool) {
+    if (typeof bool !== 'boolean') {
+      return new Error('Argument type: ' + typeof bool +
+          '. Please use a boolean.');
+    }
+    logDisabled_ = bool;
+    return (bool) ? 'adapter.js logging disabled' :
+        'adapter.js logging enabled';
+  },
+
+  /**
+   * Disable or enable deprecation warnings
+   * @param {!boolean} bool set to true to disable warnings.
+   */
+  disableWarnings: function(bool) {
+    if (typeof bool !== 'boolean') {
+      return new Error('Argument type: ' + typeof bool +
+          '. Please use a boolean.');
+    }
+    deprecationWarnings_ = !bool;
+    return 'adapter.js deprecation warnings ' + (bool ? 'disabled' : 'enabled');
+  },
+
+  log: function() {
+    if (typeof window === 'object') {
+      if (logDisabled_) {
+        return;
+      }
+      if (typeof console !== 'undefined' && typeof console.log === 'function') {
+        console.log.apply(console, arguments);
+      }
+    }
+  },
+
+  /**
+   * Shows a deprecation warning suggesting the modern and spec-compatible API.
+   */
+  deprecated: function(oldMethod, newMethod) {
+    if (!deprecationWarnings_) {
+      return;
+    }
+    console.warn(oldMethod + ' is deprecated, please use ' + newMethod +
+        ' instead.');
+  },
+
+  /**
+   * Extract browser version out of the provided user agent string.
+   *
+   * @param {!string} uastring userAgent string.
+   * @param {!string} expr Regular expression used as match criteria.
+   * @param {!number} pos position in the version string to be returned.
+   * @return {!number} browser version.
+   */
+  extractVersion: function(uastring, expr, pos) {
+    var match = uastring.match(expr);
+    return match && match.length >= pos && parseInt(match[pos], 10);
+  },
+
+  /**
+   * Browser detector.
+   *
+   * @return {object} result containing browser and version
+   *     properties.
+   */
+  detectBrowser: function(window) {
+    var navigator = window && window.navigator;
+
+    // Returned result object.
+    var result = {};
+    result.browser = null;
+    result.version = null;
+
+    // Fail early if it's not a browser
+    if (typeof window === 'undefined' || !window.navigator) {
+      result.browser = 'Not a browser.';
+      return result;
+    }
+
+    // Firefox.
+    if (navigator.mozGetUserMedia) {
+      result.browser = 'firefox';
+      result.version = this.extractVersion(navigator.userAgent,
+          /Firefox\/(\d+)\./, 1);
+    } else if (navigator.webkitGetUserMedia) {
+      // Chrome, Chromium, Webview, Opera, all use the chrome shim for now
+      if (window.webkitRTCPeerConnection) {
+        result.browser = 'chrome';
+        result.version = this.extractVersion(navigator.userAgent,
+          /Chrom(e|ium)\/(\d+)\./, 2);
+      } else { // Safari (in an unpublished version) or unknown webkit-based.
+        if (navigator.userAgent.match(/Version\/(\d+).(\d+)/)) {
+          result.browser = 'safari';
+          result.version = this.extractVersion(navigator.userAgent,
+            /AppleWebKit\/(\d+)\./, 1);
+        } else { // unknown webkit-based browser.
+          result.browser = 'Unsupported webkit-based browser ' +
+              'with GUM support but no WebRTC support.';
+          return result;
+        }
+      }
+    } else if (navigator.mediaDevices &&
+        navigator.userAgent.match(/Edge\/(\d+).(\d+)$/)) { // Edge.
+      result.browser = 'edge';
+      result.version = this.extractVersion(navigator.userAgent,
+          /Edge\/(\d+).(\d+)$/, 2);
+    } else if (navigator.mediaDevices &&
+        navigator.userAgent.match(/AppleWebKit\/(\d+)\./)) {
+        // Safari, with webkitGetUserMedia removed.
+      result.browser = 'safari';
+      result.version = this.extractVersion(navigator.userAgent,
+          /AppleWebKit\/(\d+)\./, 1);
+    } else { // Default fallthrough: not supported.
+      result.browser = 'Not a supported browser.';
+      return result;
+    }
+
+    return result;
+  },
+
+};
+
+// Export.
+module.exports = {
+  log: utils.log,
+  deprecated: utils.deprecated,
+  disableLog: utils.disableLog,
+  disableWarnings: utils.disableWarnings,
+  extractVersion: utils.extractVersion,
+  shimCreateObjectURL: utils.shimCreateObjectURL,
+  detectBrowser: utils.detectBrowser.bind(utils)
+};
+
+},{}]},{},[3])(3)
+});
\ No newline at end of file
diff --git a/bigbluebutton-html5/public/js/adjust-videos.js b/bigbluebutton-html5/public/js/adjust-videos.js
new file mode 100644
index 0000000000000000000000000000000000000000..bdacb7f984e4311ff039eefd18e3eb72d0bb2836
--- /dev/null
+++ b/bigbluebutton-html5/public/js/adjust-videos.js
@@ -0,0 +1,89 @@
+
+(function() {
+  function adjustVideos(tagId, centerVideos) {
+    const _minContentAspectRatio = 4 / 3.0;
+
+    function calculateOccupiedArea(canvasWidth, canvasHeight, numColumns, numRows, numChildren) {
+      const obj = calculateCellDimensions(canvasWidth, canvasHeight, numColumns, numRows);
+      obj.occupiedArea = obj.width * obj.height * numChildren;
+      obj.numColumns = numColumns;
+      obj.numRows = numRows;
+      obj.cellAspectRatio = _minContentAspectRatio;
+      return obj;
+    }
+
+    function calculateCellDimensions(canvasWidth, canvasHeight, numColumns, numRows) {
+      const obj = {
+        width: Math.floor(canvasWidth / numColumns),
+        height: Math.floor(canvasHeight / numRows),
+      };
+
+      if (obj.width / obj.height > _minContentAspectRatio) {
+        obj.width = Math.min(Math.floor(obj.height * _minContentAspectRatio), Math.floor(canvasWidth / numColumns));
+      } else {
+        obj.height = Math.min(Math.floor(obj.width / _minContentAspectRatio), Math.floor(canvasHeight / numRows));
+      }
+      return obj;
+    }
+
+    function findBestConfiguration(canvasWidth, canvasHeight, numChildrenInCanvas) {
+      let bestConfiguration = {
+        occupiedArea: 0,
+      };
+
+      for (let cols = 1; cols <= numChildrenInCanvas; cols++) {
+        let rows = Math.floor(numChildrenInCanvas / cols);
+
+        // That's a small HACK, different from the original algorithm
+        // Sometimes numChildren will be bigger than cols*rows, this means that this configuration
+        // can't show all the videos and shouldn't be considered. So we just increment the number of rows
+        // and get a configuration which shows all the videos albeit with a few missing slots in the end.
+        //   For example: with numChildren == 8 the loop will generate cols == 3 and rows == 2
+        //   cols * rows is 6 so we bump rows to 3 and then cols*rows is 9 which is bigger than 8
+        if (numChildrenInCanvas > cols * rows) {
+          rows += 1;
+        }
+
+        const currentConfiguration = calculateOccupiedArea(canvasWidth, canvasHeight, cols, rows, numChildrenInCanvas);
+
+        if (currentConfiguration.occupiedArea > bestConfiguration.occupiedArea) {
+          bestConfiguration = currentConfiguration;
+        }
+      }
+
+      return bestConfiguration;
+    }
+
+    // http://stackoverflow.com/a/3437825/414642
+    const e = $("#" + tagId).parent();
+    const x = e.outerWidth() - 1;
+    const y = e.outerHeight() - 1;
+
+    const videos = $("#" + tagId + " video:visible");
+
+    const best = findBestConfiguration(x, y, videos.length);
+
+    videos.each(function (i) {
+      const row = Math.floor(i / best.numColumns);
+      const col = Math.floor(i % best.numColumns);
+
+      // Free width space remaining to the right and below of the videos
+      const remX = (x - best.width * best.numColumns);
+      const remY = (y - best.height * best.numRows);
+
+      // Center videos
+      const top = Math.floor(((best.height) * row) + remY / 2);
+      const left = Math.floor(((best.width) * col) + remX / 2);
+
+      const videoTop = `top: ${top}px;`;
+      const videoLeft = `left: ${left}px;`;
+
+      $(this).attr('style', videoTop + videoLeft);
+    });
+
+    videos.attr('width', best.width);
+    videos.attr('height', best.height);
+  }
+
+  window.adjustVideos = adjustVideos;
+})();
diff --git a/bigbluebutton-html5/server/main.js b/bigbluebutton-html5/server/main.js
index 1dab09e2879dc5ec2278e623e6703d710a03efe3..ac582ccc4631a68deb3ddab454687fd7155d3a1e 100644
--- a/bigbluebutton-html5/server/main.js
+++ b/bigbluebutton-html5/server/main.js
@@ -14,6 +14,7 @@ import '/imports/api/chat/server';
 import '/imports/api/screenshare/server';
 import '/imports/api/voice-users/server';
 import '/imports/api/whiteboard-multi-user/server';
+import '/imports/api/video/server';
 
 // Commons
 import '/imports/api/log-client/server';
diff --git a/bigbluebutton-web/grails-app/conf/bigbluebutton.properties b/bigbluebutton-web/grails-app/conf/bigbluebutton.properties
index c2356afc6128fde6f1f58f82b9a0fdf0c511e148..575d87026dbee077372bba48d4e593a5ac28097c 100755
--- a/bigbluebutton-web/grails-app/conf/bigbluebutton.properties
+++ b/bigbluebutton-web/grails-app/conf/bigbluebutton.properties
@@ -153,10 +153,13 @@ allowStartStopRecording=true
 # Allow webcams streaming reception only to and from moderators
 webcamsOnlyForModerator=false 
 
+# Mute the meeting on start
+muteOnStart=false
+
 #----------------------------------------------------
 # This URL is where the BBB client is accessible. When a user sucessfully
 # enters a name and password, she is redirected here to load the client.
-bigbluebutton.web.serverURL=http://192.168.246.131
+bigbluebutton.web.serverURL=http://10.130.218.160
 
 
 #----------------------------------------------------
@@ -190,7 +193,7 @@ defaultConfigURL=${bigbluebutton.web.serverURL}/client/conf/config.xml
 apiVersion=1.1
 
 # Salt which is used by 3rd-party apps to authenticate api calls
-securitySalt=3895c0fc987abdd47edf0352fea2a458
+securitySalt=2ff5336294114f9ee7ff4596203f2a53
 
 # Directory where we drop the <meeting-id-recorded>.done file
 recordStatusDir=/var/bigbluebutton/recording/status/recorded
diff --git a/bigbluebutton-web/grails-app/conf/spring/resources.xml b/bigbluebutton-web/grails-app/conf/spring/resources.xml
index c8cc4216485de7f7439c58106eda8071d03c6d10..3ae6da0d11013c974da4279125f0ab25666c99a5 100755
--- a/bigbluebutton-web/grails-app/conf/spring/resources.xml
+++ b/bigbluebutton-web/grails-app/conf/spring/resources.xml
@@ -137,6 +137,7 @@ with BigBlueButton; if not, see <http://www.gnu.org/licenses/>.
         <property name="warnMinutesBeforeMax" value="${warnMinutesBeforeMax}"/>
         <property name="meetingExpireIfNoUserJoinedInMinutes" value="${meetingExpireIfNoUserJoinedInMinutes}"/>
         <property name="meetingExpireWhenLastUserLeftInMinutes" value="${meetingExpireWhenLastUserLeftInMinutes}"/>
+        <property name="muteOnStart" value="${muteOnStart}"/>
     </bean>
 
     <import resource="doc-conversion.xml"/>
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 6b00d4467eed18282b1b4b50747f11faea03c03e..9e4c2c3bfb172301c3fa8d4af40f0c0f714ca1f2 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
@@ -162,10 +162,6 @@ class ApiController {
 
     Meeting newMeeting = paramsProcessorUtil.processCreateParams(params);
 
-    if (! StringUtils.isEmpty(params.moderatorOnlyMessage)) {
-      newMeeting.setModeratorOnlyMessage(params.moderatorOnlyMessage);
-    }
-
     if (meetingService.createMeeting(newMeeting)) {
       // See if the request came with pre-uploading of presentation.
       uploadDocuments(newMeeting);
@@ -1381,6 +1377,10 @@ class ApiController {
               welcome = us.welcome
               if (! StringUtils.isEmpty(meeting.moderatorOnlyMessage))
                 modOnlyMessage = meeting.moderatorOnlyMessage
+
+              customLogoURL = meeting.getCustomLogoURL()
+              customCopyright = meeting.getCustomCopyright()
+              muteOnStart = meeting.getMuteOnStart()
               logoutUrl = us.logoutUrl
               defaultLayout = us.defaultLayout
               avatarURL = us.avatarURL
diff --git a/bigbluebutton-web/web-app/WEB-INF/freemarker/get-meeting-info.ftlx b/bigbluebutton-web/web-app/WEB-INF/freemarker/get-meeting-info.ftlx
index c3ba00308d32fc9b577e99bf4118c5d8b663c058..d7aebe3e2f70209c566c58a280ed6a1fc580eb5a 100755
--- a/bigbluebutton-web/web-app/WEB-INF/freemarker/get-meeting-info.ftlx
+++ b/bigbluebutton-web/web-app/WEB-INF/freemarker/get-meeting-info.ftlx
@@ -27,7 +27,7 @@
   <attendees>
   <#list meeting.getUsers() as att>
     <attendee>
-        <userID>${att.getInternalUserId()}</userID>
+        <userID>${att.getExternalUserId()}</userID>
         <fullName>${att.getFullname()?html}</fullName>
         <role>${att.getRole()}</role>
         <isPresenter>${att.isPresenter()?c}</isPresenter>
diff --git a/bigbluebutton-web/web-app/WEB-INF/freemarker/get-meetings.ftlx b/bigbluebutton-web/web-app/WEB-INF/freemarker/get-meetings.ftlx
index bcc231f5ff244d561d4f8e00951ec115f3dcbb30..a43bfaea9c23ac5679bfb2e773432cb0db2e8ace 100755
--- a/bigbluebutton-web/web-app/WEB-INF/freemarker/get-meetings.ftlx
+++ b/bigbluebutton-web/web-app/WEB-INF/freemarker/get-meetings.ftlx
@@ -32,7 +32,7 @@
         <attendees>
         <#list meetingDetail.meeting.getUsers() as att>
           <attendee>
-              <userID>${att.getInternalUserId()}</userID>
+              <userID>${att.getExternalUserId()}</userID>
               <fullName>${att.getFullname()?html}</fullName>
               <role>${att.getRole()}</role>
               <isPresenter>${att.isPresenter()?c}</isPresenter>
diff --git a/labs/kurento-screenshare/README.md b/labs/bbb-webrtc-sfu/README.md
similarity index 100%
rename from labs/kurento-screenshare/README.md
rename to labs/bbb-webrtc-sfu/README.md
diff --git a/labs/bbb-webrtc-sfu/config/default.example.yml b/labs/bbb-webrtc-sfu/config/default.example.yml
new file mode 100644
index 0000000000000000000000000000000000000000..3b9bbe8a29fa7cbbb171da6fcd190eeb7d7e5760
--- /dev/null
+++ b/labs/bbb-webrtc-sfu/config/default.example.yml
@@ -0,0 +1,15 @@
+kurentoUrl: "ws://HOST/kurento"
+kurentoIp: ""
+localIpAddress: ""
+acceptSelfSignedCertificate: false
+redisHost : "127.0.0.1"
+redisPort : "6379"
+clientPort : "3008"
+minVideoPort: 30000
+maxVideoPort: 33000
+from-screenshare: "from-screenshare-sfu"
+to-screenshare: "to-screenshare-sfu"
+from-video: "from-video-sfu"
+to-video: "to-video-sfu"
+from-audio: "from-audio-sfu"
+to-audio: "to-audio-sfu"
diff --git a/labs/kurento-screenshare/debug-start.sh b/labs/bbb-webrtc-sfu/debug-start.sh
similarity index 100%
rename from labs/kurento-screenshare/debug-start.sh
rename to labs/bbb-webrtc-sfu/debug-start.sh
diff --git a/labs/kurento-screenshare/lib/bbb/messages/Constants.js b/labs/bbb-webrtc-sfu/lib/bbb/messages/Constants.js
similarity index 88%
rename from labs/kurento-screenshare/lib/bbb/messages/Constants.js
rename to labs/bbb-webrtc-sfu/lib/bbb/messages/Constants.js
index 6cccf19f93cadf15684b9a09c2f2ddde37909dbd..3b2a77052aefe4154435efb76a485935e4ee9e43 100644
--- a/labs/kurento-screenshare/lib/bbb/messages/Constants.js
+++ b/labs/bbb-webrtc-sfu/lib/bbb/messages/Constants.js
@@ -1,4 +1,6 @@
 "use strict";
+
+const config = require('config');
 /**
  * @classdesc
  * Message constants for the communication with BigBlueButton
@@ -17,9 +19,17 @@
         FROM_BBB_TRANSCODE_SYSTEM_CHAN : "bigbluebutton:from-bbb-transcode:system",
         FROM_VOICE_CONF_SYSTEM_CHAN: "from-voice-conf-redis-channel",
         TO_BBB_TRANSCODE_SYSTEM_CHAN: "bigbluebutton:to-bbb-transcode:system",
+        FROM_SCREENSHARE: config.get('from-screenshare'),
+        TO_SCREENSHARE: config.get('to-screenshare'),
+        FROM_VIDEO: config.get('from-video'),
+        TO_VIDEO: config.get('to-video'),
+        FROM_AUDIO: config.get('from-audio'),
+        TO_AUDIO: config.get('to-audio'),
 
         // RedisWrapper events
         REDIS_MESSAGE : "redis_message",
+        WEBSOCKET_MESAGE: "ws_message",
+        GATEWAY_MESSAGE: "gateway_message",
 
         // Message identifiers 1x
         START_TRANSCODER_REQUEST: "start_transcoder_request_message",
diff --git a/labs/kurento-screenshare/lib/bbb/messages/Messaging.js b/labs/bbb-webrtc-sfu/lib/bbb/messages/Messaging.js
similarity index 69%
rename from labs/kurento-screenshare/lib/bbb/messages/Messaging.js
rename to labs/bbb-webrtc-sfu/lib/bbb/messages/Messaging.js
index 92bab79944122fb88e16bb7c30152da68a0f80b7..a352acf1e5f8cfd736e48011316209995bf2a87d 100644
--- a/labs/kurento-screenshare/lib/bbb/messages/Messaging.js
+++ b/labs/bbb-webrtc-sfu/lib/bbb/messages/Messaging.js
@@ -1,24 +1,24 @@
-var Constants = require('./Constants.js');
+const Constants = require('./Constants.js');
 
 // Messages
 
-var OutMessage = require('./OutMessage.js');
+let OutMessage = require('./OutMessage.js');
 
-var StartTranscoderRequestMessage =
+let StartTranscoderRequestMessage =
     require('./transcode/StartTranscoderRequestMessage.js')(Constants);
-var StopTranscoderRequestMessage =
+let StopTranscoderRequestMessage =
     require('./transcode/StopTranscoderRequestMessage.js')(Constants);
-var StartTranscoderSysReqMsg =
+let StartTranscoderSysReqMsg =
     require('./transcode/StartTranscoderSysReqMsg.js')();
-var StopTranscoderSysReqMsg =
+let StopTranscoderSysReqMsg =
     require('./transcode/StopTranscoderSysReqMsg.js')();
-var DeskShareRTMPBroadcastStartedEventMessage =
+let DeskShareRTMPBroadcastStartedEventMessage =
     require('./screenshare/DeskShareRTMPBroadcastStartedEventMessage.js')(Constants);
-var DeskShareRTMPBroadcastStoppedEventMessage =
+let DeskShareRTMPBroadcastStoppedEventMessage =
     require('./screenshare/DeskShareRTMPBroadcastStoppedEventMessage.js')(Constants);
-var ScreenshareRTMPBroadcastStartedEventMessage2x =
+let ScreenshareRTMPBroadcastStartedEventMessage2x =
     require('./screenshare/ScreenshareRTMPBroadcastStartedEventMessage2x.js')(Constants);
-var ScreenshareRTMPBroadcastStoppedEventMessage2x =
+let ScreenshareRTMPBroadcastStoppedEventMessage2x =
     require('./screenshare/ScreenshareRTMPBroadcastStoppedEventMessage2x.js')(Constants);
 
 
@@ -31,39 +31,38 @@ function Messaging() {}
 
 Messaging.prototype.generateStartTranscoderRequestMessage =
   function(meetingId, transcoderId, params) {
-  var statrm = new StartTranscoderSysReqMsg(meetingId, transcoderId, params);
+  let statrm = new StartTranscoderSysReqMsg(meetingId, transcoderId, params);
   return statrm.toJson();
 }
 
 Messaging.prototype.generateStopTranscoderRequestMessage =
   function(meetingId, transcoderId) {
-  var stotrm = new StopTranscoderSysReqMsg(meetingId, transcoderId);
+  let stotrm = new StopTranscoderSysReqMsg(meetingId, transcoderId);
   return stotrm.toJson();
 }
 
 Messaging.prototype.generateDeskShareRTMPBroadcastStartedEvent =
   function(conferenceName, streamUrl, vw, vh, timestamp) {
-  var stadrbem = new DeskShareRTMPBroadcastStartedEventMessage(conferenceName, streamUrl, vw, vh, timestamp);
+  let stadrbem = new DeskShareRTMPBroadcastStartedEventMessage(conferenceName, streamUrl, vw, vh, timestamp);
   return stadrbem.toJson();
 }
 
 Messaging.prototype.generateDeskShareRTMPBroadcastStoppedEvent =
   function(conferenceName, streamUrl, vw, vh, timestamp) {
-  var stodrbem = new DeskShareRTMPBroadcastStoppedEventMessage(conferenceName, streamUrl, vw, vh, timestamp);
+  let stodrbem = new DeskShareRTMPBroadcastStoppedEventMessage(conferenceName, streamUrl, vw, vh, timestamp);
   return stodrbem.toJson();
 }
 
 Messaging.prototype.generateScreenshareRTMPBroadcastStartedEvent2x =
   function(conferenceName, screenshareConf, streamUrl, vw, vh, timestamp) {
-  var stadrbem = new ScreenshareRTMPBroadcastStartedEventMessage2x(conferenceName, screenshareConf, streamUrl, vw, vh, timestamp);
+  let stadrbem = new ScreenshareRTMPBroadcastStartedEventMessage2x(conferenceName, screenshareConf, streamUrl, vw, vh, timestamp);
   return stadrbem.toJson();
 }
 
 Messaging.prototype.generateScreenshareRTMPBroadcastStoppedEvent2x =
   function(conferenceName, screenshareConf, streamUrl, vw, vh, timestamp) {
-  var stodrbem = new ScreenshareRTMPBroadcastStoppedEventMessage2x(conferenceName, screenshareConf, streamUrl, vw, vh, timestamp);
+  let stodrbem = new ScreenshareRTMPBroadcastStoppedEventMessage2x(conferenceName, screenshareConf, streamUrl, vw, vh, timestamp);
   return stodrbem.toJson();
 }
 
 module.exports = new Messaging();
-module.exports.Constants = Constants;
diff --git a/labs/kurento-screenshare/lib/bbb/messages/OutMessage.js b/labs/bbb-webrtc-sfu/lib/bbb/messages/OutMessage.js
similarity index 100%
rename from labs/kurento-screenshare/lib/bbb/messages/OutMessage.js
rename to labs/bbb-webrtc-sfu/lib/bbb/messages/OutMessage.js
diff --git a/labs/kurento-screenshare/lib/bbb/messages/OutMessage2x.js b/labs/bbb-webrtc-sfu/lib/bbb/messages/OutMessage2x.js
similarity index 100%
rename from labs/kurento-screenshare/lib/bbb/messages/OutMessage2x.js
rename to labs/bbb-webrtc-sfu/lib/bbb/messages/OutMessage2x.js
diff --git a/labs/kurento-screenshare/lib/bbb/messages/screenshare/DeskShareRTMPBroadcastStartedEventMessage.js b/labs/bbb-webrtc-sfu/lib/bbb/messages/screenshare/DeskShareRTMPBroadcastStartedEventMessage.js
similarity index 100%
rename from labs/kurento-screenshare/lib/bbb/messages/screenshare/DeskShareRTMPBroadcastStartedEventMessage.js
rename to labs/bbb-webrtc-sfu/lib/bbb/messages/screenshare/DeskShareRTMPBroadcastStartedEventMessage.js
diff --git a/labs/kurento-screenshare/lib/bbb/messages/screenshare/DeskShareRTMPBroadcastStoppedEventMessage.js b/labs/bbb-webrtc-sfu/lib/bbb/messages/screenshare/DeskShareRTMPBroadcastStoppedEventMessage.js
similarity index 100%
rename from labs/kurento-screenshare/lib/bbb/messages/screenshare/DeskShareRTMPBroadcastStoppedEventMessage.js
rename to labs/bbb-webrtc-sfu/lib/bbb/messages/screenshare/DeskShareRTMPBroadcastStoppedEventMessage.js
diff --git a/labs/kurento-screenshare/lib/bbb/messages/screenshare/ScreenshareRTMPBroadcastStartedEventMessage2x.js b/labs/bbb-webrtc-sfu/lib/bbb/messages/screenshare/ScreenshareRTMPBroadcastStartedEventMessage2x.js
similarity index 100%
rename from labs/kurento-screenshare/lib/bbb/messages/screenshare/ScreenshareRTMPBroadcastStartedEventMessage2x.js
rename to labs/bbb-webrtc-sfu/lib/bbb/messages/screenshare/ScreenshareRTMPBroadcastStartedEventMessage2x.js
diff --git a/labs/kurento-screenshare/lib/bbb/messages/screenshare/ScreenshareRTMPBroadcastStoppedEventMessage2x.js b/labs/bbb-webrtc-sfu/lib/bbb/messages/screenshare/ScreenshareRTMPBroadcastStoppedEventMessage2x.js
similarity index 100%
rename from labs/kurento-screenshare/lib/bbb/messages/screenshare/ScreenshareRTMPBroadcastStoppedEventMessage2x.js
rename to labs/bbb-webrtc-sfu/lib/bbb/messages/screenshare/ScreenshareRTMPBroadcastStoppedEventMessage2x.js
diff --git a/labs/kurento-screenshare/lib/bbb/messages/transcode/StartTranscoderRequestMessage.js b/labs/bbb-webrtc-sfu/lib/bbb/messages/transcode/StartTranscoderRequestMessage.js
similarity index 100%
rename from labs/kurento-screenshare/lib/bbb/messages/transcode/StartTranscoderRequestMessage.js
rename to labs/bbb-webrtc-sfu/lib/bbb/messages/transcode/StartTranscoderRequestMessage.js
diff --git a/labs/kurento-screenshare/lib/bbb/messages/transcode/StartTranscoderSysReqMsg.js b/labs/bbb-webrtc-sfu/lib/bbb/messages/transcode/StartTranscoderSysReqMsg.js
similarity index 100%
rename from labs/kurento-screenshare/lib/bbb/messages/transcode/StartTranscoderSysReqMsg.js
rename to labs/bbb-webrtc-sfu/lib/bbb/messages/transcode/StartTranscoderSysReqMsg.js
diff --git a/labs/kurento-screenshare/lib/bbb/messages/transcode/StopTranscoderRequestMessage.js b/labs/bbb-webrtc-sfu/lib/bbb/messages/transcode/StopTranscoderRequestMessage.js
similarity index 100%
rename from labs/kurento-screenshare/lib/bbb/messages/transcode/StopTranscoderRequestMessage.js
rename to labs/bbb-webrtc-sfu/lib/bbb/messages/transcode/StopTranscoderRequestMessage.js
diff --git a/labs/kurento-screenshare/lib/bbb/messages/transcode/StopTranscoderSysReqMsg.js b/labs/bbb-webrtc-sfu/lib/bbb/messages/transcode/StopTranscoderSysReqMsg.js
similarity index 100%
rename from labs/kurento-screenshare/lib/bbb/messages/transcode/StopTranscoderSysReqMsg.js
rename to labs/bbb-webrtc-sfu/lib/bbb/messages/transcode/StopTranscoderSysReqMsg.js
diff --git a/labs/bbb-webrtc-sfu/lib/bbb/pubsub/RedisWrapper.js b/labs/bbb-webrtc-sfu/lib/bbb/pubsub/RedisWrapper.js
new file mode 100644
index 0000000000000000000000000000000000000000..94acc7db4eeea467c5133a3aaabbb92603f5373a
--- /dev/null
+++ b/labs/bbb-webrtc-sfu/lib/bbb/pubsub/RedisWrapper.js
@@ -0,0 +1,129 @@
+/**
+ * @classdesc
+ * Redis wrapper class for connecting to Redis channels
+ */
+
+'use strict';
+
+/* Modules */
+
+const redis = require('redis');
+const config = require('config');
+const Constants = require('../messages/Constants.js');
+const EventEmitter = require('events').EventEmitter;
+
+/* Public members */
+
+module.exports = class RedisWrapper extends EventEmitter {
+  constructor(subpattern) {
+    super();
+    // Redis PubSub client holders
+    this.redisCli = null;
+    this.redisPub = null;
+    // Pub and Sub channels/patterns
+    this.subpattern = subpattern;
+  }
+
+  static get _retryThreshold() {
+    return 1000 * 60 * 60;
+  }
+
+  static get _maxRetries() {
+    return 10;
+  }
+
+  startPublisher () {
+    var options = {
+      host : config.get('redisHost'),
+      port : config.get('redisPort'),
+      //password: config.get('redis.password')
+      retry_strategy: this._redisRetry
+    };
+
+    this.redisPub = redis.createClient(options);
+  }
+
+  startSubscriber () {
+    let self = this;
+    if (this.redisCli) {
+      console.log("  [RedisWrapper] Redis Client already exists");
+      return;
+    }
+
+    var options = {
+      host : config.get('redisHost'),
+      port : config.get('redisPort'),
+      //password: config.get('redis.password')
+      retry_strategy: this._redisRetry
+    };
+
+    this.redisCli = redis.createClient(options);
+
+    console.log("  [RedisWrapper] Trying to subscribe to redis channel");
+
+    this.redisCli.on("connect", () => {
+      // console.log(" [RedisWrapper] Connected to Redis Server.");
+      // DO SOMETHING
+    });
+
+    this.redisCli.on("error", (e) => {
+      console.error(" [RedisWrapper] " + e);
+    });
+
+    this.redisCli.on("reconnecting", (e) => {
+      // DO SOMETHING
+    });
+
+    this.redisCli.on("psubscribe", (channel, count) => {
+      console.log(" [RedisWrapper] Successfully subscribed to pattern [" + channel + "]");
+    });
+
+    this.redisCli.on("pmessage", this._onMessage.bind(this));
+
+    if (!this.subpattern) {
+      throw new Error("[RedisWrapper] No subscriber pattern");
+    }
+
+    this.redisCli.psubscribe(this.subpattern);
+
+    console.log("  [RedisWrapper] Started Redis client at " + options.host + ":" + options.port +
+        " for subscription pattern: " + this.subpattern);
+
+    return ;
+  }
+
+  stopRedis (callback) {
+    if (this.redisCli){
+      this.redisCli.quit();
+    }
+    callback(false);
+  }
+
+  publishToChannel (_message, channel) {
+    let message = _message;
+    if(this.redisPub) {
+      this.redisPub.publish(channel, message);
+    }
+  }
+
+  /* Private members */
+
+  _onMessage (pattern, channel, _message) {
+    let message = (typeof _message !== 'object')?JSON.parse(_message):_message;
+    // use event emitter to throw new message
+    this.emit(Constants.REDIS_MESSAGE, message);
+  }
+
+  static _redisRetry (options) {
+    // if (options.error && options.error.code === 'ECONNREFUSED') {
+    //   return new Error('The server refused the connection');
+    // }
+    if (options.total_retry_time > RedisWrapper._retryThreshold) {
+      return new Error('Retry time exhausted');
+    }
+    if (options.times_connected > RedisWrapper._maxRetries) {
+      return undefined;
+    }
+    return Math.max(options.attempt * 100, 3000);
+  }
+}
diff --git a/labs/bbb-webrtc-sfu/lib/bbb/pubsub/bbb-gw.js b/labs/bbb-webrtc-sfu/lib/bbb/pubsub/bbb-gw.js
new file mode 100644
index 0000000000000000000000000000000000000000..b99b9b855539ba1b7f3d600da522b7e89308f372
--- /dev/null
+++ b/labs/bbb-webrtc-sfu/lib/bbb/pubsub/bbb-gw.js
@@ -0,0 +1,117 @@
+/**
+ * @classdesc
+ * BigBlueButton redis gateway for bbb-screenshare node app
+ */
+
+'use strict';
+
+/* Modules */
+
+const C = require('../messages/Constants.js');
+const RedisWrapper = require('./RedisWrapper.js');
+const config = require('config');
+const util = require('util');
+const EventEmitter = require('events').EventEmitter;
+
+let instance = null;
+
+module.exports = class BigBlueButtonGW extends EventEmitter {
+  constructor() {
+    if(!instance){
+      super();
+      this.subscribers = {};
+      this.publisher = null;
+      instance = this;
+    }
+
+    return instance;
+  }
+
+  addSubscribeChannel (channel) {
+    if (this.subscribers[channel]) {
+      return this.subscribers[channel];
+    }
+
+    let wrobj = new RedisWrapper(channel);
+    this.subscribers[channel] = {};
+    this.subscribers[channel] = wrobj;
+    try {
+      wrobj.startSubscriber();
+      wrobj.on(C.REDIS_MESSAGE, this.incomingMessage.bind(this));
+      console.log("  [BigBlueButtonGW] Added redis client to this.subscribers[" + channel + "]");
+      return Promise.resolve(wrobj);
+    }
+    catch (error) {
+      return Promise.reject("  [BigBlueButtonGW] Could not start redis client for channel " + channel);
+    }
+  }
+
+  /**
+   * Capture messages from subscribed channels and emit an event with it's
+   * identifier and payload. Check Constants.js for the identifiers.
+   *
+   * @param {Object} message  Redis message
+   */
+  incomingMessage (message) {
+    let header;
+    let payload;
+    let msg = (typeof message !== 'object')?JSON.parse(message):message;
+
+    // Trying to parse both message types, 1x and 2x
+    if (msg.header) {
+      header = msg.header;
+      payload = msg.payload;
+    }
+    else if (msg.core) {
+      header = msg.core.header;
+      payload = msg.core.body;
+    }
+
+    if (header){
+      switch (header.name) {
+        // interoperability with 1.1
+        case C.START_TRANSCODER_REPLY:
+          this.emit(C.START_TRANSCODER_REPLY, payload);
+          break;
+        case C.STOP_TRANSCODER_REPLY:
+          this.emit(C.STOP_TRANSCODER_REPLY, payload);
+          break;
+          // 2x messages
+        case C.START_TRANSCODER_RESP_2x:
+          payload[C.MEETING_ID_2x] = header[C.MEETING_ID_2x];
+          this.emit(C.START_TRANSCODER_RESP_2x, payload);
+          break;
+        case C.STOP_TRANSCODER_RESP_2x:
+          payload[C.MEETING_ID_2x] = header[C.MEETING_ID_2x];
+          this.emit(C.STOP_TRANSCODER_RESP_2x, payload);
+          break;
+
+        default:
+          this.emit(C.GATEWAY_MESSAGE, msg);
+      }
+    }
+    else {
+      this.emit(C.GATEWAY_MESSAGE, msg);
+    }
+  }
+
+  publish (message, channel) {
+    if (!this.publisher) {
+      this.publisher = new RedisWrapper();
+      this.publisher.startPublisher();
+    }
+
+    if (typeof this.publisher.publishToChannel === 'function') {
+      this.publisher.publishToChannel(message, channel);
+    }
+  }
+
+  setEventEmitter (emitter) {
+    this.emitter = emitter;
+  }
+
+  _onServerResponse(data) {
+    // Here this is the 'ws' instance
+    this.sendMessage(data);
+  }
+}
diff --git a/labs/bbb-webrtc-sfu/lib/connection-manager/ConnectionManager.js b/labs/bbb-webrtc-sfu/lib/connection-manager/ConnectionManager.js
new file mode 100644
index 0000000000000000000000000000000000000000..653aada87fa2f5483643880c0e3fc6226ccccf80
--- /dev/null
+++ b/labs/bbb-webrtc-sfu/lib/connection-manager/ConnectionManager.js
@@ -0,0 +1,101 @@
+/*
+ * Lucas Fialho Zawacki
+ * Paulo Renato Lanzarin
+ * (C) Copyright 2017 Bigbluebutton
+ *
+ */
+
+'use strict';
+
+// const express = require('express');
+// const session = require('express-session')
+// const wsModule = require('./websocket');
+
+const http = require('http');
+const fs = require('fs');
+const EventEmitter = require('events');
+const BigBlueButtonGW = require('../bbb/pubsub/bbb-gw');
+const C = require('../bbb/messages/Constants');
+
+// Global variables
+module.exports = class ConnectionManager {
+
+  constructor (settings, logger) {
+    this._logger = logger;
+    this._screenshareSessions = {};
+
+    this._setupBBB();
+
+    this._emitter = this._setupEventEmitter();
+    this._adapters = [];
+  }
+
+  setHttpServer(httpServer) {
+    this.httpServer = httpServer;
+  }
+
+  listen(callback) {
+    this.httpServer.listen(callback);
+  }
+
+  addAdapter(adapter) {
+    adapter.setEventEmitter(this._emitter);
+    this._adapters.push(adapter);
+  }
+
+  _setupEventEmitter() {
+    let self = this;
+    let emitter = new EventEmitter();
+
+    emitter.on(C.WEBSOCKET_MESSAGE, (data) => {
+      switch (data.type) {
+        case "screenshare":
+          self._bbbGW.publish(JSON.stringify(data), C.TO_SCREENSHARE);
+          break;
+
+        case "video":
+          self._bbbGW.publish(JSON.stringify(data), C.TO_VIDEO);
+          break;
+
+        case "audio":
+          self._bbbGW.publish(JSON.stringify(data), C.TO_AUDIO);
+          break;
+
+        case "default":
+          // TODO handle API error message;
+      }
+    });
+
+    return emitter;
+  }
+
+  async _setupBBB() {
+    this._bbbGW = new BigBlueButtonGW();
+
+    try {
+      const screenshare = await this._bbbGW.addSubscribeChannel(C.FROM_SCREENSHARE);
+      const video = await this._bbbGW.addSubscribeChannel(C.FROM_VIDEO);
+      const audio = await this._bbbGW.addSubscribeChannel(C.FROM_AUDIO);
+
+      screenshare.on(C.REDIS_MESSAGE, (data) => {
+        this._emitter.emit('response', data);
+      });
+
+      video.on(C.REDIS_MESSAGE, (data) => {
+        this._emitter.emit('response', data);
+      });
+
+      console.log('  [ConnectionManager] Successfully subscribed to processes redis channels');
+    }
+    catch (err) {
+      console.log('  [ConnectionManager] ' + err);
+      this._stopAll;
+    }
+  }
+
+  _stopSession(sessionId) {
+  }
+
+  _stopAll() {
+  }
+}
diff --git a/labs/bbb-webrtc-sfu/lib/connection-manager/HttpServer.js b/labs/bbb-webrtc-sfu/lib/connection-manager/HttpServer.js
new file mode 100644
index 0000000000000000000000000000000000000000..8ec5a30fac3abeb8eda002809665d17662757929
--- /dev/null
+++ b/labs/bbb-webrtc-sfu/lib/connection-manager/HttpServer.js
@@ -0,0 +1,30 @@
+"use strict";
+
+const http = require("http");
+const fs = require("fs");
+const config = require('config');
+
+module.exports = class HttpServer {
+
+  constructor() {
+    //const privateKey  = fs.readFileSync('sslcert/server.key', 'utf8');
+    //const certificate = fs.readFileSync('sslcert/server.crt', 'utf8');
+    //const credentials = {key: privateKey, cert: certificate};
+
+    this.port = config.get('clientPort');
+
+    this.server = http.createServer((req,res) => {
+      //
+    });
+  }
+
+  getServerObject() {
+    return this.server;
+  }
+
+  listen(callback) {
+    console.log(' [HttpServer] Listening in port ' + this.port);
+    this.server.listen(this.port, callback);
+  }
+
+}
diff --git a/labs/bbb-webrtc-sfu/lib/connection-manager/MessageValidator.js b/labs/bbb-webrtc-sfu/lib/connection-manager/MessageValidator.js
new file mode 100644
index 0000000000000000000000000000000000000000..84022e268838c571893d3bf0c2b6f5d092584dbf
--- /dev/null
+++ b/labs/bbb-webrtc-sfu/lib/connection-manager/MessageValidator.js
@@ -0,0 +1,81 @@
+const Joi = require('joi');
+
+let instance = null;
+
+module.exports = class MessageParser {
+  constructor() {
+    if(!instance){
+      instance = this;
+    }
+    return instance;
+  }
+
+  static const schema {
+    startScreenshare: Joi.object().keys({
+      sdpOffer : Joi.string().required(),
+      vh: Joi.number().required(),
+      vw: Joi.number().required()
+    }),
+
+    startVideo: Joi.object().keys({
+      internalMeetingId: joi.string().required(),
+      callerName : Joi.string().required(),
+    }),
+
+    startAudio: Joi.object().keys({
+      internalMeetingId: joi.string().required(),
+      callerName : Joi.string().required(),
+    }),
+
+    playStart: Joi.object().keys({
+    }),
+
+    playStop: Joi.object().keys.({
+    }),
+
+    stop: Joi.object().keys({
+    }),
+
+    onIceCandidate: Joi.object().keys({
+      internalMeetingId: joi.string().required(),
+      candidate: Joi.object().required(),
+    }),
+  }
+
+  static const messageTemplate Joi.object().keys({
+    id: Joi.string().required(),
+    type: joi.string().required(),
+    role: joi.string().required(),
+  })
+
+  static const validateMessage (msg) {
+    let res = Joi.validate(msg, messageTemplate, {allowUnknown: true});
+
+    if (!res.error) {
+      res = Joi.validate(msg, schema[msg.id]);
+    }
+
+    return res;
+  }
+
+  _parse (message) {
+    let parsed = { id: '' };
+
+    try {
+      parsed = JSON.parse(message);
+    } catch (e) {
+      console.error(e);
+    }
+
+    let res = validateMessage(parsed);
+
+    if (res.error) {
+      parsed.validMessage = false;
+      parsed.errors = res.error;
+    } else {
+      parsed.validMessage = true;
+    }
+
+    return parsed;
+  }
+}
diff --git a/labs/bbb-webrtc-sfu/lib/connection-manager/RedisConnectionManager.js b/labs/bbb-webrtc-sfu/lib/connection-manager/RedisConnectionManager.js
new file mode 100644
index 0000000000000000000000000000000000000000..6c109baf75ac71b9c54a12f1ccca3f988a46ff90
--- /dev/null
+++ b/labs/bbb-webrtc-sfu/lib/connection-manager/RedisConnectionManager.js
@@ -0,0 +1,34 @@
+'use strict';
+
+// incomplete
+
+module.exports = class RedisConnectionManager {
+
+  constructor(options) {
+
+    this._client = redis.createClient({options});
+    this._pubchannel = options.pubchannel;
+    this._subchannel = optiosn.subchannel;
+
+    if (options.pubchannel) {
+      this._client.on()
+    }
+
+    if (options.subchannel) {
+      this._client.on()
+    }
+
+    this._client.on()
+    // pub
+
+  }
+
+  setEventEmitter(emitter) {
+    this.emitter = emitter;
+  }
+
+  _onMessage() {
+
+  }
+
+}
diff --git a/labs/bbb-webrtc-sfu/lib/connection-manager/WebsocketConnectionManager.js b/labs/bbb-webrtc-sfu/lib/connection-manager/WebsocketConnectionManager.js
new file mode 100644
index 0000000000000000000000000000000000000000..17ea6c3a1d0b672b48600fec053361eeb056bdc7
--- /dev/null
+++ b/labs/bbb-webrtc-sfu/lib/connection-manager/WebsocketConnectionManager.js
@@ -0,0 +1,170 @@
+'use strict';
+
+const ws = require('ws');
+const C = require('../bbb/messages/Constants');
+
+// initialization
+let connectionIDCounter = 0;
+
+// when handling a new connection
+
+
+ws.prototype.setErrorCallback = function(callback) {
+
+  this._errorCallback = callback;
+};
+
+ws.prototype.sendMessage = function(json) {
+
+  let websocket = this;
+
+  if (this._closeCode === 1000) {
+    console.log("  [WebsocketConnectionManager] Websocket closed, not sending");
+    this._errorCallback("Error: not opened");
+  }
+
+  return this.send(JSON.stringify(json), function(error) {
+    if(error) {
+      console.log('  [WebsocketConnectionManager] server: Websocket error "' + error + '" on message "' + json.id + '"');
+
+      websocket._errorCallback(error);
+    }
+  });
+
+};
+
+module.exports = class WebsocketConnectionManager {
+  constructor (server, path) {
+    this.wss = new ws.Server({
+      server,
+      path
+    });
+
+    this.wss.on ('connection', (ws) => {
+      let self = this;
+
+      ws.id = connectionIDCounter++;
+
+      console.log(" [WebsocketConnectionManager] New connection with id [ " + ws.id + " ]");
+
+      ws.on('message', (data) => {
+        let message = {};
+
+        try {
+          message = JSON.parse(data);
+          message.connectionId = ws.id;
+
+          if (!ws.sessionId) {
+            ws.sessionId = message.voiceBridge;
+          }
+
+          if (!ws.route) {
+            ws.route = message.type;
+          }
+
+          if (!ws.role) {
+            ws.role = message.role;
+          }
+        } catch(e) {
+          console.error("  [WebsocketConnectionManager] JSON message parse error " + e);
+          message = {};
+        }
+
+        // Test for empty or invalid JSON
+        if (Object.getOwnPropertyNames(message).length !== 0) {
+          this.emitter.emit(C.WEBSOCKET_MESSAGE, message);
+        }
+      });
+
+      //ws.on('message', this._onMessage.bind(this));
+      ws.setErrorCallback(this._onError.bind(this));
+
+      ws.on('close', (ev) => {
+        console.log('  [WebsocketConnectionManager] Closed connection on [' + ws.id + ']');
+        let message = {
+          id: 'close',
+          type: ws.route,
+          role: ws.role,
+          voiceBridge: ws.sessionId,
+          connectionId: ws.id
+        }
+
+        this.emitter.emit(C.WEBSOCKET_MESSAGE, message);
+
+        ws = null;
+      });
+
+      ws.on('error', (err) => {
+        console.log('  [WebsocketConnectionManager] Connection error [' + ws.id + ']');
+        let message = {
+          id: 'error',
+          type: ws.route,
+          role: ws.role,
+          voiceBridge: ws.sessionId,
+          connectionId: ws.id
+        }
+
+        this.emitter.emit(C.WEBSOCKET_MESSAGE, message);
+
+        ws = null;
+      });
+
+      // TODO: should we delete this listener after websocket dies?
+      this.emitter.on('response', (data) => {
+        if (ws && ws.id == data.connectionId) {
+          ws.sendMessage(data);
+        }
+      });
+    });
+  }
+
+  setEventEmitter (emitter) {
+    this.emitter = emitter;
+  }
+
+  _onServerResponse (data) {
+    // Here this is the 'ws' instance
+    this.sendMessage(data);
+  }
+
+  _onMessage (data) {
+
+    let message = {};
+
+    try {
+      message = JSON.parse(data);
+    } catch(e) {
+      console.error("  [WebsocketConnectionManager] JSON message parse error " + e);
+      message = {};
+    }
+
+    // Test for empty or invalid JSON
+    if (Object.getOwnPropertyNames(message).length !== 0) {
+      this.emitter.emit(C.WEBSOCKET_MESSAGE, message);
+    }
+  }
+
+  _onError (err) {
+    console.log('  [WebsocketConnectionManager] Connection error');
+    let message = {
+      id: 'error',
+      voiceBridge: ws.sessionId,
+      connectionId: ws.id
+    }
+    this.emitter.emit(C.WEBSOCKET_MESSAGE, message);
+  }
+
+  _onClose (err) {
+    console.log('  [WebsocketConnectionManager] Closed connection [' + this.id + ']');
+    let message = {
+      id: 'close',
+      voiceBridge: this.sessionId,
+      connectionId: this.id
+    }
+
+    this.emitter.emit(C.WEBSOCKET_MESSAGE, message);
+  }
+
+  _stop () {
+  }
+}
diff --git a/labs/kurento-screenshare/lib/h264-sdp.js b/labs/bbb-webrtc-sfu/lib/h264-sdp.js
similarity index 100%
rename from labs/kurento-screenshare/lib/h264-sdp.js
rename to labs/bbb-webrtc-sfu/lib/h264-sdp.js
diff --git a/labs/bbb-webrtc-sfu/lib/mcs-core/CoreProcess.js b/labs/bbb-webrtc-sfu/lib/mcs-core/CoreProcess.js
new file mode 100644
index 0000000000000000000000000000000000000000..ee03958e42a0d3d54b74e7c76a2a1fef19234b7a
--- /dev/null
+++ b/labs/bbb-webrtc-sfu/lib/mcs-core/CoreProcess.js
@@ -0,0 +1,12 @@
+const MCSApiStub = require('./media/MCSApiStub');
+
+process.on('uncaughtException', function (error) {
+  console.log(error.stack);
+});
+
+process.on('disconnect',function() {
+  console.log("Parent exited!");
+  process.kill();
+});
+
+core = new MCSApiStub();
diff --git a/labs/bbb-webrtc-sfu/lib/mcs-core/lib/constants/Constants.js b/labs/bbb-webrtc-sfu/lib/mcs-core/lib/constants/Constants.js
new file mode 100644
index 0000000000000000000000000000000000000000..94f7acc9dc9f89aa14094232af1bb300e47468e7
--- /dev/null
+++ b/labs/bbb-webrtc-sfu/lib/mcs-core/lib/constants/Constants.js
@@ -0,0 +1,86 @@
+/*
+ * (C) Copyright 2016 Mconf Tecnologia (http://mconf.com/)
+ */
+
+/**
+ * @classdesc
+ * Message constants for the communication with BigBlueButton
+ * @constructor
+ */
+
+'use strict'
+
+exports.ALL = 'ALL'
+
+exports.LOG_LEVEL = {}
+exports.LOG_LEVEL.DEBUG = 0
+exports.LOG_LEVEL.INFO = 1
+exports.LOG_LEVEL.WARN = 2
+exports.LOG_LEVEL.ERROR = 3
+exports.LOG_LEVEL.OFF = 100
+
+exports.STATUS = {}
+exports.STATUS.STARTED = "STARTED"
+exports.STATUS.STOPPED = "STOPPED"
+exports.STATUS.RUNNING = "RUNNING'"
+exports.STATUS.STARTING = "STARTING"
+exports.STATUS.STOPPING = "STOPPING"
+exports.STATUS.RESTARTING = "RESTARTING"
+
+exports.USERS = {}
+exports.USERS.SFU = "SFU"
+exports.USERS.MCU = "MCU"
+
+exports.MEDIA_TYPE = {}
+exports.MEDIA_TYPE.WEBRTC = "WebRtcEndpoint"
+exports.MEDIA_TYPE.RTP= "RtpEndpoint"
+exports.MEDIA_TYPE.URI = "PlayerEndpoint"
+
+// Observer Constants
+exports.EVENT = {}
+exports.EVENT.DIAL_EVENT = "BRIDGE_DIAL"
+exports.EVENT.HANGUP_EVENT = "BRIDGE_HANGUP"
+exports.EVENT.SESSION_ID_EVENT = "SESSION_ID"
+exports.EVENT.AUDIO_SESSION_TERMINATED = "AUDIO_SESSION_TERMINATED"
+
+// Media server state changes 
+exports.EVENT.NEW_SESSION = "NewSession"
+exports.EVENT.MEDIA_STATE = {};
+exports.EVENT.MEDIA_STATE.MEDIA_EVENT = "MediaEvent"
+exports.EVENT.MEDIA_STATE.CHANGED = "MediaStateChanged"
+exports.EVENT.MEDIA_STATE.FLOW_OUT = "MediaFlowOutStateChange"
+exports.EVENT.MEDIA_STATE.FLOW_IN = "MediaFlowInStateChange"
+exports.EVENT.MEDIA_STATE.ENDOFSTREAM = "EndOfStream"
+exports.EVENT.MEDIA_STATE.ICE = "OnIceCandidate"
+
+
+
+// RTP params
+exports.SDP = {};
+exports.SDP.PARAMS = "params"
+exports.SDP.MEDIA_DESCRIPTION = "media_description"
+exports.SDP.LOCAL_IP_ADDRESS = "local_ip_address"
+exports.SDP.LOCAL_VIDEO_PORT = "local_video_port"
+exports.SDP.DESTINATION_IP_ADDRESS = "destination_ip_address"
+exports.SDP.DESTINATION_VIDEO_PORT = "destination_video_port"
+exports.SDP.REMOTE_VIDEO_PORT = "remote_video_port"
+exports.SDP.CODEC_NAME = "codec_name"
+exports.SDP.CODEC_ID = "codec_id"
+exports.SDP.CODEC_RATE = "codec_rate"
+exports.SDP.RTP_PROFILE = "rtp_profile"
+exports.SDP.SEND_RECEIVE = "send_receive"
+exports.SDP.FRAME_RATE = "frame_rate"
+
+// Strings
+exports.STRING = {}
+exports.STRING.ANONYMOUS = "ANONYMOUS"
+exports.STRING.FS_USER_AGENT_STRING = "Freeswitch_User_Agent"
+exports.STRING.XML_MEDIA_FAST_UPDATE = '<?xml version=\"1.0\" encoding=\"utf-8\" ?>' +
+                                          '<media_control>' +
+                                            '<vc_primitive>' +
+                                              '<to_encoder>' +
+                                                '<picture_fast_update>' +
+                                                '</picture_fast_update>' +
+                                              '</to_encoder>' +
+                                            '</vc_primitive>' +
+                                          '</media_control>'
diff --git a/labs/bbb-webrtc-sfu/lib/mcs-core/lib/media/MCSApiStub.js b/labs/bbb-webrtc-sfu/lib/mcs-core/lib/media/MCSApiStub.js
new file mode 100644
index 0000000000000000000000000000000000000000..9c42ec7efe33f34cbba1536c409fd0943408d4c3
--- /dev/null
+++ b/labs/bbb-webrtc-sfu/lib/mcs-core/lib/media/MCSApiStub.js
@@ -0,0 +1,143 @@
+'use strict'
+
+var config = require('config');
+var C = require('../constants/Constants');
+// EventEmitter
+var util = require('util');
+var EventEmitter = require('events').EventEmitter;
+var MediaController = require('./MediaController.js');
+
+let instance = null;
+
+module.exports = class MCSApiStub extends EventEmitter{
+  constructor() {
+    if(!instance) {
+      super();
+      this.listener = new EventEmitter();
+      this._mediaController = new MediaController(this.listener);
+      instance = this;
+    }
+
+    return instance;
+  }
+
+  async join (room, type, params) {
+    try {
+      const answer = await this._mediaController.join(room, type, params);
+      return Promise.resolve(answer);
+    }
+    catch (err) {
+      console.log(err);
+      Promise.reject(err);
+    }
+  }
+
+  async leave (roomId, userId) {
+    try {
+      const answer = await this._mediaController.leave(roomId, userId);
+      return Promise.resolve(answer);
+    }
+    catch (err) {
+      console.log(err);
+      return Promise.reject(err);
+    }
+  }
+
+  async publishnsubscribe (user, sourceId, sdp, params) {
+    try {
+      const answer = await this._mediaController.publishnsubscribe(user, sourceId, sdp, params);
+      return Promise.resolve(answer);
+    }
+    catch (err) {
+      console.log(err);
+      return Promise.reject(err);
+    }
+  }
+
+  async publish (user, room,  type, params) {
+    try {
+      this.listener.once(C.EVENT.NEW_SESSION+user, (event) => {
+        let sessionId = event;
+        this.listener.on(C.EVENT.MEDIA_STATE.MEDIA_EVENT+sessionId, (event) => {
+          this.emit(C.EVENT.MEDIA_STATE.MEDIA_EVENT+sessionId, event);
+        });
+      });
+      const answer = await this._mediaController.publish(user, room, type, params);
+      return Promise.resolve(answer);
+    }
+    catch (err) {
+      console.log(err);
+      return Promise.reject(err);
+    }
+  }
+  
+  async unpublish (user, mediaId) {
+    try {
+      await this._mediaController.unpublish(mediaId);
+      return Promise.resolve();
+    }
+    catch (err) {
+      console.log(err);
+      return Promise.reject(err);
+    }
+  }
+
+  async subscribe (user, sourceId, type, params) {
+    try {
+      this.listener.once(C.EVENT.NEW_SESSION+user, (event) => {
+        let sessionId = event;
+        this.listener.on(C.EVENT.MEDIA_STATE.MEDIA_EVENT+sessionId, (event) => {
+          this.emit(C.EVENT.MEDIA_STATE.MEDIA_EVENT+sessionId, event);
+        });
+      });
+
+      const answer = await this._mediaController.subscribe(user, sourceId, type, params);
+
+      return Promise.resolve(answer);
+    }
+    catch (err) {
+      console.log(err);
+      return Promise.reject(err);
+    }
+  }
+
+  async unsubscribe (user, mediaId) {
+    try {
+      await this._mediaController.unsubscribe(user, mediaId);
+      return Promise.resolve();
+    }
+    catch (err) {
+      console.log(err);
+      return Promise.reject(err);
+    }
+  }
+
+  async onEvent (eventName, mediaId) {
+    try {
+      const eventTag = this._mediaController.onEvent(eventName, mediaId);
+      this._mediaController.on(eventTag, (event) => {
+        this.emit(eventTag, event);
+      });
+
+      return Promise.resolve(eventTag);
+    }
+    catch (err) {
+      console.log(err);
+      return Promise.reject();
+    }
+  }
+
+  async addIceCandidate (mediaId, candidate) {
+    try {
+      const ack = await this._mediaController.addIceCandidate(mediaId, candidate);
+      return Promise.resolve(ack);
+    }
+    catch (err) {
+      console.log(err);
+      Promise.reject();
+    }
+  }
+  setStrategy (strategy) {
+    // TODO
+  }
+}
diff --git a/labs/bbb-webrtc-sfu/lib/mcs-core/lib/media/MediaController.js b/labs/bbb-webrtc-sfu/lib/mcs-core/lib/media/MediaController.js
new file mode 100644
index 0000000000000000000000000000000000000000..b446aa2d8ebd1433568a10a934dad0f7988dc92f
--- /dev/null
+++ b/labs/bbb-webrtc-sfu/lib/mcs-core/lib/media/MediaController.js
@@ -0,0 +1,360 @@
+'use strict'
+
+const config = require('config');
+const C = require('../constants/Constants');
+
+// Model
+const SfuUser = require('../model/SfuUser');
+const Room = require('../model/Room.js');
+
+const EventEmitter = require('events').EventEmitter;
+
+/* PRIVATE ELEMENTS */
+/**
+ * Deep copy a javascript Object
+ * @param  {Object} object The object to be copied
+ * @return {Object}        A deep copy of the given object
+ */
+function copy(object) {
+  return JSON.parse(JSON.stringify(object));
+}
+
+function getPort(min_port, max_port) {
+  return Math.floor((Math.random()*(max_port - min_port +1)+ min_port));
+}
+
+function getVideoPort() {
+  return getPort(config.get('sip.min_video_port'), config.get('sip.max_video_port'));
+}
+
+/* PUBLIC ELEMENTS */
+
+let instance = null;
+
+
+module.exports = class MediaController {
+  constructor(emitter) {
+    if (!instance) {
+      this.emitter = emitter;
+      this._rooms = {};
+      this._users = {};
+      this._mediaSessions = {};
+      instance = this;
+    }
+
+    return instance;
+  }
+
+  start (_kurentoClient, _kurentoToken, callback) {
+    var self = this;
+    return callback(null);
+  }
+
+  stop (callback) {
+    var self = this;
+    self.stopAllMedias(function (e) {
+      if (e) {
+        callback(e);
+      }
+      self._rooms = {};
+    });
+  }
+
+  getVideoPort () {
+    return getPort(config.get('sip.min_video_port'), config.get('sip.max_video_port'));
+  }
+
+  getRoom (roomId) {
+    return this._rooms[roomId];
+  }
+
+  async join (roomId, type, params) {
+    console.log("[mcs] Join room => " + roomId + ' as ' + type);
+    try {
+      let session;
+      const room = await this.createRoomMCS(roomId);
+      this._rooms[roomId] = room;
+      const user = await this.createUserMCS(roomId, type, params);
+      room.setUser(user.id);
+      this._users[user.id] = user;
+      if (params.sdp) {
+        session = user.addSdp(params.sdp);
+      }
+      if (params.uri) {
+        session = user.addUri(params.sdp);
+      }
+
+      console.log("[mcs] Resolving user " + user.id);
+      return Promise.resolve(user.id);
+    }
+    catch (err) {
+      console.log("[mcs] JOIN ERROR " + err);
+      return Promise.reject(new Error(err));
+    }
+  }
+
+  async leave (roomId, userId) {
+    try {
+      console.log("  [mcs] User => " + userId + " wants to leave ");
+      const room = this.getRoom(roomId);
+      const user = this.getUserMCS(userId);
+
+      if (!user || !room) {
+        return Promise.resolve();
+      }
+
+      const killedSessions = await user.leave();
+
+      for (var session in killedSessions) {
+        this._mediaSessions[killedSessions[session]] = null;
+      }
+
+      room.destroyUser(user.id);
+      this._users[user.id] = null;
+
+
+      return Promise.resolve();
+    }
+    catch (err) {
+      return Promise.reject(new Error(err));
+    }
+  }
+
+  async publishnsubscribe (userId, sourceId, sdp, params) {
+    console.log("[mcs] pns");
+    let type = params.type;
+    try {
+      user = this.getUserMCS(userId);
+      let userId = user.id;
+      let session = user.addSdp(sdp, type);
+      let sessionId = session.id;
+
+      if (typeof this._mediaSessions[session.id] == 'undefined' || 
+          !this._mediaSessions[session.id]) {
+        this._mediaSessions[session.id] = {};
+      }
+
+      this._mediaSessions[session.id] = session; 
+
+      const answer = await user.startSession(session.id);
+      await user.connect(sourceId, session.id);
+
+      console.log("[mcs] user with sdp session " + session.id);
+      return Promise.resolve({userId, sessionId});
+    }
+    catch (err) {
+      console.log("[mcs] PUBLISHNSUBSCRIBE ERROR " + err);
+      return Promise.reject(new Error(err));
+    }
+  }
+
+  async publish (userId, roomId, type, params) {
+    console.log("[mcs] publish");
+    let session;
+    // TODO handle mediaType
+    let mediaType = params.mediaType;
+    let answer;
+
+    try {
+      console.log("  [mcs] Fetching user => " + userId);
+
+      const user = await this.getUserMCS(userId);
+
+      console.log("  [mcs] Fetched user => " + user);
+
+      switch (type) {
+        case "RtpEndpoint":
+        case "WebRtcEndpoint":
+          session = user.addSdp(params.descriptor, type);
+          session.on('SESSION_STOPPED', (pubId) => {
+            console.log("  [mcs] SESSION ", session.id, " STOPPED ");
+            if(pubId === session.id) {
+              for (var sub in session.subscribedSessions) {
+                console.log("  [mcs] Unsubscribing session ", sub);
+                let subSession = this._mediaSessions[sub];
+                if (subSession) {
+                  subSession.stop();
+                  this._mediaSessions[sub] = null;
+                }
+              }
+            }
+          });
+
+          answer = await user.startSession(session.id);
+          break;
+        case "URI":
+          session = user.addUri(params.descriptor, type);
+
+          answer = await user.startSession(session.id);
+          break;
+
+        default: return Promise.reject(new Error("[mcs] Invalid media type"));
+      }
+    }
+    catch (err) {
+      console.log(err);
+      return Promise.reject(err);
+    }
+
+    if (typeof this._mediaSessions[session.id] == 'undefined' ||
+        !this._mediaSessions[session.id]) {
+      this._mediaSessions[session.id] = {};
+    }
+
+    this._mediaSessions[session.id] = session;
+    let sessionId = session.id;
+
+    return Promise.resolve({answer, sessionId});
+  }
+
+  async subscribe (userId, sourceId, type, params) {
+    console.log("  [mcs] subscribe");
+
+    let session;
+    // TODO handle mediaType
+    let mediaType = params.mediaType;
+    let answer;
+    let sourceSession = this._mediaSessions[sourceId];
+
+    if (typeof sourceSession === 'undefined') {
+      return Promise.reject(new Error("  [mcs] Media session " + sourceId + " was not found"));
+    }
+
+    try {
+      console.log("  [mcs] Fetching user => " + userId);
+
+      const user = await this.getUserMCS(userId);
+
+      console.log("  [mcs] Fetched user => " + user);
+
+      switch (type) {
+        case "RtpEndpoint":
+        case "WebRtcEndpoint":
+          session = user.addSdp(params.descriptor, type);
+
+          answer = await user.startSession(session.id);
+          await sourceSession.connect(session._mediaElement);
+          sourceSession.subscribedSessions.push(session.id);
+          console.log("  [mcs] ", sourceSession.id,  " subscribers list ", sourceSession.subscribedSessions);
+          break;
+        case "URI":
+          session = user.addUri(params.descriptor, type);
+          answer = await user.startSession(session.id);
+          await sourceSession.connect(session._mediaElement);
+
+          break;
+
+        default: return Promise.reject(new Error("[mcs] Invalid media type"));
+      }
+    }
+    catch (err) {
+      console.log(err);
+      return Promise.reject(err);
+    }
+
+    if (typeof this._mediaSessions[session.id] == 'undefined' ||
+        !this._mediaSessions[session.id]) {
+      this._mediaSessions[session.id] = {};
+    }
+
+    this._mediaSessions[session.id] = session;
+    let sessionId = session.id;
+
+    return Promise.resolve({answer, sessionId});
+  }
+
+  async unpublish (userId, mediaId) {
+    try {
+      const session = this._mediaSessions[mediaId];
+      const user = this.getUserMCS(userId);
+
+      if(typeof session === 'undefined' || !session) {
+        return Promise.resolve();
+      }
+
+
+      const answer = await user.unpublish(mediaId);
+      this._mediaSessions[mediaId] = null;
+      return Promise.resolve(answer);
+    }
+    catch (err) {
+      return Promise.reject(new Error(err));
+    }
+  }
+
+  async unsubscribe (userId, mediaId) {
+    try {
+      const user = this.getUserMCS(userId);
+      if (user) {
+        const answer = await user.unsubscribe(mediaId);
+        this._mediaSessions[mediaId] = null;
+      }
+      return Promise.resolve();
+    }
+    catch (err) {
+      return Promise.reject(new Error(err));
+    }
+  }
+
+  async addIceCandidate (mediaId, candidate) {
+    let session = this._mediaSessions[mediaId];
+    if (typeof session === 'undefined') {
+      return Promise.reject(new Error("  [mcs] Media session " + mediaId + " was not found"));
+    }
+    try {
+      const ack = await session.addIceCandidate(candidate);
+      return Promise.resolve(ack);
+    }
+    catch (err) {
+      console.log(err);
+      return Promise.reject(err);
+    }
+  }
+
+  /**
+   * Creates an empty {Room} room and indexes it
+   * @param {String} roomId
+   */
+  async createRoomMCS (roomId)  {
+    let self = this;
+
+    console.log("  [media] Creating new room with ID " + roomId);
+
+    if(!self._rooms[roomId]) {
+      self._rooms[roomId] = new Room(roomId);
+    }
+
+    return Promise.resolve(self._rooms[roomId]);
+  }
+
+  /**
+   * Creates an {User} of type @type
+   * @param {String} roomId
+   */
+  createUserMCS (roomId, type, params)  {
+    let self = this;
+    let user;
+    console.log("  [media] Creating a new user[" + type + "]");
+
+    switch (type) {
+      case C.USERS.SFU:
+        user  = new SfuUser(roomId, type, this.emitter, params.userAgentString, params.sdp);
+        break;
+      case C.USERS.MCU:
+        console.log("  [media] createUserMCS MCU TODO");
+        break;
+      default:
+        console.log("  [controller] Unrecognized user type");
+    }
+
+    if(!self._users[user.id]) {
+      self._users[user.id] = user;
+    }
+
+    return Promise.resolve(user);
+  }
+
+  getUserMCS (userId) {
+    return this._users[userId];
+  }
+}
diff --git a/labs/bbb-webrtc-sfu/lib/mcs-core/lib/media/media-server.js b/labs/bbb-webrtc-sfu/lib/mcs-core/lib/media/media-server.js
new file mode 100644
index 0000000000000000000000000000000000000000..6693beb97e4df4f160bc1b3beee78edb17bf532c
--- /dev/null
+++ b/labs/bbb-webrtc-sfu/lib/mcs-core/lib/media/media-server.js
@@ -0,0 +1,272 @@
+'use strict'
+
+const C = require('../constants/Constants.js');
+const config = require('config');
+const mediaServerClient = require('kurento-client');
+const util = require('util');
+const EventEmitter = require('events').EventEmitter;
+
+let instance = null;
+
+/* Public members */
+module.exports = class MediaServer extends EventEmitter {
+  constructor(serverUri) {
+    if(!instance){
+      super();
+      this._serverUri = serverUri;
+      this._mediaPipelines = {};
+      this._mediaElements= {};
+      this._mediaServer;
+      instance = this;
+    }
+
+    return instance;
+  }
+
+  async init () {
+    if (typeof this._mediaServer === 'undefined' || !this._mediaServer) {
+      this._mediaServer = await this._getMediaServerClient(this._serverUri);
+    }
+  }
+
+  _getMediaServerClient (serverUri) {
+    return new Promise((resolve, reject) =>  {
+      mediaServerClient(serverUri, (error, client) => {
+        if (error) {
+          reject(error);
+        }
+        console.log("  [media] Retrieved media server client => " + client);
+        resolve(client);
+      });
+    });
+  }
+
+  _getMediaPipeline (conference) {
+    return new Promise((resolve, reject) => {
+      if (this._mediaPipelines[conference]) {
+        console.log(' [media] Pipeline already exists. ' + JSON.stringify(this._mediaPipelines, null, 2));
+        resolve(this._mediaPipelines[conference]);
+      }
+      else {
+        this._mediaServer.create('MediaPipeline', (error, pipeline) => {
+          if (error) {
+            console.log(error);
+            reject(error);
+          }
+          this._mediaPipelines[conference] = pipeline;
+          resolve(pipeline);
+        });
+      }
+    });
+  }
+
+  _releasePipeline (pipelineId) {
+    let mediaPipeline = this._mediaPipelines[pipelineId];
+
+    if (typeof mediaPipeline !== 'undefined' && typeof mediaPipeline.release === 'function') {
+      mediaElement.release();
+    }
+  }
+
+  _createElement (pipeline, type) {
+    return new Promise((resolve, reject) => {
+      pipeline.create(type, (error, mediaElement) => {
+        if (error) {
+          return reject(error);
+        }
+        console.log("  [MediaController] Created [" + type + "] media element: " + mediaElement.id);
+        this._mediaElements[mediaElement.id] = mediaElement;
+        return resolve(mediaElement);
+      });
+    });
+  }
+
+
+  async createMediaElement (conference, type) {
+    try {
+      const pipeline = await this._getMediaPipeline(conference);
+      const mediaElement = await this._createElement(pipeline, type);
+      return Promise.resolve(mediaElement.id);
+    }
+    catch (err) {
+      return Promise.reject(new Error(err));
+    }
+  }
+
+  async connect (sourceId, sinkId, type) {
+    let source = this._mediaElements[sourceId];
+    let sink = this._mediaElements[sinkId];
+
+    if (source && sink) {
+      return new Promise((resolve, reject) => {
+        switch (type) {
+          case 'ALL': 
+            source.connect(sink, (error) => {
+              if (error) {
+                return reject(error);
+              }
+              return resolve();
+            });
+            break;
+
+
+          case 'AUDIO':
+          case 'VIDEO':
+            source.connect(sink, (error) => {
+              if (error) {
+                return reject(error);
+              }
+              return resolve();
+            });
+            break;
+
+          default: return reject("  [media] Invalid connect type");
+        }
+      });
+    }
+    else {
+      return Promise.reject("  [media] Failed to connect " + type + ": " + sourceId + " to " + sinkId);
+    }
+  }
+
+  stop (elementId) {
+    let mediaElement = this._mediaElements[elementId];
+    if (typeof mediaElement !== 'undefined' && typeof mediaElement.release === 'function') {
+      console.log("  [media] Releasing endpoint => " + elementId);
+      mediaElement.release();
+      this._mediaElements[elementId] = null;
+    }
+  }
+
+  
+  addIceCandidate (elementId, candidate) {
+    let mediaElement = this._mediaElements[elementId];
+    let kurentoCandidate = mediaServerClient.getComplexType('IceCandidate')(candidate);
+
+    if (typeof mediaElement !== 'undefined' && typeof mediaElement.addIceCandidate === 'function' &&
+        typeof candidate !== 'undefined') {
+      mediaElement.addIceCandidate(candidate);
+      console.log("  [media] Added ICE candidate for => " + elementId);
+      return Promise.resolve();
+    }
+    else {
+      return Promise.reject(new Error("Candidate could not be parsed or media element does not exist"));
+    }
+  }
+
+  gatherCandidates (elementId) {
+    console.log('  [media] Gathering ICE candidates for ' + elementId);
+    let mediaElement = this._mediaElements[elementId];
+
+    return new Promise((resolve, reject) => {
+      if (typeof mediaElement !== 'undefined' && typeof mediaElement.gatherCandidates === 'function') {
+        mediaElement.gatherCandidates((error) => {
+          if (error) {
+            return reject(new Error(error));
+          }
+          console.log('  [media] Triggered ICE gathering for ' + elementId);
+          return resolve(); 
+        });
+      }
+      else {
+        return reject("  [MediaController/gatherCandidates] There is no element " + elementId);
+      }
+    });
+  }
+
+  setInputBandwidth (elementId, min, max) {
+    let mediaElement = this._mediaElements[elementId];
+
+    if (typeof mediaElement !== 'undefined') {
+      endpoint.setMinVideoRecvBandwidth(min);
+      endpoint.setMaxVideoRecvBandwidth(max);
+    } else {
+      return (" [MediaController/setInputBandwidth] There is no element " + elementId);
+    }
+  }
+
+  setOutputBandwidth (endpoint, min, max) {
+    let mediaElement = this._mediaElements[elementId];
+
+    if (typeof mediaElement !== 'undefined') {
+      endpoint.setMinVideoSendBandwidth(min);
+      endpoint.setMaxVideoSendBandwidth(max);
+    } else {
+      return (" [MediaController/setOutputBandwidth] There is no element " + elementId );
+    }
+  }
+
+  setOutputBitrate (endpoint, min, max) {
+    let mediaElement = this._mediaElements[elementId];
+
+    if (typeof mediaElement !== 'undefined') {
+      endpoint.setMinOutputBitrate(min);
+      endpoint.setMaxOutputBitrate(max);
+    } else {
+      return (" [MediaController/setOutputBitrate] There is no element " + elementId);
+    }
+  }
+
+  processOffer (elementId, sdpOffer) {
+    let mediaElement = this._mediaElements[elementId];
+
+    return new Promise((resolve, reject) => {
+      if (typeof mediaElement !== 'undefined' && typeof mediaElement.processOffer === 'function') {
+        mediaElement.processOffer(sdpOffer, (error, answer) => {
+          if (error) {
+            return reject(error);
+          }
+          return resolve(answer);
+        });
+      }
+      else {
+        return reject("  [MediaController/processOffer] There is no element " + elementId);
+      }
+    });
+  }
+
+  trackMediaState (elementId, type) {
+    switch (type) {
+      case C.MEDIA_TYPE.URI:
+        this.addMediaEventListener(C.EVENT.MEDIA_STATE.ENDOFSTREAM, elementId);
+        this.addMediaEventListener(C.EVENT.MEDIA_STATE.CHANGED, elementId);
+        this.addMediaEventListener(C.EVENT.MEDIA_STATE.FLOW_IN, elementId);
+        this.addMediaEventListener(C.EVENT.MEDIA_STATE.FLOW_OUT, elementId);
+        break;
+
+      case C.MEDIA_TYPE.WEBRTC:
+        this.addMediaEventListener(C.EVENT.MEDIA_STATE.CHANGED, elementId);
+        this.addMediaEventListener(C.EVENT.MEDIA_STATE.FLOW_IN, elementId);
+        this.addMediaEventListener(C.EVENT.MEDIA_STATE.FLOW_OUT, elementId);
+        this.addMediaEventListener(C.EVENT.MEDIA_STATE.ICE, elementId);
+        break;
+
+      case C.MEDIA_TYPE.RTP:
+        this.addMediaEventListener(C.EVENT.MEDIA_STATE.CHANGED, elementId);
+        this.addMediaEventListener(C.EVENT.MEDIA_STATE.FLOW_IN, elementId);
+        this.addMediaEventListener(C.EVENT.MEDIA_STATE.FLOW_OUT, elementId);
+        break;
+
+      default: return;
+    }
+    return;
+  }
+
+  addMediaEventListener (eventTag, elementId) {
+    let mediaElement = this._mediaElements[elementId];
+    // TODO event type validator
+    if (typeof mediaElement !== 'undefined' && mediaElement) {
+      console.log('  [media] Adding media state listener [' + eventTag + '] for ' + elementId);
+      mediaElement.on(eventTag, (event) => {
+        if (eventTag === C.EVENT.MEDIA_STATE.ICE) {
+          event.candidate = mediaServerClient.getComplexType('IceCandidate')(event.candidate);
+        }
+        this.emit(C.EVENT.MEDIA_STATE.MEDIA_EVENT+elementId , {eventTag, event});
+      });
+    }
+  }
+
+  notifyMediaState (elementId, eventTag, event) {
+    this.emit(C.MEDIA_STATE.MEDIA_EVENT , {elementId, eventTag, event});
+  }
+};
diff --git a/labs/bbb-webrtc-sfu/lib/mcs-core/lib/model/Room.js b/labs/bbb-webrtc-sfu/lib/mcs-core/lib/model/Room.js
new file mode 100644
index 0000000000000000000000000000000000000000..1b9eb77cbea7e652595f8d8ff023f6b997ad5b1c
--- /dev/null
+++ b/labs/bbb-webrtc-sfu/lib/mcs-core/lib/model/Room.js
@@ -0,0 +1,30 @@
+/**
+ * @classdesc
+ * Model class for rooms
+ */
+
+'use strict'
+
+module.exports = class Room {
+  constructor(id) {
+    this._id = id;
+    this._users = {};
+    this._mcuUsers = {};
+  }
+
+  getUser (id) {
+    return this._users[id];
+  }
+
+  setUser (user) {
+  if (typeof this._users[user.id] == 'undefined' ||
+        !this._users[user.id]) {
+      this._users[user.id] = {};
+    }
+    this._users[user.id] = user;
+  }
+
+  destroyUser(userId) {
+    this._users[userId] = null;;
+  }
+}
diff --git a/labs/bbb-webrtc-sfu/lib/mcs-core/lib/model/SdpSession.js b/labs/bbb-webrtc-sfu/lib/mcs-core/lib/model/SdpSession.js
new file mode 100644
index 0000000000000000000000000000000000000000..e532a3d7089e2dfafe12f1842bd7d3da10e31a03
--- /dev/null
+++ b/labs/bbb-webrtc-sfu/lib/mcs-core/lib/model/SdpSession.js
@@ -0,0 +1,119 @@
+/**
+ * @classdesc
+ * Model class for external devices
+ */
+
+'use strict'
+
+const C = require('../constants/Constants');
+const SdpWrapper = require('../utils/SdpWrapper');
+const rid = require('readable-id');
+const EventEmitter = require('events').EventEmitter;
+const MediaServer = require('../media/media-server');
+const config = require('config');
+const kurentoUrl = config.get('kurentoUrl');
+
+module.exports = class SdpSession extends EventEmitter {
+  constructor(emitter, sdp = null, room, type = 'WebRtcEndpoint') {
+    super();
+    this.id = rid();
+    this.room = room;
+    this.emitter = emitter;
+    this._status = C.STATUS.STOPPED;
+    this._type = type;
+    // {SdpWrapper} SdpWrapper
+    this._sdp;
+    if (sdp && type) {
+      this.setSdp(sdp, type);
+    }
+    this._MediaServer = new MediaServer(kurentoUrl);
+    this._mediaElement;
+    this.subscribedSessions = [];
+  }
+
+  async setSdp (sdp, type) {
+    this._sdp = new SdpWrapper(sdp, type);
+    await this._sdp.processSdp();
+  }
+
+  async start (sdpId) {
+    this._status = C.STATUS.STARTING;
+    try {
+      const client = await this._MediaServer.init();
+
+      console.log("[SdpSession] start/cme");
+      this._mediaElement = await this._MediaServer.createMediaElement(this.room, this._type);
+      console.log("[SdpSession] start/po " + this._mediaElement);
+
+      this._MediaServer.trackMediaState(this._mediaElement, this._type);
+      this._MediaServer.on(C.EVENT.MEDIA_STATE.MEDIA_EVENT+this._mediaElement, (event) => {
+        setTimeout(() => {
+          event.id = this.id;
+          this.emitter.emit(C.EVENT.MEDIA_STATE.MEDIA_EVENT+this.id, event);
+        }, 50);
+      });
+
+      const answer = await this._MediaServer.processOffer(this._mediaElement, this._sdp.getPlainSdp()); 
+
+      if (this._type === 'WebRtcEndpoint') {
+        this._MediaServer.gatherCandidates(this._mediaElement);
+      }
+
+      return Promise.resolve(answer);
+    }
+    catch (err) {
+      this.handleError(err);
+      return Promise.reject(err);
+    }
+  }
+
+  // TODO move to parent Session
+  async stop () {
+    this._status = C.STATUS.STOPPING;
+    try {
+      await this._MediaServer.stop(this._mediaElement);
+      this._status = C.STATUS.STOPPED;
+      console.log("  [SdpSession] Session ", this.id, " is going to stop...");
+      this.emit('SESSION_STOPPED', this.id);
+      Promise.resolve();
+    }
+    catch (err) {
+      this.handleError(err);
+      Promise.reject(err);
+    }
+  }
+
+
+  // TODO move to parent Session
+  // TODO handle connection type
+  async connect (sinkId) {
+    try {
+      console.log("  [SdpSession] Connecting " + this._mediaElement + " => " + sinkId);
+      await this._MediaServer.connect(this._mediaElement, sinkId, 'ALL');
+      return Promise.resolve();
+    }
+    catch (err) {
+      this.handleError(err);
+      return Promise.reject(err);
+    }
+  }
+
+  async addIceCandidate (candidate) {
+    try {
+      await this._MediaServer.addIceCandidate(this._mediaElement, candidate);
+      Promise.resolve();
+    }
+    catch (err) {
+      Promise.reject(err);
+    }
+  }
+
+  addMediaEventListener (type, mediaId) {
+    this._MediaServer.addMediaEventListener (type, mediaId);
+  }
+
+  handleError (err) {
+    console.log(err);
+    this._status = C.STATUS.STOPPED;
+  }
+}
diff --git a/labs/bbb-webrtc-sfu/lib/mcs-core/lib/model/SfuUser.js b/labs/bbb-webrtc-sfu/lib/mcs-core/lib/model/SfuUser.js
new file mode 100644
index 0000000000000000000000000000000000000000..86cced861c78564e0ac7789395a7e599593a503d
--- /dev/null
+++ b/labs/bbb-webrtc-sfu/lib/mcs-core/lib/model/SfuUser.js
@@ -0,0 +1,187 @@
+/**
+ * @classdesc
+ * Model class for external devices
+ */
+
+'use strict'
+
+const User = require('./User');
+const C = require('../constants/Constants');
+const SdpWrapper = require('../utils/SdpWrapper');
+const SdpSession = require('../model/SdpSession');
+const UriSession = require('../model/UriSession');
+
+module.exports = class SfuUser extends User {
+  constructor(_roomId, type, emitter, userAgentString = C.STRING.ANONYMOUS, sdp = null, uri = null) {
+    super(_roomId);
+    // {SdpWrapper} SdpWrapper
+    this._sdp;
+    // {Object} hasAudio, hasVideo, hasContent
+    this._mediaSessions = {}
+    this.userAgentString;
+    this.emitter = emitter;
+    if (sdp) {
+      this.addSdp(sdp);
+    }
+    if (uri) {
+      this.addUri(uri);
+    }
+  }
+
+  async addUri (uri, type) {
+    // TODO switch from type to children UriSessions (RTSP|HTTP|etc)
+    let session = new UriSession(uri, type);
+
+    if (typeof this._mediaSessions[session.id] == 'undefined' || 
+        !this._mediaSessions[session.id]) {
+      this._mediaSessions[session.id] = {};
+    }
+    this._mediaSessions[session.id] = session; 
+    try {
+      await this.startSession(session.id);
+      Promise.resolve(session.id);
+    }
+    catch (err) {
+      this.handleError(err);
+      Promise.reject(new Error(err));
+    }
+  }
+
+  addSdp (sdp, type) {
+    // TODO switch from type to children SdpSessions (WebRTC|SDP)
+    let session = new SdpSession(this.emitter, sdp, this.roomId, type);
+    this.emitter.emit(C.EVENT.NEW_SESSION+this.id, session.id);
+    session.on("SESSION_STOPPED", (sessId) => {
+      console.log("  [SfuUser] Session ", sessId, "stopped, cleaning it...");
+      if (sessId === session.id) {
+        this._mediaSessions[sessId] = null;
+      }
+    });
+
+    if (typeof this._mediaSessions[session.id] == 'undefined' || 
+        !this._mediaSessions[session.id]) {
+      this._mediaSessions[session.id] = {};
+    }
+    this._mediaSessions[session.id] = session; 
+    console.log("[SfuUser] Added SDP " + session.id);
+
+    return session;
+  }
+
+  async startSession (sessionId) {
+    console.log("[SfuUser] starting session " + sessionId);
+    let session = this._mediaSessions[sessionId];
+  
+    try {
+      const answer = await session.start();
+      return Promise.resolve(answer);
+    }
+    catch (err) {
+      this.handleError(err);
+      return Promise.reject(new Error(err));
+    }
+  }
+
+  async subscribe (sdp, type,  mediaId) {
+    try {
+      const session = await this.addSdp(sdp, type);
+      await this.startSession(session.id);
+      await this.connect(session.id, mediaId);
+      Promise.resolve(session);
+    } 
+    catch (err) {
+      this.handleError(err);
+      Promise.reject(new Error(err));
+    }
+  }
+
+  async publish (sdp, mediaId) {
+    let session = await this.addSdp(sdp);
+    try {
+      await this.startSession(session.id);
+      Promise.resolve();
+    } 
+    catch (err) {
+      this.handleError(err);
+      Promise.reject(new Error(err));
+    }
+  }
+
+  async unsubscribe (mediaId) {
+    try {
+      await this.stopSession(mediaId);
+      Promise.resolve();
+    } 
+    catch (err) {
+      this.handleError(err);
+      Promise.reject(new Error(err));
+    }
+  }
+
+  async unpublish (mediaId) {
+    try {
+      await this.stopSession(mediaId);
+      Promise.resolve();
+    } 
+    catch (err) {
+      this.handleError(err);
+      Promise.reject(new Error(err));
+    }
+  }
+
+  async stopSession (sessionId) {
+    console.log("  [SfuUser] Stopping session => " + sessionId);
+    let session = this._mediaSessions[sessionId];
+
+    try {
+      await session.stop();
+      this._mediaSessions[sessionId] = null;
+      return Promise.resolve();
+    }
+    catch (err) {
+      this.handleError(err);
+      Promise.reject(new Error(err));
+    }
+  }
+
+  async connect (sourceId, sinkId) {
+    let session = this._mediaSessions[sourceId];
+    if(session) {
+      try {
+        console.log(" [SfuUser] Connecting sessions " + sourceId + "=>" + sinkId);
+        await session.connect(sinkId);
+        return Promise.resolve();
+      }
+      catch (err) {
+        this.handleError(err);
+        return Promise.reject(new Error(err));
+      }
+    }
+    else {
+      return Promise.reject(new Error("  [SfuUser] Source session " + sourceId + " not found"));
+    }
+  }
+
+  async leave () {
+    let sessions = Object.keys(this._mediaSessions);
+    console.log("  [SfuUser] User sessions will be killed");
+    console.log(sessions);
+
+    try {
+      for (var session in sessions) {
+        await this.stopSession(sessions[session]);
+      }
+
+      return Promise.resolve(sessions);
+    }
+    catch (err) {
+      this.handleError(err);
+      Promise.reject(new Error(err));
+    }
+  }
+
+  handleError (err) {
+    console.log(err);
+    this._status = C.STATUS.STOPPED;
+  }
+}
diff --git a/labs/bbb-webrtc-sfu/lib/mcs-core/lib/model/UriSession.js b/labs/bbb-webrtc-sfu/lib/mcs-core/lib/model/UriSession.js
new file mode 100644
index 0000000000000000000000000000000000000000..8193a56524e10a0958248e3abb95a0106e6312c2
--- /dev/null
+++ b/labs/bbb-webrtc-sfu/lib/mcs-core/lib/model/UriSession.js
@@ -0,0 +1,73 @@
+/**
+ * @classdesc
+ * Model class for external devices
+ */
+
+'use strict'
+
+const C = require('../constants/Constants');
+const rid = require('readable-id');
+const EventEmitter = require('events').EventEmitter;
+const MediaServer = require('../media/media-server');
+
+module.exports = class UriSession extends EventEmitter {
+  constructor(uri = null) {
+    super();
+    this.id = rid();
+    this._status = C.STATUS.STOPPED;
+    this._uri;
+    if (uri) {
+      this.setUri(uri);
+    }
+  }
+
+  setUri (uri) {
+    this._uri = uri;
+  }
+
+  async start () {
+    this._status = C.STATUS.STARTING;
+    try {
+      const mediaElement = await MediaServer.createMediaElement(this.id, C.MEDIA_TYPE.URI);
+      console.log("start/cme");
+      await MediaServer.play(this.id);
+      this._status = C.STATUS.STARTED;
+      return Promise.resolve();
+    }
+    catch (err) {
+      this.handleError(err);
+      return Promise.reject(new Error(err));
+    }
+  }
+
+  // TODO move to parent Session
+  async stop () {
+    this._status = C.STATUS.STOPPING;
+    try {
+      await MediaServer.stop(this.id);
+      this._status = C.STATUS.STOPPED;
+      return Promise.resolve();
+    }
+    catch (err) {
+      this.handleError(err);
+      return Promise.reject(new Error(err));
+    }
+  }
+
+  // TODO move to parent Session
+  async connect (sinkId) {
+    try {
+      await MediaServer.connect(this.id, sinkId);
+      return Promise.resolve()
+    }
+    catch (err) {
+      this.handleError(err);
+      return Promise.reject(new Error(err));
+    }
+  }
+
+  handleError (err) {
+    console.log(err);
+    this._status = C.STATUS.STOPPED;
+  }
+}
diff --git a/labs/bbb-webrtc-sfu/lib/mcs-core/lib/model/User.js b/labs/bbb-webrtc-sfu/lib/mcs-core/lib/model/User.js
new file mode 100644
index 0000000000000000000000000000000000000000..919a505189bcbe977d9e0801310ff6382a351bd5
--- /dev/null
+++ b/labs/bbb-webrtc-sfu/lib/mcs-core/lib/model/User.js
@@ -0,0 +1,18 @@
+/**
+ * @classdesc
+ * Model class for external devices
+ */
+
+'use strict'
+
+const rid = require('readable-id');
+const User = require('./User');
+const C = require('../constants/Constants.js');
+
+module.exports = class User {
+  constructor(roomId, type, userAgentString = C.STRING.ANONYMOUS) {
+    this.roomId = roomId;
+    this.id = rid();
+    this.userAgentString = userAgentString;
+  }
+}
diff --git a/labs/bbb-webrtc-sfu/lib/mcs-core/lib/utils/SdpWrapper.js b/labs/bbb-webrtc-sfu/lib/mcs-core/lib/utils/SdpWrapper.js
new file mode 100644
index 0000000000000000000000000000000000000000..eea0d58895e7424e145df30a93d7a20fe6db1a29
--- /dev/null
+++ b/labs/bbb-webrtc-sfu/lib/mcs-core/lib/utils/SdpWrapper.js
@@ -0,0 +1,256 @@
+/**
+ * @classdesc
+ * Utils class for manipulating SDP
+ */
+
+'use strict'
+
+var config = require('config');
+var transform = require('sdp-transform');
+
+module.exports = class SdpWrapper {
+  constructor(sdp) {
+    this._plainSdp = sdp;
+    this._jsonSdp = transform.parse(sdp);
+    this._mediaLines = {};
+    this._mediaCapabilities = {};
+    this._profileThreshold = "ffffff";
+  }
+
+  setSdp (sdp) {
+    this._plainSdp = sdp;
+    this._jsonSdp = transform.parse(sdp);
+  }
+
+  getPlainSdp () {
+    return this._plainSdp;
+  }
+  
+  getJsonSdp () {
+    return this._jsonSdp; 
+  }
+
+  removeFmtp () {
+    return this._plainSdp.replace(/(a=fmtp:).*/g, '');
+  }
+
+  replaceServerIpv4 (ipv4) {
+    return this._plainSdp.replace(/(IP4\s[0-9.]*)/g, 'IP4 ' + ipv4);
+  }
+
+  getCallId () {
+    return this._plainSdp.match(/(call-id|i):\s(.*)/i)[2];
+  }
+
+  /**
+   * Given a SDP, test if there is more than on video description
+   * @param  {string} sdp The Session Descriptor
+   * @return {boolean}    true if there is more than one video description, else false
+   */
+  hasAudio () {
+    return /(m=audio)/i.test(this._plainSdp);
+  }
+
+  /**
+   * Given a SDP, test if there is a video description in it 
+   * @param  {string} sdp The Session Descriptor
+   * @return {boolean}    true if there is a video description, else false
+   */
+  hasVideo (sdp) {
+    return /(m=video)/i.test(sdp);
+  }
+
+  /**
+   * Given a SDP, test if there is more than on video description
+   * @param  {string} sdp The Session Descriptor
+   * @return {boolean}    true if there is more than one video description, else false
+   */
+  hasMultipleVideo (sdp) {
+    return /(m=video)([\s\S]*\1){1,}/i.test(sdp);
+  }
+
+  /**
+   * Given a SDP, return its Session Description
+   * @param  {string} sdp The Session Descriptor
+   * @return {string}     Session description (SDP until the first media line)
+   */
+  getSessionDescription (sdp) {
+    return sdp.match(/[\s\S]+?(?=m=audio|m=video)/i);
+  }
+
+  removeSessionDescription (sdp) {
+    return sdp.match(/(?=[\s\S]+?)(m=audio[\s\S]+|m=video[\s\S]+)/i)[1];
+  }
+
+  getVideoParameters (sdp) {
+    var res = transform.parse(sdp);
+    console.log("  [sdp] getVideoParameters => " + JSON.stringify(res, null, 2));
+    var params = {};
+    params.fmtp = "";
+    params.codecId = 96;
+    var pt = 0;
+    for(var ml of res.media) {
+      if(ml.type == 'video') {
+        if (typeof ml.fmtp[0] != 'undefined' && ml.fmtp) {
+          params.codecId = ml.fmtp[0].payload;
+          params.fmtp = ml.fmtp[0].config;
+          console.log("  [sdp] getVideoParameters fmtp => " + JSON.stringify(params));
+          return params;
+        }
+      }
+    }
+    return params;
+  }
+
+  /**
+   * Given a SDP, return its Content Description
+   * @param  {string} sdp The Session Descriptor
+   * @return {string}     Content Description (SDP after first media description)
+   */
+  getContentDescription (sdp) {
+    var res = transform.parse(sdp);
+    res.media = res.media.filter(function (ml) { return ml.type == "video" });
+    var mangledSdp = transform.write(res);
+    if(typeof mangledSdp != undefined && mangledSdp && mangledSdp != "") {
+      return mangledSdp;
+    }
+    else
+      return sdp;
+  }
+
+  /**
+   * Given a SDP, return its first Media Description
+   * @param  {string} sdp The Session Descriptor
+   * @return {string}     Content Description (SDP after first media description)
+   */
+  getAudioDescription (sdp) {
+    var res = transform.parse(sdp);
+    res.media = res.media.filter(function (ml) { return ml.type == "audio" });
+    // Hack: Some devices (Snom, Pexip) send crypto with RTP/AVP
+    // That is forbidden according to RFC3711 and FreeSWITCH rebukes it
+    res = this.removeTransformCrypto(res);
+    var mangledSdp = transform.write(res);
+    this.getSessionDescription(mangledSdp);
+    if(typeof mangledSdp != undefined && mangledSdp && mangledSdp != "") {
+      return mangledSdp;
+    }
+    else {
+      return sdp;
+    }
+  }
+
+  /**
+   * Given a SDP, return its first Media Description
+   * @param  {string} sdp The Session Descriptor
+   * @return {string}     Content Description (SDP after first media description)
+   */
+  getMainDescription () {
+    var res = transform.parse(this._plainSdp);
+    // Filter should also carry && ml.invalid[0].value != 'content:slides';
+    // when content is enabled
+    res.media = res.media.filter(function (ml) { return ml.type == "video"}); //&& ml.invalid[0].value != 'content:slides'});
+    var mangledSdp = transform.write(res);
+    if (typeof mangledSdp != undefined && mangledSdp && mangledSdp != "") {
+      console.log("  [sdp] MAIN VIDEO SDP => " + mangledSdp);
+      return mangledSdp;
+    }
+    else {
+      return sdp;
+    }
+  }
+
+  /**
+   * Given a JSON SDP, remove associated crypto 'a=' lines from media lines
+   * WARNING: HACK MADE FOR FreeSWITCH ~1.4 COMPATIBILITY
+   * @param  {Object} sdp The Session Descriptor JSON
+   * @return {Object}     JSON SDP without crypto lines
+   */
+  removeTransformCrypto (sdp) {
+    for(var ml of sdp.media) {
+      delete ml['crypto'];
+    }
+    return sdp;
+  }
+
+  removeHighQualityFmtps (sdp) {
+    let res = transform.parse(sdp);
+    let maxProfileLevel = config.get('kurento.maximum_profile_level_hex');
+    let pt = 0;
+    let idx = 0;
+    for(var ml of res.media) {
+      if(ml.type == 'video') {
+        for(var fmtp of ml.fmtp) {
+          let fmtpConfig = transform.parseParams(fmtp.config);
+          let profileId = fmtpConfig['profile-level-id'];
+          if(typeof profileId !== 'undefined' && parseInt(profileId, 16) > parseInt(maxProfileLevel, 16)) {
+            console.log("  [sdp] Filtering profile " + parseInt(profileId, 16) + ". Higher than max "+ parseInt(maxProfileLevel, 16));
+            pt = fmtp.payload;
+            delete ml.fmtp[idx];
+            ml.rtp = ml.rtp.filter((rtp) => { return rtp.payload != pt});
+          }
+          else {
+            // Remove fmtp further specifications
+            //let configProfile = "profile-level-id="+profileId;
+            //fmtp.config = configProfile;
+          }
+          idx++;
+        }
+      }
+    }
+    var mangledSdp = transform.write(res);
+    return mangledSdp;
+  }
+
+  async processSdp () {
+    let description = this._plainSdp;
+    //if(config.get('kurento.force_low_resolution'))  {
+    //  description = this.removeFmtp(description);
+    //}
+
+    description = description.toString().replace(/telephone-event/, "TELEPHONE-EVENT");
+
+    this._mediaCapabilities.hasVideo = this.hasVideo(description);
+    this._mediaCapabilities.hasAudio = this.hasAudio(description);
+    this._mediaCapabilities.hasContent = this.hasMultipleVideo(description);
+    this.sdpSessionDescription = this.getSessionDescription(description);
+    this.audioSdp =  this.getAudioDescription(description);
+    this.mainVideoSdp = this.getMainDescription(description);
+    //this.mainVideoSdp = this.removeHighQualityFmtps(this.mainVideoSdp);
+    this.contentVideoSdp = this.getContentDescription(description);
+
+    return;
+  }
+
+  /* DEVELOPMENT METHODS */
+  _disableMedia  (sdp) {
+    return sdp.replace(/(m=application\s)\d*/g, "$10");
+  };
+
+  /**
+   * Given a SDP, add Floor Control response
+   * @param  {string} sdp The Session Descriptor
+   * @return {string}     A new Session Descriptor with Floor Control
+   */
+  _addFloorControl (sdp) {
+    return sdp.replace(/a=inactive/i, 'a=sendrecv\r\na=floorctrl:c-only\r\na=setup:active\r\na=connection:new');
+  }
+
+  /**
+   * Given a SDP, add Floor Control response to reinvite
+   * @param  {string} sdp The Session Descriptor
+   * @return {string}     A new Session Descriptor with Floor Control Id
+   */
+  _addFloorId (sdp) {
+    sdp = sdp.replace(/(a=floorctrl:c-only)/i, '$1\r\na=floorid:1 m-stream:3');
+    return sdp.replace(/(m=video.*)([\s\S]*?m=video.*)([\s\S]*)/i, '$1\r\na=content:main\r\na=label:1$2\r\na=content:slides\r\na=label:3$3');
+  }
+
+  /**
+   * Given the string representation of a Session Descriptor, remove it's video
+   * @param  {string} sdp The Session Descriptor
+   * @return {string}     A new Session Descriptor without the video
+   */
+  _removeVideoSdp  (sdp) {
+    return sdp.replace(/(m=video[\s\S]+)/g,'');
+  };
+};
diff --git a/labs/bbb-webrtc-sfu/lib/mcs-core/lib/utils/sdp-utils.js b/labs/bbb-webrtc-sfu/lib/mcs-core/lib/utils/sdp-utils.js
new file mode 100644
index 0000000000000000000000000000000000000000..11b06b0bcf49d721be79203de85fe307962b74a7
--- /dev/null
+++ b/labs/bbb-webrtc-sfu/lib/mcs-core/lib/utils/sdp-utils.js
@@ -0,0 +1,37 @@
+/**
+ * @classdesc
+ * Utils class for SDP generation
+ */
+
+module.exports.generateSdp = function(remote_ip_address, remote_video_port) {
+  return "v=0\r\n"
+    + "o=- 0 0 IN IP4 " + remote_ip_address + "\r\n"
+    + "s=No Name\r\n"
+    + "c=IN IP4 " + remote_ip_address + "\r\n"
+    + "t=0 0\r\n"
+    + "m=video " + remote_video_port + " RTP/AVP 96\r\n"
+    + "a=rtpmap:96 H264/90000\r\n"
+    + "a=ftmp:96 packetization-mode=0\r\n";
+}
+
+/**
+ * Generates a video SDP given the media specs
+ * @param  {string} sourceIpAddress The source IP address of the media
+ * @param  {string} sourceVideoPort The source video port of the media
+ * @param  {string} codecId         The ID of the codec
+ * @param  {string} sendReceive     The SDP flag of the media flow
+ * direction, 'sendonly', 'recvonly' or 'sendrecv'
+ * @param {String} rtpProfile       The RTP profile of the RTP Endpoint
+ * @param {String} codecName        The name of the codec used for the RTP
+ * Endpoint
+ * @param {String} codecRate        The codec rate
+ * @return {string}                 The Session Descriptor for the media
+ */
+module.exports.generateVideoSdp = function (sourceIpAddress, sourceVideoPort, codecId, sendReceive, rtpProfile, codecName, codecRate, fmtp) {
+  return 'm=video ' + sourceVideoPort + ' ' + rtpProfile + ' ' + codecId + '\r\n'
+    + 'a=' + sendReceive + '\r\n'
+    + 'c=IN IP4 ' + sourceIpAddress + '\r\n'
+    + 'a=rtpmap:' + codecId + ' ' + codecName + '/' + codecRate + '\r\n'
+    + 'a=fmtp:' + codecId + ' ' + fmtp + '\r\n';
+};
+
diff --git a/labs/kurento-screenshare/lib/media-handler.js b/labs/bbb-webrtc-sfu/lib/media-handler.js
similarity index 69%
rename from labs/kurento-screenshare/lib/media-handler.js
rename to labs/bbb-webrtc-sfu/lib/media-handler.js
index 2d1ab3815cb3d583be7a115c12ab6a1c6626fa92..5c611ec395d7a994c162f5e1b0e11088f5e6b233 100644
--- a/labs/kurento-screenshare/lib/media-handler.js
+++ b/labs/bbb-webrtc-sfu/lib/media-handler.js
@@ -1,42 +1,6 @@
 var config = require('config');
-var kurento = require('kurento-client');
 var Constants = require('./bbb/messages/Constants');
 
-var kurentoClient = null;
-var mediaPipelines = {};
-
-module.exports.getKurentoClient = function(kurentoUrl, callback) {
-  if (kurentoClient !== null) {
-    return callback(null, kurentoClient);
-  }
-
-  kurento(kurentoUrl, function(error, _kurentoClient) {
-    if (error) {
-      console.log("Could not find media server at address " + kurentoUrl);
-      return callback("Could not find media server at address" + kurentoUrl + ". Exiting with error " + error);
-    }
-
-    console.log(" [MediaHandler] Initiating kurento client. Connecting to: " + kurentoUrl);
-
-    kurentoClient = _kurentoClient;
-    callback(null, kurentoClient);
-  });
-}
-
-module.exports.getMediaPipeline = function(id, callback) {
-  console.log(' [MediaHandler] Creating media pipeline for ' + id);
-
-  if (mediaPipelines[id]) {
-    console.log(' [media] Pipeline already exists.');
-    callback(null, mediaPipelines[id]);
-  } else {
-    kurentoClient.create('MediaPipeline', function(err, pipeline) {
-      mediaPipelines[id] = pipeline;
-      return callback(err, pipeline);
-    });
-  }
-}
-
 module.exports.generateSdp = function(remote_ip_address, remote_video_port) {
   return "v=0\r\n"
     + "o=- 0 0 IN IP4 " + remote_ip_address + "\r\n"
@@ -76,7 +40,7 @@ module.exports.generateStreamUrl = function (address, meeting, path) {
   return "rtmp://" + address + "/video-broadcast/" + meeting + "/" + path;
 }
 
-module.exports.generateTranscoderParams = function (localIp, destIp, sendPort, recvPort, input, streamType, transcoderType, codec, callername) {
+module.exports.generateTranscoderParams = function (localIp, destIp, sendPort, recvPort, input, streamType, transcoderType, codec, callername, voiceConf) {
   var rtpParams = {};
   rtpParams[Constants.LOCAL_IP_ADDRESS] = localIp;
   rtpParams[Constants.LOCAL_VIDEO_PORT] = sendPort;
@@ -87,6 +51,7 @@ module.exports.generateTranscoderParams = function (localIp, destIp, sendPort, r
   rtpParams[Constants.TRANSCODER_TYPE] = transcoderType;
   rtpParams[Constants.TRANSCODER_CODEC] = codec;
   rtpParams[Constants.CALLERNAME] = callername;
+  rtpParams[Constants.VOICE_CONF] = voiceConf;
   return rtpParams;
 }
 
diff --git a/labs/bbb-webrtc-sfu/lib/screenshare/ScreenshareManager.js b/labs/bbb-webrtc-sfu/lib/screenshare/ScreenshareManager.js
new file mode 100644
index 0000000000000000000000000000000000000000..024bb7bf4e9eaf5a1ff8dcf5a7a888bcf9f6e27b
--- /dev/null
+++ b/labs/bbb-webrtc-sfu/lib/screenshare/ScreenshareManager.js
@@ -0,0 +1,190 @@
+/*
+ * Lucas Fialho Zawacki
+ * Paulo Renato Lanzarin
+ * (C) Copyright 2017 Bigbluebutton
+ *
+ */
+
+"use strict";
+
+const BigBlueButtonGW = require('../bbb/pubsub/bbb-gw');
+const Screenshare = require('./screenshare');
+const C = require('../bbb/messages/Constants');
+// Global variables
+
+module.exports = class ScreenshareManager {
+  constructor (logger) {
+    this._logger = logger;
+    this._clientId = 0;
+
+    this._sessions = {};
+    this._screenshareSessions = {};
+
+    this._bbbGW = new BigBlueButtonGW("MANAGER");
+    this._redisGateway;
+  }
+
+  async start() {
+    try {
+      this._redisGateway = await this._bbbGW.addSubscribeChannel(C.TO_SCREENSHARE);
+      const transcode = await this._bbbGW.addSubscribeChannel(C.FROM_BBB_TRANSCODE_SYSTEM_CHAN);
+      this._redisGateway.on(C.REDIS_MESSAGE, this._onMessage.bind(this));
+      console.log('  [ScreenshareManager] Successfully subscribed to redis channel');
+    }
+    catch (error) {
+      console.log('  [ScreenshareManager] Could not connect to transcoder redis channel, finishing app...');
+      console.log(error);
+      this.stopAll();
+    }
+  }
+
+  _onMessage(_message) {
+    console.log('  [ScreenshareManager] Received message => ');
+    let session;
+    let message = _message;
+
+    let sessionId = message.voiceBridge;
+    let connectionId = message.connectionId;
+
+    if(this._screenshareSessions[sessionId]) {
+      session = this._screenshareSessions[sessionId];
+    }
+
+    switch (message.id) {
+
+      case 'presenter':
+
+        // Checking if there's already a Screenshare session started
+        // because we shouldn't overwrite it
+
+        if (!this._screenshareSessions[message.voiceBridge]) {
+          this._screenshareSessions[message.voiceBridge] = {}
+          this._screenshareSessions[message.voiceBridge] = session;
+        }
+
+        if(session) {
+          break;
+        }
+
+        session = new Screenshare(connectionId, this._bbbGW,
+            sessionId, connectionId, message.vh, message.vw,
+            message.internalMeetingId);
+
+        this._screenshareSessions[sessionId] = {}
+        this._screenshareSessions[sessionId] = session;
+
+        // starts presenter by sending sessionID, websocket and sdpoffer
+        session._startPresenter(sessionId, message.sdpOffer, (error, sdpAnswer) => {
+          console.log("  [ScreenshareManager] Started presenter " + sessionId);
+          if (error) {
+            this._bbbGW.publish(JSON.stringify({
+              connectionId: session._id,
+              id : 'presenterResponse',
+              response : 'rejected',
+              message : error
+            }), C.FROM_SCREENSHARE);
+            return error;
+          }
+
+          this._bbbGW.publish(JSON.stringify({
+            connectionId: session._id,
+            id : 'presenterResponse',
+            response : 'accepted',
+            sdpAnswer : sdpAnswer
+          }), C.FROM_SCREENSHARE);
+
+          console.log("  [ScreenshareManager]  [websocket] Sending presenterResponse \n" + sdpAnswer);
+        });
+        break;
+
+      case 'viewer':
+        console.log("  [ScreenshareManager][viewer] Session output \n " + session);
+        if (message.sdpOffer && message.voiceBridge) {
+          if (session) {
+            session._startViewer(message.connectionId, message.voiceBridge, message.sdpOffer, connectionId,
+                this._screenshareSessions[message.voiceBridge]._presenterEndpoint);
+          } else {
+            // TODO ERROR HANDLING
+          }
+        }
+        break;
+
+      case 'stop':
+        console.log('[' + message.id + '] connection ' + sessionId);
+
+        if (session) {
+          session._stop(sessionId);
+        } else {
+          console.log(" [stop] Why is there no session on STOP?");
+        }
+        break;
+
+      case 'onIceCandidate':
+        if (session) {
+          session.onIceCandidate(message.candidate);
+        } else {
+          console.log(" [iceCandidate] Why is there no session on ICE CANDIDATE?");
+        }
+        break;
+
+      case 'viewerIceCandidate':
+        console.log("[viewerIceCandidate] Session output => " + session);
+        if (session) {
+          session.onViewerIceCandidate(message.candidate, connectionId);
+        } else {
+          console.log("[iceCandidate] Why is there no session on ICE CANDIDATE?");
+        }
+        break;
+
+      case 'close':
+        console.log('  [ScreenshareManager] Connection ' + connectionId + ' closed');
+
+        if (message.role === 'presenter' && this._screenshareSessions[sessionId]) {
+          console.log("  [ScreenshareManager] Stopping presenter " + sessionId);
+          this._stopSession(sessionId);
+        }
+        if (message.role === 'viewer' && typeof session !== 'undefined') {
+          console.log("  [ScreenshareManager] Stopping viewer " + sessionId);
+          session.stopViewer(message.connectionId);
+        }
+        break;
+
+      default:
+        this._bbbGW.publish(JSON.stringify({
+          connectionId: session._id? session._id : 'none',
+          id : 'error',
+          message: 'Invald message ' + message
+        }), C.FROM_SCREENSHARE);
+        break;
+    }
+  }
+
+  _stopSession(sessionId) {
+    console.log(' [>] Stopping session ' + sessionId);
+
+    if (typeof this._screenshareSessions === 'undefined' || typeof sessionId === 'undefined') {
+      return;
+    }
+
+    let session = this._screenshareSessions[sessionId];
+    if(typeof session !== 'undefined' && typeof session._stop === 'function') {
+      session._stop();
+    }
+
+    delete this._screenshareSessions[sessionId];
+  }
+
+  stopAll() {
+    console.log('\n [x] Stopping everything! ');
+
+    if (typeof this._screenshareSessions === 'undefined') {
+      return;
+    }
+
+    let sessionIds = Object.keys(this._screenshareSessions);
+
+    for (let i = 0; i < sessionIds.length; i++) {
+      this._stopSession(sessionIds[i]);
+    }
+  }
+};
diff --git a/labs/bbb-webrtc-sfu/lib/screenshare/ScreenshareProcess.js b/labs/bbb-webrtc-sfu/lib/screenshare/ScreenshareProcess.js
new file mode 100644
index 0000000000000000000000000000000000000000..223bde09dfb129381ca98bb591e1ecb83291af16
--- /dev/null
+++ b/labs/bbb-webrtc-sfu/lib/screenshare/ScreenshareProcess.js
@@ -0,0 +1,10 @@
+const ScreenshareManager = require('./ScreenshareManager');
+
+let c = new ScreenshareManager();
+c.start();
+
+process.on('uncaughtException', function (error) {
+  console.log(error.stack);
+});
+
+process.on('disconnect', c.stopAll);
diff --git a/labs/bbb-webrtc-sfu/lib/screenshare/screenshare.js b/labs/bbb-webrtc-sfu/lib/screenshare/screenshare.js
new file mode 100644
index 0000000000000000000000000000000000000000..df3df06f1c5470a449e64c899ea9995f8e1d4ae8
--- /dev/null
+++ b/labs/bbb-webrtc-sfu/lib/screenshare/screenshare.js
@@ -0,0 +1,357 @@
+/*
+* Lucas Fialho Zawacki
+ * Paulo Renato Lanzarin
+ * (C) Copyright 2017 Bigbluebutton
+ *
+ */
+
+'use strict'
+
+// Imports
+const C = require('../bbb/messages/Constants');
+const MediaHandler = require('../media-handler');
+const Messaging = require('../bbb/messages/Messaging');
+const moment = require('moment');
+const h264_sdp = require('../h264-sdp');
+const now = moment();
+const MCSApi = require('../mcs-core/lib/media/MCSApiStub');
+const config = require('config');
+const kurentoIp = config.get('kurentoIp');
+const localIpAddress = config.get('localIpAddress');
+
+// Global stuff
+var sharedScreens = {};
+var rtpEndpoints = {};
+
+if (config.get('acceptSelfSignedCertificate')) {
+  process.env.NODE_TLS_REJECT_UNAUTHORIZED=0;
+}
+
+module.exports = class Screenshare {
+  constructor(id, bbbgw, voiceBridge, caller = 'caller', vh, vw, meetingId) {
+    this.mcs = new MCSApi();
+    this._id = id;
+    this._BigBlueButtonGW = bbbgw;
+    this._presenterEndpoint = null;
+    this._ffmpegEndpoint = null;
+    this._voiceBridge = voiceBridge;
+    this._meetingId = meetingId;
+    this._caller = caller;
+    this._streamUrl = "";
+    this._vw = vw;
+    this._vh = vh;
+    this._presenterCandidatesQueue = [];
+    this._viewersEndpoint = [];
+    this._viewersCandidatesQueue = [];
+  }
+
+  onIceCandidate (_candidate) {
+    if (this._presenterEndpoint) {
+      try {
+        this.flushCandidatesQueue(this._presenterEndpoint, this._presenterCandidatesQueue);
+        this.mcs.addIceCandidate(this._presenterEndpoint, _candidate);
+      }
+      catch (err)   {
+        console.log(err);
+      }
+    }
+    else {
+      this._presenterCandidatesQueue.push(_candidate);
+    }
+  };
+
+  flushCandidatesQueue (mediaId, queue) {
+    if (this.mediaId) {
+      try {
+        while(queue.length) {
+          let candidate = queue.shift();
+          this.mcs.addIceCandidate(mediaId, candidate);
+        }
+      }
+      catch (err) {
+        console.log(err);
+      }
+    }
+  }
+
+  mediaStateRtp (event) {
+    let msEvent = event.event;
+
+    console.log('  [screenshare] ' + msEvent.type + '[' + msEvent.state + ']' + ' for endpoint ' + this._id);
+
+    switch (event.eventTag) {
+      case "MediaStateChanged":
+        break;
+
+      case "MediaFlowOutStateChange":
+        break;
+
+      case "MediaFlowInStateChange":
+        if (msEvent.state === 'FLOWING') {
+          this._onRtpMediaFlowing();
+        }
+        else {
+          this._onRtpMediaNotFlowing();
+        }
+        break;
+
+      default: console.log("  [video] Unrecognized event");
+    }
+  }
+
+  mediaStateWebRtc (event, id) {
+    let msEvent = event.event;
+
+    console.log('  [screenshare] ' + msEvent.type + '[' + msEvent.state + ']' + ' for endpoint ' + this._id);
+
+    switch (event.eventTag) {
+      case "OnIceCandidate":
+        let candidate = msEvent.candidate;
+        this._BigBlueButtonGW.publish(JSON.stringify({
+          connectionId: id,
+          id : 'iceCandidate',
+          cameraId: this._id,
+          candidate : candidate
+        }), C.FROM_SCREENSHARE);
+
+        break;
+
+      case "MediaStateChanged":
+        break;
+
+      case "MediaFlowOutStateChange":
+        break;
+
+      case "MediaFlowInStateChange":
+        break;
+
+      default: console.log("  [video] Unrecognized event");
+    }
+  }
+
+  async _startPresenter(id, sdpOffer, callback) {
+    let presenterSdpAnswer, rtpSdpAnswer;
+    let _callback = callback;
+
+    // Force H264 on Firefox and Chrome
+    sdpOffer = h264_sdp.transform(sdpOffer);
+    console.log(" [screenshare] Starting presenter " + id + " at voiceBridge " + this._voiceBridge);
+
+    try {
+      this.userId = await this.mcs.join(this._meetingId, 'SFU', {});
+      console.log("  [video] Join returned => " + this.userId);
+    }
+    catch (err) {
+      console.log("  [video] MCS join returned error => " + err);
+      return callback(err);
+    }
+
+    try {
+      const retSource = await this.mcs.publish(this.userId, this._meetingId, 'WebRtcEndpoint', {descriptor: sdpOffer});
+
+      this._presenterEndpoint = retSource.sessionId;
+      sharedScreens[id] = this._presenterEndpoint;
+      presenterSdpAnswer = retSource.answer;
+      this.flushCandidatesQueue(this._presenterEndpoint, this._presenterCandidatesQueue);
+
+      this.mcs.on('MediaEvent' + this._presenterEndpoint, (event) => {
+        this.mediaStateWebRtc(event, this._id)
+      });
+
+      console.log("  [video] Publish returned => " + this._presenterEndpoint);
+
+    }
+    catch (err) {
+      console.log("  [video] MCS publish returned error => " + err);
+      return callback(err);
+    }
+
+    try {
+      let sendVideoPort = MediaHandler.getVideoPort();
+      let rtpSdpOffer = MediaHandler.generateVideoSdp(localIpAddress, sendVideoPort);
+
+      const retRtp = await this.mcs.subscribe(this.userId, sharedScreens[id], 'RtpEndpoint', {descriptor: rtpSdpOffer});
+
+      this._ffmpegEndpoint = retRtp.sessionId;
+      rtpEndpoints[id] = this._ffmpegEndpoint;
+
+      let recvVideoPort = retRtp.answer.match(/m=video\s(\d*)/)[1];
+      this._rtpParams = MediaHandler.generateTranscoderParams(kurentoIp, localIpAddress,
+          sendVideoPort, recvVideoPort, this._meetingId, "stream_type_video", C.RTP_TO_RTMP, "copy", this._caller, this._voiceBridge);
+
+      this.mcs.on('MediaEvent' + this._ffmpegEndpoint, this.mediaStateRtp.bind(this));
+
+      console.log("  [video] Subscribe returned => " + this._ffmpegEndpoint);
+
+      return callback(null, presenterSdpAnswer);
+    }
+    catch (err) {
+      console.log("  [video] MCS subscribe returned error => " + err);
+      return callback(err);
+    }
+  }
+
+  onViewerIceCandidate(candidate, callerName) {
+    if (this._viewersEndpoint[callerName]) {
+      try {
+        this.flushCandidatesQueue(this._viewersEndpoint[callerName], this._viewersCandidatesQueue[callerName]);
+        this.mcs.addIceCandidate(this._viewersEndpoint[callerName], candidate);
+      }
+      catch (err)   {
+        console.log(err);
+      }
+    }
+    else {
+      if (!this._viewersCandidatesQueue[callerName]) {
+        this._viewersCandidatesQueue[callerName] = [];
+      }
+      this._viewersCandidatesQueue[callerName].push(candidate);
+    }
+  }
+
+  async _startViewer(connectionId, voiceBridge, sdp, callerName, presenterEndpoint, callback) {
+    let _callback = function(){};
+    let sdpAnswer, sdpOffer;
+    console.log("  [screenshare] Starting viewer " + callerName + " for voiceBridge " + this._voiceBridge);
+
+    sdpOffer = h264_sdp.transform(sdp);
+    sdpOffer = sdp;
+
+    this._viewersCandidatesQueue[callerName] = [];
+
+
+    try {
+      const retSource = await this.mcs.subscribe(this.userId, sharedScreens[voiceBridge], 'WebRtcEndpoint', {descriptor: sdpOffer});
+
+      this._viewersEndpoint[callerName] = retSource.sessionId;
+      sdpAnswer = retSource.answer;
+      this.flushCandidatesQueue(this._viewersEndpoint[callerName], this._viewersCandidatesQueue[callerName]);
+      this.mcs.on('MediaEvent' + this._viewersEndpoint[callerName], (event) => {
+        this.mediaStateWebRtc(event, connectionId);
+      });
+
+      this._BigBlueButtonGW.publish(JSON.stringify({
+        connectionId: connectionId,
+        id: "viewerResponse",
+        sdpAnswer: sdpAnswer,
+        response: "accepted"
+      }), C.FROM_SCREENSHARE);
+
+      console.log(" Sent sdp message to client with callerName:" + callerName);
+      console.log("  [screenshare] Subscribe returned => " + this._viewersEndpoint[callerName]);
+    }
+    catch (err) {
+      console.log("  [screenshare] MCS publish returned error => " + err);
+      return _callback(err);
+    }
+  }
+
+  async _stop() {
+    console.log(' [stop] Releasing endpoints for ' + this.userId);
+
+    this._stopScreensharing();
+
+    if (this._presenterEndpoint) {
+      try {
+        await this.mcs.leave(this._meetingId, this.userId);
+        sharedScreens[this._presenterEndpoint] = null;
+        this._candidatesQueue = null;
+        this._presenterEndpoint = null;
+        this._ffmpegEndpoint = null;
+        return;
+      }
+      catch (err) {
+        console.log(err);
+        return;
+      }
+    }
+    return;
+  }
+
+  _stopScreensharing() {
+    let strm = Messaging.generateStopTranscoderRequestMessage(this._meetingId, this._meetingId);
+
+    this._BigBlueButtonGW.publish(strm, C.TO_BBB_TRANSCODE_SYSTEM_CHAN, function(error) {});
+
+    // Interoperability: capturing 1.1 stop_transcoder_reply messages
+    this._BigBlueButtonGW.once(C.STOP_TRANSCODER_REPLY, (payload) => {
+      let meetingId = payload[C.MEETING_ID];
+      this._stopRtmpBroadcast(meetingId);
+    });
+
+    // Capturing stop transcoder responses from the 2x model
+    this._BigBlueButtonGW.once(C.STOP_TRANSCODER_RESP_2x, (payload) => {
+      let meetingId = payload[C.MEETING_ID_2x];
+      this._stopRtmpBroadcast(meetingId);
+    });
+
+  }
+
+  _onRtpMediaFlowing() {
+    console.log("  [screenshare] Media FLOWING for meeting => " + this._meetingId);
+    let strm = Messaging.generateStartTranscoderRequestMessage(this._meetingId, this._meetingId, this._rtpParams);
+
+    // Interoperability: capturing 1.1 start_transcoder_reply messages
+    this._BigBlueButtonGW.once(C.START_TRANSCODER_REPLY, (payload) => {
+      let meetingId = payload[C.MEETING_ID];
+      let output = payload["params"].output;
+      this._startRtmpBroadcast(meetingId, output);
+    });
+
+    // Capturing stop transcoder responses from the 2x model
+    this._BigBlueButtonGW.once(C.START_TRANSCODER_RESP_2x, (payload) => {
+      let meetingId = payload[C.MEETING_ID_2x];
+      let output = payload["params"].output;
+      this._startRtmpBroadcast(meetingId, output);
+    });
+
+
+    this._BigBlueButtonGW.publish(strm, C.TO_BBB_TRANSCODE_SYSTEM_CHAN, function(error) {});
+  };
+
+  _stopRtmpBroadcast (meetingId) {
+    console.log("  [screenshare] _stopRtmpBroadcast for meeting => " + meetingId);
+    if(this._meetingId === meetingId) {
+      // TODO correctly assemble this timestamp
+      let timestamp = now.format('hhmmss');
+      let dsrstom = Messaging.generateScreenshareRTMPBroadcastStoppedEvent2x(this._voiceBridge,
+          this._voiceBridge, this._streamUrl, this._vw, this._vh, timestamp);
+      this._BigBlueButtonGW.publish(dsrstom, C.FROM_VOICE_CONF_SYSTEM_CHAN, function(error) {});
+    }
+  }
+
+  _startRtmpBroadcast (meetingId, output) {
+    console.log("  [screenshare] _startRtmpBroadcast for meeting => " + meetingId);
+    if(this._meetingId === meetingId) {
+      // TODO correctly assemble this timestamp
+      let timestamp = now.format('hhmmss');
+      this._streamUrl = MediaHandler.generateStreamUrl(localIpAddress, meetingId, output);
+      let dsrbstam = Messaging.generateScreenshareRTMPBroadcastStartedEvent2x(this._voiceBridge,
+          this._voiceBridge, this._streamUrl, this._vw, this._vh, timestamp);
+
+      this._BigBlueButtonGW.publish(dsrbstam, C.FROM_VOICE_CONF_SYSTEM_CHAN, function(error) {});
+    }
+  }
+
+  _onRtpMediaNotFlowing() {
+    console.log("  [screenshare] TODO RTP NOT_FLOWING");
+  }
+
+  async stopViewer(id) {
+    let viewer = this._viewersEndpoint[id];
+    console.log(' [stop] Releasing endpoints for ' + viewer);
+
+    if (viewer) {
+      try {
+        await this.mcs.unsubscribe(this.userId, this.viewer);
+        this._viewersCandidatesQueue[id] = null;
+        this._viewersEndpoint[id] = null;
+        return;
+      }
+      catch (err) {
+        console.log(err);
+        return;
+      }
+    }
+  }
+};
diff --git a/labs/bbb-webrtc-sfu/lib/video/VideoManager.js b/labs/bbb-webrtc-sfu/lib/video/VideoManager.js
new file mode 100755
index 0000000000000000000000000000000000000000..a42a3454e80b40c8909cc7342359207d54f6fef1
--- /dev/null
+++ b/labs/bbb-webrtc-sfu/lib/video/VideoManager.js
@@ -0,0 +1,214 @@
+/*
+ * Lucas Fialho Zawacki
+ * (C) Copyright 2017 Bigbluebutton
+ *
+ */
+
+'use strict';
+
+const BigBlueButtonGW = require('../bbb/pubsub/bbb-gw');
+const Video = require('./video');
+const C = require('../bbb/messages/Constants');
+
+let sessions = {};
+
+var clientId = 0;
+
+let bbbGW = new BigBlueButtonGW("MANAGER");
+let redisGateway;
+
+bbbGW.addSubscribeChannel(C.TO_VIDEO).then((gw) => {
+  redisGateway = gw;
+  redisGateway.on(C.REDIS_MESSAGE, _onMessage);
+  console.log('  [VideoManager] Successfully subscribed to redis channel ' + C.TO_VIDEO);
+
+});
+
+var _onMessage = function (_message) {
+  let message = _message;
+  let sessionId = message.connectionId;
+  let video;
+  let role = message.role? message.role : 'any';
+  let cameraId = message.cameraId;
+  let shared = false;
+  let iceQueue = {};
+
+  if (message.role == 'share') {
+    shared = true;
+  }
+
+  if (!sessions[sessionId]) {
+    sessions[sessionId] = {};
+  }
+
+  logAvailableSessions();
+
+  switch (role) {
+    case 'share':
+      if (message.cameraId && typeof sessions[sessionId][message.cameraId+'shared'] !== 'undefined' &&  sessions[sessionId][message.cameraId+'shared']) {
+        video = sessions[sessionId][message.cameraId+'shared'];
+      }
+      break;
+    case 'viewer':
+      if (message.cameraId && sessions[sessionId][message.cameraId]) {
+        video = sessions[sessionId][message.cameraId];
+      }
+    case 'any':
+      if (message.cameraId && typeof sessions[sessionId][message.cameraId+'shared'] !== 'undefined' &&  sessions[sessionId][message.cameraId+'shared']) {
+        video = sessions[sessionId][message.cameraId+'shared'];
+      }
+      else if (message.cameraId && sessions[sessionId][message.cameraId]) {
+        video = sessions[sessionId][message.cameraId];
+      }
+
+      break;
+  }
+
+  switch (message.id) {
+    case 'start':
+      console.log('[' + message.id + '] connection ' + sessionId + " message => " + JSON.stringify(message, null, 2));
+
+      video = new Video(bbbGW, message.cameraId, shared, message.connectionId);
+
+      // Empty ice queue after starting video
+      if (iceQueue[message.cameraId]) {
+        let candidate;
+        while(candidate = iceQueue[message.cameraId].pop()) {
+          video.onIceCandidate(cand);
+        }
+      }
+
+      switch (role) {
+        case 'share':
+          sessions[sessionId][message.cameraId+'shared']= video;
+          break;
+        case 'viewer':
+          sessions[sessionId][message.cameraId] = video;
+          break;
+        default: console.log(" [VideoManager] Unknown role? ", role);
+      }
+
+      video.start(message.sdpOffer, (error, sdpAnswer) => {
+        if (error) {
+          return bbbGW.publish(JSON.stringify({
+            connectionId: sessionId,
+            type: 'video',
+            role: role,
+            id : 'error',
+            response : 'rejected',
+            message : error
+          }), C.FROM_VIDEO);
+        }
+
+        bbbGW.publish(JSON.stringify({
+          connectionId: sessionId,
+          type: 'video',
+          role: role,
+          id : 'startResponse',
+          cameraId: message.cameraId,
+          sdpAnswer : sdpAnswer
+        }), C.FROM_VIDEO);
+      });
+      break;
+
+    case 'stop':
+
+      console.log('[' + message.id + '] connection ' + sessionId + " with message => " + JSON.stringify(message, null, 2));
+
+      if (video) {
+        stopSession(sessionId, role, cameraId);
+      } else {
+        console.log(" [stop] Why is there no video on STOP?");
+      }
+      break;
+
+    case 'onIceCandidate':
+
+      if (video) {
+        video.onIceCandidate(message.candidate);
+      } else {
+        console.log(" [iceCandidate] Queueing ice candidate for later in video " + message.cameraId);
+
+        if (!iceQueue[message.cameraId]) {
+          iceQueue[message.cameraId] = [];
+        }
+        iceQueue[message.cameraId].push(message.candidate);
+      }
+      break;
+
+    case 'close':
+      console.log(" [vide] Closing session for sessionId: " + sessionId);
+
+      stopSession(sessionId);
+
+      break;
+
+    default:
+      bbbGW.publish(JSON.stringify({
+        connectionId: sessionId,
+        type: 'video',
+        id : 'error',
+        response : 'rejected',
+        message : 'Invalid message ' + JSON.stringify(message)
+      }), C.FROM_VIDEO);
+      break;
+  }
+};
+
+let stopSession = async function(sessionId, role, cameraId) {
+  console.log('  [VideoManager/x] Stopping session ' + sessionId + " with role " + role + " for camera " + cameraId);
+
+  let videoIds = Object.keys(sessions[sessionId]);
+
+  try {
+    if (role === 'share') {
+      var sharedVideo = sessions[sessionId][cameraId+'shared'];
+      await sharedVideo.stop();
+      delete sessions[sessionId][cameraId+'shared'];
+      console.log('  [VideoManager] Stopping sharer [', sessionId, '][', cameraId,'] with IDs' , videoIds);
+    }
+    else if (role === 'viewer') {
+      var video = sessions[sessionId][cameraId];
+      await video.stop();
+      delete sessions[sessionId][cameraId];
+      console.log('  [VideoManager] Stopping viewer [', sessionId, '][', cameraId,'] with IDs ', sessions[sessionId][cameraId]);
+    }
+
+    logAvailableSessions();
+  }
+  catch (err) {
+    console.log("  [VideoManager] Stop error => ", err);
+  }
+}
+
+let stopAll = function() {
+  console.log('  [Video/x] Stopping everything! ');
+
+  if (sessions == null) {
+    return;
+  }
+
+  let sessionIds = Object.keys(sessions);
+
+  for (var i = 0; i < sessionIds.length; i++) {
+
+    stopSession(sessionIds[i]);
+  }
+
+  setTimeout(process.exit, 100);
+}
+
+let logAvailableSessions = function() {
+  if(typeof sessions !== 'undefined' && sessions) {
+    console.log("  [VideoManager] Available sessions are =>");
+    let sessionMainKeys = Object.keys(sessions);
+    for (var k in sessions) {
+      if(typeof sessions[k] !== 'undefined' && sessions[k]) {
+        console.log('  [VideoManager] Session[', k,'] => ', Object.keys(sessions[k]));
+      }
+    }
+  }
+}
+
+process.on('SIGTERM', stopAll);
+process.on('SIGINT', stopAll);
diff --git a/labs/bbb-webrtc-sfu/lib/video/VideoProcess.js b/labs/bbb-webrtc-sfu/lib/video/VideoProcess.js
new file mode 100644
index 0000000000000000000000000000000000000000..b6add57d4fbd68c5ce2de4cf12a4fd710016be06
--- /dev/null
+++ b/labs/bbb-webrtc-sfu/lib/video/VideoProcess.js
@@ -0,0 +1,9 @@
+const VideoManager = require('./VideoManager');
+
+process.on('uncaughtException', function (error) {
+  console.log(error.stack);
+});
+
+process.on('disconnect',function() {
+  console.log("Parent exited!");
+});
diff --git a/labs/bbb-webrtc-sfu/lib/video/video.js b/labs/bbb-webrtc-sfu/lib/video/video.js
new file mode 100644
index 0000000000000000000000000000000000000000..b99077321b18855d86db5ecc5301aa09af7cc744
--- /dev/null
+++ b/labs/bbb-webrtc-sfu/lib/video/video.js
@@ -0,0 +1,166 @@
+'use strict';
+// Global stuff
+var sharedWebcams = {};
+
+const kurento = require('kurento-client');
+const config = require('config');
+const kurentoUrl = config.get('kurentoUrl');
+const MCSApi = require('../mcs-core/lib/media/MCSApiStub');
+const C = require('../bbb/messages/Constants');
+
+if (config.get('acceptSelfSignedCertificate')) {
+  process.env.NODE_TLS_REJECT_UNAUTHORIZED=0;
+}
+
+module.exports = class Video {
+  constructor(_bbbGW, _id, _shared, _sessionId) {
+    this.mcs = new MCSApi();
+    this.bbbGW = _bbbGW;
+    this.id = _id;
+    this.sessionId = _sessionId;
+    this.meetingId = _id;
+    this.shared = _shared;
+    this.role = this.shared? 'share' : 'view'
+    this.webRtcEndpoint = null;
+    this.mediaId = null;
+
+    this.candidatesQueue = [];
+  }
+
+  onIceCandidate (_candidate) {
+    if (this.mediaId) {
+      try {
+        this.flushCandidatesQueue();
+        this.mcs.addIceCandidate(this.mediaId, _candidate);
+      }
+      catch (err)   {
+        console.log(err);
+      }
+    }
+    else {
+      this.candidatesQueue.push(_candidate);
+    }
+  };
+
+  flushCandidatesQueue () {
+    if (this.mediaId) {
+      try {
+        while(this.candidatesQueue.length) {
+          let candidate = this.candidatesQueue.shift();
+          this.mcs.addIceCandidate(this.mediaId, candidate);
+        }
+      }
+      catch (err) {
+        console.log(err);
+      }
+    }
+  }
+
+  mediaState (event) {
+    let msEvent = event.event;
+
+    switch (event.eventTag) {
+
+      case "OnIceCandidate":
+        //console.log("  [video] Sending ICE candidate to user => " + this.id);
+        let candidate = msEvent.candidate;
+        this.bbbGW.publish(JSON.stringify({
+          connectionId: this.sessionId,
+          type: 'video',
+          role: this.role,
+          id : 'iceCandidate',
+          cameraId: this.id,
+          candidate: candidate
+        }), C.FROM_VIDEO);
+        break;
+
+      case "MediaStateChanged":
+        break;
+
+      case "MediaFlowOutStateChange":
+      case "MediaFlowInStateChange":
+        console.log(' [video] ' + msEvent.type + '[' + msEvent.state + ']' + ' for endpoint ' + this.id);
+
+        if (msEvent.state === 'NOT_FLOWING') {
+          this.bbbGW.publish(JSON.stringify({
+            connectionId: this.sessionId,
+            type: 'video',
+            role: this.role,
+            id : 'playStop',
+            cameraId: this.id,
+          }), C.FROM_VIDEO);
+        }
+        else if (msEvent.state === 'FLOWING') {
+          this.bbbGW.publish(JSON.stringify({
+            connectionId: this.sessionId,
+            type: 'video',
+            role: this.role,
+            id : 'playStart',
+            cameraId: this.id,
+          }), C.FROM_VIDEO);
+        }
+
+        break;
+
+      default: console.log("  [video] Unrecognized event");
+    }
+  }
+
+  async start (sdpOffer, callback) {
+    console.log("  [video] start");
+    let sdpAnswer;
+
+    try {
+      this.userId = await this.mcs.join(this.meetingId, 'SFU', {});
+      console.log("  [video] Join returned => " + this.userId);
+
+      if (this.shared) {
+        const ret = await this.mcs.publish(this.userId, this.meetingId, 'WebRtcEndpoint', {descriptor: sdpOffer});
+
+        this.mediaId = ret.sessionId;
+        sharedWebcams[this.id] = this.mediaId;
+        sdpAnswer = ret.answer;
+        this.flushCandidatesQueue();
+        this.mcs.on('MediaEvent' + this.mediaId, this.mediaState.bind(this));
+
+        console.log("  [video] Publish returned => " + this.mediaId);
+
+        return callback(null, sdpAnswer);
+      }
+      else {
+        const ret  = await this.mcs.subscribe(this.userId, sharedWebcams[this.id], 'WebRtcEndpoint', {descriptor: sdpOffer});
+
+        this.mediaId = ret.sessionId;
+        sdpAnswer = ret.answer;
+        this.flushCandidatesQueue();
+        this.mcs.on('MediaEvent' + this.mediaId, this.mediaState.bind(this));
+
+        console.log("  [video] Subscribe for user ", this.userId, " returned => " + this.mediaId);
+
+        return callback(null, sdpAnswer);
+      }
+    }
+    catch (err) {
+      console.log("  [video] MCS returned error => " + err);
+      return callback(err);
+    }
+  };
+
+  async stop () {
+    console.log(' [stop] Releasing endpoints for user ' + this.userId + ' at room ' + this.meetingId);
+
+    try {
+      await this.mcs.leave(this.meetingId, this.userId);
+      if (this.shared) {
+        sharedWebcams[this.id] = null;
+      }
+      this._candidatesQueue = null;
+      Promise.resolve();
+    }
+    catch (err) {
+      // TODO error handling
+      Promise.reject();
+    }
+    return;
+  };
+};
diff --git a/labs/kurento-screenshare/lib/websocket.js b/labs/bbb-webrtc-sfu/lib/video/websocket.js
similarity index 100%
rename from labs/kurento-screenshare/lib/websocket.js
rename to labs/bbb-webrtc-sfu/lib/video/websocket.js
diff --git a/labs/bbb-webrtc-sfu/lib/websocket.js b/labs/bbb-webrtc-sfu/lib/websocket.js
new file mode 100644
index 0000000000000000000000000000000000000000..c4fe9f6f18b220e8b4c43be58ec75004a6430124
--- /dev/null
+++ b/labs/bbb-webrtc-sfu/lib/websocket.js
@@ -0,0 +1,18 @@
+/*
+ * Simple wrapper around the ws library
+ *
+ */
+
+var ws = require('ws');
+
+ws.prototype.sendMessage = function(json) {
+
+  return this.send(JSON.stringify(json), function(error) {
+    if(error)
+      console.log(' [server] Websocket error "' + error + '" on message "' + json.id + '"');
+  });
+
+};
+
+
+module.exports = ws;
\ No newline at end of file
diff --git a/labs/bbb-webrtc-sfu/package-lock.json b/labs/bbb-webrtc-sfu/package-lock.json
new file mode 100644
index 0000000000000000000000000000000000000000..8fbfe4227510eb9e880d5bf83dc0de9fa24fb80e
--- /dev/null
+++ b/labs/bbb-webrtc-sfu/package-lock.json
@@ -0,0 +1,371 @@
+{
+  "name": "bbb-webrtc-sfu",
+  "version": "0.0.2",
+  "lockfileVersion": 1,
+  "requires": true,
+  "dependencies": {
+    "argparse": {
+      "version": "1.0.9",
+      "resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.9.tgz",
+      "integrity": "sha1-c9g7wmP4bpf4zE9rrhsOkKfSLIY=",
+      "requires": {
+        "sprintf-js": "1.0.3"
+      }
+    },
+    "asap": {
+      "version": "2.0.6",
+      "resolved": "https://registry.npmjs.org/asap/-/asap-2.0.6.tgz",
+      "integrity": "sha1-5QNHYR1+aQlDIIu9r+vLwvuGbUY="
+    },
+    "async": {
+      "version": "2.0.1",
+      "resolved": "https://registry.npmjs.org/async/-/async-2.0.1.tgz",
+      "integrity": "sha1-twnMAoCpw28J9FNr6CPIOKkEniU=",
+      "requires": {
+        "lodash": "4.17.4"
+      }
+    },
+    "async-limiter": {
+      "version": "1.0.0",
+      "resolved": "https://registry.npmjs.org/async-limiter/-/async-limiter-1.0.0.tgz",
+      "integrity": "sha512-jp/uFnooOiO+L211eZOoSyzpOITMXx1rBITauYykG3BRYPu8h0UcxsPNB04RR5vo4Tyz3+ay17tR6JVf9qzYWg=="
+    },
+    "backoff": {
+      "version": "2.3.0",
+      "resolved": "https://registry.npmjs.org/backoff/-/backoff-2.3.0.tgz",
+      "integrity": "sha1-7nx+OAk/kuRyhZ22NedlJFT8Ieo="
+    },
+    "bindings": {
+      "version": "1.2.1",
+      "resolved": "https://registry.npmjs.org/bindings/-/bindings-1.2.1.tgz",
+      "integrity": "sha1-FK1hE4EtLTfXLme0ystLtyZQXxE="
+    },
+    "bufferutil": {
+      "version": "1.2.1",
+      "resolved": "https://registry.npmjs.org/bufferutil/-/bufferutil-1.2.1.tgz",
+      "integrity": "sha1-N75dNuHgZJIiHmjUdLGsWOUQy9c=",
+      "requires": {
+        "bindings": "1.2.1",
+        "nan": "2.7.0"
+      }
+    },
+    "commander": {
+      "version": "2.1.0",
+      "resolved": "https://registry.npmjs.org/commander/-/commander-2.1.0.tgz",
+      "integrity": "sha1-0SG7roYNmZKj1Re6lvVliOR8Z4E="
+    },
+    "config": {
+      "version": "1.28.1",
+      "resolved": "https://registry.npmjs.org/config/-/config-1.28.1.tgz",
+      "integrity": "sha1-diXSoeTJDxMdinM0eYLZPDhzKC0=",
+      "requires": {
+        "json5": "0.4.0",
+        "os-homedir": "1.0.2"
+      }
+    },
+    "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="
+    },
+    "error-tojson": {
+      "version": "0.0.1",
+      "resolved": "https://registry.npmjs.org/error-tojson/-/error-tojson-0.0.1.tgz",
+      "integrity": "sha1-p7GqlP/ADpB4wuuibiBL2Hzyy7k="
+    },
+    "es6-promise": {
+      "version": "4.1.1",
+      "resolved": "https://registry.npmjs.org/es6-promise/-/es6-promise-4.1.1.tgz",
+      "integrity": "sha512-OaU1hHjgJf+b0NzsxCg7NdIYERD6Hy/PEmFLTjw+b65scuisG3Kt4QoTvJ66BBkPZ581gr0kpoVzKnxniM8nng=="
+    },
+    "esprima": {
+      "version": "4.0.0",
+      "resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.0.tgz",
+      "integrity": "sha512-oftTcaMu/EGrEIu904mWteKIv8vMuOgGYo7EhVJJN00R/EED9DCua/xxHRdYnKtcECzVg7xOWhflvJMnqcFZjw=="
+    },
+    "extend": {
+      "version": "3.0.1",
+      "resolved": "https://registry.npmjs.org/extend/-/extend-3.0.1.tgz",
+      "integrity": "sha1-p1Xqe8Gt/MWjHOfnYtuq3F5jZEQ="
+    },
+    "hoek": {
+      "version": "5.0.2",
+      "resolved": "https://registry.npmjs.org/hoek/-/hoek-5.0.2.tgz",
+      "integrity": "sha512-NA10UYP9ufCtY2qYGkZktcQXwVyYK4zK0gkaFSB96xhtlo6V8tKXdQgx8eHolQTRemaW0uLn8BhjhwqrOU+QLQ=="
+    },
+    "inherits": {
+      "version": "2.0.3",
+      "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz",
+      "integrity": "sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4="
+    },
+    "isbuffer": {
+      "version": "0.0.0",
+      "resolved": "https://registry.npmjs.org/isbuffer/-/isbuffer-0.0.0.tgz",
+      "integrity": "sha1-OMFG2d9Si4v5sHAcPUPPEt8/w5s="
+    },
+    "isemail": {
+      "version": "3.0.0",
+      "resolved": "https://registry.npmjs.org/isemail/-/isemail-3.0.0.tgz",
+      "integrity": "sha512-rz0ng/c+fX+zACpLgDB8fnUQ845WSU06f4hlhk4K8TJxmR6f5hyvitu9a9JdMD7aq/P4E0XdG1uaab2OiXgHlA==",
+      "requires": {
+        "punycode": "2.1.0"
+      }
+    },
+    "joi": {
+      "version": "13.0.2",
+      "resolved": "https://registry.npmjs.org/joi/-/joi-13.0.2.tgz",
+      "integrity": "sha512-kVka3LaHQyENvcMW4WJPSepGM43oCofcKxfs9HbbKd/FrwBAAt4lNNTPKOzSMmV53GIspmNO4U3O2TzoGvxxCA==",
+      "requires": {
+        "hoek": "5.0.2",
+        "isemail": "3.0.0",
+        "topo": "3.0.0"
+      }
+    },
+    "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==",
+      "requires": {
+        "argparse": "1.0.9",
+        "esprima": "4.0.0"
+      }
+    },
+    "json5": {
+      "version": "0.4.0",
+      "resolved": "https://registry.npmjs.org/json5/-/json5-0.4.0.tgz",
+      "integrity": "sha1-BUNS5MTIDIbAkjh31EneF2pzLI0="
+    },
+    "kurento-client": {
+      "version": "git+https://github.com/Kurento/kurento-client-js.git#efb160e85a4b1f376307fe1979c9fbcb5f978393",
+      "requires": {
+        "async": "2.0.1",
+        "error-tojson": "0.0.1",
+        "es6-promise": "4.1.1",
+        "extend": "3.0.1",
+        "inherits": "2.0.3",
+        "kurento-client-core": "github:Kurento/kurento-client-core-js#2160f8e6938f138b52b72a5c5c354d1e5fce1ca0",
+        "kurento-client-elements": "github:Kurento/kurento-client-elements-js#cbd1ff67fbf0faddc9f6f266bb33e449bc9e1f81",
+        "kurento-client-filters": "github:Kurento/kurento-client-filters-js#51308da53e432a2db9559dcdb308d87951417bf0",
+        "kurento-jsonrpc": "github:Kurento/kurento-jsonrpc-js#827827bbeb557e1c1901f5a562c4c700b9a51401",
+        "minimist": "1.2.0",
+        "promise": "7.1.1",
+        "promisecallback": "0.0.4",
+        "reconnect-ws": "github:KurentoForks/reconnect-ws#f287385d75861654528c352e60221f95c9209f8a"
+      },
+      "dependencies": {
+        "kurento-client-core": {
+          "version": "github:Kurento/kurento-client-core-js#2160f8e6938f138b52b72a5c5c354d1e5fce1ca0"
+        },
+        "kurento-client-elements": {
+          "version": "github:Kurento/kurento-client-elements-js#cbd1ff67fbf0faddc9f6f266bb33e449bc9e1f81"
+        },
+        "kurento-client-filters": {
+          "version": "github:Kurento/kurento-client-filters-js#51308da53e432a2db9559dcdb308d87951417bf0"
+        },
+        "kurento-jsonrpc": {
+          "version": "github:Kurento/kurento-jsonrpc-js#827827bbeb557e1c1901f5a562c4c700b9a51401",
+          "requires": {
+            "bufferutil": "1.2.1",
+            "inherits": "2.0.3",
+            "utf-8-validate": "1.2.2",
+            "ws": "1.1.5"
+          }
+        },
+        "reconnect-core": {
+          "version": "github:KurentoForks/reconnect-core#921d43e91578abb2fb2613f585c010c1939cf734",
+          "requires": {
+            "backoff": "2.3.0"
+          }
+        },
+        "reconnect-ws": {
+          "version": "github:KurentoForks/reconnect-ws#f287385d75861654528c352e60221f95c9209f8a",
+          "requires": {
+            "reconnect-core": "github:KurentoForks/reconnect-core#921d43e91578abb2fb2613f585c010c1939cf734",
+            "websocket-stream": "0.5.1"
+          }
+        },
+        "ws": {
+          "version": "1.1.5",
+          "resolved": "https://registry.npmjs.org/ws/-/ws-1.1.5.tgz",
+          "integrity": "sha512-o3KqipXNUdS7wpQzBHSe180lBGO60SoK0yVo3CYJgb2MkobuWuBX6dhkYP5ORCLd55y+SaflMOV5fqAB53ux4w==",
+          "requires": {
+            "options": "0.0.6",
+            "ultron": "1.0.2"
+          }
+        }
+      }
+    },
+    "lodash": {
+      "version": "4.17.4",
+      "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.4.tgz",
+      "integrity": "sha1-eCA6TRwyiuHYbcpkYONptX9AVa4="
+    },
+    "minimist": {
+      "version": "1.2.0",
+      "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.0.tgz",
+      "integrity": "sha1-o1AIsg9BOD7sH7kU9M1d95omQoQ="
+    },
+    "moment": {
+      "version": "2.19.2",
+      "resolved": "https://registry.npmjs.org/moment/-/moment-2.19.2.tgz",
+      "integrity": "sha512-Rf6jiHPEfxp9+dlzxPTmRHbvoFXsh2L/U8hOupUMpnuecHQmI6cF6lUbJl3QqKPko1u6ujO+FxtcajLVfLpAtA=="
+    },
+    "nan": {
+      "version": "2.7.0",
+      "resolved": "https://registry.npmjs.org/nan/-/nan-2.7.0.tgz",
+      "integrity": "sha1-2Vv3IeyHfgjbJ27T/G63j5CDrUY="
+    },
+    "options": {
+      "version": "0.0.6",
+      "resolved": "https://registry.npmjs.org/options/-/options-0.0.6.tgz",
+      "integrity": "sha1-7CLTEoBrtT5zF3Pnza788cZDEo8="
+    },
+    "os-homedir": {
+      "version": "1.0.2",
+      "resolved": "https://registry.npmjs.org/os-homedir/-/os-homedir-1.0.2.tgz",
+      "integrity": "sha1-/7xJiDNuDoM94MFox+8VISGqf7M="
+    },
+    "promise": {
+      "version": "7.1.1",
+      "resolved": "https://registry.npmjs.org/promise/-/promise-7.1.1.tgz",
+      "integrity": "sha1-SJZUxpJha4qlWwck+oCbt9tJxb8=",
+      "requires": {
+        "asap": "2.0.6"
+      }
+    },
+    "promisecallback": {
+      "version": "0.0.4",
+      "resolved": "https://registry.npmjs.org/promisecallback/-/promisecallback-0.0.4.tgz",
+      "integrity": "sha1-uTTxPATkQ2IrTWbeTkLqX2zmbnQ="
+    },
+    "punycode": {
+      "version": "2.1.0",
+      "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.1.0.tgz",
+      "integrity": "sha1-X4Y+3Im5bbCQdLrXlHvwkFbKTn0="
+    },
+    "redis": {
+      "version": "2.8.0",
+      "resolved": "https://registry.npmjs.org/redis/-/redis-2.8.0.tgz",
+      "integrity": "sha512-M1OkonEQwtRmZv4tEWF2VgpG0JWJ8Fv1PhlgT5+B+uNq2cA3Rt1Yt/ryoR+vQNOQcIEgdCdfH0jr3bDpihAw1A==",
+      "requires": {
+        "double-ended-queue": "2.1.0-0",
+        "redis-commands": "1.3.1",
+        "redis-parser": "2.6.0"
+      }
+    },
+    "redis-commands": {
+      "version": "1.3.1",
+      "resolved": "https://registry.npmjs.org/redis-commands/-/redis-commands-1.3.1.tgz",
+      "integrity": "sha1-gdgm9F+pyLIBH0zXoP5ZfSQdRCs="
+    },
+    "redis-parser": {
+      "version": "2.6.0",
+      "resolved": "https://registry.npmjs.org/redis-parser/-/redis-parser-2.6.0.tgz",
+      "integrity": "sha1-Uu0J2srBCPGmMcB+m2mUHnoZUEs="
+    },
+    "safe-buffer": {
+      "version": "5.1.1",
+      "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.1.tgz",
+      "integrity": "sha512-kKvNJn6Mm93gAczWVJg7wH+wGYWNrDHdWvpUmHyEsgCtIwwo3bqPtV4tR5tuPaUhTOo/kvhVwd8XwwOllGYkbg=="
+    },
+    "sdp-transform": {
+      "version": "2.3.0",
+      "resolved": "https://registry.npmjs.org/sdp-transform/-/sdp-transform-2.3.0.tgz",
+      "integrity": "sha1-V6lXWUIEHYV3qGnXx01MOgvYiPY="
+    },
+    "sprintf-js": {
+      "version": "1.0.3",
+      "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz",
+      "integrity": "sha1-BOaSb2YolTVPPdAVIDYzuFcpfiw="
+    },
+    "through": {
+      "version": "2.3.8",
+      "resolved": "https://registry.npmjs.org/through/-/through-2.3.8.tgz",
+      "integrity": "sha1-DdTJ/6q8NXlgsbckEV1+Doai4fU="
+    },
+    "tinycolor": {
+      "version": "0.0.1",
+      "resolved": "https://registry.npmjs.org/tinycolor/-/tinycolor-0.0.1.tgz",
+      "integrity": "sha1-MgtaUtg6u1l42Bo+iH1K77FaYWQ="
+    },
+    "topo": {
+      "version": "3.0.0",
+      "resolved": "https://registry.npmjs.org/topo/-/topo-3.0.0.tgz",
+      "integrity": "sha512-Tlu1fGlR90iCdIPURqPiufqAlCZYzLjHYVVbcFWDMcX7+tK8hdZWAfsMrD/pBul9jqHHwFjNdf1WaxA9vTRRhw==",
+      "requires": {
+        "hoek": "5.0.2"
+      }
+    },
+    "ultron": {
+      "version": "1.0.2",
+      "resolved": "https://registry.npmjs.org/ultron/-/ultron-1.0.2.tgz",
+      "integrity": "sha1-rOEWq1V80Zc4ak6I9GhTeMiy5Po="
+    },
+    "utf-8-validate": {
+      "version": "1.2.2",
+      "resolved": "https://registry.npmjs.org/utf-8-validate/-/utf-8-validate-1.2.2.tgz",
+      "integrity": "sha1-i7hxpHQeCFxwSHynrNvX1tNgKes=",
+      "requires": {
+        "bindings": "1.2.1",
+        "nan": "2.4.0"
+      },
+      "dependencies": {
+        "nan": {
+          "version": "2.4.0",
+          "resolved": "https://registry.npmjs.org/nan/-/nan-2.4.0.tgz",
+          "integrity": "sha1-+zxZ1F/k7/4hXwuJD4rfbrMtIjI="
+        }
+      }
+    },
+    "uuid": {
+      "version": "3.1.0",
+      "resolved": "https://registry.npmjs.org/uuid/-/uuid-3.1.0.tgz",
+      "integrity": "sha512-DIWtzUkw04M4k3bf1IcpS2tngXEL26YUD2M0tMDUpnUrz2hgzUBlD55a4FjdLGPvfHxS6uluGWvaVEqgBcVa+g=="
+    },
+    "websocket-stream": {
+      "version": "0.5.1",
+      "resolved": "https://registry.npmjs.org/websocket-stream/-/websocket-stream-0.5.1.tgz",
+      "integrity": "sha1-YizR8FZvuEzgpNb4VFJvPcTXDkg=",
+      "requires": {
+        "isbuffer": "0.0.0",
+        "through": "2.3.8",
+        "ws": "0.4.32"
+      },
+      "dependencies": {
+        "nan": {
+          "version": "1.0.0",
+          "resolved": "https://registry.npmjs.org/nan/-/nan-1.0.0.tgz",
+          "integrity": "sha1-riT4hQgY1mL8q1rPfzuVv6oszzg="
+        },
+        "ws": {
+          "version": "0.4.32",
+          "resolved": "https://registry.npmjs.org/ws/-/ws-0.4.32.tgz",
+          "integrity": "sha1-eHphVEFPPJntg8V3IVOyD+sM7DI=",
+          "requires": {
+            "commander": "2.1.0",
+            "nan": "1.0.0",
+            "options": "0.0.6",
+            "tinycolor": "0.0.1"
+          }
+        }
+      }
+    },
+    "ws": {
+      "version": "3.3.2",
+      "resolved": "https://registry.npmjs.org/ws/-/ws-3.3.2.tgz",
+      "integrity": "sha512-t+WGpsNxhMR4v6EClXS8r8km5ZljKJzyGhJf7goJz9k5Ye3+b5Bvno5rjqPuIBn5mnn5GBb7o8IrIWHxX1qOLQ==",
+      "requires": {
+        "async-limiter": "1.0.0",
+        "safe-buffer": "5.1.1",
+        "ultron": "1.1.1"
+      },
+      "dependencies": {
+        "ultron": {
+          "version": "1.1.1",
+          "resolved": "https://registry.npmjs.org/ultron/-/ultron-1.1.1.tgz",
+          "integrity": "sha512-UIEXBNeYmKptWH6z8ZnqTeS8fV74zG0/eRU9VGkpzz+LIJNs8W/zM/L+7ctCkRrgbNnnR0xxw4bKOr0cW0N0Og=="
+        }
+      }
+    }
+  }
+}
diff --git a/labs/bbb-webrtc-sfu/package.json b/labs/bbb-webrtc-sfu/package.json
new file mode 100644
index 0000000000000000000000000000000000000000..e34571864c335e92e9c96fee040200379f4140bf
--- /dev/null
+++ b/labs/bbb-webrtc-sfu/package.json
@@ -0,0 +1,20 @@
+{
+  "name": "bbb-webrtc-sfu",
+  "version": "0.0.2",
+  "private": true,
+  "scripts": {
+    "start": "node server.js"
+  },
+  "dependencies": {
+    "joi": "^13.0.2",
+    "kurento-client": "git+https://github.com/Kurento/kurento-client-js.git#master",
+    "moment": "^2.19.2",
+    "redis": "^2.8.0",
+    "sdp-transform": "^2.3.0",
+    "readable-id": "^1.0.0",
+    "ws": "^3.3.2",
+    "config": "^1.26.1",
+    "js-yaml": "^3.8.3"
+  },
+  "optionalDependencies": {}
+}
diff --git a/labs/bbb-webrtc-sfu/server.js b/labs/bbb-webrtc-sfu/server.js
new file mode 100755
index 0000000000000000000000000000000000000000..3252a2b96febc93847754de12567273fafc6893f
--- /dev/null
+++ b/labs/bbb-webrtc-sfu/server.js
@@ -0,0 +1,67 @@
+/*
+ * Lucas Fialho Zawacki
+ * Paulo Renato Lanzarin
+ * (C) Copyright 2017 Bigbluebutton
+ *
+ */
+
+const ConnectionManager = require('./lib/connection-manager/ConnectionManager');
+const HttpServer = require('./lib/connection-manager/HttpServer');
+const server = new HttpServer();
+const WebsocketConnectionManager = require('./lib/connection-manager/WebsocketConnectionManager');
+const cp = require('child_process');
+
+let screenshareProc = cp.fork('./lib/screenshare/ScreenshareProcess', {
+    // Pass over all of the environment.
+    env: process.ENV,
+    // Share stdout/stderr, so we can hear the inevitable errors.
+    silent: false
+});
+
+let videoProc = cp.fork('./lib/video/VideoProcess.js', {
+    // Pass over all of the environment.
+    env: process.ENV,
+    // Share stdout/stderr, so we can hear the inevitable errors.
+    silent: false
+});
+
+let onMessage = function (message) {
+  console.log('event','child message',this.pid,message);
+};
+
+let onError = function(e) {
+  console.log('event','child error',this.pid,e);
+};
+
+let onDisconnect = function(e) {
+  console.log(e);
+  console.log('event','child disconnect',this.pid,'killing...');
+  this.kill();
+};
+
+screenshareProc.on('message',onMessage);
+screenshareProc.on('error',onError);
+screenshareProc.on('disconnect',onDisconnect);
+
+videoProc.on('message',onMessage);
+videoProc.on('error',onError);
+videoProc.on('disconnect',onDisconnect);
+
+const CM = new ConnectionManager(screenshareProc, videoProc);
+
+let websocketManager = new WebsocketConnectionManager(server.getServerObject(), '/bbb-webrtc-sfu');
+
+process.on('SIGTERM', process.exit)
+process.on('SIGINT', process.exit)
+process.on('uncaughtException', function (error) {
+  console.log(error.stack);
+  process.exit('1');
+});
+
+
+CM.setHttpServer(server);
+CM.addAdapter(websocketManager);
+
+CM.listen(() => {
+  console.log(" [SERVER] Server started");
+});
diff --git a/labs/kurento-screenshare/.gitignore b/labs/kurento-screenshare/.gitignore
deleted file mode 100644
index 40b878db5b1c97fc77049537a71bb2e249abe5dc..0000000000000000000000000000000000000000
--- a/labs/kurento-screenshare/.gitignore
+++ /dev/null
@@ -1 +0,0 @@
-node_modules/
\ No newline at end of file
diff --git a/labs/kurento-screenshare/config/default.yml b/labs/kurento-screenshare/config/default.yml
deleted file mode 100644
index 46b6e2629ecf6e34603f5a34c1288266d0583918..0000000000000000000000000000000000000000
--- a/labs/kurento-screenshare/config/default.yml
+++ /dev/null
@@ -1,8 +0,0 @@
-kurentoUrl: "KURENTOURL"
-kurentoIp: "KURENTOIP"
-localIpAddress: "HOST"
-acceptSelfSignedCertificate: false
-redisHost : "127.0.0.1"
-redisPort : "6379"
-minVideoPort: 30000
-maxVideoPort: 33000
diff --git a/labs/kurento-screenshare/keys/README.md b/labs/kurento-screenshare/keys/README.md
deleted file mode 100644
index 5bc681a1c8d2ece88651b6ee63d410536eae50f6..0000000000000000000000000000000000000000
--- a/labs/kurento-screenshare/keys/README.md
+++ /dev/null
@@ -1,2 +0,0 @@
-This folder contains a dummy self-signed certificate only for demo purposses,
-**DON'T USE IT IN PRODUCTION**.
diff --git a/labs/kurento-screenshare/keys/server.crt b/labs/kurento-screenshare/keys/server.crt
deleted file mode 100644
index 65e608dad5d9fb19f68ac486e6189dfc67dcd2ff..0000000000000000000000000000000000000000
--- a/labs/kurento-screenshare/keys/server.crt
+++ /dev/null
@@ -1,19 +0,0 @@
------BEGIN CERTIFICATE-----
-MIIDBjCCAe4CCQCuf5QfyX2oDDANBgkqhkiG9w0BAQsFADBFMQswCQYDVQQGEwJB
-VTETMBEGA1UECAwKU29tZS1TdGF0ZTEhMB8GA1UECgwYSW50ZXJuZXQgV2lkZ2l0
-cyBQdHkgTHRkMB4XDTE0MDkyOTA5NDczNVoXDTE1MDkyOTA5NDczNVowRTELMAkG
-A1UEBhMCQVUxEzARBgNVBAgMClNvbWUtU3RhdGUxITAfBgNVBAoMGEludGVybmV0
-IFdpZGdpdHMgUHR5IEx0ZDCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEB
-AMJOyOHJ+rJWJEQ7P7kKoWa31ff7hKNZxF6sYE5lFi3pBYWIY6kTN/iUaxJLROFo
-FhoC/M/STY76rIryix474v/6cRoG8N+GQBEn4IAP1UitWzVO6pVvBaIt5IKlhhfm
-YA1IMweCd03vLcaHTddNmFDBTks7QDwfenTaR5VjKYc3OtEhcG8dgLAnOjbbk2Hr
-8wter2IeNgkhya3zyoXnTLT8m8IMg2mQaJs62Xlo9gs56urvVDWG4rhdGybj1uwU
-ZiDYyP4CFCUHS6UVt12vADP8vjbwmss2ScGsIf0NjaU+MpSdEbB82z4b2NiN8Wq+
-rFA/JbvyeoWWHMoa7wkVs1MCAwEAATANBgkqhkiG9w0BAQsFAAOCAQEAYLRwV9fo
-AOhJfeK199Tv6oXoNSSSe10pVLnYxPcczCVQ4b9SomKFJFbmwtPVGi6w3m+8mV7F
-9I2WKyeBHzmzfW2utZNupVybxgzEjuFLOVytSPdsB+DcJomOi8W/Cf2Vk8Wykb/t
-Ctr1gfOcI8rwEGKxm279spBs0u1snzoLyoimbMbiXbC82j1IiN3Jus08U07m/j7N
-hRBCpeHjUHT3CRpvYyTRnt+AyBd8BiyJB7nWmcNI1DksXPfehd62MAFS9e1ZE+dH
-Aavg/U8VpS7pcCQcPJvIJ2hehrt8L6kUk3YUYqZ0OeRZK27f2R5+wFlDF33esm3N
-dCSsLJlXyqAQFg==
------END CERTIFICATE-----
diff --git a/labs/kurento-screenshare/keys/server.csr b/labs/kurento-screenshare/keys/server.csr
deleted file mode 100644
index 6615b130471ce23cf8d980df5a308694ed06695b..0000000000000000000000000000000000000000
--- a/labs/kurento-screenshare/keys/server.csr
+++ /dev/null
@@ -1,16 +0,0 @@
------BEGIN CERTIFICATE REQUEST-----
-MIICijCCAXICAQAwRTELMAkGA1UEBhMCQVUxEzARBgNVBAgMClNvbWUtU3RhdGUx
-ITAfBgNVBAoMGEludGVybmV0IFdpZGdpdHMgUHR5IEx0ZDCCASIwDQYJKoZIhvcN
-AQEBBQADggEPADCCAQoCggEBAMJOyOHJ+rJWJEQ7P7kKoWa31ff7hKNZxF6sYE5l
-Fi3pBYWIY6kTN/iUaxJLROFoFhoC/M/STY76rIryix474v/6cRoG8N+GQBEn4IAP
-1UitWzVO6pVvBaIt5IKlhhfmYA1IMweCd03vLcaHTddNmFDBTks7QDwfenTaR5Vj
-KYc3OtEhcG8dgLAnOjbbk2Hr8wter2IeNgkhya3zyoXnTLT8m8IMg2mQaJs62Xlo
-9gs56urvVDWG4rhdGybj1uwUZiDYyP4CFCUHS6UVt12vADP8vjbwmss2ScGsIf0N
-jaU+MpSdEbB82z4b2NiN8Wq+rFA/JbvyeoWWHMoa7wkVs1MCAwEAAaAAMA0GCSqG
-SIb3DQEBCwUAA4IBAQBMszYHMpklgTF/3h1zAzKXUD9NrtZp8eWhL06nwVjQX8Ai
-EaCUiW0ypstokWcH9+30chd2OD++67NbxYUEucH8HrKpOoy6gs5L/mqgQ9Npz3OT
-TB1HI4kGtpVuUQ5D7L0596tKzMX/CgW/hRcHWl+PDkwGhQs1qZcJ8QN+YP6AkRrO
-5sDdDB/BLrB9PtBQbPrYIQcHQ7ooYWz/G+goqRxzZ6rt0aU2uAB6l7c82ADLAqFJ
-qlw+xqVzEETVfqM5TXKK/wV3hgm4oSX5Q4SHLKF94ODOkWcnV4nfIKz7y+5XcQ3p
-PrGimI1br07okC5rO9cgLCR0Ks20PPFcM0FvInW/
------END CERTIFICATE REQUEST-----
diff --git a/labs/kurento-screenshare/keys/server.key b/labs/kurento-screenshare/keys/server.key
deleted file mode 100644
index a69a0a279daf6a68b9eff057204cd05af1b27a5a..0000000000000000000000000000000000000000
--- a/labs/kurento-screenshare/keys/server.key
+++ /dev/null
@@ -1,27 +0,0 @@
------BEGIN RSA PRIVATE KEY-----
-MIIEogIBAAKCAQEAwk7I4cn6slYkRDs/uQqhZrfV9/uEo1nEXqxgTmUWLekFhYhj
-qRM3+JRrEktE4WgWGgL8z9JNjvqsivKLHjvi//pxGgbw34ZAESfggA/VSK1bNU7q
-lW8Foi3kgqWGF+ZgDUgzB4J3Te8txodN102YUMFOSztAPB96dNpHlWMphzc60SFw
-bx2AsCc6NtuTYevzC16vYh42CSHJrfPKhedMtPybwgyDaZBomzrZeWj2Cznq6u9U
-NYbiuF0bJuPW7BRmINjI/gIUJQdLpRW3Xa8AM/y+NvCayzZJwawh/Q2NpT4ylJ0R
-sHzbPhvY2I3xar6sUD8lu/J6hZYcyhrvCRWzUwIDAQABAoIBACwt56TW3MZxqZtN
-8WYsUZheUispJ/ZQMcLo5JjOiSV1Jwk+gpJtyTse291z+bxagzP02/CQu4u32UVa
-cmE0cp+LHO4zB8964dREwdm8P91fdS6Au/uwG5LNZniCFCQZAFvkv52Ef4XbzQen
-uf4rKWerHBck6K0C5z/sZXxE6KtScE2ZLUmkhO0nkHM6MA6gFk2OMnB+oDTOWWPt
-1mlreQlzuMYG/D4axviRYrOSYCE5Qu1SOw/DEOLQqqeBjQrKtAyOlFHZsIR6lBfe
-KHMChPUcYIwaowt2DcqH/A+AFXRtaifa6DvH8Yul+2vAp47UEpaenVfM5bpN33XV
-EzerjtECgYEA+xiXzblek67iQgRpc9eHSoqs4iRLhae8s8kpAG51Jz46Je+Dmium
-XV769oiUGUxBeoUb7ryW+4MOzHJaA1BfGejQSvwLIB9e4cnikqnAArcqbcAcOCL1
-aYYDiSmSmN/AokNZlPKEBFXP9bzXrU9smQJWNTHlcRl7JXfnwF+jwNsCgYEAxhpE
-SBr9vlUVHNh/S6C5i80NIYg6jCy2FgsmuzEqmcqV0pTyzegmq8bru+QmuvoUj2o4
-nVv4J9d1fLF6ECUVk9aK8UdJOOB6hAfurOdJCArgrsY/9t4uDzXfbPCdfSNQITE0
-XgeNGQX1EzvwwkBmyZKk0kLIr3syP8ZCWfXDROkCgYBR+dF1pJMv++R6UR5sZ20P
-9P5ERj0xwXVl7MKqFWXCDhrFz9BTQPTrftrIKgbPy4mFCnf4FTHlov/t11dzxYWG
-2+9Ey8yGDDfZ1yNVZn39ZPdBJXsRCLi+XrZAzYXCyyoEz6ArdJGNKMbgH2r6dfeq
-bIzgiQ2zQvJlZSQQNiksCQKBgCgwzAmU8EXdHRttEOZXBU3HnBJhgP9PUuHGAWWY
-4/uvjhXbAiekIbRX9xt3fiQQ+HrgIfxK3F246K0TlKAR5f7IWAf7Xm+bmz+OHG4X
-vklTa6IJtpBvIwkS9PE1H75zm54gTW+GOKoK+12bm4zNZA0hIy9FPVHcvKUTpAJ8
-SdGBAoGAHLtJnB1NO4EgO6WtLQMXt7HrIbup8eZi8/82gC3422C+ooKIrYQ07qSw
-nBOO/G0OB4yd6vCE2x5+TWSSCYGgG5A8aIv5qP76RP4hovGHxG/y2tfotw5UuOrh
-nFWlTP4Urs8PeykvK9ao8r/T8BnPIC16U6ENYvAc0mRlFA2j1GA=
------END RSA PRIVATE KEY-----
diff --git a/labs/kurento-screenshare/lib/ConnectionManager.js b/labs/kurento-screenshare/lib/ConnectionManager.js
deleted file mode 100644
index 2dbef1e75d32f7759337ef0f56a3b11ced7d4ed6..0000000000000000000000000000000000000000
--- a/labs/kurento-screenshare/lib/ConnectionManager.js
+++ /dev/null
@@ -1,206 +0,0 @@
-/*
- * Lucas Fialho Zawacki
- * Paulo Renato Lanzarin
- * (C) Copyright 2017 Bigbluebutton
- *
- */
-
-'use strict'
-
-const cookieParser = require('cookie-parser')
-const express = require('express');
-const session = require('express-session')
-const wsModule = require('./websocket');
-const http = require('http');
-const fs = require('fs');
-const BigBlueButtonGW = require('./bbb/pubsub/bbb-gw');
-var Screenshare = require('./screenshare');
-var C = require('./bbb/messages/Constants');
-
-// Global variables
-
-module.exports = class ConnectionManager {
-
-  constructor (settings, logger) {
-    this._logger = logger;
-    this._clientId = 0;
-    this._app = express();
-    this._screenshareSessions = {};
-
-    this._setupExpressSession();
-    this._setupHttpServer();
-  }
-
-  _setupExpressSession() {
-    this._app.use(cookieParser());
-
-    this._sessionHandler = session({
-      secret : 'Shawarma', rolling : true, resave : true, saveUninitialized : true
-    });
-
-    this._app.use(this._sessionHandler);
-  }
-
-  _setupHttpServer() {
-    let self = this;
-    /*
-     * Server startup
-     */
-    this._httpServer = http.createServer(this._app).listen(3008, function() {
-      console.log(' [*] Running node-apps connection manager.');
-    });
-
-    /*
-     * Management of sessions
-     */
-    this._wss = new wsModule.Server({
-      server : this._httpServer,
-      path : '/kurento-screenshare'
-    });
-
-
-    // TODO isolate this
-    this._bbbGW = new BigBlueButtonGW();
-
-    this._bbbGW.addSubscribeChannel(C.FROM_BBB_TRANSCODE_SYSTEM_CHAN, function(error, redisWrapper) {
-      if(error) {
-        console.log(' Could not connect to transcoder redis channel, finishing app...');
-        self._stopAll();
-      }
-      console.log('  [server] Successfully subscribed to redis channel');
-    });
-
-
-    this._wss.on('connection', self._onNewConnection.bind(self));
-  }
-
-  _onNewConnection(webSocket) {
-    let self = this;
-    let connectionId;
-    let request = webSocket.upgradeReq;
-    let sessionId;
-    let response = {
-      writeHead : {}
-    };
-
-    this._sessionHandler(request, response, function(err) {
-      connectionId = request.session.id + "_" + self._clientId++;
-      console.log('Connection received with connectionId ' + connectionId);
-    });
-
-    webSocket.on('error', function(error) {
-      console.log('Connection ' + connectionId + ' error');
-      self._stopSession(sessionId);
-    });
-
-    webSocket.on('close', function() {
-      console.log('Connection ' + connectionId + ' closed');
-      self._stopSession(sessionId);
-    });
-
-    webSocket.on('message', function(_message) {
-      let message = JSON.parse(_message);
-      let session;
-      // The sessionId is voiceBridge for screensharing sessions
-      sessionId = message.voiceBridge;
-
-      if(self._screenshareSessions[sessionId]) {
-        session = self._screenshareSessions[sessionId];
-      }
-
-      switch (message.id) {
-
-        case 'presenter':
-
-          // Checking if there's already a Screenshare session started
-          // because we shouldn't overwrite it
-
-          if(session) {
-            break;
-          }
-
-          session = new Screenshare(webSocket, connectionId, self._bbbGW,
-              sessionId, message.callerName, message.vh, message.vw,
-              message.internalMeetingId);
-
-          self._screenshareSessions[sessionId] = {}
-          self._screenshareSessions[sessionId] = session;
-
-          // starts presenter by sending sessionID, websocket and sdpoffer
-          session._startPresenter(connectionId, webSocket, message.sdpOffer, function(error, sdpAnswer) {
-            console.log(" Started presenter " + connectionId);
-            if (error) {
-              return webSocket.send(JSON.stringify({
-                id : 'presenterResponse',
-                response : 'rejected',
-                message : error
-              }));
-            }
-
-            webSocket.send(JSON.stringify({
-              id : 'presenterResponse',
-              response : 'accepted',
-              sdpAnswer : sdpAnswer
-            }));
-            console.log("  [websocket] Sending presenterResponse \n" + sdpAnswer);
-          });
-          break;
-
-        case 'viewer':
-          console.log('Viewer message => [' + message.id + '] connection [' + connectionId + '][' + message.presenterId + '][' + message.sessionId + '][' + message.callerName + ']');
-
-          break;
-        case 'stop':
-
-          console.log('[' + message.id + '] connection ' + connectionId);
-
-          if (session) {
-            session._stop(sessionId);
-          } else {
-            console.log(" [stop] Why is there no session on STOP?");
-          }
-          break;
-
-        case 'onIceCandidate':
-          if (session) {
-            session._onIceCandidate(message.candidate);
-          } else {
-            console.log(" [iceCandidate] Why is there no session on ICE CANDIDATE?");
-          }
-          break;
-
-        case 'ping':
-          webSocket.send(JSON.stringify({
-            id : 'pong',
-            response : 'accepted'
-          }));
-          break;
-
-        default:
-          webSocket.sendMessage({ id : 'error', message : 'Invalid message ' + message });
-          break;
-      }
-    });
-  }
-
-  _stopSession(sessionId) {
-    console.log(' [>] Stopping session ' + sessionId);
-    let session = this._screenshareSessions[sessionId];
-    if(typeof session !== 'undefined' && typeof session._stop === 'function') {
-      session._stop();
-    }
-
-    delete this._screenshareSessions[sessionId];
-  }
-
-  _stopAll() {
-    console.log('\n [x] Stopping everything! ');
-    let sessionIds = Object.keys(this._screenshareSessions);
-
-    for (let i = 0; i < sessionIds.length; i++) {
-      this._stopSession(sessionIds[i]);
-    }
-
-    setTimeout(process.exit, 1000);
-  }
-}
diff --git a/labs/kurento-screenshare/lib/bbb/pubsub/RedisWrapper.js b/labs/kurento-screenshare/lib/bbb/pubsub/RedisWrapper.js
deleted file mode 100644
index da92167e4169e8a03ee3d3cb3e03b78405c1049f..0000000000000000000000000000000000000000
--- a/labs/kurento-screenshare/lib/bbb/pubsub/RedisWrapper.js
+++ /dev/null
@@ -1,97 +0,0 @@
-/**
- * @classdesc
- * Redis wrapper class for connecting to Redis channels
- */
-
-/* Modules */
-
-var redis = require('redis');
-var config = require('config');
-var Constants = require('../messages/Constants.js');
-var util = require('util');
-const EventEmitter = require('events').EventEmitter;
-const _retryThreshold = 1000 * 60 * 60;
-const _maxRetries = 10;
-
-
-/* Public members */
-
-var RedisWrapper = function(subpattern) {
-  // Redis PubSub client holders
-  this.redisCli = null;
-  this.redisPub = null;
-  // Pub and Sub channels/patterns
-  this.subpattern = subpattern;
-  EventEmitter.call(this);
-}
-
-util.inherits(RedisWrapper, EventEmitter);
-
-RedisWrapper.prototype.startRedis = function(callback) {
-  var self = this;
-  if (this.redisCli) {
-    console.log("  [RedisWrapper] Redis Client already exists");
-    callback(false, this);
-  }
-
-  var options = {
-    host : config.get('redisHost'),
-    port : config.get('redisPort'),
-    //password: config.get('redis.password')
-    retry_strategy: redisRetry
-  };
-
-  this.redisCli = redis.createClient(options);
-  this.redisPub = redis.createClient(options);
-
-  console.log("  [RedisWrapper] Trying to subscribe to redis channel");
-
-  this.redisCli.on("psubscribe", function (channel, count) {
-    console.log(" [RedisWrapper] Successfully subscribed to pattern [" + channel + "]");
-  });
-
-  this.redisCli.on("pmessage", self.onMessage.bind(self));
-  this.redisCli.psubscribe(this.subpattern);
-
-  console.log("  [RedisWrapper] Started Redis client at " + options.host + ":" + options.port +
-    " for subscription pattern: " + this.subpattern);
-
-  callback(false, this);
-};
-
-RedisWrapper.prototype.stopRedis = function(callback) {
-  if (this.redisCli){
-    this.redisCli.quit();
-  }
-  callback(false);
-};
-
-RedisWrapper.prototype.publishToChannel = function(message, channel) {
-  if(this.redisPub) {
-    console.log("  [RedisWrapper] Sending message to channel [" + channel + "]: " + message);
-    this.redisPub.publish(channel, message);
-  }
-};
-
-RedisWrapper.prototype.onMessage = function(pattern, channel, message) {
-  console.log(" [RedisWrapper] Message received from channel [" + channel +  "] : " + message);
-  // use event emitter to throw new message
-  this.emit(Constants.REDIS_MESSAGE, message);
-}
-
-/* Private members */
-
-function redisRetry(options) {
-  if (options.error && options.error.code === 'ECONNREFUSED') {
-    return new Error('The server refused the connection');
-  }
-  if (options.total_retry_time > _retryThreshold) {
-    return new Error('Retry time exhausted');
-  }
-  if (options.times_connected > _maxRetries) {
-    return undefined;
-  }
-  return Math.max(options.attempt * 100, 3000);
-};
-
-module.exports = RedisWrapper;
diff --git a/labs/kurento-screenshare/lib/bbb/pubsub/bbb-gw.js b/labs/kurento-screenshare/lib/bbb/pubsub/bbb-gw.js
deleted file mode 100644
index 1abbb25aa010d9dc2a18d09edb2064c8516d9463..0000000000000000000000000000000000000000
--- a/labs/kurento-screenshare/lib/bbb/pubsub/bbb-gw.js
+++ /dev/null
@@ -1,105 +0,0 @@
-/**
- * @classdesc
- * BigBlueButton redis gateway for bbb-screenshare node app
- */
-
-/* Modules */
-
-var C = require('../messages/Constants.js');
-var RedisWrapper = require('./RedisWrapper.js');
-var config = require('config');
-var util = require('util');
-var EventEmitter = require('events').EventEmitter;
-
-/* Public members */
-
-var BigBlueButtonGW = function () {
-  this.redisClients = null 
-  EventEmitter.call(this);
-};
-
-util.inherits(BigBlueButtonGW, EventEmitter);
-
-BigBlueButtonGW.prototype.addSubscribeChannel = function (channel, callback) {
-  var self = this;
-
-  if (this.redisClients === null) {
-    this.redisClients = {};
-  }
-
-  if (this.redisClients[channel]) {
-    return callback(null, this.redisClients[channel]);
-  }
-
-  var wrobj = new RedisWrapper(channel);
-  this.redisClients[channel] = {};
-  this.redisClients[channel] = wrobj;
-  wrobj.startRedis(function(error, redisCli) {
-    if(error) {
-      console.log("  [BigBlueButtonGW] Could not start redis client for channel " + channel);
-      return callback(error);
-    }
-
-    console.log("  [BigBlueButtonGW] Added redis client to this.redisClients[" + channel + "]");
-    wrobj.on(C.REDIS_MESSAGE, self.incomingMessage.bind(self));
-
-    return callback(null, wrobj);
-  });
-};
-
-/**
- * Capture messages from subscribed channels and emit an event with it's
- * identifier and payload. Check Constants.js for the identifiers.
- *
- * @param {Object} message  Redis message
- */
-BigBlueButtonGW.prototype.incomingMessage = function (message) {
-  var msg = JSON.parse(message);
-
-  // Trying to parse both message types, 1x and 2x
-  if (msg.header) {
-    var header = msg.header;
-    var payload = msg.payload;
-  }
-  else if (msg.core) {
-    var header = msg.core.header;
-    var payload = msg.core.body;
-  }
-
-  if (header){
-    switch (header.name) {
-      // interoperability with 1.1
-      case C.START_TRANSCODER_REPLY:
-        this.emit(C.START_TRANSCODER_REPLY, payload);
-        break;
-      case C.STOP_TRANSCODER_REPLY:
-        this.emit(C.STOP_TRANSCODER_REPLY, payload);
-        break;
-      // 2x messages
-      case C.START_TRANSCODER_RESP_2x:
-        payload[C.MEETING_ID_2x] = header[C.MEETING_ID_2x];
-
-        this.emit(C.START_TRANSCODER_RESP_2x, payload);
-        break;
-      case C.STOP_TRANSCODER_RESP_2x:
-        payload[C.MEETING_ID_2x] = header[C.MEETING_ID_2x];
-        this.emit(C.STOP_TRANSCODER_RESP_2x, payload);
-        break;
-
-      default:
-        console.log("  [BigBlueButtonGW] Unknown Redis message with ID =>" + header.name);
-    }
-  }
-};
-
-BigBlueButtonGW.prototype.publish = function (message, channel, callback) {
-  for(var client in this.redisClients) {
-    if(typeof this.redisClients[client].publishToChannel === 'function') {
-      this.redisClients[client].publishToChannel(message, channel);
-      return callback(null);
-    }
-  }
-  return callback("Client not found");
-};
-
-module.exports = BigBlueButtonGW;
diff --git a/labs/kurento-screenshare/lib/media-controller.js b/labs/kurento-screenshare/lib/media-controller.js
deleted file mode 100644
index df9697ec6984deb5f425fbb7c210fba4b6ce62e6..0000000000000000000000000000000000000000
--- a/labs/kurento-screenshare/lib/media-controller.js
+++ /dev/null
@@ -1,141 +0,0 @@
-'use strict'
-
-const Constants = require('./bbb/messages/Constants.js');
-const config = require('config');
-const kurento = require('kurento-client');
-const mediaServerClient = null;
-
-var _mediaPipelines = {};
-var _mediaElements= {};
-
-function createMediaPipeline(id, callback) {
-  console.log(' [media] Creating media pipeline for ' + id);
-  getMediaServerClient(function (error, mediaServerClient) {
-    mediaServerClient.create('MediaPipeline', function(err, pipeline) {
-      if (error) {
-        console.log("Could not find media server at address " + kurentoUrl);
-        return callback(error);
-      }
-      return callback(null , pipeline);
-    });
-  });
-};
-
-function getMediaServerClient (callback) {
-  let kurentoUrl = config.get('kurentoUrl');
-  if (mediaServerClient) {
-    callback(null, mediaServerClient);
-  }
-  else {
-    kurento(kurentoUrl, function(error, _mediaServerClient) {
-      if (error) {
-        console.log("Could not find media server at address " + kurentoUrl);
-        return callback(error, null);
-      }
-
-      console.log(" [server] Initiating kurento client. Connecting to: " + kurentoUrl);
-      return callback(null, _mediaServerClient);
-    });
-  }
-};
-
-/* Public members */
-module.exports = {
-
-  createMediaElement : function (conference, type, callback) {
-    let self = this;
-    self.getMediaPipeline(conference, function(error, pipeline) {
-
-      pipeline.create(type, function(error, mediaElement) {
-        if (error) {
-          return callback(error, null);
-        }
-        console.log("  [MediaController] Created [" + type + "] media element: " + mediaElement.id);
-        _mediaElements[mediaElement.id] = mediaElement;
-        return callback(null, mediaElement);
-      });
-    });
-  },
-
-  connectMediaElements : function (sourceId, sinkId, type, callback) {
-    let source = _mediaElements[sourceId];
-    let sink = _mediaElements[sinkId];
-
-    if (source && sink) {
-      if (type === 'ALL') {
-        source.connect(sink, function (error) {
-          return callback (error);
-        });
-      } else {
-        console.log(typeof source.connect);
-        source.connect(sink, type, function (error) {
-          return callback (error);
-        });
-      }
-    } else {
-      return callback ("Failed to connect " + type + ": " + sourceId + " to " + sinkId);
-    }
-  },
-
-  releaseMediaElement : function (elementId) {
-    let mediaElement = _mediaElements[elementId];
-
-    if (typeof mediaElement !== 'undefined' && typeof mediaElement.release === 'function') {
-      mediaElement.release();
-    }
-  },
-
-  releasePipeline: function (pipelineId) {
-    let MediaPipeline = _mediaPipelines[pipelineId];
-
-    if (typeof mediaElement !== 'undefined' && typeof mediaElement.release === 'function') {
-      mediaElement.release();
-    }
-  },
-
-  processOffer : function (elementId, sdpOffer, callback) {
-    let mediaElement = _mediaElements[elementId];
-
-    if (typeof mediaElement !== 'undefined' && typeof mediaElement.processOffer === 'function') {
-      mediaElement.processOffer (sdpOffer, function (error, sdpAnswer) {
-        return callback (error, sdpAnswer);
-      });
-    } else {
-      return callback ("  [MediaController/processOffer] There is no element " + elementId, null);
-    }
-  },
-
-  getMediaPipeline : function(conference, callback) {
-    let self = this;
-
-    if (_mediaPipelines[conference]) {
-      console.log(' [media] Pipeline already exists. ' + JSON.stringify(_mediaPipelines, null, 2));
-      return callback(null, _mediaPipelines[conference]);
-    } else {
-      createMediaPipeline(conference, function(error, pipeline) {
-        _mediaPipelines[conference] = pipeline;
-        return callback(error, pipeline);
-      });
-    }
-  },
-
-  addIceCandidate : function (elementId, candidate) {
-    let mediaElement = _mediaElements[elementId];
-
-    if (typeof mediaElement !== 'undefined' && typeof mediaElement.addIceCandidate === 'function') {
-      mediaElement.addIceCandidate(candidate);
-    }
-  },
-
-  gatherCandidates : function (elementId, callback) {
-    let mediaElement = _mediaElements[elementId];
-
-    if (typeof mediaElement !== 'undefined' && typeof mediaElement.gatherCandidates === 'function') {
-      mediaElement.gatherCandidates(function (error) {
-        return callback(error);  
-      });
-    } else {
-      return callback ("  [MediaController/gatherCandidates] There is no element " + elementId, null);
-    }
-  },
-};
diff --git a/labs/kurento-screenshare/lib/screenshare.js b/labs/kurento-screenshare/lib/screenshare.js
deleted file mode 100644
index 411fbd54a9ac3cf994f7e0f703ed7c3f32f8718e..0000000000000000000000000000000000000000
--- a/labs/kurento-screenshare/lib/screenshare.js
+++ /dev/null
@@ -1,249 +0,0 @@
-/*
- * Lucas Fialho Zawacki
- * Paulo Renato Lanzarin
- * (C) Copyright 2017 Bigbluebutton
- *
- */
-
-'use strict'
-
-// Imports
-const C = require('./bbb/messages/Constants');
-const MediaHandler = require('./media-handler');
-const Messaging = require('./bbb/messages/Messaging');
-const moment = require('moment');
-const h264_sdp = require('./h264-sdp');
-const now = moment();
-const MediaController = require('./media-controller');
-
-// Global stuff
-var sharedScreens = {};
-var rtpEndpoints = {};
-
-const kurento = require('kurento-client');
-const config = require('config');
-const kurentoUrl = config.get('kurentoUrl');
-const kurentoIp = config.get('kurentoIp');
-const localIpAddress = config.get('localIpAddress');
-
-if (config.get('acceptSelfSignedCertificate')) {
-  process.env.NODE_TLS_REJECT_UNAUTHORIZED=0;
-}
-
-module.exports = class Screenshare {
-  constructor(ws, id, bbbgw, voiceBridge, caller, vh, vw, meetingId) {
-    this._ws = ws;
-    this._id = id;
-    this._BigBlueButtonGW = bbbgw;
-    this._presenterEndpoint = null;
-    this._ffmpegRtpEndpoint = null;
-    this._voiceBridge = voiceBridge;
-    this._meetingId = meetingId;
-    this._caller = caller;
-    this._streamUrl = "";
-    this._vw = vw;
-    this._vh = vh;
-    this._candidatesQueue = [];
-  }
-
-  // TODO isolate ICE
-  _onIceCandidate(_candidate) {
-    let candidate = kurento.getComplexType('IceCandidate')(_candidate);
-
-    if (this._presenterEndpoint) {
-      this._presenterEndpoint.addIceCandidate(candidate);
-    }
-    else {
-      this._candidatesQueue.push(candidate);
-    }
-  };
-
-  _startPresenter(id, ws, sdpOffer, callback) {
-    let self = this;
-    let _callback = callback;
-
-    // Force H264 on Firefox and Chrome
-    sdpOffer = h264_sdp.transform(sdpOffer);
-    console.log("Starting presenter for " + sdpOffer);
-    MediaController.createMediaElement(self._voiceBridge, C.WebRTC, function(error, webRtcEndpoint) {
-      if (error) {
-        console.log("Media elements error" + error);
-        return _callback(error);
-      }
-      MediaController.createMediaElement(self._voiceBridge, C.RTP, function(error, rtpEndpoint) {
-        if (error) {
-          console.log("Media elements error" + error);
-          return _callback(error);
-        }
-
-
-        while(self._candidatesQueue.length) {
-          let candidate = self._candidatesQueue.shift();
-          MediaController.addIceCandidate(webRtcEndpoint.id, candidate);
-        }
-
-        MediaController.connectMediaElements(webRtcEndpoint.id, rtpEndpoint.id, C.VIDEO, function(error) {
-          if (error) {
-            console.log("Media elements CONNECT error " + error);
-            //pipeline.release();
-            return _callback(error);
-          }
-
-          // It's a user sharing a Screen
-          sharedScreens[id] = webRtcEndpoint;
-          rtpEndpoints[id] = rtpEndpoint;
-
-          // Store our endpoint
-          self._presenterEndpoint = webRtcEndpoint;
-          self._ffmpegRtpEndpoint = rtpEndpoint;
-
-          self._presenterEndpoint.on('OnIceCandidate', function(event) {
-            let candidate = kurento.getComplexType('IceCandidate')(event.candidate);
-            ws.sendMessage({ id : 'iceCandidate', cameraId: id, candidate : candidate });
-          });
-
-          MediaController.processOffer(webRtcEndpoint.id, sdpOffer, function(error, webRtcSdpAnswer) {
-            if (error) {
-              console.log("  [webrtc] processOffer error => " + error + " for SDP " + sdpOffer);
-              //pipeline.release();
-              return _callback(error);
-            }
-
-            let sendVideoPort = MediaHandler.getVideoPort();
-
-            let rtpSdpOffer = MediaHandler.generateVideoSdp(localIpAddress, sendVideoPort);
-            console.log("  [rtpendpoint] RtpEndpoint processing => " + rtpSdpOffer);
-
-            MediaController.gatherCandidates(webRtcEndpoint.id, function(error) {
-              if (error) {
-                return _callback(error);
-              }
-
-              MediaController.processOffer(rtpEndpoint.id, rtpSdpOffer, function(error, rtpSdpAnswer) {
-                if (error) {
-                  console.log("  [rtpendpoint] processOffer error => " + error + " for SDP " + rtpSdpOffer);
-                  //pipeline.release();
-                  return _callback(error);
-                }
-
-                console.log("  [rtpendpoint] KMS answer SDP => " + rtpSdpAnswer);
-                let recvVideoPort = rtpSdpAnswer.match(/m=video\s(\d*)/)[1];
-                let rtpParams = MediaHandler.generateTranscoderParams(kurentoIp, localIpAddress,
-                    sendVideoPort, recvVideoPort, self._meetingId, "stream_type_video", C.RTP_TO_RTMP, "copy", "caller");
-
-                self._ffmpegRtpEndpoint.on('MediaFlowInStateChange', function(event) {
-                  if (event.state === 'NOT_FLOWING') {
-                    self._onRtpMediaNotFlowing();
-                  }
-                  else if (event.state === 'FLOWING') {
-                    self._onRtpMediaFlowing(self._meetingId, rtpParams);
-                  }
-                });
-                return _callback(null, webRtcSdpAnswer);
-              });
-            });
-          });
-        });
-      });
-    });
-  };
-
-  _stop() {
-
-    console.log(' [stop] Releasing endpoints for ' + this._id);
-
-    this._stopScreensharing();
-
-    if (this._presenterEndpoint) {
-      MediaController.releaseMediaElement(this._presenterEndpoint.id);
-      this._presenterEndpoint = null;
-    } else {
-      console.log(" [webRtcEndpoint] PLEASE DONT TRY STOPPING THINGS TWICE");
-    }
-
-    if (this._ffmpegRtpEndpoint) {
-      MediaController.releaseMediaElement(this._ffmpegRtpEndpoint.id);
-      this._ffmpegRtpEndpoint = null;
-    } else {
-      console.log(" [rtpEndpoint] PLEASE DONT TRY STOPPING THINGS TWICE");
-    }
-
-    console.log(' [stop] Screen is shared, releasing ' + this._id);
-
-    delete sharedScreens[this._id];
-
-    delete this._candidatesQueue;
-  };
-
-  _stopScreensharing() {
-    let self = this;
-    let strm = Messaging.generateStopTranscoderRequestMessage(this._meetingId, this._meetingId);
-
-    self._BigBlueButtonGW.publish(strm, C.TO_BBB_TRANSCODE_SYSTEM_CHAN, function(error) {});
-
-    // Interoperability: capturing 1.1 stop_transcoder_reply messages
-    self._BigBlueButtonGW.once(C.STOP_TRANSCODER_REPLY, function(payload) {
-      let meetingId = payload[C.MEETING_ID];
-      self._stopRtmpBroadcast(meetingId);
-    });
-
-    // Capturing stop transcoder responses from the 2x model
-    self._BigBlueButtonGW.once(C.STOP_TRANSCODER_RESP_2x, function(payload) {
-      let meetingId = payload[C.MEETING_ID_2x];
-      self._stopRtmpBroadcast(meetingId);
-    });
-
-  }
-
-  _onRtpMediaFlowing(meetingId, rtpParams) {
-    let self = this;
-    let strm = Messaging.generateStartTranscoderRequestMessage(meetingId, meetingId, rtpParams);
-
-    // Interoperability: capturing 1.1 start_transcoder_reply messages
-    self._BigBlueButtonGW.once(C.START_TRANSCODER_REPLY, function(payload) {
-      let meetingId = payload[C.MEETING_ID];
-      let output = payload["params"].output;
-      self._startRtmpBroadcast(meetingId, output);
-    });
-
-    // Capturing stop transcoder responses from the 2x model
-    self._BigBlueButtonGW.once(C.START_TRANSCODER_RESP_2x, function(payload) {
-      let meetingId = payload[C.MEETING_ID_2x];
-      let output = payload["params"].output;
-      self._startRtmpBroadcast(meetingId, output);
-    });
-
-
-    self._BigBlueButtonGW.publish(strm, C.TO_BBB_TRANSCODE_SYSTEM_CHAN, function(error) {});
-  };
-
-  _stopRtmpBroadcast (meetingId) {
-    var self = this;
-    if(self._meetingId === meetingId) {
-      // TODO correctly assemble this timestamp
-      let timestamp = now.format('hhmmss');
-      let dsrstom = Messaging.generateScreenshareRTMPBroadcastStoppedEvent2x(self._voiceBridge,
-          self._voiceBridge, self._streamUrl, self._vw, self._vh, timestamp);
-      self._BigBlueButtonGW.publish(dsrstom, C.FROM_VOICE_CONF_SYSTEM_CHAN, function(error) {});
-    }
-  }
-
-  _startRtmpBroadcast (meetingId, output) {
-    var self = this;
-    if(self._meetingId === meetingId) {
-      // TODO correctly assemble this timestamp
-      let timestamp = now.format('hhmmss');
-      self._streamUrl = MediaHandler.generateStreamUrl(localIpAddress, meetingId, output);
-      let dsrbstam = Messaging.generateScreenshareRTMPBroadcastStartedEvent2x(self._voiceBridge,
-          self._voiceBridge, self._streamUrl, self._vw, self._vh, timestamp);
-
-      self._BigBlueButtonGW.publish(dsrbstam, C.FROM_VOICE_CONF_SYSTEM_CHAN, function(error) {});
-    }
-  }
-
-  _onRtpMediaNotFlowing() {
-    console.log("  [screenshare] TODO RTP NOT_FLOWING");
-  };
-
-
-};
diff --git a/labs/kurento-screenshare/package.json b/labs/kurento-screenshare/package.json
deleted file mode 100644
index 187a3ab8a3f2e70c689e740178834ba1fadb5146..0000000000000000000000000000000000000000
--- a/labs/kurento-screenshare/package.json
+++ /dev/null
@@ -1,23 +0,0 @@
-{
-  "name": "bbb-screenshare-video-kurento-bridge",
-  "version": "1.0.0",
-  "private": true,
-  "scripts": {
-    "start": "nodejs server.js",
-    "postinstall": "npm start"
-  },
-  "dependencies": {
-    "cookie-parser": "^1.3.5",
-    "express": "~4.12.4",
-    "express-session": "~1.10.3",
-    "ws": "~1.0.1",
-    "kurento-client": "6.6.0",
-    "redis": "^2.6.2",
-    "sdp-transform": "*",
-    "moment": "*"
-  },
-  "devDependencies": {
-    "config": "^1.26.1",
-    "js-yaml": "^3.8.3"
-  }
-}
diff --git a/labs/kurento-screenshare/server.js b/labs/kurento-screenshare/server.js
deleted file mode 100755
index f645ac88a52ffa328944f6f775b868bda3b0ae34..0000000000000000000000000000000000000000
--- a/labs/kurento-screenshare/server.js
+++ /dev/null
@@ -1,11 +0,0 @@
-/*
- * Paulo Renato Lanzarin
- * (C) Copyright 2017 Bigbluebutton
- *
- */
-
-const ConnectionManager = require('./lib/ConnectionManager');
-const CM = new ConnectionManager(); 
-
-process.on('SIGTERM', CM._stopAll.bind(CM));
-process.on('SIGINT', CM._stopAll.bind(CM));