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="" - styleName="iconStyle" /> + styleName="icon" /> <s:Label id="audioIcon" - styleName="iconStyle" /> + styleName="icon" /> <s:Label id="lockIcon" text="" - 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); }