diff --git a/akka-bbb-apps/src/main/scala/org/bigbluebutton/core/BigBlueButtonActor.scala b/akka-bbb-apps/src/main/scala/org/bigbluebutton/core/BigBlueButtonActor.scala
index 59664d5504a30acd347f449a192564fc6e31d7cc..f7412ebe32f966fba68c2cb83e0b806ec7ecdff4 100755
--- a/akka-bbb-apps/src/main/scala/org/bigbluebutton/core/BigBlueButtonActor.scala
+++ b/akka-bbb-apps/src/main/scala/org/bigbluebutton/core/BigBlueButtonActor.scala
@@ -9,6 +9,8 @@ import org.bigbluebutton.core.bus._
 import org.bigbluebutton.core.api._
 import org.bigbluebutton.SystemConfiguration
 
+import java.util.concurrent.TimeUnit
+
 object BigBlueButtonActor extends SystemConfiguration {
   def props(system: ActorSystem,
     eventBus: IncomingEventBus,
@@ -126,17 +128,21 @@ class BigBlueButtonActor(val system: ActorSystem,
         outGW.send(new EndAndKickAll(msg.meetingID, m.mProps.recorded))
         // Eject all users from the voice conference
         outGW.send(new EjectAllVoiceUsers(msg.meetingID, m.mProps.recorded, m.mProps.voiceBridge))
-        // Disconnect all clients
-        outGW.send(new DisconnectAllUsers(msg.meetingID))
-        log.info("Destroyed meetingId={}", msg.meetingID)
-        outGW.send(new MeetingDestroyed(msg.meetingID))
 
-        /** Unsubscribe to meeting and voice events. **/
-        eventBus.unsubscribe(m.actorRef, m.mProps.meetingID)
-        eventBus.unsubscribe(m.actorRef, m.mProps.voiceBridge)
+        // Delay sending DisconnectAllUsers because of RTMPT connection being dropped before UserEject message arrives to the client  
+        context.system.scheduler.scheduleOnce(Duration.create(2500, TimeUnit.MILLISECONDS)) {
+          // Disconnect all clients
+          outGW.send(new DisconnectAllUsers(msg.meetingID))
+          log.info("Destroyed meetingId={}", msg.meetingID)
+          outGW.send(new MeetingDestroyed(msg.meetingID))
+
+          /** Unsubscribe to meeting and voice events. **/
+          eventBus.unsubscribe(m.actorRef, m.mProps.meetingID)
+          eventBus.unsubscribe(m.actorRef, m.mProps.voiceBridge)
 
-        // Stop the meeting actor.
-        context.stop(m.actorRef)
+          // Stop the meeting actor.
+          context.stop(m.actorRef)
+        }
       }
     }
   }
diff --git a/akka-bbb-apps/src/main/scala/org/bigbluebutton/core/LiveMeeting.scala b/akka-bbb-apps/src/main/scala/org/bigbluebutton/core/LiveMeeting.scala
index 1c32bd8b78c3fef30fe80e64815ba2279576714f..419d910b160e63c188d1467fd4dcca162e22c7c1 100755
--- a/akka-bbb-apps/src/main/scala/org/bigbluebutton/core/LiveMeeting.scala
+++ b/akka-bbb-apps/src/main/scala/org/bigbluebutton/core/LiveMeeting.scala
@@ -114,24 +114,12 @@ class LiveMeeting(val mProps: MeetingProperties,
   }
 
   def handleEndMeeting(msg: EndMeeting) {
-    meetingModel.meetingHasEnded
+    // Broadcast users the meeting will end
+    outGW.send(new MeetingEnding(msg.meetingId))
 
-    /**
-     * Check if this meeting has breakout rooms. If so, we also need to end them.
-     */
-    handleEndAllBreakoutRooms(new EndAllBreakoutRooms(msg.meetingId))
+    meetingModel.meetingHasEnded
 
     outGW.send(new MeetingEnded(msg.meetingId, mProps.recorded, mProps.voiceBridge))
-
-    // Eject users from the voice conference.
-    outGW.send(new EjectAllVoiceUsers(mProps.meetingID, mProps.recorded, mProps.voiceBridge))
-
-    // Delay sending DisconnectAllUsers because of RTMPT connection being dropped before UserEject message arrives to the client  
-    import context.dispatcher
-    context.system.scheduler.scheduleOnce(Duration.create(2500, TimeUnit.MILLISECONDS)) {
-      log.info("Sending delayed DisconnectUser. meetingId={}", mProps.meetingID)
-      outGW.send(new DisconnectAllUsers(msg.meetingId))
-    }
   }
 
   def handleAllowUserToShareDesktop(msg: AllowUserToShareDesktop): Unit = {
diff --git a/akka-bbb-apps/src/main/scala/org/bigbluebutton/core/MessageSenderActor.scala b/akka-bbb-apps/src/main/scala/org/bigbluebutton/core/MessageSenderActor.scala
index 391f888c22911abc84e65d5852d4d5fb6319a091..6324ca571df3412e3366e56e75daa8d4e2c80988 100755
--- a/akka-bbb-apps/src/main/scala/org/bigbluebutton/core/MessageSenderActor.scala
+++ b/akka-bbb-apps/src/main/scala/org/bigbluebutton/core/MessageSenderActor.scala
@@ -54,6 +54,7 @@ class MessageSenderActor(val service: MessageSender)
     case msg: VoiceRecordingStopped => handleVoiceRecordingStopped(msg)
     case msg: RecordingStatusChanged => handleRecordingStatusChanged(msg)
     case msg: GetRecordingStatusReply => handleGetRecordingStatusReply(msg)
+    case msg: MeetingEnding => handleMeetingEnding(msg)
     case msg: MeetingEnded => handleMeetingEnded(msg)
     case msg: MeetingHasEnded => handleMeetingHasEnded(msg)
     case msg: MeetingDestroyed => handleMeetingDestroyed(msg)
@@ -221,7 +222,12 @@ class MessageSenderActor(val service: MessageSender)
     service.send(MessagingConstants.FROM_MEETING_CHANNEL, json)
 
     val json2 = UsersMessageToJsonConverter.meetingEnded(msg)
-    service.send(MessagingConstants.FROM_MEETING_CHANNEL, json2)
+    service.send(MessagingConstants.FROM_USERS_CHANNEL, json2)
+  }
+
+  private def handleMeetingEnding(msg: MeetingEnding) {
+    val json = MeetingMessageToJsonConverter.meetingEndingToJson(msg)
+    service.send(MessagingConstants.FROM_MEETING_CHANNEL, json)
   }
 
   private def handleStartRecording(msg: StartRecording) {
@@ -265,7 +271,7 @@ class MessageSenderActor(val service: MessageSender)
     service.send(MessagingConstants.FROM_MEETING_CHANNEL, json)
 
     val json2 = UsersMessageToJsonConverter.meetingHasEnded(msg)
-    service.send(MessagingConstants.FROM_MEETING_CHANNEL, json2)
+    service.send(MessagingConstants.FROM_USERS_CHANNEL, json2)
   }
 
   private def handleGetAllMeetingsReply(msg: GetAllMeetingsReply) {
diff --git a/akka-bbb-apps/src/main/scala/org/bigbluebutton/core/api/MessageNames.scala b/akka-bbb-apps/src/main/scala/org/bigbluebutton/core/api/MessageNames.scala
index 25a05ecaaa24e3975463062921732c8308bdf699..0b803aed90806a3c7199dd559bfc998d486ce220 100755
--- a/akka-bbb-apps/src/main/scala/org/bigbluebutton/core/api/MessageNames.scala
+++ b/akka-bbb-apps/src/main/scala/org/bigbluebutton/core/api/MessageNames.scala
@@ -87,6 +87,7 @@ object MessageNames {
   val VOICE_RECORDING_STOPPED = "voice_recording_stopped_message"
   val RECORDING_STATUS_CHANGED = "recording_status_changed_message"
   val GET_RECORDING_STATUS_REPLY = "get_recording_status_reply"
+  val MEETING_ENDING = "meeting_ending_message"
   val MEETING_ENDED = "meeting_ended_message"
   val MEETING_HAS_ENDED = "meeting_has_ended_message"
   val MEETING_STATE = "meeting_state_message"
diff --git a/akka-bbb-apps/src/main/scala/org/bigbluebutton/core/api/OutMessages.scala b/akka-bbb-apps/src/main/scala/org/bigbluebutton/core/api/OutMessages.scala
index e9cdf3063a4714271f52577cb950da9f182799de..5e3138f7c2759aae9ce77551710b195f5ef49c94 100755
--- a/akka-bbb-apps/src/main/scala/org/bigbluebutton/core/api/OutMessages.scala
+++ b/akka-bbb-apps/src/main/scala/org/bigbluebutton/core/api/OutMessages.scala
@@ -17,6 +17,7 @@ case class GetRecordingStatusReply(meetingID: String, recorded: Boolean, userId:
 case class MeetingCreated(meetingID: String, externalMeetingID: String, parentMeetingID: String, recorded: Boolean, name: String,
   voiceBridge: String, duration: Int, moderatorPass: String, viewerPass: String, createTime: Long, createDate: String, isBreakout: Boolean) extends IOutMessage
 case class MeetingMuted(meetingID: String, recorded: Boolean, meetingMuted: Boolean) extends IOutMessage
+case class MeetingEnding(meetingID: String) extends IOutMessage
 case class MeetingEnded(meetingID: String, recorded: Boolean, voiceBridge: String) extends IOutMessage
 case class MeetingState(meetingID: String, recorded: Boolean, userId: String, permissions: Permissions, meetingMuted: Boolean) extends IOutMessage
 case class MeetingHasEnded(meetingID: String, userId: String) extends IOutMessage
diff --git a/akka-bbb-apps/src/main/scala/org/bigbluebutton/core/pubsub/senders/MeetingMessageToJsonConverter.scala b/akka-bbb-apps/src/main/scala/org/bigbluebutton/core/pubsub/senders/MeetingMessageToJsonConverter.scala
index e983411e1c0938462788756ec00257b3974cdcb2..244b97558afdad7d7630fad7c498d7d8cea39fb5 100755
--- a/akka-bbb-apps/src/main/scala/org/bigbluebutton/core/pubsub/senders/MeetingMessageToJsonConverter.scala
+++ b/akka-bbb-apps/src/main/scala/org/bigbluebutton/core/pubsub/senders/MeetingMessageToJsonConverter.scala
@@ -52,6 +52,14 @@ object MeetingMessageToJsonConverter {
     Util.buildJson(header, payload)
   }
 
+  def meetingEndingToJson(msg: MeetingEnding): String = {
+    val payload = new java.util.HashMap[String, Any]()
+    payload.put(Constants.MEETING_ID, msg.meetingID)
+
+    val header = Util.buildHeader(MessageNames.MEETING_ENDING, None)
+    Util.buildJson(header, payload)
+  }
+
   def voiceRecordingStartedToJson(msg: VoiceRecordingStarted): String = {
     val payload = new java.util.HashMap[String, Any]()
     payload.put(Constants.MEETING_ID, msg.meetingID)
diff --git a/bbb-client-check/.gitignore b/bbb-client-check/.gitignore
index 2d4fa21ea832bfa2d306db4c393f234c932250f1..ba9063624573321ccc76a03a47c2a5e3f5edbe51 100644
--- a/bbb-client-check/.gitignore
+++ b/bbb-client-check/.gitignore
@@ -7,5 +7,3 @@ org.eclipse.ltk.core.refactoring.prefs
 FlexPrettyPrintCommand.prefs
 index.template.html
 conf/config.xml
-resources/lib/bbb_webrtc_bridge_sip.js
-resources/lib/sip.js
diff --git a/bbb-client-check/build.xml b/bbb-client-check/build.xml
index 7f1cab5f98d59c8f53305362560931020622132e..78de46517c93b6c267d7843be8d0249624f81e2c 100755
--- a/bbb-client-check/build.xml
+++ b/bbb-client-check/build.xml
@@ -103,11 +103,6 @@
 	</target>
 	<target name="Resolve-Dependency"
 			description="Generate HTML wrapper">
-		<copy todir="resources/lib/" >
-			<fileset file="../bigbluebutton-client/resources/prod/lib/bbb_webrtc_bridge_sip.js" />
-			<fileset file="../bigbluebutton-client/resources/prod/lib/sip.js" />
-		</copy>
-
 		<get src="${TEST_IMAGE_URL}" dest="${html.output}/test_image.jpg" skipexisting="true" />
 		<copy file="html-template/index.html"
 			  tofile="${html.output}/index.html"/>
diff --git a/bbb-client-check/resources/lib/api-bridge.js b/bbb-client-check/resources/lib/api-bridge.js
old mode 100755
new mode 100644
diff --git a/bbb-client-check/resources/lib/bbb_webrtc_bridge_sip.js b/bbb-client-check/resources/lib/bbb_webrtc_bridge_sip.js
new file mode 100644
index 0000000000000000000000000000000000000000..066aeedadfddd641e3b4e87d6f2e7ef780d0f747
--- /dev/null
+++ b/bbb-client-check/resources/lib/bbb_webrtc_bridge_sip.js
@@ -0,0 +1,545 @@
+
+var userID, callerIdName=null, conferenceVoiceBridge, userAgent=null, userMicMedia, userWebcamMedia, currentSession=null, callTimeout, callActive, callICEConnected, iceConnectedTimeout, callFailCounter, callPurposefullyEnded, uaConnected, transferTimeout, iceGatheringTimeout;
+var inEchoTest = true;
+var html5StunTurn = {};
+
+function webRTCCallback(message) {
+	switch (message.status) {
+		case 'failed':
+			if (message.errorcode !== 1004) {
+				message.cause = null;
+			}
+			BBB.webRTCCallFailed(inEchoTest, message.errorcode, message.cause);
+			break;
+		case 'ended':
+			BBB.webRTCCallEnded(inEchoTest);
+			break;
+		case 'started':
+			BBB.webRTCCallStarted(inEchoTest);
+			break;
+		case 'connecting':
+			BBB.webRTCCallConnecting(inEchoTest);
+			break;
+		case 'waitingforice':
+			BBB.webRTCCallWaitingForICE(inEchoTest);
+			break;
+		case 'transferring':
+			BBB.webRTCCallTransferring(inEchoTest);
+			break;
+		case 'mediarequest':
+			BBB.webRTCMediaRequest();
+			break;
+		case 'mediasuccess':
+			BBB.webRTCMediaSuccess();
+			break;
+		case 'mediafail':
+			BBB.webRTCMediaFail();
+			break;
+	}
+}
+
+function callIntoConference(voiceBridge, callback, isListenOnly, stunTurn = null) {
+	// root of the call initiation process from the html5 client
+	// Flash will not pass in the listen only field. For html5 it is optional. Assume NOT listen only if no state passed
+	if (isListenOnly == null) {
+		isListenOnly = false;
+	}
+
+	// if additional stun configuration is passed, store the information
+	if (stunTurn != null) {
+		html5StunTurn['stunServers'] = stunTurn.stun;
+		html5StunTurn['turnServers'] = stunTurn.turn;
+	}
+
+	// reset callerIdName
+	callerIdName = null;
+	if (!callerIdName) {
+		BBB.getMyUserInfo(function(userInfo) {
+			console.log("User info callback [myUserID=" + userInfo.myUserID
+				+ ",myUsername=" + userInfo.myUsername + ",myAvatarURL=" + userInfo.myAvatarURL
+				+ ",myRole=" + userInfo.myRole + ",amIPresenter=" + userInfo.amIPresenter
+				+ ",dialNumber=" + userInfo.dialNumber + ",voiceBridge=" + userInfo.voiceBridge
+				+ ",isListenOnly=" + isListenOnly + "].");
+			userID = userInfo.myUserID;
+			callerIdName = userInfo.myUserID + "-bbbID-" + userInfo.myUsername;
+			if (isListenOnly) {
+				//prepend the callerIdName so it is recognized as a global audio user
+				callerIdName = "GLOBAL_AUDIO_" + callerIdName;
+			}
+			conferenceVoiceBridge = userInfo.voiceBridge
+			if (voiceBridge === "9196") {
+				voiceBridge = voiceBridge + conferenceVoiceBridge;
+			} else {
+				voiceBridge = conferenceVoiceBridge;
+			}
+			console.log(callerIdName);
+			webrtc_call(callerIdName, voiceBridge, callback, isListenOnly);
+		});
+	} else {
+		if (voiceBridge === "9196") {
+			voiceBridge = voiceBridge + conferenceVoiceBridge;
+		} else {
+			voiceBridge = conferenceVoiceBridge;
+		}
+		webrtc_call(callerIdName, voiceBridge, callback, isListenOnly);
+	}
+}
+
+function joinWebRTCVoiceConference() {
+	console.log("Joining to the voice conference directly");
+	inEchoTest = false;
+	// set proper callbacks to previously created user agent
+	if(userAgent) {
+		setUserAgentListeners(webRTCCallback);
+	}
+	callIntoConference(conferenceVoiceBridge, webRTCCallback);
+}
+
+function leaveWebRTCVoiceConference() {
+	console.log("Leaving the voice conference");
+	
+	webrtc_hangup();
+}
+
+function startWebRTCAudioTest(){
+	console.log("Joining the audio test first");
+	inEchoTest = true;
+	callIntoConference("9196", webRTCCallback);
+}
+
+function stopWebRTCAudioTest(){
+	console.log("Stopping webrtc audio test");
+	
+	webrtc_hangup();
+}
+
+function stopWebRTCAudioTestJoinConference(){
+	console.log("Transferring from audio test to conference");
+	
+	webRTCCallback({'status': 'transferring'});
+	
+	transferTimeout = setTimeout( function() {
+		console.log("Call transfer failed. No response after 3 seconds");
+		webRTCCallback({'status': 'failed', 'errorcode': 1008});
+		currentSession = null;
+		if (userAgent != null) {
+			var userAgentTemp = userAgent;
+			userAgent = null;
+			userAgentTemp.stop();
+		}
+	}, 5000);
+	
+	BBB.listen("UserJoinedVoiceEvent", userJoinedVoiceHandler);
+	
+	currentSession.dtmf(1);
+	inEchoTest = false;
+}
+
+function userJoinedVoiceHandler(event) {
+	console.log("UserJoinedVoiceHandler - " + event);
+	if (inEchoTest === false && userID === event.userID) {
+		BBB.unlisten("UserJoinedVoiceEvent", userJoinedVoiceHandler);
+		clearTimeout(transferTimeout);
+		webRTCCallback({'status': 'started'});
+	}
+}
+
+function createUA(username, server, callback, makeCallFunc) {
+	if (userAgent) {
+		console.log("User agent already created");
+		return;
+	}
+
+	console.log("Fetching STUN/TURN server info for user agent");
+
+	console.log(html5StunTurn);
+	if (html5StunTurn != null) {
+		createUAWithStuns(username, server, callback, html5StunTurn, makeCallFunc);
+		return;
+	}
+
+  BBB.getSessionToken(function(sessionToken) {
+  	$.ajax({
+  		dataType: 'json',
+  		url: '/bigbluebutton/api/stuns',
+  		data: {sessionToken:sessionToken}
+  	}).done(function(data) {
+  		var stunsConfig = {};
+  		stunsConfig['stunServers'] = ( data['stunServers'] ? data['stunServers'].map(function(data) {
+  			return data['url'];
+  		}) : [] );
+  		stunsConfig['turnServers'] = ( data['turnServers'] ? data['turnServers'].map(function(data) {
+  			return {
+  				'urls': data['url'],
+  				'username': data['username'],
+  				'password': data['password']
+  			};
+  		}) : [] );
+  		createUAWithStuns(username, server, callback, stunsConfig, makeCallFunc);
+  	}).fail(function(data, textStatus, errorThrown) {
+  		BBBLog.error("Could not fetch stun/turn servers", {error: textStatus, user: callerIdName, voiceBridge: conferenceVoiceBridge});
+  		callback({'status':'failed', 'errorcode': 1009});
+  	});
+  });
+}
+
+function createUAWithStuns(username, server, callback, stunsConfig, makeCallFunc) {
+	console.log("Creating new user agent");
+
+	/* VERY IMPORTANT 
+	 *	- You must escape the username because spaces will cause the connection to fail
+	 *	- We are connecting to the websocket through an nginx redirect instead of directly to 5066
+	 */
+	var configuration = {
+		uri: 'sip:' + encodeURIComponent(username) + '@' + server,
+		wsServers: 'ws://' + server + '/ws',
+		displayName: username,
+		register: false,
+		traceSip: true,
+		autostart: false,
+		userAgentString: "BigBlueButton",
+		stunServers: stunsConfig['stunServers'],
+		turnServers: stunsConfig['turnServers']
+	};
+	
+	uaConnected = false;
+	
+	userAgent = new SIP.UA(configuration);
+	setUserAgentListeners(callback, makeCallFunc);
+	userAgent.start();
+};
+
+function setUserAgentListeners(callback, makeCallFunc) {
+	console.log("resetting UA callbacks");
+	userAgent.removeAllListeners('connected');
+	userAgent.on('connected', function() {
+		uaConnected = true;
+		makeCallFunc();
+	});
+	userAgent.removeAllListeners('disconnected');
+	userAgent.on('disconnected', function() {
+		if (userAgent) {
+			if (userAgent != null) {
+				var userAgentTemp = userAgent;
+				userAgent = null;
+				userAgentTemp.stop();
+			}
+			
+			if (uaConnected) {
+				callback({'status':'failed', 'errorcode': 1001}); // WebSocket disconnected
+			} else {
+				callback({'status':'failed', 'errorcode': 1002}); // Could not make a WebSocket connection
+			}
+		}
+	});
+};
+
+function getUserMicMedia(getUserMicMediaSuccess, getUserMicMediaFailure) {
+	if (userMicMedia == undefined) {
+		if (SIP.WebRTC.isSupported()) {
+			SIP.WebRTC.getUserMedia({audio:true, video:false}, getUserMicMediaSuccess, getUserMicMediaFailure);
+		} else {
+			console.log("getUserMicMedia: webrtc not supported");
+			getUserMicMediaFailure("WebRTC is not supported");
+		}
+	} else {
+		console.log("getUserMicMedia: mic already set");
+		getUserMicMediaSuccess(userMicMedia);
+	}
+};
+
+function webrtc_call(username, voiceBridge, callback, isListenOnly) {
+	if (!isWebRTCAvailable()) {
+		callback({'status': 'failed', 'errorcode': 1003}); // Browser version not supported
+		return;
+	}
+	if (isListenOnly == null) { // assume NOT listen only unless otherwise stated
+		isListenOnly = false;
+	}
+
+	var server = window.document.location.hostname;
+	console.log("user " + username + " calling to " +  voiceBridge);
+
+	var makeCallFunc = function() {
+		// only make the call when both microphone and useragent have been created
+		// for listen only, stating listen only is a viable substitute for acquiring user media control
+		if ((isListenOnly||userMicMedia) && userAgent)
+			make_call(username, voiceBridge, server, callback, false, isListenOnly);
+	};
+
+	// Reset userAgent so we can successfully switch between listenOnly and listen+speak modes
+	userAgent = null;
+	if (!userAgent) {
+		createUA(username, server, callback, makeCallFunc);
+	}
+	// if the user requests to proceed as listen only (does not require media) or media is already acquired,
+	// proceed with making the call
+	if (isListenOnly || userMicMedia !== undefined) {
+		makeCallFunc();
+	} else {
+		callback({'status':'mediarequest'});
+		getUserMicMedia(function(stream) {
+				console.log("getUserMicMedia: success");
+				userMicMedia = stream;
+				callback({'status':'mediasuccess'});
+				makeCallFunc();
+			}, function(e) {
+				console.error("getUserMicMedia: failure - " + e);
+				callback({'status':'mediafail', 'cause': e});
+			}
+		);
+	}
+}
+
+function make_call(username, voiceBridge, server, callback, recall, isListenOnly) {
+	if (isListenOnly == null) {
+		isListenOnly = false;
+	}
+
+	if (userAgent == null) {
+		console.log("userAgent is still null. Delaying call");
+		var callDelayTimeout = setTimeout( function() {
+			make_call(username, voiceBridge, server, callback, recall, isListenOnly);
+		}, 100);
+		return;
+	}
+
+	if (!userAgent.isConnected()) {
+		console.log("Trying to make call, but UserAgent hasn't connected yet. Delaying call");
+		userAgent.once('connected', function() {
+			console.log("UserAgent has now connected, retrying the call");
+			make_call(username, voiceBridge, server, callback, recall, isListenOnly);
+		});
+		return;
+	}
+
+	if (currentSession) {
+		console.log('Active call detected ignoring second make_call');
+		return;
+	}
+
+	// Make an audio/video call:
+	console.log("Setting options.. ");
+
+	var options = {};
+	if (isListenOnly) {
+		// create necessary options for a listen only stream
+		var stream = null;
+		// handle the web browser
+		// create a stream object through the browser separated from user media
+		if (typeof webkitMediaStream !== 'undefined') {
+			// Google Chrome
+			stream = new webkitMediaStream;
+		} else {
+			// Firefox
+			audioContext = new window.AudioContext;
+			stream = audioContext.createMediaStreamDestination().stream;
+		}
+
+		options = {
+			media: {
+				stream: stream, // use the stream created above
+				constraints: {
+					audio: true,
+					video: false
+				},
+				render: {
+					remote: document.getElementById('remote-media')
+				}
+			},
+			// a list of our RTC Connection constraints
+			RTCConstraints: {
+				// our constraints are mandatory. We must received audio and must not receive audio
+				mandatory: {
+					OfferToReceiveAudio: true,
+					OfferToReceiveVideo: false
+				}
+			}
+		};
+	} else {
+		options = {
+			media: {
+				stream: userMicMedia,
+				constraints: {
+					audio: true,
+					video: false
+				},
+				render: {
+					remote: document.getElementById('remote-media')
+				}
+			}
+		};
+	}
+	
+	callTimeout = setTimeout(function() {
+		console.log('Ten seconds without updates sending timeout code');
+		callback({'status':'failed', 'errorcode': 1006}); // Failure on call
+		currentSession = null;
+		if (userAgent != null) {
+			var userAgentTemp = userAgent;
+			userAgent = null;
+			userAgentTemp.stop();
+		}
+	}, 10000);
+	
+	callActive = false;
+	callICEConnected = false;
+	callPurposefullyEnded = false;
+	callFailCounter = 0;
+	console.log("Calling to " + voiceBridge + "....");
+	currentSession = userAgent.invite('sip:' + voiceBridge + '@' + server, options); 
+	
+	// Only send the callback if it's the first try
+	if (recall === false) {
+		console.log('call connecting');
+		callback({'status':'connecting'});
+	} else {
+		console.log('call connecting again');
+	}
+	
+	/*
+	iceGatheringTimeout = setTimeout(function() {
+		console.log('Thirty seconds without ICE gathering finishing');
+		callback({'status':'failed', 'errorcode': 1011}); // ICE Gathering Failed
+		currentSession = null;
+		if (userAgent != null) {
+			var userAgentTemp = userAgent;
+			userAgent = null;
+			userAgentTemp.stop();
+		}
+	}, 30000);
+	*/
+	
+	currentSession.mediaHandler.on('iceGatheringComplete', function() {
+		clearTimeout(iceGatheringTimeout);
+	});
+	
+	// The connecting event fires before the listener can be added
+	currentSession.on('connecting', function(){
+		clearTimeout(callTimeout);
+	});
+	currentSession.on('progress', function(response){
+		console.log('call progress: ' + response);
+		clearTimeout(callTimeout);
+	});
+	currentSession.on('failed', function(response, cause){
+		console.log('call failed with cause: '+ cause);
+		
+		if (currentSession) {
+			if (callActive === false) {
+				callback({'status':'failed', 'errorcode': 1004, 'cause': cause}); // Failure on call
+				currentSession = null;
+				if (userAgent != null) {
+					var userAgentTemp = userAgent;
+					userAgent = null;
+					userAgentTemp.stop();
+				}
+			} else {
+				callActive = false;
+				//currentSession.bye();
+				currentSession = null;
+				if (userAgent != null) {
+					userAgent.stop();
+				}
+			}
+		}
+		clearTimeout(callTimeout);
+	});
+	currentSession.on('bye', function(request){
+		callActive = false;
+		
+		if (currentSession) {
+			console.log('call ended ' + currentSession.endTime);
+			
+			if (callPurposefullyEnded === true) {
+				callback({'status':'ended'});
+			} else {
+				callback({'status':'failed', 'errorcode': 1005}); // Call ended unexpectedly
+			}
+			clearTimeout(callTimeout);
+			currentSession = null;
+		} else {
+			console.log('bye event already received');
+		}
+	});
+	currentSession.on('accepted', function(data){
+		callActive = true;
+		console.log('BigBlueButton call accepted');
+		
+		if (callICEConnected === true) {
+			callback({'status':'started'});
+		} else {
+			callback({'status':'waitingforice'});
+			console.log('Waiting for ICE negotiation');
+			iceConnectedTimeout = setTimeout(function() {
+				console.log('60 seconds without ICE finishing');
+				callback({'status':'failed', 'errorcode': 1010}); // ICE negotiation timeout
+				currentSession = null;
+				if (userAgent != null) {
+					var userAgentTemp = userAgent;
+					userAgent = null;
+					userAgentTemp.stop();
+				}
+			}, 60000);
+		}
+		clearTimeout(callTimeout);
+	});
+	currentSession.mediaHandler.on('iceConnectionFailed', function() {
+		console.log('received ice negotiation failed');
+		callback({'status':'failed', 'errorcode': 1007}); // Failure on call
+		currentSession = null;
+		clearTimeout(iceConnectedTimeout);
+		if (userAgent != null) {
+			var userAgentTemp = userAgent;
+			userAgent = null;
+			userAgentTemp.stop();
+		}
+		
+		clearTimeout(callTimeout);
+	});
+	
+	// Some browsers use status of 'connected', others use 'completed', and a couple use both
+	
+	currentSession.mediaHandler.on('iceConnectionConnected', function() {
+		console.log('Received ICE status changed to connected');
+		if (callICEConnected === false) {
+			callICEConnected = true;
+			clearTimeout(iceConnectedTimeout);
+			if (callActive === true) {
+				callback({'status':'started'});
+			}
+			clearTimeout(callTimeout);
+		}
+	});
+	
+	currentSession.mediaHandler.on('iceConnectionCompleted', function() {
+		console.log('Received ICE status changed to completed');
+		if (callICEConnected === false) {
+			callICEConnected = true;
+			clearTimeout(iceConnectedTimeout);
+			if (callActive === true) {
+				callback({'status':'started'});
+			}
+			clearTimeout(callTimeout);
+		}
+	});
+}
+
+function webrtc_hangup(callback) {
+	callPurposefullyEnded = true;
+
+	console.log("Hanging up current session");
+	if (callback) {
+	  currentSession.on('bye', callback);
+	}
+	currentSession.bye();
+}
+
+function isWebRTCAvailable() {
+	return SIP.WebRTC.isSupported();
+}
+
+function getCallStatus() {
+	return currentSession;
+}
+
diff --git a/bbb-client-check/resources/lib/sip.js b/bbb-client-check/resources/lib/sip.js
new file mode 100644
index 0000000000000000000000000000000000000000..4370d9282126d2f2d86cb9034941ea0dbd775aa7
--- /dev/null
+++ b/bbb-client-check/resources/lib/sip.js
@@ -0,0 +1,11734 @@
+/*
+ * SIP version 0.7.5
+ * Copyright (c) 2014-2016 Junction Networks, Inc <http://www.onsip.com>
+ * Homepage: http://sipjs.com
+ * License: http://sipjs.com/license/
+ *
+ *
+ * ~~~SIP.js contains substantial portions of JsSIP under the following license~~~
+ * Homepage: http://jssip.net
+ * Copyright (c) 2012-2013 José Luis Millán - Versatica <http://www.versatica.com> 
+ *
+ * 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.
+ *
+ * ~~~ end JsSIP license ~~~
+ */
+
+
+(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.SIP = 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 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
+      }
+      throw TypeError('Uncaught, unspecified "error" event.');
+    }
+  }
+
+  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:
+        len = arguments.length;
+        args = new Array(len - 1);
+        for (i = 1; i < len; i++)
+          args[i - 1] = arguments[i];
+        handler.apply(this, args);
+    }
+  } else if (isObject(handler)) {
+    len = arguments.length;
+    args = new Array(len - 1);
+    for (i = 1; i < len; i++)
+      args[i - 1] = arguments[i];
+
+    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) {
+    var m;
+    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 {
+    // 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.listenerCount = function(emitter, type) {
+  var ret;
+  if (!emitter._events || !emitter._events[type])
+    ret = 0;
+  else if (isFunction(emitter._events[type]))
+    ret = 1;
+  else
+    ret = emitter._events[type].length;
+  return ret;
+};
+
+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;
+}
+
+},{}],2:[function(require,module,exports){
+module.exports={
+  "name": "sip.js",
+  "title": "SIP.js",
+  "description": "A simple, intuitive, and powerful JavaScript signaling library",
+  "version": "0.7.5",
+  "main": "src/index.js",
+  "browser": {
+    "./src/environment.js": "./src/environment_browser.js"
+  },
+  "homepage": "http://sipjs.com",
+  "author": "OnSIP <developer@onsip.com> (http://sipjs.com/authors/)",
+  "contributors": [
+    {
+      "url": "https://github.com/onsip/SIP.js/blob/master/THANKS.md"
+    }
+  ],
+  "repository": {
+    "type": "git",
+    "url": "https://github.com/onsip/SIP.js.git"
+  },
+  "keywords": [
+    "sip",
+    "websocket",
+    "webrtc",
+    "library",
+    "javascript"
+  ],
+  "devDependencies": {
+    "beefy": "^2.1.5",
+    "browserify": "^4.1.8",
+    "grunt": "~0.4.0",
+    "grunt-browserify": "^4.0.1",
+    "grunt-cli": "~0.1.6",
+    "grunt-contrib-copy": "^0.5.0",
+    "grunt-contrib-jasmine": "^0.9.2",
+    "grunt-contrib-jshint": ">0.5.0",
+    "grunt-contrib-uglify": "~0.2.0",
+    "grunt-peg": "~1.3.1",
+    "grunt-trimtrailingspaces": "^0.4.0",
+    "pegjs": "^0.8.0"
+  },
+  "engines": {
+    "node": ">=0.8"
+  },
+  "license": "MIT",
+  "scripts": {
+    "repl": "beefy test/repl.js --open",
+    "build": "grunt build",
+    "prepublish": "cd src/Grammar && mkdir -p dist && pegjs --extra-options-file peg.json src/Grammar.pegjs dist/Grammar.js",
+    "test": "grunt travis --verbose"
+  },
+  "dependencies": {
+    "ws": "^0.6.4"
+  },
+  "optionalDependencies": {
+    "promiscuous": "^0.6.0"
+  }
+}
+
+},{}],3:[function(require,module,exports){
+"use strict";
+module.exports = function (SIP) {
+var ClientContext;
+
+ClientContext = function (ua, method, target, options) {
+  var originalTarget = target;
+
+  // Validate arguments
+  if (target === undefined) {
+    throw new TypeError('Not enough arguments');
+  }
+
+  this.ua = ua;
+  this.logger = ua.getLogger('sip.clientcontext');
+  this.method = method;
+  target = ua.normalizeTarget(target);
+  if (!target) {
+    throw new TypeError('Invalid target: ' + originalTarget);
+  }
+
+  /* Options
+   * - extraHeaders
+   * - params
+   * - contentType
+   * - body
+   */
+  options = Object.create(options || Object.prototype);
+  options.extraHeaders = (options.extraHeaders || []).slice();
+
+  if (options.contentType) {
+    this.contentType = options.contentType;
+    options.extraHeaders.push('Content-Type: ' + this.contentType);
+  }
+
+  // Build the request
+  this.request = new SIP.OutgoingRequest(this.method,
+                                         target,
+                                         this.ua,
+                                         options.params,
+                                         options.extraHeaders);
+  if (options.body) {
+    this.body = options.body;
+    this.request.body = this.body;
+  }
+
+  /* Set other properties from the request */
+  this.localIdentity = this.request.from;
+  this.remoteIdentity = this.request.to;
+
+  this.data = {};
+};
+ClientContext.prototype = Object.create(SIP.EventEmitter.prototype);
+
+ClientContext.prototype.send = function () {
+  (new SIP.RequestSender(this, this.ua)).send();
+  return this;
+};
+
+ClientContext.prototype.cancel = function (options) {
+  options = options || {};
+
+  var cancel_reason = SIP.Utils.getCancelReason(options.status_code, options.reason_phrase);
+  this.request.cancel(cancel_reason);
+
+  this.emit('cancel');
+};
+
+ClientContext.prototype.receiveResponse = function (response) {
+  var cause = SIP.Utils.getReasonPhrase(response.status_code);
+
+  switch(true) {
+    case /^1[0-9]{2}$/.test(response.status_code):
+      this.emit('progress', response, cause);
+      break;
+
+    case /^2[0-9]{2}$/.test(response.status_code):
+      if(this.ua.applicants[this]) {
+        delete this.ua.applicants[this];
+      }
+      this.emit('accepted', response, cause);
+      break;
+
+    default:
+      if(this.ua.applicants[this]) {
+        delete this.ua.applicants[this];
+      }
+      this.emit('rejected', response, cause);
+      this.emit('failed', response, cause);
+      break;
+  }
+
+};
+
+ClientContext.prototype.onRequestTimeout = function () {
+  this.emit('failed', null, SIP.C.causes.REQUEST_TIMEOUT);
+};
+
+ClientContext.prototype.onTransportError = function () {
+  this.emit('failed', null, SIP.C.causes.CONNECTION_ERROR);
+};
+
+SIP.ClientContext = ClientContext;
+};
+
+},{}],4:[function(require,module,exports){
+"use strict";
+/**
+ * @fileoverview SIP Constants
+ */
+
+/**
+ * SIP Constants.
+ * @augments SIP
+ */
+
+module.exports = function (name, version) {
+return {
+  USER_AGENT: name +'/'+ version,
+
+  // SIP scheme
+  SIP:  'sip',
+  SIPS: 'sips',
+
+  // End and Failure causes
+  causes: {
+    // Generic error causes
+    CONNECTION_ERROR:         'Connection Error',
+    REQUEST_TIMEOUT:          'Request Timeout',
+    SIP_FAILURE_CODE:         'SIP Failure Code',
+    INTERNAL_ERROR:           'Internal Error',
+
+    // SIP error causes
+    BUSY:                     'Busy',
+    REJECTED:                 'Rejected',
+    REDIRECTED:               'Redirected',
+    UNAVAILABLE:              'Unavailable',
+    NOT_FOUND:                'Not Found',
+    ADDRESS_INCOMPLETE:       'Address Incomplete',
+    INCOMPATIBLE_SDP:         'Incompatible SDP',
+    AUTHENTICATION_ERROR:     'Authentication Error',
+    DIALOG_ERROR:             'Dialog Error',
+
+    // Session error causes
+    WEBRTC_NOT_SUPPORTED:     'WebRTC Not Supported',
+    WEBRTC_ERROR:             'WebRTC Error',
+    CANCELED:                 'Canceled',
+    NO_ANSWER:                'No Answer',
+    EXPIRES:                  'Expires',
+    NO_ACK:                   'No ACK',
+    NO_PRACK:                 'No PRACK',
+    USER_DENIED_MEDIA_ACCESS: 'User Denied Media Access',
+    BAD_MEDIA_DESCRIPTION:    'Bad Media Description',
+    RTP_TIMEOUT:              'RTP Timeout'
+  },
+
+  supported: {
+    UNSUPPORTED:        'none',
+    SUPPORTED:          'supported',
+    REQUIRED:           'required'
+  },
+
+  SIP_ERROR_CAUSES: {
+    REDIRECTED: [300,301,302,305,380],
+    BUSY: [486,600],
+    REJECTED: [403,603],
+    NOT_FOUND: [404,604],
+    UNAVAILABLE: [480,410,408,430],
+    ADDRESS_INCOMPLETE: [484],
+    INCOMPATIBLE_SDP: [488,606],
+    AUTHENTICATION_ERROR:[401,407]
+  },
+
+  // SIP Methods
+  ACK:        'ACK',
+  BYE:        'BYE',
+  CANCEL:     'CANCEL',
+  INFO:       'INFO',
+  INVITE:     'INVITE',
+  MESSAGE:    'MESSAGE',
+  NOTIFY:     'NOTIFY',
+  OPTIONS:    'OPTIONS',
+  REGISTER:   'REGISTER',
+  UPDATE:     'UPDATE',
+  SUBSCRIBE:  'SUBSCRIBE',
+  REFER:      'REFER',
+  PRACK:      'PRACK',
+
+  /* SIP Response Reasons
+   * DOC: http://www.iana.org/assignments/sip-parameters
+   * Copied from https://github.com/versatica/OverSIP/blob/master/lib/oversip/sip/constants.rb#L7
+   */
+  REASON_PHRASE: {
+    100: 'Trying',
+    180: 'Ringing',
+    181: 'Call Is Being Forwarded',
+    182: 'Queued',
+    183: 'Session Progress',
+    199: 'Early Dialog Terminated',  // draft-ietf-sipcore-199
+    200: 'OK',
+    202: 'Accepted',  // RFC 3265
+    204: 'No Notification',  //RFC 5839
+    300: 'Multiple Choices',
+    301: 'Moved Permanently',
+    302: 'Moved Temporarily',
+    305: 'Use Proxy',
+    380: 'Alternative Service',
+    400: 'Bad Request',
+    401: 'Unauthorized',
+    402: 'Payment Required',
+    403: 'Forbidden',
+    404: 'Not Found',
+    405: 'Method Not Allowed',
+    406: 'Not Acceptable',
+    407: 'Proxy Authentication Required',
+    408: 'Request Timeout',
+    410: 'Gone',
+    412: 'Conditional Request Failed',  // RFC 3903
+    413: 'Request Entity Too Large',
+    414: 'Request-URI Too Long',
+    415: 'Unsupported Media Type',
+    416: 'Unsupported URI Scheme',
+    417: 'Unknown Resource-Priority',  // RFC 4412
+    420: 'Bad Extension',
+    421: 'Extension Required',
+    422: 'Session Interval Too Small',  // RFC 4028
+    423: 'Interval Too Brief',
+    428: 'Use Identity Header',  // RFC 4474
+    429: 'Provide Referrer Identity',  // RFC 3892
+    430: 'Flow Failed',  // RFC 5626
+    433: 'Anonymity Disallowed',  // RFC 5079
+    436: 'Bad Identity-Info',  // RFC 4474
+    437: 'Unsupported Certificate',  // RFC 4744
+    438: 'Invalid Identity Header',  // RFC 4744
+    439: 'First Hop Lacks Outbound Support',  // RFC 5626
+    440: 'Max-Breadth Exceeded',  // RFC 5393
+    469: 'Bad Info Package',  // draft-ietf-sipcore-info-events
+    470: 'Consent Needed',  // RFC 5360
+    478: 'Unresolvable Destination',  // Custom code copied from Kamailio.
+    480: 'Temporarily Unavailable',
+    481: 'Call/Transaction Does Not Exist',
+    482: 'Loop Detected',
+    483: 'Too Many Hops',
+    484: 'Address Incomplete',
+    485: 'Ambiguous',
+    486: 'Busy Here',
+    487: 'Request Terminated',
+    488: 'Not Acceptable Here',
+    489: 'Bad Event',  // RFC 3265
+    491: 'Request Pending',
+    493: 'Undecipherable',
+    494: 'Security Agreement Required',  // RFC 3329
+    500: 'Internal Server Error',
+    501: 'Not Implemented',
+    502: 'Bad Gateway',
+    503: 'Service Unavailable',
+    504: 'Server Time-out',
+    505: 'Version Not Supported',
+    513: 'Message Too Large',
+    580: 'Precondition Failure',  // RFC 3312
+    600: 'Busy Everywhere',
+    603: 'Decline',
+    604: 'Does Not Exist Anywhere',
+    606: 'Not Acceptable'
+  },
+
+  /* SIP Option Tags
+   * DOC: http://www.iana.org/assignments/sip-parameters/sip-parameters.xhtml#sip-parameters-4
+   */
+  OPTION_TAGS: {
+    '100rel':                   true,  // RFC 3262
+    199:                        true,  // RFC 6228
+    answermode:                 true,  // RFC 5373
+    'early-session':            true,  // RFC 3959
+    eventlist:                  true,  // RFC 4662
+    explicitsub:                true,  // RFC-ietf-sipcore-refer-explicit-subscription-03
+    'from-change':              true,  // RFC 4916
+    'geolocation-http':         true,  // RFC 6442
+    'geolocation-sip':          true,  // RFC 6442
+    gin:                        true,  // RFC 6140
+    gruu:                       true,  // RFC 5627
+    histinfo:                   true,  // RFC 7044
+    ice:                        true,  // RFC 5768
+    join:                       true,  // RFC 3911
+    'multiple-refer':           true,  // RFC 5368
+    norefersub:                 true,  // RFC 4488
+    nosub:                      true,  // RFC-ietf-sipcore-refer-explicit-subscription-03
+    outbound:                   true,  // RFC 5626
+    path:                       true,  // RFC 3327
+    policy:                     true,  // RFC 6794
+    precondition:               true,  // RFC 3312
+    pref:                       true,  // RFC 3840
+    privacy:                    true,  // RFC 3323
+    'recipient-list-invite':    true,  // RFC 5366
+    'recipient-list-message':   true,  // RFC 5365
+    'recipient-list-subscribe': true,  // RFC 5367
+    replaces:                   true,  // RFC 3891
+    'resource-priority':        true,  // RFC 4412
+    'sdp-anat':                 true,  // RFC 4092
+    'sec-agree':                true,  // RFC 3329
+    tdialog:                    true,  // RFC 4538
+    timer:                      true,  // RFC 4028
+    uui:                        true   // RFC 7433
+  }
+};
+};
+
+},{}],5:[function(require,module,exports){
+"use strict";
+
+/**
+ * @fileoverview In-Dialog Request Sender
+ */
+
+/**
+ * @augments SIP.Dialog
+ * @class Class creating an In-dialog request sender.
+ * @param {SIP.Dialog} dialog
+ * @param {Object} applicant
+ * @param {SIP.OutgoingRequest} request
+ */
+/**
+ * @fileoverview in-Dialog Request Sender
+ */
+
+module.exports = function (SIP) {
+var RequestSender;
+
+RequestSender = function(dialog, applicant, request) {
+
+  this.dialog = dialog;
+  this.applicant = applicant;
+  this.request = request;
+
+  // RFC3261 14.1 Modifying an Existing Session. UAC Behavior.
+  this.reattempt = false;
+  this.reattemptTimer = null;
+};
+
+RequestSender.prototype = {
+  send: function() {
+    var self = this,
+      request_sender = new SIP.RequestSender(this, this.dialog.owner.ua);
+
+      request_sender.send();
+
+    // RFC3261 14.2 Modifying an Existing Session -UAC BEHAVIOR-
+    if (this.request.method === SIP.C.INVITE && request_sender.clientTransaction.state !== SIP.Transactions.C.STATUS_TERMINATED) {
+      this.dialog.uac_pending_reply = true;
+      request_sender.clientTransaction.on('stateChanged', function stateChanged(){
+        if (this.state === SIP.Transactions.C.STATUS_ACCEPTED ||
+            this.state === SIP.Transactions.C.STATUS_COMPLETED ||
+            this.state === SIP.Transactions.C.STATUS_TERMINATED) {
+
+          this.removeListener('stateChanged', stateChanged);
+          self.dialog.uac_pending_reply = false;
+
+          if (self.dialog.uas_pending_reply === false) {
+            self.dialog.owner.onReadyToReinvite();
+          }
+        }
+      });
+    }
+  },
+
+  onRequestTimeout: function() {
+    this.applicant.onRequestTimeout();
+  },
+
+  onTransportError: function() {
+    this.applicant.onTransportError();
+  },
+
+  receiveResponse: function(response) {
+    var self = this;
+
+    // RFC3261 12.2.1.2 408 or 481 is received for a request within a dialog.
+    if (response.status_code === 408 || response.status_code === 481) {
+      this.applicant.onDialogError(response);
+    } else if (response.method === SIP.C.INVITE && response.status_code === 491) {
+      if (this.reattempt) {
+        this.applicant.receiveResponse(response);
+      } else {
+        this.request.cseq.value = this.dialog.local_seqnum += 1;
+        this.reattemptTimer = SIP.Timers.setTimeout(
+          function() {
+            if (self.applicant.owner.status !== SIP.Session.C.STATUS_TERMINATED) {
+              self.reattempt = true;
+              self.request_sender.send();
+            }
+          },
+          this.getReattemptTimeout()
+        );
+      }
+    } else {
+      this.applicant.receiveResponse(response);
+    }
+  }
+};
+
+return RequestSender;
+};
+
+},{}],6:[function(require,module,exports){
+"use strict";
+/**
+ * @fileoverview SIP Dialog
+ */
+
+/**
+ * @augments SIP
+ * @class Class creating a SIP dialog.
+ * @param {SIP.RTCSession} owner
+ * @param {SIP.IncomingRequest|SIP.IncomingResponse} message
+ * @param {Enum} type UAC / UAS
+ * @param {Enum} state SIP.Dialog.C.STATUS_EARLY / SIP.Dialog.C.STATUS_CONFIRMED
+ */
+module.exports = function (SIP) {
+
+var RequestSender = require('./Dialog/RequestSender')(SIP);
+
+var Dialog,
+  C = {
+    // Dialog states
+    STATUS_EARLY:       1,
+    STATUS_CONFIRMED:   2
+  };
+
+// RFC 3261 12.1
+Dialog = function(owner, message, type, state) {
+  var contact;
+
+  this.uac_pending_reply = false;
+  this.uas_pending_reply = false;
+
+  if(!message.hasHeader('contact')) {
+    return {
+      error: 'unable to create a Dialog without Contact header field'
+    };
+  }
+
+  if(message instanceof SIP.IncomingResponse) {
+    state = (message.status_code < 200) ? C.STATUS_EARLY : C.STATUS_CONFIRMED;
+  } else {
+    // Create confirmed dialog if state is not defined
+    state = state || C.STATUS_CONFIRMED;
+  }
+
+  contact = message.parseHeader('contact');
+
+  // RFC 3261 12.1.1
+  if(type === 'UAS') {
+    this.id = {
+      call_id: message.call_id,
+      local_tag: message.to_tag,
+      remote_tag: message.from_tag,
+      toString: function() {
+        return this.call_id + this.local_tag + this.remote_tag;
+      }
+    };
+    this.state = state;
+    this.remote_seqnum = message.cseq;
+    this.local_uri = message.parseHeader('to').uri;
+    this.remote_uri = message.parseHeader('from').uri;
+    this.remote_target = contact.uri;
+    this.route_set = message.getHeaders('record-route');
+    this.invite_seqnum = message.cseq;
+    this.local_seqnum = message.cseq;
+  }
+  // RFC 3261 12.1.2
+  else if(type === 'UAC') {
+    this.id = {
+      call_id: message.call_id,
+      local_tag: message.from_tag,
+      remote_tag: message.to_tag,
+      toString: function() {
+        return this.call_id + this.local_tag + this.remote_tag;
+      }
+    };
+    this.state = state;
+    this.invite_seqnum = message.cseq;
+    this.local_seqnum = message.cseq;
+    this.local_uri = message.parseHeader('from').uri;
+    this.pracked = [];
+    this.remote_uri = message.parseHeader('to').uri;
+    this.remote_target = contact.uri;
+    this.route_set = message.getHeaders('record-route').reverse();
+
+    //RENDERBODY
+    if (this.state === C.STATUS_EARLY && (!owner.hasOffer)) {
+      this.mediaHandler = owner.mediaHandlerFactory(owner);
+    }
+  }
+
+  this.logger = owner.ua.getLogger('sip.dialog', this.id.toString());
+  this.owner = owner;
+  owner.ua.dialogs[this.id.toString()] = this;
+  this.logger.log('new ' + type + ' dialog created with status ' + (this.state === C.STATUS_EARLY ? 'EARLY': 'CONFIRMED'));
+  owner.emit('dialog', this);
+};
+
+Dialog.prototype = {
+  /**
+   * @param {SIP.IncomingMessage} message
+   * @param {Enum} UAC/UAS
+   */
+  update: function(message, type) {
+    this.state = C.STATUS_CONFIRMED;
+
+    this.logger.log('dialog '+ this.id.toString() +'  changed to CONFIRMED state');
+
+    if(type === 'UAC') {
+      // RFC 3261 13.2.2.4
+      this.route_set = message.getHeaders('record-route').reverse();
+    }
+  },
+
+  terminate: function() {
+    this.logger.log('dialog ' + this.id.toString() + ' deleted');
+    if (this.mediaHandler && this.state !== C.STATUS_CONFIRMED) {
+      this.mediaHandler.peerConnection.close();
+    }
+    delete this.owner.ua.dialogs[this.id.toString()];
+  },
+
+  /**
+  * @param {String} method request method
+  * @param {Object} extraHeaders extra headers
+  * @returns {SIP.OutgoingRequest}
+  */
+
+  // RFC 3261 12.2.1.1
+  createRequest: function(method, extraHeaders, body) {
+    var cseq, request;
+    extraHeaders = (extraHeaders || []).slice();
+
+    if(!this.local_seqnum) { this.local_seqnum = Math.floor(Math.random() * 10000); }
+
+    cseq = (method === SIP.C.CANCEL || method === SIP.C.ACK) ? this.invite_seqnum : this.local_seqnum += 1;
+
+    request = new SIP.OutgoingRequest(
+      method,
+      this.remote_target,
+      this.owner.ua, {
+        'cseq': cseq,
+        'call_id': this.id.call_id,
+        'from_uri': this.local_uri,
+        'from_tag': this.id.local_tag,
+        'to_uri': this.remote_uri,
+        'to_tag': this.id.remote_tag,
+        'route_set': this.route_set
+      }, extraHeaders, body);
+
+    request.dialog = this;
+
+    return request;
+  },
+
+  /**
+  * @param {SIP.IncomingRequest} request
+  * @returns {Boolean}
+  */
+
+  // RFC 3261 12.2.2
+  checkInDialogRequest: function(request) {
+    var self = this;
+
+    if(!this.remote_seqnum) {
+      this.remote_seqnum = request.cseq;
+    } else if(request.cseq < this.remote_seqnum) {
+        //Do not try to reply to an ACK request.
+        if (request.method !== SIP.C.ACK) {
+          request.reply(500);
+        }
+        if (request.cseq === this.invite_seqnum) {
+          return true;
+        }
+        return false;
+    } else if(request.cseq > this.remote_seqnum) {
+      this.remote_seqnum = request.cseq;
+    }
+
+    switch(request.method) {
+      // RFC3261 14.2 Modifying an Existing Session -UAS BEHAVIOR-
+      case SIP.C.INVITE:
+        if (this.uac_pending_reply === true) {
+          request.reply(491);
+        } else if (this.uas_pending_reply === true) {
+          var retryAfter = (Math.random() * 10 | 0) + 1;
+          request.reply(500, null, ['Retry-After:' + retryAfter]);
+          return false;
+        } else {
+          this.uas_pending_reply = true;
+          request.server_transaction.on('stateChanged', function stateChanged(){
+            if (this.state === SIP.Transactions.C.STATUS_ACCEPTED ||
+                this.state === SIP.Transactions.C.STATUS_COMPLETED ||
+                this.state === SIP.Transactions.C.STATUS_TERMINATED) {
+
+              this.removeListener('stateChanged', stateChanged);
+              self.uas_pending_reply = false;
+
+              if (self.uac_pending_reply === false) {
+                self.owner.onReadyToReinvite();
+              }
+            }
+          });
+        }
+
+        // RFC3261 12.2.2 Replace the dialog`s remote target URI if the request is accepted
+        if(request.hasHeader('contact')) {
+          request.server_transaction.on('stateChanged', function(){
+            if (this.state === SIP.Transactions.C.STATUS_ACCEPTED) {
+              self.remote_target = request.parseHeader('contact').uri;
+            }
+          });
+        }
+        break;
+      case SIP.C.NOTIFY:
+        // RFC6665 3.2 Replace the dialog`s remote target URI if the request is accepted
+        if(request.hasHeader('contact')) {
+          request.server_transaction.on('stateChanged', function(){
+            if (this.state === SIP.Transactions.C.STATUS_COMPLETED) {
+              self.remote_target = request.parseHeader('contact').uri;
+            }
+          });
+        }
+        break;
+    }
+
+    return true;
+  },
+
+  sendRequest: function(applicant, method, options) {
+    options = options || {};
+
+    var
+      extraHeaders = (options.extraHeaders || []).slice(),
+      body = options.body || null,
+      request = this.createRequest(method, extraHeaders, body),
+      request_sender = new RequestSender(this, applicant, request);
+
+    request_sender.send();
+
+    return request;
+  },
+
+  /**
+  * @param {SIP.IncomingRequest} request
+  */
+  receiveRequest: function(request) {
+    //Check in-dialog request
+    if(!this.checkInDialogRequest(request)) {
+      return;
+    }
+
+    this.owner.receiveRequest(request);
+  }
+};
+
+Dialog.C = C;
+SIP.Dialog = Dialog;
+};
+
+},{"./Dialog/RequestSender":5}],7:[function(require,module,exports){
+"use strict";
+
+/**
+ * @fileoverview SIP Digest Authentication
+ */
+
+/**
+ * SIP Digest Authentication.
+ * @augments SIP.
+ * @function Digest Authentication
+ * @param {SIP.UA} ua
+ */
+module.exports = function (Utils) {
+var DigestAuthentication;
+
+DigestAuthentication = function(ua) {
+  this.logger = ua.getLogger('sipjs.digestauthentication');
+  this.username = ua.configuration.authorizationUser;
+  this.password = ua.configuration.password;
+  this.cnonce = null;
+  this.nc = 0;
+  this.ncHex = '00000000';
+  this.response = null;
+};
+
+
+/**
+* Performs Digest authentication given a SIP request and the challenge
+* received in a response to that request.
+* Returns true if credentials were successfully generated, false otherwise.
+*
+* @param {SIP.OutgoingRequest} request
+* @param {Object} challenge
+*/
+DigestAuthentication.prototype.authenticate = function(request, challenge) {
+  // Inspect and validate the challenge.
+
+  this.algorithm = challenge.algorithm;
+  this.realm = challenge.realm;
+  this.nonce = challenge.nonce;
+  this.opaque = challenge.opaque;
+  this.stale = challenge.stale;
+
+  if (this.algorithm) {
+    if (this.algorithm !== 'MD5') {
+      this.logger.warn('challenge with Digest algorithm different than "MD5", authentication aborted');
+      return false;
+    }
+  } else {
+    this.algorithm = 'MD5';
+  }
+
+  if (! this.realm) {
+    this.logger.warn('challenge without Digest realm, authentication aborted');
+    return false;
+  }
+
+  if (! this.nonce) {
+    this.logger.warn('challenge without Digest nonce, authentication aborted');
+    return false;
+  }
+
+  // 'qop' can contain a list of values (Array). Let's choose just one.
+  if (challenge.qop) {
+    if (challenge.qop.indexOf('auth') > -1) {
+      this.qop = 'auth';
+    } else if (challenge.qop.indexOf('auth-int') > -1) {
+      this.qop = 'auth-int';
+    } else {
+      // Otherwise 'qop' is present but does not contain 'auth' or 'auth-int', so abort here.
+      this.logger.warn('challenge without Digest qop different than "auth" or "auth-int", authentication aborted');
+      return false;
+    }
+  } else {
+    this.qop = null;
+  }
+
+  // Fill other attributes.
+
+  this.method = request.method;
+  this.uri = request.ruri;
+  this.cnonce = Utils.createRandomToken(12);
+  this.nc += 1;
+  this.updateNcHex();
+
+  // nc-value = 8LHEX. Max value = 'FFFFFFFF'.
+  if (this.nc === 4294967296) {
+    this.nc = 1;
+    this.ncHex = '00000001';
+  }
+
+  // Calculate the Digest "response" value.
+  this.calculateResponse();
+
+  return true;
+};
+
+
+/**
+* Generate Digest 'response' value.
+* @private
+*/
+DigestAuthentication.prototype.calculateResponse = function() {
+  var ha1, ha2;
+
+  // HA1 = MD5(A1) = MD5(username:realm:password)
+  ha1 = Utils.calculateMD5(this.username + ":" + this.realm + ":" + this.password);
+
+  if (this.qop === 'auth') {
+    // HA2 = MD5(A2) = MD5(method:digestURI)
+    ha2 = Utils.calculateMD5(this.method + ":" + this.uri);
+    // response = MD5(HA1:nonce:nonceCount:credentialsNonce:qop:HA2)
+    this.response = Utils.calculateMD5(ha1 + ":" + this.nonce + ":" + this.ncHex + ":" + this.cnonce + ":auth:" + ha2);
+
+  } else if (this.qop === 'auth-int') {
+    // HA2 = MD5(A2) = MD5(method:digestURI:MD5(entityBody))
+    ha2 = Utils.calculateMD5(this.method + ":" + this.uri + ":" + Utils.calculateMD5(this.body ? this.body : ""));
+    // response = MD5(HA1:nonce:nonceCount:credentialsNonce:qop:HA2)
+    this.response = Utils.calculateMD5(ha1 + ":" + this.nonce + ":" + this.ncHex + ":" + this.cnonce + ":auth-int:" + ha2);
+
+  } else if (this.qop === null) {
+    // HA2 = MD5(A2) = MD5(method:digestURI)
+    ha2 = Utils.calculateMD5(this.method + ":" + this.uri);
+    // response = MD5(HA1:nonce:HA2)
+    this.response = Utils.calculateMD5(ha1 + ":" + this.nonce + ":" + ha2);
+  }
+};
+
+
+/**
+* Return the Proxy-Authorization or WWW-Authorization header value.
+*/
+DigestAuthentication.prototype.toString = function() {
+  var auth_params = [];
+
+  if (! this.response) {
+    throw new Error('response field does not exist, cannot generate Authorization header');
+  }
+
+  auth_params.push('algorithm=' + this.algorithm);
+  auth_params.push('username="' + this.username + '"');
+  auth_params.push('realm="' + this.realm + '"');
+  auth_params.push('nonce="' + this.nonce + '"');
+  auth_params.push('uri="' + this.uri + '"');
+  auth_params.push('response="' + this.response + '"');
+  if (this.opaque) {
+    auth_params.push('opaque="' + this.opaque + '"');
+  }
+  if (this.qop) {
+    auth_params.push('qop=' + this.qop);
+    auth_params.push('cnonce="' + this.cnonce + '"');
+    auth_params.push('nc=' + this.ncHex);
+  }
+
+  return 'Digest ' + auth_params.join(', ');
+};
+
+
+/**
+* Generate the 'nc' value as required by Digest in this.ncHex by reading this.nc.
+* @private
+*/
+DigestAuthentication.prototype.updateNcHex = function() {
+  var hex = Number(this.nc).toString(16);
+  this.ncHex = '00000000'.substr(0, 8-hex.length) + hex;
+};
+
+return DigestAuthentication;
+};
+
+},{}],8:[function(require,module,exports){
+"use strict";
+var NodeEventEmitter = require('events').EventEmitter;
+
+module.exports = function (console) {
+
+// Don't use `new SIP.EventEmitter()` for inheriting.
+// Use Object.create(SIP.EventEmitter.prototoype);
+function EventEmitter () {
+  NodeEventEmitter.call(this);
+}
+
+EventEmitter.prototype = Object.create(NodeEventEmitter.prototype, {
+  constructor: {
+    value: EventEmitter,
+    enumerable: false,
+    writable: true,
+    configurable: true
+  }
+});
+
+EventEmitter.prototype.off = function off (eventName, listener) {
+  var warning = '';
+  warning += 'SIP.EventEmitter#off is deprecated and may be removed in future SIP.js versions.\n';
+  warning += 'Please use removeListener or removeAllListeners instead.\n';
+  warning += 'See here for more details:\n';
+  warning += 'http://nodejs.org/api/events.html#events_emitter_removelistener_event_listener';
+  console.warn(warning);
+
+  if (arguments.length < 2) {
+    return this.removeAllListeners.apply(this, arguments);
+  } else {
+    return this.removeListener(eventName, listener);
+  }
+};
+
+return EventEmitter;
+
+};
+
+},{"events":1}],9:[function(require,module,exports){
+"use strict";
+/**
+ * @fileoverview Exceptions
+ */
+
+/**
+ * SIP Exceptions.
+ * @augments SIP
+ */
+module.exports = {
+  ConfigurationError: (function(){
+    var exception = function(parameter, value) {
+      this.code = 1;
+      this.name = 'CONFIGURATION_ERROR';
+      this.parameter = parameter;
+      this.value = value;
+      this.message = (!this.value)? 'Missing parameter: '+ this.parameter : 'Invalid value '+ JSON.stringify(this.value) +' for parameter "'+ this.parameter +'"';
+    };
+    exception.prototype = new Error();
+    return exception;
+  }()),
+
+  InvalidStateError: (function(){
+    var exception = function(status) {
+      this.code = 2;
+      this.name = 'INVALID_STATE_ERROR';
+      this.status = status;
+      this.message = 'Invalid status: ' + status;
+    };
+    exception.prototype = new Error();
+    return exception;
+  }()),
+
+  NotSupportedError: (function(){
+    var exception = function(message) {
+      this.code = 3;
+      this.name = 'NOT_SUPPORTED_ERROR';
+      this.message = message;
+    };
+    exception.prototype = new Error();
+    return exception;
+  }()),
+
+  GetDescriptionError: (function(){
+    var exception = function(message) {
+      this.code = 4;
+      this.name = 'GET_DESCRIPTION_ERROR';
+      this.message = message;
+    };
+    exception.prototype = new Error();
+    return exception;
+  }())
+};
+
+},{}],10:[function(require,module,exports){
+"use strict";
+var Grammar = require('./Grammar/dist/Grammar');
+
+module.exports = function (SIP) {
+
+return {
+  parse: function parseCustom (input, startRule) {
+    var options = {startRule: startRule, SIP: SIP};
+    try {
+      Grammar.parse(input, options);
+    } catch (e) {
+      options.data = -1;
+    }
+    return options.data;
+  }
+};
+
+};
+
+},{"./Grammar/dist/Grammar":11}],11:[function(require,module,exports){
+module.exports = (function() {
+  /*
+   * Generated by PEG.js 0.8.0.
+   *
+   * http://pegjs.majda.cz/
+   */
+
+  function peg$subclass(child, parent) {
+    function ctor() { this.constructor = child; }
+    ctor.prototype = parent.prototype;
+    child.prototype = new ctor();
+  }
+
+  function SyntaxError(message, expected, found, offset, line, column) {
+    this.message  = message;
+    this.expected = expected;
+    this.found    = found;
+    this.offset   = offset;
+    this.line     = line;
+    this.column   = column;
+
+    this.name     = "SyntaxError";
+  }
+
+  peg$subclass(SyntaxError, Error);
+
+  function parse(input) {
+    var options = arguments.length > 1 ? arguments[1] : {},
+
+        peg$FAILED = {},
+
+        peg$startRuleIndices = { Contact: 118, Name_Addr_Header: 155, Record_Route: 175, Request_Response: 81, SIP_URI: 45, Subscription_State: 185, Supported: 190, Require: 181, Via: 193, absoluteURI: 84, Call_ID: 117, Content_Disposition: 129, Content_Length: 134, Content_Type: 135, CSeq: 145, displayName: 121, Event: 148, From: 150, host: 52, Max_Forwards: 153, Min_SE: 212, Proxy_Authenticate: 156, quoted_string: 40, Refer_To: 177, Replaces: 178, Session_Expires: 209, stun_URI: 216, To: 191, turn_URI: 223, uuid: 226, WWW_Authenticate: 208, challenge: 157 },
+        peg$startRuleIndex   = 118,
+
+        peg$consts = [
+          "\r\n",
+          { type: "literal", value: "\r\n", description: "\"\\r\\n\"" },
+          /^[0-9]/,
+          { type: "class", value: "[0-9]", description: "[0-9]" },
+          /^[a-zA-Z]/,
+          { type: "class", value: "[a-zA-Z]", description: "[a-zA-Z]" },
+          /^[0-9a-fA-F]/,
+          { type: "class", value: "[0-9a-fA-F]", description: "[0-9a-fA-F]" },
+          /^[\0-\xFF]/,
+          { type: "class", value: "[\\0-\\xFF]", description: "[\\0-\\xFF]" },
+          /^["]/,
+          { type: "class", value: "[\"]", description: "[\"]" },
+          " ",
+          { type: "literal", value: " ", description: "\" \"" },
+          "\t",
+          { type: "literal", value: "\t", description: "\"\\t\"" },
+          /^[a-zA-Z0-9]/,
+          { type: "class", value: "[a-zA-Z0-9]", description: "[a-zA-Z0-9]" },
+          ";",
+          { type: "literal", value: ";", description: "\";\"" },
+          "/",
+          { type: "literal", value: "/", description: "\"/\"" },
+          "?",
+          { type: "literal", value: "?", description: "\"?\"" },
+          ":",
+          { type: "literal", value: ":", description: "\":\"" },
+          "@",
+          { type: "literal", value: "@", description: "\"@\"" },
+          "&",
+          { type: "literal", value: "&", description: "\"&\"" },
+          "=",
+          { type: "literal", value: "=", description: "\"=\"" },
+          "+",
+          { type: "literal", value: "+", description: "\"+\"" },
+          "$",
+          { type: "literal", value: "$", description: "\"$\"" },
+          ",",
+          { type: "literal", value: ",", description: "\",\"" },
+          "-",
+          { type: "literal", value: "-", description: "\"-\"" },
+          "_",
+          { type: "literal", value: "_", description: "\"_\"" },
+          ".",
+          { type: "literal", value: ".", description: "\".\"" },
+          "!",
+          { type: "literal", value: "!", description: "\"!\"" },
+          "~",
+          { type: "literal", value: "~", description: "\"~\"" },
+          "*",
+          { type: "literal", value: "*", description: "\"*\"" },
+          "'",
+          { type: "literal", value: "'", description: "\"'\"" },
+          "(",
+          { type: "literal", value: "(", description: "\"(\"" },
+          ")",
+          { type: "literal", value: ")", description: "\")\"" },
+          peg$FAILED,
+          "%",
+          { type: "literal", value: "%", description: "\"%\"" },
+          null,
+          [],
+          function() {return " "; },
+          function() {return ':'; },
+          /^[!-~]/,
+          { type: "class", value: "[!-~]", description: "[!-~]" },
+          /^[\x80-\uFFFF]/,
+          { type: "class", value: "[\\x80-\\uFFFF]", description: "[\\x80-\\uFFFF]" },
+          /^[\x80-\xBF]/,
+          { type: "class", value: "[\\x80-\\xBF]", description: "[\\x80-\\xBF]" },
+          /^[a-f]/,
+          { type: "class", value: "[a-f]", description: "[a-f]" },
+          "`",
+          { type: "literal", value: "`", description: "\"`\"" },
+          "<",
+          { type: "literal", value: "<", description: "\"<\"" },
+          ">",
+          { type: "literal", value: ">", description: "\">\"" },
+          "\\",
+          { type: "literal", value: "\\", description: "\"\\\\\"" },
+          "[",
+          { type: "literal", value: "[", description: "\"[\"" },
+          "]",
+          { type: "literal", value: "]", description: "\"]\"" },
+          "{",
+          { type: "literal", value: "{", description: "\"{\"" },
+          "}",
+          { type: "literal", value: "}", description: "\"}\"" },
+          function() {return "*"; },
+          function() {return "/"; },
+          function() {return "="; },
+          function() {return "("; },
+          function() {return ")"; },
+          function() {return ">"; },
+          function() {return "<"; },
+          function() {return ","; },
+          function() {return ";"; },
+          function() {return ":"; },
+          function() {return "\""; },
+          /^[!-']/,
+          { type: "class", value: "[!-']", description: "[!-']" },
+          /^[*-[]/,
+          { type: "class", value: "[*-[]", description: "[*-[]" },
+          /^[\]-~]/,
+          { type: "class", value: "[\\]-~]", description: "[\\]-~]" },
+          function(contents) {
+                                  return contents; },
+          /^[#-[]/,
+          { type: "class", value: "[#-[]", description: "[#-[]" },
+          /^[\0-\t]/,
+          { type: "class", value: "[\\0-\\t]", description: "[\\0-\\t]" },
+          /^[\x0B-\f]/,
+          { type: "class", value: "[\\x0B-\\f]", description: "[\\x0B-\\f]" },
+          /^[\x0E-]/,
+          { type: "class", value: "[\\x0E-]", description: "[\\x0E-]" },
+          function() {
+                                  options.data.uri = new options.SIP.URI(options.data.scheme, options.data.user, options.data.host, options.data.port);
+                                  delete options.data.scheme;
+                                  delete options.data.user;
+                                  delete options.data.host;
+                                  delete options.data.host_type;
+                                  delete options.data.port;
+                                },
+          function() {
+                                  options.data.uri = new options.SIP.URI(options.data.scheme, options.data.user, options.data.host, options.data.port, options.data.uri_params, options.data.uri_headers);
+                                  delete options.data.scheme;
+                                  delete options.data.user;
+                                  delete options.data.host;
+                                  delete options.data.host_type;
+                                  delete options.data.port;
+                                  delete options.data.uri_params;
+
+                                  if (options.startRule === 'SIP_URI') { options.data = options.data.uri;}
+                                },
+          "sips",
+          { type: "literal", value: "sips", description: "\"sips\"" },
+          "sip",
+          { type: "literal", value: "sip", description: "\"sip\"" },
+          function(uri_scheme) {
+                              options.data.scheme = uri_scheme; },
+          function() {
+                              options.data.user = decodeURIComponent(text().slice(0, -1));},
+          function() {
+                              options.data.password = text(); },
+          function() {
+                              options.data.host = text();
+                              return options.data.host; },
+          function() {
+                            options.data.host_type = 'domain';
+                            return text(); },
+          /^[a-zA-Z0-9_\-]/,
+          { type: "class", value: "[a-zA-Z0-9_\\-]", description: "[a-zA-Z0-9_\\-]" },
+          /^[a-zA-Z0-9\-]/,
+          { type: "class", value: "[a-zA-Z0-9\\-]", description: "[a-zA-Z0-9\\-]" },
+          function() {
+                              options.data.host_type = 'IPv6';
+                              return text(); },
+          "::",
+          { type: "literal", value: "::", description: "\"::\"" },
+          function() {
+                            options.data.host_type = 'IPv6';
+                            return text(); },
+          function() {
+                              options.data.host_type = 'IPv4';
+                              return text(); },
+          "25",
+          { type: "literal", value: "25", description: "\"25\"" },
+          /^[0-5]/,
+          { type: "class", value: "[0-5]", description: "[0-5]" },
+          "2",
+          { type: "literal", value: "2", description: "\"2\"" },
+          /^[0-4]/,
+          { type: "class", value: "[0-4]", description: "[0-4]" },
+          "1",
+          { type: "literal", value: "1", description: "\"1\"" },
+          /^[1-9]/,
+          { type: "class", value: "[1-9]", description: "[1-9]" },
+          function(port) {
+                              port = parseInt(port.join(''));
+                              options.data.port = port;
+                              return port; },
+          "transport=",
+          { type: "literal", value: "transport=", description: "\"transport=\"" },
+          "udp",
+          { type: "literal", value: "udp", description: "\"udp\"" },
+          "tcp",
+          { type: "literal", value: "tcp", description: "\"tcp\"" },
+          "sctp",
+          { type: "literal", value: "sctp", description: "\"sctp\"" },
+          "tls",
+          { type: "literal", value: "tls", description: "\"tls\"" },
+          function(transport) {
+                                if(!options.data.uri_params) options.data.uri_params={};
+                                options.data.uri_params['transport'] = transport.toLowerCase(); },
+          "user=",
+          { type: "literal", value: "user=", description: "\"user=\"" },
+          "phone",
+          { type: "literal", value: "phone", description: "\"phone\"" },
+          "ip",
+          { type: "literal", value: "ip", description: "\"ip\"" },
+          function(user) {
+                                if(!options.data.uri_params) options.data.uri_params={};
+                                options.data.uri_params['user'] = user.toLowerCase(); },
+          "method=",
+          { type: "literal", value: "method=", description: "\"method=\"" },
+          function(method) {
+                                if(!options.data.uri_params) options.data.uri_params={};
+                                options.data.uri_params['method'] = method; },
+          "ttl=",
+          { type: "literal", value: "ttl=", description: "\"ttl=\"" },
+          function(ttl) {
+                                if(!options.data.params) options.data.params={};
+                                options.data.params['ttl'] = ttl; },
+          "maddr=",
+          { type: "literal", value: "maddr=", description: "\"maddr=\"" },
+          function(maddr) {
+                                if(!options.data.uri_params) options.data.uri_params={};
+                                options.data.uri_params['maddr'] = maddr; },
+          "lr",
+          { type: "literal", value: "lr", description: "\"lr\"" },
+          function() {
+                                if(!options.data.uri_params) options.data.uri_params={};
+                                options.data.uri_params['lr'] = undefined; },
+          function(param, value) {
+                                if(!options.data.uri_params) options.data.uri_params = {};
+                                if (value === null){
+                                  value = undefined;
+                                }
+                                else {
+                                  value = value[1];
+                                }
+                                options.data.uri_params[param.toLowerCase()] = value && value.toLowerCase();},
+          function(hname, hvalue) {
+                                hname = hname.join('').toLowerCase();
+                                hvalue = hvalue.join('');
+                                if(!options.data.uri_headers) options.data.uri_headers = {};
+                                if (!options.data.uri_headers[hname]) {
+                                  options.data.uri_headers[hname] = [hvalue];
+                                } else {
+                                  options.data.uri_headers[hname].push(hvalue);
+                                }},
+          function() {
+                                // lots of tests fail if this isn't guarded...
+                                if (options.startRule === 'Refer_To') {
+                                  options.data.uri = new options.SIP.URI(options.data.scheme, options.data.user, options.data.host, options.data.port, options.data.uri_params, options.data.uri_headers);
+                                  delete options.data.scheme;
+                                  delete options.data.user;
+                                  delete options.data.host;
+                                  delete options.data.host_type;
+                                  delete options.data.port;
+                                  delete options.data.uri_params;
+                                }
+                              },
+          "//",
+          { type: "literal", value: "//", description: "\"//\"" },
+          function() {
+                              options.data.scheme= text(); },
+          { type: "literal", value: "SIP", description: "\"SIP\"" },
+          function() {
+                              options.data.sip_version = text(); },
+          "INVITE",
+          { type: "literal", value: "INVITE", description: "\"INVITE\"" },
+          "ACK",
+          { type: "literal", value: "ACK", description: "\"ACK\"" },
+          "VXACH",
+          { type: "literal", value: "VXACH", description: "\"VXACH\"" },
+          "OPTIONS",
+          { type: "literal", value: "OPTIONS", description: "\"OPTIONS\"" },
+          "BYE",
+          { type: "literal", value: "BYE", description: "\"BYE\"" },
+          "CANCEL",
+          { type: "literal", value: "CANCEL", description: "\"CANCEL\"" },
+          "REGISTER",
+          { type: "literal", value: "REGISTER", description: "\"REGISTER\"" },
+          "SUBSCRIBE",
+          { type: "literal", value: "SUBSCRIBE", description: "\"SUBSCRIBE\"" },
+          "NOTIFY",
+          { type: "literal", value: "NOTIFY", description: "\"NOTIFY\"" },
+          "REFER",
+          { type: "literal", value: "REFER", description: "\"REFER\"" },
+          function() {
+
+                              options.data.method = text();
+                              return options.data.method; },
+          function(status_code) {
+                            options.data.status_code = parseInt(status_code.join('')); },
+          function() {
+                            options.data.reason_phrase = text(); },
+          function() {
+                        options.data = text(); },
+          function() {
+                                  var idx, length;
+                                  length = options.data.multi_header.length;
+                                  for (idx = 0; idx < length; idx++) {
+                                    if (options.data.multi_header[idx].parsed === null) {
+                                      options.data = null;
+                                      break;
+                                    }
+                                  }
+                                  if (options.data !== null) {
+                                    options.data = options.data.multi_header;
+                                  } else {
+                                    options.data = -1;
+                                  }},
+          function() {
+                                  var header;
+                                  if(!options.data.multi_header) options.data.multi_header = [];
+                                  try {
+                                    header = new options.SIP.NameAddrHeader(options.data.uri, options.data.displayName, options.data.params);
+                                    delete options.data.uri;
+                                    delete options.data.displayName;
+                                    delete options.data.params;
+                                  } catch(e) {
+                                    header = null;
+                                  }
+                                  options.data.multi_header.push( { 'position': peg$currPos,
+                                                            'offset': offset(),
+                                                            'parsed': header
+                                                          });},
+          function(displayName) {
+                                  displayName = text().trim();
+                                  if (displayName[0] === '\"') {
+                                    displayName = displayName.substring(1, displayName.length-1);
+                                  }
+                                  options.data.displayName = displayName; },
+          "q",
+          { type: "literal", value: "q", description: "\"q\"" },
+          function(q) {
+                                  if(!options.data.params) options.data.params = {};
+                                  options.data.params['q'] = q; },
+          "expires",
+          { type: "literal", value: "expires", description: "\"expires\"" },
+          function(expires) {
+                                  if(!options.data.params) options.data.params = {};
+                                  options.data.params['expires'] = expires; },
+          function(delta_seconds) {
+                                  return parseInt(delta_seconds.join('')); },
+          "0",
+          { type: "literal", value: "0", description: "\"0\"" },
+          function() {
+                                  return parseFloat(text()); },
+          function(param, value) {
+                                  if(!options.data.params) options.data.params = {};
+                                  if (value === null){
+                                    value = undefined;
+                                  }
+                                  else {
+                                    value = value[1];
+                                  }
+                                  options.data.params[param.toLowerCase()] = value;},
+          "render",
+          { type: "literal", value: "render", description: "\"render\"" },
+          "session",
+          { type: "literal", value: "session", description: "\"session\"" },
+          "icon",
+          { type: "literal", value: "icon", description: "\"icon\"" },
+          "alert",
+          { type: "literal", value: "alert", description: "\"alert\"" },
+          function() {
+                                      if (options.startRule === 'Content_Disposition') {
+                                        options.data.type = text().toLowerCase();
+                                      }
+                                    },
+          "handling",
+          { type: "literal", value: "handling", description: "\"handling\"" },
+          "optional",
+          { type: "literal", value: "optional", description: "\"optional\"" },
+          "required",
+          { type: "literal", value: "required", description: "\"required\"" },
+          function(length) {
+                                  options.data = parseInt(length.join('')); },
+          function() {
+                                  options.data = text(); },
+          "text",
+          { type: "literal", value: "text", description: "\"text\"" },
+          "image",
+          { type: "literal", value: "image", description: "\"image\"" },
+          "audio",
+          { type: "literal", value: "audio", description: "\"audio\"" },
+          "video",
+          { type: "literal", value: "video", description: "\"video\"" },
+          "application",
+          { type: "literal", value: "application", description: "\"application\"" },
+          "message",
+          { type: "literal", value: "message", description: "\"message\"" },
+          "multipart",
+          { type: "literal", value: "multipart", description: "\"multipart\"" },
+          "x-",
+          { type: "literal", value: "x-", description: "\"x-\"" },
+          function(cseq_value) {
+                            options.data.value=parseInt(cseq_value.join('')); },
+          function(expires) {options.data = expires; },
+          function(event_type) {
+                                 options.data.event = event_type.toLowerCase(); },
+          function() {
+                          var tag = options.data.tag;
+                            options.data = new options.SIP.NameAddrHeader(options.data.uri, options.data.displayName, options.data.params);
+                            if (tag) {options.data.setParam('tag',tag)}
+                          },
+          "tag",
+          { type: "literal", value: "tag", description: "\"tag\"" },
+          function(tag) {options.data.tag = tag; },
+          function(forwards) {
+                            options.data = parseInt(forwards.join('')); },
+          function(min_expires) {options.data = min_expires; },
+          function() {
+                                  options.data = new options.SIP.NameAddrHeader(options.data.uri, options.data.displayName, options.data.params);
+                                },
+          "digest",
+          { type: "literal", value: "Digest", description: "\"Digest\"" },
+          "realm",
+          { type: "literal", value: "realm", description: "\"realm\"" },
+          function(realm) { options.data.realm = realm; },
+          "domain",
+          { type: "literal", value: "domain", description: "\"domain\"" },
+          "nonce",
+          { type: "literal", value: "nonce", description: "\"nonce\"" },
+          function(nonce) { options.data.nonce=nonce; },
+          "opaque",
+          { type: "literal", value: "opaque", description: "\"opaque\"" },
+          function(opaque) { options.data.opaque=opaque; },
+          "stale",
+          { type: "literal", value: "stale", description: "\"stale\"" },
+          "true",
+          { type: "literal", value: "true", description: "\"true\"" },
+          function() { options.data.stale=true; },
+          "false",
+          { type: "literal", value: "false", description: "\"false\"" },
+          function() { options.data.stale=false; },
+          "algorithm",
+          { type: "literal", value: "algorithm", description: "\"algorithm\"" },
+          "md5",
+          { type: "literal", value: "MD5", description: "\"MD5\"" },
+          "md5-sess",
+          { type: "literal", value: "MD5-sess", description: "\"MD5-sess\"" },
+          function(algorithm) {
+                                options.data.algorithm=algorithm.toUpperCase(); },
+          "qop",
+          { type: "literal", value: "qop", description: "\"qop\"" },
+          "auth-int",
+          { type: "literal", value: "auth-int", description: "\"auth-int\"" },
+          "auth",
+          { type: "literal", value: "auth", description: "\"auth\"" },
+          function(qop_value) {
+                                  options.data.qop || (options.data.qop=[]);
+                                  options.data.qop.push(qop_value.toLowerCase()); },
+          function(rack_value) {
+                            options.data.value=parseInt(rack_value.join('')); },
+          function() {
+                            var idx, length;
+                            length = options.data.multi_header.length;
+                            for (idx = 0; idx < length; idx++) {
+                              if (options.data.multi_header[idx].parsed === null) {
+                                options.data = null;
+                                break;
+                              }
+                            }
+                            if (options.data !== null) {
+                              options.data = options.data.multi_header;
+                            } else {
+                              options.data = -1;
+                            }},
+          function() {
+                            var header;
+                            if(!options.data.multi_header) options.data.multi_header = [];
+                            try {
+                              header = new options.SIP.NameAddrHeader(options.data.uri, options.data.displayName, options.data.params);
+                              delete options.data.uri;
+                              delete options.data.displayName;
+                              delete options.data.params;
+                            } catch(e) {
+                              header = null;
+                            }
+                            options.data.multi_header.push( { 'position': peg$currPos,
+                                                      'offset': offset(),
+                                                      'parsed': header
+                                                    });},
+          function() {
+                        options.data = new options.SIP.NameAddrHeader(options.data.uri, options.data.displayName, options.data.params);
+                      },
+          function() {
+                                if (!(options.data.replaces_from_tag && options.data.replaces_to_tag)) {
+                                  options.data = -1;
+                                }
+                              },
+          function() {
+                                options.data = {
+                                  call_id: options.data
+                                };
+                              },
+          "from-tag",
+          { type: "literal", value: "from-tag", description: "\"from-tag\"" },
+          function(from_tag) {
+                                options.data.replaces_from_tag = from_tag;
+                              },
+          "to-tag",
+          { type: "literal", value: "to-tag", description: "\"to-tag\"" },
+          function(to_tag) {
+                                options.data.replaces_to_tag = to_tag;
+                              },
+          "early-only",
+          { type: "literal", value: "early-only", description: "\"early-only\"" },
+          function() {
+                                options.data.early_only = true;
+                              },
+          function(r) {return r;},
+          function(first, rest) { return list(first, rest); },
+          function(value) {
+                          if (options.startRule === 'Require') {
+                            options.data = value || [];
+                          }
+                        },
+          function(rseq_value) {
+                            options.data.value=parseInt(rseq_value.join('')); },
+          "active",
+          { type: "literal", value: "active", description: "\"active\"" },
+          "pending",
+          { type: "literal", value: "pending", description: "\"pending\"" },
+          "terminated",
+          { type: "literal", value: "terminated", description: "\"terminated\"" },
+          function() {
+                                  options.data.state = text(); },
+          "reason",
+          { type: "literal", value: "reason", description: "\"reason\"" },
+          function(reason) {
+                                  if (typeof reason !== 'undefined') options.data.reason = reason; },
+          function(expires) {
+                                  if (typeof expires !== 'undefined') options.data.expires = expires; },
+          "retry_after",
+          { type: "literal", value: "retry_after", description: "\"retry_after\"" },
+          function(retry_after) {
+                                  if (typeof retry_after !== 'undefined') options.data.retry_after = retry_after; },
+          "deactivated",
+          { type: "literal", value: "deactivated", description: "\"deactivated\"" },
+          "probation",
+          { type: "literal", value: "probation", description: "\"probation\"" },
+          "rejected",
+          { type: "literal", value: "rejected", description: "\"rejected\"" },
+          "timeout",
+          { type: "literal", value: "timeout", description: "\"timeout\"" },
+          "giveup",
+          { type: "literal", value: "giveup", description: "\"giveup\"" },
+          "noresource",
+          { type: "literal", value: "noresource", description: "\"noresource\"" },
+          "invariant",
+          { type: "literal", value: "invariant", description: "\"invariant\"" },
+          function(value) {
+                          if (options.startRule === 'Supported') {
+                            options.data = value || [];
+                          }
+                        },
+          function() {
+                        var tag = options.data.tag;
+                          options.data = new options.SIP.NameAddrHeader(options.data.uri, options.data.displayName, options.data.params);
+                          if (tag) {options.data.setParam('tag',tag)}
+                        },
+          "ttl",
+          { type: "literal", value: "ttl", description: "\"ttl\"" },
+          function(via_ttl_value) {
+                                options.data.ttl = via_ttl_value; },
+          "maddr",
+          { type: "literal", value: "maddr", description: "\"maddr\"" },
+          function(via_maddr) {
+                                options.data.maddr = via_maddr; },
+          "received",
+          { type: "literal", value: "received", description: "\"received\"" },
+          function(via_received) {
+                                options.data.received = via_received; },
+          "branch",
+          { type: "literal", value: "branch", description: "\"branch\"" },
+          function(via_branch) {
+                                options.data.branch = via_branch; },
+          "rport",
+          { type: "literal", value: "rport", description: "\"rport\"" },
+          function() {
+                                if(typeof response_port !== 'undefined')
+                                  options.data.rport = response_port.join(''); },
+          function(via_protocol) {
+                                options.data.protocol = via_protocol; },
+          { type: "literal", value: "UDP", description: "\"UDP\"" },
+          { type: "literal", value: "TCP", description: "\"TCP\"" },
+          { type: "literal", value: "TLS", description: "\"TLS\"" },
+          { type: "literal", value: "SCTP", description: "\"SCTP\"" },
+          function(via_transport) {
+                                options.data.transport = via_transport; },
+          function() {
+                                options.data.host = text(); },
+          function(via_sent_by_port) {
+                                options.data.port = parseInt(via_sent_by_port.join('')); },
+          function(ttl) {
+                                return parseInt(ttl.join('')); },
+          function(deltaSeconds) {
+                                if (options.startRule === 'Session_Expires') {
+                                  options.data.deltaSeconds = deltaSeconds;
+                                }
+                              },
+          "refresher",
+          { type: "literal", value: "refresher", description: "\"refresher\"" },
+          "uas",
+          { type: "literal", value: "uas", description: "\"uas\"" },
+          "uac",
+          { type: "literal", value: "uac", description: "\"uac\"" },
+          function(endpoint) {
+                                if (options.startRule === 'Session_Expires') {
+                                  options.data.refresher = endpoint;
+                                }
+                              },
+          function(deltaSeconds) {
+                                if (options.startRule === 'Min_SE') {
+                                  options.data = deltaSeconds;
+                                }
+                              },
+          "stuns",
+          { type: "literal", value: "stuns", description: "\"stuns\"" },
+          "stun",
+          { type: "literal", value: "stun", description: "\"stun\"" },
+          function(scheme) {
+                                options.data.scheme = scheme; },
+          function(host) {
+                                options.data.host = host; },
+          "?transport=",
+          { type: "literal", value: "?transport=", description: "\"?transport=\"" },
+          "turns",
+          { type: "literal", value: "turns", description: "\"turns\"" },
+          "turn",
+          { type: "literal", value: "turn", description: "\"turn\"" },
+          function() {
+                                options.data.transport = transport; },
+          function() {
+                            options.data = text(); }
+        ],
+
+        peg$bytecode = [
+          peg$decode(". \"\"2 3!"),
+          peg$decode("0\"\"\"1!3#"),
+          peg$decode("0$\"\"1!3%"),
+          peg$decode("0&\"\"1!3'"),
+          peg$decode("7'*# \"7("),
+          peg$decode("0(\"\"1!3)"),
+          peg$decode("0*\"\"1!3+"),
+          peg$decode(".,\"\"2,3-"),
+          peg$decode("..\"\"2.3/"),
+          peg$decode("00\"\"1!31"),
+          peg$decode(".2\"\"2233*\x89 \".4\"\"2435*} \".6\"\"2637*q \".8\"\"2839*e \".:\"\"2:3;*Y \".<\"\"2<3=*M \".>\"\"2>3?*A \".@\"\"2@3A*5 \".B\"\"2B3C*) \".D\"\"2D3E"),
+          peg$decode("7)*# \"7,"),
+          peg$decode(".F\"\"2F3G*} \".H\"\"2H3I*q \".J\"\"2J3K*e \".L\"\"2L3M*Y \".N\"\"2N3O*M \".P\"\"2P3Q*A \".R\"\"2R3S*5 \".T\"\"2T3U*) \".V\"\"2V3W"),
+          peg$decode("!!.Y\"\"2Y3Z+7$7#+-%7#+#%'#%$## X$\"# X\"# X+! (%"),
+          peg$decode("!! \\7$,#&7$\"+-$7 +#%'\"%$\"# X\"# X*# \" [+@$ \\7$+&$,#&7$\"\"\" X+'%4\"6]\" %$\"# X\"# X"),
+          peg$decode("7.*# \" ["),
+          peg$decode("! \\7'*# \"7(,)&7'*# \"7(\"+A$.8\"\"2839+1%7/+'%4#6^# %$## X$\"# X\"# X"),
+          peg$decode("!! \\72+&$,#&72\"\"\" X+o$ \\! \\7.,#&7.\"+-$72+#%'\"%$\"# X\"# X,@&! \\7.,#&7.\"+-$72+#%'\"%$\"# X\"# X\"+#%'\"%$\"# X\"# X+! (%"),
+          peg$decode("0_\"\"1!3`*# \"73"),
+          peg$decode("0a\"\"1!3b"),
+          peg$decode("0c\"\"1!3d"),
+          peg$decode("7!*) \"0e\"\"1!3f"),
+          peg$decode("! \\7)*\x95 \".F\"\"2F3G*\x89 \".J\"\"2J3K*} \".L\"\"2L3M*q \".Y\"\"2Y3Z*e \".P\"\"2P3Q*Y \".H\"\"2H3I*M \".@\"\"2@3A*A \".g\"\"2g3h*5 \".R\"\"2R3S*) \".N\"\"2N3O+\x9E$,\x9B&7)*\x95 \".F\"\"2F3G*\x89 \".J\"\"2J3K*} \".L\"\"2L3M*q \".Y\"\"2Y3Z*e \".P\"\"2P3Q*Y \".H\"\"2H3I*M \".@\"\"2@3A*A \".g\"\"2g3h*5 \".R\"\"2R3S*) \".N\"\"2N3O\"\"\" X+! (%"),
+          peg$decode("! \\7)*\x89 \".F\"\"2F3G*} \".L\"\"2L3M*q \".Y\"\"2Y3Z*e \".P\"\"2P3Q*Y \".H\"\"2H3I*M \".@\"\"2@3A*A \".g\"\"2g3h*5 \".R\"\"2R3S*) \".N\"\"2N3O+\x92$,\x8F&7)*\x89 \".F\"\"2F3G*} \".L\"\"2L3M*q \".Y\"\"2Y3Z*e \".P\"\"2P3Q*Y \".H\"\"2H3I*M \".@\"\"2@3A*A \".g\"\"2g3h*5 \".R\"\"2R3S*) \".N\"\"2N3O\"\"\" X+! (%"),
+          peg$decode(".T\"\"2T3U*\xE3 \".V\"\"2V3W*\xD7 \".i\"\"2i3j*\xCB \".k\"\"2k3l*\xBF \".:\"\"2:3;*\xB3 \".D\"\"2D3E*\xA7 \".2\"\"2233*\x9B \".8\"\"2839*\x8F \".m\"\"2m3n*\x83 \"7&*} \".4\"\"2435*q \".o\"\"2o3p*e \".q\"\"2q3r*Y \".6\"\"2637*M \".>\"\"2>3?*A \".s\"\"2s3t*5 \".u\"\"2u3v*) \"7'*# \"7("),
+          peg$decode("! \\7)*\u012B \".F\"\"2F3G*\u011F \".J\"\"2J3K*\u0113 \".L\"\"2L3M*\u0107 \".Y\"\"2Y3Z*\xFB \".P\"\"2P3Q*\xEF \".H\"\"2H3I*\xE3 \".@\"\"2@3A*\xD7 \".g\"\"2g3h*\xCB \".R\"\"2R3S*\xBF \".N\"\"2N3O*\xB3 \".T\"\"2T3U*\xA7 \".V\"\"2V3W*\x9B \".i\"\"2i3j*\x8F \".k\"\"2k3l*\x83 \".8\"\"2839*w \".m\"\"2m3n*k \"7&*e \".4\"\"2435*Y \".o\"\"2o3p*M \".q\"\"2q3r*A \".6\"\"2637*5 \".s\"\"2s3t*) \".u\"\"2u3v+\u0134$,\u0131&7)*\u012B \".F\"\"2F3G*\u011F \".J\"\"2J3K*\u0113 \".L\"\"2L3M*\u0107 \".Y\"\"2Y3Z*\xFB \".P\"\"2P3Q*\xEF \".H\"\"2H3I*\xE3 \".@\"\"2@3A*\xD7 \".g\"\"2g3h*\xCB \".R\"\"2R3S*\xBF \".N\"\"2N3O*\xB3 \".T\"\"2T3U*\xA7 \".V\"\"2V3W*\x9B \".i\"\"2i3j*\x8F \".k\"\"2k3l*\x83 \".8\"\"2839*w \".m\"\"2m3n*k \"7&*e \".4\"\"2435*Y \".o\"\"2o3p*M \".q\"\"2q3r*A \".6\"\"2637*5 \".s\"\"2s3t*) \".u\"\"2u3v\"\"\" X+! (%"),
+          peg$decode("!7/+A$.P\"\"2P3Q+1%7/+'%4#6w# %$## X$\"# X\"# X"),
+          peg$decode("!7/+A$.4\"\"2435+1%7/+'%4#6x# %$## X$\"# X\"# X"),
+          peg$decode("!7/+A$.>\"\"2>3?+1%7/+'%4#6y# %$## X$\"# X\"# X"),
+          peg$decode("!7/+A$.T\"\"2T3U+1%7/+'%4#6z# %$## X$\"# X\"# X"),
+          peg$decode("!7/+A$.V\"\"2V3W+1%7/+'%4#6{# %$## X$\"# X\"# X"),
+          peg$decode("!.k\"\"2k3l+1$7/+'%4\"6|\" %$\"# X\"# X"),
+          peg$decode("!7/+7$.i\"\"2i3j+'%4\"6}\" %$\"# X\"# X"),
+          peg$decode("!7/+A$.D\"\"2D3E+1%7/+'%4#6~# %$## X$\"# X\"# X"),
+          peg$decode("!7/+A$.2\"\"2233+1%7/+'%4#6# %$## X$\"# X\"# X"),
+          peg$decode("!7/+A$.8\"\"2839+1%7/+'%4#6\x80# %$## X$\"# X\"# X"),
+          peg$decode("!7/+1$7&+'%4\"6\x81\" %$\"# X\"# X"),
+          peg$decode("!7&+1$7/+'%4\"6\x81\" %$\"# X\"# X"),
+          peg$decode("!7=+W$ \\7G*) \"7K*# \"7F,/&7G*) \"7K*# \"7F\"+-%7>+#%'#%$## X$\"# X\"# X"),
+          peg$decode("0\x82\"\"1!3\x83*A \"0\x84\"\"1!3\x85*5 \"0\x86\"\"1!3\x87*) \"73*# \"7."),
+          peg$decode("!!7/+U$7&+K% \\7J*# \"7K,)&7J*# \"7K\"+-%7&+#%'$%$$# X$## X$\"# X\"# X+! (%"),
+          peg$decode("!7/+`$7&+V%! \\7J*# \"7K,)&7J*# \"7K\"+! (%+2%7&+(%4$6\x88$!!%$$# X$## X$\"# X\"# X"),
+          peg$decode("7.*G \".L\"\"2L3M*; \"0\x89\"\"1!3\x8A*/ \"0\x86\"\"1!3\x87*# \"73"),
+          peg$decode("!.m\"\"2m3n+K$0\x8B\"\"1!3\x8C*5 \"0\x8D\"\"1!3\x8E*) \"0\x8F\"\"1!3\x90+#%'\"%$\"# X\"# X"),
+          peg$decode("!7N+Q$.8\"\"2839+A%7O*# \" [+1%7S+'%4$6\x91$ %$$# X$## X$\"# X\"# X"),
+          peg$decode("!7N+k$.8\"\"2839+[%7O*# \" [+K%7S+A%7_+7%7l*# \" [+'%4&6\x92& %$&# X$%# X$$# X$## X$\"# X\"# X"),
+          peg$decode("!/\x93\"\"1$3\x94*) \"/\x95\"\"1#3\x96+' 4!6\x97!! %"),
+          peg$decode("!7P+b$!.8\"\"2839+-$7R+#%'\"%$\"# X\"# X*# \" [+7%.:\"\"2:3;+'%4#6\x98# %$## X$\"# X\"# X"),
+          peg$decode(" \\7+*) \"7-*# \"7Q+2$,/&7+*) \"7-*# \"7Q\"\"\" X"),
+          peg$decode(".<\"\"2<3=*q \".>\"\"2>3?*e \".@\"\"2@3A*Y \".B\"\"2B3C*M \".D\"\"2D3E*A \".2\"\"2233*5 \".6\"\"2637*) \".4\"\"2435"),
+          peg$decode("! \\7+*_ \"7-*Y \".<\"\"2<3=*M \".>\"\"2>3?*A \".@\"\"2@3A*5 \".B\"\"2B3C*) \".D\"\"2D3E,e&7+*_ \"7-*Y \".<\"\"2<3=*M \".>\"\"2>3?*A \".@\"\"2@3A*5 \".B\"\"2B3C*) \".D\"\"2D3E\"+& 4!6\x99! %"),
+          peg$decode("!7T+N$!.8\"\"2839+-$7^+#%'\"%$\"# X\"# X*# \" [+#%'\"%$\"# X\"# X"),
+          peg$decode("!7U*) \"7\\*# \"7X+& 4!6\x9A! %"),
+          peg$decode("! \\!7V+3$.J\"\"2J3K+#%'\"%$\"# X\"# X,>&!7V+3$.J\"\"2J3K+#%'\"%$\"# X\"# X\"+G$7W+=%.J\"\"2J3K*# \" [+'%4#6\x9B# %$## X$\"# X\"# X"),
+          peg$decode(" \\0\x9C\"\"1!3\x9D+,$,)&0\x9C\"\"1!3\x9D\"\"\" X"),
+          peg$decode("!0$\"\"1!3%+A$ \\0\x9E\"\"1!3\x9F,)&0\x9E\"\"1!3\x9F\"+#%'\"%$\"# X\"# X"),
+          peg$decode("!.o\"\"2o3p+A$7Y+7%.q\"\"2q3r+'%4#6\xA0# %$## X$\"# X\"# X"),
+          peg$decode("!!7Z+\xBF$.8\"\"2839+\xAF%7Z+\xA5%.8\"\"2839+\x95%7Z+\x8B%.8\"\"2839+{%7Z+q%.8\"\"2839+a%7Z+W%.8\"\"2839+G%7Z+=%.8\"\"2839+-%7[+#%'-%$-# X$,# X$+# X$*# X$)# X$(# X$'# X$&# X$%# X$$# X$## X$\"# X\"# X*\u0838 \"!.\xA1\"\"2\xA13\xA2+\xAF$7Z+\xA5%.8\"\"2839+\x95%7Z+\x8B%.8\"\"2839+{%7Z+q%.8\"\"2839+a%7Z+W%.8\"\"2839+G%7Z+=%.8\"\"2839+-%7[+#%',%$,# X$+# X$*# X$)# X$(# X$'# X$&# X$%# X$$# X$## X$\"# X\"# X*\u0795 \"!.\xA1\"\"2\xA13\xA2+\x95$7Z+\x8B%.8\"\"2839+{%7Z+q%.8\"\"2839+a%7Z+W%.8\"\"2839+G%7Z+=%.8\"\"2839+-%7[+#%'*%$*# X$)# X$(# X$'# X$&# X$%# X$$# X$## X$\"# X\"# X*\u070C \"!.\xA1\"\"2\xA13\xA2+{$7Z+q%.8\"\"2839+a%7Z+W%.8\"\"2839+G%7Z+=%.8\"\"2839+-%7[+#%'(%$(# X$'# X$&# X$%# X$$# X$## X$\"# X\"# X*\u069D \"!.\xA1\"\"2\xA13\xA2+a$7Z+W%.8\"\"2839+G%7Z+=%.8\"\"2839+-%7[+#%'&%$&# X$%# X$$# X$## X$\"# X\"# X*\u0648 \"!.\xA1\"\"2\xA13\xA2+G$7Z+=%.8\"\"2839+-%7[+#%'$%$$# X$## X$\"# X\"# X*\u060D \"!.\xA1\"\"2\xA13\xA2+-$7[+#%'\"%$\"# X\"# X*\u05EC \"!.\xA1\"\"2\xA13\xA2+-$7Z+#%'\"%$\"# X\"# X*\u05CB \"!7Z+\xA5$.\xA1\"\"2\xA13\xA2+\x95%7Z+\x8B%.8\"\"2839+{%7Z+q%.8\"\"2839+a%7Z+W%.8\"\"2839+G%7Z+=%.8\"\"2839+-%7[+#%'+%$+# X$*# X$)# X$(# X$'# X$&# X$%# X$$# X$## X$\"# X\"# X*\u0538 \"!7Z+\xB6$!.8\"\"2839+-$7Z+#%'\"%$\"# X\"# X*# \" [+\x8B%.\xA1\"\"2\xA13\xA2+{%7Z+q%.8\"\"2839+a%7Z+W%.8\"\"2839+G%7Z+=%.8\"\"2839+-%7[+#%'*%$*# X$)# X$(# X$'# X$&# X$%# X$$# X$## X$\"# X\"# X*\u0494 \"!7Z+\xC7$!.8\"\"2839+-$7Z+#%'\"%$\"# X\"# X*# \" [+\x9C%!.8\"\"2839+-$7Z+#%'\"%$\"# X\"# X*# \" [+q%.\xA1\"\"2\xA13\xA2+a%7Z+W%.8\"\"2839+G%7Z+=%.8\"\"2839+-%7[+#%')%$)# X$(# X$'# X$&# X$%# X$$# X$## X$\"# X\"# X*\u03DF \"!7Z+\xD8$!.8\"\"2839+-$7Z+#%'\"%$\"# X\"# X*# \" [+\xAD%!.8\"\"2839+-$7Z+#%'\"%$\"# X\"# X*# \" [+\x82%!.8\"\"2839+-$7Z+#%'\"%$\"# X\"# X*# \" [+W%.\xA1\"\"2\xA13\xA2+G%7Z+=%.8\"\"2839+-%7[+#%'(%$(# X$'# X$&# X$%# X$$# X$## X$\"# X\"# X*\u0319 \"!7Z+\xE9$!.8\"\"2839+-$7Z+#%'\"%$\"# X\"# X*# \" [+\xBE%!.8\"\"2839+-$7Z+#%'\"%$\"# X\"# X*# \" [+\x93%!.8\"\"2839+-$7Z+#%'\"%$\"# X\"# X*# \" [+h%!.8\"\"2839+-$7Z+#%'\"%$\"# X\"# X*# \" [+=%.\xA1\"\"2\xA13\xA2+-%7[+#%''%$'# X$&# X$%# X$$# X$## X$\"# X\"# X*\u0242 \"!7Z+\u0114$!.8\"\"2839+-$7Z+#%'\"%$\"# X\"# X*# \" [+\xE9%!.8\"\"2839+-$7Z+#%'\"%$\"# X\"# X*# \" [+\xBE%!.8\"\"2839+-$7Z+#%'\"%$\"# X\"# X*# \" [+\x93%!.8\"\"2839+-$7Z+#%'\"%$\"# X\"# X*# \" [+h%!.8\"\"2839+-$7Z+#%'\"%$\"# X\"# X*# \" [+=%.\xA1\"\"2\xA13\xA2+-%7Z+#%'(%$(# X$'# X$&# X$%# X$$# X$## X$\"# X\"# X*\u0140 \"!7Z+\u0135$!.8\"\"2839+-$7Z+#%'\"%$\"# X\"# X*# \" [+\u010A%!.8\"\"2839+-$7Z+#%'\"%$\"# X\"# X*# \" [+\xDF%!.8\"\"2839+-$7Z+#%'\"%$\"# X\"# X*# \" [+\xB4%!.8\"\"2839+-$7Z+#%'\"%$\"# X\"# X*# \" [+\x89%!.8\"\"2839+-$7Z+#%'\"%$\"# X\"# X*# \" [+^%!.8\"\"2839+-$7Z+#%'\"%$\"# X\"# X*# \" [+3%.\xA1\"\"2\xA13\xA2+#%'(%$(# X$'# X$&# X$%# X$$# X$## X$\"# X\"# X+& 4!6\xA3! %"),
+          peg$decode("!7#+S$7#*# \" [+C%7#*# \" [+3%7#*# \" [+#%'$%$$# X$## X$\"# X\"# X"),
+          peg$decode("!7Z+=$.8\"\"2839+-%7Z+#%'#%$## X$\"# X\"# X*# \"7\\"),
+          peg$decode("!7]+u$.J\"\"2J3K+e%7]+[%.J\"\"2J3K+K%7]+A%.J\"\"2J3K+1%7]+'%4'6\xA4' %$'# X$&# X$%# X$$# X$## X$\"# X\"# X"),
+          peg$decode("!.\xA5\"\"2\xA53\xA6+3$0\xA7\"\"1!3\xA8+#%'\"%$\"# X\"# X*\xA0 \"!.\xA9\"\"2\xA93\xAA+=$0\xAB\"\"1!3\xAC+-%7!+#%'#%$## X$\"# X\"# X*o \"!.\xAD\"\"2\xAD3\xAE+7$7!+-%7!+#%'#%$## X$\"# X\"# X*D \"!0\xAF\"\"1!3\xB0+-$7!+#%'\"%$\"# X\"# X*# \"7!"),
+          peg$decode("!!7!*# \" [+c$7!*# \" [+S%7!*# \" [+C%7!*# \" [+3%7!*# \" [+#%'%%$%# X$$# X$## X$\"# X\"# X+' 4!6\xB1!! %"),
+          peg$decode(" \\!.2\"\"2233+-$7`+#%'\"%$\"# X\"# X,>&!.2\"\"2233+-$7`+#%'\"%$\"# X\"# X\""),
+          peg$decode("7a*A \"7b*; \"7c*5 \"7d*/ \"7e*) \"7f*# \"7g"),
+          peg$decode("!/\xB2\"\"1*3\xB3+b$/\xB4\"\"1#3\xB5*G \"/\xB6\"\"1#3\xB7*; \"/\xB8\"\"1$3\xB9*/ \"/\xBA\"\"1#3\xBB*# \"76+(%4\"6\xBC\"! %$\"# X\"# X"),
+          peg$decode("!/\xBD\"\"1%3\xBE+J$/\xBF\"\"1%3\xC0*/ \"/\xC1\"\"1\"3\xC2*# \"76+(%4\"6\xC3\"! %$\"# X\"# X"),
+          peg$decode("!/\xC4\"\"1'3\xC5+2$7\x8F+(%4\"6\xC6\"! %$\"# X\"# X"),
+          peg$decode("!/\xC7\"\"1$3\xC8+2$7\xEF+(%4\"6\xC9\"! %$\"# X\"# X"),
+          peg$decode("!/\xCA\"\"1&3\xCB+2$7T+(%4\"6\xCC\"! %$\"# X\"# X"),
+          peg$decode("!/\xCD\"\"1\"3\xCE+R$!.>\"\"2>3?+-$76+#%'\"%$\"# X\"# X*# \" [+'%4\"6\xCF\" %$\"# X\"# X"),
+          peg$decode("!7h+T$!.>\"\"2>3?+-$7i+#%'\"%$\"# X\"# X*# \" [+)%4\"6\xD0\"\"! %$\"# X\"# X"),
+          peg$decode("! \\7j+&$,#&7j\"\"\" X+! (%"),
+          peg$decode("! \\7j+&$,#&7j\"\"\" X+! (%"),
+          peg$decode("7k*) \"7+*# \"7-"),
+          peg$decode(".o\"\"2o3p*e \".q\"\"2q3r*Y \".4\"\"2435*M \".8\"\"2839*A \".<\"\"2<3=*5 \".@\"\"2@3A*) \".B\"\"2B3C"),
+          peg$decode("!.6\"\"2637+u$7m+k% \\!.<\"\"2<3=+-$7m+#%'\"%$\"# X\"# X,>&!.<\"\"2<3=+-$7m+#%'\"%$\"# X\"# X\"+#%'#%$## X$\"# X\"# X"),
+          peg$decode("!7n+C$.>\"\"2>3?+3%7o+)%4#6\xD1#\"\" %$## X$\"# X\"# X"),
+          peg$decode(" \\7p*) \"7+*# \"7-+2$,/&7p*) \"7+*# \"7-\"\"\" X"),
+          peg$decode(" \\7p*) \"7+*# \"7-,/&7p*) \"7+*# \"7-\""),
+          peg$decode(".o\"\"2o3p*e \".q\"\"2q3r*Y \".4\"\"2435*M \".6\"\"2637*A \".8\"\"2839*5 \".@\"\"2@3A*) \".B\"\"2B3C"),
+          peg$decode("7\x90*# \"7r"),
+          peg$decode("!7\x8F+K$7'+A%7s+7%7'+-%7\x84+#%'%%$%# X$$# X$## X$\"# X\"# X"),
+          peg$decode("7M*# \"7t"),
+          peg$decode("!7+G$.8\"\"2839+7%7u*# \"7x+'%4#6\xD2# %$## X$\"# X\"# X"),
+          peg$decode("!7v*# \"7w+N$!.6\"\"2637+-$7\x83+#%'\"%$\"# X\"# X*# \" [+#%'\"%$\"# X\"# X"),
+          peg$decode("!.\xD3\"\"2\xD33\xD4+=$7\x80+3%7w*# \" [+#%'#%$## X$\"# X\"# X"),
+          peg$decode("!.4\"\"2435+-$7{+#%'\"%$\"# X\"# X"),
+          peg$decode("!7z+5$ \\7y,#&7y\"+#%'\"%$\"# X\"# X"),
+          peg$decode("7**) \"7+*# \"7-"),
+          peg$decode("7+*\x8F \"7-*\x89 \".2\"\"2233*} \".6\"\"2637*q \".8\"\"2839*e \".:\"\"2:3;*Y \".<\"\"2<3=*M \".>\"\"2>3?*A \".@\"\"2@3A*5 \".B\"\"2B3C*) \".D\"\"2D3E"),
+          peg$decode("!7|+k$ \\!.4\"\"2435+-$7|+#%'\"%$\"# X\"# X,>&!.4\"\"2435+-$7|+#%'\"%$\"# X\"# X\"+#%'\"%$\"# X\"# X"),
+          peg$decode("! \\7~,#&7~\"+k$ \\!.2\"\"2233+-$7}+#%'\"%$\"# X\"# X,>&!.2\"\"2233+-$7}+#%'\"%$\"# X\"# X\"+#%'\"%$\"# X\"# X"),
+          peg$decode(" \\7~,#&7~\""),
+          peg$decode("7+*w \"7-*q \".8\"\"2839*e \".:\"\"2:3;*Y \".<\"\"2<3=*M \".>\"\"2>3?*A \".@\"\"2@3A*5 \".B\"\"2B3C*) \".D\"\"2D3E"),
+          peg$decode("!7\"+\x8D$ \\7\"*G \"7!*A \".@\"\"2@3A*5 \".F\"\"2F3G*) \".J\"\"2J3K,M&7\"*G \"7!*A \".@\"\"2@3A*5 \".F\"\"2F3G*) \".J\"\"2J3K\"+'%4\"6\xD5\" %$\"# X\"# X"),
+          peg$decode("7\x81*# \"7\x82"),
+          peg$decode("!!7O+3$.:\"\"2:3;+#%'\"%$\"# X\"# X*# \" [+-$7S+#%'\"%$\"# X\"# X*# \" ["),
+          peg$decode(" \\7+*\x83 \"7-*} \".B\"\"2B3C*q \".D\"\"2D3E*e \".2\"\"2233*Y \".8\"\"2839*M \".:\"\"2:3;*A \".<\"\"2<3=*5 \".>\"\"2>3?*) \".@\"\"2@3A+\x8C$,\x89&7+*\x83 \"7-*} \".B\"\"2B3C*q \".D\"\"2D3E*e \".2\"\"2233*Y \".8\"\"2839*M \".:\"\"2:3;*A \".<\"\"2<3=*5 \".>\"\"2>3?*) \".@\"\"2@3A\"\"\" X"),
+          peg$decode(" \\7y,#&7y\""),
+          peg$decode("!/\x95\"\"1#3\xD6+y$.4\"\"2435+i% \\7!+&$,#&7!\"\"\" X+P%.J\"\"2J3K+@% \\7!+&$,#&7!\"\"\" X+'%4%6\xD7% %$%# X$$# X$## X$\"# X\"# X"),
+          peg$decode(".\xD8\"\"2\xD83\xD9"),
+          peg$decode(".\xDA\"\"2\xDA3\xDB"),
+          peg$decode(".\xDC\"\"2\xDC3\xDD"),
+          peg$decode(".\xDE\"\"2\xDE3\xDF"),
+          peg$decode(".\xE0\"\"2\xE03\xE1"),
+          peg$decode(".\xE2\"\"2\xE23\xE3"),
+          peg$decode(".\xE4\"\"2\xE43\xE5"),
+          peg$decode(".\xE6\"\"2\xE63\xE7"),
+          peg$decode(".\xE8\"\"2\xE83\xE9"),
+          peg$decode(".\xEA\"\"2\xEA3\xEB"),
+          peg$decode("!7\x85*S \"7\x86*M \"7\x88*G \"7\x89*A \"7\x8A*; \"7\x8B*5 \"7\x8C*/ \"7\x8D*) \"7\x8E*# \"76+& 4!6\xEC! %"),
+          peg$decode("!7\x84+K$7'+A%7\x91+7%7'+-%7\x93+#%'%%$%# X$$# X$## X$\"# X\"# X"),
+          peg$decode("!7\x92+' 4!6\xED!! %"),
+          peg$decode("!7!+7$7!+-%7!+#%'#%$## X$\"# X\"# X"),
+          peg$decode("! \\7**A \"7+*; \"7-*5 \"73*/ \"74*) \"7'*# \"7(,G&7**A \"7+*; \"7-*5 \"73*/ \"74*) \"7'*# \"7(\"+& 4!6\xEE! %"),
+          peg$decode("!7\xB5+_$ \\!7A+-$7\xB5+#%'\"%$\"# X\"# X,8&!7A+-$7\xB5+#%'\"%$\"# X\"# X\"+#%'\"%$\"# X\"# X"),
+          peg$decode("!79+R$!.:\"\"2:3;+-$79+#%'\"%$\"# X\"# X*# \" [+'%4\"6\xEF\" %$\"# X\"# X"),
+          peg$decode("!7:*j \"!7\x97+_$ \\!7A+-$7\x97+#%'\"%$\"# X\"# X,8&!7A+-$7\x97+#%'\"%$\"# X\"# X\"+#%'\"%$\"# X\"# X+& 4!6\xF0! %"),
+          peg$decode("!7L*# \"7\x98+c$ \\!7B+-$7\x9A+#%'\"%$\"# X\"# X,8&!7B+-$7\x9A+#%'\"%$\"# X\"# X\"+'%4\"6\xF1\" %$\"# X\"# X"),
+          peg$decode("!7\x99*# \" [+A$7@+7%7M+-%7?+#%'$%$$# X$## X$\"# X\"# X"),
+          peg$decode("!!76+_$ \\!7.+-$76+#%'\"%$\"# X\"# X,8&!7.+-$76+#%'\"%$\"# X\"# X\"+#%'\"%$\"# X\"# X*# \"7H+' 4!6\xF2!! %"),
+          peg$decode("7\x9B*) \"7\x9C*# \"7\x9F"),
+          peg$decode("!/\xF3\"\"1!3\xF4+<$7<+2%7\x9E+(%4#6\xF5#! %$## X$\"# X\"# X"),
+          peg$decode("!/\xF6\"\"1'3\xF7+<$7<+2%7\x9D+(%4#6\xF8#! %$## X$\"# X\"# X"),
+          peg$decode("! \\7!+&$,#&7!\"\"\" X+' 4!6\xF9!! %"),
+          peg$decode("!.\xFA\"\"2\xFA3\xFB+x$!.J\"\"2J3K+S$7!*# \" [+C%7!*# \" [+3%7!*# \" [+#%'$%$$# X$## X$\"# X\"# X*# \" [+'%4\"6\xFC\" %$\"# X\"# X"),
+          peg$decode("!76+N$!7<+-$7\xA0+#%'\"%$\"# X\"# X*# \" [+)%4\"6\xFD\"\"! %$\"# X\"# X"),
+          peg$decode("76*) \"7T*# \"7H"),
+          peg$decode("!7\xA2+_$ \\!7B+-$7\xA3+#%'\"%$\"# X\"# X,8&!7B+-$7\xA3+#%'\"%$\"# X\"# X\"+#%'\"%$\"# X\"# X"),
+          peg$decode("!/\xFE\"\"1&3\xFF*G \"/\u0100\"\"1'3\u0101*; \"/\u0102\"\"1$3\u0103*/ \"/\u0104\"\"1%3\u0105*# \"76+& 4!6\u0106! %"),
+          peg$decode("7\xA4*# \"7\x9F"),
+          peg$decode("!/\u0107\"\"1(3\u0108+O$7<+E%/\u0109\"\"1(3\u010A*/ \"/\u010B\"\"1(3\u010C*# \"76+#%'#%$## X$\"# X\"# X"),
+          peg$decode("!76+_$ \\!7A+-$76+#%'\"%$\"# X\"# X,8&!7A+-$76+#%'\"%$\"# X\"# X\"+#%'\"%$\"# X\"# X"),
+          peg$decode("! \\7!+&$,#&7!\"\"\" X+' 4!6\u010D!! %"),
+          peg$decode("!7\xA8+& 4!6\u010E! %"),
+          peg$decode("!7\xA9+s$7;+i%7\xAE+_% \\!7B+-$7\xAF+#%'\"%$\"# X\"# X,8&!7B+-$7\xAF+#%'\"%$\"# X\"# X\"+#%'$%$$# X$## X$\"# X\"# X"),
+          peg$decode("7\xAA*# \"7\xAB"),
+          peg$decode("/\u010F\"\"1$3\u0110*S \"/\u0111\"\"1%3\u0112*G \"/\u0113\"\"1%3\u0114*; \"/\u0115\"\"1%3\u0116*/ \"/\u0117\"\"1+3\u0118*# \"7\xAC"),
+          peg$decode("/\u0119\"\"1'3\u011A*/ \"/\u011B\"\"1)3\u011C*# \"7\xAC"),
+          peg$decode("76*# \"7\xAD"),
+          peg$decode("!/\u011D\"\"1\"3\u011E+-$76+#%'\"%$\"# X\"# X"),
+          peg$decode("7\xAC*# \"76"),
+          peg$decode("!76+7$7<+-%7\xB0+#%'#%$## X$\"# X\"# X"),
+          peg$decode("76*# \"7H"),
+          peg$decode("!7\xB2+7$7.+-%7\x8F+#%'#%$## X$\"# X\"# X"),
+          peg$decode("! \\7!+&$,#&7!\"\"\" X+' 4!6\u011F!! %"),
+          peg$decode("!7\x9D+' 4!6\u0120!! %"),
+          peg$decode("!7\xB5+d$ \\!7B+-$7\x9F+#%'\"%$\"# X\"# X,8&!7B+-$7\x9F+#%'\"%$\"# X\"# X\"+(%4\"6\u0121\"!!%$\"# X\"# X"),
+          peg$decode("!!77+k$ \\!.J\"\"2J3K+-$77+#%'\"%$\"# X\"# X,>&!.J\"\"2J3K+-$77+#%'\"%$\"# X\"# X\"+#%'\"%$\"# X\"# X+! (%"),
+          peg$decode("!7L*# \"7\x98+c$ \\!7B+-$7\xB7+#%'\"%$\"# X\"# X,8&!7B+-$7\xB7+#%'\"%$\"# X\"# X\"+'%4\"6\u0122\" %$\"# X\"# X"),
+          peg$decode("7\xB8*# \"7\x9F"),
+          peg$decode("!/\u0123\"\"1#3\u0124+<$7<+2%76+(%4#6\u0125#! %$## X$\"# X\"# X"),
+          peg$decode("! \\7!+&$,#&7!\"\"\" X+' 4!6\u0126!! %"),
+          peg$decode("!7\x9D+' 4!6\u0127!! %"),
+          peg$decode("! \\7\x99,#&7\x99\"+\x81$7@+w%7M+m%7?+c% \\!7B+-$7\x9F+#%'\"%$\"# X\"# X,8&!7B+-$7\x9F+#%'\"%$\"# X\"# X\"+'%4%6\u0128% %$%# X$$# X$## X$\"# X\"# X"),
+          peg$decode("7\xBD"),
+          peg$decode("!/\u0129\"\"1&3\u012A+s$7.+i%7\xC0+_% \\!7A+-$7\xC0+#%'\"%$\"# X\"# X,8&!7A+-$7\xC0+#%'\"%$\"# X\"# X\"+#%'$%$$# X$## X$\"# X\"# X*# \"7\xBE"),
+          peg$decode("!76+s$7.+i%7\xBF+_% \\!7A+-$7\xBF+#%'\"%$\"# X\"# X,8&!7A+-$7\xBF+#%'\"%$\"# X\"# X\"+#%'$%$$# X$## X$\"# X\"# X"),
+          peg$decode("!76+=$7<+3%76*# \"7H+#%'#%$## X$\"# X\"# X"),
+          peg$decode("7\xC1*G \"7\xC3*A \"7\xC5*; \"7\xC7*5 \"7\xC8*/ \"7\xC9*) \"7\xCA*# \"7\xBF"),
+          peg$decode("!/\u012B\"\"1%3\u012C+7$7<+-%7\xC2+#%'#%$## X$\"# X\"# X"),
+          peg$decode("!7I+' 4!6\u012D!! %"),
+          peg$decode("!/\u012E\"\"1&3\u012F+\xA5$7<+\x9B%7D+\x91%7\xC4+\x87% \\! \\7'+&$,#&7'\"\"\" X+-$7\xC4+#%'\"%$\"# X\"# X,G&! \\7'+&$,#&7'\"\"\" X+-$7\xC4+#%'\"%$\"# X\"# X\"+-%7E+#%'&%$&# X$%# X$$# X$## X$\"# X\"# X"),
+          peg$decode("7t*# \"7w"),
+          peg$decode("!/\u0130\"\"1%3\u0131+7$7<+-%7\xC6+#%'#%$## X$\"# X\"# X"),
+          peg$decode("!7I+' 4!6\u0132!! %"),
+          peg$decode("!/\u0133\"\"1&3\u0134+<$7<+2%7I+(%4#6\u0135#! %$## X$\"# X\"# X"),
+          peg$decode("!/\u0136\"\"1%3\u0137+_$7<+U%!/\u0138\"\"1$3\u0139+& 4!6\u013A! %*4 \"!/\u013B\"\"1%3\u013C+& 4!6\u013D! %+#%'#%$## X$\"# X\"# X"),
+          peg$decode("!/\u013E\"\"1)3\u013F+T$7<+J%/\u0140\"\"1#3\u0141*/ \"/\u0142\"\"1(3\u0143*# \"76+(%4#6\u0144#! %$## X$\"# X\"# X"),
+          peg$decode("!/\u0145\"\"1#3\u0146+\x9E$7<+\x94%7D+\x8A%!7\xCB+k$ \\!.D\"\"2D3E+-$7\xCB+#%'\"%$\"# X\"# X,>&!.D\"\"2D3E+-$7\xCB+#%'\"%$\"# X\"# X\"+#%'\"%$\"# X\"# X+-%7E+#%'%%$%# X$$# X$## X$\"# X\"# X"),
+          peg$decode("!/\u0147\"\"1(3\u0148*/ \"/\u0149\"\"1$3\u014A*# \"76+' 4!6\u014B!! %"),
+          peg$decode("!76+_$ \\!7A+-$76+#%'\"%$\"# X\"# X,8&!7A+-$76+#%'\"%$\"# X\"# X\"+#%'\"%$\"# X\"# X"),
+          peg$decode("!7\xCE+K$7.+A%7\xCE+7%7.+-%7\x8F+#%'%%$%# X$$# X$## X$\"# X\"# X"),
+          peg$decode("! \\7!+&$,#&7!\"\"\" X+' 4!6\u014C!! %"),
+          peg$decode("!7\xD0+c$ \\!7A+-$7\xD0+#%'\"%$\"# X\"# X,8&!7A+-$7\xD0+#%'\"%$\"# X\"# X\"+'%4\"6\u014D\" %$\"# X\"# X"),
+          peg$decode("!7\x98+c$ \\!7B+-$7\x9F+#%'\"%$\"# X\"# X,8&!7B+-$7\x9F+#%'\"%$\"# X\"# X\"+'%4\"6\u014E\" %$\"# X\"# X"),
+          peg$decode("!7L*T \"7\x98*N \"!7@*# \" [+=$7t+3%7?*# \" [+#%'#%$## X$\"# X\"# X+c$ \\!7B+-$7\x9F+#%'\"%$\"# X\"# X,8&!7B+-$7\x9F+#%'\"%$\"# X\"# X\"+'%4\"6\u014F\" %$\"# X\"# X"),
+          peg$decode("!7\xD3+c$ \\!7B+-$7\xD4+#%'\"%$\"# X\"# X,8&!7B+-$7\xD4+#%'\"%$\"# X\"# X\"+'%4\"6\u0150\" %$\"# X\"# X"),
+          peg$decode("!7\x95+& 4!6\u0151! %"),
+          peg$decode("!/\u0152\"\"1(3\u0153+<$7<+2%76+(%4#6\u0154#! %$## X$\"# X\"# X*j \"!/\u0155\"\"1&3\u0156+<$7<+2%76+(%4#6\u0157#! %$## X$\"# X\"# X*: \"!/\u0158\"\"1*3\u0159+& 4!6\u015A! %*# \"7\x9F"),
+          peg$decode("!!76+o$ \\!7A+2$76+(%4\"6\u015B\"! %$\"# X\"# X,=&!7A+2$76+(%4\"6\u015B\"! %$\"# X\"# X\"+)%4\"6\u015C\"\"! %$\"# X\"# X*# \" [+' 4!6\u015D!! %"),
+          peg$decode("!7\xD7+_$ \\!7A+-$7\xD7+#%'\"%$\"# X\"# X,8&!7A+-$7\xD7+#%'\"%$\"# X\"# X\"+#%'\"%$\"# X\"# X"),
+          peg$decode("!7\x98+_$ \\!7B+-$7\x9F+#%'\"%$\"# X\"# X,8&!7B+-$7\x9F+#%'\"%$\"# X\"# X\"+#%'\"%$\"# X\"# X"),
+          peg$decode("! \\7!+&$,#&7!\"\"\" X+' 4!6\u015E!! %"),
+          peg$decode("!7\xDA+_$ \\!7B+-$7\xDB+#%'\"%$\"# X\"# X,8&!7B+-$7\xDB+#%'\"%$\"# X\"# X\"+#%'\"%$\"# X\"# X"),
+          peg$decode("!/\u015F\"\"1&3\u0160*; \"/\u0161\"\"1'3\u0162*/ \"/\u0163\"\"1*3\u0164*# \"76+& 4!6\u0165! %"),
+          peg$decode("!/\u0166\"\"1&3\u0167+<$7<+2%7\xDC+(%4#6\u0168#! %$## X$\"# X\"# X*\x83 \"!/\xF6\"\"1'3\xF7+<$7<+2%7\x9D+(%4#6\u0169#! %$## X$\"# X\"# X*S \"!/\u016A\"\"1+3\u016B+<$7<+2%7\x9D+(%4#6\u016C#! %$## X$\"# X\"# X*# \"7\x9F"),
+          peg$decode("/\u016D\"\"1+3\u016E*k \"/\u016F\"\"1)3\u0170*_ \"/\u0171\"\"1(3\u0172*S \"/\u0173\"\"1'3\u0174*G \"/\u0175\"\"1&3\u0176*; \"/\u0177\"\"1*3\u0178*/ \"/\u0179\"\"1)3\u017A*# \"76"),
+          peg$decode("71*# \" ["),
+          peg$decode("!!76+o$ \\!7A+2$76+(%4\"6\u015B\"! %$\"# X\"# X,=&!7A+2$76+(%4\"6\u015B\"! %$\"# X\"# X\"+)%4\"6\u015C\"\"! %$\"# X\"# X*# \" [+' 4!6\u017B!! %"),
+          peg$decode("!7L*# \"7\x98+c$ \\!7B+-$7\xE0+#%'\"%$\"# X\"# X,8&!7B+-$7\xE0+#%'\"%$\"# X\"# X\"+'%4\"6\u017C\" %$\"# X\"# X"),
+          peg$decode("7\xB8*# \"7\x9F"),
+          peg$decode("!7\xE2+_$ \\!7A+-$7\xE2+#%'\"%$\"# X\"# X,8&!7A+-$7\xE2+#%'\"%$\"# X\"# X\"+#%'\"%$\"# X\"# X"),
+          peg$decode("!7\xE9+s$7.+i%7\xEC+_% \\!7B+-$7\xE3+#%'\"%$\"# X\"# X,8&!7B+-$7\xE3+#%'\"%$\"# X\"# X\"+#%'$%$$# X$## X$\"# X\"# X"),
+          peg$decode("7\xE4*; \"7\xE5*5 \"7\xE6*/ \"7\xE7*) \"7\xE8*# \"7\x9F"),
+          peg$decode("!/\u017D\"\"1#3\u017E+<$7<+2%7\xEF+(%4#6\u017F#! %$## X$\"# X\"# X"),
+          peg$decode("!/\u0180\"\"1%3\u0181+<$7<+2%7T+(%4#6\u0182#! %$## X$\"# X\"# X"),
+          peg$decode("!/\u0183\"\"1(3\u0184+B$7<+8%7\\*# \"7Y+(%4#6\u0185#! %$## X$\"# X\"# X"),
+          peg$decode("!/\u0186\"\"1&3\u0187+<$7<+2%76+(%4#6\u0188#! %$## X$\"# X\"# X"),
+          peg$decode("!/\u0189\"\"1%3\u018A+T$!7<+5$ \\7!,#&7!\"+#%'\"%$\"# X\"# X*# \" [+'%4\"6\u018B\" %$\"# X\"# X"),
+          peg$decode("!7\xEA+K$7;+A%76+7%7;+-%7\xEB+#%'%%$%# X$$# X$## X$\"# X\"# X"),
+          peg$decode("!/\x95\"\"1#3\xD6*# \"76+' 4!6\u018C!! %"),
+          peg$decode("!/\xB4\"\"1#3\u018D*G \"/\xB6\"\"1#3\u018E*; \"/\xBA\"\"1#3\u018F*/ \"/\xB8\"\"1$3\u0190*# \"76+' 4!6\u0191!! %"),
+          peg$decode("!7\xED+H$!7C+-$7\xEE+#%'\"%$\"# X\"# X*# \" [+#%'\"%$\"# X\"# X"),
+          peg$decode("!7U*) \"7\\*# \"7X+& 4!6\u0192! %"),
+          peg$decode("!!7!*# \" [+c$7!*# \" [+S%7!*# \" [+C%7!*# \" [+3%7!*# \" [+#%'%%$%# X$$# X$## X$\"# X\"# X+' 4!6\u0193!! %"),
+          peg$decode("!!7!+C$7!*# \" [+3%7!*# \" [+#%'#%$## X$\"# X\"# X+' 4!6\u0194!! %"),
+          peg$decode("7\xBD"),
+          peg$decode("!7\x9D+d$ \\!7B+-$7\xF2+#%'\"%$\"# X\"# X,8&!7B+-$7\xF2+#%'\"%$\"# X\"# X\"+(%4\"6\u0195\"!!%$\"# X\"# X"),
+          peg$decode("7\xF3*# \"7\x9F"),
+          peg$decode("!.\u0196\"\"2\u01963\u0197+N$7<+D%.\u0198\"\"2\u01983\u0199*) \".\u019A\"\"2\u019A3\u019B+(%4#6\u019C#! %$## X$\"# X\"# X"),
+          peg$decode("!7\x9D+d$ \\!7B+-$7\x9F+#%'\"%$\"# X\"# X,8&!7B+-$7\x9F+#%'\"%$\"# X\"# X\"+(%4\"6\u019D\"!!%$\"# X\"# X"),
+          peg$decode("!76+7$70+-%7\xF6+#%'#%$## X$\"# X\"# X"),
+          peg$decode(" \\72*) \"74*# \"7.,/&72*) \"74*# \"7.\""),
+          peg$decode(" \\7%,#&7%\""),
+          peg$decode("!7\xF9+=$.8\"\"2839+-%7\xFA+#%'#%$## X$\"# X\"# X"),
+          peg$decode("!/\u019E\"\"1%3\u019F*) \"/\u01A0\"\"1$3\u01A1+' 4!6\u01A2!! %"),
+          peg$decode("!7\xFB+N$!.8\"\"2839+-$7^+#%'\"%$\"# X\"# X*# \" [+#%'\"%$\"# X\"# X"),
+          peg$decode("!7\\*) \"7X*# \"7\x82+' 4!6\u01A3!! %"),
+          peg$decode("! \\7\xFD*) \"7-*# \"7\xFE,/&7\xFD*) \"7-*# \"7\xFE\"+! (%"),
+          peg$decode("7\"*S \"7!*M \".F\"\"2F3G*A \".J\"\"2J3K*5 \".H\"\"2H3I*) \".N\"\"2N3O"),
+          peg$decode(".L\"\"2L3M*\x95 \".B\"\"2B3C*\x89 \".<\"\"2<3=*} \".R\"\"2R3S*q \".T\"\"2T3U*e \".V\"\"2V3W*Y \".P\"\"2P3Q*M \".@\"\"2@3A*A \".D\"\"2D3E*5 \".2\"\"2233*) \".>\"\"2>3?"),
+          peg$decode("!7\u0100+h$.8\"\"2839+X%7\xFA+N%!.\u01A4\"\"2\u01A43\u01A5+-$7\xEB+#%'\"%$\"# X\"# X*# \" [+#%'$%$$# X$## X$\"# X\"# X"),
+          peg$decode("!/\u01A6\"\"1%3\u01A7*) \"/\u01A8\"\"1$3\u01A9+' 4!6\u01A2!! %"),
+          peg$decode("!7\xEB+Q$/\xB4\"\"1#3\xB5*7 \"/\xB6\"\"1#3\xB7*+ \" \\7+,#&7+\"+'%4\"6\u01AA\" %$\"# X\"# X"),
+          peg$decode("!7\u0104+\x8F$.F\"\"2F3G+%7\u0103+u%.F\"\"2F3G+e%7\u0103+[%.F\"\"2F3G+K%7\u0103+A%.F\"\"2F3G+1%7\u0105+'%4)6\u01AB) %$)# X$(# X$'# X$&# X$%# X$$# X$## X$\"# X\"# X"),
+          peg$decode("!7#+A$7#+7%7#+-%7#+#%'$%$$# X$## X$\"# X\"# X"),
+          peg$decode("!7\u0103+-$7\u0103+#%'\"%$\"# X\"# X"),
+          peg$decode("!7\u0103+7$7\u0103+-%7\u0103+#%'#%$## X$\"# X\"# X")
+        ],
+
+        peg$currPos          = 0,
+        peg$reportedPos      = 0,
+        peg$cachedPos        = 0,
+        peg$cachedPosDetails = { line: 1, column: 1, seenCR: false },
+        peg$maxFailPos       = 0,
+        peg$maxFailExpected  = [],
+        peg$silentFails      = 0,
+
+        peg$result;
+
+    if ("startRule" in options) {
+      if (!(options.startRule in peg$startRuleIndices)) {
+        throw new Error("Can't start parsing from rule \"" + options.startRule + "\".");
+      }
+
+      peg$startRuleIndex = peg$startRuleIndices[options.startRule];
+    }
+
+    function text() {
+      return input.substring(peg$reportedPos, peg$currPos);
+    }
+
+    function offset() {
+      return peg$reportedPos;
+    }
+
+    function line() {
+      return peg$computePosDetails(peg$reportedPos).line;
+    }
+
+    function column() {
+      return peg$computePosDetails(peg$reportedPos).column;
+    }
+
+    function expected(description) {
+      throw peg$buildException(
+        null,
+        [{ type: "other", description: description }],
+        peg$reportedPos
+      );
+    }
+
+    function error(message) {
+      throw peg$buildException(message, null, peg$reportedPos);
+    }
+
+    function peg$computePosDetails(pos) {
+      function advance(details, startPos, endPos) {
+        var p, ch;
+
+        for (p = startPos; p < endPos; p++) {
+          ch = input.charAt(p);
+          if (ch === "\n") {
+            if (!details.seenCR) { details.line++; }
+            details.column = 1;
+            details.seenCR = false;
+          } else if (ch === "\r" || ch === "\u2028" || ch === "\u2029") {
+            details.line++;
+            details.column = 1;
+            details.seenCR = true;
+          } else {
+            details.column++;
+            details.seenCR = false;
+          }
+        }
+      }
+
+      if (peg$cachedPos !== pos) {
+        if (peg$cachedPos > pos) {
+          peg$cachedPos = 0;
+          peg$cachedPosDetails = { line: 1, column: 1, seenCR: false };
+        }
+        advance(peg$cachedPosDetails, peg$cachedPos, pos);
+        peg$cachedPos = pos;
+      }
+
+      return peg$cachedPosDetails;
+    }
+
+    function peg$fail(expected) {
+      if (peg$currPos < peg$maxFailPos) { return; }
+
+      if (peg$currPos > peg$maxFailPos) {
+        peg$maxFailPos = peg$currPos;
+        peg$maxFailExpected = [];
+      }
+
+      peg$maxFailExpected.push(expected);
+    }
+
+    function peg$buildException(message, expected, pos) {
+      function cleanupExpected(expected) {
+        var i = 1;
+
+        expected.sort(function(a, b) {
+          if (a.description < b.description) {
+            return -1;
+          } else if (a.description > b.description) {
+            return 1;
+          } else {
+            return 0;
+          }
+        });
+
+        while (i < expected.length) {
+          if (expected[i - 1] === expected[i]) {
+            expected.splice(i, 1);
+          } else {
+            i++;
+          }
+        }
+      }
+
+      function buildMessage(expected, found) {
+        function stringEscape(s) {
+          function hex(ch) { return ch.charCodeAt(0).toString(16).toUpperCase(); }
+
+          return s
+            .replace(/\\/g,   '\\\\')
+            .replace(/"/g,    '\\"')
+            .replace(/\x08/g, '\\b')
+            .replace(/\t/g,   '\\t')
+            .replace(/\n/g,   '\\n')
+            .replace(/\f/g,   '\\f')
+            .replace(/\r/g,   '\\r')
+            .replace(/[\x00-\x07\x0B\x0E\x0F]/g, function(ch) { return '\\x0' + hex(ch); })
+            .replace(/[\x10-\x1F\x80-\xFF]/g,    function(ch) { return '\\x'  + hex(ch); })
+            .replace(/[\u0180-\u0FFF]/g,         function(ch) { return '\\u0' + hex(ch); })
+            .replace(/[\u1080-\uFFFF]/g,         function(ch) { return '\\u'  + hex(ch); });
+        }
+
+        var expectedDescs = new Array(expected.length),
+            expectedDesc, foundDesc, i;
+
+        for (i = 0; i < expected.length; i++) {
+          expectedDescs[i] = expected[i].description;
+        }
+
+        expectedDesc = expected.length > 1
+          ? expectedDescs.slice(0, -1).join(", ")
+              + " or "
+              + expectedDescs[expected.length - 1]
+          : expectedDescs[0];
+
+        foundDesc = found ? "\"" + stringEscape(found) + "\"" : "end of input";
+
+        return "Expected " + expectedDesc + " but " + foundDesc + " found.";
+      }
+
+      var posDetails = peg$computePosDetails(pos),
+          found      = pos < input.length ? input.charAt(pos) : null;
+
+      if (expected !== null) {
+        cleanupExpected(expected);
+      }
+
+      return new SyntaxError(
+        message !== null ? message : buildMessage(expected, found),
+        expected,
+        found,
+        pos,
+        posDetails.line,
+        posDetails.column
+      );
+    }
+
+    function peg$decode(s) {
+      var bc = new Array(s.length), i;
+
+      for (i = 0; i < s.length; i++) {
+        bc[i] = s.charCodeAt(i) - 32;
+      }
+
+      return bc;
+    }
+
+    function peg$parseRule(index) {
+      var bc    = peg$bytecode[index],
+          ip    = 0,
+          ips   = [],
+          end   = bc.length,
+          ends  = [],
+          stack = [],
+          params, i;
+
+      function protect(object) {
+        return Object.prototype.toString.apply(object) === "[object Array]" ? [] : object;
+      }
+
+      while (true) {
+        while (ip < end) {
+          switch (bc[ip]) {
+            case 0:
+              stack.push(protect(peg$consts[bc[ip + 1]]));
+              ip += 2;
+              break;
+
+            case 1:
+              stack.push(peg$currPos);
+              ip++;
+              break;
+
+            case 2:
+              stack.pop();
+              ip++;
+              break;
+
+            case 3:
+              peg$currPos = stack.pop();
+              ip++;
+              break;
+
+            case 4:
+              stack.length -= bc[ip + 1];
+              ip += 2;
+              break;
+
+            case 5:
+              stack.splice(-2, 1);
+              ip++;
+              break;
+
+            case 6:
+              stack[stack.length - 2].push(stack.pop());
+              ip++;
+              break;
+
+            case 7:
+              stack.push(stack.splice(stack.length - bc[ip + 1], bc[ip + 1]));
+              ip += 2;
+              break;
+
+            case 8:
+              stack.pop();
+              stack.push(input.substring(stack[stack.length - 1], peg$currPos));
+              ip++;
+              break;
+
+            case 9:
+              ends.push(end);
+              ips.push(ip + 3 + bc[ip + 1] + bc[ip + 2]);
+
+              if (stack[stack.length - 1]) {
+                end = ip + 3 + bc[ip + 1];
+                ip += 3;
+              } else {
+                end = ip + 3 + bc[ip + 1] + bc[ip + 2];
+                ip += 3 + bc[ip + 1];
+              }
+
+              break;
+
+            case 10:
+              ends.push(end);
+              ips.push(ip + 3 + bc[ip + 1] + bc[ip + 2]);
+
+              if (stack[stack.length - 1] === peg$FAILED) {
+                end = ip + 3 + bc[ip + 1];
+                ip += 3;
+              } else {
+                end = ip + 3 + bc[ip + 1] + bc[ip + 2];
+                ip += 3 + bc[ip + 1];
+              }
+
+              break;
+
+            case 11:
+              ends.push(end);
+              ips.push(ip + 3 + bc[ip + 1] + bc[ip + 2]);
+
+              if (stack[stack.length - 1] !== peg$FAILED) {
+                end = ip + 3 + bc[ip + 1];
+                ip += 3;
+              } else {
+                end = ip + 3 + bc[ip + 1] + bc[ip + 2];
+                ip += 3 + bc[ip + 1];
+              }
+
+              break;
+
+            case 12:
+              if (stack[stack.length - 1] !== peg$FAILED) {
+                ends.push(end);
+                ips.push(ip);
+
+                end = ip + 2 + bc[ip + 1];
+                ip += 2;
+              } else {
+                ip += 2 + bc[ip + 1];
+              }
+
+              break;
+
+            case 13:
+              ends.push(end);
+              ips.push(ip + 3 + bc[ip + 1] + bc[ip + 2]);
+
+              if (input.length > peg$currPos) {
+                end = ip + 3 + bc[ip + 1];
+                ip += 3;
+              } else {
+                end = ip + 3 + bc[ip + 1] + bc[ip + 2];
+                ip += 3 + bc[ip + 1];
+              }
+
+              break;
+
+            case 14:
+              ends.push(end);
+              ips.push(ip + 4 + bc[ip + 2] + bc[ip + 3]);
+
+              if (input.substr(peg$currPos, peg$consts[bc[ip + 1]].length) === peg$consts[bc[ip + 1]]) {
+                end = ip + 4 + bc[ip + 2];
+                ip += 4;
+              } else {
+                end = ip + 4 + bc[ip + 2] + bc[ip + 3];
+                ip += 4 + bc[ip + 2];
+              }
+
+              break;
+
+            case 15:
+              ends.push(end);
+              ips.push(ip + 4 + bc[ip + 2] + bc[ip + 3]);
+
+              if (input.substr(peg$currPos, peg$consts[bc[ip + 1]].length).toLowerCase() === peg$consts[bc[ip + 1]]) {
+                end = ip + 4 + bc[ip + 2];
+                ip += 4;
+              } else {
+                end = ip + 4 + bc[ip + 2] + bc[ip + 3];
+                ip += 4 + bc[ip + 2];
+              }
+
+              break;
+
+            case 16:
+              ends.push(end);
+              ips.push(ip + 4 + bc[ip + 2] + bc[ip + 3]);
+
+              if (peg$consts[bc[ip + 1]].test(input.charAt(peg$currPos))) {
+                end = ip + 4 + bc[ip + 2];
+                ip += 4;
+              } else {
+                end = ip + 4 + bc[ip + 2] + bc[ip + 3];
+                ip += 4 + bc[ip + 2];
+              }
+
+              break;
+
+            case 17:
+              stack.push(input.substr(peg$currPos, bc[ip + 1]));
+              peg$currPos += bc[ip + 1];
+              ip += 2;
+              break;
+
+            case 18:
+              stack.push(peg$consts[bc[ip + 1]]);
+              peg$currPos += peg$consts[bc[ip + 1]].length;
+              ip += 2;
+              break;
+
+            case 19:
+              stack.push(peg$FAILED);
+              if (peg$silentFails === 0) {
+                peg$fail(peg$consts[bc[ip + 1]]);
+              }
+              ip += 2;
+              break;
+
+            case 20:
+              peg$reportedPos = stack[stack.length - 1 - bc[ip + 1]];
+              ip += 2;
+              break;
+
+            case 21:
+              peg$reportedPos = peg$currPos;
+              ip++;
+              break;
+
+            case 22:
+              params = bc.slice(ip + 4, ip + 4 + bc[ip + 3]);
+              for (i = 0; i < bc[ip + 3]; i++) {
+                params[i] = stack[stack.length - 1 - params[i]];
+              }
+
+              stack.splice(
+                stack.length - bc[ip + 2],
+                bc[ip + 2],
+                peg$consts[bc[ip + 1]].apply(null, params)
+              );
+
+              ip += 4 + bc[ip + 3];
+              break;
+
+            case 23:
+              stack.push(peg$parseRule(bc[ip + 1]));
+              ip += 2;
+              break;
+
+            case 24:
+              peg$silentFails++;
+              ip++;
+              break;
+
+            case 25:
+              peg$silentFails--;
+              ip++;
+              break;
+
+            default:
+              throw new Error("Invalid opcode: " + bc[ip] + ".");
+          }
+        }
+
+        if (ends.length > 0) {
+          end = ends.pop();
+          ip = ips.pop();
+        } else {
+          break;
+        }
+      }
+
+      return stack[0];
+    }
+
+
+      options.data = {}; // Object to which header attributes will be assigned during parsing
+
+      function list (first, rest) {
+        return [first].concat(rest);
+      }
+
+
+    peg$result = peg$parseRule(peg$startRuleIndex);
+
+    if (peg$result !== peg$FAILED && peg$currPos === input.length) {
+      return peg$result;
+    } else {
+      if (peg$result !== peg$FAILED && peg$currPos < input.length) {
+        peg$fail({ type: "end", description: "end of input" });
+      }
+
+      throw peg$buildException(null, peg$maxFailExpected, peg$maxFailPos);
+    }
+  }
+
+  return {
+    SyntaxError: SyntaxError,
+    parse:       parse
+  };
+})();
+},{}],12:[function(require,module,exports){
+"use strict";
+/**
+ * @fileoverview Hacks - This file contains all of the things we
+ * wish we didn't have to do, just for interop.  It is similar to
+ * Utils, which provides actually useful and relevant functions for
+ * a SIP library. Methods in this file are grouped by vendor, so
+ * as to most easily track when particular hacks may not be necessary anymore.
+ */
+
+module.exports = function (SIP) {
+
+//keep to quiet jshint, and remain consistent with other files
+SIP = SIP;
+
+var Hacks = {
+  AllBrowsers: {
+    maskDtls: function (message) {
+      if (message.body) {
+        message.body = message.body.replace(/ UDP\/TLS\/RTP\/SAVP/gmi, " RTP/SAVP");
+      }
+    },
+    unmaskDtls: function (sdp) {
+      /**
+       * Chrome does not handle DTLS correctly (Canaray does, but not production)
+       * keeping Chrome as SDES until DTLS is fixed (comment out 'is_opera' condition)
+       *
+       * UPDATE: May 21, 2014
+       * Chrome 35 now properly defaults to DTLS.  Only Opera remains using SDES
+       *
+       * UPDATE: 2014-09-24
+       * Opera now supports DTLS by default as well.
+       *
+       **/
+      return sdp.replace(/ RTP\/SAVP/gmi, " UDP/TLS/RTP/SAVP");
+    }
+  },
+  Firefox: {
+    /* Condition to detect if hacks are applicable */
+    isFirefox: function () {
+      return typeof mozRTCPeerConnection !== 'undefined';
+    },
+
+    cannotHandleExtraWhitespace: function (message) {
+      if (this.isFirefox() && message.body) {
+        message.body = message.body.replace(/ \r\n/g, "\r\n");
+      }
+    },
+
+    hasMissingCLineInSDP: function (sdp) {
+      /*
+       * This is a Firefox hack to insert valid sdp when getDescription is
+       * called with the constraint offerToReceiveVideo = false.
+       * We search for either a c-line at the top of the sdp above all
+       * m-lines. If that does not exist then we search for a c-line
+       * beneath each m-line. If it is missing a c-line, we insert
+       * a fake c-line with the ip address 0.0.0.0. This is then valid
+       * sdp and no media will be sent for that m-line.
+       *
+       * Valid SDP is:
+       * m=
+       * i=
+       * c=
+       */
+      var insertAt, mlines;
+      if (sdp.indexOf('c=') > sdp.indexOf('m=')) {
+
+        // Find all m= lines
+        mlines = sdp.match(/m=.*\r\n.*/g);
+        for (var i=0; i<mlines.length; i++) {
+
+          // If it has an i= line, check if the next line is the c= line
+          if (mlines[i].toString().search(/i=.*/) >= 0) {
+            insertAt = sdp.indexOf(mlines[i].toString())+mlines[i].toString().length;
+            if (sdp.substr(insertAt,2)!=='c=') {
+              sdp = sdp.substr(0,insertAt) + '\r\nc=IN IP4 0.0.0.0' + sdp.substr(insertAt);
+            }
+
+          // else add the C line if it's missing
+          } else if (mlines[i].toString().search(/c=.*/) < 0) {
+            insertAt = sdp.indexOf(mlines[i].toString().match(/.*/))+mlines[i].toString().match(/.*/).toString().length;
+            sdp = sdp.substr(0,insertAt) + '\r\nc=IN IP4 0.0.0.0' + sdp.substr(insertAt);
+          }
+        }
+      }
+      return sdp;
+    },
+  },
+
+  Chrome: {
+    needsExplicitlyInactiveSDP: function (sdp) {
+      var sub, index;
+
+      if (Hacks.Firefox.isFirefox()) { // Fix this in Firefox before sending
+        index = sdp.indexOf('m=video 0');
+        if (index !== -1) {
+          sub = sdp.substr(index);
+          sub = sub.replace(/\r\nc=IN IP4.*\r\n$/,
+                            '\r\nc=IN IP4 0.0.0.0\r\na=inactive\r\n');
+          return sdp.substr(0, index) + sub;
+        }
+      }
+      return sdp;
+    },
+
+    getsConfusedAboutGUM: function (session) {
+      if (session.mediaHandler) {
+        session.mediaHandler.close();
+      }
+    }
+  }
+};
+return Hacks;
+};
+},{}],13:[function(require,module,exports){
+"use strict";
+var levels = {
+  'error': 0,
+  'warn': 1,
+  'log': 2,
+  'debug': 3
+};
+
+module.exports = function (console) {
+
+var LoggerFactory = function () {
+  var logger,
+    level = 2,
+    builtinEnabled = true,
+    connector = null;
+
+    this.loggers = {};
+
+    logger = this.getLogger('sip.loggerfactory');
+
+
+  Object.defineProperties(this, {
+    builtinEnabled: {
+      get: function(){ return builtinEnabled; },
+      set: function(value){
+        if (typeof value === 'boolean') {
+          builtinEnabled = value;
+        } else {
+          logger.error('invalid "builtinEnabled" parameter value: '+ JSON.stringify(value));
+        }
+      }
+    },
+
+    level: {
+      get: function() {return level; },
+      set: function(value) {
+        if (value >= 0 && value <=3) {
+          level = value;
+        } else if (value > 3) {
+          level = 3;
+        } else if (levels.hasOwnProperty(value)) {
+          level = levels[value];
+        } else {
+          logger.error('invalid "level" parameter value: '+ JSON.stringify(value));
+        }
+      }
+    },
+
+    connector: {
+      get: function() {return connector; },
+      set: function(value){
+        if(value === null || value === "" || value === undefined) {
+          connector = null;
+        } else if (typeof value === 'function') {
+          connector = value;
+        } else {
+          logger.error('invalid "connector" parameter value: '+ JSON.stringify(value));
+        }
+      }
+    }
+  });
+};
+
+LoggerFactory.prototype.print = function(target, category, label, content) {
+  if (typeof content === 'string') {
+    var prefix = [new Date(), category];
+    if (label) {
+      prefix.push(label);
+    }
+    content = prefix.concat(content).join(' | ');
+  }
+  target.call(console, content);
+};
+
+function Logger (logger, category, label) {
+  this.logger = logger;
+  this.category = category;
+  this.label = label;
+}
+
+Object.keys(levels).forEach(function (targetName) {
+  Logger.prototype[targetName] = function (content) {
+    this.logger[targetName](this.category, this.label, content);
+  };
+
+  LoggerFactory.prototype[targetName] = function (category, label, content) {
+    if (this.level >= levels[targetName]) {
+      if (this.builtinEnabled) {
+        this.print(console[targetName], category, label, content);
+      }
+
+      if (this.connector) {
+        this.connector(targetName, category, label, content);
+      }
+    }
+  };
+});
+
+LoggerFactory.prototype.getLogger = function(category, label) {
+  var logger;
+
+  if (label && this.level === 3) {
+    return new Logger(this, category, label);
+  } else if (this.loggers[category]) {
+    return this.loggers[category];
+  } else {
+    logger = new Logger(this, category);
+    this.loggers[category] = logger;
+    return logger;
+  }
+};
+
+return LoggerFactory;
+};
+
+},{}],14:[function(require,module,exports){
+"use strict";
+/**
+ * @fileoverview MediaHandler
+ */
+
+/* MediaHandler
+ * @class PeerConnection helper Class.
+ * @param {SIP.Session} session
+ * @param {Object} [options]
+ */
+module.exports = function (EventEmitter) {
+var MediaHandler = function(session, options) {
+  // keep jshint happy
+  session = session;
+  options = options;
+};
+
+MediaHandler.prototype = Object.create(EventEmitter.prototype, {
+  isReady: {value: function isReady () {}},
+
+  close: {value: function close () {}},
+
+  /**
+   * @param {Object} [mediaHint] A custom object describing the media to be used during this session.
+   */
+  getDescription: {value: function getDescription (mediaHint) {
+    // keep jshint happy
+    mediaHint = mediaHint;
+  }},
+
+  /**
+  * Message reception.
+  * @param {String} type
+  * @param {String} description
+  */
+  setDescription: {value: function setDescription (description) {
+    // keep jshint happy
+    description = description;
+  }}
+});
+
+return MediaHandler;
+};
+
+},{}],15:[function(require,module,exports){
+"use strict";
+/**
+ * @fileoverview SIP NameAddrHeader
+ */
+
+/**
+ * @augments SIP
+ * @class Class creating a Name Address SIP header.
+ *
+ * @param {SIP.URI} uri
+ * @param {String} [displayName]
+ * @param {Object} [parameters]
+ *
+ */
+module.exports = function (SIP) {
+var NameAddrHeader;
+
+NameAddrHeader = function(uri, displayName, parameters) {
+  var param;
+
+  // Checks
+  if(!uri || !(uri instanceof SIP.URI)) {
+    throw new TypeError('missing or invalid "uri" parameter');
+  }
+
+  // Initialize parameters
+  this.uri = uri;
+  this.parameters = {};
+
+  for (param in parameters) {
+    this.setParam(param, parameters[param]);
+  }
+
+  Object.defineProperties(this, {
+    friendlyName: {
+      get: function() { return this.displayName || uri.aor; }
+    },
+
+    displayName: {
+      get: function() { return displayName; },
+      set: function(value) {
+        displayName = (value === 0) ? '0' : value;
+      }
+    }
+  });
+};
+NameAddrHeader.prototype = {
+  setParam: function (key, value) {
+    if(key) {
+      this.parameters[key.toLowerCase()] = (typeof value === 'undefined' || value === null) ? null : value.toString();
+    }
+  },
+  getParam: SIP.URI.prototype.getParam,
+  hasParam: SIP.URI.prototype.hasParam,
+  deleteParam: SIP.URI.prototype.deleteParam,
+  clearParams: SIP.URI.prototype.clearParams,
+
+  clone: function() {
+    return new NameAddrHeader(
+      this.uri.clone(),
+      this.displayName,
+      JSON.parse(JSON.stringify(this.parameters)));
+  },
+
+  toString: function() {
+    var body, parameter;
+
+    body  = (this.displayName || this.displayName === 0) ? '"' + this.displayName + '" ' : '';
+    body += '<' + this.uri.toString() + '>';
+
+    for (parameter in this.parameters) {
+      body += ';' + parameter;
+
+      if (this.parameters[parameter] !== null) {
+        body += '='+ this.parameters[parameter];
+      }
+    }
+
+    return body;
+  }
+};
+
+
+/**
+  * Parse the given string and returns a SIP.NameAddrHeader instance or undefined if
+  * it is an invalid NameAddrHeader.
+  * @public
+  * @param {String} name_addr_header
+  */
+NameAddrHeader.parse = function(name_addr_header) {
+  name_addr_header = SIP.Grammar.parse(name_addr_header,'Name_Addr_Header');
+
+  if (name_addr_header !== -1) {
+    return name_addr_header;
+  } else {
+    return undefined;
+  }
+};
+
+SIP.NameAddrHeader = NameAddrHeader;
+};
+
+},{}],16:[function(require,module,exports){
+"use strict";
+/**
+ * @fileoverview SIP Message Parser
+ */
+
+/**
+ * Extract and parse every header of a SIP message.
+ * @augments SIP
+ * @namespace
+ */
+module.exports = function (SIP) {
+var Parser;
+
+function getHeader(data, headerStart) {
+  var
+    // 'start' position of the header.
+    start = headerStart,
+    // 'end' position of the header.
+    end = 0,
+    // 'partial end' position of the header.
+    partialEnd = 0;
+
+  //End of message.
+  if (data.substring(start, start + 2).match(/(^\r\n)/)) {
+    return -2;
+  }
+
+  while(end === 0) {
+    // Partial End of Header.
+    partialEnd = data.indexOf('\r\n', start);
+
+    // 'indexOf' returns -1 if the value to be found never occurs.
+    if (partialEnd === -1) {
+      return partialEnd;
+    }
+
+    if(!data.substring(partialEnd + 2, partialEnd + 4).match(/(^\r\n)/) && data.charAt(partialEnd + 2).match(/(^\s+)/)) {
+      // Not the end of the message. Continue from the next position.
+      start = partialEnd + 2;
+    } else {
+      end = partialEnd;
+    }
+  }
+
+  return end;
+}
+
+function parseHeader(message, data, headerStart, headerEnd) {
+  var header, idx, length, parsed,
+    hcolonIndex = data.indexOf(':', headerStart),
+    headerName = data.substring(headerStart, hcolonIndex).trim(),
+    headerValue = data.substring(hcolonIndex + 1, headerEnd).trim();
+
+  // If header-field is well-known, parse it.
+  switch(headerName.toLowerCase()) {
+    case 'via':
+    case 'v':
+      message.addHeader('via', headerValue);
+      if(message.getHeaders('via').length === 1) {
+        parsed = message.parseHeader('Via');
+        if(parsed) {
+          message.via = parsed;
+          message.via_branch = parsed.branch;
+        }
+      } else {
+        parsed = 0;
+      }
+      break;
+    case 'from':
+    case 'f':
+      message.setHeader('from', headerValue);
+      parsed = message.parseHeader('from');
+      if(parsed) {
+        message.from = parsed;
+        message.from_tag = parsed.getParam('tag');
+      }
+      break;
+    case 'to':
+    case 't':
+      message.setHeader('to', headerValue);
+      parsed = message.parseHeader('to');
+      if(parsed) {
+        message.to = parsed;
+        message.to_tag = parsed.getParam('tag');
+      }
+      break;
+    case 'record-route':
+      parsed = SIP.Grammar.parse(headerValue, 'Record_Route');
+
+      if (parsed === -1) {
+        parsed = undefined;
+        break;
+      }
+
+      length = parsed.length;
+      for (idx = 0; idx < length; idx++) {
+        header = parsed[idx];
+        message.addHeader('record-route', headerValue.substring(header.position, header.offset));
+        message.headers['Record-Route'][message.getHeaders('record-route').length - 1].parsed = header.parsed;
+      }
+      break;
+    case 'call-id':
+    case 'i':
+      message.setHeader('call-id', headerValue);
+      parsed = message.parseHeader('call-id');
+      if(parsed) {
+        message.call_id = headerValue;
+      }
+      break;
+    case 'contact':
+    case 'm':
+      parsed = SIP.Grammar.parse(headerValue, 'Contact');
+
+      if (parsed === -1) {
+        parsed = undefined;
+        break;
+      }
+
+      length = parsed.length;
+      for (idx = 0; idx < length; idx++) {
+        header = parsed[idx];
+        message.addHeader('contact', headerValue.substring(header.position, header.offset));
+        message.headers['Contact'][message.getHeaders('contact').length - 1].parsed = header.parsed;
+      }
+      break;
+    case 'content-length':
+    case 'l':
+      message.setHeader('content-length', headerValue);
+      parsed = message.parseHeader('content-length');
+      break;
+    case 'content-type':
+    case 'c':
+      message.setHeader('content-type', headerValue);
+      parsed = message.parseHeader('content-type');
+      break;
+    case 'cseq':
+      message.setHeader('cseq', headerValue);
+      parsed = message.parseHeader('cseq');
+      if(parsed) {
+        message.cseq = parsed.value;
+      }
+      if(message instanceof SIP.IncomingResponse) {
+        message.method = parsed.method;
+      }
+      break;
+    case 'max-forwards':
+      message.setHeader('max-forwards', headerValue);
+      parsed = message.parseHeader('max-forwards');
+      break;
+    case 'www-authenticate':
+      message.setHeader('www-authenticate', headerValue);
+      parsed = message.parseHeader('www-authenticate');
+      break;
+    case 'proxy-authenticate':
+      message.setHeader('proxy-authenticate', headerValue);
+      parsed = message.parseHeader('proxy-authenticate');
+      break;
+    case 'refer-to':
+    case 'r':
+      message.setHeader('refer-to', headerValue);
+      parsed = message.parseHeader('refer-to');
+      if (parsed) {
+        message.refer_to = parsed;
+      }
+      break;
+    default:
+      // Do not parse this header.
+      message.setHeader(headerName, headerValue);
+      parsed = 0;
+  }
+
+  if (parsed === undefined) {
+    return {
+      error: 'error parsing header "'+ headerName +'"'
+    };
+  } else {
+    return true;
+  }
+}
+
+/** Parse SIP Message
+ * @function
+ * @param {String} message SIP message.
+ * @param {Object} logger object.
+ * @returns {SIP.IncomingRequest|SIP.IncomingResponse|undefined}
+ */
+Parser = {};
+Parser.parseMessage = function(data, ua) {
+  var message, firstLine, contentLength, bodyStart, parsed,
+    headerStart = 0,
+    headerEnd = data.indexOf('\r\n'),
+    logger = ua.getLogger('sip.parser');
+
+  if(headerEnd === -1) {
+    logger.warn('no CRLF found, not a SIP message, discarded');
+    return;
+  }
+
+  // Parse first line. Check if it is a Request or a Reply.
+  firstLine = data.substring(0, headerEnd);
+  parsed = SIP.Grammar.parse(firstLine, 'Request_Response');
+
+  if(parsed === -1) {
+    logger.warn('error parsing first line of SIP message: "' + firstLine + '"');
+    return;
+  } else if(!parsed.status_code) {
+    message = new SIP.IncomingRequest(ua);
+    message.method = parsed.method;
+    message.ruri = parsed.uri;
+  } else {
+    message = new SIP.IncomingResponse(ua);
+    message.status_code = parsed.status_code;
+    message.reason_phrase = parsed.reason_phrase;
+  }
+
+  message.data = data;
+  headerStart = headerEnd + 2;
+
+  /* Loop over every line in data. Detect the end of each header and parse
+  * it or simply add to the headers collection.
+  */
+  while(true) {
+    headerEnd = getHeader(data, headerStart);
+
+    // The SIP message has normally finished.
+    if(headerEnd === -2) {
+      bodyStart = headerStart + 2;
+      break;
+    }
+    // data.indexOf returned -1 due to a malformed message.
+    else if(headerEnd === -1) {
+      logger.error('malformed message');
+      return;
+    }
+
+    parsed = parseHeader(message, data, headerStart, headerEnd);
+
+    if(parsed !== true) {
+      logger.error(parsed.error);
+      return;
+    }
+
+    headerStart = headerEnd + 2;
+  }
+
+  /* RFC3261 18.3.
+   * If there are additional bytes in the transport packet
+   * beyond the end of the body, they MUST be discarded.
+   */
+  if(message.hasHeader('content-length')) {
+    contentLength = message.getHeader('content-length');
+    message.body = data.substr(bodyStart, contentLength);
+  } else {
+    message.body = data.substring(bodyStart);
+  }
+
+  return message;
+};
+
+SIP.Parser = Parser;
+};
+
+},{}],17:[function(require,module,exports){
+"use strict";
+module.exports = function (SIP) {
+
+var RegisterContext;
+
+RegisterContext = function (ua) {
+  var params = {},
+      regId = 1;
+
+  this.registrar = ua.configuration.registrarServer;
+  this.expires = ua.configuration.registerExpires;
+
+
+  // Contact header
+  this.contact = ua.contact.toString();
+
+  if(regId) {
+    this.contact += ';reg-id='+ regId;
+    this.contact += ';+sip.instance="<urn:uuid:'+ ua.configuration.instanceId+'>"';
+  }
+
+  // Call-ID and CSeq values RFC3261 10.2
+  this.call_id = SIP.Utils.createRandomToken(22);
+  this.cseq = 80;
+
+  this.to_uri = ua.configuration.uri;
+
+  params.to_uri = this.to_uri;
+  params.to_displayName = ua.configuration.displayName;
+  params.call_id = this.call_id;
+  params.cseq = this.cseq;
+
+  // Extends ClientContext
+  SIP.Utils.augment(this, SIP.ClientContext, [ua, 'REGISTER', this.registrar, {params: params}]);
+
+  this.registrationTimer = null;
+  this.registrationExpiredTimer = null;
+
+  // Set status
+  this.registered = false;
+
+  this.logger = ua.getLogger('sip.registercontext');
+};
+
+RegisterContext.prototype = {
+  register: function (options) {
+    var self = this, extraHeaders;
+
+    // Handle Options
+    this.options = options || {};
+    extraHeaders = (this.options.extraHeaders || []).slice();
+    extraHeaders.push('Contact: ' + this.contact + ';expires=' + this.expires);
+    extraHeaders.push('Allow: ' + SIP.UA.C.ALLOWED_METHODS.toString());
+
+    // Save original extraHeaders to be used in .close
+    this.closeHeaders = this.options.closeWithHeaders ?
+      (this.options.extraHeaders || []).slice() : [];
+
+    this.receiveResponse = function(response) {
+      var contact, expires,
+        contacts = response.getHeaders('contact').length,
+        cause;
+
+      // Discard responses to older REGISTER/un-REGISTER requests.
+      if(response.cseq !== this.cseq) {
+        return;
+      }
+
+      // Clear registration timer
+      if (this.registrationTimer !== null) {
+        SIP.Timers.clearTimeout(this.registrationTimer);
+        this.registrationTimer = null;
+      }
+
+      switch(true) {
+        case /^1[0-9]{2}$/.test(response.status_code):
+          this.emit('progress', response);
+          break;
+        case /^2[0-9]{2}$/.test(response.status_code):
+          this.emit('accepted', response);
+
+          if(response.hasHeader('expires')) {
+            expires = response.getHeader('expires');
+          }
+
+          if (this.registrationExpiredTimer !== null) {
+            SIP.Timers.clearTimeout(this.registrationExpiredTimer);
+            this.registrationExpiredTimer = null;
+          }
+
+          // Search the Contact pointing to us and update the expires value accordingly.
+          if (!contacts) {
+            this.logger.warn('no Contact header in response to REGISTER, response ignored');
+            break;
+          }
+
+          while(contacts--) {
+            contact = response.parseHeader('contact', contacts);
+            if(contact.uri.user === this.ua.contact.uri.user) {
+              expires = contact.getParam('expires');
+              break;
+            } else {
+              contact = null;
+            }
+          }
+
+          if (!contact) {
+            this.logger.warn('no Contact header pointing to us, response ignored');
+            break;
+          }
+
+          if(!expires) {
+            expires = this.expires;
+          }
+
+          // Re-Register before the expiration interval has elapsed.
+          // For that, decrease the expires value. ie: 3 seconds
+          this.registrationTimer = SIP.Timers.setTimeout(function() {
+            self.registrationTimer = null;
+            self.register(self.options);
+          }, (expires * 1000) - 3000);
+          this.registrationExpiredTimer = SIP.Timers.setTimeout(function () {
+            self.logger.warn('registration expired');
+            if (self.registered) {
+              self.unregistered(null, SIP.C.causes.EXPIRES);
+            }
+          }, expires * 1000);
+
+          //Save gruu values
+          if (contact.hasParam('temp-gruu')) {
+            this.ua.contact.temp_gruu = SIP.URI.parse(contact.getParam('temp-gruu').replace(/"/g,''));
+          }
+          if (contact.hasParam('pub-gruu')) {
+            this.ua.contact.pub_gruu = SIP.URI.parse(contact.getParam('pub-gruu').replace(/"/g,''));
+          }
+
+          this.registered = true;
+          this.emit('registered', response || null);
+          break;
+        // Interval too brief RFC3261 10.2.8
+        case /^423$/.test(response.status_code):
+          if(response.hasHeader('min-expires')) {
+            // Increase our registration interval to the suggested minimum
+            this.expires = response.getHeader('min-expires');
+            // Attempt the registration again immediately
+            this.register(this.options);
+          } else { //This response MUST contain a Min-Expires header field
+            this.logger.warn('423 response received for REGISTER without Min-Expires');
+            this.registrationFailure(response, SIP.C.causes.SIP_FAILURE_CODE);
+          }
+          break;
+        default:
+          cause = SIP.Utils.sipErrorCause(response.status_code);
+          this.registrationFailure(response, cause);
+      }
+    };
+
+    this.onRequestTimeout = function() {
+      this.registrationFailure(null, SIP.C.causes.REQUEST_TIMEOUT);
+    };
+
+    this.onTransportError = function() {
+      this.registrationFailure(null, SIP.C.causes.CONNECTION_ERROR);
+    };
+
+    this.cseq++;
+    this.request.cseq = this.cseq;
+    this.request.setHeader('cseq', this.cseq + ' REGISTER');
+    this.request.extraHeaders = extraHeaders;
+    this.send();
+  },
+
+  registrationFailure: function (response, cause) {
+    this.emit('failed', response || null, cause || null);
+  },
+
+  onTransportClosed: function() {
+    this.registered_before = this.registered;
+    if (this.registrationTimer !== null) {
+      SIP.Timers.clearTimeout(this.registrationTimer);
+      this.registrationTimer = null;
+    }
+
+    if (this.registrationExpiredTimer !== null) {
+      SIP.Timers.clearTimeout(this.registrationExpiredTimer);
+      this.registrationExpiredTimer = null;
+    }
+
+    if(this.registered) {
+      this.unregistered(null, SIP.C.causes.CONNECTION_ERROR);
+    }
+  },
+
+  onTransportConnected: function() {
+    this.register(this.options);
+  },
+
+  close: function() {
+    var options = {
+      all: false,
+      extraHeaders: this.closeHeaders
+    };
+
+    this.registered_before = this.registered;
+    this.unregister(options);
+  },
+
+  unregister: function(options) {
+    var extraHeaders;
+
+    options = options || {};
+
+    if(!this.registered && !options.all) {
+      this.logger.warn('already unregistered');
+      return;
+    }
+
+    extraHeaders = (options.extraHeaders || []).slice();
+
+    this.registered = false;
+
+    // Clear the registration timer.
+    if (this.registrationTimer !== null) {
+      SIP.Timers.clearTimeout(this.registrationTimer);
+      this.registrationTimer = null;
+    }
+
+    if(options.all) {
+      extraHeaders.push('Contact: *');
+      extraHeaders.push('Expires: 0');
+    } else {
+      extraHeaders.push('Contact: '+ this.contact + ';expires=0');
+    }
+
+
+    this.receiveResponse = function(response) {
+      var cause;
+
+      switch(true) {
+        case /^1[0-9]{2}$/.test(response.status_code):
+          this.emit('progress', response);
+          break;
+        case /^2[0-9]{2}$/.test(response.status_code):
+          this.emit('accepted', response);
+          if (this.registrationExpiredTimer !== null) {
+            SIP.Timers.clearTimeout(this.registrationExpiredTimer);
+            this.registrationExpiredTimer = null;
+          }
+          this.unregistered(response);
+          break;
+        default:
+          cause = SIP.Utils.sipErrorCause(response.status_code);
+          this.unregistered(response,cause);
+      }
+    };
+
+    this.onRequestTimeout = function() {
+      // Not actually unregistered...
+      //this.unregistered(null, SIP.C.causes.REQUEST_TIMEOUT);
+    };
+
+    this.onTransportError = function() {
+      // Not actually unregistered...
+      //this.unregistered(null, SIP.C.causes.CONNECTION_ERROR);
+    };
+
+    this.cseq++;
+    this.request.cseq = this.cseq;
+    this.request.setHeader('cseq', this.cseq + ' REGISTER');
+    this.request.extraHeaders = extraHeaders;
+
+    this.send();
+  },
+
+  unregistered: function(response, cause) {
+    this.registered = false;
+    this.emit('unregistered', response || null, cause || null);
+  }
+
+};
+
+
+SIP.RegisterContext = RegisterContext;
+};
+
+},{}],18:[function(require,module,exports){
+"use strict";
+
+/**
+ * @fileoverview Request Sender
+ */
+
+/**
+ * @augments SIP
+ * @class Class creating a request sender.
+ * @param {Object} applicant
+ * @param {SIP.UA} ua
+ */
+module.exports = function (SIP) {
+var RequestSender;
+
+RequestSender = function(applicant, ua) {
+  this.logger = ua.getLogger('sip.requestsender');
+  this.ua = ua;
+  this.applicant = applicant;
+  this.method = applicant.request.method;
+  this.request = applicant.request;
+  this.credentials = null;
+  this.challenged = false;
+  this.staled = false;
+
+  // If ua is in closing process or even closed just allow sending Bye and ACK
+  if (ua.status === SIP.UA.C.STATUS_USER_CLOSED && (this.method !== SIP.C.BYE || this.method !== SIP.C.ACK)) {
+    this.onTransportError();
+  }
+};
+
+/**
+* Create the client transaction and send the message.
+*/
+RequestSender.prototype = {
+  send: function() {
+    switch(this.method) {
+      case "INVITE":
+        this.clientTransaction = new SIP.Transactions.InviteClientTransaction(this, this.request, this.ua.transport);
+        break;
+      case "ACK":
+        this.clientTransaction = new SIP.Transactions.AckClientTransaction(this, this.request, this.ua.transport);
+        break;
+      default:
+        this.clientTransaction = new SIP.Transactions.NonInviteClientTransaction(this, this.request, this.ua.transport);
+    }
+    this.clientTransaction.send();
+
+    return this.clientTransaction;
+  },
+
+  /**
+  * Callback fired when receiving a request timeout error from the client transaction.
+  * To be re-defined by the applicant.
+  * @event
+  */
+  onRequestTimeout: function() {
+    this.applicant.onRequestTimeout();
+  },
+
+  /**
+  * Callback fired when receiving a transport error from the client transaction.
+  * To be re-defined by the applicant.
+  * @event
+  */
+  onTransportError: function() {
+    this.applicant.onTransportError();
+  },
+
+  /**
+  * Called from client transaction when receiving a correct response to the request.
+  * Authenticate request if needed or pass the response back to the applicant.
+  * @param {SIP.IncomingResponse} response
+  */
+  receiveResponse: function(response) {
+    var cseq, challenge, authorization_header_name,
+      status_code = response.status_code;
+
+    /*
+    * Authentication
+    * Authenticate once. _challenged_ flag used to avoid infinite authentications.
+    */
+    if (status_code === 401 || status_code === 407) {
+
+      // Get and parse the appropriate WWW-Authenticate or Proxy-Authenticate header.
+      if (response.status_code === 401) {
+        challenge = response.parseHeader('www-authenticate');
+        authorization_header_name = 'authorization';
+      } else {
+        challenge = response.parseHeader('proxy-authenticate');
+        authorization_header_name = 'proxy-authorization';
+      }
+
+      // Verify it seems a valid challenge.
+      if (! challenge) {
+        this.logger.warn(response.status_code + ' with wrong or missing challenge, cannot authenticate');
+        this.applicant.receiveResponse(response);
+        return;
+      }
+
+      if (!this.challenged || (!this.staled && challenge.stale === true)) {
+        if (!this.credentials) {
+          this.credentials = this.ua.configuration.authenticationFactory(this.ua);
+        }
+
+        // Verify that the challenge is really valid.
+        if (!this.credentials.authenticate(this.request, challenge)) {
+          this.applicant.receiveResponse(response);
+          return;
+        }
+        this.challenged = true;
+
+        if (challenge.stale) {
+          this.staled = true;
+        }
+
+        if (response.method === SIP.C.REGISTER) {
+          cseq = this.applicant.cseq += 1;
+        } else if (this.request.dialog){
+          cseq = this.request.dialog.local_seqnum += 1;
+        } else {
+          cseq = this.request.cseq + 1;
+          this.request.cseq = cseq;
+        }
+        this.request.setHeader('cseq', cseq +' '+ this.method);
+
+        this.request.setHeader(authorization_header_name, this.credentials.toString());
+        this.send();
+      } else {
+        this.applicant.receiveResponse(response);
+      }
+    } else {
+      this.applicant.receiveResponse(response);
+    }
+  }
+};
+
+SIP.RequestSender = RequestSender;
+};
+
+},{}],19:[function(require,module,exports){
+/**
+ * @name SIP
+ * @namespace
+ */
+"use strict";
+
+module.exports = function (environment) {
+
+var pkg = require('../package.json');
+
+var SIP = Object.defineProperties({}, {
+  version: {
+    get: function(){ return pkg.version; }
+  },
+  name: {
+    get: function(){ return pkg.title; }
+  }
+});
+
+require('./Utils')(SIP, environment);
+SIP.LoggerFactory = require('./LoggerFactory')(environment.console);
+SIP.EventEmitter = require('./EventEmitter')(environment.console);
+SIP.C = require('./Constants')(SIP.name, SIP.version);
+SIP.Exceptions = require('./Exceptions');
+SIP.Timers = require('./Timers')(environment.timers);
+SIP.Transport = environment.Transport(SIP, environment.WebSocket);
+require('./Parser')(SIP);
+require('./SIPMessage')(SIP);
+require('./URI')(SIP);
+require('./NameAddrHeader')(SIP);
+require('./Transactions')(SIP);
+require('./Dialogs')(SIP);
+require('./RequestSender')(SIP);
+require('./RegisterContext')(SIP);
+SIP.MediaHandler = require('./MediaHandler')(SIP.EventEmitter);
+require('./ClientContext')(SIP);
+require('./ServerContext')(SIP);
+require('./Session')(SIP, environment);
+require('./Subscription')(SIP);
+SIP.WebRTC = require('./WebRTC')(SIP, environment);
+require('./UA')(SIP, environment);
+SIP.Hacks = require('./Hacks')(SIP);
+require('./SanityCheck')(SIP);
+SIP.DigestAuthentication = require('./DigestAuthentication')(SIP.Utils);
+SIP.Grammar = require('./Grammar')(SIP);
+
+return SIP;
+};
+
+},{"../package.json":2,"./ClientContext":3,"./Constants":4,"./Dialogs":6,"./DigestAuthentication":7,"./EventEmitter":8,"./Exceptions":9,"./Grammar":10,"./Hacks":12,"./LoggerFactory":13,"./MediaHandler":14,"./NameAddrHeader":15,"./Parser":16,"./RegisterContext":17,"./RequestSender":18,"./SIPMessage":20,"./SanityCheck":21,"./ServerContext":22,"./Session":23,"./Subscription":25,"./Timers":26,"./Transactions":27,"./UA":29,"./URI":30,"./Utils":31,"./WebRTC":32}],20:[function(require,module,exports){
+"use strict";
+/**
+ * @fileoverview SIP Message
+ */
+
+module.exports = function (SIP) {
+var
+  OutgoingRequest,
+  IncomingMessage,
+  IncomingRequest,
+  IncomingResponse;
+
+function getSupportedHeader (request) {
+  var allowUnregistered = request.ua.configuration.hackAllowUnregisteredOptionTags;
+  var optionTags = [];
+  var optionTagSet = {};
+
+  if (request.method === SIP.C.REGISTER) {
+    optionTags.push('path', 'gruu');
+  } else if (request.method === SIP.C.INVITE &&
+             (request.ua.contact.pub_gruu || request.ua.contact.temp_gruu)) {
+    optionTags.push('gruu');
+  }
+
+  if (request.ua.configuration.rel100 === SIP.C.supported.SUPPORTED) {
+    optionTags.push('100rel');
+  }
+  if (request.ua.configuration.replaces === SIP.C.supported.SUPPORTED) {
+    optionTags.push('replaces');
+  }
+
+  optionTags.push('outbound');
+
+  optionTags = optionTags.concat(request.ua.configuration.extraSupported);
+
+  optionTags = optionTags.filter(function(optionTag) {
+    var registered = SIP.C.OPTION_TAGS[optionTag];
+    var unique = !optionTagSet[optionTag];
+    optionTagSet[optionTag] = true;
+    return (registered || allowUnregistered) && unique;
+  });
+
+  return 'Supported: ' + optionTags.join(', ') + '\r\n';
+}
+
+/**
+ * @augments SIP
+ * @class Class for outgoing SIP request.
+ * @param {String} method request method
+ * @param {String} ruri request uri
+ * @param {SIP.UA} ua
+ * @param {Object} params parameters that will have priority over ua.configuration parameters:
+ * <br>
+ *  - cseq, call_id, from_tag, from_uri, from_displayName, to_uri, to_tag, route_set
+ * @param {Object} [headers] extra headers
+ * @param {String} [body]
+ */
+OutgoingRequest = function(method, ruri, ua, params, extraHeaders, body) {
+  var
+    to,
+    from,
+    call_id,
+    cseq,
+    to_uri,
+    from_uri;
+
+  params = params || {};
+
+  // Mandatory parameters check
+  if(!method || !ruri || !ua) {
+    return null;
+  }
+
+  this.logger = ua.getLogger('sip.sipmessage');
+  this.ua = ua;
+  this.headers = {};
+  this.method = method;
+  this.ruri = ruri;
+  this.body = body;
+  this.extraHeaders = (extraHeaders || []).slice();
+  this.statusCode = params.status_code;
+  this.reasonPhrase = params.reason_phrase;
+
+  // Fill the Common SIP Request Headers
+
+  // Route
+  if (params.route_set) {
+    this.setHeader('route', params.route_set);
+  } else if (ua.configuration.usePreloadedRoute){
+    this.setHeader('route', ua.transport.server.sip_uri);
+  }
+
+  // Via
+  // Empty Via header. Will be filled by the client transaction.
+  this.setHeader('via', '');
+
+  // Max-Forwards
+  this.setHeader('max-forwards', SIP.UA.C.MAX_FORWARDS);
+
+  // To
+  to_uri = params.to_uri || ruri;
+  to = (params.to_displayName || params.to_displayName === 0) ? '"' + params.to_displayName + '" ' : '';
+  to += '<' + (to_uri && to_uri.toRaw ? to_uri.toRaw() : to_uri) + '>';
+  to += params.to_tag ? ';tag=' + params.to_tag : '';
+  this.to = new SIP.NameAddrHeader.parse(to);
+  this.setHeader('to', to);
+
+  // From
+  from_uri = params.from_uri || ua.configuration.uri;
+  if (params.from_displayName || params.from_displayName === 0) {
+    from = '"' + params.from_displayName + '" ';
+  } else if (ua.configuration.displayName) {
+    from = '"' + ua.configuration.displayName + '" ';
+  } else {
+    from = '';
+  }
+  from += '<' + (from_uri && from_uri.toRaw ? from_uri.toRaw() : from_uri) + '>;tag=';
+  from += params.from_tag || SIP.Utils.newTag();
+  this.from = new SIP.NameAddrHeader.parse(from);
+  this.setHeader('from', from);
+
+  // Call-ID
+  call_id = params.call_id || (ua.configuration.sipjsId + SIP.Utils.createRandomToken(15));
+  this.call_id = call_id;
+  this.setHeader('call-id', call_id);
+
+  // CSeq
+  cseq = params.cseq || Math.floor(Math.random() * 10000);
+  this.cseq = cseq;
+  this.setHeader('cseq', cseq + ' ' + method);
+};
+
+OutgoingRequest.prototype = {
+  /**
+   * Replace the the given header by the given value.
+   * @param {String} name header name
+   * @param {String | Array} value header value
+   */
+  setHeader: function(name, value) {
+    this.headers[SIP.Utils.headerize(name)] = (value instanceof Array) ? value : [value];
+  },
+
+  /**
+   * Get the value of the given header name at the given position.
+   * @param {String} name header name
+   * @returns {String|undefined} Returns the specified header, undefined if header doesn't exist.
+   */
+  getHeader: function(name) {
+    var regexp, idx,
+      length = this.extraHeaders.length,
+      header = this.headers[SIP.Utils.headerize(name)];
+
+    if(header) {
+      if(header[0]) {
+        return header[0];
+      }
+    } else {
+      regexp = new RegExp('^\\s*' + name + '\\s*:','i');
+      for (idx = 0; idx < length; idx++) {
+        header = this.extraHeaders[idx];
+        if (regexp.test(header)) {
+          return header.substring(header.indexOf(':')+1).trim();
+        }
+      }
+    }
+
+    return;
+  },
+
+  /**
+   * Get the header/s of the given name.
+   * @param {String} name header name
+   * @returns {Array} Array with all the headers of the specified name.
+   */
+  getHeaders: function(name) {
+    var idx, length, regexp,
+      header = this.headers[SIP.Utils.headerize(name)],
+      result = [];
+
+    if(header) {
+      length = header.length;
+      for (idx = 0; idx < length; idx++) {
+        result.push(header[idx]);
+      }
+      return result;
+    } else {
+      length = this.extraHeaders.length;
+      regexp = new RegExp('^\\s*' + name + '\\s*:','i');
+      for (idx = 0; idx < length; idx++) {
+        header = this.extraHeaders[idx];
+        if (regexp.test(header)) {
+          result.push(header.substring(header.indexOf(':')+1).trim());
+        }
+      }
+      return result;
+    }
+  },
+
+  /**
+   * Verify the existence of the given header.
+   * @param {String} name header name
+   * @returns {boolean} true if header with given name exists, false otherwise
+   */
+  hasHeader: function(name) {
+    var regexp, idx,
+      length = this.extraHeaders.length;
+
+    if (this.headers[SIP.Utils.headerize(name)]) {
+      return true;
+    } else {
+      regexp = new RegExp('^\\s*' + name + '\\s*:','i');
+      for (idx = 0; idx < length; idx++) {
+        if (regexp.test(this.extraHeaders[idx])) {
+          return true;
+        }
+      }
+    }
+
+    return false;
+  },
+
+  toString: function() {
+    var msg = '', header, length, idx;
+
+    msg += this.method + ' ' + (this.ruri.toRaw ? this.ruri.toRaw() : this.ruri) + ' SIP/2.0\r\n';
+
+    for (header in this.headers) {
+      length = this.headers[header].length;
+      for (idx = 0; idx < length; idx++) {
+        msg += header + ': ' + this.headers[header][idx] + '\r\n';
+      }
+    }
+
+    length = this.extraHeaders.length;
+    for (idx = 0; idx < length; idx++) {
+      msg += this.extraHeaders[idx].trim() +'\r\n';
+    }
+
+    msg += getSupportedHeader(this);
+    msg += 'User-Agent: ' + this.ua.configuration.userAgentString +'\r\n';
+
+    if(this.body) {
+      length = SIP.Utils.str_utf8_length(this.body);
+      msg += 'Content-Length: ' + length + '\r\n\r\n';
+      msg += this.body;
+    } else {
+      msg += 'Content-Length: 0\r\n\r\n';
+    }
+
+    return msg;
+  }
+};
+
+/**
+ * @augments SIP
+ * @class Class for incoming SIP message.
+ */
+IncomingMessage = function(){
+  this.data = null;
+  this.headers = null;
+  this.method =  null;
+  this.via = null;
+  this.via_branch = null;
+  this.call_id = null;
+  this.cseq = null;
+  this.from = null;
+  this.from_tag = null;
+  this.to = null;
+  this.to_tag = null;
+  this.body = null;
+};
+
+IncomingMessage.prototype = {
+  /**
+  * Insert a header of the given name and value into the last position of the
+  * header array.
+  * @param {String} name header name
+  * @param {String} value header value
+  */
+  addHeader: function(name, value) {
+    var header = { raw: value };
+
+    name = SIP.Utils.headerize(name);
+
+    if(this.headers[name]) {
+      this.headers[name].push(header);
+    } else {
+      this.headers[name] = [header];
+    }
+  },
+
+  /**
+   * Get the value of the given header name at the given position.
+   * @param {String} name header name
+   * @returns {String|undefined} Returns the specified header, null if header doesn't exist.
+   */
+  getHeader: function(name) {
+    var header = this.headers[SIP.Utils.headerize(name)];
+
+    if(header) {
+      if(header[0]) {
+        return header[0].raw;
+      }
+    } else {
+      return;
+    }
+  },
+
+  /**
+   * Get the header/s of the given name.
+   * @param {String} name header name
+   * @returns {Array} Array with all the headers of the specified name.
+   */
+  getHeaders: function(name) {
+    var idx, length,
+      header = this.headers[SIP.Utils.headerize(name)],
+      result = [];
+
+    if(!header) {
+      return [];
+    }
+
+    length = header.length;
+    for (idx = 0; idx < length; idx++) {
+      result.push(header[idx].raw);
+    }
+
+    return result;
+  },
+
+  /**
+   * Verify the existence of the given header.
+   * @param {String} name header name
+   * @returns {boolean} true if header with given name exists, false otherwise
+   */
+  hasHeader: function(name) {
+    return(this.headers[SIP.Utils.headerize(name)]) ? true : false;
+  },
+
+  /**
+  * Parse the given header on the given index.
+  * @param {String} name header name
+  * @param {Number} [idx=0] header index
+  * @returns {Object|undefined} Parsed header object, undefined if the header is not present or in case of a parsing error.
+  */
+  parseHeader: function(name, idx) {
+    var header, value, parsed;
+
+    name = SIP.Utils.headerize(name);
+
+    idx = idx || 0;
+
+    if(!this.headers[name]) {
+      this.logger.log('header "' + name + '" not present');
+      return;
+    } else if(idx >= this.headers[name].length) {
+      this.logger.log('not so many "' + name + '" headers present');
+      return;
+    }
+
+    header = this.headers[name][idx];
+    value = header.raw;
+
+    if(header.parsed) {
+      return header.parsed;
+    }
+
+    //substitute '-' by '_' for grammar rule matching.
+    parsed = SIP.Grammar.parse(value, name.replace(/-/g, '_'));
+
+    if(parsed === -1) {
+      this.headers[name].splice(idx, 1); //delete from headers
+      this.logger.warn('error parsing "' + name + '" header field with value "' + value + '"');
+      return;
+    } else {
+      header.parsed = parsed;
+      return parsed;
+    }
+  },
+
+  /**
+   * Message Header attribute selector. Alias of parseHeader.
+   * @param {String} name header name
+   * @param {Number} [idx=0] header index
+   * @returns {Object|undefined} Parsed header object, undefined if the header is not present or in case of a parsing error.
+   *
+   * @example
+   * message.s('via',3).port
+   */
+  s: function(name, idx) {
+    return this.parseHeader(name, idx);
+  },
+
+  /**
+  * Replace the value of the given header by the value.
+  * @param {String} name header name
+  * @param {String} value header value
+  */
+  setHeader: function(name, value) {
+    var header = { raw: value };
+    this.headers[SIP.Utils.headerize(name)] = [header];
+  },
+
+  toString: function() {
+    return this.data;
+  }
+};
+
+/**
+ * @augments IncomingMessage
+ * @class Class for incoming SIP request.
+ */
+IncomingRequest = function(ua) {
+  this.logger = ua.getLogger('sip.sipmessage');
+  this.ua = ua;
+  this.headers = {};
+  this.ruri = null;
+  this.transport = null;
+  this.server_transaction = null;
+};
+IncomingRequest.prototype = new IncomingMessage();
+
+/**
+* Stateful reply.
+* @param {Number} code status code
+* @param {String} reason reason phrase
+* @param {Object} headers extra headers
+* @param {String} body body
+* @param {Function} [onSuccess] onSuccess callback
+* @param {Function} [onFailure] onFailure callback
+*/
+IncomingRequest.prototype.reply = function(code, reason, extraHeaders, body, onSuccess, onFailure) {
+  var rr, vias, length, idx, response,
+    to = this.getHeader('To'),
+    r = 0,
+    v = 0;
+
+  response = SIP.Utils.buildStatusLine(code, reason);
+  extraHeaders = (extraHeaders || []).slice();
+
+  if(this.method === SIP.C.INVITE && code > 100 && code <= 200) {
+    rr = this.getHeaders('record-route');
+    length = rr.length;
+
+    for(r; r < length; r++) {
+      response += 'Record-Route: ' + rr[r] + '\r\n';
+    }
+  }
+
+  vias = this.getHeaders('via');
+  length = vias.length;
+
+  for(v; v < length; v++) {
+    response += 'Via: ' + vias[v] + '\r\n';
+  }
+
+  if(!this.to_tag && code > 100) {
+    to += ';tag=' + SIP.Utils.newTag();
+  } else if(this.to_tag && !this.s('to').hasParam('tag')) {
+    to += ';tag=' + this.to_tag;
+  }
+
+  response += 'To: ' + to + '\r\n';
+  response += 'From: ' + this.getHeader('From') + '\r\n';
+  response += 'Call-ID: ' + this.call_id + '\r\n';
+  response += 'CSeq: ' + this.cseq + ' ' + this.method + '\r\n';
+
+  length = extraHeaders.length;
+  for (idx = 0; idx < length; idx++) {
+    response += extraHeaders[idx].trim() +'\r\n';
+  }
+
+  response += getSupportedHeader(this);
+  response += 'User-Agent: ' + this.ua.configuration.userAgentString +'\r\n';
+
+  if(body) {
+    length = SIP.Utils.str_utf8_length(body);
+    response += 'Content-Type: application/sdp\r\n';
+    response += 'Content-Length: ' + length + '\r\n\r\n';
+    response += body;
+  } else {
+    response += 'Content-Length: ' + 0 + '\r\n\r\n';
+  }
+
+  this.server_transaction.receiveResponse(code, response).then(onSuccess, onFailure);
+
+  return response;
+};
+
+/**
+* Stateless reply.
+* @param {Number} code status code
+* @param {String} reason reason phrase
+*/
+IncomingRequest.prototype.reply_sl = function(code, reason) {
+  var to, response,
+    v = 0,
+    vias = this.getHeaders('via'),
+    length = vias.length;
+
+  response = SIP.Utils.buildStatusLine(code, reason);
+
+  for(v; v < length; v++) {
+    response += 'Via: ' + vias[v] + '\r\n';
+  }
+
+  to = this.getHeader('To');
+
+  if(!this.to_tag && code > 100) {
+    to += ';tag=' + SIP.Utils.newTag();
+  } else if(this.to_tag && !this.s('to').hasParam('tag')) {
+    to += ';tag=' + this.to_tag;
+  }
+
+  response += 'To: ' + to + '\r\n';
+  response += 'From: ' + this.getHeader('From') + '\r\n';
+  response += 'Call-ID: ' + this.call_id + '\r\n';
+  response += 'CSeq: ' + this.cseq + ' ' + this.method + '\r\n';
+  response += 'User-Agent: ' + this.ua.configuration.userAgentString +'\r\n';
+  response += 'Content-Length: ' + 0 + '\r\n\r\n';
+
+  this.transport.send(response);
+};
+
+
+/**
+ * @augments IncomingMessage
+ * @class Class for incoming SIP response.
+ */
+IncomingResponse = function(ua) {
+  this.logger = ua.getLogger('sip.sipmessage');
+  this.headers = {};
+  this.status_code = null;
+  this.reason_phrase = null;
+};
+IncomingResponse.prototype = new IncomingMessage();
+
+SIP.OutgoingRequest = OutgoingRequest;
+SIP.IncomingRequest = IncomingRequest;
+SIP.IncomingResponse = IncomingResponse;
+};
+
+},{}],21:[function(require,module,exports){
+"use strict";
+/**
+ * @fileoverview Incoming SIP Message Sanity Check
+ */
+
+/**
+ * SIP message sanity check.
+ * @augments SIP
+ * @function
+ * @param {SIP.IncomingMessage} message
+ * @param {SIP.UA} ua
+ * @param {SIP.Transport} transport
+ * @returns {Boolean}
+ */
+module.exports = function (SIP) {
+var sanityCheck,
+ logger,
+ message, ua, transport,
+ requests = [],
+ responses = [],
+ all = [];
+
+// Reply
+function reply(status_code) {
+  var to,
+    response = SIP.Utils.buildStatusLine(status_code),
+    vias = message.getHeaders('via'),
+    length = vias.length,
+    idx = 0;
+
+  for(idx; idx < length; idx++) {
+    response += "Via: " + vias[idx] + "\r\n";
+  }
+
+  to = message.getHeader('To');
+
+  if(!message.to_tag) {
+    to += ';tag=' + SIP.Utils.newTag();
+  }
+
+  response += "To: " + to + "\r\n";
+  response += "From: " + message.getHeader('From') + "\r\n";
+  response += "Call-ID: " + message.call_id + "\r\n";
+  response += "CSeq: " + message.cseq + " " + message.method + "\r\n";
+  response += "\r\n";
+
+  transport.send(response);
+}
+
+/*
+ * Sanity Check for incoming Messages
+ *
+ * Requests:
+ *  - _rfc3261_8_2_2_1_ Receive a Request with a non supported URI scheme
+ *  - _rfc3261_16_3_4_ Receive a Request already sent by us
+ *   Does not look at via sent-by but at sipjsId, which is inserted as
+ *   a prefix in all initial requests generated by the ua
+ *  - _rfc3261_18_3_request_ Body Content-Length
+ *  - _rfc3261_8_2_2_2_ Merged Requests
+ *
+ * Responses:
+ *  - _rfc3261_8_1_3_3_ Multiple Via headers
+ *  - _rfc3261_18_1_2_ sent-by mismatch
+ *  - _rfc3261_18_3_response_ Body Content-Length
+ *
+ * All:
+ *  - Minimum headers in a SIP message
+ */
+
+// Sanity Check functions for requests
+function rfc3261_8_2_2_1() {
+  if(!message.ruri || message.ruri.scheme !== 'sip') {
+    reply(416);
+    return false;
+  }
+}
+
+function rfc3261_16_3_4() {
+  if(!message.to_tag) {
+    if(message.call_id.substr(0, 5) === ua.configuration.sipjsId) {
+      reply(482);
+      return false;
+    }
+  }
+}
+
+function rfc3261_18_3_request() {
+  var len = SIP.Utils.str_utf8_length(message.body),
+  contentLength = message.getHeader('content-length');
+
+  if(len < contentLength) {
+    reply(400);
+    return false;
+  }
+}
+
+function rfc3261_8_2_2_2() {
+  var tr, idx,
+    fromTag = message.from_tag,
+    call_id = message.call_id,
+    cseq = message.cseq;
+
+  if(!message.to_tag) {
+    if(message.method === SIP.C.INVITE) {
+      tr = ua.transactions.ist[message.via_branch];
+      if(tr) {
+        return;
+      } else {
+        for(idx in ua.transactions.ist) {
+          tr = ua.transactions.ist[idx];
+          if(tr.request.from_tag === fromTag && tr.request.call_id === call_id && tr.request.cseq === cseq) {
+            reply(482);
+            return false;
+          }
+        }
+      }
+    } else {
+      tr = ua.transactions.nist[message.via_branch];
+      if(tr) {
+        return;
+      } else {
+        for(idx in ua.transactions.nist) {
+          tr = ua.transactions.nist[idx];
+          if(tr.request.from_tag === fromTag && tr.request.call_id === call_id && tr.request.cseq === cseq) {
+            reply(482);
+            return false;
+          }
+        }
+      }
+    }
+  }
+}
+
+// Sanity Check functions for responses
+function rfc3261_8_1_3_3() {
+  if(message.getHeaders('via').length > 1) {
+    logger.warn('More than one Via header field present in the response. Dropping the response');
+    return false;
+  }
+}
+
+function rfc3261_18_1_2() {
+  var viaHost = ua.configuration.viaHost;
+  if(message.via.host !== viaHost || message.via.port !== undefined) {
+    logger.warn('Via sent-by in the response does not match UA Via host value. Dropping the response');
+    return false;
+  }
+}
+
+function rfc3261_18_3_response() {
+  var
+    len = SIP.Utils.str_utf8_length(message.body),
+    contentLength = message.getHeader('content-length');
+
+    if(len < contentLength) {
+      logger.warn('Message body length is lower than the value in Content-Length header field. Dropping the response');
+      return false;
+    }
+}
+
+// Sanity Check functions for requests and responses
+function minimumHeaders() {
+  var
+    mandatoryHeaders = ['from', 'to', 'call_id', 'cseq', 'via'],
+    idx = mandatoryHeaders.length;
+
+  while(idx--) {
+    if(!message.hasHeader(mandatoryHeaders[idx])) {
+      logger.warn('Missing mandatory header field : '+ mandatoryHeaders[idx] +'. Dropping the response');
+      return false;
+    }
+  }
+}
+
+requests.push(rfc3261_8_2_2_1);
+requests.push(rfc3261_16_3_4);
+requests.push(rfc3261_18_3_request);
+requests.push(rfc3261_8_2_2_2);
+
+responses.push(rfc3261_8_1_3_3);
+responses.push(rfc3261_18_1_2);
+responses.push(rfc3261_18_3_response);
+
+all.push(minimumHeaders);
+
+sanityCheck = function(m, u, t) {
+  var len, pass;
+
+  message = m;
+  ua = u;
+  transport = t;
+
+  logger = ua.getLogger('sip.sanitycheck');
+
+  len = all.length;
+  while(len--) {
+    pass = all[len](message);
+    if(pass === false) {
+      return false;
+    }
+  }
+
+  if(message instanceof SIP.IncomingRequest) {
+    len = requests.length;
+    while(len--) {
+      pass = requests[len](message);
+      if(pass === false) {
+        return false;
+      }
+    }
+  }
+
+  else if(message instanceof SIP.IncomingResponse) {
+    len = responses.length;
+    while(len--) {
+      pass = responses[len](message);
+      if(pass === false) {
+        return false;
+      }
+    }
+  }
+
+  //Everything is OK
+  return true;
+};
+
+SIP.sanityCheck = sanityCheck;
+};
+
+},{}],22:[function(require,module,exports){
+"use strict";
+module.exports = function (SIP) {
+var ServerContext;
+
+ServerContext = function (ua, request) {
+  this.ua = ua;
+  this.logger = ua.getLogger('sip.servercontext');
+  this.request = request;
+  if (request.method === SIP.C.INVITE) {
+    this.transaction = new SIP.Transactions.InviteServerTransaction(request, ua);
+  } else {
+    this.transaction = new SIP.Transactions.NonInviteServerTransaction(request, ua);
+  }
+
+  if (request.body) {
+    this.body = request.body;
+  }
+  if (request.hasHeader('Content-Type')) {
+    this.contentType = request.getHeader('Content-Type');
+  }
+  this.method = request.method;
+
+  this.data = {};
+
+  this.localIdentity = request.to;
+  this.remoteIdentity = request.from;
+};
+
+ServerContext.prototype = Object.create(SIP.EventEmitter.prototype);
+
+ServerContext.prototype.progress = function (options) {
+  options = Object.create(options || Object.prototype);
+  options.statusCode || (options.statusCode = 180);
+  options.minCode = 100;
+  options.maxCode = 199;
+  options.events = ['progress'];
+  return this.reply(options);
+};
+
+ServerContext.prototype.accept = function (options) {
+  options = Object.create(options || Object.prototype);
+  options.statusCode || (options.statusCode = 200);
+  options.minCode = 200;
+  options.maxCode = 299;
+  options.events = ['accepted'];
+  return this.reply(options);
+};
+
+ServerContext.prototype.reject = function (options) {
+  options = Object.create(options || Object.prototype);
+  options.statusCode || (options.statusCode = 480);
+  options.minCode = 300;
+  options.maxCode = 699;
+  options.events = ['rejected', 'failed'];
+  return this.reply(options);
+};
+
+ServerContext.prototype.reply = function (options) {
+  options = options || {}; // This is okay, so long as we treat options as read-only in this method
+  var
+    statusCode = options.statusCode || 100,
+    minCode = options.minCode || 100,
+    maxCode = options.maxCode || 699,
+    reasonPhrase = SIP.Utils.getReasonPhrase(statusCode, options.reasonPhrase),
+    extraHeaders = options.extraHeaders || [],
+    body = options.body,
+    events = options.events || [],
+    response;
+
+  if (statusCode < minCode || statusCode > maxCode) {
+    throw new TypeError('Invalid statusCode: ' + statusCode);
+  }
+  response = this.request.reply(statusCode, reasonPhrase, extraHeaders, body);
+  events.forEach(function (event) {
+    this.emit(event, response, reasonPhrase);
+  }, this);
+
+  return this;
+};
+
+ServerContext.prototype.onRequestTimeout = function () {
+  this.emit('failed', null, SIP.C.causes.REQUEST_TIMEOUT);
+};
+
+ServerContext.prototype.onTransportError = function () {
+  this.emit('failed', null, SIP.C.causes.CONNECTION_ERROR);
+};
+
+SIP.ServerContext = ServerContext;
+};
+
+},{}],23:[function(require,module,exports){
+"use strict";
+module.exports = function (SIP, environment) {
+
+var DTMF = require('./Session/DTMF')(SIP);
+
+var Session, InviteServerContext, InviteClientContext,
+ C = {
+    //Session states
+    STATUS_NULL:                        0,
+    STATUS_INVITE_SENT:                 1,
+    STATUS_1XX_RECEIVED:                2,
+    STATUS_INVITE_RECEIVED:             3,
+    STATUS_WAITING_FOR_ANSWER:          4,
+    STATUS_ANSWERED:                    5,
+    STATUS_WAITING_FOR_PRACK:           6,
+    STATUS_WAITING_FOR_ACK:             7,
+    STATUS_CANCELED:                    8,
+    STATUS_TERMINATED:                  9,
+    STATUS_ANSWERED_WAITING_FOR_PRACK: 10,
+    STATUS_EARLY_MEDIA:                11,
+    STATUS_CONFIRMED:                  12
+  };
+
+/*
+ * @param {function returning SIP.MediaHandler} [mediaHandlerFactory]
+ *        (See the documentation for the mediaHandlerFactory argument of the UA constructor.)
+ */
+Session = function (mediaHandlerFactory) {
+  this.status = C.STATUS_NULL;
+  this.dialog = null;
+  this.earlyDialogs = {};
+  this.mediaHandlerFactory = mediaHandlerFactory || SIP.WebRTC.MediaHandler.defaultFactory;
+  // this.mediaHandler gets set by ICC/ISC constructors
+  this.hasOffer = false;
+  this.hasAnswer = false;
+
+  // Session Timers
+  this.timers = {
+    ackTimer: null,
+    expiresTimer: null,
+    invite2xxTimer: null,
+    userNoAnswerTimer: null,
+    rel1xxTimer: null,
+    prackTimer: null
+  };
+
+  // Session info
+  this.startTime = null;
+  this.endTime = null;
+  this.tones = null;
+
+  // Mute/Hold state
+  this.local_hold = false;
+  this.remote_hold = false;
+
+  this.pending_actions = {
+    actions: [],
+
+    length: function() {
+      return this.actions.length;
+    },
+
+    isPending: function(name){
+      var
+      idx = 0,
+      length = this.actions.length;
+
+      for (idx; idx<length; idx++) {
+        if (this.actions[idx].name === name) {
+          return true;
+        }
+      }
+      return false;
+    },
+
+    shift: function() {
+      return this.actions.shift();
+    },
+
+    push: function(name) {
+      this.actions.push({
+        name: name
+      });
+    },
+
+    pop: function(name) {
+      var
+      idx = 0,
+      length = this.actions.length;
+
+      for (idx; idx<length; idx++) {
+        if (this.actions[idx].name === name) {
+          this.actions.splice(idx,1);
+          length --;
+          idx--;
+        }
+      }
+    }
+   };
+
+  this.early_sdp = null;
+  this.rel100 = SIP.C.supported.UNSUPPORTED;
+};
+
+Session.prototype = {
+  dtmf: function(tones, options) {
+    var tone, dtmfs = [],
+        self = this;
+
+    options = options || {};
+
+    if (tones === undefined) {
+      throw new TypeError('Not enough arguments');
+    }
+
+    // Check Session Status
+    if (this.status !== C.STATUS_CONFIRMED && this.status !== C.STATUS_WAITING_FOR_ACK) {
+      throw new SIP.Exceptions.InvalidStateError(this.status);
+    }
+
+    // Check tones
+    if ((typeof tones !== 'string' && typeof tones !== 'number') || !tones.toString().match(/^[0-9A-D#*,]+$/i)) {
+      throw new TypeError('Invalid tones: '+ tones);
+    }
+
+    tones = tones.toString().split('');
+
+    while (tones.length > 0) { dtmfs.push(new DTMF(this, tones.shift(), options)); }
+
+    if (this.tones) {
+      // Tones are already queued, just add to the queue
+      this.tones =  this.tones.concat(dtmfs);
+      return this;
+    }
+
+    var sendDTMF = function () {
+      var dtmf, timeout;
+
+      if (self.status === C.STATUS_TERMINATED || !self.tones || self.tones.length === 0) {
+        // Stop sending DTMF
+        self.tones = null;
+        return this;
+      }
+
+      dtmf = self.tones.shift();
+
+      if (tone === ',') {
+        timeout = 2000;
+      } else {
+        dtmf.on('failed', function(){self.tones = null;});
+        dtmf.send(options);
+        timeout = dtmf.duration + dtmf.interToneGap;
+      }
+
+      // Set timeout for the next tone
+      SIP.Timers.setTimeout(sendDTMF, timeout);
+    };
+
+    this.tones = dtmfs;
+    sendDTMF();
+    return this;
+  },
+
+  bye: function(options) {
+    options = Object.create(options || Object.prototype);
+    var statusCode = options.statusCode;
+
+    // Check Session Status
+    if (this.status === C.STATUS_TERMINATED) {
+      this.logger.error('Error: Attempted to send BYE in a terminated session.');
+      return this;
+    }
+
+    this.logger.log('terminating Session');
+
+    if (statusCode && (statusCode < 200 || statusCode >= 700)) {
+      throw new TypeError('Invalid statusCode: '+ statusCode);
+    }
+
+    options.receiveResponse = function () {};
+
+    return this.
+      sendRequest(SIP.C.BYE, options).
+      terminated();
+  },
+
+  refer: function(target, options) {
+    options = options || {};
+    var extraHeaders = (options.extraHeaders || []).slice(),
+        withReplaces =
+          target instanceof SIP.InviteServerContext ||
+          target instanceof SIP.InviteClientContext,
+        originalTarget = target;
+
+    if (target === undefined) {
+      throw new TypeError('Not enough arguments');
+    }
+
+    // Check Session Status
+    if (this.status !== C.STATUS_CONFIRMED) {
+      throw new SIP.Exceptions.InvalidStateError(this.status);
+    }
+
+    // transform `target` so that it can be a Refer-To header value
+    if (withReplaces) {
+      //Attended Transfer
+      // B.transfer(C)
+      target = '"' + target.remoteIdentity.friendlyName + '" ' +
+        '<' + target.dialog.remote_target.toString() +
+        '?Replaces=' + target.dialog.id.call_id +
+        '%3Bto-tag%3D' + target.dialog.id.remote_tag +
+        '%3Bfrom-tag%3D' + target.dialog.id.local_tag + '>';
+    } else {
+      //Blind Transfer
+      // normalizeTarget allows instances of SIP.URI to pass through unaltered,
+      // so try to make one ahead of time
+      try {
+        target = SIP.Grammar.parse(target, 'Refer_To').uri || target;
+      } catch (e) {
+        this.logger.debug(".refer() cannot parse Refer_To from", target);
+        this.logger.debug("...falling through to normalizeTarget()");
+      }
+
+      // Check target validity
+      target = this.ua.normalizeTarget(target);
+      if (!target) {
+        throw new TypeError('Invalid target: ' + originalTarget);
+      }
+    }
+
+    extraHeaders.push('Contact: '+ this.contact);
+    extraHeaders.push('Allow: '+ SIP.UA.C.ALLOWED_METHODS.toString());
+    extraHeaders.push('Refer-To: '+ target);
+
+    // Send the request
+    this.sendRequest(SIP.C.REFER, {
+      extraHeaders: extraHeaders,
+      body: options.body,
+      receiveResponse: function (response) {
+        if ( ! /^2[0-9]{2}$/.test(response.status_code) ) {
+          return;
+        }
+        // hang up only if we transferred to a SIP address
+        if (withReplaces || (target.scheme && target.scheme.match("^sips?$"))) {
+          this.terminate();
+        }
+      }.bind(this)
+    });
+    return this;
+  },
+
+  followRefer: function followRefer (callback) {
+    return function referListener (callback, request) {
+      // open non-SIP URIs if possible and keep session open
+      var referTo = request.parseHeader('refer-to');
+      var target = referTo.uri;
+      if (!target.scheme.match("^sips?$")) {
+        var targetString = target.toString();
+        if (typeof environment.open === "function") {
+          environment.open(targetString);
+        } else {
+          this.logger.warn("referred to non-SIP URI but `open` isn't in the environment: " + targetString);
+        }
+        return;
+      }
+
+      var extraHeaders = [];
+
+      /* Copy the Replaces query into a Replaces header */
+      /* TODO - make sure we don't copy a poorly formatted header? */
+      var replaces = target.getHeader('Replaces');
+      if (replaces !== undefined) {
+        extraHeaders.push('Replaces: ' + decodeURIComponent(replaces));
+      }
+
+      // don't embed headers into Request-URI of INVITE
+      target.clearHeaders();
+
+      /*
+        Harmless race condition.  Both sides of REFER
+        may send a BYE, but in the end the dialogs are destroyed.
+      */
+      var getReferMedia = this.mediaHandler.getReferMedia;
+      var mediaHint = getReferMedia ? getReferMedia.call(this.mediaHandler) : this.mediaHint;
+
+      SIP.Hacks.Chrome.getsConfusedAboutGUM(this);
+
+      var referSession = this.ua.invite(target, {
+        media: mediaHint,
+        params: {
+          to_displayName: referTo.friendlyName
+        },
+        extraHeaders: extraHeaders
+      });
+
+      callback.call(this, request, referSession);
+
+      this.terminate();
+    }.bind(this, callback);
+  },
+
+  sendRequest: function(method,options) {
+    options = options || {};
+    var self = this;
+
+    var request = new SIP.OutgoingRequest(
+      method,
+      this.dialog.remote_target,
+      this.ua,
+      {
+        cseq: options.cseq || (this.dialog.local_seqnum += 1),
+        call_id: this.dialog.id.call_id,
+        from_uri: this.dialog.local_uri,
+        from_tag: this.dialog.id.local_tag,
+        to_uri: this.dialog.remote_uri,
+        to_tag: this.dialog.id.remote_tag,
+        route_set: this.dialog.route_set,
+        statusCode: options.statusCode,
+        reasonPhrase: options.reasonPhrase
+      },
+      options.extraHeaders || [],
+      options.body
+    );
+
+    new SIP.RequestSender({
+      request: request,
+      onRequestTimeout: function() {
+        self.onRequestTimeout();
+      },
+      onTransportError: function() {
+        self.onTransportError();
+      },
+      receiveResponse: options.receiveResponse || function(response) {
+        self.receiveNonInviteResponse(response);
+      }
+    }, this.ua).send();
+
+    // Emit the request event
+    this.emit(method.toLowerCase(), request);
+
+    return this;
+  },
+
+  close: function() {
+    var idx;
+
+    if(this.status === C.STATUS_TERMINATED) {
+      return this;
+    }
+
+    this.logger.log('closing INVITE session ' + this.id);
+
+    // 1st Step. Terminate media.
+    if (this.mediaHandler){
+      this.mediaHandler.close();
+    }
+
+    // 2nd Step. Terminate signaling.
+
+    // Clear session timers
+    for(idx in this.timers) {
+      SIP.Timers.clearTimeout(this.timers[idx]);
+    }
+
+    // Terminate dialogs
+
+    // Terminate confirmed dialog
+    if(this.dialog) {
+      this.dialog.terminate();
+      delete this.dialog;
+    }
+
+    // Terminate early dialogs
+    for(idx in this.earlyDialogs) {
+      this.earlyDialogs[idx].terminate();
+      delete this.earlyDialogs[idx];
+    }
+
+    this.status = C.STATUS_TERMINATED;
+
+    delete this.ua.sessions[this.id];
+    return this;
+  },
+
+  createDialog: function(message, type, early) {
+    var dialog, early_dialog,
+      local_tag = message[(type === 'UAS') ? 'to_tag' : 'from_tag'],
+      remote_tag = message[(type === 'UAS') ? 'from_tag' : 'to_tag'],
+      id = message.call_id + local_tag + remote_tag;
+
+    early_dialog = this.earlyDialogs[id];
+
+    // Early Dialog
+    if (early) {
+      if (early_dialog) {
+        return true;
+      } else {
+        early_dialog = new SIP.Dialog(this, message, type, SIP.Dialog.C.STATUS_EARLY);
+
+        // Dialog has been successfully created.
+        if(early_dialog.error) {
+          this.logger.error(early_dialog.error);
+          this.failed(message, SIP.C.causes.INTERNAL_ERROR);
+          return false;
+        } else {
+          this.earlyDialogs[id] = early_dialog;
+          return true;
+        }
+      }
+    }
+    // Confirmed Dialog
+    else {
+      // In case the dialog is in _early_ state, update it
+      if (early_dialog) {
+        early_dialog.update(message, type);
+        this.dialog = early_dialog;
+        delete this.earlyDialogs[id];
+        for (var dia in this.earlyDialogs) {
+          this.earlyDialogs[dia].terminate();
+          delete this.earlyDialogs[dia];
+        }
+        return true;
+      }
+
+      // Otherwise, create a _confirmed_ dialog
+      dialog = new SIP.Dialog(this, message, type);
+
+      if(dialog.error) {
+        this.logger.error(dialog.error);
+        this.failed(message, SIP.C.causes.INTERNAL_ERROR);
+        return false;
+      } else {
+        this.to_tag = message.to_tag;
+        this.dialog = dialog;
+        return true;
+      }
+    }
+  },
+
+  /**
+  * Check if Session is ready for a re-INVITE
+  *
+  * @returns {Boolean}
+  */
+  isReadyToReinvite: function() {
+    return this.mediaHandler.isReady() &&
+      !this.dialog.uac_pending_reply &&
+      !this.dialog.uas_pending_reply;
+  },
+
+  /**
+   * Mute
+   */
+  mute: function(options) {
+    var ret = this.mediaHandler.mute(options);
+    if (ret) {
+      this.onmute(ret);
+    }
+  },
+
+  /**
+   * Unmute
+   */
+  unmute: function(options) {
+    var ret = this.mediaHandler.unmute(options);
+    if (ret) {
+      this.onunmute(ret);
+    }
+  },
+
+  /**
+   * Hold
+   */
+  hold: function(options) {
+
+    if (this.status !== C.STATUS_WAITING_FOR_ACK && this.status !== C.STATUS_CONFIRMED) {
+      throw new SIP.Exceptions.InvalidStateError(this.status);
+    }
+
+    this.mediaHandler.hold();
+
+    // Check if RTCSession is ready to send a reINVITE
+    if (!this.isReadyToReinvite()) {
+      /* If there is a pending 'unhold' action, cancel it and don't queue this one
+       * Else, if there isn't any 'hold' action, add this one to the queue
+       * Else, if there is already a 'hold' action, skip
+       */
+      if (this.pending_actions.isPending('unhold')) {
+        this.pending_actions.pop('unhold');
+      } else if (!this.pending_actions.isPending('hold')) {
+        this.pending_actions.push('hold');
+      }
+      return;
+    } else if (this.local_hold === true) {
+        return;
+    }
+
+    this.onhold('local');
+
+    options = options || {};
+    options.mangle = function(body){
+
+      // Don't receive media
+      // TODO - This will break for media streams with different directions.
+      if (!(/a=(sendrecv|sendonly|recvonly|inactive)/).test(body)) {
+        body = body.replace(/(m=[^\r]*\r\n)/g, '$1a=sendonly\r\n');
+      } else {
+        body = body.replace(/a=sendrecv\r\n/g, 'a=sendonly\r\n');
+        body = body.replace(/a=recvonly\r\n/g, 'a=inactive\r\n');
+      }
+
+      return body;
+    };
+
+    this.sendReinvite(options);
+  },
+
+  /**
+   * Unhold
+   */
+  unhold: function(options) {
+
+    if (this.status !== C.STATUS_WAITING_FOR_ACK && this.status !== C.STATUS_CONFIRMED) {
+      throw new SIP.Exceptions.InvalidStateError(this.status);
+    }
+
+    this.mediaHandler.unhold();
+
+    if (!this.isReadyToReinvite()) {
+      /* If there is a pending 'hold' action, cancel it and don't queue this one
+       * Else, if there isn't any 'unhold' action, add this one to the queue
+       * Else, if there is already a 'unhold' action, skip
+       */
+      if (this.pending_actions.isPending('hold')) {
+        this.pending_actions.pop('hold');
+      } else if (!this.pending_actions.isPending('unhold')) {
+        this.pending_actions.push('unhold');
+      }
+      return;
+    } else if (this.local_hold === false) {
+      return;
+    }
+
+    this.onunhold('local');
+
+    this.sendReinvite(options);
+  },
+
+  /**
+   * isOnHold
+   */
+  isOnHold: function() {
+    return {
+      local: this.local_hold,
+      remote: this.remote_hold
+    };
+  },
+
+  /**
+   * In dialog INVITE Reception
+   * @private
+   */
+  receiveReinvite: function(request) {
+    var self = this;
+
+    if (!request.body) {
+      return;
+    }
+
+    if (request.getHeader('Content-Type') !== 'application/sdp') {
+      this.logger.warn('invalid Content-Type');
+      request.reply(415);
+      return;
+    }
+
+    this.mediaHandler.setDescription(request.body)
+    .then(this.mediaHandler.getDescription.bind(this.mediaHandler, this.mediaHint))
+    .then(function(body) {
+      request.reply(200, null, ['Contact: ' + self.contact], body,
+        function() {
+          self.status = C.STATUS_WAITING_FOR_ACK;
+          self.setInvite2xxTimer(request, body);
+          self.setACKTimer();
+
+          // Are we holding?
+          var hold = (/a=(sendonly|inactive)/).test(request.body);
+
+          if (self.remote_hold && !hold) {
+            self.onunhold('remote');
+          } else if (!self.remote_hold && hold) {
+            self.onhold('remote');
+          }
+        });
+    })
+    .catch(function onFailure (e) {
+      var statusCode;
+      if (e instanceof SIP.Exceptions.GetDescriptionError) {
+        statusCode = 500;
+      } else {
+        self.logger.error(e);
+        statusCode = 488;
+      }
+      request.reply(statusCode);
+    });
+  },
+
+  sendReinvite: function(options) {
+    options = options || {};
+
+    var
+      self = this,
+      extraHeaders = (options.extraHeaders || []).slice(),
+      eventHandlers = options.eventHandlers || {},
+      mangle = options.mangle || null,
+      succeeded;
+
+    if (eventHandlers.succeeded) {
+      succeeded = eventHandlers.succeeded;
+    }
+    this.reinviteSucceeded = function(){
+      SIP.Timers.clearTimeout(self.timers.ackTimer);
+      SIP.Timers.clearTimeout(self.timers.invite2xxTimer);
+      self.status = C.STATUS_CONFIRMED;
+      succeeded && succeeded.apply(this, arguments);
+    };
+    if (eventHandlers.failed) {
+      this.reinviteFailed = eventHandlers.failed;
+    } else {
+      this.reinviteFailed = function(){};
+    }
+
+    extraHeaders.push('Contact: ' + this.contact);
+    extraHeaders.push('Allow: '+ SIP.UA.C.ALLOWED_METHODS.toString());
+    extraHeaders.push('Content-Type: application/sdp');
+
+    this.receiveResponse = this.receiveReinviteResponse;
+    //REVISIT
+    this.mediaHandler.getDescription(self.mediaHint)
+    .then(mangle)
+    .then(
+      function(body){
+        self.dialog.sendRequest(self, SIP.C.INVITE, {
+          extraHeaders: extraHeaders,
+          body: body
+        });
+      },
+      function() {
+        if (self.isReadyToReinvite()) {
+          self.onReadyToReinvite();
+        }
+        self.reinviteFailed();
+      }
+    );
+  },
+
+  receiveRequest: function (request) {
+    switch (request.method) {
+      case SIP.C.BYE:
+        request.reply(200);
+        if(this.status === C.STATUS_CONFIRMED) {
+          this.emit('bye', request);
+          this.terminated(request, SIP.C.causes.BYE);
+        }
+        break;
+      case SIP.C.INVITE:
+        if(this.status === C.STATUS_CONFIRMED) {
+          this.logger.log('re-INVITE received');
+          this.receiveReinvite(request);
+        }
+        break;
+      case SIP.C.INFO:
+        if(this.status === C.STATUS_CONFIRMED || this.status === C.STATUS_WAITING_FOR_ACK) {
+          var body, tone, duration,
+              contentType = request.getHeader('content-type'),
+              reg_tone = /^(Signal\s*?=\s*?)([0-9A-D#*]{1})(\s)?.*/,
+              reg_duration = /^(Duration\s?=\s?)([0-9]{1,4})(\s)?.*/;
+
+          if (contentType) {
+            if (contentType.match(/^application\/dtmf-relay/i)) {
+              if (request.body) {
+                body = request.body.split('\r\n', 2);
+                if (body.length === 2) {
+                  if (reg_tone.test(body[0])) {
+                    tone = body[0].replace(reg_tone,"$2");
+                  }
+                  if (reg_duration.test(body[1])) {
+                    duration = parseInt(body[1].replace(reg_duration,"$2"), 10);
+                  }
+                }
+              }
+
+              new DTMF(this, tone, {duration: duration}).init_incoming(request);
+            } else {
+              request.reply(415, null, ["Accept: application/dtmf-relay"]);
+            }
+          }
+        }
+        break;
+      case SIP.C.REFER:
+        if(this.status ===  C.STATUS_CONFIRMED) {
+          this.logger.log('REFER received');
+          var hasReferListener = this.listeners('refer').length,
+              notifyBody;
+
+          if (hasReferListener) {
+            request.reply(202, 'Accepted');
+            notifyBody = 'SIP/2.0 100 Trying';
+
+            this.sendRequest(SIP.C.NOTIFY, {
+              extraHeaders:[
+                'Event: refer',
+                'Subscription-State: terminated',
+                'Content-Type: message/sipfrag'
+              ],
+              body: notifyBody,
+              receiveResponse: function() {}
+            });
+
+            this.emit('refer', request);
+          } else {
+            // RFC 3515.2.4.2: 'the UA MAY decline the request.'
+            request.reply(603, 'Declined');
+          }
+        }
+        break;
+      case SIP.C.NOTIFY:
+        request.reply(200, 'OK');
+        this.emit('notify', request);
+        break;
+    }
+  },
+
+  /**
+   * Reception of Response for in-dialog INVITE
+   * @private
+   */
+  receiveReinviteResponse: function(response) {
+    var self = this,
+        contentType = response.getHeader('Content-Type');
+
+    if (this.status === C.STATUS_TERMINATED) {
+      return;
+    }
+
+    switch(true) {
+      case /^1[0-9]{2}$/.test(response.status_code):
+        break;
+      case /^2[0-9]{2}$/.test(response.status_code):
+        this.status = C.STATUS_CONFIRMED;
+
+        this.sendRequest(SIP.C.ACK,{cseq:response.cseq});
+
+        if(!response.body) {
+          this.reinviteFailed();
+          break;
+        } else if (contentType !== 'application/sdp') {
+          this.reinviteFailed();
+          break;
+        }
+
+        //REVISIT
+        this.mediaHandler.setDescription(response.body)
+        .then(
+          function onSuccess () {
+            self.reinviteSucceeded();
+          },
+          function onFailure () {
+            self.reinviteFailed();
+          }
+        );
+        break;
+      default:
+        this.reinviteFailed();
+    }
+  },
+
+  acceptAndTerminate: function(response, status_code, reason_phrase) {
+    var extraHeaders = [];
+
+    if (status_code) {
+      extraHeaders.push('Reason: ' + SIP.Utils.getReasonHeaderValue(status_code, reason_phrase));
+    }
+
+    // An error on dialog creation will fire 'failed' event
+    if (this.dialog || this.createDialog(response, 'UAC')) {
+      this.sendRequest(SIP.C.ACK,{cseq: response.cseq});
+      this.sendRequest(SIP.C.BYE, {
+        extraHeaders: extraHeaders
+      });
+    }
+
+    return this;
+  },
+
+  /**
+   * RFC3261 13.3.1.4
+   * Response retransmissions cannot be accomplished by transaction layer
+   *  since it is destroyed when receiving the first 2xx answer
+   */
+  setInvite2xxTimer: function(request, body) {
+    var self = this,
+        timeout = SIP.Timers.T1;
+
+    this.timers.invite2xxTimer = SIP.Timers.setTimeout(function invite2xxRetransmission() {
+      if (self.status !== C.STATUS_WAITING_FOR_ACK) {
+        return;
+      }
+
+      self.logger.log('no ACK received, attempting to retransmit OK');
+
+      request.reply(200, null, ['Contact: ' + self.contact], body);
+
+      timeout = Math.min(timeout * 2, SIP.Timers.T2);
+
+      self.timers.invite2xxTimer = SIP.Timers.setTimeout(invite2xxRetransmission, timeout);
+    }, timeout);
+  },
+
+  /**
+   * RFC3261 14.2
+   * If a UAS generates a 2xx response and never receives an ACK,
+   *  it SHOULD generate a BYE to terminate the dialog.
+   */
+  setACKTimer: function() {
+    var self = this;
+
+    this.timers.ackTimer = SIP.Timers.setTimeout(function() {
+      if(self.status === C.STATUS_WAITING_FOR_ACK) {
+        self.logger.log('no ACK received for an extended period of time, terminating the call');
+        SIP.Timers.clearTimeout(self.timers.invite2xxTimer);
+        self.sendRequest(SIP.C.BYE);
+        self.terminated(null, SIP.C.causes.NO_ACK);
+      }
+    }, SIP.Timers.TIMER_H);
+  },
+
+  /*
+   * @private
+   */
+  onReadyToReinvite: function() {
+    var action = this.pending_actions.shift();
+
+    if (!action || !this[action.name]) {
+      return;
+    }
+
+    this[action.name]();
+  },
+
+  onTransportError: function() {
+    if (this.status !== C.STATUS_CONFIRMED && this.status !== C.STATUS_TERMINATED) {
+      this.failed(null, SIP.C.causes.CONNECTION_ERROR);
+    }
+  },
+
+  onRequestTimeout: function() {
+    if (this.status === C.STATUS_CONFIRMED) {
+      this.terminated(null, SIP.C.causes.REQUEST_TIMEOUT);
+    } else if (this.status !== C.STATUS_TERMINATED) {
+      this.failed(null, SIP.C.causes.REQUEST_TIMEOUT);
+      this.terminated(null, SIP.C.causes.REQUEST_TIMEOUT);
+    }
+  },
+
+  onDialogError: function(response) {
+    if (this.status === C.STATUS_CONFIRMED) {
+      this.terminated(response, SIP.C.causes.DIALOG_ERROR);
+    } else if (this.status !== C.STATUS_TERMINATED) {
+      this.failed(response, SIP.C.causes.DIALOG_ERROR);
+      this.terminated(response, SIP.C.causes.DIALOG_ERROR);
+    }
+  },
+
+  /**
+   * @private
+   */
+  onhold: function(originator) {
+    this[originator === 'local' ? 'local_hold' : 'remote_hold'] = true;
+    this.emit('hold', { originator: originator });
+  },
+
+  /**
+   * @private
+   */
+  onunhold: function(originator) {
+    this[originator === 'local' ? 'local_hold' : 'remote_hold'] = false;
+    this.emit('unhold', { originator: originator });
+  },
+
+  /*
+   * @private
+   */
+  onmute: function(options) {
+    this.emit('muted', {
+      audio: options.audio,
+      video: options.video
+    });
+  },
+
+  /*
+   * @private
+   */
+  onunmute: function(options) {
+    this.emit('unmuted', {
+      audio: options.audio,
+      video: options.video
+    });
+  },
+
+  failed: function(response, cause) {
+    if (this.status === C.STATUS_TERMINATED) {
+      return this;
+    }
+    this.emit('failed', response || null, cause || null);
+    return this;
+  },
+
+  rejected: function(response, cause) {
+    this.emit('rejected',
+      response || null,
+      cause || null
+    );
+    return this;
+  },
+
+  canceled: function() {
+    this.emit('cancel');
+    return this;
+  },
+
+  accepted: function(response, cause) {
+    cause = SIP.Utils.getReasonPhrase(response && response.status_code, cause);
+
+    this.startTime = new Date();
+
+    if (this.replacee) {
+      this.replacee.emit('replaced', this);
+      this.replacee.terminate();
+    }
+    this.emit('accepted', response, cause);
+    return this;
+  },
+
+  terminated: function(message, cause) {
+    if (this.status === C.STATUS_TERMINATED) {
+      return this;
+    }
+
+    this.endTime = new Date();
+
+    this.close();
+    this.emit('terminated',
+      message || null,
+      cause || null
+    );
+    return this;
+  },
+
+  connecting: function(request) {
+    this.emit('connecting', { request: request });
+    return this;
+  }
+};
+
+Session.desugar = function desugar(options) {
+  if (environment.HTMLMediaElement && options instanceof environment.HTMLMediaElement) {
+    options = {
+      media: {
+        constraints: {
+          audio: true,
+          video: options.tagName === 'VIDEO'
+        },
+        render: {
+          remote: options
+        }
+      }
+    };
+  }
+  return options || {};
+};
+
+
+Session.C = C;
+SIP.Session = Session;
+
+
+InviteServerContext = function(ua, request) {
+  var expires,
+    self = this,
+    contentType = request.getHeader('Content-Type'),
+    contentDisp = request.parseHeader('Content-Disposition');
+
+  // Check body and content type
+  if ((!contentDisp && contentType !== 'application/sdp') || (contentDisp && contentDisp.type === 'render')) {
+    this.renderbody = request.body;
+    this.rendertype = contentType;
+  } else if (contentType !== 'application/sdp' && (contentDisp && contentDisp.type === 'session')) {
+    request.reply(415);
+    //TODO: instead of 415, pass off to the media handler, who can then decide if we can use it
+    return;
+  }
+
+  //TODO: move this into media handler
+  SIP.Hacks.Firefox.cannotHandleExtraWhitespace(request);
+  SIP.Hacks.AllBrowsers.maskDtls(request);
+
+  SIP.Utils.augment(this, SIP.ServerContext, [ua, request]);
+  SIP.Utils.augment(this, SIP.Session, [ua.configuration.mediaHandlerFactory]);
+
+  this.status = C.STATUS_INVITE_RECEIVED;
+  this.from_tag = request.from_tag;
+  this.id = request.call_id + this.from_tag;
+  this.request = request;
+  this.contact = this.ua.contact.toString();
+
+  this.receiveNonInviteResponse = function () {}; // intentional no-op
+
+  this.logger = ua.getLogger('sip.inviteservercontext', this.id);
+
+  //Save the session into the ua sessions collection.
+  this.ua.sessions[this.id] = this;
+
+  //Get the Expires header value if exists
+  if(request.hasHeader('expires')) {
+    expires = request.getHeader('expires') * 1000;
+  }
+
+  //Set 100rel if necessary
+  function set100rel(h,c) {
+    if (request.hasHeader(h) && request.getHeader(h).toLowerCase().indexOf('100rel') >= 0) {
+      self.rel100 = c;
+    }
+  }
+  set100rel('require', SIP.C.supported.REQUIRED);
+  set100rel('supported', SIP.C.supported.SUPPORTED);
+
+  /* Set the to_tag before
+   * replying a response code that will create a dialog.
+   */
+  request.to_tag = SIP.Utils.newTag();
+
+  // An error on dialog creation will fire 'failed' event
+  if(!this.createDialog(request, 'UAS', true)) {
+    request.reply(500, 'Missing Contact header field');
+    return;
+  }
+
+  //Initialize Media Session
+  this.mediaHandler = this.mediaHandlerFactory(this, {
+    RTCConstraints: {"optional": [{'DtlsSrtpKeyAgreement': 'true'}]}
+  });
+
+  if (this.mediaHandler && this.mediaHandler.getRemoteStreams) {
+    this.getRemoteStreams = this.mediaHandler.getRemoteStreams.bind(this.mediaHandler);
+    this.getLocalStreams = this.mediaHandler.getLocalStreams.bind(this.mediaHandler);
+  }
+
+  function fireNewSession() {
+    var options = {extraHeaders: ['Contact: ' + self.contact]};
+
+    if (self.rel100 !== SIP.C.supported.REQUIRED) {
+      self.progress(options);
+    }
+    self.status = C.STATUS_WAITING_FOR_ANSWER;
+
+    // Set userNoAnswerTimer
+    self.timers.userNoAnswerTimer = SIP.Timers.setTimeout(function() {
+      request.reply(408);
+      self.failed(request, SIP.C.causes.NO_ANSWER);
+      self.terminated(request, SIP.C.causes.NO_ANSWER);
+    }, self.ua.configuration.noAnswerTimeout);
+
+    /* Set expiresTimer
+     * RFC3261 13.3.1
+     */
+    if (expires) {
+      self.timers.expiresTimer = SIP.Timers.setTimeout(function() {
+        if(self.status === C.STATUS_WAITING_FOR_ANSWER) {
+          request.reply(487);
+          self.failed(request, SIP.C.causes.EXPIRES);
+          self.terminated(request, SIP.C.causes.EXPIRES);
+        }
+      }, expires);
+    }
+
+    self.emit('invite',request);
+  }
+
+  if (!request.body || this.renderbody) {
+    SIP.Timers.setTimeout(fireNewSession, 0);
+  } else {
+    this.hasOffer = true;
+    this.mediaHandler.setDescription(request.body)
+    .then(
+      fireNewSession,
+      function onFailure (e) {
+        self.logger.warn('invalid SDP');
+        self.logger.warn(e);
+        request.reply(488);
+      }
+    );
+  }
+};
+
+InviteServerContext.prototype = {
+  reject: function(options) {
+    // Check Session Status
+    if (this.status === C.STATUS_TERMINATED) {
+      throw new SIP.Exceptions.InvalidStateError(this.status);
+    }
+
+    this.logger.log('rejecting RTCSession');
+
+    SIP.ServerContext.prototype.reject.call(this, options);
+    return this.terminated();
+  },
+
+  terminate: function(options) {
+    options = options || {};
+
+    var
+    extraHeaders = (options.extraHeaders || []).slice(),
+    body = options.body,
+    dialog,
+    self = this;
+
+    if (this.status === C.STATUS_WAITING_FOR_ACK &&
+       this.request.server_transaction.state !== SIP.Transactions.C.STATUS_TERMINATED) {
+      dialog = this.dialog;
+
+      this.receiveRequest = function(request) {
+        if (request.method === SIP.C.ACK) {
+          this.request(SIP.C.BYE, {
+            extraHeaders: extraHeaders,
+            body: body
+          });
+          dialog.terminate();
+        }
+      };
+
+      this.request.server_transaction.on('stateChanged', function(){
+        if (this.state === SIP.Transactions.C.STATUS_TERMINATED) {
+          this.request = new SIP.OutgoingRequest(
+            SIP.C.BYE,
+            this.dialog.remote_target,
+            this.ua,
+            {
+              'cseq': this.dialog.local_seqnum+=1,
+              'call_id': this.dialog.id.call_id,
+              'from_uri': this.dialog.local_uri,
+              'from_tag': this.dialog.id.local_tag,
+              'to_uri': this.dialog.remote_uri,
+              'to_tag': this.dialog.id.remote_tag,
+              'route_set': this.dialog.route_set
+            },
+            extraHeaders,
+            body
+          );
+
+          new SIP.RequestSender(
+            {
+              request: this.request,
+              onRequestTimeout: function() {
+                self.onRequestTimeout();
+              },
+              onTransportError: function() {
+                self.onTransportError();
+              },
+              receiveResponse: function() {
+                return;
+              }
+            },
+            this.ua
+          ).send();
+          dialog.terminate();
+        }
+      });
+
+      this.emit('bye', this.request);
+      this.terminated();
+
+      // Restore the dialog into 'this' in order to be able to send the in-dialog BYE :-)
+      this.dialog = dialog;
+
+      // Restore the dialog into 'ua' so the ACK can reach 'this' session
+      this.ua.dialogs[dialog.id.toString()] = dialog;
+
+    } else if (this.status === C.STATUS_CONFIRMED) {
+      this.bye(options);
+    } else {
+      this.reject(options);
+    }
+
+    return this;
+  },
+
+  /*
+   * @param {Object} [options.media] gets passed to SIP.MediaHandler.getDescription as mediaHint
+   */
+  progress: function (options) {
+    options = options || {};
+    var
+      statusCode = options.statusCode || 180,
+      reasonPhrase = options.reasonPhrase,
+      extraHeaders = (options.extraHeaders || []).slice(),
+      iceServers,
+      stunServers = options.stunServers || null,
+      turnServers = options.turnServers || null,
+      body = options.body,
+      response;
+
+    if (statusCode < 100 || statusCode > 199) {
+      throw new TypeError('Invalid statusCode: ' + statusCode);
+    }
+
+    if (this.isCanceled || this.status === C.STATUS_TERMINATED) {
+      return this;
+    }
+
+    if (stunServers || turnServers) {
+      if (stunServers) {
+        iceServers = SIP.UA.configuration_check.optional['stunServers'](stunServers);
+        if (!iceServers) {
+          throw new TypeError('Invalid stunServers: '+ stunServers);
+        } else {
+          this.stunServers = iceServers;
+        }
+      }
+
+      if (turnServers) {
+        iceServers = SIP.UA.configuration_check.optional['turnServers'](turnServers);
+        if (!iceServers) {
+          throw new TypeError('Invalid turnServers: '+ turnServers);
+        } else {
+          this.turnServers = iceServers;
+        }
+      }
+
+      this.mediaHandler.updateIceServers({
+        stunServers: this.stunServers,
+        turnServers: this.turnServers
+      });
+    }
+
+    function do100rel() {
+      /* jshint validthis: true */
+      statusCode = options.statusCode || 183;
+
+      // Set status and add extra headers
+      this.status = C.STATUS_WAITING_FOR_PRACK;
+      extraHeaders.push('Contact: '+ this.contact);
+      extraHeaders.push('Require: 100rel');
+      extraHeaders.push('RSeq: ' + Math.floor(Math.random() * 10000));
+
+      // Save media hint for later (referred sessions)
+      this.mediaHint = options.media;
+
+      // Get the session description to add to preaccept with
+      this.mediaHandler.getDescription(options.media)
+      .then(
+        function onSuccess (body) {
+          if (this.isCanceled || this.status === C.STATUS_TERMINATED) {
+            return;
+          }
+
+          this.early_sdp = body;
+          this[this.hasOffer ? 'hasAnswer' : 'hasOffer'] = true;
+
+          // Retransmit until we get a response or we time out (see prackTimer below)
+          var timeout = SIP.Timers.T1;
+          this.timers.rel1xxTimer = SIP.Timers.setTimeout(function rel1xxRetransmission() {
+            this.request.reply(statusCode, null, extraHeaders, body);
+            timeout *= 2;
+            this.timers.rel1xxTimer = SIP.Timers.setTimeout(rel1xxRetransmission.bind(this), timeout);
+          }.bind(this), timeout);
+
+          // Timeout and reject INVITE if no response
+          this.timers.prackTimer = SIP.Timers.setTimeout(function () {
+            if (this.status !== C.STATUS_WAITING_FOR_PRACK) {
+              return;
+            }
+
+            this.logger.log('no PRACK received, rejecting the call');
+            SIP.Timers.clearTimeout(this.timers.rel1xxTimer);
+            this.request.reply(504);
+            this.terminated(null, SIP.C.causes.NO_PRACK);
+          }.bind(this), SIP.Timers.T1 * 64);
+
+          // Send the initial response
+          response = this.request.reply(statusCode, reasonPhrase, extraHeaders, body);
+          this.emit('progress', response, reasonPhrase);
+        }.bind(this),
+
+        function onFailure () {
+          this.request.reply(480);
+          this.failed(null, SIP.C.causes.WEBRTC_ERROR);
+          this.terminated(null, SIP.C.causes.WEBRTC_ERROR);
+        }.bind(this)
+      );
+    } // end do100rel
+
+    function normalReply() {
+      /* jshint validthis:true */
+      response = this.request.reply(statusCode, reasonPhrase, extraHeaders, body);
+      this.emit('progress', response, reasonPhrase);
+    }
+
+    if (options.statusCode !== 100 &&
+        (this.rel100 === SIP.C.supported.REQUIRED ||
+         (this.rel100 === SIP.C.supported.SUPPORTED && options.rel100) ||
+         (this.rel100 === SIP.C.supported.SUPPORTED && (this.ua.configuration.rel100 === SIP.C.supported.REQUIRED)))) {
+      do100rel.apply(this);
+    } else {
+      normalReply.apply(this);
+    }
+    return this;
+  },
+
+  /*
+   * @param {Object} [options.media] gets passed to SIP.MediaHandler.getDescription as mediaHint
+   */
+  accept: function(options) {
+    options = Object.create(Session.desugar(options));
+    SIP.Utils.optionsOverride(options, 'media', 'mediaConstraints', true, this.logger, this.ua.configuration.media);
+    this.mediaHint = options.media;
+
+    // commented out now-unused hold-related variables for jshint. See below. JMF 2014-1-21
+    var
+      //idx, length, hasAudio, hasVideo,
+      self = this,
+      request = this.request,
+      extraHeaders = (options.extraHeaders || []).slice(),
+    //mediaStream = options.mediaStream || null,
+      iceServers,
+      stunServers = options.stunServers || null,
+      turnServers = options.turnServers || null,
+      sdpCreationSucceeded = function(body) {
+        var
+          response,
+          // run for reply success callback
+          replySucceeded = function() {
+            self.status = C.STATUS_WAITING_FOR_ACK;
+
+            self.setInvite2xxTimer(request, body);
+            self.setACKTimer();
+          },
+
+          // run for reply failure callback
+          replyFailed = function() {
+            self.failed(null, SIP.C.causes.CONNECTION_ERROR);
+            self.terminated(null, SIP.C.causes.CONNECTION_ERROR);
+          };
+
+        // Chrome might call onaddstream before accept() is called, which means
+        // mediaHandler.render() was called without a renderHint, so we need to
+        // re-render now that mediaHint.render has been set.
+        //
+        // Chrome seems to be in the right regarding this, see
+        // http://dev.w3.org/2011/webrtc/editor/webrtc.html#widl-RTCPeerConnection-onaddstream
+        self.mediaHandler.render();
+
+        extraHeaders.push('Contact: ' + self.contact);
+        extraHeaders.push('Allow: ' + SIP.UA.C.ALLOWED_METHODS.toString());
+
+        if(!self.hasOffer) {
+          self.hasOffer = true;
+        } else {
+          self.hasAnswer = true;
+        }
+        response = request.reply(200, null, extraHeaders,
+                      body,
+                      replySucceeded,
+                      replyFailed
+                     );
+        if (self.status !== C.STATUS_TERMINATED) { // Didn't fail
+          self.accepted(response, SIP.Utils.getReasonPhrase(200));
+        }
+      },
+
+      sdpCreationFailed = function() {
+        if (self.status === C.STATUS_TERMINATED) {
+          return;
+        }
+        // TODO - fail out on error
+        self.request.reply(480);
+        //self.failed(response, SIP.C.causes.USER_DENIED_MEDIA_ACCESS);
+        self.failed(null, SIP.C.causes.WEBRTC_ERROR);
+        self.terminated(null, SIP.C.causes.WEBRTC_ERROR);
+      };
+
+    // Check Session Status
+    if (this.status === C.STATUS_WAITING_FOR_PRACK) {
+      this.status = C.STATUS_ANSWERED_WAITING_FOR_PRACK;
+      return this;
+    } else if (this.status === C.STATUS_WAITING_FOR_ANSWER) {
+      this.status = C.STATUS_ANSWERED;
+    } else if (this.status !== C.STATUS_EARLY_MEDIA) {
+      throw new SIP.Exceptions.InvalidStateError(this.status);
+    }
+
+    if ((stunServers || turnServers) &&
+        (this.status !== C.STATUS_EARLY_MEDIA && this.status !== C.STATUS_ANSWERED_WAITING_FOR_PRACK)) {
+      if (stunServers) {
+        iceServers = SIP.UA.configuration_check.optional['stunServers'](stunServers);
+        if (!iceServers) {
+          throw new TypeError('Invalid stunServers: '+ stunServers);
+        } else {
+          this.stunServers = iceServers;
+        }
+      }
+
+      if (turnServers) {
+        iceServers = SIP.UA.configuration_check.optional['turnServers'](turnServers);
+        if (!iceServers) {
+          throw new TypeError('Invalid turnServers: '+ turnServers);
+        } else {
+          this.turnServers = iceServers;
+        }
+      }
+
+      this.mediaHandler.updateIceServers({
+        stunServers: this.stunServers,
+        turnServers: this.turnServers
+      });
+    }
+
+    // An error on dialog creation will fire 'failed' event
+    if(!this.createDialog(request, 'UAS')) {
+      request.reply(500, 'Missing Contact header field');
+      return this;
+    }
+
+    SIP.Timers.clearTimeout(this.timers.userNoAnswerTimer);
+
+    // this hold-related code breaks FF accepting new calls - JMF 2014-1-21
+    /*
+    length = this.getRemoteStreams().length;
+
+    for (idx = 0; idx < length; idx++) {
+      if (this.mediaHandler.getRemoteStreams()[idx].getVideoTracks().length > 0) {
+        hasVideo = true;
+      }
+      if (this.mediaHandler.getRemoteStreams()[idx].getAudioTracks().length > 0) {
+        hasAudio = true;
+      }
+    }
+
+    if (!hasAudio && this.mediaConstraints.audio === true) {
+      this.mediaConstraints.audio = false;
+      if (mediaStream) {
+        length = mediaStream.getAudioTracks().length;
+        for (idx = 0; idx < length; idx++) {
+          mediaStream.removeTrack(mediaStream.getAudioTracks()[idx]);
+        }
+      }
+    }
+
+    if (!hasVideo && this.mediaConstraints.video === true) {
+      this.mediaConstraints.video = false;
+      if (mediaStream) {
+        length = mediaStream.getVideoTracks().length;
+        for (idx = 0; idx < length; idx++) {
+          mediaStream.removeTrack(mediaStream.getVideoTracks()[idx]);
+        }
+      }
+    }
+    */
+
+    if (this.status === C.STATUS_EARLY_MEDIA) {
+      sdpCreationSucceeded();
+    } else {
+      this.mediaHandler.getDescription(self.mediaHint)
+      .then(
+        sdpCreationSucceeded,
+        sdpCreationFailed
+      );
+    }
+
+    return this;
+  },
+
+  receiveRequest: function(request) {
+
+    // ISC RECEIVE REQUEST
+
+    function confirmSession() {
+      /* jshint validthis:true */
+      var contentType;
+
+      SIP.Timers.clearTimeout(this.timers.ackTimer);
+      SIP.Timers.clearTimeout(this.timers.invite2xxTimer);
+      this.status = C.STATUS_CONFIRMED;
+      this.unmute();
+
+      // TODO - this logic assumes Content-Disposition defaults
+      contentType = request.getHeader('Content-Type');
+      if (contentType !== 'application/sdp') {
+        this.renderbody = request.body;
+        this.rendertype = contentType;
+      }
+    }
+
+    switch(request.method) {
+    case SIP.C.CANCEL:
+      /* RFC3261 15 States that a UAS may have accepted an invitation while a CANCEL
+       * was in progress and that the UAC MAY continue with the session established by
+       * any 2xx response, or MAY terminate with BYE. SIP does continue with the
+       * established session. So the CANCEL is processed only if the session is not yet
+       * established.
+       */
+
+      /*
+       * Terminate the whole session in case the user didn't accept (or yet to send the answer) nor reject the
+       *request opening the session.
+       */
+      if(this.status === C.STATUS_WAITING_FOR_ANSWER ||
+         this.status === C.STATUS_WAITING_FOR_PRACK ||
+         this.status === C.STATUS_ANSWERED_WAITING_FOR_PRACK ||
+         this.status === C.STATUS_EARLY_MEDIA ||
+         this.status === C.STATUS_ANSWERED) {
+
+        this.status = C.STATUS_CANCELED;
+        this.request.reply(487);
+        this.canceled(request);
+        this.rejected(request, SIP.C.causes.CANCELED);
+        this.failed(request, SIP.C.causes.CANCELED);
+        this.terminated(request, SIP.C.causes.CANCELED);
+      }
+      break;
+    case SIP.C.ACK:
+      if(this.status === C.STATUS_WAITING_FOR_ACK) {
+        if (!this.hasAnswer) {
+          if(request.body && request.getHeader('content-type') === 'application/sdp') {
+            // ACK contains answer to an INVITE w/o SDP negotiation
+            SIP.Hacks.Firefox.cannotHandleExtraWhitespace(request);
+            SIP.Hacks.AllBrowsers.maskDtls(request);
+
+            this.hasAnswer = true;
+            this.mediaHandler.setDescription(request.body)
+            .then(
+              confirmSession.bind(this),
+              function onFailure (e) {
+                this.logger.warn(e);
+                this.terminate({
+                  statusCode: '488',
+                  reasonPhrase: 'Bad Media Description'
+                });
+                this.failed(request, SIP.C.causes.BAD_MEDIA_DESCRIPTION);
+                this.terminated(request, SIP.C.causes.BAD_MEDIA_DESCRIPTION);
+              }.bind(this)
+            );
+          } else if (this.early_sdp) {
+            confirmSession.apply(this);
+          } else {
+            //TODO: Pass to mediahandler
+            this.failed(request, SIP.C.causes.BAD_MEDIA_DESCRIPTION);
+            this.terminated(request, SIP.C.causes.BAD_MEDIA_DESCRIPTION);
+          }
+        } else {
+          confirmSession.apply(this);
+        }
+      }
+      break;
+    case SIP.C.PRACK:
+      if (this.status === C.STATUS_WAITING_FOR_PRACK || this.status === C.STATUS_ANSWERED_WAITING_FOR_PRACK) {
+        //localMedia = session.mediaHandler.localMedia;
+        if(!this.hasAnswer) {
+          if(request.body && request.getHeader('content-type') === 'application/sdp') {
+            this.hasAnswer = true;
+            this.mediaHandler.setDescription(request.body)
+            .then(
+              function onSuccess () {
+                SIP.Timers.clearTimeout(this.timers.rel1xxTimer);
+                SIP.Timers.clearTimeout(this.timers.prackTimer);
+                request.reply(200);
+                if (this.status === C.STATUS_ANSWERED_WAITING_FOR_PRACK) {
+                  this.status = C.STATUS_EARLY_MEDIA;
+                  this.accept();
+                }
+                this.status = C.STATUS_EARLY_MEDIA;
+                //REVISIT
+                this.mute();
+              }.bind(this),
+              function onFailure (e) {
+                //TODO: Send to media handler
+                this.logger.warn(e);
+                this.terminate({
+                  statusCode: '488',
+                  reasonPhrase: 'Bad Media Description'
+                });
+                this.failed(request, SIP.C.causes.BAD_MEDIA_DESCRIPTION);
+                this.terminated(request, SIP.C.causes.BAD_MEDIA_DESCRIPTION);
+              }.bind(this)
+            );
+          } else {
+            this.terminate({
+              statusCode: '488',
+              reasonPhrase: 'Bad Media Description'
+            });
+            this.failed(request, SIP.C.causes.BAD_MEDIA_DESCRIPTION);
+            this.terminated(request, SIP.C.causes.BAD_MEDIA_DESCRIPTION);
+          }
+        } else {
+          SIP.Timers.clearTimeout(this.timers.rel1xxTimer);
+          SIP.Timers.clearTimeout(this.timers.prackTimer);
+          request.reply(200);
+
+          if (this.status === C.STATUS_ANSWERED_WAITING_FOR_PRACK) {
+            this.status = C.STATUS_EARLY_MEDIA;
+            this.accept();
+          }
+          this.status = C.STATUS_EARLY_MEDIA;
+          //REVISIT
+          this.mute();
+        }
+      } else if(this.status === C.STATUS_EARLY_MEDIA) {
+        request.reply(200);
+      }
+      break;
+    default:
+      Session.prototype.receiveRequest.apply(this, [request]);
+      break;
+    }
+  },
+
+  onTransportError: function() {
+    if (this.status !== C.STATUS_CONFIRMED && this.status !== C.STATUS_TERMINATED) {
+      this.failed(null, SIP.C.causes.CONNECTION_ERROR);
+    }
+  },
+
+  onRequestTimeout: function() {
+    if (this.status === C.STATUS_CONFIRMED) {
+      this.terminated(null, SIP.C.causes.REQUEST_TIMEOUT);
+    } else if (this.status !== C.STATUS_TERMINATED) {
+      this.failed(null, SIP.C.causes.REQUEST_TIMEOUT);
+      this.terminated(null, SIP.C.causes.REQUEST_TIMEOUT);
+    }
+  }
+
+};
+
+SIP.InviteServerContext = InviteServerContext;
+
+InviteClientContext = function(ua, target, options) {
+  options = Object.create(Session.desugar(options));
+  options.params = Object.create(options.params || Object.prototype);
+
+  var iceServers,
+    extraHeaders = (options.extraHeaders || []).slice(),
+    stunServers = options.stunServers || null,
+    turnServers = options.turnServers || null,
+    mediaHandlerFactory = options.mediaHandlerFactory || ua.configuration.mediaHandlerFactory,
+    isMediaSupported = mediaHandlerFactory.isSupported;
+
+  // Check WebRTC support
+  if (isMediaSupported && !isMediaSupported()) {
+    throw new SIP.Exceptions.NotSupportedError('Media not supported');
+  }
+
+  this.RTCConstraints = options.RTCConstraints || {};
+  this.inviteWithoutSdp = options.inviteWithoutSdp || false;
+
+  // Set anonymous property
+  this.anonymous = options.anonymous || false;
+
+  // Custom data to be sent either in INVITE or in ACK
+  this.renderbody = options.renderbody || null;
+  this.rendertype = options.rendertype || 'text/plain';
+
+  options.params.from_tag = this.from_tag;
+
+  /* Do not add ;ob in initial forming dialog requests if the registration over
+   *  the current connection got a GRUU URI.
+   */
+  this.contact = ua.contact.toString({
+    anonymous: this.anonymous,
+    outbound: this.anonymous ? !ua.contact.temp_gruu : !ua.contact.pub_gruu
+  });
+
+  if (this.anonymous) {
+    options.params.from_displayName = 'Anonymous';
+    options.params.from_uri = 'sip:anonymous@anonymous.invalid';
+
+    extraHeaders.push('P-Preferred-Identity: '+ ua.configuration.uri.toString());
+    extraHeaders.push('Privacy: id');
+  }
+  extraHeaders.push('Contact: '+ this.contact);
+  extraHeaders.push('Allow: '+ SIP.UA.C.ALLOWED_METHODS.toString());
+  if (!this.inviteWithoutSdp) {
+    extraHeaders.push('Content-Type: application/sdp');
+  } else if (this.renderbody) {
+    extraHeaders.push('Content-Type: ' + this.rendertype);
+    extraHeaders.push('Content-Disposition: render;handling=optional');
+  }
+
+  if (ua.configuration.rel100 === SIP.C.supported.REQUIRED) {
+    extraHeaders.push('Require: 100rel');
+  }
+  if (ua.configuration.replaces === SIP.C.supported.REQUIRED) {
+    extraHeaders.push('Require: replaces');
+  }
+
+  options.extraHeaders = extraHeaders;
+
+  SIP.Utils.augment(this, SIP.ClientContext, [ua, SIP.C.INVITE, target, options]);
+  SIP.Utils.augment(this, SIP.Session, [mediaHandlerFactory]);
+
+  // Check Session Status
+  if (this.status !== C.STATUS_NULL) {
+    throw new SIP.Exceptions.InvalidStateError(this.status);
+  }
+
+  // Session parameter initialization
+  this.from_tag = SIP.Utils.newTag();
+
+  // OutgoingSession specific parameters
+  this.isCanceled = false;
+  this.received_100 = false;
+
+  this.method = SIP.C.INVITE;
+
+  this.receiveNonInviteResponse = this.receiveResponse;
+  this.receiveResponse = this.receiveInviteResponse;
+
+  this.logger = ua.getLogger('sip.inviteclientcontext');
+
+  if (stunServers) {
+    iceServers = SIP.UA.configuration_check.optional['stunServers'](stunServers);
+    if (!iceServers) {
+      throw new TypeError('Invalid stunServers: '+ stunServers);
+    } else {
+      this.stunServers = iceServers;
+    }
+  }
+
+  if (turnServers) {
+    iceServers = SIP.UA.configuration_check.optional['turnServers'](turnServers);
+    if (!iceServers) {
+      throw new TypeError('Invalid turnServers: '+ turnServers);
+    } else {
+      this.turnServers = iceServers;
+    }
+  }
+
+  ua.applicants[this] = this;
+
+  this.id = this.request.call_id + this.from_tag;
+
+  //Initialize Media Session
+  this.mediaHandler = this.mediaHandlerFactory(this, {
+    RTCConstraints: this.RTCConstraints,
+    stunServers: this.stunServers,
+    turnServers: this.turnServers
+  });
+
+  if (this.mediaHandler && this.mediaHandler.getRemoteStreams) {
+    this.getRemoteStreams = this.mediaHandler.getRemoteStreams.bind(this.mediaHandler);
+    this.getLocalStreams = this.mediaHandler.getLocalStreams.bind(this.mediaHandler);
+  }
+
+  SIP.Utils.optionsOverride(options, 'media', 'mediaConstraints', true, this.logger, this.ua.configuration.media);
+  this.mediaHint = options.media;
+};
+
+InviteClientContext.prototype = {
+  invite: function () {
+    var self = this;
+
+    //Save the session into the ua sessions collection.
+    //Note: placing in constructor breaks call to request.cancel on close... User does not need this anyway
+    this.ua.sessions[this.id] = this;
+
+    //Note: due to the way Firefox handles gUM calls, it is recommended to make the gUM call at the app level
+    // and hand sip.js a stream as the mediaHint
+    if (this.inviteWithoutSdp) {
+      //just send an invite with no sdp...
+      this.request.body = self.renderbody;
+      this.status = C.STATUS_INVITE_SENT;
+      this.send();
+    } else {
+      this.mediaHandler.getDescription(self.mediaHint)
+      .then(
+        function onSuccess(offer) {
+          if (self.isCanceled || self.status === C.STATUS_TERMINATED) {
+            return;
+          }
+          self.hasOffer = true;
+          self.request.body = offer;
+          self.status = C.STATUS_INVITE_SENT;
+          self.send();
+        },
+        function onFailure() {
+          if (self.status === C.STATUS_TERMINATED) {
+            return;
+          }
+          // TODO...fail out
+          //self.failed(null, SIP.C.causes.USER_DENIED_MEDIA_ACCESS);
+          //self.failed(null, SIP.C.causes.WEBRTC_ERROR);
+          self.failed(null, SIP.C.causes.WEBRTC_ERROR);
+          self.terminated(null, SIP.C.causes.WEBRTC_ERROR);
+        }
+      );
+    }
+
+    return this;
+  },
+
+  receiveInviteResponse: function(response) {
+    var cause, //localMedia,
+      session = this,
+      id = response.call_id + response.from_tag + response.to_tag,
+      extraHeaders = [],
+      options = {};
+
+    if (this.status === C.STATUS_TERMINATED || response.method !== SIP.C.INVITE) {
+      return;
+    }
+
+    if (this.dialog && (response.status_code >= 200 && response.status_code <= 299)) {
+      if (id !== this.dialog.id.toString() ) {
+        if (!this.createDialog(response, 'UAC', true)) {
+          return;
+        }
+        this.earlyDialogs[id].sendRequest(this, SIP.C.ACK,
+                                          {
+                                            body: SIP.Utils.generateFakeSDP(response.body)
+                                          });
+        this.earlyDialogs[id].sendRequest(this, SIP.C.BYE);
+
+        /* NOTE: This fails because the forking proxy does not recognize that an unanswerable
+         * leg (due to peerConnection limitations) has been answered first. If your forking
+         * proxy does not hang up all unanswered branches on the first branch answered, remove this.
+         */
+        if(this.status !== C.STATUS_CONFIRMED) {
+          this.failed(response, SIP.C.causes.WEBRTC_ERROR);
+          this.terminated(response, SIP.C.causes.WEBRTC_ERROR);
+        }
+        return;
+      } else if (this.status === C.STATUS_CONFIRMED) {
+        this.sendRequest(SIP.C.ACK,{cseq: response.cseq});
+        return;
+      } else if (!this.hasAnswer) {
+        // invite w/o sdp is waiting for callback
+        //an invite with sdp must go on, and hasAnswer is true
+        return;
+      }
+    }
+
+    if (this.dialog && response.status_code < 200) {
+      /*
+        Early media has been set up with at least one other different branch,
+        but a final 2xx response hasn't been received
+      */
+      if (this.dialog.pracked.indexOf(response.getHeader('rseq')) !== -1 ||
+          (this.dialog.pracked[this.dialog.pracked.length-1] >= response.getHeader('rseq') && this.dialog.pracked.length > 0)) {
+        return;
+      }
+
+      if (!this.earlyDialogs[id] && !this.createDialog(response, 'UAC', true)) {
+        return;
+      }
+
+      if (this.earlyDialogs[id].pracked.indexOf(response.getHeader('rseq')) !== -1 ||
+          (this.earlyDialogs[id].pracked[this.earlyDialogs[id].pracked.length-1] >= response.getHeader('rseq') && this.earlyDialogs[id].pracked.length > 0)) {
+        return;
+      }
+
+      extraHeaders.push('RAck: ' + response.getHeader('rseq') + ' ' + response.getHeader('cseq'));
+      this.earlyDialogs[id].pracked.push(response.getHeader('rseq'));
+
+      this.earlyDialogs[id].sendRequest(this, SIP.C.PRACK, {
+        extraHeaders: extraHeaders,
+        body: SIP.Utils.generateFakeSDP(response.body)
+      });
+      return;
+    }
+
+    // Proceed to cancellation if the user requested.
+    if(this.isCanceled) {
+      if(response.status_code >= 100 && response.status_code < 200) {
+        this.request.cancel(this.cancelReason);
+        this.canceled(null);
+      } else if(response.status_code >= 200 && response.status_code < 299) {
+        this.acceptAndTerminate(response);
+        this.emit('bye', this.request);
+      } else if (response.status_code >= 300) {
+        cause = SIP.C.REASON_PHRASE[response.status_code] || SIP.C.causes.CANCELED;
+        this.rejected(response, cause);
+        this.failed(response, cause);
+        this.terminated(response, cause);
+      }
+      return;
+    }
+
+    switch(true) {
+      case /^100$/.test(response.status_code):
+        this.received_100 = true;
+        this.emit('progress', response);
+        break;
+      case (/^1[0-9]{2}$/.test(response.status_code)):
+        // Do nothing with 1xx responses without To tag.
+        if(!response.to_tag) {
+          this.logger.warn('1xx response received without to tag');
+          break;
+        }
+
+        // Create Early Dialog if 1XX comes with contact
+        if(response.hasHeader('contact')) {
+          // An error on dialog creation will fire 'failed' event
+          if (!this.createDialog(response, 'UAC', true)) {
+            break;
+          }
+        }
+
+        this.status = C.STATUS_1XX_RECEIVED;
+
+        if(response.hasHeader('require') &&
+           response.getHeader('require').indexOf('100rel') !== -1) {
+
+          // Do nothing if this.dialog is already confirmed
+          if (this.dialog || !this.earlyDialogs[id]) {
+            break;
+          }
+
+          if (this.earlyDialogs[id].pracked.indexOf(response.getHeader('rseq')) !== -1 ||
+              (this.earlyDialogs[id].pracked[this.earlyDialogs[id].pracked.length-1] >= response.getHeader('rseq') && this.earlyDialogs[id].pracked.length > 0)) {
+            return;
+          }
+
+          SIP.Hacks.Firefox.cannotHandleExtraWhitespace(response);
+          SIP.Hacks.AllBrowsers.maskDtls(response);
+
+          if (!response.body) {
+            extraHeaders.push('RAck: ' + response.getHeader('rseq') + ' ' + response.getHeader('cseq'));
+            this.earlyDialogs[id].pracked.push(response.getHeader('rseq'));
+            this.earlyDialogs[id].sendRequest(this, SIP.C.PRACK, {
+              extraHeaders: extraHeaders
+            });
+            this.emit('progress', response);
+
+          } else if (this.hasOffer) {
+            if (!this.createDialog(response, 'UAC')) {
+              break;
+            }
+            this.hasAnswer = true;
+            this.dialog.pracked.push(response.getHeader('rseq'));
+
+            this.mediaHandler.setDescription(response.body)
+            .then(
+              function onSuccess () {
+                extraHeaders.push('RAck: ' + response.getHeader('rseq') + ' ' + response.getHeader('cseq'));
+
+                session.sendRequest(SIP.C.PRACK, {
+                  extraHeaders: extraHeaders,
+                  receiveResponse: function() {}
+                });
+                session.status = C.STATUS_EARLY_MEDIA;
+                session.mute();
+                session.emit('progress', response);
+                /*
+                if (session.status === C.STATUS_EARLY_MEDIA) {
+                  localMedia = session.mediaHandler.localMedia;
+                  if (localMedia.getAudioTracks().length > 0) {
+                    localMedia.getAudioTracks()[0].enabled = false;
+                  }
+                  if (localMedia.getVideoTracks().length > 0) {
+                    localMedia.getVideoTracks()[0].enabled = false;
+                  }
+                }*/
+              },
+              function onFailure (e) {
+                session.logger.warn(e);
+                session.acceptAndTerminate(response, 488, 'Not Acceptable Here');
+                session.failed(response, SIP.C.causes.BAD_MEDIA_DESCRIPTION);
+              }
+            );
+          } else {
+            var earlyDialog = this.earlyDialogs[id];
+            var earlyMedia = earlyDialog.mediaHandler;
+
+            earlyDialog.pracked.push(response.getHeader('rseq'));
+
+            earlyMedia.setDescription(response.body)
+            .then(earlyMedia.getDescription.bind(earlyMedia, session.mediaHint))
+            .then(function onSuccess(sdp) {
+              extraHeaders.push('Content-Type: application/sdp');
+              extraHeaders.push('RAck: ' + response.getHeader('rseq') + ' ' + response.getHeader('cseq'));
+              earlyDialog.sendRequest(session, SIP.C.PRACK, {
+                extraHeaders: extraHeaders,
+                body: sdp
+              });
+              session.status = C.STATUS_EARLY_MEDIA;
+              session.emit('progress', response);
+            })
+            .catch(function onFailure(e) {
+              if (e instanceof SIP.Exceptions.GetDescriptionError) {
+                earlyDialog.pracked.push(response.getHeader('rseq'));
+                if (session.status === C.STATUS_TERMINATED) {
+                  return;
+                }
+                // TODO - fail out on error
+                // session.failed(gum error);
+                session.failed(null, SIP.C.causes.WEBRTC_ERROR);
+                session.terminated(null, SIP.C.causes.WEBRTC_ERROR);
+              } else {
+                earlyDialog.pracked.splice(earlyDialog.pracked.indexOf(response.getHeader('rseq')), 1);
+                // Could not set remote description
+                session.logger.warn('invalid SDP');
+                session.logger.warn(e);
+              }
+            });
+          }
+        } else {
+          this.emit('progress', response);
+        }
+        break;
+      case /^2[0-9]{2}$/.test(response.status_code):
+        var cseq = this.request.cseq + ' ' + this.request.method;
+        if (cseq !== response.getHeader('cseq')) {
+          break;
+        }
+
+        if (this.status === C.STATUS_EARLY_MEDIA && this.dialog) {
+          this.status = C.STATUS_CONFIRMED;
+          this.unmute();
+          /*localMedia = this.mediaHandler.localMedia;
+          if (localMedia.getAudioTracks().length > 0) {
+            localMedia.getAudioTracks()[0].enabled = true;
+          }
+          if (localMedia.getVideoTracks().length > 0) {
+            localMedia.getVideoTracks()[0].enabled = true;
+          }*/
+          options = {};
+          if (this.renderbody) {
+            extraHeaders.push('Content-Type: ' + this.rendertype);
+            options.extraHeaders = extraHeaders;
+            options.body = this.renderbody;
+          }
+          options.cseq = response.cseq;
+          this.sendRequest(SIP.C.ACK, options);
+          this.accepted(response);
+          break;
+        }
+        // Do nothing if this.dialog is already confirmed
+        if (this.dialog) {
+          break;
+        }
+
+        SIP.Hacks.Firefox.cannotHandleExtraWhitespace(response);
+        SIP.Hacks.AllBrowsers.maskDtls(response);
+
+        // This is an invite without sdp
+        if (!this.hasOffer) {
+          if (this.earlyDialogs[id] && this.earlyDialogs[id].mediaHandler.localMedia) {
+            //REVISIT
+            this.hasOffer = true;
+            this.hasAnswer = true;
+            this.mediaHandler = this.earlyDialogs[id].mediaHandler;
+            if (!this.createDialog(response, 'UAC')) {
+              break;
+            }
+            this.status = C.STATUS_CONFIRMED;
+            this.sendRequest(SIP.C.ACK, {cseq:response.cseq});
+
+            this.unmute();
+            /*
+            localMedia = session.mediaHandler.localMedia;
+            if (localMedia.getAudioTracks().length > 0) {
+              localMedia.getAudioTracks()[0].enabled = true;
+            }
+            if (localMedia.getVideoTracks().length > 0) {
+              localMedia.getVideoTracks()[0].enabled = true;
+            }*/
+            this.accepted(response);
+          } else {
+            if(!response.body) {
+              this.acceptAndTerminate(response, 400, 'Missing session description');
+              this.failed(response, SIP.C.causes.BAD_MEDIA_DESCRIPTION);
+              break;
+            }
+            if (!this.createDialog(response, 'UAC')) {
+              break;
+            }
+            this.hasOffer = true;
+            this.mediaHandler.setDescription(response.body)
+            .then(this.mediaHandler.getDescription.bind(this.mediaHandler, this.mediaHint))
+            .then(function onSuccess(sdp) {
+              //var localMedia;
+              if(session.isCanceled || session.status === C.STATUS_TERMINATED) {
+                return;
+              }
+
+              sdp = SIP.Hacks.Firefox.hasMissingCLineInSDP(sdp);
+
+              session.status = C.STATUS_CONFIRMED;
+              session.hasAnswer = true;
+
+              session.unmute();
+              /*localMedia = session.mediaHandler.localMedia;
+              if (localMedia.getAudioTracks().length > 0) {
+                localMedia.getAudioTracks()[0].enabled = true;
+              }
+              if (localMedia.getVideoTracks().length > 0) {
+                localMedia.getVideoTracks()[0].enabled = true;
+              }*/
+              session.sendRequest(SIP.C.ACK,{
+                body: sdp,
+                extraHeaders:['Content-Type: application/sdp'],
+                cseq:response.cseq
+              });
+              session.accepted(response);
+            })
+            .catch(function onFailure(e) {
+              if (e instanceof SIP.Exceptions.GetDescriptionError) {
+                // TODO do something here
+                session.logger.warn("there was a problem");
+              } else {
+                session.logger.warn('invalid SDP');
+                session.logger.warn(e);
+                response.reply(488);
+              }
+            });
+          }
+        } else if (this.hasAnswer){
+          if (this.renderbody) {
+            extraHeaders.push('Content-Type: ' + session.rendertype);
+            options.extraHeaders = extraHeaders;
+            options.body = this.renderbody;
+          }
+          this.sendRequest(SIP.C.ACK, options);
+        } else {
+          if(!response.body) {
+            this.acceptAndTerminate(response, 400, 'Missing session description');
+            this.failed(response, SIP.C.causes.BAD_MEDIA_DESCRIPTION);
+            break;
+          }
+          if (!this.createDialog(response, 'UAC')) {
+            break;
+          }
+          this.hasAnswer = true;
+          this.mediaHandler.setDescription(response.body)
+          .then(
+            function onSuccess () {
+              var options = {};//,localMedia;
+              session.status = C.STATUS_CONFIRMED;
+              session.unmute();
+              /*localMedia = session.mediaHandler.localMedia;
+              if (localMedia.getAudioTracks().length > 0) {
+                localMedia.getAudioTracks()[0].enabled = true;
+              }
+              if (localMedia.getVideoTracks().length > 0) {
+                localMedia.getVideoTracks()[0].enabled = true;
+              }*/
+              if (session.renderbody) {
+                extraHeaders.push('Content-Type: ' + session.rendertype);
+                options.extraHeaders = extraHeaders;
+                options.body = session.renderbody;
+              }
+              options.cseq = response.cseq;
+              session.sendRequest(SIP.C.ACK, options);
+              session.accepted(response);
+            },
+            function onFailure (e) {
+              session.logger.warn(e);
+              session.acceptAndTerminate(response, 488, 'Not Acceptable Here');
+              session.failed(response, SIP.C.causes.BAD_MEDIA_DESCRIPTION);
+            }
+          );
+        }
+        break;
+      default:
+        cause = SIP.Utils.sipErrorCause(response.status_code);
+        this.rejected(response, cause);
+        this.failed(response, cause);
+        this.terminated(response, cause);
+    }
+  },
+
+  cancel: function(options) {
+    options = options || {};
+
+    // Check Session Status
+    if (this.status === C.STATUS_TERMINATED || this.status === C.STATUS_CONFIRMED) {
+      throw new SIP.Exceptions.InvalidStateError(this.status);
+    }
+
+    this.logger.log('canceling RTCSession');
+
+    var cancel_reason = SIP.Utils.getCancelReason(options.status_code, options.reason_phrase);
+
+    // Check Session Status
+    if (this.status === C.STATUS_NULL ||
+        (this.status === C.STATUS_INVITE_SENT && !this.received_100)) {
+      this.isCanceled = true;
+      this.cancelReason = cancel_reason;
+    } else if (this.status === C.STATUS_INVITE_SENT ||
+               this.status === C.STATUS_1XX_RECEIVED ||
+               this.status === C.STATUS_EARLY_MEDIA) {
+      this.request.cancel(cancel_reason);
+    }
+
+    return this.canceled();
+  },
+
+  terminate: function(options) {
+    if (this.status === C.STATUS_TERMINATED) {
+      return this;
+    }
+
+    if (this.status === C.STATUS_WAITING_FOR_ACK || this.status === C.STATUS_CONFIRMED) {
+      this.bye(options);
+    } else {
+      this.cancel(options);
+    }
+
+    return this;
+  },
+
+  receiveRequest: function(request) {
+    // ICC RECEIVE REQUEST
+
+    // Reject CANCELs
+    if (request.method === SIP.C.CANCEL) {
+      // TODO; make this a switch when it gets added
+    }
+
+    if (request.method === SIP.C.ACK && this.status === C.STATUS_WAITING_FOR_ACK) {
+      SIP.Timers.clearTimeout(this.timers.ackTimer);
+      SIP.Timers.clearTimeout(this.timers.invite2xxTimer);
+      this.status = C.STATUS_CONFIRMED;
+      this.unmute();
+
+      this.accepted();
+    }
+
+    return Session.prototype.receiveRequest.apply(this, [request]);
+  },
+
+  onTransportError: function() {
+    if (this.status !== C.STATUS_CONFIRMED && this.status !== C.STATUS_TERMINATED) {
+      this.failed(null, SIP.C.causes.CONNECTION_ERROR);
+    }
+  },
+
+  onRequestTimeout: function() {
+    if (this.status === C.STATUS_CONFIRMED) {
+      this.terminated(null, SIP.C.causes.REQUEST_TIMEOUT);
+    } else if (this.status !== C.STATUS_TERMINATED) {
+      this.failed(null, SIP.C.causes.REQUEST_TIMEOUT);
+      this.terminated(null, SIP.C.causes.REQUEST_TIMEOUT);
+    }
+  }
+
+};
+
+SIP.InviteClientContext = InviteClientContext;
+
+};
+
+},{"./Session/DTMF":24}],24:[function(require,module,exports){
+"use strict";
+/**
+ * @fileoverview DTMF
+ */
+
+/**
+ * @class DTMF
+ * @param {SIP.Session} session
+ */
+module.exports = function (SIP) {
+
+var DTMF,
+  C = {
+    MIN_DURATION:            70,
+    MAX_DURATION:            6000,
+    DEFAULT_DURATION:        100,
+    MIN_INTER_TONE_GAP:      50,
+    DEFAULT_INTER_TONE_GAP:  500
+  };
+
+DTMF = function(session, tone, options) {
+  var duration, interToneGap;
+
+  if (tone === undefined) {
+    throw new TypeError('Not enough arguments');
+  }
+
+  this.logger = session.ua.getLogger('sip.invitecontext.dtmf', session.id);
+  this.owner = session;
+  this.direction = null;
+
+  options = options || {};
+  duration = options.duration || null;
+  interToneGap = options.interToneGap || null;
+
+  // Check tone type
+  if (typeof tone === 'string' ) {
+    tone = tone.toUpperCase();
+  } else if (typeof tone === 'number') {
+    tone = tone.toString();
+  } else {
+    throw new TypeError('Invalid tone: '+ tone);
+  }
+
+  // Check tone value
+  if (!tone.match(/^[0-9A-D#*]$/)) {
+    throw new TypeError('Invalid tone: '+ tone);
+  } else {
+    this.tone = tone;
+  }
+
+  // Check duration
+  if (duration && !SIP.Utils.isDecimal(duration)) {
+    throw new TypeError('Invalid tone duration: '+ duration);
+  } else if (!duration) {
+    duration = DTMF.C.DEFAULT_DURATION;
+  } else if (duration < DTMF.C.MIN_DURATION) {
+    this.logger.warn('"duration" value is lower than the minimum allowed, setting it to '+ DTMF.C.MIN_DURATION+ ' milliseconds');
+    duration = DTMF.C.MIN_DURATION;
+  } else if (duration > DTMF.C.MAX_DURATION) {
+    this.logger.warn('"duration" value is greater than the maximum allowed, setting it to '+ DTMF.C.MAX_DURATION +' milliseconds');
+    duration = DTMF.C.MAX_DURATION;
+  } else {
+    duration = Math.abs(duration);
+  }
+  this.duration = duration;
+
+  // Check interToneGap
+  if (interToneGap && !SIP.Utils.isDecimal(interToneGap)) {
+    throw new TypeError('Invalid interToneGap: '+ interToneGap);
+  } else if (!interToneGap) {
+    interToneGap = DTMF.C.DEFAULT_INTER_TONE_GAP;
+  } else if (interToneGap < DTMF.C.MIN_INTER_TONE_GAP) {
+    this.logger.warn('"interToneGap" value is lower than the minimum allowed, setting it to '+ DTMF.C.MIN_INTER_TONE_GAP +' milliseconds');
+    interToneGap = DTMF.C.MIN_INTER_TONE_GAP;
+  } else {
+    interToneGap = Math.abs(interToneGap);
+  }
+  this.interToneGap = interToneGap;
+};
+DTMF.prototype = Object.create(SIP.EventEmitter.prototype);
+
+
+DTMF.prototype.send = function(options) {
+  var extraHeaders, body;
+
+  this.direction = 'outgoing';
+
+  // Check RTCSession Status
+  if (this.owner.status !== SIP.Session.C.STATUS_CONFIRMED &&
+    this.owner.status !== SIP.Session.C.STATUS_WAITING_FOR_ACK) {
+    throw new SIP.Exceptions.InvalidStateError(this.owner.status);
+  }
+
+  // Get DTMF options
+  options = options || {};
+  extraHeaders = options.extraHeaders ? options.extraHeaders.slice() : [];
+
+  extraHeaders.push('Content-Type: application/dtmf-relay');
+
+  body = "Signal= " + this.tone + "\r\n";
+  body += "Duration= " + this.duration;
+
+  this.request = this.owner.dialog.sendRequest(this, SIP.C.INFO, {
+    extraHeaders: extraHeaders,
+    body: body
+  });
+
+  this.owner.emit('dtmf', this.request, this);
+};
+
+/**
+ * @private
+ */
+DTMF.prototype.receiveResponse = function(response) {
+  var cause;
+
+  switch(true) {
+    case /^1[0-9]{2}$/.test(response.status_code):
+      // Ignore provisional responses.
+      break;
+
+    case /^2[0-9]{2}$/.test(response.status_code):
+      this.emit('succeeded', {
+        originator: 'remote',
+        response: response
+      });
+      break;
+
+    default:
+      cause = SIP.Utils.sipErrorCause(response.status_code);
+      this.emit('failed', response, cause);
+      break;
+  }
+};
+
+/**
+ * @private
+ */
+DTMF.prototype.onRequestTimeout = function() {
+  this.emit('failed', null, SIP.C.causes.REQUEST_TIMEOUT);
+  this.owner.onRequestTimeout();
+};
+
+/**
+ * @private
+ */
+DTMF.prototype.onTransportError = function() {
+  this.emit('failed', null, SIP.C.causes.CONNECTION_ERROR);
+  this.owner.onTransportError();
+};
+
+/**
+ * @private
+ */
+DTMF.prototype.onDialogError = function(response) {
+  this.emit('failed', response, SIP.C.causes.DIALOG_ERROR);
+  this.owner.onDialogError(response);
+};
+
+/**
+ * @private
+ */
+DTMF.prototype.init_incoming = function(request) {
+  this.direction = 'incoming';
+  this.request = request;
+
+  request.reply(200);
+
+  if (!this.tone || !this.duration) {
+    this.logger.warn('invalid INFO DTMF received, discarded');
+  } else {
+    this.owner.emit('dtmf', request, this);
+  }
+};
+
+DTMF.C = C;
+return DTMF;
+};
+
+},{}],25:[function(require,module,exports){
+"use strict";
+
+/**
+ * @fileoverview SIP Subscriber (SIP-Specific Event Notifications RFC6665)
+ */
+
+/**
+ * @augments SIP
+ * @class Class creating a SIP Subscription.
+ */
+module.exports = function (SIP) {
+SIP.Subscription = function (ua, target, event, options) {
+  options = Object.create(options || Object.prototype);
+  this.extraHeaders = options.extraHeaders = (options.extraHeaders || []).slice();
+
+  this.id = null;
+  this.state = 'init';
+
+  if (!event) {
+    throw new TypeError('Event necessary to create a subscription.');
+  } else {
+    //TODO: check for valid events here probably make a list in SIP.C; or leave it up to app to check?
+    //The check may need to/should probably occur on the other side,
+    this.event = event;
+  }
+
+  if(typeof options.expires !== 'number'){
+    ua.logger.warn('expires must be a number. Using default of 3600.');
+    this.expires = 3600;
+  } else {
+    this.expires = options.expires;
+  }
+
+  options.extraHeaders.push('Event: ' + this.event);
+  options.extraHeaders.push('Expires: ' + this.expires);
+
+  if (options.body) {
+    this.body = options.body;
+  }
+
+  this.contact = ua.contact.toString();
+
+  options.extraHeaders.push('Contact: '+ this.contact);
+  options.extraHeaders.push('Allow: '+ SIP.UA.C.ALLOWED_METHODS.toString());
+
+  SIP.Utils.augment(this, SIP.ClientContext, [ua, SIP.C.SUBSCRIBE, target, options]);
+
+  this.logger = ua.getLogger('sip.subscription');
+
+  this.dialog = null;
+  this.timers = {N: null, sub_duration: null};
+  this.errorCodes  = [404,405,410,416,480,481,482,483,484,485,489,501,604];
+};
+
+SIP.Subscription.prototype = {
+  subscribe: function() {
+    var sub = this;
+
+     //these states point to an existing subscription, no subscribe is necessary
+    if (this.state === 'active') {
+      this.refresh();
+      return this;
+    } else if (this.state === 'notify_wait') {
+      return this;
+    }
+
+    SIP.Timers.clearTimeout(this.timers.sub_duration);
+    SIP.Timers.clearTimeout(this.timers.N);
+    this.timers.N = SIP.Timers.setTimeout(sub.timer_fire.bind(sub), SIP.Timers.TIMER_N);
+
+    this.send();
+
+    this.state = 'notify_wait';
+
+    return this;
+  },
+
+  refresh: function () {
+    if (this.state === 'terminated' || this.state === 'pending' || this.state === 'notify_wait') {
+      return;
+    }
+
+    this.dialog.sendRequest(this, SIP.C.SUBSCRIBE, {
+      extraHeaders: this.extraHeaders,
+      body: this.body
+    });
+  },
+
+  receiveResponse: function(response) {
+    var expires, sub = this,
+        cause = SIP.Utils.getReasonPhrase(response.status_code);
+
+    if ((this.state === 'notify_wait' && response.status_code >= 300) ||
+        (this.state !== 'notify_wait' && this.errorCodes.indexOf(response.status_code) !== -1)) {
+      this.failed(response, null);
+    } else if (/^2[0-9]{2}$/.test(response.status_code)){
+      expires = response.getHeader('Expires');
+      SIP.Timers.clearTimeout(this.timers.N);
+
+      if (this.createConfirmedDialog(response,'UAC')) {
+        this.id = this.dialog.id.toString();
+        this.ua.subscriptions[this.id] = this;
+        this.emit('accepted', response, cause);
+        // UPDATE ROUTE SET TO BE BACKWARDS COMPATIBLE?
+      }
+
+      if (expires && expires <= this.expires) {
+        // Preserve new expires value for subsequent requests
+        this.expires = expires;
+        this.timers.sub_duration = SIP.Timers.setTimeout(sub.refresh.bind(sub), expires * 900);
+      } else {
+        if (!expires) {
+          this.logger.warn('Expires header missing in a 200-class response to SUBSCRIBE');
+          this.failed(response, SIP.C.EXPIRES_HEADER_MISSING);
+        } else {
+          this.logger.warn('Expires header in a 200-class response to SUBSCRIBE with a higher value than the one in the request');
+          this.failed(response, SIP.C.INVALID_EXPIRES_HEADER);
+        }
+      }
+    } //Used to just ignore provisional responses; now ignores everything except errorCodes and 2xx
+  },
+
+  unsubscribe: function() {
+    var extraHeaders = [], sub = this;
+
+    this.state = 'terminated';
+
+    extraHeaders.push('Event: ' + this.event);
+    extraHeaders.push('Expires: 0');
+
+    extraHeaders.push('Contact: '+ this.contact);
+    extraHeaders.push('Allow: '+ SIP.UA.C.ALLOWED_METHODS.toString());
+
+    //makes sure expires isn't set, and other typical resubscribe behavior
+    this.receiveResponse = function(){};
+
+    this.dialog.sendRequest(this, this.method, {
+      extraHeaders: extraHeaders,
+      body: this.body
+    });
+
+    SIP.Timers.clearTimeout(this.timers.sub_duration);
+    SIP.Timers.clearTimeout(this.timers.N);
+    this.timers.N = SIP.Timers.setTimeout(sub.timer_fire.bind(sub), SIP.Timers.TIMER_N);
+  },
+
+  /**
+  * @private
+  */
+  timer_fire: function(){
+    if (this.state === 'terminated') {
+      this.terminateDialog();
+      SIP.Timers.clearTimeout(this.timers.N);
+      SIP.Timers.clearTimeout(this.timers.sub_duration);
+
+      delete this.ua.subscriptions[this.id];
+    } else if (this.state === 'pending' || this.state === 'notify_wait') {
+      this.close();
+    } else {
+      this.refresh();
+    }
+  },
+
+  /**
+  * @private
+  */
+  close: function() {
+    if(this.state !== 'notify_wait' && this.state !== 'terminated') {
+      this.unsubscribe();
+    }
+  },
+
+  /**
+  * @private
+  */
+  createConfirmedDialog: function(message, type) {
+    var dialog;
+
+    this.terminateDialog();
+    dialog = new SIP.Dialog(this, message, type);
+
+    if(!dialog.error) {
+      this.dialog = dialog;
+      return true;
+    }
+    // Dialog not created due to an error
+    else {
+      return false;
+    }
+  },
+
+  /**
+  * @private
+  */
+  terminateDialog: function() {
+    if(this.dialog) {
+      delete this.ua.subscriptions[this.id];
+      this.dialog.terminate();
+      delete this.dialog;
+    }
+  },
+
+  /**
+  * @private
+  */
+  receiveRequest: function(request) {
+    var sub_state, sub = this;
+
+    function setExpiresTimeout() {
+      if (sub_state.expires) {
+        SIP.Timers.clearTimeout(sub.timers.sub_duration);
+        sub_state.expires = Math.min(sub.expires,
+                                     Math.max(sub_state.expires, 0));
+        sub.timers.sub_duration = SIP.Timers.setTimeout(sub.refresh.bind(sub),
+                                                    sub_state.expires * 900);
+      }
+    }
+
+    if (!this.matchEvent(request)) { //checks event and subscription_state headers
+      request.reply(489);
+      return;
+    }
+
+    sub_state = request.parseHeader('Subscription-State');
+
+    request.reply(200, SIP.C.REASON_200);
+
+    SIP.Timers.clearTimeout(this.timers.N);
+
+    this.emit('notify', {request: request});
+
+    // if we've set state to terminated, no further processing should take place
+    // and we are only interested in cleaning up after the appropriate NOTIFY
+    if (this.state === 'terminated') {
+      if (sub_state.state === 'terminated') {
+        this.terminateDialog();
+        SIP.Timers.clearTimeout(this.timers.N);
+        SIP.Timers.clearTimeout(this.timers.sub_duration);
+
+        delete this.ua.subscriptions[this.id];
+      }
+      return;
+    }
+
+    switch (sub_state.state) {
+      case 'active':
+        this.state = 'active';
+        setExpiresTimeout();
+        break;
+      case 'pending':
+        if (this.state === 'notify_wait') {
+          setExpiresTimeout();
+        }
+        this.state = 'pending';
+        break;
+      case 'terminated':
+        SIP.Timers.clearTimeout(this.timers.sub_duration);
+        if (sub_state.reason) {
+          this.logger.log('terminating subscription with reason '+ sub_state.reason);
+          switch (sub_state.reason) {
+            case 'deactivated':
+            case 'timeout':
+              this.subscribe();
+              return;
+            case 'probation':
+            case 'giveup':
+              if(sub_state.params && sub_state.params['retry-after']) {
+                this.timers.sub_duration = SIP.Timers.setTimeout(sub.subscribe.bind(sub), sub_state.params['retry-after']);
+              } else {
+                this.subscribe();
+              }
+              return;
+            case 'rejected':
+            case 'noresource':
+            case 'invariant':
+              break;
+          }
+        }
+        this.close();
+        break;
+    }
+  },
+
+  failed: function(response, cause) {
+    this.close();
+    this.emit('failed', response, cause);
+    return this;
+  },
+
+  onDialogError: function(response) {
+    this.failed(response, SIP.C.causes.DIALOG_ERROR);
+  },
+
+  /**
+  * @private
+  */
+  matchEvent: function(request) {
+    var event;
+
+    // Check mandatory header Event
+    if (!request.hasHeader('Event')) {
+      this.logger.warn('missing Event header');
+      return false;
+    }
+    // Check mandatory header Subscription-State
+    if (!request.hasHeader('Subscription-State')) {
+      this.logger.warn('missing Subscription-State header');
+      return false;
+    }
+
+    // Check whether the event in NOTIFY matches the event in SUBSCRIBE
+    event = request.parseHeader('event').event;
+
+    if (this.event !== event) {
+      this.logger.warn('event match failed');
+      request.reply(481, 'Event Match Failed');
+      return false;
+    } else {
+      return true;
+    }
+  }
+};
+};
+
+},{}],26:[function(require,module,exports){
+"use strict";
+/**
+ * @fileoverview SIP TIMERS
+ */
+
+/**
+ * @augments SIP
+ */
+var
+  T1 = 500,
+  T2 = 4000,
+  T4 = 5000;
+module.exports = function (timers) {
+  var Timers = {
+    T1: T1,
+    T2: T2,
+    T4: T4,
+    TIMER_B: 64 * T1,
+    TIMER_D: 0  * T1,
+    TIMER_F: 64 * T1,
+    TIMER_H: 64 * T1,
+    TIMER_I: 0  * T1,
+    TIMER_J: 0  * T1,
+    TIMER_K: 0  * T4,
+    TIMER_L: 64 * T1,
+    TIMER_M: 64 * T1,
+    TIMER_N: 64 * T1,
+    PROVISIONAL_RESPONSE_INTERVAL: 60000  // See RFC 3261 Section 13.3.1.1
+  };
+
+  ['setTimeout', 'clearTimeout', 'setInterval', 'clearInterval']
+  .forEach(function (name) {
+    // can't just use timers[name].bind(timers) since it bypasses jasmine's
+    // clock-mocking
+    Timers[name] = function () {
+      return timers[name].apply(timers, arguments);
+    };
+  });
+
+  return Timers;
+};
+
+},{}],27:[function(require,module,exports){
+"use strict";
+/**
+ * @fileoverview SIP Transactions
+ */
+
+/**
+ * SIP Transactions module.
+ * @augments SIP
+ */
+module.exports = function (SIP) {
+var
+  C = {
+    // Transaction states
+    STATUS_TRYING:     1,
+    STATUS_PROCEEDING: 2,
+    STATUS_CALLING:    3,
+    STATUS_ACCEPTED:   4,
+    STATUS_COMPLETED:  5,
+    STATUS_TERMINATED: 6,
+    STATUS_CONFIRMED:  7,
+
+    // Transaction types
+    NON_INVITE_CLIENT: 'nict',
+    NON_INVITE_SERVER: 'nist',
+    INVITE_CLIENT: 'ict',
+    INVITE_SERVER: 'ist'
+  };
+
+function buildViaHeader (request_sender, transport, id) {
+  var via;
+  via = 'SIP/2.0/' + (request_sender.ua.configuration.hackViaTcp ? 'TCP' : transport.server.scheme);
+  via += ' ' + request_sender.ua.configuration.viaHost + ';branch=' + id;
+  if (request_sender.ua.configuration.forceRport) {
+    via += ';rport';
+  }
+  return via;
+}
+
+/**
+* @augments SIP.Transactions
+* @class Non Invite Client Transaction
+* @param {SIP.RequestSender} request_sender
+* @param {SIP.OutgoingRequest} request
+* @param {SIP.Transport} transport
+*/
+var NonInviteClientTransaction = function(request_sender, request, transport) {
+  var via;
+
+  this.type = C.NON_INVITE_CLIENT;
+  this.transport = transport;
+  this.id = 'z9hG4bK' + Math.floor(Math.random() * 10000000);
+  this.request_sender = request_sender;
+  this.request = request;
+
+  this.logger = request_sender.ua.getLogger('sip.transaction.nict', this.id);
+
+  via = buildViaHeader(request_sender, transport, this.id);
+  this.request.setHeader('via', via);
+
+  this.request_sender.ua.newTransaction(this);
+};
+NonInviteClientTransaction.prototype = Object.create(SIP.EventEmitter.prototype);
+
+NonInviteClientTransaction.prototype.stateChanged = function(state) {
+  this.state = state;
+  this.emit('stateChanged');
+};
+
+NonInviteClientTransaction.prototype.send = function() {
+  var tr = this;
+
+  this.stateChanged(C.STATUS_TRYING);
+  this.F = SIP.Timers.setTimeout(tr.timer_F.bind(tr), SIP.Timers.TIMER_F);
+
+  if(!this.transport.send(this.request)) {
+    this.onTransportError();
+  }
+};
+
+NonInviteClientTransaction.prototype.onTransportError = function() {
+  this.logger.log('transport error occurred, deleting non-INVITE client transaction ' + this.id);
+  SIP.Timers.clearTimeout(this.F);
+  SIP.Timers.clearTimeout(this.K);
+  this.stateChanged(C.STATUS_TERMINATED);
+  this.request_sender.ua.destroyTransaction(this);
+  this.request_sender.onTransportError();
+};
+
+NonInviteClientTransaction.prototype.timer_F = function() {
+  this.logger.log('Timer F expired for non-INVITE client transaction ' + this.id);
+  this.stateChanged(C.STATUS_TERMINATED);
+  this.request_sender.ua.destroyTransaction(this);
+  this.request_sender.onRequestTimeout();
+};
+
+NonInviteClientTransaction.prototype.timer_K = function() {
+  this.stateChanged(C.STATUS_TERMINATED);
+  this.request_sender.ua.destroyTransaction(this);
+};
+
+NonInviteClientTransaction.prototype.receiveResponse = function(response) {
+  var
+    tr = this,
+    status_code = response.status_code;
+
+  if(status_code < 200) {
+    switch(this.state) {
+      case C.STATUS_TRYING:
+      case C.STATUS_PROCEEDING:
+        this.stateChanged(C.STATUS_PROCEEDING);
+        this.request_sender.receiveResponse(response);
+        break;
+    }
+  } else {
+    switch(this.state) {
+      case C.STATUS_TRYING:
+      case C.STATUS_PROCEEDING:
+        this.stateChanged(C.STATUS_COMPLETED);
+        SIP.Timers.clearTimeout(this.F);
+
+        if(status_code === 408) {
+          this.request_sender.onRequestTimeout();
+        } else {
+          this.request_sender.receiveResponse(response);
+        }
+
+        this.K = SIP.Timers.setTimeout(tr.timer_K.bind(tr), SIP.Timers.TIMER_K);
+        break;
+      case C.STATUS_COMPLETED:
+        break;
+    }
+  }
+};
+
+
+
+/**
+* @augments SIP.Transactions
+* @class Invite Client Transaction
+* @param {SIP.RequestSender} request_sender
+* @param {SIP.OutgoingRequest} request
+* @param {SIP.Transport} transport
+*/
+var InviteClientTransaction = function(request_sender, request, transport) {
+  var via,
+    tr = this;
+
+  this.type = C.INVITE_CLIENT;
+  this.transport = transport;
+  this.id = 'z9hG4bK' + Math.floor(Math.random() * 10000000);
+  this.request_sender = request_sender;
+  this.request = request;
+
+  this.logger = request_sender.ua.getLogger('sip.transaction.ict', this.id);
+
+  via = buildViaHeader(request_sender, transport, this.id);
+  this.request.setHeader('via', via);
+
+  this.request_sender.ua.newTransaction(this);
+
+  // Add the cancel property to the request.
+  //Will be called from the request instance, not the transaction itself.
+  this.request.cancel = function(reason) {
+    tr.cancel_request(tr, reason);
+  };
+};
+InviteClientTransaction.prototype = Object.create(SIP.EventEmitter.prototype);
+
+InviteClientTransaction.prototype.stateChanged = function(state) {
+  this.state = state;
+  this.emit('stateChanged');
+};
+
+InviteClientTransaction.prototype.send = function() {
+  var tr = this;
+  this.stateChanged(C.STATUS_CALLING);
+  this.B = SIP.Timers.setTimeout(tr.timer_B.bind(tr), SIP.Timers.TIMER_B);
+
+  if(!this.transport.send(this.request)) {
+    this.onTransportError();
+  }
+};
+
+InviteClientTransaction.prototype.onTransportError = function() {
+  this.logger.log('transport error occurred, deleting INVITE client transaction ' + this.id);
+  SIP.Timers.clearTimeout(this.B);
+  SIP.Timers.clearTimeout(this.D);
+  SIP.Timers.clearTimeout(this.M);
+  this.stateChanged(C.STATUS_TERMINATED);
+  this.request_sender.ua.destroyTransaction(this);
+
+  if (this.state !== C.STATUS_ACCEPTED) {
+    this.request_sender.onTransportError();
+  }
+};
+
+// RFC 6026 7.2
+InviteClientTransaction.prototype.timer_M = function() {
+  this.logger.log('Timer M expired for INVITE client transaction ' + this.id);
+
+  if(this.state === C.STATUS_ACCEPTED) {
+    SIP.Timers.clearTimeout(this.B);
+    this.stateChanged(C.STATUS_TERMINATED);
+    this.request_sender.ua.destroyTransaction(this);
+  }
+};
+
+// RFC 3261 17.1.1
+InviteClientTransaction.prototype.timer_B = function() {
+  this.logger.log('Timer B expired for INVITE client transaction ' + this.id);
+  if(this.state === C.STATUS_CALLING) {
+    this.stateChanged(C.STATUS_TERMINATED);
+    this.request_sender.ua.destroyTransaction(this);
+    this.request_sender.onRequestTimeout();
+  }
+};
+
+InviteClientTransaction.prototype.timer_D = function() {
+  this.logger.log('Timer D expired for INVITE client transaction ' + this.id);
+  SIP.Timers.clearTimeout(this.B);
+  this.stateChanged(C.STATUS_TERMINATED);
+  this.request_sender.ua.destroyTransaction(this);
+};
+
+InviteClientTransaction.prototype.sendACK = function(response) {
+  var tr = this;
+
+  this.ack = 'ACK ' + this.request.ruri + ' SIP/2.0\r\n';
+  this.ack += 'Via: ' + this.request.headers['Via'].toString() + '\r\n';
+
+  if(this.request.headers['Route']) {
+    this.ack += 'Route: ' + this.request.headers['Route'].toString() + '\r\n';
+  }
+
+  this.ack += 'To: ' + response.getHeader('to') + '\r\n';
+  this.ack += 'From: ' + this.request.headers['From'].toString() + '\r\n';
+  this.ack += 'Call-ID: ' + this.request.headers['Call-ID'].toString() + '\r\n';
+  this.ack += 'Content-Length: 0\r\n';
+  this.ack += 'CSeq: ' + this.request.headers['CSeq'].toString().split(' ')[0];
+  this.ack += ' ACK\r\n\r\n';
+
+  this.D = SIP.Timers.setTimeout(tr.timer_D.bind(tr), SIP.Timers.TIMER_D);
+
+  this.transport.send(this.ack);
+};
+
+InviteClientTransaction.prototype.cancel_request = function(tr, reason) {
+  var request = tr.request;
+
+  this.cancel = SIP.C.CANCEL + ' ' + request.ruri + ' SIP/2.0\r\n';
+  this.cancel += 'Via: ' + request.headers['Via'].toString() + '\r\n';
+
+  if(this.request.headers['Route']) {
+    this.cancel += 'Route: ' + request.headers['Route'].toString() + '\r\n';
+  }
+
+  this.cancel += 'To: ' + request.headers['To'].toString() + '\r\n';
+  this.cancel += 'From: ' + request.headers['From'].toString() + '\r\n';
+  this.cancel += 'Call-ID: ' + request.headers['Call-ID'].toString() + '\r\n';
+  this.cancel += 'CSeq: ' + request.headers['CSeq'].toString().split(' ')[0] +
+  ' CANCEL\r\n';
+
+  if(reason) {
+    this.cancel += 'Reason: ' + reason + '\r\n';
+  }
+
+  this.cancel += 'Content-Length: 0\r\n\r\n';
+
+  // Send only if a provisional response (>100) has been received.
+  if(this.state === C.STATUS_PROCEEDING) {
+    this.transport.send(this.cancel);
+  }
+};
+
+InviteClientTransaction.prototype.receiveResponse = function(response) {
+  var
+  tr = this,
+  status_code = response.status_code;
+
+  if(status_code >= 100 && status_code <= 199) {
+    switch(this.state) {
+      case C.STATUS_CALLING:
+        this.stateChanged(C.STATUS_PROCEEDING);
+        this.request_sender.receiveResponse(response);
+        if(this.cancel) {
+          this.transport.send(this.cancel);
+        }
+        break;
+      case C.STATUS_PROCEEDING:
+        this.request_sender.receiveResponse(response);
+        break;
+    }
+  } else if(status_code >= 200 && status_code <= 299) {
+    switch(this.state) {
+      case C.STATUS_CALLING:
+      case C.STATUS_PROCEEDING:
+        this.stateChanged(C.STATUS_ACCEPTED);
+        this.M = SIP.Timers.setTimeout(tr.timer_M.bind(tr), SIP.Timers.TIMER_M);
+        this.request_sender.receiveResponse(response);
+        break;
+      case C.STATUS_ACCEPTED:
+        this.request_sender.receiveResponse(response);
+        break;
+    }
+  } else if(status_code >= 300 && status_code <= 699) {
+    switch(this.state) {
+      case C.STATUS_CALLING:
+      case C.STATUS_PROCEEDING:
+        this.stateChanged(C.STATUS_COMPLETED);
+        this.sendACK(response);
+        this.request_sender.receiveResponse(response);
+        break;
+      case C.STATUS_COMPLETED:
+        this.sendACK(response);
+        break;
+    }
+  }
+};
+
+
+/**
+ * @augments SIP.Transactions
+ * @class ACK Client Transaction
+ * @param {SIP.RequestSender} request_sender
+ * @param {SIP.OutgoingRequest} request
+ * @param {SIP.Transport} transport
+ */
+var AckClientTransaction = function(request_sender, request, transport) {
+  var via;
+
+  this.transport = transport;
+  this.id = 'z9hG4bK' + Math.floor(Math.random() * 10000000);
+  this.request_sender = request_sender;
+  this.request = request;
+
+  this.logger = request_sender.ua.getLogger('sip.transaction.nict', this.id);
+
+  via = buildViaHeader(request_sender, transport, this.id);
+  this.request.setHeader('via', via);
+};
+AckClientTransaction.prototype = Object.create(SIP.EventEmitter.prototype);
+
+AckClientTransaction.prototype.send = function() {
+  if(!this.transport.send(this.request)) {
+    this.onTransportError();
+  }
+};
+
+AckClientTransaction.prototype.onTransportError = function() {
+  this.logger.log('transport error occurred, for an ACK client transaction ' + this.id);
+  this.request_sender.onTransportError();
+};
+
+
+/**
+* @augments SIP.Transactions
+* @class Non Invite Server Transaction
+* @param {SIP.IncomingRequest} request
+* @param {SIP.UA} ua
+*/
+var NonInviteServerTransaction = function(request, ua) {
+  this.type = C.NON_INVITE_SERVER;
+  this.id = request.via_branch;
+  this.request = request;
+  this.transport = request.transport;
+  this.ua = ua;
+  this.last_response = '';
+  request.server_transaction = this;
+
+  this.logger = ua.getLogger('sip.transaction.nist', this.id);
+
+  this.state = C.STATUS_TRYING;
+
+  ua.newTransaction(this);
+};
+NonInviteServerTransaction.prototype = Object.create(SIP.EventEmitter.prototype);
+
+NonInviteServerTransaction.prototype.stateChanged = function(state) {
+  this.state = state;
+  this.emit('stateChanged');
+};
+
+NonInviteServerTransaction.prototype.timer_J = function() {
+  this.logger.log('Timer J expired for non-INVITE server transaction ' + this.id);
+  this.stateChanged(C.STATUS_TERMINATED);
+  this.ua.destroyTransaction(this);
+};
+
+NonInviteServerTransaction.prototype.onTransportError = function() {
+  if (!this.transportError) {
+    this.transportError = true;
+
+    this.logger.log('transport error occurred, deleting non-INVITE server transaction ' + this.id);
+
+    SIP.Timers.clearTimeout(this.J);
+    this.stateChanged(C.STATUS_TERMINATED);
+    this.ua.destroyTransaction(this);
+  }
+};
+
+NonInviteServerTransaction.prototype.receiveResponse = function(status_code, response) {
+  var tr = this;
+  var deferred = SIP.Utils.defer();
+
+  if(status_code === 100) {
+    /* RFC 4320 4.1
+     * 'A SIP element MUST NOT
+     * send any provisional response with a
+     * Status-Code other than 100 to a non-INVITE request.'
+     */
+    switch(this.state) {
+      case C.STATUS_TRYING:
+        this.stateChanged(C.STATUS_PROCEEDING);
+        if(!this.transport.send(response))  {
+          this.onTransportError();
+        }
+        break;
+      case C.STATUS_PROCEEDING:
+        this.last_response = response;
+        if(!this.transport.send(response)) {
+          this.onTransportError();
+          deferred.reject();
+        } else {
+          deferred.resolve();
+        }
+        break;
+    }
+  } else if(status_code >= 200 && status_code <= 699) {
+    switch(this.state) {
+      case C.STATUS_TRYING:
+      case C.STATUS_PROCEEDING:
+        this.stateChanged(C.STATUS_COMPLETED);
+        this.last_response = response;
+        this.J = SIP.Timers.setTimeout(tr.timer_J.bind(tr), SIP.Timers.TIMER_J);
+        if(!this.transport.send(response)) {
+          this.onTransportError();
+          deferred.reject();
+        } else {
+          deferred.resolve();
+        }
+        break;
+      case C.STATUS_COMPLETED:
+        break;
+    }
+  }
+
+  return deferred.promise;
+};
+
+/**
+* @augments SIP.Transactions
+* @class Invite Server Transaction
+* @param {SIP.IncomingRequest} request
+* @param {SIP.UA} ua
+*/
+var InviteServerTransaction = function(request, ua) {
+  this.type = C.INVITE_SERVER;
+  this.id = request.via_branch;
+  this.request = request;
+  this.transport = request.transport;
+  this.ua = ua;
+  this.last_response = '';
+  request.server_transaction = this;
+
+  this.logger = ua.getLogger('sip.transaction.ist', this.id);
+
+  this.state = C.STATUS_PROCEEDING;
+
+  ua.newTransaction(this);
+
+  this.resendProvisionalTimer = null;
+
+  request.reply(100);
+};
+InviteServerTransaction.prototype = Object.create(SIP.EventEmitter.prototype);
+
+InviteServerTransaction.prototype.stateChanged = function(state) {
+  this.state = state;
+  this.emit('stateChanged');
+};
+
+InviteServerTransaction.prototype.timer_H = function() {
+  this.logger.log('Timer H expired for INVITE server transaction ' + this.id);
+
+  if(this.state === C.STATUS_COMPLETED) {
+    this.logger.warn('transactions', 'ACK for INVITE server transaction was never received, call will be terminated');
+  }
+
+  this.stateChanged(C.STATUS_TERMINATED);
+  this.ua.destroyTransaction(this);
+};
+
+InviteServerTransaction.prototype.timer_I = function() {
+  this.stateChanged(C.STATUS_TERMINATED);
+  this.ua.destroyTransaction(this);
+};
+
+// RFC 6026 7.1
+InviteServerTransaction.prototype.timer_L = function() {
+  this.logger.log('Timer L expired for INVITE server transaction ' + this.id);
+
+  if(this.state === C.STATUS_ACCEPTED) {
+    this.stateChanged(C.STATUS_TERMINATED);
+    this.ua.destroyTransaction(this);
+  }
+};
+
+InviteServerTransaction.prototype.onTransportError = function() {
+  if (!this.transportError) {
+    this.transportError = true;
+
+    this.logger.log('transport error occurred, deleting INVITE server transaction ' + this.id);
+
+    if (this.resendProvisionalTimer !== null) {
+      SIP.Timers.clearInterval(this.resendProvisionalTimer);
+      this.resendProvisionalTimer = null;
+    }
+
+    SIP.Timers.clearTimeout(this.L);
+    SIP.Timers.clearTimeout(this.H);
+    SIP.Timers.clearTimeout(this.I);
+
+    this.stateChanged(C.STATUS_TERMINATED);
+    this.ua.destroyTransaction(this);
+  }
+};
+
+InviteServerTransaction.prototype.resend_provisional = function() {
+  if(!this.transport.send(this.last_response)) {
+    this.onTransportError();
+  }
+};
+
+// INVITE Server Transaction RFC 3261 17.2.1
+InviteServerTransaction.prototype.receiveResponse = function(status_code, response) {
+  var tr = this;
+  var deferred = SIP.Utils.defer();
+
+  if(status_code >= 100 && status_code <= 199) {
+    switch(this.state) {
+      case C.STATUS_PROCEEDING:
+        if(!this.transport.send(response)) {
+          this.onTransportError();
+        }
+        this.last_response = response;
+        break;
+    }
+  }
+
+  if(status_code > 100 && status_code <= 199 && this.state === C.STATUS_PROCEEDING) {
+    // Trigger the resendProvisionalTimer only for the first non 100 provisional response.
+    if(this.resendProvisionalTimer === null) {
+      this.resendProvisionalTimer = SIP.Timers.setInterval(tr.resend_provisional.bind(tr),
+        SIP.Timers.PROVISIONAL_RESPONSE_INTERVAL);
+    }
+  } else if(status_code >= 200 && status_code <= 299) {
+    switch(this.state) {
+      case C.STATUS_PROCEEDING:
+        this.stateChanged(C.STATUS_ACCEPTED);
+        this.last_response = response;
+        this.L = SIP.Timers.setTimeout(tr.timer_L.bind(tr), SIP.Timers.TIMER_L);
+
+        if (this.resendProvisionalTimer !== null) {
+          SIP.Timers.clearInterval(this.resendProvisionalTimer);
+          this.resendProvisionalTimer = null;
+        }
+        /* falls through */
+        case C.STATUS_ACCEPTED:
+          // Note that this point will be reached for proceeding tr.state also.
+          if(!this.transport.send(response)) {
+            this.onTransportError();
+            deferred.reject();
+          } else {
+            deferred.resolve();
+          }
+          break;
+    }
+  } else if(status_code >= 300 && status_code <= 699) {
+    switch(this.state) {
+      case C.STATUS_PROCEEDING:
+        if (this.resendProvisionalTimer !== null) {
+          SIP.Timers.clearInterval(this.resendProvisionalTimer);
+          this.resendProvisionalTimer = null;
+        }
+
+        if(!this.transport.send(response)) {
+          this.onTransportError();
+          deferred.reject();
+        } else {
+          this.stateChanged(C.STATUS_COMPLETED);
+          this.H = SIP.Timers.setTimeout(tr.timer_H.bind(tr), SIP.Timers.TIMER_H);
+          deferred.resolve();
+        }
+        break;
+    }
+  }
+
+  return deferred.promise;
+};
+
+/**
+ * @function
+ * @param {SIP.UA} ua
+ * @param {SIP.IncomingRequest} request
+ *
+ * @return {boolean}
+ * INVITE:
+ *  _true_ if retransmission
+ *  _false_ new request
+ *
+ * ACK:
+ *  _true_  ACK to non2xx response
+ *  _false_ ACK must be passed to TU (accepted state)
+ *          ACK to 2xx response
+ *
+ * CANCEL:
+ *  _true_  no matching invite transaction
+ *  _false_ matching invite transaction and no final response sent
+ *
+ * OTHER:
+ *  _true_  retransmission
+ *  _false_ new request
+ */
+var checkTransaction = function(ua, request) {
+  var tr;
+
+  switch(request.method) {
+    case SIP.C.INVITE:
+      tr = ua.transactions.ist[request.via_branch];
+      if(tr) {
+        switch(tr.state) {
+          case C.STATUS_PROCEEDING:
+            tr.transport.send(tr.last_response);
+            break;
+
+            // RFC 6026 7.1 Invite retransmission
+            //received while in C.STATUS_ACCEPTED state. Absorb it.
+          case C.STATUS_ACCEPTED:
+            break;
+        }
+        return true;
+      }
+      break;
+    case SIP.C.ACK:
+      tr = ua.transactions.ist[request.via_branch];
+
+      // RFC 6026 7.1
+      if(tr) {
+        if(tr.state === C.STATUS_ACCEPTED) {
+          return false;
+        } else if(tr.state === C.STATUS_COMPLETED) {
+          tr.state = C.STATUS_CONFIRMED;
+          tr.I = SIP.Timers.setTimeout(tr.timer_I.bind(tr), SIP.Timers.TIMER_I);
+          return true;
+        }
+      }
+
+      // ACK to 2XX Response.
+      else {
+        return false;
+      }
+      break;
+    case SIP.C.CANCEL:
+      tr = ua.transactions.ist[request.via_branch];
+      if(tr) {
+        request.reply_sl(200);
+        if(tr.state === C.STATUS_PROCEEDING) {
+          return false;
+        } else {
+          return true;
+        }
+      } else {
+        request.reply_sl(481);
+        return true;
+      }
+      break;
+    default:
+
+      // Non-INVITE Server Transaction RFC 3261 17.2.2
+      tr = ua.transactions.nist[request.via_branch];
+      if(tr) {
+        switch(tr.state) {
+          case C.STATUS_TRYING:
+            break;
+          case C.STATUS_PROCEEDING:
+          case C.STATUS_COMPLETED:
+            tr.transport.send(tr.last_response);
+            break;
+        }
+        return true;
+      }
+      break;
+  }
+};
+
+SIP.Transactions = {
+  C: C,
+  checkTransaction: checkTransaction,
+  NonInviteClientTransaction: NonInviteClientTransaction,
+  InviteClientTransaction: InviteClientTransaction,
+  AckClientTransaction: AckClientTransaction,
+  NonInviteServerTransaction: NonInviteServerTransaction,
+  InviteServerTransaction: InviteServerTransaction
+};
+
+};
+
+},{}],28:[function(require,module,exports){
+"use strict";
+/**
+ * @fileoverview Transport
+ */
+
+/**
+ * @augments SIP
+ * @class Transport
+ * @param {SIP.UA} ua
+ * @param {Object} server ws_server Object
+ */
+module.exports = function (SIP, WebSocket) {
+var Transport,
+  C = {
+    // Transport status codes
+    STATUS_READY:        0,
+    STATUS_DISCONNECTED: 1,
+    STATUS_ERROR:        2
+  };
+
+/**
+ * Compute an amount of time in seconds to wait before sending another
+ * keep-alive.
+ * @returns {Number}
+ */
+function computeKeepAliveTimeout(upperBound) {
+  var lowerBound = upperBound * 0.8;
+  return 1000 * (Math.random() * (upperBound - lowerBound) + lowerBound);
+}
+
+Transport = function(ua, server) {
+
+  this.logger = ua.getLogger('sip.transport');
+  this.ua = ua;
+  this.ws = null;
+  this.server = server;
+  this.reconnection_attempts = 0;
+  this.closed = false;
+  this.connected = false;
+  this.reconnectTimer = null;
+  this.lastTransportError = {};
+
+  this.keepAliveInterval = ua.configuration.keepAliveInterval;
+  this.keepAliveTimeout = null;
+  this.keepAliveTimer = null;
+
+  this.ua.transport = this;
+
+  // Connect
+  this.connect();
+};
+
+Transport.prototype = {
+  /**
+   * Send a message.
+   * @param {SIP.OutgoingRequest|String} msg
+   * @returns {Boolean}
+   */
+  send: function(msg) {
+    var message = msg.toString();
+
+    if(this.ws && this.ws.readyState === WebSocket.OPEN) {
+      if (this.ua.configuration.traceSip === true) {
+        this.logger.log('sending WebSocket message:\n\n' + message + '\n');
+      }
+      this.ws.send(message);
+      return true;
+    } else {
+      this.logger.warn('unable to send message, WebSocket is not open');
+      return false;
+    }
+  },
+
+  /**
+   * Send a keep-alive (a double-CRLF sequence).
+   * @private
+   * @returns {Boolean}
+   */
+  sendKeepAlive: function() {
+    if(this.keepAliveTimeout) { return; }
+
+    this.keepAliveTimeout = SIP.Timers.setTimeout(function() {
+      this.ua.emit('keepAliveTimeout');
+    }.bind(this), 10000);
+
+    return this.send('\r\n\r\n');
+  },
+
+  /**
+   * Start sending keep-alives.
+   * @private
+   */
+  startSendingKeepAlives: function() {
+    if (this.keepAliveInterval && !this.keepAliveTimer) {
+      this.keepAliveTimer = SIP.Timers.setTimeout(function() {
+        this.sendKeepAlive();
+        this.keepAliveTimer = null;
+        this.startSendingKeepAlives();
+      }.bind(this), computeKeepAliveTimeout(this.keepAliveInterval));
+    }
+  },
+
+  /**
+   * Stop sending keep-alives.
+   * @private
+   */
+  stopSendingKeepAlives: function() {
+    SIP.Timers.clearTimeout(this.keepAliveTimer);
+    SIP.Timers.clearTimeout(this.keepAliveTimeout);
+    this.keepAliveTimer = null;
+    this.keepAliveTimeout = null;
+  },
+
+  /**
+  * Disconnect socket.
+  */
+  disconnect: function() {
+    if(this.ws) {
+      // Clear reconnectTimer
+      SIP.Timers.clearTimeout(this.reconnectTimer);
+
+      this.stopSendingKeepAlives();
+
+      this.closed = true;
+      this.logger.log('closing WebSocket ' + this.server.ws_uri);
+      this.ws.close();
+    }
+
+    if (this.reconnectTimer !== null) {
+      SIP.Timers.clearTimeout(this.reconnectTimer);
+      this.reconnectTimer = null;
+      this.ua.emit('disconnected', {
+        transport: this,
+        code: this.lastTransportError.code,
+        reason: this.lastTransportError.reason
+      });
+    }
+  },
+
+  /**
+  * Connect socket.
+  */
+  connect: function() {
+    var transport = this;
+
+    if(this.ws && (this.ws.readyState === WebSocket.OPEN || this.ws.readyState === WebSocket.CONNECTING)) {
+      this.logger.log('WebSocket ' + this.server.ws_uri + ' is already connected');
+      return false;
+    }
+
+    if(this.ws) {
+      this.ws.close();
+    }
+
+    this.logger.log('connecting to WebSocket ' + this.server.ws_uri);
+    this.ua.onTransportConnecting(this,
+      (this.reconnection_attempts === 0)?1:this.reconnection_attempts);
+
+    try {
+      this.ws = new WebSocket(this.server.ws_uri, 'sip');
+    } catch(e) {
+      this.logger.warn('error connecting to WebSocket ' + this.server.ws_uri + ': ' + e);
+    }
+
+    this.ws.binaryType = 'arraybuffer';
+
+    this.ws.onopen = function() {
+      transport.onOpen();
+    };
+
+    this.ws.onclose = function(e) {
+      transport.onClose(e);
+    };
+
+    this.ws.onmessage = function(e) {
+      transport.onMessage(e);
+    };
+
+    this.ws.onerror = function(e) {
+      transport.onError(e);
+    };
+  },
+
+  // Transport Event Handlers
+
+  /**
+  * @event
+  * @param {event} e
+  */
+  onOpen: function() {
+    this.connected = true;
+
+    this.logger.log('WebSocket ' + this.server.ws_uri + ' connected');
+    // Clear reconnectTimer since we are not disconnected
+    if (this.reconnectTimer !== null) {
+      SIP.Timers.clearTimeout(this.reconnectTimer);
+      this.reconnectTimer = null;
+    }
+    // Reset reconnection_attempts
+    this.reconnection_attempts = 0;
+    // Disable closed
+    this.closed = false;
+    // Trigger onTransportConnected callback
+    this.ua.onTransportConnected(this);
+    // Start sending keep-alives
+    this.startSendingKeepAlives();
+  },
+
+  /**
+  * @event
+  * @param {event} e
+  */
+  onClose: function(e) {
+    var connected_before = this.connected;
+
+    this.lastTransportError.code = e.code;
+    this.lastTransportError.reason = e.reason;
+
+    this.stopSendingKeepAlives();
+
+    if (this.reconnection_attempts > 0) {
+      this.logger.log('Reconnection attempt ' + this.reconnection_attempts + ' failed (code: ' + e.code + (e.reason? '| reason: ' + e.reason : '') +')');
+      this.reconnect();
+    } else {
+      this.connected = false;
+      this.logger.log('WebSocket disconnected (code: ' + e.code + (e.reason? '| reason: ' + e.reason : '') +')');
+
+      if(e.wasClean === false) {
+        this.logger.warn('WebSocket abrupt disconnection');
+      }
+      // Transport was connected
+      if(connected_before === true) {
+        this.ua.onTransportClosed(this);
+        // Check whether the user requested to close.
+        if(!this.closed) {
+          this.reconnect();
+        } else {
+          this.ua.emit('disconnected', {
+            transport: this,
+            code: this.lastTransportError.code,
+            reason: this.lastTransportError.reason
+          });
+
+        }
+      } else {
+        // This is the first connection attempt
+        //Network error
+        this.ua.onTransportError(this);
+      }
+    }
+  },
+
+  /**
+  * @event
+  * @param {event} e
+  */
+  onMessage: function(e) {
+    var message, transaction,
+      data = e.data;
+
+    // CRLF Keep Alive response from server. Ignore it.
+    if(data === '\r\n') {
+      SIP.Timers.clearTimeout(this.keepAliveTimeout);
+      this.keepAliveTimeout = null;
+
+      if (this.ua.configuration.traceSip === true) {
+        this.logger.log('received WebSocket message with CRLF Keep Alive response');
+      }
+
+      return;
+    }
+
+    // WebSocket binary message.
+    else if (typeof data !== 'string') {
+      try {
+        data = String.fromCharCode.apply(null, new Uint8Array(data));
+      } catch(evt) {
+        this.logger.warn('received WebSocket binary message failed to be converted into string, message discarded');
+        return;
+      }
+
+      if (this.ua.configuration.traceSip === true) {
+        this.logger.log('received WebSocket binary message:\n\n' + data + '\n');
+      }
+    }
+
+    // WebSocket text message.
+    else {
+      if (this.ua.configuration.traceSip === true) {
+        this.logger.log('received WebSocket text message:\n\n' + data + '\n');
+      }
+    }
+
+    message = SIP.Parser.parseMessage(data, this.ua);
+
+    if (!message) {
+      return;
+    }
+
+    if(this.ua.status === SIP.UA.C.STATUS_USER_CLOSED && message instanceof SIP.IncomingRequest) {
+      return;
+    }
+
+    // Do some sanity check
+    if(SIP.sanityCheck(message, this.ua, this)) {
+      if(message instanceof SIP.IncomingRequest) {
+        message.transport = this;
+        this.ua.receiveRequest(message);
+      } else if(message instanceof SIP.IncomingResponse) {
+        /* Unike stated in 18.1.2, if a response does not match
+        * any transaction, it is discarded here and no passed to the core
+        * in order to be discarded there.
+        */
+        switch(message.method) {
+          case SIP.C.INVITE:
+            transaction = this.ua.transactions.ict[message.via_branch];
+            if(transaction) {
+              transaction.receiveResponse(message);
+            }
+            break;
+          case SIP.C.ACK:
+            // Just in case ;-)
+            break;
+          default:
+            transaction = this.ua.transactions.nict[message.via_branch];
+            if(transaction) {
+              transaction.receiveResponse(message);
+            }
+            break;
+        }
+      }
+    }
+  },
+
+  /**
+  * @event
+  * @param {event} e
+  */
+  onError: function(e) {
+    this.logger.warn('WebSocket connection error: ' + JSON.stringify(e));
+  },
+
+  /**
+  * Reconnection attempt logic.
+  * @private
+  */
+  reconnect: function() {
+    var transport = this;
+
+    this.reconnection_attempts += 1;
+
+    if(this.reconnection_attempts > this.ua.configuration.wsServerMaxReconnection) {
+      this.logger.warn('maximum reconnection attempts for WebSocket ' + this.server.ws_uri);
+      this.ua.onTransportError(this);
+    } else if (this.reconnection_attempts === 1) {
+      this.logger.log('Connection to WebSocket ' + this.server.ws_uri + ' severed, attempting first reconnect');
+      transport.connect();
+    } else {
+      this.logger.log('trying to reconnect to WebSocket ' + this.server.ws_uri + ' (reconnection attempt ' + this.reconnection_attempts + ')');
+
+      this.reconnectTimer = SIP.Timers.setTimeout(function() {
+        transport.connect();
+        transport.reconnectTimer = null;
+      }, this.ua.configuration.wsServerReconnectionTimeout * 1000);
+    }
+  }
+};
+
+Transport.C = C;
+return Transport;
+};
+
+},{}],29:[function(require,module,exports){
+(function (global){
+"use strict";
+/**
+ * @augments SIP
+ * @class Class creating a SIP User Agent.
+ * @param {function returning SIP.MediaHandler} [configuration.mediaHandlerFactory]
+ *        A function will be invoked by each of the UA's Sessions to build the MediaHandler for that Session.
+ *        If no (or a falsy) value is provided, each Session will use a default (WebRTC) MediaHandler.
+ *
+ * @param {Object} [configuration.media] gets passed to SIP.MediaHandler.getDescription as mediaHint
+ */
+module.exports = function (SIP, environment) {
+var UA,
+  C = {
+    // UA status codes
+    STATUS_INIT:                0,
+    STATUS_STARTING:            1,
+    STATUS_READY:               2,
+    STATUS_USER_CLOSED:         3,
+    STATUS_NOT_READY:           4,
+
+    // UA error codes
+    CONFIGURATION_ERROR:  1,
+    NETWORK_ERROR:        2,
+
+    ALLOWED_METHODS: [
+      'ACK',
+      'CANCEL',
+      'INVITE',
+      'MESSAGE',
+      'BYE',
+      'OPTIONS',
+      'INFO',
+      'NOTIFY',
+      'REFER'
+    ],
+
+    ACCEPTED_BODY_TYPES: [
+      'application/sdp',
+      'application/dtmf-relay'
+    ],
+
+    MAX_FORWARDS: 70,
+    TAG_LENGTH: 10
+  };
+
+UA = function(configuration) {
+  var self = this;
+
+  // Helper function for forwarding events
+  function selfEmit(type) {
+    //registrationFailed handler is invoked with two arguments. Allow event handlers to be invoked with a variable no. of arguments
+    return self.emit.bind(self, type);
+  }
+
+  // Set Accepted Body Types
+  C.ACCEPTED_BODY_TYPES = C.ACCEPTED_BODY_TYPES.toString();
+
+  this.log = new SIP.LoggerFactory();
+  this.logger = this.getLogger('sip.ua');
+
+  this.cache = {
+    credentials: {}
+  };
+
+  this.configuration = {};
+  this.dialogs = {};
+
+  //User actions outside any session/dialog (MESSAGE)
+  this.applicants = {};
+
+  this.data = {};
+  this.sessions = {};
+  this.subscriptions = {};
+  this.transport = null;
+  this.contact = null;
+  this.status = C.STATUS_INIT;
+  this.error = null;
+  this.transactions = {
+    nist: {},
+    nict: {},
+    ist: {},
+    ict: {}
+  };
+
+  this.transportRecoverAttempts = 0;
+  this.transportRecoveryTimer = null;
+
+  Object.defineProperties(this, {
+    transactionsCount: {
+      get: function() {
+        var type,
+          transactions = ['nist','nict','ist','ict'],
+          count = 0;
+
+        for (type in transactions) {
+          count += Object.keys(this.transactions[transactions[type]]).length;
+        }
+
+        return count;
+      }
+    },
+
+    nictTransactionsCount: {
+      get: function() {
+        return Object.keys(this.transactions['nict']).length;
+      }
+    },
+
+    nistTransactionsCount: {
+      get: function() {
+        return Object.keys(this.transactions['nist']).length;
+      }
+    },
+
+    ictTransactionsCount: {
+      get: function() {
+        return Object.keys(this.transactions['ict']).length;
+      }
+    },
+
+    istTransactionsCount: {
+      get: function() {
+        return Object.keys(this.transactions['ist']).length;
+      }
+    }
+  });
+
+  /**
+   * Load configuration
+   *
+   * @throws {SIP.Exceptions.ConfigurationError}
+   * @throws {TypeError}
+   */
+
+  if(configuration === undefined) {
+    configuration = {};
+  } else if (typeof configuration === 'string' || configuration instanceof String) {
+    configuration = {
+      uri: configuration
+    };
+  }
+
+  // Apply log configuration if present
+  if (configuration.log) {
+    if (configuration.log.hasOwnProperty('builtinEnabled')) {
+      this.log.builtinEnabled = configuration.log.builtinEnabled;
+    }
+
+    if (configuration.log.hasOwnProperty('level')) {
+      this.log.level = configuration.log.level;
+    }
+
+    if (configuration.log.hasOwnProperty('connector')) {
+      this.log.connector = configuration.log.connector;
+    }
+  }
+
+  try {
+    this.loadConfig(configuration);
+  } catch(e) {
+    this.status = C.STATUS_NOT_READY;
+    this.error = C.CONFIGURATION_ERROR;
+    throw e;
+  }
+
+  // Initialize registerContext
+  this.registerContext = new SIP.RegisterContext(this);
+  this.registerContext.on('failed', selfEmit('registrationFailed'));
+  this.registerContext.on('registered', selfEmit('registered'));
+  this.registerContext.on('unregistered', selfEmit('unregistered'));
+
+  if(this.configuration.autostart) {
+    this.start();
+  }
+
+  if (typeof environment.addEventListener === 'function') {
+    // Google Chrome Packaged Apps don't allow 'unload' listeners:
+    // unload is not available in packaged apps
+    if (!(global.chrome && global.chrome.app && global.chrome.app.runtime)) {
+      environment.addEventListener('unload', this.stop.bind(this));
+    }
+  }
+};
+UA.prototype = Object.create(SIP.EventEmitter.prototype);
+
+//=================
+//  High Level API
+//=================
+
+UA.prototype.register = function(options) {
+  this.configuration.register = true;
+  this.registerContext.register(options);
+
+  return this;
+};
+
+/**
+ * Unregister.
+ *
+ * @param {Boolean} [all] unregister all user bindings.
+ *
+ */
+UA.prototype.unregister = function(options) {
+  this.configuration.register = false;
+
+  var context = this.registerContext;
+  this.afterConnected(context.unregister.bind(context, options));
+
+  return this;
+};
+
+UA.prototype.isRegistered = function() {
+  return this.registerContext.registered;
+};
+
+/**
+ * Connection state.
+ * @param {Boolean}
+ */
+UA.prototype.isConnected = function() {
+  return this.transport ? this.transport.connected : false;
+};
+
+UA.prototype.afterConnected = function afterConnected (callback) {
+  if (this.isConnected()) {
+    callback();
+  } else {
+    this.once('connected', callback);
+  }
+};
+
+/**
+ * Make an outgoing call.
+ *
+ * @param {String} target
+ * @param {Object} views
+ * @param {Object} [options.media] gets passed to SIP.MediaHandler.getDescription as mediaHint
+ *
+ * @throws {TypeError}
+ *
+ */
+UA.prototype.invite = function(target, options) {
+  var context = new SIP.InviteClientContext(this, target, options);
+
+  this.afterConnected(context.invite.bind(context));
+  return context;
+};
+
+UA.prototype.subscribe = function(target, event, options) {
+  var sub = new SIP.Subscription(this, target, event, options);
+
+  this.afterConnected(sub.subscribe.bind(sub));
+  return sub;
+};
+
+/**
+ * Send a message.
+ *
+ * @param {String} target
+ * @param {String} body
+ * @param {Object} [options]
+ *
+ * @throws {TypeError}
+ *
+ */
+UA.prototype.message = function(target, body, options) {
+  if (body === undefined) {
+    throw new TypeError('Not enough arguments');
+  }
+
+  // There is no Message module, so it is okay that the UA handles defaults here.
+  options = Object.create(options || Object.prototype);
+  options.contentType || (options.contentType = 'text/plain');
+  options.body = body;
+
+  return this.request(SIP.C.MESSAGE, target, options);
+};
+
+UA.prototype.request = function (method, target, options) {
+  var req = new SIP.ClientContext(this, method, target, options);
+
+  this.afterConnected(req.send.bind(req));
+  return req;
+};
+
+/**
+ * Gracefully close.
+ *
+ */
+UA.prototype.stop = function() {
+  var session, subscription, applicant,
+    ua = this;
+
+  function transactionsListener() {
+    if (ua.nistTransactionsCount === 0 && ua.nictTransactionsCount === 0) {
+        ua.removeListener('transactionDestroyed', transactionsListener);
+        ua.transport.disconnect();
+    }
+  }
+
+  this.logger.log('user requested closure...');
+
+  if(this.status === C.STATUS_USER_CLOSED) {
+    this.logger.warn('UA already closed');
+    return this;
+  }
+
+  // Clear transportRecoveryTimer
+  SIP.Timers.clearTimeout(this.transportRecoveryTimer);
+
+  // Close registerContext
+  this.logger.log('closing registerContext');
+  this.registerContext.close();
+
+  // Run  _terminate_ on every Session
+  for(session in this.sessions) {
+    this.logger.log('closing session ' + session);
+    this.sessions[session].terminate();
+  }
+
+  //Run _close_ on every Subscription
+  for(subscription in this.subscriptions) {
+    this.logger.log('unsubscribing from subscription ' + subscription);
+    this.subscriptions[subscription].close();
+  }
+
+  // Run  _close_ on every applicant
+  for(applicant in this.applicants) {
+    this.applicants[applicant].close();
+  }
+
+  this.status = C.STATUS_USER_CLOSED;
+
+  /*
+   * If the remaining transactions are all INVITE transactions, there is no need to
+   * wait anymore because every session has already been closed by this method.
+   * - locally originated sessions where terminated (CANCEL or BYE)
+   * - remotely originated sessions where rejected (4XX) or terminated (BYE)
+   * Remaining INVITE transactions belong tho sessions that where answered. This are in
+   * 'accepted' state due to timers 'L' and 'M' defined in [RFC 6026]
+   */
+  if (this.nistTransactionsCount === 0 && this.nictTransactionsCount === 0) {
+    this.transport.disconnect();
+  } else {
+    this.on('transactionDestroyed', transactionsListener);
+  }
+
+  return this;
+};
+
+/**
+ * Connect to the WS server if status = STATUS_INIT.
+ * Resume UA after being closed.
+ *
+ */
+UA.prototype.start = function() {
+  var server;
+
+  this.logger.log('user requested startup...');
+  if (this.status === C.STATUS_INIT) {
+    server = this.getNextWsServer();
+    this.status = C.STATUS_STARTING;
+    new SIP.Transport(this, server);
+  } else if(this.status === C.STATUS_USER_CLOSED) {
+    this.logger.log('resuming');
+    this.status = C.STATUS_READY;
+    this.transport.connect();
+  } else if (this.status === C.STATUS_STARTING) {
+    this.logger.log('UA is in STARTING status, not opening new connection');
+  } else if (this.status === C.STATUS_READY) {
+    this.logger.log('UA is in READY status, not resuming');
+  } else {
+    this.logger.error('Connection is down. Auto-Recovery system is trying to connect');
+  }
+
+  return this;
+};
+
+/**
+ * Normalize a string into a valid SIP request URI
+ *
+ * @param {String} target
+ *
+ * @returns {SIP.URI|undefined}
+ */
+UA.prototype.normalizeTarget = function(target) {
+  return SIP.Utils.normalizeTarget(target, this.configuration.hostportParams);
+};
+
+
+//===============================
+//  Private (For internal use)
+//===============================
+
+UA.prototype.saveCredentials = function(credentials) {
+  this.cache.credentials[credentials.realm] = this.cache.credentials[credentials.realm] || {};
+  this.cache.credentials[credentials.realm][credentials.uri] = credentials;
+
+  return this;
+};
+
+UA.prototype.getCredentials = function(request) {
+  var realm, credentials;
+
+  realm = request.ruri.host;
+
+  if (this.cache.credentials[realm] && this.cache.credentials[realm][request.ruri]) {
+    credentials = this.cache.credentials[realm][request.ruri];
+    credentials.method = request.method;
+  }
+
+  return credentials;
+};
+
+UA.prototype.getLogger = function(category, label) {
+  return this.log.getLogger(category, label);
+};
+
+
+//==============================
+// Event Handlers
+//==============================
+
+/**
+ * Transport Close event
+ * @private
+ * @event
+ * @param {SIP.Transport} transport.
+ */
+UA.prototype.onTransportClosed = function(transport) {
+  // Run _onTransportError_ callback on every client transaction using _transport_
+  var type, idx, length,
+    client_transactions = ['nict', 'ict', 'nist', 'ist'];
+
+  transport.server.status = SIP.Transport.C.STATUS_DISCONNECTED;
+  this.logger.log('connection state set to '+ SIP.Transport.C.STATUS_DISCONNECTED);
+
+  length = client_transactions.length;
+  for (type = 0; type < length; type++) {
+    for(idx in this.transactions[client_transactions[type]]) {
+      this.transactions[client_transactions[type]][idx].onTransportError();
+    }
+  }
+
+  // Close sessions if GRUU is not being used
+  if (!this.contact.pub_gruu) {
+    this.closeSessionsOnTransportError();
+  }
+
+};
+
+/**
+ * Unrecoverable transport event.
+ * Connection reattempt logic has been done and didn't success.
+ * @private
+ * @event
+ * @param {SIP.Transport} transport.
+ */
+UA.prototype.onTransportError = function(transport) {
+  var server;
+
+  this.logger.log('transport ' + transport.server.ws_uri + ' failed | connection state set to '+ SIP.Transport.C.STATUS_ERROR);
+
+  // Close sessions.
+  //Mark this transport as 'down'
+  transport.server.status = SIP.Transport.C.STATUS_ERROR;
+
+  this.emit('disconnected', {
+    transport: transport
+  });
+
+  // try the next transport if the UA isn't closed
+  if(this.status === C.STATUS_USER_CLOSED) {
+    return;
+  }
+
+  server = this.getNextWsServer();
+
+  if(server) {
+    new SIP.Transport(this, server);
+  }else {
+    this.closeSessionsOnTransportError();
+    if (!this.error || this.error !== C.NETWORK_ERROR) {
+      this.status = C.STATUS_NOT_READY;
+      this.error = C.NETWORK_ERROR;
+    }
+    // Transport Recovery process
+    this.recoverTransport();
+  }
+};
+
+/**
+ * Transport connection event.
+ * @private
+ * @event
+ * @param {SIP.Transport} transport.
+ */
+UA.prototype.onTransportConnected = function(transport) {
+  this.transport = transport;
+
+  // Reset transport recovery counter
+  this.transportRecoverAttempts = 0;
+
+  transport.server.status = SIP.Transport.C.STATUS_READY;
+  this.logger.log('connection state set to '+ SIP.Transport.C.STATUS_READY);
+
+  if(this.status === C.STATUS_USER_CLOSED) {
+    return;
+  }
+
+  this.status = C.STATUS_READY;
+  this.error = null;
+
+  if(this.configuration.register) {
+    this.configuration.authenticationFactory.initialize().then(function () {
+      this.registerContext.onTransportConnected();
+    }.bind(this));
+  }
+
+  this.emit('connected', {
+    transport: transport
+  });
+};
+
+
+/**
+ * Transport connecting event
+ * @private
+ * @param {SIP.Transport} transport.
+ * #param {Integer} attempts.
+ */
+  UA.prototype.onTransportConnecting = function(transport, attempts) {
+    this.emit('connecting', {
+      transport: transport,
+      attempts: attempts
+    });
+  };
+
+
+/**
+ * new Transaction
+ * @private
+ * @param {SIP.Transaction} transaction.
+ */
+UA.prototype.newTransaction = function(transaction) {
+  this.transactions[transaction.type][transaction.id] = transaction;
+  this.emit('newTransaction', {transaction: transaction});
+};
+
+
+/**
+ * destroy Transaction
+ * @private
+ * @param {SIP.Transaction} transaction.
+ */
+UA.prototype.destroyTransaction = function(transaction) {
+  delete this.transactions[transaction.type][transaction.id];
+  this.emit('transactionDestroyed', {
+    transaction: transaction
+  });
+};
+
+
+//=========================
+// receiveRequest
+//=========================
+
+/**
+ * Request reception
+ * @private
+ * @param {SIP.IncomingRequest} request.
+ */
+UA.prototype.receiveRequest = function(request) {
+  var dialog, session, message,
+    method = request.method,
+    transaction,
+    replaces,
+    replacedDialog,
+    self = this;
+
+  function ruriMatches (uri) {
+    return uri && uri.user === request.ruri.user;
+  }
+
+  // Check that request URI points to us
+  if(!(ruriMatches(this.configuration.uri) ||
+       ruriMatches(this.contact.uri) ||
+       ruriMatches(this.contact.pub_gruu) ||
+       ruriMatches(this.contact.temp_gruu))) {
+    this.logger.warn('Request-URI does not point to us');
+    if (request.method !== SIP.C.ACK) {
+      request.reply_sl(404);
+    }
+    return;
+  }
+
+  // Check request URI scheme
+  if(request.ruri.scheme === SIP.C.SIPS) {
+    request.reply_sl(416);
+    return;
+  }
+
+  // Check transaction
+  if(SIP.Transactions.checkTransaction(this, request)) {
+    return;
+  }
+
+  /* RFC3261 12.2.2
+   * Requests that do not change in any way the state of a dialog may be
+   * received within a dialog (for example, an OPTIONS request).
+   * They are processed as if they had been received outside the dialog.
+   */
+  if(method === SIP.C.OPTIONS) {
+    new SIP.Transactions.NonInviteServerTransaction(request, this);
+    request.reply(200, null, [
+      'Allow: '+ SIP.UA.C.ALLOWED_METHODS.toString(),
+      'Accept: '+ C.ACCEPTED_BODY_TYPES
+    ]);
+  } else if (method === SIP.C.MESSAGE) {
+    message = new SIP.ServerContext(this, request);
+    message.body = request.body;
+    message.content_type = request.getHeader('Content-Type') || 'text/plain';
+
+    request.reply(200, null);
+    this.emit('message', message);
+  } else if (method !== SIP.C.INVITE &&
+             method !== SIP.C.ACK) {
+    // Let those methods pass through to normal processing for now.
+    transaction = new SIP.ServerContext(this, request);
+  }
+
+  // Initial Request
+  if(!request.to_tag) {
+    switch(method) {
+      case SIP.C.INVITE:
+        replaces =
+          this.configuration.replaces !== SIP.C.supported.UNSUPPORTED &&
+          request.parseHeader('replaces');
+
+        if (replaces) {
+          replacedDialog = this.dialogs[replaces.call_id + replaces.replaces_to_tag + replaces.replaces_from_tag];
+
+          if (!replacedDialog) {
+            //Replaced header without a matching dialog, reject
+            request.reply_sl(481, null);
+            return;
+          } else if (replacedDialog.owner.status === SIP.Session.C.STATUS_TERMINATED) {
+            request.reply_sl(603, null);
+            return;
+          } else if (replacedDialog.state === SIP.Dialog.C.STATUS_CONFIRMED && replaces.early_only) {
+            request.reply_sl(486, null);
+            return;
+          }
+        }
+
+        var isMediaSupported = this.configuration.mediaHandlerFactory.isSupported;
+        if(!isMediaSupported || isMediaSupported()) {
+          session = new SIP.InviteServerContext(this, request);
+          session.replacee = replacedDialog && replacedDialog.owner;
+          session.on('invite', function() {
+            self.emit('invite', this);
+          });
+        } else {
+          this.logger.warn('INVITE received but WebRTC is not supported');
+          request.reply(488);
+        }
+        break;
+      case SIP.C.BYE:
+        // Out of dialog BYE received
+        request.reply(481);
+        break;
+      case SIP.C.CANCEL:
+        session = this.findSession(request);
+        if(session) {
+          session.receiveRequest(request);
+        } else {
+          this.logger.warn('received CANCEL request for a non existent session');
+        }
+        break;
+      case SIP.C.ACK:
+        /* Absorb it.
+         * ACK request without a corresponding Invite Transaction
+         * and without To tag.
+         */
+        break;
+      default:
+        request.reply(405);
+        break;
+    }
+  }
+  // In-dialog request
+  else {
+    dialog = this.findDialog(request);
+
+    if(dialog) {
+      if (method === SIP.C.INVITE) {
+        new SIP.Transactions.InviteServerTransaction(request, this);
+      }
+      dialog.receiveRequest(request);
+    } else if (method === SIP.C.NOTIFY) {
+      session = this.findSession(request);
+      if(session) {
+        session.receiveRequest(request);
+      } else {
+        this.logger.warn('received NOTIFY request for a non existent session');
+        request.reply(481, 'Subscription does not exist');
+      }
+    }
+    /* RFC3261 12.2.2
+     * Request with to tag, but no matching dialog found.
+     * Exception: ACK for an Invite request for which a dialog has not
+     * been created.
+     */
+    else {
+      if(method !== SIP.C.ACK) {
+        request.reply(481);
+      }
+    }
+  }
+};
+
+//=================
+// Utils
+//=================
+
+/**
+ * Get the session to which the request belongs to, if any.
+ * @private
+ * @param {SIP.IncomingRequest} request.
+ * @returns {SIP.OutgoingSession|SIP.IncomingSession|null}
+ */
+UA.prototype.findSession = function(request) {
+  return this.sessions[request.call_id + request.from_tag] ||
+          this.sessions[request.call_id + request.to_tag] ||
+          null;
+};
+
+/**
+ * Get the dialog to which the request belongs to, if any.
+ * @private
+ * @param {SIP.IncomingRequest}
+ * @returns {SIP.Dialog|null}
+ */
+UA.prototype.findDialog = function(request) {
+  return this.dialogs[request.call_id + request.from_tag + request.to_tag] ||
+          this.dialogs[request.call_id + request.to_tag + request.from_tag] ||
+          null;
+};
+
+/**
+ * Retrieve the next server to which connect.
+ * @private
+ * @returns {Object} ws_server
+ */
+UA.prototype.getNextWsServer = function() {
+  // Order servers by weight
+  var idx, length, ws_server,
+    candidates = [];
+
+  length = this.configuration.wsServers.length;
+  for (idx = 0; idx < length; idx++) {
+    ws_server = this.configuration.wsServers[idx];
+
+    if (ws_server.status === SIP.Transport.C.STATUS_ERROR) {
+      continue;
+    } else if (candidates.length === 0) {
+      candidates.push(ws_server);
+    } else if (ws_server.weight > candidates[0].weight) {
+      candidates = [ws_server];
+    } else if (ws_server.weight === candidates[0].weight) {
+      candidates.push(ws_server);
+    }
+  }
+
+  idx = Math.floor(Math.random() * candidates.length);
+
+  return candidates[idx];
+};
+
+/**
+ * Close all sessions on transport error.
+ * @private
+ */
+UA.prototype.closeSessionsOnTransportError = function() {
+  var idx;
+
+  // Run _transportError_ for every Session
+  for(idx in this.sessions) {
+    this.sessions[idx].onTransportError();
+  }
+  // Call registerContext _onTransportClosed_
+  this.registerContext.onTransportClosed();
+};
+
+UA.prototype.recoverTransport = function(ua) {
+  var idx, length, k, nextRetry, count, server;
+
+  ua = ua || this;
+  count = ua.transportRecoverAttempts;
+
+  length = ua.configuration.wsServers.length;
+  for (idx = 0; idx < length; idx++) {
+    ua.configuration.wsServers[idx].status = 0;
+  }
+
+  server = ua.getNextWsServer();
+
+  k = Math.floor((Math.random() * Math.pow(2,count)) +1);
+  nextRetry = k * ua.configuration.connectionRecoveryMinInterval;
+
+  if (nextRetry > ua.configuration.connectionRecoveryMaxInterval) {
+    this.logger.log('time for next connection attempt exceeds connectionRecoveryMaxInterval, resetting counter');
+    nextRetry = ua.configuration.connectionRecoveryMinInterval;
+    count = 0;
+  }
+
+  this.logger.log('next connection attempt in '+ nextRetry +' seconds');
+
+  this.transportRecoveryTimer = SIP.Timers.setTimeout(
+    function(){
+      ua.transportRecoverAttempts = count + 1;
+      new SIP.Transport(ua, server);
+    }, nextRetry * 1000);
+};
+
+function checkAuthenticationFactory (authenticationFactory) {
+  if (!(authenticationFactory instanceof Function)) {
+    return;
+  }
+  if (!authenticationFactory.initialize) {
+    authenticationFactory.initialize = function initialize () {
+      return SIP.Utils.Promise.resolve();
+    };
+  }
+  return authenticationFactory;
+}
+
+/**
+ * Configuration load.
+ * @private
+ * returns {Boolean}
+ */
+UA.prototype.loadConfig = function(configuration) {
+  // Settings and default values
+  var parameter, value, checked_value, hostportParams, registrarServer,
+    settings = {
+      /* Host address
+      * Value to be set in Via sent_by and host part of Contact FQDN
+      */
+      viaHost: SIP.Utils.createRandomToken(12) + '.invalid',
+
+      uri: new SIP.URI('sip', 'anonymous.' + SIP.Utils.createRandomToken(6), 'anonymous.invalid', null, null),
+      wsServers: [{
+        scheme: 'WSS',
+        sip_uri: '<sip:edge.sip.onsip.com;transport=ws;lr>',
+        status: 0,
+        weight: 0,
+        ws_uri: 'wss://edge.sip.onsip.com'
+      }],
+
+      // Password
+      password: null,
+
+      // Registration parameters
+      registerExpires: 600,
+      register: true,
+      registrarServer: null,
+
+      // Transport related parameters
+      wsServerMaxReconnection: 3,
+      wsServerReconnectionTimeout: 4,
+
+      connectionRecoveryMinInterval: 2,
+      connectionRecoveryMaxInterval: 30,
+
+      keepAliveInterval: 0,
+
+      extraSupported: [],
+
+      usePreloadedRoute: false,
+
+      //string to be inserted into User-Agent request header
+      userAgentString: SIP.C.USER_AGENT,
+
+      // Session parameters
+      iceCheckingTimeout: 5000,
+      noAnswerTimeout: 60,
+      stunServers: ['stun:stun.l.google.com:19302'],
+      turnServers: [],
+
+      // Logging parameters
+      traceSip: false,
+
+      // Hacks
+      hackViaTcp: false,
+      hackIpInContact: false,
+      hackWssInTransport: false,
+      hackAllowUnregisteredOptionTags: false,
+
+      contactTransport: 'ws',
+      forceRport: false,
+
+      //autostarting
+      autostart: true,
+
+      //Reliable Provisional Responses
+      rel100: SIP.C.supported.UNSUPPORTED,
+
+      // Replaces header (RFC 3891)
+      // http://tools.ietf.org/html/rfc3891
+      replaces: SIP.C.supported.UNSUPPORTED,
+
+      mediaHandlerFactory: SIP.WebRTC.MediaHandler.defaultFactory,
+
+      authenticationFactory: checkAuthenticationFactory(function authenticationFactory (ua) {
+        return new SIP.DigestAuthentication(ua);
+      })
+    };
+
+  // Pre-Configuration
+  function aliasUnderscored (parameter, logger) {
+    var underscored = parameter.replace(/([a-z][A-Z])/g, function (m) {
+      return m[0] + '_' + m[1].toLowerCase();
+    });
+
+    if (parameter === underscored) {
+      return;
+    }
+
+    var hasParameter = configuration.hasOwnProperty(parameter);
+    if (configuration.hasOwnProperty(underscored)) {
+      logger.warn(underscored + ' is deprecated, please use ' + parameter);
+      if (hasParameter) {
+        logger.warn(parameter + ' overriding ' + underscored);
+      }
+    }
+
+    configuration[parameter] = hasParameter ? configuration[parameter] : configuration[underscored];
+  }
+
+  // Check Mandatory parameters
+  for(parameter in UA.configuration_check.mandatory) {
+    aliasUnderscored(parameter, this.logger);
+    if(!configuration.hasOwnProperty(parameter)) {
+      throw new SIP.Exceptions.ConfigurationError(parameter);
+    } else {
+      value = configuration[parameter];
+      checked_value = UA.configuration_check.mandatory[parameter](value);
+      if (checked_value !== undefined) {
+        settings[parameter] = checked_value;
+      } else {
+        throw new SIP.Exceptions.ConfigurationError(parameter, value);
+      }
+    }
+  }
+
+  SIP.Utils.optionsOverride(configuration, 'rel100', 'reliable', true, this.logger, SIP.C.supported.UNSUPPORTED);
+
+  var emptyArraysAllowed = ['stunServers', 'turnServers'];
+
+  // Check Optional parameters
+  for(parameter in UA.configuration_check.optional) {
+    aliasUnderscored(parameter, this.logger);
+    if(configuration.hasOwnProperty(parameter)) {
+      value = configuration[parameter];
+
+      // If the parameter value is an empty array, but shouldn't be, apply its default value.
+      if (value instanceof Array && value.length === 0 && emptyArraysAllowed.indexOf(parameter) < 0) { continue; }
+
+      // If the parameter value is null, empty string, or undefined then apply its default value.
+      if(value === null || value === "" || value === undefined) { continue; }
+      // If it's a number with NaN value then also apply its default value.
+      // NOTE: JS does not allow "value === NaN", the following does the work:
+      else if(typeof(value) === 'number' && isNaN(value)) { continue; }
+
+      checked_value = UA.configuration_check.optional[parameter](value);
+      if (checked_value !== undefined) {
+        settings[parameter] = checked_value;
+      } else {
+        throw new SIP.Exceptions.ConfigurationError(parameter, value);
+      }
+    }
+  }
+
+  // Sanity Checks
+
+  // Connection recovery intervals
+  if(settings.connectionRecoveryMaxInterval < settings.connectionRecoveryMinInterval) {
+    throw new SIP.Exceptions.ConfigurationError('connectionRecoveryMaxInterval', settings.connectionRecoveryMaxInterval);
+  }
+
+  // Post Configuration Process
+
+  // Allow passing 0 number as displayName.
+  if (settings.displayName === 0) {
+    settings.displayName = '0';
+  }
+
+  // Instance-id for GRUU
+  if (!settings.instanceId) {
+    settings.instanceId = SIP.Utils.newUUID();
+  }
+
+  // sipjsId instance parameter. Static random tag of length 5
+  settings.sipjsId = SIP.Utils.createRandomToken(5);
+
+  // String containing settings.uri without scheme and user.
+  hostportParams = settings.uri.clone();
+  hostportParams.user = null;
+  settings.hostportParams = hostportParams.toRaw().replace(/^sip:/i, '');
+
+  /* Check whether authorizationUser is explicitly defined.
+   * Take 'settings.uri.user' value if not.
+   */
+  if (!settings.authorizationUser) {
+    settings.authorizationUser = settings.uri.user;
+  }
+
+  /* If no 'registrarServer' is set use the 'uri' value without user portion. */
+  if (!settings.registrarServer) {
+    registrarServer = settings.uri.clone();
+    registrarServer.user = null;
+    settings.registrarServer = registrarServer;
+  }
+
+  // User noAnswerTimeout
+  settings.noAnswerTimeout = settings.noAnswerTimeout * 1000;
+
+  // Via Host
+  if (settings.hackIpInContact) {
+    if (typeof settings.hackIpInContact === 'boolean') {
+      settings.viaHost = SIP.Utils.getRandomTestNetIP();
+    }
+    else if (typeof settings.hackIpInContact === 'string') {
+      settings.viaHost = settings.hackIpInContact;
+    }
+  }
+
+  // Contact transport parameter
+  if (settings.hackWssInTransport) {
+    settings.contactTransport = 'wss';
+  }
+
+  this.contact = {
+    pub_gruu: null,
+    temp_gruu: null,
+    uri: new SIP.URI('sip', SIP.Utils.createRandomToken(8), settings.viaHost, null, {transport: settings.contactTransport}),
+    toString: function(options){
+      options = options || {};
+
+      var
+        anonymous = options.anonymous || null,
+        outbound = options.outbound || null,
+        contact = '<';
+
+      if (anonymous) {
+        contact += (this.temp_gruu || ('sip:anonymous@anonymous.invalid;transport='+settings.contactTransport)).toString();
+      } else {
+        contact += (this.pub_gruu || this.uri).toString();
+      }
+
+      if (outbound) {
+        contact += ';ob';
+      }
+
+      contact += '>';
+
+      return contact;
+    }
+  };
+
+  // media overrides mediaConstraints
+  SIP.Utils.optionsOverride(settings, 'media', 'mediaConstraints', true, this.logger);
+
+  // Fill the value of the configuration_skeleton
+  for(parameter in settings) {
+    UA.configuration_skeleton[parameter].value = settings[parameter];
+  }
+
+  Object.defineProperties(this.configuration, UA.configuration_skeleton);
+
+  // Clean UA.configuration_skeleton
+  for(parameter in settings) {
+    UA.configuration_skeleton[parameter].value = '';
+  }
+
+  this.logger.log('configuration parameters after validation:');
+  for(parameter in settings) {
+    switch(parameter) {
+      case 'uri':
+      case 'registrarServer':
+      case 'mediaHandlerFactory':
+        this.logger.log('· ' + parameter + ': ' + settings[parameter]);
+        break;
+      case 'password':
+        this.logger.log('· ' + parameter + ': ' + 'NOT SHOWN');
+        break;
+      default:
+        this.logger.log('· ' + parameter + ': ' + JSON.stringify(settings[parameter]));
+    }
+  }
+
+  return;
+};
+
+/**
+ * Configuration Object skeleton.
+ * @private
+ */
+UA.configuration_skeleton = (function() {
+  var idx,  parameter,
+    skeleton = {},
+    parameters = [
+      // Internal parameters
+      "sipjsId",
+      "hostportParams",
+
+      // Optional user configurable parameters
+      "uri",
+      "wsServers",
+      "authorizationUser",
+      "connectionRecoveryMaxInterval",
+      "connectionRecoveryMinInterval",
+      "keepAliveInterval",
+      "extraSupported",
+      "displayName",
+      "hackViaTcp", // false.
+      "hackIpInContact", //false
+      "hackWssInTransport", //false
+      "hackAllowUnregisteredOptionTags", //false
+      "contactTransport", // 'ws'
+      "forceRport", // false
+      "iceCheckingTimeout",
+      "instanceId",
+      "noAnswerTimeout", // 30 seconds.
+      "password",
+      "registerExpires", // 600 seconds.
+      "registrarServer",
+      "reliable",
+      "rel100",
+      "replaces",
+      "userAgentString", //SIP.C.USER_AGENT
+      "autostart",
+      "stunServers",
+      "traceSip",
+      "turnServers",
+      "usePreloadedRoute",
+      "wsServerMaxReconnection",
+      "wsServerReconnectionTimeout",
+      "mediaHandlerFactory",
+      "media",
+      "mediaConstraints",
+      "authenticationFactory",
+
+      // Post-configuration generated parameters
+      "via_core_value",
+      "viaHost"
+    ];
+
+  for(idx in parameters) {
+    parameter = parameters[idx];
+    skeleton[parameter] = {
+      value: '',
+      writable: false,
+      configurable: false
+    };
+  }
+
+  skeleton['register'] = {
+    value: '',
+    writable: true,
+    configurable: false
+  };
+
+  return skeleton;
+}());
+
+/**
+ * Configuration checker.
+ * @private
+ * @return {Boolean}
+ */
+UA.configuration_check = {
+  mandatory: {
+  },
+
+  optional: {
+
+    uri: function(uri) {
+      var parsed;
+
+      if (!(/^sip:/i).test(uri)) {
+        uri = SIP.C.SIP + ':' + uri;
+      }
+      parsed = SIP.URI.parse(uri);
+
+      if(!parsed) {
+        return;
+      } else if(!parsed.user) {
+        return;
+      } else {
+        return parsed;
+      }
+    },
+
+    //Note: this function used to call 'this.logger.error' but calling 'this' with anything here is invalid
+    wsServers: function(wsServers) {
+      var idx, length, url;
+
+      /* Allow defining wsServers parameter as:
+       *  String: "host"
+       *  Array of Strings: ["host1", "host2"]
+       *  Array of Objects: [{ws_uri:"host1", weight:1}, {ws_uri:"host2", weight:0}]
+       *  Array of Objects and Strings: [{ws_uri:"host1"}, "host2"]
+       */
+      if (typeof wsServers === 'string') {
+        wsServers = [{ws_uri: wsServers}];
+      } else if (wsServers instanceof Array) {
+        length = wsServers.length;
+        for (idx = 0; idx < length; idx++) {
+          if (typeof wsServers[idx] === 'string'){
+            wsServers[idx] = {ws_uri: wsServers[idx]};
+          }
+        }
+      } else {
+        return;
+      }
+
+      if (wsServers.length === 0) {
+        return false;
+      }
+
+      length = wsServers.length;
+      for (idx = 0; idx < length; idx++) {
+        if (!wsServers[idx].ws_uri) {
+          return;
+        }
+        if (wsServers[idx].weight && !Number(wsServers[idx].weight)) {
+          return;
+        }
+
+        url = SIP.Grammar.parse(wsServers[idx].ws_uri, 'absoluteURI');
+
+        if(url === -1) {
+          return;
+        } else if(['wss', 'ws', 'udp'].indexOf(url.scheme) < 0) {
+          return;
+        } else {
+          wsServers[idx].sip_uri = '<sip:' + url.host + (url.port ? ':' + url.port : '') + ';transport=' + url.scheme.replace(/^wss$/i, 'ws') + ';lr>';
+
+          if (!wsServers[idx].weight) {
+            wsServers[idx].weight = 0;
+          }
+
+          wsServers[idx].status = 0;
+          wsServers[idx].scheme = url.scheme.toUpperCase();
+        }
+      }
+      return wsServers;
+    },
+
+    authorizationUser: function(authorizationUser) {
+      if(SIP.Grammar.parse('"'+ authorizationUser +'"', 'quoted_string') === -1) {
+        return;
+      } else {
+        return authorizationUser;
+      }
+    },
+
+    connectionRecoveryMaxInterval: function(connectionRecoveryMaxInterval) {
+      var value;
+      if(SIP.Utils.isDecimal(connectionRecoveryMaxInterval)) {
+        value = Number(connectionRecoveryMaxInterval);
+        if(value > 0) {
+          return value;
+        }
+      }
+    },
+
+    connectionRecoveryMinInterval: function(connectionRecoveryMinInterval) {
+      var value;
+      if(SIP.Utils.isDecimal(connectionRecoveryMinInterval)) {
+        value = Number(connectionRecoveryMinInterval);
+        if(value > 0) {
+          return value;
+        }
+      }
+    },
+
+    displayName: function(displayName) {
+      if(SIP.Grammar.parse('"' + displayName + '"', 'displayName') === -1) {
+        return;
+      } else {
+        return displayName;
+      }
+    },
+
+    hackViaTcp: function(hackViaTcp) {
+      if (typeof hackViaTcp === 'boolean') {
+        return hackViaTcp;
+      }
+    },
+
+    hackIpInContact: function(hackIpInContact) {
+      if (typeof hackIpInContact === 'boolean') {
+        return hackIpInContact;
+      }
+      else if (typeof hackIpInContact === 'string' && SIP.Grammar.parse(hackIpInContact, 'host') !== -1) {
+        return hackIpInContact;
+      }
+    },
+
+    iceCheckingTimeout: function(iceCheckingTimeout) {
+      if(SIP.Utils.isDecimal(iceCheckingTimeout)) {
+        return Math.max(500, iceCheckingTimeout);
+      }
+    },
+
+    hackWssInTransport: function(hackWssInTransport) {
+      if (typeof hackWssInTransport === 'boolean') {
+        return hackWssInTransport;
+      }
+    },
+
+    hackAllowUnregisteredOptionTags: function(hackAllowUnregisteredOptionTags) {
+      if (typeof hackAllowUnregisteredOptionTags === 'boolean') {
+        return hackAllowUnregisteredOptionTags;
+      }
+    },
+
+    contactTransport: function(contactTransport) {
+      if (typeof contactTransport === 'string') {
+        return contactTransport;
+      }
+    },
+
+    forceRport: function(forceRport) {
+      if (typeof forceRport === 'boolean') {
+        return forceRport;
+      }
+    },
+
+    instanceId: function(instanceId) {
+      if(typeof instanceId !== 'string') {
+        return;
+      }
+
+      if ((/^uuid:/i.test(instanceId))) {
+        instanceId = instanceId.substr(5);
+      }
+
+      if(SIP.Grammar.parse(instanceId, 'uuid') === -1) {
+        return;
+      } else {
+        return instanceId;
+      }
+    },
+
+    keepAliveInterval: function(keepAliveInterval) {
+      var value;
+      if (SIP.Utils.isDecimal(keepAliveInterval)) {
+        value = Number(keepAliveInterval);
+        if (value > 0) {
+          return value;
+        }
+      }
+    },
+
+    extraSupported: function(optionTags) {
+      var idx, length;
+
+      if (!(optionTags instanceof Array)) {
+        return;
+      }
+
+      length = optionTags.length;
+      for (idx = 0; idx < length; idx++) {
+        if (typeof optionTags[idx] !== 'string') {
+          return;
+        }
+      }
+
+      return optionTags;
+    },
+
+    noAnswerTimeout: function(noAnswerTimeout) {
+      var value;
+      if (SIP.Utils.isDecimal(noAnswerTimeout)) {
+        value = Number(noAnswerTimeout);
+        if (value > 0) {
+          return value;
+        }
+      }
+    },
+
+    password: function(password) {
+      return String(password);
+    },
+
+    rel100: function(rel100) {
+      if(rel100 === SIP.C.supported.REQUIRED) {
+        return SIP.C.supported.REQUIRED;
+      } else if (rel100 === SIP.C.supported.SUPPORTED) {
+        return SIP.C.supported.SUPPORTED;
+      } else  {
+        return SIP.C.supported.UNSUPPORTED;
+      }
+    },
+
+    replaces: function(replaces) {
+      if(replaces === SIP.C.supported.REQUIRED) {
+        return SIP.C.supported.REQUIRED;
+      } else if (replaces === SIP.C.supported.SUPPORTED) {
+        return SIP.C.supported.SUPPORTED;
+      } else  {
+        return SIP.C.supported.UNSUPPORTED;
+      }
+    },
+
+    register: function(register) {
+      if (typeof register === 'boolean') {
+        return register;
+      }
+    },
+
+    registerExpires: function(registerExpires) {
+      var value;
+      if (SIP.Utils.isDecimal(registerExpires)) {
+        value = Number(registerExpires);
+        if (value > 0) {
+          return value;
+        }
+      }
+    },
+
+    registrarServer: function(registrarServer) {
+      var parsed;
+
+      if(typeof registrarServer !== 'string') {
+        return;
+      }
+
+      if (!/^sip:/i.test(registrarServer)) {
+        registrarServer = SIP.C.SIP + ':' + registrarServer;
+      }
+      parsed = SIP.URI.parse(registrarServer);
+
+      if(!parsed) {
+        return;
+      } else if(parsed.user) {
+        return;
+      } else {
+        return parsed;
+      }
+    },
+
+    stunServers: function(stunServers) {
+      var idx, length, stun_server;
+
+      if (typeof stunServers === 'string') {
+        stunServers = [stunServers];
+      } else if (!(stunServers instanceof Array)) {
+        return;
+      }
+
+      length = stunServers.length;
+      for (idx = 0; idx < length; idx++) {
+        stun_server = stunServers[idx];
+        if (!(/^stuns?:/.test(stun_server))) {
+          stun_server = 'stun:' + stun_server;
+        }
+
+        if(SIP.Grammar.parse(stun_server, 'stun_URI') === -1) {
+          return;
+        } else {
+          stunServers[idx] = stun_server;
+        }
+      }
+      return stunServers;
+    },
+
+    traceSip: function(traceSip) {
+      if (typeof traceSip === 'boolean') {
+        return traceSip;
+      }
+    },
+
+    turnServers: function(turnServers) {
+      var idx, jdx, length, turn_server, num_turn_server_urls, url;
+
+      if (turnServers instanceof Array) {
+        // Do nothing
+      } else {
+        turnServers = [turnServers];
+      }
+
+      length = turnServers.length;
+      for (idx = 0; idx < length; idx++) {
+        turn_server = turnServers[idx];
+        //Backwards compatibility: Allow defining the turn_server url with the 'server' property.
+        if (turn_server.server) {
+          turn_server.urls = [turn_server.server];
+        }
+
+        if (!turn_server.urls || !turn_server.username || !turn_server.password) {
+          return;
+        }
+
+        if (turn_server.urls instanceof Array) {
+          num_turn_server_urls = turn_server.urls.length;
+        } else {
+          turn_server.urls = [turn_server.urls];
+          num_turn_server_urls = 1;
+        }
+
+        for (jdx = 0; jdx < num_turn_server_urls; jdx++) {
+          url = turn_server.urls[jdx];
+
+          if (!(/^turns?:/.test(url))) {
+            url = 'turn:' + url;
+          }
+
+          if(SIP.Grammar.parse(url, 'turn_URI') === -1) {
+            return;
+          }
+        }
+      }
+      return turnServers;
+    },
+
+    userAgentString: function(userAgentString) {
+      if (typeof userAgentString === 'string') {
+        return userAgentString;
+      }
+    },
+
+    usePreloadedRoute: function(usePreloadedRoute) {
+      if (typeof usePreloadedRoute === 'boolean') {
+        return usePreloadedRoute;
+      }
+    },
+
+    wsServerMaxReconnection: function(wsServerMaxReconnection) {
+      var value;
+      if (SIP.Utils.isDecimal(wsServerMaxReconnection)) {
+        value = Number(wsServerMaxReconnection);
+        if (value > 0) {
+          return value;
+        }
+      }
+    },
+
+    wsServerReconnectionTimeout: function(wsServerReconnectionTimeout) {
+      var value;
+      if (SIP.Utils.isDecimal(wsServerReconnectionTimeout)) {
+        value = Number(wsServerReconnectionTimeout);
+        if (value > 0) {
+          return value;
+        }
+      }
+    },
+
+    autostart: function(autostart) {
+      if (typeof autostart === 'boolean') {
+        return autostart;
+      }
+    },
+
+    mediaHandlerFactory: function(mediaHandlerFactory) {
+      if (mediaHandlerFactory instanceof Function) {
+        var promisifiedFactory = function promisifiedFactory () {
+          var mediaHandler = mediaHandlerFactory.apply(this, arguments);
+
+          function patchMethod (methodName) {
+            var method = mediaHandler[methodName];
+            if (method.length > 1) {
+              var callbacksFirst = methodName === 'getDescription';
+              mediaHandler[methodName] = SIP.Utils.promisify(mediaHandler, methodName, callbacksFirst);
+            }
+          }
+
+          patchMethod('getDescription');
+          patchMethod('setDescription');
+
+          return mediaHandler;
+        };
+
+        promisifiedFactory.isSupported = mediaHandlerFactory.isSupported;
+        return promisifiedFactory;
+      }
+    },
+
+    authenticationFactory: checkAuthenticationFactory
+  }
+};
+
+UA.C = C;
+SIP.UA = UA;
+};
+
+}).call(this,typeof global !== "undefined" ? global : typeof self !== "undefined" ? self : typeof window !== "undefined" ? window : {})
+},{}],30:[function(require,module,exports){
+"use strict";
+/**
+ * @fileoverview SIP URI
+ */
+
+/**
+ * @augments SIP
+ * @class Class creating a SIP URI.
+ *
+ * @param {String} [scheme]
+ * @param {String} [user]
+ * @param {String} host
+ * @param {String} [port]
+ * @param {Object} [parameters]
+ * @param {Object} [headers]
+ *
+ */
+module.exports = function (SIP) {
+var URI;
+
+URI = function(scheme, user, host, port, parameters, headers) {
+  var param, header, raw, normal;
+
+  // Checks
+  if(!host) {
+    throw new TypeError('missing or invalid "host" parameter');
+  }
+
+  // Initialize parameters
+  scheme = scheme || SIP.C.SIP;
+  this.parameters = {};
+  this.headers = {};
+
+  for (param in parameters) {
+    this.setParam(param, parameters[param]);
+  }
+
+  for (header in headers) {
+    this.setHeader(header, headers[header]);
+  }
+
+  // Raw URI
+  raw = {
+    scheme: scheme,
+    user: user,
+    host: host,
+    port: port
+  };
+
+  // Normalized URI
+  normal = {
+    scheme: scheme.toLowerCase(),
+    user: user,
+    host: host.toLowerCase(),
+    port: port
+  };
+
+  Object.defineProperties(this, {
+    _normal: {
+      get: function() { return normal; }
+    },
+
+    _raw: {
+      get: function() { return raw; }
+    },
+
+    scheme: {
+      get: function() { return normal.scheme; },
+      set: function(value) {
+        raw.scheme = value;
+        normal.scheme = value.toLowerCase();
+      }
+    },
+
+    user: {
+      get: function() { return normal.user; },
+      set: function(value) {
+        normal.user = raw.user = value;
+      }
+    },
+
+    host: {
+      get: function() { return normal.host; },
+      set: function(value) {
+        raw.host = value;
+        normal.host = value.toLowerCase();
+      }
+    },
+
+    aor: {
+      get: function() { return normal.user + '@' + normal.host; }
+    },
+
+    port: {
+      get: function() { return normal.port; },
+      set: function(value) {
+        normal.port = raw.port = value === 0 ? value : (parseInt(value,10) || null);
+      }
+    }
+  });
+};
+
+URI.prototype = {
+  setParam: function(key, value) {
+    if(key) {
+      this.parameters[key.toLowerCase()] = (typeof value === 'undefined' || value === null) ? null : value.toString().toLowerCase();
+    }
+  },
+
+  getParam: function(key) {
+    if(key) {
+      return this.parameters[key.toLowerCase()];
+    }
+  },
+
+  hasParam: function(key) {
+    if(key) {
+      return (this.parameters.hasOwnProperty(key.toLowerCase()) && true) || false;
+    }
+  },
+
+  deleteParam: function(parameter) {
+    var value;
+    parameter = parameter.toLowerCase();
+    if (this.parameters.hasOwnProperty(parameter)) {
+      value = this.parameters[parameter];
+      delete this.parameters[parameter];
+      return value;
+    }
+  },
+
+  clearParams: function() {
+    this.parameters = {};
+  },
+
+  setHeader: function(name, value) {
+    this.headers[SIP.Utils.headerize(name)] = (value instanceof Array) ? value : [value];
+  },
+
+  getHeader: function(name) {
+    if(name) {
+      return this.headers[SIP.Utils.headerize(name)];
+    }
+  },
+
+  hasHeader: function(name) {
+    if(name) {
+      return (this.headers.hasOwnProperty(SIP.Utils.headerize(name)) && true) || false;
+    }
+  },
+
+  deleteHeader: function(header) {
+    var value;
+    header = SIP.Utils.headerize(header);
+    if(this.headers.hasOwnProperty(header)) {
+      value = this.headers[header];
+      delete this.headers[header];
+      return value;
+    }
+  },
+
+  clearHeaders: function() {
+    this.headers = {};
+  },
+
+  clone: function() {
+    return new URI(
+      this._raw.scheme,
+      this._raw.user,
+      this._raw.host,
+      this._raw.port,
+      JSON.parse(JSON.stringify(this.parameters)),
+      JSON.parse(JSON.stringify(this.headers)));
+  },
+
+  toRaw: function() {
+    return this._toString(this._raw);
+  },
+
+  toString: function() {
+    return this._toString(this._normal);
+  },
+
+  _toString: function(uri) {
+    var header, parameter, idx, uriString, headers = [];
+
+    uriString  = uri.scheme + ':';
+    // add slashes if it's not a sip(s) URI
+    if (!uri.scheme.toLowerCase().match("^sips?$")) {
+      uriString += "//";
+    }
+    if (uri.user) {
+      uriString += SIP.Utils.escapeUser(uri.user) + '@';
+    }
+    uriString += uri.host;
+    if (uri.port || uri.port === 0) {
+      uriString += ':' + uri.port;
+    }
+
+    for (parameter in this.parameters) {
+      uriString += ';' + parameter;
+
+      if (this.parameters[parameter] !== null) {
+        uriString += '='+ this.parameters[parameter];
+      }
+    }
+
+    for(header in this.headers) {
+      for(idx in this.headers[header]) {
+        headers.push(header + '=' + this.headers[header][idx]);
+      }
+    }
+
+    if (headers.length > 0) {
+      uriString += '?' + headers.join('&');
+    }
+
+    return uriString;
+  }
+};
+
+
+/**
+  * Parse the given string and returns a SIP.URI instance or undefined if
+  * it is an invalid URI.
+  * @public
+  * @param {String} uri
+  */
+URI.parse = function(uri) {
+  uri = SIP.Grammar.parse(uri,'SIP_URI');
+
+  if (uri !== -1) {
+    return uri;
+  } else {
+    return undefined;
+  }
+};
+
+SIP.URI = URI;
+};
+
+},{}],31:[function(require,module,exports){
+"use strict";
+/**
+ * @fileoverview Utils
+ */
+
+module.exports = function (SIP, environment) {
+var Utils;
+
+Utils= {
+
+  Promise: environment.Promise,
+
+  defer: function defer () {
+    var deferred = {};
+    deferred.promise = new Utils.Promise(function (resolve, reject) {
+      deferred.resolve = resolve;
+      deferred.reject = reject;
+    });
+    return deferred;
+  },
+
+  promisify: function promisify (object, methodName, callbacksFirst) {
+    var oldMethod = object[methodName];
+    return function promisifiedMethod (arg, onSuccess, onFailure) {
+      return new Utils.Promise(function (resolve, reject) {
+        var oldArgs = [arg, resolve, reject];
+        if (callbacksFirst) {
+          oldArgs = [resolve, reject, arg];
+        }
+        oldMethod.apply(object, oldArgs);
+      }).then(onSuccess, onFailure);
+    };
+  },
+
+  augment: function (object, constructor, args, override) {
+    var idx, proto;
+
+    // Add public properties from constructor's prototype onto object
+    proto = constructor.prototype;
+    for (idx in proto) {
+      if (override || object[idx] === undefined) {
+        object[idx] = proto[idx];
+      }
+    }
+
+    // Construct the object as though it were just created by constructor
+    constructor.apply(object, args);
+  },
+
+  optionsOverride: function (options, winner, loser, isDeprecated, logger, defaultValue) {
+    if (isDeprecated && options[loser]) {
+      logger.warn(loser + ' is deprecated, please use ' + winner + ' instead');
+    }
+
+    if (options[winner] && options[loser]) {
+      logger.warn(winner + ' overriding ' + loser);
+    }
+
+    options[winner] = options[winner] || options[loser] || defaultValue;
+  },
+
+  str_utf8_length: function(string) {
+    return encodeURIComponent(string).replace(/%[A-F\d]{2}/g, 'U').length;
+  },
+
+  generateFakeSDP: function(body) {
+    if (!body) {
+      return;
+    }
+
+    var start = body.indexOf('o=');
+    var end = body.indexOf('\r\n', start);
+
+    return 'v=0\r\n' + body.slice(start, end) + '\r\ns=-\r\nt=0 0\r\nc=IN IP4 0.0.0.0';
+  },
+
+  isFunction: function(fn) {
+    if (fn !== undefined) {
+      return Object.prototype.toString.call(fn) === '[object Function]';
+    } else {
+      return false;
+    }
+  },
+
+  isDecimal: function (num) {
+    return !isNaN(num) && (parseFloat(num) === parseInt(num,10));
+  },
+
+  createRandomToken: function(size, base) {
+    var i, r,
+      token = '';
+
+    base = base || 32;
+
+    for( i=0; i < size; i++ ) {
+      r = Math.random() * base|0;
+      token += r.toString(base);
+    }
+
+    return token;
+  },
+
+  newTag: function() {
+    return SIP.Utils.createRandomToken(SIP.UA.C.TAG_LENGTH);
+  },
+
+  // http://stackoverflow.com/users/109538/broofa
+  newUUID: function() {
+    var UUID =  'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, function(c) {
+      var r = Math.random()*16|0, v = c === 'x' ? r : (r&0x3|0x8);
+      return v.toString(16);
+    });
+
+    return UUID;
+  },
+
+  hostType: function(host) {
+    if (!host) {
+      return;
+    } else {
+      host = SIP.Grammar.parse(host,'host');
+      if (host !== -1) {
+        return host.host_type;
+      }
+    }
+  },
+
+  /**
+  * Normalize SIP URI.
+  * NOTE: It does not allow a SIP URI without username.
+  * Accepts 'sip', 'sips' and 'tel' URIs and convert them into 'sip'.
+  * Detects the domain part (if given) and properly hex-escapes the user portion.
+  * If the user portion has only 'tel' number symbols the user portion is clean of 'tel' visual separators.
+  * @private
+  * @param {String} target
+  * @param {String} [domain]
+  */
+  normalizeTarget: function(target, domain) {
+    var uri, target_array, target_user, target_domain;
+
+    // If no target is given then raise an error.
+    if (!target) {
+      return;
+    // If a SIP.URI instance is given then return it.
+    } else if (target instanceof SIP.URI) {
+      return target;
+
+    // If a string is given split it by '@':
+    // - Last fragment is the desired domain.
+    // - Otherwise append the given domain argument.
+    } else if (typeof target === 'string') {
+      target_array = target.split('@');
+
+      switch(target_array.length) {
+        case 1:
+          if (!domain) {
+            return;
+          }
+          target_user = target;
+          target_domain = domain;
+          break;
+        case 2:
+          target_user = target_array[0];
+          target_domain = target_array[1];
+          break;
+        default:
+          target_user = target_array.slice(0, target_array.length-1).join('@');
+          target_domain = target_array[target_array.length-1];
+      }
+
+      // Remove the URI scheme (if present).
+      target_user = target_user.replace(/^(sips?|tel):/i, '');
+
+      // Remove 'tel' visual separators if the user portion just contains 'tel' number symbols.
+      if (/^[\-\.\(\)]*\+?[0-9\-\.\(\)]+$/.test(target_user)) {
+        target_user = target_user.replace(/[\-\.\(\)]/g, '');
+      }
+
+      // Build the complete SIP URI.
+      target = SIP.C.SIP + ':' + SIP.Utils.escapeUser(target_user) + '@' + target_domain;
+
+      // Finally parse the resulting URI.
+      if (uri = SIP.URI.parse(target)) {
+        return uri;
+      } else {
+        return;
+      }
+    } else {
+      return;
+    }
+  },
+
+  /**
+  * Hex-escape a SIP URI user.
+  * @private
+  * @param {String} user
+  */
+  escapeUser: function(user) {
+    // Don't hex-escape ':' (%3A), '+' (%2B), '?' (%3F"), '/' (%2F).
+    return encodeURIComponent(decodeURIComponent(user)).replace(/%3A/ig, ':').replace(/%2B/ig, '+').replace(/%3F/ig, '?').replace(/%2F/ig, '/');
+  },
+
+  headerize: function(string) {
+    var exceptions = {
+      'Call-Id': 'Call-ID',
+      'Cseq': 'CSeq',
+      'Min-Se': 'Min-SE',
+      'Rack': 'RAck',
+      'Rseq': 'RSeq',
+      'Www-Authenticate': 'WWW-Authenticate'
+      },
+      name = string.toLowerCase().replace(/_/g,'-').split('-'),
+      hname = '',
+      parts = name.length, part;
+
+    for (part = 0; part < parts; part++) {
+      if (part !== 0) {
+        hname +='-';
+      }
+      hname += name[part].charAt(0).toUpperCase()+name[part].substring(1);
+    }
+    if (exceptions[hname]) {
+      hname = exceptions[hname];
+    }
+    return hname;
+  },
+
+  sipErrorCause: function(status_code) {
+    var cause;
+
+    for (cause in SIP.C.SIP_ERROR_CAUSES) {
+      if (SIP.C.SIP_ERROR_CAUSES[cause].indexOf(status_code) !== -1) {
+        return SIP.C.causes[cause];
+      }
+    }
+
+    return SIP.C.causes.SIP_FAILURE_CODE;
+  },
+
+  getReasonPhrase: function getReasonPhrase (code, specific) {
+    return specific || SIP.C.REASON_PHRASE[code] || '';
+  },
+
+  getReasonHeaderValue: function getReasonHeaderValue (code, reason) {
+    reason = SIP.Utils.getReasonPhrase(code, reason);
+    return 'SIP ;cause=' + code + ' ;text="' + reason + '"';
+  },
+
+  getCancelReason: function getCancelReason (code, reason) {
+    if (code && code < 200 || code > 699) {
+      throw new TypeError('Invalid status_code: ' + code);
+    } else if (code) {
+      return SIP.Utils.getReasonHeaderValue(code, reason);
+    }
+  },
+
+  buildStatusLine: function buildStatusLine (code, reason) {
+    code = code || null;
+    reason = reason || null;
+
+    // Validate code and reason values
+    if (!code || (code < 100 || code > 699)) {
+      throw new TypeError('Invalid status_code: '+ code);
+    } else if (reason && typeof reason !== 'string' && !(reason instanceof String)) {
+      throw new TypeError('Invalid reason_phrase: '+ reason);
+    }
+
+    reason = Utils.getReasonPhrase(code, reason);
+
+    return 'SIP/2.0 ' + code + ' ' + reason + '\r\n';
+  },
+
+  /**
+  * Generate a random Test-Net IP (http://tools.ietf.org/html/rfc5735)
+  * @private
+  */
+  getRandomTestNetIP: function() {
+    function getOctet(from,to) {
+      return Math.floor(Math.random()*(to-from+1)+from);
+    }
+    return '192.0.2.' + getOctet(1, 254);
+  },
+
+  // MD5 (Message-Digest Algorithm) http://www.webtoolkit.info
+  calculateMD5: function(string) {
+    function RotateLeft(lValue, iShiftBits) {
+      return (lValue<<iShiftBits) | (lValue>>>(32-iShiftBits));
+    }
+
+    function AddUnsigned(lX,lY) {
+      var lX4,lY4,lX8,lY8,lResult;
+      lX8 = (lX & 0x80000000);
+      lY8 = (lY & 0x80000000);
+      lX4 = (lX & 0x40000000);
+      lY4 = (lY & 0x40000000);
+      lResult = (lX & 0x3FFFFFFF)+(lY & 0x3FFFFFFF);
+      if (lX4 & lY4) {
+        return (lResult ^ 0x80000000 ^ lX8 ^ lY8);
+      }
+      if (lX4 | lY4) {
+        if (lResult & 0x40000000) {
+          return (lResult ^ 0xC0000000 ^ lX8 ^ lY8);
+        } else {
+          return (lResult ^ 0x40000000 ^ lX8 ^ lY8);
+        }
+      } else {
+        return (lResult ^ lX8 ^ lY8);
+      }
+    }
+
+    function F(x,y,z) {
+      return (x & y) | ((~x) & z);
+    }
+
+    function G(x,y,z) {
+      return (x & z) | (y & (~z));
+    }
+
+    function H(x,y,z) {
+      return (x ^ y ^ z);
+    }
+
+    function I(x,y,z) {
+      return (y ^ (x | (~z)));
+    }
+
+    function FF(a,b,c,d,x,s,ac) {
+      a = AddUnsigned(a, AddUnsigned(AddUnsigned(F(b, c, d), x), ac));
+      return AddUnsigned(RotateLeft(a, s), b);
+    }
+
+    function GG(a,b,c,d,x,s,ac) {
+      a = AddUnsigned(a, AddUnsigned(AddUnsigned(G(b, c, d), x), ac));
+      return AddUnsigned(RotateLeft(a, s), b);
+    }
+
+    function HH(a,b,c,d,x,s,ac) {
+      a = AddUnsigned(a, AddUnsigned(AddUnsigned(H(b, c, d), x), ac));
+      return AddUnsigned(RotateLeft(a, s), b);
+    }
+
+    function II(a,b,c,d,x,s,ac) {
+      a = AddUnsigned(a, AddUnsigned(AddUnsigned(I(b, c, d), x), ac));
+      return AddUnsigned(RotateLeft(a, s), b);
+    }
+
+    function ConvertToWordArray(string) {
+      var lWordCount;
+      var lMessageLength = string.length;
+      var lNumberOfWords_temp1=lMessageLength + 8;
+      var lNumberOfWords_temp2=(lNumberOfWords_temp1-(lNumberOfWords_temp1 % 64))/64;
+      var lNumberOfWords = (lNumberOfWords_temp2+1)*16;
+      var lWordArray=Array(lNumberOfWords-1);
+      var lBytePosition = 0;
+      var lByteCount = 0;
+      while ( lByteCount < lMessageLength ) {
+        lWordCount = (lByteCount-(lByteCount % 4))/4;
+        lBytePosition = (lByteCount % 4)*8;
+        lWordArray[lWordCount] = (lWordArray[lWordCount] | (string.charCodeAt(lByteCount)<<lBytePosition));
+        lByteCount++;
+      }
+      lWordCount = (lByteCount-(lByteCount % 4))/4;
+      lBytePosition = (lByteCount % 4)*8;
+      lWordArray[lWordCount] = lWordArray[lWordCount] | (0x80<<lBytePosition);
+      lWordArray[lNumberOfWords-2] = lMessageLength<<3;
+      lWordArray[lNumberOfWords-1] = lMessageLength>>>29;
+      return lWordArray;
+    }
+
+    function WordToHex(lValue) {
+      var WordToHexValue="",WordToHexValue_temp="",lByte,lCount;
+      for (lCount = 0;lCount<=3;lCount++) {
+        lByte = (lValue>>>(lCount*8)) & 255;
+        WordToHexValue_temp = "0" + lByte.toString(16);
+        WordToHexValue = WordToHexValue + WordToHexValue_temp.substr(WordToHexValue_temp.length-2,2);
+      }
+      return WordToHexValue;
+    }
+
+    function Utf8Encode(string) {
+      string = string.replace(/\r\n/g,"\n");
+      var utftext = "";
+
+      for (var n = 0; n < string.length; n++) {
+        var c = string.charCodeAt(n);
+
+        if (c < 128) {
+          utftext += String.fromCharCode(c);
+        }
+        else if((c > 127) && (c < 2048)) {
+          utftext += String.fromCharCode((c >> 6) | 192);
+          utftext += String.fromCharCode((c & 63) | 128);
+        }
+        else {
+          utftext += String.fromCharCode((c >> 12) | 224);
+          utftext += String.fromCharCode(((c >> 6) & 63) | 128);
+          utftext += String.fromCharCode((c & 63) | 128);
+        }
+      }
+      return utftext;
+    }
+
+    var x=[];
+    var k,AA,BB,CC,DD,a,b,c,d;
+    var S11=7, S12=12, S13=17, S14=22;
+    var S21=5, S22=9 , S23=14, S24=20;
+    var S31=4, S32=11, S33=16, S34=23;
+    var S41=6, S42=10, S43=15, S44=21;
+
+    string = Utf8Encode(string);
+
+    x = ConvertToWordArray(string);
+
+    a = 0x67452301; b = 0xEFCDAB89; c = 0x98BADCFE; d = 0x10325476;
+
+    for (k=0;k<x.length;k+=16) {
+      AA=a; BB=b; CC=c; DD=d;
+      a=FF(a,b,c,d,x[k+0], S11,0xD76AA478);
+      d=FF(d,a,b,c,x[k+1], S12,0xE8C7B756);
+      c=FF(c,d,a,b,x[k+2], S13,0x242070DB);
+      b=FF(b,c,d,a,x[k+3], S14,0xC1BDCEEE);
+      a=FF(a,b,c,d,x[k+4], S11,0xF57C0FAF);
+      d=FF(d,a,b,c,x[k+5], S12,0x4787C62A);
+      c=FF(c,d,a,b,x[k+6], S13,0xA8304613);
+      b=FF(b,c,d,a,x[k+7], S14,0xFD469501);
+      a=FF(a,b,c,d,x[k+8], S11,0x698098D8);
+      d=FF(d,a,b,c,x[k+9], S12,0x8B44F7AF);
+      c=FF(c,d,a,b,x[k+10],S13,0xFFFF5BB1);
+      b=FF(b,c,d,a,x[k+11],S14,0x895CD7BE);
+      a=FF(a,b,c,d,x[k+12],S11,0x6B901122);
+      d=FF(d,a,b,c,x[k+13],S12,0xFD987193);
+      c=FF(c,d,a,b,x[k+14],S13,0xA679438E);
+      b=FF(b,c,d,a,x[k+15],S14,0x49B40821);
+      a=GG(a,b,c,d,x[k+1], S21,0xF61E2562);
+      d=GG(d,a,b,c,x[k+6], S22,0xC040B340);
+      c=GG(c,d,a,b,x[k+11],S23,0x265E5A51);
+      b=GG(b,c,d,a,x[k+0], S24,0xE9B6C7AA);
+      a=GG(a,b,c,d,x[k+5], S21,0xD62F105D);
+      d=GG(d,a,b,c,x[k+10],S22,0x2441453);
+      c=GG(c,d,a,b,x[k+15],S23,0xD8A1E681);
+      b=GG(b,c,d,a,x[k+4], S24,0xE7D3FBC8);
+      a=GG(a,b,c,d,x[k+9], S21,0x21E1CDE6);
+      d=GG(d,a,b,c,x[k+14],S22,0xC33707D6);
+      c=GG(c,d,a,b,x[k+3], S23,0xF4D50D87);
+      b=GG(b,c,d,a,x[k+8], S24,0x455A14ED);
+      a=GG(a,b,c,d,x[k+13],S21,0xA9E3E905);
+      d=GG(d,a,b,c,x[k+2], S22,0xFCEFA3F8);
+      c=GG(c,d,a,b,x[k+7], S23,0x676F02D9);
+      b=GG(b,c,d,a,x[k+12],S24,0x8D2A4C8A);
+      a=HH(a,b,c,d,x[k+5], S31,0xFFFA3942);
+      d=HH(d,a,b,c,x[k+8], S32,0x8771F681);
+      c=HH(c,d,a,b,x[k+11],S33,0x6D9D6122);
+      b=HH(b,c,d,a,x[k+14],S34,0xFDE5380C);
+      a=HH(a,b,c,d,x[k+1], S31,0xA4BEEA44);
+      d=HH(d,a,b,c,x[k+4], S32,0x4BDECFA9);
+      c=HH(c,d,a,b,x[k+7], S33,0xF6BB4B60);
+      b=HH(b,c,d,a,x[k+10],S34,0xBEBFBC70);
+      a=HH(a,b,c,d,x[k+13],S31,0x289B7EC6);
+      d=HH(d,a,b,c,x[k+0], S32,0xEAA127FA);
+      c=HH(c,d,a,b,x[k+3], S33,0xD4EF3085);
+      b=HH(b,c,d,a,x[k+6], S34,0x4881D05);
+      a=HH(a,b,c,d,x[k+9], S31,0xD9D4D039);
+      d=HH(d,a,b,c,x[k+12],S32,0xE6DB99E5);
+      c=HH(c,d,a,b,x[k+15],S33,0x1FA27CF8);
+      b=HH(b,c,d,a,x[k+2], S34,0xC4AC5665);
+      a=II(a,b,c,d,x[k+0], S41,0xF4292244);
+      d=II(d,a,b,c,x[k+7], S42,0x432AFF97);
+      c=II(c,d,a,b,x[k+14],S43,0xAB9423A7);
+      b=II(b,c,d,a,x[k+5], S44,0xFC93A039);
+      a=II(a,b,c,d,x[k+12],S41,0x655B59C3);
+      d=II(d,a,b,c,x[k+3], S42,0x8F0CCC92);
+      c=II(c,d,a,b,x[k+10],S43,0xFFEFF47D);
+      b=II(b,c,d,a,x[k+1], S44,0x85845DD1);
+      a=II(a,b,c,d,x[k+8], S41,0x6FA87E4F);
+      d=II(d,a,b,c,x[k+15],S42,0xFE2CE6E0);
+      c=II(c,d,a,b,x[k+6], S43,0xA3014314);
+      b=II(b,c,d,a,x[k+13],S44,0x4E0811A1);
+      a=II(a,b,c,d,x[k+4], S41,0xF7537E82);
+      d=II(d,a,b,c,x[k+11],S42,0xBD3AF235);
+      c=II(c,d,a,b,x[k+2], S43,0x2AD7D2BB);
+      b=II(b,c,d,a,x[k+9], S44,0xEB86D391);
+      a=AddUnsigned(a,AA);
+      b=AddUnsigned(b,BB);
+      c=AddUnsigned(c,CC);
+      d=AddUnsigned(d,DD);
+    }
+
+    var temp = WordToHex(a)+WordToHex(b)+WordToHex(c)+WordToHex(d);
+
+    return temp.toLowerCase();
+  }
+};
+
+SIP.Utils = Utils;
+};
+
+},{}],32:[function(require,module,exports){
+"use strict";
+/**
+ * @fileoverview WebRTC
+ */
+
+module.exports = function (SIP, environment) {
+var WebRTC;
+
+WebRTC = {};
+
+WebRTC.MediaHandler = require('./WebRTC/MediaHandler')(SIP);
+WebRTC.MediaStreamManager = require('./WebRTC/MediaStreamManager')(SIP, environment);
+
+var _isSupported;
+
+WebRTC.isSupported = function () {
+  if (_isSupported !== undefined) {
+    return _isSupported;
+  }
+
+  WebRTC.MediaStream = environment.MediaStream;
+  WebRTC.getUserMedia = environment.getUserMedia;
+  WebRTC.RTCPeerConnection = environment.RTCPeerConnection;
+  WebRTC.RTCSessionDescription = environment.RTCSessionDescription;
+
+  if (WebRTC.RTCPeerConnection && WebRTC.RTCSessionDescription) {
+    if (WebRTC.getUserMedia) {
+      WebRTC.getUserMedia = SIP.Utils.promisify(environment, 'getUserMedia');
+    }
+    _isSupported = true;
+  }
+  else {
+    _isSupported = false;
+  }
+  return _isSupported;
+};
+
+return WebRTC;
+};
+
+},{"./WebRTC/MediaHandler":33,"./WebRTC/MediaStreamManager":34}],33:[function(require,module,exports){
+"use strict";
+/**
+ * @fileoverview MediaHandler
+ */
+
+/* MediaHandler
+ * @class PeerConnection helper Class.
+ * @param {SIP.Session} session
+ * @param {Object} [options]
+ * @param {SIP.WebRTC.MediaStreamManager} [options.mediaStreamManager]
+ *        The MediaStreamManager to acquire/release streams from/to.
+ *        If not provided, a default MediaStreamManager will be used.
+ */
+module.exports = function (SIP) {
+
+var MediaHandler = function(session, options) {
+  options = options || {};
+
+  this.logger = session.ua.getLogger('sip.invitecontext.mediahandler', session.id);
+  this.session = session;
+  this.localMedia = null;
+  this.ready = true;
+  this.mediaStreamManager = options.mediaStreamManager || new SIP.WebRTC.MediaStreamManager(this.logger);
+  this.audioMuted = false;
+  this.videoMuted = false;
+
+  // old init() from here on
+  var servers = this.prepareIceServers(options.stunServers, options.turnServers);
+  this.RTCConstraints = options.RTCConstraints || {};
+
+  this.initPeerConnection(servers, this.RTCConstraints);
+
+  function selfEmit(mh, event) {
+    if (mh.mediaStreamManager.on) {
+      mh.mediaStreamManager.on(event, function () {
+        mh.emit.apply(mh, [event].concat(Array.prototype.slice.call(arguments)));
+      });
+    }
+  }
+
+  selfEmit(this, 'userMediaRequest');
+  selfEmit(this, 'userMedia');
+  selfEmit(this, 'userMediaFailed');
+};
+
+MediaHandler.defaultFactory = function defaultFactory (session, options) {
+  return new MediaHandler(session, options);
+};
+MediaHandler.defaultFactory.isSupported = function () {
+  return SIP.WebRTC.isSupported();
+};
+
+MediaHandler.prototype = Object.create(SIP.MediaHandler.prototype, {
+// Functions the session can use
+  isReady: {writable: true, value: function isReady () {
+    return this.ready;
+  }},
+
+  close: {writable: true, value: function close () {
+    this.logger.log('closing PeerConnection');
+    this._remoteStreams = [];
+    // have to check signalingState since this.close() gets called multiple times
+    // TODO figure out why that happens
+    if(this.peerConnection && this.peerConnection.signalingState !== 'closed') {
+      this.peerConnection.close();
+
+      if(this.localMedia) {
+        this.mediaStreamManager.release(this.localMedia);
+      }
+    }
+  }},
+
+  /**
+   * @param {SIP.WebRTC.MediaStream | (getUserMedia constraints)} [mediaHint]
+   *        the MediaStream (or the constraints describing it) to be used for the session
+   */
+  getDescription: {writable: true, value: function getDescription (mediaHint) {
+    var self = this;
+    var acquire = self.mediaStreamManager.acquire;
+    if (acquire.length > 1) {
+      acquire = SIP.Utils.promisify(this.mediaStreamManager, 'acquire', true);
+    }
+    mediaHint = mediaHint || {};
+    if (mediaHint.dataChannel === true) {
+      mediaHint.dataChannel = {};
+    }
+    this.mediaHint = mediaHint;
+
+    /*
+     * 1. acquire streams (skip if MediaStreams passed in)
+     * 2. addStreams
+     * 3. createOffer/createAnswer
+     */
+
+    var streamPromise;
+    if (self.localMedia) {
+      self.logger.log('already have local media');
+      streamPromise = SIP.Utils.Promise.resolve(self.localMedia);
+    }
+    else {
+      self.logger.log('acquiring local media');
+      streamPromise = acquire.call(self.mediaStreamManager, mediaHint)
+        .then(function acquireSucceeded(streams) {
+          self.logger.log('acquired local media streams');
+          self.localMedia = streams;
+          self.session.connecting();
+          return streams;
+        }, function acquireFailed(err) {
+          self.logger.error('unable to acquire streams');
+          self.logger.error(err);
+          self.session.connecting();
+          throw err;
+        })
+        .then(this.addStreams.bind(this))
+      ;
+    }
+
+    return streamPromise
+      .then(function streamAdditionSucceeded() {
+        if (self.hasOffer('remote')) {
+          self.peerConnection.ondatachannel = function (evt) {
+            self.dataChannel = evt.channel;
+            self.emit('dataChannel', self.dataChannel);
+          };
+        } else if (mediaHint.dataChannel &&
+                   self.peerConnection.createDataChannel) {
+          self.dataChannel = self.peerConnection.createDataChannel(
+            'sipjs',
+            mediaHint.dataChannel
+          );
+          self.emit('dataChannel', self.dataChannel);
+        }
+
+        self.render();
+        return self.createOfferOrAnswer(self.RTCConstraints);
+      })
+    ;
+  }},
+
+  /**
+  * Message reception.
+  * @param {String} type
+  * @param {String} sdp
+  */
+  setDescription: {writable: true, value: function setDescription (sdp) {
+    var rawDescription = {
+      type: this.hasOffer('local') ? 'answer' : 'offer',
+      sdp: sdp
+    };
+
+    this.emit('setDescription', rawDescription);
+
+    var description = new SIP.WebRTC.RTCSessionDescription(rawDescription);
+    return SIP.Utils.promisify(this.peerConnection, 'setRemoteDescription')(description);
+  }},
+
+  /**
+   * If the Session associated with this MediaHandler were to be referred,
+   * what mediaHint should be provided to the UA's invite method?
+   */
+  getReferMedia: {writable: true, value: function getReferMedia () {
+    function hasTracks (trackGetter, stream) {
+      return stream[trackGetter]().length > 0;
+    }
+
+    function bothHaveTracks (trackGetter) {
+      /* jshint validthis:true */
+      return this.getLocalStreams().some(hasTracks.bind(null, trackGetter)) &&
+             this.getRemoteStreams().some(hasTracks.bind(null, trackGetter));
+    }
+
+    return {
+      constraints: {
+        audio: bothHaveTracks.call(this, 'getAudioTracks'),
+        video: bothHaveTracks.call(this, 'getVideoTracks')
+      }
+    };
+  }},
+
+  updateIceServers: {writeable:true, value: function (options) {
+    var servers = this.prepareIceServers(options.stunServers, options.turnServers);
+    this.RTCConstraints = options.RTCConstraints || this.RTCConstraints;
+
+    this.initPeerConnection(servers, this.RTCConstraints);
+
+    /* once updateIce is implemented correctly, this is better than above
+    //no op if browser does not support this
+    if (!this.peerConnection.updateIce) {
+      return;
+    }
+
+    this.peerConnection.updateIce({'iceServers': servers}, this.RTCConstraints);
+    */
+  }},
+
+// Functions the session can use, but only because it's convenient for the application
+  isMuted: {writable: true, value: function isMuted () {
+    return {
+      audio: this.audioMuted,
+      video: this.videoMuted
+    };
+  }},
+
+  mute: {writable: true, value: function mute (options) {
+    if (this.getLocalStreams().length === 0) {
+      return;
+    }
+
+    options = options || {
+      audio: this.getLocalStreams()[0].getAudioTracks().length > 0,
+      video: this.getLocalStreams()[0].getVideoTracks().length > 0
+    };
+
+    var audioMuted = false,
+        videoMuted = false;
+
+    if (options.audio && !this.audioMuted) {
+      audioMuted = true;
+      this.audioMuted = true;
+      this.toggleMuteAudio(true);
+    }
+
+    if (options.video && !this.videoMuted) {
+      videoMuted = true;
+      this.videoMuted = true;
+      this.toggleMuteVideo(true);
+    }
+
+    //REVISIT
+    if (audioMuted || videoMuted) {
+      return {
+        audio: audioMuted,
+        video: videoMuted
+      };
+      /*this.session.onmute({
+        audio: audioMuted,
+        video: videoMuted
+      });*/
+    }
+  }},
+
+  unmute: {writable: true, value: function unmute (options) {
+    if (this.getLocalStreams().length === 0) {
+      return;
+    }
+
+    options = options || {
+      audio: this.getLocalStreams()[0].getAudioTracks().length > 0,
+      video: this.getLocalStreams()[0].getVideoTracks().length > 0
+    };
+
+    var audioUnMuted = false,
+        videoUnMuted = false;
+
+    if (options.audio && this.audioMuted) {
+      audioUnMuted = true;
+      this.audioMuted = false;
+      this.toggleMuteAudio(false);
+    }
+
+    if (options.video && this.videoMuted) {
+      videoUnMuted = true;
+      this.videoMuted = false;
+      this.toggleMuteVideo(false);
+    }
+
+    //REVISIT
+    if (audioUnMuted || videoUnMuted) {
+      return {
+        audio: audioUnMuted,
+        video: videoUnMuted
+      };
+      /*this.session.onunmute({
+        audio: audioUnMuted,
+        video: videoUnMuted
+      });*/
+    }
+  }},
+
+  hold: {writable: true, value: function hold () {
+    this.toggleMuteAudio(true);
+    this.toggleMuteVideo(true);
+  }},
+
+  unhold: {writable: true, value: function unhold () {
+    if (!this.audioMuted) {
+      this.toggleMuteAudio(false);
+    }
+
+    if (!this.videoMuted) {
+      this.toggleMuteVideo(false);
+    }
+  }},
+
+// Functions the application can use, but not the session
+  getLocalStreams: {writable: true, value: function getLocalStreams () {
+    var pc = this.peerConnection;
+    if (pc && pc.signalingState === 'closed') {
+      this.logger.warn('peerConnection is closed, getLocalStreams returning []');
+      return [];
+    }
+    return (pc.getLocalStreams && pc.getLocalStreams()) ||
+      pc.localStreams || [];
+  }},
+
+  getRemoteStreams: {writable: true, value: function getRemoteStreams () {
+    var pc = this.peerConnection;
+    if (pc && pc.signalingState === 'closed') {
+      this.logger.warn('peerConnection is closed, getRemoteStreams returning this._remoteStreams');
+      return this._remoteStreams;
+    }
+    return(pc.getRemoteStreams && pc.getRemoteStreams()) ||
+      pc.remoteStreams || [];
+  }},
+
+  render: {writable: true, value: function render (renderHint) {
+    renderHint = renderHint || (this.mediaHint && this.mediaHint.render);
+    if (!renderHint) {
+      return false;
+    }
+    var streamGetters = {
+      local: 'getLocalStreams',
+      remote: 'getRemoteStreams'
+    };
+    Object.keys(streamGetters).forEach(function (loc) {
+      var streamGetter = streamGetters[loc];
+      var streams = this[streamGetter]();
+      SIP.WebRTC.MediaStreamManager.render(streams, renderHint[loc]);
+    }.bind(this));
+  }},
+
+// Internal functions
+  hasOffer: {writable: true, value: function hasOffer (where) {
+    var offerState = 'have-' + where + '-offer';
+    return this.peerConnection.signalingState === offerState;
+    // TODO consider signalingStates with 'pranswer'?
+  }},
+
+  prepareIceServers: {writable: true, value: function prepareIceServers (stunServers, turnServers) {
+    var servers = [],
+      config = this.session.ua.configuration;
+
+    stunServers = stunServers || config.stunServers;
+    turnServers = turnServers || config.turnServers;
+
+    [].concat(stunServers).forEach(function (server) {
+      servers.push({'urls': server});
+    });
+
+    [].concat(turnServers).forEach(function (server) {
+      servers.push({
+        'urls': server.urls,
+        'username': server.username,
+        'credential': server.password
+      });
+    });
+
+    return servers;
+  }},
+
+  initPeerConnection: {writable: true, value: function initPeerConnection(servers, RTCConstraints) {
+    var self = this,
+      config = this.session.ua.configuration;
+
+    this.onIceCompleted = SIP.Utils.defer();
+    this.onIceCompleted.promise.then(function(pc) {
+      self.emit('iceGatheringComplete', pc);
+      if (self.iceCheckingTimer) {
+        SIP.Timers.clearTimeout(self.iceCheckingTimer);
+        self.iceCheckingTimer = null;
+      }
+    });
+
+    if (this.peerConnection) {
+      this.peerConnection.close();
+    }
+
+    this.peerConnection = new SIP.WebRTC.RTCPeerConnection({'iceServers': servers}, RTCConstraints);
+
+    // Firefox (35.0.1) sometimes throws on calls to peerConnection.getRemoteStreams
+    // even if peerConnection.onaddstream was just called. In order to make
+    // MediaHandler.prototype.getRemoteStreams work, keep track of them manually
+    this._remoteStreams = [];
+
+    this.peerConnection.onaddstream = function(e) {
+      self.logger.log('stream added: '+ e.stream.id);
+      self._remoteStreams.push(e.stream);
+      self.render();
+      self.emit('addStream', e);
+    };
+
+    this.peerConnection.onremovestream = function(e) {
+      self.logger.log('stream removed: '+ e.stream.id);
+    };
+
+    this.startIceCheckingTimer = function () {
+      if (!self.iceCheckingTimer) {
+        self.iceCheckingTimer = SIP.Timers.setTimeout(function() {
+          self.logger.log('RTCIceChecking Timeout Triggered after '+config.iceCheckingTimeout+' milliseconds');
+          self.onIceCompleted.resolve(this);
+        }.bind(this.peerConnection), config.iceCheckingTimeout);
+      }
+    };
+
+    this.peerConnection.onicecandidate = function(e) {
+      self.emit('iceCandidate', e);
+      if (e.candidate) {
+        self.logger.log('ICE candidate received: '+ (e.candidate.candidate === null ? null : e.candidate.candidate.trim()));
+        self.startIceCheckingTimer();
+      } else {
+        self.onIceCompleted.resolve(this);
+      }
+    };
+
+    this.peerConnection.onicegatheringstatechange = function () {
+      self.logger.log('RTCIceGatheringState changed: ' + this.iceGatheringState);
+      if (this.iceGatheringState === 'gathering') {
+        self.emit('iceGathering', this);
+      }
+      if (this.iceGatheringState === 'complete') {
+        self.onIceCompleted.resolve(this);
+      }
+    };
+
+    this.peerConnection.oniceconnectionstatechange = function() {  //need e for commented out case
+      var stateEvent;
+
+      if (this.iceConnectionState === 'checking') {
+        self.startIceCheckingTimer();
+      }
+
+      switch (this.iceConnectionState) {
+      case 'new':
+        stateEvent = 'iceConnection';
+        break;
+      case 'checking':
+        stateEvent = 'iceConnectionChecking';
+        break;
+      case 'connected':
+        stateEvent = 'iceConnectionConnected';
+        break;
+      case 'completed':
+        stateEvent = 'iceConnectionCompleted';
+        break;
+      case 'failed':
+        stateEvent = 'iceConnectionFailed';
+        break;
+      case 'disconnected':
+        stateEvent = 'iceConnectionDisconnected';
+        break;
+      case 'closed':
+        stateEvent = 'iceConnectionClosed';
+        break;
+      default:
+        self.logger.warn('Unknown iceConnection state:', this.iceConnectionState);
+        return;
+      }
+      self.emit(stateEvent, this);
+
+      //Bria state changes are always connected -> disconnected -> connected on accept, so session gets terminated
+      //normal calls switch from failed to connected in some cases, so checking for failed and terminated
+      /*if (this.iceConnectionState === 'failed') {
+        self.session.terminate({
+        cause: SIP.C.causes.RTP_TIMEOUT,
+        status_code: 200,
+        reason_phrase: SIP.C.causes.RTP_TIMEOUT
+      });
+      } else if (e.currentTarget.iceGatheringState === 'complete' && this.iceConnectionState !== 'closed') {
+      self.onIceCompleted(this);
+      }*/
+    };
+
+    this.peerConnection.onstatechange = function() {
+      self.logger.log('PeerConnection state changed to "'+ this.readyState +'"');
+    };
+  }},
+
+  createOfferOrAnswer: {writable: true, value: function createOfferOrAnswer (constraints) {
+    var self = this;
+    var methodName;
+    var pc = self.peerConnection;
+
+    self.ready = false;
+    methodName = self.hasOffer('remote') ? 'createAnswer' : 'createOffer';
+
+    return SIP.Utils.promisify(pc, methodName, true)(constraints)
+      .then(SIP.Utils.promisify(pc, 'setLocalDescription'))
+      .then(function onSetLocalDescriptionSuccess() {
+        var deferred = SIP.Utils.defer();
+        if (pc.iceGatheringState === 'complete' && (pc.iceConnectionState === 'connected' || pc.iceConnectionState === 'completed')) {
+          deferred.resolve();
+        } else {
+          self.onIceCompleted.promise.then(deferred.resolve);
+        }
+        return deferred.promise;
+      })
+      .then(function readySuccess () {
+        var sdp = pc.localDescription.sdp;
+
+        sdp = SIP.Hacks.Chrome.needsExplicitlyInactiveSDP(sdp);
+        sdp = SIP.Hacks.AllBrowsers.unmaskDtls(sdp);
+
+        var sdpWrapper = {
+          type: methodName === 'createOffer' ? 'offer' : 'answer',
+          sdp: sdp
+        };
+
+        self.emit('getDescription', sdpWrapper);
+
+        self.ready = true;
+        return sdpWrapper.sdp;
+      })
+      .catch(function methodFailed (e) {
+        self.logger.error(e);
+        self.ready = true;
+        throw new SIP.Exceptions.GetDescriptionError(e);
+      })
+    ;
+  }},
+
+  addStreams: {writable: true, value: function addStreams (streams) {
+    try {
+      streams = [].concat(streams);
+      streams.forEach(function (stream) {
+        this.peerConnection.addStream(stream);
+      }, this);
+    } catch(e) {
+      this.logger.error('error adding stream');
+      this.logger.error(e);
+      return SIP.Utils.Promise.reject(e);
+    }
+
+    return SIP.Utils.Promise.resolve();
+  }},
+
+  toggleMuteHelper: {writable: true, value: function toggleMuteHelper (trackGetter, mute) {
+    this.getLocalStreams().forEach(function (stream) {
+      stream[trackGetter]().forEach(function (track) {
+        track.enabled = !mute;
+      });
+    });
+  }},
+
+  toggleMuteAudio: {writable: true, value: function toggleMuteAudio (mute) {
+    this.toggleMuteHelper('getAudioTracks', mute);
+  }},
+
+  toggleMuteVideo: {writable: true, value: function toggleMuteVideo (mute) {
+    this.toggleMuteHelper('getVideoTracks', mute);
+  }}
+});
+
+// Return since it will be assigned to a variable.
+return MediaHandler;
+};
+
+},{}],34:[function(require,module,exports){
+"use strict";
+/**
+ * @fileoverview MediaStreamManager
+ */
+
+/* MediaStreamManager
+ * @class Manages the acquisition and release of MediaStreams.
+ * @param {mediaHint} [defaultMediaHint] The mediaHint to use if none is provided to acquire()
+ */
+module.exports = function (SIP, environment) {
+
+// Default MediaStreamManager provides single-use streams created with getUserMedia
+var MediaStreamManager = function MediaStreamManager (logger, defaultMediaHint) {
+  if (!SIP.WebRTC.isSupported()) {
+    throw new SIP.Exceptions.NotSupportedError('Media not supported');
+  }
+
+  this.mediaHint = defaultMediaHint || {
+    constraints: {audio: true, video: true}
+  };
+
+  // map of streams to acquisition manner:
+  // true -> passed in as mediaHint.stream
+  // false -> getUserMedia
+  this.acquisitions = {};
+};
+MediaStreamManager.streamId = function (stream) {
+  return stream.getAudioTracks().concat(stream.getVideoTracks())
+    .map(function trackId (track) {
+      return track.id;
+    })
+    .join('');
+};
+
+/**
+ * @param {(Array of) MediaStream} streams - The streams to render
+ *
+ * @param {(Array of) HTMLMediaElement} elements
+ *        - The <audio>/<video> element(s) that should render the streams
+ *
+ * Each stream in streams renders to the corresponding element in elements,
+ * wrapping around elements if needed.
+ */
+MediaStreamManager.render = function render (streams, elements) {
+  if (!elements) {
+    return false;
+  }
+  if (Array.isArray(elements) && !elements.length) {
+    throw new TypeError('elements must not be empty');
+  }
+
+  function attachMediaStream(element, stream) {
+    if (typeof element.src !== 'undefined') {
+      environment.revokeObjectURL(element.src);
+      element.src = environment.createObjectURL(stream);
+    } else if (typeof (element.srcObject || element.mozSrcObject) !== 'undefined') {
+      element.srcObject = element.mozSrcObject = stream;
+    } else {
+      return false;
+    }
+
+    return true;
+  }
+
+  function ensureMediaPlaying (mediaElement) {
+    var interval = 100;
+    mediaElement.ensurePlayingIntervalId = SIP.Timers.setInterval(function () {
+      if (mediaElement.paused) {
+        mediaElement.play();
+      }
+      else {
+        SIP.Timers.clearInterval(mediaElement.ensurePlayingIntervalId);
+      }
+    }, interval);
+  }
+
+  function attachAndPlay (elements, stream, index) {
+    if (typeof elements === 'function') {
+      elements = elements();
+    }
+    var element = elements[index % elements.length];
+    (environment.attachMediaStream || attachMediaStream)(element, stream);
+    ensureMediaPlaying(element);
+  }
+
+  // [].concat "casts" `elements` into an array
+  // so forEach works even if `elements` was a single element
+  elements = [].concat(elements);
+  [].concat(streams).forEach(attachAndPlay.bind(null, elements));
+};
+
+MediaStreamManager.prototype = Object.create(SIP.EventEmitter.prototype, {
+  'acquire': {writable: true, value: function acquire (mediaHint) {
+    mediaHint = Object.keys(mediaHint || {}).length ? mediaHint : this.mediaHint;
+
+    var saveSuccess = function (isHintStream, streams) {
+      streams = [].concat(streams);
+      streams.forEach(function (stream) {
+        var streamId = MediaStreamManager.streamId(stream);
+        this.acquisitions[streamId] = !!isHintStream;
+      }, this);
+      return SIP.Utils.Promise.resolve(streams);
+    }.bind(this);
+
+    if (mediaHint.stream) {
+      return saveSuccess(true, mediaHint.stream);
+    } else {
+      // Fallback to audio/video enabled if no mediaHint can be found.
+      var constraints = mediaHint.constraints ||
+        (this.mediaHint && this.mediaHint.constraints) ||
+        {audio: true, video: true};
+
+      var deferred = SIP.Utils.defer();
+
+      /*
+       * Make the call asynchronous, so that ICCs have a chance
+       * to define callbacks to `userMediaRequest`
+       */
+      SIP.Timers.setTimeout(function () {
+        this.emit('userMediaRequest', constraints);
+
+        var emitThenCall = function (eventName, callback) {
+          var callbackArgs = Array.prototype.slice.call(arguments, 2);
+          // Emit with all of the arguments from the real callback.
+          var newArgs = [eventName].concat(callbackArgs);
+
+          this.emit.apply(this, newArgs);
+
+          return callback.apply(null, callbackArgs);
+        }.bind(this);
+
+        if (constraints.audio || constraints.video) {
+          deferred.resolve(
+            SIP.WebRTC.getUserMedia(constraints)
+            .then(
+              emitThenCall.bind(this, 'userMedia', saveSuccess.bind(null, false)),
+              emitThenCall.bind(this, 'userMediaFailed', function(e){throw e;})
+            )
+          );
+        } else {
+          // Local streams were explicitly excluded.
+          deferred.resolve([]);
+        }
+      }.bind(this), 0);
+
+      return deferred.promise;
+    }
+  }},
+
+  'release': {writable: true, value: function release (streams) {
+    streams = [].concat(streams);
+    streams.forEach(function (stream) {
+      var streamId = MediaStreamManager.streamId(stream);
+      if (this.acquisitions[streamId] === false) {
+        stream.getTracks().forEach(function (track) {
+          track.stop();
+        });
+      }
+      delete this.acquisitions[streamId];
+    }, this);
+  }},
+});
+
+// Return since it will be assigned to a variable.
+return MediaStreamManager;
+};
+
+},{}],35:[function(require,module,exports){
+(function (global){
+"use strict";
+
+var toplevel = global.window || global;
+
+function getPrefixedProperty (object, name) {
+  if (object == null) {
+    return;
+  }
+  var capitalizedName = name.charAt(0).toUpperCase() + name.slice(1);
+  var prefixedNames = [name, 'webkit' + capitalizedName, 'moz' + capitalizedName];
+  for (var i in prefixedNames) {
+    var property = object[prefixedNames[i]];
+    if (property) {
+      return property.bind(object);
+    }
+  }
+}
+
+module.exports = {
+  WebSocket: toplevel.WebSocket,
+  Transport: require('./Transport'),
+  open: toplevel.open,
+  Promise: toplevel.Promise,
+  timers: toplevel,
+
+  // Console is not defined in ECMAScript, so just in case...
+  console: toplevel.console || {
+    debug: function () {},
+    log: function () {},
+    warn: function () {},
+    error: function () {}
+  },
+
+  MediaStream: getPrefixedProperty(toplevel, 'MediaStream'),
+  getUserMedia: getPrefixedProperty(toplevel.navigator, 'getUserMedia'),
+  RTCPeerConnection: getPrefixedProperty(toplevel, 'RTCPeerConnection'),
+  RTCSessionDescription: getPrefixedProperty(toplevel, 'RTCSessionDescription'),
+
+  addEventListener: getPrefixedProperty(toplevel, 'addEventListener'),
+  HTMLMediaElement: toplevel.HTMLMediaElement,
+
+  attachMediaStream: toplevel.attachMediaStream,
+  createObjectURL: toplevel.URL && toplevel.URL.createObjectURL,
+  revokeObjectURL: toplevel.URL && toplevel.URL.revokeObjectURL
+};
+
+}).call(this,typeof global !== "undefined" ? global : typeof self !== "undefined" ? self : typeof window !== "undefined" ? window : {})
+},{"./Transport":28}],36:[function(require,module,exports){
+"use strict";
+module.exports = require('./SIP')(require('./environment'));
+
+},{"./SIP":19,"./environment":35}]},{},[36])(36)
+});
\ No newline at end of file
diff --git a/bbb-common-message/src/main/java/org/bigbluebutton/common/messages/MeetingEndingMessage.java b/bbb-common-message/src/main/java/org/bigbluebutton/common/messages/MeetingEndingMessage.java
new file mode 100644
index 0000000000000000000000000000000000000000..03881993e94bc882cf859ec9edddccbfcbe4d9f1
--- /dev/null
+++ b/bbb-common-message/src/main/java/org/bigbluebutton/common/messages/MeetingEndingMessage.java
@@ -0,0 +1,48 @@
+package org.bigbluebutton.common.messages;
+
+import java.util.HashMap;
+
+import com.google.gson.JsonObject;
+import com.google.gson.JsonParser;
+
+public class MeetingEndingMessage implements ISubscribedMessage {
+	public static final String MEETING_ENDING = "meeting_ending_message";
+	public final String VERSION = "0.0.1";
+
+	public final String meetingId;
+
+	public MeetingEndingMessage(String meetingID) {
+		this.meetingId = meetingID;
+	}
+
+	public String toJson() {
+		HashMap<String, Object> payload = new HashMap<String, Object>();
+		payload.put(Constants.MEETING_ID, meetingId);
+
+		java.util.HashMap<String, Object> header = MessageBuilder.buildHeader(MEETING_ENDING, VERSION, null);
+
+		return MessageBuilder.buildJson(header, payload);
+	}
+	public static MeetingEndingMessage fromJson(String message) {
+		JsonParser parser = new JsonParser();
+		JsonObject obj = (JsonObject) parser.parse(message);
+
+		if (obj.has("header") && obj.has("payload")) {
+			JsonObject header = (JsonObject) obj.get("header");
+			JsonObject payload = (JsonObject) obj.get("payload");
+
+			if (header.has("name")) {
+				String messageName = header.get("name").getAsString();
+				if (MEETING_ENDING.equals(messageName)) {
+					if (payload.has(Constants.MEETING_ID)) {
+						String meetingId = payload.get(Constants.MEETING_ID).getAsString();
+
+						return new 	MeetingEndingMessage(meetingId);
+					}
+				}
+			}
+		}
+
+		return null;
+	}
+}
diff --git a/bigbluebutton-apps/src/main/java/org/bigbluebutton/red5/client/MeetingClientMessageSender.java b/bigbluebutton-apps/src/main/java/org/bigbluebutton/red5/client/MeetingClientMessageSender.java
index 8bec181d5d93de419466081c9edeeb9055f2c0ed..4b632fda5e25b6877992a83ed487ff70b163152d 100755
--- a/bigbluebutton-apps/src/main/java/org/bigbluebutton/red5/client/MeetingClientMessageSender.java
+++ b/bigbluebutton-apps/src/main/java/org/bigbluebutton/red5/client/MeetingClientMessageSender.java
@@ -7,6 +7,7 @@ import org.bigbluebutton.common.messages.Constants;
 import org.bigbluebutton.common.messages.DisconnectAllUsersMessage;
 import org.bigbluebutton.common.messages.DisconnectUserMessage;
 import org.bigbluebutton.common.messages.MeetingEndedMessage;
+import org.bigbluebutton.common.messages.MeetingEndingMessage;
 import org.bigbluebutton.common.messages.MeetingHasEndedMessage;
 import org.bigbluebutton.common.messages.MeetingMutedMessage;
 import org.bigbluebutton.common.messages.MeetingStateMessage;
@@ -58,6 +59,12 @@ public class MeetingClientMessageSender {
 						  processMeetingEndedMessage(mem);
 					  }
 					  break; 
+				  case MeetingEndingMessage.MEETING_ENDING:
+					  MeetingEndingMessage me = MeetingEndingMessage.fromJson(message);
+					  if (me != null) {
+						  processMeetingEndingMessage(me);
+					  }
+					  break;
 				  case MeetingHasEndedMessage.MEETING_HAS_ENDED:
 					  MeetingHasEndedMessage mhem = MeetingHasEndedMessage.fromJson(message);
 					  if (mhem != null) {
@@ -162,6 +169,18 @@ public class MeetingClientMessageSender {
 	  	service.sendMessage(m); 
 	}
 	
+	private void processMeetingEndingMessage(MeetingEndingMessage msg) {
+		Map<String, Object> args = new HashMap<String, Object>();
+		args.put("status", "Meeting is ending.");
+
+		Map<String, Object> message = new HashMap<String, Object>();
+		Gson gson = new Gson();
+		message.put("msg", gson.toJson(args));
+
+		BroadcastClientMessage m = new BroadcastClientMessage(msg.meetingId, "meetingEnding", message);
+		service.sendMessage(m);
+	}
+
 	private void processDisconnectAllUsersMessage(DisconnectAllUsersMessage msg) {
 		DisconnectAllClientsMessage dm = new DisconnectAllClientsMessage(msg.meetingId);
 		service.sendMessage(dm);	  	 
diff --git a/bigbluebutton-client/src/org/bigbluebutton/common/CustomMdiWindow.as b/bigbluebutton-client/src/org/bigbluebutton/common/CustomMdiWindow.as
index 2454ba9a208ebf7e13df369fe1c1dc2d09857d90..fc887d64fe5570e2d91fd34c7f7cf56c813a58c2 100644
--- a/bigbluebutton-client/src/org/bigbluebutton/common/CustomMdiWindow.as
+++ b/bigbluebutton-client/src/org/bigbluebutton/common/CustomMdiWindow.as
@@ -40,10 +40,20 @@ package org.bigbluebutton.common
         MDIManager.CONTEXT_MENU_LABEL_CASCADE,
         MDIManager.CONTEXT_MENU_LABEL_SHOW_ALL );
         
+    private static const LOCKABLE_MENU_ITEMS:Array = new Array(
+      MDIWindow.CONTEXT_MENU_LABEL_MINIMIZE,
+      MDIWindow.CONTEXT_MENU_LABEL_MAXIMIZE,
+      MDIWindow.CONTEXT_MENU_LABEL_RESTORE);
+
     private var _customContextMenuItems:Array = null;
+
+    private var _unlocked:Boolean = true;
     
     private function filterContextMenu(item:*, index:int, array:Array):Boolean {
-      return IGNORED_MENU_ITEMS.indexOf(item.caption) < 0;
+      var filter:Boolean = this._unlocked ?
+          IGNORED_MENU_ITEMS.indexOf(item.caption) < 0 :
+          IGNORED_MENU_ITEMS.indexOf(item.caption) < 0 && LOCKABLE_MENU_ITEMS.indexOf(item.caption) < 0;
+      return filter;
     }
     
     override public function updateContextMenu():void {
@@ -87,5 +97,25 @@ package org.bigbluebutton.common
       
       updateContextMenu();
     }
+
+    public function get unlocked():Boolean {
+      return this._unlocked;
+    }
+
+    public function set unlocked(value:Boolean):void {
+      this._unlocked
+          = this.draggable
+          = this.resizable
+          = this.titleBarOverlay.includeInLayout
+          = this.titleBarOverlay.enabled
+          = this.titleBarOverlay.visible
+          = value;
+
+      if (!this.minimized) {
+        this.showControls = this._unlocked;
+      }
+
+      updateContextMenu();
+    }
   }
 }
diff --git a/bigbluebutton-client/src/org/bigbluebutton/main/model/users/BBBUser.as b/bigbluebutton-client/src/org/bigbluebutton/main/model/users/BBBUser.as
index 4623400cb483865f569433bb08d5bfb5a35a9731..6f1a8e7dfd23d1f267fa8ad461e958fe59b1d97d 100755
--- a/bigbluebutton-client/src/org/bigbluebutton/main/model/users/BBBUser.as
+++ b/bigbluebutton-client/src/org/bigbluebutton/main/model/users/BBBUser.as
@@ -290,12 +290,6 @@ package org.bigbluebutton.main.model.users
     }
 
 		public function changeStatus(status:Status):void {
-			if (status.name == "presenter") {
-				presenter=(status.value.toString().toUpperCase() == "TRUE") ? true : false;
-
-				//As the lock settings are now not applied to presenters, when the presenter flag is changed, we need to apply the lock settings
-				applyLockSettings();
-			}
 			switch (status.name) {
 				case "presenter":
 					presenter=(status.value.toString().toUpperCase() == "TRUE") ? true : false;
diff --git a/bigbluebutton-client/src/org/bigbluebutton/main/model/users/Conference.as b/bigbluebutton-client/src/org/bigbluebutton/main/model/users/Conference.as
index 8cbf366250d46ee6034e9d0a19990ce438dfc6be..c6b8a1202250a839c72ab9e469503aca87a9e3c9 100755
--- a/bigbluebutton-client/src/org/bigbluebutton/main/model/users/Conference.as
+++ b/bigbluebutton-client/src/org/bigbluebutton/main/model/users/Conference.as
@@ -276,6 +276,7 @@ package org.bigbluebutton.main.model.users {
 		
 		public function set amIPresenter(presenter:Boolean):void {
 			me.presenter = presenter;
+			applyLockSettings();
 		}
 		
 		[Bindable]
diff --git a/bigbluebutton-client/src/org/bigbluebutton/modules/layout/managers/LayoutManager.as b/bigbluebutton-client/src/org/bigbluebutton/modules/layout/managers/LayoutManager.as
index a28d5a8fa1263fc709efd75ede1665e793a46012..65889d7e5cc56564d3a9251cfce31f5ca8b10a53 100644
--- a/bigbluebutton-client/src/org/bigbluebutton/modules/layout/managers/LayoutManager.as
+++ b/bigbluebutton-client/src/org/bigbluebutton/modules/layout/managers/LayoutManager.as
@@ -35,11 +35,13 @@ package org.bigbluebutton.modules.layout.managers
   
   import org.as3commons.logging.api.ILogger;
   import org.as3commons.logging.api.getClassLogger;
+  import org.bigbluebutton.common.CustomMdiWindow;
   import org.bigbluebutton.core.UsersUtil;
   import org.bigbluebutton.core.events.SwitchedLayoutEvent;
   import org.bigbluebutton.core.managers.UserManager;
   import org.bigbluebutton.main.model.LayoutOptions;
   import org.bigbluebutton.modules.layout.events.LayoutFromRemoteEvent;
+  import org.bigbluebutton.main.model.users.BBBUser;
   import org.bigbluebutton.modules.layout.events.LayoutLockedEvent;
   import org.bigbluebutton.modules.layout.events.LayoutsLoadedEvent;
   import org.bigbluebutton.modules.layout.events.LayoutsReadyEvent;
@@ -239,6 +241,12 @@ package org.bigbluebutton.modules.layout.managers
       _locked = e.locked;
       checkPermissionsOverWindow();
     }
+
+    public function lockSettingsChanged():void {
+      var myUser:BBBUser = UserManager.getInstance().getConference().getMyUser();
+      _locked = myUser.lockedLayout;
+      checkPermissionsOverWindow();
+    }
     
 		public function applyRemoteLayout(e:LayoutFromRemoteEvent):void {
 			var layout:LayoutDefinition = e.layout;
@@ -265,14 +273,9 @@ package org.bigbluebutton.modules.layout.managers
 		}
 		
 		private function checkPermissionsOverWindow(window:MDIWindow=null):void {
-			if (window != null) {
-				if (!UserManager.getInstance().getConference().amIModerator()
-						&& !LayoutDefinition.ignoreWindow(window)) {
-					window.draggable 
-							= window.resizable
-							= window.showControls
-							= !_locked;
-				}
+			if (UsersUtil.amIModerator()) return;
+			if (window != null && !LayoutDefinition.ignoreWindow(window)) {
+				(window as CustomMdiWindow).unlocked = !_locked;
 			} else {
 				for each (window in _canvas.windowManager.windowList) {
 					checkPermissionsOverWindow(window);
diff --git a/bigbluebutton-client/src/org/bigbluebutton/modules/layout/maps/LayoutEventMap.mxml b/bigbluebutton-client/src/org/bigbluebutton/modules/layout/maps/LayoutEventMap.mxml
index 877b027688c6870fc7e5219023a578c3d9107bb4..3482f9b7cb66225b63f87952cc2f725e296cf27e 100755
--- a/bigbluebutton-client/src/org/bigbluebutton/modules/layout/maps/LayoutEventMap.mxml
+++ b/bigbluebutton-client/src/org/bigbluebutton/modules/layout/maps/LayoutEventMap.mxml
@@ -24,6 +24,7 @@ with BigBlueButton; if not, see <http://www.gnu.org/licenses/>.
       import mx.events.FlexEvent;
       
       import org.bigbluebutton.core.EventConstants;
+      import org.bigbluebutton.core.events.LockControlEvent;
       import org.bigbluebutton.main.events.MadePresenterEvent;
       import org.bigbluebutton.modules.layout.events.ChangeLayoutEvent;
       import org.bigbluebutton.modules.layout.events.ComboBoxCreatedEvent;
@@ -116,6 +117,10 @@ with BigBlueButton; if not, see <http://www.gnu.org/licenses/>.
     <MethodInvoker generator="{LayoutManager}" method="remoteSyncLayout" arguments="{event}"/>
   </EventHandlers>
 
+  <EventHandlers type="{LockControlEvent.CHANGED_LOCK_SETTINGS}">
+    <MethodInvoker generator="{LayoutManager}" method="lockSettingsChanged"/>
+  </EventHandlers>
+
   <Injectors target="{LayoutService}">
     <PropertyInjector targetKey="receiver" source="{MessageReceiver}"/>
     <PropertyInjector targetKey="sender" source="{MessageSender}"/>
diff --git a/bigbluebutton-client/src/org/bigbluebutton/modules/layout/services/MessageReceiver.as b/bigbluebutton-client/src/org/bigbluebutton/modules/layout/services/MessageReceiver.as
index 090a7abd7acaa3b4e26d60cec901b67ac481a86c..74d2e90a90156d02b917c564a5f59e72c1eafa88 100755
--- a/bigbluebutton-client/src/org/bigbluebutton/modules/layout/services/MessageReceiver.as
+++ b/bigbluebutton-client/src/org/bigbluebutton/modules/layout/services/MessageReceiver.as
@@ -66,10 +66,10 @@ package org.bigbluebutton.modules.layout.services
 	  if(message.layout == "" || UsersUtil.amIModerator())
 		  _dispatcher.dispatchEvent(new LayoutEvent(LayoutEvent.APPLY_DEFAULT_LAYOUT_EVENT));
 	  else {
-      	lockLayout(message.locked, message.setById);
       	handleSyncLayout(message);
 	  }
 	  
+      handleLayoutLocked(message);
       _dispatcher.dispatchEvent(new ModuleLoadEvent(ModuleLoadEvent.LAYOUT_MODULE_STARTED));
     }
  
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 4db0aa50849a064123d7b298f9eec45d5bf2a67a..cc55686dc9fec847d40e12b183b2be979ef1ad3a 100755
--- a/bigbluebutton-client/src/org/bigbluebutton/modules/users/services/MessageReceiver.as
+++ b/bigbluebutton-client/src/org/bigbluebutton/modules/users/services/MessageReceiver.as
@@ -76,6 +76,9 @@ package org.bigbluebutton.modules.users.services
         case "meetingEnded":
           handleLogout(message);
           break;
+        case "meetingEnding":
+          handleMeetingEnding(message);
+          break;
         case "meetingHasEnded":
           handleMeetingHasEnded(message);
           break;
@@ -453,6 +456,15 @@ package org.bigbluebutton.modules.users.services
       dispatcher.dispatchEvent(endMeetingEvent);
     }
     
+    /**
+     * This meeting is in the process of ending by the server
+     */
+    public function handleMeetingEnding(msg:Object):void {
+      // Avoid trying to reconnect
+      var endMeetingEvent:BBBEvent = new BBBEvent(BBBEvent.CANCEL_RECONNECTION_EVENT);
+      dispatcher.dispatchEvent(endMeetingEvent);
+    }
+
     private function handleGetUsersReply(msg:Object):void {    
       var map:Object = JSON.parse(msg.msg);
       var users:Object = map.users as Array;
diff --git a/bigbluebutton-client/src/org/bigbluebutton/modules/videoconf/maps/VideoEventMapDelegate.as b/bigbluebutton-client/src/org/bigbluebutton/modules/videoconf/maps/VideoEventMapDelegate.as
index 6cb0b79b80a0374474f7091162218445cf5f89f9..02c7732c3087f1420fd8ab34d0b12610c7d3ee83 100755
--- a/bigbluebutton-client/src/org/bigbluebutton/modules/videoconf/maps/VideoEventMapDelegate.as
+++ b/bigbluebutton-client/src/org/bigbluebutton/modules/videoconf/maps/VideoEventMapDelegate.as
@@ -198,9 +198,10 @@ package org.bigbluebutton.modules.videoconf.maps
 
     private function skipCameraSettingsCheck(camIndex:int = -1):void {
       if (camIndex == -1) {
-        var cam:Camera = changeDefaultCamForMac();
+        var cam:Camera = getDefaultCamera();
         if (cam == null) {
-          cam = Camera.getCamera();
+          LOGGER.debug("VideoEventMapDelegate:: Could not find a default camera");
+          return;
         }
         camIndex = cam.index;
       }
@@ -209,6 +210,16 @@ package org.bigbluebutton.modules.videoconf.maps
       initCameraWithSettings(camIndex, videoProfile);
     }
 
+    private function getDefaultCamera():Camera {
+      var cam:Camera = null;
+      cam = changeDefaultCamForMac();
+      if (cam == null) {
+        cam = Camera.getCamera();
+      }
+
+      return cam;
+    }
+
     private function openWebcamWindows():void {
 		LOGGER.debug("VideoEventMapDelegate:: [{0}] openWebcamWindows. ready = [{1}]", [me, _ready]);
 
@@ -282,7 +293,7 @@ package org.bigbluebutton.modules.videoconf.maps
     }
 
     private function openViewWindowFor(userID:String):void {
-      if (!proxy.connection.connected) {
+      if (!proxy.connection.connected || hasWindow(userID)) {
         return;
       }
       
@@ -318,7 +329,7 @@ package org.bigbluebutton.modules.videoconf.maps
 
       _dispatcher.dispatchEvent(broadcastEvent);
 	  if (proxy.videoOptions.showButton) {
-		  button.publishingStatus(button.START_PUBLISHING);
+		  button.callLater(button.publishingStatus, [button.START_PUBLISHING]);
 	  }
     }
 
@@ -380,7 +391,7 @@ package org.bigbluebutton.modules.videoconf.maps
     public function handleShareCameraRequestEvent(event:ShareCameraRequestEvent):void {
 		LOGGER.debug("[VideoEventMapDelegate:handleShareCameraRequestEvent]");
 		if (options.skipCamSettingsCheck) {
-			skipCameraSettingsCheck(int(event.defaultCamera));
+			skipCameraSettingsCheck();
 		} else {
 			openWebcamPreview(event.publishInClient, event.defaultCamera, event.camerasArray);
 		}
diff --git a/bigbluebutton-client/src/org/bigbluebutton/modules/videoconf/views/GraphicsWrapper.as b/bigbluebutton-client/src/org/bigbluebutton/modules/videoconf/views/GraphicsWrapper.as
index 423ebfc68af43f9a265eabf0b64b630d5240519e..67c1cf9e735851724eea0eeb919cb885a93e7a53 100644
--- a/bigbluebutton-client/src/org/bigbluebutton/modules/videoconf/views/GraphicsWrapper.as
+++ b/bigbluebutton-client/src/org/bigbluebutton/modules/videoconf/views/GraphicsWrapper.as
@@ -45,36 +45,41 @@ package org.bigbluebutton.modules.videoconf.views
             return result;
         }
 
-        private function calculateCellDimensions(canvasWidth:int, canvasHeight:int, numColumns:int, numRows:int):Object {
+        private function calculateCellDimensions(canvasWidth:int, canvasHeight:int, numColumns:int, numRows:int, priority:Boolean):Object {
             var obj:Object = {
                 width: Math.floor(canvasWidth / numColumns)-5,
                 height: Math.floor(canvasHeight / numRows)-5
             }
-            if (obj.width / obj.height > _minContentAspectRatio) {
-                obj.width = Math.floor(obj.height * _minContentAspectRatio);
+
+            var item:UserGraphicHolder = priorityItem as UserGraphicHolder;
+            var aspectRatio:Number = (item != null && priority) ? item.contentAspectRatio : _minContentAspectRatio;
+            obj.cellAspectRatio = aspectRatio;
+
+            if (obj.width / obj.height > aspectRatio) {
+                obj.width = Math.floor(obj.height * aspectRatio);
             } else {
-                obj.height = Math.floor(obj.width / _minContentAspectRatio);
+                obj.height = Math.floor(obj.width / aspectRatio);
             }
             return obj;
         }
 
-        private function calculateOccupiedArea(canvasWidth:int, canvasHeight:int, numColumns:int, numRows:int):Object {
-            var obj:Object = calculateCellDimensions(canvasWidth, canvasHeight, numColumns, numRows);
+        private function calculateOccupiedArea(canvasWidth:int, canvasHeight:int, numColumns:int, numRows:int, priority:Boolean):Object {
+            var obj:Object = calculateCellDimensions(canvasWidth, canvasHeight, numColumns, numRows, priority);
             obj.occupiedArea = obj.width * obj.height * numChildren;
             obj.numColumns = numColumns;
             obj.numRows = numRows;
-            obj.cellAspectRatio = _minContentAspectRatio;
+
             return obj;
         }
 
-        private function findBestConfiguration(canvasWidth:int, canvasHeight:int, numChildrenInCanvas:int):Object {
+        private function findBestConfiguration(canvasWidth:int, canvasHeight:int, numChildrenInCanvas:int, priority:Boolean = false):Object {
             var bestConfiguration:Object = {
                 occupiedArea: 0
             }
 
             for (var numColumns:int = 1; numColumns <= numChildrenInCanvas; ++numColumns) {
                 var numRows:int = Math.ceil(numChildrenInCanvas / numColumns);
-                var currentConfiguration:Object = calculateOccupiedArea(canvasWidth, canvasHeight, numColumns, numRows);
+                var currentConfiguration:Object = calculateOccupiedArea(canvasWidth, canvasHeight, numColumns, numRows, priority);
                 if (currentConfiguration.occupiedArea > bestConfiguration.occupiedArea) {
                     bestConfiguration = currentConfiguration;
                 }
@@ -132,8 +137,8 @@ package org.bigbluebutton.modules.videoconf.views
             var oBestConf:Object = pBestConf;
             var isVertSplit:Boolean = false;
             if (numChildren > 1){
-                var pBestConfVer:Object = findBestConfiguration(Math.floor(unscaledWidth * priorityWeight), unscaledHeight, 1);
-                var pBestConfHor:Object = findBestConfiguration(unscaledWidth, Math.floor(unscaledHeight * priorityWeight), 1);
+                var pBestConfVer:Object = findBestConfiguration(Math.floor(unscaledWidth * priorityWeight), unscaledHeight, 1, true);
+                var pBestConfHor:Object = findBestConfiguration(unscaledWidth, Math.floor(unscaledHeight * priorityWeight), 1, true);
                 isVertSplit = (pBestConfVer.occupiedArea > pBestConfHor.occupiedArea);
                 if (isVertSplit) {
                     pBestConf = pBestConfVer;
@@ -174,13 +179,8 @@ package org.bigbluebutton.modules.videoconf.views
             var item:UserGraphicHolder = priorityItem as UserGraphicHolder;
 
             // set size and position of the prioritized video
-            if (item.contentAspectRatio > _minContentAspectRatio) {
-                itemWidth = pWidth;
-                itemHeight = Math.floor(pWidth / item.contentAspectRatio);
-            } else {
-                itemHeight = pHeight;
-                itemWidth = Math.floor(pHeight * item.contentAspectRatio);
-            }
+            itemWidth = pWidth;
+            itemHeight = pHeight;
 
             if (bestConf.isVertSplit) {
                 blockX = Math.floor((3*(unscaledWidth - oWidth*numColumns) + itemWidth)/4);
diff --git a/bigbluebutton-client/src/org/bigbluebutton/modules/videoconf/views/ToolbarPopupButton.mxml b/bigbluebutton-client/src/org/bigbluebutton/modules/videoconf/views/ToolbarPopupButton.mxml
index 106a4f1dc40db5f9621d6bb648fde8acde21a985..00fb0c3e2f3e9643da218c01bcb1d491d09ee20f 100755
--- a/bigbluebutton-client/src/org/bigbluebutton/modules/videoconf/views/ToolbarPopupButton.mxml
+++ b/bigbluebutton-client/src/org/bigbluebutton/modules/videoconf/views/ToolbarPopupButton.mxml
@@ -22,7 +22,8 @@ with BigBlueButton; if not, see <http://www.gnu.org/licenses/>.
 
 <mx:PopUpButton xmlns:mx="http://www.adobe.com/2006/mxml" styleName="webcamDefaultButtonStyle"
 		   xmlns:mate="http://mate.asfusion.com/"
-		   click="openPublishWindow()" creationComplete="init()" 
+		   click="openPublishWindow()"
+		   initialize="init()"
 		   mouseOver = "mouseOverHandler(event)"
 		   mouseOut = "mouseOutHandler(event)"
            height="24"
diff --git a/bigbluebutton-config/bin/bbb-conf b/bigbluebutton-config/bin/bbb-conf
index c023f7af50eca406f1119d0a35bac2f1f48f3de1..948e21e3b0b78d41d230c9548c91c1d6a434a9a4 100755
--- a/bigbluebutton-config/bin/bbb-conf
+++ b/bigbluebutton-config/bin/bbb-conf
@@ -145,12 +145,10 @@ VOICE_CONFERENCE="bbb-voice-freeswitch.xml"
 # Determine IP so it works on multilingual installations
 #
 
-if [[ `sudo ifconfig` == *venet0:0* ]]; then
-	# IP detection for OpenVZ environment
-	IP=$(ifconfig | grep -v '127.0.0.1' | grep -E "[0-9]*\.[0-9]*\.[0-9]*\.[0-9]*" | tail -1 | cut -d: -f2 | awk '{ print $1}')
+if ifconfig eth0 > /dev/null 2>&1; then
+	IP=$(LANG=c ifconfig eth0 | grep "inet addr" | awk -F: '{print $2}' | awk '{print $1}')
 else
-	# IP detection for regular environment
-	IP=$(ifconfig | grep -v '127.0.0.1' | grep -E "[0-9]*\.[0-9]*\.[0-9]*\.[0-9]*" | head -1 | cut -d: -f2 | awk '{ print $1}')
+	IP=$(ifconfig | grep -v '127.0.0.1' | grep -E "[0-9]*\.[0-9]*\.[0-9]*\.[0-9]*" | tail -1 | cut -d: -f2 | awk '{ print $1}')
 fi
 
 MEM=`free -m | grep Mem | awk '{ print $2}'`
@@ -404,6 +402,50 @@ start_bigbluebutton () {
         #        exit 1
 	#fi
     fi
+
+        #
+        # Look for Starting up context to be recently added to the log files
+        #
+        COUNT=0
+        while [ $COUNT -lt 20 ]; do
+          let COUNT=COUNT+1
+          if [ -f $RED5_DIR/log/sip.log ] && [ -f $RED5_DIR/log/bigbluebutton.log ] && [ -f $RED5_DIR/log/red5.log ] && [ -f $RED5_DIR/log/video.log ] && [ -f $RED5_DIR/log/screenshare-slf.log ]; then
+            let COUNT=20
+          else
+            echo -n "."
+            sleep 1
+          fi
+        done
+
+
+	#
+	# All the log files exist, now check for the text "Starting up context"
+	#
+	COUNT=0
+	while [ $COUNT -lt 20 ]; do
+	  let COUNT=COUNT+1
+	  if [ -f $RED5_DIR/log/video.log ]; then
+	    if ! cat $RED5_DIR/log/video.log | tail -n100 | grep -q "Starting up context"; then
+	      echo -n "."
+	      sleep 1
+	    else
+	      let COUNT=20
+            fi
+	  fi
+	done
+
+	echo
+	BBB_APPS="sip video bigbluebutton screenshare-slf"
+	for bbb_app in $BBB_APPS ; do
+	        if [ -a $RED5_DIR/log/$bbb_app.log ]; then
+	                if ! grep -q "Starting up context" $RED5_DIR/log/$bbb_app.log; then
+	                        echo "#    $bbb_app may not have started properly"
+	                fi
+	        else
+	                echo "#    $RED5_DIR/log/$bbb_app.log not found"
+	        fi
+	done
+
 }
 
 display_bigbluebutton_status () {
@@ -1040,21 +1082,18 @@ check_state() {
 	#
 	# Checking red5 apps log
 	#
-	COUNT=0
-	while [ $COUNT -lt 20 ]; do
-    	  let COUNT=COUNT+1
+
+        # Give the files a chance to be created (in case we got started with a clean)
+        COUNT=0
+        while [ $COUNT -lt 20 ]; do
+          let COUNT=COUNT+1
           if [ -f $RED5_DIR/log/sip.log ] && [ -f $RED5_DIR/log/bigbluebutton.log ] && [ -f $RED5_DIR/log/red5.log ] && [ -f $RED5_DIR/log/video.log ] && [ -f $RED5_DIR/log/screenshare-slf.log ]; then
-            if [ $COUNT -gt 1 ]; then
-              sleep 5		# Looks like a clean restart -- give red5 a bit more time to startup 
-            fi
-	    let COUNT=20
+            let COUNT=20
           else
             echo -n "."
             sleep 1
           fi
-	done
-
-        echo -n " "
+        done
 
 	RED5_LOG_FILES="bigbluebutton red5 sip video screenshare-slf"
 	AVAIL_RED5_LOG=""
@@ -1068,35 +1107,38 @@ check_state() {
 	done
 
 	if [ "$UNAVAIL_RED5_LOG" != "" ]; then
+		echo "# Error: Red5 log files not found"
+		echo
 		echo "# Unavailable red5 logs ($RED5_DIR/log): $UNAVAIL_RED5_LOG"
+		echo
 	fi
 
+	#
+	# Check FreeSWITCH
+	#
+
+	if grep -q "Thread ended for mod_event_socket" /opt/freeswitch/var/log/freeswitch/freeswitch.log; then
+		echo
+		echo "# Error: Found text in freeswitch.log:"
+		echo "#"
+		echo "#    Thread ended for mod_event_socket"
+		echo "#"
+		echo "# FreeSWITCH may not be responding to requests on port 8021 (event socket layer)"
+		echo "# and users may have errors joining audio."
+		echo "#"
+	fi
 
 	#
-	# Check if any of the red5 BigBlueButton applications did not start properly
+	# Check FreeSWITCH
 	#
-	COUNT=0
-	while [ $COUNT -lt 20 ]; do
-    	  let COUNT=COUNT+1
-          if ! cat $RED5_DIR/log/bigbluebutton.log | tail -n10 | grep -q "Starting up context"; then
-            echo -n "."
-            sleep 1
-	  else
-	    let COUNT=20
-          fi
-	done
-        echo
 
-	BBB_APPS="sip video bigbluebutton screenshare-slf"
-	for bbb_app in $BBB_APPS ; do
-		if [ -a $RED5_DIR/log/$bbb_app.log ]; then
-			if cat $RED5_DIR/log/$bbb_app.log | tail -n1 | grep -q "Starting up context"; then
-				echo "#    $bbb_app did not start properly"
-			fi
-		else
-			echo "#    $RED5_DIR/log/$bbb_app.log not found"
-		fi
-	done
+	if ! echo "/quit" | /opt/freeswitch/bin/fs_cli - > /dev/null 2>&1; then
+		echo
+                echo "#"
+                echo "# Error: Unable to connect to the FreeSWITCH Event Socket Layer on port 8021"
+                echo "#"
+	fi
+
 
 	#
 	# Check for required external commands
@@ -1134,7 +1176,7 @@ check_state() {
 		# Checking if voice app registered successfully
 		#
         	if cat /usr/share/red5/log/sip.log | grep -q "Failed to register with Sip Server"; then
-                	echo "# Error: The voice application failed to register with the sip server."
+                	echo "# Warning: The voice application may not have registered with the sip server."
                 	echo "#   Try running: "
                 	echo "#"
                 	echo "#      sudo bbb-conf --clean"
@@ -1151,7 +1193,8 @@ check_state() {
         		echo 
 		fi
 	else
-		echo "# Error: /usr/share/red5/log/sip.log"
+		echo "# Error: The following log file was not found"
+		echo "#   /usr/share/red5/log/sip.log "
         	echo 
         fi
 
@@ -1169,7 +1212,7 @@ check_state() {
 	if [ -f $RED5_DIR/log/bigbluebutton.log ]; then
 		BBB_RED5_LOG=$(stat -c%s $RED5_DIR/log/bigbluebutton.log)
 		if [ $BBB_RED5_LOG -lt 100 ]; then
-        		echo "#       bigbluebutton failed to start: $RED5_DIR/log/bigbluebutton.log (red5)"
+        		echo "#       Log file too short (less than 100 lines): $RED5_DIR/log/bigbluebutton.log (red5)"
 		fi
 	else
 		echo "#      No $RED5_DIR/log/bigbluebutton.log"
@@ -1318,7 +1361,6 @@ check_state() {
                 echo
 	fi
 
-	
 	if [ -f ${SERVLET_DIR}/demo/demo1.jsp ]; then
                 BBB_WEB_URL=$(cat ${SERVLET_DIR}/bigbluebutton/WEB-INF/classes/bigbluebutton.properties | grep -v '#' | sed -n '/^bigbluebutton.web.serverURL/{s/.*=//;p}')
 		echo "# Warning: The API demos are installed and accessible from:"
@@ -1766,7 +1808,7 @@ if [ -n "$HOST" ]; then
 		sed -i "s/rtmpt:\/\/\([^\"\/]*\)\//rtmpt:\/\/$HOST\//g" /var/www/bigbluebutton/check/conf/config.xml
 	fi
 
-	echo "Restarting the bigbluebutton server ..."
+	echo "Restarting the BigBlueButton $BIGBLUEBUTTON_RELEASE ..."
 	stop_bigbluebutton
 	echo
 	start_bigbluebutton
@@ -1779,7 +1821,7 @@ if [ $RESTART ]; then
 	need_root
 	check_configuration
 	
-	echo "Restarting BigBlueButton ..."
+	echo "Restarting BigBlueButton $BIGBLUEBUTTON_RELEASE ..."
 
 	stop_bigbluebutton
 	start_bigbluebutton
@@ -1790,7 +1832,7 @@ if [ $CLEAN ]; then
 	need_root
 	check_configuration
 	
-	echo "Doing a restart of BigBlueButton and cleaning out all log files..."
+	echo "Restaring BigBlueButton $BIGBLUEBUTTON_RELEASE (and cleaning out all log files) ..."
 
 	stop_bigbluebutton
 
@@ -1798,8 +1840,7 @@ if [ $CLEAN ]; then
 	# Clean log files
 	#
 
-	echo 
-	echo "Cleaning Log Files"
+	echo " ... cleaning log files"
 	rm -f /var/log/bigbluebutton/bbb-web.log*
 	rm -f /var/log/bigbluebutton/*.log
 
diff --git a/bigbluebutton-html5/imports/api/cursor/server/modifiers/updateCursor.js b/bigbluebutton-html5/imports/api/cursor/server/modifiers/updateCursor.js
index 2e6217849d566b849965d418e55c4c921a33be6d..13928c2c5e5b618a609fa0dd48fe0d35de91d5b6 100755
--- a/bigbluebutton-html5/imports/api/cursor/server/modifiers/updateCursor.js
+++ b/bigbluebutton-html5/imports/api/cursor/server/modifiers/updateCursor.js
@@ -1,5 +1,6 @@
 import Logger from '/imports/startup/server/logger';
 import Cursor from '/imports/api/cursor';
+import { check } from 'meteor/check';
 
 export default function updateCursor(meetingId, x = 0, y = 0) {
   check(meetingId, String);
@@ -29,7 +30,7 @@ export default function updateCursor(meetingId, x = 0, y = 0) {
     }
 
     if (numChanged) {
-      return Logger.info(`Updated cursor meeting=${meetingId}`);
+      return Logger.debug(`Updated cursor meeting=${meetingId}`);
     }
   };
 
diff --git a/bigbluebutton-html5/imports/locales/en.json b/bigbluebutton-html5/imports/locales/en.json
index 9f7d48fecc075952a3a87a1ea9f80dd2796c7bc0..4d43412e8699d56f187b208bd39123ea774f3a4f 100755
--- a/bigbluebutton-html5/imports/locales/en.json
+++ b/bigbluebutton-html5/imports/locales/en.json
@@ -30,9 +30,11 @@
   "app.navBar.settingsDropdown.optionsLabel": "Options",
   "app.navBar.settingsDropdown.fullscreenLabel": "Make fullscreen",
   "app.navBar.settingsDropdown.settingsLabel": "Open settings",
+  "app.navBar.settingsDropdown.aboutLabel": "About",
   "app.navBar.settingsDropdown.leaveSessionLabel": "Logout",
   "app.navBar.settingsDropdown.fullscreenDesc": "Make the settings menu fullscreen",
   "app.navBar.settingsDropdown.settingsDesc": "Change the general settings",
+  "app.navBar.settingsDropdown.aboutDesc": "Show information about the client",
   "app.navBar.settingsDropdown.leaveSessionDesc": "Leave the meeting",
   "app.leaveConfirmation.title": "Leave Session",
   "app.leaveConfirmation.message": "Do you want to leave this meeting?",
@@ -40,6 +42,13 @@
   "app.leaveConfirmation.confirmDesc": "Logs you out of the meeting",
   "app.leaveConfirmation.dismissLabel": "Cancel",
   "app.leaveConfirmation.dismissDesc": "Closes and rejects the leave confirmation",
+  "app.about.title": "About",
+  "app.about.version": "Client Build:",
+  "app.about.copyright": "Copyright:",
+  "app.about.confirmLabel": "OK",
+  "app.about.confirmDesc": "OK",
+  "app.about.dismissLabel": "Cancel",
+  "app.about.dismissDesc": "Close about client information",
   "app.actionsBar.muteLabel": "Mute",
   "app.actionsBar.camOffLabel": "Cam Off",
   "app.actionsBar.raiseLabel": "Raise",
diff --git a/bigbluebutton-html5/imports/ui/components/about/component.jsx b/bigbluebutton-html5/imports/ui/components/about/component.jsx
new file mode 100644
index 0000000000000000000000000000000000000000..02d794475070e3de34bfa5949947684b82f8f4db
--- /dev/null
+++ b/bigbluebutton-html5/imports/ui/components/about/component.jsx
@@ -0,0 +1,70 @@
+import React, { Component } from 'react';
+import { defineMessages, injectIntl } from 'react-intl';
+import Modal from '/imports/ui/components/modal/component';
+
+const intlMessages = defineMessages({
+  title: {
+    id: 'app.about.title',
+    defaultMessage: 'About',
+  },
+  version: {
+    id: 'app.about.version',
+    defaultMessage: 'Client Build:',
+  },
+  copyright: {
+    id: 'app.about.copyright',
+    defaultMessage: (new Date().getFullYear()),
+  },
+  confirmLabel: {
+    id: 'app.about.confirmLabel',
+    defaultMessage: 'OK',
+  },
+  confirmDesc: {
+    id: 'app.about.confirmDesc',
+    defaultMessage: 'OK',
+  },
+  dismissLabel: {
+    id: 'app.about.dismissLabel',
+    defaultMessage: 'Cancel',
+  },
+  dismissDesc: {
+    id: 'app.about.dismissDesc',
+    defaultMessage: 'Close about client information',
+  },
+});
+
+class AboutComponent extends Component {
+  constructor(props) {
+    super(props);
+
+    this.handleAboutComponent = this.handleAboutComponent.bind(this);
+  }
+
+  handleAboutComponent() {
+    console.log("TODO");
+  }
+
+  render() {
+    const { intl, clientBuild, copyright } = this.props;
+
+    return (
+      <Modal
+        title={intl.formatMessage(intlMessages.title)}
+        confirm={{
+          callback: this.handleAboutComponent,
+          label: intl.formatMessage(intlMessages.confirmLabel),
+          description: intl.formatMessage(intlMessages.confirmDesc),
+        }}
+        dismiss={{
+          callback: this.handleAboutComponent,
+          label: intl.formatMessage(intlMessages.dismissLabel),
+          description: intl.formatMessage(intlMessages.dismissDesc),
+        }}>
+        {`${intl.formatMessage(intlMessages.copyright)} ${copyright}`} <br/>
+        {`${intl.formatMessage(intlMessages.version)} ${clientBuild}`}
+      </Modal>
+    );
+  }
+};
+
+export default injectIntl(AboutComponent);
diff --git a/bigbluebutton-html5/imports/ui/components/about/container.jsx b/bigbluebutton-html5/imports/ui/components/about/container.jsx
new file mode 100644
index 0000000000000000000000000000000000000000..5a591892036bd7eabdb160fcac78920632b98d6f
--- /dev/null
+++ b/bigbluebutton-html5/imports/ui/components/about/container.jsx
@@ -0,0 +1,29 @@
+import React, { Component, PropTypes } from 'react';
+import { createContainer } from 'meteor/react-meteor-data';
+
+import AboutComponent from './component';
+
+class AboutContainer extends Component {
+  constructor(props) {
+    super(props);
+  }
+
+  render() {
+    return (
+      <AboutComponent {...this.props}>
+        {this.props.children}
+      </AboutComponent>
+    );
+  }
+}
+
+const getClientBuildInfo = () => {
+  return {
+    clientBuild: Meteor.settings.public.app.html5ClientBuild,
+    copyright: Meteor.settings.public.app.copyright,
+  };
+};
+
+export default createContainer(() => {
+  return getClientBuildInfo();
+}, AboutContainer);
diff --git a/bigbluebutton-html5/imports/ui/components/actions-bar/audio-menu/component.jsx b/bigbluebutton-html5/imports/ui/components/actions-bar/audio-menu/component.jsx
index c7cc5fcedbe47513d2ee012dc27437240f5a0fde..4f2cb14bcd90d46620b11bc04df0ddbc43dcc242 100755
--- a/bigbluebutton-html5/imports/ui/components/actions-bar/audio-menu/component.jsx
+++ b/bigbluebutton-html5/imports/ui/components/actions-bar/audio-menu/component.jsx
@@ -3,34 +3,24 @@ import { createContainer } from 'meteor/react-meteor-data';
 import Button from '/imports/ui/components/button/component';
 import Users from '/imports/api/users/index';
 import Auth from '/imports/ui/services/auth/index';
-import MuteAudioContainer from '../mute-button/container';
 
 export default class JoinAudioOptions extends React.Component {
 
   renderLeaveButton() {
     return (
-      <span>
-      <Button
-        onClick={this.props.close}
-        label={'Leave Audio'}
-        color={'danger'}
-        icon={'audio'}
-        size={'lg'}
-        circle={true}
-      />
-      </span>
+        <Button
+          onClick={this.props.close}
+          label={'Leave Audio'}
+          color={'danger'}
+          icon={'audio'}
+          size={'lg'}
+          circle={true}
+        />
     );
   }
 
   render() {
-    if (this.props.isInAudio) {
-      return (
-        <span>
-          <MuteAudioContainer/>
-          {this.renderLeaveButton()}
-        </span>
-      );
-    } else if (this.props.isInListenOnly) {
+    if (this.props.isInAudio || this.props.isInListenOnly) {
       return this.renderLeaveButton();
     }
 
diff --git a/bigbluebutton-html5/imports/ui/components/actions-bar/component.jsx b/bigbluebutton-html5/imports/ui/components/actions-bar/component.jsx
index a909d29674b22d698a5d278a2da13dba080c72c6..4af9c49aa5d73226500c9b1e231a89fe4c16b1d6 100755
--- a/bigbluebutton-html5/imports/ui/components/actions-bar/component.jsx
+++ b/bigbluebutton-html5/imports/ui/components/actions-bar/component.jsx
@@ -8,6 +8,7 @@ import ActionsDropdown from './actions-dropdown/component';
 import Auth from '/imports/ui/services/auth/index';
 import Users from '/imports/api/users/index';
 import JoinAudioOptionsContainer from './audio-menu/container';
+import MuteAudioContainer from './mute-button/container';
 import { exitAudio } from '/imports/api/phone';
 
 const openJoinAudio = () => showModal(<Audio />);
@@ -27,6 +28,7 @@ export default class ActionsBar extends Component {
           <ActionsDropdown />
         </div>
         <div className={styles.center}>
+          <MuteAudioContainer />
           <JoinAudioOptionsContainer
             open={openJoinAudio.bind(this)}
             close={() => {exitAudio();}}
@@ -53,9 +55,11 @@ export default class ActionsBar extends Component {
     return (
       <div className={styles.actionsbar}>
         <div className={styles.center}>
+          <MuteAudioContainer />
           <JoinAudioOptionsContainer
             open={openJoinAudio.bind(this)}
-            close={exitAudio}
+            close={() => {exitAudio();}}
+
           />
 
           <Button
@@ -76,7 +80,7 @@ export default class ActionsBar extends Component {
 
   render() {
     const { isUserPresenter } = this.props;
-    
+
     return isUserPresenter ?
       this.renderForPresenter() :
       this.renderForUser();
diff --git a/bigbluebutton-html5/imports/ui/components/actions-bar/mute-button/component.jsx b/bigbluebutton-html5/imports/ui/components/actions-bar/mute-button/component.jsx
index 2b2f6867601be47c55df5259843ca2128f9a3e28..ed8c61c15db3cf8d2d93309e2acf53ba7d3bfe91 100755
--- a/bigbluebutton-html5/imports/ui/components/actions-bar/mute-button/component.jsx
+++ b/bigbluebutton-html5/imports/ui/components/actions-bar/mute-button/component.jsx
@@ -1,26 +1,26 @@
 import React from 'react';
 import Button from '/imports/ui/components/button/component';
+import styles from '../styles.scss';
 
-export default class MuteAudioComponent extends React.Component {
+export default class MuteAudio extends React.Component {
 
   render() {
-    const { isMuted, muteUser, unmuteUser } = this.props;
-    let onClick = muteUser;
-    let label = 'Mute';
-
-    if (isMuted) {
-      onClick = unmuteUser;
-      label = 'Unmute';
-    }
+    const { isInAudio, isMuted, callback } = this.props;
+    let label = !isMuted ? 'Mute' : 'Unmute';
+    let icon = !isMuted ? 'mute' : 'unmute';
+    let className = !isInAudio ? styles.invisible : null;
+    let tabIndex = !isInAudio ? -1 : 0;
 
     return (
       <Button
-        onClick={onClick}
+        onClick={callback}
         label={label}
         color={'primary'}
-        icon={'audio'}
+        icon={icon}
         size={'lg'}
         circle={true}
+        className={className}
+        tabIndex={tabIndex}
       />
     );
   }
diff --git a/bigbluebutton-html5/imports/ui/components/actions-bar/mute-button/container.jsx b/bigbluebutton-html5/imports/ui/components/actions-bar/mute-button/container.jsx
index 0af6901b66e98baabd4607aa5539d9576bc7b72d..9a8ad360884ad3df95df95a425e9b378de2d36c7 100755
--- a/bigbluebutton-html5/imports/ui/components/actions-bar/mute-button/container.jsx
+++ b/bigbluebutton-html5/imports/ui/components/actions-bar/mute-button/container.jsx
@@ -4,25 +4,35 @@ import {callServer} from '/imports/ui/services/api';
 import Button from '/imports/ui/components/button/component';
 import Users from '/imports/api/users/index';
 import Auth from '/imports/ui/services/auth/index';
-import MuteAudioComponent from './component';
+import MuteAudio from './component';
 
 class MuteAudioContainer extends React.Component {
   render() {
     return (
-      <MuteAudioComponent
-        isMuted = {this.props.isMuted}
-        muteUser = {this.props.muteUser}
-        unmuteUser = {this.props.unmuteUser}
-      />
+      <MuteAudio {...this.props} />
     );
   }
 }
 
 export default createContainer((params) => {
+  const userId = Auth.userID;
+  const user = Users.findOne({ userId: userId }).user;
+  const isMuted = user.voiceUser.muted;
+  const isInAudio = user.voiceUser.joined;
+  let callback = () => {};
+
+  if (isInAudio && !isMuted) {
+    callback = () => callServer('muteUser', userId);
+  }
+
+  if (isInAudio && isMuted) {
+    callback = () => callServer('unmuteUser', userId);
+  }
+
   const data = {
-    isMuted: Users.findOne({ userId: Auth.userID }).user.voiceUser.muted,
-    muteUser: () => callServer('muteUser', Auth.userID),
-    unmuteUser: () => callServer('unmuteUser', Auth.userID),
+    isInAudio,
+    isMuted,
+    callback,
   };
   return data;
 }, MuteAudioContainer);
diff --git a/bigbluebutton-html5/imports/ui/components/actions-bar/styles.scss b/bigbluebutton-html5/imports/ui/components/actions-bar/styles.scss
index 021b59e46a832008262b325ce98af5e7b76584c8..04f69baa3d0c52de8347bfeea400d4ccc552508d 100755
--- a/bigbluebutton-html5/imports/ui/components/actions-bar/styles.scss
+++ b/bigbluebutton-html5/imports/ui/components/actions-bar/styles.scss
@@ -26,3 +26,7 @@
 .center {
   flex: 1;
 }
+
+.invisible {
+  visibility: hidden;
+}
diff --git a/bigbluebutton-html5/imports/ui/components/audio-modal/listen-only/component.jsx b/bigbluebutton-html5/imports/ui/components/audio-modal/listen-only/component.jsx
index 5c7ad22418e30939faad262fd99fa6ec1ea088bc..c46bd050ef7f4c1b2879f4644225de02edaec54b 100755
--- a/bigbluebutton-html5/imports/ui/components/audio-modal/listen-only/component.jsx
+++ b/bigbluebutton-html5/imports/ui/components/audio-modal/listen-only/component.jsx
@@ -17,7 +17,7 @@ export default class ListenOnly extends React.Component {
 
     this.state = {
       inputDeviceId: undefined,
-    }
+    };
   }
 
   chooseAudio() {
@@ -41,7 +41,7 @@ export default class ListenOnly extends React.Component {
             onClick={this.chooseAudio}
           />
           <div>
-            Listen only message
+            Choose your listen only settings
           </div>
         </div>
         <div>
diff --git a/bigbluebutton-html5/imports/ui/components/enter-audio/component.jsx b/bigbluebutton-html5/imports/ui/components/enter-audio/component.jsx
index 01d3e2f254de9e23988ae82e8607112224db7953..457879ec1e90b1928e75cdffea12a90721626e55 100755
--- a/bigbluebutton-html5/imports/ui/components/enter-audio/component.jsx
+++ b/bigbluebutton-html5/imports/ui/components/enter-audio/component.jsx
@@ -1,27 +1,27 @@
-import React from 'react';
-import Button from '/imports/ui/components/button/component';
-import styles from '../settings/styles.scss';
-
-export default class EnterAudio extends React.Component {
-  constructor(props) {
-    super(props);
-  }
-
-  render() {
-    return (
-      <div className={styles.half}>
-        Please note, a dialog will appear in your browser,
-        requiring you to accept sharing your microphone.
-        <br />
-        <img src='resources/images/allow-mic.png' alt='allow microphone image' width='100%'/>
-        <br />
-        <Button className={styles.enterBtn}
-          label={'Enter Session'}
-          size={'md'}
-          color={'primary'}
-          onClick={this.props.handleJoin}
-        />
-      </div>
-    );
-  }
-};
+import React from 'react';
+import Button from '/imports/ui/components/button/component';
+import styles from '../settings/styles.scss';
+
+export default class EnterAudio extends React.Component {
+  constructor(props) {
+    super(props);
+  }
+
+  render() {
+    return (
+      <div className={styles.half}>
+        Please note, a dialog will appear in your browser,
+        requiring you to accept sharing your microphone.
+        <br />
+        <img src='resources/images/allow-mic.png' alt='allow microphone image' width='100%'/>
+        <br />
+        <Button className={styles.enterBtn}
+          label={'Enter Session'}
+          size={'md'}
+          color={'primary'}
+          onClick={this.props.handleJoin}
+        />
+      </div>
+    );
+  }
+};
diff --git a/bigbluebutton-html5/imports/ui/components/enter-audio/container.jsx b/bigbluebutton-html5/imports/ui/components/enter-audio/container.jsx
index 6c8f7f42ba6dfa1000ba1cb5338d5ac67f0e91c8..299f254333ce1356f5ea0cccc52ab959e700bf2b 100755
--- a/bigbluebutton-html5/imports/ui/components/enter-audio/container.jsx
+++ b/bigbluebutton-html5/imports/ui/components/enter-audio/container.jsx
@@ -1,22 +1,26 @@
-import React, { Component, PropTypes } from 'react';
-import { createContainer } from 'meteor/react-meteor-data';
-import {joinListenOnly, joinMicrophone} from '/imports/api/phone';
-import EnterAudio from './component';
-
-export default class EnterAudioContainer extends Component {
-  constructor(props) {
-    super(props);
-  }
-
-  render() {
-    const {
-      isFullAudio,
-    } = this.props;
-
-    let handleJoin = () => isFullAudio ? joinMicrophone() : joinListenOnly();
-
-    return (
-      <EnterAudio handleJoin={handleJoin} />
-    );
-  }
-}
+import React, { Component, PropTypes } from 'react';
+import { createContainer } from 'meteor/react-meteor-data';
+import {joinListenOnly, joinMicrophone} from '/imports/api/phone';
+import { clearModal } from '/imports/ui/components/app/service';
+import EnterAudio from './component';
+
+export default class EnterAudioContainer extends Component {
+  constructor(props) {
+    super(props);
+  }
+
+  render() {
+    const {
+      isFullAudio,
+    } = this.props;
+
+    let handleJoin = () => {
+      clearModal();
+      return isFullAudio ? joinMicrophone() : joinListenOnly();
+    };
+
+    return (
+      <EnterAudio handleJoin={handleJoin} />
+    );
+  }
+}
diff --git a/bigbluebutton-html5/imports/ui/components/nav-bar/component.jsx b/bigbluebutton-html5/imports/ui/components/nav-bar/component.jsx
index d58b764454c56726d0c4e05018185d4e6d43819a..0db954613c53f0d79b88928359f58242841046cd 100755
--- a/bigbluebutton-html5/imports/ui/components/nav-bar/component.jsx
+++ b/bigbluebutton-html5/imports/ui/components/nav-bar/component.jsx
@@ -166,8 +166,8 @@ class NavBar extends Component {
         className={styles.actionsHeader}
         key={_.uniqueId('action-header')}
         label={breakoutName}
-        onClick={openBreakoutJoinConfirmation.bind(this, breakoutURL, breakout.name)}
-        defaultMessage={'batata'}/>
+        onClick={openBreakoutJoinConfirmation.bind(this, breakoutURL, breakoutName)}
+      />
     );
   }
 }
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 65a25d17cb116254943afbb0e08e0e04e0b4c4f0..8891f6814fbcb2ebba5c1326e9c6b8fd54af029b 100755
--- a/bigbluebutton-html5/imports/ui/components/nav-bar/settings-dropdown/component.jsx
+++ b/bigbluebutton-html5/imports/ui/components/nav-bar/settings-dropdown/component.jsx
@@ -5,6 +5,7 @@ import styles from '../styles';
 
 import { showModal } from '/imports/ui/components/app/service';
 import LogoutConfirmation from '/imports/ui/components/logout-confirmation/component';
+import AboutContainer from '/imports/ui/components/about/container';
 import SettingsMenuContainer from '/imports/ui/components/settings/container';
 
 import Button from '/imports/ui/components/button/component';
@@ -28,6 +29,14 @@ const intlMessages = defineMessages({
     id: 'app.navBar.settingsDropdown.settingsLabel',
     defaultMessage: 'Open settings',
   },
+  aboutLabel: {
+    id: 'app.navBar.settingsDropdown.aboutLabel',
+    defaultMessage: 'About',
+  },
+  aboutDesc: {
+    id: 'app.navBar.settingsDropdown.aboutDesc',
+    defaultMessage: 'About',
+  },
   leaveSessionLabel: {
     id: 'app.navBar.settingsDropdown.leaveSessionLabel',
     defaultMessage: 'Logout',
@@ -84,6 +93,8 @@ const toggleFullScreen = () => {
 
 const openSettings = () => showModal(<SettingsMenuContainer  />);
 
+const openAbout = () => showModal(<AboutContainer /> );
+
 const openLogoutConfirmation = () => showModal(<LogoutConfirmation />);
 
 class SettingsDropdown extends Component {
@@ -123,6 +134,11 @@ class SettingsDropdown extends Component {
               description={intl.formatMessage(intlMessages.settingsDesc)}
               onClick={openSettings.bind(this)}
             />
+            <DropdownListItem
+              label={intl.formatMessage(intlMessages.aboutLabel)}
+              description={intl.formatMessage(intlMessages.aboutDesc)}
+              onClick={openAbout.bind(this)}
+            />
             <DropdownListSeparator />
             <DropdownListItem
               icon="logout"
diff --git a/bigbluebutton-html5/private/config/public/app.yaml b/bigbluebutton-html5/private/config/public/app.yaml
index 07c5999144574e27d66a7dbfc6d7b76bbd208ee5..91a7e3ec50f9e0ea18901afecd3ef798407d5497 100755
--- a/bigbluebutton-html5/private/config/public/app.yaml
+++ b/bigbluebutton-html5/private/config/public/app.yaml
@@ -11,9 +11,9 @@ app:
 
   # Default global variables
   appName: "BigBlueButton HTML5 Client"
-  bbbServerVersion: "1.0"
-  copyrightYear: "2015"
-  html5ClientBuild: "NNNN"
+  bbbServerVersion: "1.1-beta"
+  copyright: "©2016 BigBlueButton Inc."
+  html5ClientBuild: "HTML5_CLIENT_VERSION"
   defaultWelcomeMessage: Welcome to %%CONFNAME%%!<br /><br />For help on using BigBlueButton see these (short) <a href="event:http://www.bigbluebutton.org/content/videos"><u>tutorial videos</u></a>.<br /><br />To join the audio bridge click the gear icon (upper-right hand corner).  Use a headset to avoid causing background noise for others.<br /><br /><br />
   lockOnJoin: true
   defaultWelcomeMessageFooter: This server is running a build of <a href="http://docs.bigbluebutton.org/1.0/10overview.html" target="_blank"><u>BigBlueButton 1.0</u></a>.
diff --git a/clients/flash/air-client/src/ColorPalette.as b/clients/flash/air-client/src/ColorPalette.as
deleted file mode 120000
index c3f03f7db6f0cef6358430304d4c3021287b9d91..0000000000000000000000000000000000000000
--- a/clients/flash/air-client/src/ColorPalette.as
+++ /dev/null
@@ -1 +0,0 @@
-../../common-library/src/main/actionscript/ColorPalette
\ No newline at end of file
diff --git a/clients/flash/air-client/src/CustomRuntimeDPIProvider.as b/clients/flash/air-client/src/CustomRuntimeDPIProvider.as
deleted file mode 100644
index 48337f98a2eac42c4b7565453763af68773f44aa..0000000000000000000000000000000000000000
--- a/clients/flash/air-client/src/CustomRuntimeDPIProvider.as
+++ /dev/null
@@ -1,27 +0,0 @@
-package {
-	
-	import flash.system.Capabilities;
-	import mx.core.DPIClassification;
-	import mx.core.RuntimeDPIProvider;
-	
-	public class CustomRuntimeDPIProvider extends RuntimeDPIProvider {
-		public function CustomRuntimeDPIProvider() {
-		}
-		
-		override public function get runtimeDPI():Number {
-			if (Capabilities.screenDPI < 200) {
-				return DPIClassification.DPI_160;
-			}
-			if (Capabilities.screenDPI <= 280) {
-				return DPIClassification.DPI_240;
-			}
-			if (Capabilities.screenDPI <= 400) {
-				return DPIClassification.DPI_320;
-			}
-			if (Capabilities.screenDPI <= 560) {
-				return 480;
-			}
-			return 640;
-		}
-	}
-}
diff --git a/clients/flash/air-client/src/Default.css b/clients/flash/air-client/src/Default.css
index 49d2a2347cb90e869677ea874c3c82a4da4d62d3..3e511758d56ddc9b11f2a81e98eecec0f25d7b59 100755
--- a/clients/flash/air-client/src/Default.css
+++ b/clients/flash/air-client/src/Default.css
@@ -1,211 +1,203 @@
-/* CSS file */
 @namespace s "library://ns.adobe.com/flex/spark";
 @namespace mx "library://ns.adobe.com/flex/mx";
 @namespace libChat "org.bigbluebutton.lib.chat.views.*";
+@namespace main "org.bigbluebutton.air.main.views.*";
 
 @font-face { 
-	src: 		url("assets/fonts/SourceSansPro/SourceSansPro-Regular.ttf"); 
-	fontFamily:	SourceSansPro; 
-	fontStyle:	normal; 
-	fontWeight:	normal; 
-	embedAsCFF:	true;
+	src        : url("../../shared/assets/fonts/SourceSansPro/SourceSansPro-Regular.ttf");
+	fontFamily : SourceSansPro;
+	fontStyle  : normal;
+	fontWeight : normal;
+	embedAsCFF : true;
 }
 
 @font-face { 
-	src:		url("assets/fonts/SourceSansPro/SourceSansPro-Semibold.ttf"); 
-	fontFamily:	SourceSansPro; 
-	fontStyle:	normal; 
-	fontWeight:	bold; 
-	embedAsCFF:	true;
+	src        : url("../../shared/assets/fonts/SourceSansPro/SourceSansPro-Semibold.ttf");
+	fontFamily : SourceSansPro;
+	fontStyle  : normal;
+	fontWeight : bold;
+	embedAsCFF : true;
 }
 
 @font-face { 
-	src:		url("assets/fonts/SourceSansPro/SourceSansPro-Italic.ttf"); 
-	fontFamily:	SourceSansPro; 
-	fontStyle:	italic; 
-	fontWeight:	normal; 
-	embedAsCFF:	true;
+	src        : url("../../shared/assets/fonts/SourceSansPro/SourceSansPro-Italic.ttf");
+	fontFamily : SourceSansPro;
+	fontStyle  : italic;
+	fontWeight : normal;
+	embedAsCFF : true;
 }
 
 @font-face { 
-	src:		url("assets/fonts/SourceSansPro/SourceSansPro-Bold.ttf"); 
-	fontFamily:	SourceSansPro; 
-	fontStyle:	normal; 
-	fontWeight:	heavy; 
-	embedAsCFF:	true;
+	src        : url("../../shared/assets/fonts/SourceSansPro/SourceSansPro-Bold.ttf");
+	fontFamily : SourceSansPro;
+	fontStyle  : normal;
+	fontWeight : heavy;
+	embedAsCFF : true;
 }
 
 @font-face { 
-	src: 		url("assets/fonts/SourceSansPro/SourceSansPro-Regular.ttf"); 
-	fontFamily:	SourceSansProMX; 
-	fontStyle:	normal; 
-	fontWeight:	normal; 
-	embedAsCFF:	false;
+	src        : url("../../shared/assets/fonts/SourceSansPro/SourceSansPro-Regular.ttf");
+	fontFamily : SourceSansProMX;
+	fontStyle  : normal;
+	fontWeight : normal;
+	embedAsCFF : false;
 }
 
 @font-face {
-	src: 		url("assets/fonts/BBBIcons/bbb-icons.ttf"); 
-	fontFamily:	BBBIcons; 
-	fontStyle:	normal; 
-	fontWeight:	normal; 
-	embedAsCFF:	true;
+	src        : url("../../shared/assets/fonts/BBBIcons/bbb-icons.ttf");
+	fontFamily : BBBIcons;
+	fontStyle  : normal;
+	fontWeight : normal;
+	embedAsCFF : true;
 }
 
 @font-face {
-	src: 		url("assets/fonts/BBBIcons/bbb-icons.ttf"); 
-	fontFamily:	BBBIcons; 
-	fontStyle:	normal; 
-	fontWeight:	bold; 
-	embedAsCFF:	true;
+	src        : url("../../shared/assets/fonts/BBBIcons/bbb-icons.ttf");
+	fontFamily : BBBIcons;
+	fontStyle  : normal;
+	fontWeight : bold;
+	embedAsCFF : true;
 }
 
+/* Shared styles */
+
 global
 {
-	font-family:	SourceSansPro;
+	font-family : SourceSansPro;
 }
 
+/* Classes */
+
 s|Application
 {
-	backgroundColor: PropertyReference("bbbBlack");	
+	backgroundColor : PropertyReference("bbbBlack");	
 }
 
-s|TextInput
-{
-	fontFamily:		SourceSansPro;
+/* Loading screen */
+
+main|LoadingScreen {
+	color           : PropertyReference("bbbWhite");	
+	backgroundColor : PropertyReference("bbbBlack");
+	textAlign       : center;
 }
 
-libChat|NewMessagesIndicator
+/* Main view */
+
+main|MainView
 {
-	backgroundColor: 	PropertyReference("bbbRed");
-	backgroundAlpha:	1;
-	borderColor: 		PropertyReference("bbbRed");
-	borderAlpha: 		0;
-	color:				PropertyReference("bbbWhite");
-	cornerRadius:		6;
-	skinClass:			ClassReference("org.bigbluebutton.lib.chat.views.skins.NewMessagesIndicatorSkin");
+	backgroundColor : PropertyReference("bbbBlack");	
 }
 
-.mainViewStyle
+libChat|NewMessagesIndicator
 {
-	backgroundColor:	PropertyReference("bbbBlack");	
+	backgroundColor : PropertyReference("bbbRed");
+	backgroundAlpha : 1;
+	borderColor     : PropertyReference("bbbRed");
+	borderAlpha     : 0;
+	color           : PropertyReference("bbbWhite");
+	cornerRadius    : 6;
+	skinClass       : ClassReference("org.bigbluebutton.lib.chat.views.skins.NewMessagesIndicatorSkin");
 }
 
-.subViewContentStyle
+.subViewContent
 {
-	backgroundColor:	PropertyReference("bbbWhite");
+	backgroundColor : PropertyReference("bbbWhite");
 }
 
-.titleLabelStyle
+.titleLabel
 {
-	color: 		PropertyReference("bbbWhite");
-	fontSize:	24;
+	color : PropertyReference("bbbWhite");
 }
 
-.topButtonStyle
+.topButton
 {
-	iconSize				: 30;
-	iconColor				: PropertyReference("bbbWhite");
-	iconFont				: BBBIcons;
-	skinClass:			ClassReference("org.bigbluebutton.lib.main.views.skins.TopButtonSkin");
+	iconColor : PropertyReference("bbbWhite");
+	iconFont  : BBBIcons;
+	skinClass : ClassReference("org.bigbluebutton.lib.main.views.skins.TopButtonSkin");
 }
 
-.participantsButtonStyle
+.participantsButton
 {
-	icon: "\ue906";
+	icon : "\ue906";
 }
 
-.settingsButtonStyle
+.settingsButton
 {
-	icon: "\ue902";
+	icon : "\ue902";
 }
 
-.presentationButtonStyle
+.presentationButton
 {
-	icon: "\ue90c";
+	icon : "\ue90c";
 }
 
-.backButtonStyle
+.backButton
 {
-	icon: "\ue90e";
+	icon : "\ue90e";
 }
 
-.menuButtonStyle
+.menuButton
 {
 	backgroundColor         : PropertyReference("bbbBlue");
 	selectedBackgroundColor : PropertyReference("bbbGrey");
 	color                   : PropertyReference("bbbWhite");
-	fontSize				: 14;
-	iconSize				: 30;
 	iconColor				: PropertyReference("bbbWhite");
 	iconFont				: BBBIcons;
 	skinClass               : ClassReference("org.bigbluebutton.lib.main.views.skins.MenuButtonSkin");
 }
 
-.micOnButtonStyle
+.micOnButton
 {
 	icon : "\ue91b";
 }
 
-.micOffButtonStyle
+.micOffButton
 {
 	icon : "\ue91c";
 }
 
-.camOnButtonStyle
+.camOnButton
 {
 	icon : "\ue921";
 }
 
-.camOffButtonStyle
+.camOffButton
 {
 	icon : "\ue905";
 }
 
-.handStatusButtonStyle
+.handStatusButton
 {
 	icon : "\ue910";
 }
 
-.participantIconStyle
+.participantIcon
 {
 	circleColor : PropertyReference("bbbBlack");
-	width  : 32;
-	height : 32;
-	fontSize : 12;
 }
 
-.chatMessageStyle
+.sendButton
 {
-	nameFontSize: 		16;
-	timeFontSize:		16;
+	color       : PropertyReference("bbbGrey");
+	fontFamily  : BBBIcons;
+	borderColor : PropertyReference("bbbGrey");
+	skinClass   : ClassReference("org.bigbluebutton.lib.chat.views.skins.SendButtonSkin");
 }
 
-.sendButtonStyle
+.messageInput
 {
-	color:		PropertyReference("bbbGrey");
-	fontFamily:	BBBIcons;
-	fontSize:	20;
-	borderColor:	PropertyReference("bbbGrey");
-	borderWeight:	2;
-	cornerRadius:	4;
-	skinClass:	ClassReference("org.bigbluebutton.lib.chat.views.skins.SendButtonSkin");
+	fontFamily             : SourceSansProMX;
+	contentBackgroundAlpha : 0;
+	skinClass              : ClassReference("org.bigbluebutton.air.chat.views.skins.MessageInputSkin");
 }
 
-.messageInputStyle
+.content
 {
-	fontFamily: SourceSansProMX;
-	contentBackgroundAlpha: 0;
-	skinClass:	ClassReference("org.bigbluebutton.air.chat.views.skins.MessageInputSkin");
-}
-
-.contentStyle
-{
-	fontSize:	14;
-	color:		PropertyReference("bbbBlack");
+	color : PropertyReference("bbbBlack");
 }
 
 /*
-.panelStyle
+.panel
 {
 	backgroundColor:	PropertyReference("bbbWhite");
 	backgroundAlpha:	1;
@@ -213,7 +205,7 @@ libChat|NewMessagesIndicator
 	skinClass:			ClassReference("org.bigbluebutton.web.common.skins.PanelSkin");
 }
 
-.panelTitleStyle
+.panelTitle
 {
 	fontSize:		18;
 	fontWeight:		bold;
@@ -223,9 +215,8 @@ libChat|NewMessagesIndicator
 
 */
 
-.iconStyle
+.icon
 {
-	fontSize:	20;
-	fontFamily:	BBBIcons;
-	color:		PropertyReference("bbbGrey");
-}
\ No newline at end of file
+	fontFamily : BBBIcons;
+	color      : PropertyReference("bbbGrey");
+}
diff --git a/clients/flash/air-client/src/Main-app.xml b/clients/flash/air-client/src/Main-app.xml
index a0e7f285105a9a222637d5e50769a0660a728a68..7ec60f6c9678eb34bf1b0c69b207158be570568f 100755
--- a/clients/flash/air-client/src/Main-app.xml
+++ b/clients/flash/air-client/src/Main-app.xml
@@ -1,40 +1,42 @@
 <?xml version="1.0" encoding="utf-8" standalone="no"?>
-<application xmlns="http://ns.adobe.com/air/application/3.2">
-
-	<!-- Adobe AIR Application Descriptor File Template.
-
-		Specifies parameters for identifying, installing, and launching AIR applications.
-
-		xmlns - The Adobe AIR namespace: http://ns.adobe.com/air/application/3.1
-				The last segment of the namespace specifies the version
-				of the AIR runtime required for this application to run.
-
-		minimumPatchLevel - The minimum patch level of the AIR runtime required to run
-				the application. Optional.
-	-->
-
-	<!-- A universally unique application identifier. Must be unique across all AIR applications.
+<application xmlns="http://ns.adobe.com/air/application/23.0">
+
+<!-- Adobe AIR Application Descriptor File Template.
+
+	Specifies parameters for identifying, installing, and launching AIR applications.
+
+	xmlns - The Adobe AIR namespace: http://ns.adobe.com/air/application/3.5
+			The last segment of the namespace specifies the version 
+			of the AIR runtime required for this application to run.
+			
+	minimumPatchLevel - The minimum patch level of the AIR runtime required to run 
+			the application. Optional.
+-->
+
+	<!-- A universally unique application identifier. Must be unique across all AIR applications.
 	Using a reverse DNS-style name as the id is recommended. (Eg. com.example.ExampleApplication.) Required. -->
 	<id>Main</id>
 
 	<!-- Used as the filename for the application. Required. -->
 	<filename>BigBlueButton</filename>
 
-	<!-- The name that is displayed in the AIR application installer.
+	<!-- The name that is displayed in the AIR application installer. 
 	May have multiple values for each language. See samples or xsd schema file. Optional. -->
 	<name>BigBlueButton</name>
-	<!-- A string value of the format <0-999>.<0-999>.<0-999> that represents application version which can be used to check for application upgrade.
-	Values can also be 1-part or 2-part. It is not necessary to have a 3-part value.
-	An updated version of application must have a versionNumber value higher than the previous version. Required for namespace >= 2.5 . -->
+	
+	<!-- A string value of the format <0-999>.<0-999>.<0-999> that represents application version which can be used to check for application upgrade. 
+	Values can also be 1-part or 2-part. It is not necessary to have a 3-part value.
+	An updated version of application must have a versionNumber value higher than the previous version. Required for namespace >= 2.5 .
+    For iOS Apps, this represents build number. -->
 	<versionNumber>0.1.0</versionNumber>
-
-	<!-- Whether the application can be launched when the user clicks a link in a web browser. Optional. Default false. -->
-	<allowBrowserInvocation>true</allowBrowserInvocation>
-
-	<!-- A string value (such as "v1", "2.5", or "Alpha 1") that represents the version of the application, as it should be shown to users. Optional. -->
-	<!-- <versionLabel>Pre-alpha</versionLabel> -->
-
-	<!-- Description, displayed in the AIR application installer.
+		         
+	<!-- A string value (such as "v1", "2.5", or "Alpha 1") that represents the version of the application, as it should be shown to users. 
+     For iOS Apps, this represents application version number and format should be (<0-999>.<0-999>.<0-999>).
+     If this tag is not specified or the value is not according to the format, then this tag will be ignored 
+     and <versionNumber> will be used for application version number. Optional. -->
+	<!-- <versionLabel></versionLabel> -->
+
+	<!-- Description, displayed in the AIR application installer.
 	May have multiple values for each language. See samples or xsd schema file. Optional. -->
 	<!-- <description></description> -->
 
@@ -49,7 +51,7 @@
 		<!-- The main SWF or HTML file of the application. Required. -->
 		<!-- Note: In Flash Builder, the SWF reference is set automatically. -->
 		<content>[This value will be overwritten by Flash Builder in the output app.xml]</content>
-
+		
 		<!-- The title of the main window. Optional. -->
 		<!-- <title></title> -->
 
@@ -89,29 +91,35 @@
 		<!-- The window's initial maximum size, specified as a width/height pair in pixels, such as "1600 1200". Optional. -->
 		<!-- <maxSize></maxSize> -->
 
-		<!-- The initial aspect ratio of the app when launched (either "portrait" or "landscape"). Optional. Mobile only. Default is the natural orientation of the device -->
+        <!-- The aspect ratio of the app ("portrait" or "landscape" or "any"). Optional. Mobile only. Default is the natural orientation of the device -->
 
-		<!-- <aspectRatio></aspectRatio> -->
+        <!-- <aspectRatio></aspectRatio> -->
 
-		<!-- Whether the app will begin auto-orienting on launch. Optional. Mobile only. Default false -->
+        <!-- Whether the app will begin auto-orienting on launch. Optional. Mobile only. Default false -->
 
-		<!-- <autoOrients></autoOrients> -->
+        <!-- <autoOrients></autoOrients> -->
 
-		<!-- Whether the app launches in full screen. Optional. Mobile only. Default false -->
+        <!-- Whether the app launches in full screen. Optional. Mobile only. Default false -->
 
-		<!-- <fullScreen></fullScreen> -->
+        <!-- <fullScreen></fullScreen> -->
 
-		<!-- The render mode for the app (either auto, cpu, gpu, or direct). Optional. Default auto -->
+        <!-- The render mode for the app (either auto, cpu, gpu, or direct). Optional. Default auto -->
 
-		<!-- <renderMode></renderMode> -->
+        <!-- <renderMode></renderMode> -->
+
+        <!-- Whether the default direct mode rendering context allocates storage for depth and stencil buffers.  Optional.  Default false. -->
+        <!-- <depthAndStencil></depthAndStencil> -->
 
 		<!-- Whether or not to pan when a soft keyboard is raised or lowered (either "pan" or "none").  Optional.  Defaults "pan." -->
 		<!-- <softKeyboardBehavior></softKeyboardBehavior> -->
-		<autoOrients>true</autoOrients>
-		<fullScreen>false</fullScreen>
-		<visible>true</visible>
-		<softKeyboardBehavior>none</softKeyboardBehavior>
-	</initialWindow>
+
+		<!-- Display Resolution for the app (either "standard" or "high"). Optional, OSX-only. Default "standard" -->
+		<!-- <requestedDisplayResolution></requestedDisplayResolution> -->
+	<autoOrients>true</autoOrients>
+        <fullScreen>false</fullScreen>
+        <visible>true</visible>
+        <softKeyboardBehavior>none</softKeyboardBehavior>
+    </initialWindow>
 
 	<!-- We recommend omitting the supportedProfiles element, -->
 	<!-- which in turn permits your application to be deployed to all -->
@@ -120,134 +128,156 @@
 	<!-- only the profiles which your application does support. -->
 	<!-- <supportedProfiles>desktop extendedDesktop mobileDevice extendedMobileDevice</supportedProfiles> -->
 
+	<!-- Languages supported by application -->
+	<!-- Only these languages can be specified -->
+	<!-- <supportedLanguages>en de cs es fr it ja ko nl pl pt ru sv tr zh</supportedLanguages> -->
+
 	<!-- The subpath of the standard default installation location to use. Optional. -->
 	<!-- <installFolder></installFolder> -->
 
 	<!-- The subpath of the Programs menu to use. (Ignored on operating systems without a Programs menu.) Optional. -->
 	<!-- <programMenuFolder></programMenuFolder> -->
 
-	<!-- The icon the system uses for the application. For at least one resolution,
+	<!-- The icon the system uses for the application. For at least one resolution,
 	specify the path to a PNG file included in the AIR package. Optional. -->
 	<icon>
 		<!-- http://help.adobe.com/en_US/air/build/WSfffb011ac560372f2fea1812938a6e463-8000.html#WSfffb011ac560372f-6fd06f0f1293d3b33ea-7ffc -->
+		<image16x16></image16x16>
 		<image29x29>assets/icons/sizes/icon-29.png</image29x29>
 		<image32x32>assets/icons/sizes/icon-32.png</image32x32>
+		<image36x36>assets/icons/sizes/icon-36.png</image36x36>
 		<image40x40>assets/icons/sizes/icon-40.png</image40x40>
+		<image44x44>assets/icons/sizes/icon-44.png</image44x44>
 		<image48x48>assets/icons/sizes/icon-48.png</image48x48>
 		<image50x50>assets/icons/sizes/icon-50.png</image50x50>
 		<image57x57>assets/icons/sizes/icon-57.png</image57x57>
 		<image58x58>assets/icons/sizes/icon-58.png</image58x58>
 		<image60x60>assets/icons/sizes/icon-60.png</image60x60>
+		<image66x66>assets/icons/sizes/icon-66.png</image66x66>
 		<image72x72>assets/icons/sizes/icon-72.png</image72x72>
 		<image75x75>assets/icons/sizes/icon-75.png</image75x75>
 		<image76x76>assets/icons/sizes/icon-76.png</image76x76>
 		<image80x80>assets/icons/sizes/icon-80.png</image80x80>
 		<image87x87>assets/icons/sizes/icon-87.png</image87x87>
 		<image96x96>assets/icons/sizes/icon-96.png</image96x96>
-		<image114x114>assets/icons/sizes/icon-114.png</image114x114>
 		<image100x100>assets/icons/sizes/icon-100.png</image100x100>
+		<image114x114>assets/icons/sizes/icon-114.png</image114x114>
 		<image120x120>assets/icons/sizes/icon-120.png</image120x120>
+		<image128x128>assets/icons/sizes/icon-128.png</image128x128>
 		<image144x144>assets/icons/sizes/icon-144.png</image144x144>
 		<image152x152>assets/icons/sizes/icon-152.png</image152x152>
 		<image167x167>assets/icons/sizes/icon-167.png</image167x167>
 		<image180x180>assets/icons/sizes/icon-180.png</image180x180>
+		<image192x192>assets/icons/sizes/icon-192.png</image192x192>
 		<image512x512>assets/icons/sizes/icon-512.png</image512x512>
+		<image732x412>assets/icons/sizes/icon-732.png</image732x412>
 		<image1024x1024>assets/icons/sizes/icon-1024.png</image1024x1024>
 	</icon>
 
-	<!-- Whether the application handles the update when a user double-clicks an update version
-	of the AIR file (true), or the default AIR application installer handles the update (false).
+	<!-- Whether the application handles the update when a user double-clicks an update version
+	of the AIR file (true), or the default AIR application installer handles the update (false).
 	Optional. Default false. -->
 	<!-- <customUpdateUI></customUpdateUI> -->
-
-	<!-- Whether the application can be launched when the user clicks a link in a web browser.
+	
+	<!-- Whether the application can be launched when the user clicks a link in a web browser.
 	Optional. Default false. -->
-	<!-- <allowBrowserInvocation></allowBrowserInvocation> -->
+	<allowBrowserInvocation></allowBrowserInvocation>
 
 	<!-- Listing of file types for which the application can register. Optional. -->
 	<!-- <fileTypes> -->
 
-	<!-- Defines one file type. Optional. -->
-	<!-- <fileType> -->
-
-	<!-- The name that the system displays for the registered file type. Required. -->
-	<!-- <name></name> -->
-
-	<!-- The extension to register. Required. -->
-	<!-- <extension></extension> -->
-
-	<!-- The description of the file type. Optional. -->
-	<!-- <description></description> -->
-
-	<!-- The MIME content type. -->
-	<!-- <contentType></contentType> -->
+		<!-- Defines one file type. Optional. -->
+		<!-- <fileType> -->
 
-	<!-- The icon to display for the file type. Optional. -->
-	<!-- <icon>
-		<image16x16></image16x16>
-		<image32x32></image32x32>
-		<image48x48></image48x48>
-		<image128x128></image128x128>
-	</icon> -->
+			<!-- The name that the system displays for the registered file type. Required. -->
+			<!-- <name></name> -->
 
-	<!-- </fileType> -->
+			<!-- The extension to register. Required. -->
+			<!-- <extension></extension> -->
+			
+			<!-- The description of the file type. Optional. -->
+			<!-- <description></description> -->
+			
+			<!-- The MIME content type. -->
+			<!-- <contentType></contentType> -->
+			
+			<!-- The icon to display for the file type. Optional. -->
+			<!-- <icon>
+				<image16x16></image16x16>
+				<image32x32></image32x32>
+				<image48x48></image48x48>
+				<image128x128></image128x128>
+			</icon> -->
+			
+		<!-- </fileType> -->
 	<!-- </fileTypes> -->
 
-	<!-- iOS specific capabilities -->
+    <!-- iOS specific capabilities -->
 	<!-- <iPhone> -->
-	<!-- A list of plist key/value pairs to be added to the application Info.plist -->
-	<!-- <InfoAdditions>
-		<![CDATA[
-			<key>UIDeviceFamily</key>
-			<array>
-				<string>1</string>
-				<string>2</string>
-			</array>
-			<key>UIStatusBarStyle</key>
-			<string>UIStatusBarStyleBlackOpaque</string>
-			<key>UIRequiresPersistentWiFi</key>
-			<string>YES</string>
-		]]>
-	</InfoAdditions> -->
-	<!-- A list of plist key/value pairs to be added to the application Entitlements.plist -->
-	<!-- <Entitlements>
-		<![CDATA[
-			<key>keychain-access-groups</key>
-			<array>
-				<string></string>
-				<string></string>
-			</array>
-		]]>
-	</Entitlements> -->
+		<!-- A list of plist key/value pairs to be added to the application Info.plist -->
+		<!-- <InfoAdditions>
+            <![CDATA[
+                <key>UIDeviceFamily</key>
+                <array>
+                    <string>1</string>
+                    <string>2</string>
+                </array>
+                <key>UIStatusBarStyle</key>
+                <string>UIStatusBarStyleBlackOpaque</string>
+                <key>UIRequiresPersistentWiFi</key>
+                <string>YES</string>
+            ]]>
+        </InfoAdditions> -->
+        <!-- A list of plist key/value pairs to be added to the application Entitlements.plist -->
+		<!-- <Entitlements>
+            <![CDATA[
+                <key>keychain-access-groups</key>
+                <array>
+                    <string></string>
+                    <string></string>
+                </array>
+            ]]>
+        </Entitlements> -->
 	<!-- Display Resolution for the app (either "standard" or "high"). Optional. Default "standard" -->
 	<!-- <requestedDisplayResolution></requestedDisplayResolution> -->
+	<!-- Forcing Render Mode CPU for the devices mentioned. Optional  -->
+	<!-- <forceCPURenderModeForDevices></forceCPURenderModeForDevices> -->
+	<!-- File containing line separated list of external swf paths. These swfs won't be 
+	packaged inside the application and corresponding stripped swfs will be output in 
+	externalStrippedSwfs folder. -->
+	<!-- <externalSwfs></externalSwfs> -->
 	<!-- </iPhone> -->
 
 	<!-- Specify Android specific tags that get passed to AndroidManifest.xml file. -->
-	<!--<android> -->
-	<!--	<manifestAdditions>
-		<![CDATA[
-			<manifest android:installLocation="auto">
-				<uses-permission android:name="android.permission.INTERNET"/>
-				<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
-				<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION"/>
-				<uses-feature android:required="true" android:name="android.hardware.touchscreen.multitouch"/>
-				<application android:enabled="true">
-					<activity android:excludeFromRecents="false">
-						<intent-filter>
-							<action android:name="android.intent.action.MAIN"/>
-							<category android:name="android.intent.category.LAUNCHER"/>
-						</intent-filter>
-					</activity>
-				</application>
-			</manifest>
-		]]>
-		</manifestAdditions> -->
-	<!-- Color depth for the app (either "32bit" or "16bit"). Optional. Default 16bit before namespace 3.0, 32bit after -->
-	<!-- <colorDepth></colorDepth> -->
-	<!-- </android> -->
+    <!--<android> -->
+    <!--	<manifestAdditions>
+		<![CDATA[
+			<manifest android:installLocation="auto">
+				<uses-permission android:name="android.permission.INTERNET"/>
+				<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
+				<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION"/>
+				<uses-feature android:required="true" android:name="android.hardware.touchscreen.multitouch"/>
+				<application android:enabled="true">
+					<activity android:excludeFromRecents="false">
+						<intent-filter>
+							<action android:name="android.intent.action.MAIN"/>
+							<category android:name="android.intent.category.LAUNCHER"/>
+						</intent-filter>
+					</activity>
+				</application>
+            </manifest>
+		]]>
+        </manifestAdditions> -->
+	    <!-- Color depth for the app (either "32bit" or "16bit"). Optional. Default 16bit before namespace 3.0, 32bit after -->
+        <!-- <colorDepth></colorDepth> -->
+        <!-- Indicates if the app contains video or not. Necessary for ordering of video planes with graphics plane, especially in Jellybean - if you app does video this must be set to true - valid values are true or false -->
+        <!-- <containsVideo></containsVideo> -->
+        <!-- Indicates if webContents (HTML/CSS/JavaScript) can be inspected in browser. Optional . Default value is 'false' -->
+        <!-- <webContentsDebuggingEnabled></webContentsDebuggingEnabled> -->
+    <!-- </android> -->
 	<!-- End of the schema for adding the android specific tags in AndroidManifest.xml file -->
 
+
 	<android>
 		<colorDepth>16bit</colorDepth>
 		<manifestAdditions>
@@ -332,7 +362,7 @@
 		<requestedDisplayResolution>high</requestedDisplayResolution>
 	</iPhone>
 	
-<extensions>
+	<extensions>
         <extensionID>com.freshplanet.AirCapabilities</extensionID>
         <extensionID>com.juankpro.ane.LocalNotification</extensionID>
     </extensions>
diff --git a/clients/flash/air-client/src/Main.mxml b/clients/flash/air-client/src/Main.mxml
index a80084fc93f3f949f6dc7871b411a5d188e89998..bae2dbc5f895f21de8f7c01bbd530500c7fa983b 100755
--- a/clients/flash/air-client/src/Main.mxml
+++ b/clients/flash/air-client/src/Main.mxml
@@ -3,10 +3,16 @@
 			   xmlns:s="library://ns.adobe.com/flex/spark"
 			   applicationComplete="applicationCompleteHandler(event)"
 			   preinitialize="preinitializeHandler(event)"
-			   runtimeDPIProvider="CustomRuntimeDPIProvider"
-			   styleName="mainshellStyle"
+			   styleName="mainshell"
 			   xmlns:main="org.bigbluebutton.air.main.views.*">
+	<fx:Style source="../../shared/assets/css/common.css" />
 	<fx:Style source="Default.css" />
+	<fx:Style source="css/ldpi.css" />
+	<fx:Style source="css/mdpi.css" />
+	<fx:Style source="css/hdpi.css" />
+	<fx:Style source="css/xhdpi.css" />
+	<fx:Style source="css/xxhdpi.css" />
+	<fx:Style source="css/xxxhdpi.css" />
 
 	<fx:Declarations>
 		<!-- Place non-visual elements (e.g., services, value objects) here -->
@@ -14,7 +20,7 @@
 
 	<fx:Script>
 		<![CDATA[
-			include "ColorPalette.as"
+			include "../../shared/ColorPalette.as"
 			
 			import com.adobe.utils.LocaleUtil;
 			
@@ -92,17 +98,14 @@
 	<main:LoadingScreen width="100%"
 						height="100%" />
 
-	<s:Group width="100%"
-			 height="100%"
-			 id="mainshell"
-			 visible="false">
-		<s:layout>
-			<s:VerticalLayout gap="0"
-							  horizontalAlign="center" />
-		</s:layout>
-
+	<s:VGroup width="100%"
+			  height="100%"
+			  id="mainshell"
+			  visible="false"
+			  gap="0"
+			  horizontalAlign="center">
 		<main:PagesNavigatorView id="pagesNavigatorView"
 								 width="100%"
 								 height="100%" />
-	</s:Group>
+	</s:VGroup>
 </s:Application>
diff --git a/clients/flash/air-client/src/assets b/clients/flash/air-client/src/assets
deleted file mode 120000
index aa0e12fe3d9ec3d718ebc57bde4940f8888c00a2..0000000000000000000000000000000000000000
--- a/clients/flash/air-client/src/assets
+++ /dev/null
@@ -1 +0,0 @@
-../../common-library/src/main/actionscript/assets/
\ No newline at end of file
diff --git a/clients/flash/air-client/src/css/hdpi.css b/clients/flash/air-client/src/css/hdpi.css
new file mode 100644
index 0000000000000000000000000000000000000000..4b7186bbda67e231ec28de325e073e71007af8fb
--- /dev/null
+++ b/clients/flash/air-client/src/css/hdpi.css
@@ -0,0 +1,8 @@
+@namespace s "library://ns.adobe.com/flex/spark";
+@namespace main "org.bigbluebutton.air.main.views.*";
+
+@media (application-dpi: 240) {
+	main|LoadingScreen {
+		fontSize        : 24;
+	}
+}
diff --git a/clients/flash/air-client/src/css/ldpi.css b/clients/flash/air-client/src/css/ldpi.css
new file mode 100644
index 0000000000000000000000000000000000000000..f1d77ca86f5410687958273d466017c126a662bc
--- /dev/null
+++ b/clients/flash/air-client/src/css/ldpi.css
@@ -0,0 +1,8 @@
+@namespace s "library://ns.adobe.com/flex/spark";
+@namespace main "org.bigbluebutton.air.main.views.*";
+
+@media (application-dpi: 120) {
+	main|LoadingScreen {
+		fontSize        : 12;
+	}
+}
\ No newline at end of file
diff --git a/clients/flash/air-client/src/css/mdpi.css b/clients/flash/air-client/src/css/mdpi.css
new file mode 100644
index 0000000000000000000000000000000000000000..bc12234814e8376b28cb46f8e5680132cb810ec4
--- /dev/null
+++ b/clients/flash/air-client/src/css/mdpi.css
@@ -0,0 +1,8 @@
+@namespace s "library://ns.adobe.com/flex/spark";
+@namespace main "org.bigbluebutton.air.main.views.*";
+
+@media (application-dpi: 160) {
+	main|LoadingScreen {
+		fontSize        : 16;
+	}
+}
diff --git a/clients/flash/air-client/src/css/xhdpi.css b/clients/flash/air-client/src/css/xhdpi.css
new file mode 100644
index 0000000000000000000000000000000000000000..ccd825d91fefdd4b1633979cb03bdf6876702692
--- /dev/null
+++ b/clients/flash/air-client/src/css/xhdpi.css
@@ -0,0 +1,83 @@
+@namespace s "library://ns.adobe.com/flex/spark";
+@namespace main "org.bigbluebutton.air.main.views.*";
+@namespace mainviews "org.bigbluebutton.lib.main.views.*";
+@namespace skins "org.bigbluebutton.lib.main.views.skins.*";
+
+@media (application-dpi: 320) {
+	main|LoadingScreen {
+		fontSize : 34;
+	}
+	
+	main|TopToolbarAIR {
+		height : 80;
+	}
+	
+	main|MainView {
+		menuHeight    : 160;
+		toolbarHeight : 80;
+	}
+	
+	mainviews|MenuButtonsBase {
+		bottom   : 35;
+		top      : 14;
+		gap      : 45;
+	}
+	
+	skins|MenuButtonSkin {
+		fontSize     : 24;
+		iconSize     : 36;
+		labelPadding : 12;
+	}
+	
+	.titleLabel
+	{
+		fontSize : 34;
+		left     : 90;
+		right    : 90;
+		top      : 25;
+	}
+	
+	.topButton
+	{
+		iconSize : 30;
+		top      : 25;
+	}
+	
+	.topLeftButton {
+		left : 30;
+	}
+	
+	.topRightButton {
+		right : 30;
+	}
+	
+	.participantIcon
+	{
+		width    : 32;
+		height   : 32;
+		fontSize : 12;
+	}
+	
+	.chatMessage
+	{
+		nameFontSize : 16;
+		timeFontSize : 16;
+	}
+	
+	.sendButton
+	{
+		fontSize     : 20;
+		borderWeight : 2;
+		cornerRadius : 4;
+	}
+	
+	.content
+	{
+		fontSize : 14;
+	}
+	
+	.icon
+	{
+		fontSize : 20;
+	}
+}
diff --git a/clients/flash/air-client/src/css/xxhdpi.css b/clients/flash/air-client/src/css/xxhdpi.css
new file mode 100644
index 0000000000000000000000000000000000000000..a5ce269179e61aaeab42cefd2fc6aceebbefe01a
--- /dev/null
+++ b/clients/flash/air-client/src/css/xxhdpi.css
@@ -0,0 +1,8 @@
+@namespace s "library://ns.adobe.com/flex/spark";
+@namespace main "org.bigbluebutton.air.main.views.*";
+
+@media (application-dpi: 480) {
+	main|LoadingScreen {
+		fontSize        : 48;
+	}
+}
\ No newline at end of file
diff --git a/clients/flash/air-client/src/css/xxxhdpi.css b/clients/flash/air-client/src/css/xxxhdpi.css
new file mode 100644
index 0000000000000000000000000000000000000000..c74043acccede1257208e42a646f76a0c1e16df0
--- /dev/null
+++ b/clients/flash/air-client/src/css/xxxhdpi.css
@@ -0,0 +1,8 @@
+@namespace s "library://ns.adobe.com/flex/spark";
+@namespace main "org.bigbluebutton.air.main.views.*";
+
+@media (application-dpi: 640) {
+	main|LoadingScreen {
+		fontSize        : 64;
+	}
+}
\ No newline at end of file
diff --git a/clients/flash/air-client/src/org/bigbluebutton/air/chat/views/ChatRoomView.as b/clients/flash/air-client/src/org/bigbluebutton/air/chat/views/ChatRoomView.as
index 5a7d6040e8efe93a21d4692b183d4189cc9b39b4..60bde8a2373f6aad3fe38b5fe3dc8e33b815fa8d 100755
--- a/clients/flash/air-client/src/org/bigbluebutton/air/chat/views/ChatRoomView.as
+++ b/clients/flash/air-client/src/org/bigbluebutton/air/chat/views/ChatRoomView.as
@@ -8,7 +8,7 @@ package org.bigbluebutton.air.chat.views {
 	public class ChatRoomView extends NoTabView {
 		public function ChatRoomView() {
 			super();
-			styleName = "mainViewStyle";
+			styleName = "mainView";
 			
 			var l:VerticalLayout = new VerticalLayout();
 			l.gap = 0;
@@ -21,7 +21,7 @@ package org.bigbluebutton.air.chat.views {
 			addElement(topToolbar);
 			
 			var skinnableWrapper:SkinnableContainer = new SkinnableContainer();
-			skinnableWrapper.styleName = "subViewContentStyle";
+			skinnableWrapper.styleName = "subViewContent";
 			skinnableWrapper.percentWidth = 100;
 			skinnableWrapper.percentHeight = 100;
 			
diff --git a/clients/flash/air-client/src/org/bigbluebutton/air/chat/views/TopToolbarChat.as b/clients/flash/air-client/src/org/bigbluebutton/air/chat/views/TopToolbarChat.as
index 5b37d1412456a95462eb5286018ce0ae9db9859c..aab3809db17128a6b3627c05d5c96652558e59d0 100755
--- a/clients/flash/air-client/src/org/bigbluebutton/air/chat/views/TopToolbarChat.as
+++ b/clients/flash/air-client/src/org/bigbluebutton/air/chat/views/TopToolbarChat.as
@@ -5,8 +5,8 @@ package org.bigbluebutton.air.chat.views {
 		public function TopToolbarChat() {
 			super();
 			
-			leftButton.styleName = "backButtonStyle topButtonStyle";
-			rightButton.styleName = "presentationButtonStyle topButtonStyle";
+			leftButton.styleName = "backButton topButton";
+			rightButton.styleName = "presentationButton topButton";
 		}
 	}
 }
diff --git a/clients/flash/air-client/src/org/bigbluebutton/air/common/views/NoTabView.as b/clients/flash/air-client/src/org/bigbluebutton/air/common/views/NoTabView.as
index e5ce34ef7de5d89b7f4bfc0a1b80b8d45eac2976..00d3965eb79e88a5efd76d8988c58680d9f8838b 100644
--- a/clients/flash/air-client/src/org/bigbluebutton/air/common/views/NoTabView.as
+++ b/clients/flash/air-client/src/org/bigbluebutton/air/common/views/NoTabView.as
@@ -1,6 +1,5 @@
 package org.bigbluebutton.air.common.views {
 	
-	import flash.events.StageOrientationEvent;
 	import spark.components.View;
 	
 	public class NoTabView extends View {
diff --git a/clients/flash/air-client/src/org/bigbluebutton/air/common/views/ParticipantIcon.mxml b/clients/flash/air-client/src/org/bigbluebutton/air/common/views/ParticipantIcon.mxml
index 7cb0fb8d57b227f5ec1e5304ceb97ead9a874411..5428433f9f47808b593b4ea64e4685fa9b732a91 100644
--- a/clients/flash/air-client/src/org/bigbluebutton/air/common/views/ParticipantIcon.mxml
+++ b/clients/flash/air-client/src/org/bigbluebutton/air/common/views/ParticipantIcon.mxml
@@ -1,7 +1,7 @@
 <s:Group xmlns:fx="http://ns.adobe.com/mxml/2009"
 		 xmlns:s="library://ns.adobe.com/flex/spark"
 		 xmlns:mx="library://ns.adobe.com/flex/mx"
-		 styleName="participantIconStyle">
+		 styleName="participantIcon">
 
 	<s:Label id="firstLetters"
 			 styleName="nameIconText"
diff --git a/clients/flash/air-client/src/org/bigbluebutton/air/main/services/JoinService.as b/clients/flash/air-client/src/org/bigbluebutton/air/main/services/JoinService.as
index ecd9fbcef543f9c3c1c48357324df9225d3a1838..98c90bf94393d785156fe2cc6ddb295c13008f31 100755
--- a/clients/flash/air-client/src/org/bigbluebutton/air/main/services/JoinService.as
+++ b/clients/flash/air-client/src/org/bigbluebutton/air/main/services/JoinService.as
@@ -82,10 +82,10 @@ package org.bigbluebutton.air.main.services {
 		protected function onSuccess(data:Object, responseUrl:String, urlRequest:URLRequest, httpStatusCode:Number = 200):void {
 			if (httpStatusCode == 200) {
 				try {
-					/* If redirect is set to false on the join url the response will be XML and there will be 
-					 * an auth_token in the response that can be used to join. If redirect is set to true or 
-					 * left off there will be a sessionToken attached to the responseURL that can be used to 
-					 * join. And if there is an issue with the join request there is a redirect and error 
+					/* If redirect is set to false on the join url the response will be XML and there will be
+					 * an auth_token in the response that can be used to join. If redirect is set to true or
+					 * left off there will be a sessionToken attached to the responseURL that can be used to
+					 * join. And if there is an issue with the join request there is a redirect and error
 					 *  message is in the responseURL as error.
 					 */
 					var xml:XML = new XML(data);
@@ -107,14 +107,14 @@ package org.bigbluebutton.air.main.services {
 					// Need to grab either the error or the sessionToken from the response URL
 					var infoIndex:int = responseUrl.indexOf(ERROR_QUERY_PARAM);
 					if (infoIndex != -1) {
-						var errors:String = unescape(responseUrl.substring(infoIndex+ERROR_QUERY_PARAM.length));
+						var errors:String = unescape(responseUrl.substring(infoIndex + ERROR_QUERY_PARAM.length));
 						trace(errors);
 						onFailure(errors);
 						return
 					}
 					infoIndex = responseUrl.indexOf(TOKEN_QUERY_PARAM);
 					if (infoIndex != -1) {
-						var sessionToken:String = responseUrl.substring(infoIndex+TOKEN_QUERY_PARAM.length);
+						var sessionToken:String = responseUrl.substring(infoIndex + TOKEN_QUERY_PARAM.length);
 						successSignal.dispatch(urlRequest, responseUrl, sessionToken);
 						return;
 					}
diff --git a/clients/flash/air-client/src/org/bigbluebutton/air/main/utils/OrientationCalculator.as b/clients/flash/air-client/src/org/bigbluebutton/air/main/utils/OrientationCalculator.as
index 3ccefb1c049eabce936641605fc4c530a1ae74d9..b37ee6626724f8dca1f57e31b1c40a818e30eaaa 100644
--- a/clients/flash/air-client/src/org/bigbluebutton/air/main/utils/OrientationCalculator.as
+++ b/clients/flash/air-client/src/org/bigbluebutton/air/main/utils/OrientationCalculator.as
@@ -6,16 +6,11 @@
 package org.bigbluebutton.air.main.utils {
 	
 	import flash.display.DisplayObject;
-	import flash.display.Stage;
 	import flash.display.StageOrientation;
 	import flash.events.AccelerometerEvent;
-	import flash.events.Event;
-	import flash.events.EventDispatcher;
 	import flash.events.TimerEvent;
 	import flash.sensors.Accelerometer;
 	import flash.utils.Timer;
-	import flash.utils.clearInterval;
-	import flash.utils.setInterval;
 	
 	public class OrientationCalculator {
 		private static var _checkFrequency:int = 500;
diff --git a/clients/flash/air-client/src/org/bigbluebutton/air/main/views/LoadingScreen.as b/clients/flash/air-client/src/org/bigbluebutton/air/main/views/LoadingScreen.as
index 8d24654d3d3aad49d665d94bd950f4c92524a22c..f2e8fcd566e74c8b55f9390695ec139b9df037f6 100755
--- a/clients/flash/air-client/src/org/bigbluebutton/air/main/views/LoadingScreen.as
+++ b/clients/flash/air-client/src/org/bigbluebutton/air/main/views/LoadingScreen.as
@@ -1,7 +1,8 @@
 package org.bigbluebutton.air.main.views {
-	import spark.components.Group;
 	import spark.components.Label;
 	import spark.components.SkinnableContainer;
+	import spark.layouts.HorizontalAlign;
+	import spark.layouts.VerticalAlign;
 	import spark.layouts.VerticalLayout;
 	
 	public class LoadingScreen extends SkinnableContainer {
@@ -14,20 +15,15 @@ package org.bigbluebutton.air.main.views {
 		public function LoadingScreen() {
 			super();
 			
-			var l:VerticalLayout = new VerticalLayout();
-			l.horizontalAlign = "center";
-			l.verticalAlign = "middle";
-			this.layout = l;
-			this.setStyle("backgroundColor", 0xDDDDDD);
-			this.setStyle("backgroundAlpha", 1);
+			var layout:VerticalLayout = new VerticalLayout();
+			layout.horizontalAlign = HorizontalAlign.CENTER;
+			layout.verticalAlign = VerticalAlign.MIDDLE;
+			this.layout = layout;
 			
 			_stateLabel = new Label();
 			_stateLabel.text = "Loading";
+			_stateLabel.percentWidth = 80;
 			addElement(_stateLabel);
-			
-			percentHeight = percentWidth = 100;
 		}
 	}
 }
-
-
diff --git a/clients/flash/air-client/src/org/bigbluebutton/air/main/views/MainView.as b/clients/flash/air-client/src/org/bigbluebutton/air/main/views/MainView.as
index 880498c431835dda7365cd50f01c2aa48b834e76..6882e93761ba118e16b0be1409cc6eab943dae2d 100755
--- a/clients/flash/air-client/src/org/bigbluebutton/air/main/views/MainView.as
+++ b/clients/flash/air-client/src/org/bigbluebutton/air/main/views/MainView.as
@@ -1,33 +1,36 @@
 package org.bigbluebutton.air.main.views {
+	import spark.layouts.VerticalLayout;
+	
 	import org.bigbluebutton.air.common.views.NoTabView;
 	import org.bigbluebutton.lib.main.views.MenuButtonsBase;
 	import org.bigbluebutton.lib.presentation.views.PresentationViewBase;
+	import org.osmf.layout.HorizontalAlign;
 	
-	import spark.layouts.VerticalLayout;
-	
+	[Style(name = "menuHeight", inherit = "no", type = "Number")]
+	[Style(name = "toolbarHeight", inherit = "no", type = "Number")]
 	public class MainView extends NoTabView {
 		private var _topToolbar:TopToolbarAIR;
+		
 		private var _presentationView:PresentationViewBase;
+		
 		private var _menuButtons:MenuButtonsBase;
 		
 		public function MainView() {
 			super();
-			styleName = "mainViewStyle";
 			
-			var l:VerticalLayout = new VerticalLayout();
-			l.gap = 0;
-			l.horizontalAlign = "center";
-			layout = l;
+			var vLayout:VerticalLayout = new VerticalLayout();
+			vLayout.gap = 0;
+			vLayout.horizontalAlign = HorizontalAlign.CENTER;
+			layout = vLayout;
 			
 			_topToolbar = new TopToolbarAIR();
 			_topToolbar.percentWidth = 100;
-			_topToolbar.height = 60;
 			addElement(_topToolbar);
 			
 			_presentationView = new PresentationViewBase();
 			_presentationView.percentWidth = 100;
 			_presentationView.percentHeight = 100;
-			addElement(_presentationView);			
+			addElement(_presentationView);
 			
 			_menuButtons = new MenuButtonsBase();
 			addElement(_menuButtons);
@@ -38,6 +41,9 @@ package org.bigbluebutton.air.main.views {
 			
 			_presentationView.width = w;
 			_presentationView.height = h - _topToolbar.height - _menuButtons.height;
+			
+			_menuButtons.height = getStyle("menuHeight");
+			_topToolbar.height = getStyle("toolbarHeight");
 		}
 	}
 }
diff --git a/clients/flash/air-client/src/org/bigbluebutton/air/main/views/PagesNavigatorViewMediator.as b/clients/flash/air-client/src/org/bigbluebutton/air/main/views/PagesNavigatorViewMediator.as
index e58daf2a7fa3b79ac915f86ce0152d685a0780e4..f217abffc162cd1eee91f1e42db514a7c9c390b6 100755
--- a/clients/flash/air-client/src/org/bigbluebutton/air/main/views/PagesNavigatorViewMediator.as
+++ b/clients/flash/air-client/src/org/bigbluebutton/air/main/views/PagesNavigatorViewMediator.as
@@ -1,94 +1,94 @@
-package org.bigbluebutton.air.main.views {
-	
-	import flash.desktop.NativeApplication;
-	import flash.events.KeyboardEvent;
-	import flash.ui.Keyboard;
-	
-	import mx.events.FlexEvent;
-	
-	import spark.transitions.CrossFadeViewTransition;
-	import spark.transitions.SlideViewTransition;
-	import spark.transitions.ViewTransitionBase;
-	import spark.transitions.ViewTransitionDirection;
-	
-	import org.bigbluebutton.air.common.PageEnum;
-	import org.bigbluebutton.air.common.TransitionAnimationEnum;
-	import org.bigbluebutton.air.main.models.IUISession;
-	
-	import robotlegs.bender.bundles.mvcs.Mediator;
-	import org.bigbluebutton.air.main.viewsold.pagesnavigator.IPagesNavigatorView;
-	
-	public class PagesNavigatorViewMediator extends Mediator {
-		
-		[Inject]
-		public var view:PagesNavigatorView;
-		
-		[Inject]
-		public var uiSession:IUISession
-		
-		override public function initialize():void {
-			NativeApplication.nativeApplication.addEventListener(KeyboardEvent.KEY_DOWN, onKeyDown, false, 0, true);
-			uiSession.pageChangedSignal.add(changePage);
-			uiSession.pushPage(PageEnum.MAIN);
-		}
-		
-		private function onKeyDown(event:KeyboardEvent):void {
-			if (event.keyCode == Keyboard.BACK) {
-				event.preventDefault();
-				event.stopImmediatePropagation();
-				uiSession.pushPage(PageEnum.EXIT);
-			}
-		}
-		
-		protected function changePage(pageName:String, pageRemoved:Boolean = false, animation:int = TransitionAnimationEnum.APPEAR, transition:ViewTransitionBase = null):void {
-			switch (animation) {
-				case TransitionAnimationEnum.APPEAR:  {
-					var appear:CrossFadeViewTransition = new CrossFadeViewTransition;
-					appear.duration = 50;
-					appear.addEventListener(FlexEvent.TRANSITION_START, onTransitionStart);
-					transition = appear;
-					break;
-				}
-				case TransitionAnimationEnum.SLIDE_LEFT:  {
-					var slideLeft:SlideViewTransition = new SlideViewTransition();
-					slideLeft.duration = 300;
-					slideLeft.direction = ViewTransitionDirection.LEFT;
-					slideLeft.addEventListener(FlexEvent.TRANSITION_START, onTransitionStart);
-					transition = slideLeft;
-					break;
-				}
-				case TransitionAnimationEnum.SLIDE_RIGHT:  {
-					var slideRight:SlideViewTransition = new SlideViewTransition();
-					slideRight.duration = 300;
-					slideRight.direction = ViewTransitionDirection.RIGHT;
-					slideRight.addEventListener(FlexEvent.TRANSITION_START, onTransitionStart);
-					transition = slideRight;
-					break;
-				}
-				default:  {
-					break;
-				}
-			}
-			if (pageName == PageEnum.MAIN) {
-				view.popAll();
-				view.pushView(PageEnum.getClassfromName(pageName), null, null, transition);
-			} else if (pageRemoved) {
-				view.popView(transition);
-			} else if (pageName != null && pageName != "") {
-				view.pushView(PageEnum.getClassfromName(pageName), null, null, transition);
-			}
-		}
-		
-		protected function onTransitionStart(event:FlexEvent):void {
-			uiSession.pageTransitionStartSignal.dispatch(uiSession.lastPage);
-		}
-		
-		override public function destroy():void {
-			NativeApplication.nativeApplication.removeEventListener(KeyboardEvent.KEY_DOWN, onKeyDown);
-			uiSession.pageChangedSignal.remove(changePage);
-			super.destroy();
-			view.dispose();
-			view = null;
-		}
-	}
-}
+package org.bigbluebutton.air.main.views {
+	
+	import flash.desktop.NativeApplication;
+	import flash.events.KeyboardEvent;
+	import flash.ui.Keyboard;
+	
+	import mx.events.FlexEvent;
+	
+	import spark.transitions.CrossFadeViewTransition;
+	import spark.transitions.SlideViewTransition;
+	import spark.transitions.ViewTransitionBase;
+	import spark.transitions.ViewTransitionDirection;
+	
+	import org.bigbluebutton.air.common.PageEnum;
+	import org.bigbluebutton.air.common.TransitionAnimationEnum;
+	import org.bigbluebutton.air.main.models.IUISession;
+	
+	import robotlegs.bender.bundles.mvcs.Mediator;
+	
+	public class PagesNavigatorViewMediator extends Mediator {
+		
+		[Inject]
+		public var view:PagesNavigatorView;
+		
+		[Inject]
+		public var uiSession:IUISession
+		
+		override public function initialize():void {
+			NativeApplication.nativeApplication.addEventListener(KeyboardEvent.KEY_DOWN, onKeyDown, false, 0, true);
+			uiSession.pageChangedSignal.add(changePage);
+			uiSession.pushPage(PageEnum.MAIN);
+		}
+		
+		private function onKeyDown(event:KeyboardEvent):void {
+			if (event.keyCode == Keyboard.BACK) {
+				event.preventDefault();
+				event.stopImmediatePropagation();
+				uiSession.pushPage(PageEnum.EXIT);
+			}
+		}
+		
+		protected function changePage(pageName:String, pageRemoved:Boolean = false, animation:int = TransitionAnimationEnum.APPEAR, transition:ViewTransitionBase = null):void {
+			trace("PagesNavigatorViewMediator request change page to: " + pageName);
+			switch (animation) {
+				case TransitionAnimationEnum.APPEAR:  {
+					var appear:CrossFadeViewTransition = new CrossFadeViewTransition;
+					appear.duration = 50;
+					appear.addEventListener(FlexEvent.TRANSITION_START, onTransitionStart);
+					transition = appear;
+					break;
+				}
+				case TransitionAnimationEnum.SLIDE_LEFT:  {
+					var slideLeft:SlideViewTransition = new SlideViewTransition();
+					slideLeft.duration = 300;
+					slideLeft.direction = ViewTransitionDirection.LEFT;
+					slideLeft.addEventListener(FlexEvent.TRANSITION_START, onTransitionStart);
+					transition = slideLeft;
+					break;
+				}
+				case TransitionAnimationEnum.SLIDE_RIGHT:  {
+					var slideRight:SlideViewTransition = new SlideViewTransition();
+					slideRight.duration = 300;
+					slideRight.direction = ViewTransitionDirection.RIGHT;
+					slideRight.addEventListener(FlexEvent.TRANSITION_START, onTransitionStart);
+					transition = slideRight;
+					break;
+				}
+				default:  {
+					break;
+				}
+			}
+			if (pageName == PageEnum.MAIN) {
+				view.popAll();
+				view.pushView(PageEnum.getClassfromName(pageName), null, null, transition);
+			} else if (pageRemoved) {
+				view.popView(transition);
+			} else if (pageName != null && pageName != "") {
+				view.pushView(PageEnum.getClassfromName(pageName), null, null, transition);
+			}
+		}
+		
+		protected function onTransitionStart(event:FlexEvent):void {
+			uiSession.pageTransitionStartSignal.dispatch(uiSession.lastPage);
+		}
+		
+		override public function destroy():void {
+			NativeApplication.nativeApplication.removeEventListener(KeyboardEvent.KEY_DOWN, onKeyDown);
+			uiSession.pageChangedSignal.remove(changePage);
+			super.destroy();
+			view.dispose();
+			view = null;
+		}
+	}
+}
diff --git a/clients/flash/air-client/src/org/bigbluebutton/air/participants/views/ParticipantsView.as b/clients/flash/air-client/src/org/bigbluebutton/air/participants/views/ParticipantsView.as
index 4e7e6afaa746b15bad4e746ff1d6f7b8e5e9bc49..38ee270bd2687e43e9c05665a7f4c75102dbed59 100755
--- a/clients/flash/air-client/src/org/bigbluebutton/air/participants/views/ParticipantsView.as
+++ b/clients/flash/air-client/src/org/bigbluebutton/air/participants/views/ParticipantsView.as
@@ -1,15 +1,14 @@
 package org.bigbluebutton.air.participants.views {
-	import org.bigbluebutton.air.common.views.NoTabView;
-	import org.bigbluebutton.lib.participants.views.ParticipantsViewBase;
-	
-	import spark.components.Label;
 	import spark.components.SkinnableContainer;
 	import spark.layouts.VerticalLayout;
 	
+	import org.bigbluebutton.air.common.views.NoTabView;
+	import org.bigbluebutton.lib.participants.views.ParticipantsViewBase;
+	
 	public class ParticipantsView extends NoTabView {
 		public function ParticipantsView() {
 			super();
-			styleName = "mainViewStyle";
+			styleName = "mainView";
 			
 			var l:VerticalLayout = new VerticalLayout();
 			l.gap = 0;
@@ -18,11 +17,11 @@ package org.bigbluebutton.air.participants.views {
 			
 			var topToolbar:TopToolbarParticipants = new TopToolbarParticipants();
 			topToolbar.percentWidth = 100;
-			topToolbar.height = 60;
+			topToolbar.height = 80;
 			addElement(topToolbar);
 			
 			var skinnableWrapper:SkinnableContainer = new SkinnableContainer();
-			skinnableWrapper.styleName = "subViewContentStyle";
+			skinnableWrapper.styleName = "subViewContent";
 			skinnableWrapper.percentWidth = 100;
 			skinnableWrapper.percentHeight = 100;
 			
diff --git a/clients/flash/air-client/src/org/bigbluebutton/air/participants/views/TopToolbarParticipants.as b/clients/flash/air-client/src/org/bigbluebutton/air/participants/views/TopToolbarParticipants.as
index 58b69f7f2588eaef22349e7a50369c2c356b44a5..827cde4181deaedd5b2edea156cca1562b696290 100755
--- a/clients/flash/air-client/src/org/bigbluebutton/air/participants/views/TopToolbarParticipants.as
+++ b/clients/flash/air-client/src/org/bigbluebutton/air/participants/views/TopToolbarParticipants.as
@@ -5,8 +5,8 @@ package org.bigbluebutton.air.participants.views {
 		public function TopToolbarParticipants() {
 			super();
 			
-			leftButton.styleName = "topButtonStyle";
-			rightButton.styleName = "presentationButtonStyle topButtonStyle";
+			leftButton.styleName = "topButton";
+			rightButton.styleName = "presentationButton topButton";
 		}
 	}
 }
diff --git a/clients/flash/air-client/src/org/bigbluebutton/air/presentation/views/selectwebcam/StreamItemRenderer.mxml b/clients/flash/air-client/src/org/bigbluebutton/air/presentation/views/selectwebcam/StreamItemRenderer.mxml
index d4124c89453af3e622d4ed7032c165b8eb582673..82f3a21851369ed1f3d538bd364bac8d7d249db4 100644
--- a/clients/flash/air-client/src/org/bigbluebutton/air/presentation/views/selectwebcam/StreamItemRenderer.mxml
+++ b/clients/flash/air-client/src/org/bigbluebutton/air/presentation/views/selectwebcam/StreamItemRenderer.mxml
@@ -3,7 +3,7 @@
 				xmlns:s="library://ns.adobe.com/flex/spark"
 				xmlns:mx="library://ns.adobe.com/flex/mx"
 				xmlns:views="org.bigbluebutton.air.common.views.*"
-				styleName="streamItemStyle"
+				styleName="streamItem"
 				dataChange="onDataChange(event)"
 				height="100%">
 	<s:states>
@@ -69,8 +69,8 @@
 		</s:VGroup>
 
 
-		<s:Image styleName.selected="iconStyle selectedIconStyle"
-				 styleName.normal="iconStyle unselectedIconStyle" />
+		<s:Image styleName.selected="icon selectedIcon"
+				 styleName.normal="icon unselectedIcon" />
 	</s:Group>
 
 	<s:Line width="100%">
diff --git a/clients/flash/air-client/src/org/bigbluebutton/air/settings/views/audio/AudioSettingsViewBase.mxml b/clients/flash/air-client/src/org/bigbluebutton/air/settings/views/audio/AudioSettingsViewBase.mxml
index 49d310e120549b5a12662267ec495fd6da6e246f..e147b67ab4cc42c5db1c1d6cdaf8b108553a536f 100644
--- a/clients/flash/air-client/src/org/bigbluebutton/air/settings/views/audio/AudioSettingsViewBase.mxml
+++ b/clients/flash/air-client/src/org/bigbluebutton/air/settings/views/audio/AudioSettingsViewBase.mxml
@@ -3,7 +3,7 @@
 				 xmlns:s="library://ns.adobe.com/flex/spark"
 				 xmlns:mx="library://ns.adobe.com/flex/mx"
 				 xmlns:views="org.bigbluebutton.air.common.views.*"
-				 styleName="audioSettingsStyle">
+				 styleName="audioSettings">
 
 	<s:Scroller width="100%"
 				height="100%">
@@ -118,7 +118,7 @@
 			<s:Button id="continueToMeeting"
 					  width="90%"
 					  label="{resourceManager.getString('resources', 'audioSettings.continue')}"
-					  styleName="userSettingsButtonStyle logoutButtonStyle contentFontSize" />
+					  styleName="userSettingsButton logoutButton contentFontSize" />
 
 		</s:VGroup>
 	</s:Scroller>
diff --git a/clients/flash/air-client/src/org/bigbluebutton/air/settings/views/camera/CameraSettingsViewBase.mxml b/clients/flash/air-client/src/org/bigbluebutton/air/settings/views/camera/CameraSettingsViewBase.mxml
index b17e708a97ebfbf647af33cba60b79082a3cc686..c59b3e920dac063c7471d40ecdf2bfb9efbde615 100644
--- a/clients/flash/air-client/src/org/bigbluebutton/air/settings/views/camera/CameraSettingsViewBase.mxml
+++ b/clients/flash/air-client/src/org/bigbluebutton/air/settings/views/camera/CameraSettingsViewBase.mxml
@@ -3,7 +3,7 @@
 				xmlns:s="library://ns.adobe.com/flex/spark"
 				xmlns:mx="library://ns.adobe.com/flex/mx"
 				xmlns:view="org.bigbluebutton.air.common.views.*"
-				styleName="cameraSettingsViewStyle">
+				styleName="cameraSettingsView">
 
 	<s:VGroup width="100%"
 			  height="100%"
@@ -21,7 +21,7 @@
 									height="100%" />
 					<s:Label id="noVideoMessage0"
 							 visible="false"
-							 styleName="noVideoMsgStyle contentFontSize"
+							 styleName="noVideoMsg contentFontSize"
 							 textAlign="center"
 							 width="100%"
 							 height="100%"
@@ -37,7 +37,7 @@
 					</s:layout>
 					<s:List id="cameraprofileslist"
 							width="100%"
-							styleName="cameraProfilesListStyle"
+							styleName="cameraProfilesList"
 							labelField="name"
 							itemRenderer="spark.components.LabelItemRenderer">
 						<s:layout>
@@ -52,15 +52,15 @@
 								  includeInLayout="false"
 								  width="100%"
 								  id="rotateCameraBtn0"
-								  styleName="cameraSettingsRotateButtonStyle videoAudioSettingStyle contentFontSize"
+								  styleName="cameraSettingsRotateButton videoAudioSetting contentFontSize"
 								  label="{resourceManager.getString('resources', 'cameraSettings.rotate')}" />
 					</s:HGroup>
 					<s:Button id="swapCameraBtn0"
-							  styleName="cameraSettingsSwapButtonStyle videoAudioSettingStyle contentFontSize"
+							  styleName="cameraSettingsSwapButton videoAudioSetting contentFontSize"
 							  width="100%"
 							  label="{resourceManager.getString('resources', 'cameraSettings.swap')}" />
 					<s:Button id="startCameraButton0"
-							  styleName="cameraSettingsStartButtonStyle videoAudioSettingStyle contentFontSize"
+							  styleName="cameraSettingsStartButton videoAudioSetting contentFontSize"
 							  width="100%"
 							  label="{resourceManager.getString('resources', 'userDetail.cameraBtn.text')}" />
 				</s:Group>
diff --git a/clients/flash/air-client/src/org/bigbluebutton/air/settings/views/lock/LockSettingsViewBase.mxml b/clients/flash/air-client/src/org/bigbluebutton/air/settings/views/lock/LockSettingsViewBase.mxml
index 21789a58a10a4d042821645b4311a10e684534a3..a4eb7e2371aa885c38ee700f97deaa41e5204d1f 100644
--- a/clients/flash/air-client/src/org/bigbluebutton/air/settings/views/lock/LockSettingsViewBase.mxml
+++ b/clients/flash/air-client/src/org/bigbluebutton/air/settings/views/lock/LockSettingsViewBase.mxml
@@ -3,7 +3,7 @@
 				xmlns:s="library://ns.adobe.com/flex/spark"
 				xmlns:mx="library://ns.adobe.com/flex/mx"
 				xmlns:view="org.bigbluebutton.air.common.views.*"
-				styleName="lockSettingsStyle">
+				styleName="lockSettings">
 
 	<s:Scroller width="100%"
 				height="100%">
@@ -91,7 +91,7 @@
 			<s:Button id="apply"
 					  width="90%"
 					  label="{resourceManager.getString('resources', 'lockSettings.apply')}"
-					  styleName="userSettingsButtonStyle logoutButtonStyle contentFontSize" />
+					  styleName="userSettingsButton logoutButton contentFontSize" />
 
 
 		</s:VGroup>
diff --git a/clients/flash/air-client/src/org/bigbluebutton/air/users/views/userdetails/UserDetailsViewBase.mxml b/clients/flash/air-client/src/org/bigbluebutton/air/users/views/userdetails/UserDetailsViewBase.mxml
index 8c46d0ded5d6ee2ad4d2f653d34682d181d82492..a48dd27f6b51b2213cb44539b5f878447edf3ef9 100644
--- a/clients/flash/air-client/src/org/bigbluebutton/air/users/views/userdetails/UserDetailsViewBase.mxml
+++ b/clients/flash/air-client/src/org/bigbluebutton/air/users/views/userdetails/UserDetailsViewBase.mxml
@@ -3,7 +3,7 @@
 				  xmlns:s="library://ns.adobe.com/flex/spark"
 				  xmlns:mx="library://ns.adobe.com/flex/mx"
 				  xmlns:common="org.bigbluebutton.air.common.views.*"
-				  styleName="userDetailViewStyle">
+				  styleName="userDetailView">
 	<s:Scroller width="100%"
 				height="100%"
 				layoutDirection="">
@@ -21,15 +21,15 @@
 									  horizontalAlign="center" />
 				</s:layout>
 				<s:Image id="userDetailIcon"
-						 styleName="iconStyle userDetailIconStyle" />
+						 styleName="icon userDetailIcon" />
 				<s:Label id="userNameText"
 						 width="100%"
 						 textAlign="center"
-						 styleName="userDetailsNameStyle contentFontSize" />
+						 styleName="userDetailsName contentFontSize" />
 				<s:Label id="roleText"
 						 width="100%"
 						 textAlign="center"
-						 styleName="userDetailsStatusStyle subContentFontSize" />
+						 styleName="userDetailsStatus subContentFontSize" />
 			</s:BorderContainer>
 			<s:VGroup width="100%"
 					  height="30%"
@@ -37,20 +37,20 @@
 					  verticalAlign="middle">
 				<s:Label width="50%"
 						 textAlign="center"
-						 styleName="userDetailsStatusStyle contentFontSize"
+						 styleName="userDetailsStatus contentFontSize"
 						 text="{resourceManager.getString('resources', 'userDetail.media')}" />
 				<s:HGroup horizontalAlign="center">
 					<s:Image id="cameraIcon"
-							 styleName="iconStyle cameraIconStyle" />
+							 styleName="icon cameraIcon" />
 					<s:Image id="micIcon"
-							 styleName="iconStyle micIconStyle" />
+							 styleName="icon micIcon" />
 					<s:Image id="micOffIcon"
-							 styleName="iconStyle micOffIconStyle"
+							 styleName="icon micOffIcon"
 							 visible="false"
 							 includeInLayout="false" />
 					<s:Label id="noMediaText"
 							 width="100%"
-							 styleName="userDetailsStatusStyle subContentFontSize"
+							 styleName="userDetailsStatus subContentFontSize"
 							 visible="false"
 							 includeInLayout="false"
 							 text="{resourceManager.getString('resources', 'userDetail.media.noMedia')}" />
@@ -62,40 +62,40 @@
 				<s:Button id="showCameraButton0"
 						  width="90%"
 						  label="{resourceManager.getString('resources', 'userDetail.cameraBtn.text')}"
-						  styleName="userSettingsButtonStyle logoutButtonStyle contentFontSize" />
+						  styleName="userSettingsButton logoutButton contentFontSize" />
 				<s:Button id="showPrivateChat0"
 						  width="90%"
 						  label="{resourceManager.getString('resources', 'userDetail.privateChatBtn.text')}"
-						  styleName="userSettingsButtonStyle logoutButtonStyle contentFontSize" />
+						  styleName="userSettingsButton logoutButton contentFontSize" />
 				<s:Button includeInLayout="false"
 						  visible="false"
 						  width="90%"
 						  label="{resourceManager.getString('resources', 'userDetail.clearStatus')}"
 						  id="clearStatusButton0"
-						  styleName="userSettingsButtonStyle logoutButtonStyle contentFontSize" />
+						  styleName="userSettingsButton logoutButton contentFontSize" />
 				<s:Button includeInLayout="false"
 						  visible="false"
 						  id="makePresenterButton0"
 						  width="90%"
 						  label="{resourceManager.getString('resources', 'userDetail.presenterBtn.text')}"
-						  styleName="userSettingsButtonStyle logoutButtonStyle contentFontSize" />
+						  styleName="userSettingsButton logoutButton contentFontSize" />
 				<s:Button includeInLayout="false"
 						  visible="false"
 						  width="90%"
 						  id="promoteButton0"
-						  styleName="userSettingsButtonStyle logoutButtonStyle contentFontSize" />
+						  styleName="userSettingsButton logoutButton contentFontSize" />
 				<s:Button includeInLayout="false"
 						  visible="false"
 						  width="90%"
 						  id="lockButton0"
 						  label="{resourceManager.getString('resources', 'userDetail.lockButton.text')}"
-						  styleName="userSettingsButtonStyle logoutButtonStyle contentFontSize" />
+						  styleName="userSettingsButton logoutButton contentFontSize" />
 				<s:Button includeInLayout="false"
 						  visible="false"
 						  width="90%"
 						  id="unlockButton0"
 						  label="{resourceManager.getString('resources', 'userDetail.unlockButton.text')}"
-						  styleName="userSettingsButtonStyle logoutButtonStyle contentFontSize" />
+						  styleName="userSettingsButton logoutButton contentFontSize" />
 			</s:VGroup>
 
 		</s:VGroup>
diff --git a/clients/flash/air-client/src/org/bigbluebutton/air/video/VideoConfig.as b/clients/flash/air-client/src/org/bigbluebutton/air/video/VideoConfig.as
index f9f68c1fc50c1b2f82e690e3de36675560329ae7..becfbc6b19eadc7a1c0458edc18a9a5f0df060c6 100755
--- a/clients/flash/air-client/src/org/bigbluebutton/air/video/VideoConfig.as
+++ b/clients/flash/air-client/src/org/bigbluebutton/air/video/VideoConfig.as
@@ -1,14 +1,6 @@
 package org.bigbluebutton.air.video {
 	
-	import org.bigbluebutton.air.main.viewsold.ui.videobutton.IVideoButton;
-	import org.bigbluebutton.air.main.viewsold.ui.videobutton.VideoButtonMediator;
 	import org.bigbluebutton.air.video.commands.ShareCameraCommand;
-	import org.bigbluebutton.air.video.views.swapcamera.ISwapCameraButton;
-	import org.bigbluebutton.air.video.views.swapcamera.SwapCameraMediator;
-	import org.bigbluebutton.air.video.views.videochat.IVideoChatView;
-	import org.bigbluebutton.air.video.views.videochat.VideoChatViewMediator;
-	import org.bigbluebutton.lib.video.commands.CameraQualityCommand;
-	import org.bigbluebutton.lib.video.commands.CameraQualitySignal;
 	import org.bigbluebutton.lib.video.commands.ShareCameraSignal;
 	
 	import robotlegs.bender.extensions.mediatorMap.api.IMediatorMap;
diff --git a/clients/flash/air-client/src/org/bigbluebutton/air/video/views/videochat/VideoChatVideoView.as b/clients/flash/air-client/src/org/bigbluebutton/air/video/views/videochat/VideoChatVideoView.as
index 61fc7e90df1c0e453fa0634240576377ef91de63..c084d28c3c36dcda7358d026150186df3316ca6b 100644
--- a/clients/flash/air-client/src/org/bigbluebutton/air/video/views/videochat/VideoChatVideoView.as
+++ b/clients/flash/air-client/src/org/bigbluebutton/air/video/views/videochat/VideoChatVideoView.as
@@ -34,7 +34,7 @@ package org.bigbluebutton.air.video.views.videochat {
 		}
 		
 		private function identifyVideoStream(x:Number, y:Number, name:String):void {
-			this.styleName = "videoTextFieldStyle";
+			this.styleName = "videoTextField";
 			var nameFormat:TextFormat = new TextFormat();
 			nameFormat.size = this.getStyle("fontSize");
 			nameFormat.font = this.getStyle("font-family");
diff --git a/clients/flash/air-client/src/org/bigbluebutton/air/video/views/videochat/VideoChatViewBase.mxml b/clients/flash/air-client/src/org/bigbluebutton/air/video/views/videochat/VideoChatViewBase.mxml
index b9685ab657e991eba90c206fdeef3190aa825788..315afb07dc08401893710d3231601592e8092125 100644
--- a/clients/flash/air-client/src/org/bigbluebutton/air/video/views/videochat/VideoChatViewBase.mxml
+++ b/clients/flash/air-client/src/org/bigbluebutton/air/video/views/videochat/VideoChatViewBase.mxml
@@ -4,7 +4,7 @@
 				xmlns:mx="library://ns.adobe.com/flex/mx"
 				xmlns:view="org.bigbluebutton.air.common.views.*">
 	<s:Label id="noVideoMessage0"
-			 styleName="noVideoMsgStyle contentFontSize"
+			 styleName="noVideoMsg contentFontSize"
 			 textAlign="center"
 			 width="100%"
 			 height="100%"
@@ -19,7 +19,7 @@
 					 width="100%">
 				<s:VideoDisplay id="videostream"
 								width="100%"
-								styleName="videoTextFieldStyle" />
+								styleName="videoTextField" />
 			</s:Group>
 
 			<s:List id="videoStreamsList"
diff --git a/clients/flash/air-client/src/org/bigbluebutton/air/video/views/videochat/VideoStreamsItemRenderer.mxml b/clients/flash/air-client/src/org/bigbluebutton/air/video/views/videochat/VideoStreamsItemRenderer.mxml
index 0b1f912be79f1848ec7cb58f5fbc7cf3f2c0d499..0f250f339fdfab607eb0757e0b15c1653717bdfb 100644
--- a/clients/flash/air-client/src/org/bigbluebutton/air/video/views/videochat/VideoStreamsItemRenderer.mxml
+++ b/clients/flash/air-client/src/org/bigbluebutton/air/video/views/videochat/VideoStreamsItemRenderer.mxml
@@ -16,7 +16,7 @@
 					micOffIcon.visible = micOffIcon.includeInLayout = (obj.user.voiceJoined && obj.user.muted);
 					listenOnlyIcon.includeInLayout = !micIcon.visible;
 					listenOnlyIcon.visible = listenOnlyIcon.includeInLayout = (!obj.user.voiceJoined && obj.user.listenOnly);
-					micIcon.styleName = (obj.user.talking) ? "iconStyle speakingMicIconStyle" : "iconStyle micIconStyle";
+					micIcon.styleName = (obj.user.talking) ? "icon speakingMicIcon" : "icon micIcon";
 					presentationIcon.visible = obj.user.presenter;
 				}
 			}
@@ -30,7 +30,7 @@
 								paddingBottom="{getStyle('paddingBottom')}" />
 		</s:layout>
 		<s:Image id="presentationIcon"
-				 styleName="iconStyle presentationIconStyle"
+				 styleName="icon presentationIcon"
 				 height="100%" />
 		<s:Label id="title"
 				 width="60%"
@@ -38,16 +38,16 @@
 				 textAlign="center"
 				 styleName="videoStreamName contentFontSize" />
 		<s:Image id="cameraIcon"
-				 styleName="iconStyle cameraIconStyle"
+				 styleName="icon cameraIcon"
 				 height="100%" />
 		<s:Image id="micIcon"
-				 styleName="iconStyle micIconStyle"
+				 styleName="icon micIcon"
 				 height="100%" />
 		<s:Image id="micOffIcon"
-				 styleName="iconStyle micOffIconStyle"
+				 styleName="icon micOffIcon"
 				 height="100%" />
 		<s:Image id="listenOnlyIcon"
-				 styleName="iconStyle listenOnlyIconStyle"
+				 styleName="icon listenOnlyIcon"
 				 height="100%" />
 	</s:Group>
 </s:ItemRenderer>
diff --git a/clients/flash/build.gradle b/clients/flash/build.gradle
index 0aa1e0baa4eb8265209a5805123925793465f938..828649750e67652c7546bad113ad269a2fa5660b 100644
--- a/clients/flash/build.gradle
+++ b/clients/flash/build.gradle
@@ -10,6 +10,7 @@ buildscript {
 
 subprojects {
 	apply plugin: 'gradlefx'
+	apply plugin: 'flashbuilder'
 
 	version = '0.1-SNAPSHOT'
 }
diff --git a/clients/flash/common-library/src/main/actionscript/org/bigbluebutton/lib/chat/models/IChatMessagesSession.as b/clients/flash/common-library/src/main/actionscript/org/bigbluebutton/lib/chat/models/IChatMessagesSession.as
index e23c7949718a0f4db323368725f8e93f6a039762..fe09f42ba06fb632351427e015e56d5c7c3facb5 100644
--- a/clients/flash/common-library/src/main/actionscript/org/bigbluebutton/lib/chat/models/IChatMessagesSession.as
+++ b/clients/flash/common-library/src/main/actionscript/org/bigbluebutton/lib/chat/models/IChatMessagesSession.as
@@ -2,8 +2,6 @@ package org.bigbluebutton.lib.chat.models {
 	
 	import mx.collections.ArrayCollection;
 	
-	import org.osflash.signals.ISignal;
-	
 	public interface IChatMessagesSession {
 		function get chats():ArrayCollection;
 		function set chats(val:ArrayCollection):void;
diff --git a/clients/flash/common-library/src/main/actionscript/org/bigbluebutton/lib/chat/views/ChatItemRenderer.mxml b/clients/flash/common-library/src/main/actionscript/org/bigbluebutton/lib/chat/views/ChatItemRenderer.mxml
index 17ab973e7d3b54a57081a82b5408ea7bc319bfab..3ebc1b9a8e6fab5f2aaecc14a8748ce0a4b8bebc 100644
--- a/clients/flash/common-library/src/main/actionscript/org/bigbluebutton/lib/chat/views/ChatItemRenderer.mxml
+++ b/clients/flash/common-library/src/main/actionscript/org/bigbluebutton/lib/chat/views/ChatItemRenderer.mxml
@@ -4,7 +4,7 @@
 				xmlns:mx="library://ns.adobe.com/flex/mx"
 				xmlns:views="org.bigbluebutton.lib.common.views.*"
 				width="100%"
-				styleName="chatMessageStyle">
+				styleName="chatMessage">
 
 	<fx:Script>
 		<![CDATA[
@@ -86,7 +86,7 @@
 										selectable="false"
 										id="message"
 										width="100%"
-										styleName="contentStyle" />
+										styleName="content" />
 				</s:HGroup>
 			</s:VGroup>
 		</s:HGroup>
diff --git a/clients/flash/common-library/src/main/actionscript/org/bigbluebutton/lib/chat/views/ChatRoomsItemRenderer.mxml b/clients/flash/common-library/src/main/actionscript/org/bigbluebutton/lib/chat/views/ChatRoomsItemRenderer.mxml
index 18b1ed8e2641c15a074bb4e290ffcda3d22fdd69..32c11dc200616d6cc7a6326eb10cfe372172704e 100644
--- a/clients/flash/common-library/src/main/actionscript/org/bigbluebutton/lib/chat/views/ChatRoomsItemRenderer.mxml
+++ b/clients/flash/common-library/src/main/actionscript/org/bigbluebutton/lib/chat/views/ChatRoomsItemRenderer.mxml
@@ -4,7 +4,7 @@
 				xmlns:mx="library://ns.adobe.com/flex/mx"
 				xmlns:common="org.bigbluebutton.lib.common.views.*"
 				width="100%"
-				styleName="chatRoomItemStyle"
+				styleName="chatRoomItem"
 				xmlns:views="org.bigbluebutton.lib.chat.views.*">
 
 	<fx:Script>
@@ -41,7 +41,7 @@
 		<s:Image id="publicChatIcon"
 				 visible="false"
 				 includeInLayout="false"
-				 styleName="iconStyle publicChatIconStyle" />
+				 styleName="icon publicChatIcon" />
 		<common:ParticipantIcon id="participantIcon"
 								visible="false"
 								includeInLayout="false" />
@@ -49,6 +49,6 @@
 				 width="100%"
 				 styleName="contentFontSize" />
 		<views:NewMessagesIndicator id="newMessages"
-									styleName="newMessagesIndicatorStyle" />
+									styleName="newMessagesIndicator" />
 	</s:Group>
 </s:ItemRenderer>
diff --git a/clients/flash/common-library/src/main/actionscript/org/bigbluebutton/lib/chat/views/ChatRoomsViewBase.as b/clients/flash/common-library/src/main/actionscript/org/bigbluebutton/lib/chat/views/ChatRoomsViewBase.as
index 05ce1fc870f26fc833ed3eaa536c67a50bc1b9c3..dc36deb0d25bab74b3e7d3d3c07a762b9b9b591c 100644
--- a/clients/flash/common-library/src/main/actionscript/org/bigbluebutton/lib/chat/views/ChatRoomsViewBase.as
+++ b/clients/flash/common-library/src/main/actionscript/org/bigbluebutton/lib/chat/views/ChatRoomsViewBase.as
@@ -25,7 +25,7 @@ package org.bigbluebutton.lib.chat.views {
 			
 			_chatLabel = new Label();
 			_chatLabel.percentWidth = 100;
-			_chatLabel.styleName = "contentStyle";
+			_chatLabel.styleName = "content";
 			_chatLabel.text = "Messages";
 			addElement(_chatLabel);
 			
diff --git a/clients/flash/common-library/src/main/actionscript/org/bigbluebutton/lib/chat/views/ChatViewBase.as b/clients/flash/common-library/src/main/actionscript/org/bigbluebutton/lib/chat/views/ChatViewBase.as
index b636a7d326199e8b50c5cc27a1f21de3b69c455d..d0abb4bbdc9db545ad228b4190a8665d4b73478e 100755
--- a/clients/flash/common-library/src/main/actionscript/org/bigbluebutton/lib/chat/views/ChatViewBase.as
+++ b/clients/flash/common-library/src/main/actionscript/org/bigbluebutton/lib/chat/views/ChatViewBase.as
@@ -65,14 +65,14 @@ package org.bigbluebutton.lib.chat.views {
 			_textInput.percentWidth = 100;
 			_textInput.percentHeight = 100;
 			//_textInput.showPromptWhenFocused = false;
-			_textInput.styleName = "messageInputStyle";
+			_textInput.styleName = "messageInput";
 			_inputGroup.addElement(_textInput);
 			
 			_sendButton = new Button();
 			_sendButton.percentHeight = 100;
 			_sendButton.label = "\ue90b";
 			//enabled="{inputMessage0.text!=''}"
-			_sendButton.styleName = "sendButtonStyle";
+			_sendButton.styleName = "sendButton";
 			_inputGroup.addElement(_sendButton);
 			
 			addElement(_inputGroup);
diff --git a/clients/flash/common-library/src/main/actionscript/org/bigbluebutton/lib/common/services/BaseConnection.as b/clients/flash/common-library/src/main/actionscript/org/bigbluebutton/lib/common/services/BaseConnection.as
index 5d5cf629087232a0746ed09d1138c41bfa333a32..b44844f9342b86a988fd54c0c398fc366012b93d 100755
--- a/clients/flash/common-library/src/main/actionscript/org/bigbluebutton/lib/common/services/BaseConnection.as
+++ b/clients/flash/common-library/src/main/actionscript/org/bigbluebutton/lib/common/services/BaseConnection.as
@@ -152,7 +152,7 @@ package org.bigbluebutton.lib.common.services {
 		
 		protected function netASyncError(event:AsyncErrorEvent):void {
 			trace(LOG + "Asynchronous code error - " + event.error + " on " + _uri);
-      trace(event.toString());
+      		trace(event.toString());
 			sendConnectionFailedSignal(ConnectionFailedEvent.UNKNOWN_REASON);
 		}
 		
diff --git a/clients/flash/common-library/src/main/actionscript/org/bigbluebutton/lib/common/views/ParticipantIcon.mxml b/clients/flash/common-library/src/main/actionscript/org/bigbluebutton/lib/common/views/ParticipantIcon.mxml
index 3e34cb03ec1cc520c1720e1b73ef2047fbf630ca..14f05d2524a7c6180b5dbc45ace33235a5e563b9 100644
--- a/clients/flash/common-library/src/main/actionscript/org/bigbluebutton/lib/common/views/ParticipantIcon.mxml
+++ b/clients/flash/common-library/src/main/actionscript/org/bigbluebutton/lib/common/views/ParticipantIcon.mxml
@@ -1,7 +1,7 @@
 <s:Group xmlns:fx="http://ns.adobe.com/mxml/2009"
 		 xmlns:s="library://ns.adobe.com/flex/spark"
 		 xmlns:mx="library://ns.adobe.com/flex/mx"
-		 styleName="participantIconStyle">
+		 styleName="participantIcon">
 
 	<fx:Script>
 		<![CDATA[
diff --git a/clients/flash/common-library/src/main/actionscript/org/bigbluebutton/lib/main/commands/ConnectCommand.as b/clients/flash/common-library/src/main/actionscript/org/bigbluebutton/lib/main/commands/ConnectCommand.as
index 5cf124b6a998fc977a36d9b0e674cc6fde082fa4..c0b0736abdd8ac210815533763acadebb4052c22 100755
--- a/clients/flash/common-library/src/main/actionscript/org/bigbluebutton/lib/main/commands/ConnectCommand.as
+++ b/clients/flash/common-library/src/main/actionscript/org/bigbluebutton/lib/main/commands/ConnectCommand.as
@@ -132,8 +132,8 @@ package org.bigbluebutton.lib.main.commands {
 				audioOptions.listenOnly = userSession.userList.me.listenOnly = true;
 				shareMicrophoneSignal.dispatch(audioOptions);
 			}
-      
-      trace("Configuring deskshare");
+			
+			trace("Configuring deskshare");
 			//deskshareConnection.applicationURI = userSession.config.getConfigFor("DeskShareModule").@uri;
 			//deskshareConnection.room = conferenceParameters.room;
 			//deskshareConnection.connect();
diff --git a/clients/flash/common-library/src/main/actionscript/org/bigbluebutton/lib/main/views/MenuButtonsBase.as b/clients/flash/common-library/src/main/actionscript/org/bigbluebutton/lib/main/views/MenuButtonsBase.as
index 81aed9ecb9e546ba815a8ac20b12c48d8bb38852..bcaa5cb24a9666168c25fc373e5078a6c3f264bd 100755
--- a/clients/flash/common-library/src/main/actionscript/org/bigbluebutton/lib/main/views/MenuButtonsBase.as
+++ b/clients/flash/common-library/src/main/actionscript/org/bigbluebutton/lib/main/views/MenuButtonsBase.as
@@ -1,12 +1,12 @@
 package org.bigbluebutton.lib.main.views {
 	
-	import org.bigbluebutton.lib.main.views.skins.MenuButtonSkin;
-	
 	import spark.components.Button;
-	import spark.components.Group;
-	import spark.layouts.HorizontalLayout;
+	import spark.components.HGroup;
 	
-	public class MenuButtonsBase extends Group {
+	[Style(name = "bottom", inherit = "no", type = "Number")]
+	[Style(name = "gap", inherit = "no", type = "Number")]
+	[Style(name = "top", inherit = "no", type = "Number")]
+	public class MenuButtonsBase extends HGroup {
 		private var _micButton:Button;
 		
 		public function get micButton():Button {
@@ -28,30 +28,35 @@ package org.bigbluebutton.lib.main.views {
 		public function MenuButtonsBase() {
 			super();
 			
-			var l:HorizontalLayout = new HorizontalLayout();
-			l.gap = 10;
-			layout = l;
 			
 			_micButton = new Button();
 			_micButton.percentWidth = 100;
 			_micButton.percentHeight = 100;
 			_micButton.label = "Mic on";
-			_micButton.styleName = "micOnButtonStyle menuButtonStyle";
+			_micButton.styleName = "micOnButton menuButton";
 			addElement(_micButton);
 			
 			_camButton = new Button();
 			_camButton.percentWidth = 100;
 			_camButton.percentHeight = 100;
 			_camButton.label = "Cam on";
-			_camButton.styleName = "camOnButtonStyle menuButtonStyle";
+			_camButton.styleName = "camOnButton menuButton";
 			addElement(_camButton);
 			
 			_statusButton = new Button();
 			_statusButton.percentWidth = 100;
 			_statusButton.percentHeight = 100;
 			_statusButton.label = "Status";
-			_statusButton.styleName = "handStatusButtonStyle menuButtonStyle";
+			_statusButton.styleName = "handStatusButton menuButton";
 			addElement(_statusButton);
 		}
+		
+		override protected function updateDisplayList(w:Number, h:Number):void {
+			super.updateDisplayList(w, h);
+			
+			bottom = getStyle("bottom");
+			gap = getStyle("gap");
+			top = getStyle("top");
+		}
 	}
 }
diff --git a/clients/flash/common-library/src/main/actionscript/org/bigbluebutton/lib/main/views/MenuButtonsMediatorBase.as b/clients/flash/common-library/src/main/actionscript/org/bigbluebutton/lib/main/views/MenuButtonsMediatorBase.as
index e1b96e9381c5f25cc1746dab6dad7b2dd3817876..cb26c7f532ed675029343a73c6a1b1e9b19cfeb7 100644
--- a/clients/flash/common-library/src/main/actionscript/org/bigbluebutton/lib/main/views/MenuButtonsMediatorBase.as
+++ b/clients/flash/common-library/src/main/actionscript/org/bigbluebutton/lib/main/views/MenuButtonsMediatorBase.as
@@ -62,17 +62,17 @@ package org.bigbluebutton.lib.main.views {
 			if (user && user.me) {
 				if (user.hasStream) {
 					view.camButton.label = "Cam off";// ResourceManager.getInstance().getString('resources', 'menuButtons.camOff');
-					view.camButton.styleName = "camOffButtonStyle menuButtonStyle"
+					view.camButton.styleName = "camOffButton menuButton"
 				} else {
 					view.camButton.label = "Cam on";// ResourceManager.getInstance().getString('resources', 'menuButtons.camOn');
-					view.camButton.styleName = "camOnButtonStyle menuButtonStyle"
+					view.camButton.styleName = "camOnButton menuButton"
 				}
 				if (userSession.userList.me.voiceJoined) {
 					view.micButton.label = "Mic off";// ResourceManager.getInstance().getString('resources', 'menuButtons.micOff');
-					view.micButton.styleName = "micOffButtonStyle menuButtonStyle"
+					view.micButton.styleName = "micOffButton menuButton"
 				} else {
 					view.micButton.label = "Mic on";// ResourceManager.getInstance().getString('resources', 'menuButtons.micOn');
-					view.micButton.styleName = "micOnButtonStyle menuButtonStyle"
+					view.micButton.styleName = "micOnButton menuButton"
 				}
 			}
 		}
diff --git a/clients/flash/common-library/src/main/actionscript/org/bigbluebutton/lib/main/views/TopToolbarBase.as b/clients/flash/common-library/src/main/actionscript/org/bigbluebutton/lib/main/views/TopToolbarBase.as
index 160f093913c6dcf206dbc636bd7ad4fa60f446da..684129e21030e35ae01597bf89bd12e26b6b8be8 100644
--- a/clients/flash/common-library/src/main/actionscript/org/bigbluebutton/lib/main/views/TopToolbarBase.as
+++ b/clients/flash/common-library/src/main/actionscript/org/bigbluebutton/lib/main/views/TopToolbarBase.as
@@ -1,53 +1,43 @@
 package org.bigbluebutton.lib.main.views {
-	import mx.controls.Spacer;
-	
+
 	import spark.components.Button;
-	import spark.components.HGroup;
+	import spark.components.Group;
 	import spark.components.Label;
-	
-	public class TopToolbarBase extends HGroup {
+
+	public class TopToolbarBase extends Group {
 		private var _leftButton:Button;
-		
+
 		public function get leftButton():Button {
 			return _leftButton;
 		}
-		
+
 		private var _titleLabel:Label;
-		
+
 		public function get titleLabel():Label {
 			return _titleLabel;
 		}
-		
+
 		private var _rightButton:Button;
-		
+
 		public function get rightButton():Button {
 			return _rightButton;
 		}
-		
+
 		public function TopToolbarBase() {
 			super();
 			
-			horizontalAlign = "center";
-			verticalAlign = "middle";
-			
+			verticalCenter = 0;
+
 			_leftButton = new Button();
-			_leftButton.styleName = "participantsButtonStyle topButtonStyle";
+			_leftButton.styleName = "participantsButton topButton topLeftButton";
 			addElement(_leftButton);
-			
-			var s:Spacer = new Spacer();
-			s.percentWidth = 100;
-			addElement(s);
-			
+
 			_titleLabel = new Label();
-			_titleLabel.styleName = "titleLabelStyle";
+			_titleLabel.styleName = "titleLabel";
 			addElement(_titleLabel);
-			
-			s = new Spacer();
-			s.percentWidth = 100;
-			addElement(s);
-			
+
 			_rightButton = new Button();
-			_rightButton.styleName = "settingsButtonStyle topButtonStyle";
+			_rightButton.styleName = "settingsButton topButton topRightButton";
 			addElement(_rightButton);
 		}
 	}
diff --git a/clients/flash/common-library/src/main/actionscript/org/bigbluebutton/lib/main/views/skins/MenuButtonSkin.mxml b/clients/flash/common-library/src/main/actionscript/org/bigbluebutton/lib/main/views/skins/MenuButtonSkin.mxml
index 2bbfabb35020f09ebac7b93adbca8387c1b33877..33dd681de63dd0e008deef1447eb66df386628f7 100755
--- a/clients/flash/common-library/src/main/actionscript/org/bigbluebutton/lib/main/views/skins/MenuButtonSkin.mxml
+++ b/clients/flash/common-library/src/main/actionscript/org/bigbluebutton/lib/main/views/skins/MenuButtonSkin.mxml
@@ -4,6 +4,7 @@
 	<!-- host component -->
 	<fx:Metadata>
 		[HostComponent("spark.components.Button")]
+		[Style(name="labelPadding", inherit="no", type="Number")]
 	</fx:Metadata>
 
 	<fx:Script>
@@ -18,7 +19,7 @@
 			}
 		]]>
 	</fx:Script>
-	
+
 	<!-- states -->
 	<s:states>
 		<s:State name="disabled" />
@@ -28,7 +29,7 @@
 	</s:states>
 
 	<s:Ellipse id="backgroundEllipse"
-			   height="{getStyle('iconSize') * 3/2}"
+			   height="{getStyle('iconSize') * 2}"
 			   width="{backgroundEllipse.height}"
 			   x="{(this.width - backgroundEllipse.width)/2}"
 			   y="{(this.height - backgroundEllipse.height)/2}">
@@ -37,17 +38,17 @@
 						  color.down="{hostComponent.getStyle('selectedBackgroundColor')}" />
 		</s:fill>
 	</s:Ellipse>
-	<s:Label 	id="icon"
-				   fontSize="{getStyle('iconSize')}"
-				   fontFamily="{getStyle('iconFont')}"
-				   color="{getStyle('iconColor')}"
-				   horizontalCenter="0"
-				   verticalCenter="0" />
+	<s:Label id="icon"
+			 fontSize="{getStyle('iconSize')}"
+			 fontFamily="{getStyle('iconFont')}"
+			 color="{getStyle('iconColor')}"
+			 horizontalCenter="0"
+			 verticalCenter="0" />
 
 	<s:Label id="iconLabel"
 			 text="{hostComponent.label}"
 			 x="{(this.width - iconLabel.width)/2}"
-			 y="{backgroundEllipse.y + backgroundEllipse.height * 9/8}"
-			 fontSize="{getStyle('fontSize')}"/>
+			 y="{backgroundEllipse.y + backgroundEllipse.height + getStyle('labelPadding')}"
+			 fontSize="{getStyle('fontSize')}" />
 
 </s:Skin>
diff --git a/clients/flash/common-library/src/main/actionscript/org/bigbluebutton/lib/user/services/UsersMessageReceiver.as b/clients/flash/common-library/src/main/actionscript/org/bigbluebutton/lib/user/services/UsersMessageReceiver.as
index 29d09d1ffc706666dfcce2483ee6fdf3e6a66906..c031b5a007355c4e5b2dd7911069d26383ab4575 100755
--- a/clients/flash/common-library/src/main/actionscript/org/bigbluebutton/lib/user/services/UsersMessageReceiver.as
+++ b/clients/flash/common-library/src/main/actionscript/org/bigbluebutton/lib/user/services/UsersMessageReceiver.as
@@ -23,6 +23,7 @@ package org.bigbluebutton.lib.user.services {
 		}
 		
 		public function onMessage(messageName:String, message:Object):void {
+			trace(LOG + "RECEIVED MESSAGE: [" + messageName + "]");
 			switch (messageName) {
 				case "voiceUserTalking":
 					handleVoiceUserTalking(message);
@@ -274,8 +275,9 @@ package org.bigbluebutton.lib.user.services {
 			} else {
 				// why 2 different signals for authentication??  
 				//userUISession.loading = false; in authentication command can break order of functions
-        trace(LOG + "got here!");
-        if (userSession == null) trace(LOG + "User Session is NULL!!!!");
+				trace(LOG + "got here!");
+				if (userSession == null)
+					trace(LOG + "User Session is NULL!!!!");
 				userSession.authTokenSignal.dispatch(true);
 			}
 		}
diff --git a/clients/flash/common-library/src/main/actionscript/org/bigbluebutton/lib/user/views/UserItemRenderer.mxml b/clients/flash/common-library/src/main/actionscript/org/bigbluebutton/lib/user/views/UserItemRenderer.mxml
index 1bf7b8a72c60df9d87dbacb63da4cd06f1c735b7..0ad945352c2be9457ce8b31ac6342bdce110eefa 100755
--- a/clients/flash/common-library/src/main/actionscript/org/bigbluebutton/lib/user/views/UserItemRenderer.mxml
+++ b/clients/flash/common-library/src/main/actionscript/org/bigbluebutton/lib/user/views/UserItemRenderer.mxml
@@ -5,7 +5,7 @@
 				width="100%"
 				click="onClick()"
 				keyUp="onKeyUp()"
-				styleName="participantItemStyle">
+				styleName="participantItem">
 	<s:states>
 		<s:State name="normal" />
 		<s:State name="hovered" />
@@ -83,17 +83,17 @@
 				<s:VerticalLayout gap="0" />
 			</s:layout>
 			<s:Label id="labelDisplay"
-					 styleName="participantNameStyle" />
+					 styleName="participantName" />
 			<s:Label id="subLabelDisplay"
-					 styleName="participantStatusStyle" />
+					 styleName="participantStatus" />
 		</s:Group>
 		<s:Label id="cameraIcon"
 				 text="&#xe921;"
-				 styleName="iconStyle" />
+				 styleName="icon" />
 		<s:Label id="audioIcon"
-				 styleName="iconStyle" />
+				 styleName="icon" />
 		<s:Label id="lockIcon"
 				 text="&#xe927;"
-				 styleName="iconStyle" />
+				 styleName="icon" />
 	</s:Group>
 </s:ItemRenderer>
diff --git a/clients/flash/common-library/src/main/actionscript/org/bigbluebutton/lib/user/views/UsersViewBase.as b/clients/flash/common-library/src/main/actionscript/org/bigbluebutton/lib/user/views/UsersViewBase.as
index 401fea971bc1c162ddf2e8c0161fa3ebe7a8f286..7e62e7dbec4ac6f25954e2ad654d5b5ea26b7d85 100644
--- a/clients/flash/common-library/src/main/actionscript/org/bigbluebutton/lib/user/views/UsersViewBase.as
+++ b/clients/flash/common-library/src/main/actionscript/org/bigbluebutton/lib/user/views/UsersViewBase.as
@@ -26,7 +26,7 @@ package org.bigbluebutton.lib.user.views
 			
 			_userLabel = new Label();
 			_userLabel.percentWidth = 100;
-			_userLabel.styleName = "contentStyle";
+			_userLabel.styleName = "content";
 			_userLabel.text = "Users";
 			addElement(_userLabel);
 			
diff --git a/clients/flash/common-library/src/main/actionscript/ColorPalette b/clients/flash/shared/ColorPalette.as
old mode 100755
new mode 100644
similarity index 80%
rename from clients/flash/common-library/src/main/actionscript/ColorPalette
rename to clients/flash/shared/ColorPalette.as
index 8f598dad2b911f814282e003fed653d27b0499f6..97523f05ee5093c355695b007f00be69a7bea6c0
--- a/clients/flash/common-library/src/main/actionscript/ColorPalette
+++ b/clients/flash/shared/ColorPalette.as
@@ -1,7 +1,13 @@
 private const bbbBlack:uint = 0x2A2D33;
+
 private const bbbBlue:uint = 0x299AD5;
+
 private const bbbGreen:uint = 0x4DC0A2;
+
 private const bbbRed:uint = 0xF0615F;
+
 private const bbbDarkGrey:uint = 0x353B42;
+
 private const bbbGrey:uint = 0x8B9AA8;
-private const bbbWhite:uint = 0xF3F6F9;
\ No newline at end of file
+
+private const bbbWhite:uint = 0xF3F6F9;
diff --git a/clients/flash/common-library/src/main/actionscript/assets/README.txt b/clients/flash/shared/assets/README.txt
old mode 100755
new mode 100644
similarity index 100%
rename from clients/flash/common-library/src/main/actionscript/assets/README.txt
rename to clients/flash/shared/assets/README.txt
diff --git a/clients/flash/shared/assets/css/common.css b/clients/flash/shared/assets/css/common.css
new file mode 100644
index 0000000000000000000000000000000000000000..7efbf85f4f04945adbe0ea818564791191f6c89b
--- /dev/null
+++ b/clients/flash/shared/assets/css/common.css
@@ -0,0 +1,7 @@
+@namespace s "library://ns.adobe.com/flex/spark";
+@namespace mx "library://ns.adobe.com/flex/mx";
+@namespace views "org.bigbluebutton.lib.main.views.*";
+
+views|TopToolbarBase {
+	textAlign: center;
+}
diff --git a/clients/flash/common-library/src/main/actionscript/assets/fonts/BBBIcons/bbb-icons.ttf b/clients/flash/shared/assets/fonts/BBBIcons/bbb-icons.ttf
old mode 100755
new mode 100644
similarity index 100%
rename from clients/flash/common-library/src/main/actionscript/assets/fonts/BBBIcons/bbb-icons.ttf
rename to clients/flash/shared/assets/fonts/BBBIcons/bbb-icons.ttf
diff --git a/clients/flash/common-library/src/main/actionscript/assets/fonts/SourceSansPro/OFL.txt b/clients/flash/shared/assets/fonts/SourceSansPro/OFL.txt
old mode 100755
new mode 100644
similarity index 100%
rename from clients/flash/common-library/src/main/actionscript/assets/fonts/SourceSansPro/OFL.txt
rename to clients/flash/shared/assets/fonts/SourceSansPro/OFL.txt
diff --git a/clients/flash/common-library/src/main/actionscript/assets/fonts/SourceSansPro/SourceSansPro-Black.ttf b/clients/flash/shared/assets/fonts/SourceSansPro/SourceSansPro-Black.ttf
old mode 100755
new mode 100644
similarity index 100%
rename from clients/flash/common-library/src/main/actionscript/assets/fonts/SourceSansPro/SourceSansPro-Black.ttf
rename to clients/flash/shared/assets/fonts/SourceSansPro/SourceSansPro-Black.ttf
diff --git a/clients/flash/common-library/src/main/actionscript/assets/fonts/SourceSansPro/SourceSansPro-BlackItalic.ttf b/clients/flash/shared/assets/fonts/SourceSansPro/SourceSansPro-BlackItalic.ttf
old mode 100755
new mode 100644
similarity index 100%
rename from clients/flash/common-library/src/main/actionscript/assets/fonts/SourceSansPro/SourceSansPro-BlackItalic.ttf
rename to clients/flash/shared/assets/fonts/SourceSansPro/SourceSansPro-BlackItalic.ttf
diff --git a/clients/flash/common-library/src/main/actionscript/assets/fonts/SourceSansPro/SourceSansPro-Bold.ttf b/clients/flash/shared/assets/fonts/SourceSansPro/SourceSansPro-Bold.ttf
old mode 100755
new mode 100644
similarity index 100%
rename from clients/flash/common-library/src/main/actionscript/assets/fonts/SourceSansPro/SourceSansPro-Bold.ttf
rename to clients/flash/shared/assets/fonts/SourceSansPro/SourceSansPro-Bold.ttf
diff --git a/clients/flash/common-library/src/main/actionscript/assets/fonts/SourceSansPro/SourceSansPro-BoldItalic.ttf b/clients/flash/shared/assets/fonts/SourceSansPro/SourceSansPro-BoldItalic.ttf
old mode 100755
new mode 100644
similarity index 100%
rename from clients/flash/common-library/src/main/actionscript/assets/fonts/SourceSansPro/SourceSansPro-BoldItalic.ttf
rename to clients/flash/shared/assets/fonts/SourceSansPro/SourceSansPro-BoldItalic.ttf
diff --git a/clients/flash/common-library/src/main/actionscript/assets/fonts/SourceSansPro/SourceSansPro-ExtraLight.ttf b/clients/flash/shared/assets/fonts/SourceSansPro/SourceSansPro-ExtraLight.ttf
old mode 100755
new mode 100644
similarity index 100%
rename from clients/flash/common-library/src/main/actionscript/assets/fonts/SourceSansPro/SourceSansPro-ExtraLight.ttf
rename to clients/flash/shared/assets/fonts/SourceSansPro/SourceSansPro-ExtraLight.ttf
diff --git a/clients/flash/common-library/src/main/actionscript/assets/fonts/SourceSansPro/SourceSansPro-ExtraLightItalic.ttf b/clients/flash/shared/assets/fonts/SourceSansPro/SourceSansPro-ExtraLightItalic.ttf
old mode 100755
new mode 100644
similarity index 100%
rename from clients/flash/common-library/src/main/actionscript/assets/fonts/SourceSansPro/SourceSansPro-ExtraLightItalic.ttf
rename to clients/flash/shared/assets/fonts/SourceSansPro/SourceSansPro-ExtraLightItalic.ttf
diff --git a/clients/flash/common-library/src/main/actionscript/assets/fonts/SourceSansPro/SourceSansPro-Italic.ttf b/clients/flash/shared/assets/fonts/SourceSansPro/SourceSansPro-Italic.ttf
old mode 100755
new mode 100644
similarity index 100%
rename from clients/flash/common-library/src/main/actionscript/assets/fonts/SourceSansPro/SourceSansPro-Italic.ttf
rename to clients/flash/shared/assets/fonts/SourceSansPro/SourceSansPro-Italic.ttf
diff --git a/clients/flash/common-library/src/main/actionscript/assets/fonts/SourceSansPro/SourceSansPro-Light.ttf b/clients/flash/shared/assets/fonts/SourceSansPro/SourceSansPro-Light.ttf
old mode 100755
new mode 100644
similarity index 100%
rename from clients/flash/common-library/src/main/actionscript/assets/fonts/SourceSansPro/SourceSansPro-Light.ttf
rename to clients/flash/shared/assets/fonts/SourceSansPro/SourceSansPro-Light.ttf
diff --git a/clients/flash/common-library/src/main/actionscript/assets/fonts/SourceSansPro/SourceSansPro-LightItalic.ttf b/clients/flash/shared/assets/fonts/SourceSansPro/SourceSansPro-LightItalic.ttf
old mode 100755
new mode 100644
similarity index 100%
rename from clients/flash/common-library/src/main/actionscript/assets/fonts/SourceSansPro/SourceSansPro-LightItalic.ttf
rename to clients/flash/shared/assets/fonts/SourceSansPro/SourceSansPro-LightItalic.ttf
diff --git a/clients/flash/common-library/src/main/actionscript/assets/fonts/SourceSansPro/SourceSansPro-Regular.ttf b/clients/flash/shared/assets/fonts/SourceSansPro/SourceSansPro-Regular.ttf
old mode 100755
new mode 100644
similarity index 100%
rename from clients/flash/common-library/src/main/actionscript/assets/fonts/SourceSansPro/SourceSansPro-Regular.ttf
rename to clients/flash/shared/assets/fonts/SourceSansPro/SourceSansPro-Regular.ttf
diff --git a/clients/flash/common-library/src/main/actionscript/assets/fonts/SourceSansPro/SourceSansPro-Semibold.ttf b/clients/flash/shared/assets/fonts/SourceSansPro/SourceSansPro-Semibold.ttf
old mode 100755
new mode 100644
similarity index 100%
rename from clients/flash/common-library/src/main/actionscript/assets/fonts/SourceSansPro/SourceSansPro-Semibold.ttf
rename to clients/flash/shared/assets/fonts/SourceSansPro/SourceSansPro-Semibold.ttf
diff --git a/clients/flash/common-library/src/main/actionscript/assets/fonts/SourceSansPro/SourceSansPro-SemiboldItalic.ttf b/clients/flash/shared/assets/fonts/SourceSansPro/SourceSansPro-SemiboldItalic.ttf
old mode 100755
new mode 100644
similarity index 100%
rename from clients/flash/common-library/src/main/actionscript/assets/fonts/SourceSansPro/SourceSansPro-SemiboldItalic.ttf
rename to clients/flash/shared/assets/fonts/SourceSansPro/SourceSansPro-SemiboldItalic.ttf
diff --git a/clients/flash/common-library/src/main/actionscript/assets/icons/icon-100.png b/clients/flash/shared/assets/icons/icon-100.png
old mode 100755
new mode 100644
similarity index 100%
rename from clients/flash/common-library/src/main/actionscript/assets/icons/icon-100.png
rename to clients/flash/shared/assets/icons/icon-100.png
diff --git a/clients/flash/common-library/src/main/actionscript/assets/icons/icon-1024.png b/clients/flash/shared/assets/icons/icon-1024.png
old mode 100755
new mode 100644
similarity index 100%
rename from clients/flash/common-library/src/main/actionscript/assets/icons/icon-1024.png
rename to clients/flash/shared/assets/icons/icon-1024.png
diff --git a/clients/flash/common-library/src/main/actionscript/assets/icons/icon-114.png b/clients/flash/shared/assets/icons/icon-114.png
old mode 100755
new mode 100644
similarity index 100%
rename from clients/flash/common-library/src/main/actionscript/assets/icons/icon-114.png
rename to clients/flash/shared/assets/icons/icon-114.png
diff --git a/clients/flash/common-library/src/main/actionscript/assets/icons/icon-120.png b/clients/flash/shared/assets/icons/icon-120.png
old mode 100755
new mode 100644
similarity index 100%
rename from clients/flash/common-library/src/main/actionscript/assets/icons/icon-120.png
rename to clients/flash/shared/assets/icons/icon-120.png
diff --git a/clients/flash/shared/assets/icons/icon-128.png b/clients/flash/shared/assets/icons/icon-128.png
new file mode 100644
index 0000000000000000000000000000000000000000..7bd8a6675afb25b773b7b91f2ecc5ec6d2707e99
Binary files /dev/null and b/clients/flash/shared/assets/icons/icon-128.png differ
diff --git a/clients/flash/common-library/src/main/actionscript/assets/icons/icon-144.png b/clients/flash/shared/assets/icons/icon-144.png
old mode 100755
new mode 100644
similarity index 100%
rename from clients/flash/common-library/src/main/actionscript/assets/icons/icon-144.png
rename to clients/flash/shared/assets/icons/icon-144.png
diff --git a/clients/flash/common-library/src/main/actionscript/assets/icons/icon-152.png b/clients/flash/shared/assets/icons/icon-152.png
old mode 100755
new mode 100644
similarity index 100%
rename from clients/flash/common-library/src/main/actionscript/assets/icons/icon-152.png
rename to clients/flash/shared/assets/icons/icon-152.png
diff --git a/clients/flash/common-library/src/main/actionscript/assets/icons/icon-167.png b/clients/flash/shared/assets/icons/icon-167.png
old mode 100755
new mode 100644
similarity index 100%
rename from clients/flash/common-library/src/main/actionscript/assets/icons/icon-167.png
rename to clients/flash/shared/assets/icons/icon-167.png
diff --git a/clients/flash/common-library/src/main/actionscript/assets/icons/icon-180.png b/clients/flash/shared/assets/icons/icon-180.png
old mode 100755
new mode 100644
similarity index 100%
rename from clients/flash/common-library/src/main/actionscript/assets/icons/icon-180.png
rename to clients/flash/shared/assets/icons/icon-180.png
diff --git a/clients/flash/shared/assets/icons/icon-192.png b/clients/flash/shared/assets/icons/icon-192.png
new file mode 100644
index 0000000000000000000000000000000000000000..e5dcf3d436ba10e9406b99a3b1e3aa99af09f194
Binary files /dev/null and b/clients/flash/shared/assets/icons/icon-192.png differ
diff --git a/clients/flash/common-library/src/main/actionscript/assets/icons/icon-29.png b/clients/flash/shared/assets/icons/icon-29.png
old mode 100755
new mode 100644
similarity index 100%
rename from clients/flash/common-library/src/main/actionscript/assets/icons/icon-29.png
rename to clients/flash/shared/assets/icons/icon-29.png
diff --git a/clients/flash/common-library/src/main/actionscript/assets/icons/icon-36.png b/clients/flash/shared/assets/icons/icon-36.png
old mode 100755
new mode 100644
similarity index 100%
rename from clients/flash/common-library/src/main/actionscript/assets/icons/icon-36.png
rename to clients/flash/shared/assets/icons/icon-36.png
diff --git a/clients/flash/common-library/src/main/actionscript/assets/icons/icon-40.png b/clients/flash/shared/assets/icons/icon-40.png
old mode 100755
new mode 100644
similarity index 100%
rename from clients/flash/common-library/src/main/actionscript/assets/icons/icon-40.png
rename to clients/flash/shared/assets/icons/icon-40.png
diff --git a/clients/flash/shared/assets/icons/icon-44.png b/clients/flash/shared/assets/icons/icon-44.png
new file mode 100644
index 0000000000000000000000000000000000000000..9874a8265752cd0334729680074a2e47f69b53f3
Binary files /dev/null and b/clients/flash/shared/assets/icons/icon-44.png differ
diff --git a/clients/flash/common-library/src/main/actionscript/assets/icons/icon-48.png b/clients/flash/shared/assets/icons/icon-48.png
old mode 100755
new mode 100644
similarity index 100%
rename from clients/flash/common-library/src/main/actionscript/assets/icons/icon-48.png
rename to clients/flash/shared/assets/icons/icon-48.png
diff --git a/clients/flash/common-library/src/main/actionscript/assets/icons/icon-50.png b/clients/flash/shared/assets/icons/icon-50.png
old mode 100755
new mode 100644
similarity index 100%
rename from clients/flash/common-library/src/main/actionscript/assets/icons/icon-50.png
rename to clients/flash/shared/assets/icons/icon-50.png
diff --git a/clients/flash/common-library/src/main/actionscript/assets/icons/icon-512.png b/clients/flash/shared/assets/icons/icon-512.png
old mode 100755
new mode 100644
similarity index 100%
rename from clients/flash/common-library/src/main/actionscript/assets/icons/icon-512.png
rename to clients/flash/shared/assets/icons/icon-512.png
diff --git a/clients/flash/common-library/src/main/actionscript/assets/icons/icon-57.png b/clients/flash/shared/assets/icons/icon-57.png
old mode 100755
new mode 100644
similarity index 100%
rename from clients/flash/common-library/src/main/actionscript/assets/icons/icon-57.png
rename to clients/flash/shared/assets/icons/icon-57.png
diff --git a/clients/flash/common-library/src/main/actionscript/assets/icons/icon-58.png b/clients/flash/shared/assets/icons/icon-58.png
old mode 100755
new mode 100644
similarity index 100%
rename from clients/flash/common-library/src/main/actionscript/assets/icons/icon-58.png
rename to clients/flash/shared/assets/icons/icon-58.png
diff --git a/clients/flash/common-library/src/main/actionscript/assets/icons/icon-60.png b/clients/flash/shared/assets/icons/icon-60.png
old mode 100755
new mode 100644
similarity index 100%
rename from clients/flash/common-library/src/main/actionscript/assets/icons/icon-60.png
rename to clients/flash/shared/assets/icons/icon-60.png
diff --git a/clients/flash/shared/assets/icons/icon-66.png b/clients/flash/shared/assets/icons/icon-66.png
new file mode 100644
index 0000000000000000000000000000000000000000..9874a8265752cd0334729680074a2e47f69b53f3
Binary files /dev/null and b/clients/flash/shared/assets/icons/icon-66.png differ
diff --git a/clients/flash/common-library/src/main/actionscript/assets/icons/icon-72.png b/clients/flash/shared/assets/icons/icon-72.png
old mode 100755
new mode 100644
similarity index 100%
rename from clients/flash/common-library/src/main/actionscript/assets/icons/icon-72.png
rename to clients/flash/shared/assets/icons/icon-72.png
diff --git a/clients/flash/shared/assets/icons/icon-732.png b/clients/flash/shared/assets/icons/icon-732.png
new file mode 100644
index 0000000000000000000000000000000000000000..258b3b2484d388d57c470b855b4d5db2a32426bd
Binary files /dev/null and b/clients/flash/shared/assets/icons/icon-732.png differ
diff --git a/clients/flash/common-library/src/main/actionscript/assets/icons/icon-75.png b/clients/flash/shared/assets/icons/icon-75.png
old mode 100755
new mode 100644
similarity index 100%
rename from clients/flash/common-library/src/main/actionscript/assets/icons/icon-75.png
rename to clients/flash/shared/assets/icons/icon-75.png
diff --git a/clients/flash/common-library/src/main/actionscript/assets/icons/icon-76.png b/clients/flash/shared/assets/icons/icon-76.png
old mode 100755
new mode 100644
similarity index 100%
rename from clients/flash/common-library/src/main/actionscript/assets/icons/icon-76.png
rename to clients/flash/shared/assets/icons/icon-76.png
diff --git a/clients/flash/common-library/src/main/actionscript/assets/icons/icon-80.png b/clients/flash/shared/assets/icons/icon-80.png
old mode 100755
new mode 100644
similarity index 100%
rename from clients/flash/common-library/src/main/actionscript/assets/icons/icon-80.png
rename to clients/flash/shared/assets/icons/icon-80.png
diff --git a/clients/flash/common-library/src/main/actionscript/assets/icons/icon-87.png b/clients/flash/shared/assets/icons/icon-87.png
old mode 100755
new mode 100644
similarity index 100%
rename from clients/flash/common-library/src/main/actionscript/assets/icons/icon-87.png
rename to clients/flash/shared/assets/icons/icon-87.png
diff --git a/clients/flash/common-library/src/main/actionscript/assets/icons/icon-96.png b/clients/flash/shared/assets/icons/icon-96.png
old mode 100755
new mode 100644
similarity index 100%
rename from clients/flash/common-library/src/main/actionscript/assets/icons/icon-96.png
rename to clients/flash/shared/assets/icons/icon-96.png
diff --git a/clients/flash/common-library/src/main/actionscript/assets/res/common/cursor.png b/clients/flash/shared/assets/res/common/cursor.png
old mode 100755
new mode 100644
similarity index 100%
rename from clients/flash/common-library/src/main/actionscript/assets/res/common/cursor.png
rename to clients/flash/shared/assets/res/common/cursor.png
diff --git a/clients/flash/common-library/src/main/actionscript/assets/res/drawable-hdpi/SwapCamera_up.png b/clients/flash/shared/assets/res/drawable-hdpi/SwapCamera_up.png
old mode 100755
new mode 100644
similarity index 100%
rename from clients/flash/common-library/src/main/actionscript/assets/res/drawable-hdpi/SwapCamera_up.png
rename to clients/flash/shared/assets/res/drawable-hdpi/SwapCamera_up.png
diff --git a/clients/flash/common-library/src/main/actionscript/assets/res/drawable-hdpi/agree.png b/clients/flash/shared/assets/res/drawable-hdpi/agree.png
old mode 100755
new mode 100644
similarity index 100%
rename from clients/flash/common-library/src/main/actionscript/assets/res/drawable-hdpi/agree.png
rename to clients/flash/shared/assets/res/drawable-hdpi/agree.png
diff --git a/clients/flash/common-library/src/main/actionscript/assets/res/drawable-hdpi/arrowRight.png b/clients/flash/shared/assets/res/drawable-hdpi/arrowRight.png
old mode 100755
new mode 100644
similarity index 100%
rename from clients/flash/common-library/src/main/actionscript/assets/res/drawable-hdpi/arrowRight.png
rename to clients/flash/shared/assets/res/drawable-hdpi/arrowRight.png
diff --git a/clients/flash/common-library/src/main/actionscript/assets/res/drawable-hdpi/arrow_messages.png b/clients/flash/shared/assets/res/drawable-hdpi/arrow_messages.png
old mode 100755
new mode 100644
similarity index 100%
rename from clients/flash/common-library/src/main/actionscript/assets/res/drawable-hdpi/arrow_messages.png
rename to clients/flash/shared/assets/res/drawable-hdpi/arrow_messages.png
diff --git a/clients/flash/common-library/src/main/actionscript/assets/res/drawable-hdpi/backButton.png b/clients/flash/shared/assets/res/drawable-hdpi/backButton.png
old mode 100755
new mode 100644
similarity index 100%
rename from clients/flash/common-library/src/main/actionscript/assets/res/drawable-hdpi/backButton.png
rename to clients/flash/shared/assets/res/drawable-hdpi/backButton.png
diff --git a/clients/flash/common-library/src/main/actionscript/assets/res/drawable-hdpi/cam.png b/clients/flash/shared/assets/res/drawable-hdpi/cam.png
old mode 100755
new mode 100644
similarity index 100%
rename from clients/flash/common-library/src/main/actionscript/assets/res/drawable-hdpi/cam.png
rename to clients/flash/shared/assets/res/drawable-hdpi/cam.png
diff --git a/clients/flash/common-library/src/main/actionscript/assets/res/drawable-hdpi/cam_off.png b/clients/flash/shared/assets/res/drawable-hdpi/cam_off.png
old mode 100755
new mode 100644
similarity index 100%
rename from clients/flash/common-library/src/main/actionscript/assets/res/drawable-hdpi/cam_off.png
rename to clients/flash/shared/assets/res/drawable-hdpi/cam_off.png
diff --git a/clients/flash/common-library/src/main/actionscript/assets/res/drawable-hdpi/chat.png b/clients/flash/shared/assets/res/drawable-hdpi/chat.png
old mode 100755
new mode 100644
similarity index 100%
rename from clients/flash/common-library/src/main/actionscript/assets/res/drawable-hdpi/chat.png
rename to clients/flash/shared/assets/res/drawable-hdpi/chat.png
diff --git a/clients/flash/common-library/src/main/actionscript/assets/res/drawable-hdpi/chat_new_message.png b/clients/flash/shared/assets/res/drawable-hdpi/chat_new_message.png
old mode 100755
new mode 100644
similarity index 100%
rename from clients/flash/common-library/src/main/actionscript/assets/res/drawable-hdpi/chat_new_message.png
rename to clients/flash/shared/assets/res/drawable-hdpi/chat_new_message.png
diff --git a/clients/flash/common-library/src/main/actionscript/assets/res/drawable-hdpi/clear.png b/clients/flash/shared/assets/res/drawable-hdpi/clear.png
old mode 100755
new mode 100644
similarity index 100%
rename from clients/flash/common-library/src/main/actionscript/assets/res/drawable-hdpi/clear.png
rename to clients/flash/shared/assets/res/drawable-hdpi/clear.png
diff --git a/clients/flash/common-library/src/main/actionscript/assets/res/drawable-hdpi/clear_status.png b/clients/flash/shared/assets/res/drawable-hdpi/clear_status.png
old mode 100755
new mode 100644
similarity index 100%
rename from clients/flash/common-library/src/main/actionscript/assets/res/drawable-hdpi/clear_status.png
rename to clients/flash/shared/assets/res/drawable-hdpi/clear_status.png
diff --git a/clients/flash/common-library/src/main/actionscript/assets/res/drawable-hdpi/confused.png b/clients/flash/shared/assets/res/drawable-hdpi/confused.png
old mode 100755
new mode 100644
similarity index 100%
rename from clients/flash/common-library/src/main/actionscript/assets/res/drawable-hdpi/confused.png
rename to clients/flash/shared/assets/res/drawable-hdpi/confused.png
diff --git a/clients/flash/common-library/src/main/actionscript/assets/res/drawable-hdpi/deskshare.png b/clients/flash/shared/assets/res/drawable-hdpi/deskshare.png
old mode 100755
new mode 100644
similarity index 100%
rename from clients/flash/common-library/src/main/actionscript/assets/res/drawable-hdpi/deskshare.png
rename to clients/flash/shared/assets/res/drawable-hdpi/deskshare.png
diff --git a/clients/flash/common-library/src/main/actionscript/assets/res/drawable-hdpi/disagree.png b/clients/flash/shared/assets/res/drawable-hdpi/disagree.png
old mode 100755
new mode 100644
similarity index 100%
rename from clients/flash/common-library/src/main/actionscript/assets/res/drawable-hdpi/disagree.png
rename to clients/flash/shared/assets/res/drawable-hdpi/disagree.png
diff --git a/clients/flash/common-library/src/main/actionscript/assets/res/drawable-hdpi/hand.png b/clients/flash/shared/assets/res/drawable-hdpi/hand.png
old mode 100755
new mode 100644
similarity index 100%
rename from clients/flash/common-library/src/main/actionscript/assets/res/drawable-hdpi/hand.png
rename to clients/flash/shared/assets/res/drawable-hdpi/hand.png
diff --git a/clients/flash/common-library/src/main/actionscript/assets/res/drawable-hdpi/ic_access_time.png b/clients/flash/shared/assets/res/drawable-hdpi/ic_access_time.png
old mode 100755
new mode 100644
similarity index 100%
rename from clients/flash/common-library/src/main/actionscript/assets/res/drawable-hdpi/ic_access_time.png
rename to clients/flash/shared/assets/res/drawable-hdpi/ic_access_time.png
diff --git a/clients/flash/common-library/src/main/actionscript/assets/res/drawable-hdpi/ic_fast_forward.png b/clients/flash/shared/assets/res/drawable-hdpi/ic_fast_forward.png
old mode 100755
new mode 100644
similarity index 100%
rename from clients/flash/common-library/src/main/actionscript/assets/res/drawable-hdpi/ic_fast_forward.png
rename to clients/flash/shared/assets/res/drawable-hdpi/ic_fast_forward.png
diff --git a/clients/flash/common-library/src/main/actionscript/assets/res/drawable-hdpi/ic_fast_rewind.png b/clients/flash/shared/assets/res/drawable-hdpi/ic_fast_rewind.png
old mode 100755
new mode 100644
similarity index 100%
rename from clients/flash/common-library/src/main/actionscript/assets/res/drawable-hdpi/ic_fast_rewind.png
rename to clients/flash/shared/assets/res/drawable-hdpi/ic_fast_rewind.png
diff --git a/clients/flash/common-library/src/main/actionscript/assets/res/drawable-hdpi/ic_headset.png b/clients/flash/shared/assets/res/drawable-hdpi/ic_headset.png
old mode 100755
new mode 100644
similarity index 100%
rename from clients/flash/common-library/src/main/actionscript/assets/res/drawable-hdpi/ic_headset.png
rename to clients/flash/shared/assets/res/drawable-hdpi/ic_headset.png
diff --git a/clients/flash/common-library/src/main/actionscript/assets/res/drawable-hdpi/ic_launcher.png b/clients/flash/shared/assets/res/drawable-hdpi/ic_launcher.png
old mode 100755
new mode 100644
similarity index 100%
rename from clients/flash/common-library/src/main/actionscript/assets/res/drawable-hdpi/ic_launcher.png
rename to clients/flash/shared/assets/res/drawable-hdpi/ic_launcher.png
diff --git a/clients/flash/common-library/src/main/actionscript/assets/res/drawable-hdpi/ic_thumb_down.png b/clients/flash/shared/assets/res/drawable-hdpi/ic_thumb_down.png
old mode 100755
new mode 100644
similarity index 100%
rename from clients/flash/common-library/src/main/actionscript/assets/res/drawable-hdpi/ic_thumb_down.png
rename to clients/flash/shared/assets/res/drawable-hdpi/ic_thumb_down.png
diff --git a/clients/flash/common-library/src/main/actionscript/assets/res/drawable-hdpi/ic_thumb_up.png b/clients/flash/shared/assets/res/drawable-hdpi/ic_thumb_up.png
old mode 100755
new mode 100644
similarity index 100%
rename from clients/flash/common-library/src/main/actionscript/assets/res/drawable-hdpi/ic_thumb_up.png
rename to clients/flash/shared/assets/res/drawable-hdpi/ic_thumb_up.png
diff --git a/clients/flash/common-library/src/main/actionscript/assets/res/drawable-hdpi/ic_volume_down.png b/clients/flash/shared/assets/res/drawable-hdpi/ic_volume_down.png
old mode 100755
new mode 100644
similarity index 100%
rename from clients/flash/common-library/src/main/actionscript/assets/res/drawable-hdpi/ic_volume_down.png
rename to clients/flash/shared/assets/res/drawable-hdpi/ic_volume_down.png
diff --git a/clients/flash/common-library/src/main/actionscript/assets/res/drawable-hdpi/ic_volume_up.png b/clients/flash/shared/assets/res/drawable-hdpi/ic_volume_up.png
old mode 100755
new mode 100644
similarity index 100%
rename from clients/flash/common-library/src/main/actionscript/assets/res/drawable-hdpi/ic_volume_up.png
rename to clients/flash/shared/assets/res/drawable-hdpi/ic_volume_up.png
diff --git a/clients/flash/common-library/src/main/actionscript/assets/res/drawable-hdpi/icon-6-smiling-face.png b/clients/flash/shared/assets/res/drawable-hdpi/icon-6-smiling-face.png
old mode 100755
new mode 100644
similarity index 100%
rename from clients/flash/common-library/src/main/actionscript/assets/res/drawable-hdpi/icon-6-smiling-face.png
rename to clients/flash/shared/assets/res/drawable-hdpi/icon-6-smiling-face.png
diff --git a/clients/flash/common-library/src/main/actionscript/assets/res/drawable-hdpi/icon-6-thumb-up.png b/clients/flash/shared/assets/res/drawable-hdpi/icon-6-thumb-up.png
old mode 100755
new mode 100644
similarity index 100%
rename from clients/flash/common-library/src/main/actionscript/assets/res/drawable-hdpi/icon-6-thumb-up.png
rename to clients/flash/shared/assets/res/drawable-hdpi/icon-6-thumb-up.png
diff --git a/clients/flash/common-library/src/main/actionscript/assets/res/drawable-hdpi/icon-7-sad-face.png b/clients/flash/shared/assets/res/drawable-hdpi/icon-7-sad-face.png
old mode 100755
new mode 100644
similarity index 100%
rename from clients/flash/common-library/src/main/actionscript/assets/res/drawable-hdpi/icon-7-sad-face.png
rename to clients/flash/shared/assets/res/drawable-hdpi/icon-7-sad-face.png
diff --git a/clients/flash/common-library/src/main/actionscript/assets/res/drawable-hdpi/icon-7-thumb-down.png b/clients/flash/shared/assets/res/drawable-hdpi/icon-7-thumb-down.png
old mode 100755
new mode 100644
similarity index 100%
rename from clients/flash/common-library/src/main/actionscript/assets/res/drawable-hdpi/icon-7-thumb-down.png
rename to clients/flash/shared/assets/res/drawable-hdpi/icon-7-thumb-down.png
diff --git a/clients/flash/common-library/src/main/actionscript/assets/res/drawable-hdpi/icon-happy-face.png b/clients/flash/shared/assets/res/drawable-hdpi/icon-happy-face.png
old mode 100755
new mode 100644
similarity index 100%
rename from clients/flash/common-library/src/main/actionscript/assets/res/drawable-hdpi/icon-happy-face.png
rename to clients/flash/shared/assets/res/drawable-hdpi/icon-happy-face.png
diff --git a/clients/flash/common-library/src/main/actionscript/assets/res/drawable-hdpi/loader.png b/clients/flash/shared/assets/res/drawable-hdpi/loader.png
old mode 100755
new mode 100644
similarity index 100%
rename from clients/flash/common-library/src/main/actionscript/assets/res/drawable-hdpi/loader.png
rename to clients/flash/shared/assets/res/drawable-hdpi/loader.png
diff --git a/clients/flash/common-library/src/main/actionscript/assets/res/drawable-hdpi/lock.png b/clients/flash/shared/assets/res/drawable-hdpi/lock.png
old mode 100755
new mode 100644
similarity index 100%
rename from clients/flash/common-library/src/main/actionscript/assets/res/drawable-hdpi/lock.png
rename to clients/flash/shared/assets/res/drawable-hdpi/lock.png
diff --git a/clients/flash/common-library/src/main/actionscript/assets/res/drawable-hdpi/media_record.png b/clients/flash/shared/assets/res/drawable-hdpi/media_record.png
old mode 100755
new mode 100644
similarity index 100%
rename from clients/flash/common-library/src/main/actionscript/assets/res/drawable-hdpi/media_record.png
rename to clients/flash/shared/assets/res/drawable-hdpi/media_record.png
diff --git a/clients/flash/common-library/src/main/actionscript/assets/res/drawable-hdpi/mic.png b/clients/flash/shared/assets/res/drawable-hdpi/mic.png
old mode 100755
new mode 100644
similarity index 100%
rename from clients/flash/common-library/src/main/actionscript/assets/res/drawable-hdpi/mic.png
rename to clients/flash/shared/assets/res/drawable-hdpi/mic.png
diff --git a/clients/flash/common-library/src/main/actionscript/assets/res/drawable-hdpi/micBtn_off.png b/clients/flash/shared/assets/res/drawable-hdpi/micBtn_off.png
old mode 100755
new mode 100644
similarity index 100%
rename from clients/flash/common-library/src/main/actionscript/assets/res/drawable-hdpi/micBtn_off.png
rename to clients/flash/shared/assets/res/drawable-hdpi/micBtn_off.png
diff --git a/clients/flash/common-library/src/main/actionscript/assets/res/drawable-hdpi/micBtn_on.png b/clients/flash/shared/assets/res/drawable-hdpi/micBtn_on.png
old mode 100755
new mode 100644
similarity index 100%
rename from clients/flash/common-library/src/main/actionscript/assets/res/drawable-hdpi/micBtn_on.png
rename to clients/flash/shared/assets/res/drawable-hdpi/micBtn_on.png
diff --git a/clients/flash/common-library/src/main/actionscript/assets/res/drawable-hdpi/micOffIcon_dark.png b/clients/flash/shared/assets/res/drawable-hdpi/micOffIcon_dark.png
old mode 100755
new mode 100644
similarity index 100%
rename from clients/flash/common-library/src/main/actionscript/assets/res/drawable-hdpi/micOffIcon_dark.png
rename to clients/flash/shared/assets/res/drawable-hdpi/micOffIcon_dark.png
diff --git a/clients/flash/common-library/src/main/actionscript/assets/res/drawable-hdpi/mic_off.png b/clients/flash/shared/assets/res/drawable-hdpi/mic_off.png
old mode 100755
new mode 100644
similarity index 100%
rename from clients/flash/common-library/src/main/actionscript/assets/res/drawable-hdpi/mic_off.png
rename to clients/flash/shared/assets/res/drawable-hdpi/mic_off.png
diff --git a/clients/flash/common-library/src/main/actionscript/assets/res/drawable-hdpi/microphone.png b/clients/flash/shared/assets/res/drawable-hdpi/microphone.png
old mode 100755
new mode 100644
similarity index 100%
rename from clients/flash/common-library/src/main/actionscript/assets/res/drawable-hdpi/microphone.png
rename to clients/flash/shared/assets/res/drawable-hdpi/microphone.png
diff --git a/clients/flash/common-library/src/main/actionscript/assets/res/drawable-hdpi/microphone_talk.png b/clients/flash/shared/assets/res/drawable-hdpi/microphone_talk.png
old mode 100755
new mode 100644
similarity index 100%
rename from clients/flash/common-library/src/main/actionscript/assets/res/drawable-hdpi/microphone_talk.png
rename to clients/flash/shared/assets/res/drawable-hdpi/microphone_talk.png
diff --git a/clients/flash/common-library/src/main/actionscript/assets/res/drawable-hdpi/participantCircle.png b/clients/flash/shared/assets/res/drawable-hdpi/participantCircle.png
old mode 100755
new mode 100644
similarity index 100%
rename from clients/flash/common-library/src/main/actionscript/assets/res/drawable-hdpi/participantCircle.png
rename to clients/flash/shared/assets/res/drawable-hdpi/participantCircle.png
diff --git a/clients/flash/common-library/src/main/actionscript/assets/res/drawable-hdpi/participants.png b/clients/flash/shared/assets/res/drawable-hdpi/participants.png
old mode 100755
new mode 100644
similarity index 100%
rename from clients/flash/common-library/src/main/actionscript/assets/res/drawable-hdpi/participants.png
rename to clients/flash/shared/assets/res/drawable-hdpi/participants.png
diff --git a/clients/flash/common-library/src/main/actionscript/assets/res/drawable-hdpi/presentation.png b/clients/flash/shared/assets/res/drawable-hdpi/presentation.png
old mode 100755
new mode 100644
similarity index 100%
rename from clients/flash/common-library/src/main/actionscript/assets/res/drawable-hdpi/presentation.png
rename to clients/flash/shared/assets/res/drawable-hdpi/presentation.png
diff --git a/clients/flash/common-library/src/main/actionscript/assets/res/drawable-hdpi/profile.png b/clients/flash/shared/assets/res/drawable-hdpi/profile.png
old mode 100755
new mode 100644
similarity index 100%
rename from clients/flash/common-library/src/main/actionscript/assets/res/drawable-hdpi/profile.png
rename to clients/flash/shared/assets/res/drawable-hdpi/profile.png
diff --git a/clients/flash/common-library/src/main/actionscript/assets/res/drawable-hdpi/publicChat.png b/clients/flash/shared/assets/res/drawable-hdpi/publicChat.png
old mode 100755
new mode 100644
similarity index 100%
rename from clients/flash/common-library/src/main/actionscript/assets/res/drawable-hdpi/publicChat.png
rename to clients/flash/shared/assets/res/drawable-hdpi/publicChat.png
diff --git a/clients/flash/common-library/src/main/actionscript/assets/res/drawable-hdpi/quit.png b/clients/flash/shared/assets/res/drawable-hdpi/quit.png
old mode 100755
new mode 100644
similarity index 100%
rename from clients/flash/common-library/src/main/actionscript/assets/res/drawable-hdpi/quit.png
rename to clients/flash/shared/assets/res/drawable-hdpi/quit.png
diff --git a/clients/flash/common-library/src/main/actionscript/assets/res/drawable-hdpi/rotateRight.png b/clients/flash/shared/assets/res/drawable-hdpi/rotateRight.png
old mode 100755
new mode 100644
similarity index 100%
rename from clients/flash/common-library/src/main/actionscript/assets/res/drawable-hdpi/rotateRight.png
rename to clients/flash/shared/assets/res/drawable-hdpi/rotateRight.png
diff --git a/clients/flash/common-library/src/main/actionscript/assets/res/drawable-hdpi/selected.png b/clients/flash/shared/assets/res/drawable-hdpi/selected.png
old mode 100755
new mode 100644
similarity index 100%
rename from clients/flash/common-library/src/main/actionscript/assets/res/drawable-hdpi/selected.png
rename to clients/flash/shared/assets/res/drawable-hdpi/selected.png
diff --git a/clients/flash/common-library/src/main/actionscript/assets/res/drawable-hdpi/sendMsgIcon.png b/clients/flash/shared/assets/res/drawable-hdpi/sendMsgIcon.png
old mode 100755
new mode 100644
similarity index 100%
rename from clients/flash/common-library/src/main/actionscript/assets/res/drawable-hdpi/sendMsgIcon.png
rename to clients/flash/shared/assets/res/drawable-hdpi/sendMsgIcon.png
diff --git a/clients/flash/common-library/src/main/actionscript/assets/res/drawable-hdpi/settings.png b/clients/flash/shared/assets/res/drawable-hdpi/settings.png
old mode 100755
new mode 100644
similarity index 100%
rename from clients/flash/common-library/src/main/actionscript/assets/res/drawable-hdpi/settings.png
rename to clients/flash/shared/assets/res/drawable-hdpi/settings.png
diff --git a/clients/flash/common-library/src/main/actionscript/assets/res/drawable-hdpi/sound.png b/clients/flash/shared/assets/res/drawable-hdpi/sound.png
old mode 100755
new mode 100644
similarity index 100%
rename from clients/flash/common-library/src/main/actionscript/assets/res/drawable-hdpi/sound.png
rename to clients/flash/shared/assets/res/drawable-hdpi/sound.png
diff --git a/clients/flash/common-library/src/main/actionscript/assets/res/drawable-hdpi/undecided.png b/clients/flash/shared/assets/res/drawable-hdpi/undecided.png
old mode 100755
new mode 100644
similarity index 100%
rename from clients/flash/common-library/src/main/actionscript/assets/res/drawable-hdpi/undecided.png
rename to clients/flash/shared/assets/res/drawable-hdpi/undecided.png
diff --git a/clients/flash/common-library/src/main/actionscript/assets/res/drawable-hdpi/unselected.png b/clients/flash/shared/assets/res/drawable-hdpi/unselected.png
old mode 100755
new mode 100644
similarity index 100%
rename from clients/flash/common-library/src/main/actionscript/assets/res/drawable-hdpi/unselected.png
rename to clients/flash/shared/assets/res/drawable-hdpi/unselected.png
diff --git a/clients/flash/common-library/src/main/actionscript/assets/res/drawable-hdpi/user.png b/clients/flash/shared/assets/res/drawable-hdpi/user.png
old mode 100755
new mode 100644
similarity index 100%
rename from clients/flash/common-library/src/main/actionscript/assets/res/drawable-hdpi/user.png
rename to clients/flash/shared/assets/res/drawable-hdpi/user.png
diff --git a/clients/flash/common-library/src/main/actionscript/assets/res/drawable-hdpi/userProfile.png b/clients/flash/shared/assets/res/drawable-hdpi/userProfile.png
old mode 100755
new mode 100644
similarity index 100%
rename from clients/flash/common-library/src/main/actionscript/assets/res/drawable-hdpi/userProfile.png
rename to clients/flash/shared/assets/res/drawable-hdpi/userProfile.png
diff --git a/clients/flash/common-library/src/main/actionscript/assets/res/drawable-hdpi/user_detail.png b/clients/flash/shared/assets/res/drawable-hdpi/user_detail.png
old mode 100755
new mode 100644
similarity index 100%
rename from clients/flash/common-library/src/main/actionscript/assets/res/drawable-hdpi/user_detail.png
rename to clients/flash/shared/assets/res/drawable-hdpi/user_detail.png
diff --git a/clients/flash/common-library/src/main/actionscript/assets/res/drawable-hdpi/video.png b/clients/flash/shared/assets/res/drawable-hdpi/video.png
old mode 100755
new mode 100644
similarity index 100%
rename from clients/flash/common-library/src/main/actionscript/assets/res/drawable-hdpi/video.png
rename to clients/flash/shared/assets/res/drawable-hdpi/video.png
diff --git a/clients/flash/common-library/src/main/actionscript/assets/res/drawable-mdpi/SwapCamera_up.png b/clients/flash/shared/assets/res/drawable-mdpi/SwapCamera_up.png
old mode 100755
new mode 100644
similarity index 100%
rename from clients/flash/common-library/src/main/actionscript/assets/res/drawable-mdpi/SwapCamera_up.png
rename to clients/flash/shared/assets/res/drawable-mdpi/SwapCamera_up.png
diff --git a/clients/flash/common-library/src/main/actionscript/assets/res/drawable-mdpi/agree.png b/clients/flash/shared/assets/res/drawable-mdpi/agree.png
old mode 100755
new mode 100644
similarity index 100%
rename from clients/flash/common-library/src/main/actionscript/assets/res/drawable-mdpi/agree.png
rename to clients/flash/shared/assets/res/drawable-mdpi/agree.png
diff --git a/clients/flash/common-library/src/main/actionscript/assets/res/drawable-mdpi/arrowRight.png b/clients/flash/shared/assets/res/drawable-mdpi/arrowRight.png
old mode 100755
new mode 100644
similarity index 100%
rename from clients/flash/common-library/src/main/actionscript/assets/res/drawable-mdpi/arrowRight.png
rename to clients/flash/shared/assets/res/drawable-mdpi/arrowRight.png
diff --git a/clients/flash/common-library/src/main/actionscript/assets/res/drawable-mdpi/arrow_messages.png b/clients/flash/shared/assets/res/drawable-mdpi/arrow_messages.png
old mode 100755
new mode 100644
similarity index 100%
rename from clients/flash/common-library/src/main/actionscript/assets/res/drawable-mdpi/arrow_messages.png
rename to clients/flash/shared/assets/res/drawable-mdpi/arrow_messages.png
diff --git a/clients/flash/common-library/src/main/actionscript/assets/res/drawable-mdpi/backButton.png b/clients/flash/shared/assets/res/drawable-mdpi/backButton.png
old mode 100755
new mode 100644
similarity index 100%
rename from clients/flash/common-library/src/main/actionscript/assets/res/drawable-mdpi/backButton.png
rename to clients/flash/shared/assets/res/drawable-mdpi/backButton.png
diff --git a/clients/flash/common-library/src/main/actionscript/assets/res/drawable-mdpi/cam.png b/clients/flash/shared/assets/res/drawable-mdpi/cam.png
old mode 100755
new mode 100644
similarity index 100%
rename from clients/flash/common-library/src/main/actionscript/assets/res/drawable-mdpi/cam.png
rename to clients/flash/shared/assets/res/drawable-mdpi/cam.png
diff --git a/clients/flash/common-library/src/main/actionscript/assets/res/drawable-mdpi/cam_off.png b/clients/flash/shared/assets/res/drawable-mdpi/cam_off.png
old mode 100755
new mode 100644
similarity index 100%
rename from clients/flash/common-library/src/main/actionscript/assets/res/drawable-mdpi/cam_off.png
rename to clients/flash/shared/assets/res/drawable-mdpi/cam_off.png
diff --git a/clients/flash/common-library/src/main/actionscript/assets/res/drawable-mdpi/chat.png b/clients/flash/shared/assets/res/drawable-mdpi/chat.png
old mode 100755
new mode 100644
similarity index 100%
rename from clients/flash/common-library/src/main/actionscript/assets/res/drawable-mdpi/chat.png
rename to clients/flash/shared/assets/res/drawable-mdpi/chat.png
diff --git a/clients/flash/common-library/src/main/actionscript/assets/res/drawable-mdpi/chat_new_message.png b/clients/flash/shared/assets/res/drawable-mdpi/chat_new_message.png
old mode 100755
new mode 100644
similarity index 100%
rename from clients/flash/common-library/src/main/actionscript/assets/res/drawable-mdpi/chat_new_message.png
rename to clients/flash/shared/assets/res/drawable-mdpi/chat_new_message.png
diff --git a/clients/flash/common-library/src/main/actionscript/assets/res/drawable-mdpi/clear.png b/clients/flash/shared/assets/res/drawable-mdpi/clear.png
old mode 100755
new mode 100644
similarity index 100%
rename from clients/flash/common-library/src/main/actionscript/assets/res/drawable-mdpi/clear.png
rename to clients/flash/shared/assets/res/drawable-mdpi/clear.png
diff --git a/clients/flash/common-library/src/main/actionscript/assets/res/drawable-mdpi/clear_status.png b/clients/flash/shared/assets/res/drawable-mdpi/clear_status.png
old mode 100755
new mode 100644
similarity index 100%
rename from clients/flash/common-library/src/main/actionscript/assets/res/drawable-mdpi/clear_status.png
rename to clients/flash/shared/assets/res/drawable-mdpi/clear_status.png
diff --git a/clients/flash/common-library/src/main/actionscript/assets/res/drawable-mdpi/confused.png b/clients/flash/shared/assets/res/drawable-mdpi/confused.png
old mode 100755
new mode 100644
similarity index 100%
rename from clients/flash/common-library/src/main/actionscript/assets/res/drawable-mdpi/confused.png
rename to clients/flash/shared/assets/res/drawable-mdpi/confused.png
diff --git a/clients/flash/common-library/src/main/actionscript/assets/res/drawable-mdpi/deskshare.png b/clients/flash/shared/assets/res/drawable-mdpi/deskshare.png
old mode 100755
new mode 100644
similarity index 100%
rename from clients/flash/common-library/src/main/actionscript/assets/res/drawable-mdpi/deskshare.png
rename to clients/flash/shared/assets/res/drawable-mdpi/deskshare.png
diff --git a/clients/flash/common-library/src/main/actionscript/assets/res/drawable-mdpi/disagree.png b/clients/flash/shared/assets/res/drawable-mdpi/disagree.png
old mode 100755
new mode 100644
similarity index 100%
rename from clients/flash/common-library/src/main/actionscript/assets/res/drawable-mdpi/disagree.png
rename to clients/flash/shared/assets/res/drawable-mdpi/disagree.png
diff --git a/clients/flash/common-library/src/main/actionscript/assets/res/drawable-mdpi/hand.png b/clients/flash/shared/assets/res/drawable-mdpi/hand.png
old mode 100755
new mode 100644
similarity index 100%
rename from clients/flash/common-library/src/main/actionscript/assets/res/drawable-mdpi/hand.png
rename to clients/flash/shared/assets/res/drawable-mdpi/hand.png
diff --git a/clients/flash/common-library/src/main/actionscript/assets/res/drawable-mdpi/ic_access_time.png b/clients/flash/shared/assets/res/drawable-mdpi/ic_access_time.png
old mode 100755
new mode 100644
similarity index 100%
rename from clients/flash/common-library/src/main/actionscript/assets/res/drawable-mdpi/ic_access_time.png
rename to clients/flash/shared/assets/res/drawable-mdpi/ic_access_time.png
diff --git a/clients/flash/common-library/src/main/actionscript/assets/res/drawable-mdpi/ic_fast_forward.png b/clients/flash/shared/assets/res/drawable-mdpi/ic_fast_forward.png
old mode 100755
new mode 100644
similarity index 100%
rename from clients/flash/common-library/src/main/actionscript/assets/res/drawable-mdpi/ic_fast_forward.png
rename to clients/flash/shared/assets/res/drawable-mdpi/ic_fast_forward.png
diff --git a/clients/flash/common-library/src/main/actionscript/assets/res/drawable-mdpi/ic_fast_rewind.png b/clients/flash/shared/assets/res/drawable-mdpi/ic_fast_rewind.png
old mode 100755
new mode 100644
similarity index 100%
rename from clients/flash/common-library/src/main/actionscript/assets/res/drawable-mdpi/ic_fast_rewind.png
rename to clients/flash/shared/assets/res/drawable-mdpi/ic_fast_rewind.png
diff --git a/clients/flash/common-library/src/main/actionscript/assets/res/drawable-mdpi/ic_headset.png b/clients/flash/shared/assets/res/drawable-mdpi/ic_headset.png
old mode 100755
new mode 100644
similarity index 100%
rename from clients/flash/common-library/src/main/actionscript/assets/res/drawable-mdpi/ic_headset.png
rename to clients/flash/shared/assets/res/drawable-mdpi/ic_headset.png
diff --git a/clients/flash/common-library/src/main/actionscript/assets/res/drawable-mdpi/ic_launcher.png b/clients/flash/shared/assets/res/drawable-mdpi/ic_launcher.png
old mode 100755
new mode 100644
similarity index 100%
rename from clients/flash/common-library/src/main/actionscript/assets/res/drawable-mdpi/ic_launcher.png
rename to clients/flash/shared/assets/res/drawable-mdpi/ic_launcher.png
diff --git a/clients/flash/common-library/src/main/actionscript/assets/res/drawable-mdpi/ic_thumb_down.png b/clients/flash/shared/assets/res/drawable-mdpi/ic_thumb_down.png
old mode 100755
new mode 100644
similarity index 100%
rename from clients/flash/common-library/src/main/actionscript/assets/res/drawable-mdpi/ic_thumb_down.png
rename to clients/flash/shared/assets/res/drawable-mdpi/ic_thumb_down.png
diff --git a/clients/flash/common-library/src/main/actionscript/assets/res/drawable-mdpi/ic_thumb_up.png b/clients/flash/shared/assets/res/drawable-mdpi/ic_thumb_up.png
old mode 100755
new mode 100644
similarity index 100%
rename from clients/flash/common-library/src/main/actionscript/assets/res/drawable-mdpi/ic_thumb_up.png
rename to clients/flash/shared/assets/res/drawable-mdpi/ic_thumb_up.png
diff --git a/clients/flash/common-library/src/main/actionscript/assets/res/drawable-mdpi/ic_volume_down.png b/clients/flash/shared/assets/res/drawable-mdpi/ic_volume_down.png
old mode 100755
new mode 100644
similarity index 100%
rename from clients/flash/common-library/src/main/actionscript/assets/res/drawable-mdpi/ic_volume_down.png
rename to clients/flash/shared/assets/res/drawable-mdpi/ic_volume_down.png
diff --git a/clients/flash/common-library/src/main/actionscript/assets/res/drawable-mdpi/ic_volume_up.png b/clients/flash/shared/assets/res/drawable-mdpi/ic_volume_up.png
old mode 100755
new mode 100644
similarity index 100%
rename from clients/flash/common-library/src/main/actionscript/assets/res/drawable-mdpi/ic_volume_up.png
rename to clients/flash/shared/assets/res/drawable-mdpi/ic_volume_up.png
diff --git a/clients/flash/common-library/src/main/actionscript/assets/res/drawable-mdpi/icon-6-smiling-face.png b/clients/flash/shared/assets/res/drawable-mdpi/icon-6-smiling-face.png
old mode 100755
new mode 100644
similarity index 100%
rename from clients/flash/common-library/src/main/actionscript/assets/res/drawable-mdpi/icon-6-smiling-face.png
rename to clients/flash/shared/assets/res/drawable-mdpi/icon-6-smiling-face.png
diff --git a/clients/flash/common-library/src/main/actionscript/assets/res/drawable-mdpi/icon-6-thumb-up.png b/clients/flash/shared/assets/res/drawable-mdpi/icon-6-thumb-up.png
old mode 100755
new mode 100644
similarity index 100%
rename from clients/flash/common-library/src/main/actionscript/assets/res/drawable-mdpi/icon-6-thumb-up.png
rename to clients/flash/shared/assets/res/drawable-mdpi/icon-6-thumb-up.png
diff --git a/clients/flash/common-library/src/main/actionscript/assets/res/drawable-mdpi/icon-7-sad-face.png b/clients/flash/shared/assets/res/drawable-mdpi/icon-7-sad-face.png
old mode 100755
new mode 100644
similarity index 100%
rename from clients/flash/common-library/src/main/actionscript/assets/res/drawable-mdpi/icon-7-sad-face.png
rename to clients/flash/shared/assets/res/drawable-mdpi/icon-7-sad-face.png
diff --git a/clients/flash/common-library/src/main/actionscript/assets/res/drawable-mdpi/icon-7-thumb-down.png b/clients/flash/shared/assets/res/drawable-mdpi/icon-7-thumb-down.png
old mode 100755
new mode 100644
similarity index 100%
rename from clients/flash/common-library/src/main/actionscript/assets/res/drawable-mdpi/icon-7-thumb-down.png
rename to clients/flash/shared/assets/res/drawable-mdpi/icon-7-thumb-down.png
diff --git a/clients/flash/common-library/src/main/actionscript/assets/res/drawable-mdpi/icon-happy-face.png b/clients/flash/shared/assets/res/drawable-mdpi/icon-happy-face.png
old mode 100755
new mode 100644
similarity index 100%
rename from clients/flash/common-library/src/main/actionscript/assets/res/drawable-mdpi/icon-happy-face.png
rename to clients/flash/shared/assets/res/drawable-mdpi/icon-happy-face.png
diff --git a/clients/flash/common-library/src/main/actionscript/assets/res/drawable-mdpi/loader.png b/clients/flash/shared/assets/res/drawable-mdpi/loader.png
old mode 100755
new mode 100644
similarity index 100%
rename from clients/flash/common-library/src/main/actionscript/assets/res/drawable-mdpi/loader.png
rename to clients/flash/shared/assets/res/drawable-mdpi/loader.png
diff --git a/clients/flash/common-library/src/main/actionscript/assets/res/drawable-mdpi/lock.png b/clients/flash/shared/assets/res/drawable-mdpi/lock.png
old mode 100755
new mode 100644
similarity index 100%
rename from clients/flash/common-library/src/main/actionscript/assets/res/drawable-mdpi/lock.png
rename to clients/flash/shared/assets/res/drawable-mdpi/lock.png
diff --git a/clients/flash/common-library/src/main/actionscript/assets/res/drawable-mdpi/media_record.png b/clients/flash/shared/assets/res/drawable-mdpi/media_record.png
old mode 100755
new mode 100644
similarity index 100%
rename from clients/flash/common-library/src/main/actionscript/assets/res/drawable-mdpi/media_record.png
rename to clients/flash/shared/assets/res/drawable-mdpi/media_record.png
diff --git a/clients/flash/common-library/src/main/actionscript/assets/res/drawable-mdpi/mic.png b/clients/flash/shared/assets/res/drawable-mdpi/mic.png
old mode 100755
new mode 100644
similarity index 100%
rename from clients/flash/common-library/src/main/actionscript/assets/res/drawable-mdpi/mic.png
rename to clients/flash/shared/assets/res/drawable-mdpi/mic.png
diff --git a/clients/flash/common-library/src/main/actionscript/assets/res/drawable-mdpi/micBtn_off.png b/clients/flash/shared/assets/res/drawable-mdpi/micBtn_off.png
old mode 100755
new mode 100644
similarity index 100%
rename from clients/flash/common-library/src/main/actionscript/assets/res/drawable-mdpi/micBtn_off.png
rename to clients/flash/shared/assets/res/drawable-mdpi/micBtn_off.png
diff --git a/clients/flash/common-library/src/main/actionscript/assets/res/drawable-mdpi/micBtn_on.png b/clients/flash/shared/assets/res/drawable-mdpi/micBtn_on.png
old mode 100755
new mode 100644
similarity index 100%
rename from clients/flash/common-library/src/main/actionscript/assets/res/drawable-mdpi/micBtn_on.png
rename to clients/flash/shared/assets/res/drawable-mdpi/micBtn_on.png
diff --git a/clients/flash/common-library/src/main/actionscript/assets/res/drawable-mdpi/micOffIcon_dark.png b/clients/flash/shared/assets/res/drawable-mdpi/micOffIcon_dark.png
old mode 100755
new mode 100644
similarity index 100%
rename from clients/flash/common-library/src/main/actionscript/assets/res/drawable-mdpi/micOffIcon_dark.png
rename to clients/flash/shared/assets/res/drawable-mdpi/micOffIcon_dark.png
diff --git a/clients/flash/common-library/src/main/actionscript/assets/res/drawable-mdpi/mic_off.png b/clients/flash/shared/assets/res/drawable-mdpi/mic_off.png
old mode 100755
new mode 100644
similarity index 100%
rename from clients/flash/common-library/src/main/actionscript/assets/res/drawable-mdpi/mic_off.png
rename to clients/flash/shared/assets/res/drawable-mdpi/mic_off.png
diff --git a/clients/flash/common-library/src/main/actionscript/assets/res/drawable-mdpi/microphone.png b/clients/flash/shared/assets/res/drawable-mdpi/microphone.png
old mode 100755
new mode 100644
similarity index 100%
rename from clients/flash/common-library/src/main/actionscript/assets/res/drawable-mdpi/microphone.png
rename to clients/flash/shared/assets/res/drawable-mdpi/microphone.png
diff --git a/clients/flash/common-library/src/main/actionscript/assets/res/drawable-mdpi/microphone_talk.png b/clients/flash/shared/assets/res/drawable-mdpi/microphone_talk.png
old mode 100755
new mode 100644
similarity index 100%
rename from clients/flash/common-library/src/main/actionscript/assets/res/drawable-mdpi/microphone_talk.png
rename to clients/flash/shared/assets/res/drawable-mdpi/microphone_talk.png
diff --git a/clients/flash/common-library/src/main/actionscript/assets/res/drawable-mdpi/participantCircle.png b/clients/flash/shared/assets/res/drawable-mdpi/participantCircle.png
old mode 100755
new mode 100644
similarity index 100%
rename from clients/flash/common-library/src/main/actionscript/assets/res/drawable-mdpi/participantCircle.png
rename to clients/flash/shared/assets/res/drawable-mdpi/participantCircle.png
diff --git a/clients/flash/common-library/src/main/actionscript/assets/res/drawable-mdpi/participants.png b/clients/flash/shared/assets/res/drawable-mdpi/participants.png
old mode 100755
new mode 100644
similarity index 100%
rename from clients/flash/common-library/src/main/actionscript/assets/res/drawable-mdpi/participants.png
rename to clients/flash/shared/assets/res/drawable-mdpi/participants.png
diff --git a/clients/flash/common-library/src/main/actionscript/assets/res/drawable-mdpi/presentation.png b/clients/flash/shared/assets/res/drawable-mdpi/presentation.png
old mode 100755
new mode 100644
similarity index 100%
rename from clients/flash/common-library/src/main/actionscript/assets/res/drawable-mdpi/presentation.png
rename to clients/flash/shared/assets/res/drawable-mdpi/presentation.png
diff --git a/clients/flash/common-library/src/main/actionscript/assets/res/drawable-mdpi/profile.png b/clients/flash/shared/assets/res/drawable-mdpi/profile.png
old mode 100755
new mode 100644
similarity index 100%
rename from clients/flash/common-library/src/main/actionscript/assets/res/drawable-mdpi/profile.png
rename to clients/flash/shared/assets/res/drawable-mdpi/profile.png
diff --git a/clients/flash/common-library/src/main/actionscript/assets/res/drawable-mdpi/publicChat.png b/clients/flash/shared/assets/res/drawable-mdpi/publicChat.png
old mode 100755
new mode 100644
similarity index 100%
rename from clients/flash/common-library/src/main/actionscript/assets/res/drawable-mdpi/publicChat.png
rename to clients/flash/shared/assets/res/drawable-mdpi/publicChat.png
diff --git a/clients/flash/common-library/src/main/actionscript/assets/res/drawable-mdpi/quit.png b/clients/flash/shared/assets/res/drawable-mdpi/quit.png
old mode 100755
new mode 100644
similarity index 100%
rename from clients/flash/common-library/src/main/actionscript/assets/res/drawable-mdpi/quit.png
rename to clients/flash/shared/assets/res/drawable-mdpi/quit.png
diff --git a/clients/flash/common-library/src/main/actionscript/assets/res/drawable-mdpi/rotateRight.png b/clients/flash/shared/assets/res/drawable-mdpi/rotateRight.png
old mode 100755
new mode 100644
similarity index 100%
rename from clients/flash/common-library/src/main/actionscript/assets/res/drawable-mdpi/rotateRight.png
rename to clients/flash/shared/assets/res/drawable-mdpi/rotateRight.png
diff --git a/clients/flash/common-library/src/main/actionscript/assets/res/drawable-mdpi/selected.png b/clients/flash/shared/assets/res/drawable-mdpi/selected.png
old mode 100755
new mode 100644
similarity index 100%
rename from clients/flash/common-library/src/main/actionscript/assets/res/drawable-mdpi/selected.png
rename to clients/flash/shared/assets/res/drawable-mdpi/selected.png
diff --git a/clients/flash/common-library/src/main/actionscript/assets/res/drawable-mdpi/sendMsgIcon.png b/clients/flash/shared/assets/res/drawable-mdpi/sendMsgIcon.png
old mode 100755
new mode 100644
similarity index 100%
rename from clients/flash/common-library/src/main/actionscript/assets/res/drawable-mdpi/sendMsgIcon.png
rename to clients/flash/shared/assets/res/drawable-mdpi/sendMsgIcon.png
diff --git a/clients/flash/common-library/src/main/actionscript/assets/res/drawable-mdpi/settings.png b/clients/flash/shared/assets/res/drawable-mdpi/settings.png
old mode 100755
new mode 100644
similarity index 100%
rename from clients/flash/common-library/src/main/actionscript/assets/res/drawable-mdpi/settings.png
rename to clients/flash/shared/assets/res/drawable-mdpi/settings.png
diff --git a/clients/flash/common-library/src/main/actionscript/assets/res/drawable-mdpi/sound.png b/clients/flash/shared/assets/res/drawable-mdpi/sound.png
old mode 100755
new mode 100644
similarity index 100%
rename from clients/flash/common-library/src/main/actionscript/assets/res/drawable-mdpi/sound.png
rename to clients/flash/shared/assets/res/drawable-mdpi/sound.png
diff --git a/clients/flash/common-library/src/main/actionscript/assets/res/drawable-mdpi/undecided.png b/clients/flash/shared/assets/res/drawable-mdpi/undecided.png
old mode 100755
new mode 100644
similarity index 100%
rename from clients/flash/common-library/src/main/actionscript/assets/res/drawable-mdpi/undecided.png
rename to clients/flash/shared/assets/res/drawable-mdpi/undecided.png
diff --git a/clients/flash/common-library/src/main/actionscript/assets/res/drawable-mdpi/unselected.png b/clients/flash/shared/assets/res/drawable-mdpi/unselected.png
old mode 100755
new mode 100644
similarity index 100%
rename from clients/flash/common-library/src/main/actionscript/assets/res/drawable-mdpi/unselected.png
rename to clients/flash/shared/assets/res/drawable-mdpi/unselected.png
diff --git a/clients/flash/common-library/src/main/actionscript/assets/res/drawable-mdpi/user.png b/clients/flash/shared/assets/res/drawable-mdpi/user.png
old mode 100755
new mode 100644
similarity index 100%
rename from clients/flash/common-library/src/main/actionscript/assets/res/drawable-mdpi/user.png
rename to clients/flash/shared/assets/res/drawable-mdpi/user.png
diff --git a/clients/flash/common-library/src/main/actionscript/assets/res/drawable-mdpi/userProfile.png b/clients/flash/shared/assets/res/drawable-mdpi/userProfile.png
old mode 100755
new mode 100644
similarity index 100%
rename from clients/flash/common-library/src/main/actionscript/assets/res/drawable-mdpi/userProfile.png
rename to clients/flash/shared/assets/res/drawable-mdpi/userProfile.png
diff --git a/clients/flash/common-library/src/main/actionscript/assets/res/drawable-mdpi/user_detail.png b/clients/flash/shared/assets/res/drawable-mdpi/user_detail.png
old mode 100755
new mode 100644
similarity index 100%
rename from clients/flash/common-library/src/main/actionscript/assets/res/drawable-mdpi/user_detail.png
rename to clients/flash/shared/assets/res/drawable-mdpi/user_detail.png
diff --git a/clients/flash/common-library/src/main/actionscript/assets/res/drawable-mdpi/video.png b/clients/flash/shared/assets/res/drawable-mdpi/video.png
old mode 100755
new mode 100644
similarity index 100%
rename from clients/flash/common-library/src/main/actionscript/assets/res/drawable-mdpi/video.png
rename to clients/flash/shared/assets/res/drawable-mdpi/video.png
diff --git a/clients/flash/common-library/src/main/actionscript/assets/res/drawable-xhdpi/SwapCamera_up.png b/clients/flash/shared/assets/res/drawable-xhdpi/SwapCamera_up.png
old mode 100755
new mode 100644
similarity index 100%
rename from clients/flash/common-library/src/main/actionscript/assets/res/drawable-xhdpi/SwapCamera_up.png
rename to clients/flash/shared/assets/res/drawable-xhdpi/SwapCamera_up.png
diff --git a/clients/flash/common-library/src/main/actionscript/assets/res/drawable-xhdpi/agree.png b/clients/flash/shared/assets/res/drawable-xhdpi/agree.png
old mode 100755
new mode 100644
similarity index 100%
rename from clients/flash/common-library/src/main/actionscript/assets/res/drawable-xhdpi/agree.png
rename to clients/flash/shared/assets/res/drawable-xhdpi/agree.png
diff --git a/clients/flash/common-library/src/main/actionscript/assets/res/drawable-xhdpi/arrowRight.png b/clients/flash/shared/assets/res/drawable-xhdpi/arrowRight.png
old mode 100755
new mode 100644
similarity index 100%
rename from clients/flash/common-library/src/main/actionscript/assets/res/drawable-xhdpi/arrowRight.png
rename to clients/flash/shared/assets/res/drawable-xhdpi/arrowRight.png
diff --git a/clients/flash/common-library/src/main/actionscript/assets/res/drawable-xhdpi/arrow_messages.png b/clients/flash/shared/assets/res/drawable-xhdpi/arrow_messages.png
old mode 100755
new mode 100644
similarity index 100%
rename from clients/flash/common-library/src/main/actionscript/assets/res/drawable-xhdpi/arrow_messages.png
rename to clients/flash/shared/assets/res/drawable-xhdpi/arrow_messages.png
diff --git a/clients/flash/common-library/src/main/actionscript/assets/res/drawable-xhdpi/backButton.png b/clients/flash/shared/assets/res/drawable-xhdpi/backButton.png
old mode 100755
new mode 100644
similarity index 100%
rename from clients/flash/common-library/src/main/actionscript/assets/res/drawable-xhdpi/backButton.png
rename to clients/flash/shared/assets/res/drawable-xhdpi/backButton.png
diff --git a/clients/flash/common-library/src/main/actionscript/assets/res/drawable-xhdpi/cam.png b/clients/flash/shared/assets/res/drawable-xhdpi/cam.png
old mode 100755
new mode 100644
similarity index 100%
rename from clients/flash/common-library/src/main/actionscript/assets/res/drawable-xhdpi/cam.png
rename to clients/flash/shared/assets/res/drawable-xhdpi/cam.png
diff --git a/clients/flash/common-library/src/main/actionscript/assets/res/drawable-xhdpi/cam_off.png b/clients/flash/shared/assets/res/drawable-xhdpi/cam_off.png
old mode 100755
new mode 100644
similarity index 100%
rename from clients/flash/common-library/src/main/actionscript/assets/res/drawable-xhdpi/cam_off.png
rename to clients/flash/shared/assets/res/drawable-xhdpi/cam_off.png
diff --git a/clients/flash/common-library/src/main/actionscript/assets/res/drawable-xhdpi/chat.png b/clients/flash/shared/assets/res/drawable-xhdpi/chat.png
old mode 100755
new mode 100644
similarity index 100%
rename from clients/flash/common-library/src/main/actionscript/assets/res/drawable-xhdpi/chat.png
rename to clients/flash/shared/assets/res/drawable-xhdpi/chat.png
diff --git a/clients/flash/common-library/src/main/actionscript/assets/res/drawable-xhdpi/chat_new_message.png b/clients/flash/shared/assets/res/drawable-xhdpi/chat_new_message.png
old mode 100755
new mode 100644
similarity index 100%
rename from clients/flash/common-library/src/main/actionscript/assets/res/drawable-xhdpi/chat_new_message.png
rename to clients/flash/shared/assets/res/drawable-xhdpi/chat_new_message.png
diff --git a/clients/flash/common-library/src/main/actionscript/assets/res/drawable-xhdpi/clear.png b/clients/flash/shared/assets/res/drawable-xhdpi/clear.png
old mode 100755
new mode 100644
similarity index 100%
rename from clients/flash/common-library/src/main/actionscript/assets/res/drawable-xhdpi/clear.png
rename to clients/flash/shared/assets/res/drawable-xhdpi/clear.png
diff --git a/clients/flash/common-library/src/main/actionscript/assets/res/drawable-xhdpi/clear_status.png b/clients/flash/shared/assets/res/drawable-xhdpi/clear_status.png
old mode 100755
new mode 100644
similarity index 100%
rename from clients/flash/common-library/src/main/actionscript/assets/res/drawable-xhdpi/clear_status.png
rename to clients/flash/shared/assets/res/drawable-xhdpi/clear_status.png
diff --git a/clients/flash/common-library/src/main/actionscript/assets/res/drawable-xhdpi/confused.png b/clients/flash/shared/assets/res/drawable-xhdpi/confused.png
old mode 100755
new mode 100644
similarity index 100%
rename from clients/flash/common-library/src/main/actionscript/assets/res/drawable-xhdpi/confused.png
rename to clients/flash/shared/assets/res/drawable-xhdpi/confused.png
diff --git a/clients/flash/common-library/src/main/actionscript/assets/res/drawable-xhdpi/deskshare.png b/clients/flash/shared/assets/res/drawable-xhdpi/deskshare.png
old mode 100755
new mode 100644
similarity index 100%
rename from clients/flash/common-library/src/main/actionscript/assets/res/drawable-xhdpi/deskshare.png
rename to clients/flash/shared/assets/res/drawable-xhdpi/deskshare.png
diff --git a/clients/flash/common-library/src/main/actionscript/assets/res/drawable-xhdpi/disagree.png b/clients/flash/shared/assets/res/drawable-xhdpi/disagree.png
old mode 100755
new mode 100644
similarity index 100%
rename from clients/flash/common-library/src/main/actionscript/assets/res/drawable-xhdpi/disagree.png
rename to clients/flash/shared/assets/res/drawable-xhdpi/disagree.png
diff --git a/clients/flash/common-library/src/main/actionscript/assets/res/drawable-xhdpi/hand.png b/clients/flash/shared/assets/res/drawable-xhdpi/hand.png
old mode 100755
new mode 100644
similarity index 100%
rename from clients/flash/common-library/src/main/actionscript/assets/res/drawable-xhdpi/hand.png
rename to clients/flash/shared/assets/res/drawable-xhdpi/hand.png
diff --git a/clients/flash/common-library/src/main/actionscript/assets/res/drawable-xhdpi/ic_access_time.png b/clients/flash/shared/assets/res/drawable-xhdpi/ic_access_time.png
old mode 100755
new mode 100644
similarity index 100%
rename from clients/flash/common-library/src/main/actionscript/assets/res/drawable-xhdpi/ic_access_time.png
rename to clients/flash/shared/assets/res/drawable-xhdpi/ic_access_time.png
diff --git a/clients/flash/common-library/src/main/actionscript/assets/res/drawable-xhdpi/ic_fast_forward.png b/clients/flash/shared/assets/res/drawable-xhdpi/ic_fast_forward.png
old mode 100755
new mode 100644
similarity index 100%
rename from clients/flash/common-library/src/main/actionscript/assets/res/drawable-xhdpi/ic_fast_forward.png
rename to clients/flash/shared/assets/res/drawable-xhdpi/ic_fast_forward.png
diff --git a/clients/flash/common-library/src/main/actionscript/assets/res/drawable-xhdpi/ic_fast_rewind.png b/clients/flash/shared/assets/res/drawable-xhdpi/ic_fast_rewind.png
old mode 100755
new mode 100644
similarity index 100%
rename from clients/flash/common-library/src/main/actionscript/assets/res/drawable-xhdpi/ic_fast_rewind.png
rename to clients/flash/shared/assets/res/drawable-xhdpi/ic_fast_rewind.png
diff --git a/clients/flash/common-library/src/main/actionscript/assets/res/drawable-xhdpi/ic_headset.png b/clients/flash/shared/assets/res/drawable-xhdpi/ic_headset.png
old mode 100755
new mode 100644
similarity index 100%
rename from clients/flash/common-library/src/main/actionscript/assets/res/drawable-xhdpi/ic_headset.png
rename to clients/flash/shared/assets/res/drawable-xhdpi/ic_headset.png
diff --git a/clients/flash/common-library/src/main/actionscript/assets/res/drawable-xhdpi/ic_launcher.png b/clients/flash/shared/assets/res/drawable-xhdpi/ic_launcher.png
old mode 100755
new mode 100644
similarity index 100%
rename from clients/flash/common-library/src/main/actionscript/assets/res/drawable-xhdpi/ic_launcher.png
rename to clients/flash/shared/assets/res/drawable-xhdpi/ic_launcher.png
diff --git a/clients/flash/common-library/src/main/actionscript/assets/res/drawable-xhdpi/ic_thumb_down.png b/clients/flash/shared/assets/res/drawable-xhdpi/ic_thumb_down.png
old mode 100755
new mode 100644
similarity index 100%
rename from clients/flash/common-library/src/main/actionscript/assets/res/drawable-xhdpi/ic_thumb_down.png
rename to clients/flash/shared/assets/res/drawable-xhdpi/ic_thumb_down.png
diff --git a/clients/flash/common-library/src/main/actionscript/assets/res/drawable-xhdpi/ic_thumb_up.png b/clients/flash/shared/assets/res/drawable-xhdpi/ic_thumb_up.png
old mode 100755
new mode 100644
similarity index 100%
rename from clients/flash/common-library/src/main/actionscript/assets/res/drawable-xhdpi/ic_thumb_up.png
rename to clients/flash/shared/assets/res/drawable-xhdpi/ic_thumb_up.png
diff --git a/clients/flash/common-library/src/main/actionscript/assets/res/drawable-xhdpi/ic_volume_down.png b/clients/flash/shared/assets/res/drawable-xhdpi/ic_volume_down.png
old mode 100755
new mode 100644
similarity index 100%
rename from clients/flash/common-library/src/main/actionscript/assets/res/drawable-xhdpi/ic_volume_down.png
rename to clients/flash/shared/assets/res/drawable-xhdpi/ic_volume_down.png
diff --git a/clients/flash/common-library/src/main/actionscript/assets/res/drawable-xhdpi/ic_volume_up.png b/clients/flash/shared/assets/res/drawable-xhdpi/ic_volume_up.png
old mode 100755
new mode 100644
similarity index 100%
rename from clients/flash/common-library/src/main/actionscript/assets/res/drawable-xhdpi/ic_volume_up.png
rename to clients/flash/shared/assets/res/drawable-xhdpi/ic_volume_up.png
diff --git a/clients/flash/common-library/src/main/actionscript/assets/res/drawable-xhdpi/icon-6-smiling-face.png b/clients/flash/shared/assets/res/drawable-xhdpi/icon-6-smiling-face.png
old mode 100755
new mode 100644
similarity index 100%
rename from clients/flash/common-library/src/main/actionscript/assets/res/drawable-xhdpi/icon-6-smiling-face.png
rename to clients/flash/shared/assets/res/drawable-xhdpi/icon-6-smiling-face.png
diff --git a/clients/flash/common-library/src/main/actionscript/assets/res/drawable-xhdpi/icon-6-thumb-up.png b/clients/flash/shared/assets/res/drawable-xhdpi/icon-6-thumb-up.png
old mode 100755
new mode 100644
similarity index 100%
rename from clients/flash/common-library/src/main/actionscript/assets/res/drawable-xhdpi/icon-6-thumb-up.png
rename to clients/flash/shared/assets/res/drawable-xhdpi/icon-6-thumb-up.png
diff --git a/clients/flash/common-library/src/main/actionscript/assets/res/drawable-xhdpi/icon-7-sad-face.png b/clients/flash/shared/assets/res/drawable-xhdpi/icon-7-sad-face.png
old mode 100755
new mode 100644
similarity index 100%
rename from clients/flash/common-library/src/main/actionscript/assets/res/drawable-xhdpi/icon-7-sad-face.png
rename to clients/flash/shared/assets/res/drawable-xhdpi/icon-7-sad-face.png
diff --git a/clients/flash/common-library/src/main/actionscript/assets/res/drawable-xhdpi/icon-7-thumb-down.png b/clients/flash/shared/assets/res/drawable-xhdpi/icon-7-thumb-down.png
old mode 100755
new mode 100644
similarity index 100%
rename from clients/flash/common-library/src/main/actionscript/assets/res/drawable-xhdpi/icon-7-thumb-down.png
rename to clients/flash/shared/assets/res/drawable-xhdpi/icon-7-thumb-down.png
diff --git a/clients/flash/common-library/src/main/actionscript/assets/res/drawable-xhdpi/icon-happy-face.png b/clients/flash/shared/assets/res/drawable-xhdpi/icon-happy-face.png
old mode 100755
new mode 100644
similarity index 100%
rename from clients/flash/common-library/src/main/actionscript/assets/res/drawable-xhdpi/icon-happy-face.png
rename to clients/flash/shared/assets/res/drawable-xhdpi/icon-happy-face.png
diff --git a/clients/flash/common-library/src/main/actionscript/assets/res/drawable-xhdpi/loader.png b/clients/flash/shared/assets/res/drawable-xhdpi/loader.png
old mode 100755
new mode 100644
similarity index 100%
rename from clients/flash/common-library/src/main/actionscript/assets/res/drawable-xhdpi/loader.png
rename to clients/flash/shared/assets/res/drawable-xhdpi/loader.png
diff --git a/clients/flash/common-library/src/main/actionscript/assets/res/drawable-xhdpi/lock.png b/clients/flash/shared/assets/res/drawable-xhdpi/lock.png
old mode 100755
new mode 100644
similarity index 100%
rename from clients/flash/common-library/src/main/actionscript/assets/res/drawable-xhdpi/lock.png
rename to clients/flash/shared/assets/res/drawable-xhdpi/lock.png
diff --git a/clients/flash/common-library/src/main/actionscript/assets/res/drawable-xhdpi/media_record.png b/clients/flash/shared/assets/res/drawable-xhdpi/media_record.png
old mode 100755
new mode 100644
similarity index 100%
rename from clients/flash/common-library/src/main/actionscript/assets/res/drawable-xhdpi/media_record.png
rename to clients/flash/shared/assets/res/drawable-xhdpi/media_record.png
diff --git a/clients/flash/common-library/src/main/actionscript/assets/res/drawable-xhdpi/mic.png b/clients/flash/shared/assets/res/drawable-xhdpi/mic.png
old mode 100755
new mode 100644
similarity index 100%
rename from clients/flash/common-library/src/main/actionscript/assets/res/drawable-xhdpi/mic.png
rename to clients/flash/shared/assets/res/drawable-xhdpi/mic.png
diff --git a/clients/flash/common-library/src/main/actionscript/assets/res/drawable-xhdpi/micBtn_off.png b/clients/flash/shared/assets/res/drawable-xhdpi/micBtn_off.png
old mode 100755
new mode 100644
similarity index 100%
rename from clients/flash/common-library/src/main/actionscript/assets/res/drawable-xhdpi/micBtn_off.png
rename to clients/flash/shared/assets/res/drawable-xhdpi/micBtn_off.png
diff --git a/clients/flash/common-library/src/main/actionscript/assets/res/drawable-xhdpi/micBtn_on.png b/clients/flash/shared/assets/res/drawable-xhdpi/micBtn_on.png
old mode 100755
new mode 100644
similarity index 100%
rename from clients/flash/common-library/src/main/actionscript/assets/res/drawable-xhdpi/micBtn_on.png
rename to clients/flash/shared/assets/res/drawable-xhdpi/micBtn_on.png
diff --git a/clients/flash/common-library/src/main/actionscript/assets/res/drawable-xhdpi/micOffIcon_dark.png b/clients/flash/shared/assets/res/drawable-xhdpi/micOffIcon_dark.png
old mode 100755
new mode 100644
similarity index 100%
rename from clients/flash/common-library/src/main/actionscript/assets/res/drawable-xhdpi/micOffIcon_dark.png
rename to clients/flash/shared/assets/res/drawable-xhdpi/micOffIcon_dark.png
diff --git a/clients/flash/common-library/src/main/actionscript/assets/res/drawable-xhdpi/mic_off.png b/clients/flash/shared/assets/res/drawable-xhdpi/mic_off.png
old mode 100755
new mode 100644
similarity index 100%
rename from clients/flash/common-library/src/main/actionscript/assets/res/drawable-xhdpi/mic_off.png
rename to clients/flash/shared/assets/res/drawable-xhdpi/mic_off.png
diff --git a/clients/flash/common-library/src/main/actionscript/assets/res/drawable-xhdpi/microphone.png b/clients/flash/shared/assets/res/drawable-xhdpi/microphone.png
old mode 100755
new mode 100644
similarity index 100%
rename from clients/flash/common-library/src/main/actionscript/assets/res/drawable-xhdpi/microphone.png
rename to clients/flash/shared/assets/res/drawable-xhdpi/microphone.png
diff --git a/clients/flash/common-library/src/main/actionscript/assets/res/drawable-xhdpi/microphone_talk.png b/clients/flash/shared/assets/res/drawable-xhdpi/microphone_talk.png
old mode 100755
new mode 100644
similarity index 100%
rename from clients/flash/common-library/src/main/actionscript/assets/res/drawable-xhdpi/microphone_talk.png
rename to clients/flash/shared/assets/res/drawable-xhdpi/microphone_talk.png
diff --git a/clients/flash/common-library/src/main/actionscript/assets/res/drawable-xhdpi/participantCircle.png b/clients/flash/shared/assets/res/drawable-xhdpi/participantCircle.png
old mode 100755
new mode 100644
similarity index 100%
rename from clients/flash/common-library/src/main/actionscript/assets/res/drawable-xhdpi/participantCircle.png
rename to clients/flash/shared/assets/res/drawable-xhdpi/participantCircle.png
diff --git a/clients/flash/common-library/src/main/actionscript/assets/res/drawable-xhdpi/participants.png b/clients/flash/shared/assets/res/drawable-xhdpi/participants.png
old mode 100755
new mode 100644
similarity index 100%
rename from clients/flash/common-library/src/main/actionscript/assets/res/drawable-xhdpi/participants.png
rename to clients/flash/shared/assets/res/drawable-xhdpi/participants.png
diff --git a/clients/flash/common-library/src/main/actionscript/assets/res/drawable-xhdpi/presentation.png b/clients/flash/shared/assets/res/drawable-xhdpi/presentation.png
old mode 100755
new mode 100644
similarity index 100%
rename from clients/flash/common-library/src/main/actionscript/assets/res/drawable-xhdpi/presentation.png
rename to clients/flash/shared/assets/res/drawable-xhdpi/presentation.png
diff --git a/clients/flash/common-library/src/main/actionscript/assets/res/drawable-xhdpi/profile.png b/clients/flash/shared/assets/res/drawable-xhdpi/profile.png
old mode 100755
new mode 100644
similarity index 100%
rename from clients/flash/common-library/src/main/actionscript/assets/res/drawable-xhdpi/profile.png
rename to clients/flash/shared/assets/res/drawable-xhdpi/profile.png
diff --git a/clients/flash/common-library/src/main/actionscript/assets/res/drawable-xhdpi/publicChat.png b/clients/flash/shared/assets/res/drawable-xhdpi/publicChat.png
old mode 100755
new mode 100644
similarity index 100%
rename from clients/flash/common-library/src/main/actionscript/assets/res/drawable-xhdpi/publicChat.png
rename to clients/flash/shared/assets/res/drawable-xhdpi/publicChat.png
diff --git a/clients/flash/common-library/src/main/actionscript/assets/res/drawable-xhdpi/quit.png b/clients/flash/shared/assets/res/drawable-xhdpi/quit.png
old mode 100755
new mode 100644
similarity index 100%
rename from clients/flash/common-library/src/main/actionscript/assets/res/drawable-xhdpi/quit.png
rename to clients/flash/shared/assets/res/drawable-xhdpi/quit.png
diff --git a/clients/flash/common-library/src/main/actionscript/assets/res/drawable-xhdpi/rotateRight.png b/clients/flash/shared/assets/res/drawable-xhdpi/rotateRight.png
old mode 100755
new mode 100644
similarity index 100%
rename from clients/flash/common-library/src/main/actionscript/assets/res/drawable-xhdpi/rotateRight.png
rename to clients/flash/shared/assets/res/drawable-xhdpi/rotateRight.png
diff --git a/clients/flash/common-library/src/main/actionscript/assets/res/drawable-xhdpi/selected.png b/clients/flash/shared/assets/res/drawable-xhdpi/selected.png
old mode 100755
new mode 100644
similarity index 100%
rename from clients/flash/common-library/src/main/actionscript/assets/res/drawable-xhdpi/selected.png
rename to clients/flash/shared/assets/res/drawable-xhdpi/selected.png
diff --git a/clients/flash/common-library/src/main/actionscript/assets/res/drawable-xhdpi/sendMsgIcon.png b/clients/flash/shared/assets/res/drawable-xhdpi/sendMsgIcon.png
old mode 100755
new mode 100644
similarity index 100%
rename from clients/flash/common-library/src/main/actionscript/assets/res/drawable-xhdpi/sendMsgIcon.png
rename to clients/flash/shared/assets/res/drawable-xhdpi/sendMsgIcon.png
diff --git a/clients/flash/common-library/src/main/actionscript/assets/res/drawable-xhdpi/settings.png b/clients/flash/shared/assets/res/drawable-xhdpi/settings.png
old mode 100755
new mode 100644
similarity index 100%
rename from clients/flash/common-library/src/main/actionscript/assets/res/drawable-xhdpi/settings.png
rename to clients/flash/shared/assets/res/drawable-xhdpi/settings.png
diff --git a/clients/flash/common-library/src/main/actionscript/assets/res/drawable-xhdpi/sound.png b/clients/flash/shared/assets/res/drawable-xhdpi/sound.png
old mode 100755
new mode 100644
similarity index 100%
rename from clients/flash/common-library/src/main/actionscript/assets/res/drawable-xhdpi/sound.png
rename to clients/flash/shared/assets/res/drawable-xhdpi/sound.png
diff --git a/clients/flash/common-library/src/main/actionscript/assets/res/drawable-xhdpi/undecided.png b/clients/flash/shared/assets/res/drawable-xhdpi/undecided.png
old mode 100755
new mode 100644
similarity index 100%
rename from clients/flash/common-library/src/main/actionscript/assets/res/drawable-xhdpi/undecided.png
rename to clients/flash/shared/assets/res/drawable-xhdpi/undecided.png
diff --git a/clients/flash/common-library/src/main/actionscript/assets/res/drawable-xhdpi/unselected.png b/clients/flash/shared/assets/res/drawable-xhdpi/unselected.png
old mode 100755
new mode 100644
similarity index 100%
rename from clients/flash/common-library/src/main/actionscript/assets/res/drawable-xhdpi/unselected.png
rename to clients/flash/shared/assets/res/drawable-xhdpi/unselected.png
diff --git a/clients/flash/common-library/src/main/actionscript/assets/res/drawable-xhdpi/user.png b/clients/flash/shared/assets/res/drawable-xhdpi/user.png
old mode 100755
new mode 100644
similarity index 100%
rename from clients/flash/common-library/src/main/actionscript/assets/res/drawable-xhdpi/user.png
rename to clients/flash/shared/assets/res/drawable-xhdpi/user.png
diff --git a/clients/flash/common-library/src/main/actionscript/assets/res/drawable-xhdpi/userProfile.png b/clients/flash/shared/assets/res/drawable-xhdpi/userProfile.png
old mode 100755
new mode 100644
similarity index 100%
rename from clients/flash/common-library/src/main/actionscript/assets/res/drawable-xhdpi/userProfile.png
rename to clients/flash/shared/assets/res/drawable-xhdpi/userProfile.png
diff --git a/clients/flash/common-library/src/main/actionscript/assets/res/drawable-xhdpi/user_detail.png b/clients/flash/shared/assets/res/drawable-xhdpi/user_detail.png
old mode 100755
new mode 100644
similarity index 100%
rename from clients/flash/common-library/src/main/actionscript/assets/res/drawable-xhdpi/user_detail.png
rename to clients/flash/shared/assets/res/drawable-xhdpi/user_detail.png
diff --git a/clients/flash/common-library/src/main/actionscript/assets/res/drawable-xhdpi/video.png b/clients/flash/shared/assets/res/drawable-xhdpi/video.png
old mode 100755
new mode 100644
similarity index 100%
rename from clients/flash/common-library/src/main/actionscript/assets/res/drawable-xhdpi/video.png
rename to clients/flash/shared/assets/res/drawable-xhdpi/video.png
diff --git a/clients/flash/common-library/src/main/actionscript/assets/res/drawable-xxhdpi/SwapCamera_up.png b/clients/flash/shared/assets/res/drawable-xxhdpi/SwapCamera_up.png
old mode 100755
new mode 100644
similarity index 100%
rename from clients/flash/common-library/src/main/actionscript/assets/res/drawable-xxhdpi/SwapCamera_up.png
rename to clients/flash/shared/assets/res/drawable-xxhdpi/SwapCamera_up.png
diff --git a/clients/flash/common-library/src/main/actionscript/assets/res/drawable-xxhdpi/agree.png b/clients/flash/shared/assets/res/drawable-xxhdpi/agree.png
old mode 100755
new mode 100644
similarity index 100%
rename from clients/flash/common-library/src/main/actionscript/assets/res/drawable-xxhdpi/agree.png
rename to clients/flash/shared/assets/res/drawable-xxhdpi/agree.png
diff --git a/clients/flash/common-library/src/main/actionscript/assets/res/drawable-xxhdpi/arrowRight.png b/clients/flash/shared/assets/res/drawable-xxhdpi/arrowRight.png
old mode 100755
new mode 100644
similarity index 100%
rename from clients/flash/common-library/src/main/actionscript/assets/res/drawable-xxhdpi/arrowRight.png
rename to clients/flash/shared/assets/res/drawable-xxhdpi/arrowRight.png
diff --git a/clients/flash/common-library/src/main/actionscript/assets/res/drawable-xxhdpi/arrow_messages.png b/clients/flash/shared/assets/res/drawable-xxhdpi/arrow_messages.png
old mode 100755
new mode 100644
similarity index 100%
rename from clients/flash/common-library/src/main/actionscript/assets/res/drawable-xxhdpi/arrow_messages.png
rename to clients/flash/shared/assets/res/drawable-xxhdpi/arrow_messages.png
diff --git a/clients/flash/common-library/src/main/actionscript/assets/res/drawable-xxhdpi/backButton.png b/clients/flash/shared/assets/res/drawable-xxhdpi/backButton.png
old mode 100755
new mode 100644
similarity index 100%
rename from clients/flash/common-library/src/main/actionscript/assets/res/drawable-xxhdpi/backButton.png
rename to clients/flash/shared/assets/res/drawable-xxhdpi/backButton.png
diff --git a/clients/flash/common-library/src/main/actionscript/assets/res/drawable-xxhdpi/cam.png b/clients/flash/shared/assets/res/drawable-xxhdpi/cam.png
old mode 100755
new mode 100644
similarity index 100%
rename from clients/flash/common-library/src/main/actionscript/assets/res/drawable-xxhdpi/cam.png
rename to clients/flash/shared/assets/res/drawable-xxhdpi/cam.png
diff --git a/clients/flash/common-library/src/main/actionscript/assets/res/drawable-xxhdpi/cam_off.png b/clients/flash/shared/assets/res/drawable-xxhdpi/cam_off.png
old mode 100755
new mode 100644
similarity index 100%
rename from clients/flash/common-library/src/main/actionscript/assets/res/drawable-xxhdpi/cam_off.png
rename to clients/flash/shared/assets/res/drawable-xxhdpi/cam_off.png
diff --git a/clients/flash/common-library/src/main/actionscript/assets/res/drawable-xxhdpi/chat.png b/clients/flash/shared/assets/res/drawable-xxhdpi/chat.png
old mode 100755
new mode 100644
similarity index 100%
rename from clients/flash/common-library/src/main/actionscript/assets/res/drawable-xxhdpi/chat.png
rename to clients/flash/shared/assets/res/drawable-xxhdpi/chat.png
diff --git a/clients/flash/common-library/src/main/actionscript/assets/res/drawable-xxhdpi/chat_new_message.png b/clients/flash/shared/assets/res/drawable-xxhdpi/chat_new_message.png
old mode 100755
new mode 100644
similarity index 100%
rename from clients/flash/common-library/src/main/actionscript/assets/res/drawable-xxhdpi/chat_new_message.png
rename to clients/flash/shared/assets/res/drawable-xxhdpi/chat_new_message.png
diff --git a/clients/flash/common-library/src/main/actionscript/assets/res/drawable-xxhdpi/clear.png b/clients/flash/shared/assets/res/drawable-xxhdpi/clear.png
old mode 100755
new mode 100644
similarity index 100%
rename from clients/flash/common-library/src/main/actionscript/assets/res/drawable-xxhdpi/clear.png
rename to clients/flash/shared/assets/res/drawable-xxhdpi/clear.png
diff --git a/clients/flash/common-library/src/main/actionscript/assets/res/drawable-xxhdpi/clear_status.png b/clients/flash/shared/assets/res/drawable-xxhdpi/clear_status.png
old mode 100755
new mode 100644
similarity index 100%
rename from clients/flash/common-library/src/main/actionscript/assets/res/drawable-xxhdpi/clear_status.png
rename to clients/flash/shared/assets/res/drawable-xxhdpi/clear_status.png
diff --git a/clients/flash/common-library/src/main/actionscript/assets/res/drawable-xxhdpi/confused.png b/clients/flash/shared/assets/res/drawable-xxhdpi/confused.png
old mode 100755
new mode 100644
similarity index 100%
rename from clients/flash/common-library/src/main/actionscript/assets/res/drawable-xxhdpi/confused.png
rename to clients/flash/shared/assets/res/drawable-xxhdpi/confused.png
diff --git a/clients/flash/common-library/src/main/actionscript/assets/res/drawable-xxhdpi/deskshare.png b/clients/flash/shared/assets/res/drawable-xxhdpi/deskshare.png
old mode 100755
new mode 100644
similarity index 100%
rename from clients/flash/common-library/src/main/actionscript/assets/res/drawable-xxhdpi/deskshare.png
rename to clients/flash/shared/assets/res/drawable-xxhdpi/deskshare.png
diff --git a/clients/flash/common-library/src/main/actionscript/assets/res/drawable-xxhdpi/disagree.png b/clients/flash/shared/assets/res/drawable-xxhdpi/disagree.png
old mode 100755
new mode 100644
similarity index 100%
rename from clients/flash/common-library/src/main/actionscript/assets/res/drawable-xxhdpi/disagree.png
rename to clients/flash/shared/assets/res/drawable-xxhdpi/disagree.png
diff --git a/clients/flash/common-library/src/main/actionscript/assets/res/drawable-xxhdpi/hand.png b/clients/flash/shared/assets/res/drawable-xxhdpi/hand.png
old mode 100755
new mode 100644
similarity index 100%
rename from clients/flash/common-library/src/main/actionscript/assets/res/drawable-xxhdpi/hand.png
rename to clients/flash/shared/assets/res/drawable-xxhdpi/hand.png
diff --git a/clients/flash/common-library/src/main/actionscript/assets/res/drawable-xxhdpi/ic_access_time.png b/clients/flash/shared/assets/res/drawable-xxhdpi/ic_access_time.png
old mode 100755
new mode 100644
similarity index 100%
rename from clients/flash/common-library/src/main/actionscript/assets/res/drawable-xxhdpi/ic_access_time.png
rename to clients/flash/shared/assets/res/drawable-xxhdpi/ic_access_time.png
diff --git a/clients/flash/common-library/src/main/actionscript/assets/res/drawable-xxhdpi/ic_fast_forward.png b/clients/flash/shared/assets/res/drawable-xxhdpi/ic_fast_forward.png
old mode 100755
new mode 100644
similarity index 100%
rename from clients/flash/common-library/src/main/actionscript/assets/res/drawable-xxhdpi/ic_fast_forward.png
rename to clients/flash/shared/assets/res/drawable-xxhdpi/ic_fast_forward.png
diff --git a/clients/flash/common-library/src/main/actionscript/assets/res/drawable-xxhdpi/ic_fast_rewind.png b/clients/flash/shared/assets/res/drawable-xxhdpi/ic_fast_rewind.png
old mode 100755
new mode 100644
similarity index 100%
rename from clients/flash/common-library/src/main/actionscript/assets/res/drawable-xxhdpi/ic_fast_rewind.png
rename to clients/flash/shared/assets/res/drawable-xxhdpi/ic_fast_rewind.png
diff --git a/clients/flash/common-library/src/main/actionscript/assets/res/drawable-xxhdpi/ic_headset.png b/clients/flash/shared/assets/res/drawable-xxhdpi/ic_headset.png
old mode 100755
new mode 100644
similarity index 100%
rename from clients/flash/common-library/src/main/actionscript/assets/res/drawable-xxhdpi/ic_headset.png
rename to clients/flash/shared/assets/res/drawable-xxhdpi/ic_headset.png
diff --git a/clients/flash/common-library/src/main/actionscript/assets/res/drawable-xxhdpi/ic_launcher.png b/clients/flash/shared/assets/res/drawable-xxhdpi/ic_launcher.png
old mode 100755
new mode 100644
similarity index 100%
rename from clients/flash/common-library/src/main/actionscript/assets/res/drawable-xxhdpi/ic_launcher.png
rename to clients/flash/shared/assets/res/drawable-xxhdpi/ic_launcher.png
diff --git a/clients/flash/common-library/src/main/actionscript/assets/res/drawable-xxhdpi/ic_thumb_down.png b/clients/flash/shared/assets/res/drawable-xxhdpi/ic_thumb_down.png
old mode 100755
new mode 100644
similarity index 100%
rename from clients/flash/common-library/src/main/actionscript/assets/res/drawable-xxhdpi/ic_thumb_down.png
rename to clients/flash/shared/assets/res/drawable-xxhdpi/ic_thumb_down.png
diff --git a/clients/flash/common-library/src/main/actionscript/assets/res/drawable-xxhdpi/ic_thumb_up.png b/clients/flash/shared/assets/res/drawable-xxhdpi/ic_thumb_up.png
old mode 100755
new mode 100644
similarity index 100%
rename from clients/flash/common-library/src/main/actionscript/assets/res/drawable-xxhdpi/ic_thumb_up.png
rename to clients/flash/shared/assets/res/drawable-xxhdpi/ic_thumb_up.png
diff --git a/clients/flash/common-library/src/main/actionscript/assets/res/drawable-xxhdpi/ic_volume_down.png b/clients/flash/shared/assets/res/drawable-xxhdpi/ic_volume_down.png
old mode 100755
new mode 100644
similarity index 100%
rename from clients/flash/common-library/src/main/actionscript/assets/res/drawable-xxhdpi/ic_volume_down.png
rename to clients/flash/shared/assets/res/drawable-xxhdpi/ic_volume_down.png
diff --git a/clients/flash/common-library/src/main/actionscript/assets/res/drawable-xxhdpi/ic_volume_up.png b/clients/flash/shared/assets/res/drawable-xxhdpi/ic_volume_up.png
old mode 100755
new mode 100644
similarity index 100%
rename from clients/flash/common-library/src/main/actionscript/assets/res/drawable-xxhdpi/ic_volume_up.png
rename to clients/flash/shared/assets/res/drawable-xxhdpi/ic_volume_up.png
diff --git a/clients/flash/common-library/src/main/actionscript/assets/res/drawable-xxhdpi/icon-6-smiling-face.png b/clients/flash/shared/assets/res/drawable-xxhdpi/icon-6-smiling-face.png
old mode 100755
new mode 100644
similarity index 100%
rename from clients/flash/common-library/src/main/actionscript/assets/res/drawable-xxhdpi/icon-6-smiling-face.png
rename to clients/flash/shared/assets/res/drawable-xxhdpi/icon-6-smiling-face.png
diff --git a/clients/flash/common-library/src/main/actionscript/assets/res/drawable-xxhdpi/icon-6-thumb-up.png b/clients/flash/shared/assets/res/drawable-xxhdpi/icon-6-thumb-up.png
old mode 100755
new mode 100644
similarity index 100%
rename from clients/flash/common-library/src/main/actionscript/assets/res/drawable-xxhdpi/icon-6-thumb-up.png
rename to clients/flash/shared/assets/res/drawable-xxhdpi/icon-6-thumb-up.png
diff --git a/clients/flash/common-library/src/main/actionscript/assets/res/drawable-xxhdpi/icon-7-sad-face.png b/clients/flash/shared/assets/res/drawable-xxhdpi/icon-7-sad-face.png
old mode 100755
new mode 100644
similarity index 100%
rename from clients/flash/common-library/src/main/actionscript/assets/res/drawable-xxhdpi/icon-7-sad-face.png
rename to clients/flash/shared/assets/res/drawable-xxhdpi/icon-7-sad-face.png
diff --git a/clients/flash/common-library/src/main/actionscript/assets/res/drawable-xxhdpi/icon-7-thumb-down.png b/clients/flash/shared/assets/res/drawable-xxhdpi/icon-7-thumb-down.png
old mode 100755
new mode 100644
similarity index 100%
rename from clients/flash/common-library/src/main/actionscript/assets/res/drawable-xxhdpi/icon-7-thumb-down.png
rename to clients/flash/shared/assets/res/drawable-xxhdpi/icon-7-thumb-down.png
diff --git a/clients/flash/common-library/src/main/actionscript/assets/res/drawable-xxhdpi/icon-happy-face.png b/clients/flash/shared/assets/res/drawable-xxhdpi/icon-happy-face.png
old mode 100755
new mode 100644
similarity index 100%
rename from clients/flash/common-library/src/main/actionscript/assets/res/drawable-xxhdpi/icon-happy-face.png
rename to clients/flash/shared/assets/res/drawable-xxhdpi/icon-happy-face.png
diff --git a/clients/flash/common-library/src/main/actionscript/assets/res/drawable-xxhdpi/loader.png b/clients/flash/shared/assets/res/drawable-xxhdpi/loader.png
old mode 100755
new mode 100644
similarity index 100%
rename from clients/flash/common-library/src/main/actionscript/assets/res/drawable-xxhdpi/loader.png
rename to clients/flash/shared/assets/res/drawable-xxhdpi/loader.png
diff --git a/clients/flash/common-library/src/main/actionscript/assets/res/drawable-xxhdpi/lock.png b/clients/flash/shared/assets/res/drawable-xxhdpi/lock.png
old mode 100755
new mode 100644
similarity index 100%
rename from clients/flash/common-library/src/main/actionscript/assets/res/drawable-xxhdpi/lock.png
rename to clients/flash/shared/assets/res/drawable-xxhdpi/lock.png
diff --git a/clients/flash/common-library/src/main/actionscript/assets/res/drawable-xxhdpi/media_record.png b/clients/flash/shared/assets/res/drawable-xxhdpi/media_record.png
old mode 100755
new mode 100644
similarity index 100%
rename from clients/flash/common-library/src/main/actionscript/assets/res/drawable-xxhdpi/media_record.png
rename to clients/flash/shared/assets/res/drawable-xxhdpi/media_record.png
diff --git a/clients/flash/common-library/src/main/actionscript/assets/res/drawable-xxhdpi/mic.png b/clients/flash/shared/assets/res/drawable-xxhdpi/mic.png
old mode 100755
new mode 100644
similarity index 100%
rename from clients/flash/common-library/src/main/actionscript/assets/res/drawable-xxhdpi/mic.png
rename to clients/flash/shared/assets/res/drawable-xxhdpi/mic.png
diff --git a/clients/flash/common-library/src/main/actionscript/assets/res/drawable-xxhdpi/micBtn_off.png b/clients/flash/shared/assets/res/drawable-xxhdpi/micBtn_off.png
old mode 100755
new mode 100644
similarity index 100%
rename from clients/flash/common-library/src/main/actionscript/assets/res/drawable-xxhdpi/micBtn_off.png
rename to clients/flash/shared/assets/res/drawable-xxhdpi/micBtn_off.png
diff --git a/clients/flash/common-library/src/main/actionscript/assets/res/drawable-xxhdpi/micBtn_on.png b/clients/flash/shared/assets/res/drawable-xxhdpi/micBtn_on.png
old mode 100755
new mode 100644
similarity index 100%
rename from clients/flash/common-library/src/main/actionscript/assets/res/drawable-xxhdpi/micBtn_on.png
rename to clients/flash/shared/assets/res/drawable-xxhdpi/micBtn_on.png
diff --git a/clients/flash/common-library/src/main/actionscript/assets/res/drawable-xxhdpi/micOffIcon_dark.png b/clients/flash/shared/assets/res/drawable-xxhdpi/micOffIcon_dark.png
old mode 100755
new mode 100644
similarity index 100%
rename from clients/flash/common-library/src/main/actionscript/assets/res/drawable-xxhdpi/micOffIcon_dark.png
rename to clients/flash/shared/assets/res/drawable-xxhdpi/micOffIcon_dark.png
diff --git a/clients/flash/common-library/src/main/actionscript/assets/res/drawable-xxhdpi/mic_off.png b/clients/flash/shared/assets/res/drawable-xxhdpi/mic_off.png
old mode 100755
new mode 100644
similarity index 100%
rename from clients/flash/common-library/src/main/actionscript/assets/res/drawable-xxhdpi/mic_off.png
rename to clients/flash/shared/assets/res/drawable-xxhdpi/mic_off.png
diff --git a/clients/flash/common-library/src/main/actionscript/assets/res/drawable-xxhdpi/microphone.png b/clients/flash/shared/assets/res/drawable-xxhdpi/microphone.png
old mode 100755
new mode 100644
similarity index 100%
rename from clients/flash/common-library/src/main/actionscript/assets/res/drawable-xxhdpi/microphone.png
rename to clients/flash/shared/assets/res/drawable-xxhdpi/microphone.png
diff --git a/clients/flash/common-library/src/main/actionscript/assets/res/drawable-xxhdpi/microphone_talk.png b/clients/flash/shared/assets/res/drawable-xxhdpi/microphone_talk.png
old mode 100755
new mode 100644
similarity index 100%
rename from clients/flash/common-library/src/main/actionscript/assets/res/drawable-xxhdpi/microphone_talk.png
rename to clients/flash/shared/assets/res/drawable-xxhdpi/microphone_talk.png
diff --git a/clients/flash/common-library/src/main/actionscript/assets/res/drawable-xxhdpi/participantCircle.png b/clients/flash/shared/assets/res/drawable-xxhdpi/participantCircle.png
old mode 100755
new mode 100644
similarity index 100%
rename from clients/flash/common-library/src/main/actionscript/assets/res/drawable-xxhdpi/participantCircle.png
rename to clients/flash/shared/assets/res/drawable-xxhdpi/participantCircle.png
diff --git a/clients/flash/common-library/src/main/actionscript/assets/res/drawable-xxhdpi/participants.png b/clients/flash/shared/assets/res/drawable-xxhdpi/participants.png
old mode 100755
new mode 100644
similarity index 100%
rename from clients/flash/common-library/src/main/actionscript/assets/res/drawable-xxhdpi/participants.png
rename to clients/flash/shared/assets/res/drawable-xxhdpi/participants.png
diff --git a/clients/flash/common-library/src/main/actionscript/assets/res/drawable-xxhdpi/presentation.png b/clients/flash/shared/assets/res/drawable-xxhdpi/presentation.png
old mode 100755
new mode 100644
similarity index 100%
rename from clients/flash/common-library/src/main/actionscript/assets/res/drawable-xxhdpi/presentation.png
rename to clients/flash/shared/assets/res/drawable-xxhdpi/presentation.png
diff --git a/clients/flash/common-library/src/main/actionscript/assets/res/drawable-xxhdpi/profile.png b/clients/flash/shared/assets/res/drawable-xxhdpi/profile.png
old mode 100755
new mode 100644
similarity index 100%
rename from clients/flash/common-library/src/main/actionscript/assets/res/drawable-xxhdpi/profile.png
rename to clients/flash/shared/assets/res/drawable-xxhdpi/profile.png
diff --git a/clients/flash/common-library/src/main/actionscript/assets/res/drawable-xxhdpi/publicChat.png b/clients/flash/shared/assets/res/drawable-xxhdpi/publicChat.png
old mode 100755
new mode 100644
similarity index 100%
rename from clients/flash/common-library/src/main/actionscript/assets/res/drawable-xxhdpi/publicChat.png
rename to clients/flash/shared/assets/res/drawable-xxhdpi/publicChat.png
diff --git a/clients/flash/common-library/src/main/actionscript/assets/res/drawable-xxhdpi/quit.png b/clients/flash/shared/assets/res/drawable-xxhdpi/quit.png
old mode 100755
new mode 100644
similarity index 100%
rename from clients/flash/common-library/src/main/actionscript/assets/res/drawable-xxhdpi/quit.png
rename to clients/flash/shared/assets/res/drawable-xxhdpi/quit.png
diff --git a/clients/flash/common-library/src/main/actionscript/assets/res/drawable-xxhdpi/rotateRight.png b/clients/flash/shared/assets/res/drawable-xxhdpi/rotateRight.png
old mode 100755
new mode 100644
similarity index 100%
rename from clients/flash/common-library/src/main/actionscript/assets/res/drawable-xxhdpi/rotateRight.png
rename to clients/flash/shared/assets/res/drawable-xxhdpi/rotateRight.png
diff --git a/clients/flash/common-library/src/main/actionscript/assets/res/drawable-xxhdpi/selected.png b/clients/flash/shared/assets/res/drawable-xxhdpi/selected.png
old mode 100755
new mode 100644
similarity index 100%
rename from clients/flash/common-library/src/main/actionscript/assets/res/drawable-xxhdpi/selected.png
rename to clients/flash/shared/assets/res/drawable-xxhdpi/selected.png
diff --git a/clients/flash/common-library/src/main/actionscript/assets/res/drawable-xxhdpi/sendMsgIcon.png b/clients/flash/shared/assets/res/drawable-xxhdpi/sendMsgIcon.png
old mode 100755
new mode 100644
similarity index 100%
rename from clients/flash/common-library/src/main/actionscript/assets/res/drawable-xxhdpi/sendMsgIcon.png
rename to clients/flash/shared/assets/res/drawable-xxhdpi/sendMsgIcon.png
diff --git a/clients/flash/common-library/src/main/actionscript/assets/res/drawable-xxhdpi/settings.png b/clients/flash/shared/assets/res/drawable-xxhdpi/settings.png
old mode 100755
new mode 100644
similarity index 100%
rename from clients/flash/common-library/src/main/actionscript/assets/res/drawable-xxhdpi/settings.png
rename to clients/flash/shared/assets/res/drawable-xxhdpi/settings.png
diff --git a/clients/flash/common-library/src/main/actionscript/assets/res/drawable-xxhdpi/sound.png b/clients/flash/shared/assets/res/drawable-xxhdpi/sound.png
old mode 100755
new mode 100644
similarity index 100%
rename from clients/flash/common-library/src/main/actionscript/assets/res/drawable-xxhdpi/sound.png
rename to clients/flash/shared/assets/res/drawable-xxhdpi/sound.png
diff --git a/clients/flash/common-library/src/main/actionscript/assets/res/drawable-xxhdpi/undecided.png b/clients/flash/shared/assets/res/drawable-xxhdpi/undecided.png
old mode 100755
new mode 100644
similarity index 100%
rename from clients/flash/common-library/src/main/actionscript/assets/res/drawable-xxhdpi/undecided.png
rename to clients/flash/shared/assets/res/drawable-xxhdpi/undecided.png
diff --git a/clients/flash/common-library/src/main/actionscript/assets/res/drawable-xxhdpi/unselected.png b/clients/flash/shared/assets/res/drawable-xxhdpi/unselected.png
old mode 100755
new mode 100644
similarity index 100%
rename from clients/flash/common-library/src/main/actionscript/assets/res/drawable-xxhdpi/unselected.png
rename to clients/flash/shared/assets/res/drawable-xxhdpi/unselected.png
diff --git a/clients/flash/common-library/src/main/actionscript/assets/res/drawable-xxhdpi/user.png b/clients/flash/shared/assets/res/drawable-xxhdpi/user.png
old mode 100755
new mode 100644
similarity index 100%
rename from clients/flash/common-library/src/main/actionscript/assets/res/drawable-xxhdpi/user.png
rename to clients/flash/shared/assets/res/drawable-xxhdpi/user.png
diff --git a/clients/flash/common-library/src/main/actionscript/assets/res/drawable-xxhdpi/userProfile.png b/clients/flash/shared/assets/res/drawable-xxhdpi/userProfile.png
old mode 100755
new mode 100644
similarity index 100%
rename from clients/flash/common-library/src/main/actionscript/assets/res/drawable-xxhdpi/userProfile.png
rename to clients/flash/shared/assets/res/drawable-xxhdpi/userProfile.png
diff --git a/clients/flash/common-library/src/main/actionscript/assets/res/drawable-xxhdpi/user_detail.png b/clients/flash/shared/assets/res/drawable-xxhdpi/user_detail.png
old mode 100755
new mode 100644
similarity index 100%
rename from clients/flash/common-library/src/main/actionscript/assets/res/drawable-xxhdpi/user_detail.png
rename to clients/flash/shared/assets/res/drawable-xxhdpi/user_detail.png
diff --git a/clients/flash/common-library/src/main/actionscript/assets/res/drawable-xxhdpi/video.png b/clients/flash/shared/assets/res/drawable-xxhdpi/video.png
old mode 100755
new mode 100644
similarity index 100%
rename from clients/flash/common-library/src/main/actionscript/assets/res/drawable-xxhdpi/video.png
rename to clients/flash/shared/assets/res/drawable-xxhdpi/video.png
diff --git a/clients/flash/common-library/src/main/actionscript/assets/res/drawable-xxxhdpi/ic_launcher.png b/clients/flash/shared/assets/res/drawable-xxxhdpi/ic_launcher.png
old mode 100755
new mode 100644
similarity index 100%
rename from clients/flash/common-library/src/main/actionscript/assets/res/drawable-xxxhdpi/ic_launcher.png
rename to clients/flash/shared/assets/res/drawable-xxxhdpi/ic_launcher.png
diff --git a/clients/flash/web-client/src/main/actionscript/Default.css b/clients/flash/web-client/src/main/actionscript/Default.css
index 033612835cbdb2cf7d60181bb8439d855a276299..da3d673793af2654619c1463317c0675efe37848 100755
--- a/clients/flash/web-client/src/main/actionscript/Default.css
+++ b/clients/flash/web-client/src/main/actionscript/Default.css
@@ -4,7 +4,7 @@
 @namespace libChat "org.bigbluebutton.lib.chat.views.*";
 
 @font-face { 
-	src: 		url("assets/fonts/SourceSansPro/SourceSansPro-Regular.ttf"); 
+	src: 		url("../../../../shared/assets/fonts/SourceSansPro/SourceSansPro-Regular.ttf"); 
 	fontFamily:	SourceSansPro; 
 	fontStyle:	normal; 
 	fontWeight:	normal; 
@@ -12,7 +12,7 @@
 }
 
 @font-face { 
-	src:		url("assets/fonts/SourceSansPro/SourceSansPro-Semibold.ttf"); 
+	src:		url("../../../../shared/assets/fonts/SourceSansPro/SourceSansPro-Semibold.ttf"); 
 	fontFamily:	SourceSansPro; 
 	fontStyle:	normal; 
 	fontWeight:	bold; 
@@ -20,7 +20,7 @@
 }
 
 @font-face { 
-	src:		url("assets/fonts/SourceSansPro/SourceSansPro-Italic.ttf"); 
+	src:		url("../../../../shared/assets/fonts/SourceSansPro/SourceSansPro-Italic.ttf"); 
 	fontFamily:	SourceSansPro; 
 	fontStyle:	italic; 
 	fontWeight:	normal; 
@@ -28,7 +28,7 @@
 }
 
 @font-face { 
-	src:		url("assets/fonts/SourceSansPro/SourceSansPro-Bold.ttf"); 
+	src:		url("../../../../shared/assets/fonts/SourceSansPro/SourceSansPro-Bold.ttf"); 
 	fontFamily:	SourceSansPro; 
 	fontStyle:	normal; 
 	fontWeight:	heavy; 
@@ -36,7 +36,7 @@
 }
 
 @font-face { 
-	src: 		url("assets/fonts/SourceSansPro/SourceSansPro-Regular.ttf"); 
+	src: 		url("../../../../shared/assets/fonts/SourceSansPro/SourceSansPro-Regular.ttf"); 
 	fontFamily:	SourceSansProMX; 
 	fontStyle:	normal; 
 	fontWeight:	normal; 
@@ -69,13 +69,13 @@ libChat|NewMessagesIndicator
 	skinClass:			ClassReference("org.bigbluebutton.lib.chat.views.skins.NewMessagesIndicatorSkin");
 }
 
-.titleLabelStyle
+.titleLabel
 {
 	color: 		#F3F6F9;
 	fontSize:	24;
 }
 
-.topButtonStyle
+.topButton
 {
 	paddingLeft:		24;
 	paddingRight:		24;
@@ -87,17 +87,17 @@ libChat|NewMessagesIndicator
 
 }
 
-.participantsButtonStyle
+.participantsButton
 {
-	backgroundImage:	Embed(source="assets/res/drawable-mdpi/participants.png");
+	backgroundImage:	Embed(source="../../../../shared/assets/res/drawable-mdpi/participants.png");
 }
 
-.settingsButtonStyle
+.settingsButton
 {
-	backgroundImage:	Embed(source="assets/res/drawable-mdpi/settings.png");
+	backgroundImage:	Embed(source="../../../../shared/assets/res/drawable-mdpi/settings.png");
 }
 
-.menuButtonStyle
+.menuButton
 {
 	backgroundColor         : #299AD5;
 	selectedBackgroundColor : #8A9AA7;
@@ -106,32 +106,32 @@ libChat|NewMessagesIndicator
 	skinClass               : ClassReference("org.bigbluebutton.lib.main.views.skins.MenuButtonSkin");
 }
 
-.micOnButtonStyle
+.micOnButton
 {
-	backgroundImage : Embed(source="assets/res/drawable-mdpi/mic.png");
+	backgroundImage : Embed(source="../../../../shared/assets/res/drawable-mdpi/mic.png");
 }
 
-.micOffButtonStyle
+.micOffButton
 {
-	backgroundImage : Embed(source="assets/res/drawable-mdpi/mic_off.png");
+	backgroundImage : Embed(source="../../../../shared/assets/res/drawable-mdpi/mic_off.png");
 }
 
-.camOnButtonStyle
+.camOnButton
 {
-	backgroundImage : Embed(source="assets/res/drawable-mdpi/cam.png");
+	backgroundImage : Embed(source="../../../../shared/assets/res/drawable-mdpi/cam.png");
 }
 
-.camOffButtonStyle
+.camOffButton
 {
-	backgroundImage : Embed(source="assets/res/drawable-mdpi/cam_off.png");
+	backgroundImage : Embed(source="../../../../shared/assets/res/drawable-mdpi/cam_off.png");
 }
 
-.handStatusButtonStyle
+.handStatusButton
 {
-	backgroundImage : Embed(source="assets/res/drawable-mdpi/hand.png");
+	backgroundImage : Embed(source="../../../../shared/assets/res/drawable-mdpi/hand.png");
 }
 
-.participantIconStyle
+.participantIcon
 {
 	circleColor : #8898A5;
 	width  : 32;
@@ -139,19 +139,19 @@ libChat|NewMessagesIndicator
 	fontSize : 12;
 }
 
-.chatMessageStyle
+.chatMessage
 {
 	nameFontSize: 		16;
 	timeFontSize:		16;
 }
 
-.contentStyle
+.content
 {
 	fontSize:	14;
 	color:		#8B9AA8;
 }
 
-.panelStyle
+.panel
 {
 	backgroundColor:	#F3F6F9;
 	backgroundAlpha:	1;
@@ -159,7 +159,7 @@ libChat|NewMessagesIndicator
 	skinClass:			ClassReference("org.bigbluebutton.web.common.skins.PanelSkin");
 }
 
-.panelTitleStyle
+.panelTitle
 {
 	fontSize:		18;
 	fontWeight:		bold;
@@ -167,39 +167,39 @@ libChat|NewMessagesIndicator
 	paddingBottom:	10;
 }
 
-.iconStyle
+.icon
 {
-	width:		32;
-	height:		32;
-	skinClass:	ClassReference("org.bigbluebutton.lib.common.skins.IconSkin")
+	fontSize   :    20;
+	fontFamily :    BBBIcons;
+	color      :    PropertyReference("bbbGrey");
 }
 
-.cameraIconStyle
+.cameraIcon
 {
-	backgroundImage : Embed(source="assets/res/drawable-mdpi/video.png");
+	backgroundImage : Embed(source="../../../../shared/assets/res/drawable-mdpi/video.png");
 }
 
-.micIconStyle
+.micIcon
 {
-	backgroundImage : Embed(source="assets/res/drawable-mdpi/microphone.png");
+	backgroundImage : Embed(source="../../../../shared/assets/res/drawable-mdpi/microphone.png");
 }
 
-.micOffIconStyle
+.micOffIcon
 {
-	backgroundImage : Embed(source="assets/res/drawable-mdpi/micOffIcon_dark.png");
+	backgroundImage : Embed(source="../../../../shared/assets/res/drawable-mdpi/micOffIcon_dark.png");
 }
 
-.speakingMicIconStyle
+.speakingMicIcon
 {
-	backgroundImage : Embed(source="assets/res/drawable-mdpi/microphone_talk.png");
+	backgroundImage : Embed(source="../../../../shared/assets/res/drawable-mdpi/microphone_talk.png");
 }
 
-.listenOnlyIconStyle
+.listenOnlyIcon
 {
-	backgroundImage : Embed(source="assets/res/drawable-mdpi/ic_headset.png");
+	backgroundImage : Embed(source="../../../../shared/assets/res/drawable-mdpi/ic_headset.png");
 }
 
-.lockIconStyle
+.lockIcon
 {
-	backgroundImage : Embed(source="assets/res/drawable-mdpi/lock.png");
+	backgroundImage : Embed(source="../../../../shared/assets/res/drawable-mdpi/lock.png");
 }
diff --git a/clients/flash/web-client/src/main/actionscript/Main.mxml b/clients/flash/web-client/src/main/actionscript/Main.mxml
index 363a0b9078467c9a3cc31e398b859d06d1c2660c..1f7c48eea719477eeb2a41c8e0a612f2f5b31f8a 100755
--- a/clients/flash/web-client/src/main/actionscript/Main.mxml
+++ b/clients/flash/web-client/src/main/actionscript/Main.mxml
@@ -10,6 +10,8 @@
 	
 	<fx:Script>
 		<![CDATA[
+			include "../../../../shared/ColorPalette.as"
+
 			import mx.events.FlexEvent;
 			
 			import org.bigbluebutton.lib.whiteboard.views.WhiteboardConfig;
diff --git a/clients/flash/web-client/src/main/actionscript/org/bigbluebutton/web/chat/views/ChatPanel.as b/clients/flash/web-client/src/main/actionscript/org/bigbluebutton/web/chat/views/ChatPanel.as
index 2936ecc650684f2483dbdc262ab8e81091dee69e..286d19c38bda442ac59f4ac62fd456981ace0ba1 100644
--- a/clients/flash/web-client/src/main/actionscript/org/bigbluebutton/web/chat/views/ChatPanel.as
+++ b/clients/flash/web-client/src/main/actionscript/org/bigbluebutton/web/chat/views/ChatPanel.as
@@ -37,7 +37,7 @@ package org.bigbluebutton.web.chat.views {
 			var l:VerticalLayout = new VerticalLayout();
 			this.layout = l;
 			this.minWidth = 50;
-			this.styleName = "panelStyle";
+			this.styleName = "panel";
 			
 			var g:HGroup = new HGroup();
 			g.percentWidth = 100;
@@ -45,7 +45,7 @@ package org.bigbluebutton.web.chat.views {
 			_title = new Label();
 			_title.percentWidth = 100;
 			_title.text = "Public Chat";
-			_title.styleName = "panelTitleStyle";
+			_title.styleName = "panelTitle";
 			g.addElement(title);
 			
 			_closeButton = new Button();
diff --git a/clients/flash/web-client/src/main/actionscript/org/bigbluebutton/web/main/commands/JoinMeetingCommandWeb.as b/clients/flash/web-client/src/main/actionscript/org/bigbluebutton/web/main/commands/JoinMeetingCommandWeb.as
index 005435916acd51d44c4c4b42e2783546a8e5f6ee..343676010f47a422667737e25d615747e8f486ef 100644
--- a/clients/flash/web-client/src/main/actionscript/org/bigbluebutton/web/main/commands/JoinMeetingCommandWeb.as
+++ b/clients/flash/web-client/src/main/actionscript/org/bigbluebutton/web/main/commands/JoinMeetingCommandWeb.as
@@ -40,7 +40,7 @@ package org.bigbluebutton.web.main.commands {
 			loginService.getConfigSuccessSignal.add(configSuccess);
 			loginService.getProfilesSuccessSignal.add(profilesSuccess);
 			loginService.loginFailureSignal.add(loginFailure);
-			loginService.login(urlRequest, url);
+			loginService.login(urlRequest, url, "token");
 		}
 		
 		protected function profilesSuccess(profiles:VideoProfileManager):void {
diff --git a/clients/flash/web-client/src/main/actionscript/org/bigbluebutton/web/participants/views/ParticipantsPanel.as b/clients/flash/web-client/src/main/actionscript/org/bigbluebutton/web/participants/views/ParticipantsPanel.as
index 6fa5d95d6825d4d94beb05a8705aa9e19d526234..8a3fc494aaffe41ad20aba9991266a4cf8456288 100644
--- a/clients/flash/web-client/src/main/actionscript/org/bigbluebutton/web/participants/views/ParticipantsPanel.as
+++ b/clients/flash/web-client/src/main/actionscript/org/bigbluebutton/web/participants/views/ParticipantsPanel.as
@@ -33,11 +33,11 @@ package org.bigbluebutton.web.participants.views {
 			var l:VerticalLayout = new VerticalLayout();
 			this.layout = l;
 			this.minWidth = 50;
-			this.styleName = "panelStyle";
+			this.styleName = "panel";
 			
 			var title:Label = new Label();
 			title.text = "Participants";
-			title.styleName = "panelTitleStyle";
+			title.styleName = "panelTitle";
 			addElement(title);
 			
 			var participantsView:ParticipantsViewBase = new ParticipantsViewBase();
diff --git a/clients/flash/web-client/src/main/actionscript/org/bigbluebutton/web/video/views/WebcamView.as b/clients/flash/web-client/src/main/actionscript/org/bigbluebutton/web/video/views/WebcamView.as
index 2c81f69b1da7fac9d3577d35c21e435d3a62d8ee..0a39419f0a765aa099cd586f16160faf96e52c68 100644
--- a/clients/flash/web-client/src/main/actionscript/org/bigbluebutton/web/video/views/WebcamView.as
+++ b/clients/flash/web-client/src/main/actionscript/org/bigbluebutton/web/video/views/WebcamView.as
@@ -54,7 +54,7 @@ package org.bigbluebutton.web.video.views {
 			closeBtn.height = OVERLAY_HEIGHT;
 			closeBtn.width = OVERLAY_HEIGHT;
 			closeBtn.buttonMode = true;
-			closeBtn.styleName = "webcamCloseButtonStyle";
+			closeBtn.styleName = "webcamCloseButton";
 			closeBtn.addEventListener(MouseEvent.CLICK, onCloseClick);
 			addChild(closeBtn);
 		}